Browse Source

CLX: A new graphics format

The format is almost identical to CL2, except it uses the frame header
to store frame width and height instead of 5 32-line offsets.

This means we always have access to frame dimensions, so we can use it
as an on-disk format for our graphics as well.

Additionally, we may be able to optimize the rendering even more
in the future now that we have guaranteed knowledge of frame dimensions.
pull/5182/head
Gleb Mazovetskiy 4 years ago committed by Anders Jenbo
parent
commit
961866e1c4
  1. 8
      Source/CMakeLists.txt
  2. 18
      Source/DiabloUI/button.cpp
  3. 4
      Source/DiabloUI/button.h
  4. 12
      Source/DiabloUI/credits.cpp
  5. 107
      Source/DiabloUI/diabloui.cpp
  6. 12
      Source/DiabloUI/diabloui.h
  7. 28
      Source/DiabloUI/dialogs.cpp
  8. 2
      Source/DiabloUI/mainmenu.cpp
  9. 24
      Source/DiabloUI/progress.cpp
  10. 12
      Source/DiabloUI/scrollbar.cpp
  11. 20
      Source/DiabloUI/scrollbar.h
  12. 2
      Source/DiabloUI/selgame.cpp
  13. 11
      Source/DiabloUI/selhero.cpp
  14. 2
      Source/DiabloUI/selstart.cpp
  15. 2
      Source/DiabloUI/settingsmenu.cpp
  16. 14
      Source/DiabloUI/title.cpp
  17. 42
      Source/DiabloUI/ui_item.h
  18. 63
      Source/control.cpp
  19. 2
      Source/control.h
  20. 2
      Source/controls/modifier_hints.cpp
  21. 6
      Source/controls/touch/renderers.cpp
  22. 36
      Source/cursor.cpp
  23. 9
      Source/cursor.h
  24. 14
      Source/dead.cpp
  25. 14
      Source/dead.h
  26. 4
      Source/debug.cpp
  27. 4
      Source/debug.h
  28. 16
      Source/diablo.cpp
  29. 12
      Source/doom.cpp
  30. 8
      Source/engine/animationinfo.cpp
  31. 17
      Source/engine/animationinfo.h
  32. 68
      Source/engine/cel_header.hpp
  33. 344
      Source/engine/cel_sprite.hpp
  34. 599
      Source/engine/clx_sprite.hpp
  35. 17
      Source/engine/load_cel.cpp
  36. 21
      Source/engine/load_cel.hpp
  37. 18
      Source/engine/load_cl2.cpp
  38. 63
      Source/engine/load_cl2.hpp
  39. 13
      Source/engine/load_file.hpp
  40. 20
      Source/engine/load_pcx.cpp
  41. 21
      Source/engine/load_pcx.hpp
  42. 41
      Source/engine/render/blit_impl.hpp
  43. 115
      Source/engine/render/cl2_render.hpp
  44. 234
      Source/engine/render/clx_render.cpp
  45. 115
      Source/engine/render/clx_render.hpp
  46. 13
      Source/engine/render/common_impl.h
  47. 177
      Source/engine/render/scrollrt.cpp
  48. 28
      Source/engine/render/text_render.cpp
  49. 4
      Source/engine/render/text_render.hpp
  50. 19
      Source/error.cpp
  51. 36
      Source/gmenu.cpp
  52. 22
      Source/interfac.cpp
  53. 43
      Source/inv.cpp
  54. 22
      Source/items.cpp
  55. 2
      Source/levels/gendung.cpp
  56. 4
      Source/levels/gendung.h
  57. 10
      Source/minitext.cpp
  58. 10
      Source/misdat.cpp
  59. 33
      Source/misdat.h
  60. 18
      Source/missiles.cpp
  61. 6
      Source/missiles.h
  62. 76
      Source/monster.cpp
  63. 21
      Source/monster.h
  64. 22
      Source/objects.cpp
  65. 60
      Source/objects.h
  66. 13
      Source/panels/charpanel.cpp
  67. 4
      Source/panels/charpanel.hpp
  68. 8
      Source/panels/info_box.cpp
  69. 6
      Source/panels/info_box.hpp
  70. 2
      Source/panels/mainpanel.cpp
  71. 22
      Source/panels/spell_book.cpp
  72. 12
      Source/panels/spell_icons.cpp
  73. 4
      Source/panels/spell_icons.hpp
  74. 54
      Source/player.cpp
  75. 19
      Source/player.h
  76. 4
      Source/qol/itemlabels.cpp
  77. 11
      Source/qol/stash.cpp
  78. 10
      Source/quests.cpp
  79. 4
      Source/quests.h
  80. 21
      Source/stores.cpp
  81. 2
      Source/stores.h
  82. 2
      Source/storm/storm_net.cpp
  83. 29
      Source/towners.cpp
  84. 10
      Source/towners.h
  85. 13
      Source/utils/cel_to_cl2.hpp
  86. 31
      Source/utils/cel_to_clx.cpp
  87. 13
      Source/utils/cel_to_clx.hpp
  88. 97
      Source/utils/cl2_to_clx.cpp
  89. 26
      Source/utils/cl2_to_clx.hpp
  90. 81
      Source/utils/intrusive_optional.hpp
  91. 41
      Source/utils/pcx_to_clx.cpp
  92. 6
      Source/utils/pcx_to_clx.hpp

8
Source/CMakeLists.txt

@ -100,6 +100,7 @@ set(libdevilutionx_SRCS
engine/direction.cpp
engine/dx.cpp
engine/load_cel.cpp
engine/load_cl2.cpp
engine/load_pcx.cpp
engine/palette.cpp
engine/path.cpp
@ -108,7 +109,7 @@ set(libdevilutionx_SRCS
engine/trn.cpp
engine/render/automap_render.cpp
engine/render/cl2_render.cpp
engine/render/clx_render.cpp
engine/render/dun_render.cpp
engine/render/scrollrt.cpp
engine/render/text_render.cpp
@ -149,7 +150,8 @@ set(libdevilutionx_SRCS
storm/storm_net.cpp
storm/storm_svid.cpp
utils/cel_to_cl2.cpp
utils/cel_to_clx.cpp
utils/cl2_to_clx.cpp
utils/console.cpp
utils/display.cpp
utils/file_util.cpp
@ -158,7 +160,7 @@ set(libdevilutionx_SRCS
utils/logged_fstream.cpp
utils/paths.cpp
utils/pcx.cpp
utils/pcx_to_cl2.cpp
utils/pcx_to_clx.cpp
utils/sdl_bilinear_scale.cpp
utils/sdl_thread.cpp
utils/str_cat.cpp

18
Source/DiabloUI/button.cpp

@ -1,9 +1,9 @@
#include "DiabloUI/button.h"
#include "DiabloUI/diabloui.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/load_pcx.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/text_render.hpp"
#include "utils/display.h"
@ -11,15 +11,15 @@ namespace devilution {
namespace {
std::optional<OwnedCelSpriteSheetWithFrameHeight> ButtonSprites;
OptionalOwnedClxSpriteList ButtonSprites;
} // namespace
void LoadDialogButtonGraphics()
{
ButtonSprites = LoadPcxSpriteSheetAsCl2("ui_art\\dvl_but_sml.pcx", 2);
if (ButtonSprites == std::nullopt) {
ButtonSprites = LoadPcxSpriteSheetAsCl2("ui_art\\but_sml.pcx", 15);
ButtonSprites = LoadPcxSpriteList("ui_art\\dvl_but_sml.pcx", 2);
if (!ButtonSprites) {
ButtonSprites = LoadPcxSpriteList("ui_art\\but_sml.pcx", 15);
}
}
@ -28,15 +28,15 @@ void FreeDialogButtonGraphics()
ButtonSprites = std::nullopt;
}
CelFrameWithHeight ButtonSprite(bool pressed)
ClxSprite ButtonSprite(bool pressed)
{
return ButtonSprites->sprite(pressed ? 1 : 0);
return (*ButtonSprites)[pressed ? 1 : 0];
}
void RenderButton(UiButton *button)
{
const Surface &out = Surface(DiabloUiSurface()).subregion(button->m_rect.x, button->m_rect.y, button->m_rect.w, button->m_rect.h);
RenderCl2Sprite(out, ButtonSprite(button->IsPressed()), { 0, 0 });
RenderClxSprite(out, ButtonSprite(button->IsPressed()), { 0, 0 });
Rectangle textRect { { 0, 0 }, { button->m_rect.w, button->m_rect.h } };
if (!button->IsPressed()) {

4
Source/DiabloUI/button.h

@ -1,7 +1,7 @@
#pragma once
#include "DiabloUI/ui_item.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
namespace devilution {
@ -10,7 +10,7 @@ const Uint16 DialogButtonHeight = 28;
void LoadDialogButtonGraphics();
void FreeDialogButtonGraphics();
CelFrameWithHeight ButtonSprite(bool pressed);
ClxSprite ButtonSprite(bool pressed);
void RenderButton(UiButton *button);
bool HandleMouseEventButton(const SDL_Event &event, UiButton *button);
void HandleGlobalMouseUpButton(UiButton *button);

12
Source/DiabloUI/credits.cpp

@ -11,7 +11,7 @@
#include "controls/input.h"
#include "controls/menu_controls.h"
#include "engine/load_pcx.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/text_render.hpp"
#include "hwcursor.hpp"
#include "utils/display.h"
@ -98,8 +98,8 @@ void CreditsRenderer::Render()
SDL_FillRect(DiabloUiSurface(), nullptr, 0x000000);
const Point uiPosition = GetUIRectangle().position;
if (ArtBackgroundWidescreen)
RenderCl2Sprite(Surface(DiabloUiSurface()), ArtBackgroundWidescreen->sprite(), uiPosition - Displacement { 320, 0 });
RenderCl2Sprite(Surface(DiabloUiSurface()), ArtBackground->sprite(0), uiPosition);
RenderClxSprite(Surface(DiabloUiSurface()), (*ArtBackgroundWidescreen)[0], uiPosition - Displacement { 320, 0 });
RenderClxSprite(Surface(DiabloUiSurface()), (*ArtBackground)[0], uiPosition);
const std::size_t linesBegin = std::max(offsetY / LINE_H, 0);
const std::size_t linesEnd = std::min(linesBegin + MAX_VISIBLE_LINES, linesToRender.size());
@ -170,7 +170,7 @@ bool TextDialog(char const *const *text, std::size_t textLines)
bool UiCreditsDialog()
{
ArtBackgroundWidescreen = LoadPcxAsCl2("ui_art\\creditsw.pcx");
ArtBackgroundWidescreen = LoadPcx("ui_art\\creditsw.pcx");
LoadBackgroundArt("ui_art\\credits.pcx");
return TextDialog(CreditLines, CreditLinesSize);
@ -179,10 +179,10 @@ bool UiCreditsDialog()
bool UiSupportDialog()
{
if (gbIsHellfire) {
ArtBackgroundWidescreen = LoadPcxAsCl2("ui_art\\supportw.pcx");
ArtBackgroundWidescreen = LoadPcx("ui_art\\supportw.pcx");
LoadBackgroundArt("ui_art\\support.pcx");
} else {
ArtBackgroundWidescreen = LoadPcxAsCl2("ui_art\\creditsw.pcx");
ArtBackgroundWidescreen = LoadPcx("ui_art\\creditsw.pcx");
LoadBackgroundArt("ui_art\\credits.pcx");
}

107
Source/DiabloUI/diabloui.cpp

@ -13,15 +13,15 @@
#include "controls/plrctrls.h"
#include "discord/discord.h"
#include "engine/assets.hpp"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/dx.h"
#include "engine/load_pcx.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "hwcursor.hpp"
#include "utils/display.h"
#include "utils/language.h"
#include "utils/log.hpp"
#include "utils/pcx_to_cl2.hpp"
#include "utils/pcx_to_clx.hpp"
#include "utils/sdl_compat.h"
#include "utils/sdl_geometry.h"
#include "utils/sdl_wrap.h"
@ -44,12 +44,12 @@
namespace devilution {
std::optional<OwnedCelSpriteSheetWithFrameHeight> ArtLogo;
OptionalOwnedClxSpriteList ArtLogo;
std::array<std::optional<OwnedCelSpriteSheetWithFrameHeight>, 3> ArtFocus;
std::array<OptionalOwnedClxSpriteList, 3> ArtFocus;
std::optional<OwnedCelSpriteWithFrameHeight> ArtBackgroundWidescreen;
std::optional<OwnedCelSpriteSheetWithFrameHeight> ArtBackground;
OptionalOwnedClxSpriteList ArtBackgroundWidescreen;
OptionalOwnedClxSpriteList ArtBackground;
Art ArtCursor;
bool textInputActive = true;
@ -57,9 +57,9 @@ std::size_t SelectedItem = 0;
namespace {
std::optional<OwnedCelSpriteSheetWithFrameHeight> ArtHero;
OptionalOwnedClxSpriteList ArtHero;
std::array<uint8_t, enum_size<HeroClass>::value + 1> ArtHeroPortraitOrder;
std::array<std::optional<OwnedCelSpriteWithFrameHeight>, enum_size<HeroClass>::value + 1> ArtHeroOverrides;
std::array<OptionalOwnedClxSpriteList, enum_size<HeroClass>::value + 1> ArtHeroOverrides;
std::size_t SelectedItemMax;
std::size_t ListViewportSize = 1;
@ -527,10 +527,10 @@ bool IsInsideRect(const SDL_Event &event, const SDL_Rect &rect)
void LoadHeros()
{
constexpr unsigned PortraitHeight = 76;
ArtHero = LoadPcxSpriteSheetAsCl2("ui_art\\heros.pcx", -static_cast<int>(PortraitHeight));
ArtHero = LoadPcxSpriteList("ui_art\\heros.pcx", -static_cast<int>(PortraitHeight));
if (!ArtHero)
return;
const uint16_t numPortraits = ArtHero->sheet().numFrames();
const uint16_t numPortraits = ClxSpriteList { *ArtHero }.numSprites();
ArtHeroPortraitOrder = { 0, 1, 2, 2, 1, 0, 3 };
if (numPortraits >= 6) {
@ -552,20 +552,20 @@ void LoadHeros()
SDL_ClearError();
continue;
}
ArtHeroOverrides[i] = PcxToCl2(handle);
ArtHeroOverrides[i] = PcxToClx(handle);
}
}
void LoadUiGFX()
{
if (gbIsHellfire) {
ArtLogo = LoadPcxSpriteSheetAsCl2("ui_art\\hf_logo2.pcx", /*numFrames=*/16, /*transparentColor=*/0);
ArtLogo = LoadPcxSpriteList("ui_art\\hf_logo2.pcx", /*numFrames=*/16, /*transparentColor=*/0);
} else {
ArtLogo = LoadPcxSpriteSheetAsCl2("ui_art\\smlogo.pcx", /*numFrames=*/15, /*transparentColor=*/250);
ArtLogo = LoadPcxSpriteList("ui_art\\smlogo.pcx", /*numFrames=*/15, /*transparentColor=*/250);
}
ArtFocus[FOCUS_SMALL] = LoadPcxSpriteSheetAsCl2("ui_art\\focus16.pcx", /*numFrames=*/8, /*transparentColor=*/250);
ArtFocus[FOCUS_MED] = LoadPcxSpriteSheetAsCl2("ui_art\\focus.pcx", /*numFrames=*/8, /*transparentColor=*/250);
ArtFocus[FOCUS_BIG] = LoadPcxSpriteSheetAsCl2("ui_art\\focus42.pcx", /*numFrames=*/8, /*transparentColor=*/250);
ArtFocus[FOCUS_SMALL] = LoadPcxSpriteList("ui_art\\focus16.pcx", /*numFrames=*/8, /*transparentColor=*/250);
ArtFocus[FOCUS_MED] = LoadPcxSpriteList("ui_art\\focus.pcx", /*numFrames=*/8, /*transparentColor=*/250);
ArtFocus[FOCUS_BIG] = LoadPcxSpriteList("ui_art\\focus42.pcx", /*numFrames=*/8, /*transparentColor=*/250);
LoadMaskedArt("ui_art\\cursor.pcx", &ArtCursor, 1, 0);
@ -581,17 +581,17 @@ void LoadUiGFX()
} // namespace
CelFrameWithHeight UiGetHeroDialogSprite(size_t heroClassIndex)
ClxSprite UiGetHeroDialogSprite(size_t heroClassIndex)
{
return ArtHeroOverrides[heroClassIndex]
? ArtHeroOverrides[heroClassIndex]->sprite()
: ArtHero->sprite(ArtHeroPortraitOrder[heroClassIndex]);
? (*ArtHeroOverrides[heroClassIndex])[0]
: (*ArtHero)[ArtHeroPortraitOrder[heroClassIndex]];
}
void UnloadUiGFX()
{
ArtHero = std::nullopt;
for (std::optional<devilution::OwnedCelSpriteWithFrameHeight> &override : ArtHeroOverrides)
for (OptionalOwnedClxSpriteList &override : ArtHeroOverrides)
override = std::nullopt;
ArtCursor.Unload();
for (auto &art : ArtFocus)
@ -684,7 +684,7 @@ void LoadBackgroundArt(const char *pszFile, int frames)
{
ArtBackground = std::nullopt;
SDL_Color pPal[256];
ArtBackground = LoadPcxSpriteSheetAsCl2(pszFile, static_cast<uint16_t>(frames), /*transparentColor=*/std::nullopt, pPal);
ArtBackground = LoadPcxSpriteList(pszFile, static_cast<uint16_t>(frames), /*transparentColor=*/std::nullopt, pPal);
if (!ArtBackground)
return;
@ -710,17 +710,17 @@ void UiAddBackground(std::vector<std::unique_ptr<UiItemBase>> *vecDialog)
{
const SDL_Rect rect = MakeSdlRect(0, GetUIRectangle().position.y, 0, 0);
if (ArtBackgroundWidescreen) {
vecDialog->push_back(std::make_unique<UiImageCl2>(ArtBackgroundWidescreen->sprite(), rect, UiFlags::AlignCenter));
vecDialog->push_back(std::make_unique<UiImageClx>((*ArtBackgroundWidescreen)[0], rect, UiFlags::AlignCenter));
}
if (ArtBackground) {
vecDialog->push_back(std::make_unique<UiImageCl2>(ArtBackground->sprite(0), rect, UiFlags::AlignCenter));
vecDialog->push_back(std::make_unique<UiImageClx>((*ArtBackground)[0], rect, UiFlags::AlignCenter));
}
}
void UiAddLogo(std::vector<std::unique_ptr<UiItemBase>> *vecDialog)
{
vecDialog->push_back(std::make_unique<UiImageAnimatedCl2>(
ArtLogo->sheet(), MakeSdlRect(0, GetUIRectangle().position.y, 0, 0), UiFlags::AlignCenter));
vecDialog->push_back(std::make_unique<UiImageAnimatedClx>(
*ArtLogo, MakeSdlRect(0, GetUIRectangle().position.y, 0, 0), UiFlags::AlignCenter));
}
void UiFadeIn()
@ -743,19 +743,6 @@ void UiFadeIn()
RenderPresent();
}
void DrawCel(CelSpriteWithFrameHeight sprite, Point p)
{
const Surface &out = Surface(DiabloUiSurface());
Cl2Draw(out, { p.x, static_cast<int>(p.y + sprite.frameHeight) }, sprite.sprite, 0);
}
void DrawAnimatedCel(CelSpriteWithFrameHeight sprite, Point p)
{
const Surface &out = Surface(DiabloUiSurface());
const int frame = GetAnimationFrame(LoadLE32(sprite.sprite.Data()));
Cl2Draw(out, { p.x, static_cast<int>(p.y + sprite.frameHeight) }, sprite.sprite, frame);
}
void DrawSelector(const SDL_Rect &rect)
{
int size = FOCUS_SMALL;
@ -763,20 +750,20 @@ void DrawSelector(const SDL_Rect &rect)
size = FOCUS_BIG;
else if (rect.h >= 30)
size = FOCUS_MED;
CelSpriteSheetWithFrameHeight sheet = ArtFocus[size]->sheet();
const CelFrameWithHeight sprite = sheet.sprite(GetAnimationFrame(sheet.numFrames()));
const ClxSpriteList sprites = *ArtFocus[size];
const ClxSprite sprite = sprites[GetAnimationFrame(sprites.numSprites())];
// TODO FOCUS_MED appares higher than the box
const int y = rect.y + (rect.h - static_cast<int>(sprite.frameHeight)) / 2;
const int y = rect.y + (rect.h - static_cast<int>(sprite.height())) / 2;
const Surface &out = Surface(DiabloUiSurface());
RenderCl2Sprite(out, sprite, { rect.x, y });
RenderCl2Sprite(out, sprite, { rect.x + rect.w - sprite.width(), y });
RenderClxSprite(out, sprite, { rect.x, y });
RenderClxSprite(out, sprite, { rect.x + rect.w - sprite.width(), y });
}
void UiClearScreen()
{
if (!ArtBackground || gnScreenWidth > ArtBackground->sprite(0).width() || gnScreenHeight > ArtBackground->frameHeight)
if (!ArtBackground || gnScreenWidth > (*ArtBackground)[0].width() || gnScreenHeight > (*ArtBackground)[0].height())
SDL_FillRect(DiabloUiSurface(), nullptr, 0x000000);
}
@ -821,24 +808,24 @@ void Render(const UiArtText *uiArtText)
DrawString(out, uiArtText->GetText(), MakeRectangle(uiArtText->m_rect), uiArtText->GetFlags(), uiArtText->GetSpacing(), uiArtText->GetLineHeight());
}
void Render(const UiImageCl2 *uiImage)
void Render(const UiImageClx *uiImage)
{
CelFrameWithHeight sprite = uiImage->sprite();
ClxSprite sprite = uiImage->sprite();
int x = uiImage->m_rect.x;
if (uiImage->isCentered()) {
x += GetCenterOffset(sprite.width(), uiImage->m_rect.w);
}
RenderCl2Sprite(Surface(DiabloUiSurface()), sprite, { x, uiImage->m_rect.y });
RenderClxSprite(Surface(DiabloUiSurface()), sprite, { x, uiImage->m_rect.y });
}
void Render(const UiImageAnimatedCl2 *uiImage)
void Render(const UiImageAnimatedClx *uiImage)
{
CelFrameWithHeight sprite = uiImage->sprite(GetAnimationFrame(uiImage->numFrames()));
ClxSprite sprite = uiImage->sprite(GetAnimationFrame(uiImage->numFrames()));
int x = uiImage->m_rect.x;
if (uiImage->isCentered()) {
x += GetCenterOffset(sprite.width(), uiImage->m_rect.w);
}
RenderCl2Sprite(Surface(DiabloUiSurface()), sprite, { x, uiImage->m_rect.y });
RenderClxSprite(Surface(DiabloUiSurface()), sprite, { x, uiImage->m_rect.y });
}
void Render(const UiArtTextButton *uiButton)
@ -871,12 +858,12 @@ void Render(const UiScrollbar *uiSb)
// Bar background (tiled):
{
const int bgY = uiSb->m_rect.y + uiSb->m_arrow.frameHeight;
const int bgY = uiSb->m_rect.y + uiSb->m_arrow[0].height();
const int bgH = DownArrowRect(uiSb).y - bgY;
const Surface backgroundOut = out.subregion(uiSb->m_rect.x, bgY, SCROLLBAR_BG_WIDTH, bgH);
int y = 0;
while (y < bgH) {
RenderCl2Sprite(backgroundOut, uiSb->m_bg, { 0, y });
RenderClxSprite(backgroundOut, uiSb->m_bg, { 0, y });
y += uiSb->m_bg.height();
}
}
@ -885,18 +872,18 @@ void Render(const UiScrollbar *uiSb)
{
const SDL_Rect rect = UpArrowRect(uiSb);
const auto frame = static_cast<uint16_t>(scrollBarState.upArrowPressed ? ScrollBarArrowFrame_UP_ACTIVE : ScrollBarArrowFrame_UP);
RenderCl2Sprite(out.subregion(rect.x, 0, SCROLLBAR_ARROW_WIDTH, out.h()), uiSb->m_arrow.sprite(frame), { 0, rect.y });
RenderClxSprite(out.subregion(rect.x, 0, SCROLLBAR_ARROW_WIDTH, out.h()), uiSb->m_arrow[frame], { 0, rect.y });
}
{
const SDL_Rect rect = DownArrowRect(uiSb);
const auto frame = static_cast<uint16_t>(scrollBarState.downArrowPressed ? ScrollBarArrowFrame_DOWN_ACTIVE : ScrollBarArrowFrame_DOWN);
RenderCl2Sprite(out.subregion(rect.x, 0, SCROLLBAR_ARROW_WIDTH, out.h()), uiSb->m_arrow.sprite(frame), { 0, rect.y });
RenderClxSprite(out.subregion(rect.x, 0, SCROLLBAR_ARROW_WIDTH, out.h()), uiSb->m_arrow[frame], { 0, rect.y });
}
// Thumb:
if (SelectedItemMax > 0) {
const SDL_Rect rect = ThumbRect(uiSb, SelectedItem, SelectedItemMax + 1);
RenderCl2Sprite(out, uiSb->m_thumb, { rect.x, rect.y });
RenderClxSprite(out, uiSb->m_thumb, { rect.x, rect.y });
}
}
@ -922,11 +909,11 @@ void RenderItem(UiItemBase *item)
case UiType::ArtText:
Render(static_cast<UiArtText *>(item));
break;
case UiType::ImageCl2:
Render(static_cast<UiImageCl2 *>(item));
case UiType::ImageClx:
Render(static_cast<UiImageClx *>(item));
break;
case UiType::ImageAnimatedCl2:
Render(static_cast<UiImageAnimatedCl2 *>(item));
case UiType::ImageAnimatedClx:
Render(static_cast<UiImageAnimatedClx *>(item));
break;
case UiType::ArtTextButton:
Render(static_cast<UiArtTextButton *>(item));

12
Source/DiabloUI/diabloui.h

@ -7,7 +7,7 @@
#include "DiabloUI/art.h"
#include "DiabloUI/ui_item.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "player.h"
#include "utils/display.h"
@ -61,10 +61,10 @@ struct _uiheroinfo {
bool spawned;
};
extern std::optional<OwnedCelSpriteSheetWithFrameHeight> ArtLogo;
extern std::array<std::optional<OwnedCelSpriteSheetWithFrameHeight>, 3> ArtFocus;
extern std::optional<OwnedCelSpriteWithFrameHeight> ArtBackgroundWidescreen;
extern std::optional<OwnedCelSpriteSheetWithFrameHeight> ArtBackground;
extern OptionalOwnedClxSpriteList ArtLogo;
extern std::array<OptionalOwnedClxSpriteList, 3> ArtFocus;
extern OptionalOwnedClxSpriteList ArtBackgroundWidescreen;
extern OptionalOwnedClxSpriteList ArtBackground;
extern Art ArtCursor;
extern bool (*gfnHeroInfo)(bool (*fninfofunc)(_uiheroinfo *));
@ -107,7 +107,7 @@ void UiPollAndRender(std::function<bool(SDL_Event &)> eventHandler = nullptr);
void UiRenderItems(const std::vector<UiItemBase *> &items);
void UiRenderItems(const std::vector<std::unique_ptr<UiItemBase>> &items);
void UiInitList_clear();
CelFrameWithHeight UiGetHeroDialogSprite(size_t heroClassIndex);
ClxSprite UiGetHeroDialogSprite(size_t heroClassIndex);
void mainmenu_restart_repintro();
} // namespace devilution

28
Source/DiabloUI/dialogs.cpp

@ -7,7 +7,7 @@
#include "control.h"
#include "controls/input.h"
#include "controls/menu_controls.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/dx.h"
#include "engine/load_pcx.hpp"
#include "engine/palette.h"
@ -21,7 +21,7 @@ namespace devilution {
namespace {
std::optional<OwnedCelSpriteWithFrameHeight> dialogPcx;
OptionalOwnedClxSpriteList ownedDialogSprite;
std::string wrappedText;
bool dialogEnd;
@ -34,22 +34,22 @@ void DialogActionOK()
std::vector<std::unique_ptr<UiItemBase>> vecNULL;
std::vector<std::unique_ptr<UiItemBase>> vecOkDialog;
std::optional<CelFrameWithHeight> LoadDialogSprite(bool hasCaption, bool isError)
OptionalClxSprite LoadDialogSprite(bool hasCaption, bool isError)
{
constexpr uint8_t TransparentColor = 255;
if (!hasCaption) {
dialogPcx = LoadPcxAsCl2(isError ? "ui_art\\srpopup.pcx" : "ui_art\\spopup.pcx", TransparentColor);
ownedDialogSprite = LoadPcx(isError ? "ui_art\\srpopup.pcx" : "ui_art\\spopup.pcx", TransparentColor);
} else if (isError) {
dialogPcx = LoadPcxAsCl2("ui_art\\dvl_lrpopup.pcx");
if (dialogPcx == std::nullopt) {
dialogPcx = LoadPcxAsCl2("ui_art\\lrpopup.pcx", /*transparentColor=*/255);
ownedDialogSprite = LoadPcx("ui_art\\dvl_lrpopup.pcx", /*transparentColor=*/std::nullopt);
if (!ownedDialogSprite) {
ownedDialogSprite = LoadPcx("ui_art\\lrpopup.pcx", TransparentColor);
}
} else {
dialogPcx = LoadPcxAsCl2("ui_art\\lpopup.pcx", TransparentColor);
ownedDialogSprite = LoadPcx("ui_art\\lpopup.pcx", TransparentColor);
}
if (!dialogPcx)
if (!ownedDialogSprite)
return std::nullopt;
return dialogPcx->sprite();
return (*ownedDialogSprite)[0];
}
bool Init(string_view caption, string_view text, bool error, bool renderBehind)
@ -62,7 +62,7 @@ bool Init(string_view caption, string_view text, bool error, bool renderBehind)
}
LoadDialogButtonGraphics();
std::optional<CelFrameWithHeight> dialogSprite = LoadDialogSprite(!caption.empty(), error);
OptionalClxSprite dialogSprite = LoadDialogSprite(!caption.empty(), error);
if (!dialogSprite)
return false;
@ -74,7 +74,7 @@ bool Init(string_view caption, string_view text, bool error, bool renderBehind)
const Point uiPosition = GetUIRectangle().position;
if (caption.empty()) {
SDL_Rect rect1 = MakeSdlRect(uiPosition.x + 180, uiPosition.y + 168, dialogSprite->width(), dialogSprite->height());
vecOkDialog.push_back(std::make_unique<UiImageCl2>(*dialogSprite, rect1));
vecOkDialog.push_back(std::make_unique<UiImageClx>(*dialogSprite, rect1));
SDL_Rect rect2 = MakeSdlRect(uiPosition.x + 200, uiPosition.y + 211, textWidth, 80);
vecOkDialog.push_back(std::make_unique<UiText>(wrappedText, rect2, UiFlags::AlignCenter | UiFlags::ColorDialogWhite));
@ -82,7 +82,7 @@ bool Init(string_view caption, string_view text, bool error, bool renderBehind)
vecOkDialog.push_back(std::make_unique<UiButton>(_("OK"), &DialogActionOK, rect3));
} else {
SDL_Rect rect1 = MakeSdlRect(uiPosition.x + 127, uiPosition.y + 100, dialogSprite->width(), dialogSprite->height());
vecOkDialog.push_back(std::make_unique<UiImageCl2>(*dialogSprite, rect1));
vecOkDialog.push_back(std::make_unique<UiImageClx>(*dialogSprite, rect1));
SDL_Rect rect2 = MakeSdlRect(uiPosition.x + 147, uiPosition.y + 110, textWidth, 20);
vecOkDialog.push_back(std::make_unique<UiText>(caption, rect2, UiFlags::AlignCenter | UiFlags::ColorDialogYellow));
@ -98,7 +98,7 @@ bool Init(string_view caption, string_view text, bool error, bool renderBehind)
void Deinit()
{
dialogPcx = std::nullopt;
ownedDialogSprite = std::nullopt;
vecOkDialog.clear();
ArtBackground = std::nullopt;
FreeDialogButtonGraphics();

2
Source/DiabloUI/mainmenu.cpp

@ -45,7 +45,7 @@ void MainmenuLoad(const char *name)
if (!gbIsSpawn || gbIsHellfire) {
if (gbIsHellfire)
ArtBackgroundWidescreen = LoadPcxAsCl2("ui_art\\mainmenuw.pcx");
ArtBackgroundWidescreen = LoadPcx("ui_art\\mainmenuw.pcx");
LoadBackgroundArt("ui_art\\mainmenu.pcx");
} else {
LoadBackgroundArt("ui_art\\swmmenu.pcx");

24
Source/DiabloUI/progress.cpp

@ -6,20 +6,20 @@
#include "control.h"
#include "controls/input.h"
#include "controls/menu_controls.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/dx.h"
#include "engine/load_pcx.hpp"
#include "engine/palette.h"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "hwcursor.hpp"
#include "utils/display.h"
#include "utils/language.h"
namespace devilution {
namespace {
std::optional<OwnedCelSpriteWithFrameHeight> ArtPopupSm;
std::optional<OwnedCelSpriteWithFrameHeight> ArtProgBG;
std::optional<OwnedCelSpriteWithFrameHeight> ProgFil;
OptionalOwnedClxSpriteList ArtPopupSm;
OptionalOwnedClxSpriteList ArtProgBG;
OptionalOwnedClxSpriteList ProgFil;
std::vector<std::unique_ptr<UiItemBase>> vecProgress;
bool endMenu;
@ -31,14 +31,14 @@ void DialogActionCancel()
void ProgressLoadBackground()
{
UiLoadBlackBackground();
ArtPopupSm = LoadPcxAsCl2("ui_art\\spopup.pcx");
ArtProgBG = LoadPcxAsCl2("ui_art\\prog_bg.pcx");
ArtPopupSm = LoadPcx("ui_art\\spopup.pcx");
ArtProgBG = LoadPcx("ui_art\\prog_bg.pcx");
}
void ProgressLoadForeground()
{
LoadDialogButtonGraphics();
ProgFil = LoadPcxAsCl2("ui_art\\prog_fil.pcx");
ProgFil = LoadPcx("ui_art\\prog_fil.pcx");
const Point uiPosition = GetUIRectangle().position;
SDL_Rect rect3 = { (Sint16)(uiPosition.x + 265), (Sint16)(uiPosition.y + 267), DialogButtonWidth, DialogButtonHeight };
@ -70,8 +70,8 @@ void ProgressRenderBackground()
const Surface &out = Surface(DiabloUiSurface());
const Point position = GetPosition();
RenderCl2Sprite(out.subregion(position.x, position.y, 280, 140), ArtPopupSm->sprite(), { 0, 0 });
RenderCl2Sprite(out.subregion(GetCenterOffset(227), 0, 227, out.h()), ArtProgBG->sprite(), { 0, position.y + 52 });
RenderClxSprite(out.subregion(position.x, position.y, 280, 140), (*ArtPopupSm)[0], { 0, 0 });
RenderClxSprite(out.subregion(GetCenterOffset(227), 0, 227, out.h()), (*ArtProgBG)[0], { 0, position.y + 52 });
}
void ProgressRenderForeground(int progress)
@ -81,10 +81,10 @@ void ProgressRenderForeground(int progress)
if (progress != 0) {
const int x = GetCenterOffset(227);
const int w = 227 * progress / 100;
RenderCl2Sprite(out.subregion(x, 0, w, out.h()), ProgFil->sprite(), { 0, position.y + 52 });
RenderClxSprite(out.subregion(x, 0, w, out.h()), (*ProgFil)[0], { 0, position.y + 52 });
}
// Not rendering an actual button, only the top 2 rows of its graphics.
RenderCl2Sprite(
RenderClxSprite(
out.subregion(GetCenterOffset(110), position.y + 99, DialogButtonWidth, 2),
ButtonSprite(/*pressed=*/false), { 0, 0 });
}

12
Source/DiabloUI/scrollbar.cpp

@ -4,15 +4,15 @@
namespace devilution {
std::optional<OwnedCelSpriteWithFrameHeight> ArtScrollBarBackground;
std::optional<OwnedCelSpriteWithFrameHeight> ArtScrollBarThumb;
std::optional<OwnedCelSpriteSheetWithFrameHeight> ArtScrollBarArrow;
OptionalOwnedClxSpriteList ArtScrollBarBackground;
OptionalOwnedClxSpriteList ArtScrollBarThumb;
OptionalOwnedClxSpriteList ArtScrollBarArrow;
void LoadScrollBar()
{
ArtScrollBarBackground = LoadPcxAsCl2("ui_art\\sb_bg.pcx");
ArtScrollBarThumb = LoadPcxAsCl2("ui_art\\sb_thumb.pcx");
ArtScrollBarArrow = LoadPcxSpriteSheetAsCl2("ui_art\\sb_arrow.pcx", 4);
ArtScrollBarBackground = LoadPcx("ui_art\\sb_bg.pcx");
ArtScrollBarThumb = LoadPcx("ui_art\\sb_thumb.pcx");
ArtScrollBarArrow = LoadPcxSpriteList("ui_art\\sb_arrow.pcx", 4);
}
void UnloadScrollBar()

20
Source/DiabloUI/scrollbar.h

@ -3,13 +3,13 @@
#include <cstdint>
#include "DiabloUI/ui_item.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
namespace devilution {
extern std::optional<OwnedCelSpriteWithFrameHeight> ArtScrollBarBackground;
extern std::optional<OwnedCelSpriteWithFrameHeight> ArtScrollBarThumb;
extern std::optional<OwnedCelSpriteSheetWithFrameHeight> ArtScrollBarArrow;
extern OptionalOwnedClxSpriteList ArtScrollBarBackground;
extern OptionalOwnedClxSpriteList ArtScrollBarThumb;
extern OptionalOwnedClxSpriteList ArtScrollBarArrow;
const Uint16 SCROLLBAR_BG_WIDTH = 25;
enum ScrollBarArrowFrame : uint8_t {
@ -27,7 +27,7 @@ inline SDL_Rect UpArrowRect(const UiScrollbar *sb)
Tmp.x = sb->m_rect.x;
Tmp.y = sb->m_rect.y;
Tmp.w = SCROLLBAR_ARROW_WIDTH;
Tmp.h = static_cast<Uint16>(sb->m_arrow.frameHeight);
Tmp.h = static_cast<Uint16>(sb->m_arrow[0].height());
return Tmp;
}
@ -36,23 +36,23 @@ inline SDL_Rect DownArrowRect(const UiScrollbar *sb)
{
SDL_Rect Tmp;
Tmp.x = sb->m_rect.x;
Tmp.y = static_cast<Sint16>(sb->m_rect.y + sb->m_rect.h - sb->m_arrow.frameHeight);
Tmp.y = static_cast<Sint16>(sb->m_rect.y + sb->m_rect.h - sb->m_arrow[0].height());
Tmp.w = SCROLLBAR_ARROW_WIDTH,
Tmp.h = static_cast<Uint16>(sb->m_arrow.frameHeight);
Tmp.h = static_cast<Uint16>(sb->m_arrow[0].height());
return Tmp;
}
inline Uint16 BarHeight(const UiScrollbar *sb)
{
return sb->m_rect.h - 2 * sb->m_arrow.frameHeight;
return sb->m_rect.h - 2 * sb->m_arrow[0].height();
}
inline SDL_Rect BarRect(const UiScrollbar *sb)
{
SDL_Rect Tmp;
Tmp.x = sb->m_rect.x;
Tmp.y = static_cast<Sint16>(sb->m_rect.y + sb->m_arrow.frameHeight);
Tmp.y = static_cast<Sint16>(sb->m_rect.y + sb->m_arrow[0].height());
Tmp.w = SCROLLBAR_ARROW_WIDTH,
Tmp.h = BarHeight(sb);
@ -67,7 +67,7 @@ inline SDL_Rect ThumbRect(const UiScrollbar *sb, std::size_t selected_index, std
SDL_Rect Tmp;
Tmp.x = static_cast<Sint16>(sb->m_rect.x + THUMB_OFFSET_X);
Tmp.y = static_cast<Sint16>(sb->m_rect.y + sb->m_arrow.frameHeight + thumb_y);
Tmp.y = static_cast<Sint16>(sb->m_rect.y + sb->m_arrow[0].height() + thumb_y);
Tmp.w = static_cast<Uint16>(sb->m_rect.w - THUMB_OFFSET_X);
Tmp.h = static_cast<Uint16>(sb->m_thumb.height());

2
Source/DiabloUI/selgame.cpp

@ -126,7 +126,7 @@ void UiInitGameSelectionList(string_view search)
const Point uiPosition = GetUIRectangle().position;
SDL_Rect rectScrollbar = { (Sint16)(uiPosition.x + 590), (Sint16)(uiPosition.y + 244), 25, 178 };
vecSelGameDialog.push_back(std::make_unique<UiScrollbar>(ArtScrollBarBackground->sprite(), ArtScrollBarThumb->sprite(), ArtScrollBarArrow->sheet(), rectScrollbar));
vecSelGameDialog.push_back(std::make_unique<UiScrollbar>((*ArtScrollBarBackground)[0], (*ArtScrollBarThumb)[0], *ArtScrollBarArrow, rectScrollbar));
SDL_Rect rect1 = { (Sint16)(uiPosition.x + 24), (Sint16)(uiPosition.y + 161), 590, 35 };
vecSelGameDialog.push_back(std::make_unique<UiArtText>(_(ConnectionNames[provider]).data(), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3));

11
Source/DiabloUI/selhero.cpp

@ -45,7 +45,7 @@ std::vector<std::unique_ptr<UiItemBase>> vecSelHeroDialog;
std::vector<std::unique_ptr<UiListItem>> vecSelHeroDlgItems;
std::vector<std::unique_ptr<UiItemBase>> vecSelDlgItems;
UiImageCl2 *SELHERO_DIALOG_HERO_IMG;
UiImageClx *SELHERO_DIALOG_HERO_IMG;
void SelheroListFocus(int value);
void SelheroListSelect(int value);
@ -236,10 +236,7 @@ void AddSelHeroBackground()
{
LoadBackgroundArt("ui_art\\selhero.pcx");
vecSelHeroDialog.insert(vecSelHeroDialog.begin(),
std::make_unique<UiImageCl2>(
ArtBackground->sprite(0),
MakeSdlRect(0, GetUIRectangle().position.y, 0, 0),
UiFlags::AlignCenter));
std::make_unique<UiImageClx>((*ArtBackground)[0], MakeSdlRect(0, GetUIRectangle().position.y, 0, 0), UiFlags::AlignCenter));
}
void SelheroClassSelectorSelect(int value)
@ -450,7 +447,7 @@ void selhero_Init()
vecSelHeroDialog.push_back(std::make_unique<UiArtText>(&title, rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3));
SDL_Rect rect2 = { (Sint16)(uiPosition.x + 30), (Sint16)(uiPosition.y + 211), 180, 76 };
auto heroImg = std::make_unique<UiImageCl2>(UiGetHeroDialogSprite(0), rect2, UiFlags::None);
auto heroImg = std::make_unique<UiImageClx>(UiGetHeroDialogSprite(0), rect2, UiFlags::None);
SELHERO_DIALOG_HERO_IMG = heroImg.get();
vecSelHeroDialog.push_back(std::move(heroImg));
@ -511,7 +508,7 @@ void selhero_List_Init()
vecSelDlgItems.push_back(std::make_unique<UiList>(vecSelHeroDlgItems, 6, uiPosition.x + 265, (uiPosition.y + 256), 320, 26, UiFlags::AlignCenter | UiFlags::FontSize24 | UiFlags::ColorUiGold));
SDL_Rect rect2 = { (Sint16)(uiPosition.x + 585), (Sint16)(uiPosition.y + 244), 25, 178 };
vecSelDlgItems.push_back(std::make_unique<UiScrollbar>(ArtScrollBarBackground->sprite(), ArtScrollBarThumb->sprite(), ArtScrollBarArrow->sheet(), rect2));
vecSelDlgItems.push_back(std::make_unique<UiScrollbar>((*ArtScrollBarBackground)[0], (*ArtScrollBarThumb)[0], *ArtScrollBarArrow, rect2));
SDL_Rect rect3 = { (Sint16)(uiPosition.x + 239), (Sint16)(uiPosition.y + 429), 120, 35 };
vecSelDlgItems.push_back(std::make_unique<UiArtTextButton>(_("OK"), &UiFocusNavigationSelect, rect3, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiGold));

2
Source/DiabloUI/selstart.cpp

@ -32,7 +32,7 @@ void EscPressed()
void UiSelStartUpGameOption()
{
ArtBackgroundWidescreen = LoadPcxAsCl2("ui_art\\mainmenuw.pcx");
ArtBackgroundWidescreen = LoadPcx("ui_art\\mainmenuw.pcx");
LoadBackgroundArt("ui_art\\mainmenu.pcx");
UiAddBackground(&vecDialog);
UiAddLogo(&vecDialog);

2
Source/DiabloUI/settingsmenu.cpp

@ -263,7 +263,7 @@ void UiSettingsMenu()
string_view titleText = shownMenu == ShownMenuType::Settings ? _("Settings") : selectedOption->GetName();
vecDialog.push_back(std::make_unique<UiArtText>(titleText.data(), MakeSdlRect(uiRectangle.position.x, uiRectangle.position.y + 161, uiRectangle.size.width, 35), UiFlags::FontSize30 | UiFlags::ColorUiSilver | UiFlags::AlignCenter, 8));
vecDialog.push_back(std::make_unique<UiScrollbar>(ArtScrollBarBackground->sprite(), ArtScrollBarThumb->sprite(), ArtScrollBarArrow->sheet(), MakeSdlRect(rectList.position.x + rectList.size.width + 5, rectList.position.y, 25, rectList.size.height)));
vecDialog.push_back(std::make_unique<UiScrollbar>((*ArtScrollBarBackground)[0], (*ArtScrollBarThumb)[0], *ArtScrollBarArrow, MakeSdlRect(rectList.position.x + rectList.size.width + 5, rectList.position.y, 25, rectList.size.height)));
vecDialog.push_back(std::make_unique<UiArtText>(optionDescription, MakeSdlRect(rectDescription), UiFlags::FontSize12 | UiFlags::ColorUiSilverDark | UiFlags::AlignCenter, 1, IsSmallFontTall() ? 22 : 18));
size_t itemToSelect = 1;

14
Source/DiabloUI/title.cpp

@ -11,7 +11,7 @@
namespace devilution {
namespace {
std::optional<OwnedCelSpriteSheetWithFrameHeight> DiabloTitleLogo;
OptionalOwnedClxSpriteList DiabloTitleLogo;
std::vector<std::unique_ptr<UiItemBase>> vecTitleScreen;
@ -19,10 +19,10 @@ void TitleLoad()
{
if (gbIsHellfire) {
LoadBackgroundArt("ui_art\\hf_logo1.pcx", 16);
ArtBackgroundWidescreen = LoadPcxAsCl2("ui_art\\hf_titlew.pcx");
ArtBackgroundWidescreen = LoadPcx("ui_art\\hf_titlew.pcx");
} else {
LoadBackgroundArt("ui_art\\title.pcx");
DiabloTitleLogo = LoadPcxSpriteSheetAsCl2("ui_art\\logo.pcx", /*numFrames=*/15, /*transparentColor=*/250);
DiabloTitleLogo = LoadPcxSpriteList("ui_art\\logo.pcx", /*numFrames=*/15, /*transparentColor=*/250);
}
}
@ -44,13 +44,13 @@ void UiTitleDialog()
if (gbIsHellfire) {
SDL_Rect rect = MakeSdlRect(0, uiPosition.y, 0, 0);
if (ArtBackgroundWidescreen)
vecTitleScreen.push_back(std::make_unique<UiImageCl2>(ArtBackgroundWidescreen->sprite(), rect, UiFlags::AlignCenter));
vecTitleScreen.push_back(std::make_unique<UiImageAnimatedCl2>(ArtBackground->sheet(), rect, UiFlags::AlignCenter));
vecTitleScreen.push_back(std::make_unique<UiImageClx>((*ArtBackgroundWidescreen)[0], rect, UiFlags::AlignCenter));
vecTitleScreen.push_back(std::make_unique<UiImageAnimatedClx>(*ArtBackground, rect, UiFlags::AlignCenter));
} else {
UiAddBackground(&vecTitleScreen);
vecTitleScreen.push_back(std::make_unique<UiImageAnimatedCl2>(
DiabloTitleLogo->sheet(), MakeSdlRect(0, uiPosition.y + 182, 0, 0), UiFlags::AlignCenter));
vecTitleScreen.push_back(std::make_unique<UiImageAnimatedClx>(
*DiabloTitleLogo, MakeSdlRect(0, uiPosition.y + 182, 0, 0), UiFlags::AlignCenter));
SDL_Rect rect = MakeSdlRect(uiPosition.x, uiPosition.y + 410, 640, 26);
vecTitleScreen.push_back(std::make_unique<UiArtText>(_("Copyright © 1996-2001 Blizzard Entertainment").data(), rect, UiFlags::AlignCenter | UiFlags::FontSize24 | UiFlags::ColorUiSilver));

42
Source/DiabloUI/ui_item.h

@ -7,7 +7,7 @@
#include "DiabloUI/art.h"
#include "DiabloUI/ui_flags.hpp"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/render/text_render.hpp"
#include "utils/enum_traits.h"
#include "utils/stubs.h"
@ -18,8 +18,8 @@ enum class UiType {
Text,
ArtText,
ArtTextButton,
ImageCl2,
ImageAnimatedCl2,
ImageClx,
ImageAnimatedClx,
Button,
List,
Scrollbar,
@ -89,10 +89,10 @@ private:
};
//=============================================================================
class UiImageCl2 : public UiItemBase {
class UiImageClx : public UiItemBase {
public:
UiImageCl2(CelFrameWithHeight sprite, SDL_Rect rect, UiFlags flags = UiFlags::None)
: UiItemBase(UiType::ImageCl2, rect, flags)
UiImageClx(ClxSprite sprite, SDL_Rect rect, UiFlags flags = UiFlags::None)
: UiItemBase(UiType::ImageClx, rect, flags)
, sprite_(sprite)
{
}
@ -102,26 +102,26 @@ public:
return HasAnyOf(GetFlags(), UiFlags::AlignCenter);
}
[[nodiscard]] CelFrameWithHeight sprite() const
[[nodiscard]] ClxSprite sprite() const
{
return sprite_;
}
void setSprite(CelFrameWithHeight sprite)
void setSprite(ClxSprite sprite)
{
sprite_ = sprite;
}
private:
CelFrameWithHeight sprite_;
ClxSprite sprite_;
};
//=============================================================================
class UiImageAnimatedCl2 : public UiItemBase {
class UiImageAnimatedClx : public UiItemBase {
public:
UiImageAnimatedCl2(CelSpriteSheetWithFrameHeight sheet, SDL_Rect rect, UiFlags flags = UiFlags::None)
: UiItemBase(UiType::ImageAnimatedCl2, rect, flags)
, sheet_(sheet)
UiImageAnimatedClx(ClxSpriteList list, SDL_Rect rect, UiFlags flags = UiFlags::None)
: UiItemBase(UiType::ImageAnimatedClx, rect, flags)
, list_(list)
{
}
@ -130,18 +130,18 @@ public:
return HasAnyOf(GetFlags(), UiFlags::AlignCenter);
}
[[nodiscard]] CelFrameWithHeight sprite(uint16_t frame) const
[[nodiscard]] ClxSprite sprite(uint16_t frame) const
{
return sheet_.sprite(frame);
return list_[frame];
}
[[nodiscard]] uint16_t numFrames() const
{
return sheet_.numFrames();
return list_.numSprites();
}
private:
CelSpriteSheetWithFrameHeight sheet_;
ClxSpriteList list_;
};
//=============================================================================
@ -208,7 +208,7 @@ private:
class UiScrollbar : public UiItemBase {
public:
UiScrollbar(CelFrameWithHeight bg, CelFrameWithHeight thumb, CelSpriteSheetWithFrameHeight arrow, SDL_Rect rect, UiFlags flags = UiFlags::None)
UiScrollbar(ClxSprite bg, ClxSprite thumb, ClxSpriteList arrow, SDL_Rect rect, UiFlags flags = UiFlags::None)
: UiItemBase(UiType::Scrollbar, rect, flags)
, m_bg(bg)
, m_thumb(thumb)
@ -217,9 +217,9 @@ public:
}
// private:
CelFrameWithHeight m_bg;
CelFrameWithHeight m_thumb;
CelSpriteSheetWithFrameHeight m_arrow;
ClxSprite m_bg;
ClxSprite m_thumb;
ClxSpriteList m_arrow;
};
//=============================================================================

63
Source/control.cpp

@ -18,9 +18,9 @@
#include "controls/modifier_hints.h"
#include "controls/plrctrls.h"
#include "cursor.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/load_cel.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/text_render.hpp"
#include "engine/trn.hpp"
#include "error.h"
@ -87,7 +87,7 @@ Rectangle MainPanel;
Rectangle LeftPanel;
Rectangle RightPanel;
std::optional<OwnedSurface> pBtmBuff;
OptionalOwnedCelSprite pGBoxBuff;
OptionalOwnedClxSpriteList pGBoxBuff;
const Rectangle &GetMainPanel()
{
@ -137,10 +137,10 @@ namespace {
std::optional<OwnedSurface> pLifeBuff;
std::optional<OwnedSurface> pManaBuff;
OptionalOwnedCelSprite talkButtons;
OptionalOwnedCelSprite pDurIcons;
OptionalOwnedCelSprite multiButtons;
OptionalOwnedCelSprite pPanelButtons;
OptionalOwnedClxSpriteList talkButtons;
OptionalOwnedClxSpriteList pDurIcons;
OptionalOwnedClxSpriteList multiButtons;
OptionalOwnedClxSpriteList pPanelButtons;
bool PanelButtons[8];
int PanelButtonIndex;
@ -316,7 +316,7 @@ int DrawDurIcon4Item(const Surface &out, Item &pItem, int x, int c)
}
if (pItem._iDurability > 2)
c += 8;
Cl2Draw(out, { x, -17 + GetMainPanel().position.y }, CelSprite { *pDurIcons }, c);
ClxDraw(out, { x, -17 + GetMainPanel().position.y }, (*pDurIcons)[c]);
return x - 32 - 8;
}
@ -552,25 +552,25 @@ void InitControlPan()
LoadCharPanel();
LoadSpellIcons();
{
const OwnedCelSprite sprite = LoadCelAsCl2("CtrlPan\\Panel8.CEL", GetMainPanel().size.width);
Cl2Draw(*pBtmBuff, { 0, (GetMainPanel().size.height + 16) - 1 }, CelSprite { sprite }, 0);
const OwnedClxSpriteList sprite = LoadCel("CtrlPan\\Panel8.CEL", GetMainPanel().size.width);
ClxDraw(*pBtmBuff, { 0, (GetMainPanel().size.height + 16) - 1 }, sprite[0]);
}
{
const Point bulbsPosition { 0, 87 };
const OwnedCelSprite statusPanel = LoadCelAsCl2("CtrlPan\\P8Bulbs.CEL", 88);
Cl2Draw(*pLifeBuff, bulbsPosition, CelSprite { statusPanel }, 0);
Cl2Draw(*pManaBuff, bulbsPosition, CelSprite { statusPanel }, 1);
const OwnedClxSpriteList statusPanel = LoadCel("CtrlPan\\P8Bulbs.CEL", 88);
ClxDraw(*pLifeBuff, bulbsPosition, statusPanel[0]);
ClxDraw(*pManaBuff, bulbsPosition, statusPanel[1]);
}
}
talkflag = false;
if (IsChatAvailable()) {
if (!HeadlessMode) {
{
const OwnedCelSprite sprite = LoadCelAsCl2("CtrlPan\\TalkPanl.CEL", GetMainPanel().size.width);
Cl2Draw(*pBtmBuff, { 0, (GetMainPanel().size.height + 16) * 2 - 1 }, CelSprite { sprite }, 0);
const OwnedClxSpriteList sprite = LoadCel("CtrlPan\\TalkPanl.CEL", GetMainPanel().size.width);
ClxDraw(*pBtmBuff, { 0, (GetMainPanel().size.height + 16) * 2 - 1 }, sprite[0]);
}
multiButtons = LoadCelAsCl2("CtrlPan\\P8But2.CEL", 33);
talkButtons = LoadCelAsCl2("CtrlPan\\TalkButt.CEL", 61);
multiButtons = LoadCel("CtrlPan\\P8But2.CEL", 33);
talkButtons = LoadCel("CtrlPan\\TalkButt.CEL", 61);
}
sgbPlrTalkTbl = 0;
TalkMessage[0] = '\0';
@ -583,10 +583,10 @@ void InitControlPan()
lvlbtndown = false;
if (!HeadlessMode) {
LoadMainPanel();
pPanelButtons = LoadCelAsCl2("CtrlPan\\Panel8bu.CEL", 71);
pPanelButtons = LoadCel("CtrlPan\\Panel8bu.CEL", 71);
static const uint16_t CharButtonsFrameWidths[9] { 95, 41, 41, 41, 41, 41, 41, 41, 41 };
pChrButtons = LoadCelAsCl2("Data\\CharBut.CEL", CharButtonsFrameWidths);
pChrButtons = LoadCel("Data\\CharBut.CEL", CharButtonsFrameWidths);
}
ClearPanBtn();
if (!IsChatAvailable())
@ -594,7 +594,7 @@ void InitControlPan()
else
PanelButtonIndex = 8;
if (!HeadlessMode)
pDurIcons = LoadCelAsCl2("Items\\DurIcons.CEL", 32);
pDurIcons = LoadCel("Items\\DurIcons.CEL", 32);
for (bool &buttonEnabled : chrbtn)
buttonEnabled = false;
chrbtnactive = false;
@ -609,8 +609,8 @@ void InitControlPan()
if (!HeadlessMode) {
InitSpellBook();
pQLogCel = LoadCelAsCl2("Data\\Quest.CEL", static_cast<uint16_t>(SidePanelSize.width));
pGBoxBuff = LoadCelAsCl2("CtrlPan\\Golddrop.cel", 261);
pQLogCel = LoadCel("Data\\Quest.CEL", static_cast<uint16_t>(SidePanelSize.width));
pGBoxBuff = LoadCel("CtrlPan\\Golddrop.cel", 261);
}
CloseGoldDrop();
dropGoldValue = 0;
@ -637,17 +637,17 @@ void DrawCtrlBtns(const Surface &out)
DrawPanelBox(out, MakeSdlRect(PanBtnPos[i].x, PanBtnPos[i].y + 16, 71, 20), mainPanelPosition + Displacement { PanBtnPos[i].x, PanBtnPos[i].y });
} else {
Point position = mainPanelPosition + Displacement { PanBtnPos[i].x, PanBtnPos[i].y + 18 };
Cl2Draw(out, position, CelSprite { *pPanelButtons }, i);
ClxDraw(out, position, (*pPanelButtons)[i]);
DrawArt(out, position + Displacement { 4, -18 }, &PanelButtonDown, i);
}
}
if (PanelButtonIndex == 8) {
CelSprite sprite { *multiButtons };
Cl2Draw(out, mainPanelPosition + Displacement { 87, 122 }, sprite, PanelButtons[6] ? 1 : 0);
ClxDraw(out, mainPanelPosition + Displacement { 87, 122 }, (*multiButtons)[PanelButtons[6] ? 1 : 0]);
if (MyPlayer->friendlyMode)
Cl2Draw(out, mainPanelPosition + Displacement { 527, 122 }, sprite, PanelButtons[7] ? 3 : 2);
ClxDraw(out, mainPanelPosition + Displacement { 527, 122 }, (*multiButtons)[PanelButtons[7] ? 3 : 2]);
else
Cl2Draw(out, mainPanelPosition + Displacement { 527, 122 }, sprite, PanelButtons[7] ? 5 : 4);
ClxDraw(out, mainPanelPosition + Displacement { 527, 122 }, (*multiButtons)[PanelButtons[7] ? 5 : 4]);
}
}
@ -972,7 +972,7 @@ void DrawLevelUpIcon(const Surface &out)
if (IsLevelUpButtonVisible()) {
int nCel = lvlbtndown ? 2 : 1;
DrawString(out, _("Level Up"), { GetMainPanel().position + Displacement { 0, -62 }, { 120, 0 } }, UiFlags::ColorWhite | UiFlags::AlignCenter);
Cl2Draw(out, GetMainPanel().position + Displacement { 40, -17 }, CelSprite { *pChrButtons }, nCel);
ClxDraw(out, GetMainPanel().position + Displacement { 40, -17 }, (*pChrButtons)[nCel]);
}
}
@ -1074,7 +1074,7 @@ void DrawGoldSplit(const Surface &out, int amount)
{
const int dialogX = 30;
Cl2Draw(out, GetPanelPosition(UiPanels::Inventory, { dialogX, 178 }), CelSprite { *pGBoxBuff }, 0);
ClxDraw(out, GetPanelPosition(UiPanels::Inventory, { dialogX, 178 }), (*pGBoxBuff)[0]);
const std::string description = fmt::format(
fmt::runtime(ngettext(
@ -1162,15 +1162,14 @@ void DrawTalkPan(const Surface &out)
const Point talkPanPosition = mainPanelPosition + Displacement { 172, 84 + 18 * talkBtn };
if (WhisperList[i]) {
if (TalkButtonsDown[talkBtn]) {
int nCel = talkBtn != 0 ? 3 : 2;
Cl2Draw(out, talkPanPosition, CelSprite { *talkButtons }, nCel);
ClxDraw(out, talkPanPosition, (*talkButtons)[talkBtn != 0 ? 3 : 2]);
DrawArt(out, talkPanPosition + Displacement { 4, -15 }, &TalkButton, 2);
}
} else {
int nCel = talkBtn != 0 ? 1 : 0;
if (TalkButtonsDown[talkBtn])
nCel += 4;
Cl2Draw(out, talkPanPosition, CelSprite { *talkButtons }, nCel);
ClxDraw(out, talkPanPosition, (*talkButtons)[nCel]);
DrawArt(out, talkPanPosition + Displacement { 4, -15 }, &TalkButton, TalkButtonsDown[talkBtn] ? 1 : 0);
}
if (player.plractive) {

2
Source/control.h

@ -58,7 +58,7 @@ const Rectangle &GetRightPanel();
bool IsLeftPanelOpen();
bool IsRightPanelOpen();
extern std::optional<OwnedSurface> pBtmBuff;
extern OptionalOwnedCelSprite pGBoxBuff;
extern OptionalOwnedClxSpriteList pGBoxBuff;
extern SDL_Rect PanBtnPos[8];
void CalculatePanelAreas();

2
Source/controls/modifier_hints.cpp

@ -16,7 +16,7 @@
namespace devilution {
extern OptionalOwnedCelSprite pSBkIconCels;
extern OptionalOwnedClxSpriteList pSBkIconCels;
namespace {

6
Source/controls/touch/renderers.cpp

@ -5,7 +5,7 @@
#include "diablo.h"
#include "doom.h"
#include "engine.h"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "init.h"
#include "inv.h"
#include "levels/gendung.h"
@ -140,10 +140,8 @@ void LoadPotionArt(Art *potionArt, SDL_Renderer *renderer)
Point position { 0, 0 };
for (item_cursor_graphic graphic : potionGraphics) {
const int cursorID = static_cast<int>(CURSOR_FIRSTITEM) + graphic;
const int frame = GetInvItemFrame(cursorID);
const CelSprite potionSprite { GetInvItemSprite(cursorID) };
position.y += potionSize.height;
Cl2Draw(Surface(surface.get()), position, potionSprite, frame);
ClxDraw(Surface(surface.get()), position, GetInvItemSprite(cursorID));
}
potionArt->logical_width = potionSize.width;

36
Source/cursor.cpp

@ -15,7 +15,7 @@
#include "engine.h"
#include "engine/load_cel.hpp"
#include "engine/point.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/trn.hpp"
#include "hwcursor.hpp"
#include "inv.h"
@ -33,8 +33,8 @@
namespace devilution {
namespace {
/** Cursor images CEL */
OptionalOwnedCelSprite pCursCels;
OptionalOwnedCelSprite pCursCels2;
OptionalOwnedClxSpriteList pCursCels;
OptionalOwnedClxSpriteList pCursCels2;
/** Maps from objcurs.cel frame number to frame width. */
const uint16_t InvItemWidth1[] = {
@ -133,9 +133,9 @@ int pcurs;
void InitCursor()
{
assert(!pCursCels);
pCursCels = LoadCelAsCl2("Data\\Inv\\Objcurs.CEL", InvItemWidth1);
pCursCels = LoadCel("Data\\Inv\\Objcurs.CEL", InvItemWidth1);
if (gbIsHellfire)
pCursCels2 = LoadCelAsCl2("Data\\Inv\\Objcurs2.CEL", InvItemWidth2);
pCursCels2 = LoadCel("Data\\Inv\\Objcurs2.CEL", InvItemWidth2);
ClearCursor();
}
@ -146,14 +146,11 @@ void FreeCursor()
ClearCursor();
}
const OwnedCelSprite &GetInvItemSprite(int cursId)
ClxSprite GetInvItemSprite(int cursId)
{
return cursId <= InvItems1Size ? *pCursCels : *pCursCels2;
}
int GetInvItemFrame(int cursId)
{
return cursId <= InvItems1Size ? cursId - 1 : cursId - InvItems1Size - 1;
if (cursId <= InvItems1Size)
return (*pCursCels)[cursId - 1];
return (*pCursCels2)[cursId - InvItems1Size - 1];
}
Size GetInvItemSize(int cursId)
@ -164,13 +161,13 @@ Size GetInvItemSize(int cursId)
return { InvItemWidth1[i], InvItemHeight1[i] };
}
void DrawItem(const Item &item, const Surface &out, Point position, CelSprite cel, int frame)
void DrawItem(const Item &item, const Surface &out, Point position, ClxSprite clx)
{
const bool usable = item._iStatFlag;
if (usable) {
Cl2Draw(out, position, cel, frame);
ClxDraw(out, position, clx);
} else {
Cl2DrawTRN(out, position, cel, frame, GetInfravisionTRN());
ClxDrawTRN(out, position, clx, GetInfravisionTRN());
}
}
@ -209,14 +206,13 @@ void NewCursor(int cursId)
void DrawSoftwareCursor(const Surface &out, Point position, int cursId)
{
const CelSprite sprite { GetInvItemSprite(cursId) };
const int frame = GetInvItemFrame(cursId);
const ClxSprite sprite = GetInvItemSprite(cursId);
if (!MyPlayer->HoldItem.isEmpty()) {
const auto &heldItem = MyPlayer->HoldItem;
Cl2DrawOutline(out, GetOutlineColor(heldItem, true), position, sprite, frame);
DrawItem(heldItem, out, position, sprite, frame);
ClxDrawOutline(out, GetOutlineColor(heldItem, true), position, sprite);
DrawItem(heldItem, out, position, sprite);
} else {
Cl2Draw(out, position, sprite, frame);
ClxDraw(out, position, sprite);
}
}

9
Source/cursor.h

@ -9,7 +9,7 @@
#include <utility>
#include "engine.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "utils/attributes.h"
#include "utils/stdcompat/optional.hpp"
@ -62,13 +62,10 @@ void CheckCursMove();
void DrawSoftwareCursor(const Surface &out, Point position, int cursId);
void DrawItem(const Item &item, const Surface &out, Point position, CelSprite cel, int frame);
void DrawItem(const Item &item, const Surface &out, Point position, ClxSprite clx);
/** Returns the sprite for the given inventory index. */
const OwnedCelSprite &GetInvItemSprite(int i);
/** Returns the CEL frame index for the given inventory index. */
int GetInvItemFrame(int cursId);
ClxSprite GetInvItemSprite(int cursId);
/** Returns the width and height for an inventory index. */
Size GetInvItemSize(int cursId);

14
Source/dead.cpp

@ -5,6 +5,7 @@
*/
#include "dead.h"
#include "diablo.h"
#include "levels/gendung.h"
#include "lighting.h"
#include "misdat.h"
@ -18,8 +19,12 @@ int8_t stonendx;
namespace {
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());
const AnimStruct &animData = mon.getAnimData(MonsterGraphic::Death);
if (animData.sprites) {
corpse.sprites.emplace(*animData.sprites);
} else {
corpse.sprites = std::nullopt;
}
corpse.frame = animData.frames - 1;
corpse.width = animData.width;
}
@ -46,9 +51,8 @@ void InitCorpses()
nd++; // Unused blood spatter
for (auto &corpse : Corpses[nd].data)
corpse = MissileSpriteData[MFILE_SHATTER1].GetFirstFrame();
if (!HeadlessMode)
Corpses[nd].sprites.emplace(*MissileSpriteData[MFILE_SHATTER1].sprites);
Corpses[nd].frame = 11;
Corpses[nd].width = 128;
Corpses[nd].translationPaletteIndex = 0;

14
Source/dead.h

@ -9,6 +9,7 @@
#include <cstdint>
#include "engine.h"
#include "engine/clx_sprite.hpp"
#include "engine/point.hpp"
namespace devilution {
@ -16,10 +17,21 @@ namespace devilution {
static constexpr unsigned MaxCorpses = 31;
struct Corpse {
std::array<const byte *, 8> data;
OptionalClxSpriteListOrSheet sprites;
int frame;
uint16_t width;
uint8_t translationPaletteIndex;
/**
* @brief Returns the sprite list for a given direction.
*
* @param direction One of the 16 directions. Valid range: [0, 15].
* @return ClxSpriteList
*/
[[nodiscard]] ClxSpriteList spritesForDirection(Direction direction) const
{
return sprites->isSheet() ? sprites->sheet()[static_cast<size_t>(direction)] : sprites->list();
}
};
extern Corpse Corpses[MaxCorpses];

4
Source/debug.cpp

@ -35,7 +35,7 @@
namespace devilution {
std::string TestMapPath;
OptionalOwnedCelSprite pSquareCel;
OptionalOwnedClxSpriteList pSquareCel;
bool DebugToggle = false;
bool DebugGodMode = false;
bool DebugVision = false;
@ -990,7 +990,7 @@ std::vector<DebugCmdItem> DebugCmdList = {
void LoadDebugGFX()
{
pSquareCel = LoadCelAsCl2("Data\\Square.CEL", 64);
pSquareCel = LoadCel("Data\\Square.CEL", 64);
}
void FreeDebugGFX()

4
Source/debug.h

@ -8,13 +8,13 @@
#include <unordered_map>
#include "engine.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "utils/stdcompat/string_view.hpp"
namespace devilution {
extern std::string TestMapPath;
extern OptionalOwnedCelSprite pSquareCel;
extern OptionalOwnedClxSpriteList pSquareCel;
extern bool DebugToggle;
extern bool DebugGodMode;
extern bool DebugVision;

16
Source/diablo.cpp

@ -26,7 +26,7 @@
#include "discord/discord.h"
#include "doom.h"
#include "encrypt.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/demomode.h"
#include "engine/dx.h"
#include "engine/load_cel.hpp"
@ -1091,37 +1091,37 @@ void LoadLvlGFX()
pDungeonCels = LoadFileInMem("Levels\\TownData\\Town.CEL");
pMegaTiles = LoadFileInMem<MegaTile>("Levels\\TownData\\Town.TIL");
}
pSpecialCels = LoadCelAsCl2("Levels\\TownData\\TownS.CEL", SpecialCelWidth);
pSpecialCels = LoadCel("Levels\\TownData\\TownS.CEL", SpecialCelWidth);
break;
case DTYPE_CATHEDRAL:
pDungeonCels = LoadFileInMem("Levels\\L1Data\\L1.CEL");
pMegaTiles = LoadFileInMem<MegaTile>("Levels\\L1Data\\L1.TIL");
pSpecialCels = LoadCelAsCl2("Levels\\L1Data\\L1S.CEL", SpecialCelWidth);
pSpecialCels = LoadCel("Levels\\L1Data\\L1S.CEL", SpecialCelWidth);
break;
case DTYPE_CATACOMBS:
pDungeonCels = LoadFileInMem("Levels\\L2Data\\L2.CEL");
pMegaTiles = LoadFileInMem<MegaTile>("Levels\\L2Data\\L2.TIL");
pSpecialCels = LoadCelAsCl2("Levels\\L2Data\\L2S.CEL", SpecialCelWidth);
pSpecialCels = LoadCel("Levels\\L2Data\\L2S.CEL", SpecialCelWidth);
break;
case DTYPE_CAVES:
pDungeonCels = LoadFileInMem("Levels\\L3Data\\L3.CEL");
pMegaTiles = LoadFileInMem<MegaTile>("Levels\\L3Data\\L3.TIL");
pSpecialCels = LoadCelAsCl2("Levels\\L1Data\\L1S.CEL", SpecialCelWidth);
pSpecialCels = LoadCel("Levels\\L1Data\\L1S.CEL", SpecialCelWidth);
break;
case DTYPE_HELL:
pDungeonCels = LoadFileInMem("Levels\\L4Data\\L4.CEL");
pMegaTiles = LoadFileInMem<MegaTile>("Levels\\L4Data\\L4.TIL");
pSpecialCels = LoadCelAsCl2("Levels\\L2Data\\L2S.CEL", SpecialCelWidth);
pSpecialCels = LoadCel("Levels\\L2Data\\L2S.CEL", SpecialCelWidth);
break;
case DTYPE_NEST:
pDungeonCels = LoadFileInMem("NLevels\\L6Data\\L6.CEL");
pMegaTiles = LoadFileInMem<MegaTile>("NLevels\\L6Data\\L6.TIL");
pSpecialCels = LoadCelAsCl2("Levels\\L1Data\\L1S.CEL", SpecialCelWidth);
pSpecialCels = LoadCel("Levels\\L1Data\\L1S.CEL", SpecialCelWidth);
break;
case DTYPE_CRYPT:
pDungeonCels = LoadFileInMem("NLevels\\L5Data\\L5.CEL");
pMegaTiles = LoadFileInMem<MegaTile>("NLevels\\L5Data\\L5.TIL");
pSpecialCels = LoadCelAsCl2("NLevels\\L5Data\\L5S.CEL", SpecialCelWidth);
pSpecialCels = LoadCel("NLevels\\L5Data\\L5S.CEL", SpecialCelWidth);
break;
default:
app_fatal("LoadLvlGFX");

12
Source/doom.cpp

@ -7,28 +7,28 @@
#include "control.h"
#include "engine.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/load_cel.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "utils/stdcompat/optional.hpp"
namespace devilution {
namespace {
OptionalOwnedCelSprite DoomCel;
OptionalOwnedClxSpriteList DoomSprite;
} // namespace
bool DoomFlag;
void doom_init()
{
DoomCel = LoadCelAsCl2("Items\\Map\\MapZtown.CEL", 640);
DoomSprite = LoadCel("Items\\Map\\MapZtown.CEL", 640);
DoomFlag = true;
}
void doom_close()
{
DoomFlag = false;
DoomCel = std::nullopt;
DoomSprite = std::nullopt;
}
void doom_draw(const Surface &out)
@ -37,7 +37,7 @@ void doom_draw(const Surface &out)
return;
}
Cl2Draw(out, GetUIRectangle().position + Displacement { 0, 352 }, CelSprite { *DoomCel }, 0);
ClxDraw(out, GetUIRectangle().position + Displacement { 0, 352 }, (*DoomSprite)[0]);
}
} // namespace devilution

8
Source/engine/animationinfo.cpp

@ -74,7 +74,7 @@ float AnimationInfo::getAnimationProgress() const
return animationFraction;
}
void AnimationInfo::setNewAnimation(OptionalCelSprite celSprite, int8_t numberOfFrames, int8_t ticksPerFrame, AnimationDistributionFlags flags /*= AnimationDistributionFlags::None*/, int8_t numSkippedFrames /*= 0*/, int8_t distributeFramesBeforeFrame /*= 0*/, float previewShownGameTickFragments /*= 0.F*/)
void AnimationInfo::setNewAnimation(OptionalClxSpriteList celSprite, int8_t numberOfFrames, int8_t ticksPerFrame, AnimationDistributionFlags flags /*= AnimationDistributionFlags::None*/, int8_t numSkippedFrames /*= 0*/, int8_t distributeFramesBeforeFrame /*= 0*/, float previewShownGameTickFragments /*= 0.F*/)
{
if ((flags & AnimationDistributionFlags::RepeatedAction) == AnimationDistributionFlags::RepeatedAction && distributeFramesBeforeFrame != 0 && this->numberOfFrames == numberOfFrames && currentFrame + 1 >= distributeFramesBeforeFrame && currentFrame != this->numberOfFrames - 1) {
// We showed the same Animation (for example a melee attack) before but truncated the Animation.
@ -89,7 +89,7 @@ void AnimationInfo::setNewAnimation(OptionalCelSprite celSprite, int8_t numberOf
ticksPerFrame = 1;
}
this->celSprite = celSprite;
this->sprites = celSprite;
this->numberOfFrames = numberOfFrames;
currentFrame = numSkippedFrames;
tickCounterOfCurrentFrame = 0;
@ -167,7 +167,7 @@ void AnimationInfo::setNewAnimation(OptionalCelSprite celSprite, int8_t numberOf
}
}
void AnimationInfo::changeAnimationData(OptionalCelSprite celSprite, int8_t numberOfFrames, int8_t ticksPerFrame)
void AnimationInfo::changeAnimationData(OptionalClxSpriteList celSprite, int8_t numberOfFrames, int8_t ticksPerFrame)
{
if (numberOfFrames != this->numberOfFrames || ticksPerFrame != this->ticksPerFrame) {
// Ensure that the currentFrame is still valid and that we disable ADL cause the calculcated values (for example tickModifier_) could be wrong
@ -182,7 +182,7 @@ void AnimationInfo::changeAnimationData(OptionalCelSprite celSprite, int8_t numb
relevantFramesForDistributing_ = 0;
tickModifier_ = 0.0F;
}
this->celSprite = celSprite;
this->sprites = celSprite;
}
void AnimationInfo::processAnimation(bool reverseAnimation /*= false*/)

17
Source/engine/animationinfo.h

@ -8,7 +8,7 @@
#include <cstdint>
#include <type_traits>
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
namespace devilution {
@ -39,7 +39,7 @@ public:
/**
* @brief Animation sprite
*/
OptionalCelSprite celSprite;
OptionalClxSpriteList sprites;
/**
* @brief How many game ticks are needed to advance one Animation Frame
*/
@ -61,6 +61,11 @@ public:
*/
bool isPetrified;
[[nodiscard]] ClxSprite currentSprite() const
{
return (*sprites)[getFrameToUseForRendering()];
}
/**
* @brief Calculates the Frame to use for the Animation rendering
* @return The Frame to use for rendering
@ -74,7 +79,7 @@ public:
/**
* @brief Sets the new Animation with all relevant information for rendering
* @param celSprite Pointer to Animation Sprite
* @param sprites Animation sprites
* @param numberOfFrames Number of Frames in Animation
* @param ticksPerFrame How many game ticks are needed to advance one Animation Frame
* @param flags Specifies what special logics are applied to this Animation
@ -82,15 +87,15 @@ public:
* @param distributeFramesBeforeFrame Distribute the numSkippedFrames only before this frame
* @param previewShownGameTickFragments Defines how long (in game ticks fraction) the preview animation was shown
*/
void setNewAnimation(OptionalCelSprite celSprite, int8_t numberOfFrames, int8_t ticksPerFrame, AnimationDistributionFlags flags = AnimationDistributionFlags::None, int8_t numSkippedFrames = 0, int8_t distributeFramesBeforeFrame = 0, float previewShownGameTickFragments = 0.F);
void setNewAnimation(OptionalClxSpriteList sprites, int8_t numberOfFrames, int8_t ticksPerFrame, AnimationDistributionFlags flags = AnimationDistributionFlags::None, int8_t numSkippedFrames = 0, int8_t distributeFramesBeforeFrame = 0, float previewShownGameTickFragments = 0.F);
/**
* @brief Changes the Animation Data on-the-fly. This is needed if a animation is currently in progress and the player changes his gear.
* @param celSprite Pointer to Animation Sprite
* @param sprites Animation sprites
* @param numberOfFrames Number of Frames in Animation
* @param ticksPerFrame How many game ticks are needed to advance one Animation Frame
*/
void changeAnimationData(OptionalCelSprite celSprite, int8_t numberOfFrames, int8_t ticksPerFrame);
void changeAnimationData(OptionalClxSpriteList sprites, int8_t numberOfFrames, int8_t ticksPerFrame);
/**
* @brief Process the Animation for a game tick (for example advances the frame)

68
Source/engine/cel_header.hpp

@ -1,68 +0,0 @@
#pragma once
#include <cstdint>
#include <cstring>
#include <SDL_endian.h>
#include "utils/endian.hpp"
#include "utils/stdcompat/cstddef.hpp"
namespace devilution {
/*
* 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 void CelGetDirectionFrames(const byte *data, const byte **out)
{
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])];
}
}
/**
* Returns the pointer to the start of the frame data (often a header) and sets `frameSize` to the size of the data in bytes.
*/
inline byte *CelGetFrame(byte *data, int frame, int *frameSize)
{
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];
}
/**
* Returns the pointer to the start of the frame data (often a header) and sets `frameSize` to the size of the data in bytes.
*/
inline const byte *CelGetFrame(const byte *data, int frame, int *frameSize)
{
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];
}
/**
* Returns the pointer to the start of the frame's pixel data and sets `frameSize` to the size of the data in bytes.
*/
inline const byte *CelGetFrameClipped(const byte *data, int frame, int *frameSize)
{
const byte *frameData = CelGetFrame(data, frame, frameSize);
// The frame begins with a header that consists of 5 little-endian 16-bit integers
// pointing to the start of the pixel data for rows 0, 32, 64, 96, and 128.
const std::uint16_t begin = LoadLE16(frameData);
*frameSize -= begin;
return &frameData[begin];
}
} // namespace devilution

344
Source/engine/cel_sprite.hpp

@ -1,344 +0,0 @@
#pragma once
#include <memory>
#include <utility>
#include "appfat.h"
#include "engine/cel_header.hpp"
#include "utils/pointer_value_union.hpp"
#include "utils/stdcompat/cstddef.hpp"
#include "utils/stdcompat/optional.hpp"
namespace devilution {
class OwnedCelSprite;
class OptionalCelSprite;
/**
* Stores a CEL or CL2 sprite and its width(s).
* Does not own the data.
*/
class CelSprite {
public:
CelSprite(const byte *data, uint16_t width)
: data_ptr_(data)
, width_(width)
{
}
CelSprite(const byte *data, const uint16_t *widths)
: data_ptr_(data)
, width_(widths)
{
}
CelSprite(const byte *data, PointerOrValue<uint16_t> widths)
: data_ptr_(data)
, width_(widths)
{
}
explicit CelSprite(const OwnedCelSprite &owned);
CelSprite(const CelSprite &) = default;
CelSprite(CelSprite &&) noexcept = default;
CelSprite &operator=(const CelSprite &) = default;
CelSprite &operator=(CelSprite &&) noexcept = default;
[[nodiscard]] const byte *Data() const
{
return data_ptr_;
}
[[nodiscard]] uint16_t Width(std::size_t frame = 0) const
{
return width_.HoldsPointer() ? width_.AsPointer()[frame] : width_.AsValue();
}
[[nodiscard]] PointerOrValue<uint16_t> widthOrWidths() const
{
return width_;
}
[[nodiscard]] bool operator==(CelSprite other) const
{
return data_ptr_ == other.data_ptr_;
}
[[nodiscard]] bool operator!=(CelSprite other) const
{
return data_ptr_ != other.data_ptr_;
}
private:
// for OptionalCelSprite
CelSprite()
: data_ptr_(nullptr)
, width_(nullptr)
{
}
const byte *data_ptr_;
PointerOrValue<uint16_t> width_;
friend class OptionalCelSprite;
};
/**
* @brief Equivalent to `std::optional<CelSprite>` but smaller.
*/
class OptionalCelSprite {
public:
OptionalCelSprite() = default;
OptionalCelSprite(CelSprite sprite)
: sprite_(sprite)
{
}
explicit OptionalCelSprite(const OwnedCelSprite &owned);
OptionalCelSprite(std::nullopt_t)
: OptionalCelSprite()
{
}
template <typename... Args>
CelSprite &emplace(Args &&...args)
{
sprite_ = CelSprite(std::forward<Args>(args)...);
return sprite_;
}
OptionalCelSprite &operator=(CelSprite sprite)
{
sprite_ = sprite;
return *this;
}
OptionalCelSprite &operator=(std::nullopt_t)
{
sprite_ = {};
return *this;
}
CelSprite operator*() const
{
assert(sprite_.data_ptr_ != nullptr);
return sprite_;
}
CelSprite *operator->()
{
assert(sprite_.data_ptr_ != nullptr);
return &sprite_;
}
const CelSprite *operator->() const
{
assert(sprite_.data_ptr_ != nullptr);
return &sprite_;
}
operator bool() const
{
return sprite_.data_ptr_ != nullptr;
}
private:
CelSprite sprite_;
};
class OptionalOwnedCelSprite;
/**
* Stores a CEL or CL2 sprite and its width(s).
* Owns the data.
*/
class OwnedCelSprite {
public:
OwnedCelSprite(std::unique_ptr<byte[]> data, uint16_t width)
: data_(std::move(data))
, width_(width)
{
}
OwnedCelSprite(std::unique_ptr<byte[]> data, const uint16_t *widths)
: data_(std::move(data))
, width_(widths)
{
}
OwnedCelSprite(std::unique_ptr<byte[]> data, PointerOrValue<uint16_t> widths)
: data_(std::move(data))
, width_(widths)
{
}
OwnedCelSprite(OwnedCelSprite &&) noexcept = default;
OwnedCelSprite &operator=(OwnedCelSprite &&) noexcept = default;
[[nodiscard]] byte *MutableData()
{
return data_.get();
}
std::unique_ptr<byte[]> data() &&
{
return std::move(data_);
}
private:
// for OptionalOwnedCelSprite.
OwnedCelSprite()
: data_(nullptr)
, width_(nullptr)
{
}
std::unique_ptr<byte[]> data_;
PointerOrValue<uint16_t> width_;
friend class CelSprite;
friend class OptionalOwnedCelSprite;
};
/**
* @brief Equivalent to `std::optional<OwnedCelSprite>` but smaller.
*/
class OptionalOwnedCelSprite {
public:
OptionalOwnedCelSprite() = default;
OptionalOwnedCelSprite(OwnedCelSprite &&sprite)
: sprite_(std::move(sprite))
{
}
OptionalOwnedCelSprite(std::nullopt_t)
: OptionalOwnedCelSprite()
{
}
template <typename... Args>
OwnedCelSprite &emplace(Args &&...args)
{
sprite_ = OwnedCelSprite(std::forward<Args>(args)...);
return sprite_;
}
OptionalOwnedCelSprite &operator=(OwnedCelSprite &&sprite)
{
sprite_ = std::move(sprite);
return *this;
}
OptionalOwnedCelSprite &operator=(std::nullopt_t)
{
sprite_ = {};
return *this;
}
OwnedCelSprite &operator*()
{
assert(sprite_.data_ != nullptr);
return sprite_;
}
const OwnedCelSprite &operator*() const
{
assert(sprite_.data_ != nullptr);
return sprite_;
}
OwnedCelSprite *operator->()
{
assert(sprite_.data_ != nullptr);
return &sprite_;
}
const OwnedCelSprite *operator->() const
{
assert(sprite_.data_ != nullptr);
return &sprite_;
}
operator bool() const
{
return sprite_.data_ != nullptr;
}
private:
OwnedCelSprite sprite_;
};
inline CelSprite::CelSprite(const OwnedCelSprite &owned)
: CelSprite(owned.data_.get(), owned.width_)
{
}
inline OptionalCelSprite::OptionalCelSprite(const OwnedCelSprite &owned)
{
sprite_ = CelSprite { owned };
}
struct CelSpriteWithFrameHeight {
CelSprite sprite;
unsigned frameHeight;
};
struct CelFrameWithHeight {
CelSprite sprite;
uint16_t frameHeight;
uint16_t frame;
[[nodiscard]] uint16_t width() const
{
return sprite.Width(frame);
}
[[nodiscard]] uint16_t height() const
{
return frameHeight;
}
};
struct OwnedCelSpriteWithFrameHeight {
OwnedCelSprite ownedSprite;
uint16_t frameHeight;
[[nodiscard]] CelFrameWithHeight sprite() const
{
return { CelSprite { ownedSprite }, frameHeight, 0 };
}
};
struct CelSpriteSheetWithFrameHeight {
CelSprite sheet;
uint16_t frameHeight;
[[nodiscard]] unsigned numFrames() const
{
return LoadLE32(sheet.Data());
}
[[nodiscard]] CelFrameWithHeight sprite(uint16_t frame) const
{
return { CelSprite { sheet }, frameHeight, frame };
}
};
struct OwnedCelSpriteSheetWithFrameHeight {
OwnedCelSprite ownedSprite;
uint16_t frameHeight;
[[nodiscard]] CelSpriteSheetWithFrameHeight sheet() const
{
return CelSpriteSheetWithFrameHeight { CelSprite { ownedSprite }, frameHeight };
}
[[nodiscard]] CelFrameWithHeight sprite(uint16_t frame) const
{
return CelFrameWithHeight { CelSprite { ownedSprite }, frameHeight, frame };
}
};
} // namespace devilution

599
Source/engine/clx_sprite.hpp

@ -0,0 +1,599 @@
#pragma once
/**
* @file clx_sprite.hpp
*
* @brief CLX format sprites.
*
* CLX is a format used for DevilutionX graphics at runtime.
*
* It is identical to CL2, except we use the frame header to store the frame's width and height.
*
* CLX frame header (10 bytes, same as CL2):
*
* Bytes | Type | Value
* :-----:|:--------:|------------------------------------
* 0..2 | uint16_t | offset to data start (same as CL2)
* 2..4 | uint16_t | width
* 4..6 | uint16_t | height
* 6..10 | - | unused
*
* The CLX format is otherwise identical to CL2.
*
* Since the header is identical to CL2, CL2 can be converted to CLX without reallocation.
*
* CL2 reference: https://github.com/savagesteel/d1-file-formats/blob/master/PC-Mac/CL2.md#2-file-structure
*/
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <memory>
#include "appfat.h"
#include "utils/endian.hpp"
#include "utils/intrusive_optional.hpp"
namespace devilution {
class OptionalClxSprite;
/**
* @brief A single CLX sprite.
*/
class ClxSprite {
static constexpr uint32_t HeaderSize = 10;
public:
explicit constexpr ClxSprite(const uint8_t *data, uint32_t dataSize)
: data_(data)
, pixel_data_size_(dataSize - HeaderSize)
{
assert(data != nullptr);
}
[[nodiscard]] constexpr uint16_t width() const
{
return LoadLE16(&data_[2]);
}
[[nodiscard]] constexpr uint16_t height() const
{
return LoadLE16(&data_[4]);
}
/**
* @brief The raw pixel data (CL2 frame data).
*
* Format: https://github.com/savagesteel/d1-file-formats/blob/master/PC-Mac/CL2.md#42-cl2-frame-data
*/
[[nodiscard]] constexpr const uint8_t *pixelData() const
{
return &data_[HeaderSize];
}
[[nodiscard]] constexpr uint32_t pixelDataSize() const
{
return pixel_data_size_;
}
constexpr bool operator==(const ClxSprite &other) const
{
return data_ == other.data_;
}
constexpr bool operator!=(const ClxSprite &other) const
{
return !(*this == other);
}
private:
// For OptionalClxSprite.
constexpr ClxSprite()
: data_(nullptr)
, pixel_data_size_(0)
{
}
const uint8_t *data_;
uint32_t pixel_data_size_;
friend class OptionalClxSprite;
};
class OwnedClxSpriteList;
class OptionalClxSpriteList;
class ClxSpriteListIterator;
/**
* @brief A list of `ClxSprite`s.
*/
class ClxSpriteList {
public:
explicit constexpr ClxSpriteList(const uint8_t *data)
: data_(data)
{
assert(data != nullptr);
}
ClxSpriteList(const OwnedClxSpriteList &owned);
[[nodiscard]] constexpr uint32_t numSprites() const
{
return LoadLE32(data_);
}
[[nodiscard]] constexpr ClxSprite operator[](size_t spriteIndex) const
{
assert(spriteIndex < numSprites());
const uint32_t begin = spriteOffset(spriteIndex);
const uint32_t end = spriteOffset(spriteIndex + 1);
return ClxSprite { &data_[begin], end - begin };
}
[[nodiscard]] constexpr uint32_t spriteOffset(size_t spriteIndex) const
{
return LoadLE32(&data_[4 + spriteIndex * 4]);
}
/** @brief The offset to the next sprite sheet, or file size if this is the last sprite sheet. */
[[nodiscard]] constexpr uint32_t nextSpriteSheetOffsetOrFileSize() const
{
return LoadLE32(&data_[4 + numSprites()]);
}
[[nodiscard]] constexpr const uint8_t *data() const
{
return data_;
}
[[nodiscard]] constexpr ClxSpriteListIterator begin() const;
[[nodiscard]] constexpr ClxSpriteListIterator end() const;
private:
// For OptionalClxSpriteList.
constexpr ClxSpriteList()
: data_(nullptr)
{
}
const uint8_t *data_;
friend class OptionalClxSpriteList;
};
class ClxSpriteListIterator {
public:
using iterator_category = std::forward_iterator_tag;
using difference_type = int;
using value_type = ClxSprite;
using pointer = void;
using reference = value_type &;
constexpr ClxSpriteListIterator(ClxSpriteList list, size_t index)
: list_(list)
, index_(index)
{
}
constexpr ClxSprite operator*()
{
return list_[index_];
}
constexpr ClxSpriteListIterator &operator++()
{
++index_;
return *this;
}
constexpr ClxSpriteListIterator operator++(int)
{
auto copy = *this;
++(*this);
return copy;
}
constexpr bool operator==(const ClxSpriteListIterator &other) const
{
return index_ == other.index_;
}
constexpr bool operator!=(const ClxSpriteListIterator &other) const
{
return !(*this == other);
}
private:
ClxSpriteList list_;
size_t index_;
};
inline constexpr ClxSpriteListIterator ClxSpriteList::begin() const
{
return { *this, 0 };
}
inline constexpr ClxSpriteListIterator ClxSpriteList::end() const
{
return { *this, numSprites() };
}
class OwnedClxSpriteSheet;
class OptionalClxSpriteSheet;
class ClxSpriteSheetIterator;
/**
* @brief A sprite sheet is a list of `ClxSpriteList`s.
*/
class ClxSpriteSheet {
public:
explicit constexpr ClxSpriteSheet(const uint8_t *data, uint16_t numLists)
: data_(data)
, num_lists_(numLists)
{
assert(data != nullptr);
assert(num_lists_ > 0);
}
ClxSpriteSheet(const OwnedClxSpriteSheet &owned);
[[nodiscard]] constexpr uint16_t numLists() const
{
return num_lists_;
}
[[nodiscard]] constexpr ClxSpriteList operator[](size_t sheetIndex) const
{
return ClxSpriteList { &data_[sheetOffset(sheetIndex)] };
}
[[nodiscard]] constexpr uint32_t sheetOffset(size_t sheetIndex) const
{
assert(sheetIndex < num_lists_);
return LoadLE32(&data_[4 * sheetIndex]);
}
[[nodiscard]] constexpr const uint8_t *data() const
{
return data_;
}
[[nodiscard]] constexpr ClxSpriteSheetIterator begin() const;
[[nodiscard]] constexpr ClxSpriteSheetIterator end() const;
private:
// For OptionalClxSpriteSheet.
constexpr ClxSpriteSheet()
: data_(nullptr)
, num_lists_(0)
{
}
const uint8_t *data_;
uint16_t num_lists_;
friend class OptionalClxSpriteSheet;
};
class ClxSpriteSheetIterator {
public:
using iterator_category = std::forward_iterator_tag;
using difference_type = int;
using value_type = ClxSpriteList;
using pointer = void;
using reference = value_type &;
constexpr ClxSpriteSheetIterator(ClxSpriteSheet sheet, size_t index)
: sheet_(sheet)
, index_(index)
{
}
constexpr ClxSpriteList operator*()
{
return sheet_[index_];
}
constexpr ClxSpriteSheetIterator &operator++()
{
++index_;
return *this;
}
constexpr ClxSpriteSheetIterator operator++(int)
{
auto copy = *this;
++(*this);
return copy;
}
constexpr bool operator==(const ClxSpriteSheetIterator &other) const
{
return index_ == other.index_;
}
constexpr bool operator!=(const ClxSpriteSheetIterator &other) const
{
return !(*this == other);
}
private:
ClxSpriteSheet sheet_;
size_t index_;
};
inline constexpr ClxSpriteSheetIterator ClxSpriteSheet::begin() const
{
return { *this, 0 };
}
inline constexpr ClxSpriteSheetIterator ClxSpriteSheet::end() const
{
return { *this, num_lists_ };
}
class OptionalOwnedClxSpriteList;
class OwnedClxSpriteListOrSheet;
/**
* @brief Implicitly convertible to `ClxSpriteList` and owns its data.
*/
class OwnedClxSpriteList {
public:
explicit OwnedClxSpriteList(std::unique_ptr<uint8_t[]> &&data)
: data_(std::move(data))
{
assert(data_ != nullptr);
}
OwnedClxSpriteList(OwnedClxSpriteList &&) noexcept = default;
OwnedClxSpriteList &operator=(OwnedClxSpriteList &&) noexcept = default;
[[nodiscard]] ClxSprite operator[](size_t spriteIndex) const
{
return ClxSpriteList { *this }[spriteIndex];
}
private:
// For OptionalOwnedClxSpriteList.
OwnedClxSpriteList() = default;
std::unique_ptr<uint8_t[]> data_;
friend class ClxSpriteList; // for implicit conversion
friend class OptionalOwnedClxSpriteList;
friend class OwnedClxSpriteListOrSheet;
};
inline ClxSpriteList::ClxSpriteList(const OwnedClxSpriteList &owned)
: data_(owned.data_.get())
{
}
/**
* @brief Implicitly convertible to `ClxSpriteSheet` and owns its data.
*/
class OwnedClxSpriteSheet {
public:
OwnedClxSpriteSheet(std::unique_ptr<uint8_t[]> &&data, uint16_t numLists)
: data_(std::move(data))
, num_lists_(numLists)
{
assert(data_ != nullptr);
assert(numLists > 0);
}
OwnedClxSpriteSheet(OwnedClxSpriteSheet &&) noexcept = default;
OwnedClxSpriteSheet &operator=(OwnedClxSpriteSheet &&) noexcept = default;
[[nodiscard]] ClxSpriteList operator[](size_t sheetIndex) const
{
return ClxSpriteSheet { *this }[sheetIndex];
}
[[nodiscard]] ClxSpriteSheetIterator begin() const
{
return ClxSpriteSheet { *this }.begin();
}
[[nodiscard]] ClxSpriteSheetIterator end() const
{
return ClxSpriteSheet { *this }.end();
}
private:
// For OptionalOwnedClxSpriteList.
OwnedClxSpriteSheet()
: data_(nullptr)
, num_lists_(0)
{
}
std::unique_ptr<uint8_t[]> data_;
uint16_t num_lists_;
friend class ClxSpriteSheet; // for implicit conversion.
friend class OptionalOwnedClxSpriteSheet;
friend class OwnedClxSpriteListOrSheet;
};
inline ClxSpriteSheet::ClxSpriteSheet(const OwnedClxSpriteSheet &owned)
: data_(owned.data_.get())
, num_lists_(owned.num_lists_)
{
}
class OwnedClxSpriteListOrSheet;
class OptionalClxSpriteListOrSheet;
/**
* @brief A CLX sprite list or a sprite sheet (list of lists).
*/
class ClxSpriteListOrSheet {
public:
constexpr ClxSpriteListOrSheet(const uint8_t *data, uint16_t numLists)
: data_(data)
, num_lists_(numLists)
{
}
ClxSpriteListOrSheet(const OwnedClxSpriteListOrSheet &listOrSheet);
[[nodiscard]] constexpr ClxSpriteList list() const
{
assert(num_lists_ == 0);
return ClxSpriteList { data_ };
}
[[nodiscard]] constexpr ClxSpriteSheet sheet() const
{
assert(num_lists_ != 0);
return ClxSpriteSheet { data_, num_lists_ };
}
[[nodiscard]] constexpr bool isSheet() const
{
return num_lists_ != 0;
}
private:
const uint8_t *data_;
uint16_t num_lists_;
// For OptionalClxSpriteListOrSheet.
constexpr ClxSpriteListOrSheet()
: data_(nullptr)
, num_lists_(0)
{
}
friend class OptionalClxSpriteListOrSheet;
};
class OptionalOwnedClxSpriteListOrSheet;
/**
* @brief A CLX sprite list or a sprite sheet (list of lists).
*/
class OwnedClxSpriteListOrSheet {
public:
explicit OwnedClxSpriteListOrSheet(std::unique_ptr<uint8_t[]> &&data, uint16_t numLists = 0)
: data_(std::move(data))
, num_lists_(numLists)
{
}
explicit OwnedClxSpriteListOrSheet(OwnedClxSpriteSheet &&sheet)
: data_(std::move(sheet.data_))
, num_lists_(sheet.num_lists_)
{
}
explicit OwnedClxSpriteListOrSheet(OwnedClxSpriteList &&list)
: data_(std::move(list.data_))
, num_lists_(0)
{
}
[[nodiscard]] ClxSpriteList list() const &
{
assert(num_lists_ == 0);
return ClxSpriteList { data_.get() };
}
[[nodiscard]] OwnedClxSpriteList list() &&
{
assert(num_lists_ == 0);
return OwnedClxSpriteList { std::move(data_) };
}
[[nodiscard]] ClxSpriteSheet sheet() const &
{
assert(num_lists_ != 0);
return ClxSpriteSheet { data_.get(), num_lists_ };
}
[[nodiscard]] OwnedClxSpriteSheet sheet() &&
{
assert(num_lists_ != 0);
return OwnedClxSpriteSheet { std::move(data_), num_lists_ };
}
[[nodiscard]] bool isSheet() const
{
return num_lists_ != 0;
}
private:
std::unique_ptr<uint8_t[]> data_;
uint16_t num_lists_;
// For OptionalOwnedClxSpriteListOrSheet.
OwnedClxSpriteListOrSheet()
: data_(nullptr)
, num_lists_(0)
{
}
friend class ClxSpriteListOrSheet;
friend class OptionalOwnedClxSpriteListOrSheet;
};
inline ClxSpriteListOrSheet::ClxSpriteListOrSheet(const OwnedClxSpriteListOrSheet &listOrSheet)
: data_(listOrSheet.data_.get())
, num_lists_(listOrSheet.num_lists_)
{
}
/**
* @brief Equivalent to `std::optional<ClxSprite>` but smaller.
*/
class OptionalClxSprite {
DEFINE_CONSTEXPR_INTRUSIVE_OPTIONAL(OptionalClxSprite, ClxSprite, data_, nullptr)
};
/**
* @brief Equivalent to `std::optional<ClxSpriteList>` but smaller.
*/
class OptionalClxSpriteList {
DEFINE_CONSTEXPR_INTRUSIVE_OPTIONAL(OptionalClxSpriteList, ClxSpriteList, data_, nullptr)
};
/**
* @brief Equivalent to `std::optional<ClxSpriteSheet>` but smaller.
*/
class OptionalClxSpriteSheet {
DEFINE_CONSTEXPR_INTRUSIVE_OPTIONAL(OptionalClxSpriteSheet, ClxSpriteSheet, data_, nullptr)
};
/**
* @brief Equivalent to `std::optional<ClxSpriteListOrSheet>` but smaller.
*/
class OptionalClxSpriteListOrSheet {
public:
DEFINE_INTRUSIVE_OPTIONAL(OptionalClxSpriteListOrSheet, ClxSpriteListOrSheet, data_, nullptr);
};
/**
* @brief Equivalent to `std::optional<OwnedClxSpriteList>` but smaller.
*/
class OptionalOwnedClxSpriteList {
public:
DEFINE_INTRUSIVE_OPTIONAL(OptionalOwnedClxSpriteList, OwnedClxSpriteList, data_, nullptr)
};
/**
* @brief Equivalent to `std::optional<OwnedClxSpriteSheet>` but smaller.
*/
class OptionalOwnedClxSpriteSheet {
public:
DEFINE_INTRUSIVE_OPTIONAL(OptionalOwnedClxSpriteSheet, OwnedClxSpriteSheet, data_, nullptr)
};
class OptionalOwnedClxSpriteListOrSheet {
public:
DEFINE_INTRUSIVE_OPTIONAL(OptionalOwnedClxSpriteListOrSheet, OwnedClxSpriteListOrSheet, data_, nullptr);
};
} // namespace devilution

17
Source/engine/load_cel.cpp

@ -5,29 +5,18 @@
#endif
#include "engine/load_file.hpp"
#include "utils/cel_to_cl2.hpp"
#include "utils/pointer_value_union.hpp"
#include "utils/cel_to_clx.hpp"
namespace devilution {
OwnedCelSprite LoadCelAsCl2(const char *pszName, uint16_t width)
OwnedClxSpriteListOrSheet LoadCelListOrSheet(const char *pszName, PointerOrValue<uint16_t> widthOrWidths)
{
size_t size;
std::unique_ptr<uint8_t[]> data = LoadFileInMem<uint8_t>(pszName, &size);
#ifdef DEBUG_CEL_TO_CL2_SIZE
std::cout << pszName;
#endif
return CelToCl2(data.get(), size, PointerOrValue<uint16_t> { width });
}
OwnedCelSprite LoadCelAsCl2(const char *pszName, const uint16_t *widths)
{
size_t size;
std::unique_ptr<uint8_t[]> data = LoadFileInMem<uint8_t>(pszName, &size);
#ifdef DEBUG_CEL_TO_CL2_SIZE
std::cout << pszName;
#endif
return CelToCl2(data.get(), size, PointerOrValue<uint16_t> { widths });
return CelToClx(data.get(), size, widthOrWidths);
}
} // namespace devilution

21
Source/engine/load_cel.hpp

@ -2,11 +2,26 @@
#include <cstdint>
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "utils/pointer_value_union.hpp"
namespace devilution {
OwnedCelSprite LoadCelAsCl2(const char *pszName, uint16_t width);
OwnedCelSprite LoadCelAsCl2(const char *pszName, const uint16_t *widths);
OwnedClxSpriteListOrSheet LoadCelListOrSheet(const char *pszName, PointerOrValue<uint16_t> widthOrWidths);
inline OwnedClxSpriteList LoadCel(const char *pszName, uint16_t width)
{
return LoadCelListOrSheet(pszName, PointerOrValue<uint16_t> { width }).list();
}
inline OwnedClxSpriteList LoadCel(const char *pszName, const uint16_t *widths)
{
return LoadCelListOrSheet(pszName, PointerOrValue<uint16_t> { widths }).list();
}
inline OwnedClxSpriteSheet LoadCelSheet(const char *pszName, uint16_t width)
{
return LoadCelListOrSheet(pszName, PointerOrValue<uint16_t> { width }).sheet();
}
} // namespace devilution

18
Source/engine/load_cl2.cpp

@ -0,0 +1,18 @@
#include "engine/load_cl2.hpp"
#include <memory>
#include <utility>
#include "engine/load_file.hpp"
#include "utils/cl2_to_clx.hpp"
namespace devilution {
OwnedClxSpriteListOrSheet LoadCl2ListOrSheet(const char *pszName, PointerOrValue<uint16_t> widthOrWidths)
{
size_t size;
std::unique_ptr<uint8_t[]> data = LoadFileInMem<uint8_t>(pszName, &size);
return Cl2ToClx(std::move(data), size, widthOrWidths);
}
} // namespace devilution

63
Source/engine/load_cl2.hpp

@ -0,0 +1,63 @@
#pragma once
#include <cstdint>
#include <function_ref.hpp>
#include "appfat.h"
#include "engine/clx_sprite.hpp"
#include "engine/load_file.hpp"
#include "utils/cl2_to_clx.hpp"
#include "utils/endian.hpp"
#include "utils/pointer_value_union.hpp"
#include "utils/static_vector.hpp"
namespace devilution {
OwnedClxSpriteListOrSheet LoadCl2ListOrSheet(const char *pszName, PointerOrValue<uint16_t> widthOrWidths);
template <size_t MaxCount>
OwnedClxSpriteSheet LoadMultipleCl2Sheet(tl::function_ref<const char *(size_t)> filenames, size_t count, uint16_t width)
{
StaticVector<SFile, MaxCount> files;
StaticVector<size_t, MaxCount> fileSizes;
const size_t sheetHeaderSize = 4 * count;
size_t totalSize = sheetHeaderSize;
for (size_t i = 0; i < count; ++i) {
const char *filename = filenames(i);
files.emplace_back(filename);
const size_t size = files[i].Size();
fileSizes.emplace_back(size);
totalSize += size;
}
auto data = std::unique_ptr<uint8_t[]> { new uint8_t[totalSize] };
const PointerOrValue<uint16_t> frameWidth { width };
size_t accumulatedSize = sheetHeaderSize;
for (size_t i = 0; i < count; ++i) {
const size_t size = fileSizes[i];
if (!files[i].Read(&data[accumulatedSize], size))
app_fatal(StrCat("Read failed: ", SDL_GetError()));
WriteLE32(&data[i * 4], accumulatedSize);
[[maybe_unused]] const uint16_t numLists = Cl2ToClx(&data[accumulatedSize], size, frameWidth);
assert(numLists == 0);
accumulatedSize += size;
}
return OwnedClxSpriteSheet { std::move(data), static_cast<uint16_t>(count) };
}
inline OwnedClxSpriteList LoadCl2(const char *pszName, uint16_t width)
{
return LoadCl2ListOrSheet(pszName, PointerOrValue<uint16_t> { width }).list();
}
inline OwnedClxSpriteList LoadCl2(const char *pszName, const uint16_t *widths)
{
return LoadCl2ListOrSheet(pszName, PointerOrValue<uint16_t> { widths }).list();
}
inline OwnedClxSpriteSheet LoadCl2Sheet(const char *pszName, uint16_t width)
{
return LoadCl2ListOrSheet(pszName, PointerOrValue<uint16_t> { width }).sheet();
}
} // namespace devilution

13
Source/engine/load_file.hpp

@ -121,7 +121,7 @@ struct MultiFileLoader {
/**
* @param numFiles number of files to read
* @param pathFn a function that returns the path for the given index
* @param outOffsets a buffer index for the start of each file will be written here
* @param outOffsets a buffer index for the start of each file will be written here, then the total file size at the end.
* @param filterFn a function that returns whether to load a file for the given index
* @return std::unique_ptr<byte[]> the buffer with all the files
*/
@ -132,20 +132,21 @@ struct MultiFileLoader {
StaticVector<SFile, MaxFiles> files;
StaticVector<uint32_t, MaxFiles> sizes;
size_t totalSize = 0;
for (size_t i = 0; i < numFiles; ++i) {
for (size_t i = 0, j = 0; i < numFiles; ++i) {
if (!filterFn(i))
continue;
const size_t size = files.emplace_back(pathFn(i)).Size();
sizes.emplace_back(static_cast<uint32_t>(size));
outOffsets[i] = static_cast<uint32_t>(totalSize);
outOffsets[j] = static_cast<uint32_t>(totalSize);
totalSize += size;
++j;
}
outOffsets[files.size()] = totalSize;
std::unique_ptr<byte[]> buf { new byte[totalSize] };
size_t j = 0;
for (size_t i = 0; i < numFiles; ++i) {
for (size_t i = 0, j = 0; i < numFiles; ++i) {
if (!filterFn(i))
continue;
files[j].Read(&buf[outOffsets[i]], sizes[j]);
files[j].Read(&buf[outOffsets[j]], sizes[j]);
++j;
}
return buf;

20
Source/engine/load_pcx.cpp

@ -13,7 +13,7 @@
#include "engine/assets.hpp"
#include "utils/log.hpp"
#include "utils/pcx.hpp"
#include "utils/pcx_to_cl2.hpp"
#include "utils/pcx_to_clx.hpp"
#ifdef USE_SDL1
#include "utils/sdl2_to_1_2_backports.h"
@ -21,8 +21,7 @@
namespace devilution {
namespace {
std::optional<OwnedCelSpriteWithFrameHeight> LoadPcxSpriteAsCl2(const char *filename, int numFramesOrFrameHeight, std::optional<uint8_t> transparentColor, SDL_Color *outPalette)
OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFramesOrFrameHeight, std::optional<uint8_t> transparentColor, SDL_Color *outPalette)
{
SDL_RWops *handle = OpenAsset(filename);
if (handle == nullptr) {
@ -32,23 +31,10 @@ std::optional<OwnedCelSpriteWithFrameHeight> LoadPcxSpriteAsCl2(const char *file
#ifdef DEBUG_PCX_TO_CL2_SIZE
std::cout << filename;
#endif
std::optional<OwnedCelSpriteWithFrameHeight> result = PcxToCl2(handle, numFramesOrFrameHeight, transparentColor, outPalette);
OptionalOwnedClxSpriteList result = PcxToClx(handle, numFramesOrFrameHeight, transparentColor, outPalette);
if (!result)
return std::nullopt;
return result;
}
} // namespace
std::optional<OwnedCelSpriteSheetWithFrameHeight> LoadPcxSpriteSheetAsCl2(const char *filename, int numFramesOrFrameHeight, std::optional<uint8_t> transparentColor, SDL_Color *outPalette)
{
std::optional<OwnedCelSpriteWithFrameHeight> result = LoadPcxSpriteAsCl2(filename, numFramesOrFrameHeight, transparentColor, outPalette);
return OwnedCelSpriteSheetWithFrameHeight { std::move(result->ownedSprite), result->frameHeight };
}
std::optional<OwnedCelSpriteWithFrameHeight> LoadPcxAsCl2(const char *filename, std::optional<uint8_t> transparentColor, SDL_Color *outPalette)
{
return LoadPcxSpriteAsCl2(filename, 1, transparentColor, outPalette);
}
} // namespace devilution

21
Source/engine/load_pcx.hpp

@ -4,22 +4,33 @@
#include <SDL.h>
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "utils/stdcompat/optional.hpp"
namespace devilution {
/**
* @brief Loads a PCX file as a CL2 sprite sheet.
* @brief Loads a PCX file as a CLX sprite list.
*
* @param filename
* @param numFramesOrFrameHeight Pass a positive value with the number of frames, or the frame height as a negative value.
* @param transparentColor
* @param outPalette
* @return std::optional<OwnedCelSpriteSheetWithFrameHeight>
* @return OptionalOwnedClxSpriteList
*/
std::optional<OwnedCelSpriteSheetWithFrameHeight> LoadPcxSpriteSheetAsCl2(const char *filename, int numFramesOrFrameHeight, std::optional<uint8_t> transparentColor = std::nullopt, SDL_Color *outPalette = nullptr);
OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFramesOrFrameHeight, std::optional<uint8_t> transparentColor = std::nullopt, SDL_Color *outPalette = nullptr);
std::optional<OwnedCelSpriteWithFrameHeight> LoadPcxAsCl2(const char *filename, std::optional<uint8_t> transparentColor = std::nullopt, SDL_Color *outPalette = nullptr);
/**
* @brief Loads a PCX file as a CLX sprite list with a single sprite.
*
* @param filename
* @param transparentColor
* @param outPalette
* @return OptionalOwnedClxSpriteList
*/
inline OptionalOwnedClxSpriteList LoadPcx(const char *filename, std::optional<uint8_t> transparentColor = std::nullopt, SDL_Color *outPalette = nullptr)
{
return LoadPcxSpriteList(filename, 1, transparentColor, outPalette);
}
} // namespace devilution

41
Source/engine/render/blit_impl.hpp

@ -11,7 +11,6 @@ namespace devilution {
enum class BlitType : uint8_t {
Transparent,
Pixels,
Pixel,
Fill
};
@ -22,22 +21,6 @@ struct BlitCommand {
uint8_t color; // For `BlitType::Pixel` and `BlitType::Fill` only.
};
DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT BlitCommand TransformBlitCommandNoop(BlitCommand cmd)
{
return cmd;
}
struct TransformBlitCommandTransparentColor {
uint8_t transparentColor;
BlitCommand operator()(BlitCommand cmd) const
{
if (cmd.color == transparentColor)
cmd.type = BlitType::Transparent;
return cmd;
}
};
DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void BlitFillDirect(uint8_t *dst, unsigned length, uint8_t color)
{
std::memset(dst, color, length);
@ -48,11 +31,6 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void BlitPixelsDirect(uint8_t *dst, const ui
std::memcpy(dst, src, length);
}
DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void BlitPixelDirect(uint8_t *dst, uint8_t color)
{
*dst = color;
}
struct BlitDirect {
DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void operator()(BlitCommand cmd, uint8_t *dst, const uint8_t *src)
{
@ -63,9 +41,6 @@ struct BlitDirect {
case BlitType::Pixels:
BlitPixelsDirect(dst, src, cmd.length);
return;
case BlitType::Pixel:
BlitPixelDirect(dst, cmd.color);
return;
case BlitType::Transparent:
return;
}
@ -83,11 +58,6 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void BlitPixelsWithMap(uint8_t *dst, const u
*dst++ = colorMap[*src++];
}
DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void BlitPixelWithMap(uint8_t *dst, uint8_t color, const uint8_t *colorMap)
{
*dst = colorMap[color];
}
struct BlitWithMap {
const uint8_t *colorMap;
@ -100,9 +70,6 @@ struct BlitWithMap {
case BlitType::Pixels:
BlitPixelsWithMap(dst, src, cmd.length, colorMap);
return;
case BlitType::Pixel:
BlitPixelWithMap(dst, cmd.color, colorMap);
return;
case BlitType::Transparent:
return;
}
@ -126,11 +93,6 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void BlitPixelsBlendedWithMap(uint8_t *dst,
}
}
DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void BlitPixelBlendedWithMap(uint8_t *dst, uint8_t color, const uint8_t *colorMap)
{
*dst = paletteTransparencyLookup[*dst][colorMap[color]];
}
struct BlitBlendedWithMap {
const uint8_t *colorMap;
@ -143,9 +105,6 @@ struct BlitBlendedWithMap {
case BlitType::Pixels:
BlitPixelsBlendedWithMap(dst, src, cmd.length, colorMap);
return;
case BlitType::Pixel:
BlitPixelBlendedWithMap(dst, cmd.color, colorMap);
return;
case BlitType::Transparent:
return;
}

115
Source/engine/render/cl2_render.hpp

@ -1,115 +0,0 @@
/**
* @file cl2_render.hpp
*
* CL2 rendering.
*/
#pragma once
#include <cstdint>
#include <array>
#include <utility>
#include "engine.h"
#include "engine/cel_sprite.hpp"
#include "engine/point.hpp"
#include "lighting.h"
namespace devilution {
/**
* @brief Apply the color swaps to a CL2 sprite
* @param p CL2 buffer
* @param ttbl Palette translation table
* @param numFrames Number of frames in the CL2 file
*/
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
* @param out Output buffer
* @param position Target buffer coordinate
* @param cel CL2 buffer
* @param frame CL2 frame number
*/
void Cl2Draw(const Surface &out, Point position, CelSprite cel, int frame);
/**
* @brief Same as Cl2Draw but position.y is the top of the sprite instead of the bottom.
*/
inline void RenderCl2Sprite(const Surface &out, CelFrameWithHeight cel, Point position)
{
Cl2Draw(out, { position.x, position.y + static_cast<int>(cel.frameHeight) - 1 }, cel.sprite, cel.frame);
}
/**
* @brief Blit a solid colder shape one pixel larger than the given sprite shape, to the given buffer at the given coordinates
* @param col Color index from current palette
* @param out Output buffer
* @param position Target buffer coordinate
* @param cel CL2 buffer
* @param frame CL2 frame number
*/
void Cl2DrawOutline(const Surface &out, uint8_t col, Point position, CelSprite cel, int frame);
/**
* @brief Same as `Cl2DrawOutline` but considers colors with index 0 (usually shadows) to be transparent.
*
* @param col Color index from current palette
* @param out Output buffer
* @param position Target buffer coordinate
* @param cel CL2 buffer
* @param frame CL2 frame number
*/
void Cl2DrawOutlineSkipColorZero(const Surface &out, uint8_t col, Point position, CelSprite cel, int frame);
/**
* @brief Blit CL2 sprite, and apply given TRN to the given buffer at the given coordinates
* @param out Output buffer
* @param position Target buffer coordinate
* @param cel CL2 buffer
* @param frame CL2 frame number
* @param trn TRN to use
*/
void Cl2DrawTRN(const Surface &out, Point position, CelSprite cel, int frame, uint8_t *trn);
/**
* @brief Same as Cl2DrawTRN but position.y is the top of the sprite instead of the bottom.
*/
inline void RenderCl2SpriteWithTRN(const Surface &out, CelFrameWithHeight cel, Point position, uint8_t *trn)
{
Cl2DrawTRN(out, { position.x, position.y + static_cast<int>(cel.frameHeight) - 1 }, cel.sprite, cel.frame, trn);
}
void Cl2DrawBlendedTRN(const Surface &out, Point position, CelSprite cel, int frame, uint8_t *trn);
// defined in scrollrt.cpp
extern int LightTableIndex;
/**
* @brief Blit CL2 sprite, and apply lighting, to the given buffer at the given coordinates
* @param out Output buffer
* @param position Target buffer coordinate
* @param cel CL2 buffer
* @param frame CL2 frame number
*/
inline void Cl2DrawLight(const Surface &out, Point position, CelSprite cel, int frame)
{
if (LightTableIndex != 0)
Cl2DrawTRN(out, position, cel, frame, &LightTables[LightTableIndex * 256]);
else
Cl2Draw(out, position, cel, frame);
}
inline void Cl2DrawLightBlended(const Surface &out, Point position, CelSprite cel, int frame)
{
Cl2DrawBlendedTRN(out, position, cel, frame, &LightTables[LightTableIndex * 256]);
}
/**
* Returns a pair of X coordinates containing the start (inclusive) and end (exclusive)
* of fully transparent columns in the sprite.
*/
std::pair<int, int> Cl2MeasureSolidHorizontalBounds(CelSprite cel, int frame = 0);
} // namespace devilution

234
Source/engine/render/cl2_render.cpp → Source/engine/render/clx_render.cpp

@ -1,13 +1,12 @@
/**
* @file cl2_render.cpp
* @file clx_render.cpp
*
* CL2 rendering.
*/
#include "cl2_render.hpp"
#include "clx_render.hpp"
#include <algorithm>
#include "engine/cel_header.hpp"
#include "engine/render/common_impl.h"
#include "engine/render/scrollrt.h"
#include "utils/attributes.h"
@ -60,45 +59,8 @@ BlitCommand Cl2GetBlitCommand(const uint8_t *src)
return BlitCommand { BlitType::Pixels, src + width, width, 0 };
}
/**
* @brief Blit CL2 sprite to the given buffer
* @param out Target buffer
* @param sx Target buffer coordinate
* @param sy Target buffer coordinate
* @param pRLEBytes CL2 pixel stream (run-length encoded)
* @param nDataSize Size of CL2 in bytes
* @param nWidth Width of sprite
*/
void Cl2Blit(const Surface &out, Point position, const byte *pRLEBytes, int nDataSize, int nWidth)
{
DoRenderBackwards<Cl2GetBlitCommand>(
out, position, reinterpret_cast<const uint8_t *>(pRLEBytes), nDataSize, nWidth, BlitDirect {});
}
/**
* @brief Blit CL2 sprite, and apply lighting, to the given buffer
* @param out Target buffer
* @param sx Target buffer coordinate
* @param sy Target buffer coordinate
* @param pRLEBytes CL2 pixel stream (run-length encoded)
* @param nDataSize Size of CL2 in bytes
* @param nWidth With of CL2 sprite
* @param pTable Light color table
*/
void Cl2BlitTRN(const Surface &out, Point position, const byte *pRLEBytes, int nDataSize, int nWidth, uint8_t *pTable)
{
DoRenderBackwards<Cl2GetBlitCommand>(
out, position, reinterpret_cast<const uint8_t *>(pRLEBytes), nDataSize, nWidth, BlitWithMap { pTable });
}
void Cl2BlitBlendedTRN(const Surface &out, Point position, const byte *pRLEBytes, int nDataSize, int nWidth, uint8_t *pTable)
{
DoRenderBackwards<Cl2GetBlitCommand>(
out, position, reinterpret_cast<const uint8_t *>(pRLEBytes), nDataSize, nWidth, BlitBlendedWithMap { pTable });
}
template <bool Fill, bool North, bool West, bool South, bool East, bool SkipColorIndexZero>
uint8_t *RenderCl2OutlinePixelsCheckFirstColumn(
uint8_t *RenderClxOutlinePixelsCheckFirstColumn(
uint8_t *dst, int dstPitch, int dstX,
const uint8_t *src, uint8_t width, uint8_t color)
{
@ -132,7 +94,7 @@ uint8_t *RenderCl2OutlinePixelsCheckFirstColumn(
}
template <bool Fill, bool North, bool West, bool South, bool East, bool SkipColorIndexZero>
uint8_t *RenderCl2OutlinePixelsCheckLastColumn(
uint8_t *RenderClxOutlinePixelsCheckLastColumn(
uint8_t *dst, int dstPitch, int dstX, int dstW,
const uint8_t *src, uint8_t width, uint8_t color)
{
@ -167,7 +129,7 @@ uint8_t *RenderCl2OutlinePixelsCheckLastColumn(
}
template <bool Fill, bool North, bool West, bool South, bool East, bool SkipColorIndexZero, bool CheckFirstColumn, bool CheckLastColumn>
uint8_t *RenderCl2OutlinePixels(
uint8_t *RenderClxOutlinePixels(
uint8_t *dst, int dstPitch, int dstX, int dstW,
const uint8_t *src, uint8_t width, uint8_t color)
{
@ -175,11 +137,11 @@ uint8_t *RenderCl2OutlinePixels(
return dst + width;
if (CheckFirstColumn && dstX <= 0) {
return RenderCl2OutlinePixelsCheckFirstColumn<Fill, North, West, South, East, SkipColorIndexZero>(
return RenderClxOutlinePixelsCheckFirstColumn<Fill, North, West, South, East, SkipColorIndexZero>(
dst, dstPitch, dstX, src, width, color);
}
if (CheckLastColumn && dstX + width >= dstW) {
return RenderCl2OutlinePixelsCheckLastColumn<Fill, North, West, South, East, SkipColorIndexZero>(
return RenderClxOutlinePixelsCheckLastColumn<Fill, North, West, South, East, SkipColorIndexZero>(
dst, dstPitch, dstX, dstW, src, width, color);
}
if (Fill) {
@ -192,7 +154,7 @@ uint8_t *RenderCl2OutlinePixels(
template <bool North, bool West, bool South, bool East, bool SkipColorIndexZero,
bool ClipWidth = false, bool CheckFirstColumn = false, bool CheckLastColumn = false>
const uint8_t *RenderCl2OutlineRowClipped( // NOLINT(readability-function-cognitive-complexity)
const uint8_t *RenderClxOutlineRowClipped( // NOLINT(readability-function-cognitive-complexity)
const Surface &out, Point position, const uint8_t *src, std::size_t srcWidth,
ClipX clipX, uint8_t color, SkipSize &skipSize)
{
@ -204,11 +166,11 @@ const uint8_t *RenderCl2OutlineRowClipped( // NOLINT(readability-function-cognit
const auto renderPixels = [&](bool fill, uint8_t w) {
if (fill) {
dst = RenderCl2OutlinePixels</*Fill=*/true, North, West, South, East, SkipColorIndexZero, CheckFirstColumn, CheckLastColumn>(
dst = RenderClxOutlinePixels</*Fill=*/true, North, West, South, East, SkipColorIndexZero, CheckFirstColumn, CheckLastColumn>(
dst, dstPitch, position.x, out.w(), src, w, color);
++src;
} else {
dst = RenderCl2OutlinePixels</*Fill=*/false, North, West, South, East, SkipColorIndexZero, CheckFirstColumn, CheckLastColumn>(
dst = RenderClxOutlinePixels</*Fill=*/false, North, West, South, East, SkipColorIndexZero, CheckFirstColumn, CheckLastColumn>(
dst, dstPitch, position.x, out.w(), src, w, color);
src += v;
}
@ -283,7 +245,7 @@ const uint8_t *RenderCl2OutlineRowClipped( // NOLINT(readability-function-cognit
}
template <bool SkipColorIndexZero>
void RenderCl2OutlineClippedY(const Surface &out, Point position, RenderSrcBackwards src, // NOLINT(readability-function-cognitive-complexity)
void RenderClxOutlineClippedY(const Surface &out, Point position, RenderSrcBackwards src, // NOLINT(readability-function-cognitive-complexity)
uint8_t color)
{
// Skip the bottom clipped lines.
@ -296,7 +258,7 @@ void RenderCl2OutlineClippedY(const Surface &out, Point position, RenderSrcBackw
if (position.y == dstHeight) {
// After-bottom line - can only draw north.
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false, SkipColorIndexZero>(
src.begin = RenderClxOutlineRowClipped</*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false, SkipColorIndexZero>(
out, position, src.begin, src.width, clipX, color, skipSize);
position.y -= static_cast<int>(skipSize.wholeLines);
}
@ -305,13 +267,13 @@ void RenderCl2OutlineClippedY(const Surface &out, Point position, RenderSrcBackw
if (position.y + 1 == dstHeight) {
// Bottom line - cannot draw south.
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true, SkipColorIndexZero>(
src.begin = RenderClxOutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true, SkipColorIndexZero>(
out, position, src.begin, src.width, clipX, color, skipSize);
position.y -= static_cast<int>(skipSize.wholeLines);
}
while (position.y > 0 && src.begin != src.end) {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero>(
src.begin = RenderClxOutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero>(
out, position, src.begin, src.width, clipX, color, skipSize);
position.y -= static_cast<int>(skipSize.wholeLines);
}
@ -319,7 +281,7 @@ void RenderCl2OutlineClippedY(const Surface &out, Point position, RenderSrcBackw
return;
if (position.y == 0) {
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero>(
src.begin = RenderClxOutlineRowClipped</*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero>(
out, position, src.begin, src.width, clipX, color, skipSize);
position.y -= static_cast<int>(skipSize.wholeLines);
}
@ -328,13 +290,13 @@ void RenderCl2OutlineClippedY(const Surface &out, Point position, RenderSrcBackw
if (position.y == -1) {
// Special case: the top of the sprite is 1px below the last line, render just the outline above.
RenderCl2OutlineRowClipped</*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false, SkipColorIndexZero>(
RenderClxOutlineRowClipped</*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false, SkipColorIndexZero>(
out, position, src.begin, src.width, clipX, color, skipSize);
}
}
template <bool SkipColorIndexZero>
void RenderCl2OutlineClippedXY(const Surface &out, Point position, RenderSrcBackwards src, // NOLINT(readability-function-cognitive-complexity)
void RenderClxOutlineClippedXY(const Surface &out, Point position, RenderSrcBackwards src, // NOLINT(readability-function-cognitive-complexity)
uint8_t color)
{
// Skip the bottom clipped lines.
@ -356,13 +318,13 @@ void RenderCl2OutlineClippedXY(const Surface &out, Point position, RenderSrcBack
if (position.y == dstHeight) {
// After-bottom line - can only draw north.
if (position.x <= 0) {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false, SkipColorIndexZero,
src.begin = RenderClxOutlineRowClipped</*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/true, /*CheckLastColumn=*/false>(out, position, src.begin, src.width, clipX, color, skipSize);
} else if (position.x + clipX.width >= out.w()) {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false, SkipColorIndexZero,
src.begin = RenderClxOutlineRowClipped</*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/false, /*CheckLastColumn=*/true>(out, position, src.begin, src.width, clipX, color, skipSize);
} else {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false, SkipColorIndexZero,
src.begin = RenderClxOutlineRowClipped</*North=*/true, /*West=*/false, /*South=*/false, /*East=*/false, SkipColorIndexZero,
/*ClipWidth=*/true>(out, position, src.begin, src.width, clipX, color, skipSize);
}
position.y -= static_cast<int>(skipSize.wholeLines);
@ -373,15 +335,15 @@ void RenderCl2OutlineClippedXY(const Surface &out, Point position, RenderSrcBack
if (position.y + 1 == dstHeight) {
// Bottom line - cannot draw south.
if (position.x <= 0) {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true, SkipColorIndexZero,
src.begin = RenderClxOutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/true, /*CheckLastColumn=*/false>(
out, position, src.begin, src.width, clipX, color, skipSize);
} else if (position.x + clipX.width >= out.w()) {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true, SkipColorIndexZero,
src.begin = RenderClxOutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/false, /*CheckLastColumn=*/true>(
out, position, src.begin, src.width, clipX, color, skipSize);
} else {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true, SkipColorIndexZero,
src.begin = RenderClxOutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/false, /*East=*/true, SkipColorIndexZero,
/*ClipWidth=*/true>(
out, position, src.begin, src.width, clipX, color, skipSize);
}
@ -390,21 +352,21 @@ void RenderCl2OutlineClippedXY(const Surface &out, Point position, RenderSrcBack
if (position.x <= 0) {
while (position.y > 0 && src.begin != src.end) {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero,
src.begin = RenderClxOutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/true, /*CheckLastColumn=*/false>(
out, position, src.begin, src.width, clipX, color, skipSize);
position.y -= static_cast<int>(skipSize.wholeLines);
}
} else if (position.x + clipX.width >= out.w()) {
while (position.y > 0 && src.begin != src.end) {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero,
src.begin = RenderClxOutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/false, /*CheckLastColumn=*/true>(
out, position, src.begin, src.width, clipX, color, skipSize);
position.y -= static_cast<int>(skipSize.wholeLines);
}
} else {
while (position.y > 0 && src.begin != src.end) {
src.begin = RenderCl2OutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero,
src.begin = RenderClxOutlineRowClipped</*North=*/true, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero,
/*ClipWidth=*/true>(
out, position, src.begin, src.width, clipX, color, skipSize);
position.y -= static_cast<int>(skipSize.wholeLines);
@ -415,15 +377,15 @@ void RenderCl2OutlineClippedXY(const Surface &out, Point position, RenderSrcBack
if (position.y == 0) {
if (position.x <= 0) {
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero,
src.begin = RenderClxOutlineRowClipped</*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/true, /*CheckLastColumn=*/false>(
out, position, src.begin, src.width, clipX, color, skipSize);
} else if (position.x + clipX.width >= out.w()) {
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero,
src.begin = RenderClxOutlineRowClipped</*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/false, /*CheckLastColumn=*/true>(
out, position, src.begin, src.width, clipX, color, skipSize);
} else {
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero,
src.begin = RenderClxOutlineRowClipped</*North=*/false, /*West=*/true, /*South=*/true, /*East=*/true, SkipColorIndexZero,
/*ClipWidth=*/true>(
out, position, src.begin, src.width, clipX, color, skipSize);
}
@ -435,78 +397,83 @@ void RenderCl2OutlineClippedXY(const Surface &out, Point position, RenderSrcBack
if (position.y == -1) {
// Before-top line - can only draw south.
if (position.x <= 0) {
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false, SkipColorIndexZero,
src.begin = RenderClxOutlineRowClipped</*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/true, /*CheckLastColumn=*/false>(out, position, src.begin, src.width, clipX, color, skipSize);
} else if (position.x + clipX.width >= out.w()) {
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false, SkipColorIndexZero,
src.begin = RenderClxOutlineRowClipped</*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false, SkipColorIndexZero,
/*ClipWidth=*/true, /*CheckFirstColumn=*/false, /*CheckLastColumn=*/true>(out, position, src.begin, src.width, clipX, color, skipSize);
} else {
src.begin = RenderCl2OutlineRowClipped</*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false, SkipColorIndexZero,
src.begin = RenderClxOutlineRowClipped</*North=*/false, /*West=*/false, /*South=*/true, /*East=*/false, SkipColorIndexZero,
/*ClipWidth=*/true>(out, position, src.begin, src.width, clipX, color, skipSize);
}
}
}
template <bool SkipColorIndexZero>
void RenderCl2Outline(const Surface &out, Point position, const uint8_t *src, std::size_t srcSize,
void RenderClxOutline(const Surface &out, Point position, const uint8_t *src, std::size_t srcSize,
std::size_t srcWidth, uint8_t color)
{
RenderSrcBackwards srcForBackwards { src, src + srcSize, static_cast<uint_fast16_t>(srcWidth) };
if (position.x > 0 && position.x + static_cast<int>(srcWidth) < static_cast<int>(out.w())) {
RenderCl2OutlineClippedY<SkipColorIndexZero>(out, position, srcForBackwards, color);
RenderClxOutlineClippedY<SkipColorIndexZero>(out, position, srcForBackwards, color);
} else {
RenderCl2OutlineClippedXY<SkipColorIndexZero>(out, position, srcForBackwards, color);
RenderClxOutlineClippedXY<SkipColorIndexZero>(out, position, srcForBackwards, color);
}
}
} // namespace
void Cl2ApplyTrans(byte *p, const std::array<uint8_t, 256> &ttbl, int numFrames)
void ClxApplyTrans(ClxSprite sprite, const uint8_t *trn)
{
assert(p != nullptr);
for (int i = 0; i < numFrames; ++i) {
constexpr int FrameHeaderSize = 10;
int nDataSize;
byte *dst = CelGetFrame(p, i, &nDataSize) + FrameHeaderSize;
nDataSize -= FrameHeaderSize;
while (nDataSize > 0) {
auto v = static_cast<uint8_t>(*dst++);
nDataSize--;
assert(nDataSize >= 0);
if (!IsCl2Opaque(v))
continue;
if (IsCl2OpaqueFill(v)) {
nDataSize--;
assert(nDataSize >= 0);
*dst = static_cast<byte>(ttbl[static_cast<uint8_t>(*dst)]);
// A bit of a hack but this is the only place in the code where we need mutable sprites.
auto *dst = const_cast<uint8_t *>(sprite.pixelData());
uint16_t remaining = sprite.pixelDataSize();
while (remaining != 0) {
uint8_t val = *dst++;
--remaining;
if (!IsCl2Opaque(val))
continue;
if (IsCl2OpaqueFill(val)) {
--remaining;
*dst = trn[*dst];
dst++;
} else {
val = GetCl2OpaquePixelsWidth(val);
remaining -= val;
while (val-- > 0) {
*dst = trn[*dst];
dst++;
} else {
v = GetCl2OpaquePixelsWidth(v);
nDataSize -= v;
assert(nDataSize >= 0);
while (v-- > 0) {
*dst = static_cast<byte>(ttbl[static_cast<uint8_t>(*dst)]);
dst++;
}
}
}
}
}
std::pair<int, int> Cl2MeasureSolidHorizontalBounds(CelSprite cel, int frame)
} // namespace
void ClxApplyTrans(ClxSpriteList list, const uint8_t *trn)
{
for (ClxSprite sprite : list) {
ClxApplyTrans(sprite, trn);
}
}
void ClxApplyTrans(ClxSpriteSheet sheet, const uint8_t *trn)
{
for (ClxSpriteList list : sheet) {
ClxApplyTrans(list, trn);
}
}
std::pair<int, int> ClxMeasureSolidHorizontalBounds(ClxSprite clx)
{
int nDataSize;
const byte *src = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
const auto *end = &src[nDataSize];
const int celWidth = cel.Width(frame);
const uint8_t *src = clx.pixelData();
const uint8_t *end = src + clx.pixelDataSize();
const uint16_t width = clx.width();
int xBegin = celWidth;
int xBegin = width;
int xEnd = 0;
int xCur = 0;
while (src < end) {
while (xCur < celWidth) {
auto val = static_cast<uint8_t>(*src++);
while (xCur < width) {
auto val = *src++;
if (!IsCl2Opaque(val)) {
xCur += val;
continue;
@ -522,60 +489,37 @@ std::pair<int, int> Cl2MeasureSolidHorizontalBounds(CelSprite cel, int frame)
xCur += val;
xEnd = std::max(xEnd, xCur);
}
while (xCur >= celWidth)
xCur -= celWidth;
if (xBegin == 0 && xEnd == celWidth)
while (xCur >= width)
xCur -= width;
if (xBegin == 0 && xEnd == width)
break;
}
return { xBegin, xEnd };
}
void Cl2Draw(const Surface &out, Point position, CelSprite cel, int frame)
void ClxDraw(const Surface &out, Point position, ClxSprite clx)
{
assert(frame >= 0);
int nDataSize;
const byte *pRLEBytes = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
Cl2Blit(out, position, pRLEBytes, nDataSize, cel.Width(frame));
DoRenderBackwards<Cl2GetBlitCommand>(out, position, clx.pixelData(), clx.pixelDataSize(), clx.width(), clx.height(), BlitDirect {});
}
void Cl2DrawOutline(const Surface &out, uint8_t col, Point position, CelSprite cel, int frame)
void ClxDrawTRN(const Surface &out, Point position, ClxSprite clx, const uint8_t *trn)
{
assert(frame >= 0);
int nDataSize;
const byte *src = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
RenderCl2Outline</*SkipColorIndexZero=*/true>(out, position, reinterpret_cast<const uint8_t *>(src), nDataSize, cel.Width(frame), col);
DoRenderBackwards<Cl2GetBlitCommand>(out, position, clx.pixelData(), clx.pixelDataSize(), clx.width(), clx.height(), BlitWithMap { trn });
}
void Cl2DrawOutlineSkipColorZero(const Surface &out, uint8_t col, Point position, CelSprite cel, int frame)
void ClxDrawBlendedTRN(const Surface &out, Point position, ClxSprite clx, const uint8_t *trn)
{
assert(frame >= 0);
int nDataSize;
const byte *src = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
RenderCl2Outline</*SkipColorIndexZero=*/true>(out, position, reinterpret_cast<const uint8_t *>(src), nDataSize, cel.Width(frame), col);
DoRenderBackwards<Cl2GetBlitCommand>(out, position, clx.pixelData(), clx.pixelDataSize(), clx.width(), clx.height(), BlitBlendedWithMap { trn });
}
void Cl2DrawTRN(const Surface &out, Point position, CelSprite cel, int frame, uint8_t *trn)
void ClxDrawOutline(const Surface &out, uint8_t col, Point position, ClxSprite clx)
{
assert(frame >= 0);
int nDataSize;
const byte *pRLEBytes = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
Cl2BlitTRN(out, position, pRLEBytes, nDataSize, cel.Width(frame), trn);
RenderClxOutline</*SkipColorIndexZero=*/false>(out, position, clx.pixelData(), clx.pixelDataSize(), clx.width(), col);
}
void Cl2DrawBlendedTRN(const Surface &out, Point position, CelSprite cel, int frame, uint8_t *trn)
void ClxDrawOutlineSkipColorZero(const Surface &out, uint8_t col, Point position, ClxSprite clx)
{
assert(frame >= 0);
int nDataSize;
const byte *pRLEBytes = CelGetFrameClipped(cel.Data(), frame, &nDataSize);
Cl2BlitBlendedTRN(out, position, pRLEBytes, nDataSize, cel.Width(frame), trn);
RenderClxOutline</*SkipColorIndexZero=*/true>(out, position, clx.pixelData(), clx.pixelDataSize(), clx.width(), col);
}
} // namespace devilution

115
Source/engine/render/clx_render.hpp

@ -0,0 +1,115 @@
/**
* @file clx_render.hpp
*
* CL2 rendering.
*/
#pragma once
#include <cstdint>
#include <array>
#include <utility>
#include "engine.h"
#include "engine/clx_sprite.hpp"
#include "engine/point.hpp"
#include "lighting.h"
namespace devilution {
/**
* @brief Apply the color swaps to a CLX sprite list;
*/
void ClxApplyTrans(ClxSpriteList list, const uint8_t *trn);
void ClxApplyTrans(ClxSpriteSheet sheet, const uint8_t *trn);
/**
* @brief Blit CL2 sprite, to the back buffer at the given coordianates
* @param out Output buffer
* @param position Target buffer coordinate
* @param clx CLX frame
* @param frame CL2 frame number
*/
void ClxDraw(const Surface &out, Point position, ClxSprite clx);
/**
* @brief Same as ClxDraw but position.y is the top of the sprite instead of the bottom.
*/
inline void RenderClxSprite(const Surface &out, ClxSprite clx, Point position)
{
ClxDraw(out, { position.x, position.y + static_cast<int>(clx.height()) - 1 }, clx);
}
/**
* @brief Blit a solid colder shape one pixel larger than the given sprite shape, to the given buffer at the given coordinates
* @param col Color index from current palette
* @param out Output buffer
* @param position Target buffer coordinate
* @param clx CLX frame
*/
void ClxDrawOutline(const Surface &out, uint8_t col, Point position, ClxSprite clx);
/**
* @brief Same as `ClxDrawOutline` but considers colors with index 0 (usually shadows) to be transparent.
*
* @param col Color index from current palette
* @param out Output buffer
* @param position Target buffer coordinate
* @param clx CLX frame
*/
void ClxDrawOutlineSkipColorZero(const Surface &out, uint8_t col, Point position, ClxSprite clx);
/**
* @brief Blit CL2 sprite, and apply given TRN to the given buffer at the given coordinates
* @param out Output buffer
* @param position Target buffer coordinate
* @param clx CLX frame
* @param trn TRN to use
*/
void ClxDrawTRN(const Surface &out, Point position, ClxSprite clx, const uint8_t *trn);
/**
* @brief Same as ClxDrawTRN but position.y is the top of the sprite instead of the bottom.
*/
inline void RenderClxSpriteWithTRN(const Surface &out, ClxSprite clx, Point position, const uint8_t *trn)
{
ClxDrawTRN(out, { position.x, position.y + static_cast<int>(clx.height()) - 1 }, clx, trn);
}
void ClxDrawBlendedTRN(const Surface &out, Point position, ClxSprite clx, const uint8_t *trn);
// defined in scrollrt.cpp
extern int LightTableIndex;
/**
* @brief Blit CL2 sprite, and apply lighting, to the given buffer at the given coordinates
* @param out Output buffer
* @param position Target buffer coordinate
* @param clx CLX frame
*/
inline void ClxDrawLight(const Surface &out, Point position, ClxSprite clx)
{
if (LightTableIndex != 0)
ClxDrawTRN(out, position, clx, &LightTables[LightTableIndex * 256]);
else
ClxDraw(out, position, clx);
}
/**
* @brief Blit CL2 sprite, and apply lighting and transparency blending, to the given buffer at the given coordinates
* @param out Output buffer
* @param position Target buffer coordinate
* @param clx CLX frame
*/
inline void ClxDrawLightBlended(const Surface &out, Point position, ClxSprite clx)
{
ClxDrawBlendedTRN(out, position, clx, &LightTables[LightTableIndex * 256]);
}
/**
* Returns a pair of X coordinates containing the start (inclusive) and end (exclusive)
* of fully transparent columns in the sprite.
*/
std::pair<int, int> ClxMeasureSolidHorizontalBounds(ClxSprite clx);
} // namespace devilution

13
Source/engine/render/common_impl.h

@ -38,13 +38,6 @@ struct RenderSrcBackwards {
uint_fast16_t width;
};
// Source data for rendering forwards: first line of input -> first line of output.
struct RenderSrcForwards {
const uint8_t *begin;
uint_fast16_t width;
uint_fast16_t height;
};
struct SkipSize {
int_fast16_t wholeLines;
int_fast16_t xOffset;
@ -198,9 +191,11 @@ void DoRenderBackwardsClipXY(
template <GetBlitCommandFn GetBlitCommand, typename BlitFn>
void DoRenderBackwards(
const Surface &out, Point position, const uint8_t *src, size_t srcSize, unsigned srcWidth,
BlitFn &&blitFn)
const Surface &out, Point position, const uint8_t *src, size_t srcSize,
unsigned srcWidth, unsigned srcHeight, BlitFn &&blitFn)
{
if (position.y < 0 || position.y + 1 >= static_cast<int>(out.h() + srcHeight))
return;
const ClipX clipX = CalculateClipX(position.x, srcWidth, out);
if (clipX.width <= 0)
return;

177
Source/engine/render/scrollrt.cpp

@ -13,7 +13,7 @@
#include "dead.h"
#include "doom.h"
#include "engine/dx.h"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/dun_render.hpp"
#include "engine/render/text_render.hpp"
#include "engine/trn.hpp"
@ -320,25 +320,14 @@ void DrawMissilePrivate(const Surface &out, const Missile &missile, Point target
if (missile._miPreFlag != pre || !missile._miDrawFlag)
return;
if (missile._miAnimData == nullptr) {
Log("Draw Missile 2 type {}: NULL Cel Buffer", static_cast<int>(missile._mitype));
return;
}
int nCel = missile._miAnimFrame - 1;
const uint32_t frames = LoadLE32(missile._miAnimData);
if (nCel < 0 || frames > 50 || nCel >= static_cast<int>(frames)) {
Log("Draw Missile 2: frame {} of {}, missile type {}", nCel, frames, static_cast<int>(missile._mitype));
return;
}
const Point missileRenderPosition { targetBufferPosition + missile.position.offsetForRendering - Displacement { missile._miAnimWidth2, 0 } };
CelSprite cel { missile._miAnimData, missile._miAnimWidth };
const ClxSprite sprite = (*missile._miAnimData)[missile._miAnimFrame - 1];
if (missile._miUniqTrans != 0)
Cl2DrawTRN(out, missileRenderPosition, cel, nCel, Monsters[missile._misource].uniqueMonsterTRN.get());
ClxDrawTRN(out, missileRenderPosition, sprite, Monsters[missile._misource].uniqueMonsterTRN.get());
else if (missile._miLightFlag)
Cl2DrawLight(out, missileRenderPosition, cel, nCel);
ClxDrawLight(out, missileRenderPosition, sprite);
else
Cl2Draw(out, missileRenderPosition, cel, nCel);
ClxDraw(out, missileRenderPosition, sprite);
}
/**
@ -365,7 +354,7 @@ void DrawMissile(const Surface &out, Point tilePosition, Point targetBufferPosit
*/
void DrawMonster(const Surface &out, Point tilePosition, Point targetBufferPosition, const Monster &monster)
{
if (!monster.animInfo.celSprite) {
if (!monster.animInfo.sprites) {
Log("Draw Monster \"{}\": NULL Cel Buffer", monster.name());
return;
}
@ -431,23 +420,10 @@ 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 < 0 || frames > 50 || nCel >= static_cast<int>(frames)) {
Log(
"Draw Monster \"{}\" {}: facing {}, frame {} of {}",
monster.name(),
getMonsterModeDisplayName(monster.mode),
DirectionToString(monster.direction),
nCel,
frames);
return;
}
const auto &cel = *monster.animInfo.celSprite;
const ClxSprite sprite = monster.animInfo.currentSprite();
if (!IsTileLit(tilePosition)) {
Cl2DrawTRN(out, targetBufferPosition, cel, nCel, GetInfravisionTRN());
ClxDrawTRN(out, targetBufferPosition, sprite, GetInfravisionTRN());
return;
}
uint8_t *trn = nullptr;
@ -458,9 +434,9 @@ void DrawMonster(const Surface &out, Point tilePosition, Point targetBufferPosit
if (MyPlayer->_pInfraFlag && LightTableIndex > 8)
trn = GetInfravisionTRN();
if (trn != nullptr)
Cl2DrawTRN(out, targetBufferPosition, cel, nCel, trn);
ClxDrawTRN(out, targetBufferPosition, sprite, trn);
else
Cl2DrawLight(out, targetBufferPosition, cel, nCel);
ClxDrawLight(out, targetBufferPosition, sprite);
}
/**
@ -470,19 +446,19 @@ void DrawPlayerIconHelper(const Surface &out, missile_graphic_id missileGraphicI
{
position.x -= MissileSpriteData[missileGraphicId].animWidth2;
const CelSprite cel = MissileSpriteData[missileGraphicId].Sprite();
const ClxSprite sprite = (*MissileSpriteData[missileGraphicId].sprites).list()[0];
if (!lighting) {
Cl2Draw(out, position, cel, 0);
ClxDraw(out, position, sprite);
return;
}
if (infraVision) {
Cl2DrawTRN(out, position, cel, 0, GetInfravisionTRN());
ClxDrawTRN(out, position, sprite, GetInfravisionTRN());
return;
}
Cl2DrawLight(out, position, cel, 0);
ClxDrawLight(out, position, sprite);
}
/**
@ -513,48 +489,21 @@ void DrawPlayer(const Surface &out, const Player &player, Point tilePosition, Po
return;
}
OptionalCelSprite sprite = player.AnimInfo.celSprite;
int nCel = player.AnimInfo.getFrameToUseForRendering();
const ClxSprite sprite = player.previewCelSprite ? *player.previewCelSprite : player.AnimInfo.currentSprite();
if (player.previewCelSprite) {
sprite = player.previewCelSprite;
nCel = 0;
}
if (!sprite) {
Log("Drawing player {} \"{}\": NULL CelSprite", std::distance(const_cast<const Player *>(&Players[0]), &player), player._pName);
return;
}
Point spriteBufferPosition = targetBufferPosition - Displacement { CalculateWidth2(sprite ? sprite->Width() : 96), 0 };
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];
Log(
"Drawing player {} \"{}\" {}: facing {}, frame {} of {}",
std::distance(const_cast<const Player *>(&Players[0]), &player),
player._pName,
szMode,
DirectionToString(player._pdir),
nCel,
frames);
return;
}
Point spriteBufferPosition = targetBufferPosition - Displacement { CalculateWidth2(sprite.width()), 0 };
if (pcursplr >= 0 && pcursplr < MAX_PLRS && &player == &Players[pcursplr])
Cl2DrawOutlineSkipColorZero(out, 165, spriteBufferPosition, *sprite, nCel);
ClxDrawOutlineSkipColorZero(out, 165, spriteBufferPosition, sprite);
if (&player == MyPlayer) {
Cl2Draw(out, spriteBufferPosition, *sprite, nCel);
ClxDraw(out, spriteBufferPosition, sprite);
DrawPlayerIcons(out, player, targetBufferPosition, false);
return;
}
if (!IsTileLit(tilePosition) || (MyPlayer->_pInfraFlag && LightTableIndex > 8)) {
Cl2DrawTRN(out, spriteBufferPosition, *sprite, nCel, GetInfravisionTRN());
ClxDrawTRN(out, spriteBufferPosition, sprite, GetInfravisionTRN());
DrawPlayerIcons(out, player, targetBufferPosition, true);
return;
}
@ -565,7 +514,7 @@ void DrawPlayer(const Surface &out, const Player &player, Point tilePosition, Po
else
LightTableIndex -= 5;
Cl2DrawLight(out, spriteBufferPosition, *sprite, nCel);
ClxDrawLight(out, spriteBufferPosition, sprite);
DrawPlayerIcons(out, player, targetBufferPosition, false);
LightTableIndex = l;
@ -613,34 +562,22 @@ void DrawObject(const Surface &out, Point tilePosition, Point targetBufferPositi
return;
}
Point screenPosition = targetBufferPosition - Displacement { CalculateWidth2(objectToDraw._oAnimWidth), 0 };
const ClxSprite sprite = (*objectToDraw._oAnimData)[objectToDraw._oAnimFrame - 1];
Point screenPosition = targetBufferPosition - Displacement { CalculateWidth2(sprite.width()), 0 };
if (objectToDraw.position != tilePosition) {
// drawing a large or offset object, calculate the correct position for the center of the sprite
Displacement worldOffset = objectToDraw.position - tilePosition;
screenPosition -= worldOffset.worldToScreen();
}
byte *pCelBuff = objectToDraw._oAnimData;
if (pCelBuff == nullptr) {
Log("Draw Object type {}: NULL Cel Buffer", static_cast<int>(objectToDraw._otype));
return;
}
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, static_cast<int>(objectToDraw._otype));
return;
}
CelSprite cel { objectToDraw._oAnimData, objectToDraw._oAnimWidth };
if (&objectToDraw == ObjectUnderCursor) {
Cl2DrawOutlineSkipColorZero(out, 194, screenPosition, cel, nCel);
ClxDrawOutlineSkipColorZero(out, 194, screenPosition, sprite);
}
if (objectToDraw._oLight) {
Cl2DrawLight(out, screenPosition, cel, nCel);
ClxDrawLight(out, screenPosition, sprite);
} else {
Cl2Draw(out, screenPosition, cel, nCel);
ClxDraw(out, screenPosition, sprite);
}
}
@ -716,25 +653,13 @@ void DrawItem(const Surface &out, Point tilePosition, Point targetBufferPosition
if (item._iPostDraw == pre)
return;
OptionalCelSprite cel = item.AnimInfo.celSprite;
if (!cel) {
Log("Draw Item \"{}\" 1: NULL CelSprite", item._iIName);
return;
}
int nCel = item.AnimInfo.getFrameToUseForRendering();
const uint32_t frames = LoadLE32(cel->Data());
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;
}
int px = targetBufferPosition.x - CalculateWidth2(cel->Width());
const ClxSprite sprite = item.AnimInfo.currentSprite();
int px = targetBufferPosition.x - CalculateWidth2(sprite.width());
const Point position { px, targetBufferPosition.y };
if (bItem - 1 == pcursitem || AutoMapShowItems) {
Cl2DrawOutlineSkipColorZero(out, GetOutlineColor(item, false), position, *cel, nCel);
ClxDrawOutlineSkipColorZero(out, GetOutlineColor(item, false), position, sprite);
}
Cl2DrawLight(out, position, *cel, nCel);
ClxDrawLight(out, position, sprite);
if (item.AnimInfo.currentFrame == item.AnimInfo.numberOfFrames - 1 || item._iCurs == ICURS_MAGIC_ROCK)
AddItemToLabelQueue(bItem - 1, px, targetBufferPosition.y);
}
@ -757,12 +682,11 @@ void DrawMonsterHelper(const Surface &out, Point tilePosition, Point targetBuffe
auto &towner = Towners[mi];
int px = targetBufferPosition.x - CalculateWidth2(towner._tAnimWidth);
const Point position { px, targetBufferPosition.y };
const CelSprite sprite = towner.Sprite();
const ClxSprite sprite = towner.currentSprite();
if (mi == pcursmonst) {
Cl2DrawOutlineSkipColorZero(out, 166, position, sprite, towner._tAnimFrame);
ClxDrawOutlineSkipColorZero(out, 166, position, sprite);
}
assert(towner._tAnimData);
Cl2Draw(out, position, sprite, towner._tAnimFrame);
ClxDraw(out, position, sprite);
return;
}
@ -779,7 +703,7 @@ void DrawMonsterHelper(const Surface &out, Point tilePosition, Point targetBuffe
return;
}
CelSprite cel = *monster.animInfo.celSprite;
const ClxSprite sprite = monster.animInfo.currentSprite();
Displacement offset = monster.position.offset;
if (monster.isWalking()) {
@ -795,9 +719,9 @@ void DrawMonsterHelper(const Surface &out, Point tilePosition, Point targetBuffe
return;
}
const Point monsterRenderPosition { targetBufferPosition + offset - Displacement { CalculateWidth2(cel.Width()), 0 } };
const Point monsterRenderPosition { targetBufferPosition + offset - Displacement { CalculateWidth2(sprite.width()), 0 } };
if (mi == pcursmonst) {
Cl2DrawOutlineSkipColorZero(out, 233, monsterRenderPosition, cel, monster.animInfo.getFrameToUseForRendering());
ClxDrawOutlineSkipColorZero(out, 233, monsterRenderPosition, sprite);
}
DrawMonster(out, tilePosition, monsterRenderPosition, monster);
}
@ -844,7 +768,7 @@ void DrawDungeon(const Surface &out, Point tilePosition, Point targetBufferPosit
#ifdef _DEBUG
if (DebugVision && IsTileLit(tilePosition)) {
Cl2Draw(out, targetBufferPosition, CelSprite { *pSquareCel }, 1);
ClxDraw(out, targetBufferPosition, (*pSquareCel)[0]);
}
#endif
@ -854,21 +778,14 @@ void DrawDungeon(const Surface &out, Point tilePosition, Point targetBufferPosit
if (LightTableIndex < LightsMax && bDead != 0) {
do {
Corpse *pDeadGuy = &Corpses[(bDead & 0x1F) - 1];
const Point position { targetBufferPosition.x - CalculateWidth2(pDeadGuy->width), targetBufferPosition.y };
const byte *pCelBuff = pDeadGuy->data[(bDead >> 5) & 7];
assert(pCelBuff != nullptr);
const uint32_t frames = LoadLE32(pCelBuff);
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;
}
if (pDeadGuy->translationPaletteIndex != 0) {
uint8_t *trn = Monsters[pDeadGuy->translationPaletteIndex - 1].uniqueMonsterTRN.get();
Cl2DrawTRN(out, position, CelSprite(pCelBuff, pDeadGuy->width), nCel, trn);
Corpse &corpse = Corpses[(bDead & 0x1F) - 1];
const Point position { targetBufferPosition.x - CalculateWidth2(corpse.width), targetBufferPosition.y };
const ClxSprite sprite = corpse.spritesForDirection(static_cast<Direction>((bDead >> 5) & 7))[corpse.frame];
if (corpse.translationPaletteIndex != 0) {
const uint8_t *trn = Monsters[corpse.translationPaletteIndex - 1].uniqueMonsterTRN.get();
ClxDrawTRN(out, position, sprite, trn);
} else {
Cl2DrawLight(out, position, CelSprite(pCelBuff, pDeadGuy->width), nCel);
ClxDrawLight(out, position, sprite);
}
} while (false);
}
@ -899,9 +816,9 @@ void DrawDungeon(const Surface &out, Point tilePosition, Point targetBufferPosit
}
#endif
if (cel_transparency_active) {
Cl2DrawLightBlended(out, targetBufferPosition, CelSprite { *pSpecialCels }, bArch - 1);
ClxDrawLightBlended(out, targetBufferPosition, (*pSpecialCels)[bArch - 1]);
} else {
Cl2DrawLight(out, targetBufferPosition, CelSprite { *pSpecialCels }, bArch - 1);
ClxDrawLight(out, targetBufferPosition, (*pSpecialCels)[bArch - 1]);
}
#ifdef _DEBUG
if ((SDL_GetModState() & KMOD_ALT) != 0) {
@ -916,7 +833,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) {
Cl2Draw(out, targetBufferPosition + Displacement { 0, -TILE_HEIGHT }, CelSprite { *pSpecialCels }, bArch - 1);
ClxDraw(out, targetBufferPosition + Displacement { 0, -TILE_HEIGHT }, (*pSpecialCels)[bArch - 1]);
}
}
}

28
Source/engine/render/text_render.cpp

@ -21,7 +21,7 @@
#include "engine/load_pcx.hpp"
#include "engine/palette.h"
#include "engine/point.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "utils/display.h"
#include "utils/language.h"
#include "utils/sdl_compat.h"
@ -30,14 +30,14 @@
namespace devilution {
OptionalOwnedCelSprite pSPentSpn2Cels;
OptionalOwnedClxSpriteList pSPentSpn2Cels;
namespace {
constexpr char32_t ZWSP = U'\u200B'; // Zero-width space
using Font = const OwnedCelSpriteSheetWithFrameHeight;
std::unordered_map<uint32_t, std::optional<OwnedCelSpriteSheetWithFrameHeight>> Fonts;
using Font = const OwnedClxSpriteList;
std::unordered_map<uint32_t, OptionalOwnedClxSpriteList> Fonts;
std::unordered_map<uint32_t, std::array<uint8_t, 256>> FontKerns;
std::array<int, 6> FontSizes = { 12, 24, 30, 42, 46, 22 };
@ -180,7 +180,7 @@ uint32_t GetFontId(GameFontTables size, uint16_t row)
return (size << 16) | row;
}
const OwnedCelSpriteSheetWithFrameHeight *LoadFont(GameFontTables size, text_color color, uint16_t row)
const OwnedClxSpriteList *LoadFont(GameFontTables size, text_color color, uint16_t row)
{
if (ColorTranslations[color] != nullptr && !ColorTranslationsData[color]) {
ColorTranslationsData[color].emplace();
@ -196,9 +196,9 @@ const OwnedCelSpriteSheetWithFrameHeight *LoadFont(GameFontTables size, text_col
char path[32];
GetFontPath(size, row, "pcx", &path[0]);
std::optional<OwnedCelSpriteSheetWithFrameHeight> &font = Fonts[fontId];
OptionalOwnedClxSpriteList &font = Fonts[fontId];
constexpr unsigned NumFrames = 256;
font = LoadPcxSpriteSheetAsCl2(path, NumFrames, /*transparentColor=*/1);
font = LoadPcxSpriteList(path, NumFrames, /*transparentColor=*/1);
if (!font) {
LogError("Error loading font: {}", path);
return nullptr;
@ -207,13 +207,13 @@ const OwnedCelSpriteSheetWithFrameHeight *LoadFont(GameFontTables size, text_col
return &(*font);
}
void DrawFont(const Surface &out, Point position, const OwnedCelSpriteSheetWithFrameHeight *font, text_color color, int frame)
void DrawFont(const Surface &out, Point position, const OwnedClxSpriteList *font, text_color color, int frame)
{
CelFrameWithHeight glyph = font->sprite(frame);
ClxSprite glyph = (*font)[frame];
if (ColorTranslationsData[color]) {
RenderCl2SpriteWithTRN(out, glyph, position, ColorTranslationsData[color]->data());
RenderClxSpriteWithTRN(out, glyph, position, ColorTranslationsData[color]->data());
} else {
RenderCl2Sprite(out, glyph, position);
RenderClxSprite(out, glyph, position);
}
}
@ -404,7 +404,7 @@ int DoDrawString(const Surface &out, string_view text, Rectangle rect, Point &ch
void LoadSmallSelectionSpinner()
{
pSPentSpn2Cels = LoadCelAsCl2("Data\\PentSpn2.CEL", 12);
pSPentSpn2Cels = LoadCel("Data\\PentSpn2.CEL", 12);
}
void UnloadFonts(GameFontTables size, text_color color)
@ -651,7 +651,7 @@ uint32_t DrawString(const Surface &out, string_view text, const Rectangle &rect,
const int bytesDrawn = DoDrawString(out, text, rect, characterPosition, spacing, lineHeight, lineWidth, rightMargin, bottomMargin, flags, size, color);
if (HasAnyOf(flags, UiFlags::PentaCursor)) {
Cl2Draw(out, characterPosition + Displacement { 0, lineHeight - BaseLineOffset[size] }, CelSprite { *pSPentSpn2Cels }, PentSpn2Spin());
ClxDraw(out, characterPosition + Displacement { 0, lineHeight - BaseLineOffset[size] }, (*pSPentSpn2Cels)[PentSpn2Spin()]);
} else if (HasAnyOf(flags, UiFlags::TextCursor) && GetAnimationFrame(2, 500) != 0) {
DrawFont(out, characterPosition, LoadFont(size, color, 0), color, '|');
}
@ -759,7 +759,7 @@ void DrawStringWithColors(const Surface &out, string_view fmt, DrawStringFormatA
}
if (HasAnyOf(flags, UiFlags::PentaCursor)) {
Cl2Draw(out, characterPosition + Displacement { 0, lineHeight - BaseLineOffset[size] }, CelSprite { *pSPentSpn2Cels }, PentSpn2Spin());
ClxDraw(out, characterPosition + Displacement { 0, lineHeight - BaseLineOffset[size] }, (*pSPentSpn2Cels)[PentSpn2Spin()]);
} else if (HasAnyOf(flags, UiFlags::TextCursor) && GetAnimationFrame(2, 500) != 0) {
DrawFont(out, characterPosition, LoadFont(size, color, 0), color, '|');
}

4
Source/engine/render/text_render.hpp

@ -14,7 +14,7 @@
#include "DiabloUI/ui_flags.hpp"
#include "engine.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/rectangle.hpp"
#include "utils/stdcompat/optional.hpp"
#include "utils/stdcompat/string_view.hpp"
@ -123,7 +123,7 @@ private:
*
* Also used in the stores and the quest log.
*/
extern OptionalOwnedCelSprite pSPentSpn2Cels;
extern OptionalOwnedClxSpriteList pSPentSpn2Cels;
void LoadSmallSelectionSpinner();

19
Source/error.cpp

@ -9,7 +9,7 @@
#include "error.h"
#include "DiabloUI/ui_flags.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/text_render.hpp"
#include "panels/info_box.hpp"
#include "stores.h"
@ -144,22 +144,21 @@ void DrawDiabloMsg(const Surface &out)
auto &uiRectanglePosition = GetUIRectangle().position;
int dialogStartY = ((gnScreenHeight - GetMainPanel().size.height) / 2) - (ErrorWindowHeight / 2) + 9;
CelSprite sprite { *pSTextSlidCels };
Cl2Draw(out, { uiRectanglePosition.x + 101, dialogStartY }, sprite, 0);
Cl2Draw(out, { uiRectanglePosition.x + 101, dialogStartY + ErrorWindowHeight - 6 }, sprite, 1);
Cl2Draw(out, { uiRectanglePosition.x + 527, dialogStartY + ErrorWindowHeight - 6 }, sprite, 2);
Cl2Draw(out, { uiRectanglePosition.x + 527, dialogStartY }, sprite, 3);
ClxDraw(out, { uiRectanglePosition.x + 101, dialogStartY }, (*pSTextSlidCels)[0]);
ClxDraw(out, { uiRectanglePosition.x + 101, dialogStartY + ErrorWindowHeight - 6 }, (*pSTextSlidCels)[1]);
ClxDraw(out, { uiRectanglePosition.x + 527, dialogStartY + ErrorWindowHeight - 6 }, (*pSTextSlidCels)[2]);
ClxDraw(out, { uiRectanglePosition.x + 527, dialogStartY }, (*pSTextSlidCels)[3]);
int sx = uiRectanglePosition.x + 109;
for (int i = 0; i < 35; i++) {
Cl2Draw(out, { sx, dialogStartY }, sprite, 4);
Cl2Draw(out, { sx, dialogStartY + ErrorWindowHeight - 6 }, sprite, 6);
ClxDraw(out, { sx, dialogStartY }, (*pSTextSlidCels)[4]);
ClxDraw(out, { sx, dialogStartY + ErrorWindowHeight - 6 }, (*pSTextSlidCels)[6]);
sx += 12;
}
int drawnYborder = 12;
while ((drawnYborder + 12) < ErrorWindowHeight) {
Cl2Draw(out, { uiRectanglePosition.x + 101, dialogStartY + drawnYborder }, sprite, 5);
Cl2Draw(out, { uiRectanglePosition.x + 527, dialogStartY + drawnYborder }, sprite, 7);
ClxDraw(out, { uiRectanglePosition.x + 101, dialogStartY + drawnYborder }, (*pSTextSlidCels)[5]);
ClxDraw(out, { uiRectanglePosition.x + 527, dialogStartY + drawnYborder }, (*pSTextSlidCels)[7]);
drawnYborder += 12;
}

36
Source/gmenu.cpp

@ -10,9 +10,9 @@
#include "controls/axis_direction.h"
#include "controls/controller_motion.h"
#include "engine.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/load_cel.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/text_render.hpp"
#include "miniwin/misc_msg.h"
#include "options.h"
@ -25,10 +25,10 @@ namespace devilution {
namespace {
OptionalOwnedCelSprite optbar_cel;
OptionalOwnedCelSprite PentSpin_cel;
OptionalOwnedCelSprite option_cel;
OptionalOwnedCelSprite sgpLogo;
OptionalOwnedClxSpriteList optbar_cel;
OptionalOwnedClxSpriteList PentSpin_cel;
OptionalOwnedClxSpriteList option_cel;
OptionalOwnedClxSpriteList sgpLogo;
bool mouseNavigation;
TMenuItem *sgpCurrItem;
int LogoAnim_tick;
@ -108,21 +108,21 @@ void GmenuDrawMenuItem(const Surface &out, TMenuItem *pItem, int y)
if ((pItem->dwFlags & GMENU_SLIDER) != 0) {
int uiPositionX = GetUIRectangle().position.x;
int x = 16 + w / 2;
Cl2Draw(out, { x + uiPositionX, y + 40 }, CelSprite { *optbar_cel }, 0);
ClxDraw(out, { x + uiPositionX, 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 + uiPositionX, y + 38, pos + 13, 28);
Cl2Draw(out, { x + 2 + pos + uiPositionX, y + 38 }, CelSprite { *option_cel }, 0);
ClxDraw(out, { x + 2 + pos + uiPositionX, y + 38 }, (*option_cel)[0]);
}
int x = (gnScreenWidth - w) / 2;
UiFlags style = (pItem->dwFlags & GMENU_ENABLED) != 0 ? UiFlags::ColorGold : UiFlags::ColorBlack;
DrawString(out, _(pItem->pszStr), Point { x, y }, style | UiFlags::FontSize46, 2);
if (pItem == sgpCurrItem) {
CelSprite sprite { *PentSpin_cel };
Cl2Draw(out, { x - 54, y + 51 }, sprite, PentSpn2Spin());
Cl2Draw(out, { x + 4 + w, y + 51 }, sprite, PentSpn2Spin());
const ClxSprite sprite = (*PentSpin_cel)[PentSpn2Spin()];
ClxDraw(out, { x - 54, y + 51 }, sprite);
ClxDraw(out, { x + 4 + w, y + 51 }, sprite);
}
}
@ -195,12 +195,12 @@ void gmenu_init_menu()
return;
if (gbIsHellfire)
sgpLogo = LoadCelAsCl2("Data\\hf_logo3.CEL", 430);
sgpLogo = LoadCel("Data\\hf_logo3.CEL", 430);
else
sgpLogo = LoadCelAsCl2("Data\\Diabsmal.CEL", 296);
PentSpin_cel = LoadCelAsCl2("Data\\PentSpin.CEL", 48);
option_cel = LoadCelAsCl2("Data\\option.CEL", 27);
optbar_cel = LoadCelAsCl2("Data\\optbar.CEL", 287);
sgpLogo = LoadCel("Data\\Diabsmal.CEL", 296);
PentSpin_cel = LoadCel("Data\\PentSpin.CEL", 48);
option_cel = LoadCel("Data\\option.CEL", 27);
optbar_cel = LoadCel("Data\\optbar.CEL", 287);
}
bool gmenu_is_active()
@ -246,8 +246,8 @@ void gmenu_draw(const Surface &out)
}
}
int uiPositionY = GetUIRectangle().position.y;
CelSprite sprite { *sgpLogo };
Cl2Draw(out, { (gnScreenWidth - sprite.Width()) / 2, 102 + uiPositionY }, sprite, LogoAnim_frame);
const ClxSprite sprite = (*sgpLogo)[LogoAnim_frame];
ClxDraw(out, { (gnScreenWidth - sprite.width()) / 2, 102 + uiPositionY }, sprite);
int y = 110 + uiPositionY;
TMenuItem *i = sgpCurrentMenu;
if (sgpCurrentMenu->fnMenu != nullptr) {

22
Source/interfac.cpp

@ -10,12 +10,12 @@
#include "control.h"
#include "engine.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/dx.h"
#include "engine/load_cel.hpp"
#include "engine/load_pcx.hpp"
#include "engine/palette.h"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "hwcursor.hpp"
#include "init.h"
#include "loadsave.h"
@ -31,7 +31,7 @@ namespace {
constexpr uint32_t MaxProgress = 534;
OptionalOwnedCelSprite sgpBackCel;
OptionalOwnedClxSpriteList sgpBackCel;
bool IsProgress;
uint32_t sgdwProgress;
@ -42,7 +42,7 @@ const uint8_t BarColor[3] = { 138, 43, 254 };
/** The screen position of the top left corner of the progress bar. */
const int BarPos[3][2] = { { 53, 37 }, { 53, 421 }, { 53, 37 } };
std::optional<OwnedCelSpriteWithFrameHeight> ArtCutsceneWidescreen;
OptionalOwnedClxSpriteList ArtCutsceneWidescreen;
Cutscenes PickCutscene(interface_mode uMsg)
{
@ -102,7 +102,7 @@ void LoadCutsceneBackground(interface_mode uMsg)
switch (PickCutscene(uMsg)) {
case CutStart:
ArtCutsceneWidescreen = LoadPcxAsCl2("gendata\\cutstartw.pcx");
ArtCutsceneWidescreen = LoadPcx("gendata\\cutstartw.pcx");
celPath = "Gendata\\Cutstart.cel";
palPath = "Gendata\\Cutstart.pal";
progress_id = 1;
@ -143,13 +143,13 @@ void LoadCutsceneBackground(interface_mode uMsg)
progress_id = 1;
break;
case CutPortal:
ArtCutsceneWidescreen = LoadPcxAsCl2("gendata\\cutportlw.pcx");
ArtCutsceneWidescreen = LoadPcx("gendata\\cutportlw.pcx");
celPath = "Gendata\\Cutportl.cel";
palPath = "Gendata\\Cutportl.pal";
progress_id = 1;
break;
case CutPortalRed:
ArtCutsceneWidescreen = LoadPcxAsCl2("gendata\\cutportrw.pcx");
ArtCutsceneWidescreen = LoadPcx("gendata\\cutportrw.pcx");
celPath = "Gendata\\Cutportr.cel";
palPath = "Gendata\\Cutportr.pal";
progress_id = 1;
@ -162,7 +162,7 @@ void LoadCutsceneBackground(interface_mode uMsg)
}
assert(!sgpBackCel);
sgpBackCel = LoadCelAsCl2(celPath, 640);
sgpBackCel = LoadCel(celPath, 640);
LoadPalette(palPath);
sgdwProgress = 0;
@ -179,10 +179,10 @@ void DrawCutsceneBackground()
const Rectangle &uiRectangle = GetUIRectangle();
const Surface &out = GlobalBackBuffer();
if (ArtCutsceneWidescreen) {
const CelFrameWithHeight sprite = ArtCutsceneWidescreen->sprite();
RenderCl2Sprite(out, sprite, { uiRectangle.position.x - (sprite.width() - uiRectangle.size.width) / 2, uiRectangle.position.y });
const ClxSprite sprite = (*ArtCutsceneWidescreen)[0];
RenderClxSprite(out, sprite, { uiRectangle.position.x - (sprite.width() - uiRectangle.size.width) / 2, uiRectangle.position.y });
}
Cl2Draw(out, { uiRectangle.position.x, 480 - 1 + uiRectangle.position.y }, CelSprite { *sgpBackCel }, 0);
ClxDraw(out, { uiRectangle.position.x, 480 - 1 + uiRectangle.position.y }, (*sgpBackCel)[0]);
}
void DrawCutsceneForeground()

43
Source/inv.cpp

@ -11,9 +11,9 @@
#include "DiabloUI/ui_flags.hpp"
#include "controls/plrctrls.h"
#include "cursor.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/load_cel.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/text_render.hpp"
#include "engine/size.hpp"
#include "hwcursor.hpp"
@ -142,7 +142,7 @@ const Point InvRect[] = {
namespace {
OptionalOwnedCelSprite pInvCels;
OptionalOwnedClxSpriteList pInvCels;
/**
* @brief Adds an item to a player's InvGrid array
@ -1064,24 +1064,24 @@ void InitInv()
switch (MyPlayer->_pClass) {
case HeroClass::Warrior:
case HeroClass::Barbarian:
pInvCels = LoadCelAsCl2("Data\\Inv\\Inv.CEL", static_cast<uint16_t>(SidePanelSize.width));
pInvCels = LoadCel("Data\\Inv\\Inv.CEL", static_cast<uint16_t>(SidePanelSize.width));
break;
case HeroClass::Rogue:
case HeroClass::Bard:
pInvCels = LoadCelAsCl2("Data\\Inv\\Inv_rog.CEL", static_cast<uint16_t>(SidePanelSize.width));
pInvCels = LoadCel("Data\\Inv\\Inv_rog.CEL", static_cast<uint16_t>(SidePanelSize.width));
break;
case HeroClass::Sorcerer:
pInvCels = LoadCelAsCl2("Data\\Inv\\Inv_Sor.CEL", static_cast<uint16_t>(SidePanelSize.width));
pInvCels = LoadCel("Data\\Inv\\Inv_Sor.CEL", static_cast<uint16_t>(SidePanelSize.width));
break;
case HeroClass::Monk:
pInvCels = LoadCelAsCl2(!gbIsSpawn ? "Data\\Inv\\Inv_Sor.CEL" : "Data\\Inv\\Inv.CEL", static_cast<uint16_t>(SidePanelSize.width));
pInvCels = LoadCel(!gbIsSpawn ? "Data\\Inv\\Inv_Sor.CEL" : "Data\\Inv\\Inv.CEL", static_cast<uint16_t>(SidePanelSize.width));
break;
}
}
void DrawInv(const Surface &out)
{
Cl2Draw(out, GetPanelPosition(UiPanels::Inventory, { 0, 351 }), CelSprite { *pInvCels }, 0);
ClxDraw(out, GetPanelPosition(UiPanels::Inventory, { 0, 351 }), (*pInvCels)[0]);
Size slotSize[] = {
{ 2, 2 }, // head
@ -1124,15 +1124,14 @@ void DrawInv(const Surface &out)
screenY += frameSize.height == (3 * InventorySlotSizeInPixels.height) ? 0 : -INV_SLOT_HALF_SIZE_PX;
}
const CelSprite cel { GetInvItemSprite(cursId) };
const int celFrame = GetInvItemFrame(cursId);
const ClxSprite sprite = GetInvItemSprite(cursId);
const Point position = GetPanelPosition(UiPanels::Inventory, { screenX, screenY });
if (pcursinvitem == slot) {
Cl2DrawOutline(out, GetOutlineColor(myPlayer.InvBody[slot], true), position, cel, celFrame);
ClxDrawOutline(out, GetOutlineColor(myPlayer.InvBody[slot], true), position, sprite);
}
DrawItem(myPlayer.InvBody[slot], out, position, cel, celFrame);
DrawItem(myPlayer.InvBody[slot], out, position, sprite);
if (slot == INVLOC_HAND_LEFT) {
if (myPlayer.GetItemLocation(myPlayer.InvBody[slot]) == ILOC_TWOHAND) {
@ -1142,7 +1141,7 @@ void DrawInv(const Surface &out)
const int dstX = GetRightPanel().position.x + slotPos[INVLOC_HAND_RIGHT].x + (frameSize.width == InventorySlotSizeInPixels.width ? INV_SLOT_HALF_SIZE_PX : 0) - 1;
const int dstY = GetRightPanel().position.y + slotPos[INVLOC_HAND_RIGHT].y;
Cl2DrawLightBlended(out, { dstX, dstY }, cel, celFrame);
ClxDrawLightBlended(out, { dstX, dstY }, sprite);
cel_transparency_active = false;
}
@ -1164,18 +1163,13 @@ void DrawInv(const Surface &out)
int ii = myPlayer.InvGrid[j] - 1;
int cursId = myPlayer.InvList[ii]._iCurs + CURSOR_FIRSTITEM;
const CelSprite cel { GetInvItemSprite(cursId) };
const int celFrame = GetInvItemFrame(cursId);
const ClxSprite sprite = GetInvItemSprite(cursId);
const Point position = GetPanelPosition(UiPanels::Inventory, InvRect[j + SLOTXY_INV_FIRST]) + Displacement { 0, -1 };
if (pcursinvitem == ii + INVITEM_INV_FIRST) {
Cl2DrawOutline(out, GetOutlineColor(myPlayer.InvList[ii], true), position, cel, celFrame);
ClxDrawOutline(out, GetOutlineColor(myPlayer.InvList[ii], true), position, sprite);
}
DrawItem(
myPlayer.InvList[ii],
out,
position,
cel, celFrame);
DrawItem(myPlayer.InvList[ii], out, position, sprite);
}
}
}
@ -1201,16 +1195,15 @@ void DrawInvBelt(const Surface &out)
InvDrawSlotBack(out, position, InventorySlotSizeInPixels);
const int cursId = myPlayer.SpdList[i]._iCurs + CURSOR_FIRSTITEM;
const CelSprite cel { GetInvItemSprite(cursId) };
const int celFrame = GetInvItemFrame(cursId);
const ClxSprite sprite = GetInvItemSprite(cursId);
if (pcursinvitem == i + INVITEM_BELT_FIRST) {
if (ControlMode == ControlTypes::KeyboardAndMouse || invflag) {
Cl2DrawOutline(out, GetOutlineColor(myPlayer.SpdList[i], true), position, cel, celFrame);
ClxDrawOutline(out, GetOutlineColor(myPlayer.SpdList[i], true), position, sprite);
}
}
DrawItem(myPlayer.SpdList[i], out, position, cel, celFrame);
DrawItem(myPlayer.SpdList[i], out, position, sprite);
if (AllItemsList[myPlayer.SpdList[i].IDidx].iUsable
&& myPlayer.SpdList[i]._itype != ItemType::Gold) {

22
Source/items.cpp

@ -20,11 +20,11 @@
#include "controls/plrctrls.h"
#include "cursor.h"
#include "doom.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/dx.h"
#include "engine/load_cel.hpp"
#include "engine/random.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/text_render.hpp"
#include "init.h"
#include "inv_iterators.hpp"
@ -132,7 +132,7 @@ _sfx_id ItemInvSnds[] = {
namespace {
OptionalOwnedCelSprite itemanims[ITEMTYPES];
OptionalOwnedClxSpriteList itemanims[ITEMTYPES];
enum class PlayerArmorGraphic : uint8_t {
// clang-format off
@ -1758,7 +1758,7 @@ void PrintItemOil(char iDidx)
void DrawUniqueInfoWindow(const Surface &out)
{
Cl2Draw(out, GetPanelPosition(UiPanels::Inventory, { 24 - SidePanelSize.width, 327 }), CelSprite { *pSTextBoxCels }, 0);
ClxDraw(out, GetPanelPosition(UiPanels::Inventory, { 24 - SidePanelSize.width, 327 }), (*pSTextBoxCels)[0]);
DrawHalfTransparentRectTo(out, GetRightPanel().position.x - SidePanelSize.width + 27, GetRightPanel().position.y + 28, 265, 297);
}
@ -2283,7 +2283,7 @@ void InitItemGFX()
int itemTypes = gbIsHellfire ? ITEMTYPES : 35;
for (int i = 0; i < itemTypes; i++) {
*BufCopy(arglist, "Items\\", ItemDropNames[i], ".CEL") = '\0';
itemanims[i] = LoadCelAsCl2(arglist, ItemAnimWidth);
itemanims[i] = LoadCel(arglist, ItemAnimWidth);
}
memset(UniqueItemFlags, 0, sizeof(UniqueItemFlags));
}
@ -2667,10 +2667,10 @@ void CalcPlrItemVals(Player &player, bool loadgfx)
player.previewCelSprite = std::nullopt;
if (player._pmode == PM_STAND) {
LoadPlrGFX(player, player_graphic::Stand);
player.AnimInfo.changeAnimationData(player.AnimationData[static_cast<size_t>(player_graphic::Stand)].GetCelSpritesForDirection(player._pdir), player._pNFrames, 4);
player.AnimInfo.changeAnimationData(player.AnimationData[static_cast<size_t>(player_graphic::Stand)].spritesForDirection(player._pdir), player._pNFrames, 4);
} else {
LoadPlrGFX(player, player_graphic::Walk);
player.AnimInfo.changeAnimationData(player.AnimationData[static_cast<size_t>(player_graphic::Walk)].GetCelSpritesForDirection(player._pdir), player._pWFrames, 1);
player.AnimInfo.changeAnimationData(player.AnimationData[static_cast<size_t>(player_graphic::Walk)].spritesForDirection(player._pdir), player._pWFrames, 1);
}
} else {
player._pgfxnum = gfxNum;
@ -3430,7 +3430,7 @@ void GetItemFrm(Item &item)
{
int it = ItemCAnimTbl[item._iCurs];
if (itemanims[it])
item.AnimInfo.celSprite.emplace(*itemanims[it]);
item.AnimInfo.sprites.emplace(*itemanims[it]);
}
void GetItemStr(Item &item)
@ -4564,11 +4564,11 @@ void Item::setNewAnimation(bool showAnimation)
{
int8_t it = ItemCAnimTbl[_iCurs];
int8_t numberOfFrames = ItemAnimLs[it];
auto celSprite = itemanims[it] ? OptionalCelSprite { *itemanims[it] } : std::nullopt;
OptionalClxSpriteList sprite = itemanims[it] ? OptionalClxSpriteList { *itemanims[static_cast<size_t>(it)] } : std::nullopt;
if (_iCurs != ICURS_MAGIC_ROCK)
AnimInfo.setNewAnimation(celSprite, numberOfFrames, 1, AnimationDistributionFlags::ProcessAnimationPending, 0, numberOfFrames);
AnimInfo.setNewAnimation(sprite, numberOfFrames, 1, AnimationDistributionFlags::ProcessAnimationPending, 0, numberOfFrames);
else
AnimInfo.setNewAnimation(celSprite, numberOfFrames, 1);
AnimInfo.setNewAnimation(sprite, numberOfFrames, 1);
_iPostDraw = false;
_iRequest = false;
if (showAnimation) {

2
Source/levels/gendung.cpp

@ -22,7 +22,7 @@ Bitset2d<DMAXX, DMAXY> Protected;
Rectangle SetPieceRoom;
Rectangle SetPiece;
std::unique_ptr<uint16_t[]> pSetPiece;
OptionalOwnedCelSprite pSpecialCels;
OptionalOwnedClxSpriteList pSpecialCels;
std::unique_ptr<MegaTile[]> pMegaTiles;
std::unique_ptr<byte[]> pDungeonCels;
std::array<TileProperties, MAXTILES> SOLData;

4
Source/levels/gendung.h

@ -9,7 +9,7 @@
#include <memory>
#include "engine.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/point.hpp"
#include "engine/rectangle.hpp"
#include "engine/render/scrollrt.h"
@ -150,7 +150,7 @@ extern Rectangle SetPieceRoom;
extern Rectangle SetPiece;
/** Contains the contents of the single player quest DUN file. */
extern std::unique_ptr<uint16_t[]> pSetPiece;
extern OptionalOwnedCelSprite pSpecialCels;
extern OptionalOwnedClxSpriteList pSpecialCels;
/** Specifies the tile definitions of the active dungeon type; (e.g. levels/l1data/l1.til). */
extern DVL_API_FOR_TEST std::unique_ptr<MegaTile[]> pMegaTiles;
extern std::unique_ptr<byte[]> pDungeonCels;

10
Source/minitext.cpp

@ -9,10 +9,10 @@
#include "DiabloUI/ui_flags.hpp"
#include "control.h"
#include "engine.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/dx.h"
#include "engine/load_cel.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/text_render.hpp"
#include "textdat.h"
#include "utils/language.h"
@ -30,7 +30,7 @@ int qtextSpd;
/** Start time of scrolling */
Uint32 ScrollStart;
/** Graphics for the window border */
OptionalOwnedCelSprite pTextBoxCels;
OptionalOwnedClxSpriteList pTextBoxCels;
/** Pixels for a line of text and the empty space under it. */
const int LineHeight = 38;
@ -126,7 +126,7 @@ void FreeQuestText()
void InitQuestText()
{
pTextBoxCels = LoadCelAsCl2("Data\\TextBox.CEL", 591);
pTextBoxCels = LoadCel("Data\\TextBox.CEL", 591);
}
void InitQTextMsg(_speech_id m)
@ -144,7 +144,7 @@ void InitQTextMsg(_speech_id m)
void DrawQTextBack(const Surface &out)
{
const Point uiPosition = GetUIRectangle().position;
Cl2Draw(out, uiPosition + Displacement { 24, 327 }, CelSprite { *pTextBoxCels }, 0);
ClxDraw(out, uiPosition + Displacement { 24, 327 }, (*pTextBoxCels)[0]);
DrawHalfTransparentRectTo(out, uiPosition.x + 27, uiPosition.y + 28, 585, 297);
}

10
Source/misdat.cpp

@ -5,8 +5,7 @@
*/
#include "misdat.h"
#include "engine/cel_header.hpp"
#include "engine/load_file.hpp"
#include "engine/load_cl2.hpp"
#include "missiles.h"
#include "utils/file_name_generator.hpp"
@ -231,7 +230,7 @@ MissileFileData::MissileFileData(string_view name, uint8_t animName, uint8_t ani
void MissileFileData::LoadGFX()
{
if (animData != nullptr)
if (sprites)
return;
if (name.empty())
@ -239,10 +238,9 @@ void MissileFileData::LoadGFX()
FileNameGenerator pathGenerator({ "Missiles\\", name }, ".CL2");
if (animFAmt == 1) {
animData = LoadFileInMem(pathGenerator());
frameOffsets[0] = 0;
sprites.emplace(OwnedClxSpriteListOrSheet { LoadCl2(pathGenerator(), animWidth) });
} else {
animData = MultiFileLoader<16> {}(animFAmt, pathGenerator, &frameOffsets[0]);
sprites.emplace(OwnedClxSpriteListOrSheet { LoadMultipleCl2Sheet<16>(pathGenerator, animFAmt, animWidth) });
}
}

33
Source/misdat.h

@ -10,7 +10,7 @@
#include "effects.h"
#include "engine.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "utils/stdcompat/cstddef.hpp"
#include "utils/stdcompat/string_view.hpp"
@ -144,8 +144,7 @@ struct MissileFileData {
std::array<uint8_t, 16> animLen = {};
uint16_t animWidth;
int16_t animWidth2;
std::unique_ptr<byte[]> animData;
std::array<uint32_t, 16> frameOffsets;
OptionalOwnedClxSpriteListOrSheet sprites;
MissileFileData(string_view name, uint8_t animName, uint8_t animFAmt, MissileDataFlags flags,
std::initializer_list<uint8_t> animDelay, std::initializer_list<uint8_t> animLen,
@ -153,26 +152,22 @@ struct MissileFileData {
void LoadGFX();
[[nodiscard]] const byte *GetFirstFrame() const
{
return animData.get();
}
[[nodiscard]] const byte *GetFrame(size_t i) const
{
// For a "null" missile, `frameOffsets[i]` is 0 and `animData` is nullptr
// `animData[frameOffsets[i]]` is UB, so we use get() instead.
return animData.get() + frameOffsets[i];
}
CelSprite Sprite() const
void FreeGFX()
{
return CelSprite { GetFirstFrame(), animWidth };
sprites = std::nullopt;
}
void FreeGFX()
/**
* @brief Returns the sprite list for a given direction.
*
* @param direction One of the 16 directions. Valid range: [0, 15].
* @return OptionalClxSpriteList
*/
[[nodiscard]] OptionalClxSpriteList spritesForDirection(size_t direction) const
{
animData = nullptr;
if (!sprites)
return std::nullopt;
return sprites->isSheet() ? sprites->sheet()[direction] : sprites->list();
}
};

18
Source/missiles.cpp

@ -14,7 +14,6 @@
#ifdef _DEBUG
#include "debug.h"
#endif
#include "engine/cel_header.hpp"
#include "engine/load_file.hpp"
#include "engine/random.hpp"
#include "init.h"
@ -538,7 +537,9 @@ void SetMissAnim(Missile &missile, int animtype)
missile._miAnimType = animtype;
missile._miAnimFlags = MissileSpriteData[animtype].flags;
missile._miAnimData = MissileSpriteData[animtype].GetFrame(static_cast<size_t>(dir));
if (!HeadlessMode) {
missile._miAnimData = MissileSpriteData[animtype].spritesForDirection(static_cast<size_t>(dir));
}
missile._miAnimDelay = MissileSpriteData[animtype].animDelay[dir];
missile._miAnimLen = MissileSpriteData[animtype].animLen[dir];
missile._miAnimWidth = MissileSpriteData[animtype].animWidth;
@ -2035,12 +2036,13 @@ void InitMissileAnimationFromMonster(Missile &mis, Direction midir, const Monste
const AnimStruct &anim = mon.type().getAnimData(graphic);
mis._mimfnum = static_cast<int32_t>(midir);
mis._miAnimFlags = MissileDataFlags::None;
CelSprite celSprite = *anim.getCelSpritesForDirection(midir);
mis._miAnimData = celSprite.Data();
ClxSpriteList sprites = *anim.spritesForDirection(midir);
const uint16_t width = sprites[0].width();
mis._miAnimData.emplace(sprites);
mis._miAnimDelay = anim.rate;
mis._miAnimLen = anim.frames;
mis._miAnimWidth = celSprite.Width();
mis._miAnimWidth2 = CalculateWidth2(celSprite.Width());
mis._miAnimWidth = width;
mis._miAnimWidth2 = CalculateWidth2(width);
mis._miAnimAdd = 1;
mis.var1 = 0;
mis.var2 = 0;
@ -4043,7 +4045,7 @@ void ProcessMissiles()
void missiles_process_charge()
{
for (auto &missile : Missiles) {
missile._miAnimData = MissileSpriteData[missile._miAnimType].GetFrame(missile._mimfnum);
missile._miAnimData = MissileSpriteData[missile._miAnimType].spritesForDirection(missile._mimfnum);
if (missile._mitype != MIS_RHINO)
continue;
@ -4057,7 +4059,7 @@ void missiles_process_charge()
} else {
graphic = MonsterGraphic::Walk;
}
missile._miAnimData = mon.getAnimData(graphic).celSpritesForDirections[missile._mimfnum];
missile._miAnimData = mon.getAnimData(graphic).spritesForDirection(static_cast<Direction>(missile._mimfnum));
}
}

6
Source/missiles.h

@ -100,10 +100,14 @@ struct Missile {
bool _miDelFlag; // Indicate whether the missile should be deleted
uint8_t _miAnimType;
MissileDataFlags _miAnimFlags;
const byte *_miAnimData;
OptionalClxSpriteList _miAnimData;
int _miAnimDelay; // Tick length of each frame in the current animation
int _miAnimLen; // Number of frames in current animation
// TODO: This field is no longer used and is always equal to
// (*_miAnimData)[0].width()
uint16_t _miAnimWidth;
int16_t _miAnimWidth2;
int _miAnimCnt; // Increases by one each game tick, counting how close we are to _pAnimDelay
int _miAnimAdd;

76
Source/monster.cpp

@ -5,9 +5,10 @@
*/
#include "monster.h"
#include <climits>
#include <algorithm>
#include <array>
#include <climits>
#include <fmt/compile.h>
#include <fmt/format.h>
@ -15,11 +16,11 @@
#include "control.h"
#include "cursor.h"
#include "dead.h"
#include "engine/cel_header.hpp"
#include "engine/load_cl2.hpp"
#include "engine/load_file.hpp"
#include "engine/points_in_rectangle_range.hpp"
#include "engine/random.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/world_tile.hpp"
#include "init.h"
#include "levels/crypt.h"
@ -34,6 +35,7 @@
#include "spelldat.h"
#include "storm/storm_net.hpp"
#include "towners.h"
#include "utils/cl2_to_clx.hpp"
#include "utils/file_name_generator.hpp"
#include "utils/language.h"
#include "utils/stdcompat/string_view.hpp"
@ -156,18 +158,12 @@ size_t GetNumAnims(const MonsterData &monsterData)
return monsterData.hasSpecial ? 6 : 5;
}
bool IsDirectionalAnim(const CMonster &monster, size_t animIndex)
{
return monster.type != MT_GOLEM || animIndex < 4;
}
void InitMonsterTRN(CMonster &monst)
{
char path[64];
std::array<uint8_t, 256> colorTranslations;
*BufCopy(path, "Monsters\\", monst.data->trnFile, ".TRN") = '\0';
std::array<uint8_t, 256> colorTranslations;
LoadFileInMem(path, colorTranslations);
std::replace(colorTranslations.begin(), colorTranslations.end(), 255, 0);
const size_t numAnims = GetNumAnims(*monst.data);
@ -177,16 +173,10 @@ void InitMonsterTRN(CMonster &monst)
}
AnimStruct &anim = monst.anims[i];
if (IsDirectionalAnim(monst, i)) {
for (size_t i = 0; i < 8; i++) {
Cl2ApplyTrans(anim.celSpritesForDirections[i], colorTranslations, anim.frames);
}
if (anim.sprites->isSheet()) {
ClxApplyTrans(ClxSpriteSheet { anim.sprites->sheet() }, colorTranslations.data());
} else {
byte *frames[8];
CelGetDirectionFrames(anim.celSpritesForDirections[0], frames);
for (byte *frame : frames) {
Cl2ApplyTrans(frame, colorTranslations, anim.frames);
}
ClxApplyTrans(ClxSpriteList { anim.sprites->list() }, colorTranslations.data());
}
}
}
@ -668,7 +658,7 @@ void DeleteMonster(size_t activeIndex)
void NewMonsterAnim(Monster &monster, MonsterGraphic graphic, Direction md, AnimationDistributionFlags flags = AnimationDistributionFlags::None, int8_t numSkippedFrames = 0, int8_t distributeFramesBeforeFrame = 0)
{
const auto &animData = monster.type().getAnimData(graphic);
monster.animInfo.setNewAnimation(animData.getCelSpritesForDirection(md), animData.frames, animData.rate, flags, numSkippedFrames, distributeFramesBeforeFrame);
monster.animInfo.setNewAnimation(animData.spritesForDirection(md), animData.frames, animData.rate, flags, numSkippedFrames, distributeFramesBeforeFrame);
monster.flags &= ~(MFLAG_LOCK_ANIMATION | MFLAG_ALLOW_SPECIAL);
monster.direction = md;
}
@ -3455,15 +3445,12 @@ void InitMonsterGFX(CMonster &monsterType)
{
const _monster_id mtype = monsterType.type;
const MonsterData &monsterData = MonstersData[mtype];
const int width = monsterData.width;
constexpr size_t MaxAnims = sizeof(Animletter) / sizeof(Animletter[0]) - 1;
const size_t numAnims = GetNumAnims(monsterData);
const auto hasAnim = [&monsterData](size_t i) {
return monsterData.frames[i] != 0;
const auto hasAnim = [&monsterData](size_t index) {
return monsterData.frames[index] != 0;
};
std::array<uint32_t, MaxAnims> animOffsets;
constexpr size_t MaxAnims = 6;
std::array<uint32_t, MaxAnims + 1> animOffsets;
if (!HeadlessMode) {
monsterType.animData = MultiFileLoader<MaxAnims> {}(
numAnims,
@ -3472,29 +3459,23 @@ void InitMonsterGFX(CMonster &monsterType)
hasAnim);
}
for (unsigned animIndex = 0; animIndex < numAnims; animIndex++) {
AnimStruct &anim = monsterType.anims[animIndex];
if (!hasAnim(animIndex)) {
for (size_t i = 0, j = 0; i < numAnims; ++i) {
AnimStruct &anim = monsterType.anims[i];
if (!hasAnim(i)) {
anim.frames = 0;
continue;
}
anim.frames = monsterData.frames[animIndex];
anim.rate = monsterData.rate[animIndex];
anim.width = width;
if (HeadlessMode)
continue;
byte *cl2Data = &monsterType.animData[animOffsets[animIndex]];
if (IsDirectionalAnim(monsterType, animIndex)) {
CelGetDirectionFrames(cl2Data, anim.celSpritesForDirections.data());
} else {
for (size_t i = 0; i < 8; ++i) {
anim.celSpritesForDirections[i] = cl2Data;
}
anim.frames = monsterData.frames[i];
anim.rate = monsterData.rate[i];
anim.width = monsterData.width;
if (!HeadlessMode) {
const uint32_t begin = animOffsets[j];
const uint32_t end = animOffsets[j + 1];
auto spritesData = reinterpret_cast<uint8_t *>(&monsterType.animData[begin]);
const uint16_t numLists = Cl2ToClx(spritesData, end - begin, PointerOrValue<uint16_t> { monsterData.width });
anim.sprites = ClxSpriteListOrSheet { spritesData, numLists };
}
++j;
}
monsterType.data = &monsterData;
@ -4116,6 +4097,9 @@ void FreeMonsters()
{
for (CMonster &monsterType : LevelMonsterTypes) {
monsterType.animData = nullptr;
for (AnimStruct &animData : monsterType.anims) {
animData.sprites = std::nullopt;
}
for (auto &variants : monsterType.sounds) {
for (auto &sound : variants) {

21
Source/monster.h

@ -14,7 +14,7 @@
#include "engine.h"
#include "engine/actor_position.hpp"
#include "engine/animationinfo.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/point.hpp"
#include "engine/sound.h"
#include "engine/world_tile.hpp"
@ -136,15 +136,18 @@ enum class LeaderRelation : uint8_t {
};
struct AnimStruct {
[[nodiscard]] OptionalCelSprite getCelSpritesForDirection(Direction direction) const
/**
* @brief Sprite lists for each of the 8 directions.
*/
OptionalClxSpriteListOrSheet sprites;
[[nodiscard]] OptionalClxSpriteList spritesForDirection(Direction direction) const
{
const byte *spriteData = celSpritesForDirections[static_cast<size_t>(direction)];
if (spriteData == nullptr)
if (!sprites)
return std::nullopt;
return CelSprite(spriteData, width);
return sprites->isSheet() ? (*sprites).sheet()[static_cast<size_t>(direction)] : (*sprites).list();
}
std::array<byte *, 8> celSpritesForDirections;
uint16_t width;
int8_t frames;
int8_t rate;
@ -171,7 +174,7 @@ struct CMonster {
/**
* @brief Returns AnimStruct for specified graphic
*/
const AnimStruct &getAnimData(MonsterGraphic graphic) const
[[nodiscard]] const AnimStruct &getAnimData(MonsterGraphic graphic) const
{
return anims[static_cast<int>(graphic)];
}
@ -265,10 +268,10 @@ struct Monster { // note: missing field _mAFNum
*/
void changeAnimationData(MonsterGraphic graphic, Direction desiredDirection)
{
auto &animationData = type().getAnimData(graphic);
const AnimStruct &animationData = type().getAnimData(graphic);
// Passing the frames and rate properties here is only relevant when initialising a monster, but doesn't cause any harm when switching animations.
this->animInfo.changeAnimationData(animationData.getCelSpritesForDirection(desiredDirection), animationData.frames, animationData.rate);
this->animInfo.changeAnimationData(animationData.spritesForDirection(desiredDirection), animationData.frames, animationData.rate);
}
/**

22
Source/objects.cpp

@ -101,7 +101,7 @@ enum {
int trapid;
int trapdir;
std::unique_ptr<byte[]> pObjCels[40];
OptionalOwnedClxSpriteList pObjCels[40];
object_graphic_id ObjFileList[40];
/** Specifies the number of active objects. */
int leverid;
@ -415,7 +415,9 @@ void InitRndLocObj5x5(int min, int max, _object_id objtype)
void ClrAllObjects()
{
memset(Objects, 0, sizeof(Objects));
for (Object &object : Objects) {
object = {};
}
ActiveObjectCount = 0;
for (int i = 0; i < MAXOBJECTS; i++) {
AvailableObjects[i] = i;
@ -780,7 +782,11 @@ void SetupObject(Object &object, Point position, _object_id ot)
const int j = std::distance(std::begin(ObjFileList), found);
object._oAnimData = pObjCels[j].get();
if (pObjCels[j]) {
object._oAnimData.emplace(*pObjCels[j]);
} else {
object._oAnimData = std::nullopt;
}
}
object._oAnimFlag = objectData.oAnimFlag;
if (object._oAnimFlag) {
@ -3978,7 +3984,7 @@ void LoadLevelObjects(uint16_t filesWidths[65])
ObjFileList[numobjfiles] = static_cast<object_graphic_id>(i);
char filestr[32];
*BufCopy(filestr, "Objects\\", ObjMasterLoadList[i], ".CEL") = '\0';
pObjCels[numobjfiles] = LoadCelAsCl2(filestr, filesWidths[i]).data();
pObjCels[numobjfiles] = LoadCel(filestr, filesWidths[i]);
numobjfiles++;
}
}
@ -4019,7 +4025,7 @@ void InitObjectGFX()
void FreeObjectGFX()
{
for (int i = 0; i < numobjfiles; i++) {
pObjCels[i] = nullptr;
pObjCels[i] = std::nullopt;
}
numobjfiles = 0;
}
@ -5015,7 +5021,11 @@ void SyncObjectAnim(Object &object)
const int i = std::distance(std::begin(ObjFileList), found);
object._oAnimData = pObjCels[i].get();
if (pObjCels[i]) {
object._oAnimData.emplace(*pObjCels[i]);
} else {
object._oAnimData = std::nullopt;
}
}
switch (object._otype) {

60
Source/objects.h

@ -7,6 +7,7 @@
#include <cstdint>
#include "engine/clx_sprite.hpp"
#include "engine/point.hpp"
#include "engine/rectangle.hpp"
#include "itemdat.h"
@ -20,44 +21,49 @@ namespace devilution {
#define MAXOBJECTS 127
struct Object {
_object_id _otype;
_object_id _otype = OBJ_NULL;
Point position;
bool _oLight;
uint32_t _oAnimFlag;
byte *_oAnimData;
int _oAnimDelay; // Tick length of each frame in the current animation
int _oAnimCnt; // Increases by one each game tick, counting how close we are to _pAnimDelay
uint32_t _oAnimLen; // Number of frames in current animation
uint32_t _oAnimFrame; // Current frame of animation.
uint16_t _oAnimWidth;
bool _oDelFlag;
int8_t _oBreak;
bool _oSolidFlag;
bool _oLight = false;
uint32_t _oAnimFlag = 0;
OptionalClxSpriteList _oAnimData;
int _oAnimDelay = 0; // Tick length of each frame in the current animation
int _oAnimCnt = 0; // Increases by one each game tick, counting how close we are to _pAnimDelay
uint32_t _oAnimLen = 0; // Number of frames in current animation
uint32_t _oAnimFrame = 0; // Current frame of animation.
// TODO: Remove this field, it is unused and always equal to:
// (*_oAnimData)[0].width()
uint16_t _oAnimWidth = 0;
bool _oDelFlag = false;
int8_t _oBreak = 0;
bool _oSolidFlag = false;
/** True if the object allows missiles to pass through, false if it collides with missiles */
bool _oMissFlag;
uint8_t _oSelFlag;
bool _oPreFlag;
bool _oTrapFlag;
bool _oDoorFlag;
int _olid;
bool _oMissFlag = false;
uint8_t _oSelFlag = 0;
bool _oPreFlag = false;
bool _oTrapFlag = false;
bool _oDoorFlag = false;
int _olid = 0;
/**
* Saves the absolute value of the engine state (typically from a call to AdvanceRndSeed()) to later use when spawning items from a container object
* This is an unsigned value to avoid implementation defined behaviour when reading from this variable.
*/
uint32_t _oRndSeed;
int _oVar1;
int _oVar2;
int _oVar3;
int _oVar4;
int _oVar5;
uint32_t _oVar6;
uint32_t _oRndSeed = 0;
int _oVar1 = 0;
int _oVar2 = 0;
int _oVar3 = 0;
int _oVar4 = 0;
int _oVar5 = 0;
uint32_t _oVar6 = 0;
/**
* @brief ID of a quest message to play when this object is activated.
*
* Used by spell book objects which trigger quest progress for Halls of the Blind, Valor, or Warlord of Blood
*/
_speech_id bookMessage;
int _oVar8;
// TODO: Should be TEXT_NONE (timedemo save will need to be updated).
_speech_id bookMessage = TEXT_KING1;
int _oVar8 = 0;
/**
* @brief Returns the network identifier for this object

13
Source/panels/charpanel.cpp

@ -7,7 +7,7 @@
#include "DiabloUI/art.h"
#include "DiabloUI/art_draw.h"
#include "control.h"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/text_render.hpp"
#include "panels/ui_panels.hpp"
#include "player.h"
@ -18,7 +18,7 @@
namespace devilution {
OptionalOwnedCelSprite pChrButtons;
OptionalOwnedClxSpriteList pChrButtons;
/** Map of hero class names */
const char *const ClassStrTbl[] = {
@ -253,15 +253,14 @@ void DrawShadowString(const Surface &out, const PanelEntry &entry)
void DrawStatButtons(const Surface &out)
{
if (MyPlayer->_pStatPts > 0) {
CelSprite sprite { *pChrButtons };
if (MyPlayer->_pBaseStr < MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Strength))
Cl2Draw(out, GetPanelPosition(UiPanels::Character, { 137, 157 }), sprite, chrbtn[static_cast<size_t>(CharacterAttribute::Strength)] ? 2 : 1);
ClxDraw(out, GetPanelPosition(UiPanels::Character, { 137, 157 }), (*pChrButtons)[chrbtn[static_cast<size_t>(CharacterAttribute::Strength)] ? 2 : 1]);
if (MyPlayer->_pBaseMag < MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Magic))
Cl2Draw(out, GetPanelPosition(UiPanels::Character, { 137, 185 }), sprite, chrbtn[static_cast<size_t>(CharacterAttribute::Magic)] ? 4 : 3);
ClxDraw(out, GetPanelPosition(UiPanels::Character, { 137, 185 }), (*pChrButtons)[chrbtn[static_cast<size_t>(CharacterAttribute::Magic)] ? 4 : 3]);
if (MyPlayer->_pBaseDex < MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Dexterity))
Cl2Draw(out, GetPanelPosition(UiPanels::Character, { 137, 214 }), sprite, chrbtn[static_cast<size_t>(CharacterAttribute::Dexterity)] ? 6 : 5);
ClxDraw(out, GetPanelPosition(UiPanels::Character, { 137, 214 }), (*pChrButtons)[chrbtn[static_cast<size_t>(CharacterAttribute::Dexterity)] ? 6 : 5]);
if (MyPlayer->_pBaseVit < MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Vitality))
Cl2Draw(out, GetPanelPosition(UiPanels::Character, { 137, 242 }), sprite, chrbtn[static_cast<size_t>(CharacterAttribute::Vitality)] ? 8 : 7);
ClxDraw(out, GetPanelPosition(UiPanels::Character, { 137, 242 }), (*pChrButtons)[chrbtn[static_cast<size_t>(CharacterAttribute::Vitality)] ? 8 : 7]);
}
}

4
Source/panels/charpanel.hpp

@ -1,11 +1,11 @@
#pragma once
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/surface.hpp"
namespace devilution {
extern OptionalOwnedCelSprite pChrButtons;
extern OptionalOwnedClxSpriteList pChrButtons;
extern const char *const ClassStrTbl[];
void DrawChr(const Surface &);

8
Source/panels/info_box.cpp

@ -4,13 +4,13 @@
namespace devilution {
OptionalOwnedCelSprite pSTextBoxCels;
OptionalOwnedCelSprite pSTextSlidCels;
OptionalOwnedClxSpriteList pSTextBoxCels;
OptionalOwnedClxSpriteList pSTextSlidCels;
void InitInfoBoxGfx()
{
pSTextSlidCels = LoadCelAsCl2("Data\\TextSlid.CEL", 12);
pSTextBoxCels = LoadCelAsCl2("Data\\TextBox2.CEL", 271);
pSTextSlidCels = LoadCel("Data\\TextSlid.CEL", 12);
pSTextBoxCels = LoadCel("Data\\TextBox2.CEL", 271);
}
void FreeInfoBoxGfx()

6
Source/panels/info_box.hpp

@ -1,6 +1,6 @@
#pragma once
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
namespace devilution {
@ -9,14 +9,14 @@ namespace devilution {
*
* Used in stores, the quest log, the help window, and the unique item info window.
*/
extern OptionalOwnedCelSprite pSTextBoxCels;
extern OptionalOwnedClxSpriteList pSTextBoxCels;
/**
* @brief Info box scrollbar graphics.
*
* Used in stores and `DrawDiabloMsg`.
*/
extern OptionalOwnedCelSprite pSTextSlidCels;
extern OptionalOwnedClxSpriteList pSTextSlidCels;
void InitInfoBoxGfx();
void FreeInfoBoxGfx();

2
Source/panels/mainpanel.cpp

@ -1,7 +1,7 @@
#include "panels/mainpanel.hpp"
#include "control.h"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/text_render.hpp"
#include "utils/display.h"
#include "utils/language.h"

22
Source/panels/spell_book.cpp

@ -3,10 +3,10 @@
#include <fmt/format.h>
#include "control.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/load_cel.hpp"
#include "engine/rectangle.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/text_render.hpp"
#include "init.h"
#include "missiles.h"
@ -21,12 +21,12 @@
namespace devilution {
OptionalOwnedCelSprite pSBkIconCels;
OptionalOwnedClxSpriteList pSBkIconCels;
namespace {
OptionalOwnedCelSprite pSBkBtnCel;
OptionalOwnedCelSprite pSpellBkCel;
OptionalOwnedClxSpriteList pSBkBtnCel;
OptionalOwnedClxSpriteList pSpellBkCel;
/** Maps from spellbook page number and position to spell_id. */
spell_id SpellPages[6][7] = {
@ -81,9 +81,9 @@ spell_type GetSBookTrans(spell_id ii, bool townok)
void InitSpellBook()
{
pSpellBkCel = LoadCelAsCl2("Data\\SpellBk.CEL", static_cast<uint16_t>(SidePanelSize.width));
pSBkBtnCel = LoadCelAsCl2("Data\\SpellBkB.CEL", gbIsHellfire ? 61 : 76);
pSBkIconCels = LoadCelAsCl2("Data\\SpellI2.CEL", 37);
pSpellBkCel = LoadCel("Data\\SpellBk.CEL", static_cast<uint16_t>(SidePanelSize.width));
pSBkBtnCel = LoadCel("Data\\SpellBkB.CEL", gbIsHellfire ? 61 : 76);
pSBkIconCels = LoadCel("Data\\SpellI2.CEL", 37);
Player &player = *MyPlayer;
if (player._pClass == HeroClass::Warrior) {
@ -110,16 +110,16 @@ void FreeSpellBook()
void DrawSpellBook(const Surface &out)
{
Cl2Draw(out, GetPanelPosition(UiPanels::Spell, { 0, 351 }), CelSprite { *pSpellBkCel }, 0);
ClxDraw(out, GetPanelPosition(UiPanels::Spell, { 0, 351 }), (*pSpellBkCel)[0]);
if (gbIsHellfire && sbooktab < 5) {
Cl2Draw(out, GetPanelPosition(UiPanels::Spell, { 61 * sbooktab + 7, 348 }), CelSprite { *pSBkBtnCel }, sbooktab);
ClxDraw(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++;
}
Cl2Draw(out, GetPanelPosition(UiPanels::Spell, { sx, 348 }), CelSprite { *pSBkBtnCel }, sbooktab);
ClxDraw(out, GetPanelPosition(UiPanels::Spell, { sx, 348 }), (*pSBkBtnCel)[sbooktab]);
}
Player &player = *MyPlayer;
uint64_t spl = player._pMemSpells | player._pISpells | player._pAblSpells;

12
Source/panels/spell_icons.cpp

@ -2,14 +2,14 @@
#include "engine/load_cel.hpp"
#include "engine/palette.h"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "init.h"
#include "utils/stdcompat/optional.hpp"
namespace devilution {
namespace {
OptionalOwnedCelSprite pSpellCels;
OptionalOwnedClxSpriteList pSpellCels;
uint8_t SplTransTbl[256];
} // namespace
@ -71,9 +71,9 @@ const char SpellITbl[] = {
void LoadSpellIcons()
{
if (!gbIsHellfire)
pSpellCels = LoadCelAsCl2("CtrlPan\\SpelIcon.CEL", SPLICONLENGTH);
pSpellCels = LoadCel("CtrlPan\\SpelIcon.CEL", SPLICONLENGTH);
else
pSpellCels = LoadCelAsCl2("Data\\SpelIcon.CEL", SPLICONLENGTH);
pSpellCels = LoadCel("Data\\SpelIcon.CEL", SPLICONLENGTH);
SetSpellTrans(RSPLTYPE_SKILL);
}
@ -87,9 +87,9 @@ void DrawSpellCel(const Surface &out, Point position, int nCel)
DrawSpellCel(out, position, *pSpellCels, nCel);
}
void DrawSpellCel(const Surface &out, Point position, const OwnedCelSprite &sprite, int nCel)
void DrawSpellCel(const Surface &out, Point position, const OwnedClxSpriteList &sprite, int nCel)
{
Cl2DrawTRN(out, position, CelSprite { sprite }, nCel, SplTransTbl);
ClxDrawTRN(out, position, sprite[nCel], SplTransTbl);
}
void SetSpellTrans(spell_type t)

4
Source/panels/spell_icons.hpp

@ -1,6 +1,6 @@
#pragma once
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/point.hpp"
#include "engine/surface.hpp"
#include "spelldat.h"
@ -27,7 +27,7 @@ void DrawSpellCel(const Surface &out, Point position, int nCel);
* @param sprite Icons sprite sheet.
* @param nCel Index of the cel frame to draw. 0 based.
*/
void DrawSpellCel(const Surface &out, Point position, const OwnedCelSprite &sprite, int nCel);
void DrawSpellCel(const Surface &out, Point position, const OwnedClxSpriteList &sprite, int nCel);
void SetSpellTrans(spell_type t);

54
Source/player.cpp

@ -16,7 +16,7 @@
#ifdef _DEBUG
#include "debug.h"
#endif
#include "engine/cel_header.hpp"
#include "engine/load_cl2.hpp"
#include "engine/load_file.hpp"
#include "engine/points_in_rectangle_range.hpp"
#include "engine/random.hpp"
@ -366,20 +366,6 @@ void StartWalk(Player &player, Displacement vel, Direction dir, bool pmWillBeCal
StartWalkAnimation(player, dir, pmWillBeCalled);
}
void SetPlayerGPtrs(const char *path, std::unique_ptr<byte[]> &data, std::array<OptionalCelSprite, 8> &anim, int width)
{
data = nullptr;
data = LoadFileInMem(path);
if (data == nullptr)
return;
const byte *directionFrames[8];
CelGetDirectionFrames(data.get(), directionFrames);
for (size_t i = 0; i < 8; i++) {
anim[i].emplace(directionFrames[i], width);
}
}
void ClearStateVariables(Player &player)
{
player.position.temp = { 0, 0 };
@ -2090,7 +2076,7 @@ player_graphic Player::getGraphic() const
uint16_t Player::getSpriteWidth() const
{
if (!HeadlessMode)
return AnimInfo.celSprite->Width();
return (*AnimInfo.sprites)[0].width();
const player_graphic graphic = getGraphic();
const HeroClass cls = GetPlayerSpriteClass(_pClass);
const PlayerWeaponGraphic weaponGraphic = GetPlayerWeaponGraphic(graphic, static_cast<PlayerWeaponGraphic>(_pgfxnum & 0xF));
@ -2229,13 +2215,13 @@ void Player::UpdatePreviewCelSprite(_cmd_id cmdId, Point point, uint16_t wParam1
}
}
if (!graphic)
if (!graphic || HeadlessMode)
return;
LoadPlrGFX(*this, *graphic);
OptionalCelSprite celSprites = AnimationData[static_cast<size_t>(*graphic)].GetCelSpritesForDirection(dir);
if (celSprites && previewCelSprite != celSprites) {
previewCelSprite = celSprites;
ClxSpriteList sprites = AnimationData[static_cast<size_t>(*graphic)].spritesForDirection(dir);
if (!previewCelSprite || *previewCelSprite != sprites[0]) {
previewCelSprite = sprites[0];
progressToNextGameTickWhenPreviewWasSet = gfProgressToNextGameTick;
}
}
@ -2258,7 +2244,7 @@ void LoadPlrGFX(Player &player, player_graphic graphic)
return;
auto &animationData = player.AnimationData[static_cast<size_t>(graphic)];
if (animationData.RawData != nullptr)
if (animationData.sprites)
return;
const HeroClass cls = GetPlayerSpriteClass(player._pClass);
@ -2320,7 +2306,7 @@ void LoadPlrGFX(Player &player, player_graphic graphic)
char pszName[256];
*fmt::format_to(pszName, FMT_COMPILE(R"(PlrGFX\{0}\{1}\{1}{2}.CL2)"), path, string_view(prefix, 3), szCel) = 0;
const uint16_t animationWidth = GetPlayerSpriteWidth(cls, graphic, animWeaponId);
SetPlayerGPtrs(pszName, animationData.RawData, animationData.CelSpritesForDirections, animationWidth);
animationData.sprites = LoadCl2Sheet(pszName, animationWidth);
}
void InitPlayerGFX(Player &player)
@ -2346,11 +2332,9 @@ void InitPlayerGFX(Player &player)
void ResetPlayerGFX(Player &player)
{
player.AnimInfo.celSprite = std::nullopt;
for (auto &animData : player.AnimationData) {
for (auto &celSprite : animData.CelSpritesForDirections)
celSprite = std::nullopt;
animData.RawData = nullptr;
player.AnimInfo.sprites = std::nullopt;
for (PlayerAnimationData &animData : player.AnimationData) {
animData.sprites = std::nullopt;
}
}
@ -2358,12 +2342,15 @@ void NewPlrAnim(Player &player, player_graphic graphic, Direction dir, int8_t nu
{
LoadPlrGFX(player, graphic);
OptionalCelSprite celSprite = player.AnimationData[static_cast<size_t>(graphic)].GetCelSpritesForDirection(dir);
OptionalClxSpriteList sprites;
float previewShownGameTickFragments = 0.F;
if (celSprite == player.previewCelSprite && !player.IsWalking())
previewShownGameTickFragments = clamp(1.F - player.progressToNextGameTickWhenPreviewWasSet, 0.F, 1.F);
player.AnimInfo.setNewAnimation(celSprite, numberOfFrames, delayLen, flags, numSkippedFrames, distributeFramesBeforeFrame, previewShownGameTickFragments);
if (!HeadlessMode) {
sprites = player.AnimationData[static_cast<size_t>(graphic)].spritesForDirection(dir);
if (player.previewCelSprite && (*sprites)[0] == *player.previewCelSprite && !player.IsWalking()) {
previewShownGameTickFragments = clamp(1.F - player.progressToNextGameTickWhenPreviewWasSet, 0.F, 1.F);
}
}
player.AnimInfo.setNewAnimation(sprites, numberOfFrames, delayLen, flags, numSkippedFrames, distributeFramesBeforeFrame, previewShownGameTickFragments);
}
void SetPlrAnims(Player &player)
@ -3512,7 +3499,8 @@ void CheckPlrSpell(bool isShiftHeld, spell_id spellID, spell_type spellType)
void SyncPlrAnim(Player &player)
{
const player_graphic graphic = player.getGraphic();
player.AnimInfo.celSprite = player.AnimationData[static_cast<size_t>(graphic)].GetCelSpritesForDirection(player._pdir);
if (!HeadlessMode)
player.AnimInfo.sprites = player.AnimationData[static_cast<size_t>(graphic)].spritesForDirection(player._pdir);
// Ensure ScrollInfo is initialized correctly
ScrollViewPort(player, WalkSettings[static_cast<size_t>(player._pdir)].scrollDir);
}

19
Source/player.h

@ -14,7 +14,7 @@
#include "engine.h"
#include "engine/actor_position.hpp"
#include "engine/animationinfo.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/path.h"
#include "engine/point.hpp"
#include "interfac.h"
@ -195,18 +195,13 @@ constexpr std::array<char, 6> CharChar = {
*/
struct PlayerAnimationData {
/**
* @brief CelSprites for the different directions
* @brief Sprite lists for each of the 8 directions.
*/
std::array<OptionalCelSprite, 8> CelSpritesForDirections;
/**
* @brief Raw Data (binary) of the CL2 file.
* Is referenced from CelSprite in celSpritesForDirections
*/
std::unique_ptr<byte[]> RawData;
OptionalOwnedClxSpriteSheet sprites;
[[nodiscard]] OptionalCelSprite GetCelSpritesForDirection(Direction direction) const
[[nodiscard]] ClxSpriteList spritesForDirection(Direction direction) const
{
return CelSpritesForDirections[static_cast<size_t>(direction)];
return (*sprites)[static_cast<size_t>(direction)];
}
};
@ -242,9 +237,9 @@ struct Player {
*/
AnimationInfo AnimInfo;
/**
* @brief Contains a optional preview CelSprite that is displayed until the current command is handled by the game logic
* @brief Contains a optional preview ClxSprite that is displayed until the current command is handled by the game logic
*/
OptionalCelSprite previewCelSprite;
OptionalClxSprite previewCelSprite;
/**
* @brief Contains the progress to next game tick when previewCelSprite was set
*/

4
Source/qol/itemlabels.cpp

@ -9,7 +9,7 @@
#include "control.h"
#include "cursor.h"
#include "engine/point.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "gmenu.h"
#include "inv.h"
#include "itemlabels.h"
@ -80,7 +80,7 @@ void AddItemToLabelQueue(int id, int x, int y)
nameWidth += MarginX * 2;
int index = ItemCAnimTbl[item._iCurs];
if (!labelCenterOffsets[index]) {
std::pair<int, int> itemBounds = Cl2MeasureSolidHorizontalBounds(*item.AnimInfo.celSprite, item.AnimInfo.currentFrame);
std::pair<int, int> itemBounds = ClxMeasureSolidHorizontalBounds((*item.AnimInfo.sprites)[item.AnimInfo.currentFrame]);
labelCenterOffsets[index].emplace((itemBounds.first + itemBounds.second) / 2);
}

11
Source/qol/stash.cpp

@ -10,7 +10,7 @@
#include "cursor.h"
#include "engine/points_in_rectangle_range.hpp"
#include "engine/rectangle.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/text_render.hpp"
#include "engine/size.hpp"
#include "hwcursor.hpp"
@ -370,15 +370,14 @@ void DrawStash(const Surface &out)
int frame = item._iCurs + CURSOR_FIRSTITEM;
const Point position = GetStashSlotCoord(item.position) + offset;
const CelSprite cel { GetInvItemSprite(frame) };
const int celFrame = GetInvItemFrame(frame);
const ClxSprite sprite = GetInvItemSprite(frame);
if (pcursstashitem == itemId) {
uint8_t color = GetOutlineColor(item, true);
Cl2DrawOutline(out, color, position, cel, celFrame);
ClxDrawOutline(out, color, position, sprite);
}
DrawItem(item, out, position, cel, celFrame);
DrawItem(item, out, position, sprite);
}
Point position = GetPanelPosition(UiPanels::Stash);
@ -621,7 +620,7 @@ void DrawGoldWithdraw(const Surface &out, int amount)
const int dialogX = 30;
Cl2Draw(out, GetPanelPosition(UiPanels::Stash, { dialogX, 178 }), CelSprite { *pGBoxBuff }, 0);
ClxDraw(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);

10
Source/quests.cpp

@ -12,7 +12,7 @@
#include "cursor.h"
#include "engine/load_file.hpp"
#include "engine/random.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/text_render.hpp"
#include "init.h"
#include "levels/gendung.h"
@ -30,7 +30,7 @@
namespace devilution {
bool QuestLogIsOpen;
OptionalOwnedCelSprite pQLogCel;
OptionalOwnedClxSpriteList pQLogCel;
/** Contains the quests of the current game. */
Quest Quests[MAXQUESTS];
Point ReturnLvlPosition;
@ -215,11 +215,11 @@ void PrintQLString(const Surface &out, int x, int y, string_view str, bool marke
int width = GetLineWidth(str);
x += std::max((257 - width) / 2, 0);
if (marked) {
Cl2Draw(out, GetPanelPosition(UiPanels::Quest, { x - 20, y + 13 }), CelSprite { *pSPentSpn2Cels }, PentSpn2Spin());
ClxDraw(out, GetPanelPosition(UiPanels::Quest, { x - 20, y + 13 }), (*pSPentSpn2Cels)[PentSpn2Spin()]);
}
DrawString(out, str, { GetPanelPosition(UiPanels::Quest, { x, y }), { 257, 0 } }, disabled ? UiFlags::ColorWhitegold : UiFlags::ColorWhite);
if (marked) {
Cl2Draw(out, GetPanelPosition(UiPanels::Quest, { x + width + 7, y + 13 }), CelSprite { *pSPentSpn2Cels }, PentSpn2Spin());
ClxDraw(out, GetPanelPosition(UiPanels::Quest, { x + width + 7, y + 13 }), (*pSPentSpn2Cels)[PentSpn2Spin()]);
}
}
@ -676,7 +676,7 @@ void DrawQuestLog(const Surface &out)
SelectedQuest = l;
}
const auto x = InnerPanel.position.x;
Cl2Draw(out, GetPanelPosition(UiPanels::Quest, { 0, 351 }), CelSprite { *pQLogCel }, 0);
ClxDraw(out, GetPanelPosition(UiPanels::Quest, { 0, 351 }), (*pQLogCel)[0]);
int y = InnerPanel.position.y + ListYOffset;
for (int i = 0; i < EncounteredQuestCount; i++) {
if (i == FirstFinishedQuest) {

4
Source/quests.h

@ -8,7 +8,7 @@
#include <cstdint>
#include "engine.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/point.hpp"
#include "levels/gendung.h"
#include "monster.h"
@ -73,7 +73,7 @@ struct QuestData {
};
extern bool QuestLogIsOpen;
extern OptionalOwnedCelSprite pQLogCel;
extern OptionalOwnedClxSpriteList pQLogCel;
extern DVL_API_FOR_TEST Quest Quests[MAXQUESTS];
extern Point ReturnLvlPosition;
extern dungeon_type ReturnLevelType;

21
Source/stores.cpp

@ -13,7 +13,7 @@
#include "cursor.h"
#include "engine/load_cel.hpp"
#include "engine/random.hpp"
#include "engine/render/cl2_render.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/text_render.hpp"
#include "init.h"
#include "minitext.h"
@ -196,7 +196,7 @@ void CalculateLineHeights()
void DrawSTextBack(const Surface &out)
{
const Point uiPosition = GetUIRectangle().position;
Cl2Draw(out, { uiPosition.x + 320 + 24, 327 + uiPosition.y }, CelSprite { *pSTextBoxCels }, 0);
ClxDraw(out, { uiPosition.x + 320 + 24, 327 + uiPosition.y }, (*pSTextBoxCels)[0]);
DrawHalfTransparentRectTo(out, uiPosition.x + 347, uiPosition.y + 28, 265, 297);
}
@ -205,19 +205,18 @@ void DrawSSlider(const Surface &out, int y1, int y2)
const Point uiPosition = GetUIRectangle().position;
int yd1 = y1 * 12 + 44 + uiPosition.y;
int yd2 = y2 * 12 + 44 + uiPosition.y;
CelSprite sprite { *pSTextSlidCels };
if (stextscrlubtn != -1)
Cl2Draw(out, { uiPosition.x + 601, yd1 }, sprite, 11);
ClxDraw(out, { uiPosition.x + 601, yd1 }, (*pSTextSlidCels)[11]);
else
Cl2Draw(out, { uiPosition.x + 601, yd1 }, sprite, 9);
ClxDraw(out, { uiPosition.x + 601, yd1 }, (*pSTextSlidCels)[9]);
if (stextscrldbtn != -1)
Cl2Draw(out, { uiPosition.x + 601, yd2 }, sprite, 10);
ClxDraw(out, { uiPosition.x + 601, yd2 }, (*pSTextSlidCels)[10]);
else
Cl2Draw(out, { uiPosition.x + 601, yd2 }, sprite, 8);
ClxDraw(out, { uiPosition.x + 601, yd2 }, (*pSTextSlidCels)[8]);
yd1 += 12;
int yd3 = yd1;
for (; yd3 < yd2; yd3 += 12) {
Cl2Draw(out, { uiPosition.x + 601, yd3 }, sprite, 13);
ClxDraw(out, { uiPosition.x + 601, yd3 }, (*pSTextSlidCels)[13]);
}
if (stextsel == BackButtonLine())
yd3 = stextlhold;
@ -227,7 +226,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;
Cl2Draw(out, { uiPosition.x + 601, (y1 + 1) * 12 + 44 + uiPosition.y + yd3 }, sprite, 12);
ClxDraw(out, { uiPosition.x + 601, (y1 + 1) * 12 + 44 + uiPosition.y + yd3 }, (*pSTextSlidCels)[12]);
}
void AddSLine(size_t y)
@ -2174,13 +2173,13 @@ void DrawSelector(const Surface &out, const Rectangle &rect, string_view text, U
if (HasAnyOf(flags, UiFlags::AlignCenter))
x1 += (rect.size.width - lineWidth) / 2;
Cl2Draw(out, { x1, rect.position.y + 13 }, CelSprite { *pSPentSpn2Cels }, PentSpn2Spin());
ClxDraw(out, { x1, rect.position.y + 13 }, (*pSPentSpn2Cels)[PentSpn2Spin()]);
int x2 = rect.position.x + rect.size.width + 5;
if (HasAnyOf(flags, UiFlags::AlignCenter))
x2 = rect.position.x + (rect.size.width - lineWidth) / 2 + lineWidth + 5;
Cl2Draw(out, { x2, rect.position.y + 13 }, CelSprite { *pSPentSpn2Cels }, PentSpn2Spin());
ClxDraw(out, { x2, rect.position.y + 13 }, (*pSPentSpn2Cels)[PentSpn2Spin()]);
}
} // namespace

2
Source/stores.h

@ -8,7 +8,7 @@
#include "DiabloUI/ui_flags.hpp"
#include "control.h"
#include "engine.h"
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "utils/attributes.h"
#include "utils/stdcompat/optional.hpp"

2
Source/storm/storm_net.cpp

@ -91,6 +91,8 @@ bool SNetUnregisterEventHandler(event_type evtype)
#ifndef NONET
std::lock_guard<SdlMutex> lg(storm_net_mutex);
#endif
if (dvlnet_inst == nullptr)
return true;
return dvlnet_inst->SNetUnregisterEventHandler(evtype);
}

29
Source/towners.cpp

@ -1,20 +1,19 @@
#include "towners.h"
#include "cursor.h"
#include "engine/cel_header.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/load_cel.hpp"
#include "engine/load_file.hpp"
#include "engine/random.hpp"
#include "inv.h"
#include "minitext.h"
#include "stores.h"
#include "utils/cel_to_cl2.hpp"
#include "utils/language.h"
namespace devilution {
namespace {
std::unique_ptr<byte[]> CowCels;
OptionalOwnedClxSpriteSheet CowSprites;
int CowMsg;
int CowClicks;
@ -29,9 +28,9 @@ struct TownerData {
void (*talk)(Player &player, Towner &towner);
};
void NewTownerAnim(Towner &towner, byte *pAnim, uint8_t numFrames, int delay)
void NewTownerAnim(Towner &towner, ClxSpriteList sprites, uint8_t numFrames, int delay)
{
towner._tAnimData = pAnim;
towner.anim.emplace(sprites);
towner._tAnimLen = numFrames;
towner._tAnimFrame = 0;
towner._tAnimCnt = 0;
@ -54,8 +53,9 @@ void InitTownerInfo(int i, const TownerData &townerData)
void LoadTownerAnimations(Towner &towner, const char *path, int frames, int delay)
{
towner.data = LoadCelAsCl2(path, towner._tAnimWidth).data();
NewTownerAnim(towner, towner.data.get(), frames, delay);
towner.ownedAnim = std::nullopt;
towner.ownedAnim = LoadCel(path, towner._tAnimWidth);
NewTownerAnim(towner, *towner.ownedAnim, frames, delay);
}
/**
@ -212,8 +212,8 @@ void InitCows(Towner &towner, const TownerData &townerData)
towner._tAnimWidth = 128;
towner.animOrder = nullptr;
towner.animOrderSize = 0;
CelGetDirectionFrames(CowCels.get(), towner._tNAnim);
NewTownerAnim(towner, towner._tNAnim[static_cast<size_t>(townerData.dir)], 12, 3);
NewTownerAnim(towner, (*CowSprites)[static_cast<size_t>(townerData.dir)], 12, 3);
towner._tAnimFrame = GenerateRnd(11);
towner.name = _("Cow");
@ -818,9 +818,9 @@ bool IsTownerPresent(_talker_id npc)
void InitTowners()
{
assert(CowCels == nullptr);
assert(!CowSprites);
CowCels = LoadCelAsCl2("Towners\\Animals\\Cow.CEL", 128).data();
CowSprites.emplace(LoadCelSheet("Towners\\Animals\\Cow.CEL", 128));
int i = 0;
for (const auto &townerData : TownersData) {
@ -834,11 +834,10 @@ void InitTowners()
void FreeTownerGFX()
{
for (auto &towner : Towners) {
towner.data = nullptr;
for (Towner &towner : Towners) {
towner.ownedAnim = std::nullopt;
}
CowCels = nullptr;
CowSprites = std::nullopt;
}
void ProcessTowners()

10
Source/towners.h

@ -36,9 +36,9 @@ enum _talker_id : uint8_t {
};
struct Towner {
byte *_tNAnim[8];
std::unique_ptr<byte[]> data;
byte *_tAnimData;
OptionalOwnedClxSpriteList ownedAnim;
OptionalClxSpriteList anim;
/** Used to get a voice line and text related to active quests when the player speaks to a town npc */
int16_t seed;
/** Tile position of NPC */
@ -60,9 +60,9 @@ struct Towner {
void (*talk)(Player &player, Towner &towner);
_talker_id _ttype;
CelSprite Sprite() const
ClxSprite currentSprite() const
{
return CelSprite { _tAnimData, _tAnimWidth };
return (*anim)[_tAnimFrame];
}
};

13
Source/utils/cel_to_cl2.hpp

@ -1,13 +0,0 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include "engine/cel_sprite.hpp"
#include "utils/pointer_value_union.hpp"
namespace devilution {
OwnedCelSprite CelToCl2(const uint8_t *data, size_t size, PointerOrValue<uint16_t> widthOrWidths);
} // namespace devilution

31
Source/utils/cel_to_cl2.cpp → Source/utils/cel_to_clx.cpp

@ -1,4 +1,4 @@
#include "utils/cel_to_cl2.hpp"
#include "utils/cel_to_clx.hpp"
#include <cstring>
@ -98,7 +98,7 @@ void AppendCl2PixelsOrFillRun(const uint8_t *src, unsigned length, std::vector<u
} // namespace
OwnedCelSprite CelToCl2(const uint8_t *data, size_t size, PointerOrValue<uint16_t> widthOrWidths)
OwnedClxSpriteListOrSheet CelToClx(const uint8_t *data, size_t size, PointerOrValue<uint16_t> widthOrWidths)
{
// A CEL file either begins with:
// 1. A CEL header.
@ -151,16 +151,15 @@ OwnedCelSprite CelToCl2(const uint8_t *data, size_t size, PointerOrValue<uint16_
const unsigned frameWidth = widthOrWidths.HoldsPointer() ? widthOrWidths.AsPointer()[frame - 1] : widthOrWidths.AsValue();
// Frame header: 5 16-bit offsets to 32-pixel height blocks.
// CLX frame header.
const size_t frameHeaderPos = cl2Data.size();
constexpr size_t FrameHeaderSize = 10;
cl2Data.resize(cl2Data.size() + FrameHeaderSize);
// Frame header offset (first of five):
WriteLE16(&cl2Data[frameHeaderPos], FrameHeaderSize);
WriteLE16(&cl2Data[frameHeaderPos + 2], frameWidth);
unsigned transparentRunWidth = 0;
size_t line = 0;
size_t frameHeight = 0;
while (src != srcEnd) {
// Process line:
for (unsigned remainingCelWidth = frameWidth; remainingCelWidth != 0;) {
@ -176,20 +175,10 @@ OwnedCelSprite CelToCl2(const uint8_t *data, size_t size, PointerOrValue<uint16_
}
remainingCelWidth -= val;
}
// Frame header offset:
switch (++line) {
case 32:
case 64:
case 96:
case 128:
// Finish any active transparent run to not allow it to go over an offset line boundary.
AppendCl2TransparentRun(transparentRunWidth, cl2Data);
transparentRunWidth = 0;
WriteLE16(&cl2Data[frameHeaderPos + line / 16], static_cast<uint16_t>(cl2Data.size() - frameHeaderPos));
break;
}
++frameHeight;
}
WriteLE16(&cl2Data[frameHeaderPos + 4], frameHeight);
memset(&cl2Data[frameHeaderPos + 6], 0, 4);
AppendCl2TransparentRun(transparentRunWidth, cl2Data);
}
@ -197,12 +186,12 @@ OwnedCelSprite CelToCl2(const uint8_t *data, size_t size, PointerOrValue<uint16_
data = srcEnd;
}
auto out = std::unique_ptr<byte[]>(new byte[cl2Data.size()]);
auto out = std::unique_ptr<uint8_t[]>(new uint8_t[cl2Data.size()]);
memcpy(&out[0], cl2Data.data(), cl2Data.size());
#ifdef DEBUG_CEL_TO_CL2_SIZE
std::cout << "\t" << size << "\t" << cl2Data.size() << "\t" << std::setprecision(1) << std::fixed << (static_cast<int>(cl2Data.size()) - static_cast<int>(size)) / ((float)size) * 100 << "%" << std::endl;
#endif
return OwnedCelSprite { std::move(out), widthOrWidths };
return OwnedClxSpriteListOrSheet { std::move(out), static_cast<uint16_t>(numGroups == 1 ? 0 : numGroups) };
}
} // namespace devilution

13
Source/utils/cel_to_clx.hpp

@ -0,0 +1,13 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include "engine/clx_sprite.hpp"
#include "utils/pointer_value_union.hpp"
namespace devilution {
OwnedClxSpriteListOrSheet CelToClx(const uint8_t *data, size_t size, PointerOrValue<uint16_t> widthOrWidths);
} // namespace devilution

97
Source/utils/cl2_to_clx.cpp

@ -0,0 +1,97 @@
#include "utils/cl2_to_clx.hpp"
#include <cstring>
#include "utils/endian.hpp"
namespace devilution {
namespace {
constexpr bool IsCl2Opaque(uint8_t control)
{
constexpr uint8_t Cl2OpaqueMin = 0x80;
return control >= Cl2OpaqueMin;
}
constexpr uint8_t GetCl2OpaquePixelsWidth(uint8_t control)
{
return -static_cast<std::int8_t>(control);
}
constexpr bool IsCl2OpaqueFill(uint8_t control)
{
constexpr uint8_t Cl2FillMax = 0xBE;
return control <= Cl2FillMax;
}
constexpr uint8_t GetCl2OpaqueFillWidth(uint8_t control)
{
constexpr uint8_t Cl2FillEnd = 0xBF;
return static_cast<int_fast16_t>(Cl2FillEnd - control);
}
size_t CountCl2FramePixels(const uint8_t *src, const uint8_t *srcEnd)
{
size_t numPixels = 0;
while (src != srcEnd) {
uint8_t val = *src++;
if (IsCl2Opaque(val)) {
if (IsCl2OpaqueFill(val)) {
numPixels += GetCl2OpaqueFillWidth(val);
++src;
} else {
val = GetCl2OpaquePixelsWidth(val);
numPixels += val;
src += val;
}
} else {
numPixels += val;
}
}
return numPixels;
}
} // namespace
uint16_t Cl2ToClx(uint8_t *data, size_t size, PointerOrValue<uint16_t> widthOrWidths)
{
uint32_t numGroups = 1;
const uint32_t maybeNumFrames = LoadLE32(data);
uint8_t *groupBegin = data;
// If it is a number of frames, then the last frame offset will be equal to the size of the file.
if (LoadLE32(&data[maybeNumFrames * 4 + 4]) != size) {
// maybeNumFrames is the address of the first group, right after
// the list of group offsets.
numGroups = maybeNumFrames / 4;
}
for (size_t group = 0; group < numGroups; ++group) {
uint32_t numFrames;
if (numGroups == 1) {
numFrames = maybeNumFrames;
} else {
groupBegin = &data[LoadLE32(&data[group * 4])];
numFrames = LoadLE32(groupBegin);
}
uint8_t *frameEnd = &groupBegin[LoadLE32(&groupBegin[4])];
for (size_t frame = 1; frame <= numFrames; ++frame) {
uint8_t *frameBegin = frameEnd;
frameEnd = &groupBegin[LoadLE32(&groupBegin[4 * (frame + 1)])];
constexpr size_t Cl2FrameHeaderSize = 10;
const size_t numPixels = CountCl2FramePixels(frameBegin + Cl2FrameHeaderSize, frameEnd);
const uint16_t frameWidth = widthOrWidths.HoldsPointer() ? widthOrWidths.AsPointer()[frame - 1] : widthOrWidths.AsValue();
const uint16_t frameHeight = numPixels / frameWidth;
WriteLE16(&frameBegin[2], frameWidth);
WriteLE16(&frameBegin[4], frameHeight);
memset(&frameBegin[6], 0, 4);
}
}
return numGroups == 1 ? 0 : numGroups;
}
} // namespace devilution

26
Source/utils/cl2_to_clx.hpp

@ -0,0 +1,26 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <memory>
#include "engine/clx_sprite.hpp"
#include "utils/pointer_value_union.hpp"
namespace devilution {
/**
* @brief Converts CL2 to CLX in-place.
*
* @return uint16_t The number of lists in a sheet if it is a sheet, 0 otherwise.
*/
uint16_t Cl2ToClx(uint8_t *data, size_t size, PointerOrValue<uint16_t> widthOrWidths);
inline OwnedClxSpriteListOrSheet Cl2ToClx(std::unique_ptr<uint8_t[]> &&data, size_t size, PointerOrValue<uint16_t> widthOrWidths)
{
const uint16_t numLists = Cl2ToClx(data.get(), size, widthOrWidths);
return OwnedClxSpriteListOrSheet { std::move(data), numLists };
}
} // namespace devilution

81
Source/utils/intrusive_optional.hpp

@ -0,0 +1,81 @@
#pragma once
#include <type_traits>
#include <utility>
#include "appfat.h"
#include "utils/stdcompat/optional.hpp"
/// An optional that uses a field of the stored class and some value to store nullopt.
#define DEFINE_INTRUSIVE_OPTIONAL_IMPL(OPTIONAL_CLASS, VALUE_CLASS, FIELD, NULL_VALUE, CONSTEXPR) \
public: \
CONSTEXPR OPTIONAL_CLASS() = default; \
\
template <class U = VALUE_CLASS> \
CONSTEXPR OPTIONAL_CLASS(VALUE_CLASS &&value) \
: value_(std::forward<U>(value)) \
{ \
} \
\
CONSTEXPR OPTIONAL_CLASS(std::nullopt_t) \
: OPTIONAL_CLASS() \
{ \
} \
\
template <typename... Args> \
CONSTEXPR VALUE_CLASS &emplace(Args &&...args) \
{ \
value_ = VALUE_CLASS(std::forward<Args>(args)...); \
return value_; \
} \
\
template <class U = VALUE_CLASS> \
CONSTEXPR OPTIONAL_CLASS &operator=(VALUE_CLASS &&value) \
{ \
value_ = std::forward<U>(value); \
return *this; \
} \
\
CONSTEXPR OPTIONAL_CLASS &operator=(std::nullopt_t) \
{ \
value_ = VALUE_CLASS {}; \
return *this; \
} \
\
CONSTEXPR const VALUE_CLASS &operator*() const \
{ \
assert(value_.FIELD != NULL_VALUE); \
return value_; \
} \
\
CONSTEXPR VALUE_CLASS &operator*() \
{ \
assert(value_.FIELD != NULL_VALUE); \
return value_; \
} \
\
CONSTEXPR const VALUE_CLASS *operator->() const \
{ \
assert(value_.FIELD != NULL_VALUE); \
return &value_; \
} \
\
CONSTEXPR VALUE_CLASS *operator->() \
{ \
assert(value_.FIELD != NULL_VALUE); \
return &value_; \
} \
\
CONSTEXPR operator bool() const \
{ \
return value_.FIELD != NULL_VALUE; \
} \
\
private: \
VALUE_CLASS value_;
#define DEFINE_CONSTEXPR_INTRUSIVE_OPTIONAL(OPTIONAL_CLASS, VALUE_CLASS, FIELD, NULL_VALUE) \
DEFINE_INTRUSIVE_OPTIONAL_IMPL(OPTIONAL_CLASS, VALUE_CLASS, FIELD, NULL_VALUE, constexpr)
#define DEFINE_INTRUSIVE_OPTIONAL(OPTIONAL_CLASS, VALUE_CLASS, FIELD, NULL_VALUE) \
DEFINE_INTRUSIVE_OPTIONAL_IMPL(OPTIONAL_CLASS, VALUE_CLASS, FIELD, NULL_VALUE, )

41
Source/utils/pcx_to_cl2.cpp → Source/utils/pcx_to_clx.cpp

@ -1,8 +1,9 @@
#include "utils/pcx_to_cl2.hpp"
#include "utils/pcx_to_clx.hpp"
#include <array>
#include <cstdint>
#include <cstring>
#include <array>
#include <memory>
#include <vector>
@ -110,7 +111,7 @@ size_t GetReservationSize(size_t pcxSize)
} // namespace
std::optional<OwnedCelSpriteWithFrameHeight> PcxToCl2(SDL_RWops *handle, int numFramesOrFrameHeight, std::optional<uint8_t> transparentColor, SDL_Color *outPalette)
OptionalOwnedClxSpriteList PcxToClx(SDL_RWops *handle, int numFramesOrFrameHeight, std::optional<uint8_t> transparentColor, SDL_Color *outPalette)
{
int width;
int height;
@ -145,7 +146,7 @@ std::optional<OwnedCelSpriteWithFrameHeight> PcxToCl2(SDL_RWops *handle, int num
return std::nullopt;
}
// CEL header: frame count, frame offset for each frame, file size
// CLX header: frame count, frame offset for each frame, file size
std::vector<uint8_t> cl2Data;
cl2Data.reserve(GetReservationSize(pixelDataSize));
cl2Data.resize(4 * (2 + static_cast<size_t>(numFrames)));
@ -159,13 +160,20 @@ std::optional<OwnedCelSpriteWithFrameHeight> PcxToCl2(SDL_RWops *handle, int num
for (unsigned frame = 1; frame <= numFrames; ++frame) {
WriteLE32(&cl2Data[4 * static_cast<size_t>(frame)], static_cast<uint32_t>(cl2Data.size()));
// Frame header: 5 16-bit offsets to 32-pixel height blocks.
// Frame header: 5 16-bit values:
// 1. Offset to start of the pixel data.
// 2. Width
// 3. Height
// 4..5. Unused (0)
const size_t frameHeaderPos = cl2Data.size();
constexpr size_t FrameHeaderSize = 10;
cl2Data.resize(cl2Data.size() + FrameHeaderSize);
// Frame header offset (first of five):
// Frame header:
WriteLE16(&cl2Data[frameHeaderPos], FrameHeaderSize);
WriteLE16(&cl2Data[frameHeaderPos + 2], static_cast<uint16_t>(width));
WriteLE16(&cl2Data[frameHeaderPos + 4], static_cast<uint16_t>(frameHeight));
memset(&cl2Data[frameHeaderPos + 6], 0, 4);
for (unsigned j = 0; j < frameHeight; ++j) {
uint8_t *buffer = &frameBuffer[static_cast<size_t>(j) * width];
@ -212,19 +220,7 @@ std::optional<OwnedCelSpriteWithFrameHeight> PcxToCl2(SDL_RWops *handle, int num
} else {
AppendCl2PixelsOrFillRun(src, width, cl2Data);
}
// Frame header offset:
switch (++line) {
case 32:
case 64:
case 96:
case 128:
// Finish any active transparent run to not allow it to go over an offset line boundary.
AppendCl2TransparentRun(transparentRunWidth, cl2Data);
transparentRunWidth = 0;
WriteLE16(&cl2Data[frameHeaderPos + line / 16], static_cast<uint16_t>(cl2Data.size() - frameHeaderPos));
break;
}
++line;
}
AppendCl2TransparentRun(transparentRunWidth, cl2Data);
}
@ -252,15 +248,12 @@ std::optional<OwnedCelSpriteWithFrameHeight> PcxToCl2(SDL_RWops *handle, int num
frameBuffer = nullptr;
fileBuffer = nullptr;
auto out = std::unique_ptr<byte[]>(new byte[cl2Data.size()]);
auto out = std::unique_ptr<uint8_t[]>(new uint8_t[cl2Data.size()]);
memcpy(&out[0], cl2Data.data(), cl2Data.size());
#ifdef DEBUG_PCX_TO_CL2_SIZE
std::cout << "\t" << pixelDataSize << "\t" << cl2Data.size() << "\t" << std::setprecision(1) << std::fixed << (static_cast<int>(cl2Data.size()) - static_cast<int>(pixelDataSize)) / ((float)pixelDataSize) * 100 << "%" << std::endl;
#endif
return OwnedCelSpriteWithFrameHeight {
OwnedCelSprite { std::move(out), static_cast<uint16_t>(width) },
static_cast<uint16_t>(frameHeight)
};
return OwnedClxSpriteList { std::move(out) };
}
} // namespace devilution

6
Source/utils/pcx_to_cl2.hpp → Source/utils/pcx_to_clx.hpp

@ -4,18 +4,18 @@
#include <SDL.h>
#include "engine/cel_sprite.hpp"
#include "engine/clx_sprite.hpp"
#include "utils/stdcompat/optional.hpp"
namespace devilution {
/**
* @brief Loads a PCX file as a CL2 sprite.
* @brief Loads a PCX file as a CLX sprite.
*
* @param handle A non-null SDL_RWops handle. Closed by this function.
* @param numFramesOrFrameHeight Pass a positive value with the number of frames, or the frame height as a negative value.
* @param transparentColorIndex The PCX palette index of the transparent color.
*/
std::optional<OwnedCelSpriteWithFrameHeight> PcxToCl2(SDL_RWops *handle, int numFramesOrFrameHeight = 1, std::optional<uint8_t> transparentColor = std::nullopt, SDL_Color *outPalette = nullptr);
OptionalOwnedClxSpriteList PcxToClx(SDL_RWops *handle, int numFramesOrFrameHeight = 1, std::optional<uint8_t> transparentColor = std::nullopt, SDL_Color *outPalette = nullptr);
} // namespace devilution
Loading…
Cancel
Save