From 60af627ddc2f42afa922f241067ea272cdcae5fb Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 17 Feb 2024 15:01:47 +0000 Subject: [PATCH] Extract objcurs widths into a file Read from the widths file when using the original CEL. When using CLX, use width in CLX. --- CMake/Assets.cmake | 6 + Source/cursor.cpp | 149 +++++++++-------------- Source/engine/clx_sprite.hpp | 5 + assets/data/inv/objcurs-widths.txt | 179 ++++++++++++++++++++++++++++ assets/data/inv/objcurs2-widths.txt | 61 ++++++++++ test/inv_test.cpp | 8 ++ test/pack_test.cpp | 9 ++ test/player_test.cpp | 11 ++ test/writehero_test.cpp | 11 ++ 9 files changed, 347 insertions(+), 92 deletions(-) create mode 100644 assets/data/inv/objcurs-widths.txt create mode 100644 assets/data/inv/objcurs2-widths.txt diff --git a/CMake/Assets.cmake b/CMake/Assets.cmake index da98f020e0a2..6a735014052d 100644 --- a/CMake/Assets.cmake +++ b/CMake/Assets.cmake @@ -173,6 +173,12 @@ set(devilutionx_assets ui_art/mainmenuw.clx ui_art/supportw.clx) +if(NOT UNPACKED_MPQS) + list(APPEND devilutionx_assets + data/inv/objcurs-widths.txt + data/inv/objcurs2-widths.txt) +endif() + if(NOT USE_SDL1 AND NOT VITA) list(APPEND devilutionx_assets ui_art/button.png diff --git a/Source/cursor.cpp b/Source/cursor.cpp index 31e39cf30dd6..61f86b2f52d0 100644 --- a/Source/cursor.cpp +++ b/Source/cursor.cpp @@ -7,6 +7,9 @@ #include #include +#include +#include +#include #include @@ -17,7 +20,6 @@ #include "engine.h" #include "engine/backbuffer_state.hpp" #include "engine/demomode.h" -#include "engine/load_cel.hpp" #include "engine/point.hpp" #include "engine/points_in_rectangle_range.hpp" #include "engine/render/clx_render.hpp" @@ -37,84 +39,20 @@ #include "utils/surface_to_clx.hpp" #include "utils/utf8.hpp" +#ifdef UNPACKED_MPQS +#include "engine/load_clx.hpp" +#else +#include "engine/load_cel.hpp" +#include "engine/load_file.hpp" +#include "utils/parse_int.hpp" +#endif + namespace devilution { namespace { /** Cursor images CEL */ OptionalOwnedClxSpriteList pCursCels; OptionalOwnedClxSpriteList pCursCels2; -/** Maps from objcurs.cel frame number to frame width. */ -const uint16_t InvItemWidth1[] = { - // clang-format off - // Cursors - 33, 32, 32, 32, 32, 32, 32, 32, 32, 32, 23, - // Items - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, - 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, - 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, - 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, - 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, - 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, - 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, - 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, - 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, - 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, -}; -const uint16_t InvItemWidth2[] = { - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, - 2 * 28, 2 * 28, 1 * 28, 1 * 28, 1 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, - 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, - 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, - 2 * 28 - // clang-format on -}; -constexpr uint16_t InvItems1Size = sizeof(InvItemWidth1) / sizeof(InvItemWidth1[0]); -constexpr uint16_t InvItems2Size = sizeof(InvItemWidth2) / sizeof(InvItemWidth2[0]); - -/** Maps from objcurs.cel frame number to frame height. */ -const uint16_t InvItemHeight1[InvItems1Size] = { - // clang-format off - // Cursors - 29, 32, 32, 32, 32, 32, 32, 32, 32, 32, 35, - // Items - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, - 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, - 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, - 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, - 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, - 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, 2 * 28, - 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, - 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, - 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, - 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, - 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, - 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, - 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, -}; -const uint16_t InvItemHeight2[InvItems2Size] = { - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, - 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, 1 * 28, - 2 * 28, 2 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, - 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, - 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, 3 * 28, - 3 * 28 - // clang-format on -}; - OptionalOwnedClxSpriteList *HalfSizeItemSprites; OptionalOwnedClxSpriteList *HalfSizeItemSpritesRed; @@ -430,6 +368,28 @@ bool TrySelectPixelBased(Point tile) return false; } +std::vector ReadWidths(const char *path) +{ + size_t len; + const std::unique_ptr data = LoadFileInMem(path, &len); + std::string_view str { data.get(), len }; + std::vector result; + while (!str.empty()) { + const char *end; + const ParseIntResult parseResult = ParseInt(str, std::numeric_limits::min(), + std::numeric_limits::max(), &end); + if (!parseResult.has_value()) { + app_fatal(StrCat("Failed to parse ", path, ": [", str, "]")); + } + result.push_back(parseResult.value()); + str.remove_prefix(end - str.data()); + while (!str.empty() && (str[0] == '\r' || str[0] == '\n')) { + str.remove_prefix(1); + } + } + return result; +} + } // namespace /** Current highlighted monster */ @@ -455,9 +415,17 @@ int pcurs; void InitCursor() { assert(!pCursCels); - pCursCels = LoadCel("data\\inv\\objcurs", InvItemWidth1); - if (gbIsHellfire) - pCursCels2 = LoadCel("data\\inv\\objcurs2", InvItemWidth2); +#ifdef UNPACKED_MPQS + pCursCels = LoadClxListOrSheet("data\\inv\\objcurs.clx"); + if (gbIsHellfire) { + pCursCels2 = LoadClxListOrSheet("data\\inv\\objcurs2.clx"); + } +#else + pCursCels = LoadCel("data\\inv\\objcurs", ReadWidths("data\\inv\\objcurs-widths.txt").data()); + if (gbIsHellfire) { + pCursCels2 = LoadCel("data\\inv\\objcurs2", ReadWidths("data\\inv\\objcurs2-widths.txt").data()); + } +#endif ClearCursor(); } @@ -470,22 +438,20 @@ void FreeCursor() ClxSprite GetInvItemSprite(int cursId) { - if (cursId <= InvItems1Size) + assert(cursId > 0); + const size_t numSprites = pCursCels->numSprites(); + if (static_cast(cursId) <= numSprites) { return (*pCursCels)[cursId - 1]; - return (*pCursCels2)[cursId - InvItems1Size - 1]; -} - -size_t GetNumInvItems() -{ - return InvItems1Size + InvItems2Size; + } + assert(gbIsHellfire); + assert(cursId - numSprites <= pCursCels2->numSprites()); + return (*pCursCels2)[cursId - numSprites - 1]; } Size GetInvItemSize(int cursId) { - const int i = cursId - 1; - if (i >= InvItems1Size) - return { InvItemWidth2[i - InvItems1Size], InvItemHeight2[i - InvItems1Size] }; - return { InvItemWidth1[i], InvItemHeight1[i] }; + const ClxSprite sprite = GetInvItemSprite(cursId); + return { sprite.width(), sprite.height() }; } ClxSprite GetHalfSizeItemSprite(int cursId) @@ -502,9 +468,8 @@ void CreateHalfSizeItemSprites() { if (HalfSizeItemSprites != nullptr) return; - const int numInvItems = gbIsHellfire - ? InvItems1Size + InvItems2Size - (static_cast(CURSOR_FIRSTITEM) - 1) - : InvItems1Size + (static_cast(CURSOR_FIRSTITEM) - 1); + const int numInvItems = pCursCels->numSprites() - (static_cast(CURSOR_FIRSTITEM) - 1) + + (gbIsHellfire ? pCursCels2->numSprites() : 0); HalfSizeItemSprites = new OptionalOwnedClxSpriteList[numInvItems]; HalfSizeItemSpritesRed = new OptionalOwnedClxSpriteList[numInvItems]; const uint8_t *redTrn = GetInfravisionTRN(); @@ -538,11 +503,11 @@ void CreateHalfSizeItemSprites() }; size_t outputIndex = 0; - for (size_t i = static_cast(CURSOR_FIRSTITEM) - 1; i < InvItems1Size; ++i, ++outputIndex) { + for (size_t i = static_cast(CURSOR_FIRSTITEM) - 1, n = pCursCels->numSprites(); i < n; ++i, ++outputIndex) { createHalfSize((*pCursCels)[i], outputIndex); } if (gbIsHellfire) { - for (size_t i = 0; i < InvItems2Size; ++i, ++outputIndex) { + for (size_t i = 0, n = pCursCels2->numSprites(); i < n; ++i, ++outputIndex) { createHalfSize((*pCursCels2)[i], outputIndex); } } diff --git a/Source/engine/clx_sprite.hpp b/Source/engine/clx_sprite.hpp index b451c27c5024..b41e8174c682 100644 --- a/Source/engine/clx_sprite.hpp +++ b/Source/engine/clx_sprite.hpp @@ -362,6 +362,11 @@ class OwnedClxSpriteList { return ClxSpriteList { *this }[spriteIndex]; } + [[nodiscard]] uint32_t numSprites() const + { + return ClxSpriteList { *this }.numSprites(); + } + private: // For OptionalOwnedClxSpriteList. OwnedClxSpriteList() = default; diff --git a/assets/data/inv/objcurs-widths.txt b/assets/data/inv/objcurs-widths.txt new file mode 100644 index 000000000000..b52d57ee19db --- /dev/null +++ b/assets/data/inv/objcurs-widths.txt @@ -0,0 +1,179 @@ +33 +32 +32 +32 +32 +32 +32 +32 +32 +32 +23 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 diff --git a/assets/data/inv/objcurs2-widths.txt b/assets/data/inv/objcurs2-widths.txt new file mode 100644 index 000000000000..a4b95220e0f0 --- /dev/null +++ b/assets/data/inv/objcurs2-widths.txt @@ -0,0 +1,61 @@ +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +28 +56 +56 +28 +28 +28 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 +56 diff --git a/test/inv_test.cpp b/test/inv_test.cpp index 29a9ba28b851..3613f2d7f7c9 100644 --- a/test/inv_test.cpp +++ b/test/inv_test.cpp @@ -18,6 +18,14 @@ class InvTest : public ::testing::Test { static void SetUpTestSuite() { + LoadCoreArchives(); + LoadGameArchives(); + + // The tests need spawn.mpq or diabdat.mpq + // Please provide them so that the tests can run successfully + ASSERT_TRUE(HaveSpawn() || HaveDiabdat()); + + InitCursor(); LoadSpellData(); LoadItemData(); } diff --git a/test/pack_test.cpp b/test/pack_test.cpp index 1601fcb92a21..bc59a715f962 100644 --- a/test/pack_test.cpp +++ b/test/pack_test.cpp @@ -2,6 +2,7 @@ #include +#include "cursor.h" #include "monstdat.h" #include "pack.h" #include "playerdat.hpp" @@ -954,6 +955,14 @@ class NetPackTest : public ::testing::Test { static void SetUpTestSuite() { + LoadCoreArchives(); + LoadGameArchives(); + + // The tests need spawn.mpq or diabdat.mpq + // Please provide them so that the tests can run successfully + ASSERT_TRUE(HaveSpawn() || HaveDiabdat()); + + InitCursor(); LoadSpellData(); LoadPlayerDataFiles(); LoadMonsterData(); diff --git a/test/player_test.cpp b/test/player_test.cpp index a423b96988f7..5d4d7a43daa5 100644 --- a/test/player_test.cpp +++ b/test/player_test.cpp @@ -2,6 +2,8 @@ #include +#include "cursor.h" +#include "init.h" #include "playerdat.hpp" using namespace devilution; @@ -179,6 +181,15 @@ static void AssertPlayer(Player &player) TEST(Player, CreatePlayer) { + LoadCoreArchives(); + LoadGameArchives(); + + // The tests need spawn.mpq or diabdat.mpq + // Please provide them so that the tests can run successfully + ASSERT_TRUE(HaveSpawn() || HaveDiabdat()); + + InitCursor(); + LoadPlayerDataFiles(); LoadItemData(); Players.resize(1); diff --git a/test/writehero_test.cpp b/test/writehero_test.cpp index 88e6004418df..522da00ab9d3 100644 --- a/test/writehero_test.cpp +++ b/test/writehero_test.cpp @@ -8,6 +8,8 @@ #include #include +#include "cursor.h" +#include "init.h" #include "loadsave.h" #include "pack.h" #include "pfile.h" @@ -360,6 +362,15 @@ void AssertPlayer(Player &player) TEST(Writehero, pfile_write_hero) { + LoadCoreArchives(); + LoadGameArchives(); + + // The tests need spawn.mpq or diabdat.mpq + // Please provide them so that the tests can run successfully + ASSERT_TRUE(HaveSpawn() || HaveDiabdat()); + + InitCursor(); + paths::SetPrefPath("."); std::remove("multi_0.sv");