Browse Source

Zero-based frame indexing

Index frames starting at 0 instead of 1.
pull/4428/head
Gleb Mazovetskiy 4 years ago
parent
commit
a66ca44695
  1. 18
      Source/control.cpp
  2. 29
      Source/cursor.cpp
  3. 2
      Source/cursor.h
  4. 4
      Source/dead.cpp
  5. 2
      Source/doom.cpp
  6. 43
      Source/engine/animationinfo.cpp
  7. 29
      Source/engine/cel_header.hpp
  8. 2
      Source/engine/cel_sprite.hpp
  9. 2
      Source/engine/render/cel_render.hpp
  10. 12
      Source/engine/render/cl2_render.cpp
  11. 4
      Source/engine/render/cl2_render.hpp
  12. 2
      Source/engine/render/text_render.cpp
  13. 16
      Source/error.cpp
  14. 12
      Source/gmenu.cpp
  15. 2
      Source/interfac.cpp
  16. 22
      Source/inv.cpp
  17. 26
      Source/items.cpp
  18. 12
      Source/loadsave.cpp
  19. 2
      Source/minitext.cpp
  20. 2
      Source/missiles.h
  21. 80
      Source/monster.cpp
  22. 2
      Source/msg.cpp
  23. 2
      Source/multi.cpp
  24. 8
      Source/panels/charpanel.cpp
  25. 8
      Source/panels/spell_book.cpp
  26. 58
      Source/panels/spell_icons.cpp
  27. 2
      Source/panels/spell_list.cpp
  28. 47
      Source/player.cpp
  29. 8
      Source/player.h
  30. 2
      Source/qol/stash.cpp
  31. 2
      Source/quests.cpp
  32. 46
      Source/scrollrt.cpp
  33. 14
      Source/stores.cpp
  34. 98
      Source/towners.cpp
  35. 282
      test/animationinfo_test.cpp
  36. 2
      test/writehero_test.cpp

18
Source/control.cpp

@ -528,16 +528,16 @@ void InitControlPan()
LoadCharPanel();
LoadSpellIcons();
CelDrawUnsafeTo(*pBtmBuff, { 0, (PANEL_HEIGHT + 16) - 1 }, LoadCel("CtrlPan\\Panel8.CEL", PANEL_WIDTH), 1);
CelDrawUnsafeTo(*pBtmBuff, { 0, (PANEL_HEIGHT + 16) - 1 }, LoadCel("CtrlPan\\Panel8.CEL", PANEL_WIDTH), 0);
{
const Point bulbsPosition { 0, 87 };
const OwnedCelSprite statusPanel = LoadCel("CtrlPan\\P8Bulbs.CEL", 88);
CelDrawUnsafeTo(*pLifeBuff, bulbsPosition, statusPanel, 1);
CelDrawUnsafeTo(*pManaBuff, bulbsPosition, statusPanel, 2);
CelDrawUnsafeTo(*pLifeBuff, bulbsPosition, statusPanel, 0);
CelDrawUnsafeTo(*pManaBuff, bulbsPosition, statusPanel, 1);
}
talkflag = false;
if (IsChatAvailable()) {
CelDrawUnsafeTo(*pBtmBuff, { 0, (PANEL_HEIGHT + 16) * 2 - 1 }, LoadCel("CtrlPan\\TalkPanl.CEL", PANEL_WIDTH), 1);
CelDrawUnsafeTo(*pBtmBuff, { 0, (PANEL_HEIGHT + 16) * 2 - 1 }, LoadCel("CtrlPan\\TalkPanl.CEL", PANEL_WIDTH), 0);
multiButtons = LoadCel("CtrlPan\\P8But2.CEL", 33);
talkButtons = LoadCel("CtrlPan\\TalkButt.CEL", 61);
sgbPlrTalkTbl = 0;
@ -596,16 +596,16 @@ void DrawCtrlBtns(const Surface &out)
DrawPanelBox(out, MakeSdlRect(PanBtnPos[i].x, PanBtnPos[i].y + 16, 71, 20), { PanBtnPos[i].x + PANEL_X, PanBtnPos[i].y + PANEL_Y });
} else {
Point position { PanBtnPos[i].x + PANEL_X, PanBtnPos[i].y + PANEL_Y + 18 };
CelDrawTo(out, position, *pPanelButtons, i + 1);
CelDrawTo(out, position, *pPanelButtons, i);
DrawArt(out, position + Displacement { 4, -18 }, &PanelButtonDown, i);
}
}
if (PanelButtonIndex == 8) {
CelDrawTo(out, { 87 + PANEL_X, 122 + PANEL_Y }, *multiButtons, PanelButtons[6] ? 2 : 1);
CelDrawTo(out, { 87 + PANEL_X, 122 + PANEL_Y }, *multiButtons, PanelButtons[6] ? 1 : 0);
if (gbFriendlyMode)
CelDrawTo(out, { 527 + PANEL_X, 122 + PANEL_Y }, *multiButtons, PanelButtons[7] ? 4 : 3);
CelDrawTo(out, { 527 + PANEL_X, 122 + PANEL_Y }, *multiButtons, PanelButtons[7] ? 3 : 2);
else
CelDrawTo(out, { 527 + PANEL_X, 122 + PANEL_Y }, *multiButtons, PanelButtons[7] ? 6 : 5);
CelDrawTo(out, { 527 + PANEL_X, 122 + PANEL_Y }, *multiButtons, PanelButtons[7] ? 5 : 4);
}
}
@ -1031,7 +1031,7 @@ void DrawGoldSplit(const Surface &out, int amount)
{
const int dialogX = 30;
CelDrawTo(out, GetPanelPosition(UiPanels::Inventory, { dialogX, 178 }), *pGBoxBuff, 1);
CelDrawTo(out, GetPanelPosition(UiPanels::Inventory, { dialogX, 178 }), *pGBoxBuff, 0);
const std::string description = fmt::format(
ngettext(

29
Source/cursor.cpp

@ -33,13 +33,12 @@ namespace {
/** Cursor images CEL */
std::optional<OwnedCelSprite> pCursCels;
std::optional<OwnedCelSprite> pCursCels2;
constexpr uint16_t InvItems1Size = 180;
/** Maps from objcurs.cel frame number to frame width. */
const uint16_t InvItemWidth1[] = {
// clang-format off
// Cursors
0, 33, 32, 32, 32, 32, 32, 32, 32, 32, 32, 23,
33, 32, 32, 32, 32, 32, 32, 32, 32, 32, 23,
// Items
1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28,
1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28,
@ -60,7 +59,6 @@ const uint16_t InvItemWidth1[] = {
2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28,
};
const uint16_t InvItemWidth2[] = {
0,
1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28,
1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28,
1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28,
@ -69,12 +67,13 @@ const uint16_t InvItemWidth2[] = {
2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28
// clang-format on
};
constexpr uint16_t InvItems1Size = sizeof(InvItemWidth1) / sizeof(InvItemWidth1[0]);
/** Maps from objcurs.cel frame number to frame height. */
const uint16_t InvItemHeight1[] = {
const uint16_t InvItemHeight1[InvItems1Size] = {
// clang-format off
// Cursors
0, 29, 32, 32, 32, 32, 32, 32, 32, 32, 32, 35,
29, 32, 32, 32, 32, 32, 32, 32, 32, 32, 35,
// Items
1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28,
1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28,
@ -95,7 +94,6 @@ const uint16_t InvItemHeight1[] = {
3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28,
};
const uint16_t InvItemHeight2[] = {
0,
1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28,
1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28,
1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28,
@ -149,26 +147,27 @@ void FreeCursor()
ClearCursor();
}
const OwnedCelSprite &GetInvItemSprite(int i)
const OwnedCelSprite &GetInvItemSprite(int cursId)
{
return i < InvItems1Size ? *pCursCels : *pCursCels2;
return cursId <= InvItems1Size ? *pCursCels : *pCursCels2;
}
int GetInvItemFrame(int i)
int GetInvItemFrame(int cursId)
{
return i < InvItems1Size ? i : i - (InvItems1Size - 1);
return cursId <= InvItems1Size ? cursId - 1 : cursId - InvItems1Size - 1;
}
Size GetInvItemSize(int cursId)
{
if (cursId >= InvItems1Size)
return { InvItemWidth2[cursId - (InvItems1Size - 1)], InvItemHeight2[cursId - (InvItems1Size - 1)] };
return { InvItemWidth1[cursId], InvItemHeight1[cursId] };
const int i = cursId - 1;
if (i >= InvItems1Size)
return { InvItemWidth2[i - InvItems1Size], InvItemHeight2[i - InvItems1Size] };
return { InvItemWidth1[i], InvItemHeight1[i] };
}
void SetICursor(int cursId)
{
icursSize = GetInvItemSize(cursId);
icursSize = cursId == CURSOR_NONE ? Size { 0, 0 } : GetInvItemSize(cursId);
icursSize28 = icursSize / 28;
}
@ -183,7 +182,7 @@ void NewCursor(int cursId)
MyPlayer->HoldItem._itype = ItemType::None;
}
pcurs = cursId;
cursSize = GetInvItemSize(cursId);
cursSize = cursId == CURSOR_NONE ? Size { 0, 0 } : GetInvItemSize(cursId);
SetICursor(cursId);
if (IsHardwareCursorEnabled() && ControlDevice == ControlTypes::KeyboardAndMouse) {

2
Source/cursor.h

@ -65,7 +65,7 @@ void CelDrawCursor(const Surface &out, Point position, int cursId);
const OwnedCelSprite &GetInvItemSprite(int i);
/** Returns the CEL frame index for the given inventory index. */
int GetInvItemFrame(int i);
int GetInvItemFrame(int cursId);
/** Returns the width and height for an inventory index. */
Size GetInvItemSize(int cursId);

4
Source/dead.cpp

@ -20,7 +20,7 @@ void InitDeadAnimationFromMonster(Corpse &corpse, const CMonster &mon)
{
const auto &animData = mon.GetAnimData(MonsterGraphic::Death);
memcpy(&corpse.data[0], &animData.CelSpritesForDirections[0], sizeof(animData.CelSpritesForDirections[0]) * animData.CelSpritesForDirections.size());
corpse.frame = animData.Frames;
corpse.frame = animData.Frames - 1;
corpse.width = animData.Width;
}
} // namespace
@ -48,7 +48,7 @@ void InitCorpses()
for (auto &corpse : Corpses[nd].data)
corpse = MissileSpriteData[MFILE_SHATTER1].GetFirstFrame();
Corpses[nd].frame = 12;
Corpses[nd].frame = 11;
Corpses[nd].width = 128;
Corpses[nd].translationPaletteIndex = 0;
nd++;

2
Source/doom.cpp

@ -37,7 +37,7 @@ void doom_draw(const Surface &out)
return;
}
CelDrawTo(out, { PANEL_X, PANEL_Y - 1 }, *DoomCel, 1);
CelDrawTo(out, { PANEL_X, PANEL_Y - 1 }, *DoomCel, 0);
}
} // namespace devilution

43
Source/engine/animationinfo.cpp

@ -19,9 +19,9 @@ int AnimationInfo::GetFrameToUseForRendering() const
// 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 (RelevantFramesForDistributing <= 0)
return std::max(1, CurrentFrame);
return std::max(0, CurrentFrame);
if (CurrentFrame > RelevantFramesForDistributing)
if (CurrentFrame >= RelevantFramesForDistributing)
return CurrentFrame;
float ticksSinceSequenceStarted = TicksSinceSequenceStarted;
@ -33,26 +33,25 @@ int AnimationInfo::GetFrameToUseForRendering() const
// we don't use the processed game ticks alone but also the fraction of the next game tick (if a rendering happens between game ticks). This helps to smooth the animations.
float totalTicksForCurrentAnimationSequence = GetProgressToNextGameTick() + ticksSinceSequenceStarted;
// 1 added for rounding reasons. float to int cast always truncate.
int absoluteAnimationFrame = 1 + static_cast<int>(totalTicksForCurrentAnimationSequence * TickModifier);
int absoluteAnimationFrame = static_cast<int>(totalTicksForCurrentAnimationSequence * TickModifier);
if (SkippedFramesFromPreviousAnimation > 0) {
// absoluteAnimationFrames contains also the Frames from the previous Animation, so if we want to get the current Frame we have to remove them
absoluteAnimationFrame -= SkippedFramesFromPreviousAnimation;
if (absoluteAnimationFrame <= 0) {
if (absoluteAnimationFrame < 0) {
// We still display the remains of the previous Animation
absoluteAnimationFrame = NumberOfFrames + absoluteAnimationFrame;
}
} else if (absoluteAnimationFrame > RelevantFramesForDistributing) {
} else if (absoluteAnimationFrame >= RelevantFramesForDistributing) {
// this can happen if we are at the last frame and the next game tick is due (gfProgressToNextGameTick >= 1.0f)
if (absoluteAnimationFrame > (RelevantFramesForDistributing + 1)) {
if (absoluteAnimationFrame >= (RelevantFramesForDistributing + 1)) {
// we should never have +2 frames even if next game tick is due
Log("GetFrameToUseForRendering: Calculated an invalid Animation Frame (Calculated {} MaxFrame {})", absoluteAnimationFrame, RelevantFramesForDistributing);
}
return RelevantFramesForDistributing;
return RelevantFramesForDistributing - 1;
}
if (absoluteAnimationFrame <= 0) {
if (absoluteAnimationFrame < 0) {
Log("GetFrameToUseForRendering: Calculated an invalid Animation Frame (Calculated {})", absoluteAnimationFrame);
return 1;
return 0;
}
return absoluteAnimationFrame;
}
@ -65,7 +64,7 @@ float AnimationInfo::GetAnimationProgress() const
if (RelevantFramesForDistributing <= 0) {
// This logic is used if animation distribution is not active (see GetFrameToUseForRendering).
// In this case the variables calculated with animation distribution are not initialized and we have to calculate them on the fly with the given information.
ticksSinceSequenceStarted = static_cast<float>(((CurrentFrame - 1) * TicksPerFrame) + TickCounterOfCurrentFrame);
ticksSinceSequenceStarted = static_cast<float>((CurrentFrame * TicksPerFrame) + TickCounterOfCurrentFrame);
tickModifier = 1.0F / static_cast<float>(TicksPerFrame);
}
@ -77,10 +76,10 @@ float AnimationInfo::GetAnimationProgress() const
void AnimationInfo::SetNewAnimation(std::optional<CelSprite> celSprite, int numberOfFrames, int ticksPerFrame, AnimationDistributionFlags flags /*= AnimationDistributionFlags::None*/, int numSkippedFrames /*= 0*/, int distributeFramesBeforeFrame /*= 0*/, float previewShownGameTickFragments /*= 0.F*/)
{
if ((flags & AnimationDistributionFlags::RepeatedAction) == AnimationDistributionFlags::RepeatedAction && distributeFramesBeforeFrame != 0 && NumberOfFrames == numberOfFrames && CurrentFrame >= distributeFramesBeforeFrame && CurrentFrame != NumberOfFrames) {
if ((flags & AnimationDistributionFlags::RepeatedAction) == AnimationDistributionFlags::RepeatedAction && distributeFramesBeforeFrame != 0 && NumberOfFrames == numberOfFrames && CurrentFrame + 1 >= distributeFramesBeforeFrame && CurrentFrame != NumberOfFrames - 1) {
// We showed the same Animation (for example a melee attack) before but truncated the Animation.
// So now we should add them back to the new Animation. This increases the speed of the current Animation but the game logic/ticks isn't affected.
SkippedFramesFromPreviousAnimation = NumberOfFrames - CurrentFrame;
SkippedFramesFromPreviousAnimation = NumberOfFrames - CurrentFrame - 1;
} else {
SkippedFramesFromPreviousAnimation = 0;
}
@ -92,7 +91,7 @@ void AnimationInfo::SetNewAnimation(std::optional<CelSprite> celSprite, int numb
this->celSprite = celSprite;
NumberOfFrames = numberOfFrames;
CurrentFrame = 1 + numSkippedFrames;
CurrentFrame = numSkippedFrames;
TickCounterOfCurrentFrame = 0;
TicksPerFrame = ticksPerFrame;
TicksSinceSequenceStarted = 0.F;
@ -173,9 +172,9 @@ void AnimationInfo::ChangeAnimationData(std::optional<CelSprite> celSprite, int
if (numberOfFrames != NumberOfFrames || ticksPerFrame != TicksPerFrame) {
// Ensure that the CurrentFrame is still valid and that we disable ADL cause the calculcated values (for example TickModifier) could be wrong
if (numberOfFrames >= 1)
CurrentFrame = clamp(CurrentFrame, 1, numberOfFrames);
CurrentFrame = clamp(CurrentFrame, 0, numberOfFrames - 1);
else
CurrentFrame = 0;
CurrentFrame = -1;
NumberOfFrames = numberOfFrames;
TicksPerFrame = ticksPerFrame;
@ -195,15 +194,15 @@ void AnimationInfo::ProcessAnimation(bool reverseAnimation /*= false*/, bool don
if (TickCounterOfCurrentFrame >= TicksPerFrame) {
TickCounterOfCurrentFrame = 0;
if (reverseAnimation) {
CurrentFrame--;
if (CurrentFrame == 0) {
CurrentFrame = NumberOfFrames;
--CurrentFrame;
if (CurrentFrame == -1) {
CurrentFrame = NumberOfFrames - 1;
TicksSinceSequenceStarted = 0.F;
}
} else {
CurrentFrame++;
if (CurrentFrame > NumberOfFrames) {
CurrentFrame = 1;
++CurrentFrame;
if (CurrentFrame >= NumberOfFrames) {
CurrentFrame = 0;
TicksSinceSequenceStarted = 0.F;
}
}

29
Source/engine/cel_header.hpp

@ -10,13 +10,24 @@
namespace devilution {
/**
* Returns the pointer to the start of the frame data (often a header).
/*
* When a CEL is a multi-direction animation, it begins with 8 offsets to the start of
* the animation for each direction.
*
* Fills the `out` array with a pointer to each direction.
*/
inline byte *CelGetFrame(byte *data, int frame)
inline void CelGetDirectionFrames(const byte *data, const byte **out)
{
const std::uint32_t begin = LoadLE32(&data[frame * sizeof(std::uint32_t)]);
return &data[begin];
for (size_t i = 0; i < 8; ++i) {
out[i] = &data[LoadLE32(&data[i * 4])];
}
}
inline void CelGetDirectionFrames(byte *data, byte **out)
{
for (size_t i = 0; i < 8; ++i) {
out[i] = &data[LoadLE32(&data[i * 4])];
}
}
/**
@ -24,8 +35,8 @@ inline byte *CelGetFrame(byte *data, int frame)
*/
inline byte *CelGetFrame(byte *data, int frame, int *frameSize)
{
const std::uint32_t begin = LoadLE32(&data[frame * sizeof(std::uint32_t)]);
*frameSize = static_cast<int>(LoadLE32(&data[(frame + 1) * sizeof(std::uint32_t)]) - begin);
const std::uint32_t begin = LoadLE32(&data[(frame + 1) * sizeof(std::uint32_t)]);
*frameSize = static_cast<int>(LoadLE32(&data[(frame + 2) * sizeof(std::uint32_t)]) - begin);
return &data[begin];
}
@ -34,8 +45,8 @@ inline byte *CelGetFrame(byte *data, int frame, int *frameSize)
*/
inline const byte *CelGetFrame(const byte *data, int frame, int *frameSize)
{
const std::uint32_t begin = LoadLE32(&data[frame * sizeof(std::uint32_t)]);
*frameSize = static_cast<int>(LoadLE32(&data[(frame + 1) * sizeof(std::uint32_t)]) - begin);
const std::uint32_t begin = LoadLE32(&data[(frame + 1) * sizeof(std::uint32_t)]);
*frameSize = static_cast<int>(LoadLE32(&data[(frame + 2) * sizeof(std::uint32_t)]) - begin);
return &data[begin];
}

2
Source/engine/cel_sprite.hpp

@ -40,7 +40,7 @@ public:
return data_ptr_;
}
[[nodiscard]] uint16_t Width(std::size_t frame = 1) const
[[nodiscard]] uint16_t Width(std::size_t frame = 0) const
{
return width_.HoldsPointer() ? width_.AsPointer()[frame] : width_.AsValue();
}

2
Source/engine/render/cel_render.hpp

@ -18,7 +18,7 @@ namespace devilution {
* Returns a pair of X coordinates containing the start (inclusive) and end (exclusive)
* of fully transparent columns in the sprite.
*/
std::pair<int, int> MeasureSolidHorizontalBounds(CelSprite cel, int frame = 1);
std::pair<int, int> MeasureSolidHorizontalBounds(CelSprite cel, int frame = 0);
/**
* @brief Blit CEL sprite to the back buffer at the given coordinates

12
Source/engine/render/cl2_render.cpp

@ -745,11 +745,11 @@ void RenderCl2Outline(const Surface &out, Point position, const byte *src, std::
} // namespace
void Cl2ApplyTrans(byte *p, const std::array<uint8_t, 256> &ttbl, int nCel)
void Cl2ApplyTrans(byte *p, const std::array<uint8_t, 256> &ttbl, int numFrames)
{
assert(p != nullptr);
for (int i = 1; i <= nCel; i++) {
for (int i = 0; i < numFrames; ++i) {
constexpr int FrameHeaderSize = 10;
int nDataSize;
byte *dst = CelGetFrame(p, i, &nDataSize) + FrameHeaderSize;
@ -780,7 +780,7 @@ void Cl2ApplyTrans(byte *p, const std::array<uint8_t, 256> &ttbl, int nCel)
void Cl2Draw(const Surface &out, int sx, int sy, CelSprite cel, int frame)
{
assert(frame > 0);
assert(frame >= 0);
int nDataSize;
const byte *pRLEBytes = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
@ -790,7 +790,7 @@ void Cl2Draw(const Surface &out, int sx, int sy, CelSprite cel, int frame)
void Cl2DrawOutline(const Surface &out, uint8_t col, int sx, int sy, CelSprite cel, int frame)
{
assert(frame > 0);
assert(frame >= 0);
int nDataSize;
const byte *pRLEBytes = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
@ -800,7 +800,7 @@ void Cl2DrawOutline(const Surface &out, uint8_t col, int sx, int sy, CelSprite c
void Cl2DrawTRN(const Surface &out, int sx, int sy, CelSprite cel, int frame, uint8_t *trn)
{
assert(frame > 0);
assert(frame >= 0);
int nDataSize;
const byte *pRLEBytes = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
@ -809,7 +809,7 @@ void Cl2DrawTRN(const Surface &out, int sx, int sy, CelSprite cel, int frame, ui
void Cl2DrawLight(const Surface &out, int sx, int sy, CelSprite cel, int frame)
{
assert(frame > 0);
assert(frame >= 0);
int nDataSize;
const byte *pRLEBytes = CelGetFrameClipped(cel.Data(), frame, &nDataSize);

4
Source/engine/render/cl2_render.hpp

@ -18,9 +18,9 @@ namespace devilution {
* @brief Apply the color swaps to a CL2 sprite
* @param p CL2 buffer
* @param ttbl Palette translation table
* @param nCel Frame number in CL2 file
* @param numFrames Number of frames in the CL2 file
*/
void Cl2ApplyTrans(byte *p, const std::array<uint8_t, 256> &ttbl, int nCel);
void Cl2ApplyTrans(byte *p, const std::array<uint8_t, 256> &ttbl, int numFrames);
/**
* @brief Blit CL2 sprite, to the back buffer at the given coordianates

2
Source/engine/render/text_render.cpp

@ -740,7 +740,7 @@ void DrawStringWithColors(const Surface &out, string_view fmt, DrawStringFormatA
uint8_t PentSpn2Spin()
{
return (SDL_GetTicks() / 50) % 8 + 1;
return (SDL_GetTicks() / 50) % 8;
}
} // namespace devilution

16
Source/error.cpp

@ -143,21 +143,21 @@ void DrawDiabloMsg(const Surface &out)
{
int dialogStartY = ((gnScreenHeight - PANEL_HEIGHT) / 2) - (ErrorWindowHeight / 2) + 9;
CelDrawTo(out, { PANEL_X + 101, dialogStartY }, *pSTextSlidCels, 1);
CelDrawTo(out, { PANEL_X + 527, dialogStartY }, *pSTextSlidCels, 4);
CelDrawTo(out, { PANEL_X + 101, dialogStartY + ErrorWindowHeight - 6 }, *pSTextSlidCels, 2);
CelDrawTo(out, { PANEL_X + 527, dialogStartY + ErrorWindowHeight - 6 }, *pSTextSlidCels, 3);
CelDrawTo(out, { PANEL_X + 101, dialogStartY }, *pSTextSlidCels, 0);
CelDrawTo(out, { PANEL_X + 101, dialogStartY + ErrorWindowHeight - 6 }, *pSTextSlidCels, 1);
CelDrawTo(out, { PANEL_X + 527, dialogStartY + ErrorWindowHeight - 6 }, *pSTextSlidCels, 2);
CelDrawTo(out, { PANEL_X + 527, dialogStartY }, *pSTextSlidCels, 3);
int sx = PANEL_X + 109;
for (int i = 0; i < 35; i++) {
CelDrawTo(out, { sx, dialogStartY }, *pSTextSlidCels, 5);
CelDrawTo(out, { sx, dialogStartY + ErrorWindowHeight - 6 }, *pSTextSlidCels, 7);
CelDrawTo(out, { sx, dialogStartY }, *pSTextSlidCels, 4);
CelDrawTo(out, { sx, dialogStartY + ErrorWindowHeight - 6 }, *pSTextSlidCels, 6);
sx += 12;
}
int drawnYborder = 12;
while ((drawnYborder + 12) < ErrorWindowHeight) {
CelDrawTo(out, { PANEL_X + 101, dialogStartY + drawnYborder }, *pSTextSlidCels, 6);
CelDrawTo(out, { PANEL_X + 527, dialogStartY + drawnYborder }, *pSTextSlidCels, 8);
CelDrawTo(out, { PANEL_X + 101, dialogStartY + drawnYborder }, *pSTextSlidCels, 5);
CelDrawTo(out, { PANEL_X + 527, dialogStartY + drawnYborder }, *pSTextSlidCels, 7);
drawnYborder += 12;
}

12
Source/gmenu.cpp

@ -106,12 +106,12 @@ void GmenuDrawMenuItem(const Surface &out, TMenuItem *pItem, int y)
int w = GmenuGetLineWidth(pItem);
if ((pItem->dwFlags & GMENU_SLIDER) != 0) {
int x = 16 + w / 2;
CelDrawTo(out, { x + PANEL_LEFT, y + 40 }, *optbar_cel, 1);
CelDrawTo(out, { x + PANEL_LEFT, y + 40 }, *optbar_cel, 0);
uint16_t step = pItem->dwFlags & 0xFFF;
uint16_t steps = std::max<uint16_t>((pItem->dwFlags & 0xFFF000) >> 12, 2);
uint16_t pos = step * 256 / steps;
GmenuClearBuffer(out, x + 2 + PANEL_LEFT, y + 38, pos + 13, 28);
CelDrawTo(out, { x + 2 + pos + PANEL_LEFT, y + 38 }, *option_cel, 1);
CelDrawTo(out, { x + 2 + pos + PANEL_LEFT, y + 38 }, *option_cel, 0);
}
int x = (gnScreenWidth - w) / 2;
@ -179,7 +179,7 @@ void FreeGMenu()
void gmenu_init_menu()
{
LogoAnim_frame = 1;
LogoAnim_frame = 0;
sgpCurrentMenu = nullptr;
sgpCurrItem = nullptr;
gmenu_current_option = nullptr;
@ -230,9 +230,9 @@ void gmenu_draw(const Surface &out)
if (gbIsHellfire) {
const uint32_t ticks = SDL_GetTicks();
if ((int)(ticks - LogoAnim_tick) > 25) {
LogoAnim_frame++;
if (LogoAnim_frame > 16)
LogoAnim_frame = 1;
++LogoAnim_frame;
if (LogoAnim_frame >= 16)
LogoAnim_frame = 0;
LogoAnim_tick = ticks;
}
}

2
Source/interfac.cpp

@ -172,7 +172,7 @@ void DrawCutscene()
{
const Surface &out = GlobalBackBuffer();
DrawArt(out, { PANEL_X - (ArtCutsceneWidescreen.w() - PANEL_WIDTH) / 2, UI_OFFSET_Y }, &ArtCutsceneWidescreen);
CelDrawTo(out, { PANEL_X, 480 - 1 + UI_OFFSET_Y }, *sgpBackCel, 1);
CelDrawTo(out, { PANEL_X, 480 - 1 + UI_OFFSET_Y }, *sgpBackCel, 0);
constexpr int ProgressHeight = 22;
SDL_Rect rect = MakeSdlRect(

22
Source/inv.cpp

@ -1171,7 +1171,7 @@ void InitInv()
void DrawInv(const Surface &out)
{
CelDrawTo(out, GetPanelPosition(UiPanels::Inventory, { 0, 351 }), *pInvCels, 1);
CelDrawTo(out, GetPanelPosition(UiPanels::Inventory, { 0, 351 }), *pInvCels, 0);
Size slotSize[] = {
{ 2, 2 }, // head
@ -1201,9 +1201,9 @@ void DrawInv(const Surface &out)
int screenY = slotPos[slot].y;
InvDrawSlotBack(out, GetPanelPosition(UiPanels::Inventory, { screenX, screenY }), { slotSize[slot].width * InventorySlotSizeInPixels.width, slotSize[slot].height * InventorySlotSizeInPixels.height });
int frame = myPlayer.InvBody[slot]._iCurs + CURSOR_FIRSTITEM;
const int cursId = myPlayer.InvBody[slot]._iCurs + CURSOR_FIRSTITEM;
auto frameSize = GetInvItemSize(frame);
auto frameSize = GetInvItemSize(cursId);
// calc item offsets for weapons smaller than 2x3 slots
if (slot == INVLOC_HAND_LEFT) {
@ -1214,8 +1214,8 @@ void DrawInv(const Surface &out)
screenY += frameSize.height == (3 * InventorySlotSizeInPixels.height) ? 0 : -INV_SLOT_HALF_SIZE_PX;
}
const auto &cel = GetInvItemSprite(frame);
const int celFrame = GetInvItemFrame(frame);
const auto &cel = GetInvItemSprite(cursId);
const int celFrame = GetInvItemFrame(cursId);
const Point position = GetPanelPosition(UiPanels::Inventory, { screenX, screenY });
if (pcursinvitem == slot) {
@ -1252,10 +1252,10 @@ void DrawInv(const Surface &out)
for (int j = 0; j < NUM_INV_GRID_ELEM; j++) {
if (myPlayer.InvGrid[j] > 0) { // first slot of an item
int ii = myPlayer.InvGrid[j] - 1;
int frame = myPlayer.InvList[ii]._iCurs + CURSOR_FIRSTITEM;
int cursId = myPlayer.InvList[ii]._iCurs + CURSOR_FIRSTITEM;
const auto &cel = GetInvItemSprite(frame);
const int celFrame = GetInvItemFrame(frame);
const auto &cel = GetInvItemSprite(cursId);
const int celFrame = GetInvItemFrame(cursId);
const Point position = GetPanelPosition(UiPanels::Inventory, InvRect[j + SLOTXY_INV_FIRST]) + Displacement { 0, -1 };
if (pcursinvitem == ii + INVITEM_INV_FIRST) {
CelBlitOutlineTo(
@ -1291,10 +1291,10 @@ void DrawInvBelt(const Surface &out)
const Point position { InvRect[i + SLOTXY_BELT_FIRST].x + PANEL_X, InvRect[i + SLOTXY_BELT_FIRST].y + PANEL_Y - 1 };
InvDrawSlotBack(out, position, InventorySlotSizeInPixels);
int frame = myPlayer.SpdList[i]._iCurs + CURSOR_FIRSTITEM;
const int cursId = myPlayer.SpdList[i]._iCurs + CURSOR_FIRSTITEM;
const auto &cel = GetInvItemSprite(frame);
const int celFrame = GetInvItemFrame(frame);
const auto &cel = GetInvItemSprite(cursId);
const int celFrame = GetInvItemFrame(cursId);
if (pcursinvitem == i + INVITEM_BELT_FIRST) {
if (ControlMode == ControlTypes::KeyboardAndMouse || invflag) {

26
Source/items.cpp

@ -443,7 +443,7 @@ void AddInitItems()
item._iCreateInfo = curlv | CF_PREGEN;
SetupItem(item);
item.AnimInfo.CurrentFrame = item.AnimInfo.NumberOfFrames;
item.AnimInfo.CurrentFrame = item.AnimInfo.NumberOfFrames - 1;
item._iAnimFlag = false;
item._iSelFlag = 1;
DeltaAddItem(ii);
@ -1686,7 +1686,7 @@ void SpawnRock()
SetupItem(item);
item._iSelFlag = 2;
item._iPostDraw = true;
item.AnimInfo.CurrentFrame = 11;
item.AnimInfo.CurrentFrame = 10;
}
void ItemDoppel()
@ -1815,7 +1815,7 @@ void PrintItemOil(char iDidx)
void DrawUniqueInfoWindow(const Surface &out)
{
CelDrawTo(out, GetPanelPosition(UiPanels::Inventory, { 24 - SPANEL_WIDTH, 327 }), *pSTextBoxCels, 1);
CelDrawTo(out, GetPanelPosition(UiPanels::Inventory, { 24 - SPANEL_WIDTH, 327 }), *pSTextBoxCels, 0);
DrawHalfTransparentRectTo(out, GetRightPanel().position.x - SPANEL_WIDTH + 27, GetRightPanel().position.y + 28, 265, 297);
}
@ -3358,7 +3358,7 @@ void SpawnQuestItem(int itemid, Point position, int randarea, int selflag)
item._iPostDraw = true;
if (selflag != 0) {
item._iSelFlag = selflag;
item.AnimInfo.CurrentFrame = item.AnimInfo.NumberOfFrames;
item.AnimInfo.CurrentFrame = item.AnimInfo.NumberOfFrames - 1;
item._iAnimFlag = false;
}
}
@ -3442,16 +3442,16 @@ void ProcessItems()
continue;
item.AnimInfo.ProcessAnimation();
if (item._iCurs == ICURS_MAGIC_ROCK) {
if (item._iSelFlag == 1 && item.AnimInfo.CurrentFrame == 11)
item.AnimInfo.CurrentFrame = 1;
if (item._iSelFlag == 2 && item.AnimInfo.CurrentFrame == 21)
item.AnimInfo.CurrentFrame = 11;
if (item._iSelFlag == 1 && item.AnimInfo.CurrentFrame == 10)
item.AnimInfo.CurrentFrame = 0;
if (item._iSelFlag == 2 && item.AnimInfo.CurrentFrame == 20)
item.AnimInfo.CurrentFrame = 10;
} else {
if (item.AnimInfo.CurrentFrame == item.AnimInfo.NumberOfFrames / 2)
if (item.AnimInfo.CurrentFrame == (item.AnimInfo.NumberOfFrames - 1) / 2)
PlaySfxLoc(ItemDropSnds[ItemCAnimTbl[item._iCurs]], item.position);
if (item.AnimInfo.CurrentFrame >= item.AnimInfo.NumberOfFrames) {
item.AnimInfo.CurrentFrame = item.AnimInfo.NumberOfFrames;
if (item.AnimInfo.CurrentFrame >= item.AnimInfo.NumberOfFrames - 1) {
item.AnimInfo.CurrentFrame = item.AnimInfo.NumberOfFrames - 1;
item._iAnimFlag = false;
item._iSelFlag = 1;
}
@ -4360,7 +4360,7 @@ void MakeGoldStack(Item &goldItem, int value)
int ItemNoFlippy()
{
int r = ActiveItems[ActiveItemCount - 1];
Items[r].AnimInfo.CurrentFrame = Items[r].AnimInfo.NumberOfFrames;
Items[r].AnimInfo.CurrentFrame = Items[r].AnimInfo.NumberOfFrames - 1;
Items[r]._iAnimFlag = false;
Items[r]._iSelFlag = 1;
@ -4617,7 +4617,7 @@ void Item::SetNewAnimation(bool showAnimation)
_iAnimFlag = true;
_iSelFlag = 0;
} else {
AnimInfo.CurrentFrame = AnimInfo.NumberOfFrames;
AnimInfo.CurrentFrame = AnimInfo.NumberOfFrames - 1;
_iAnimFlag = false;
_iSelFlag = 1;
}

12
Source/loadsave.cpp

@ -230,7 +230,7 @@ void LoadItemData(LoadHelper &file, Item &item)
file.Skip(4); // Skip pointer _iAnimData
item.AnimInfo = {};
item.AnimInfo.NumberOfFrames = file.NextLE<int32_t>();
item.AnimInfo.CurrentFrame = file.NextLE<int32_t>();
item.AnimInfo.CurrentFrame = file.NextLE<int32_t>() - 1;
file.Skip(8); // Skip _iAnimWidth and _iAnimWidth2
file.Skip(4); // Unused since 1.02
item._iSelFlag = file.NextLE<uint8_t>();
@ -344,7 +344,7 @@ void LoadPlayer(LoadHelper &file, Player &player)
player.AnimInfo.TicksPerFrame = file.NextLE<int32_t>() + 1;
player.AnimInfo.TickCounterOfCurrentFrame = file.NextLE<int32_t>();
player.AnimInfo.NumberOfFrames = file.NextLE<int32_t>();
player.AnimInfo.CurrentFrame = file.NextLE<int32_t>();
player.AnimInfo.CurrentFrame = file.NextLE<int32_t>() - 1;
file.Skip(4); // Skip _pAnimWidth
file.Skip(4); // Skip _pAnimWidth2
file.Skip(4); // Skip _peflag
@ -581,7 +581,7 @@ void LoadMonster(LoadHelper *file, Monster &monster)
monster.AnimInfo.TicksPerFrame = file->NextLE<int32_t>();
monster.AnimInfo.TickCounterOfCurrentFrame = file->NextLE<int32_t>();
monster.AnimInfo.NumberOfFrames = file->NextLE<int32_t>();
monster.AnimInfo.CurrentFrame = file->NextLE<int32_t>();
monster.AnimInfo.CurrentFrame = file->NextLE<int32_t>() - 1;
file->Skip(4); // Skip _meflag
monster._mDelFlag = file->NextBool32();
monster._mVar1 = file->NextLE<int32_t>();
@ -953,7 +953,7 @@ void SaveItem(SaveHelper &file, const Item &item)
file.WriteLE<uint32_t>(item._iAnimFlag ? 1 : 0);
file.Skip(4); // Skip pointer _iAnimData
file.WriteLE<int32_t>(item.AnimInfo.NumberOfFrames);
file.WriteLE<int32_t>(item.AnimInfo.CurrentFrame);
file.WriteLE<int32_t>(item.AnimInfo.CurrentFrame + 1);
// write _iAnimWidth for vanilla compatibility
file.WriteLE<int32_t>(ItemAnimWidth);
// write _iAnimWidth2 for vanilla compatibility
@ -1062,7 +1062,7 @@ void SavePlayer(SaveHelper &file, const Player &player)
file.WriteLE<int32_t>(std::max(0, player.AnimInfo.TicksPerFrame - 1));
file.WriteLE<int32_t>(player.AnimInfo.TickCounterOfCurrentFrame);
file.WriteLE<int32_t>(player.AnimInfo.NumberOfFrames);
file.WriteLE<int32_t>(player.AnimInfo.CurrentFrame);
file.WriteLE<int32_t>(player.AnimInfo.CurrentFrame + 1);
// write _pAnimWidth for vanilla compatibility
int animWidth = player.AnimInfo.celSprite ? player.AnimInfo.celSprite->Width() : 96;
file.WriteLE<int32_t>(animWidth);
@ -1290,7 +1290,7 @@ void SaveMonster(SaveHelper *file, Monster &monster)
file->WriteLE<int32_t>(monster.AnimInfo.TicksPerFrame);
file->WriteLE<int32_t>(monster.AnimInfo.TickCounterOfCurrentFrame);
file->WriteLE<int32_t>(monster.AnimInfo.NumberOfFrames);
file->WriteLE<int32_t>(monster.AnimInfo.CurrentFrame);
file->WriteLE<int32_t>(monster.AnimInfo.CurrentFrame + 1);
file->Skip<uint32_t>(); // Skip _meflag
file->WriteLE<uint32_t>(monster._mDelFlag ? 1 : 0);
file->WriteLE<int32_t>(monster._mVar1);

2
Source/minitext.cpp

@ -145,7 +145,7 @@ void InitQTextMsg(_speech_id m)
void DrawQTextBack(const Surface &out)
{
CelDrawTo(out, { PANEL_X + 24, 327 + UI_OFFSET_Y }, *pTextBoxCels, 1);
CelDrawTo(out, { PANEL_X + 24, 327 + UI_OFFSET_Y }, *pTextBoxCels, 0);
DrawHalfTransparentRectTo(out, PANEL_X + 27, UI_OFFSET_Y + 28, 585, 297);
}

2
Source/missiles.h

@ -101,7 +101,7 @@ struct Missile {
int16_t _miAnimWidth2;
int _miAnimCnt; // Increases by one each game tick, counting how close we are to _pAnimDelay
int _miAnimAdd;
int _miAnimFrame; // Current frame of animation.
int _miAnimFrame; // Current frame of animation + 1.
bool _miDrawFlag;
bool _miLightFlag;
bool _miPreFlag;

80
Source/monster.cpp

@ -157,12 +157,14 @@ void InitMonsterTRN(CMonster &monst)
AnimStruct &anim = monst.Anims[i];
if (IsDirectionalAnim(monst, i)) {
for (int j = 0; j < 8; j++) {
for (size_t j = 0; j < 8; ++j) {
Cl2ApplyTrans(anim.CelSpritesForDirections[j], colorTranslations, anim.Frames);
}
} else {
for (int j = 0; j < 8; j++) {
Cl2ApplyTrans(CelGetFrame(anim.CelSpritesForDirections[0], j), colorTranslations, anim.Frames);
byte *frames[8];
CelGetDirectionFrames(anim.CelSpritesForDirections[0], frames);
for (byte *frame : frames) {
Cl2ApplyTrans(frame, colorTranslations, anim.Frames);
}
}
}
@ -182,7 +184,7 @@ void InitMonster(Monster &monster, Direction rd, int mtype, Point position)
monster.AnimInfo = {};
monster.ChangeAnimationData(MonsterGraphic::Stand);
monster.AnimInfo.TickCounterOfCurrentFrame = GenerateRnd(monster.AnimInfo.TicksPerFrame - 1);
monster.AnimInfo.CurrentFrame = GenerateRnd(monster.AnimInfo.NumberOfFrames - 1) + 1;
monster.AnimInfo.CurrentFrame = GenerateRnd(monster.AnimInfo.NumberOfFrames - 1);
monster.mLevel = monster.MData->mLevel;
int maxhp = monster.MData->mMinHP + GenerateRnd(monster.MData->mMaxHP - monster.MData->mMinHP + 1);
@ -226,7 +228,7 @@ void InitMonster(Monster &monster, Direction rd, int mtype, Point position)
if (monster._mAi == AI_GARG) {
monster.ChangeAnimationData(MonsterGraphic::Special);
monster.AnimInfo.CurrentFrame = 1;
monster.AnimInfo.CurrentFrame = 0;
monster._mFlags |= MFLAG_ALLOW_SPECIAL;
monster._mmode = MonsterMode::SpecialMeleeAttack;
}
@ -362,7 +364,7 @@ void PlaceGroup(int mtype, int num, UniqueMonsterPack uniqueMonsterPack, int lea
if (minion._mAi != AI_GARG) {
minion.ChangeAnimationData(MonsterGraphic::Stand);
minion.AnimInfo.CurrentFrame = GenerateRnd(minion.AnimInfo.NumberOfFrames - 1) + 1;
minion.AnimInfo.CurrentFrame = GenerateRnd(minion.AnimInfo.NumberOfFrames - 1);
minion._mFlags &= ~MFLAG_ALLOW_SPECIAL;
minion._mmode = MonsterMode::Stand;
}
@ -1186,7 +1188,7 @@ void StartFadein(Monster &monster, Direction md, bool backwards)
monster._mFlags &= ~MFLAG_HIDDEN;
if (backwards) {
monster._mFlags |= MFLAG_LOCK_ANIMATION;
monster.AnimInfo.CurrentFrame = monster.AnimInfo.NumberOfFrames;
monster.AnimInfo.CurrentFrame = monster.AnimInfo.NumberOfFrames - 1;
}
}
@ -1199,14 +1201,14 @@ void StartFadeout(Monster &monster, Direction md, bool backwards)
monster.position.old = monster.position.tile;
if (backwards) {
monster._mFlags |= MFLAG_LOCK_ANIMATION;
monster.AnimInfo.CurrentFrame = monster.AnimInfo.NumberOfFrames;
monster.AnimInfo.CurrentFrame = monster.AnimInfo.NumberOfFrames - 1;
}
}
void StartHeal(Monster &monster)
{
monster.ChangeAnimationData(MonsterGraphic::Special);
monster.AnimInfo.CurrentFrame = monster.MType->GetAnimData(MonsterGraphic::Special).Frames;
monster.AnimInfo.CurrentFrame = monster.MType->GetAnimData(MonsterGraphic::Special).Frames - 1;
monster._mFlags |= MFLAG_LOCK_ANIMATION;
monster._mmode = MonsterMode::Heal;
monster._mVar1 = monster._mmaxhp / (16 * (GenerateRnd(5) + 4));
@ -1228,7 +1230,7 @@ bool MonsterIdle(Monster &monster)
else
monster.ChangeAnimationData(MonsterGraphic::Stand);
if (monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames)
if (monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames - 1)
UpdateEnemy(monster);
monster._mVar2++;
@ -1246,7 +1248,7 @@ bool MonsterWalk(int i, MonsterMode variant)
assert(monster.MType != nullptr);
// Check if we reached new tile
bool isAnimationEnd = monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames;
const bool isAnimationEnd = monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames - 1;
if (isAnimationEnd) {
switch (variant) {
case MonsterMode::MoveNorthwards:
@ -1454,22 +1456,22 @@ bool MonsterAttack(int i)
assert(monster.MType != nullptr);
assert(monster.MData != nullptr);
if (monster.AnimInfo.CurrentFrame == monster.MData->mAFNum) {
if (monster.AnimInfo.CurrentFrame == monster.MData->mAFNum - 1) {
MonsterAttackPlayer(i, monster._menemy, monster.mHit, monster.mMinDamage, monster.mMaxDamage);
if (monster._mAi != AI_SNAKE)
PlayEffect(monster, 0);
}
if (monster.MType->mtype >= MT_NMAGMA && monster.MType->mtype <= MT_WMAGMA && monster.AnimInfo.CurrentFrame == 9) {
if (monster.MType->mtype >= MT_NMAGMA && monster.MType->mtype <= MT_WMAGMA && monster.AnimInfo.CurrentFrame == 8) {
MonsterAttackPlayer(i, monster._menemy, monster.mHit + 10, monster.mMinDamage - 2, monster.mMaxDamage - 2);
PlayEffect(monster, 0);
}
if (monster.MType->mtype >= MT_STORM && monster.MType->mtype <= MT_MAEL && monster.AnimInfo.CurrentFrame == 13) {
if (monster.MType->mtype >= MT_STORM && monster.MType->mtype <= MT_MAEL && monster.AnimInfo.CurrentFrame == 12) {
MonsterAttackPlayer(i, monster._menemy, monster.mHit - 20, monster.mMinDamage + 4, monster.mMaxDamage + 4);
PlayEffect(monster, 0);
}
if (monster._mAi == AI_SNAKE && monster.AnimInfo.CurrentFrame == 1)
if (monster._mAi == AI_SNAKE && monster.AnimInfo.CurrentFrame == 0)
PlayEffect(monster, 0);
if (monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames) {
if (monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames - 1) {
M_StartStand(monster, monster._mdir);
return true;
}
@ -1484,7 +1486,7 @@ bool MonaterRangedAttack(int i)
assert(monster.MType != nullptr);
assert(monster.MData != nullptr);
if (monster.AnimInfo.CurrentFrame == monster.MData->mAFNum) {
if (monster.AnimInfo.CurrentFrame == monster.MData->mAFNum - 1) {
const auto &missileType = static_cast<missile_id>(monster._mVar1);
if (missileType != MIS_NULL) {
int multimissiles = 1;
@ -1505,7 +1507,7 @@ bool MonaterRangedAttack(int i)
PlayEffect(monster, 0);
}
if (monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames) {
if (monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames - 1) {
M_StartStand(monster, monster._mdir);
return true;
}
@ -1520,7 +1522,7 @@ bool MonsterRangedSpecialAttack(int i)
assert(monster.MType != nullptr);
assert(monster.MData != nullptr);
if (monster.AnimInfo.CurrentFrame == monster.MData->mAFNum2 && monster.AnimInfo.TickCounterOfCurrentFrame == 0) {
if (monster.AnimInfo.CurrentFrame == monster.MData->mAFNum2 - 1 && monster.AnimInfo.TickCounterOfCurrentFrame == 0) {
if (AddMissile(
monster.position.tile,
monster.enemyPosition,
@ -1535,7 +1537,7 @@ bool MonsterRangedSpecialAttack(int i)
}
}
if (monster._mAi == AI_MEGA && monster.AnimInfo.CurrentFrame == monster.MData->mAFNum2) {
if (monster._mAi == AI_MEGA && monster.AnimInfo.CurrentFrame == monster.MData->mAFNum2 - 1) {
if (monster._mVar2++ == 0) {
monster._mFlags |= MFLAG_ALLOW_SPECIAL;
} else if (monster._mVar2 == 15) {
@ -1543,7 +1545,7 @@ bool MonsterRangedSpecialAttack(int i)
}
}
if (monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames) {
if (monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames - 1) {
M_StartStand(monster, monster._mdir);
return true;
}
@ -1558,10 +1560,10 @@ bool MonsterSpecialAttack(int i)
assert(monster.MType != nullptr);
assert(monster.MData != nullptr);
if (monster.AnimInfo.CurrentFrame == monster.MData->mAFNum2)
if (monster.AnimInfo.CurrentFrame == monster.MData->mAFNum2 - 1)
MonsterAttackPlayer(i, monster._menemy, monster.mHit2, monster.mMinDamage2, monster.mMaxDamage2);
if (monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames) {
if (monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames - 1) {
M_StartStand(monster, monster._mdir);
return true;
}
@ -1571,8 +1573,8 @@ bool MonsterSpecialAttack(int i)
bool MonsterFadein(Monster &monster)
{
if (((monster._mFlags & MFLAG_LOCK_ANIMATION) == 0 || monster.AnimInfo.CurrentFrame != 1)
&& ((monster._mFlags & MFLAG_LOCK_ANIMATION) != 0 || monster.AnimInfo.CurrentFrame != monster.AnimInfo.NumberOfFrames)) {
if (((monster._mFlags & MFLAG_LOCK_ANIMATION) == 0 || monster.AnimInfo.CurrentFrame != 0)
&& ((monster._mFlags & MFLAG_LOCK_ANIMATION) != 0 || monster.AnimInfo.CurrentFrame != monster.AnimInfo.NumberOfFrames - 1)) {
return false;
}
@ -1584,8 +1586,8 @@ bool MonsterFadein(Monster &monster)
bool MonsterFadeout(Monster &monster)
{
if (((monster._mFlags & MFLAG_LOCK_ANIMATION) == 0 || monster.AnimInfo.CurrentFrame != 1)
&& ((monster._mFlags & MFLAG_LOCK_ANIMATION) != 0 || monster.AnimInfo.CurrentFrame != monster.AnimInfo.NumberOfFrames)) {
if (((monster._mFlags & MFLAG_LOCK_ANIMATION) == 0 || monster.AnimInfo.CurrentFrame != 0)
&& ((monster._mFlags & MFLAG_LOCK_ANIMATION) != 0 || monster.AnimInfo.CurrentFrame != monster.AnimInfo.NumberOfFrames - 1)) {
return false;
}
@ -1610,7 +1612,7 @@ bool MonsterHeal(Monster &monster)
return false;
}
if (monster.AnimInfo.CurrentFrame == 1) {
if (monster.AnimInfo.CurrentFrame == 0) {
monster._mFlags &= ~MFLAG_LOCK_ANIMATION;
monster._mFlags |= MFLAG_ALLOW_SPECIAL;
if (monster._mVar1 + monster._mhitpoints < monster._mmaxhp) {
@ -1688,7 +1690,7 @@ bool MonsterTalk(Monster &monster)
bool MonsterGotHit(Monster &monster)
{
if (monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames) {
if (monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames - 1) {
M_StartStand(monster, monster._mdir);
return true;
@ -1719,7 +1721,7 @@ bool MonsterDeath(int i)
if (monster._mVar1 == 140)
PrepDoEnding();
} else if (monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames) {
} else if (monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames - 1) {
if (monster._uniqtype == 0)
AddCorpse(monster.position.tile, monster.MType->mdeadval, monster._mdir);
else
@ -1735,10 +1737,10 @@ bool MonsterDeath(int i)
bool MonsterSpecialStand(Monster &monster)
{
if (monster.AnimInfo.CurrentFrame == monster.MData->mAFNum2)
if (monster.AnimInfo.CurrentFrame == monster.MData->mAFNum2 - 1)
PlayEffect(monster, 3);
if (monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames) {
if (monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames - 1) {
M_StartStand(monster, monster._mdir);
return true;
}
@ -2522,7 +2524,7 @@ void FallenAi(int i)
}
}
if (monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames) {
if (monster.AnimInfo.CurrentFrame == monster.AnimInfo.NumberOfFrames - 1) {
if (GenerateRnd(4) != 0) {
return;
}
@ -3578,7 +3580,7 @@ void PrepareUniqueMonst(Monster &monster, int uniqindex, int miniontype, int bos
if (monster._mAi != AI_GARG) {
monster.ChangeAnimationData(MonsterGraphic::Stand);
monster.AnimInfo.CurrentFrame = GenerateRnd(monster.AnimInfo.NumberOfFrames - 1) + 1;
monster.AnimInfo.CurrentFrame = GenerateRnd(monster.AnimInfo.NumberOfFrames - 1);
monster._mFlags &= ~MFLAG_ALLOW_SPECIAL;
monster._mmode = MonsterMode::Stand;
}
@ -3747,11 +3749,9 @@ void InitMonsterGFX(int monst)
byte *cl2Data = &monster.animData[animOffsets[animIndex]];
if (IsDirectionalAnim(monster, animIndex)) {
for (int i = 0; i < 8; i++) {
anim.CelSpritesForDirections[i] = CelGetFrame(cl2Data, i);
}
CelGetDirectionFrames(cl2Data, anim.CelSpritesForDirections.data());
} else {
for (int i = 0; i < 8; i++) {
for (size_t i = 0; i < 8; ++i) {
anim.CelSpritesForDirections[i] = cl2Data;
}
}
@ -4587,10 +4587,10 @@ void SyncMonsterAnim(Monster &monster)
break;
case MonsterMode::Charge:
graphic = MonsterGraphic::Attack;
monster.AnimInfo.CurrentFrame = 1;
monster.AnimInfo.CurrentFrame = 0;
break;
default:
monster.AnimInfo.CurrentFrame = 1;
monster.AnimInfo.CurrentFrame = 0;
break;
}

2
Source/msg.cpp

@ -1696,7 +1696,7 @@ DWORD OnPlayerJoinLevel(const TCmd *pCmd, int pnum)
player._pgfxnum &= ~0xF;
player._pmode = PM_DEATH;
NewPlrAnim(player, player_graphic::Death, Direction::South, player._pDFrames, 1);
player.AnimInfo.CurrentFrame = player.AnimInfo.NumberOfFrames - 1;
player.AnimInfo.CurrentFrame = player.AnimInfo.NumberOfFrames - 2;
dFlags[player.position.tile.x][player.position.tile.y] |= DungeonFlag::DeadPlayer;
}

2
Source/multi.cpp

@ -804,7 +804,7 @@ void recv_plrinfo(int pnum, const TCmdPlrInfoHdr &header, bool recv)
player._pgfxnum &= ~0xF;
player._pmode = PM_DEATH;
NewPlrAnim(player, player_graphic::Death, Direction::South, player._pDFrames, 1);
player.AnimInfo.CurrentFrame = player.AnimInfo.NumberOfFrames - 1;
player.AnimInfo.CurrentFrame = player.AnimInfo.NumberOfFrames - 2;
dFlags[player.position.tile.x][player.position.tile.y] |= DungeonFlag::DeadPlayer;
}

8
Source/panels/charpanel.cpp

@ -248,13 +248,13 @@ void DrawStatButtons(const Surface &out)
{
if (MyPlayer->_pStatPts > 0) {
if (MyPlayer->_pBaseStr < MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Strength))
CelDrawTo(out, GetPanelPosition(UiPanels::Character, { 137, 157 }), *pChrButtons, chrbtn[static_cast<size_t>(CharacterAttribute::Strength)] ? 3 : 2);
CelDrawTo(out, GetPanelPosition(UiPanels::Character, { 137, 157 }), *pChrButtons, chrbtn[static_cast<size_t>(CharacterAttribute::Strength)] ? 2 : 1);
if (MyPlayer->_pBaseMag < MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Magic))
CelDrawTo(out, GetPanelPosition(UiPanels::Character, { 137, 185 }), *pChrButtons, chrbtn[static_cast<size_t>(CharacterAttribute::Magic)] ? 5 : 4);
CelDrawTo(out, GetPanelPosition(UiPanels::Character, { 137, 185 }), *pChrButtons, chrbtn[static_cast<size_t>(CharacterAttribute::Magic)] ? 4 : 3);
if (MyPlayer->_pBaseDex < MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Dexterity))
CelDrawTo(out, GetPanelPosition(UiPanels::Character, { 137, 214 }), *pChrButtons, chrbtn[static_cast<size_t>(CharacterAttribute::Dexterity)] ? 7 : 6);
CelDrawTo(out, GetPanelPosition(UiPanels::Character, { 137, 214 }), *pChrButtons, chrbtn[static_cast<size_t>(CharacterAttribute::Dexterity)] ? 6 : 5);
if (MyPlayer->_pBaseVit < MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Vitality))
CelDrawTo(out, GetPanelPosition(UiPanels::Character, { 137, 242 }), *pChrButtons, chrbtn[static_cast<size_t>(CharacterAttribute::Vitality)] ? 9 : 8);
CelDrawTo(out, GetPanelPosition(UiPanels::Character, { 137, 242 }), *pChrButtons, chrbtn[static_cast<size_t>(CharacterAttribute::Vitality)] ? 8 : 7);
}
}

8
Source/panels/spell_book.cpp

@ -85,7 +85,7 @@ void InitSpellBook()
pSpellBkCel = LoadCel("Data\\SpellBk.CEL", SPANEL_WIDTH);
if (gbIsHellfire) {
static const uint16_t SBkBtnHellfireWidths[] = { 0, 61, 61, 61, 61, 61, 76 };
static const uint16_t SBkBtnHellfireWidths[] = { 61, 61, 61, 61, 61, 76 };
pSBkBtnCel = LoadCel("Data\\SpellBkB.CEL", SBkBtnHellfireWidths);
} else {
pSBkBtnCel = LoadCel("Data\\SpellBkB.CEL", 76);
@ -117,16 +117,16 @@ void FreeSpellBook()
void DrawSpellBook(const Surface &out)
{
CelDrawTo(out, GetPanelPosition(UiPanels::Spell, { 0, 351 }), *pSpellBkCel, 1);
CelDrawTo(out, GetPanelPosition(UiPanels::Spell, { 0, 351 }), *pSpellBkCel, 0);
if (gbIsHellfire && sbooktab < 5) {
CelDrawTo(out, GetPanelPosition(UiPanels::Spell, { 61 * sbooktab + 7, 348 }), *pSBkBtnCel, sbooktab + 1);
CelDrawTo(out, GetPanelPosition(UiPanels::Spell, { 61 * sbooktab + 7, 348 }), *pSBkBtnCel, sbooktab);
} else {
// BUGFIX: rendering of page 3 and page 4 buttons are both off-by-one pixel (fixed).
int sx = 76 * sbooktab + 7;
if (sbooktab == 2 || sbooktab == 3) {
sx++;
}
CelDrawTo(out, GetPanelPosition(UiPanels::Spell, { sx, 348 }), *pSBkBtnCel, sbooktab + 1);
CelDrawTo(out, GetPanelPosition(UiPanels::Spell, { sx, 348 }), *pSBkBtnCel, sbooktab);
}
auto &player = Players[MyPlayerId];
uint64_t spl = player._pMemSpells | player._pISpells | player._pAblSpells;

58
Source/panels/spell_icons.cpp

@ -14,7 +14,8 @@ uint8_t SplTransTbl[256];
} // namespace
const char SpellITbl[] = {
27,
26,
0,
1,
2,
3,
@ -23,49 +24,48 @@ const char SpellITbl[] = {
6,
7,
8,
9,
28,
13,
27,
12,
11,
17,
15,
13,
17,
18,
16,
14,
18,
10,
19,
11,
14,
20,
15,
21,
22,
23,
24,
21,
25,
22,
26,
29,
28,
36,
37,
38,
39,
42,
41,
40,
10,
36,
30,
51,
51,
39,
9,
35,
29,
50,
50,
49,
45,
46,
42,
44,
47,
43,
45,
48,
49,
44,
35,
35,
35,
35,
35,
43,
34,
34,
34,
34,
34,
};
void LoadSpellIcons()

2
Source/panels/spell_list.cpp

@ -113,7 +113,7 @@ void DrawSpell(const Surface &out)
if (currlevel == 0 && st != RSPLTYPE_INVALID && !spelldata[spl].sTownSpell)
st = RSPLTYPE_INVALID;
SetSpellTrans(st);
const int nCel = (spl != SPL_INVALID) ? SpellITbl[spl] : 27;
const int nCel = (spl != SPL_INVALID) ? SpellITbl[spl] : 26;
const Point position { PANEL_X + 565, PANEL_Y + 119 };
DrawSpellCel(out, position, nCel);

47
Source/player.cpp

@ -371,9 +371,10 @@ void SetPlayerGPtrs(const char *path, std::unique_ptr<byte[]> &data, std::array<
if (data == nullptr && gbQuietMode)
return;
for (int i = 0; i < 8; i++) {
byte *pCelStart = CelGetFrame(data.get(), i);
anim[i].emplace(pCelStart, width);
const byte *directionFrames[8];
CelGetDirectionFrames(data.get(), directionFrames);
for (size_t i = 0; i < 8; i++) {
anim[i].emplace(directionFrames[i], width);
}
}
@ -667,14 +668,14 @@ bool DoWalk(int pnum, int variant)
// Play walking sound effect on certain animation frames
if (*sgOptions.Audio.walkingSound && (currlevel != 0 || sgGameInitInfo.bRunInTown == 0)) {
if (player.AnimInfo.CurrentFrame == 1
|| player.AnimInfo.CurrentFrame == 5) {
if (player.AnimInfo.CurrentFrame == 0
|| player.AnimInfo.CurrentFrame == 4) {
PlaySfxLoc(PS_WALK1, player.position.tile);
}
}
// Check if we reached new tile
if (player.AnimInfo.CurrentFrame >= player._pWFrames) {
if (player.AnimInfo.CurrentFrame >= player._pWFrames - 1) {
// Update the player's tile position
switch (variant) {
@ -1113,13 +1114,13 @@ bool DoAttack(int pnum)
}
auto &player = Players[pnum];
if (player.AnimInfo.CurrentFrame == player._pAFNum - 1) {
if (player.AnimInfo.CurrentFrame == player._pAFNum - 2) {
PlaySfxLoc(PS_SWING, player.position.tile);
}
bool didhit = false;
if (player.AnimInfo.CurrentFrame == player._pAFNum) {
if (player.AnimInfo.CurrentFrame == player._pAFNum - 1) {
Point position = player.position.tile + player._pdir;
int dx = position.x;
int dy = position.y;
@ -1206,7 +1207,7 @@ bool DoAttack(int pnum)
}
}
if (player.AnimInfo.CurrentFrame == player._pAFrames) {
if (player.AnimInfo.CurrentFrame == player._pAFrames - 1) {
StartStand(pnum, player._pdir);
ClearStateVariables(player);
return true;
@ -1223,10 +1224,10 @@ bool DoRangeAttack(int pnum)
auto &player = Players[pnum];
int arrows = 0;
if (player.AnimInfo.CurrentFrame == player._pAFNum) {
if (player.AnimInfo.CurrentFrame == player._pAFNum - 1) {
arrows = 1;
}
if ((player._pIFlags & ISPL_MULT_ARROWS) != 0 && player.AnimInfo.CurrentFrame == player._pAFNum + 2) {
if ((player._pIFlags & ISPL_MULT_ARROWS) != 0 && player.AnimInfo.CurrentFrame == player._pAFNum + 1) {
arrows = 2;
}
@ -1277,7 +1278,7 @@ bool DoRangeAttack(int pnum)
}
}
if (player.AnimInfo.CurrentFrame >= player._pAFrames) {
if (player.AnimInfo.CurrentFrame >= player._pAFrames - 1) {
StartStand(pnum, player._pdir);
ClearStateVariables(player);
return true;
@ -1326,7 +1327,7 @@ bool DoBlock(int pnum)
}
auto &player = Players[pnum];
if (player.AnimInfo.CurrentFrame >= player._pBFrames) {
if (player.AnimInfo.CurrentFrame >= player._pBFrames - 1) {
StartStand(pnum, player._pdir);
ClearStateVariables(player);
@ -1395,7 +1396,7 @@ bool DoSpell(int pnum)
auto &player = Players[pnum];
int currentSpellFrame = leveltype != DTYPE_TOWN ? player.AnimInfo.CurrentFrame : ((player.AnimInfo.CurrentFrame * player.AnimInfo.TicksPerFrame) + player.AnimInfo.TickCounterOfCurrentFrame);
if (currentSpellFrame == (player._pSFNum + 1)) {
if (currentSpellFrame == player._pSFNum) {
CastSpell(
pnum,
player._pSpell,
@ -1410,7 +1411,7 @@ bool DoSpell(int pnum)
}
}
if (currentSpellFrame >= player._pSFrames) {
if (currentSpellFrame >= player._pSFrames - 1) {
StartStand(pnum, player._pdir);
ClearStateVariables(player);
return true;
@ -1426,7 +1427,7 @@ bool DoGotHit(int pnum)
}
auto &player = Players[pnum];
if (player.AnimInfo.CurrentFrame >= player._pHFrames) {
if (player.AnimInfo.CurrentFrame >= player._pHFrames - 1) {
StartStand(pnum, player._pdir);
ClearStateVariables(player);
if (GenerateRnd(4) != 0) {
@ -1446,7 +1447,7 @@ bool DoDeath(int pnum)
}
auto &player = Players[pnum];
if (player.AnimInfo.CurrentFrame == player.AnimInfo.NumberOfFrames) {
if (player.AnimInfo.CurrentFrame == player.AnimInfo.NumberOfFrames - 1) {
if (player.AnimInfo.TickCounterOfCurrentFrame == 0) {
player.AnimInfo.TicksPerFrame = 1000000000;
dFlags[player.position.tile.x][player.position.tile.y] |= DungeonFlag::DeadPlayer;
@ -1728,7 +1729,7 @@ void CheckNewPath(int pnum, bool pmWillBeCalled)
return;
}
if (player._pmode == PM_ATTACK && player.AnimInfo.CurrentFrame > player._pAFNum) {
if (player._pmode == PM_ATTACK && player.AnimInfo.CurrentFrame >= player._pAFNum) {
if (player.destAction == ACTION_ATTACK) {
d = GetDirection(player.position.future, { player.destParam1, player.destParam2 });
StartAttack(pnum, d);
@ -1759,7 +1760,7 @@ void CheckNewPath(int pnum, bool pmWillBeCalled)
}
}
if (player._pmode == PM_RATTACK && player.AnimInfo.CurrentFrame > player._pAFNum) {
if (player._pmode == PM_RATTACK && player.AnimInfo.CurrentFrame >= player._pAFNum) {
if (player.destAction == ACTION_RATTACK) {
d = GetDirection(player.position.tile, { player.destParam1, player.destParam2 });
StartRangeAttack(pnum, d, player.destParam1, player.destParam2);
@ -1775,8 +1776,8 @@ void CheckNewPath(int pnum, bool pmWillBeCalled)
}
}
int currentSpellFrame = leveltype != DTYPE_TOWN ? player.AnimInfo.CurrentFrame : (player.AnimInfo.CurrentFrame * (player.AnimInfo.TicksPerFrame + 1) + player.AnimInfo.TickCounterOfCurrentFrame);
if (player._pmode == PM_SPELL && currentSpellFrame > player._pSFNum) {
const int currentSpellFrame = leveltype != DTYPE_TOWN ? player.AnimInfo.CurrentFrame : (player.AnimInfo.CurrentFrame * (player.AnimInfo.TicksPerFrame + 1) + player.AnimInfo.TickCounterOfCurrentFrame);
if (player._pmode == PM_SPELL && currentSpellFrame >= player._pSFNum) {
if (player.destAction == ACTION_SPELL) {
d = GetDirection(player.position.tile, { player.destParam1, player.destParam2 });
StartSpell(pnum, d, player.destParam1, player.destParam2);
@ -2832,12 +2833,12 @@ void InitPlayer(Player &player, bool firstTime)
if (player._pHitPoints >> 6 > 0) {
player._pmode = PM_STAND;
NewPlrAnim(player, player_graphic::Stand, Direction::South, player._pNFrames, 4);
player.AnimInfo.CurrentFrame = GenerateRnd(player._pNFrames - 1) + 1;
player.AnimInfo.CurrentFrame = GenerateRnd(player._pNFrames - 1);
player.AnimInfo.TickCounterOfCurrentFrame = GenerateRnd(3);
} else {
player._pmode = PM_DEATH;
NewPlrAnim(player, player_graphic::Death, Direction::South, player._pDFrames, 2);
player.AnimInfo.CurrentFrame = player.AnimInfo.NumberOfFrames - 1;
player.AnimInfo.CurrentFrame = player.AnimInfo.NumberOfFrames - 2;
}
player._pdir = Direction::South;

8
Source/player.h

@ -693,13 +693,13 @@ struct Player {
{
if (_pmode == PM_STAND)
return true;
if (_pmode == PM_ATTACK && AnimInfo.CurrentFrame > _pAFNum)
if (_pmode == PM_ATTACK && AnimInfo.CurrentFrame >= _pAFNum)
return true;
if (_pmode == PM_RATTACK && AnimInfo.CurrentFrame > _pAFNum)
if (_pmode == PM_RATTACK && AnimInfo.CurrentFrame >= _pAFNum)
return true;
if (_pmode == PM_SPELL && AnimInfo.CurrentFrame > _pSFNum)
if (_pmode == PM_SPELL && AnimInfo.CurrentFrame >= _pSFNum)
return true;
if (IsWalking() && AnimInfo.CurrentFrame == AnimInfo.NumberOfFrames)
if (IsWalking() && AnimInfo.CurrentFrame == AnimInfo.NumberOfFrames - 1)
return true;
return false;
}

2
Source/qol/stash.cpp

@ -613,7 +613,7 @@ void DrawGoldWithdraw(const Surface &out, int amount)
const int dialogX = 30;
CelDrawTo(out, GetPanelPosition(UiPanels::Stash, { dialogX, 178 }), *pGBoxBuff, 1);
CelDrawTo(out, GetPanelPosition(UiPanels::Stash, { dialogX, 178 }), *pGBoxBuff, 0);
// Pre-wrap the string at spaces, otherwise DrawString would hard wrap in the middle of words
const std::string wrapped = WordWrapString(_("How many gold pieces do you want to withdraw?"), 200);

2
Source/quests.cpp

@ -743,7 +743,7 @@ void DrawQuestLog(const Surface &out)
SelectedQuest = l;
}
const auto x = InnerPanel.position.x;
CelDrawTo(out, GetPanelPosition(UiPanels::Quest, { 0, 351 }), *pQLogCel, 1);
CelDrawTo(out, GetPanelPosition(UiPanels::Quest, { 0, 351 }), *pQLogCel, 0);
int y = InnerPanel.position.y + ListYOffset;
for (int i = 0; i < EncounteredQuestCount; i++) {
if (i == FirstFinishedQuest) {

46
Source/scrollrt.cpp

@ -312,9 +312,9 @@ void DrawMissilePrivate(const Surface &out, const Missile &missile, Point target
Log("Draw Missile 2 type {}: NULL Cel Buffer", missile._mitype);
return;
}
int nCel = missile._miAnimFrame;
int nCel = missile._miAnimFrame - 1;
const uint32_t frames = LoadLE32(missile._miAnimData);
if (nCel < 1 || frames > 50 || nCel > static_cast<int>(frames)) {
if (nCel < 0 || frames > 50 || nCel >= static_cast<int>(frames)) {
Log("Draw Missile 2: frame {} of {}, missile type=={}", nCel, frames, missile._mitype);
return;
}
@ -322,11 +322,11 @@ void DrawMissilePrivate(const Surface &out, const Missile &missile, Point target
const Point missileRenderPosition { targetBufferPosition + missile.position.offsetForRendering - Displacement { missile._miAnimWidth2, 0 } };
CelSprite cel { missile._miAnimData, missile._miAnimWidth };
if (missile._miUniqTrans != 0)
Cl2DrawTRN(out, missileRenderPosition.x, missileRenderPosition.y, cel, missile._miAnimFrame, Monsters[missile._misource].uniqueTRN.get());
Cl2DrawTRN(out, missileRenderPosition.x, missileRenderPosition.y, cel, nCel, Monsters[missile._misource].uniqueTRN.get());
else if (missile._miLightFlag)
Cl2DrawLight(out, missileRenderPosition.x, missileRenderPosition.y, cel, missile._miAnimFrame);
Cl2DrawLight(out, missileRenderPosition.x, missileRenderPosition.y, cel, nCel);
else
Cl2Draw(out, missileRenderPosition.x, missileRenderPosition.y, cel, missile._miAnimFrame);
Cl2Draw(out, missileRenderPosition.x, missileRenderPosition.y, cel, nCel);
}
/**
@ -421,7 +421,7 @@ void DrawMonster(const Surface &out, Point tilePosition, Point targetBufferPosit
int nCel = monster.AnimInfo.GetFrameToUseForRendering();
const uint32_t frames = LoadLE32(monster.AnimInfo.celSprite->Data());
if (nCel < 1 || frames > 50 || nCel > static_cast<int>(frames)) {
if (nCel < 0 || frames > 50 || nCel >= static_cast<int>(frames)) {
Log(
"Draw Monster \"{}\" {}: facing {}, frame {} of {}",
monster.mName,
@ -461,16 +461,16 @@ void DrawPlayerIconHelper(const Surface &out, int pnum, missile_graphic_id missi
const CelSprite cel = MissileSpriteData[missileGraphicId].Sprite();
if (pnum == MyPlayerId) {
Cl2Draw(out, position.x, position.y, cel, 1);
Cl2Draw(out, position.x, position.y, cel, 0);
return;
}
if (lighting) {
Cl2DrawTRN(out, position.x, position.y, cel, 1, GetInfravisionTRN());
Cl2DrawTRN(out, position.x, position.y, cel, 0, GetInfravisionTRN());
return;
}
Cl2DrawLight(out, position.x, position.y, cel, 1);
Cl2DrawLight(out, position.x, position.y, cel, 0);
}
/**
@ -512,7 +512,7 @@ void DrawPlayer(const Surface &out, int pnum, Point tilePosition, Point targetBu
if (player.previewCelSprite) {
sprite = player.previewCelSprite;
nCel = 1;
nCel = 0;
}
if (!sprite) {
@ -522,8 +522,8 @@ void DrawPlayer(const Surface &out, int pnum, Point tilePosition, Point targetBu
Point spriteBufferPosition = targetBufferPosition - Displacement { CalculateWidth2(sprite ? sprite->Width() : 96), 0 };
int frames = SDL_SwapLE32(*reinterpret_cast<const DWORD *>(sprite->Data()));
if (nCel < 1 || frames > 50 || nCel > frames) {
const uint32_t frames = LoadLE32(sprite->Data());
if (nCel < 0 || frames > 50 || nCel >= static_cast<int>(frames)) {
const char *szMode = "unknown action";
if (player._pmode <= PM_QUIT)
szMode = PlayerModeNames[player._pmode];
@ -622,21 +622,21 @@ void DrawObject(const Surface &out, Point tilePosition, Point targetBufferPositi
return;
}
uint32_t nCel = objectToDraw._oAnimFrame;
uint32_t frames = LoadLE32(pCelBuff);
if (nCel < 1 || frames > 50 || nCel > frames) {
const uint32_t nCel = objectToDraw._oAnimFrame - 1;
const uint32_t frames = LoadLE32(pCelBuff);
if (nCel == static_cast<uint32_t>(-1) || frames > 50 || nCel >= frames) {
Log("Draw Object: frame {} of {}, object type=={}", nCel, frames, objectToDraw._otype);
return;
}
CelSprite cel { objectToDraw._oAnimData, objectToDraw._oAnimWidth };
if (pcursobj != -1 && &objectToDraw == &Objects[pcursobj]) {
CelBlitOutlineTo(out, 194, screenPosition, cel, objectToDraw._oAnimFrame);
CelBlitOutlineTo(out, 194, screenPosition, cel, nCel);
}
if (objectToDraw._oLight) {
CelClippedDrawLightTo(out, screenPosition, cel, objectToDraw._oAnimFrame);
CelClippedDrawLightTo(out, screenPosition, cel, nCel);
} else {
CelClippedDrawTo(out, screenPosition, cel, objectToDraw._oAnimFrame);
CelClippedDrawTo(out, screenPosition, cel, nCel);
}
}
@ -719,7 +719,7 @@ void DrawItem(const Surface &out, Point tilePosition, Point targetBufferPosition
int nCel = item.AnimInfo.GetFrameToUseForRendering();
const uint32_t frames = LoadLE32(cel->Data());
if (nCel < 1 || frames > 50 || nCel > static_cast<int>(frames)) {
if (nCel < 0 || frames > 50 || nCel >= static_cast<int>(frames)) {
Log("Draw \"{}\" Item 1: frame {} of {}, item type=={}", item._iIName, nCel, frames, ItemTypeToString(item._itype));
return;
}
@ -730,7 +730,7 @@ void DrawItem(const Surface &out, Point tilePosition, Point targetBufferPosition
CelBlitOutlineTo(out, GetOutlineColor(item, false), position, *cel, nCel);
}
CelClippedDrawLightTo(out, position, *cel, nCel);
if (item.AnimInfo.CurrentFrame == item.AnimInfo.NumberOfFrames || item._iCurs == ICURS_MAGIC_ROCK)
if (item.AnimInfo.CurrentFrame == item.AnimInfo.NumberOfFrames - 1 || item._iCurs == ICURS_MAGIC_ROCK)
AddItemToLabelQueue(bItem - 1, px, targetBufferPosition.y);
}
@ -853,8 +853,8 @@ void DrawDungeon(const Surface &out, Point tilePosition, Point targetBufferPosit
const byte *pCelBuff = pDeadGuy->data[(bDead >> 5) & 7];
assert(pCelBuff != nullptr);
const uint32_t frames = LoadLE32(pCelBuff);
int nCel = pDeadGuy->frame;
if (nCel < 1 || frames > 50 || nCel > static_cast<int>(frames)) {
const int nCel = pDeadGuy->frame;
if (nCel < 0 || frames >= 50 || nCel > static_cast<int>(frames)) {
Log("Unclipped dead: frame {} of {}, deadnum=={}", nCel, frames, (bDead & 0x1F) - 1);
break;
}
@ -905,7 +905,7 @@ void DrawDungeon(const Surface &out, Point tilePosition, Point targetBufferPosit
if (tilePosition.x > 0 && tilePosition.y > 0 && targetBufferPosition.y > TILE_HEIGHT) {
char bArch = dSpecial[tilePosition.x - 1][tilePosition.y - 1];
if (bArch != 0) {
CelDrawTo(out, targetBufferPosition + Displacement { 0, -TILE_HEIGHT }, *pSpecialCels, bArch);
CelDrawTo(out, targetBufferPosition + Displacement { 0, -TILE_HEIGHT }, *pSpecialCels, bArch - 1);
}
}
}

14
Source/stores.cpp

@ -159,7 +159,7 @@ void CalculateLineHeights()
void DrawSTextBack(const Surface &out)
{
CelDrawTo(out, { PANEL_X + 320 + 24, 327 + UI_OFFSET_Y }, *pSTextBoxCels, 1);
CelDrawTo(out, { PANEL_X + 320 + 24, 327 + UI_OFFSET_Y }, *pSTextBoxCels, 0);
DrawHalfTransparentRectTo(out, PANEL_X + 347, UI_OFFSET_Y + 28, 265, 297);
}
@ -168,17 +168,17 @@ void DrawSSlider(const Surface &out, int y1, int y2)
int yd1 = y1 * 12 + 44 + UI_OFFSET_Y;
int yd2 = y2 * 12 + 44 + UI_OFFSET_Y;
if (stextscrlubtn != -1)
CelDrawTo(out, { PANEL_X + 601, yd1 }, *pSTextSlidCels, 12);
CelDrawTo(out, { PANEL_X + 601, yd1 }, *pSTextSlidCels, 11);
else
CelDrawTo(out, { PANEL_X + 601, yd1 }, *pSTextSlidCels, 10);
CelDrawTo(out, { PANEL_X + 601, yd1 }, *pSTextSlidCels, 9);
if (stextscrldbtn != -1)
CelDrawTo(out, { PANEL_X + 601, yd2 }, *pSTextSlidCels, 11);
CelDrawTo(out, { PANEL_X + 601, yd2 }, *pSTextSlidCels, 10);
else
CelDrawTo(out, { PANEL_X + 601, yd2 }, *pSTextSlidCels, 9);
CelDrawTo(out, { PANEL_X + 601, yd2 }, *pSTextSlidCels, 8);
yd1 += 12;
int yd3 = yd1;
for (; yd3 < yd2; yd3 += 12) {
CelDrawTo(out, { PANEL_X + 601, yd3 }, *pSTextSlidCels, 14);
CelDrawTo(out, { PANEL_X + 601, yd3 }, *pSTextSlidCels, 13);
}
if (stextsel == BackButtonLine())
yd3 = stextlhold;
@ -188,7 +188,7 @@ void DrawSSlider(const Surface &out, int y1, int y2)
yd3 = 1000 * (stextsval + ((yd3 - stextup) / 4)) / (storenumh - 1) * (y2 * 12 - y1 * 12 - 24) / 1000;
else
yd3 = 0;
CelDrawTo(out, { PANEL_X + 601, (y1 + 1) * 12 + 44 + UI_OFFSET_Y + yd3 }, *pSTextSlidCels, 13);
CelDrawTo(out, { PANEL_X + 601, (y1 + 1) * 12 + 44 + UI_OFFSET_Y + yd3 }, *pSTextSlidCels, 12);
}
void AddSLine(int y)

98
Source/towners.cpp

@ -31,7 +31,7 @@ void NewTownerAnim(Towner &towner, byte *pAnim, uint8_t numFrames, int delay)
{
towner._tAnimData = pAnim;
towner._tAnimLen = numFrames;
towner._tAnimFrame = 1;
towner._tAnimFrame = 0;
towner._tAnimCnt = 0;
towner._tAnimDelay = delay;
}
@ -64,13 +64,13 @@ void InitSmith(Towner &towner, const TownerData &townerData)
towner._tAnimWidth = 96;
static const uint8_t AnimOrder[] = {
// clang-format off
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4,
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4,
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4,
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4,
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4,
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3
// clang-format on
};
towner.animOrder = AnimOrder;
@ -84,15 +84,15 @@ void InitBarOwner(Towner &towner, const TownerData &townerData)
towner._tAnimWidth = 96;
static const uint8_t AnimOrder[] = {
// clang-format off
1, 2, 3, 3, 2, 1, 16, 15, 14, 14, 15, 16,
1, 2, 3, 3, 2, 1, 16, 15, 14, 14, 15, 16,
1, 2, 3, 3, 2, 1, 16, 15, 14, 14, 15, 16,
1, 2, 3, 3, 2, 1, 16, 15, 14, 14, 15, 16,
1, 2, 3, 3, 2, 1, 16, 15, 14, 14, 15, 16,
1, 2, 3, 3, 2, 1, 16, 15, 14, 14, 15, 16,
1, 2, 3, 3, 2, 1, 16, 15, 14, 14, 15, 16,
1, 2, 3, 2, 1, 16, 15, 14, 14, 15, 16,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
0, 1, 2, 2, 1, 0, 15, 14, 13, 13, 14, 15,
0, 1, 2, 2, 1, 0, 15, 14, 13, 13, 14, 15,
0, 1, 2, 2, 1, 0, 15, 14, 13, 13, 14, 15,
0, 1, 2, 2, 1, 0, 15, 14, 13, 13, 14, 15,
0, 1, 2, 2, 1, 0, 15, 14, 13, 13, 14, 15,
0, 1, 2, 2, 1, 0, 15, 14, 13, 13, 14, 15,
0, 1, 2, 2, 1, 0, 15, 14, 13, 13, 14, 15,
0, 1, 2, 1, 0, 15, 14, 13, 13, 14, 15,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
// clang-format on
};
towner.animOrder = AnimOrder;
@ -115,15 +115,15 @@ void InitWitch(Towner &towner, const TownerData &townerData)
towner._tAnimWidth = 96;
static const uint8_t AnimOrder[] = {
// clang-format off
4, 4, 4, 5, 6, 6, 6, 5, 4, 15, 14, 13, 13, 13, 14, 15, 4, 5, 6, 6, 6, 5,
4, 4, 4, 5, 6, 6, 6, 5, 4, 15, 14, 13, 13, 13, 14, 15, 4, 5, 6, 6, 6, 5,
4, 4, 4, 5, 6, 6, 6, 5, 4, 15, 14, 13, 13, 13, 14, 15, 4, 5, 6, 6, 6, 5,
4, 3, 2, 1, 19, 18, 19, 1, 2, 1, 19, 18, 19, 1, 2,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
15, 15, 14, 13, 13, 13, 13, 14, 15,
15, 15, 14, 13, 12, 12, 12, 11, 10, 10, 10, 9,
8, 9, 10, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
1, 2, 1, 19, 18, 19, 1, 2, 1, 2, 3
3, 3, 3, 4, 5, 5, 5, 4, 3, 14, 13, 12, 12, 12, 13, 14, 3, 4, 5, 5, 5, 4,
3, 3, 3, 4, 5, 5, 5, 4, 3, 14, 13, 12, 12, 12, 13, 14, 3, 4, 5, 5, 5, 4,
3, 3, 3, 4, 5, 5, 5, 4, 3, 14, 13, 12, 12, 12, 13, 14, 3, 4, 5, 5, 5, 4,
3, 2, 1, 0, 18, 17, 18, 0, 1, 0, 18, 17, 18, 0, 1,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
14, 14, 13, 12, 12, 12, 12, 13, 14,
14, 14, 13, 12, 11, 11, 11, 10, 9, 9, 9, 8,
7, 8, 9, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
0, 1, 0, 18, 17, 18, 0, 1, 0, 1, 2
// clang-format on
};
towner.animOrder = AnimOrder;
@ -155,15 +155,15 @@ void InitHealer(Towner &towner, const TownerData &townerData)
towner._tAnimWidth = 96;
static const uint8_t AnimOrder[] = {
// clang-format off
1, 2, 3, 3, 2, 1, 20, 19, 19, 20,
1, 2, 3, 3, 2, 1, 20, 19, 19, 20,
1, 2, 3, 3, 2, 1, 20, 19, 19, 20,
1, 2, 3, 3, 2, 1, 20, 19, 19, 20,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
0, 1, 2, 2, 1, 0, 19, 18, 18, 19,
0, 1, 2, 2, 1, 0, 19, 18, 18, 19,
0, 1, 2, 2, 1, 0, 19, 18, 18, 19,
0, 1, 2, 2, 1, 0, 19, 18, 18, 19,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3,
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3,
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
// clang-format on
};
towner.animOrder = AnimOrder;
@ -177,10 +177,10 @@ void InitTeller(Towner &towner, const TownerData &townerData)
towner._tAnimWidth = 96;
static const uint8_t AnimOrder[] = {
// clang-format off
1, 1, 25, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 25, 25, 1, 1, 1, 25,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
0, 0, 24, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 24, 24, 0, 0, 0, 24,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
// clang-format on
};
towner.animOrder = AnimOrder;
@ -194,9 +194,9 @@ void InitDrunk(Towner &towner, const TownerData &townerData)
towner._tAnimWidth = 96;
static const uint8_t AnimOrder[] = {
// clang-format off
1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11, 11, 11, 12, 13, 14, 15, 16, 17, 18, 18,
1, 1, 1, 18, 17, 16, 15, 14, 13, 12, 11, 10, 11, 12, 13, 14, 15, 16, 17, 18,
1, 2, 3, 4, 5, 5, 5, 4, 3, 2
0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 11, 12, 13, 14, 15, 16, 17, 17,
0, 0, 0, 17, 16, 15, 14, 13, 12, 11, 10, 9, 10, 11, 12, 13, 14, 15, 16, 17,
0, 1, 2, 3, 4, 4, 4, 3, 2, 1
// clang-format on
};
towner.animOrder = AnimOrder;
@ -210,11 +210,9 @@ void InitCows(Towner &towner, const TownerData &townerData)
towner._tAnimWidth = 128;
towner.animOrder = nullptr;
towner.animOrderSize = 0;
for (int i = 0; i < 8; i++) {
towner._tNAnim[i] = CelGetFrame(CowCels.get(), i);
}
CelGetDirectionFrames(CowCels.get(), towner._tNAnim);
NewTownerAnim(towner, towner._tNAnim[static_cast<size_t>(townerData.dir)], 12, 3);
towner._tAnimFrame = GenerateRnd(11) + 1;
towner._tAnimFrame = GenerateRnd(11);
towner.name = _("Cow");
const Point position = townerData.position;
@ -650,7 +648,7 @@ void TalkToCowFarmer(Player &player, Towner &cowFarmer)
quest._qactive = QUEST_DONE;
auto curFrame = cowFarmer._tAnimFrame;
LoadTownerAnimations(cowFarmer, "Towners\\Farmer\\mfrmrn2.CEL", 15, 3);
cowFarmer._tAnimFrame = std::min(curFrame, cowFarmer._tAnimLen);
cowFarmer._tAnimFrame = std::min<uint8_t>(curFrame, cowFarmer._tAnimLen - 1);
return;
}
@ -730,7 +728,7 @@ void TalkToGirl(Player &player, Towner &girl)
quest._qactive = QUEST_DONE;
auto curFrame = girl._tAnimFrame;
LoadTownerAnimations(girl, "Towners\\Girl\\Girls1.CEL", 20, 6);
girl._tAnimFrame = std::min(curFrame, girl._tAnimLen);
girl._tAnimFrame = std::min<uint8_t>(curFrame, girl._tAnimLen - 1);
if (gbIsMultiplayer)
NetSendCmdQuest(true, quest);
return;
@ -866,8 +864,8 @@ void ProcessTowners()
}
towner._tAnimFrame++;
if (towner._tAnimFrame > towner._tAnimLen)
towner._tAnimFrame = 1;
if (towner._tAnimFrame >= towner._tAnimLen)
towner._tAnimFrame = 0;
}
}

282
test/animationinfo_test.cpp

@ -108,11 +108,16 @@ TEST(AnimationInfo, AttackSwordWarrior) // ProcessAnimationPending should be con
{
new SetNewAnimationData(16, 1, AnimationDistributionFlags::ProcessAnimationPending, 0, 9),
// ProcessAnimation directly after StartAttack (in same GameTick). So we don't see any rendering before.
new GameTickData(1, 0),
new RenderingData(0.0f, 0),
new RenderingData(0.3f, 0),
new RenderingData(0.6f, 0),
new RenderingData(0.8f, 0),
new GameTickData(2, 0),
new RenderingData(0.0f, 1),
new RenderingData(0.3f, 1),
new RenderingData(0.6f, 1),
new RenderingData(0.8f, 1),
new RenderingData(0.8f, 2),
new GameTickData(3, 0),
new RenderingData(0.0f, 2),
new RenderingData(0.3f, 2),
@ -121,7 +126,7 @@ TEST(AnimationInfo, AttackSwordWarrior) // ProcessAnimationPending should be con
new GameTickData(4, 0),
new RenderingData(0.0f, 3),
new RenderingData(0.3f, 3),
new RenderingData(0.6f, 3),
new RenderingData(0.6f, 4),
new RenderingData(0.8f, 4),
new GameTickData(5, 0),
new RenderingData(0.0f, 4),
@ -130,7 +135,7 @@ TEST(AnimationInfo, AttackSwordWarrior) // ProcessAnimationPending should be con
new RenderingData(0.8f, 5),
new GameTickData(6, 0),
new RenderingData(0.0f, 5),
new RenderingData(0.3f, 5),
new RenderingData(0.3f, 6),
new RenderingData(0.6f, 6),
new RenderingData(0.8f, 6),
new GameTickData(7, 0),
@ -138,30 +143,25 @@ TEST(AnimationInfo, AttackSwordWarrior) // ProcessAnimationPending should be con
new RenderingData(0.3f, 7),
new RenderingData(0.6f, 7),
new RenderingData(0.8f, 7),
new GameTickData(8, 0),
new RenderingData(0.0f, 7),
new RenderingData(0.3f, 8),
new RenderingData(0.6f, 8),
new RenderingData(0.8f, 8),
// After this GameTick, the Animation Distribution Logic is disabled
new GameTickData(8, 0),
new RenderingData(0.1f, 8),
new GameTickData(9, 0),
new RenderingData(0.1f, 9),
new RenderingData(0.4f, 9),
new GameTickData(10, 0),
new RenderingData(0.4f, 10),
new GameTickData(11, 0),
new RenderingData(0.4f, 11),
new RenderingData(0.3f, 11),
new GameTickData(12, 0),
new RenderingData(0.3f, 12),
new RenderingData(0.0f, 12),
new GameTickData(13, 0),
new RenderingData(0.0f, 13),
new RenderingData(0.6f, 13),
new GameTickData(14, 0),
new RenderingData(0.6f, 14),
new GameTickData(15, 0),
new RenderingData(0.6f, 15),
new GameTickData(16, 0),
new RenderingData(0.6f, 16),
// Animation stopped cause PM_DoAttack would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame == plr[pnum]._pAFrames) {"
// Animation stopped cause PM_DoAttack would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame == plr[pnum]._pAFrames - 1) {"
});
}
@ -171,50 +171,50 @@ TEST(AnimationInfo, AttackSwordWarriorWithFastestAttack) // Skipped frames and P
{
new SetNewAnimationData(16, 1, AnimationDistributionFlags::ProcessAnimationPending, 2, 9),
// ProcessAnimation directly after StartAttack (in same GameTick). So we don't see any rendering before.
new GameTickData(3, 0),
new RenderingData(0.0f, 0),
new RenderingData(0.3f, 0),
new RenderingData(0.6f, 0),
new RenderingData(0.8f, 1),
new GameTickData(4, 0),
new RenderingData(0.0f, 1),
new RenderingData(0.3f, 1),
new RenderingData(0.6f, 1),
new RenderingData(0.3f, 2),
new RenderingData(0.6f, 2),
new RenderingData(0.8f, 2),
new GameTickData(5, 0),
new RenderingData(0.0f, 2),
new RenderingData(0.0f, 3),
new RenderingData(0.3f, 3),
new RenderingData(0.6f, 3),
new RenderingData(0.8f, 3),
new RenderingData(0.6f, 4),
new RenderingData(0.8f, 4),
new GameTickData(6, 0),
new RenderingData(0.0f, 4),
new RenderingData(0.3f, 4),
new RenderingData(0.3f, 5),
new RenderingData(0.6f, 5),
new RenderingData(0.8f, 5),
new RenderingData(0.8f, 6),
new GameTickData(7, 0),
new RenderingData(0.0f, 5),
new RenderingData(0.0f, 6),
new RenderingData(0.3f, 6),
new RenderingData(0.6f, 6),
new RenderingData(0.6f, 7),
new RenderingData(0.8f, 7),
new GameTickData(8, 0),
new RenderingData(0.0f, 7),
new RenderingData(0.3f, 7),
new RenderingData(0.6f, 8),
new RenderingData(0.8f, 8),
// After this GameTick, the Animation Distribution Logic is disabled
new GameTickData(8, 0),
new RenderingData(0.1f, 8),
new GameTickData(9, 0),
new RenderingData(0.1f, 9),
new RenderingData(0.4f, 9),
new GameTickData(10, 0),
new RenderingData(0.4f, 10),
new GameTickData(11, 0),
new RenderingData(0.4f, 11),
new RenderingData(0.3f, 11),
new GameTickData(12, 0),
new RenderingData(0.3f, 12),
new RenderingData(0.0f, 12),
new GameTickData(13, 0),
new RenderingData(0.0f, 13),
new RenderingData(0.6f, 13),
new GameTickData(14, 0),
new RenderingData(0.6f, 14),
new GameTickData(15, 0),
new RenderingData(0.6f, 15),
new GameTickData(16, 0),
new RenderingData(0.6f, 16),
// Animation stopped cause PM_DoAttack would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame == plr[pnum]._pAFrames) {"
// Animation stopped cause PM_DoAttack would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame == plr[pnum]._pAFrames - 1) {"
});
}
@ -227,11 +227,16 @@ TEST(AnimationInfo, AttackSwordWarriorRepeated)
{
new SetNewAnimationData(16, 1, AnimationDistributionFlags::ProcessAnimationPending, 0, 9),
// ProcessAnimation directly after StartAttack (in same GameTick). So we don't see any rendering before.
new GameTickData(1, 0),
new RenderingData(0.0f, 0),
new RenderingData(0.3f, 0),
new RenderingData(0.6f, 0),
new RenderingData(0.8f, 0),
new GameTickData(2, 0),
new RenderingData(0.0f, 1),
new RenderingData(0.3f, 1),
new RenderingData(0.6f, 1),
new RenderingData(0.8f, 1),
new RenderingData(0.8f, 2),
new GameTickData(3, 0),
new RenderingData(0.0f, 2),
new RenderingData(0.3f, 2),
@ -240,7 +245,7 @@ TEST(AnimationInfo, AttackSwordWarriorRepeated)
new GameTickData(4, 0),
new RenderingData(0.0f, 3),
new RenderingData(0.3f, 3),
new RenderingData(0.6f, 3),
new RenderingData(0.6f, 4),
new RenderingData(0.8f, 4),
new GameTickData(5, 0),
new RenderingData(0.0f, 4),
@ -249,7 +254,7 @@ TEST(AnimationInfo, AttackSwordWarriorRepeated)
new RenderingData(0.8f, 5),
new GameTickData(6, 0),
new RenderingData(0.0f, 5),
new RenderingData(0.3f, 5),
new RenderingData(0.3f, 6),
new RenderingData(0.6f, 6),
new RenderingData(0.8f, 6),
new GameTickData(7, 0),
@ -257,75 +262,70 @@ TEST(AnimationInfo, AttackSwordWarriorRepeated)
new RenderingData(0.3f, 7),
new RenderingData(0.6f, 7),
new RenderingData(0.8f, 7),
new GameTickData(8, 0),
new RenderingData(0.0f, 7),
new RenderingData(0.3f, 8),
new RenderingData(0.6f, 8),
new RenderingData(0.8f, 8),
// After this GameTick, the Animation Distribution Logic is disabled
new GameTickData(8, 0),
new RenderingData(0.1f, 8),
new GameTickData(9, 0),
new RenderingData(0.1f, 9),
new GameTickData(10, 0),
new RenderingData(0.3f, 10),
new RenderingData(0.3f, 9),
// Start of repeated attack, cause plr[pnum].AnimInfo.CurrentFrame > plr[myplr]._pAFNum
new SetNewAnimationData(16, 1, static_cast<AnimationDistributionFlags>(AnimationDistributionFlags::ProcessAnimationPending | AnimationDistributionFlags::RepeatedAction), 0, 9),
// ProcessAnimation directly after StartAttack (in same GameTick). So we don't see any rendering before.
new GameTickData(1, 0),
new RenderingData(0.0f, 10),
new RenderingData(0.3f, 10),
new RenderingData(0.6f, 11),
new RenderingData(0.8f, 11),
new GameTickData(2, 0),
new RenderingData(0.0f, 11),
new RenderingData(0.3f, 11),
new RenderingData(0.6f, 12),
new RenderingData(0.8f, 12),
new RenderingData(0.0f, 12),
new RenderingData(0.3f, 12),
new RenderingData(0.6f, 13),
new RenderingData(0.8f, 13),
new GameTickData(3, 0),
new RenderingData(0.0f, 13),
new RenderingData(0.3f, 13),
new RenderingData(0.6f, 14),
new RenderingData(0.8f, 14),
new RenderingData(0.0f, 14),
new RenderingData(0.3f, 14),
new RenderingData(0.6f, 15),
new RenderingData(0.8f, 15),
new GameTickData(4, 0),
new RenderingData(0.0f, 15),
new RenderingData(0.3f, 15),
new RenderingData(0.6f, 16),
new RenderingData(0.8f, 16),
new RenderingData(0.0f, 0),
new RenderingData(0.3f, 0),
new RenderingData(0.6f, 1),
new RenderingData(0.8f, 1),
new GameTickData(5, 0),
new RenderingData(0.0f, 1),
new RenderingData(0.3f, 1),
new RenderingData(0.6f, 2),
new RenderingData(0.8f, 2),
new RenderingData(0.0f, 2),
new RenderingData(0.3f, 2),
new RenderingData(0.6f, 3),
new RenderingData(0.8f, 3),
new GameTickData(6, 0),
new RenderingData(0.0f, 3),
new RenderingData(0.3f, 3),
new RenderingData(0.6f, 4),
new RenderingData(0.8f, 4),
new RenderingData(0.0f, 4),
new RenderingData(0.3f, 4),
new RenderingData(0.6f, 5),
new RenderingData(0.8f, 5),
new GameTickData(7, 0),
new RenderingData(0.0f, 5),
new RenderingData(0.3f, 5),
new RenderingData(0.6f, 6),
new RenderingData(0.8f, 6),
new GameTickData(8, 0),
new RenderingData(0.0f, 7),
new RenderingData(0.3f, 7),
new RenderingData(0.6f, 8),
new RenderingData(0.8f, 8),
new RenderingData(0.0f, 6),
new RenderingData(0.3f, 6),
new RenderingData(0.6f, 7),
new RenderingData(0.8f, 7),
// After this GameTick, the Animation Distribution Logic is disabled
new GameTickData(8, 0),
new RenderingData(0.1f, 8),
new GameTickData(9, 0),
new RenderingData(0.1f, 9),
new RenderingData(0.4f, 9),
new GameTickData(10, 0),
new RenderingData(0.4f, 10),
new GameTickData(11, 0),
new RenderingData(0.4f, 11),
new RenderingData(0.3f, 11),
new GameTickData(12, 0),
new RenderingData(0.3f, 12),
new RenderingData(0.0f, 12),
new GameTickData(13, 0),
new RenderingData(0.0f, 13),
new RenderingData(0.6f, 13),
new GameTickData(14, 0),
new RenderingData(0.6f, 14),
new GameTickData(15, 0),
new RenderingData(0.6f, 15),
new GameTickData(16, 0),
new RenderingData(0.6f, 16),
// Animation stopped cause PM_DoAttack would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame == plr[pnum]._pAFrames) {"
// Animation stopped cause PM_DoAttack would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame == plr[pnum]._pAFrames - 1) {"
});
}
@ -334,25 +334,25 @@ TEST(AnimationInfo, BlockingWarriorNormal) // Ignored delay for last Frame shoul
RunAnimationTest(
{
new SetNewAnimationData(2, 3, AnimationDistributionFlags::SkipsDelayOfLastFrame),
new RenderingData(0.0f, 0),
new RenderingData(0.3f, 0),
new RenderingData(0.6f, 0),
new RenderingData(0.8f, 0),
new GameTickData(0, 1),
new RenderingData(0.0f, 0),
new RenderingData(0.3f, 0),
new RenderingData(0.6f, 0),
new RenderingData(0.8f, 0),
new GameTickData(0, 2),
new RenderingData(0.0f, 1),
new RenderingData(0.3f, 1),
new RenderingData(0.6f, 1),
new RenderingData(0.8f, 1),
new GameTickData(1, 1),
new GameTickData(1, 0),
new RenderingData(0.0f, 1),
new RenderingData(0.3f, 1),
new RenderingData(0.6f, 1),
new RenderingData(0.8f, 1),
new GameTickData(1, 2),
new RenderingData(0.0f, 2),
new RenderingData(0.3f, 2),
new RenderingData(0.6f, 2),
new RenderingData(0.8f, 2),
new GameTickData(2, 0),
new RenderingData(0.0f, 2),
new RenderingData(0.3f, 2),
new RenderingData(0.6f, 2),
new RenderingData(0.8f, 2),
// Animation stopped cause PM_DoBlock would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame >= plr[pnum]._pBFrames) {"
});
}
@ -362,25 +362,25 @@ TEST(AnimationInfo, BlockingSorcererWithFastBlock) // Skipped frames and ignored
RunAnimationTest(
{
new SetNewAnimationData(6, 3, AnimationDistributionFlags::SkipsDelayOfLastFrame, 4),
new RenderingData(0.0f, 0),
new RenderingData(0.3f, 0),
new RenderingData(0.6f, 0),
new RenderingData(0.8f, 1),
new GameTickData(4, 1),
new RenderingData(0.0f, 1),
new RenderingData(0.3f, 1),
new RenderingData(0.6f, 1),
new RenderingData(0.6f, 2),
new RenderingData(0.8f, 2),
new GameTickData(5, 1),
new RenderingData(0.0f, 2),
new RenderingData(0.3f, 2),
new GameTickData(4, 2),
new RenderingData(0.0f, 3),
new RenderingData(0.3f, 3),
new RenderingData(0.6f, 3),
new RenderingData(0.8f, 3),
new GameTickData(5, 2),
new RenderingData(0.8f, 4),
new GameTickData(5, 0),
new RenderingData(0.0f, 4),
new RenderingData(0.3f, 4),
new RenderingData(0.6f, 4),
new RenderingData(0.6f, 5),
new RenderingData(0.8f, 5),
new GameTickData(6, 0),
new RenderingData(0.0f, 5),
new RenderingData(0.3f, 5),
new RenderingData(0.6f, 6),
new RenderingData(0.8f, 6),
// Animation stopped cause PM_DoBlock would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame >= plr[pnum]._pBFrames) {"
});
}
@ -390,25 +390,25 @@ TEST(AnimationInfo, HitRecoverySorcererZenMode) // Skipped frames and ignored de
RunAnimationTest(
{
new SetNewAnimationData(8, 1, AnimationDistributionFlags::None, 4),
new RenderingData(0.0f, 1),
new RenderingData(0.3f, 1),
new RenderingData(0.6f, 2),
new RenderingData(0.8f, 2),
new RenderingData(0.0f, 0),
new RenderingData(0.3f, 0),
new RenderingData(0.6f, 1),
new RenderingData(0.8f, 1),
new GameTickData(5, 0),
new RenderingData(0.0f, 2),
new RenderingData(0.3f, 2),
new RenderingData(0.6f, 3),
new RenderingData(0.8f, 3),
new GameTickData(6, 0),
new RenderingData(0.0f, 3),
new RenderingData(0.3f, 3),
new RenderingData(0.6f, 4),
new RenderingData(0.8f, 4),
new RenderingData(0.0f, 4),
new RenderingData(0.3f, 4),
new RenderingData(0.6f, 5),
new RenderingData(0.8f, 5),
new GameTickData(7, 0),
new RenderingData(0.0f, 5),
new RenderingData(0.3f, 5),
new RenderingData(0.6f, 6),
new RenderingData(0.8f, 6),
new GameTickData(8, 0),
new RenderingData(0.0f, 7),
new RenderingData(0.3f, 7),
new RenderingData(0.6f, 8),
new RenderingData(0.8f, 8),
new RenderingData(0.0f, 6),
new RenderingData(0.3f, 6),
new RenderingData(0.6f, 7),
new RenderingData(0.8f, 7),
// Animation stopped cause PM_DoGotHit would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame >= plr[pnum]._pHFrames) {"
});
}
@ -417,7 +417,16 @@ TEST(AnimationInfo, Stand) // Distribution Logic shouldn't change anything here
RunAnimationTest(
{
new SetNewAnimationData(10, 4),
new RenderingData(0.1f, 1),
new RenderingData(0.1f, 0),
new GameTickData(0, 1),
new RenderingData(0.6f, 0),
new GameTickData(0, 2),
new RenderingData(0.6f, 0),
new GameTickData(0, 3),
new RenderingData(0.6f, 0),
new GameTickData(1, 0),
new RenderingData(0.6f, 1),
new GameTickData(1, 1),
new RenderingData(0.6f, 1),
new GameTickData(1, 2),
@ -497,23 +506,14 @@ TEST(AnimationInfo, Stand) // Distribution Logic shouldn't change anything here
new GameTickData(9, 3),
new RenderingData(0.6f, 9),
new GameTickData(10, 0),
new RenderingData(0.6f, 10),
new GameTickData(10, 1),
new RenderingData(0.6f, 10),
new GameTickData(10, 2),
new RenderingData(0.6f, 10),
new GameTickData(10, 3),
new RenderingData(0.6f, 10),
// Animation starts again
new GameTickData(1, 0),
new RenderingData(0.1f, 1),
new GameTickData(1, 1),
new RenderingData(0.6f, 1),
new GameTickData(1, 2),
new RenderingData(0.6f, 1),
new GameTickData(1, 3),
new RenderingData(0.6f, 1),
new GameTickData(0, 0),
new RenderingData(0.1f, 0),
new GameTickData(0, 1),
new RenderingData(0.6f, 0),
new GameTickData(0, 2),
new RenderingData(0.6f, 0),
new GameTickData(0, 3),
new RenderingData(0.6f, 0),
});
}

2
test/writehero_test.cpp

@ -267,7 +267,7 @@ static void AssertPlayer(Player &player)
ASSERT_EQ(player.AnimInfo.TicksPerFrame, 4);
ASSERT_EQ(player.AnimInfo.TickCounterOfCurrentFrame, 1);
ASSERT_EQ(player.AnimInfo.NumberOfFrames, 20);
ASSERT_EQ(player.AnimInfo.CurrentFrame, 1);
ASSERT_EQ(player.AnimInfo.CurrentFrame, 0);
ASSERT_EQ(player._pSpell, -1);
ASSERT_EQ(player._pSplType, 4);
ASSERT_EQ(player._pSplFrom, 0);

Loading…
Cancel
Save