From 9ee9056f1215cabbfce83a619f12e9ae8b905d27 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 13 Jul 2024 00:43:23 +0100 Subject: [PATCH] Global sprite storage Keeps track of all the currently loaded sprites. This should eventually allow Lua to access them by name. Not sure if name-based access to currently-loaded sprites is something that we'll ever want (might be too unstructured), but at least this is useful for debugging sprite issues. If this only ends up useful for debugging, we can add a couple of `ifdefs` to only keep track of the sprites in debug mode. --- Source/CMakeLists.txt | 2 + Source/cursor.cpp | 11 +- Source/engine/clx_sprite.hpp | 263 +++++++++++++++++++++------- Source/engine/clx_store.cpp | 68 +++++++ Source/engine/clx_store.hpp | 95 ++++++++++ Source/engine/load_cel.cpp | 2 +- Source/engine/load_cl2.cpp | 2 +- Source/engine/load_cl2.hpp | 6 +- Source/engine/load_clx.cpp | 11 +- Source/engine/load_pcx.cpp | 2 +- Source/lua/modules/dev.cpp | 2 + Source/lua/modules/dev/sprites.cpp | 49 ++++++ Source/lua/modules/dev/sprites.hpp | 10 ++ Source/misdat.cpp | 6 +- Source/monster.cpp | 110 +++++------- Source/monster.h | 12 +- Source/panels/charpanel.cpp | 2 +- Source/panels/mainpanel.cpp | 4 +- Source/qol/monhealthbar.cpp | 2 +- Source/utils/cel_to_clx.cpp | 5 +- Source/utils/cel_to_clx.hpp | 3 +- Source/utils/cl2_to_clx.cpp | 2 +- Source/utils/cl2_to_clx.hpp | 4 +- Source/utils/intrusive_optional.hpp | 17 +- Source/utils/language.cpp | 10 +- Source/utils/pcx_to_clx.cpp | 5 +- Source/utils/pcx_to_clx.hpp | 3 +- Source/utils/string_view_hash.hpp | 29 +++ Source/utils/surface_to_clx.cpp | 5 +- Source/utils/surface_to_clx.hpp | 4 +- 30 files changed, 566 insertions(+), 180 deletions(-) create mode 100644 Source/engine/clx_store.cpp create mode 100644 Source/engine/clx_store.hpp create mode 100644 Source/lua/modules/dev/sprites.cpp create mode 100644 Source/lua/modules/dev/sprites.hpp create mode 100644 Source/utils/string_view_hash.hpp 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..40404cc8a07b 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 { @@ -139,7 +136,7 @@ class ClxSpriteList { } /** @brief The offset to the next sprite sheet, or file size if this is the last sprite sheet. */ - [[nodiscard]] constexpr uint32_t nextSpriteSheetOffsetOrFileSize() const + [[nodiscard]] constexpr uint32_t dataSize() const { return LoadLE32(&data_[4 + numSprites() * 4]); } @@ -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 static_cast(&data_[sheetOffset(num_lists_ - 1)] + (*this)[num_lists_ - 1].dataSize() - &data_[0]); } - 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,85 @@ 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_; + friend class ClxStore; +}; + /** * @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 +419,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 +434,11 @@ class OwnedClxSpriteList { return ClxSpriteList { *this }.numSprites(); } + [[nodiscard]] size_t dataSize() const + { + return ClxSpriteList { *this }.dataSize(); + } + private: // For OptionalOwnedClxSpriteList. OwnedClxSpriteList() = default; @@ -383,21 +455,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) }; + const size_t size = dataSize(); + std::unique_ptr data { new uint8_t[size] }; + memcpy(data.get(), data_, size); + 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 +514,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 +565,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 +584,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().dataSize(); } +private: + // For OptionalClxSpriteListOrSheet. + constexpr ClxSpriteListOrSheet() = default; + + const uint8_t *data_ = nullptr; + uint16_t num_lists_ = 0; + friend class OptionalClxSpriteListOrSheet; }; @@ -508,32 +604,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 +659,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 +672,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 +681,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 +703,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..205cc5b4a753 --- /dev/null +++ b/Source/engine/clx_store.cpp @@ -0,0 +1,68 @@ +#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]; + ClxStoreSprite *next = list.empty() ? nullptr : &*list.begin(); + list.push_front(std::move(ref)); + if (next != nullptr) { + std::visit([&list](auto *p) { p->handle_.it_before_ = list.begin(); }, next->ptr); + } + 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..7ac9f20e63d1 --- /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< + OwnedClxSpriteListOrSheet *, + OwnedClxSpriteList *, + 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(T &sprite, const std::string &name, std::string &&trnName) + { + return registerClx(name, ClxStoreSprite { ClxStoreSprite::PtrVariant { &sprite }, std::move(trnName) }); + } + + template + [[nodiscard]] ClxStoreHandle registerClx(T &sprite, const std::string &name, std::string_view trnName) + { + return registerClx(name, ClxStoreSprite { ClxStoreSprite::PtrVariant { &sprite }, std::string(trnName) }); + } + + template + ClxStoreHandle replaceClx(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..1dc8648f4c90 --- /dev/null +++ b/Source/lua/modules/dev/sprites.cpp @@ -0,0 +1,49 @@ +#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; + 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.cpp b/Source/utils/cl2_to_clx.cpp index d1c4ae7cb32a..20151bb55a1b 100644 --- a/Source/utils/cl2_to_clx.cpp +++ b/Source/utils/cl2_to_clx.cpp @@ -58,7 +58,7 @@ uint16_t Cl2ToClx(const uint8_t *data, size_t size, WriteLE32(&clxData[4 * group], static_cast(clxData.size())); } - // CLX header: frame count, frame offset for each frame, file size + // CLX header: frame count, frame offset for each frame, size of the frame list in bytes const size_t clxDataOffset = clxData.size(); clxData.resize(clxData.size() + 4 * (2 + static_cast(numFrames))); WriteLE32(&clxData[clxDataOffset], numFrames); 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..1813992ad043 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