diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index ec178f4a0..68fb4d1e2 100644 --- a/Source/CMakeLists.txt +++ b/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 diff --git a/Source/DiabloUI/button.cpp b/Source/DiabloUI/button.cpp index 79f7ea20f..55c9d8c74 100644 --- a/Source/DiabloUI/button.cpp +++ b/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 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()) { diff --git a/Source/DiabloUI/button.h b/Source/DiabloUI/button.h index 008554074..91b46b0bd 100644 --- a/Source/DiabloUI/button.h +++ b/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); diff --git a/Source/DiabloUI/credits.cpp b/Source/DiabloUI/credits.cpp index 2e440c8e2..0e48f4431 100644 --- a/Source/DiabloUI/credits.cpp +++ b/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"); } diff --git a/Source/DiabloUI/diabloui.cpp b/Source/DiabloUI/diabloui.cpp index 437e7af50..5a3495569 100644 --- a/Source/DiabloUI/diabloui.cpp +++ b/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 ArtLogo; +OptionalOwnedClxSpriteList ArtLogo; -std::array, 3> ArtFocus; +std::array ArtFocus; -std::optional ArtBackgroundWidescreen; -std::optional ArtBackground; +OptionalOwnedClxSpriteList ArtBackgroundWidescreen; +OptionalOwnedClxSpriteList ArtBackground; Art ArtCursor; bool textInputActive = true; @@ -57,9 +57,9 @@ std::size_t SelectedItem = 0; namespace { -std::optional ArtHero; +OptionalOwnedClxSpriteList ArtHero; std::array::value + 1> ArtHeroPortraitOrder; -std::array, enum_size::value + 1> ArtHeroOverrides; +std::array::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(PortraitHeight)); + ArtHero = LoadPcxSpriteList("ui_art\\heros.pcx", -static_cast(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 &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(frames), /*transparentColor=*/std::nullopt, pPal); + ArtBackground = LoadPcxSpriteList(pszFile, static_cast(frames), /*transparentColor=*/std::nullopt, pPal); if (!ArtBackground) return; @@ -710,17 +710,17 @@ void UiAddBackground(std::vector> *vecDialog) { const SDL_Rect rect = MakeSdlRect(0, GetUIRectangle().position.y, 0, 0); if (ArtBackgroundWidescreen) { - vecDialog->push_back(std::make_unique(ArtBackgroundWidescreen->sprite(), rect, UiFlags::AlignCenter)); + vecDialog->push_back(std::make_unique((*ArtBackgroundWidescreen)[0], rect, UiFlags::AlignCenter)); } if (ArtBackground) { - vecDialog->push_back(std::make_unique(ArtBackground->sprite(0), rect, UiFlags::AlignCenter)); + vecDialog->push_back(std::make_unique((*ArtBackground)[0], rect, UiFlags::AlignCenter)); } } void UiAddLogo(std::vector> *vecDialog) { - vecDialog->push_back(std::make_unique( - ArtLogo->sheet(), MakeSdlRect(0, GetUIRectangle().position.y, 0, 0), UiFlags::AlignCenter)); + vecDialog->push_back(std::make_unique( + *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(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(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(sprite.frameHeight)) / 2; + const int y = rect.y + (rect.h - static_cast(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(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(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(item)); break; - case UiType::ImageCl2: - Render(static_cast(item)); + case UiType::ImageClx: + Render(static_cast(item)); break; - case UiType::ImageAnimatedCl2: - Render(static_cast(item)); + case UiType::ImageAnimatedClx: + Render(static_cast(item)); break; case UiType::ArtTextButton: Render(static_cast(item)); diff --git a/Source/DiabloUI/diabloui.h b/Source/DiabloUI/diabloui.h index 27e3b40bc..1b8dfbf6c 100644 --- a/Source/DiabloUI/diabloui.h +++ b/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 ArtLogo; -extern std::array, 3> ArtFocus; -extern std::optional ArtBackgroundWidescreen; -extern std::optional ArtBackground; +extern OptionalOwnedClxSpriteList ArtLogo; +extern std::array ArtFocus; +extern OptionalOwnedClxSpriteList ArtBackgroundWidescreen; +extern OptionalOwnedClxSpriteList ArtBackground; extern Art ArtCursor; extern bool (*gfnHeroInfo)(bool (*fninfofunc)(_uiheroinfo *)); @@ -107,7 +107,7 @@ void UiPollAndRender(std::function eventHandler = nullptr); void UiRenderItems(const std::vector &items); void UiRenderItems(const std::vector> &items); void UiInitList_clear(); -CelFrameWithHeight UiGetHeroDialogSprite(size_t heroClassIndex); +ClxSprite UiGetHeroDialogSprite(size_t heroClassIndex); void mainmenu_restart_repintro(); } // namespace devilution diff --git a/Source/DiabloUI/dialogs.cpp b/Source/DiabloUI/dialogs.cpp index bf411cbff..732c1d83b 100644 --- a/Source/DiabloUI/dialogs.cpp +++ b/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 dialogPcx; +OptionalOwnedClxSpriteList ownedDialogSprite; std::string wrappedText; bool dialogEnd; @@ -34,22 +34,22 @@ void DialogActionOK() std::vector> vecNULL; std::vector> vecOkDialog; -std::optional 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 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(*dialogSprite, rect1)); + vecOkDialog.push_back(std::make_unique(*dialogSprite, rect1)); SDL_Rect rect2 = MakeSdlRect(uiPosition.x + 200, uiPosition.y + 211, textWidth, 80); vecOkDialog.push_back(std::make_unique(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(_("OK"), &DialogActionOK, rect3)); } else { SDL_Rect rect1 = MakeSdlRect(uiPosition.x + 127, uiPosition.y + 100, dialogSprite->width(), dialogSprite->height()); - vecOkDialog.push_back(std::make_unique(*dialogSprite, rect1)); + vecOkDialog.push_back(std::make_unique(*dialogSprite, rect1)); SDL_Rect rect2 = MakeSdlRect(uiPosition.x + 147, uiPosition.y + 110, textWidth, 20); vecOkDialog.push_back(std::make_unique(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(); diff --git a/Source/DiabloUI/mainmenu.cpp b/Source/DiabloUI/mainmenu.cpp index 94d03dc87..ce1063cfb 100644 --- a/Source/DiabloUI/mainmenu.cpp +++ b/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"); diff --git a/Source/DiabloUI/progress.cpp b/Source/DiabloUI/progress.cpp index b8dd0854a..d6c8a8def 100644 --- a/Source/DiabloUI/progress.cpp +++ b/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 ArtPopupSm; -std::optional ArtProgBG; -std::optional ProgFil; +OptionalOwnedClxSpriteList ArtPopupSm; +OptionalOwnedClxSpriteList ArtProgBG; +OptionalOwnedClxSpriteList ProgFil; std::vector> 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 }); } diff --git a/Source/DiabloUI/scrollbar.cpp b/Source/DiabloUI/scrollbar.cpp index e3a6fb69d..d0da8d689 100644 --- a/Source/DiabloUI/scrollbar.cpp +++ b/Source/DiabloUI/scrollbar.cpp @@ -4,15 +4,15 @@ namespace devilution { -std::optional ArtScrollBarBackground; -std::optional ArtScrollBarThumb; -std::optional 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() diff --git a/Source/DiabloUI/scrollbar.h b/Source/DiabloUI/scrollbar.h index 6250238bc..ed6a34676 100644 --- a/Source/DiabloUI/scrollbar.h +++ b/Source/DiabloUI/scrollbar.h @@ -3,13 +3,13 @@ #include #include "DiabloUI/ui_item.h" -#include "engine/cel_sprite.hpp" +#include "engine/clx_sprite.hpp" namespace devilution { -extern std::optional ArtScrollBarBackground; -extern std::optional ArtScrollBarThumb; -extern std::optional 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(sb->m_arrow.frameHeight); + Tmp.h = static_cast(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(sb->m_rect.y + sb->m_rect.h - sb->m_arrow.frameHeight); + Tmp.y = static_cast(sb->m_rect.y + sb->m_rect.h - sb->m_arrow[0].height()); Tmp.w = SCROLLBAR_ARROW_WIDTH, - Tmp.h = static_cast(sb->m_arrow.frameHeight); + Tmp.h = static_cast(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(sb->m_rect.y + sb->m_arrow.frameHeight); + Tmp.y = static_cast(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(sb->m_rect.x + THUMB_OFFSET_X); - Tmp.y = static_cast(sb->m_rect.y + sb->m_arrow.frameHeight + thumb_y); + Tmp.y = static_cast(sb->m_rect.y + sb->m_arrow[0].height() + thumb_y); Tmp.w = static_cast(sb->m_rect.w - THUMB_OFFSET_X); Tmp.h = static_cast(sb->m_thumb.height()); diff --git a/Source/DiabloUI/selgame.cpp b/Source/DiabloUI/selgame.cpp index a8e821833..e39c96d5e 100644 --- a/Source/DiabloUI/selgame.cpp +++ b/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(ArtScrollBarBackground->sprite(), ArtScrollBarThumb->sprite(), ArtScrollBarArrow->sheet(), rectScrollbar)); + vecSelGameDialog.push_back(std::make_unique((*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(_(ConnectionNames[provider]).data(), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); diff --git a/Source/DiabloUI/selhero.cpp b/Source/DiabloUI/selhero.cpp index 9e167d770..f17e67245 100644 --- a/Source/DiabloUI/selhero.cpp +++ b/Source/DiabloUI/selhero.cpp @@ -45,7 +45,7 @@ std::vector> vecSelHeroDialog; std::vector> vecSelHeroDlgItems; std::vector> 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( - ArtBackground->sprite(0), - MakeSdlRect(0, GetUIRectangle().position.y, 0, 0), - UiFlags::AlignCenter)); + std::make_unique((*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(&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(UiGetHeroDialogSprite(0), rect2, UiFlags::None); + auto heroImg = std::make_unique(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(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(ArtScrollBarBackground->sprite(), ArtScrollBarThumb->sprite(), ArtScrollBarArrow->sheet(), rect2)); + vecSelDlgItems.push_back(std::make_unique((*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(_("OK"), &UiFocusNavigationSelect, rect3, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiGold)); diff --git a/Source/DiabloUI/selstart.cpp b/Source/DiabloUI/selstart.cpp index 9ad72516d..4ddc0daef 100644 --- a/Source/DiabloUI/selstart.cpp +++ b/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); diff --git a/Source/DiabloUI/settingsmenu.cpp b/Source/DiabloUI/settingsmenu.cpp index 31bf25be7..c47303b27 100644 --- a/Source/DiabloUI/settingsmenu.cpp +++ b/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(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(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((*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(optionDescription, MakeSdlRect(rectDescription), UiFlags::FontSize12 | UiFlags::ColorUiSilverDark | UiFlags::AlignCenter, 1, IsSmallFontTall() ? 22 : 18)); size_t itemToSelect = 1; diff --git a/Source/DiabloUI/title.cpp b/Source/DiabloUI/title.cpp index 863423cf3..4988f3070 100644 --- a/Source/DiabloUI/title.cpp +++ b/Source/DiabloUI/title.cpp @@ -11,7 +11,7 @@ namespace devilution { namespace { -std::optional DiabloTitleLogo; +OptionalOwnedClxSpriteList DiabloTitleLogo; std::vector> 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(ArtBackgroundWidescreen->sprite(), rect, UiFlags::AlignCenter)); - vecTitleScreen.push_back(std::make_unique(ArtBackground->sheet(), rect, UiFlags::AlignCenter)); + vecTitleScreen.push_back(std::make_unique((*ArtBackgroundWidescreen)[0], rect, UiFlags::AlignCenter)); + vecTitleScreen.push_back(std::make_unique(*ArtBackground, rect, UiFlags::AlignCenter)); } else { UiAddBackground(&vecTitleScreen); - vecTitleScreen.push_back(std::make_unique( - DiabloTitleLogo->sheet(), MakeSdlRect(0, uiPosition.y + 182, 0, 0), UiFlags::AlignCenter)); + vecTitleScreen.push_back(std::make_unique( + *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(_("Copyright © 1996-2001 Blizzard Entertainment").data(), rect, UiFlags::AlignCenter | UiFlags::FontSize24 | UiFlags::ColorUiSilver)); diff --git a/Source/DiabloUI/ui_item.h b/Source/DiabloUI/ui_item.h index 2f9ed5137..d49cab33b 100644 --- a/Source/DiabloUI/ui_item.h +++ b/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; }; //============================================================================= diff --git a/Source/control.cpp b/Source/control.cpp index 11ff28aaf..12c2f6739 100644 --- a/Source/control.cpp +++ b/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 pBtmBuff; -OptionalOwnedCelSprite pGBoxBuff; +OptionalOwnedClxSpriteList pGBoxBuff; const Rectangle &GetMainPanel() { @@ -137,10 +137,10 @@ namespace { std::optional pLifeBuff; std::optional 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(SidePanelSize.width)); - pGBoxBuff = LoadCelAsCl2("CtrlPan\\Golddrop.cel", 261); + pQLogCel = LoadCel("Data\\Quest.CEL", static_cast(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) { diff --git a/Source/control.h b/Source/control.h index 919f4a6c4..b5f777152 100644 --- a/Source/control.h +++ b/Source/control.h @@ -58,7 +58,7 @@ const Rectangle &GetRightPanel(); bool IsLeftPanelOpen(); bool IsRightPanelOpen(); extern std::optional pBtmBuff; -extern OptionalOwnedCelSprite pGBoxBuff; +extern OptionalOwnedClxSpriteList pGBoxBuff; extern SDL_Rect PanBtnPos[8]; void CalculatePanelAreas(); diff --git a/Source/controls/modifier_hints.cpp b/Source/controls/modifier_hints.cpp index c4a79fb42..718044769 100644 --- a/Source/controls/modifier_hints.cpp +++ b/Source/controls/modifier_hints.cpp @@ -16,7 +16,7 @@ namespace devilution { -extern OptionalOwnedCelSprite pSBkIconCels; +extern OptionalOwnedClxSpriteList pSBkIconCels; namespace { diff --git a/Source/controls/touch/renderers.cpp b/Source/controls/touch/renderers.cpp index 5a303ccec..1e388f929 100644 --- a/Source/controls/touch/renderers.cpp +++ b/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(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; diff --git a/Source/cursor.cpp b/Source/cursor.cpp index 68ff96b80..04cef0ca7 100644 --- a/Source/cursor.cpp +++ b/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); } } diff --git a/Source/cursor.h b/Source/cursor.h index 8667df2e5..14d02f9c3 100644 --- a/Source/cursor.h +++ b/Source/cursor.h @@ -9,7 +9,7 @@ #include #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); diff --git a/Source/dead.cpp b/Source/dead.cpp index 8e1e8150e..985f74074 100644 --- a/Source/dead.cpp +++ b/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; diff --git a/Source/dead.h b/Source/dead.h index b34b66107..dedefcfb1 100644 --- a/Source/dead.h +++ b/Source/dead.h @@ -9,6 +9,7 @@ #include #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 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(direction)] : sprites->list(); + } }; extern Corpse Corpses[MaxCorpses]; diff --git a/Source/debug.cpp b/Source/debug.cpp index 364df73d6..a292ff9f1 100644 --- a/Source/debug.cpp +++ b/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 DebugCmdList = { void LoadDebugGFX() { - pSquareCel = LoadCelAsCl2("Data\\Square.CEL", 64); + pSquareCel = LoadCel("Data\\Square.CEL", 64); } void FreeDebugGFX() diff --git a/Source/debug.h b/Source/debug.h index 198e2207d..8342a028c 100644 --- a/Source/debug.h +++ b/Source/debug.h @@ -8,13 +8,13 @@ #include #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; diff --git a/Source/diablo.cpp b/Source/diablo.cpp index cd6b1dd8f..637d02b11 100644 --- a/Source/diablo.cpp +++ b/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("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("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("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("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("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("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("NLevels\\L5Data\\L5.TIL"); - pSpecialCels = LoadCelAsCl2("NLevels\\L5Data\\L5S.CEL", SpecialCelWidth); + pSpecialCels = LoadCel("NLevels\\L5Data\\L5S.CEL", SpecialCelWidth); break; default: app_fatal("LoadLvlGFX"); diff --git a/Source/doom.cpp b/Source/doom.cpp index 626e46160..c6bab3bb7 100644 --- a/Source/doom.cpp +++ b/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 diff --git a/Source/engine/animationinfo.cpp b/Source/engine/animationinfo.cpp index f43324166..c254adfa8 100644 --- a/Source/engine/animationinfo.cpp +++ b/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*/) diff --git a/Source/engine/animationinfo.h b/Source/engine/animationinfo.h index 19cc8d47b..ce10133eb 100644 --- a/Source/engine/animationinfo.h +++ b/Source/engine/animationinfo.h @@ -8,7 +8,7 @@ #include #include -#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) diff --git a/Source/engine/cel_header.hpp b/Source/engine/cel_header.hpp deleted file mode 100644 index 06c0a381e..000000000 --- a/Source/engine/cel_header.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include -#include - -#include - -#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(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(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 diff --git a/Source/engine/cel_sprite.hpp b/Source/engine/cel_sprite.hpp deleted file mode 100644 index a62abf723..000000000 --- a/Source/engine/cel_sprite.hpp +++ /dev/null @@ -1,344 +0,0 @@ -#pragma once - -#include -#include - -#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 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 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 width_; - - friend class OptionalCelSprite; -}; - -/** - * @brief Equivalent to `std::optional` but smaller. - */ -class OptionalCelSprite { -public: - OptionalCelSprite() = default; - - OptionalCelSprite(CelSprite sprite) - : sprite_(sprite) - { - } - - explicit OptionalCelSprite(const OwnedCelSprite &owned); - - OptionalCelSprite(std::nullopt_t) - : OptionalCelSprite() - { - } - - template - CelSprite &emplace(Args &&...args) - { - sprite_ = CelSprite(std::forward(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 data, uint16_t width) - : data_(std::move(data)) - , width_(width) - { - } - - OwnedCelSprite(std::unique_ptr data, const uint16_t *widths) - : data_(std::move(data)) - , width_(widths) - { - } - - OwnedCelSprite(std::unique_ptr data, PointerOrValue 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 data() && - { - return std::move(data_); - } - -private: - // for OptionalOwnedCelSprite. - OwnedCelSprite() - : data_(nullptr) - , width_(nullptr) - { - } - - std::unique_ptr data_; - PointerOrValue width_; - - friend class CelSprite; - friend class OptionalOwnedCelSprite; -}; - -/** - * @brief Equivalent to `std::optional` but smaller. - */ -class OptionalOwnedCelSprite { -public: - OptionalOwnedCelSprite() = default; - - OptionalOwnedCelSprite(OwnedCelSprite &&sprite) - : sprite_(std::move(sprite)) - { - } - - OptionalOwnedCelSprite(std::nullopt_t) - : OptionalOwnedCelSprite() - { - } - - template - OwnedCelSprite &emplace(Args &&...args) - { - sprite_ = OwnedCelSprite(std::forward(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 diff --git a/Source/engine/clx_sprite.hpp b/Source/engine/clx_sprite.hpp new file mode 100644 index 000000000..ccc789589 --- /dev/null +++ b/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 +#include + +#include +#include + +#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 &&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 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 &&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 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 &&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 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` but smaller. + */ +class OptionalClxSprite { + DEFINE_CONSTEXPR_INTRUSIVE_OPTIONAL(OptionalClxSprite, ClxSprite, data_, nullptr) +}; + +/** + * @brief Equivalent to `std::optional` but smaller. + */ +class OptionalClxSpriteList { + DEFINE_CONSTEXPR_INTRUSIVE_OPTIONAL(OptionalClxSpriteList, ClxSpriteList, data_, nullptr) +}; + +/** + * @brief Equivalent to `std::optional` but smaller. + */ +class OptionalClxSpriteSheet { + DEFINE_CONSTEXPR_INTRUSIVE_OPTIONAL(OptionalClxSpriteSheet, ClxSpriteSheet, data_, nullptr) +}; + +/** + * @brief Equivalent to `std::optional` but smaller. + */ +class OptionalClxSpriteListOrSheet { +public: + DEFINE_INTRUSIVE_OPTIONAL(OptionalClxSpriteListOrSheet, ClxSpriteListOrSheet, data_, nullptr); +}; + +/** + * @brief Equivalent to `std::optional` but smaller. + */ +class OptionalOwnedClxSpriteList { +public: + DEFINE_INTRUSIVE_OPTIONAL(OptionalOwnedClxSpriteList, OwnedClxSpriteList, data_, nullptr) +}; + +/** + * @brief Equivalent to `std::optional` but smaller. + */ +class OptionalOwnedClxSpriteSheet { +public: + DEFINE_INTRUSIVE_OPTIONAL(OptionalOwnedClxSpriteSheet, OwnedClxSpriteSheet, data_, nullptr) +}; + +class OptionalOwnedClxSpriteListOrSheet { +public: + DEFINE_INTRUSIVE_OPTIONAL(OptionalOwnedClxSpriteListOrSheet, OwnedClxSpriteListOrSheet, data_, nullptr); +}; + +} // namespace devilution diff --git a/Source/engine/load_cel.cpp b/Source/engine/load_cel.cpp index e567cca55..31800ca86 100644 --- a/Source/engine/load_cel.cpp +++ b/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 widthOrWidths) { size_t size; std::unique_ptr data = LoadFileInMem(pszName, &size); #ifdef DEBUG_CEL_TO_CL2_SIZE std::cout << pszName; #endif - return CelToCl2(data.get(), size, PointerOrValue { width }); -} - -OwnedCelSprite LoadCelAsCl2(const char *pszName, const uint16_t *widths) -{ - size_t size; - std::unique_ptr data = LoadFileInMem(pszName, &size); -#ifdef DEBUG_CEL_TO_CL2_SIZE - std::cout << pszName; -#endif - return CelToCl2(data.get(), size, PointerOrValue { widths }); + return CelToClx(data.get(), size, widthOrWidths); } } // namespace devilution diff --git a/Source/engine/load_cel.hpp b/Source/engine/load_cel.hpp index 7b2ed8abb..070e9ab64 100644 --- a/Source/engine/load_cel.hpp +++ b/Source/engine/load_cel.hpp @@ -2,11 +2,26 @@ #include -#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 widthOrWidths); + +inline OwnedClxSpriteList LoadCel(const char *pszName, uint16_t width) +{ + return LoadCelListOrSheet(pszName, PointerOrValue { width }).list(); +} + +inline OwnedClxSpriteList LoadCel(const char *pszName, const uint16_t *widths) +{ + return LoadCelListOrSheet(pszName, PointerOrValue { widths }).list(); +} + +inline OwnedClxSpriteSheet LoadCelSheet(const char *pszName, uint16_t width) +{ + return LoadCelListOrSheet(pszName, PointerOrValue { width }).sheet(); +} } // namespace devilution diff --git a/Source/engine/load_cl2.cpp b/Source/engine/load_cl2.cpp new file mode 100644 index 000000000..e366fe351 --- /dev/null +++ b/Source/engine/load_cl2.cpp @@ -0,0 +1,18 @@ +#include "engine/load_cl2.hpp" + +#include +#include + +#include "engine/load_file.hpp" +#include "utils/cl2_to_clx.hpp" + +namespace devilution { + +OwnedClxSpriteListOrSheet LoadCl2ListOrSheet(const char *pszName, PointerOrValue widthOrWidths) +{ + size_t size; + std::unique_ptr data = LoadFileInMem(pszName, &size); + return Cl2ToClx(std::move(data), size, widthOrWidths); +} + +} // namespace devilution diff --git a/Source/engine/load_cl2.hpp b/Source/engine/load_cl2.hpp new file mode 100644 index 000000000..9caf1d6c1 --- /dev/null +++ b/Source/engine/load_cl2.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include + +#include + +#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 widthOrWidths); + +template +OwnedClxSpriteSheet LoadMultipleCl2Sheet(tl::function_ref filenames, size_t count, uint16_t width) +{ + StaticVector files; + StaticVector 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 { new uint8_t[totalSize] }; + const PointerOrValue 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(count) }; +} + +inline OwnedClxSpriteList LoadCl2(const char *pszName, uint16_t width) +{ + return LoadCl2ListOrSheet(pszName, PointerOrValue { width }).list(); +} + +inline OwnedClxSpriteList LoadCl2(const char *pszName, const uint16_t *widths) +{ + return LoadCl2ListOrSheet(pszName, PointerOrValue { widths }).list(); +} + +inline OwnedClxSpriteSheet LoadCl2Sheet(const char *pszName, uint16_t width) +{ + return LoadCl2ListOrSheet(pszName, PointerOrValue { width }).sheet(); +} + +} // namespace devilution diff --git a/Source/engine/load_file.hpp b/Source/engine/load_file.hpp index 02dd7a9ba..454049fe0 100644 --- a/Source/engine/load_file.hpp +++ b/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 the buffer with all the files */ @@ -132,20 +132,21 @@ struct MultiFileLoader { StaticVector files; StaticVector 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(size)); - outOffsets[i] = static_cast(totalSize); + outOffsets[j] = static_cast(totalSize); totalSize += size; + ++j; } + outOffsets[files.size()] = totalSize; std::unique_ptr 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; diff --git a/Source/engine/load_pcx.cpp b/Source/engine/load_pcx.cpp index ac6139b32..f2de79796 100644 --- a/Source/engine/load_pcx.cpp +++ b/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 LoadPcxSpriteAsCl2(const char *filename, int numFramesOrFrameHeight, std::optional transparentColor, SDL_Color *outPalette) +OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFramesOrFrameHeight, std::optional transparentColor, SDL_Color *outPalette) { SDL_RWops *handle = OpenAsset(filename); if (handle == nullptr) { @@ -32,23 +31,10 @@ std::optional LoadPcxSpriteAsCl2(const char *file #ifdef DEBUG_PCX_TO_CL2_SIZE std::cout << filename; #endif - std::optional result = PcxToCl2(handle, numFramesOrFrameHeight, transparentColor, outPalette); + OptionalOwnedClxSpriteList result = PcxToClx(handle, numFramesOrFrameHeight, transparentColor, outPalette); if (!result) return std::nullopt; return result; } -} // namespace - -std::optional LoadPcxSpriteSheetAsCl2(const char *filename, int numFramesOrFrameHeight, std::optional transparentColor, SDL_Color *outPalette) -{ - std::optional result = LoadPcxSpriteAsCl2(filename, numFramesOrFrameHeight, transparentColor, outPalette); - return OwnedCelSpriteSheetWithFrameHeight { std::move(result->ownedSprite), result->frameHeight }; -} - -std::optional LoadPcxAsCl2(const char *filename, std::optional transparentColor, SDL_Color *outPalette) -{ - return LoadPcxSpriteAsCl2(filename, 1, transparentColor, outPalette); -} - } // namespace devilution diff --git a/Source/engine/load_pcx.hpp b/Source/engine/load_pcx.hpp index 25e7a58ee..89c56189e 100644 --- a/Source/engine/load_pcx.hpp +++ b/Source/engine/load_pcx.hpp @@ -4,22 +4,33 @@ #include -#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 + * @return OptionalOwnedClxSpriteList */ -std::optional LoadPcxSpriteSheetAsCl2(const char *filename, int numFramesOrFrameHeight, std::optional transparentColor = std::nullopt, SDL_Color *outPalette = nullptr); +OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFramesOrFrameHeight, std::optional transparentColor = std::nullopt, SDL_Color *outPalette = nullptr); -std::optional LoadPcxAsCl2(const char *filename, std::optional 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 transparentColor = std::nullopt, SDL_Color *outPalette = nullptr) +{ + return LoadPcxSpriteList(filename, 1, transparentColor, outPalette); +} } // namespace devilution diff --git a/Source/engine/render/blit_impl.hpp b/Source/engine/render/blit_impl.hpp index e313da99f..a8a8535ac 100644 --- a/Source/engine/render/blit_impl.hpp +++ b/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; } diff --git a/Source/engine/render/cl2_render.hpp b/Source/engine/render/cl2_render.hpp deleted file mode 100644 index 9389a32e3..000000000 --- a/Source/engine/render/cl2_render.hpp +++ /dev/null @@ -1,115 +0,0 @@ -/** - * @file cl2_render.hpp - * - * CL2 rendering. - */ -#pragma once - -#include - -#include -#include - -#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 &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(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(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 Cl2MeasureSolidHorizontalBounds(CelSprite cel, int frame = 0); - -} // namespace devilution diff --git a/Source/engine/render/cl2_render.cpp b/Source/engine/render/clx_render.cpp similarity index 68% rename from Source/engine/render/cl2_render.cpp rename to Source/engine/render/clx_render.cpp index 63ce32016..0a86c4e0e 100644 --- a/Source/engine/render/cl2_render.cpp +++ b/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 -#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( - out, position, reinterpret_cast(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( - out, position, reinterpret_cast(pRLEBytes), nDataSize, nWidth, BlitWithMap { pTable }); -} - -void Cl2BlitBlendedTRN(const Surface &out, Point position, const byte *pRLEBytes, int nDataSize, int nWidth, uint8_t *pTable) -{ - DoRenderBackwards( - out, position, reinterpret_cast(pRLEBytes), nDataSize, nWidth, BlitBlendedWithMap { pTable }); -} - template -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 -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 -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( + return RenderClxOutlinePixelsCheckFirstColumn( dst, dstPitch, dstX, src, width, color); } if (CheckLastColumn && dstX + width >= dstW) { - return RenderCl2OutlinePixelsCheckLastColumn( + return RenderClxOutlinePixelsCheckLastColumn( dst, dstPitch, dstX, dstW, src, width, color); } if (Fill) { @@ -192,7 +154,7 @@ uint8_t *RenderCl2OutlinePixels( template -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( + dst = RenderClxOutlinePixels( dst, dstPitch, position.x, out.w(), src, w, color); ++src; } else { - dst = RenderCl2OutlinePixels( + dst = RenderClxOutlinePixels( dst, dstPitch, position.x, out.w(), src, w, color); src += v; } @@ -283,7 +245,7 @@ const uint8_t *RenderCl2OutlineRowClipped( // NOLINT(readability-function-cognit } template -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( + src.begin = RenderClxOutlineRowClipped( out, position, src.begin, src.width, clipX, color, skipSize); position.y -= static_cast(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( + src.begin = RenderClxOutlineRowClipped( out, position, src.begin, src.width, clipX, color, skipSize); position.y -= static_cast(skipSize.wholeLines); } while (position.y > 0 && src.begin != src.end) { - src.begin = RenderCl2OutlineRowClipped( + src.begin = RenderClxOutlineRowClipped( out, position, src.begin, src.width, clipX, color, skipSize); position.y -= static_cast(skipSize.wholeLines); } @@ -319,7 +281,7 @@ void RenderCl2OutlineClippedY(const Surface &out, Point position, RenderSrcBackw return; if (position.y == 0) { - src.begin = RenderCl2OutlineRowClipped( + src.begin = RenderClxOutlineRowClipped( out, position, src.begin, src.width, clipX, color, skipSize); position.y -= static_cast(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( + RenderClxOutlineRowClipped( out, position, src.begin, src.width, clipX, color, skipSize); } } template -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(out, position, src.begin, src.width, clipX, color, skipSize); } else if (position.x + clipX.width >= out.w()) { - src.begin = RenderCl2OutlineRowClipped(out, position, src.begin, src.width, clipX, color, skipSize); } else { - src.begin = RenderCl2OutlineRowClipped(out, position, src.begin, src.width, clipX, color, skipSize); } position.y -= static_cast(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( out, position, src.begin, src.width, clipX, color, skipSize); } else if (position.x + clipX.width >= out.w()) { - src.begin = RenderCl2OutlineRowClipped( out, position, src.begin, src.width, clipX, color, skipSize); } else { - src.begin = RenderCl2OutlineRowClipped( 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( out, position, src.begin, src.width, clipX, color, skipSize); position.y -= static_cast(skipSize.wholeLines); } } else if (position.x + clipX.width >= out.w()) { while (position.y > 0 && src.begin != src.end) { - src.begin = RenderCl2OutlineRowClipped( out, position, src.begin, src.width, clipX, color, skipSize); position.y -= static_cast(skipSize.wholeLines); } } else { while (position.y > 0 && src.begin != src.end) { - src.begin = RenderCl2OutlineRowClipped( out, position, src.begin, src.width, clipX, color, skipSize); position.y -= static_cast(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( out, position, src.begin, src.width, clipX, color, skipSize); } else if (position.x + clipX.width >= out.w()) { - src.begin = RenderCl2OutlineRowClipped( out, position, src.begin, src.width, clipX, color, skipSize); } else { - src.begin = RenderCl2OutlineRowClipped( 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(out, position, src.begin, src.width, clipX, color, skipSize); } else if (position.x + clipX.width >= out.w()) { - src.begin = RenderCl2OutlineRowClipped(out, position, src.begin, src.width, clipX, color, skipSize); } else { - src.begin = RenderCl2OutlineRowClipped(out, position, src.begin, src.width, clipX, color, skipSize); } } } template -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(srcWidth) }; if (position.x > 0 && position.x + static_cast(srcWidth) < static_cast(out.w())) { - RenderCl2OutlineClippedY(out, position, srcForBackwards, color); + RenderClxOutlineClippedY(out, position, srcForBackwards, color); } else { - RenderCl2OutlineClippedXY(out, position, srcForBackwards, color); + RenderClxOutlineClippedXY(out, position, srcForBackwards, color); } } -} // namespace - -void Cl2ApplyTrans(byte *p, const std::array &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(*dst++); - nDataSize--; - assert(nDataSize >= 0); - if (!IsCl2Opaque(v)) - continue; - if (IsCl2OpaqueFill(v)) { - nDataSize--; - assert(nDataSize >= 0); - *dst = static_cast(ttbl[static_cast(*dst)]); + // A bit of a hack but this is the only place in the code where we need mutable sprites. + auto *dst = const_cast(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(ttbl[static_cast(*dst)]); - dst++; - } } } } } -std::pair 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 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(*src++); + while (xCur < width) { + auto val = *src++; if (!IsCl2Opaque(val)) { xCur += val; continue; @@ -522,60 +489,37 @@ std::pair 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(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(out, position, reinterpret_cast(src), nDataSize, cel.Width(frame), col); + DoRenderBackwards(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(out, position, reinterpret_cast(src), nDataSize, cel.Width(frame), col); + DoRenderBackwards(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(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(out, position, clx.pixelData(), clx.pixelDataSize(), clx.width(), col); } } // namespace devilution diff --git a/Source/engine/render/clx_render.hpp b/Source/engine/render/clx_render.hpp new file mode 100644 index 000000000..d1dd670c6 --- /dev/null +++ b/Source/engine/render/clx_render.hpp @@ -0,0 +1,115 @@ +/** + * @file clx_render.hpp + * + * CL2 rendering. + */ +#pragma once + +#include + +#include +#include + +#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(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(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 ClxMeasureSolidHorizontalBounds(ClxSprite clx); + +} // namespace devilution diff --git a/Source/engine/render/common_impl.h b/Source/engine/render/common_impl.h index 9fcdf21c6..6d064ec78 100644 --- a/Source/engine/render/common_impl.h +++ b/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 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(out.h() + srcHeight)) + return; const ClipX clipX = CalculateClipX(position.x, srcWidth, out); if (clipX.width <= 0) return; diff --git a/Source/engine/render/scrollrt.cpp b/Source/engine/render/scrollrt.cpp index 8959bf8cc..5bce1999b 100644 --- a/Source/engine/render/scrollrt.cpp +++ b/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(missile._mitype)); - return; - } - int nCel = missile._miAnimFrame - 1; - const uint32_t frames = LoadLE32(missile._miAnimData); - if (nCel < 0 || frames > 50 || nCel >= static_cast(frames)) { - Log("Draw Missile 2: frame {} of {}, missile type {}", nCel, frames, static_cast(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(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(&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(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(&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(objectToDraw._otype)); - return; - } - - const uint32_t nCel = objectToDraw._oAnimFrame - 1; - const uint32_t frames = LoadLE32(pCelBuff); - if (nCel == static_cast(-1) || frames > 50 || nCel >= frames) { - Log("Draw Object: frame {} of {}, object type {}", nCel, frames, static_cast(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(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(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((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]); } } } diff --git a/Source/engine/render/text_render.cpp b/Source/engine/render/text_render.cpp index f88eb5d10..2f37cd06b 100644 --- a/Source/engine/render/text_render.cpp +++ b/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> Fonts; +using Font = const OwnedClxSpriteList; +std::unordered_map Fonts; std::unordered_map> FontKerns; std::array 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 &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, '|'); } diff --git a/Source/engine/render/text_render.hpp b/Source/engine/render/text_render.hpp index ce84e40f6..79786b852 100644 --- a/Source/engine/render/text_render.hpp +++ b/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(); diff --git a/Source/error.cpp b/Source/error.cpp index b0e6d7b19..902736a1c 100644 --- a/Source/error.cpp +++ b/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; } diff --git a/Source/gmenu.cpp b/Source/gmenu.cpp index 40be49c77..5edcc97a5 100644 --- a/Source/gmenu.cpp +++ b/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((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) { diff --git a/Source/interfac.cpp b/Source/interfac.cpp index b68484069..9881daa99 100644 --- a/Source/interfac.cpp +++ b/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 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() diff --git a/Source/inv.cpp b/Source/inv.cpp index dd2178c24..e2ecbafe6 100644 --- a/Source/inv.cpp +++ b/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(SidePanelSize.width)); + pInvCels = LoadCel("Data\\Inv\\Inv.CEL", static_cast(SidePanelSize.width)); break; case HeroClass::Rogue: case HeroClass::Bard: - pInvCels = LoadCelAsCl2("Data\\Inv\\Inv_rog.CEL", static_cast(SidePanelSize.width)); + pInvCels = LoadCel("Data\\Inv\\Inv_rog.CEL", static_cast(SidePanelSize.width)); break; case HeroClass::Sorcerer: - pInvCels = LoadCelAsCl2("Data\\Inv\\Inv_Sor.CEL", static_cast(SidePanelSize.width)); + pInvCels = LoadCel("Data\\Inv\\Inv_Sor.CEL", static_cast(SidePanelSize.width)); break; case HeroClass::Monk: - pInvCels = LoadCelAsCl2(!gbIsSpawn ? "Data\\Inv\\Inv_Sor.CEL" : "Data\\Inv\\Inv.CEL", static_cast(SidePanelSize.width)); + pInvCels = LoadCel(!gbIsSpawn ? "Data\\Inv\\Inv_Sor.CEL" : "Data\\Inv\\Inv.CEL", static_cast(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) { diff --git a/Source/items.cpp b/Source/items.cpp index a64e61c1d..75957a0fc 100644 --- a/Source/items.cpp +++ b/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(player_graphic::Stand)].GetCelSpritesForDirection(player._pdir), player._pNFrames, 4); + player.AnimInfo.changeAnimationData(player.AnimationData[static_cast(player_graphic::Stand)].spritesForDirection(player._pdir), player._pNFrames, 4); } else { LoadPlrGFX(player, player_graphic::Walk); - player.AnimInfo.changeAnimationData(player.AnimationData[static_cast(player_graphic::Walk)].GetCelSpritesForDirection(player._pdir), player._pWFrames, 1); + player.AnimInfo.changeAnimationData(player.AnimationData[static_cast(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(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) { diff --git a/Source/levels/gendung.cpp b/Source/levels/gendung.cpp index 036c86643..4c6dc4bad 100644 --- a/Source/levels/gendung.cpp +++ b/Source/levels/gendung.cpp @@ -22,7 +22,7 @@ Bitset2d Protected; Rectangle SetPieceRoom; Rectangle SetPiece; std::unique_ptr pSetPiece; -OptionalOwnedCelSprite pSpecialCels; +OptionalOwnedClxSpriteList pSpecialCels; std::unique_ptr pMegaTiles; std::unique_ptr pDungeonCels; std::array SOLData; diff --git a/Source/levels/gendung.h b/Source/levels/gendung.h index e2253c286..f6762f331 100644 --- a/Source/levels/gendung.h +++ b/Source/levels/gendung.h @@ -9,7 +9,7 @@ #include #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 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 pMegaTiles; extern std::unique_ptr pDungeonCels; diff --git a/Source/minitext.cpp b/Source/minitext.cpp index 005123f5a..84fa8d080 100644 --- a/Source/minitext.cpp +++ b/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); } diff --git a/Source/misdat.cpp b/Source/misdat.cpp index 0a5dc0046..6a4be7aff 100644 --- a/Source/misdat.cpp +++ b/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) }); } } diff --git a/Source/misdat.h b/Source/misdat.h index bec646bc0..370dee68c 100644 --- a/Source/misdat.h +++ b/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 animLen = {}; uint16_t animWidth; int16_t animWidth2; - std::unique_ptr animData; - std::array frameOffsets; + OptionalOwnedClxSpriteListOrSheet sprites; MissileFileData(string_view name, uint8_t animName, uint8_t animFAmt, MissileDataFlags flags, std::initializer_list animDelay, std::initializer_list 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(); } }; diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 186d18f42..026d24035 100644 --- a/Source/missiles.cpp +++ b/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(dir)); + if (!HeadlessMode) { + missile._miAnimData = MissileSpriteData[animtype].spritesForDirection(static_cast(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(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(missile._mimfnum)); } } diff --git a/Source/missiles.h b/Source/missiles.h index 3b8084a0f..cd4ace836 100644 --- a/Source/missiles.h +++ b/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; diff --git a/Source/monster.cpp b/Source/monster.cpp index 5b55f80a4..dbc39173b 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -5,9 +5,10 @@ */ #include "monster.h" +#include + #include #include -#include #include #include @@ -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 colorTranslations; *BufCopy(path, "Monsters\\", monst.data->trnFile, ".TRN") = '\0'; + std::array 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 animOffsets; + constexpr size_t MaxAnims = 6; + std::array animOffsets; if (!HeadlessMode) { monsterType.animData = MultiFileLoader {}( 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(&monsterType.animData[begin]); + const uint16_t numLists = Cl2ToClx(spritesData, end - begin, PointerOrValue { 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) { diff --git a/Source/monster.h b/Source/monster.h index 856584499..a3a7c5dc3 100644 --- a/Source/monster.h +++ b/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(direction)]; - if (spriteData == nullptr) + if (!sprites) return std::nullopt; - return CelSprite(spriteData, width); + return sprites->isSheet() ? (*sprites).sheet()[static_cast(direction)] : (*sprites).list(); } - std::array 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(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); } /** diff --git a/Source/objects.cpp b/Source/objects.cpp index 490d31f3f..9051e61c5 100644 --- a/Source/objects.cpp +++ b/Source/objects.cpp @@ -101,7 +101,7 @@ enum { int trapid; int trapdir; -std::unique_ptr 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(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) { diff --git a/Source/objects.h b/Source/objects.h index 3aec2f3e6..1cfff9bb8 100644 --- a/Source/objects.h +++ b/Source/objects.h @@ -7,6 +7,7 @@ #include +#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 diff --git a/Source/panels/charpanel.cpp b/Source/panels/charpanel.cpp index 26e8cc6f8..c0d82adb4 100644 --- a/Source/panels/charpanel.cpp +++ b/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(CharacterAttribute::Strength)] ? 2 : 1); + ClxDraw(out, GetPanelPosition(UiPanels::Character, { 137, 157 }), (*pChrButtons)[chrbtn[static_cast(CharacterAttribute::Strength)] ? 2 : 1]); if (MyPlayer->_pBaseMag < MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Magic)) - Cl2Draw(out, GetPanelPosition(UiPanels::Character, { 137, 185 }), sprite, chrbtn[static_cast(CharacterAttribute::Magic)] ? 4 : 3); + ClxDraw(out, GetPanelPosition(UiPanels::Character, { 137, 185 }), (*pChrButtons)[chrbtn[static_cast(CharacterAttribute::Magic)] ? 4 : 3]); if (MyPlayer->_pBaseDex < MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Dexterity)) - Cl2Draw(out, GetPanelPosition(UiPanels::Character, { 137, 214 }), sprite, chrbtn[static_cast(CharacterAttribute::Dexterity)] ? 6 : 5); + ClxDraw(out, GetPanelPosition(UiPanels::Character, { 137, 214 }), (*pChrButtons)[chrbtn[static_cast(CharacterAttribute::Dexterity)] ? 6 : 5]); if (MyPlayer->_pBaseVit < MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Vitality)) - Cl2Draw(out, GetPanelPosition(UiPanels::Character, { 137, 242 }), sprite, chrbtn[static_cast(CharacterAttribute::Vitality)] ? 8 : 7); + ClxDraw(out, GetPanelPosition(UiPanels::Character, { 137, 242 }), (*pChrButtons)[chrbtn[static_cast(CharacterAttribute::Vitality)] ? 8 : 7]); } } diff --git a/Source/panels/charpanel.hpp b/Source/panels/charpanel.hpp index 551fbf7e1..c50047c9e 100644 --- a/Source/panels/charpanel.hpp +++ b/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 &); diff --git a/Source/panels/info_box.cpp b/Source/panels/info_box.cpp index 9dc9ee5cf..0bd9e55d3 100644 --- a/Source/panels/info_box.cpp +++ b/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() diff --git a/Source/panels/info_box.hpp b/Source/panels/info_box.hpp index c436495b4..3fbf583c2 100644 --- a/Source/panels/info_box.hpp +++ b/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(); diff --git a/Source/panels/mainpanel.cpp b/Source/panels/mainpanel.cpp index 61e8c6fb8..365cb5203 100644 --- a/Source/panels/mainpanel.cpp +++ b/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" diff --git a/Source/panels/spell_book.cpp b/Source/panels/spell_book.cpp index dbc317e3b..e6a25e715 100644 --- a/Source/panels/spell_book.cpp +++ b/Source/panels/spell_book.cpp @@ -3,10 +3,10 @@ #include #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(SidePanelSize.width)); - pSBkBtnCel = LoadCelAsCl2("Data\\SpellBkB.CEL", gbIsHellfire ? 61 : 76); - pSBkIconCels = LoadCelAsCl2("Data\\SpellI2.CEL", 37); + pSpellBkCel = LoadCel("Data\\SpellBk.CEL", static_cast(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; diff --git a/Source/panels/spell_icons.cpp b/Source/panels/spell_icons.cpp index d13f9a865..5f1d56ea0 100644 --- a/Source/panels/spell_icons.cpp +++ b/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) diff --git a/Source/panels/spell_icons.hpp b/Source/panels/spell_icons.hpp index 25f99ef6c..871ea57b5 100644 --- a/Source/panels/spell_icons.hpp +++ b/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); diff --git a/Source/player.cpp b/Source/player.cpp index d19ce2ba8..52894056e 100644 --- a/Source/player.cpp +++ b/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 &data, std::array &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(_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(*graphic)].GetCelSpritesForDirection(dir); - if (celSprites && previewCelSprite != celSprites) { - previewCelSprite = celSprites; + ClxSpriteList sprites = AnimationData[static_cast(*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(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(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(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(graphic)].GetCelSpritesForDirection(player._pdir); + if (!HeadlessMode) + player.AnimInfo.sprites = player.AnimationData[static_cast(graphic)].spritesForDirection(player._pdir); // Ensure ScrollInfo is initialized correctly ScrollViewPort(player, WalkSettings[static_cast(player._pdir)].scrollDir); } diff --git a/Source/player.h b/Source/player.h index bbc991b78..fa51f9585 100644 --- a/Source/player.h +++ b/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 CharChar = { */ struct PlayerAnimationData { /** - * @brief CelSprites for the different directions + * @brief Sprite lists for each of the 8 directions. */ - std::array CelSpritesForDirections; - /** - * @brief Raw Data (binary) of the CL2 file. - * Is referenced from CelSprite in celSpritesForDirections - */ - std::unique_ptr RawData; + OptionalOwnedClxSpriteSheet sprites; - [[nodiscard]] OptionalCelSprite GetCelSpritesForDirection(Direction direction) const + [[nodiscard]] ClxSpriteList spritesForDirection(Direction direction) const { - return CelSpritesForDirections[static_cast(direction)]; + return (*sprites)[static_cast(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 */ diff --git a/Source/qol/itemlabels.cpp b/Source/qol/itemlabels.cpp index 066fe365a..7f749c3c6 100644 --- a/Source/qol/itemlabels.cpp +++ b/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 itemBounds = Cl2MeasureSolidHorizontalBounds(*item.AnimInfo.celSprite, item.AnimInfo.currentFrame); + std::pair itemBounds = ClxMeasureSolidHorizontalBounds((*item.AnimInfo.sprites)[item.AnimInfo.currentFrame]); labelCenterOffsets[index].emplace((itemBounds.first + itemBounds.second) / 2); } diff --git a/Source/qol/stash.cpp b/Source/qol/stash.cpp index 07d9d3628..d7f2a69c6 100644 --- a/Source/qol/stash.cpp +++ b/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); diff --git a/Source/quests.cpp b/Source/quests.cpp index feee92906..9989ca3b4 100644 --- a/Source/quests.cpp +++ b/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) { diff --git a/Source/quests.h b/Source/quests.h index a48adada7..4bd254b39 100644 --- a/Source/quests.h +++ b/Source/quests.h @@ -8,7 +8,7 @@ #include #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; diff --git a/Source/stores.cpp b/Source/stores.cpp index 20e2bc171..611144f70 100644 --- a/Source/stores.cpp +++ b/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 diff --git a/Source/stores.h b/Source/stores.h index 93edf6c1e..dc2f7f8b8 100644 --- a/Source/stores.h +++ b/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" diff --git a/Source/storm/storm_net.cpp b/Source/storm/storm_net.cpp index 5095ea804..59b0805da 100644 --- a/Source/storm/storm_net.cpp +++ b/Source/storm/storm_net.cpp @@ -91,6 +91,8 @@ bool SNetUnregisterEventHandler(event_type evtype) #ifndef NONET std::lock_guard lg(storm_net_mutex); #endif + if (dvlnet_inst == nullptr) + return true; return dvlnet_inst->SNetUnregisterEventHandler(evtype); } diff --git a/Source/towners.cpp b/Source/towners.cpp index 85efe5dbd..28389ce6c 100644 --- a/Source/towners.cpp +++ b/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 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(townerData.dir)], 12, 3); + + NewTownerAnim(towner, (*CowSprites)[static_cast(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() diff --git a/Source/towners.h b/Source/towners.h index 38340100d..9d76cc91d 100644 --- a/Source/towners.h +++ b/Source/towners.h @@ -36,9 +36,9 @@ enum _talker_id : uint8_t { }; struct Towner { - byte *_tNAnim[8]; - std::unique_ptr 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]; } }; diff --git a/Source/utils/cel_to_cl2.hpp b/Source/utils/cel_to_cl2.hpp deleted file mode 100644 index 8bfaaa3ed..000000000 --- a/Source/utils/cel_to_cl2.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include -#include - -#include "engine/cel_sprite.hpp" -#include "utils/pointer_value_union.hpp" - -namespace devilution { - -OwnedCelSprite CelToCl2(const uint8_t *data, size_t size, PointerOrValue widthOrWidths); - -} // namespace devilution diff --git a/Source/utils/cel_to_cl2.cpp b/Source/utils/cel_to_clx.cpp similarity index 84% rename from Source/utils/cel_to_cl2.cpp rename to Source/utils/cel_to_clx.cpp index 97dfd474f..30d381f06 100644 --- a/Source/utils/cel_to_cl2.cpp +++ b/Source/utils/cel_to_clx.cpp @@ -1,4 +1,4 @@ -#include "utils/cel_to_cl2.hpp" +#include "utils/cel_to_clx.hpp" #include @@ -98,7 +98,7 @@ void AppendCl2PixelsOrFillRun(const uint8_t *src, unsigned length, std::vector widthOrWidths) +OwnedClxSpriteListOrSheet CelToClx(const uint8_t *data, size_t size, PointerOrValue 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(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(new byte[cl2Data.size()]); + auto out = std::unique_ptr(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(cl2Data.size()) - static_cast(size)) / ((float)size) * 100 << "%" << std::endl; #endif - return OwnedCelSprite { std::move(out), widthOrWidths }; + return OwnedClxSpriteListOrSheet { std::move(out), static_cast(numGroups == 1 ? 0 : numGroups) }; } } // namespace devilution diff --git a/Source/utils/cel_to_clx.hpp b/Source/utils/cel_to_clx.hpp new file mode 100644 index 000000000..631083761 --- /dev/null +++ b/Source/utils/cel_to_clx.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +#include "engine/clx_sprite.hpp" +#include "utils/pointer_value_union.hpp" + +namespace devilution { + +OwnedClxSpriteListOrSheet CelToClx(const uint8_t *data, size_t size, PointerOrValue widthOrWidths); + +} // namespace devilution diff --git a/Source/utils/cl2_to_clx.cpp b/Source/utils/cl2_to_clx.cpp new file mode 100644 index 000000000..76177631e --- /dev/null +++ b/Source/utils/cl2_to_clx.cpp @@ -0,0 +1,97 @@ +#include "utils/cl2_to_clx.hpp" + +#include + +#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(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(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 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 diff --git a/Source/utils/cl2_to_clx.hpp b/Source/utils/cl2_to_clx.hpp new file mode 100644 index 000000000..cf6b78d71 --- /dev/null +++ b/Source/utils/cl2_to_clx.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +#include + +#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 widthOrWidths); + +inline OwnedClxSpriteListOrSheet Cl2ToClx(std::unique_ptr &&data, size_t size, PointerOrValue widthOrWidths) +{ + const uint16_t numLists = Cl2ToClx(data.get(), size, widthOrWidths); + return OwnedClxSpriteListOrSheet { std::move(data), numLists }; +} + +} // namespace devilution diff --git a/Source/utils/intrusive_optional.hpp b/Source/utils/intrusive_optional.hpp new file mode 100644 index 000000000..4bfd47f64 --- /dev/null +++ b/Source/utils/intrusive_optional.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include +#include + +#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 \ + CONSTEXPR OPTIONAL_CLASS(VALUE_CLASS &&value) \ + : value_(std::forward(value)) \ + { \ + } \ + \ + CONSTEXPR OPTIONAL_CLASS(std::nullopt_t) \ + : OPTIONAL_CLASS() \ + { \ + } \ + \ + template \ + CONSTEXPR VALUE_CLASS &emplace(Args &&...args) \ + { \ + value_ = VALUE_CLASS(std::forward(args)...); \ + return value_; \ + } \ + \ + template \ + CONSTEXPR OPTIONAL_CLASS &operator=(VALUE_CLASS &&value) \ + { \ + value_ = std::forward(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, ) diff --git a/Source/utils/pcx_to_cl2.cpp b/Source/utils/pcx_to_clx.cpp similarity index 84% rename from Source/utils/pcx_to_cl2.cpp rename to Source/utils/pcx_to_clx.cpp index f2e6024d7..961e53c44 100644 --- a/Source/utils/pcx_to_cl2.cpp +++ b/Source/utils/pcx_to_clx.cpp @@ -1,8 +1,9 @@ -#include "utils/pcx_to_cl2.hpp" +#include "utils/pcx_to_clx.hpp" -#include #include #include + +#include #include #include @@ -110,7 +111,7 @@ size_t GetReservationSize(size_t pcxSize) } // namespace -std::optional PcxToCl2(SDL_RWops *handle, int numFramesOrFrameHeight, std::optional transparentColor, SDL_Color *outPalette) +OptionalOwnedClxSpriteList PcxToClx(SDL_RWops *handle, int numFramesOrFrameHeight, std::optional transparentColor, SDL_Color *outPalette) { int width; int height; @@ -145,7 +146,7 @@ std::optional 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 cl2Data; cl2Data.reserve(GetReservationSize(pixelDataSize)); cl2Data.resize(4 * (2 + static_cast(numFrames))); @@ -159,13 +160,20 @@ std::optional PcxToCl2(SDL_RWops *handle, int num for (unsigned frame = 1; frame <= numFrames; ++frame) { WriteLE32(&cl2Data[4 * static_cast(frame)], static_cast(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(width)); + WriteLE16(&cl2Data[frameHeaderPos + 4], static_cast(frameHeight)); + memset(&cl2Data[frameHeaderPos + 6], 0, 4); for (unsigned j = 0; j < frameHeight; ++j) { uint8_t *buffer = &frameBuffer[static_cast(j) * width]; @@ -212,19 +220,7 @@ std::optional 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(cl2Data.size() - frameHeaderPos)); - break; - } + ++line; } AppendCl2TransparentRun(transparentRunWidth, cl2Data); } @@ -252,15 +248,12 @@ std::optional PcxToCl2(SDL_RWops *handle, int num frameBuffer = nullptr; fileBuffer = nullptr; - auto out = std::unique_ptr(new byte[cl2Data.size()]); + auto out = std::unique_ptr(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(cl2Data.size()) - static_cast(pixelDataSize)) / ((float)pixelDataSize) * 100 << "%" << std::endl; #endif - return OwnedCelSpriteWithFrameHeight { - OwnedCelSprite { std::move(out), static_cast(width) }, - static_cast(frameHeight) - }; + return OwnedClxSpriteList { std::move(out) }; } } // namespace devilution diff --git a/Source/utils/pcx_to_cl2.hpp b/Source/utils/pcx_to_clx.hpp similarity index 59% rename from Source/utils/pcx_to_cl2.hpp rename to Source/utils/pcx_to_clx.hpp index d2e7f7ec0..f893957f4 100644 --- a/Source/utils/pcx_to_cl2.hpp +++ b/Source/utils/pcx_to_clx.hpp @@ -4,18 +4,18 @@ #include -#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 PcxToCl2(SDL_RWops *handle, int numFramesOrFrameHeight = 1, std::optional transparentColor = std::nullopt, SDL_Color *outPalette = nullptr); +OptionalOwnedClxSpriteList PcxToClx(SDL_RWops *handle, int numFramesOrFrameHeight = 1, std::optional transparentColor = std::nullopt, SDL_Color *outPalette = nullptr); } // namespace devilution