diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 072e5aec3..2b6824d1c 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -473,6 +473,7 @@ add_devilutionx_object_library(libdevilutionx_items ) target_link_dependencies(libdevilutionx_items PUBLIC DevilutionX::SDL + sol2::sol2 tl libdevilutionx_headless_mode libdevilutionx_sound diff --git a/Source/itemdat.cpp b/Source/itemdat.cpp index 5d5fca31d..44f07e84a 100644 --- a/Source/itemdat.cpp +++ b/Source/itemdat.cpp @@ -10,10 +10,12 @@ #include #include +#include #include "data/file.hpp" #include "data/iterators.hpp" #include "data/record_reader.hpp" +#include "lua/lua_global.hpp" #include "spelldat.h" #include "utils/str_cat.hpp" @@ -25,6 +27,9 @@ std::vector AllItemsList; /** Contains the data related to each unique item ID. */ std::vector UniqueItems; +/** Contains unique item mapping IDs, with unique item indices assigned to them. This is used for loading saved games. */ +ankerl::unordered_dense::map UniqueItemMappingIdsToIndices; + /** Contains the data related to each item prefix. */ std::vector ItemPrefixes; @@ -559,14 +564,14 @@ void ReadItemPower(RecordReader &reader, std::string_view fieldName, ItemPower & reader.readOptionalInt(StrCat(fieldName, ".value2"), power.param2); } -void LoadUniqueItemDat() +} // namespace + +void LoadUniqueItemDatFromFile(DataFile &dataFile, std::string_view filename, int32_t baseMappingId) { - const std::string_view filename = "txtdata\\items\\unique_itemdat.tsv"; - DataFile dataFile = DataFile::loadOrDie(filename); dataFile.skipHeaderOrDie(filename); - UniqueItems.clear(); - UniqueItems.reserve(dataFile.numRecords()); + int32_t currentMappingId = baseMappingId; + UniqueItems.reserve(UniqueItems.size() + dataFile.numRecords()); for (DataFileRecord record : dataFile) { RecordReader reader { record, filename }; UniqueItem &item = UniqueItems.emplace_back(); @@ -583,10 +588,32 @@ void LoadUniqueItemDat() break; ReadItemPower(reader, StrCat("power", i), item.powers[item.UINumPL++]); } + + item.mappingId = currentMappingId; + const auto [it, inserted] = UniqueItemMappingIdsToIndices.emplace(item.mappingId, static_cast(UniqueItems.size()) - 1); + if (!inserted) { + DisplayFatalErrorAndExit("Adding Unique Item Failed", fmt::format("A unique item already exists for mapping ID {}.", item.mappingId)); + } + + ++currentMappingId; } UniqueItems.shrink_to_fit(); } +namespace { + +void LoadUniqueItemDat() +{ + const std::string_view filename = "txtdata\\items\\unique_itemdat.tsv"; + DataFile dataFile = DataFile::loadOrDie(filename); + + UniqueItems.clear(); + UniqueItemMappingIdsToIndices.clear(); + LoadUniqueItemDatFromFile(dataFile, filename, 0); + + LuaEvent("UniqueItemDataLoaded"); +} + void LoadItemAffixesDat(std::string_view filename, std::vector &out) { DataFile dataFile = DataFile::loadOrDie(filename); diff --git a/Source/itemdat.h b/Source/itemdat.h index ae747ae91..620548e70 100644 --- a/Source/itemdat.h +++ b/Source/itemdat.h @@ -9,11 +9,15 @@ #include #include +#include + #include "objdat.h" #include "spelldat.h" namespace devilution { +class DataFile; + /** @todo add missing values and apply */ enum _item_indexes : int16_t { // TODO defines all indexes in AllItemsList IDI_GOLD, @@ -637,13 +641,16 @@ struct UniqueItem { uint8_t UINumPL; int UIValue; ItemPower powers[6]; + int32_t mappingId; }; extern DVL_API_FOR_TEST std::vector AllItemsList; extern std::vector ItemPrefixes; extern std::vector ItemSuffixes; extern DVL_API_FOR_TEST std::vector UniqueItems; +extern ankerl::unordered_dense::map UniqueItemMappingIdsToIndices; +void LoadUniqueItemDatFromFile(DataFile &dataFile, std::string_view filename, int32_t baseMappingId); void LoadItemData(); } // namespace devilution diff --git a/Source/items.h b/Source/items.h index 82202f351..70bf4e0e0 100644 --- a/Source/items.h +++ b/Source/items.h @@ -43,7 +43,7 @@ enum item_quality : uint8_t { ITEM_QUALITY_UNIQUE, }; -enum _unique_items : int8_t { +enum _unique_items : int32_t { UITEM_CLEAVER, UITEM_SKCROWN, UITEM_INFRARING, diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 5a410752d..9092e7b27 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -263,7 +263,7 @@ struct LevelConversionData { MonsterConversionData monsterConversionData[MaxMonsters]; }; -void LoadItemData(LoadHelper &file, Item &item) +[[nodiscard]] bool LoadItemData(LoadHelper &file, Item &item) { item._iSeed = file.NextLE(); item._iCreateInfo = file.NextLE(); @@ -321,7 +321,20 @@ void LoadItemData(LoadHelper &file, Item &item) item._iSplLvlAdd = file.NextLE(); item._iRequest = file.NextBool8(); file.Skip(2); // Alignment - item._iUid = file.NextLE(); + + const int32_t uniqueMappingId = file.NextLE(); + if (item._iMagical == ITEM_QUALITY_UNIQUE) { + const auto findIt = UniqueItemMappingIdsToIndices.find(uniqueMappingId); + if (findIt == UniqueItemMappingIdsToIndices.end()) { + return false; + } + + const int uniqueIndex = findIt->second; + item._iUid = uniqueIndex; + } else { + item._iUid = 0; + } + item._iFMinDam = file.NextLE(); item._iFMaxDam = file.NextLE(); item._iLMinDam = file.NextLE(); @@ -352,11 +365,18 @@ void LoadItemData(LoadHelper &file, Item &item) else item._iDamAcFlags = ItemSpecialEffectHf::None; UpdateHellfireFlag(item, item._iIName); + + return true; } void LoadAndValidateItemData(LoadHelper &file, Item &item) { - LoadItemData(file, item); + const bool success = LoadItemData(file, item); + if (!success) { + item.clear(); + return; + } + RemoveInvalidItem(item); } @@ -1052,7 +1072,11 @@ void LoadMatchingItems(LoadHelper &file, const Player &player, const int n, Item for (int i = 0; i < n; i++) { Item &unpackedItem = pItem[i]; - LoadItemData(file, heroItem); + const bool success = LoadItemData(file, heroItem); + if (!success) { + heroItem.clear(); + unpackedItem = Item(); + } if (unpackedItem.isEmpty() || heroItem.isEmpty()) continue; if (unpackedItem._iSeed != heroItem._iSeed) @@ -1190,7 +1214,7 @@ void SaveItem(SaveHelper &file, const Item &item) file.WriteLE(item._iSplLvlAdd); file.WriteLE(item._iRequest ? 1 : 0); file.Skip(2); // Alignment - file.WriteLE(item._iUid); + file.WriteLE(UniqueItems[item._iUid].mappingId); file.WriteLE(item._iFMinDam); file.WriteLE(item._iFMaxDam); file.WriteLE(item._iLMinDam); diff --git a/Source/lua/modules/items.cpp b/Source/lua/modules/items.cpp index a2e34acb4..999e3b6c8 100644 --- a/Source/lua/modules/items.cpp +++ b/Source/lua/modules/items.cpp @@ -2,8 +2,11 @@ #include +#include #include +#include "data/file.hpp" +#include "itemdat.h" #include "items.h" #include "lua/metadoc.hpp" #include "player.h" @@ -451,6 +454,12 @@ void RegisterItemSpecialEffectHfEnum(sol::state_view &lua) }); } +void AddUniqueItemDataFromTsv(const std::string_view path, const int32_t baseMappingId) +{ + DataFile dataFile = DataFile::loadOrDie(path); + LoadUniqueItemDatFromFile(dataFile, path, baseMappingId); +} + } // namespace sol::table LuaItemModule(sol::state_view &lua) @@ -468,6 +477,8 @@ sol::table LuaItemModule(sol::state_view &lua) sol::table table = lua.create_table(); + LuaSetDocFn(table, "addUniqueItemDataFromTsv", "(path: string, baseMappingId: number)", AddUniqueItemDataFromTsv); + return table; } diff --git a/assets/lua/devilutionx/events.lua b/assets/lua/devilutionx/events.lua index 4df5a9787..8bf4afa35 100644 --- a/assets/lua/devilutionx/events.lua +++ b/assets/lua/devilutionx/events.lua @@ -44,6 +44,10 @@ local events = { LoadModsComplete = CreateEvent(), __doc_LoadModsComplete = "Called after all mods have been loaded.", + ---Called after the unique item data TSV file has been loaded. + UniqueItemDataLoaded = CreateEvent(), + __doc_UniqueItemDataLoaded = "Called after the unique item data TSV file has been loaded.", + ---Called after the monster data TSV file has been loaded. MonsterDataLoaded = CreateEvent(), __doc_MonsterDataLoaded = "Called after the monster data TSV file has been loaded.",