From 359a0b889b2e109ede419dad55683f620dae4e32 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 13 Jul 2024 00:43:23 +0100 Subject: [PATCH] Global resource storage Keeps track of all the currently loaded resources (currently only sprites but can be extended to sounds). 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 | 109 +++++++++++---- 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/engine/resource_store.cpp | 108 +++++++++++++++ Source/engine/resource_store.hpp | 194 +++++++++++++++++++++++++++ Source/lua/modules/dev.cpp | 2 + Source/lua/modules/dev/resources.cpp | 85 ++++++++++++ Source/lua/modules/dev/resources.hpp | 10 ++ Source/misdat.cpp | 6 +- Source/monster.cpp | 110 +++++++-------- Source/monster.h | 12 +- Source/panels/charpanel.cpp | 2 +- Source/panels/console.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/pcx_to_clx.cpp | 5 +- Source/utils/pcx_to_clx.hpp | 3 +- Source/utils/surface_to_clx.cpp | 5 +- Source/utils/surface_to_clx.hpp | 4 +- 28 files changed, 587 insertions(+), 126 deletions(-) create mode 100644 Source/engine/resource_store.cpp create mode 100644 Source/engine/resource_store.hpp create mode 100644 Source/lua/modules/dev/resources.cpp create mode 100644 Source/lua/modules/dev/resources.hpp 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