diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 945cc35b73e..9dc4bde9908 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/resource_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/resources.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 269ad096a88..e7680ec8586 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 e8957ed4eca..e691e3be166 100644 --- a/Source/engine/clx_sprite.hpp +++ b/Source/engine/clx_sprite.hpp @@ -31,6 +31,7 @@ #include #include "appfat.h" +#include "engine/resource_store.hpp" #include "utils/endian.hpp" #include "utils/intrusive_optional.hpp" @@ -114,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 { @@ -335,10 +336,25 @@ class OwnedClxSpriteListOrSheet; /** * @brief Implicitly convertible to `ClxSpriteList` and owns its data. */ -class OwnedClxSpriteList { +class OwnedClxSpriteList : public OwnedResource { 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) + : OwnedResource(name, trnName) + , data_(std::move(data)) + { + assert(data_ != nullptr); + } + + explicit OwnedClxSpriteList(std::string &&name, std::string &&trnName, std::unique_ptr &&data) + : OwnedResource(std::move(name), std::move(trnName)) + , data_(std::move(data)) + { + assert(data_ != nullptr); + } + + explicit OwnedClxSpriteList(ResourceStoreHandle &&handle, std::unique_ptr &&data) + : OwnedResource(std::move(handle)) + , data_(std::move(data)) { assert(data_ != nullptr); } @@ -346,9 +362,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(resourceName(), trnName); } [[nodiscard]] ClxSprite operator[](size_t spriteIndex) const @@ -382,21 +398,40 @@ 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 size = dataSize(); std::unique_ptr data { new uint8_t[size] }; memcpy(data.get(), data_, size); - 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 OwnedResource { 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) + : OwnedResource(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) + : OwnedResource(std::move(name), std::move(trnName)) + , data_(std::move(data)) + , num_lists_(numLists) + { + assert(data_ != nullptr); + assert(numLists > 0); + } + + explicit OwnedClxSpriteSheet(ResourceStoreHandle &&handle, std::unique_ptr &&data, uint16_t numLists) + : OwnedResource(std::move(handle)) + , data_(std::move(data)) , num_lists_(numLists) { assert(data_ != nullptr); @@ -472,6 +507,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); @@ -509,32 +546,50 @@ class OptionalOwnedClxSpriteListOrSheet; /** * @brief A CLX sprite list or a sprite sheet (list of lists). */ -class OwnedClxSpriteListOrSheet { +class OwnedClxSpriteListOrSheet : public OwnedResource { 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::unique_ptr &&data, uint16_t numLists) - : data_(std::move(data)) + explicit OwnedClxSpriteListOrSheet(std::string_view name, std::string_view trnName, std::unique_ptr &&data, uint16_t numLists) + : OwnedResource(name, trnName) + , data_(std::move(data)) , num_lists_(numLists) { } - explicit OwnedClxSpriteListOrSheet(OwnedClxSpriteSheet &&sheet) - : data_(std::move(sheet.data_)) + explicit OwnedClxSpriteListOrSheet(std::string &&name, std::string &&trnName, std::unique_ptr &&data, uint16_t numLists) + : OwnedResource(std::move(name), std::move(trnName)) + , data_(std::move(data)) + , num_lists_(numLists) + { + } + + explicit OwnedClxSpriteListOrSheet(OwnedClxSpriteSheet &&sheet) noexcept + : OwnedResource(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 + : OwnedResource(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(resourceName(), trnName); + } + [[nodiscard]] ClxSpriteList list() const & { assert(num_lists_ == 0); @@ -544,7 +599,7 @@ class OwnedClxSpriteListOrSheet { [[nodiscard]] OwnedClxSpriteList list() && { assert(num_lists_ == 0); - return OwnedClxSpriteList { std::move(data_) }; + return OwnedClxSpriteList { std::move(handle_), std::move(data_) }; } [[nodiscard]] ClxSpriteSheet sheet() const & @@ -556,7 +611,7 @@ class OwnedClxSpriteListOrSheet { [[nodiscard]] OwnedClxSpriteSheet sheet() && { assert(num_lists_ != 0); - return OwnedClxSpriteSheet { std::move(data_), num_lists_ }; + return OwnedClxSpriteSheet { std::move(handle_), std::move(data_), num_lists_ }; } [[nodiscard]] bool isSheet() const @@ -588,6 +643,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/load_cel.cpp b/Source/engine/load_cel.cpp index c9879c40bf1..3e3ab34cce2 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 a8deb6991d1..64f2ca6da33 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 95dec272b75..e0c98bba6fd 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 6326a04ae10..0a013200b79 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 48b23a8b908..f43c6523c2d 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/engine/resource_store.cpp b/Source/engine/resource_store.cpp new file mode 100644 index 00000000000..e131ad52f29 --- /dev/null +++ b/Source/engine/resource_store.cpp @@ -0,0 +1,108 @@ +#include "engine/resource_store.hpp" + +#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 { + +ResourceStore &GetResourceStore() +{ + static auto *instance = new ResourceStore(); + return *instance; +} + +std::string_view ResourceStoreEntry::name() const +{ + return std::visit([](const auto *p) { return p->resourceName(); }, ptr); +} + +size_t ResourceStoreEntry::dataSize() const +{ + return std::visit([](const auto *p) { return p->dataSize(); }, ptr); +} + +void ResourceStoreEntry::updateHandleIterator(const std::forward_list::iterator &newBefore) +{ + std::visit([newBefore](auto &p) { p->handle_.it_before_ = newBefore; }, ptr); +} + +const ResourceStoreEntry &ResourceStoreHandle::value() const { return GetResourceStore().resolveHandle(*this); } +ResourceStoreEntry &ResourceStoreHandle::value() { return GetResourceStore().resolveHandle(*this); } + +const ResourceStoreEntry *ResourceStore::get(std::string_view name, std::string_view variant) const +{ + const auto it = map_.find(name); + if (it == map_.end()) return nullptr; + for (const ResourceStoreEntry &ref : it->second) { + if (ref.variant == variant) return &ref; + } + return nullptr; +} + +const ResourceStoreEntry &ResourceStore::resolveHandle(const ResourceStoreHandle &handle) const +{ + if (!handle.isFront()) return *std::next(handle.it_before_); + return *map_.at(handle.name_).begin(); +} + +ResourceStoreEntry &ResourceStore::resolveHandle(ResourceStoreHandle &handle) +{ + if (!handle.isFront()) return *std::next(handle.it_before_); + return *map_.at(handle.name_).begin(); +} + +ResourceStoreHandle ResourceStore::registerResource(std::string_view name, ResourceStoreEntry &&ref) +{ + auto [it, inserted] = map_.try_emplace(name); + if (inserted) { + it->second.push_front(std::move(ref)); + } else { + std::forward_list &list = it->second; + ResourceStoreEntry &next = *list.begin(); + list.push_front(std::move(ref)); + next.updateHandleIterator(list.begin()); + } + + // We return the name-based handle instead without a `list.before_begin()` iterator + // `list.before_begin()` can move when the `map_` changes. + return ResourceStoreHandle { name }; +} + +void ResourceStore::unregisterResource(const ResourceStoreHandle &handle) +{ + const std::string_view name = handle.name_; + const auto it = map_.find(name); + if (it == map_.end() || it->second.empty()) { + app_fatal(StrCat("ResourceStore: Invalid unregister request for [", name, "]: list is empty")); + } + + std::forward_list &list = it->second; + + if (handle.isFront()) { + list.erase_after(list.before_begin()); + if (!list.empty()) { + // Iterators for front-of-the-list handles must be empty because + // `list.before_begin()` is unstable wrt to map changes. + list.begin()->updateHandleIterator({}); + } + } else { + list.erase_after(handle.it_before_); + auto it = handle.it_before_; + ++it; + if (it != std::forward_list::iterator {}) { + it->updateHandleIterator(handle.it_before_); + } + } + if (list.empty()) { + map_.erase(it); + } +} + +} // namespace devilution diff --git a/Source/engine/resource_store.hpp b/Source/engine/resource_store.hpp new file mode 100644 index 00000000000..38454ca2140 --- /dev/null +++ b/Source/engine/resource_store.hpp @@ -0,0 +1,194 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include "utils/string_view_hash.hpp" + +namespace devilution { + +class ResourceStore; +class OwnedClxSpriteListOrSheet; +class OwnedClxSpriteList; +class OwnedClxSpriteSheet; + +/** + * @brief An entry stored in the resource store. + * Contains the pointer to the underlying resource and variant name. + */ +struct ResourceStoreEntry { + // Resources must inherit from `OwnedResource`. + using PtrVariant = std::variant< + OwnedClxSpriteListOrSheet *, + OwnedClxSpriteList *, + OwnedClxSpriteSheet *>; + + PtrVariant ptr; + std::string variant; + + [[nodiscard]] std::string_view name() const; + [[nodiscard]] size_t dataSize() const; + +private: + void updateHandleIterator(const std::forward_list::iterator &newBefore); + + friend class ResourceStore; +}; + +/** + * @returns The global resource store. + */ +ResourceStore &GetResourceStore(); + +/** + * @brief A handle to a resource in the store. + */ +class ResourceStoreHandle { +public: + ResourceStoreHandle() = default; + + // NOLINTNEXTLINE(readability-identifier-naming): match std::optional API. + [[nodiscard]] bool has_value() const { return !name_.empty(); } + [[nodiscard]] const ResourceStoreEntry &value() const; + [[nodiscard]] ResourceStoreEntry &value(); + [[nodiscard]] std::string_view name() const { return name_; } + +private: + explicit ResourceStoreHandle(std::string_view name) + : name_(name) + { + } + + // Handles to the front of the list have no iterator and must resolved by name. + // They have no iterator because it would be unstable wrt map modifications. + [[nodiscard]] bool isFront() const + { + return it_before_ == std::forward_list::iterator {}; + } + + std::string name_; + std::forward_list::iterator it_before_; + + friend class ResourceStore; + friend class ResourceStoreEntry; +}; + +template +class OwnedResource; + +/** + * @brief Keeps track of all the currently loaded resources. + */ +class ResourceStore { +public: + using Map = ankerl::unordered_dense::segmented_map, StringViewHash, StringViewEquals>; + + [[nodiscard]] const ResourceStoreEntry *get(std::string_view name, std::string_view variant = {}) const; + + template + [[nodiscard]] ResourceStoreHandle registerResource(std::string_view name, T &resource, std::string &&variant) + { + return registerResource(name, ResourceStoreEntry { ResourceStoreEntry::PtrVariant { &resource }, std::move(variant) }); + } + + template + [[nodiscard]] ResourceStoreHandle registerResource(std::string_view name, T &resource, std::string_view variant) + { + return registerResource(name, ResourceStoreEntry { ResourceStoreEntry::PtrVariant { &resource }, std::string(variant) }); + } + + template + ResourceStoreHandle replaceResource(T &resource, ResourceStoreHandle &&handle) + { + ResourceStoreHandle result = std::move(handle); + if (result.has_value()) { + result.value().ptr = &resource; + } + handle = {}; + return result; + } + + void unregisterResource(const ResourceStoreHandle &handle); + + [[nodiscard]] const Map &getAll() const { return map_; } + +private: + [[nodiscard]] ResourceStoreHandle registerResource(std::string_view name, ResourceStoreEntry &&ref); + + // For internal use by OwnedResource + [[nodiscard]] const ResourceStoreEntry &resolveHandle(const ResourceStoreHandle &handle) const; + [[nodiscard]] ResourceStoreEntry &resolveHandle(ResourceStoreHandle &handle); + + Map map_; + + friend class OwnedResource; + friend class OwnedResource; + friend class OwnedResource; + friend class ResourceStoreHandle; +}; + +/** + * @brief Base class for the owning instance of a resource. + */ +template +class OwnedResource { +public: + ~OwnedResource() + { + if (handle_.has_value()) GetResourceStore().unregisterResource(handle_); + } + + [[nodiscard]] std::string_view resourceName() const { return handle_.name(); } + [[nodiscard]] std::string_view resourceVariant() const { return storeEntry().variant; } + + void setVariant(std::string_view variant) + { + storeEntry().variant = variant; + } + +protected: + OwnedResource(std::string_view name, std::string_view variant) + : handle_(GetResourceStore().registerResource(name, *static_cast(this), variant)) + { + } + + OwnedResource(std::string_view name, std::string &&variant) + : handle_(GetResourceStore().registerResource(name, *static_cast(this), std::move(variant))) + { + } + + explicit OwnedResource(ResourceStoreHandle &&handle) + : handle_(GetResourceStore().replaceResource(*static_cast(this), std::move(handle))) + { + } + + OwnedResource(OwnedResource &&other) noexcept + : handle_(GetResourceStore().replaceResource(*static_cast(this), std::move(other.handle_))) + { + } + + OwnedResource &operator=(OwnedResource &&other) noexcept + { + if (handle_.has_value()) GetResourceStore().unregisterResource(handle_); + handle_ = GetResourceStore().replaceResource(*static_cast(this), std::move(other.handle_)); + return *this; + } + + [[nodiscard]] const ResourceStoreEntry &storeEntry() const { return handle_.value(); } + [[nodiscard]] ResourceStoreEntry &storeEntry() { return handle_.value(); } + + // For OptionalOwned* types + OwnedResource() = default; + + ResourceStoreHandle handle_; + friend class ResourceStore; + friend class ResourceStoreEntry; +}; + +} // namespace devilution diff --git a/Source/lua/modules/dev.cpp b/Source/lua/modules/dev.cpp index e3de74633d7..c19cdb6c067 100644 --- a/Source/lua/modules/dev.cpp +++ b/Source/lua/modules/dev.cpp @@ -10,6 +10,7 @@ #include "lua/modules/dev/monsters.hpp" #include "lua/modules/dev/player.hpp" #include "lua/modules/dev/quests.hpp" +#include "lua/modules/dev/resources.hpp" #include "lua/modules/dev/search.hpp" #include "lua/modules/dev/towners.hpp" @@ -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, "resources", "", "Resource commands.", LuaDevResourcesModule(lua)); SetDocumented(table, "towners", "", "Town NPC commands.", LuaDevTownersModule(lua)); return table; } diff --git a/Source/lua/modules/dev/resources.cpp b/Source/lua/modules/dev/resources.cpp new file mode 100644 index 00000000000..a77be4c0938 --- /dev/null +++ b/Source/lua/modules/dev/resources.cpp @@ -0,0 +1,85 @@ +#ifdef _DEBUG +#include "lua/modules/dev/resources.hpp" + +#include +#include +#include + +#include + +#include "engine/clx_sprite.hpp" +#include "engine/resource_store.hpp" +#include "lua/metadoc.hpp" +#include "utils/algorithm/container.hpp" +#include "utils/format_int.hpp" +#include "utils/str_cat.hpp" + +namespace devilution { +namespace { + +struct ResourceInfo { + [[nodiscard]] std::string operator()(const ClxSpriteList &list) const + { + if (list.numSprites() == 1) return "sprite"; + return StrCat("sprite list with ", list.numSprites(), " sprites"); + } + [[nodiscard]] std::string operator()(const ClxSpriteSheet &sheet) const + { + return StrCat("sprite sheet with ", sheet.numLists(), " lists"); + } + [[nodiscard]] std::string operator()(const OwnedClxSpriteListOrSheet *ptr) const + { + return ptr->isSheet() ? (*this)(ptr->sheet()) : (*this)(ptr->list()); + } + [[nodiscard]] std::string operator()(const OwnedClxSpriteList *ptr) const + { + return (*this)(ClxSpriteList { *ptr }); + } + [[nodiscard]] std::string operator()(const OwnedClxSpriteSheet *ptr) const + { + return (*this)(ClxSpriteSheet { *ptr }); + } +}; + +std::string DebugCmdListLoadedResources() +{ + size_t count = 0; + size_t totalSize = 0; + const auto &all = GetResourceStore().getAll(); + std::vector entries; + entries.reserve(all.size()); + for (const auto &[name, sprites] : all) { + std::string &entry = entries.emplace_back(); + StrAppend(entry, name); + for (const ResourceStoreEntry &sprite : sprites) { + if (!sprite.variant.empty()) StrAppend(entry, " ", sprite.variant); + StrAppend(entry, " (", std::visit(ResourceInfo {}, sprite.ptr), ")"); + const size_t size = sprite.dataSize(); + StrAppend(entry, " ", FormatInteger(static_cast(size))); + totalSize += sprite.dataSize(); + ++count; + } + } + c_sort(entries); + std::string result; + + size_t i = 0; + for (const std::string &entry : entries) { + StrAppend(result, ++i, ". ", entry); + result += '\n'; + } + StrAppend(result, FormatInteger(static_cast(count)), " resources, total size: ", FormatInteger(static_cast(totalSize)), " bytes"); + return result; +} + +} // namespace + +sol::table LuaDevResourcesModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "listLoaded", "()", "Information about the loaded resources.", &DebugCmdListLoadedResources); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/resources.hpp b/Source/lua/modules/dev/resources.hpp new file mode 100644 index 00000000000..caba9136cbd --- /dev/null +++ b/Source/lua/modules/dev/resources.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevResourcesModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/misdat.cpp b/Source/misdat.cpp index 8fa823a9fe6..e6728ffc954 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 c2bde27e185..4ac053fb701 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->setVariant(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 44d46ed31a5..dc5d264d0b2 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 10c3e513963..5317964b856 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/console.cpp b/Source/panels/console.cpp index 3d7e414f82c..84d3143e7d2 100644 --- a/Source/panels/console.cpp +++ b/Source/panels/console.cpp @@ -45,7 +45,7 @@ constexpr std::string_view HelpText = std::optional> ConsolePrelude; bool IsConsoleVisible; -char ConsoleInputBuffer[4096]; +char ConsoleInputBuffer[16384]; TextInputCursorState ConsoleInputCursor; TextInputState ConsoleInputState { TextInputState::Options { diff --git a/Source/panels/mainpanel.cpp b/Source/panels/mainpanel.cpp index b5d6e19ca31..c73177bb5ac 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 978721baebd..6925dc251f4 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 76fdcc3aaf5..3d103fe0ca8 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 6310837619b..9fc5677abcb 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 d1c4ae7cb32..20151bb55a1 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 14a36e8208f..3596a631895 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/pcx_to_clx.cpp b/Source/utils/pcx_to_clx.cpp index cb94648cce9..0665176b8d9 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 2eca4cc3b37..73f2305d74b 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/surface_to_clx.cpp b/Source/utils/surface_to_clx.cpp index 82460554f7f..740b6aa3420 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 f0edb87b1a8..b8756c17912 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