diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 058d30e9368b..a4e1744a195a 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -100,6 +100,7 @@ set(libdevilutionx_SRCS engine/animationinfo.cpp engine/assets.cpp engine/backbuffer_state.cpp + engine/clx_store.cpp engine/direction.cpp engine/dx.cpp engine/events.cpp @@ -147,6 +148,7 @@ set(libdevilutionx_SRCS lua/modules/dev/player/stats.cpp lua/modules/dev/quests.cpp lua/modules/dev/search.cpp + lua/modules/dev/sprites.cpp lua/modules/dev/towners.cpp lua/modules/log.cpp lua/modules/render.cpp diff --git a/Source/cursor.cpp b/Source/cursor.cpp index 269ad096a882..e7680ec85862 100644 --- a/Source/cursor.cpp +++ b/Source/cursor.cpp @@ -481,7 +481,7 @@ void CreateHalfSizeItemSprites() OwnedSurface ownedItemSurface { MaxWidth, MaxHeight }; OwnedSurface ownedHalfSurface { MaxWidth / 2, MaxHeight / 2 }; - const auto createHalfSize = [&, redTrn](const ClxSprite itemSprite, size_t outputIndex) { + const auto createHalfSize = [&, redTrn](const ClxSprite itemSprite, size_t outputIndex, int cursId) { if (itemSprite.width() <= 28 && itemSprite.height() <= 28) { // Skip creating half-size sprites for 1x1 items because we always render them at full size anyway. return; @@ -495,22 +495,23 @@ void CreateHalfSizeItemSprites() const Surface halfSurface = ownedHalfSurface.subregion(0, 0, itemSurface.w() / 2, itemSurface.h() / 2); SDL_Rect halfSurfaceRect = MakeSdlRect(0, 0, halfSurface.w(), halfSurface.h()); SDL_SetClipRect(halfSurface.surface, &halfSurfaceRect); + std::string name = StrCat("runtime\\objcurs_half_size\\", cursId); BilinearDownscaleByHalf8(itemSurface.surface, paletteTransparencyLookup, halfSurface.surface, 1); - HalfSizeItemSprites[outputIndex].emplace(SurfaceToClx(halfSurface, 1, 1)); + HalfSizeItemSprites[outputIndex].emplace(SurfaceToClx(std::string(name), /*trnName=*/ {}, halfSurface, 1, 1)); SDL_FillRect(itemSurface.surface, nullptr, 1); ClxDrawTRN(itemSurface, { 0, itemSurface.h() }, itemSprite, redTrn); BilinearDownscaleByHalf8(itemSurface.surface, paletteTransparencyLookup, halfSurface.surface, 1); - HalfSizeItemSpritesRed[outputIndex].emplace(SurfaceToClx(halfSurface, 1, 1)); + HalfSizeItemSpritesRed[outputIndex].emplace(SurfaceToClx(std::move(name), /*trnName=*/"red", halfSurface, 1, 1)); }; size_t outputIndex = 0; for (size_t i = static_cast(CURSOR_FIRSTITEM) - 1, n = pCursCels->numSprites(); i < n; ++i, ++outputIndex) { - createHalfSize((*pCursCels)[i], outputIndex); + createHalfSize((*pCursCels)[i], outputIndex, i + 1); } if (gbIsHellfire) { for (size_t i = 0, n = pCursCels2->numSprites(); i < n; ++i, ++outputIndex) { - createHalfSize((*pCursCels2)[i], outputIndex); + createHalfSize((*pCursCels2)[i], outputIndex, i + pCursCels->numSprites() + 1); } } } diff --git a/Source/engine/clx_sprite.hpp b/Source/engine/clx_sprite.hpp index b41e8174c682..5800a4c23e74 100644 --- a/Source/engine/clx_sprite.hpp +++ b/Source/engine/clx_sprite.hpp @@ -31,6 +31,7 @@ #include #include "appfat.h" +#include "engine/clx_store.hpp" #include "utils/endian.hpp" #include "utils/intrusive_optional.hpp" @@ -89,14 +90,10 @@ class ClxSprite { private: // For OptionalClxSprite. - constexpr ClxSprite() - : data_(nullptr) - , pixel_data_size_(0) - { - } + constexpr ClxSprite() = default; - const uint8_t *data_; - uint32_t pixel_data_size_; + const uint8_t *data_ = nullptr; + uint32_t pixel_data_size_ = 0; friend class OptionalClxSprite; }; @@ -118,7 +115,7 @@ class ClxSpriteList { ClxSpriteList(const OwnedClxSpriteList &owned); - [[nodiscard]] OwnedClxSpriteList clone() const; + [[nodiscard]] OwnedClxSpriteList clone(std::string_view name, std::string_view trnName = {}) const; [[nodiscard]] constexpr uint32_t numSprites() const { @@ -154,12 +151,9 @@ class ClxSpriteList { private: // For OptionalClxSpriteList. - constexpr ClxSpriteList() - : data_(nullptr) - { - } + constexpr ClxSpriteList() = default; - const uint8_t *data_; + const uint8_t *data_ = nullptr; friend class OptionalClxSpriteList; }; @@ -264,16 +258,17 @@ class ClxSpriteSheet { [[nodiscard]] constexpr ClxSpriteSheetIterator begin() const; [[nodiscard]] constexpr ClxSpriteSheetIterator end() const; -private: - // For OptionalClxSpriteSheet. - constexpr ClxSpriteSheet() - : data_(nullptr) - , num_lists_(0) + [[nodiscard]] size_t dataSize() const { + return (*this)[num_lists_ - 1].nextSpriteSheetOffsetOrFileSize(); } - const uint8_t *data_; - uint16_t num_lists_; +private: + // For OptionalClxSpriteSheet. + constexpr ClxSpriteSheet() = default; + + const uint8_t *data_ = nullptr; + uint16_t num_lists_ = 0; friend class OptionalClxSpriteSheet; }; @@ -338,13 +333,84 @@ inline constexpr ClxSpriteSheetIterator ClxSpriteSheet::end() const class OptionalOwnedClxSpriteList; class OwnedClxSpriteListOrSheet; +template +class OwnedClxResource { +public: + OwnedClxResource(std::string_view name, std::string_view trnName) + : name_(name) + , handle_(GetClxStore().registerClx(*static_cast(this), name_, trnName)) + { + } + + OwnedClxResource(std::string &&name, std::string &&trnName) + : name_(std::move(name)) + , handle_(GetClxStore().registerClx(*static_cast(this), name_, std::move(trnName))) + { + } + + OwnedClxResource(std::string &&name, ClxStoreHandle &&handle) + : name_(std::move(name)) + // NOLINTNEXTLINE(performance-move-const-arg): Move is for semantics rather than performance. + , handle_(GetClxStore().replaceClx(*static_cast(this), std::move(handle))) + { + } + + OwnedClxResource(OwnedClxResource &&other) noexcept + : name_(std::move(other.name_)) + , handle_(GetClxStore().replaceClx(*static_cast(this), std::move(other.handle_))) + { + } + + OwnedClxResource &operator=(OwnedClxResource &&other) noexcept + { + if (handle_.has_value()) GetClxStore().unregisterClx(name_, handle_); + name_ = std::move(other.name_); + // NOLINTNEXTLINE(performance-move-const-arg): Move is for semantics rather than performance. + handle_ = GetClxStore().replaceClx(*static_cast(this), std::move(other.handle_)); + return *this; + } + + ~OwnedClxResource() + { + if (handle_.has_value()) GetClxStore().unregisterClx(name_, handle_); + } + + [[nodiscard]] std::string_view name() const { return name_; } + [[nodiscard]] std::string_view trnName() const { return handle_.value().trnName; } + + void setTrnName(std::string_view trnName) { handle_.value().trnName = trnName; } + +protected: + // For OptionalOwned* types/ + OwnedClxResource() = default; + + std::string name_; + ClxStoreHandle handle_; +}; + /** * @brief Implicitly convertible to `ClxSpriteList` and owns its data. */ -class OwnedClxSpriteList { +class OwnedClxSpriteList : public OwnedClxResource { public: - explicit OwnedClxSpriteList(std::unique_ptr &&data) - : data_(std::move(data)) + explicit OwnedClxSpriteList(std::string_view name, std::string_view trnName, std::unique_ptr &&data) + : OwnedClxResource(name, trnName) + , data_(std::move(data)) + { + assert(data_ != nullptr); + } + + explicit OwnedClxSpriteList(std::string &&name, std::string &&trnName, std::unique_ptr &&data) + : OwnedClxResource(std::move(name), std::move(trnName)) + , data_(std::move(data)) + { + assert(data_ != nullptr); + } + + explicit OwnedClxSpriteList(std::string &&name, ClxStoreHandle &&handle, std::unique_ptr &&data) + // NOLINTNEXTLINE(performance-move-const-arg): Move is for semantics rather than performance. + : OwnedClxResource(std::move(name), std::move(handle)) + , data_(std::move(data)) { assert(data_ != nullptr); } @@ -352,9 +418,9 @@ class OwnedClxSpriteList { OwnedClxSpriteList(OwnedClxSpriteList &&) noexcept = default; OwnedClxSpriteList &operator=(OwnedClxSpriteList &&) noexcept = default; - [[nodiscard]] OwnedClxSpriteList clone() const + [[nodiscard]] OwnedClxSpriteList clone(std::string_view trnName = {}) const { - return ClxSpriteList { *this }.clone(); + return ClxSpriteList { *this }.clone(name_, trnName); } [[nodiscard]] ClxSprite operator[](size_t spriteIndex) const @@ -367,6 +433,11 @@ class OwnedClxSpriteList { return ClxSpriteList { *this }.numSprites(); } + [[nodiscard]] size_t dataSize() const + { + return ClxSpriteList { *this }.nextSpriteSheetOffsetOrFileSize(); + } + private: // For OptionalOwnedClxSpriteList. OwnedClxSpriteList() = default; @@ -383,21 +454,41 @@ inline ClxSpriteList::ClxSpriteList(const OwnedClxSpriteList &owned) { } -inline OwnedClxSpriteList ClxSpriteList::clone() const +inline OwnedClxSpriteList ClxSpriteList::clone(std::string_view name, std::string_view trnName) const { const size_t dataSize = nextSpriteSheetOffsetOrFileSize(); std::unique_ptr data { new uint8_t[dataSize] }; memcpy(data.get(), data_, dataSize); - return OwnedClxSpriteList { std::move(data) }; + return OwnedClxSpriteList { name, trnName, std::move(data) }; } /** * @brief Implicitly convertible to `ClxSpriteSheet` and owns its data. */ -class OwnedClxSpriteSheet { +class OwnedClxSpriteSheet : public OwnedClxResource { public: - OwnedClxSpriteSheet(std::unique_ptr &&data, uint16_t numLists) - : data_(std::move(data)) + OwnedClxSpriteSheet(std::string_view name, std::string_view trnName, std::unique_ptr &&data, uint16_t numLists) + : OwnedClxResource(name, trnName) + , data_(std::move(data)) + , num_lists_(numLists) + { + assert(data_ != nullptr); + assert(numLists > 0); + } + + OwnedClxSpriteSheet(std::string &&name, std::string &&trnName, std::unique_ptr &&data, uint16_t numLists) + : OwnedClxResource(std::move(name), std::move(trnName)) + , data_(std::move(data)) + , num_lists_(numLists) + { + assert(data_ != nullptr); + assert(numLists > 0); + } + + explicit OwnedClxSpriteSheet(std::string &&name, ClxStoreHandle &&handle, std::unique_ptr &&data, uint16_t numLists) + // NOLINTNEXTLINE(performance-move-const-arg): Move is for semantics rather than performance. + : OwnedClxResource(std::move(name), std::move(handle)) + , data_(std::move(data)) , num_lists_(numLists) { assert(data_ != nullptr); @@ -422,16 +513,17 @@ class OwnedClxSpriteSheet { return ClxSpriteSheet { *this }.end(); } -private: - // For OptionalOwnedClxSpriteList. - OwnedClxSpriteSheet() - : data_(nullptr) - , num_lists_(0) + [[nodiscard]] size_t dataSize() const { + return ClxSpriteSheet { *this }.dataSize(); } +private: + // For OptionalOwnedClxSpriteList. + OwnedClxSpriteSheet() = default; + std::unique_ptr data_; - uint16_t num_lists_; + uint16_t num_lists_ = 0; friend class ClxSpriteSheet; // for implicit conversion. friend class OptionalOwnedClxSpriteSheet; @@ -472,6 +564,8 @@ class ClxSpriteListOrSheet { ClxSpriteListOrSheet(const OwnedClxSpriteListOrSheet &listOrSheet); + [[nodiscard]] OwnedClxSpriteListOrSheet clone(std::string_view name, std::string_view trnName = {}) const; + [[nodiscard]] constexpr ClxSpriteList list() const { assert(num_lists_ == 0); @@ -489,17 +583,18 @@ class ClxSpriteListOrSheet { return num_lists_ != 0; } -private: - const uint8_t *data_; - uint16_t num_lists_; - - // For OptionalClxSpriteListOrSheet. - constexpr ClxSpriteListOrSheet() - : data_(nullptr) - , num_lists_(0) + [[nodiscard]] size_t dataSize() const { + return isSheet() ? sheet().dataSize() : list().nextSpriteSheetOffsetOrFileSize(); } +private: + // For OptionalClxSpriteListOrSheet. + constexpr ClxSpriteListOrSheet() = default; + + const uint8_t *data_ = nullptr; + uint16_t num_lists_ = 0; + friend class OptionalClxSpriteListOrSheet; }; @@ -508,32 +603,52 @@ class OptionalOwnedClxSpriteListOrSheet; /** * @brief A CLX sprite list or a sprite sheet (list of lists). */ -class OwnedClxSpriteListOrSheet { +class OwnedClxSpriteListOrSheet : public OwnedClxResource { public: - static OwnedClxSpriteListOrSheet FromBuffer(std::unique_ptr &&data, size_t size) + static OwnedClxSpriteListOrSheet fromBuffer( + std::string_view name, std::string_view trnName, std::unique_ptr &&data, size_t size) { const uint16_t numLists = GetNumListsFromClxListOrSheetBuffer(data.get(), size); - return OwnedClxSpriteListOrSheet { std::move(data), numLists }; + return OwnedClxSpriteListOrSheet { name, trnName, std::move(data), numLists }; + } + + explicit OwnedClxSpriteListOrSheet(std::string_view name, std::string_view trnName, std::unique_ptr &&data, uint16_t numLists) + : OwnedClxResource(name, trnName) + , data_(std::move(data)) + , num_lists_(numLists) + { } - explicit OwnedClxSpriteListOrSheet(std::unique_ptr &&data, uint16_t numLists) - : data_(std::move(data)) + explicit OwnedClxSpriteListOrSheet(std::string &&name, std::string &&trnName, std::unique_ptr &&data, uint16_t numLists) + : OwnedClxResource(std::move(name), std::move(trnName)) + , data_(std::move(data)) , num_lists_(numLists) { } - explicit OwnedClxSpriteListOrSheet(OwnedClxSpriteSheet &&sheet) - : data_(std::move(sheet.data_)) + explicit OwnedClxSpriteListOrSheet(OwnedClxSpriteSheet &&sheet) noexcept + // NOLINTNEXTLINE(performance-move-const-arg): Move is for semantics rather than performance. + : OwnedClxResource(std::move(sheet.name_), std::move(sheet.handle_)) + , data_(std::move(sheet.data_)) , num_lists_(sheet.num_lists_) { } - explicit OwnedClxSpriteListOrSheet(OwnedClxSpriteList &&list) - : data_(std::move(list.data_)) - , num_lists_(0) + explicit OwnedClxSpriteListOrSheet(OwnedClxSpriteList &&list) noexcept + // NOLINTNEXTLINE(performance-move-const-arg): Move is for semantics rather than performance. + : OwnedClxResource(std::move(list.name_), std::move(list.handle_)) + , data_(std::move(list.data_)) { } + OwnedClxSpriteListOrSheet(OwnedClxSpriteListOrSheet &&) noexcept = default; + OwnedClxSpriteListOrSheet &operator=(OwnedClxSpriteListOrSheet &&) noexcept = default; + + [[nodiscard]] OwnedClxSpriteListOrSheet clone(std::string_view trnName = {}) const + { + return ClxSpriteListOrSheet { *this }.clone(name_, trnName); + } + [[nodiscard]] ClxSpriteList list() const & { assert(num_lists_ == 0); @@ -543,7 +658,8 @@ class OwnedClxSpriteListOrSheet { [[nodiscard]] OwnedClxSpriteList list() && { assert(num_lists_ == 0); - return OwnedClxSpriteList { std::move(data_) }; + // NOLINTNEXTLINE(performance-move-const-arg): Move is for semantics rather than performance. + return OwnedClxSpriteList { std::move(name_), std::move(handle_), std::move(data_) }; } [[nodiscard]] ClxSpriteSheet sheet() const & @@ -555,7 +671,8 @@ class OwnedClxSpriteListOrSheet { [[nodiscard]] OwnedClxSpriteSheet sheet() && { assert(num_lists_ != 0); - return OwnedClxSpriteSheet { std::move(data_), num_lists_ }; + // NOLINTNEXTLINE(performance-move-const-arg): Move is for semantics rather than performance. + return OwnedClxSpriteSheet { std::move(name_), std::move(handle_), std::move(data_), num_lists_ }; } [[nodiscard]] bool isSheet() const @@ -563,17 +680,18 @@ class OwnedClxSpriteListOrSheet { return num_lists_ != 0; } -private: - std::unique_ptr data_; - uint16_t num_lists_; - - // For OptionalOwnedClxSpriteListOrSheet. - OwnedClxSpriteListOrSheet() - : data_(nullptr) - , num_lists_(0) + [[nodiscard]] size_t dataSize() const { + return ClxSpriteListOrSheet { *this }.dataSize(); } +private: + // For OptionalOwnedClxSpriteListOrSheet. + OwnedClxSpriteListOrSheet() = default; + + std::unique_ptr data_; + uint16_t num_lists_ = 0; + friend class ClxSpriteListOrSheet; friend class OptionalOwnedClxSpriteListOrSheet; }; @@ -584,6 +702,14 @@ inline ClxSpriteListOrSheet::ClxSpriteListOrSheet(const OwnedClxSpriteListOrShee { } +inline OwnedClxSpriteListOrSheet ClxSpriteListOrSheet::clone(std::string_view name, std::string_view trnName) const +{ + const size_t size = this->dataSize(); + std::unique_ptr data { new uint8_t[size] }; + memcpy(data.get(), data_, size); + return OwnedClxSpriteListOrSheet { name, trnName, std::move(data), num_lists_ }; +} + /** * @brief Equivalent to `std::optional` but smaller. */ diff --git a/Source/engine/clx_store.cpp b/Source/engine/clx_store.cpp new file mode 100644 index 000000000000..52e77362aba3 --- /dev/null +++ b/Source/engine/clx_store.cpp @@ -0,0 +1,64 @@ +#include "engine/clx_store.hpp" + +#include +#include +#include +#include +#include +#include + +#include "appfat.h" +// NOLINTNEXTLINE(misc-include-cleaner): Used via templated lambdas passed to `std::visit`. +#include "engine/clx_sprite.hpp" +#include "utils/str_cat.hpp" + +namespace devilution { + +ClxStore &GetClxStore() +{ + static ClxStore *instance = new ClxStore(); + return *instance; +} + +std::string_view ClxStoreSprite::name() const +{ + return std::visit([](const auto *p) { return p->name(); }, ptr); +} + +size_t ClxStoreSprite::dataSize() const +{ + return std::visit([](const auto *p) { return p->dataSize(); }, ptr); +} + +std::optional ClxStore::get(std::string_view name, std::string_view trnName) const +{ + const auto it = map_.find(name); + if (it == map_.end()) return std::nullopt; + for (const ClxStoreSprite &ref : it->second) { + if (ref.trnName == trnName) return ref; + } + return std::nullopt; +} + +ClxStoreHandle ClxStore::registerClx(const std::string &name, ClxStoreSprite &&ref) +{ + std::forward_list &list = map_[name]; + list.push_front(std::move(ref)); + return ClxStoreHandle(list.before_begin()); +} + +void ClxStore::unregisterClx(std::string_view name, const ClxStoreHandle &handle) +{ + const auto it = map_.find(name); + if (it == map_.end() || it->second.empty()) { + app_fatal(StrCat("ClxStore: Invalid unregister request for [", name, "]: list is empty")); + } + + std::forward_list &list = it->second; + list.erase_after(handle.it_before_); + if (list.empty()) { + map_.erase(it); + } +} + +} // namespace devilution diff --git a/Source/engine/clx_store.hpp b/Source/engine/clx_store.hpp new file mode 100644 index 000000000000..c6f05e87ed48 --- /dev/null +++ b/Source/engine/clx_store.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace devilution { + +class ClxStore; +class OwnedClxSpriteListOrSheet; +class OwnedClxSpriteList; +class OwnedClxSpriteSheet; + +struct ClxStoreSprite { + using PtrVariant = std::variant< + const OwnedClxSpriteListOrSheet *, + const OwnedClxSpriteList *, + const OwnedClxSpriteSheet *>; + + PtrVariant ptr; + std::string trnName; + + [[nodiscard]] std::string_view name() const; + [[nodiscard]] size_t dataSize() const; +}; + +class ClxStoreHandle { +public: + ClxStoreHandle() = default; + + [[nodiscard]] bool has_value() const // NOLINT(readability-identifier-naming): match std::optional API. + { + return it_before_ != std::forward_list::iterator {}; + } + [[nodiscard]] const ClxStoreSprite &value() const { return *std::next(it_before_); } + [[nodiscard]] ClxStoreSprite &value() { return *std::next(it_before_); } + +private: + explicit ClxStoreHandle(std::forward_list::iterator itBefore) + : it_before_(itBefore) + { + } + std::forward_list::iterator it_before_; + + friend class ClxStore; +}; + +class ClxStore { +public: + // Once we're fully C++20, we can switch to `std::unordered_map` with `devilution::StringViewHash`. + using Map = std::map, std::less<>>; + + [[nodiscard]] std::optional get(std::string_view name, std::string_view trnName = {}) const; + + template + [[nodiscard]] ClxStoreHandle registerClx(const T &sprite, const std::string &name, std::string &&trnName) + { + return registerClx(name, ClxStoreSprite { ClxStoreSprite::PtrVariant { &sprite }, std::move(trnName) }); + } + + template + [[nodiscard]] ClxStoreHandle registerClx(const T &sprite, const std::string &name, std::string_view trnName) + { + return registerClx(name, ClxStoreSprite { ClxStoreSprite::PtrVariant { &sprite }, std::string(trnName) }); + } + + template + ClxStoreHandle replaceClx(const T &sprite, ClxStoreHandle &&handle) + { + ClxStoreHandle result = handle; + if (result.has_value()) result.value().ptr = &sprite; + handle = {}; + return result; + } + + void unregisterClx(std::string_view name, const ClxStoreHandle &handle); + + [[nodiscard]] const Map &getAll() const { return map_; } + +private: + [[nodiscard]] ClxStoreHandle registerClx(const std::string &name, ClxStoreSprite &&ref); + + Map map_; +}; + +ClxStore &GetClxStore(); + +} // namespace devilution diff --git a/Source/engine/load_cel.cpp b/Source/engine/load_cel.cpp index c9879c40bf1b..3e3ab34cce2b 100644 --- a/Source/engine/load_cel.cpp +++ b/Source/engine/load_cel.cpp @@ -31,7 +31,7 @@ OwnedClxSpriteListOrSheet LoadCelListOrSheet(const char *pszName, PointerOrValue #ifdef DEBUG_CEL_TO_CL2_SIZE std::cout << path; #endif - return CelToClx(data.get(), size, widthOrWidths); + return CelToClx(pszName, /*trnName=*/ {}, data.get(), size, widthOrWidths); #endif } diff --git a/Source/engine/load_cl2.cpp b/Source/engine/load_cl2.cpp index a8deb6991d11..64f2ca6da33b 100644 --- a/Source/engine/load_cl2.cpp +++ b/Source/engine/load_cl2.cpp @@ -25,7 +25,7 @@ OwnedClxSpriteListOrSheet LoadCl2ListOrSheet(const char *pszName, PointerOrValue #else size_t size; std::unique_ptr data = LoadFileInMem(path, &size); - return Cl2ToClx(std::move(data), size, widthOrWidths); + return Cl2ToClx(pszName, /*trnName=*/ {}, std::move(data), size, widthOrWidths); #endif } diff --git a/Source/engine/load_cl2.hpp b/Source/engine/load_cl2.hpp index 95dec272b75e..e0c98bba6fd2 100644 --- a/Source/engine/load_cl2.hpp +++ b/Source/engine/load_cl2.hpp @@ -27,7 +27,7 @@ namespace devilution { OwnedClxSpriteListOrSheet LoadCl2ListOrSheet(const char *pszName, PointerOrValue widthOrWidths); template -OwnedClxSpriteSheet LoadMultipleCl2Sheet(tl::function_ref filenames, size_t count, uint16_t width) +OwnedClxSpriteSheet LoadMultipleCl2Sheet(std::string_view name, tl::function_ref filenames, size_t count, uint16_t width) { StaticVector, MaxCount> paths; StaticVector files; @@ -64,9 +64,9 @@ OwnedClxSpriteSheet LoadMultipleCl2Sheet(tl::function_ref accumulatedSize += size; } #ifdef UNPACKED_MPQS - return OwnedClxSpriteSheet { std::move(data), static_cast(count) }; + return OwnedClxSpriteSheet { name, /*trnName=*/ {}, std::move(data), static_cast(count) }; #else - return Cl2ToClx(std::move(data), accumulatedSize, frameWidth).sheet(); + return Cl2ToClx(name, /*trnName=*/ {}, std::move(data), accumulatedSize, frameWidth).sheet(); #endif } diff --git a/Source/engine/load_clx.cpp b/Source/engine/load_clx.cpp index 6326a04ae109..0a013200b79f 100644 --- a/Source/engine/load_clx.cpp +++ b/Source/engine/load_clx.cpp @@ -9,13 +9,15 @@ #endif #include "engine/assets.hpp" +#include "engine/clx_sprite.hpp" #include "engine/load_file.hpp" namespace devilution { OptionalOwnedClxSpriteListOrSheet LoadOptionalClxListOrSheet(const char *path) { - AssetRef ref = FindAsset(path); + std::string_view pathStrView = path; + AssetRef ref = FindAsset(pathStrView); if (!ref.ok()) return std::nullopt; const size_t size = ref.size(); @@ -25,14 +27,17 @@ OptionalOwnedClxSpriteListOrSheet LoadOptionalClxListOrSheet(const char *path) if (!handle.ok() || !handle.read(data.get(), size)) return std::nullopt; } - return OwnedClxSpriteListOrSheet::FromBuffer(std::move(data), size); + pathStrView.remove_suffix(4); + return OwnedClxSpriteListOrSheet::fromBuffer(pathStrView, /*trnName=*/ {}, std::move(data), size); } OwnedClxSpriteListOrSheet LoadClxListOrSheet(const char *path) { size_t size; std::unique_ptr data = LoadFileInMem(path, &size); - return OwnedClxSpriteListOrSheet::FromBuffer(std::move(data), size); + std::string_view pathStrView = path; + pathStrView.remove_suffix(4); + return OwnedClxSpriteListOrSheet::fromBuffer(pathStrView, /*trnName=*/ {}, std::move(data), size); } } // namespace devilution diff --git a/Source/engine/load_pcx.cpp b/Source/engine/load_pcx.cpp index 48b23a8b9080..f43c6523c2df 100644 --- a/Source/engine/load_pcx.cpp +++ b/Source/engine/load_pcx.cpp @@ -68,7 +68,7 @@ OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFrames #ifdef DEBUG_PCX_TO_CL2_SIZE std::cout << filename; #endif - OptionalOwnedClxSpriteList result = PcxToClx(handle, fileSize, numFramesOrFrameHeight, transparentColor, outPalette); + OptionalOwnedClxSpriteList result = PcxToClx(filename, handle, fileSize, numFramesOrFrameHeight, transparentColor, outPalette); if (!result) return std::nullopt; return result; diff --git a/Source/lua/modules/dev.cpp b/Source/lua/modules/dev.cpp index e3de74633d7e..960a059b86cc 100644 --- a/Source/lua/modules/dev.cpp +++ b/Source/lua/modules/dev.cpp @@ -11,6 +11,7 @@ #include "lua/modules/dev/player.hpp" #include "lua/modules/dev/quests.hpp" #include "lua/modules/dev/search.hpp" +#include "lua/modules/dev/sprites.hpp" #include "lua/modules/dev/towners.hpp" namespace devilution { @@ -25,6 +26,7 @@ sol::table LuaDevModule(sol::state_view &lua) SetDocumented(table, "player", "", "Player-related commands.", LuaDevPlayerModule(lua)); SetDocumented(table, "quests", "", "Quest-related commands.", LuaDevQuestsModule(lua)); SetDocumented(table, "search", "", "Search the map for monsters / items / objects.", LuaDevSearchModule(lua)); + SetDocumented(table, "sprites", "", "Sprite graphics commands.", LuaDevSpritesModule(lua)); SetDocumented(table, "towners", "", "Town NPC commands.", LuaDevTownersModule(lua)); return table; } diff --git a/Source/lua/modules/dev/sprites.cpp b/Source/lua/modules/dev/sprites.cpp new file mode 100644 index 000000000000..91aeb113423d --- /dev/null +++ b/Source/lua/modules/dev/sprites.cpp @@ -0,0 +1,50 @@ +#ifdef _DEBUG +#include "lua/modules/dev/sprites.hpp" + +#include +#include +#include + +#include + +#include "engine/clx_store.hpp" +#include "lua/metadoc.hpp" +#include "utils/format_int.hpp" +#include "utils/str_cat.hpp" + +namespace devilution { +namespace { + +std::string DebugCmdListLoadedSprites() +{ + std::string result; + const auto &map = GetClxStore().getAll(); + size_t numSprites = 0; + size_t totalSize = 0; + size_t i = 0; + for (const auto &[name, sprites] : GetClxStore().getAll()) { + StrAppend(result, ++i, ". ", name); + for (const ClxStoreSprite &sprite : sprites) { + if (!sprite.trnName.empty()) StrAppend(result, " ", sprite.trnName); + const size_t size = sprite.dataSize(); + StrAppend(result, " ", FormatInteger(static_cast(size))); + totalSize += sprite.dataSize(); + ++numSprites; + } + result += '\n'; + } + StrAppend(result, FormatInteger(static_cast(numSprites)), " sprite files, total size: ", FormatInteger(static_cast(totalSize)), " bytes"); + return result; +} + +} // namespace + +sol::table LuaDevSpritesModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "listLoaded", "()", "Information about the loaded sprites.", &DebugCmdListLoadedSprites); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/sprites.hpp b/Source/lua/modules/dev/sprites.hpp new file mode 100644 index 000000000000..000d16704c84 --- /dev/null +++ b/Source/lua/modules/dev/sprites.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevSpritesModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/misdat.cpp b/Source/misdat.cpp index 8fa823a9fe6a..e6728ffc954a 100644 --- a/Source/misdat.cpp +++ b/Source/misdat.cpp @@ -235,13 +235,13 @@ void MissileFileData::LoadGFX() *BufCopy(path, "missiles\\", name, ".clx") = '\0'; sprites.emplace(LoadClxListOrSheet(path)); #else + char path[MaxMpqPathSize]; + *BufCopy(path, "missiles\\", name) = '\0'; if (animFAmt == 1) { - char path[MaxMpqPathSize]; - *BufCopy(path, "missiles\\", name) = '\0'; sprites.emplace(OwnedClxSpriteListOrSheet { LoadCl2(path, animWidth) }); } else { FileNameGenerator pathGenerator({ "missiles\\", name }, DEVILUTIONX_CL2_EXT); - sprites.emplace(OwnedClxSpriteListOrSheet { LoadMultipleCl2Sheet<16>(pathGenerator, animFAmt, animWidth) }); + sprites.emplace(OwnedClxSpriteListOrSheet { LoadMultipleCl2Sheet<16>(path, pathGenerator, animFAmt, animWidth) }); } #endif } diff --git a/Source/monster.cpp b/Source/monster.cpp index c2bde27e185d..04436e256e75 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -65,6 +65,29 @@ size_t ActiveMonsterCount; int MonsterKillCounts[NUM_MTYPES]; bool sgbSaveSoundOn; +MonsterSpritesData MonsterSpritesData::clone() const +{ + MonsterSpritesData result; + for (const OwnedClxSpriteListOrSheet &sprite : sprites) { + result.sprites.emplace_back(sprite.clone()); + } + return result; +} + +bool CMonster::hasGraphics() const +{ + return c_any_of(anims, [](const AnimStruct &anim) { + return anim.sprites.has_value(); + }); +} + +void CMonster::clearGraphics() +{ + for (AnimStruct &animData : anims) { + animData.sprites = std::nullopt; + } +} + namespace { constexpr int NightmareToHitBonus = 85; @@ -101,19 +124,6 @@ size_t GetNumAnims(const MonsterData &monsterData) return monsterData.hasSpecial ? 6 : 5; } -size_t GetNumAnimsWithGraphics(const MonsterData &monsterData) -{ - // Monster graphics can be missing for certain actions, - // e.g. Golem has no standing graphics. - const size_t numAnims = GetNumAnims(monsterData); - size_t result = 0; - for (size_t i = 0; i < numAnims; ++i) { - if (monsterData.hasAnim(i)) - ++result; - } - return result; -} - void InitMonsterTRN(CMonster &monst) { char path[64]; @@ -129,6 +139,7 @@ void InitMonsterTRN(CMonster &monst) } AnimStruct &anim = monst.anims[i]; + anim.sprites->setTrnName(monst.data().trnFile); if (anim.sprites->isSheet()) { ClxApplyTrans(ClxSpriteSheet { anim.sprites->sheet() }, colorTranslations.data()); } else { @@ -3066,39 +3077,15 @@ bool UpdateModeStance(Monster &monster) MonsterSpritesData LoadMonsterSpritesData(const MonsterData &monsterData) { - const size_t numAnims = GetNumAnims(monsterData); - MonsterSpritesData result; - result.data = MultiFileLoader {}( - numAnims, - FileNameWithCharAffixGenerator({ "monsters\\", monsterData.spritePath() }, DEVILUTIONX_CL2_EXT, Animletter), - result.offsets.data(), - [&monsterData](size_t index) { return monsterData.hasAnim(index); }); - -#ifndef UNPACKED_MPQS - // Convert CL2 to CLX: - std::vector> clxData; - size_t accumulatedSize = 0; - for (size_t i = 0, j = 0; i < numAnims; ++i) { - if (!monsterData.hasAnim(i)) - continue; - const uint32_t begin = result.offsets[j]; - const uint32_t end = result.offsets[j + 1]; - clxData.emplace_back(); - Cl2ToClx(reinterpret_cast(&result.data[begin]), end - begin, - PointerOrValue { monsterData.width }, clxData.back()); - result.offsets[j] = static_cast(accumulatedSize); - accumulatedSize += clxData.back().size(); - ++j; + char path[MaxMpqPathSize]; + char *pathLast = BufCopy(path, "monsters\\", monsterData.spritePath()); + *(pathLast + 1) = '\0'; + for (size_t i = 0; i < MonsterSpritesData::MaxAnims; ++i) { + if (!monsterData.hasAnim(i)) continue; + *pathLast = Animletter[i]; + result.sprites.emplace_back(LoadCl2ListOrSheet(path, PointerOrValue { monsterData.width })); } - result.offsets[clxData.size()] = static_cast(accumulatedSize); - result.data = nullptr; - result.data = std::unique_ptr(new std::byte[accumulatedSize]); - for (size_t i = 0; i < clxData.size(); ++i) { - memcpy(&result.data[result.offsets[i]], clxData[i].data(), clxData[i].size()); - } -#endif - return result; } @@ -3378,14 +3365,14 @@ void InitMonsterSND(CMonster &monsterType) void InitMonsterGFX(CMonster &monsterType, MonsterSpritesData &&spritesData) { - if (HeadlessMode) + if (HeadlessMode || monsterType.hasGraphics()) return; const _monster_id mtype = monsterType.type; const MonsterData &monsterData = MonstersData[mtype]; - if (spritesData.data == nullptr) + if (spritesData.sprites.empty()) { spritesData = LoadMonsterSpritesData(monsterData); - monsterType.animData = std::move(spritesData.data); + } const size_t numAnims = GetNumAnims(monsterData); for (size_t i = 0, j = 0; i < numAnims; ++i) { @@ -3393,11 +3380,7 @@ void InitMonsterGFX(CMonster &monsterType, MonsterSpritesData &&spritesData) monsterType.anims[i].sprites = std::nullopt; continue; } - const uint32_t begin = spritesData.offsets[j]; - const uint32_t end = spritesData.offsets[j + 1]; - auto spritesData = reinterpret_cast(&monsterType.animData[begin]); - const uint16_t numLists = GetNumListsFromClxListOrSheetBuffer(spritesData, end - begin); - monsterType.anims[i].sprites = ClxSpriteListOrSheet { spritesData, numLists }; + monsterType.anims[i].sprites = std::move(spritesData.sprites[j]); ++j; } @@ -3461,26 +3444,26 @@ void InitAllMonsterGFX() size_t totalUniqueBytes = 0; size_t totalBytes = 0; for (const LevelMonsterTypeIndices &monsterTypes : monstersBySprite) { - if (monsterTypes.empty()) - continue; + if (monsterTypes.empty()) continue; CMonster &firstMonster = LevelMonsterTypes[monsterTypes[0]]; - if (firstMonster.animData != nullptr) - continue; + if (firstMonster.hasGraphics()) continue; MonsterSpritesData spritesData = LoadMonsterSpritesData(firstMonster.data()); - const size_t spritesDataSize = spritesData.offsets[GetNumAnimsWithGraphics(firstMonster.data())]; + size_t spritesDataSize = 0; + for (const OwnedClxSpriteListOrSheet &sprite : spritesData.sprites) { + spritesDataSize += sprite.dataSize(); + } for (size_t i = 1; i < monsterTypes.size(); ++i) { - MonsterSpritesData spritesDataCopy { std::unique_ptr { new std::byte[spritesDataSize] }, spritesData.offsets }; - memcpy(spritesDataCopy.data.get(), spritesData.data.get(), spritesDataSize); - InitMonsterGFX(LevelMonsterTypes[monsterTypes[i]], std::move(spritesDataCopy)); + InitMonsterGFX(LevelMonsterTypes[monsterTypes[i]], spritesData.clone()); } LogVerbose("Loaded monster graphics: {:15s} {:>4d} KiB x{:d}", firstMonster.data().spritePath(), spritesDataSize / 1024, monsterTypes.size()); totalUniqueBytes += spritesDataSize; totalBytes += spritesDataSize * monsterTypes.size(); InitMonsterGFX(firstMonster, std::move(spritesData)); } - LogVerbose(" Total monster graphics: {:>4d} KiB {:>4d} KiB", totalUniqueBytes / 1024, totalBytes / 1024); if (totalUniqueBytes > 0) { + LogVerbose(" Total monster graphics: {:>4d} KiB {:>4d} KiB", totalUniqueBytes / 1024, totalBytes / 1024); + // we loaded new sprites, check if we need to update existing monsters for (size_t i = 0; i < ActiveMonsterCount; i++) { Monster &monster = Monsters[ActiveMonsters[i]]; @@ -4107,11 +4090,8 @@ void ProcessMonsters() void FreeMonsters() { for (CMonster &monsterType : LevelMonsterTypes) { - monsterType.animData = nullptr; + monsterType.clearGraphics(); monsterType.corpseId = 0; - 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 44d46ed31a5d..dc5d264d0b28 100644 --- a/Source/monster.h +++ b/Source/monster.h @@ -26,6 +26,7 @@ #include "spelldat.h" #include "textdat.h" #include "utils/language.h" +#include "utils/static_vector.hpp" namespace devilution { @@ -154,7 +155,7 @@ struct AnimStruct { /** * @brief Sprite lists for each of the 8 directions. */ - OptionalClxSpriteListOrSheet sprites; + OptionalOwnedClxSpriteListOrSheet sprites; [[nodiscard]] OptionalClxSpriteList spritesForDirection(Direction direction) const { @@ -177,12 +178,12 @@ enum class MonsterSound : uint8_t { struct MonsterSpritesData { static constexpr size_t MaxAnims = 6; - std::unique_ptr data; - std::array offsets; + StaticVector sprites; + + MonsterSpritesData clone() const; }; struct CMonster { - std::unique_ptr animData; AnimStruct anims[6]; std::unique_ptr sounds[4][2]; @@ -191,6 +192,9 @@ struct CMonster { uint8_t placeFlags; int8_t corpseId = 0; + [[nodiscard]] bool hasGraphics() const; + void clearGraphics(); + const MonsterData &data() const { return MonstersData[type]; diff --git a/Source/panels/charpanel.cpp b/Source/panels/charpanel.cpp index 10c3e513963b..5317964b8564 100644 --- a/Source/panels/charpanel.cpp +++ b/Source/panels/charpanel.cpp @@ -294,7 +294,7 @@ void LoadCharPanel() } } - Panel = SurfaceToClx(out); + Panel = SurfaceToClx("runtime\\char_panel", /*trnName=*/ {}, out); } void FreeCharPanel() diff --git a/Source/panels/mainpanel.cpp b/Source/panels/mainpanel.cpp index b5d6e19ca311..c73177bb5acd 100644 --- a/Source/panels/mainpanel.cpp +++ b/Source/panels/mainpanel.cpp @@ -90,7 +90,7 @@ void LoadMainPanel() RenderMainButton(*out, 3, _("menu"), 0); RenderMainButton(*out, 4, _("inv"), 1); RenderMainButton(*out, 5, _("spells"), 0); - PanelButtonDown = SurfaceToClx(*out, NumButtonSprites); + PanelButtonDown = SurfaceToClx("runtime\\panel_button_down", /*trnName=*/ {}, *out, NumButtonSprites); out = std::nullopt; if (IsChatAvailable()) { @@ -128,7 +128,7 @@ void LoadMainPanel() int voiceWidth = GetLineWidth(_("voice"), GameFont12, 2); RenderClxSprite(talkSurface.subregion((talkButtonWidth - voiceWidth) / 2, 39, voiceWidth, 9), (*PanelButtonGrime)[1], { 0, 0 }); DrawButtonText(talkSurface, _("voice"), { { 0, 33 }, { talkButtonWidth, 0 } }, UiFlags::ColorButtonpushed); - TalkButton = SurfaceToClx(talkSurface, NumTalkButtonSprites); + TalkButton = SurfaceToClx("runtime\\talk_button", /*trnName=*/ {}, talkSurface, NumTalkButtonSprites); } PanelButtonDownGrime = std::nullopt; diff --git a/Source/qol/monhealthbar.cpp b/Source/qol/monhealthbar.cpp index 978721baebd6..6925dc251f43 100644 --- a/Source/qol/monhealthbar.cpp +++ b/Source/qol/monhealthbar.cpp @@ -43,7 +43,7 @@ void InitMonsterHealthBar() healthBlueTrn[234] = 185; healthBlueTrn[235] = 186; healthBlueTrn[236] = 187; - healthBlue = health->clone(); + healthBlue = health->clone("healthBlue"); ClxApplyTrans(*healthBlue, healthBlueTrn.data()); } diff --git a/Source/utils/cel_to_clx.cpp b/Source/utils/cel_to_clx.cpp index 76fdcc3aaf59..3d103fe0ca80 100644 --- a/Source/utils/cel_to_clx.cpp +++ b/Source/utils/cel_to_clx.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #ifdef DEBUG_CEL_TO_CL2_SIZE @@ -31,7 +32,7 @@ constexpr uint8_t GetCelTransparentWidth(uint8_t control) } // namespace -OwnedClxSpriteListOrSheet CelToClx(const uint8_t *data, size_t size, PointerOrValue widthOrWidths) +OwnedClxSpriteListOrSheet CelToClx(std::string_view name, std::string_view trnName, const uint8_t *data, size_t size, PointerOrValue widthOrWidths) { // A CEL file either begins with: // 1. A CEL header. @@ -124,7 +125,7 @@ OwnedClxSpriteListOrSheet CelToClx(const uint8_t *data, size_t size, PointerOrVa #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 OwnedClxSpriteListOrSheet { std::move(out), static_cast(numGroups == 1 ? 0 : numGroups) }; + return OwnedClxSpriteListOrSheet { name, trnName, 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 index 6310837619b2..9fc5677abcb9 100644 --- a/Source/utils/cel_to_clx.hpp +++ b/Source/utils/cel_to_clx.hpp @@ -2,12 +2,13 @@ #include #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); +OwnedClxSpriteListOrSheet CelToClx(std::string_view name, std::string_view trnName, const uint8_t *data, size_t size, PointerOrValue widthOrWidths); } // namespace devilution diff --git a/Source/utils/cl2_to_clx.hpp b/Source/utils/cl2_to_clx.hpp index 14a36e8208f2..3596a6318953 100644 --- a/Source/utils/cl2_to_clx.hpp +++ b/Source/utils/cl2_to_clx.hpp @@ -20,14 +20,14 @@ namespace devilution { uint16_t Cl2ToClx(const uint8_t *data, size_t size, PointerOrValue widthOrWidths, std::vector &clxData); -inline OwnedClxSpriteListOrSheet Cl2ToClx(std::unique_ptr &&data, size_t size, PointerOrValue widthOrWidths) +inline OwnedClxSpriteListOrSheet Cl2ToClx(std::string_view name, std::string_view trnName, std::unique_ptr &&data, size_t size, PointerOrValue widthOrWidths) { std::vector clxData; const uint16_t numLists = Cl2ToClx(data.get(), size, widthOrWidths, clxData); data = nullptr; data = std::unique_ptr(new uint8_t[clxData.size()]); memcpy(&data[0], clxData.data(), clxData.size()); - return OwnedClxSpriteListOrSheet { std::move(data), numLists }; + return OwnedClxSpriteListOrSheet { name, trnName, std::move(data), numLists }; } } // namespace devilution diff --git a/Source/utils/intrusive_optional.hpp b/Source/utils/intrusive_optional.hpp index 40523b473a54..c3266ade59f3 100644 --- a/Source/utils/intrusive_optional.hpp +++ b/Source/utils/intrusive_optional.hpp @@ -29,8 +29,16 @@ public: return value_; \ } \ \ + CONSTEXPR OPTIONAL_CLASS(const OPTIONAL_CLASS &) = default; \ + CONSTEXPR OPTIONAL_CLASS(OPTIONAL_CLASS &&) noexcept = default; \ + CONSTEXPR OPTIONAL_CLASS &operator=(const OPTIONAL_CLASS &) = default; \ + CONSTEXPR OPTIONAL_CLASS &operator=(OPTIONAL_CLASS &&) noexcept = default; \ + \ template \ - CONSTEXPR OPTIONAL_CLASS &operator=(VALUE_CLASS &&value) \ + CONSTEXPR std::enable_if_t< \ + !std::is_same_v>, \ + OPTIONAL_CLASS> & \ + operator=(U &&value) noexcept \ { \ value_ = std::forward(value); \ return *this; \ @@ -66,11 +74,16 @@ public: return &value_; \ } \ \ - CONSTEXPR operator bool() const \ + [[nodiscard]] CONSTEXPR bool has_value() const \ { \ return value_.FIELD != NULL_VALUE; \ } \ \ + CONSTEXPR operator bool() const \ + { \ + return has_value(); \ + } \ + \ private: \ VALUE_CLASS value_; diff --git a/Source/utils/language.cpp b/Source/utils/language.cpp index 1e86f945bd38..a2ce3588c75a 100644 --- a/Source/utils/language.cpp +++ b/Source/utils/language.cpp @@ -14,6 +14,7 @@ #include "utils/file_util.h" #include "utils/log.hpp" #include "utils/paths.h" +#include "utils/string_view_hash.hpp" #ifdef USE_SDL1 #include "utils/sdl2_to_1_2_backports.h" @@ -37,13 +38,6 @@ std::unique_ptr translationValues; using TranslationRef = uint32_t; -struct StringHash { - size_t operator()(const char *str) const noexcept - { - return std::hash {}(str); - } -}; - struct StringEq { bool operator()(const char *lhs, const char *rhs) const noexcept { @@ -51,7 +45,7 @@ struct StringEq { } }; -std::vector> translation = { {}, {} }; +std::vector> translation = { {}, {} }; constexpr uint32_t TranslationRefOffsetBits = 19; constexpr uint32_t TranslationRefSizeBits = 32 - TranslationRefOffsetBits; // 13 diff --git a/Source/utils/pcx_to_clx.cpp b/Source/utils/pcx_to_clx.cpp index cb94648cce99..0665176b8d9e 100644 --- a/Source/utils/pcx_to_clx.cpp +++ b/Source/utils/pcx_to_clx.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -53,7 +54,7 @@ bool LoadPcxMeta(AssetHandle &handle, int &width, int &height, uint8_t &bpp) } // namespace -OptionalOwnedClxSpriteList PcxToClx(AssetHandle &handle, size_t fileSize, int numFramesOrFrameHeight, std::optional transparentColor, SDL_Color *outPalette) +OptionalOwnedClxSpriteList PcxToClx(std::string_view name, AssetHandle &handle, size_t fileSize, int numFramesOrFrameHeight, std::optional transparentColor, SDL_Color *outPalette) { int width; int height; @@ -189,7 +190,7 @@ OptionalOwnedClxSpriteList PcxToClx(AssetHandle &handle, size_t fileSize, int nu #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 OwnedClxSpriteList { std::move(out) }; + return OwnedClxSpriteList { name, /*trnName=*/ {}, std::move(out) }; } } // namespace devilution diff --git a/Source/utils/pcx_to_clx.hpp b/Source/utils/pcx_to_clx.hpp index 2eca4cc3b37c..73f2305d74b8 100644 --- a/Source/utils/pcx_to_clx.hpp +++ b/Source/utils/pcx_to_clx.hpp @@ -2,6 +2,7 @@ #include #include +#include #include "engine/assets.hpp" #include "engine/clx_sprite.hpp" @@ -15,6 +16,6 @@ namespace devilution { * @param numFramesOrFrameHeight Pass a positive value with the number of frames, or the frame height as a negative value. * @param transparentColor The PCX palette index of the transparent color. */ -OptionalOwnedClxSpriteList PcxToClx(AssetHandle &handle, size_t fileSize, int numFramesOrFrameHeight = 1, std::optional transparentColor = std::nullopt, SDL_Color *outPalette = nullptr); +OptionalOwnedClxSpriteList PcxToClx(std::string_view name, AssetHandle &handle, size_t fileSize, int numFramesOrFrameHeight = 1, std::optional transparentColor = std::nullopt, SDL_Color *outPalette = nullptr); } // namespace devilution diff --git a/Source/utils/string_view_hash.hpp b/Source/utils/string_view_hash.hpp new file mode 100644 index 000000000000..aeb8315c851d --- /dev/null +++ b/Source/utils/string_view_hash.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +namespace devilution { + +// A hash functor that enables heterogenous lookup for `unordered_set/map`. +struct StringViewHash { + using is_transparent = void; + + size_t operator()(const char *str) const noexcept + { + return std::hash {}(str); + } + + [[nodiscard]] size_t operator()(std::string_view str) const noexcept + { + return std::hash {}(str); + } + + [[nodiscard]] size_t operator()(const std::string &str) const noexcept + { + return std::hash {}(str); + } +}; + +} // namespace devilution diff --git a/Source/utils/surface_to_clx.cpp b/Source/utils/surface_to_clx.cpp index 82460554f7fe..740b6aa3420e 100644 --- a/Source/utils/surface_to_clx.cpp +++ b/Source/utils/surface_to_clx.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "utils/clx_encode.hpp" @@ -14,7 +15,7 @@ namespace devilution { -OwnedClxSpriteList SurfaceToClx(const Surface &surface, unsigned numFrames, +OwnedClxSpriteList SurfaceToClx(std::string &&name, std::string &&trnName, const Surface &surface, unsigned numFrames, std::optional transparentColor) { // CLX header: frame count, frame offset for each frame, file size @@ -91,7 +92,7 @@ OwnedClxSpriteList SurfaceToClx(const Surface &surface, unsigned numFrames, << std::setprecision(1) << std::fixed << (static_cast(clxData.size()) - surfaceSize) / ((float)surfaceSize) * 100 << "%" << std::endl; #endif - return OwnedClxSpriteList { std::move(out) }; + return OwnedClxSpriteList { std::move(name), std::move(trnName), std::move(out) }; } } // namespace devilution diff --git a/Source/utils/surface_to_clx.hpp b/Source/utils/surface_to_clx.hpp index f0edb87b1a82..b8756c179127 100644 --- a/Source/utils/surface_to_clx.hpp +++ b/Source/utils/surface_to_clx.hpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include "engine/clx_sprite.hpp" #include "engine/surface.hpp" @@ -15,7 +17,7 @@ namespace devilution { * @param numFrames The number of vertically stacked frames in the surface. * @param transparentColor The PCX palette index of the transparent color. */ -OwnedClxSpriteList SurfaceToClx(const Surface &surface, unsigned numFrames = 1, +OwnedClxSpriteList SurfaceToClx(std::string &&name, std::string &&trnName, const Surface &surface, unsigned numFrames = 1, std::optional transparentColor = std::nullopt); } // namespace devilution