diff --git a/Packaging/resources/assets/txtdata/items/item_prefixes.tsv b/Packaging/resources/assets/txtdata/items/item_prefixes.tsv index ff47a50a8..fbc7cb4ce 100644 --- a/Packaging/resources/assets/txtdata/items/item_prefixes.tsv +++ b/Packaging/resources/assets/txtdata/items/item_prefixes.tsv @@ -1,4 +1,4 @@ -name power.type power.param1 power.param2 minLevel itemTypes alignment doubleChance useful minVal maxVal multVal +name power power.value1 power.value2 minLevel itemTypes alignment doubleChance useful minVal maxVal multVal Tin TOHIT_CURSE 6 10 3 Weapon,Bow,Misc Any true false 0 0 -3 Brass TOHIT_CURSE 1 5 1 Weapon,Bow,Misc Any true false 0 0 -2 Bronze TOHIT 1 5 1 Weapon,Bow,Misc Any true true 100 500 2 diff --git a/Packaging/resources/assets/txtdata/items/item_suffixes.tsv b/Packaging/resources/assets/txtdata/items/item_suffixes.tsv index 00d2540c2..16f905dea 100644 --- a/Packaging/resources/assets/txtdata/items/item_suffixes.tsv +++ b/Packaging/resources/assets/txtdata/items/item_suffixes.tsv @@ -1,4 +1,4 @@ -name power.type power.param1 power.param2 minLevel itemTypes alignment doubleChance useful minVal maxVal multVal +name power power.value1 power.value2 minLevel itemTypes alignment doubleChance useful minVal maxVal multVal quality DAMMOD 1 2 2 Weapon,Staff,Bow Any false true 100 200 2 maiming DAMMOD 3 5 7 Weapon,Staff,Bow Any false true 1300 1500 3 slaying DAMMOD 6 8 15 Weapon Any false true 2600 3000 5 diff --git a/Packaging/resources/assets/txtdata/items/unique_itemdat.tsv b/Packaging/resources/assets/txtdata/items/unique_itemdat.tsv index 45965f88e..84c1c8070 100644 --- a/Packaging/resources/assets/txtdata/items/unique_itemdat.tsv +++ b/Packaging/resources/assets/txtdata/items/unique_itemdat.tsv @@ -1,4 +1,4 @@ -name uniqueBaseItem minLevel value powers[0].type powers[0].param1 powers[0].param2 powers[1].type powers[1].param1 powers[1].param2 powers[2].type powers[2].param1 powers[2].param2 powers[3].type powers[3].param1 powers[3].param2 powers[4].type powers[4].param1 powers[4].param2 powers[5].type powers[5].param1 powers[5].param2 +name uniqueBaseItem minLevel value power0 power0.value1 power0.value2 power1 power1.value1 power1.value2 power2 power2.value1 power2.value2 power3 power3.value1 power3.value2 power4 power4.value1 power4.value2 power5 power5.value1 power5.value2 The Butcher's Cleaver CLEAVER 1 3650 STR 10 10 SETDAM 4 24 SETDUR 10 10 The Undead Crown SKCROWN 1 16650 RNDSTEALLIFE SETAC 8 8 INVCURS 77 Empyrean Band INFRARING 1 8000 ATTRIBS 2 2 LIGHT 2 2 FASTRECOVER 1 1 ABSHALFTRAP diff --git a/Source/.clang-tidy b/Source/.clang-tidy index 6a93dcc0d..13bc05437 100644 --- a/Source/.clang-tidy +++ b/Source/.clang-tidy @@ -53,7 +53,8 @@ Checks: > -modernize-avoid-c-arrays, -modernize-use-trailing-return-type, -modernize-concat-nested-namespaces, - -modernize-avoid-bind + -modernize-avoid-bind, + -modernize-use-constraints HeaderFilterRegex: "^(Source|test)\\.h$" diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index a1d62bfe1..0af332af5 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -70,6 +70,7 @@ set(libdevilutionx_SRCS data/file.cpp data/parser.cpp + data/record_reader.cpp DiabloUI/button.cpp DiabloUI/credits.cpp diff --git a/Source/data/iterators.hpp b/Source/data/iterators.hpp index b76145e64..32f608314 100644 --- a/Source/data/iterators.hpp +++ b/Source/data/iterators.hpp @@ -146,7 +146,7 @@ public: } template - [[nodiscard]] tl::expected parseEnumList(T &destination, ParseFn &&parseFn) + [[nodiscard]] tl::expected parseEnumList(T &destination, ParseFn &&parseFn) { destination = {}; const std::string_view str = value(); @@ -155,7 +155,7 @@ public: for (const std::string_view part : SplitByChar(str, ',')) { auto result = parseFn(part); if (!result.has_value()) - return tl::make_unexpected(Error::InvalidValue); + return tl::make_unexpected(std::move(result).error()); destination |= result.value(); } return {}; diff --git a/Source/data/record_reader.cpp b/Source/data/record_reader.cpp new file mode 100644 index 000000000..6b6ef6960 --- /dev/null +++ b/Source/data/record_reader.cpp @@ -0,0 +1,17 @@ +#include "data/record_reader.hpp" + +namespace devilution { + +void RecordReader::advance() +{ + if (needsIncrement_) { + ++it_; + } else { + needsIncrement_ = true; + } + if (it_ == end_) { + DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename_); + } +} + +} // namespace devilution diff --git a/Source/data/record_reader.hpp b/Source/data/record_reader.hpp new file mode 100644 index 000000000..e65079a47 --- /dev/null +++ b/Source/data/record_reader.hpp @@ -0,0 +1,123 @@ +#pragma once + +#include +#include + +#include +#include + +#include "data/file.hpp" +#include "data/iterators.hpp" + +namespace devilution { + +/** + * @brief A record reader that treats every error as fatal. + */ +class RecordReader { +public: + RecordReader(DataFileRecord &record, std::string_view filename) + : it_(record.begin()) + , end_(record.end()) + , filename_(filename) + { + } + + template + typename std::enable_if_t, void> + readInt(std::string_view name, T &out) + { + advance(); + DataFileField field = *it_; + if (tl::expected result = field.parseInt(out); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename_, name, field); + } + } + + template + typename std::enable_if_t, void> + readOptionalInt(std::string_view name, T &out) + { + advance(); + DataFileField field = *it_; + if (field.value().empty()) return; + if (tl::expected result = field.parseInt(out); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename_, name, field); + } + } + + template + void readIntArray(std::string_view name, T (&out)[N]) + { + advance(); + DataFileField field = *it_; + if (tl::expected result = field.parseIntArray(out); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename_, name, field); + } + } + + template + typename std::enable_if_t, void> + readFixed6(std::string_view name, T &out) + { + advance(); + DataFileField field = *it_; + if (tl::expected result = field.parseFixed6(out); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename_, name, field); + } + } + + void readBool(std::string_view name, bool &out) + { + advance(); + DataFileField field = *it_; + if (tl::expected result = field.parseBool(out); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename_, name, field); + } + } + + void readString(std::string_view name, std::string &out) + { + advance(); + out = (*it_).value(); + } + + template + void read(std::string_view name, T &out, F &&parseFn) + { + advance(); + DataFileField field = *it_; + if (tl::expected result = parseFn(field.value()); result.has_value()) { + out = *std::move(result); + } else { + DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename_, name, field, result.error()); + } + } + + template + void readEnumList(std::string_view name, T &out, F &&parseFn) + { + advance(); + DataFileField field = *it_; + if (tl::expected result = field.parseEnumList(out, std::forward(parseFn)); !result.has_value()) { + DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename_, name, field, result.error()); + } + } + + std::string_view value() + { + advance(); + needsIncrement_ = false; + return (*it_).value(); + } + + void advance(); + +private: + FieldIterator it_; + const FieldIterator end_; + std::string_view filename_; + bool needsIncrement_ = false; +}; + +} // namespace devilution diff --git a/Source/itemdat.cpp b/Source/itemdat.cpp index 029ee6a28..70fdb4141 100644 --- a/Source/itemdat.cpp +++ b/Source/itemdat.cpp @@ -13,6 +13,7 @@ #include "data/file.hpp" #include "data/iterators.hpp" +#include "data/record_reader.hpp" #include "spelldat.h" #include "utils/str_cat.hpp" @@ -491,200 +492,40 @@ void LoadItemDat() AllItemsList.clear(); AllItemsList.reserve(dataFile.numRecords()); for (DataFileRecord record : dataFile) { - FieldIterator fieldIt = record.begin(); - const FieldIterator endField = record.end(); - - AllItemsList.emplace_back(); - ItemData &item = AllItemsList.back(); - - const auto advance = [&]() { - ++fieldIt; - if (fieldIt == endField) { - DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename); - } - }; - - // Skip the first column (item ID). - - // dropRate - advance(); - if (tl::expected result = ParseItemDropRate((*fieldIt).value()); result.has_value()) { - item.iRnd = *std::move(result); - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "dropRate", *fieldIt, result.error()); - } - - // class - advance(); - if (tl::expected result = ParseItemClass((*fieldIt).value()); result.has_value()) { - item.iClass = *std::move(result); - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "class", *fieldIt, result.error()); - } - - // equipType - advance(); - if (tl::expected result = ParseItemEquipType((*fieldIt).value()); result.has_value()) { - item.iLoc = *std::move(result); - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "equipType", *fieldIt, result.error()); - } - - // cursorGraphic - advance(); - if (tl::expected result = ParseItemCursorGraphic((*fieldIt).value()); result.has_value()) { - item.iCurs = *std::move(result); - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "cursorGraphic", *fieldIt, result.error()); - } - - // itemType - advance(); - if (tl::expected result = ParseItemType((*fieldIt).value()); result.has_value()) { - item.itype = *std::move(result); - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "itemType", *fieldIt, result.error()); - } - - // uniqueBaseItem - advance(); - if (tl::expected result = ParseUniqueBaseItem((*fieldIt).value()); result.has_value()) { - item.iItemId = *std::move(result); - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "uniqueBaseItem", *fieldIt, result.error()); - } - - // name - advance(); - item.iName = (*fieldIt).value(); - - // shortName - advance(); - item.iSName = (*fieldIt).value(); - - // minMonsterLevel - advance(); - if (tl::expected result = (*fieldIt).parseInt(item.iMinMLvl); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "minMonsterLevel", *fieldIt); - } - - // durability - advance(); - if (tl::expected result = (*fieldIt).parseInt(item.iDurability); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "durability", *fieldIt); - } - - // minDamage - advance(); - if (tl::expected result = (*fieldIt).parseInt(item.iMinDam); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "minDamage", *fieldIt); - } - - // maxDamage - advance(); - if (tl::expected result = (*fieldIt).parseInt(item.iMaxDam); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "maxDamage", *fieldIt); - } - - // minArmor - advance(); - if (tl::expected result = (*fieldIt).parseInt(item.iMinAC); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "minArmor", *fieldIt); - } - - // maxArmor - advance(); - if (tl::expected result = (*fieldIt).parseInt(item.iMaxAC); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "maxArmor", *fieldIt); - } - - // minStrength - advance(); - if (tl::expected result = (*fieldIt).parseInt(item.iMinStr); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "minStrength", *fieldIt); - } - - // minMagic - advance(); - if (tl::expected result = (*fieldIt).parseInt(item.iMinMag); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "minMagic", *fieldIt); - } - - // minDexterity - advance(); - if (tl::expected result = (*fieldIt).parseInt(item.iMinDex); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "minDexterity", *fieldIt); - } - - // specialEffects - advance(); - if (tl::expected result = (*fieldIt).parseEnumList(item.iFlags, ParseItemSpecialEffect); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "specialEffects", *fieldIt); - } - - // miscId - advance(); - if (tl::expected result = ParseItemMiscId((*fieldIt).value()); result.has_value()) { - item.iMiscId = *std::move(result); - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "miscId", *fieldIt, result.error()); - } - - // spell - advance(); - if (tl::expected result = ParseSpellId((*fieldIt).value()); result.has_value()) { - item.iSpell = *std::move(result); - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "spell", *fieldIt, result.error()); - } - - // usable - advance(); - if (tl::expected result = (*fieldIt).parseBool(item.iUsable); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "usable", *fieldIt); - } - - // value - advance(); - if (tl::expected result = (*fieldIt).parseInt(item.iValue); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "value", *fieldIt); - } + RecordReader reader { record, filename }; + ItemData &item = AllItemsList.emplace_back(); + reader.advance(); // Skip the first column (item ID). + reader.read("dropRate", item.iRnd, ParseItemDropRate); + reader.read("class", item.iClass, ParseItemClass); + reader.read("equipType", item.iLoc, ParseItemEquipType); + reader.read("cursorGraphic", item.iCurs, ParseItemCursorGraphic); + reader.read("itemType", item.itype, ParseItemType); + reader.read("uniqueBaseItem", item.iItemId, ParseUniqueBaseItem); + reader.readString("name", item.iName); + reader.readString("shortName", item.iSName); + reader.readInt("minMonsterLevel", item.iMinMLvl); + reader.readInt("durability", item.iDurability); + reader.readInt("minDamage", item.iMinDam); + reader.readInt("maxDamage", item.iMaxDam); + reader.readInt("minArmor", item.iMinAC); + reader.readInt("maxArmor", item.iMaxAC); + reader.readInt("minStrength", item.iMinStr); + reader.readInt("minMagic", item.iMinMag); + reader.readInt("minDexterity", item.iMinDex); + reader.readEnumList("specialEffects", item.iFlags, ParseItemSpecialEffect); + reader.read("miscId", item.iMiscId, ParseItemMiscId); + reader.read("spell", item.iSpell, ParseSpellId); + reader.readBool("usable", item.iUsable); + reader.readInt("value", item.iValue); } - AllItemsList.shrink_to_fit(); } -void ParseItemPower(devilution::FieldIterator &fieldIt, const FieldIterator &endField, - std::string_view filename, std::string_view fieldName, ItemPower &power) +void ReadItemPower(RecordReader &reader, std::string_view fieldName, ItemPower &power) { - const auto advance = [&]() { - ++fieldIt; - if (fieldIt == endField) { - DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename); - } - }; - - if (tl::expected result = ParseItemEffectType((*fieldIt).value()); result.has_value()) { - power.type = *std::move(result); - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, StrCat(fieldName, ".type"), *fieldIt, result.error()); - } - - // param1 - advance(); - if (!(*fieldIt).value().empty()) { - if (tl::expected result = (*fieldIt).parseInt(power.param1); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, StrCat(fieldName, ".param1"), *fieldIt); - } - } - - // param2 - advance(); - if (!(*fieldIt).value().empty()) { - if (tl::expected result = (*fieldIt).parseInt(power.param2); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, StrCat(fieldName, ".param2"), *fieldIt); - } - } + reader.read(fieldName, power.type, ParseItemEffectType); + reader.readOptionalInt(StrCat(fieldName, ".value1"), power.param1); + reader.readOptionalInt(StrCat(fieldName, ".value2"), power.param2); } void LoadUniqueItemDat() @@ -704,53 +545,21 @@ void LoadUniqueItemDat() UniqueItems.clear(); UniqueItems.reserve(dataFile.numRecords()); for (DataFileRecord record : dataFile) { - FieldIterator fieldIt = record.begin(); - const FieldIterator endField = record.end(); - - UniqueItems.emplace_back(); - UniqueItem &item = UniqueItems.back(); - - const auto advance = [&]() { - ++fieldIt; - if (fieldIt == endField) { - DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename); - } - }; - - // name - item.UIName = (*fieldIt).value(); - - // uniqueBaseItem - advance(); - if (tl::expected result = ParseUniqueBaseItem((*fieldIt).value()); result.has_value()) { - item.UIItemId = *std::move(result); - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "class", *fieldIt, result.error()); - } - - // minLevel - advance(); - if (tl::expected result = (*fieldIt).parseInt(item.UIMinLvl); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "minLevel", *fieldIt); - } - - // value - advance(); - if (tl::expected result = (*fieldIt).parseInt(item.UIValue); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "value", *fieldIt); - } + RecordReader reader { record, filename }; + UniqueItem &item = UniqueItems.emplace_back(); + reader.readString("name", item.UIName); + reader.read("uniqueBaseItem", item.UIItemId, ParseUniqueBaseItem); + reader.readInt("minLevel", item.UIMinLvl); + reader.readInt("value", item.UIValue); // powers (up to 6) item.UINumPL = 0; for (size_t i = 0; i < 6; ++i) { - // type - advance(); - if ((*fieldIt).value().empty()) - continue; - ParseItemPower(fieldIt, endField, filename, StrCat("powers[", i, "]"), item.powers[item.UINumPL++]); + if (reader.value().empty()) + break; + ReadItemPower(reader, StrCat("power", i), item.powers[item.UINumPL++]); } } - UniqueItems.shrink_to_fit(); } @@ -770,77 +579,19 @@ void LoadItemAffixesDat(std::string_view filename, std::vector &out) out.clear(); out.reserve(dataFile.numRecords()); for (DataFileRecord record : dataFile) { - FieldIterator fieldIt = record.begin(); - const FieldIterator endField = record.end(); - - out.emplace_back(); - PLStruct &item = out.back(); - - const auto advance = [&]() { - ++fieldIt; - if (fieldIt == endField) { - DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename); - } - }; - - // name - item.PLName = (*fieldIt).value(); - - // power - advance(); - ParseItemPower(fieldIt, endField, filename, "power", item.power); - - // minLevel - advance(); - if (tl::expected result = (*fieldIt).parseInt(item.PLMinLvl); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "minLevel", *fieldIt); - } - - // itemTypes - advance(); - if (tl::expected result = (*fieldIt).parseEnumList(item.PLIType, ParseAffixItemType); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "itemTypes", *fieldIt); - } - - // alignment - advance(); - if (tl::expected result = ParseAffixAlignment((*fieldIt).value()); result.has_value()) { - item.PLGOE = *std::move(result); - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "alignment", *fieldIt, result.error()); - } - - // doubleChance - advance(); - if (tl::expected result = (*fieldIt).parseBool(item.PLDouble); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "doubleChance", *fieldIt); - } - - // useful - advance(); - if (tl::expected result = (*fieldIt).parseBool(item.PLOk); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "useful", *fieldIt); - } - - // minVal - advance(); - if (tl::expected result = (*fieldIt).parseInt(item.minVal); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "minVal", *fieldIt); - } - - // maxVal - advance(); - if (tl::expected result = (*fieldIt).parseInt(item.maxVal); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "maxVal", *fieldIt); - } - - // multVal - advance(); - if (tl::expected result = (*fieldIt).parseInt(item.multVal); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "multVal", *fieldIt); - } + RecordReader reader { record, filename }; + PLStruct &item = out.emplace_back(); + reader.readString("name", item.PLName); + ReadItemPower(reader, "power", item.power); + reader.readInt("minLevel", item.PLMinLvl); + reader.readEnumList("itemTypes", item.PLIType, ParseAffixItemType); + reader.read("alignment", item.PLGOE, ParseAffixAlignment); + reader.readBool("doubleChance", item.PLDouble); + reader.readBool("useful", item.PLOk); + reader.readInt("minVal", item.minVal); + reader.readInt("maxVal", item.maxVal); + reader.readInt("multVal", item.multVal); } - out.shrink_to_fit(); } diff --git a/Source/monstdat.cpp b/Source/monstdat.cpp index 45e9b9a24..6e165bb2f 100644 --- a/Source/monstdat.cpp +++ b/Source/monstdat.cpp @@ -11,6 +11,7 @@ #include #include "data/file.hpp" +#include "data/record_reader.hpp" #include "items.h" #include "monster.h" #include "textdat.h" @@ -484,242 +485,61 @@ void LoadMonstDat() MonstersData.reserve(dataFile.numRecords()); std::unordered_map spritePathToId; for (DataFileRecord record : dataFile) { - FieldIterator fieldIt = record.begin(); - const FieldIterator endField = record.end(); - - MonstersData.emplace_back(); - MonsterData &monster = MonstersData.back(); - - const auto advance = [&]() { - ++fieldIt; - if (fieldIt == endField) { - DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename); - } - }; - - // Skip the first column (monster ID). - - // name - advance(); - monster.name = (*fieldIt).value(); - - // assetsSuffix - advance(); + RecordReader reader { record, filename }; + MonsterData &monster = MonstersData.emplace_back(); + reader.advance(); // Skip the first column (monster ID). + reader.readString("name", monster.name); { - std::string assetsSuffix { (*fieldIt).value() }; + std::string assetsSuffix; + reader.readString("assetsSuffix", assetsSuffix); const auto [it, inserted] = spritePathToId.emplace(assetsSuffix, spritePathToId.size()); if (inserted) MonsterSpritePaths.push_back(it->first); monster.spriteId = it->second; } - - // soundSuffix - advance(); - monster.soundSuffix = (*fieldIt).value(); - - // trnFile - advance(); - monster.trnFile = (*fieldIt).value(); - - // availability - advance(); - if (tl::expected result = ParseMonsterAvailability((*fieldIt).value()); result.has_value()) { - monster.availability = *std::move(result); - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "availability", *fieldIt, result.error()); - } - - // width - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.width); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "width", *fieldIt); - } - - // image - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.image); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "image", *fieldIt); - } - - // hasSpecial - advance(); - if (tl::expected result = (*fieldIt).parseBool(monster.hasSpecial); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "hasSpecial", *fieldIt); - } - - // hasSpecialSound - advance(); - if (tl::expected result = (*fieldIt).parseBool(monster.hasSpecialSound); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "hasSpecialSound", *fieldIt); - } - - // frames[6] - advance(); - if (tl::expected result = (*fieldIt).parseIntArray(monster.frames); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "frames", *fieldIt); - } - - // rate[6] - advance(); - if (tl::expected result = (*fieldIt).parseIntArray(monster.rate); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "rate", *fieldIt); - } - - // minDunLvl - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.minDunLvl); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "minDunLvl", *fieldIt); - } - - // maxDunLvl - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.maxDunLvl); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "maxDunLvl", *fieldIt); - } - - // level - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.level); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "level", *fieldIt); - } - - // hitPointsMinimum - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.hitPointsMinimum); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "hitPointsMinimum", *fieldIt); - } - - // hitPointsMaximum - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.hitPointsMaximum); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "hitPointsMaximum", *fieldIt); - } - - // ai - advance(); - if (tl::expected result = ParseAiId((*fieldIt).value()); result.has_value()) { - monster.ai = *std::move(result); - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "ai", *fieldIt, result.error()); - } - - // abilityFlags - advance(); - if (tl::expected result = (*fieldIt).parseEnumList(monster.abilityFlags, ParseMonsterFlag); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "abilityFlags", *fieldIt); - } - - // intelligence - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.intelligence); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "intelligence", *fieldIt); - } - - // toHit - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.toHit); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "toHit", *fieldIt); - } - - // animFrameNum - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.animFrameNum); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "animFrameNum", *fieldIt); - } - - // minDamage - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.minDamage); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "minDamage", *fieldIt); - } - - // maxDamage - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.maxDamage); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "maxDamage", *fieldIt); - } - - // toHitSpecial - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.toHitSpecial); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "toHitSpecial", *fieldIt); - } - - // animFrameNumSpecial - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.animFrameNumSpecial); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "animFrameNumSpecial", *fieldIt); - } - - // minDamageSpecial - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.minDamageSpecial); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "minDamageSpecial", *fieldIt); - } - - // maxDamageSpecial - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.maxDamageSpecial); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "maxDamageSpecial", *fieldIt); - } - - // armorClass - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.armorClass); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "armorClass", *fieldIt); - } - - // monsterClass - advance(); - if (tl::expected result = ParseMonsterClass((*fieldIt).value()); result.has_value()) { - monster.monsterClass = *std::move(result); - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "monsterClass", *fieldIt, result.error()); - } - - // resistance - advance(); - if (tl::expected result = (*fieldIt).parseEnumList(monster.resistance, ParseMonsterResistance); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "resistance", *fieldIt); - } - - // resistanceHell - advance(); - if (tl::expected result = (*fieldIt).parseEnumList(monster.resistanceHell, ParseMonsterResistance); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "resistanceHell", *fieldIt); - } - - // selectionType - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.selectionType); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "selectionType", *fieldIt); - } + reader.readString("soundSuffix", monster.soundSuffix); + reader.readString("trnFile", monster.trnFile); + reader.read("availability", monster.availability, ParseMonsterAvailability); + reader.readInt("width", monster.width); + reader.readInt("image", monster.image); + reader.readBool("hasSpecial", monster.hasSpecial); + reader.readBool("hasSpecialSound", monster.hasSpecialSound); + reader.readIntArray("frames", monster.frames); + reader.readIntArray("rate", monster.rate); + reader.readInt("minDunLvl", monster.minDunLvl); + reader.readInt("maxDunLvl", monster.maxDunLvl); + reader.readInt("level", monster.level); + reader.readInt("hitPointsMinimum", monster.hitPointsMinimum); + reader.readInt("hitPointsMaximum", monster.hitPointsMaximum); + reader.read("ai", monster.ai, ParseAiId); + reader.readEnumList("abilityFlags", monster.abilityFlags, ParseMonsterFlag); + reader.readInt("intelligence", monster.intelligence); + reader.readInt("toHit", monster.toHit); + reader.readInt("animFrameNum", monster.animFrameNum); + reader.readInt("minDamage", monster.minDamage); + reader.readInt("maxDamage", monster.maxDamage); + reader.readInt("toHitSpecial", monster.toHitSpecial); + reader.readInt("animFrameNumSpecial", monster.animFrameNumSpecial); + reader.readInt("minDamageSpecial", monster.minDamageSpecial); + reader.readInt("maxDamageSpecial", monster.maxDamageSpecial); + reader.readInt("armorClass", monster.armorClass); + reader.read("monsterClass", monster.monsterClass, ParseMonsterClass); + reader.readEnumList("resistance", monster.resistance, ParseMonsterResistance); + reader.readEnumList("resistanceHell", monster.resistanceHell, ParseMonsterResistance); + reader.readInt("selectionType", monster.selectionType); // treasure // TODO: Replace this hack with proper parsing once items have been migrated to data files. - advance(); - { - const std::string_view value = (*fieldIt).value(); - if (value.empty()) { - monster.treasure = 0; - } else if (value == "None") { - monster.treasure = T_NODROP; - } else if (value == "Uniq(SKCROWN)") { - monster.treasure = Uniq(UITEM_SKCROWN); - } else if (value == "Uniq(CLEAVER)") { - monster.treasure = Uniq(UITEM_CLEAVER); - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "treasure", *fieldIt, "NOTE: Parser is incomplete"); - } - } - - // exp - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.exp); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "exp", *fieldIt); - } + reader.read("treasure", monster.treasure, [](std::string_view value) -> tl::expected { + if (value.empty()) return 0; + if (value == "None") return T_NODROP; + if (value == "Uniq(SKCROWN)") return Uniq(UITEM_SKCROWN); + if (value == "Uniq(CLEAVER)") return Uniq(UITEM_CLEAVER); + return tl::make_unexpected("Invalid value. NOTE: Parser is incomplete"); + }); + + reader.readInt("exp", monster.exp); } - MonstersData.shrink_to_fit(); } @@ -740,123 +560,35 @@ void LoadUniqueMonstDat() UniqueMonstersData.clear(); UniqueMonstersData.reserve(dataFile.numRecords()); for (DataFileRecord record : dataFile) { - FieldIterator fieldIt = record.begin(); - const FieldIterator endField = record.end(); - - UniqueMonstersData.emplace_back(); - UniqueMonsterData &monster = UniqueMonstersData.back(); - - const auto advance = [&]() { - ++fieldIt; - if (fieldIt == endField) { - DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename); - } - }; - - // type - if (tl::expected<_monster_id, std::string> result = ParseMonsterId((*fieldIt).value()); result.has_value()) { - monster.mtype = *std::move(result); - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "type", *fieldIt, result.error()); - } - - // name - advance(); - monster.mName = (*fieldIt).value(); - - // trn - advance(); - monster.mTrnName = (*fieldIt).value(); - - // level - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.mlevel); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "level", *fieldIt); - } - - // maxHp - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.mmaxhp); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "maxHp", *fieldIt); - } - - // ai - advance(); - if (tl::expected result = ParseAiId((*fieldIt).value()); result.has_value()) { - monster.mAi = *std::move(result); - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "ai", *fieldIt, result.error()); - } - - // intelligence - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.mint); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "intelligence", *fieldIt); - } - - // minDamage - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.mMinDamage); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "minDamage", *fieldIt); - } - - // maxDamage - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.mMaxDamage); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "maxDamage", *fieldIt); - } - - // resistance - advance(); - if (tl::expected result = (*fieldIt).parseEnumList(monster.mMagicRes, ParseMonsterResistance); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "resistance", *fieldIt); - } - - // monsterPack - advance(); - if (tl::expected result = ParseUniqueMonsterPack((*fieldIt).value()); result.has_value()) { - monster.monsterPack = *std::move(result); - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "monsterPack", *fieldIt, result.error()); - } - - // customToHit - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.customToHit); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "customToHit", *fieldIt); - } - - // customArmorClass - advance(); - if (tl::expected result = (*fieldIt).parseInt(monster.customArmorClass); !result.has_value()) { - DataFile::reportFatalFieldError(result.error(), filename, "customArmorClass", *fieldIt); - } + RecordReader reader { record, filename }; + UniqueMonsterData &monster = UniqueMonstersData.emplace_back(); + reader.read("type", monster.mtype, ParseMonsterId); + reader.readString("name", monster.mName); + reader.readString("trn", monster.mTrnName); + reader.readInt("level", monster.mlevel); + reader.readInt("maxHp", monster.mmaxhp); + reader.read("ai", monster.mAi, ParseAiId); + reader.readInt("intelligence", monster.mint); + reader.readInt("minDamage", monster.mMinDamage); + reader.readInt("maxDamage", monster.mMaxDamage); + reader.readEnumList("resistance", monster.mMagicRes, ParseMonsterResistance); + reader.read("monsterPack", monster.monsterPack, ParseUniqueMonsterPack); + reader.readInt("customToHit", monster.customToHit); + reader.readInt("customArmorClass", monster.customArmorClass); // talkMessage // TODO: Replace this hack with proper parsing once messages have been migrated to data files. - advance(); - { - const std::string_view value = (*fieldIt).value(); - if (value.empty()) { - monster.mtalkmsg = TEXT_NONE; - } else if (value == "TEXT_GARBUD1") { - monster.mtalkmsg = TEXT_GARBUD1; - } else if (value == "TEXT_ZHAR1") { - monster.mtalkmsg = TEXT_ZHAR1; - } else if (value == "TEXT_BANNER10") { - monster.mtalkmsg = TEXT_BANNER10; - } else if (value == "TEXT_VILE13") { - monster.mtalkmsg = TEXT_VILE13; - } else if (value == "TEXT_VEIL9") { - monster.mtalkmsg = TEXT_VEIL9; - } else if (value == "TEXT_WARLRD9") { - monster.mtalkmsg = TEXT_WARLRD9; - } else { - DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "talkMessage", *fieldIt, "NOTE: Parser is incomplete"); - } - } + reader.read("talkMessage", monster.mtalkmsg, [](std::string_view value) -> tl::expected<_speech_id, std::string> { + if (value.empty()) return TEXT_NONE; + if (value == "TEXT_GARBUD1") return TEXT_GARBUD1; + if (value == "TEXT_ZHAR1") return TEXT_ZHAR1; + if (value == "TEXT_BANNER10") return TEXT_BANNER10; + if (value == "TEXT_VILE13") return TEXT_VILE13; + if (value == "TEXT_VEIL9") return TEXT_VEIL9; + if (value == "TEXT_WARLRD9") return TEXT_WARLRD9; + return tl::make_unexpected("Invalid value. NOTE: Parser is incomplete"); + }); } - UniqueMonstersData.shrink_to_fit(); }