Browse Source

Simplify txtdata parsing with a helper class

pull/6809/head
Gleb Mazovetskiy 2 years ago
parent
commit
cfdade5f37
  1. 2
      Packaging/resources/assets/txtdata/items/item_prefixes.tsv
  2. 2
      Packaging/resources/assets/txtdata/items/item_suffixes.tsv
  3. 2
      Packaging/resources/assets/txtdata/items/unique_itemdat.tsv
  4. 3
      Source/.clang-tidy
  5. 1
      Source/CMakeLists.txt
  6. 4
      Source/data/iterators.hpp
  7. 17
      Source/data/record_reader.cpp
  8. 123
      Source/data/record_reader.hpp
  9. 351
      Source/itemdat.cpp
  10. 410
      Source/monstdat.cpp

2
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 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 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 Bronze TOHIT 1 5 1 Weapon,Bow,Misc Any true true 100 500 2

1 name power.type power power.param1 power.value1 power.param2 power.value2 minLevel itemTypes alignment doubleChance useful minVal maxVal multVal
2 Tin TOHIT_CURSE 6 10 3 Weapon,Bow,Misc Any true false 0 0 -3
3 Brass TOHIT_CURSE 1 5 1 Weapon,Bow,Misc Any true false 0 0 -2
4 Bronze TOHIT 1 5 1 Weapon,Bow,Misc Any true true 100 500 2

2
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 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 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 slaying DAMMOD 6 8 15 Weapon Any false true 2600 3000 5

1 name power.type power power.param1 power.value1 power.param2 power.value2 minLevel itemTypes alignment doubleChance useful minVal maxVal multVal
2 quality DAMMOD 1 2 2 Weapon,Staff,Bow Any false true 100 200 2
3 maiming DAMMOD 3 5 7 Weapon,Staff,Bow Any false true 1300 1500 3
4 slaying DAMMOD 6 8 15 Weapon Any false true 2600 3000 5

2
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 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 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 Empyrean Band INFRARING 1 8000 ATTRIBS 2 2 LIGHT 2 2 FASTRECOVER 1 1 ABSHALFTRAP

1 name uniqueBaseItem minLevel value powers[0].type power0 powers[0].param1 power0.value1 powers[0].param2 power0.value2 powers[1].type power1 powers[1].param1 power1.value1 powers[1].param2 power1.value2 powers[2].type power2 powers[2].param1 power2.value1 powers[2].param2 power2.value2 powers[3].type power3 powers[3].param1 power3.value1 powers[3].param2 power3.value2 powers[4].type power4 powers[4].param1 power4.value1 powers[4].param2 power4.value2 powers[5].type power5 powers[5].param1 power5.value1 powers[5].param2 power5.value2
2 The Butcher's Cleaver CLEAVER 1 3650 STR 10 10 SETDAM 4 24 SETDUR 10 10
3 The Undead Crown SKCROWN 1 16650 RNDSTEALLIFE SETAC 8 8 INVCURS 77
4 Empyrean Band INFRARING 1 8000 ATTRIBS 2 2 LIGHT 2 2 FASTRECOVER 1 1 ABSHALFTRAP

3
Source/.clang-tidy

@ -53,7 +53,8 @@ Checks: >
-modernize-avoid-c-arrays, -modernize-avoid-c-arrays,
-modernize-use-trailing-return-type, -modernize-use-trailing-return-type,
-modernize-concat-nested-namespaces, -modernize-concat-nested-namespaces,
-modernize-avoid-bind -modernize-avoid-bind,
-modernize-use-constraints
HeaderFilterRegex: "^(Source|test)\\.h$" HeaderFilterRegex: "^(Source|test)\\.h$"

1
Source/CMakeLists.txt

@ -70,6 +70,7 @@ set(libdevilutionx_SRCS
data/file.cpp data/file.cpp
data/parser.cpp data/parser.cpp
data/record_reader.cpp
DiabloUI/button.cpp DiabloUI/button.cpp
DiabloUI/credits.cpp DiabloUI/credits.cpp

4
Source/data/iterators.hpp

@ -146,7 +146,7 @@ public:
} }
template <typename T, typename ParseFn> template <typename T, typename ParseFn>
[[nodiscard]] tl::expected<void, Error> parseEnumList(T &destination, ParseFn &&parseFn) [[nodiscard]] tl::expected<void, std::string> parseEnumList(T &destination, ParseFn &&parseFn)
{ {
destination = {}; destination = {};
const std::string_view str = value(); const std::string_view str = value();
@ -155,7 +155,7 @@ public:
for (const std::string_view part : SplitByChar(str, ',')) { for (const std::string_view part : SplitByChar(str, ',')) {
auto result = parseFn(part); auto result = parseFn(part);
if (!result.has_value()) if (!result.has_value())
return tl::make_unexpected(Error::InvalidValue); return tl::make_unexpected(std::move(result).error());
destination |= result.value(); destination |= result.value();
} }
return {}; return {};

17
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

123
Source/data/record_reader.hpp

@ -0,0 +1,123 @@
#pragma once
#include <string_view>
#include <type_traits>
#include <expected.hpp>
#include <function_ref.hpp>
#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 T>
typename std::enable_if_t<std::is_integral_v<T>, void>
readInt(std::string_view name, T &out)
{
advance();
DataFileField field = *it_;
if (tl::expected<void, DataFileField::Error> result = field.parseInt(out); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename_, name, field);
}
}
template <typename T>
typename std::enable_if_t<std::is_integral_v<T>, void>
readOptionalInt(std::string_view name, T &out)
{
advance();
DataFileField field = *it_;
if (field.value().empty()) return;
if (tl::expected<void, DataFileField::Error> result = field.parseInt(out); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename_, name, field);
}
}
template <typename T, size_t N>
void readIntArray(std::string_view name, T (&out)[N])
{
advance();
DataFileField field = *it_;
if (tl::expected<void, DataFileField::Error> result = field.parseIntArray(out); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename_, name, field);
}
}
template <typename T>
typename std::enable_if_t<std::is_integral_v<T>, void>
readFixed6(std::string_view name, T &out)
{
advance();
DataFileField field = *it_;
if (tl::expected<void, DataFileField::Error> 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<void, DataFileField::Error> 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 <typename T, typename F>
void read(std::string_view name, T &out, F &&parseFn)
{
advance();
DataFileField field = *it_;
if (tl::expected<T, std::string> result = parseFn(field.value()); result.has_value()) {
out = *std::move(result);
} else {
DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename_, name, field, result.error());
}
}
template <typename T, typename F>
void readEnumList(std::string_view name, T &out, F &&parseFn)
{
advance();
DataFileField field = *it_;
if (tl::expected<void, std::string> result = field.parseEnumList(out, std::forward<F>(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

351
Source/itemdat.cpp

@ -13,6 +13,7 @@
#include "data/file.hpp" #include "data/file.hpp"
#include "data/iterators.hpp" #include "data/iterators.hpp"
#include "data/record_reader.hpp"
#include "spelldat.h" #include "spelldat.h"
#include "utils/str_cat.hpp" #include "utils/str_cat.hpp"
@ -491,200 +492,40 @@ void LoadItemDat()
AllItemsList.clear(); AllItemsList.clear();
AllItemsList.reserve(dataFile.numRecords()); AllItemsList.reserve(dataFile.numRecords());
for (DataFileRecord record : dataFile) { for (DataFileRecord record : dataFile) {
FieldIterator fieldIt = record.begin(); RecordReader reader { record, filename };
const FieldIterator endField = record.end(); ItemData &item = AllItemsList.emplace_back();
reader.advance(); // Skip the first column (item ID).
AllItemsList.emplace_back(); reader.read("dropRate", item.iRnd, ParseItemDropRate);
ItemData &item = AllItemsList.back(); reader.read("class", item.iClass, ParseItemClass);
reader.read("equipType", item.iLoc, ParseItemEquipType);
const auto advance = [&]() { reader.read("cursorGraphic", item.iCurs, ParseItemCursorGraphic);
++fieldIt; reader.read("itemType", item.itype, ParseItemType);
if (fieldIt == endField) { reader.read("uniqueBaseItem", item.iItemId, ParseUniqueBaseItem);
DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename); reader.readString("name", item.iName);
} reader.readString("shortName", item.iSName);
}; reader.readInt("minMonsterLevel", item.iMinMLvl);
reader.readInt("durability", item.iDurability);
// Skip the first column (item ID). reader.readInt("minDamage", item.iMinDam);
reader.readInt("maxDamage", item.iMaxDam);
// dropRate reader.readInt("minArmor", item.iMinAC);
advance(); reader.readInt("maxArmor", item.iMaxAC);
if (tl::expected<item_drop_rate, std::string> result = ParseItemDropRate((*fieldIt).value()); result.has_value()) { reader.readInt("minStrength", item.iMinStr);
item.iRnd = *std::move(result); reader.readInt("minMagic", item.iMinMag);
} else { reader.readInt("minDexterity", item.iMinDex);
DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "dropRate", *fieldIt, result.error()); reader.readEnumList("specialEffects", item.iFlags, ParseItemSpecialEffect);
} reader.read("miscId", item.iMiscId, ParseItemMiscId);
reader.read("spell", item.iSpell, ParseSpellId);
// class reader.readBool("usable", item.iUsable);
advance(); reader.readInt("value", item.iValue);
if (tl::expected<item_class, std::string> 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<item_equip_type, std::string> 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<item_cursor_graphic, std::string> 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<ItemType, std::string> 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<unique_base_item, std::string> 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<void, DataFileField::Error> result = (*fieldIt).parseInt(item.iMinMLvl); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "minMonsterLevel", *fieldIt);
}
// durability
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(item.iDurability); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "durability", *fieldIt);
}
// minDamage
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(item.iMinDam); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "minDamage", *fieldIt);
}
// maxDamage
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(item.iMaxDam); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "maxDamage", *fieldIt);
}
// minArmor
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(item.iMinAC); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "minArmor", *fieldIt);
}
// maxArmor
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(item.iMaxAC); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "maxArmor", *fieldIt);
}
// minStrength
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(item.iMinStr); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "minStrength", *fieldIt);
}
// minMagic
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(item.iMinMag); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "minMagic", *fieldIt);
}
// minDexterity
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(item.iMinDex); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "minDexterity", *fieldIt);
}
// specialEffects
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseEnumList(item.iFlags, ParseItemSpecialEffect); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "specialEffects", *fieldIt);
}
// miscId
advance();
if (tl::expected<item_misc_id, std::string> 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<SpellID, std::string> 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<void, DataFileField::Error> result = (*fieldIt).parseBool(item.iUsable); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "usable", *fieldIt);
}
// value
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(item.iValue); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "value", *fieldIt);
}
} }
AllItemsList.shrink_to_fit(); AllItemsList.shrink_to_fit();
} }
void ParseItemPower(devilution::FieldIterator &fieldIt, const FieldIterator &endField, void ReadItemPower(RecordReader &reader, std::string_view fieldName, ItemPower &power)
std::string_view filename, std::string_view fieldName, ItemPower &power)
{ {
const auto advance = [&]() { reader.read(fieldName, power.type, ParseItemEffectType);
++fieldIt; reader.readOptionalInt(StrCat(fieldName, ".value1"), power.param1);
if (fieldIt == endField) { reader.readOptionalInt(StrCat(fieldName, ".value2"), power.param2);
DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename);
}
};
if (tl::expected<item_effect_type, std::string> 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<void, DataFileField::Error> 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<void, DataFileField::Error> result = (*fieldIt).parseInt(power.param2); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, StrCat(fieldName, ".param2"), *fieldIt);
}
}
} }
void LoadUniqueItemDat() void LoadUniqueItemDat()
@ -704,53 +545,21 @@ void LoadUniqueItemDat()
UniqueItems.clear(); UniqueItems.clear();
UniqueItems.reserve(dataFile.numRecords()); UniqueItems.reserve(dataFile.numRecords());
for (DataFileRecord record : dataFile) { for (DataFileRecord record : dataFile) {
FieldIterator fieldIt = record.begin(); RecordReader reader { record, filename };
const FieldIterator endField = record.end(); UniqueItem &item = UniqueItems.emplace_back();
reader.readString("name", item.UIName);
UniqueItems.emplace_back(); reader.read("uniqueBaseItem", item.UIItemId, ParseUniqueBaseItem);
UniqueItem &item = UniqueItems.back(); reader.readInt("minLevel", item.UIMinLvl);
reader.readInt("value", item.UIValue);
const auto advance = [&]() {
++fieldIt;
if (fieldIt == endField) {
DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename);
}
};
// name
item.UIName = (*fieldIt).value();
// uniqueBaseItem
advance();
if (tl::expected<unique_base_item, std::string> 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<void, DataFileField::Error> result = (*fieldIt).parseInt(item.UIMinLvl); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "minLevel", *fieldIt);
}
// value
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(item.UIValue); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "value", *fieldIt);
}
// powers (up to 6) // powers (up to 6)
item.UINumPL = 0; item.UINumPL = 0;
for (size_t i = 0; i < 6; ++i) { for (size_t i = 0; i < 6; ++i) {
// type if (reader.value().empty())
advance(); break;
if ((*fieldIt).value().empty()) ReadItemPower(reader, StrCat("power", i), item.powers[item.UINumPL++]);
continue;
ParseItemPower(fieldIt, endField, filename, StrCat("powers[", i, "]"), item.powers[item.UINumPL++]);
} }
} }
UniqueItems.shrink_to_fit(); UniqueItems.shrink_to_fit();
} }
@ -770,77 +579,19 @@ void LoadItemAffixesDat(std::string_view filename, std::vector<PLStruct> &out)
out.clear(); out.clear();
out.reserve(dataFile.numRecords()); out.reserve(dataFile.numRecords());
for (DataFileRecord record : dataFile) { for (DataFileRecord record : dataFile) {
FieldIterator fieldIt = record.begin(); RecordReader reader { record, filename };
const FieldIterator endField = record.end(); PLStruct &item = out.emplace_back();
reader.readString("name", item.PLName);
out.emplace_back(); ReadItemPower(reader, "power", item.power);
PLStruct &item = out.back(); reader.readInt("minLevel", item.PLMinLvl);
reader.readEnumList("itemTypes", item.PLIType, ParseAffixItemType);
const auto advance = [&]() { reader.read("alignment", item.PLGOE, ParseAffixAlignment);
++fieldIt; reader.readBool("doubleChance", item.PLDouble);
if (fieldIt == endField) { reader.readBool("useful", item.PLOk);
DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename); reader.readInt("minVal", item.minVal);
} reader.readInt("maxVal", item.maxVal);
}; reader.readInt("multVal", item.multVal);
// name
item.PLName = (*fieldIt).value();
// power
advance();
ParseItemPower(fieldIt, endField, filename, "power", item.power);
// minLevel
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(item.PLMinLvl); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "minLevel", *fieldIt);
}
// itemTypes
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseEnumList(item.PLIType, ParseAffixItemType); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "itemTypes", *fieldIt);
}
// alignment
advance();
if (tl::expected<goodorevil, std::string> 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<void, DataFileField::Error> result = (*fieldIt).parseBool(item.PLDouble); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "doubleChance", *fieldIt);
}
// useful
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseBool(item.PLOk); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "useful", *fieldIt);
}
// minVal
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(item.minVal); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "minVal", *fieldIt);
}
// maxVal
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(item.maxVal); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "maxVal", *fieldIt);
}
// multVal
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(item.multVal); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "multVal", *fieldIt);
}
} }
out.shrink_to_fit(); out.shrink_to_fit();
} }

410
Source/monstdat.cpp

@ -11,6 +11,7 @@
#include <expected.hpp> #include <expected.hpp>
#include "data/file.hpp" #include "data/file.hpp"
#include "data/record_reader.hpp"
#include "items.h" #include "items.h"
#include "monster.h" #include "monster.h"
#include "textdat.h" #include "textdat.h"
@ -484,242 +485,61 @@ void LoadMonstDat()
MonstersData.reserve(dataFile.numRecords()); MonstersData.reserve(dataFile.numRecords());
std::unordered_map<std::string, size_t> spritePathToId; std::unordered_map<std::string, size_t> spritePathToId;
for (DataFileRecord record : dataFile) { for (DataFileRecord record : dataFile) {
FieldIterator fieldIt = record.begin(); RecordReader reader { record, filename };
const FieldIterator endField = record.end(); MonsterData &monster = MonstersData.emplace_back();
reader.advance(); // Skip the first column (monster ID).
MonstersData.emplace_back(); reader.readString("name", monster.name);
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();
{ {
std::string assetsSuffix { (*fieldIt).value() }; std::string assetsSuffix;
reader.readString("assetsSuffix", assetsSuffix);
const auto [it, inserted] = spritePathToId.emplace(assetsSuffix, spritePathToId.size()); const auto [it, inserted] = spritePathToId.emplace(assetsSuffix, spritePathToId.size());
if (inserted) if (inserted)
MonsterSpritePaths.push_back(it->first); MonsterSpritePaths.push_back(it->first);
monster.spriteId = it->second; monster.spriteId = it->second;
} }
reader.readString("soundSuffix", monster.soundSuffix);
// soundSuffix reader.readString("trnFile", monster.trnFile);
advance(); reader.read("availability", monster.availability, ParseMonsterAvailability);
monster.soundSuffix = (*fieldIt).value(); reader.readInt("width", monster.width);
reader.readInt("image", monster.image);
// trnFile reader.readBool("hasSpecial", monster.hasSpecial);
advance(); reader.readBool("hasSpecialSound", monster.hasSpecialSound);
monster.trnFile = (*fieldIt).value(); reader.readIntArray("frames", monster.frames);
reader.readIntArray("rate", monster.rate);
// availability reader.readInt("minDunLvl", monster.minDunLvl);
advance(); reader.readInt("maxDunLvl", monster.maxDunLvl);
if (tl::expected<MonsterAvailability, std::string> result = ParseMonsterAvailability((*fieldIt).value()); result.has_value()) { reader.readInt("level", monster.level);
monster.availability = *std::move(result); reader.readInt("hitPointsMinimum", monster.hitPointsMinimum);
} else { reader.readInt("hitPointsMaximum", monster.hitPointsMaximum);
DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "availability", *fieldIt, result.error()); reader.read("ai", monster.ai, ParseAiId);
} reader.readEnumList("abilityFlags", monster.abilityFlags, ParseMonsterFlag);
reader.readInt("intelligence", monster.intelligence);
// width reader.readInt("toHit", monster.toHit);
advance(); reader.readInt("animFrameNum", monster.animFrameNum);
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.width); !result.has_value()) { reader.readInt("minDamage", monster.minDamage);
DataFile::reportFatalFieldError(result.error(), filename, "width", *fieldIt); reader.readInt("maxDamage", monster.maxDamage);
} reader.readInt("toHitSpecial", monster.toHitSpecial);
reader.readInt("animFrameNumSpecial", monster.animFrameNumSpecial);
// image reader.readInt("minDamageSpecial", monster.minDamageSpecial);
advance(); reader.readInt("maxDamageSpecial", monster.maxDamageSpecial);
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.image); !result.has_value()) { reader.readInt("armorClass", monster.armorClass);
DataFile::reportFatalFieldError(result.error(), filename, "image", *fieldIt); reader.read("monsterClass", monster.monsterClass, ParseMonsterClass);
} reader.readEnumList("resistance", monster.resistance, ParseMonsterResistance);
reader.readEnumList("resistanceHell", monster.resistanceHell, ParseMonsterResistance);
// hasSpecial reader.readInt("selectionType", monster.selectionType);
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseBool(monster.hasSpecial); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "hasSpecial", *fieldIt);
}
// hasSpecialSound
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseBool(monster.hasSpecialSound); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "hasSpecialSound", *fieldIt);
}
// frames[6]
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseIntArray(monster.frames); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "frames", *fieldIt);
}
// rate[6]
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseIntArray(monster.rate); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "rate", *fieldIt);
}
// minDunLvl
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.minDunLvl); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "minDunLvl", *fieldIt);
}
// maxDunLvl
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.maxDunLvl); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "maxDunLvl", *fieldIt);
}
// level
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.level); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "level", *fieldIt);
}
// hitPointsMinimum
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.hitPointsMinimum); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "hitPointsMinimum", *fieldIt);
}
// hitPointsMaximum
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.hitPointsMaximum); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "hitPointsMaximum", *fieldIt);
}
// ai
advance();
if (tl::expected<MonsterAIID, std::string> 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<void, DataFileField::Error> result = (*fieldIt).parseEnumList(monster.abilityFlags, ParseMonsterFlag); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "abilityFlags", *fieldIt);
}
// intelligence
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.intelligence); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "intelligence", *fieldIt);
}
// toHit
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.toHit); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "toHit", *fieldIt);
}
// animFrameNum
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.animFrameNum); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "animFrameNum", *fieldIt);
}
// minDamage
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.minDamage); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "minDamage", *fieldIt);
}
// maxDamage
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.maxDamage); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "maxDamage", *fieldIt);
}
// toHitSpecial
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.toHitSpecial); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "toHitSpecial", *fieldIt);
}
// animFrameNumSpecial
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.animFrameNumSpecial); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "animFrameNumSpecial", *fieldIt);
}
// minDamageSpecial
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.minDamageSpecial); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "minDamageSpecial", *fieldIt);
}
// maxDamageSpecial
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.maxDamageSpecial); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "maxDamageSpecial", *fieldIt);
}
// armorClass
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.armorClass); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "armorClass", *fieldIt);
}
// monsterClass
advance();
if (tl::expected<MonsterClass, std::string> 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<void, DataFileField::Error> result = (*fieldIt).parseEnumList(monster.resistance, ParseMonsterResistance); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "resistance", *fieldIt);
}
// resistanceHell
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseEnumList(monster.resistanceHell, ParseMonsterResistance); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "resistanceHell", *fieldIt);
}
// selectionType
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.selectionType); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "selectionType", *fieldIt);
}
// treasure // treasure
// TODO: Replace this hack with proper parsing once items have been migrated to data files. // TODO: Replace this hack with proper parsing once items have been migrated to data files.
advance(); reader.read("treasure", monster.treasure, [](std::string_view value) -> tl::expected<uint16_t, std::string> {
{ if (value.empty()) return 0;
const std::string_view value = (*fieldIt).value(); if (value == "None") return T_NODROP;
if (value.empty()) { if (value == "Uniq(SKCROWN)") return Uniq(UITEM_SKCROWN);
monster.treasure = 0; if (value == "Uniq(CLEAVER)") return Uniq(UITEM_CLEAVER);
} else if (value == "None") { return tl::make_unexpected("Invalid value. NOTE: Parser is incomplete");
monster.treasure = T_NODROP; });
} else if (value == "Uniq(SKCROWN)") {
monster.treasure = Uniq(UITEM_SKCROWN); reader.readInt("exp", monster.exp);
} 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<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.exp); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "exp", *fieldIt);
}
} }
MonstersData.shrink_to_fit(); MonstersData.shrink_to_fit();
} }
@ -740,123 +560,35 @@ void LoadUniqueMonstDat()
UniqueMonstersData.clear(); UniqueMonstersData.clear();
UniqueMonstersData.reserve(dataFile.numRecords()); UniqueMonstersData.reserve(dataFile.numRecords());
for (DataFileRecord record : dataFile) { for (DataFileRecord record : dataFile) {
FieldIterator fieldIt = record.begin(); RecordReader reader { record, filename };
const FieldIterator endField = record.end(); UniqueMonsterData &monster = UniqueMonstersData.emplace_back();
reader.read("type", monster.mtype, ParseMonsterId);
UniqueMonstersData.emplace_back(); reader.readString("name", monster.mName);
UniqueMonsterData &monster = UniqueMonstersData.back(); reader.readString("trn", monster.mTrnName);
reader.readInt("level", monster.mlevel);
const auto advance = [&]() { reader.readInt("maxHp", monster.mmaxhp);
++fieldIt; reader.read("ai", monster.mAi, ParseAiId);
if (fieldIt == endField) { reader.readInt("intelligence", monster.mint);
DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename); reader.readInt("minDamage", monster.mMinDamage);
} reader.readInt("maxDamage", monster.mMaxDamage);
}; reader.readEnumList("resistance", monster.mMagicRes, ParseMonsterResistance);
reader.read("monsterPack", monster.monsterPack, ParseUniqueMonsterPack);
// type reader.readInt("customToHit", monster.customToHit);
if (tl::expected<_monster_id, std::string> result = ParseMonsterId((*fieldIt).value()); result.has_value()) { reader.readInt("customArmorClass", monster.customArmorClass);
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<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.mlevel); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "level", *fieldIt);
}
// maxHp
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.mmaxhp); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "maxHp", *fieldIt);
}
// ai
advance();
if (tl::expected<MonsterAIID, std::string> 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<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.mint); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "intelligence", *fieldIt);
}
// minDamage
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.mMinDamage); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "minDamage", *fieldIt);
}
// maxDamage
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.mMaxDamage); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "maxDamage", *fieldIt);
}
// resistance
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseEnumList(monster.mMagicRes, ParseMonsterResistance); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "resistance", *fieldIt);
}
// monsterPack
advance();
if (tl::expected<UniqueMonsterPack, std::string> 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<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.customToHit); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "customToHit", *fieldIt);
}
// customArmorClass
advance();
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.customArmorClass); !result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "customArmorClass", *fieldIt);
}
// talkMessage // talkMessage
// TODO: Replace this hack with proper parsing once messages have been migrated to data files. // TODO: Replace this hack with proper parsing once messages have been migrated to data files.
advance(); reader.read("talkMessage", monster.mtalkmsg, [](std::string_view value) -> tl::expected<_speech_id, std::string> {
{ if (value.empty()) return TEXT_NONE;
const std::string_view value = (*fieldIt).value(); if (value == "TEXT_GARBUD1") return TEXT_GARBUD1;
if (value.empty()) { if (value == "TEXT_ZHAR1") return TEXT_ZHAR1;
monster.mtalkmsg = TEXT_NONE; if (value == "TEXT_BANNER10") return TEXT_BANNER10;
} else if (value == "TEXT_GARBUD1") { if (value == "TEXT_VILE13") return TEXT_VILE13;
monster.mtalkmsg = TEXT_GARBUD1; if (value == "TEXT_VEIL9") return TEXT_VEIL9;
} else if (value == "TEXT_ZHAR1") { if (value == "TEXT_WARLRD9") return TEXT_WARLRD9;
monster.mtalkmsg = TEXT_ZHAR1; return tl::make_unexpected("Invalid value. NOTE: Parser is incomplete");
} 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");
}
}
} }
UniqueMonstersData.shrink_to_fit(); UniqueMonstersData.shrink_to_fit();
} }

Loading…
Cancel
Save