diff --git a/Source/dead.cpp b/Source/dead.cpp index fa3e0377e..ecdff0d5d 100644 --- a/Source/dead.cpp +++ b/Source/dead.cpp @@ -48,7 +48,7 @@ void InitCorpses() nd++; // Unused blood spatter for (auto &corpse : Corpses[nd].data) - corpse = MissileSpriteData[MFILE_SHATTER1].animData[0].get(); + corpse = MissileSpriteData[MFILE_SHATTER1].GetFirstFrame(); Corpses[nd].frame = 12; Corpses[nd].width = 128; diff --git a/Source/engine/load_file.hpp b/Source/engine/load_file.hpp index 495aa6e35..f4204a2f7 100644 --- a/Source/engine/load_file.hpp +++ b/Source/engine/load_file.hpp @@ -8,6 +8,7 @@ #include "appfat.h" #include "diablo.h" #include "engine/assets.hpp" +#include "utils/static_vector.hpp" #include "utils/stdcompat/cstddef.hpp" namespace devilution { @@ -100,4 +101,37 @@ std::unique_ptr LoadFileInMem(const char *path, std::size_t *numRead = null return buf; } +/** + * @brief Reads multiple files into a single buffer + * + * @tparam MaxFiles maximum number of files + */ +template +struct MultiFileLoader { + /** + * @param numFiles number of files to read + * @param pathFn a function that returns the path for the given index + * @param outOffsets a buffer index for the start of each file will be written here + * @return std::unique_ptr the buffer with all the files + */ + template + [[nodiscard]] std::unique_ptr operator()(size_t numFiles, PathFn &&pathFn, uint32_t *outOffsets) + { + StaticVector files; + StaticVector sizes; + size_t totalSize = 0; + for (size_t i = 0; i < numFiles; ++i) { + const size_t size = files.emplace_back(pathFn(i)).Size(); + sizes.emplace_back(static_cast(size)); + outOffsets[i] = static_cast(totalSize); + totalSize += size; + } + std::unique_ptr buf { new byte[totalSize] }; + for (size_t i = 0; i < numFiles; ++i) { + files[i].Read(&buf[outOffsets[i]], sizes[i]); + } + return buf; + } +}; + } // namespace devilution diff --git a/Source/misdat.cpp b/Source/misdat.cpp index 889e72e11..0b517fdbb 100644 --- a/Source/misdat.cpp +++ b/Source/misdat.cpp @@ -8,6 +8,7 @@ #include "engine/cel_header.hpp" #include "engine/load_file.hpp" #include "missiles.h" +#include "utils/file_name_generator.hpp" namespace devilution { @@ -140,13 +141,13 @@ MissileFileData MissileSpriteData[] = { { "Bluexfr", MFILE_BLUEXFR, 1, MissileDataFlags::None, { 0 }, { 19 }, 160, 48 }, { "Bluexbk", MFILE_BLUEXBK, 1, MissileDataFlags::None, { 0 }, { 19 }, 160, 48 }, { "Manashld", MFILE_MANASHLD, 1, MissileDataFlags::NotAnimated, { 0 }, { 1 }, 96, 16 }, - { nullptr, MFILE_BLOOD, 4, MissileDataFlags::None, { 0 }, { 15 }, 96, 16 }, - { nullptr, MFILE_BONE, 3, MissileDataFlags::None, { 2 }, { 8 }, 128, 32 }, - { nullptr, MFILE_METLHIT, 3, MissileDataFlags::None, { 2 }, { 10 }, 96, 16 }, + { {}, MFILE_BLOOD, 4, MissileDataFlags::None, { 0 }, { 15 }, 96, 16 }, + { {}, MFILE_BONE, 3, MissileDataFlags::None, { 2 }, { 8 }, 128, 32 }, + { {}, MFILE_METLHIT, 3, MissileDataFlags::None, { 2 }, { 10 }, 96, 16 }, { "Farrow", MFILE_FARROW, 16, MissileDataFlags::None, { 0 }, { 4 }, 96, 16 }, { "Doom", MFILE_DOOM, 9, MissileDataFlags::MonsterOwned, { 1 }, { 15 }, 96, 16 }, - { nullptr, MFILE_0F, 1, MissileDataFlags::MonsterOwned, { 0 }, { 0 }, 0, 0 }, - { nullptr, MFILE_BLODBUR, 2, MissileDataFlags::None, { 2 }, { 8 }, 128, 32 }, + { {}, MFILE_0F, 1, MissileDataFlags::MonsterOwned, { 0 }, { 0 }, 0, 0 }, + { {}, MFILE_BLODBUR, 2, MissileDataFlags::None, { 2 }, { 8 }, 128, 32 }, { "Newexp", MFILE_NEWEXP, 1, MissileDataFlags::None, { 1 }, { 15 }, 96, 16 }, { "Shatter1", MFILE_SHATTER1, 1, MissileDataFlags::None, { 1 }, { 12 }, 128, 32 }, { "Bigexp", MFILE_BIGEXP, 1, MissileDataFlags::None, { 0 }, { 15 }, 160, 48 }, @@ -160,11 +161,11 @@ MissileFileData MissileSpriteData[] = { { "Holy", MFILE_HOLY, 16, MissileDataFlags::None, { 1, 0 }, { 14 }, 96, 16 }, { "Holyexpl", MFILE_HOLYEXPL, 1, MissileDataFlags::None, { 0 }, { 8 }, 160, 48 }, { "Larrow", MFILE_LARROW, 16, MissileDataFlags::None, { 0 }, { 4 }, 96, 16 }, - { nullptr, MFILE_FIRARWEX, 1, MissileDataFlags::None, { 0 }, { 6 }, 64, 0 }, + { {}, MFILE_FIRARWEX, 1, MissileDataFlags::None, { 0 }, { 6 }, 64, 0 }, { "Acidbf", MFILE_ACIDBF, 16, MissileDataFlags::MonsterOwned, { 0 }, { 8 }, 96, 16 }, { "Acidspla", MFILE_ACIDSPLA, 1, MissileDataFlags::MonsterOwned, { 0 }, { 8 }, 96, 16 }, { "Acidpud", MFILE_ACIDPUD, 2, MissileDataFlags::MonsterOwned, { 0 }, { 9, 4 }, 96, 16 }, - { nullptr, MFILE_ETHRSHLD, 1, MissileDataFlags::None, { 0 }, { 1 }, 96, 16 }, + { {}, MFILE_ETHRSHLD, 1, MissileDataFlags::None, { 0 }, { 1 }, 96, 16 }, { "Firerun", MFILE_FIRERUN, 8, MissileDataFlags::None, { 1 }, { 12 }, 96, 16 }, { "Ressur1", MFILE_RESSUR1, 1, MissileDataFlags::None, { 0 }, { 16 }, 96, 16 }, { "Sklball", MFILE_SKLBALL, 9, MissileDataFlags::None, { 1 }, { 16, 16, 16, 16, 16, 16, 16, 16, 8 }, 96, 16 }, @@ -189,7 +190,7 @@ MissileFileData MissileSpriteData[] = { { "ms_blb", MFILE_BONEDEMON, 16, MissileDataFlags::MonsterOwned, { 0 }, { 15 }, 96, 8 }, { "ex_ora1", MFILE_EXORA1, 1, MissileDataFlags::MonsterOwned, { 0 }, { 13 }, 96, -12 }, { "ex_blu3", MFILE_EXBL3, 1, MissileDataFlags::MonsterOwned, { 0 }, { 7 }, 292, 114 }, - { "", MFILE_NONE, 0, MissileDataFlags::None, { }, { }, 0, 0 }, + { {}, MFILE_NONE, 0, MissileDataFlags::None, { }, { }, 0, 0 }, // clang-format on }; @@ -214,7 +215,7 @@ std::array maybeAutofill(std::initializer_list list) } // namespace -MissileFileData::MissileFileData(const char *name, uint8_t animName, uint8_t animFAmt, MissileDataFlags flags, +MissileFileData::MissileFileData(string_view name, uint8_t animName, uint8_t animFAmt, MissileDataFlags flags, std::initializer_list animDelay, std::initializer_list animLen, int16_t animWidth, int16_t animWidth2) : name(name) @@ -230,21 +231,18 @@ MissileFileData::MissileFileData(const char *name, uint8_t animName, uint8_t ani void MissileFileData::LoadGFX() { - if (animData[0] != nullptr) + if (animData != nullptr) return; - if (name == nullptr) + if (name.empty()) return; - char pszName[256]; + FileNameGenerator pathGenerator({ "Missiles\\", name }, ".CL2"); if (animFAmt == 1) { - sprintf(pszName, "Missiles\\%s.CL2", name); - animData[0] = LoadFileInMem(pszName); + animData = LoadFileInMem(pathGenerator()); + frameOffsets[0] = 0; } else { - for (unsigned i = 0; i < animFAmt; i++) { - sprintf(pszName, "Missiles\\%s%u.CL2", name, i + 1); - animData[i] = LoadFileInMem(pszName); - } + animData = MultiFileLoader<16> {}(animFAmt, pathGenerator, &frameOffsets[0]); } } diff --git a/Source/misdat.h b/Source/misdat.h index 34306fcf9..2514d6add 100644 --- a/Source/misdat.h +++ b/Source/misdat.h @@ -11,6 +11,7 @@ #include "effects.h" #include "engine.h" #include "utils/stdcompat/cstddef.hpp" +#include "utils/stdcompat/string_view.hpp" namespace devilution { @@ -134,7 +135,7 @@ enum class MissileDataFlags { }; struct MissileFileData { - const char *name; + string_view name; uint8_t animName; uint8_t animFAmt; MissileDataFlags flags; @@ -142,17 +143,28 @@ struct MissileFileData { std::array animLen = {}; int16_t animWidth; int16_t animWidth2; - std::array, 16> animData; + std::unique_ptr animData; + std::array frameOffsets; - MissileFileData(const char *name, uint8_t animName, uint8_t animFAmt, MissileDataFlags flags, + MissileFileData(string_view name, uint8_t animName, uint8_t animFAmt, MissileDataFlags flags, std::initializer_list animDelay, std::initializer_list animLen, int16_t animWidth, int16_t animWidth2); void LoadGFX(); + [[nodiscard]] const byte *GetFirstFrame() const + { + return animData.get(); + } + + [[nodiscard]] const byte *GetFrame(size_t i) const + { + return &animData[frameOffsets[i]]; + } + void FreeGFX() { - animData = {}; + animData = nullptr; } }; diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 7d90743c8..696b2ddb7 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -642,7 +642,7 @@ void SetMissAnim(Missile &missile, int animtype) missile._miAnimType = animtype; missile._miAnimFlags = MissileSpriteData[animtype].flags; - missile._miAnimData = MissileSpriteData[animtype].animData[dir].get(); + missile._miAnimData = MissileSpriteData[animtype].GetFrame(static_cast(dir)); missile._miAnimDelay = MissileSpriteData[animtype].animDelay[dir]; missile._miAnimLen = MissileSpriteData[animtype].animLen[dir]; missile._miAnimWidth = MissileSpriteData[animtype].animWidth; @@ -4164,7 +4164,7 @@ void ProcessMissiles() void missiles_process_charge() { for (auto &missile : Missiles) { - missile._miAnimData = MissileSpriteData[missile._miAnimType].animData[missile._mimfnum].get(); + missile._miAnimData = MissileSpriteData[missile._miAnimType].GetFrame(missile._mimfnum); if (missile._mitype != MIS_RHINO) continue; diff --git a/Source/scrollrt.cpp b/Source/scrollrt.cpp index afb6d2686..4e1aa2dd9 100644 --- a/Source/scrollrt.cpp +++ b/Source/scrollrt.cpp @@ -310,8 +310,7 @@ void DrawMissilePrivate(const Surface &out, const Missile &missile, Point target return; } int nCel = missile._miAnimFrame; - const auto *frameTable = reinterpret_cast(missile._miAnimData); - int frames = SDL_SwapLE32(frameTable[0]); + const int frames = LoadLE32(missile._miAnimData); if (nCel < 1 || frames > 50 || nCel > frames) { Log("Draw Missile 2: frame {} of {}, missile type=={}", nCel, frames, missile._mitype); return; @@ -458,7 +457,7 @@ void DrawPlayerIconHelper(const Surface &out, int pnum, missile_graphic_id missi position.x += CalculateWidth2(Players[pnum].AnimInfo.pCelSprite->Width()) - MissileSpriteData[missileGraphicId].animWidth2; int width = MissileSpriteData[missileGraphicId].animWidth; - byte *pCelBuff = MissileSpriteData[missileGraphicId].animData[0].get(); + const byte *pCelBuff = MissileSpriteData[missileGraphicId].GetFirstFrame(); CelSprite cel { pCelBuff, width }; diff --git a/Source/utils/file_name_generator.hpp b/Source/utils/file_name_generator.hpp new file mode 100644 index 000000000..ae90b9067 --- /dev/null +++ b/Source/utils/file_name_generator.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include + +#include + +#include "utils/stdcompat/string_view.hpp" + +namespace devilution { + +/** + * @brief Generates file names from prefixes, a suffix, and an index. + * + * @example FileNameGenerator f({"a/", "b"}, ".txt", 1); + * f() // "a/b.txt" + * f(0) // "a/b1.txt" + * f(1) // "a/b2.txt" + */ +class FileNameGenerator { +public: + FileNameGenerator(std::initializer_list prefixes, string_view suffix, unsigned min = 1) + : suffix_(suffix) + , min_(min) + , prefixEnd_(Append(buf_, prefixes)) + { + } + + const char *operator()() const + { + *Append(prefixEnd_, suffix_) = '\0'; + return buf_; + } + + const char *operator()(size_t i) const + { + *Append(fmt::format_to(prefixEnd_, "{}", static_cast(min_ + i)), suffix_) = '\0'; + return buf_; + } + +private: + static char *Append(char *buf, std::initializer_list strings) + { + for (string_view str : strings) + buf = Append(buf, str); + return buf; + } + + static char *Append(char *buf, string_view str) + { + memcpy(buf, str.data(), str.size()); + return buf + str.size(); + } + + string_view suffix_; + unsigned min_; + char *prefixEnd_; + char buf_[256]; +}; + +} // namespace devilution diff --git a/Source/utils/static_vector.hpp b/Source/utils/static_vector.hpp new file mode 100644 index 000000000..80c98d469 --- /dev/null +++ b/Source/utils/static_vector.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include + +#include "appfat.h" + +namespace devilution { + +/** + * @brief A stack-allocated vector with a fixed capacity. + * + * @tparam T element type. + * @tparam N capacity. + */ +template +class StaticVector { +public: + template + T &emplace_back(Args &&...args) // NOLINT(readability-identifier-naming) + { + assert(size_ < N); + ::new (&data_[size_]) T(std::forward(args)...); +#if __cplusplus >= 201703L + T &result = *std::launder(reinterpret_cast(&data_[size_])); +#else + T &result = *reinterpret_cast(&data_[size_]); +#endif + ++size_; + return result; + } + + const T &operator[](std::size_t pos) const + { +#if __cplusplus >= 201703L + return *std::launder(reinterpret_cast(&data_[pos])); +#else + return *reinterpret_cast(&data_[pos]); +#endif + } + + ~StaticVector() + { + for (std::size_t pos = 0; pos < size_; ++pos) { +#if __cplusplus >= 201703L + std::destroy_at(std::launder(reinterpret_cast(&data_[pos]))); +#else + reinterpret_cast(&data_[pos])->~T(); +#endif + } + } + +private: + std::aligned_storage_t data_[N]; + std::size_t size_ = 0; +}; + +} // namespace devilution