Browse Source

Make Player Class Data Data-Driven (#8167)

pull/8177/head
Andrettin 6 months ago committed by GitHub
parent
commit
f6b4665e94
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 25
      CMake/Assets.cmake
  2. 2
      CMake/Mods.cmake
  3. 1
      Source/CMakeLists.txt
  4. 21
      Source/DiabloUI/diabloui.cpp
  5. 33
      Source/DiabloUI/hero/selhero.cpp
  6. 37
      Source/data/value_reader.cpp
  7. 93
      Source/data/value_reader.hpp
  8. 2
      Source/discord/discord.cpp
  9. 18
      Source/effects.cpp
  10. 1
      Source/effects.h
  11. 9
      Source/effects_stubs.cpp
  12. 27
      Source/engine/trn.cpp
  13. 20
      Source/inv.cpp
  14. 10
      Source/itemdat.cpp
  15. 9
      Source/itemdat.h
  16. 3
      Source/items.cpp
  17. 15
      Source/minitext.cpp
  18. 2
      Source/multi.cpp
  19. 4
      Source/pack.cpp
  20. 20
      Source/player.cpp
  21. 10
      Source/player.h
  22. 351
      Source/playerdat.cpp
  23. 37
      Source/playerdat.hpp
  24. 28
      assets/txtdata/classes/barbarian/animations.tsv
  25. 104
      assets/txtdata/classes/barbarian/sounds.tsv
  26. 14
      assets/txtdata/classes/barbarian/sprites.tsv
  27. 10
      assets/txtdata/classes/barbarian/starting_loadout.tsv
  28. 28
      assets/txtdata/classes/bard/animations.tsv
  29. 104
      assets/txtdata/classes/bard/sounds.tsv
  30. 14
      assets/txtdata/classes/bard/sprites.tsv
  31. 10
      assets/txtdata/classes/bard/starting_loadout.tsv
  32. 7
      assets/txtdata/classes/classdat.tsv
  33. 28
      assets/txtdata/classes/monk/animations.tsv
  34. 104
      assets/txtdata/classes/monk/sounds.tsv
  35. 14
      assets/txtdata/classes/monk/sprites.tsv
  36. 10
      assets/txtdata/classes/monk/starting_loadout.tsv
  37. 28
      assets/txtdata/classes/rogue/animations.tsv
  38. 104
      assets/txtdata/classes/rogue/sounds.tsv
  39. 14
      assets/txtdata/classes/rogue/sprites.tsv
  40. 10
      assets/txtdata/classes/rogue/starting_loadout.tsv
  41. 28
      assets/txtdata/classes/sorcerer/animations.tsv
  42. 104
      assets/txtdata/classes/sorcerer/sounds.tsv
  43. 14
      assets/txtdata/classes/sorcerer/sprites.tsv
  44. 10
      assets/txtdata/classes/sorcerer/starting_loadout.tsv
  45. 28
      assets/txtdata/classes/warrior/animations.tsv
  46. 104
      assets/txtdata/classes/warrior/sounds.tsv
  47. 14
      assets/txtdata/classes/warrior/sprites.tsv
  48. 10
      assets/txtdata/classes/warrior/starting_loadout.tsv
  49. 7
      mods/Hellfire/txtdata/classes/classdat.tsv
  50. 10
      mods/Hellfire/txtdata/classes/sorcerer/starting_loadout.tsv
  51. 5
      test/player_test.cpp
  52. 9
      tools/extract_translation_data.py

25
CMake/Assets.cmake

@ -158,12 +158,37 @@ set(devilutionx_assets
plrgfx/warrior/whu/whulm.trn
plrgfx/warrior/whu/whuqm.trn
txtdata/Experience.tsv
txtdata/classes/barbarian/animations.tsv
txtdata/classes/barbarian/attributes.tsv
txtdata/classes/barbarian/sounds.tsv
txtdata/classes/barbarian/sprites.tsv
txtdata/classes/barbarian/starting_loadout.tsv
txtdata/classes/bard/animations.tsv
txtdata/classes/bard/attributes.tsv
txtdata/classes/bard/sounds.tsv
txtdata/classes/bard/sprites.tsv
txtdata/classes/bard/starting_loadout.tsv
txtdata/classes/monk/animations.tsv
txtdata/classes/monk/attributes.tsv
txtdata/classes/monk/sounds.tsv
txtdata/classes/monk/sprites.tsv
txtdata/classes/monk/starting_loadout.tsv
txtdata/classes/rogue/animations.tsv
txtdata/classes/rogue/attributes.tsv
txtdata/classes/rogue/sounds.tsv
txtdata/classes/rogue/sprites.tsv
txtdata/classes/rogue/starting_loadout.tsv
txtdata/classes/sorcerer/animations.tsv
txtdata/classes/sorcerer/attributes.tsv
txtdata/classes/sorcerer/sounds.tsv
txtdata/classes/sorcerer/sprites.tsv
txtdata/classes/sorcerer/starting_loadout.tsv
txtdata/classes/warrior/animations.tsv
txtdata/classes/warrior/attributes.tsv
txtdata/classes/warrior/sounds.tsv
txtdata/classes/warrior/sprites.tsv
txtdata/classes/warrior/starting_loadout.tsv
txtdata/classes/classdat.tsv
txtdata/items/item_prefixes.tsv
txtdata/items/item_suffixes.tsv
txtdata/items/itemdat.tsv

2
CMake/Mods.cmake

@ -8,6 +8,8 @@ set(hellfire_mod
nlevels/cutl6w.clx
nlevels/l5data/cornerstone.dun
nlevels/l5data/uberroom.dun
txtdata/classes/sorcerer/starting_loadout.tsv
txtdata/classes/classdat.tsv
txtdata/items/item_prefixes.tsv
txtdata/items/item_suffixes.tsv
txtdata/items/unique_itemdat.tsv

1
Source/CMakeLists.txt

@ -709,6 +709,7 @@ add_devilutionx_object_library(libdevilutionx_txtdata
data/file.cpp
data/parser.cpp
data/record_reader.cpp
data/value_reader.cpp
)
target_link_dependencies(libdevilutionx_txtdata PUBLIC
fmt::fmt

21
Source/DiabloUI/diabloui.cpp

@ -82,8 +82,8 @@ std::size_t SelectedItem = 0;
namespace {
OptionalOwnedClxSpriteList ArtHero;
std::array<uint8_t, enum_size<HeroClass>::value + 1> ArtHeroPortraitOrder;
std::array<OptionalOwnedClxSpriteList, enum_size<HeroClass>::value + 1> ArtHeroOverrides;
std::vector<uint8_t> ArtHeroPortraitOrder;
std::vector<OptionalOwnedClxSpriteList> ArtHeroOverrides;
std::size_t SelectedItemMax;
std::size_t ListViewportSize = 1;
@ -581,17 +581,18 @@ void LoadHeros()
return;
const uint16_t numPortraits = ClxSpriteList { *ArtHero }.numSprites();
ArtHeroPortraitOrder = { 0, 1, 2, 2, 1, 0, 3 };
if (numPortraits >= 6) {
ArtHeroPortraitOrder[static_cast<std::size_t>(HeroClass::Monk)] = 3;
ArtHeroPortraitOrder[static_cast<std::size_t>(HeroClass::Bard)] = 4;
ArtHeroPortraitOrder[enum_size<HeroClass>::value] = 5;
ArtHeroPortraitOrder.resize(GetNumPlayerClasses() + 1);
for (size_t i = 0; i < GetNumPlayerClasses(); ++i) {
const PlayerData &playerClassData = GetPlayerDataForClass(static_cast<HeroClass>(i));
ArtHeroPortraitOrder[i] = playerClassData.portrait;
}
if (numPortraits >= 7) {
ArtHeroPortraitOrder[static_cast<std::size_t>(HeroClass::Barbarian)] = 6;
ArtHeroPortraitOrder.back() = 3;
if (numPortraits >= 6) {
ArtHeroPortraitOrder.back() = 5;
}
for (size_t i = 0; i <= enum_size<HeroClass>::value; ++i) {
ArtHeroOverrides.resize(GetNumPlayerClasses() + 1);
for (size_t i = 0; i <= GetNumPlayerClasses(); ++i) {
char portraitPath[18];
*BufCopy(portraitPath, "ui_art\\hero", i) = '\0';
ArtHeroOverrides[i] = LoadPcx(portraitPath, /*transparentColor=*/std::nullopt, /*outPalette=*/nullptr, /*logError=*/false);

33
Source/DiabloUI/hero/selhero.cpp

@ -143,7 +143,7 @@ void SelheroListFocus(size_t value)
return;
}
SELHERO_DIALOG_HERO_IMG->setSprite(UiGetHeroDialogSprite(enum_size<HeroClass>::value));
SELHERO_DIALOG_HERO_IMG->setSprite(UiGetHeroDialogSprite(GetNumPlayerClasses()));
for (char *textStat : textStats)
strcpy(textStat, "--");
SELLIST_DIALOG_DELETE_BUTTON->SetFlags(baseFlags | UiFlags::ColorUiSilver | UiFlags::ElementDisabled);
@ -169,22 +169,31 @@ void SelheroListSelect(size_t value)
vecSelHeroDlgItems.clear();
int itemH = 33;
vecSelHeroDlgItems.push_back(std::make_unique<UiListItem>(_("Warrior"), static_cast<int>(HeroClass::Warrior)));
vecSelHeroDlgItems.push_back(std::make_unique<UiListItem>(_("Rogue"), static_cast<int>(HeroClass::Rogue)));
vecSelHeroDlgItems.push_back(std::make_unique<UiListItem>(_("Sorcerer"), static_cast<int>(HeroClass::Sorcerer)));
if (gbIsHellfire) {
vecSelHeroDlgItems.push_back(std::make_unique<UiListItem>(_("Monk"), static_cast<int>(HeroClass::Monk)));
if (HaveBardAssets() || *GetOptions().Gameplay.testBard) {
vecSelHeroDlgItems.push_back(std::make_unique<UiListItem>(_("Bard"), static_cast<int>(HeroClass::Bard)));
for (size_t i = 0; i < GetNumPlayerClasses(); ++i) {
const HeroClass heroClass = static_cast<HeroClass>(i);
if (heroClass == HeroClass::Monk && !gbIsHellfire) {
continue;
}
if (heroClass == HeroClass::Bard && !HaveBardAssets() && !(*GetOptions().Gameplay.testBard)) {
continue;
}
if (HaveBarbarianAssets() || *GetOptions().Gameplay.testBarbarian) {
vecSelHeroDlgItems.push_back(std::make_unique<UiListItem>(_("Barbarian"), static_cast<int>(HeroClass::Barbarian)));
if (heroClass == HeroClass::Barbarian && !HaveBarbarianAssets() && !(*GetOptions().Gameplay.testBarbarian)) {
continue;
}
const PlayerData &playerData = GetPlayerDataForClass(heroClass);
vecSelHeroDlgItems.push_back(std::make_unique<UiListItem>(std::string_view(playerData.className), static_cast<int>(heroClass)));
}
if (vecSelHeroDlgItems.size() > 4)
itemH = 26;
const int itemY = static_cast<int>(246 + (176 - vecSelHeroDlgItems.size() * itemH) / 2);
vecSelDlgItems.push_back(std::make_unique<UiList>(vecSelHeroDlgItems, vecSelHeroDlgItems.size(), uiPosition.x + 264, (uiPosition.y + itemY), 320, itemH, UiFlags::AlignCenter | UiFlags::FontSize24 | UiFlags::ColorUiGold));
const int itemY = static_cast<int>(246 + (176 - std::min<size_t>(vecSelHeroDlgItems.size(), 6) * itemH) / 2);
vecSelDlgItems.push_back(std::make_unique<UiList>(vecSelHeroDlgItems, std::min<size_t>(vecSelHeroDlgItems.size(), 6), uiPosition.x + 264, (uiPosition.y + itemY), 320, itemH, UiFlags::AlignCenter | UiFlags::FontSize24 | UiFlags::ColorUiGold));
const SDL_Rect rectScrollBar = { (Sint16)(uiPosition.x + 585), (Sint16)(uiPosition.y + 244), 25, 178 };
vecSelDlgItems.push_back(std::make_unique<UiScrollbar>((*ArtScrollBarBackground)[0], (*ArtScrollBarThumb)[0], *ArtScrollBarArrow, rectScrollBar));
const SDL_Rect rect2 = { (Sint16)(uiPosition.x + 279), (Sint16)(uiPosition.y + 429), 140, 35 };
vecSelDlgItems.push_back(std::make_unique<UiArtTextButton>(_("OK"), &UiFocusNavigationSelect, rect2, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiGold));

37
Source/data/value_reader.cpp

@ -0,0 +1,37 @@
#include "data/value_reader.hpp"
#include <fmt/core.h>
#include "appfat.h"
namespace devilution {
ValueReader::ValueReader(DataFile &dataFile, std::string_view filename)
: it_(dataFile.begin())
, end_(dataFile.end())
, filename_(filename)
{
}
DataFileField ValueReader::getValueField(std::string_view expectedKey)
{
if (it_ == end_) {
app_fatal(fmt::format("Missing field {} in {}", expectedKey, filename_));
}
DataFileRecord record = *it_;
FieldIterator fieldIt = record.begin();
const FieldIterator endField = record.end();
const std::string_view key = (*fieldIt).value();
if (key != expectedKey) {
app_fatal(fmt::format("Unexpected field in {}: got {}, expected {}", filename_, key, expectedKey));
}
++fieldIt;
if (fieldIt == endField) {
DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename_);
}
return *fieldIt;
}
} // namespace devilution

93
Source/data/value_reader.hpp

@ -0,0 +1,93 @@
#pragma once
#include <array>
#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 value reader.
*/
class ValueReader {
public:
ValueReader(DataFile &dataFile, std::string_view filename);
DataFileField getValueField(std::string_view expectedKey);
template <typename T, typename F>
void readValue(std::string_view expectedKey, T &outValue, F &&readFn)
{
DataFileField valueField = getValueField(expectedKey);
if (const tl::expected<void, devilution::DataFileField::Error> result = readFn(valueField, outValue);
!result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename_, "Value", valueField);
}
++it_;
}
template <typename T, typename F>
void read(std::string_view expectedKey, T &outValue, F &&parseFn)
{
readValue(expectedKey, outValue, [&parseFn](DataFileField &valueField, T &outValue) -> tl::expected<void, devilution::DataFileField::Error> {
const auto result = parseFn(valueField.value());
if (!result.has_value()) {
return tl::make_unexpected(devilution::DataFileField::Error::InvalidValue);
}
outValue = result.value();
return {};
});
}
template <typename T>
typename std::enable_if_t<std::is_integral_v<T>, void>
readInt(std::string_view expectedKey, T &outValue)
{
readValue(expectedKey, outValue, [](DataFileField &valueField, T &outValue) {
return valueField.parseInt(outValue);
});
}
template <typename T>
typename std::enable_if_t<std::is_integral_v<T>, void>
readDecimal(std::string_view expectedKey, T &outValue)
{
readValue(expectedKey, outValue, [](DataFileField &valueField, T &outValue) {
return valueField.parseFixed6(outValue);
});
}
void readString(std::string_view expectedKey, std::string &outValue)
{
readValue(expectedKey, outValue, [](DataFileField &valueField, std::string &outValue) {
outValue = valueField.value();
return tl::expected<void, devilution::DataFileField::Error> {};
});
}
void readChar(std::string_view expectedKey, char &outValue)
{
readValue(expectedKey, outValue, [](DataFileField &valueField, char &outValue) -> tl::expected<void, devilution::DataFileField::Error> {
if (valueField.value().size() != 1) {
return tl::make_unexpected(devilution::DataFileField::Error::InvalidValue);
}
outValue = valueField.value().at(0);
return {};
});
}
private:
RecordIterator it_;
const RecordIterator end_;
std::string_view filename_;
};
} // namespace devilution

2
Source/discord/discord.cpp

@ -124,7 +124,7 @@ std::string GetTooltipString()
std::string GetPlayerAssetString()
{
char chars[5] {
CharChar[static_cast<int>(MyPlayer->_pClass)],
GetPlayerSpriteDataForClass(MyPlayer->_pClass).classChar,
ArmourChar[tracked_data.playerGfx >> 4],
WepChar[tracked_data.playerGfx & 0xF],
'a',

18
Source/effects.cpp

@ -278,7 +278,14 @@ void sound_init()
mask |= sfx_MONK;
break;
default:
app_fatal("effects:1");
if (static_cast<size_t>(MyPlayer->_pClass) < GetNumPlayerClasses()) {
// this is a custom class, so we need to add init sounds, since we can't determine which ones will be used by it
mask |= (sfx_WARRIOR | sfx_MONK);
if (!gbIsSpawn)
mask |= (sfx_ROGUE | sfx_SORCERER);
} else {
app_fatal("effects:1");
}
}
}
@ -311,6 +318,15 @@ int GetSFXLength(SfxID nSFX)
return sfx.pSnd->DSB.GetLength();
}
tl::expected<HeroSpeech, std::string> ParseHeroSpeech(std::string_view value)
{
const std::optional<HeroSpeech> enumValueOpt = magic_enum::enum_cast<HeroSpeech>(value);
if (enumValueOpt.has_value()) {
return enumValueOpt.value();
}
return tl::make_unexpected("Unknown enum value.");
}
tl::expected<SfxID, std::string> ParseSfxId(std::string_view value)
{
const std::optional<SfxID> enumValueOpt = magic_enum::enum_cast<SfxID>(value);

1
Source/effects.h

@ -37,6 +37,7 @@ void ui_sound_init();
void effects_play_sound(SfxID);
int GetSFXLength(SfxID nSFX);
tl::expected<HeroSpeech, std::string> ParseHeroSpeech(std::string_view value);
tl::expected<SfxID, std::string> ParseSfxId(std::string_view value);
} // namespace devilution

9
Source/effects_stubs.cpp

@ -50,6 +50,15 @@ void ui_sound_init() { }
void effects_play_sound(SfxID id) { }
int GetSFXLength(SfxID nSFX) { return 0; }
tl::expected<HeroSpeech, std::string> ParseHeroSpeech(std::string_view value)
{
const std::optional<HeroSpeech> enumValueOpt = magic_enum::enum_cast<HeroSpeech>(value);
if (enumValueOpt.has_value()) {
return enumValueOpt.value();
}
return tl::make_unexpected("Unknown enum value.");
}
tl::expected<SfxID, std::string> ParseSfxId(std::string_view value)
{
const std::optional<SfxID> enumValueOpt = magic_enum::enum_cast<SfxID>(value);

27
Source/engine/trn.cpp

@ -9,6 +9,7 @@
#endif
#include "engine/load_file.hpp"
#include "lighting.h"
#include "utils/str_cat.hpp"
namespace devilution {
@ -30,32 +31,14 @@ uint8_t *GetPauseTRN()
std::optional<std::array<uint8_t, 256>> GetClassTRN(Player &player)
{
std::array<uint8_t, 256> trn;
const char *path;
char path[64];
switch (player._pClass) {
case HeroClass::Warrior:
path = "plrgfx\\warrior.trn";
break;
case HeroClass::Rogue:
path = "plrgfx\\rogue.trn";
break;
case HeroClass::Sorcerer:
path = "plrgfx\\sorcerer.trn";
break;
case HeroClass::Monk:
path = "plrgfx\\monk.trn";
break;
case HeroClass::Bard:
path = "plrgfx\\bard.trn";
break;
case HeroClass::Barbarian:
path = "plrgfx\\barbarian.trn";
break;
}
const PlayerSpriteData &spriteData = GetPlayerSpriteDataForClass(player._pClass);
*BufCopy(path, "plrgfx\\", spriteData.trn, ".trn") = '\0';
#ifdef _DEBUG
if (!debugTRN.empty()) {
path = debugTRN.c_str();
*BufCopy(path, debugTRN.c_str()) = '\0';
}
#endif
if (LoadOptionalFileInMem(path, &trn[0], 256)) {

20
Source/inv.cpp

@ -1158,22 +1158,12 @@ void FreeInvGFX()
void InitInv()
{
switch (MyPlayer->_pClass) {
case HeroClass::Warrior:
case HeroClass::Barbarian:
pInvCels = LoadCel("data\\inv\\inv", static_cast<uint16_t>(SidePanelSize.width));
break;
case HeroClass::Rogue:
case HeroClass::Bard:
pInvCels = LoadCel("data\\inv\\inv_rog", static_cast<uint16_t>(SidePanelSize.width));
break;
case HeroClass::Sorcerer:
pInvCels = LoadCel("data\\inv\\inv_sor", static_cast<uint16_t>(SidePanelSize.width));
break;
case HeroClass::Monk:
pInvCels = LoadCel(!gbIsSpawn ? "data\\inv\\inv_sor" : "data\\inv\\inv", static_cast<uint16_t>(SidePanelSize.width));
break;
const PlayerData &playerClassData = GetPlayerDataForClass(MyPlayer->_pClass);
const char *invName = playerClassData.inv.c_str();
if (gbIsSpawn && (playerClassData.inv == "inv_rog" || playerClassData.inv == "inv_sor")) {
invName = "inv";
}
pInvCels = LoadCel(StrCat("data\\inv\\", invName).c_str(), static_cast<uint16_t>(SidePanelSize.width));
}
void DrawInv(const Surface &out)

10
Source/itemdat.cpp

@ -9,7 +9,6 @@
#include <string_view>
#include <vector>
#include <expected.hpp>
#include <fmt/format.h>
#include "data/file.hpp"
@ -42,6 +41,15 @@ std::vector<PLStruct> ItemPrefixes;
/** Contains the data related to each item suffix. */
std::vector<PLStruct> ItemSuffixes;
tl::expected<_item_indexes, std::string> ParseItemId(std::string_view value)
{
const std::optional<_item_indexes> enumValueOpt = magic_enum::enum_cast<_item_indexes>(value);
if (enumValueOpt.has_value()) {
return enumValueOpt.value();
}
return tl::make_unexpected("Unknown enum value");
}
namespace {
tl::expected<item_class, std::string> ParseItemClass(std::string_view value)

9
Source/itemdat.h

@ -10,6 +10,8 @@
#include <vector>
#include <ankerl/unordered_dense.h>
#include <expected.hpp>
#include <magic_enum/magic_enum.hpp>
#include "objdat.h"
#include "spelldat.h"
@ -654,8 +656,15 @@ extern std::vector<PLStruct> ItemSuffixes;
extern DVL_API_FOR_TEST std::vector<UniqueItem> UniqueItems;
extern ankerl::unordered_dense::map<int32_t, int32_t> UniqueItemMappingIdsToIndices;
tl::expected<_item_indexes, std::string> ParseItemId(std::string_view value);
void LoadItemDatFromFile(DataFile &dataFile, std::string_view filename, int32_t baseMappingId);
void LoadUniqueItemDatFromFile(DataFile &dataFile, std::string_view filename, int32_t baseMappingId);
void LoadItemData();
} // namespace devilution
template <>
struct magic_enum::customize::enum_range<devilution::_item_indexes> {
static constexpr int min = devilution::_item_indexes::IDI_NONE;
static constexpr int max = devilution::_item_indexes::IDI_NUM_DEFAULT_ITEMS;
};

3
Source/items.cpp

@ -3012,8 +3012,7 @@ void CreatePlrItems(Player &player)
}
InitCursor();
for (const auto &itemChoice : loadout.items) {
const _item_indexes itemData = gbIsHellfire && itemChoice.hellfire != _item_indexes::IDI_NONE ? itemChoice.hellfire : itemChoice.diablo;
for (const _item_indexes itemData : loadout.items) {
if (itemData != _item_indexes::IDI_NONE)
CreateStartingItem(player, itemData);
}

15
Source/minitext.cpp

@ -136,28 +136,27 @@ void InitQuestText()
void InitQTextMsg(_speech_id m)
{
SfxID sfxnr = Speeches[m].sfxnr;
const SfxID *classSounds = herosounds[static_cast<size_t>(MyPlayer->_pClass)];
switch (sfxnr) {
case SfxID::Warrior1:
sfxnr = classSounds[static_cast<size_t>(HeroSpeech::ChamberOfBoneLore)];
sfxnr = GetHeroSound(MyPlayer->_pClass, HeroSpeech::ChamberOfBoneLore);
break;
case SfxID::Warrior10:
sfxnr = classSounds[static_cast<size_t>(HeroSpeech::ValorLore)];
sfxnr = GetHeroSound(MyPlayer->_pClass, HeroSpeech::ValorLore);
break;
case SfxID::Warrior11:
sfxnr = classSounds[static_cast<size_t>(HeroSpeech::HallsOfTheBlindLore)];
sfxnr = GetHeroSound(MyPlayer->_pClass, HeroSpeech::HallsOfTheBlindLore);
break;
case SfxID::Warrior12:
sfxnr = classSounds[static_cast<size_t>(HeroSpeech::WarlordOfBloodLore)];
sfxnr = GetHeroSound(MyPlayer->_pClass, HeroSpeech::WarlordOfBloodLore);
break;
case SfxID::Warrior54:
sfxnr = classSounds[static_cast<size_t>(HeroSpeech::InSpirituSanctum)];
sfxnr = GetHeroSound(MyPlayer->_pClass, HeroSpeech::InSpirituSanctum);
break;
case SfxID::Warrior55:
sfxnr = classSounds[static_cast<size_t>(HeroSpeech::PraedictumOtium)];
sfxnr = GetHeroSound(MyPlayer->_pClass, HeroSpeech::PraedictumOtium);
break;
case SfxID::Warrior56:
sfxnr = classSounds[static_cast<size_t>(HeroSpeech::EfficioObitusUtInimicus)];
sfxnr = GetHeroSound(MyPlayer->_pClass, HeroSpeech::EfficioObitusUtInimicus);
break;
default:
break;

2
Source/multi.cpp

@ -177,7 +177,7 @@ bool IsNetPlayerValid(const Player &player)
{
// we no longer check character level here, players with out-of-range clevels are not allowed to join the game and we don't observe change clevel messages that would set it out of range
// (there's no code path that would result in _pLevel containing an out of range value in the DevilutionX code)
return static_cast<uint8_t>(player._pClass) < enum_size<HeroClass>::value
return static_cast<uint8_t>(player._pClass) < GetNumPlayerClasses()
&& player.plrlevel < NUMLEVELS
&& InDungeonBounds(player.position.tile)
&& !std::string_view(player._pName).empty();

4
Source/pack.cpp

@ -359,7 +359,7 @@ void UnPackPlayer(const PlayerPack &packed, Player &player)
player.position.future = position;
player.setLevel(std::clamp<int8_t>(packed.plrlevel, 0, NUMLEVELS));
player._pClass = static_cast<HeroClass>(std::clamp<uint8_t>(packed.pClass, 0, enum_size<HeroClass>::value - 1));
player._pClass = static_cast<HeroClass>(std::clamp<uint8_t>(packed.pClass, 0, static_cast<uint8_t>(GetNumPlayerClasses() - 1)));
ClrPlrPath(player);
player.destAction = ACTION_NONE;
@ -464,7 +464,7 @@ bool UnPackNetPlayer(const PlayerNetPack &packed, Player &player)
{
CopyUtf8(player._pName, packed.pName, sizeof(player._pName));
ValidateField(packed.pClass, packed.pClass < enum_size<HeroClass>::value);
ValidateField(packed.pClass, packed.pClass < GetNumPlayerClasses());
player._pClass = static_cast<HeroClass>(packed.pClass);
const Point position { packed.px, packed.py };

20
Source/player.cpp

@ -1486,7 +1486,7 @@ PlayerWeaponGraphic GetPlayerWeaponGraphic(player_graphic graphic, PlayerWeaponG
uint16_t GetPlayerSpriteWidth(HeroClass cls, player_graphic graphic, PlayerWeaponGraphic weaponGraphic)
{
const PlayerSpriteData spriteData = PlayersSpriteData[static_cast<size_t>(cls)];
const PlayerSpriteData spriteData = GetPlayerSpriteDataForClass(cls);
switch (graphic) {
case player_graphic::Stand:
@ -1683,7 +1683,7 @@ int Player::GetPositionPathIndex(Point pos)
void Player::Say(HeroSpeech speechId) const
{
const SfxID soundEffect = herosounds[static_cast<size_t>(_pClass)][static_cast<size_t>(speechId)];
const SfxID soundEffect = GetHeroSound(_pClass, speechId);
if (soundEffect == SfxID::None)
return;
@ -1693,7 +1693,7 @@ void Player::Say(HeroSpeech speechId) const
void Player::SaySpecific(HeroSpeech speechId) const
{
const SfxID soundEffect = herosounds[static_cast<size_t>(_pClass)][static_cast<size_t>(speechId)];
const SfxID soundEffect = GetHeroSound(_pClass, speechId);
if (soundEffect == SfxID::None || effect_is_playing(soundEffect))
return;
@ -1704,7 +1704,7 @@ void Player::SaySpecific(HeroSpeech speechId) const
void Player::Say(HeroSpeech speechId, int delay) const
{
sfxdelay = delay;
sfxdnum = herosounds[static_cast<size_t>(_pClass)][static_cast<size_t>(speechId)];
sfxdnum = GetHeroSound(_pClass, speechId);
}
void Player::Stop()
@ -2048,7 +2048,8 @@ ClxSprite GetPlayerPortraitSprite(Player &player)
const HeroClass cls = GetPlayerSpriteClass(player._pClass);
const PlayerWeaponGraphic animWeaponId = GetPlayerWeaponGraphic(player_graphic::Stand, static_cast<PlayerWeaponGraphic>(player._pgfxnum & 0xF));
const char *path = PlayersSpriteData[static_cast<std::size_t>(cls)].classPath;
const PlayerSpriteData &spriteData = GetPlayerSpriteDataForClass(cls);
const char *path = spriteData.classPath.c_str();
const char *szCel;
if (!inDungeon)
@ -2064,7 +2065,7 @@ ClxSprite GetPlayerPortraitSprite(Player &player)
}
}
char prefix[3] = { CharChar[static_cast<std::size_t>(cls)], ArmourChar[player._pgfxnum >> 4], WepChar[static_cast<std::size_t>(animWeaponId)] };
char prefix[3] = { spriteData.classChar, ArmourChar[player._pgfxnum >> 4], WepChar[static_cast<std::size_t>(animWeaponId)] };
char pszName[256];
*fmt::format_to(pszName, R"(plrgfx\{0}\{1}\{1}{2})", path, std::string_view(prefix, 3), szCel) = 0;
@ -2103,7 +2104,8 @@ void LoadPlrGFX(Player &player, player_graphic graphic)
const HeroClass cls = GetPlayerSpriteClass(player._pClass);
const PlayerWeaponGraphic animWeaponId = GetPlayerWeaponGraphic(graphic, static_cast<PlayerWeaponGraphic>(player._pgfxnum & 0xF));
const char *path = PlayersSpriteData[static_cast<std::size_t>(cls)].classPath;
const PlayerSpriteData &spriteData = GetPlayerSpriteDataForClass(cls);
const char *path = spriteData.classPath.c_str();
const char *szCel;
switch (graphic) {
@ -2152,7 +2154,7 @@ void LoadPlrGFX(Player &player, player_graphic graphic)
app_fatal("PLR:2");
}
char prefix[3] = { CharChar[static_cast<std::size_t>(cls)], ArmourChar[player._pgfxnum >> 4], WepChar[static_cast<std::size_t>(animWeaponId)] };
char prefix[3] = { spriteData.classChar, ArmourChar[player._pgfxnum >> 4], WepChar[static_cast<std::size_t>(animWeaponId)] };
char pszName[256];
*fmt::format_to(pszName, R"(plrgfx\{0}\{1}\{1}{2})", path, std::string_view(prefix, 3), szCel) = 0;
const uint16_t animationWidth = GetPlayerSpriteWidth(cls, graphic, animWeaponId);
@ -2224,7 +2226,7 @@ void NewPlrAnim(Player &player, player_graphic graphic, Direction dir, Animation
void SetPlrAnims(Player &player)
{
const HeroClass pc = player._pClass;
const PlayerAnimData plrAtkAnimData = PlayersAnimData[static_cast<uint8_t>(pc)];
const PlayerAnimData &plrAtkAnimData = GetPlayerAnimDataForClass(pc);
auto gn = static_cast<PlayerWeaponGraphic>(player._pgfxnum & 0xFU);
if (leveltype == DTYPE_TOWN) {

10
Source/player.h

@ -183,16 +183,6 @@ constexpr std::array<char, 9> WepChar = {
't', // staff
};
/** Maps from player class to letter used in graphic files. */
constexpr std::array<char, 6> CharChar = {
'w', // warrior
'r', // rogue
's', // sorcerer
'm', // monk
'b', // bard
'c', // barbarian
};
/**
* @brief Contains Data (CelSprites) for a player graphic (player_graphic)
*/

351
Source/playerdat.cpp

@ -15,8 +15,11 @@
#include <expected.hpp>
#include <fmt/format.h>
#include <magic_enum/magic_enum_utility.hpp>
#include "data/file.hpp"
#include "data/record_reader.hpp"
#include "data/value_reader.hpp"
#include "items.h"
#include "player.h"
#include "textdat.h"
@ -156,123 +159,204 @@ void ReloadExperienceData()
void LoadClassData(std::string_view classPath, ClassAttributes &attributes, PlayerCombatData &combat)
{
const std::string filename = StrCat("txtdata\\classes\\", classPath, "\\attributes.tsv");
tl::expected<DataFile, DataFile::Error> dataFileResult = DataFile::load(filename);
if (!dataFileResult.has_value()) {
DataFile::reportFatalError(dataFileResult.error(), filename);
}
tl::expected<DataFile, DataFile::Error> dataFileResult = DataFile::loadOrDie(filename);
DataFile &dataFile = dataFileResult.value();
dataFile.skipHeaderOrDie(filename);
ValueReader reader { dataFile, filename };
reader.readInt("baseStr", attributes.baseStr);
reader.readInt("baseMag", attributes.baseMag);
reader.readInt("baseDex", attributes.baseDex);
reader.readInt("baseVit", attributes.baseVit);
reader.readInt("maxStr", attributes.maxStr);
reader.readInt("maxMag", attributes.maxMag);
reader.readInt("maxDex", attributes.maxDex);
reader.readInt("maxVit", attributes.maxVit);
reader.readInt("blockBonus", combat.baseToBlock);
reader.readDecimal("adjLife", attributes.adjLife);
reader.readDecimal("adjMana", attributes.adjMana);
reader.readDecimal("lvlLife", attributes.lvlLife);
reader.readDecimal("lvlMana", attributes.lvlMana);
reader.readDecimal("chrLife", attributes.chrLife);
reader.readDecimal("chrMana", attributes.chrMana);
reader.readDecimal("itmLife", attributes.itmLife);
reader.readDecimal("itmMana", attributes.itmMana);
reader.readInt("baseMagicToHit", combat.baseMagicToHit);
reader.readInt("baseMeleeToHit", combat.baseMeleeToHit);
reader.readInt("baseRangedToHit", combat.baseRangedToHit);
}
if (tl::expected<void, DataFile::Error> result = dataFile.skipHeader();
!result.has_value()) {
DataFile::reportFatalError(result.error(), filename);
void LoadClassStartingLoadoutData(std::string_view classPath, PlayerStartingLoadoutData &startingLoadoutData)
{
const std::string filename = StrCat("txtdata\\classes\\", classPath, "\\starting_loadout.tsv");
tl::expected<DataFile, DataFile::Error> dataFileResult = DataFile::loadOrDie(filename);
DataFile &dataFile = dataFileResult.value();
dataFile.skipHeaderOrDie(filename);
ValueReader reader { dataFile, filename };
reader.read("skill", startingLoadoutData.skill, ParseSpellId);
reader.read("spell", startingLoadoutData.spell, ParseSpellId);
reader.readInt("spellLevel", startingLoadoutData.spellLevel);
for (size_t i = 0; i < startingLoadoutData.items.size(); ++i) {
reader.read(StrCat("item", i), startingLoadoutData.items[i], ParseItemId);
}
reader.readInt("gold", startingLoadoutData.gold);
}
auto recordIt = dataFile.begin();
const auto recordEnd = dataFile.end();
void LoadClassSpriteData(std::string_view classPath, PlayerSpriteData &spriteData)
{
const std::string filename = StrCat("txtdata\\classes\\", classPath, "\\sprites.tsv");
tl::expected<DataFile, DataFile::Error> dataFileResult = DataFile::loadOrDie(filename);
DataFile &dataFile = dataFileResult.value();
dataFile.skipHeaderOrDie(filename);
ValueReader reader { dataFile, filename };
reader.readString("classPath", spriteData.classPath);
reader.readChar("classChar", spriteData.classChar);
reader.readString("trn", spriteData.trn);
reader.readInt("stand", spriteData.stand);
reader.readInt("walk", spriteData.walk);
reader.readInt("attack", spriteData.attack);
reader.readInt("bow", spriteData.bow);
reader.readInt("swHit", spriteData.swHit);
reader.readInt("block", spriteData.block);
reader.readInt("lightning", spriteData.lightning);
reader.readInt("fire", spriteData.fire);
reader.readInt("magic", spriteData.magic);
reader.readInt("death", spriteData.death);
}
const auto getValueField = [&](std::string_view expectedKey) {
if (recordIt == recordEnd) {
app_fatal(fmt::format("Missing field {} in {}", expectedKey, filename));
}
DataFileRecord record = *recordIt;
FieldIterator fieldIt = record.begin();
const FieldIterator endField = record.end();
void LoadClassAnimData(std::string_view classPath, PlayerAnimData &animData)
{
const std::string filename = StrCat("txtdata\\classes\\", classPath, "\\animations.tsv");
tl::expected<DataFile, DataFile::Error> dataFileResult = DataFile::loadOrDie(filename);
DataFile &dataFile = dataFileResult.value();
dataFile.skipHeaderOrDie(filename);
ValueReader reader { dataFile, filename };
reader.readInt("unarmedFrames", animData.unarmedFrames);
reader.readInt("unarmedActionFrame", animData.unarmedActionFrame);
reader.readInt("unarmedShieldFrames", animData.unarmedShieldFrames);
reader.readInt("unarmedShieldActionFrame", animData.unarmedShieldActionFrame);
reader.readInt("swordFrames", animData.swordFrames);
reader.readInt("swordActionFrame", animData.swordActionFrame);
reader.readInt("swordShieldFrames", animData.swordShieldFrames);
reader.readInt("swordShieldActionFrame", animData.swordShieldActionFrame);
reader.readInt("bowFrames", animData.bowFrames);
reader.readInt("bowActionFrame", animData.bowActionFrame);
reader.readInt("axeFrames", animData.axeFrames);
reader.readInt("axeActionFrame", animData.axeActionFrame);
reader.readInt("maceFrames", animData.maceFrames);
reader.readInt("maceActionFrame", animData.maceActionFrame);
reader.readInt("maceShieldFrames", animData.maceShieldFrames);
reader.readInt("maceShieldActionFrame", animData.maceShieldActionFrame);
reader.readInt("staffFrames", animData.staffFrames);
reader.readInt("staffActionFrame", animData.staffActionFrame);
reader.readInt("idleFrames", animData.idleFrames);
reader.readInt("walkingFrames", animData.walkingFrames);
reader.readInt("blockingFrames", animData.blockingFrames);
reader.readInt("deathFrames", animData.deathFrames);
reader.readInt("castingFrames", animData.castingFrames);
reader.readInt("recoveryFrames", animData.recoveryFrames);
reader.readInt("townIdleFrames", animData.townIdleFrames);
reader.readInt("townWalkingFrames", animData.townWalkingFrames);
reader.readInt("castingActionFrame", animData.castingActionFrame);
}
const std::string_view key = (*fieldIt).value();
if (key != expectedKey) {
app_fatal(fmt::format("Unexpected field in {}: got {}, expected {}", filename, key, expectedKey));
}
void LoadClassSounds(std::string_view classPath, ankerl::unordered_dense::map<HeroSpeech, SfxID> &sounds)
{
const std::string filename = StrCat("txtdata\\classes\\", classPath, "\\sounds.tsv");
tl::expected<DataFile, DataFile::Error> dataFileResult = DataFile::loadOrDie(filename);
DataFile &dataFile = dataFileResult.value();
dataFile.skipHeaderOrDie(filename);
++fieldIt;
if (fieldIt == endField) {
DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename);
}
return *fieldIt;
};
const auto valueReader = [&](auto &&readFn) {
return [&](std::string_view expectedKey, auto &outValue) {
DataFileField valueField = getValueField(expectedKey);
if (const tl::expected<void, devilution::DataFileField::Error> result = readFn(valueField, outValue);
!result.has_value()) {
DataFile::reportFatalFieldError(result.error(), filename, "Value", valueField);
}
++recordIt;
};
};
ValueReader reader { dataFile, filename };
const auto readInt = valueReader([](DataFileField &valueField, auto &outValue) {
return valueField.parseInt(outValue);
magic_enum::enum_for_each<HeroSpeech>([&](const HeroSpeech speech) {
reader.read(magic_enum::enum_name(speech), sounds[speech], ParseSfxId);
});
const auto readDecimal = valueReader([](DataFileField &valueField, auto &outValue) {
return valueField.parseFixed6(outValue);
});
readInt("baseStr", attributes.baseStr);
readInt("baseMag", attributes.baseMag);
readInt("baseDex", attributes.baseDex);
readInt("baseVit", attributes.baseVit);
readInt("maxStr", attributes.maxStr);
readInt("maxMag", attributes.maxMag);
readInt("maxDex", attributes.maxDex);
readInt("maxVit", attributes.maxVit);
readInt("blockBonus", combat.baseToBlock);
readDecimal("adjLife", attributes.adjLife);
readDecimal("adjMana", attributes.adjMana);
readDecimal("lvlLife", attributes.lvlLife);
readDecimal("lvlMana", attributes.lvlMana);
readDecimal("chrLife", attributes.chrLife);
readDecimal("chrMana", attributes.chrMana);
readDecimal("itmLife", attributes.itmLife);
readDecimal("itmMana", attributes.itmMana);
readInt("baseMagicToHit", combat.baseMagicToHit);
readInt("baseMeleeToHit", combat.baseMeleeToHit);
readInt("baseRangedToHit", combat.baseRangedToHit);
}
/** Contains the data related to each player class. */
std::vector<PlayerData> PlayersData;
std::vector<ClassAttributes> ClassAttributesPerClass;
std::vector<PlayerCombatData> PlayersCombatData;
std::vector<PlayerStartingLoadoutData> PlayersStartingLoadoutData;
/** Contains the data related to each player class. */
std::vector<PlayerSpriteData> PlayersSpriteData;
std::vector<PlayerAnimData> PlayersAnimData;
std::vector<ankerl::unordered_dense::map<HeroSpeech, SfxID>> herosounds;
} // namespace
void LoadClassDatFromFile(DataFile &dataFile, const std::string_view filename)
{
dataFile.skipHeaderOrDie(filename);
PlayersData.reserve(PlayersData.size() + dataFile.numRecords());
for (DataFileRecord record : dataFile) {
if (PlayersData.size() >= static_cast<size_t>(HeroClass::NUM_MAX_CLASSES)) {
DisplayFatalErrorAndExit(_("Loading Class Data Failed"), fmt::format(fmt::runtime(_("Could not add a class, since the maximum class number of {} has already been reached.")), static_cast<size_t>(HeroClass::NUM_MAX_CLASSES)));
}
RecordReader reader { record, filename };
PlayerData &playerData = PlayersData.emplace_back();
reader.readString("className", playerData.className);
reader.readString("folderName", playerData.folderName);
reader.readInt("portrait", playerData.portrait);
reader.readString("inv", playerData.inv);
}
}
namespace {
void LoadClassDat()
{
const std::string_view filename = "txtdata\\classes\\classdat.tsv";
DataFile dataFile = DataFile::loadOrDie(filename);
PlayersData.clear();
LoadClassDatFromFile(dataFile, filename);
PlayersData.shrink_to_fit();
}
void LoadClassesAttributes()
{
const std::array classPaths { "warrior", "rogue", "sorcerer", "monk", "bard", "barbarian" };
ClassAttributesPerClass.clear();
ClassAttributesPerClass.reserve(classPaths.size());
ClassAttributesPerClass.reserve(PlayersData.size());
PlayersCombatData.clear();
PlayersCombatData.reserve(classPaths.size());
for (const std::string_view path : classPaths) {
LoadClassData(path, ClassAttributesPerClass.emplace_back(), PlayersCombatData.emplace_back());
PlayersCombatData.reserve(PlayersData.size());
PlayersStartingLoadoutData.clear();
PlayersStartingLoadoutData.reserve(PlayersData.size());
PlayersSpriteData.clear();
PlayersSpriteData.reserve(PlayersData.size());
PlayersAnimData.clear();
PlayersAnimData.reserve(PlayersData.size());
herosounds.clear();
herosounds.reserve(PlayersData.size());
for (const PlayerData &playerData : PlayersData) {
LoadClassData(playerData.folderName, ClassAttributesPerClass.emplace_back(), PlayersCombatData.emplace_back());
LoadClassStartingLoadoutData(playerData.folderName, PlayersStartingLoadoutData.emplace_back());
LoadClassSpriteData(playerData.folderName, PlayersSpriteData.emplace_back());
LoadClassAnimData(playerData.folderName, PlayersAnimData.emplace_back());
LoadClassSounds(playerData.folderName, herosounds.emplace_back());
}
}
/** Contains the data related to each player class. */
const PlayerData PlayersData[] = {
// clang-format off
// HeroClass className
// TRANSLATORS: Player Block start
/* HeroClass::Warrior */ { N_("Warrior"), },
/* HeroClass::Rogue */ { N_("Rogue"), },
/* HeroClass::Sorcerer */ { N_("Sorcerer"), },
/* HeroClass::Monk */ { N_("Monk"), },
/* HeroClass::Bard */ { N_("Bard"), },
// TRANSLATORS: Player Block end
/* HeroClass::Barbarian */ { N_("Barbarian"), },
// clang-format on
};
const std::array<PlayerStartingLoadoutData, enum_size<HeroClass>::value> PlayersStartingLoadoutData { {
// clang-format off
// HeroClass skill, spell, spellLevel, items[0].diablo, items[0].hellfire, items[1].diablo, items[1].hellfire, items[2].diablo, items[2].hellfire, items[3].diablo, items[3].hellfire, items[4].diablo, items[4].hellfire, gold,
/* HeroClass::Warrior */ { SpellID::ItemRepair, SpellID::Null, 0, { { { IDI_WARRIOR, IDI_WARRIOR, }, { IDI_WARRSHLD, IDI_WARRSHLD, }, { IDI_WARRCLUB, IDI_WARRCLUB, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, }, }, 100, },
/* HeroClass::Rogue */ { SpellID::TrapDisarm, SpellID::Null, 0, { { { IDI_ROGUE, IDI_ROGUE, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, { IDI_NONE, IDI_NONE, }, }, }, 100, },
/* HeroClass::Sorcerer */ { SpellID::StaffRecharge, SpellID::Firebolt, 2, { { { IDI_SORCERER_DIABLO, IDI_SORCERER, }, { IDI_MANA, IDI_HEAL, }, { IDI_MANA, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, { IDI_NONE, IDI_NONE, }, }, }, 100, },
/* HeroClass::Monk */ { SpellID::Search, SpellID::Null, 0, { { { IDI_SHORTSTAFF, IDI_SHORTSTAFF, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, { IDI_NONE, IDI_NONE, }, }, }, 100, },
/* HeroClass::Bard */ { SpellID::Identify, SpellID::Null, 0, { { { IDI_BARDSWORD, IDI_BARDSWORD, }, { IDI_BARDDAGGER, IDI_BARDDAGGER, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, }, }, 100, },
/* HeroClass::Barbarian */ { SpellID::Rage, SpellID::Null, 0, { { { IDI_BARBARIAN, IDI_BARBARIAN, }, { IDI_WARRSHLD, IDI_WARRSHLD, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, }, }, 100, }
// clang-format on
} };
} // namespace
const ClassAttributes &GetClassAttributes(HeroClass playerClass)
@ -283,9 +367,22 @@ const ClassAttributes &GetClassAttributes(HeroClass playerClass)
void LoadPlayerDataFiles()
{
ReloadExperienceData();
LoadClassDat();
LoadClassesAttributes();
}
SfxID GetHeroSound(HeroClass clazz, HeroSpeech speech)
{
const size_t playerClassIndex = static_cast<size_t>(clazz);
assert(playerClassIndex < herosounds.size());
const auto findIt = herosounds[playerClassIndex].find(speech);
if (findIt != herosounds[playerClassIndex].end()) {
return findIt->second;
}
return SfxID::None;
}
uint32_t GetNextExperienceThresholdForLevel(unsigned level)
{
return ExperienceData.getThresholdForLevel(level);
@ -296,56 +393,44 @@ uint8_t GetMaximumCharacterLevel()
return ExperienceData.getMaxLevel();
}
const PlayerData &GetPlayerDataForClass(HeroClass playerClass)
size_t GetNumPlayerClasses()
{
return PlayersData[static_cast<size_t>(playerClass)];
return PlayersData.size();
}
const SfxID herosounds[enum_size<HeroClass>::value][enum_size<HeroSpeech>::value] = {
// clang-format off
{ SfxID::Warrior1, SfxID::Warrior2, SfxID::Warrior3, SfxID::Warrior4, SfxID::Warrior5, SfxID::Warrior6, SfxID::Warrior7, SfxID::Warrior8, SfxID::Warrior9, SfxID::Warrior10, SfxID::Warrior11, SfxID::Warrior12, SfxID::Warrior13, SfxID::Warrior14, SfxID::Warrior15, SfxID::Warrior16, SfxID::Warrior17, SfxID::Warrior18, SfxID::Warrior19, SfxID::Warrior20, SfxID::Warrior21, SfxID::Warrior22, SfxID::Warrior23, SfxID::Warrior24, SfxID::Warrior25, SfxID::Warrior26, SfxID::Warrior27, SfxID::Warrior28, SfxID::Warrior29, SfxID::Warrior30, SfxID::Warrior31, SfxID::Warrior32, SfxID::Warrior33, SfxID::Warrior34, SfxID::Warrior35, SfxID::Warrior36, SfxID::Warrior37, SfxID::Warrior38, SfxID::Warrior39, SfxID::Warrior40, SfxID::Warrior41, SfxID::Warrior42, SfxID::Warrior43, SfxID::Warrior44, SfxID::Warrior45, SfxID::Warrior46, SfxID::Warrior47, SfxID::Warrior48, SfxID::Warrior49, SfxID::Warrior50, SfxID::Warrior51, SfxID::Warrior52, SfxID::Warrior53, SfxID::Warrior54, SfxID::Warrior55, SfxID::Warrior56, SfxID::Warrior57, SfxID::Warrior58, SfxID::Warrior59, SfxID::Warrior60, SfxID::Warrior61, SfxID::Warrior62, SfxID::Warrior63, SfxID::Warrior64, SfxID::Warrior65, SfxID::Warrior66, SfxID::Warrior67, SfxID::Warrior68, SfxID::Warrior69, SfxID::Warrior70, SfxID::Warrior71, SfxID::Warrior72, SfxID::Warrior73, SfxID::Warrior74, SfxID::Warrior75, SfxID::Warrior76, SfxID::Warrior77, SfxID::Warrior78, SfxID::Warrior79, SfxID::Warrior80, SfxID::Warrior81, SfxID::Warrior82, SfxID::Warrior83, SfxID::Warrior84, SfxID::Warrior85, SfxID::Warrior86, SfxID::Warrior87, SfxID::Warrior88, SfxID::Warrior89, SfxID::Warrior90, SfxID::Warrior91, SfxID::Warrior92, SfxID::Warrior93, SfxID::Warrior94, SfxID::Warrior95, SfxID::Warrior96b, SfxID::Warrior97, SfxID::Warrior98, SfxID::Warrior99, SfxID::Warrior100, SfxID::Warrior101, SfxID::Warrior102, SfxID::WarriorDeath },
{ SfxID::Rogue1, SfxID::Rogue2, SfxID::Rogue3, SfxID::Rogue4, SfxID::Rogue5, SfxID::Rogue6, SfxID::Rogue7, SfxID::Rogue8, SfxID::Rogue9, SfxID::Rogue10, SfxID::Rogue11, SfxID::Rogue12, SfxID::Rogue13, SfxID::Rogue14, SfxID::Rogue15, SfxID::Rogue16, SfxID::Rogue17, SfxID::Rogue18, SfxID::Rogue19, SfxID::Rogue20, SfxID::Rogue21, SfxID::Rogue22, SfxID::Rogue23, SfxID::Rogue24, SfxID::Rogue25, SfxID::Rogue26, SfxID::Rogue27, SfxID::Rogue28, SfxID::Rogue29, SfxID::Rogue30, SfxID::Rogue31, SfxID::Rogue32, SfxID::Rogue33, SfxID::Rogue34, SfxID::Rogue35, SfxID::Rogue36, SfxID::Rogue37, SfxID::Rogue38, SfxID::Rogue39, SfxID::Rogue40, SfxID::Rogue41, SfxID::Rogue42, SfxID::Rogue43, SfxID::Rogue44, SfxID::Rogue45, SfxID::Rogue46, SfxID::Rogue47, SfxID::Rogue48, SfxID::Rogue49, SfxID::Rogue50, SfxID::Rogue51, SfxID::Rogue52, SfxID::Rogue53, SfxID::Rogue54, SfxID::Rogue55, SfxID::Rogue56, SfxID::Rogue57, SfxID::Rogue58, SfxID::Rogue59, SfxID::Rogue60, SfxID::Rogue61, SfxID::Rogue62, SfxID::Rogue63, SfxID::Rogue64, SfxID::Rogue65, SfxID::Rogue66, SfxID::Rogue67, SfxID::Rogue68, SfxID::Rogue69, SfxID::Rogue70, SfxID::Rogue71, SfxID::Rogue72, SfxID::Rogue73, SfxID::Rogue74, SfxID::Rogue75, SfxID::Rogue76, SfxID::Rogue77, SfxID::Rogue78, SfxID::Rogue79, SfxID::Rogue80, SfxID::Rogue81, SfxID::Rogue82, SfxID::Rogue83, SfxID::Rogue84, SfxID::Rogue85, SfxID::Rogue86, SfxID::Rogue87, SfxID::Rogue88, SfxID::Rogue89, SfxID::Rogue90, SfxID::Rogue91, SfxID::Rogue92, SfxID::Rogue93, SfxID::Rogue94, SfxID::Rogue95, SfxID::Rogue96, SfxID::Rogue97, SfxID::Rogue98, SfxID::Rogue99, SfxID::Rogue100, SfxID::Rogue101, SfxID::Rogue102, SfxID::Rogue71 },
{ SfxID::Sorceror1, SfxID::Sorceror2, SfxID::Sorceror3, SfxID::Sorceror4, SfxID::Sorceror5, SfxID::Sorceror6, SfxID::Sorceror7, SfxID::Sorceror8, SfxID::Sorceror9, SfxID::Sorceror10, SfxID::Sorceror11, SfxID::Sorceror12, SfxID::Sorceror13, SfxID::Sorceror14, SfxID::Sorceror15, SfxID::Sorceror16, SfxID::Sorceror17, SfxID::Sorceror18, SfxID::Sorceror19, SfxID::Sorceror20, SfxID::Sorceror21, SfxID::Sorceror22, SfxID::Sorceror23, SfxID::Sorceror24, SfxID::Sorceror25, SfxID::Sorceror26, SfxID::Sorceror27, SfxID::Sorceror28, SfxID::Sorceror29, SfxID::Sorceror30, SfxID::Sorceror31, SfxID::Sorceror32, SfxID::Sorceror33, SfxID::Sorceror34, SfxID::Sorceror35, SfxID::Sorceror36, SfxID::Sorceror37, SfxID::Sorceror38, SfxID::Sorceror39, SfxID::Sorceror40, SfxID::Sorceror41, SfxID::Sorceror42, SfxID::Sorceror43, SfxID::Sorceror44, SfxID::Sorceror45, SfxID::Sorceror46, SfxID::Sorceror47, SfxID::Sorceror48, SfxID::Sorceror49, SfxID::Sorceror50, SfxID::Sorceror51, SfxID::Sorceror52, SfxID::Sorceror53, SfxID::Sorceror54, SfxID::Sorceror55, SfxID::Sorceror56, SfxID::Sorceror57, SfxID::Sorceror58, SfxID::Sorceror59, SfxID::Sorceror60, SfxID::Sorceror61, SfxID::Sorceror62, SfxID::Sorceror63, SfxID::Sorceror64, SfxID::Sorceror65, SfxID::Sorceror66, SfxID::Sorceror67, SfxID::Sorceror68, SfxID::Sorceror69, SfxID::Sorceror70, SfxID::Sorceror71, SfxID::Sorceror72, SfxID::Sorceror73, SfxID::Sorceror74, SfxID::Sorceror75, SfxID::Sorceror76, SfxID::Sorceror77, SfxID::Sorceror78, SfxID::Sorceror79, SfxID::Sorceror80, SfxID::Sorceror81, SfxID::Sorceror82, SfxID::Sorceror83, SfxID::Sorceror84, SfxID::Sorceror85, SfxID::Sorceror86, SfxID::Sorceror87, SfxID::Sorceror88, SfxID::Sorceror89, SfxID::Sorceror90, SfxID::Sorceror91, SfxID::Sorceror92, SfxID::Sorceror93, SfxID::Sorceror94, SfxID::Sorceror95, SfxID::Sorceror96, SfxID::Sorceror97, SfxID::Sorceror98, SfxID::Sorceror99, SfxID::Sorceror100, SfxID::Sorceror101, SfxID::Sorceror102, SfxID::Sorceror71 },
{ SfxID::Monk1, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::Monk8, SfxID::Monk9, SfxID::Monk10, SfxID::Monk11, SfxID::Monk12, SfxID::Monk13, SfxID::Monk14, SfxID::Monk15, SfxID::Monk16, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::Monk24, SfxID::None, SfxID::None, SfxID::Monk27, SfxID::None, SfxID::Monk29, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::Monk34, SfxID::Monk35, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::Monk43, SfxID::None, SfxID::None, SfxID::Monk46, SfxID::None, SfxID::None, SfxID::Monk49, SfxID::Monk50, SfxID::None, SfxID::Monk52, SfxID::None, SfxID::Monk54, SfxID::Monk55, SfxID::Monk56, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::Monk61, SfxID::Monk62, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::Monk68, SfxID::Monk69, SfxID::Monk70, SfxID::Monk71, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::Monk79, SfxID::Monk80, SfxID::None, SfxID::Monk82, SfxID::Monk83, SfxID::None, SfxID::None, SfxID::None, SfxID::Monk87, SfxID::Monk88, SfxID::Monk89, SfxID::None, SfxID::Monk91, SfxID::Monk92, SfxID::None, SfxID::Monk94, SfxID::Monk95, SfxID::Monk96, SfxID::Monk97, SfxID::Monk98, SfxID::Monk99, SfxID::None, SfxID::None, SfxID::None, SfxID::Monk71 },
{ SfxID::Rogue1, SfxID::Rogue2, SfxID::Rogue3, SfxID::Rogue4, SfxID::Rogue5, SfxID::Rogue6, SfxID::Rogue7, SfxID::Rogue8, SfxID::Rogue9, SfxID::Rogue10, SfxID::Rogue11, SfxID::Rogue12, SfxID::Rogue13, SfxID::Rogue14, SfxID::Rogue15, SfxID::Rogue16, SfxID::Rogue17, SfxID::Rogue18, SfxID::Rogue19, SfxID::Rogue20, SfxID::Rogue21, SfxID::Rogue22, SfxID::Rogue23, SfxID::Rogue24, SfxID::Rogue25, SfxID::Rogue26, SfxID::Rogue27, SfxID::Rogue28, SfxID::Rogue29, SfxID::Rogue30, SfxID::Rogue31, SfxID::Rogue32, SfxID::Rogue33, SfxID::Rogue34, SfxID::Rogue35, SfxID::Rogue36, SfxID::Rogue37, SfxID::Rogue38, SfxID::Rogue39, SfxID::Rogue40, SfxID::Rogue41, SfxID::Rogue42, SfxID::Rogue43, SfxID::Rogue44, SfxID::Rogue45, SfxID::Rogue46, SfxID::Rogue47, SfxID::Rogue48, SfxID::Rogue49, SfxID::Rogue50, SfxID::Rogue51, SfxID::Rogue52, SfxID::Rogue53, SfxID::Rogue54, SfxID::Rogue55, SfxID::Rogue56, SfxID::Rogue57, SfxID::Rogue58, SfxID::Rogue59, SfxID::Rogue60, SfxID::Rogue61, SfxID::Rogue62, SfxID::Rogue63, SfxID::Rogue64, SfxID::Rogue65, SfxID::Rogue66, SfxID::Rogue67, SfxID::Rogue68, SfxID::Rogue69, SfxID::Rogue70, SfxID::Rogue71, SfxID::Rogue72, SfxID::Rogue73, SfxID::Rogue74, SfxID::Rogue75, SfxID::Rogue76, SfxID::Rogue77, SfxID::Rogue78, SfxID::Rogue79, SfxID::Rogue80, SfxID::Rogue81, SfxID::Rogue82, SfxID::Rogue83, SfxID::Rogue84, SfxID::Rogue85, SfxID::Rogue86, SfxID::Rogue87, SfxID::Rogue88, SfxID::Rogue89, SfxID::Rogue90, SfxID::Rogue91, SfxID::Rogue92, SfxID::Rogue93, SfxID::Rogue94, SfxID::Rogue95, SfxID::Rogue96, SfxID::Rogue97, SfxID::Rogue98, SfxID::Rogue99, SfxID::Rogue100, SfxID::Rogue101, SfxID::Rogue102, SfxID::Rogue71 },
{ SfxID::Warrior1, SfxID::Warrior2, SfxID::Warrior3, SfxID::Warrior4, SfxID::Warrior5, SfxID::Warrior6, SfxID::Warrior7, SfxID::Warrior8, SfxID::Warrior9, SfxID::Warrior10, SfxID::Warrior11, SfxID::Warrior12, SfxID::Warrior13, SfxID::Warrior14, SfxID::Warrior15, SfxID::Warrior16, SfxID::Warrior17, SfxID::Warrior18, SfxID::Warrior19, SfxID::Warrior20, SfxID::Warrior21, SfxID::Warrior22, SfxID::Warrior23, SfxID::Warrior24, SfxID::Warrior25, SfxID::Warrior26, SfxID::Warrior27, SfxID::Warrior28, SfxID::Warrior29, SfxID::Warrior30, SfxID::Warrior31, SfxID::Warrior32, SfxID::Warrior33, SfxID::Warrior34, SfxID::Warrior35, SfxID::Warrior36, SfxID::Warrior37, SfxID::Warrior38, SfxID::Warrior39, SfxID::Warrior40, SfxID::Warrior41, SfxID::Warrior42, SfxID::Warrior43, SfxID::Warrior44, SfxID::Warrior45, SfxID::Warrior46, SfxID::Warrior47, SfxID::Warrior48, SfxID::Warrior49, SfxID::Warrior50, SfxID::Warrior51, SfxID::Warrior52, SfxID::Warrior53, SfxID::Warrior54, SfxID::Warrior55, SfxID::Warrior56, SfxID::Warrior57, SfxID::Warrior58, SfxID::Warrior59, SfxID::Warrior60, SfxID::Warrior61, SfxID::Warrior62, SfxID::Warrior63, SfxID::Warrior64, SfxID::Warrior65, SfxID::Warrior66, SfxID::Warrior67, SfxID::Warrior68, SfxID::Warrior69, SfxID::Warrior70, SfxID::Warrior71, SfxID::Warrior72, SfxID::Warrior73, SfxID::Warrior74, SfxID::Warrior75, SfxID::Warrior76, SfxID::Warrior77, SfxID::Warrior78, SfxID::Warrior79, SfxID::Warrior80, SfxID::Warrior81, SfxID::Warrior82, SfxID::Warrior83, SfxID::Warrior84, SfxID::Warrior85, SfxID::Warrior86, SfxID::Warrior87, SfxID::Warrior88, SfxID::Warrior89, SfxID::Warrior90, SfxID::Warrior91, SfxID::Warrior92, SfxID::Warrior93, SfxID::Warrior94, SfxID::Warrior95, SfxID::Warrior96b, SfxID::Warrior97, SfxID::Warrior98, SfxID::Warrior99, SfxID::Warrior100, SfxID::Warrior101, SfxID::Warrior102, SfxID::Warrior71 },
// clang-format on
};
const PlayerData &GetPlayerDataForClass(HeroClass playerClass)
{
const size_t playerClassIndex = static_cast<size_t>(playerClass);
assert(playerClassIndex < PlayersData.size());
return PlayersData[playerClassIndex];
}
const PlayerCombatData &GetPlayerCombatDataForClass(HeroClass pClass)
{
return PlayersCombatData[static_cast<size_t>(pClass)];
const size_t playerClassIndex = static_cast<size_t>(pClass);
assert(playerClassIndex < PlayersCombatData.size());
return PlayersCombatData[playerClassIndex];
}
const PlayerStartingLoadoutData &GetPlayerStartingLoadoutForClass(HeroClass pClass)
{
return PlayersStartingLoadoutData[static_cast<size_t>(pClass)];
const size_t playerClassIndex = static_cast<size_t>(pClass);
assert(playerClassIndex < PlayersStartingLoadoutData.size());
return PlayersStartingLoadoutData[playerClassIndex];
}
/** Contains the data related to each player class. */
const PlayerSpriteData PlayersSpriteData[] = {
// clang-format off
// HeroClass classPath, stand, walk, attack, bow, swHit, block, lightning, fire, magic, death
/* HeroClass::Warrior */ { "warrior", 96, 96, 128, 96, 96, 96, 96, 96, 96, 128 },
/* HeroClass::Rogue */ { "rogue", 96, 96, 128, 128, 96, 96, 96, 96, 96, 128 },
/* HeroClass::Sorcerer */ { "sorceror", 96, 96, 128, 128, 96, 96, 128, 128, 128, 128 },
/* HeroClass::Monk */ { "monk", 112, 112, 130, 130, 98, 98, 114, 114, 114, 160 },
/* HeroClass::Bard */ { "rogue", 96, 96, 128, 128, 96, 96, 96, 96, 96, 128 },
/* HeroClass::Barbarian */ { "warrior", 96, 96, 128, 96, 96, 96, 96, 96, 96, 128 },
// clang-format on
};
const PlayerSpriteData &GetPlayerSpriteDataForClass(HeroClass pClass)
{
const size_t playerClassIndex = static_cast<size_t>(pClass);
assert(playerClassIndex < PlayersSpriteData.size());
return PlayersSpriteData[playerClassIndex];
}
const PlayerAnimData PlayersAnimData[] = {
// clang-format off
// HeroClass unarmedFrames, unarmedActionFrame, unarmedShieldFrames, unarmedShieldActionFrame, swordFrames, swordActionFrame, swordShieldFrames, swordShieldActionFrame, bowFrames, bowActionFrame, axeFrames, axeActionFrame, maceFrames, maceActionFrame, maceShieldFrames, maceShieldActionFrame, staffFrames, staffActionFrame, idleFrames, walkingFrames, blockingFrames, deathFrames, castingFrames, recoveryFrames, townIdleFrames, townWalkingFrames, castingActionFrame
/* HeroClass::Warrior */ { 16, 9, 16, 9, 16, 9, 16, 9, 16, 11, 20, 10, 16, 9, 16, 9, 16, 11, 10, 8, 2, 20, 20, 6, 20, 8, 14 },
/* HeroClass::Rogue */ { 18, 10, 18, 10, 18, 10, 18, 10, 12, 7, 22, 13, 18, 10, 18, 10, 16, 11, 8, 8, 4, 20, 16, 7, 20, 8, 12 },
/* HeroClass::Sorcerer */ { 20, 12, 16, 9, 16, 12, 16, 12, 20, 16, 24, 16, 16, 12, 16, 12, 16, 12, 8, 8, 6, 20, 12, 8, 20, 8, 8 },
/* HeroClass::Monk */ { 12, 7, 12, 7, 16, 12, 16, 12, 20, 14, 23, 14, 16, 12, 16, 12, 13, 8, 8, 8, 3, 20, 18, 6, 20, 8, 13 },
/* HeroClass::Bard */ { 18, 10, 18, 10, 18, 10, 18, 10, 12, 11, 22, 13, 18, 10, 18, 10, 16, 11, 8, 8, 4, 20, 16, 7, 20, 8, 12 },
/* HeroClass::Barbarian */ { 16, 9, 16, 9, 16, 9, 16, 9, 16, 11, 20, 8, 16, 8, 16, 8, 16, 11, 10, 8, 2, 20, 20, 6, 20, 8, 14 },
// clang-format on
};
const PlayerAnimData &GetPlayerAnimDataForClass(HeroClass pClass)
{
const size_t playerClassIndex = static_cast<size_t>(pClass);
assert(playerClassIndex < PlayersAnimData.size());
return PlayersAnimData[playerClassIndex];
}
} // namespace devilution

37
Source/playerdat.hpp

@ -22,14 +22,20 @@ enum class HeroClass : uint8_t {
Bard,
Barbarian,
LAST = Barbarian
NUM_MAX_CLASSES = std::numeric_limits<uint8_t>::max(),
LAST = Barbarian,
};
struct PlayerData {
/* Class Name */
const char *className;
/* Class Skill */
SpellID skill = SpellID::Null;
std::string className;
/* Class Folder Name */
std::string folderName;
/* Class Portrait Index */
uint8_t portrait;
/* Class Inventory UI File */
std::string inv;
};
struct ClassAttributes {
@ -98,12 +104,7 @@ struct PlayerStartingLoadoutData {
/* Initial level of the starting spell */
uint8_t spellLevel;
struct ItemType {
_item_indexes diablo;
_item_indexes hellfire;
};
std::array<ItemType, 5> items;
std::array<_item_indexes, 5> items;
/* Initial gold amount, up to a single stack (5000 gold) */
uint16_t gold;
@ -111,7 +112,11 @@ struct PlayerStartingLoadoutData {
struct PlayerSpriteData {
/* Class Directory Path */
const char *classPath;
std::string classPath;
/* Class letter used in graphic files */
char classChar;
/* Class TRN file */
std::string trn;
/* Sprite width: Stand */
uint8_t stand;
/* Sprite width: Walk */
@ -192,17 +197,19 @@ struct PlayerAnimData {
};
/**
* @brief Attempts to load data values from external files, currently only Experience.tsv is supported.
* @brief Attempts to load data values from external files.
*/
void LoadClassDatFromFile(DataFile &dataFile, const std::string_view filename);
void LoadPlayerDataFiles();
extern const SfxID herosounds[enum_size<HeroClass>::value][enum_size<HeroSpeech>::value];
SfxID GetHeroSound(HeroClass clazz, HeroSpeech speech);
uint32_t GetNextExperienceThresholdForLevel(unsigned level);
uint8_t GetMaximumCharacterLevel();
size_t GetNumPlayerClasses();
const PlayerData &GetPlayerDataForClass(HeroClass clazz);
const PlayerCombatData &GetPlayerCombatDataForClass(HeroClass clazz);
const PlayerStartingLoadoutData &GetPlayerStartingLoadoutForClass(HeroClass clazz);
extern const PlayerSpriteData PlayersSpriteData[];
extern const PlayerAnimData PlayersAnimData[];
const PlayerSpriteData &GetPlayerSpriteDataForClass(HeroClass clazz);
const PlayerAnimData &GetPlayerAnimDataForClass(HeroClass clazz);
} // namespace devilution

28
assets/txtdata/classes/barbarian/animations.tsv

@ -0,0 +1,28 @@
Variable Value
unarmedFrames 16
unarmedActionFrame 9
unarmedShieldFrames 16
unarmedShieldActionFrame 9
swordFrames 16
swordActionFrame 9
swordShieldFrames 16
swordShieldActionFrame 9
bowFrames 16
bowActionFrame 11
axeFrames 20
axeActionFrame 8
maceFrames 16
maceActionFrame 8
maceShieldFrames 16
maceShieldActionFrame 8
staffFrames 16
staffActionFrame 11
idleFrames 10
walkingFrames 8
blockingFrames 2
deathFrames 20
castingFrames 20
recoveryFrames 6
townIdleFrames 20
townWalkingFrames 8
castingActionFrame 14
1 Variable Value
2 unarmedFrames 16
3 unarmedActionFrame 9
4 unarmedShieldFrames 16
5 unarmedShieldActionFrame 9
6 swordFrames 16
7 swordActionFrame 9
8 swordShieldFrames 16
9 swordShieldActionFrame 9
10 bowFrames 16
11 bowActionFrame 11
12 axeFrames 20
13 axeActionFrame 8
14 maceFrames 16
15 maceActionFrame 8
16 maceShieldFrames 16
17 maceShieldActionFrame 8
18 staffFrames 16
19 staffActionFrame 11
20 idleFrames 10
21 walkingFrames 8
22 blockingFrames 2
23 deathFrames 20
24 castingFrames 20
25 recoveryFrames 6
26 townIdleFrames 20
27 townWalkingFrames 8
28 castingActionFrame 14

104
assets/txtdata/classes/barbarian/sounds.tsv

@ -0,0 +1,104 @@
speech sfx
ChamberOfBoneLore Warrior1
HorazonsSanctumLore Warrior2
GolemSpellLore Warrior3
HorazonsCreatureOfFlameLore Warrior4
MortaVespaGaieaInnuminoEvegeenJatanLuaGraton Warrior5
GrimspikeLieutenantOfBelialLore Warrior6
HorazonsJournal Warrior7
YourDeathWillBeAvenged Warrior8
RestInPeaceMyFriend Warrior9
ValorLore Warrior10
HallsOfTheBlindLore Warrior11
WarlordOfBloodLore Warrior12
ICantUseThisYet Warrior13
ICantCarryAnymore Warrior14
IHaveNoRoom Warrior15
WhereWouldIPutThis Warrior16
NoWay Warrior17
NotAChance Warrior18
IdNeverUseThis Warrior19
IdHaveToEquipThat Warrior20
ICantMoveThis Warrior21
ICantMoveThisYet Warrior22
ICantOpenThis Warrior23
ICantOpenThisYet Warrior24
ICantLiftThis Warrior25
ICantLiftThisYet Warrior26
ICantCastThatHere Warrior27
ICantCastThatYet Warrior28
ThatDidntDoAnything Warrior29
ICanAlreadyDoThat Warrior30
IDontNeedThat Warrior31
IDontNeedToDoThat Warrior32
IDontWantThat Warrior33
IDontHaveASpellReady Warrior34
NotEnoughMana Warrior35
ThatWouldKillMe Warrior36
ICantDoThat Warrior37
No Warrior38
Yes Warrior39
ThatWontWork Warrior40
ThatWontWorkHere Warrior41
ThatWontWorkYet Warrior42
ICantGetThereFromHere Warrior43
ItsTooHeavy Warrior44
ItsTooBig Warrior45
JustWhatIWasLookingFor Warrior46
IveGotABadFeelingAboutThis Warrior47
GotMilk Warrior48
ImNotThirsty Warrior49
ImNoMilkmaid Warrior50
ICouldBlowUpTheWholeVillage Warrior51
YepThatsACowAlright Warrior52
TooUghHeavy Warrior53
InSpirituSanctum Warrior54
PraedictumOtium Warrior55
EfficioObitusUtInimicus Warrior56
TheEnchantmentIsGone Warrior57
OhTooEasy Warrior58
BackToTheGrave Warrior59
TimeToDie Warrior60
ImNotImpressed Warrior61
ImSorryDidIBreakYourConcentration Warrior62
VengeanceIsMine Warrior63
Die Warrior64
Yeah Warrior65
Ah Warrior66
Phew Warrior67
Argh Warrior68
ArghClang Warrior69
Aaaaargh Warrior70
OofAh Warrior71
HeavyBreathing Warrior72
Oh Warrior73
Wow Warrior74
ThankTheLight Warrior75
WhatWasThat Warrior76
MmHmm Warrior77
Hmm Warrior78
UhHuh Warrior79
TheSpiritsOfTheDeadAreNowAvenged Warrior80
TheTownIsSafeFromTheseFoulSpawn Warrior81
RestWellLeoricIllFindYourSon Warrior82
YourMadnessEndsHereBetrayer Warrior83
YoullLureNoMoreMenToTheirDeaths Warrior84
ReturnToHeavenWarriorOfLight Warrior85
ICanSeeWhyTheyFearThisWeapon Warrior86
ThisMustBeWhatGriswoldWanted Warrior87
INeedToGetThisToLachdanan Warrior88
INeedToGetThisToGriswold Warrior89
IveNeverBeenHereBefore Warrior90
MayTheSpiritOfArkaineProtectMe Warrior91
ThisIsAPlaceOfGreatPower Warrior92
ThisBladeMustBeDestroyed Warrior93
YourReignOfPainHasEnded Warrior94
NowThatsOneBigMushroom Warrior95
TheSmellOfDeathSurroundsMe Warrior96b
TheSanctityOfThisPlaceHasBeenFouled Warrior97
ItsHotDownHere Warrior98
IMustBeGettingClose Warrior99
MaybeItsLockedFromTheInside Warrior100
LooksLikeItsRustedShut Warrior101
MaybeTheresAnotherWay Warrior102
AuughUh Warrior71
1 speech sfx
2 ChamberOfBoneLore Warrior1
3 HorazonsSanctumLore Warrior2
4 GolemSpellLore Warrior3
5 HorazonsCreatureOfFlameLore Warrior4
6 MortaVespaGaieaInnuminoEvegeenJatanLuaGraton Warrior5
7 GrimspikeLieutenantOfBelialLore Warrior6
8 HorazonsJournal Warrior7
9 YourDeathWillBeAvenged Warrior8
10 RestInPeaceMyFriend Warrior9
11 ValorLore Warrior10
12 HallsOfTheBlindLore Warrior11
13 WarlordOfBloodLore Warrior12
14 ICantUseThisYet Warrior13
15 ICantCarryAnymore Warrior14
16 IHaveNoRoom Warrior15
17 WhereWouldIPutThis Warrior16
18 NoWay Warrior17
19 NotAChance Warrior18
20 IdNeverUseThis Warrior19
21 IdHaveToEquipThat Warrior20
22 ICantMoveThis Warrior21
23 ICantMoveThisYet Warrior22
24 ICantOpenThis Warrior23
25 ICantOpenThisYet Warrior24
26 ICantLiftThis Warrior25
27 ICantLiftThisYet Warrior26
28 ICantCastThatHere Warrior27
29 ICantCastThatYet Warrior28
30 ThatDidntDoAnything Warrior29
31 ICanAlreadyDoThat Warrior30
32 IDontNeedThat Warrior31
33 IDontNeedToDoThat Warrior32
34 IDontWantThat Warrior33
35 IDontHaveASpellReady Warrior34
36 NotEnoughMana Warrior35
37 ThatWouldKillMe Warrior36
38 ICantDoThat Warrior37
39 No Warrior38
40 Yes Warrior39
41 ThatWontWork Warrior40
42 ThatWontWorkHere Warrior41
43 ThatWontWorkYet Warrior42
44 ICantGetThereFromHere Warrior43
45 ItsTooHeavy Warrior44
46 ItsTooBig Warrior45
47 JustWhatIWasLookingFor Warrior46
48 IveGotABadFeelingAboutThis Warrior47
49 GotMilk Warrior48
50 ImNotThirsty Warrior49
51 ImNoMilkmaid Warrior50
52 ICouldBlowUpTheWholeVillage Warrior51
53 YepThatsACowAlright Warrior52
54 TooUghHeavy Warrior53
55 InSpirituSanctum Warrior54
56 PraedictumOtium Warrior55
57 EfficioObitusUtInimicus Warrior56
58 TheEnchantmentIsGone Warrior57
59 OhTooEasy Warrior58
60 BackToTheGrave Warrior59
61 TimeToDie Warrior60
62 ImNotImpressed Warrior61
63 ImSorryDidIBreakYourConcentration Warrior62
64 VengeanceIsMine Warrior63
65 Die Warrior64
66 Yeah Warrior65
67 Ah Warrior66
68 Phew Warrior67
69 Argh Warrior68
70 ArghClang Warrior69
71 Aaaaargh Warrior70
72 OofAh Warrior71
73 HeavyBreathing Warrior72
74 Oh Warrior73
75 Wow Warrior74
76 ThankTheLight Warrior75
77 WhatWasThat Warrior76
78 MmHmm Warrior77
79 Hmm Warrior78
80 UhHuh Warrior79
81 TheSpiritsOfTheDeadAreNowAvenged Warrior80
82 TheTownIsSafeFromTheseFoulSpawn Warrior81
83 RestWellLeoricIllFindYourSon Warrior82
84 YourMadnessEndsHereBetrayer Warrior83
85 YoullLureNoMoreMenToTheirDeaths Warrior84
86 ReturnToHeavenWarriorOfLight Warrior85
87 ICanSeeWhyTheyFearThisWeapon Warrior86
88 ThisMustBeWhatGriswoldWanted Warrior87
89 INeedToGetThisToLachdanan Warrior88
90 INeedToGetThisToGriswold Warrior89
91 IveNeverBeenHereBefore Warrior90
92 MayTheSpiritOfArkaineProtectMe Warrior91
93 ThisIsAPlaceOfGreatPower Warrior92
94 ThisBladeMustBeDestroyed Warrior93
95 YourReignOfPainHasEnded Warrior94
96 NowThatsOneBigMushroom Warrior95
97 TheSmellOfDeathSurroundsMe Warrior96b
98 TheSanctityOfThisPlaceHasBeenFouled Warrior97
99 ItsHotDownHere Warrior98
100 IMustBeGettingClose Warrior99
101 MaybeItsLockedFromTheInside Warrior100
102 LooksLikeItsRustedShut Warrior101
103 MaybeTheresAnotherWay Warrior102
104 AuughUh Warrior71

14
assets/txtdata/classes/barbarian/sprites.tsv

@ -0,0 +1,14 @@
Variable Value
classPath warrior
classChar c
trn barbarian
stand 96
walk 96
attack 128
bow 96
swHit 96
block 96
lightning 96
fire 96
magic 96
death 128
1 Variable Value
2 classPath warrior
3 classChar c
4 trn barbarian
5 stand 96
6 walk 96
7 attack 128
8 bow 96
9 swHit 96
10 block 96
11 lightning 96
12 fire 96
13 magic 96
14 death 128

10
assets/txtdata/classes/barbarian/starting_loadout.tsv

@ -0,0 +1,10 @@
Variable Value
skill Rage
spell Null
spellLevel 0
item0 IDI_BARBARIAN
item1 IDI_WARRSHLD
item2 IDI_HEAL
item3 IDI_HEAL
item4 IDI_NONE
gold 100
1 Variable Value
2 skill Rage
3 spell Null
4 spellLevel 0
5 item0 IDI_BARBARIAN
6 item1 IDI_WARRSHLD
7 item2 IDI_HEAL
8 item3 IDI_HEAL
9 item4 IDI_NONE
10 gold 100

28
assets/txtdata/classes/bard/animations.tsv

@ -0,0 +1,28 @@
Variable Value
unarmedFrames 18
unarmedActionFrame 10
unarmedShieldFrames 18
unarmedShieldActionFrame 10
swordFrames 18
swordActionFrame 10
swordShieldFrames 18
swordShieldActionFrame 10
bowFrames 12
bowActionFrame 11
axeFrames 22
axeActionFrame 13
maceFrames 18
maceActionFrame 10
maceShieldFrames 18
maceShieldActionFrame 10
staffFrames 16
staffActionFrame 11
idleFrames 8
walkingFrames 8
blockingFrames 4
deathFrames 20
castingFrames 16
recoveryFrames 7
townIdleFrames 20
townWalkingFrames 8
castingActionFrame 12
1 Variable Value
2 unarmedFrames 18
3 unarmedActionFrame 10
4 unarmedShieldFrames 18
5 unarmedShieldActionFrame 10
6 swordFrames 18
7 swordActionFrame 10
8 swordShieldFrames 18
9 swordShieldActionFrame 10
10 bowFrames 12
11 bowActionFrame 11
12 axeFrames 22
13 axeActionFrame 13
14 maceFrames 18
15 maceActionFrame 10
16 maceShieldFrames 18
17 maceShieldActionFrame 10
18 staffFrames 16
19 staffActionFrame 11
20 idleFrames 8
21 walkingFrames 8
22 blockingFrames 4
23 deathFrames 20
24 castingFrames 16
25 recoveryFrames 7
26 townIdleFrames 20
27 townWalkingFrames 8
28 castingActionFrame 12

104
assets/txtdata/classes/bard/sounds.tsv

@ -0,0 +1,104 @@
speech sfx
ChamberOfBoneLore Rogue1
HorazonsSanctumLore Rogue2
GolemSpellLore Rogue3
HorazonsCreatureOfFlameLore Rogue4
MortaVespaGaieaInnuminoEvegeenJatanLuaGraton Rogue5
GrimspikeLieutenantOfBelialLore Rogue6
HorazonsJournal Rogue7
YourDeathWillBeAvenged Rogue8
RestInPeaceMyFriend Rogue9
ValorLore Rogue10
HallsOfTheBlindLore Rogue11
WarlordOfBloodLore Rogue12
ICantUseThisYet Rogue13
ICantCarryAnymore Rogue14
IHaveNoRoom Rogue15
WhereWouldIPutThis Rogue16
NoWay Rogue17
NotAChance Rogue18
IdNeverUseThis Rogue19
IdHaveToEquipThat Rogue20
ICantMoveThis Rogue21
ICantMoveThisYet Rogue22
ICantOpenThis Rogue23
ICantOpenThisYet Rogue24
ICantLiftThis Rogue25
ICantLiftThisYet Rogue26
ICantCastThatHere Rogue27
ICantCastThatYet Rogue28
ThatDidntDoAnything Rogue29
ICanAlreadyDoThat Rogue30
IDontNeedThat Rogue31
IDontNeedToDoThat Rogue32
IDontWantThat Rogue33
IDontHaveASpellReady Rogue34
NotEnoughMana Rogue35
ThatWouldKillMe Rogue36
ICantDoThat Rogue37
No Rogue38
Yes Rogue39
ThatWontWork Rogue40
ThatWontWorkHere Rogue41
ThatWontWorkYet Rogue42
ICantGetThereFromHere Rogue43
ItsTooHeavy Rogue44
ItsTooBig Rogue45
JustWhatIWasLookingFor Rogue46
IveGotABadFeelingAboutThis Rogue47
GotMilk Rogue48
ImNotThirsty Rogue49
ImNoMilkmaid Rogue50
ICouldBlowUpTheWholeVillage Rogue51
YepThatsACowAlright Rogue52
TooUghHeavy Rogue53
InSpirituSanctum Rogue54
PraedictumOtium Rogue55
EfficioObitusUtInimicus Rogue56
TheEnchantmentIsGone Rogue57
OhTooEasy Rogue58
BackToTheGrave Rogue59
TimeToDie Rogue60
ImNotImpressed Rogue61
ImSorryDidIBreakYourConcentration Rogue62
VengeanceIsMine Rogue63
Die Rogue64
Yeah Rogue65
Ah Rogue66
Phew Rogue67
Argh Rogue68
ArghClang Rogue69
Aaaaargh Rogue70
OofAh Rogue71
HeavyBreathing Rogue72
Oh Rogue73
Wow Rogue74
ThankTheLight Rogue75
WhatWasThat Rogue76
MmHmm Rogue77
Hmm Rogue78
UhHuh Rogue79
TheSpiritsOfTheDeadAreNowAvenged Rogue80
TheTownIsSafeFromTheseFoulSpawn Rogue81
RestWellLeoricIllFindYourSon Rogue82
YourMadnessEndsHereBetrayer Rogue83
YoullLureNoMoreMenToTheirDeaths Rogue84
ReturnToHeavenWarriorOfLight Rogue85
ICanSeeWhyTheyFearThisWeapon Rogue86
ThisMustBeWhatGriswoldWanted Rogue87
INeedToGetThisToLachdanan Rogue88
INeedToGetThisToGriswold Rogue89
IveNeverBeenHereBefore Rogue90
MayTheSpiritOfArkaineProtectMe Rogue91
ThisIsAPlaceOfGreatPower Rogue92
ThisBladeMustBeDestroyed Rogue93
YourReignOfPainHasEnded Rogue94
NowThatsOneBigMushroom Rogue95
TheSmellOfDeathSurroundsMe Rogue96
TheSanctityOfThisPlaceHasBeenFouled Rogue97
ItsHotDownHere Rogue98
IMustBeGettingClose Rogue99
MaybeItsLockedFromTheInside Rogue100
LooksLikeItsRustedShut Rogue101
MaybeTheresAnotherWay Rogue102
AuughUh Rogue71
1 speech sfx
2 ChamberOfBoneLore Rogue1
3 HorazonsSanctumLore Rogue2
4 GolemSpellLore Rogue3
5 HorazonsCreatureOfFlameLore Rogue4
6 MortaVespaGaieaInnuminoEvegeenJatanLuaGraton Rogue5
7 GrimspikeLieutenantOfBelialLore Rogue6
8 HorazonsJournal Rogue7
9 YourDeathWillBeAvenged Rogue8
10 RestInPeaceMyFriend Rogue9
11 ValorLore Rogue10
12 HallsOfTheBlindLore Rogue11
13 WarlordOfBloodLore Rogue12
14 ICantUseThisYet Rogue13
15 ICantCarryAnymore Rogue14
16 IHaveNoRoom Rogue15
17 WhereWouldIPutThis Rogue16
18 NoWay Rogue17
19 NotAChance Rogue18
20 IdNeverUseThis Rogue19
21 IdHaveToEquipThat Rogue20
22 ICantMoveThis Rogue21
23 ICantMoveThisYet Rogue22
24 ICantOpenThis Rogue23
25 ICantOpenThisYet Rogue24
26 ICantLiftThis Rogue25
27 ICantLiftThisYet Rogue26
28 ICantCastThatHere Rogue27
29 ICantCastThatYet Rogue28
30 ThatDidntDoAnything Rogue29
31 ICanAlreadyDoThat Rogue30
32 IDontNeedThat Rogue31
33 IDontNeedToDoThat Rogue32
34 IDontWantThat Rogue33
35 IDontHaveASpellReady Rogue34
36 NotEnoughMana Rogue35
37 ThatWouldKillMe Rogue36
38 ICantDoThat Rogue37
39 No Rogue38
40 Yes Rogue39
41 ThatWontWork Rogue40
42 ThatWontWorkHere Rogue41
43 ThatWontWorkYet Rogue42
44 ICantGetThereFromHere Rogue43
45 ItsTooHeavy Rogue44
46 ItsTooBig Rogue45
47 JustWhatIWasLookingFor Rogue46
48 IveGotABadFeelingAboutThis Rogue47
49 GotMilk Rogue48
50 ImNotThirsty Rogue49
51 ImNoMilkmaid Rogue50
52 ICouldBlowUpTheWholeVillage Rogue51
53 YepThatsACowAlright Rogue52
54 TooUghHeavy Rogue53
55 InSpirituSanctum Rogue54
56 PraedictumOtium Rogue55
57 EfficioObitusUtInimicus Rogue56
58 TheEnchantmentIsGone Rogue57
59 OhTooEasy Rogue58
60 BackToTheGrave Rogue59
61 TimeToDie Rogue60
62 ImNotImpressed Rogue61
63 ImSorryDidIBreakYourConcentration Rogue62
64 VengeanceIsMine Rogue63
65 Die Rogue64
66 Yeah Rogue65
67 Ah Rogue66
68 Phew Rogue67
69 Argh Rogue68
70 ArghClang Rogue69
71 Aaaaargh Rogue70
72 OofAh Rogue71
73 HeavyBreathing Rogue72
74 Oh Rogue73
75 Wow Rogue74
76 ThankTheLight Rogue75
77 WhatWasThat Rogue76
78 MmHmm Rogue77
79 Hmm Rogue78
80 UhHuh Rogue79
81 TheSpiritsOfTheDeadAreNowAvenged Rogue80
82 TheTownIsSafeFromTheseFoulSpawn Rogue81
83 RestWellLeoricIllFindYourSon Rogue82
84 YourMadnessEndsHereBetrayer Rogue83
85 YoullLureNoMoreMenToTheirDeaths Rogue84
86 ReturnToHeavenWarriorOfLight Rogue85
87 ICanSeeWhyTheyFearThisWeapon Rogue86
88 ThisMustBeWhatGriswoldWanted Rogue87
89 INeedToGetThisToLachdanan Rogue88
90 INeedToGetThisToGriswold Rogue89
91 IveNeverBeenHereBefore Rogue90
92 MayTheSpiritOfArkaineProtectMe Rogue91
93 ThisIsAPlaceOfGreatPower Rogue92
94 ThisBladeMustBeDestroyed Rogue93
95 YourReignOfPainHasEnded Rogue94
96 NowThatsOneBigMushroom Rogue95
97 TheSmellOfDeathSurroundsMe Rogue96
98 TheSanctityOfThisPlaceHasBeenFouled Rogue97
99 ItsHotDownHere Rogue98
100 IMustBeGettingClose Rogue99
101 MaybeItsLockedFromTheInside Rogue100
102 LooksLikeItsRustedShut Rogue101
103 MaybeTheresAnotherWay Rogue102
104 AuughUh Rogue71

14
assets/txtdata/classes/bard/sprites.tsv

@ -0,0 +1,14 @@
Variable Value
classPath rogue
classChar b
trn bard
stand 96
walk 96
attack 128
bow 128
swHit 96
block 96
lightning 96
fire 96
magic 96
death 128
1 Variable Value
2 classPath rogue
3 classChar b
4 trn bard
5 stand 96
6 walk 96
7 attack 128
8 bow 128
9 swHit 96
10 block 96
11 lightning 96
12 fire 96
13 magic 96
14 death 128

10
assets/txtdata/classes/bard/starting_loadout.tsv

@ -0,0 +1,10 @@
Variable Value
skill Identify
spell Null
spellLevel 0
item0 IDI_BARDSWORD
item1 IDI_BARDDAGGER
item2 IDI_HEAL
item3 IDI_HEAL
item4 IDI_NONE
gold 100
1 Variable Value
2 skill Identify
3 spell Null
4 spellLevel 0
5 item0 IDI_BARDSWORD
6 item1 IDI_BARDDAGGER
7 item2 IDI_HEAL
8 item3 IDI_HEAL
9 item4 IDI_NONE
10 gold 100

7
assets/txtdata/classes/classdat.tsv

@ -0,0 +1,7 @@
className folderName portrait inv
Warrior warrior 0 inv
Rogue rogue 1 inv_rog
Sorcerer sorcerer 2 inv_sor
Monk monk 2 inv_sor
Bard bard 1 inv_rog
Barbarian barbarian 0 inv
1 className folderName portrait inv
2 Warrior warrior 0 inv
3 Rogue rogue 1 inv_rog
4 Sorcerer sorcerer 2 inv_sor
5 Monk monk 2 inv_sor
6 Bard bard 1 inv_rog
7 Barbarian barbarian 0 inv

28
assets/txtdata/classes/monk/animations.tsv

@ -0,0 +1,28 @@
Variable Value
unarmedFrames 12
unarmedActionFrame 7
unarmedShieldFrames 12
unarmedShieldActionFrame 7
swordFrames 16
swordActionFrame 12
swordShieldFrames 16
swordShieldActionFrame 12
bowFrames 20
bowActionFrame 14
axeFrames 23
axeActionFrame 14
maceFrames 16
maceActionFrame 12
maceShieldFrames 16
maceShieldActionFrame 12
staffFrames 13
staffActionFrame 8
idleFrames 8
walkingFrames 8
blockingFrames 3
deathFrames 20
castingFrames 18
recoveryFrames 6
townIdleFrames 20
townWalkingFrames 8
castingActionFrame 13
1 Variable Value
2 unarmedFrames 12
3 unarmedActionFrame 7
4 unarmedShieldFrames 12
5 unarmedShieldActionFrame 7
6 swordFrames 16
7 swordActionFrame 12
8 swordShieldFrames 16
9 swordShieldActionFrame 12
10 bowFrames 20
11 bowActionFrame 14
12 axeFrames 23
13 axeActionFrame 14
14 maceFrames 16
15 maceActionFrame 12
16 maceShieldFrames 16
17 maceShieldActionFrame 12
18 staffFrames 13
19 staffActionFrame 8
20 idleFrames 8
21 walkingFrames 8
22 blockingFrames 3
23 deathFrames 20
24 castingFrames 18
25 recoveryFrames 6
26 townIdleFrames 20
27 townWalkingFrames 8
28 castingActionFrame 13

104
assets/txtdata/classes/monk/sounds.tsv

@ -0,0 +1,104 @@
speech sfx
ChamberOfBoneLore Monk1
HorazonsSanctumLore None
GolemSpellLore None
HorazonsCreatureOfFlameLore None
MortaVespaGaieaInnuminoEvegeenJatanLuaGraton None
GrimspikeLieutenantOfBelialLore None
HorazonsJournal None
YourDeathWillBeAvenged Monk8
RestInPeaceMyFriend Monk9
ValorLore Monk10
HallsOfTheBlindLore Monk11
WarlordOfBloodLore Monk12
ICantUseThisYet Monk13
ICantCarryAnymore Monk14
IHaveNoRoom Monk15
WhereWouldIPutThis Monk16
NoWay None
NotAChance None
IdNeverUseThis None
IdHaveToEquipThat None
ICantMoveThis None
ICantMoveThisYet None
ICantOpenThis None
ICantOpenThisYet Monk24
ICantLiftThis None
ICantLiftThisYet None
ICantCastThatHere Monk27
ICantCastThatYet None
ThatDidntDoAnything Monk29
ICanAlreadyDoThat None
IDontNeedThat None
IDontNeedToDoThat None
IDontWantThat None
IDontHaveASpellReady Monk34
NotEnoughMana Monk35
ThatWouldKillMe None
ICantDoThat None
No None
Yes None
ThatWontWork None
ThatWontWorkHere None
ThatWontWorkYet None
ICantGetThereFromHere Monk43
ItsTooHeavy None
ItsTooBig None
JustWhatIWasLookingFor Monk46
IveGotABadFeelingAboutThis None
GotMilk None
ImNotThirsty Monk49
ImNoMilkmaid Monk50
ICouldBlowUpTheWholeVillage None
YepThatsACowAlright Monk52
TooUghHeavy None
InSpirituSanctum Monk54
PraedictumOtium Monk55
EfficioObitusUtInimicus Monk56
TheEnchantmentIsGone None
OhTooEasy None
BackToTheGrave None
TimeToDie None
ImNotImpressed Monk61
ImSorryDidIBreakYourConcentration Monk62
VengeanceIsMine None
Die None
Yeah None
Ah None
Phew None
Argh Monk68
ArghClang Monk69
Aaaaargh Monk70
OofAh Monk71
HeavyBreathing None
Oh None
Wow None
ThankTheLight None
WhatWasThat None
MmHmm None
Hmm None
UhHuh Monk79
TheSpiritsOfTheDeadAreNowAvenged Monk80
TheTownIsSafeFromTheseFoulSpawn None
RestWellLeoricIllFindYourSon Monk82
YourMadnessEndsHereBetrayer Monk83
YoullLureNoMoreMenToTheirDeaths None
ReturnToHeavenWarriorOfLight None
ICanSeeWhyTheyFearThisWeapon None
ThisMustBeWhatGriswoldWanted Monk87
INeedToGetThisToLachdanan Monk88
INeedToGetThisToGriswold Monk89
IveNeverBeenHereBefore None
MayTheSpiritOfArkaineProtectMe Monk91
ThisIsAPlaceOfGreatPower Monk92
ThisBladeMustBeDestroyed None
YourReignOfPainHasEnded Monk94
NowThatsOneBigMushroom Monk95
TheSmellOfDeathSurroundsMe Monk96
TheSanctityOfThisPlaceHasBeenFouled Monk97
ItsHotDownHere Monk98
IMustBeGettingClose Monk99
MaybeItsLockedFromTheInside None
LooksLikeItsRustedShut None
MaybeTheresAnotherWay None
AuughUh Monk71
1 speech sfx
2 ChamberOfBoneLore Monk1
3 HorazonsSanctumLore None
4 GolemSpellLore None
5 HorazonsCreatureOfFlameLore None
6 MortaVespaGaieaInnuminoEvegeenJatanLuaGraton None
7 GrimspikeLieutenantOfBelialLore None
8 HorazonsJournal None
9 YourDeathWillBeAvenged Monk8
10 RestInPeaceMyFriend Monk9
11 ValorLore Monk10
12 HallsOfTheBlindLore Monk11
13 WarlordOfBloodLore Monk12
14 ICantUseThisYet Monk13
15 ICantCarryAnymore Monk14
16 IHaveNoRoom Monk15
17 WhereWouldIPutThis Monk16
18 NoWay None
19 NotAChance None
20 IdNeverUseThis None
21 IdHaveToEquipThat None
22 ICantMoveThis None
23 ICantMoveThisYet None
24 ICantOpenThis None
25 ICantOpenThisYet Monk24
26 ICantLiftThis None
27 ICantLiftThisYet None
28 ICantCastThatHere Monk27
29 ICantCastThatYet None
30 ThatDidntDoAnything Monk29
31 ICanAlreadyDoThat None
32 IDontNeedThat None
33 IDontNeedToDoThat None
34 IDontWantThat None
35 IDontHaveASpellReady Monk34
36 NotEnoughMana Monk35
37 ThatWouldKillMe None
38 ICantDoThat None
39 No None
40 Yes None
41 ThatWontWork None
42 ThatWontWorkHere None
43 ThatWontWorkYet None
44 ICantGetThereFromHere Monk43
45 ItsTooHeavy None
46 ItsTooBig None
47 JustWhatIWasLookingFor Monk46
48 IveGotABadFeelingAboutThis None
49 GotMilk None
50 ImNotThirsty Monk49
51 ImNoMilkmaid Monk50
52 ICouldBlowUpTheWholeVillage None
53 YepThatsACowAlright Monk52
54 TooUghHeavy None
55 InSpirituSanctum Monk54
56 PraedictumOtium Monk55
57 EfficioObitusUtInimicus Monk56
58 TheEnchantmentIsGone None
59 OhTooEasy None
60 BackToTheGrave None
61 TimeToDie None
62 ImNotImpressed Monk61
63 ImSorryDidIBreakYourConcentration Monk62
64 VengeanceIsMine None
65 Die None
66 Yeah None
67 Ah None
68 Phew None
69 Argh Monk68
70 ArghClang Monk69
71 Aaaaargh Monk70
72 OofAh Monk71
73 HeavyBreathing None
74 Oh None
75 Wow None
76 ThankTheLight None
77 WhatWasThat None
78 MmHmm None
79 Hmm None
80 UhHuh Monk79
81 TheSpiritsOfTheDeadAreNowAvenged Monk80
82 TheTownIsSafeFromTheseFoulSpawn None
83 RestWellLeoricIllFindYourSon Monk82
84 YourMadnessEndsHereBetrayer Monk83
85 YoullLureNoMoreMenToTheirDeaths None
86 ReturnToHeavenWarriorOfLight None
87 ICanSeeWhyTheyFearThisWeapon None
88 ThisMustBeWhatGriswoldWanted Monk87
89 INeedToGetThisToLachdanan Monk88
90 INeedToGetThisToGriswold Monk89
91 IveNeverBeenHereBefore None
92 MayTheSpiritOfArkaineProtectMe Monk91
93 ThisIsAPlaceOfGreatPower Monk92
94 ThisBladeMustBeDestroyed None
95 YourReignOfPainHasEnded Monk94
96 NowThatsOneBigMushroom Monk95
97 TheSmellOfDeathSurroundsMe Monk96
98 TheSanctityOfThisPlaceHasBeenFouled Monk97
99 ItsHotDownHere Monk98
100 IMustBeGettingClose Monk99
101 MaybeItsLockedFromTheInside None
102 LooksLikeItsRustedShut None
103 MaybeTheresAnotherWay None
104 AuughUh Monk71

14
assets/txtdata/classes/monk/sprites.tsv

@ -0,0 +1,14 @@
Variable Value
classPath monk
classChar m
trn monk
stand 112
walk 112
attack 130
bow 130
swHit 98
block 98
lightning 114
fire 114
magic 114
death 160
1 Variable Value
2 classPath monk
3 classChar m
4 trn monk
5 stand 112
6 walk 112
7 attack 130
8 bow 130
9 swHit 98
10 block 98
11 lightning 114
12 fire 114
13 magic 114
14 death 160

10
assets/txtdata/classes/monk/starting_loadout.tsv

@ -0,0 +1,10 @@
Variable Value
skill Search
spell Null
spellLevel 0
item0 IDI_SHORTSTAFF
item1 IDI_HEAL
item2 IDI_HEAL
item3 IDI_NONE
item4 IDI_NONE
gold 100
1 Variable Value
2 skill Search
3 spell Null
4 spellLevel 0
5 item0 IDI_SHORTSTAFF
6 item1 IDI_HEAL
7 item2 IDI_HEAL
8 item3 IDI_NONE
9 item4 IDI_NONE
10 gold 100

28
assets/txtdata/classes/rogue/animations.tsv

@ -0,0 +1,28 @@
Variable Value
unarmedFrames 18
unarmedActionFrame 10
unarmedShieldFrames 18
unarmedShieldActionFrame 10
swordFrames 18
swordActionFrame 10
swordShieldFrames 18
swordShieldActionFrame 10
bowFrames 12
bowActionFrame 7
axeFrames 22
axeActionFrame 13
maceFrames 18
maceActionFrame 10
maceShieldFrames 18
maceShieldActionFrame 10
staffFrames 16
staffActionFrame 11
idleFrames 8
walkingFrames 8
blockingFrames 4
deathFrames 20
castingFrames 16
recoveryFrames 7
townIdleFrames 20
townWalkingFrames 8
castingActionFrame 12
1 Variable Value
2 unarmedFrames 18
3 unarmedActionFrame 10
4 unarmedShieldFrames 18
5 unarmedShieldActionFrame 10
6 swordFrames 18
7 swordActionFrame 10
8 swordShieldFrames 18
9 swordShieldActionFrame 10
10 bowFrames 12
11 bowActionFrame 7
12 axeFrames 22
13 axeActionFrame 13
14 maceFrames 18
15 maceActionFrame 10
16 maceShieldFrames 18
17 maceShieldActionFrame 10
18 staffFrames 16
19 staffActionFrame 11
20 idleFrames 8
21 walkingFrames 8
22 blockingFrames 4
23 deathFrames 20
24 castingFrames 16
25 recoveryFrames 7
26 townIdleFrames 20
27 townWalkingFrames 8
28 castingActionFrame 12

104
assets/txtdata/classes/rogue/sounds.tsv

@ -0,0 +1,104 @@
speech sfx
ChamberOfBoneLore Rogue1
HorazonsSanctumLore Rogue2
GolemSpellLore Rogue3
HorazonsCreatureOfFlameLore Rogue4
MortaVespaGaieaInnuminoEvegeenJatanLuaGraton Rogue5
GrimspikeLieutenantOfBelialLore Rogue6
HorazonsJournal Rogue7
YourDeathWillBeAvenged Rogue8
RestInPeaceMyFriend Rogue9
ValorLore Rogue10
HallsOfTheBlindLore Rogue11
WarlordOfBloodLore Rogue12
ICantUseThisYet Rogue13
ICantCarryAnymore Rogue14
IHaveNoRoom Rogue15
WhereWouldIPutThis Rogue16
NoWay Rogue17
NotAChance Rogue18
IdNeverUseThis Rogue19
IdHaveToEquipThat Rogue20
ICantMoveThis Rogue21
ICantMoveThisYet Rogue22
ICantOpenThis Rogue23
ICantOpenThisYet Rogue24
ICantLiftThis Rogue25
ICantLiftThisYet Rogue26
ICantCastThatHere Rogue27
ICantCastThatYet Rogue28
ThatDidntDoAnything Rogue29
ICanAlreadyDoThat Rogue30
IDontNeedThat Rogue31
IDontNeedToDoThat Rogue32
IDontWantThat Rogue33
IDontHaveASpellReady Rogue34
NotEnoughMana Rogue35
ThatWouldKillMe Rogue36
ICantDoThat Rogue37
No Rogue38
Yes Rogue39
ThatWontWork Rogue40
ThatWontWorkHere Rogue41
ThatWontWorkYet Rogue42
ICantGetThereFromHere Rogue43
ItsTooHeavy Rogue44
ItsTooBig Rogue45
JustWhatIWasLookingFor Rogue46
IveGotABadFeelingAboutThis Rogue47
GotMilk Rogue48
ImNotThirsty Rogue49
ImNoMilkmaid Rogue50
ICouldBlowUpTheWholeVillage Rogue51
YepThatsACowAlright Rogue52
TooUghHeavy Rogue53
InSpirituSanctum Rogue54
PraedictumOtium Rogue55
EfficioObitusUtInimicus Rogue56
TheEnchantmentIsGone Rogue57
OhTooEasy Rogue58
BackToTheGrave Rogue59
TimeToDie Rogue60
ImNotImpressed Rogue61
ImSorryDidIBreakYourConcentration Rogue62
VengeanceIsMine Rogue63
Die Rogue64
Yeah Rogue65
Ah Rogue66
Phew Rogue67
Argh Rogue68
ArghClang Rogue69
Aaaaargh Rogue70
OofAh Rogue71
HeavyBreathing Rogue72
Oh Rogue73
Wow Rogue74
ThankTheLight Rogue75
WhatWasThat Rogue76
MmHmm Rogue77
Hmm Rogue78
UhHuh Rogue79
TheSpiritsOfTheDeadAreNowAvenged Rogue80
TheTownIsSafeFromTheseFoulSpawn Rogue81
RestWellLeoricIllFindYourSon Rogue82
YourMadnessEndsHereBetrayer Rogue83
YoullLureNoMoreMenToTheirDeaths Rogue84
ReturnToHeavenWarriorOfLight Rogue85
ICanSeeWhyTheyFearThisWeapon Rogue86
ThisMustBeWhatGriswoldWanted Rogue87
INeedToGetThisToLachdanan Rogue88
INeedToGetThisToGriswold Rogue89
IveNeverBeenHereBefore Rogue90
MayTheSpiritOfArkaineProtectMe Rogue91
ThisIsAPlaceOfGreatPower Rogue92
ThisBladeMustBeDestroyed Rogue93
YourReignOfPainHasEnded Rogue94
NowThatsOneBigMushroom Rogue95
TheSmellOfDeathSurroundsMe Rogue96
TheSanctityOfThisPlaceHasBeenFouled Rogue97
ItsHotDownHere Rogue98
IMustBeGettingClose Rogue99
MaybeItsLockedFromTheInside Rogue100
LooksLikeItsRustedShut Rogue101
MaybeTheresAnotherWay Rogue102
AuughUh Rogue71
1 speech sfx
2 ChamberOfBoneLore Rogue1
3 HorazonsSanctumLore Rogue2
4 GolemSpellLore Rogue3
5 HorazonsCreatureOfFlameLore Rogue4
6 MortaVespaGaieaInnuminoEvegeenJatanLuaGraton Rogue5
7 GrimspikeLieutenantOfBelialLore Rogue6
8 HorazonsJournal Rogue7
9 YourDeathWillBeAvenged Rogue8
10 RestInPeaceMyFriend Rogue9
11 ValorLore Rogue10
12 HallsOfTheBlindLore Rogue11
13 WarlordOfBloodLore Rogue12
14 ICantUseThisYet Rogue13
15 ICantCarryAnymore Rogue14
16 IHaveNoRoom Rogue15
17 WhereWouldIPutThis Rogue16
18 NoWay Rogue17
19 NotAChance Rogue18
20 IdNeverUseThis Rogue19
21 IdHaveToEquipThat Rogue20
22 ICantMoveThis Rogue21
23 ICantMoveThisYet Rogue22
24 ICantOpenThis Rogue23
25 ICantOpenThisYet Rogue24
26 ICantLiftThis Rogue25
27 ICantLiftThisYet Rogue26
28 ICantCastThatHere Rogue27
29 ICantCastThatYet Rogue28
30 ThatDidntDoAnything Rogue29
31 ICanAlreadyDoThat Rogue30
32 IDontNeedThat Rogue31
33 IDontNeedToDoThat Rogue32
34 IDontWantThat Rogue33
35 IDontHaveASpellReady Rogue34
36 NotEnoughMana Rogue35
37 ThatWouldKillMe Rogue36
38 ICantDoThat Rogue37
39 No Rogue38
40 Yes Rogue39
41 ThatWontWork Rogue40
42 ThatWontWorkHere Rogue41
43 ThatWontWorkYet Rogue42
44 ICantGetThereFromHere Rogue43
45 ItsTooHeavy Rogue44
46 ItsTooBig Rogue45
47 JustWhatIWasLookingFor Rogue46
48 IveGotABadFeelingAboutThis Rogue47
49 GotMilk Rogue48
50 ImNotThirsty Rogue49
51 ImNoMilkmaid Rogue50
52 ICouldBlowUpTheWholeVillage Rogue51
53 YepThatsACowAlright Rogue52
54 TooUghHeavy Rogue53
55 InSpirituSanctum Rogue54
56 PraedictumOtium Rogue55
57 EfficioObitusUtInimicus Rogue56
58 TheEnchantmentIsGone Rogue57
59 OhTooEasy Rogue58
60 BackToTheGrave Rogue59
61 TimeToDie Rogue60
62 ImNotImpressed Rogue61
63 ImSorryDidIBreakYourConcentration Rogue62
64 VengeanceIsMine Rogue63
65 Die Rogue64
66 Yeah Rogue65
67 Ah Rogue66
68 Phew Rogue67
69 Argh Rogue68
70 ArghClang Rogue69
71 Aaaaargh Rogue70
72 OofAh Rogue71
73 HeavyBreathing Rogue72
74 Oh Rogue73
75 Wow Rogue74
76 ThankTheLight Rogue75
77 WhatWasThat Rogue76
78 MmHmm Rogue77
79 Hmm Rogue78
80 UhHuh Rogue79
81 TheSpiritsOfTheDeadAreNowAvenged Rogue80
82 TheTownIsSafeFromTheseFoulSpawn Rogue81
83 RestWellLeoricIllFindYourSon Rogue82
84 YourMadnessEndsHereBetrayer Rogue83
85 YoullLureNoMoreMenToTheirDeaths Rogue84
86 ReturnToHeavenWarriorOfLight Rogue85
87 ICanSeeWhyTheyFearThisWeapon Rogue86
88 ThisMustBeWhatGriswoldWanted Rogue87
89 INeedToGetThisToLachdanan Rogue88
90 INeedToGetThisToGriswold Rogue89
91 IveNeverBeenHereBefore Rogue90
92 MayTheSpiritOfArkaineProtectMe Rogue91
93 ThisIsAPlaceOfGreatPower Rogue92
94 ThisBladeMustBeDestroyed Rogue93
95 YourReignOfPainHasEnded Rogue94
96 NowThatsOneBigMushroom Rogue95
97 TheSmellOfDeathSurroundsMe Rogue96
98 TheSanctityOfThisPlaceHasBeenFouled Rogue97
99 ItsHotDownHere Rogue98
100 IMustBeGettingClose Rogue99
101 MaybeItsLockedFromTheInside Rogue100
102 LooksLikeItsRustedShut Rogue101
103 MaybeTheresAnotherWay Rogue102
104 AuughUh Rogue71

14
assets/txtdata/classes/rogue/sprites.tsv

@ -0,0 +1,14 @@
Variable Value
classPath rogue
classChar r
trn rogue
stand 96
walk 96
attack 128
bow 128
swHit 96
block 96
lightning 96
fire 96
magic 96
death 128
1 Variable Value
2 classPath rogue
3 classChar r
4 trn rogue
5 stand 96
6 walk 96
7 attack 128
8 bow 128
9 swHit 96
10 block 96
11 lightning 96
12 fire 96
13 magic 96
14 death 128

10
assets/txtdata/classes/rogue/starting_loadout.tsv

@ -0,0 +1,10 @@
Variable Value
skill TrapDisarm
spell Null
spellLevel 0
item0 IDI_ROGUE
item1 IDI_HEAL
item2 IDI_HEAL
item3 IDI_NONE
item4 IDI_NONE
gold 100
1 Variable Value
2 skill TrapDisarm
3 spell Null
4 spellLevel 0
5 item0 IDI_ROGUE
6 item1 IDI_HEAL
7 item2 IDI_HEAL
8 item3 IDI_NONE
9 item4 IDI_NONE
10 gold 100

28
assets/txtdata/classes/sorcerer/animations.tsv

@ -0,0 +1,28 @@
Variable Value
unarmedFrames 20
unarmedActionFrame 12
unarmedShieldFrames 16
unarmedShieldActionFrame 9
swordFrames 16
swordActionFrame 12
swordShieldFrames 16
swordShieldActionFrame 12
bowFrames 20
bowActionFrame 16
axeFrames 24
axeActionFrame 16
maceFrames 16
maceActionFrame 12
maceShieldFrames 16
maceShieldActionFrame 12
staffFrames 16
staffActionFrame 12
idleFrames 8
walkingFrames 8
blockingFrames 6
deathFrames 20
castingFrames 12
recoveryFrames 8
townIdleFrames 20
townWalkingFrames 8
castingActionFrame 8
1 Variable Value
2 unarmedFrames 20
3 unarmedActionFrame 12
4 unarmedShieldFrames 16
5 unarmedShieldActionFrame 9
6 swordFrames 16
7 swordActionFrame 12
8 swordShieldFrames 16
9 swordShieldActionFrame 12
10 bowFrames 20
11 bowActionFrame 16
12 axeFrames 24
13 axeActionFrame 16
14 maceFrames 16
15 maceActionFrame 12
16 maceShieldFrames 16
17 maceShieldActionFrame 12
18 staffFrames 16
19 staffActionFrame 12
20 idleFrames 8
21 walkingFrames 8
22 blockingFrames 6
23 deathFrames 20
24 castingFrames 12
25 recoveryFrames 8
26 townIdleFrames 20
27 townWalkingFrames 8
28 castingActionFrame 8

104
assets/txtdata/classes/sorcerer/sounds.tsv

@ -0,0 +1,104 @@
speech sfx
ChamberOfBoneLore Sorceror1
HorazonsSanctumLore Sorceror2
GolemSpellLore Sorceror3
HorazonsCreatureOfFlameLore Sorceror4
MortaVespaGaieaInnuminoEvegeenJatanLuaGraton Sorceror5
GrimspikeLieutenantOfBelialLore Sorceror6
HorazonsJournal Sorceror7
YourDeathWillBeAvenged Sorceror8
RestInPeaceMyFriend Sorceror9
ValorLore Sorceror10
HallsOfTheBlindLore Sorceror11
WarlordOfBloodLore Sorceror12
ICantUseThisYet Sorceror13
ICantCarryAnymore Sorceror14
IHaveNoRoom Sorceror15
WhereWouldIPutThis Sorceror16
NoWay Sorceror17
NotAChance Sorceror18
IdNeverUseThis Sorceror19
IdHaveToEquipThat Sorceror20
ICantMoveThis Sorceror21
ICantMoveThisYet Sorceror22
ICantOpenThis Sorceror23
ICantOpenThisYet Sorceror24
ICantLiftThis Sorceror25
ICantLiftThisYet Sorceror26
ICantCastThatHere Sorceror27
ICantCastThatYet Sorceror28
ThatDidntDoAnything Sorceror29
ICanAlreadyDoThat Sorceror30
IDontNeedThat Sorceror31
IDontNeedToDoThat Sorceror32
IDontWantThat Sorceror33
IDontHaveASpellReady Sorceror34
NotEnoughMana Sorceror35
ThatWouldKillMe Sorceror36
ICantDoThat Sorceror37
No Sorceror38
Yes Sorceror39
ThatWontWork Sorceror40
ThatWontWorkHere Sorceror41
ThatWontWorkYet Sorceror42
ICantGetThereFromHere Sorceror43
ItsTooHeavy Sorceror44
ItsTooBig Sorceror45
JustWhatIWasLookingFor Sorceror46
IveGotABadFeelingAboutThis Sorceror47
GotMilk Sorceror48
ImNotThirsty Sorceror49
ImNoMilkmaid Sorceror50
ICouldBlowUpTheWholeVillage Sorceror51
YepThatsACowAlright Sorceror52
TooUghHeavy Sorceror53
InSpirituSanctum Sorceror54
PraedictumOtium Sorceror55
EfficioObitusUtInimicus Sorceror56
TheEnchantmentIsGone Sorceror57
OhTooEasy Sorceror58
BackToTheGrave Sorceror59
TimeToDie Sorceror60
ImNotImpressed Sorceror61
ImSorryDidIBreakYourConcentration Sorceror62
VengeanceIsMine Sorceror63
Die Sorceror64
Yeah Sorceror65
Ah Sorceror66
Phew Sorceror67
Argh Sorceror68
ArghClang Sorceror69
Aaaaargh Sorceror70
OofAh Sorceror71
HeavyBreathing Sorceror72
Oh Sorceror73
Wow Sorceror74
ThankTheLight Sorceror75
WhatWasThat Sorceror76
MmHmm Sorceror77
Hmm Sorceror78
UhHuh Sorceror79
TheSpiritsOfTheDeadAreNowAvenged Sorceror80
TheTownIsSafeFromTheseFoulSpawn Sorceror81
RestWellLeoricIllFindYourSon Sorceror82
YourMadnessEndsHereBetrayer Sorceror83
YoullLureNoMoreMenToTheirDeaths Sorceror84
ReturnToHeavenWarriorOfLight Sorceror85
ICanSeeWhyTheyFearThisWeapon Sorceror86
ThisMustBeWhatGriswoldWanted Sorceror87
INeedToGetThisToLachdanan Sorceror88
INeedToGetThisToGriswold Sorceror89
IveNeverBeenHereBefore Sorceror90
MayTheSpiritOfArkaineProtectMe Sorceror91
ThisIsAPlaceOfGreatPower Sorceror92
ThisBladeMustBeDestroyed Sorceror93
YourReignOfPainHasEnded Sorceror94
NowThatsOneBigMushroom Sorceror95
TheSmellOfDeathSurroundsMe Sorceror96
TheSanctityOfThisPlaceHasBeenFouled Sorceror97
ItsHotDownHere Sorceror98
IMustBeGettingClose Sorceror99
MaybeItsLockedFromTheInside Sorceror100
LooksLikeItsRustedShut Sorceror101
MaybeTheresAnotherWay Sorceror102
AuughUh Sorceror71
1 speech sfx
2 ChamberOfBoneLore Sorceror1
3 HorazonsSanctumLore Sorceror2
4 GolemSpellLore Sorceror3
5 HorazonsCreatureOfFlameLore Sorceror4
6 MortaVespaGaieaInnuminoEvegeenJatanLuaGraton Sorceror5
7 GrimspikeLieutenantOfBelialLore Sorceror6
8 HorazonsJournal Sorceror7
9 YourDeathWillBeAvenged Sorceror8
10 RestInPeaceMyFriend Sorceror9
11 ValorLore Sorceror10
12 HallsOfTheBlindLore Sorceror11
13 WarlordOfBloodLore Sorceror12
14 ICantUseThisYet Sorceror13
15 ICantCarryAnymore Sorceror14
16 IHaveNoRoom Sorceror15
17 WhereWouldIPutThis Sorceror16
18 NoWay Sorceror17
19 NotAChance Sorceror18
20 IdNeverUseThis Sorceror19
21 IdHaveToEquipThat Sorceror20
22 ICantMoveThis Sorceror21
23 ICantMoveThisYet Sorceror22
24 ICantOpenThis Sorceror23
25 ICantOpenThisYet Sorceror24
26 ICantLiftThis Sorceror25
27 ICantLiftThisYet Sorceror26
28 ICantCastThatHere Sorceror27
29 ICantCastThatYet Sorceror28
30 ThatDidntDoAnything Sorceror29
31 ICanAlreadyDoThat Sorceror30
32 IDontNeedThat Sorceror31
33 IDontNeedToDoThat Sorceror32
34 IDontWantThat Sorceror33
35 IDontHaveASpellReady Sorceror34
36 NotEnoughMana Sorceror35
37 ThatWouldKillMe Sorceror36
38 ICantDoThat Sorceror37
39 No Sorceror38
40 Yes Sorceror39
41 ThatWontWork Sorceror40
42 ThatWontWorkHere Sorceror41
43 ThatWontWorkYet Sorceror42
44 ICantGetThereFromHere Sorceror43
45 ItsTooHeavy Sorceror44
46 ItsTooBig Sorceror45
47 JustWhatIWasLookingFor Sorceror46
48 IveGotABadFeelingAboutThis Sorceror47
49 GotMilk Sorceror48
50 ImNotThirsty Sorceror49
51 ImNoMilkmaid Sorceror50
52 ICouldBlowUpTheWholeVillage Sorceror51
53 YepThatsACowAlright Sorceror52
54 TooUghHeavy Sorceror53
55 InSpirituSanctum Sorceror54
56 PraedictumOtium Sorceror55
57 EfficioObitusUtInimicus Sorceror56
58 TheEnchantmentIsGone Sorceror57
59 OhTooEasy Sorceror58
60 BackToTheGrave Sorceror59
61 TimeToDie Sorceror60
62 ImNotImpressed Sorceror61
63 ImSorryDidIBreakYourConcentration Sorceror62
64 VengeanceIsMine Sorceror63
65 Die Sorceror64
66 Yeah Sorceror65
67 Ah Sorceror66
68 Phew Sorceror67
69 Argh Sorceror68
70 ArghClang Sorceror69
71 Aaaaargh Sorceror70
72 OofAh Sorceror71
73 HeavyBreathing Sorceror72
74 Oh Sorceror73
75 Wow Sorceror74
76 ThankTheLight Sorceror75
77 WhatWasThat Sorceror76
78 MmHmm Sorceror77
79 Hmm Sorceror78
80 UhHuh Sorceror79
81 TheSpiritsOfTheDeadAreNowAvenged Sorceror80
82 TheTownIsSafeFromTheseFoulSpawn Sorceror81
83 RestWellLeoricIllFindYourSon Sorceror82
84 YourMadnessEndsHereBetrayer Sorceror83
85 YoullLureNoMoreMenToTheirDeaths Sorceror84
86 ReturnToHeavenWarriorOfLight Sorceror85
87 ICanSeeWhyTheyFearThisWeapon Sorceror86
88 ThisMustBeWhatGriswoldWanted Sorceror87
89 INeedToGetThisToLachdanan Sorceror88
90 INeedToGetThisToGriswold Sorceror89
91 IveNeverBeenHereBefore Sorceror90
92 MayTheSpiritOfArkaineProtectMe Sorceror91
93 ThisIsAPlaceOfGreatPower Sorceror92
94 ThisBladeMustBeDestroyed Sorceror93
95 YourReignOfPainHasEnded Sorceror94
96 NowThatsOneBigMushroom Sorceror95
97 TheSmellOfDeathSurroundsMe Sorceror96
98 TheSanctityOfThisPlaceHasBeenFouled Sorceror97
99 ItsHotDownHere Sorceror98
100 IMustBeGettingClose Sorceror99
101 MaybeItsLockedFromTheInside Sorceror100
102 LooksLikeItsRustedShut Sorceror101
103 MaybeTheresAnotherWay Sorceror102
104 AuughUh Sorceror71

14
assets/txtdata/classes/sorcerer/sprites.tsv

@ -0,0 +1,14 @@
Variable Value
classPath sorceror
classChar s
trn sorcerer
stand 96
walk 96
attack 128
bow 128
swHit 96
block 96
lightning 128
fire 128
magic 128
death 128
1 Variable Value
2 classPath sorceror
3 classChar s
4 trn sorcerer
5 stand 96
6 walk 96
7 attack 128
8 bow 128
9 swHit 96
10 block 96
11 lightning 128
12 fire 128
13 magic 128
14 death 128

10
assets/txtdata/classes/sorcerer/starting_loadout.tsv

@ -0,0 +1,10 @@
Variable Value
skill StaffRecharge
spell Firebolt
spellLevel 2
item0 IDI_SORCERER_DIABLO
item1 IDI_MANA
item2 IDI_MANA
item3 IDI_NONE
item4 IDI_NONE
gold 100
1 Variable Value
2 skill StaffRecharge
3 spell Firebolt
4 spellLevel 2
5 item0 IDI_SORCERER_DIABLO
6 item1 IDI_MANA
7 item2 IDI_MANA
8 item3 IDI_NONE
9 item4 IDI_NONE
10 gold 100

28
assets/txtdata/classes/warrior/animations.tsv

@ -0,0 +1,28 @@
Variable Value
unarmedFrames 16
unarmedActionFrame 9
unarmedShieldFrames 16
unarmedShieldActionFrame 9
swordFrames 16
swordActionFrame 9
swordShieldFrames 16
swordShieldActionFrame 9
bowFrames 16
bowActionFrame 11
axeFrames 20
axeActionFrame 10
maceFrames 16
maceActionFrame 9
maceShieldFrames 16
maceShieldActionFrame 9
staffFrames 16
staffActionFrame 11
idleFrames 10
walkingFrames 8
blockingFrames 2
deathFrames 20
castingFrames 20
recoveryFrames 6
townIdleFrames 20
townWalkingFrames 8
castingActionFrame 14
1 Variable Value
2 unarmedFrames 16
3 unarmedActionFrame 9
4 unarmedShieldFrames 16
5 unarmedShieldActionFrame 9
6 swordFrames 16
7 swordActionFrame 9
8 swordShieldFrames 16
9 swordShieldActionFrame 9
10 bowFrames 16
11 bowActionFrame 11
12 axeFrames 20
13 axeActionFrame 10
14 maceFrames 16
15 maceActionFrame 9
16 maceShieldFrames 16
17 maceShieldActionFrame 9
18 staffFrames 16
19 staffActionFrame 11
20 idleFrames 10
21 walkingFrames 8
22 blockingFrames 2
23 deathFrames 20
24 castingFrames 20
25 recoveryFrames 6
26 townIdleFrames 20
27 townWalkingFrames 8
28 castingActionFrame 14

104
assets/txtdata/classes/warrior/sounds.tsv

@ -0,0 +1,104 @@
speech sfx
ChamberOfBoneLore Warrior1
HorazonsSanctumLore Warrior2
GolemSpellLore Warrior3
HorazonsCreatureOfFlameLore Warrior4
MortaVespaGaieaInnuminoEvegeenJatanLuaGraton Warrior5
GrimspikeLieutenantOfBelialLore Warrior6
HorazonsJournal Warrior7
YourDeathWillBeAvenged Warrior8
RestInPeaceMyFriend Warrior9
ValorLore Warrior10
HallsOfTheBlindLore Warrior11
WarlordOfBloodLore Warrior12
ICantUseThisYet Warrior13
ICantCarryAnymore Warrior14
IHaveNoRoom Warrior15
WhereWouldIPutThis Warrior16
NoWay Warrior17
NotAChance Warrior18
IdNeverUseThis Warrior19
IdHaveToEquipThat Warrior20
ICantMoveThis Warrior21
ICantMoveThisYet Warrior22
ICantOpenThis Warrior23
ICantOpenThisYet Warrior24
ICantLiftThis Warrior25
ICantLiftThisYet Warrior26
ICantCastThatHere Warrior27
ICantCastThatYet Warrior28
ThatDidntDoAnything Warrior29
ICanAlreadyDoThat Warrior30
IDontNeedThat Warrior31
IDontNeedToDoThat Warrior32
IDontWantThat Warrior33
IDontHaveASpellReady Warrior34
NotEnoughMana Warrior35
ThatWouldKillMe Warrior36
ICantDoThat Warrior37
No Warrior38
Yes Warrior39
ThatWontWork Warrior40
ThatWontWorkHere Warrior41
ThatWontWorkYet Warrior42
ICantGetThereFromHere Warrior43
ItsTooHeavy Warrior44
ItsTooBig Warrior45
JustWhatIWasLookingFor Warrior46
IveGotABadFeelingAboutThis Warrior47
GotMilk Warrior48
ImNotThirsty Warrior49
ImNoMilkmaid Warrior50
ICouldBlowUpTheWholeVillage Warrior51
YepThatsACowAlright Warrior52
TooUghHeavy Warrior53
InSpirituSanctum Warrior54
PraedictumOtium Warrior55
EfficioObitusUtInimicus Warrior56
TheEnchantmentIsGone Warrior57
OhTooEasy Warrior58
BackToTheGrave Warrior59
TimeToDie Warrior60
ImNotImpressed Warrior61
ImSorryDidIBreakYourConcentration Warrior62
VengeanceIsMine Warrior63
Die Warrior64
Yeah Warrior65
Ah Warrior66
Phew Warrior67
Argh Warrior68
ArghClang Warrior69
Aaaaargh Warrior70
OofAh Warrior71
HeavyBreathing Warrior72
Oh Warrior73
Wow Warrior74
ThankTheLight Warrior75
WhatWasThat Warrior76
MmHmm Warrior77
Hmm Warrior78
UhHuh Warrior79
TheSpiritsOfTheDeadAreNowAvenged Warrior80
TheTownIsSafeFromTheseFoulSpawn Warrior81
RestWellLeoricIllFindYourSon Warrior82
YourMadnessEndsHereBetrayer Warrior83
YoullLureNoMoreMenToTheirDeaths Warrior84
ReturnToHeavenWarriorOfLight Warrior85
ICanSeeWhyTheyFearThisWeapon Warrior86
ThisMustBeWhatGriswoldWanted Warrior87
INeedToGetThisToLachdanan Warrior88
INeedToGetThisToGriswold Warrior89
IveNeverBeenHereBefore Warrior90
MayTheSpiritOfArkaineProtectMe Warrior91
ThisIsAPlaceOfGreatPower Warrior92
ThisBladeMustBeDestroyed Warrior93
YourReignOfPainHasEnded Warrior94
NowThatsOneBigMushroom Warrior95
TheSmellOfDeathSurroundsMe Warrior96b
TheSanctityOfThisPlaceHasBeenFouled Warrior97
ItsHotDownHere Warrior98
IMustBeGettingClose Warrior99
MaybeItsLockedFromTheInside Warrior100
LooksLikeItsRustedShut Warrior101
MaybeTheresAnotherWay Warrior102
AuughUh WarriorDeath
1 speech sfx
2 ChamberOfBoneLore Warrior1
3 HorazonsSanctumLore Warrior2
4 GolemSpellLore Warrior3
5 HorazonsCreatureOfFlameLore Warrior4
6 MortaVespaGaieaInnuminoEvegeenJatanLuaGraton Warrior5
7 GrimspikeLieutenantOfBelialLore Warrior6
8 HorazonsJournal Warrior7
9 YourDeathWillBeAvenged Warrior8
10 RestInPeaceMyFriend Warrior9
11 ValorLore Warrior10
12 HallsOfTheBlindLore Warrior11
13 WarlordOfBloodLore Warrior12
14 ICantUseThisYet Warrior13
15 ICantCarryAnymore Warrior14
16 IHaveNoRoom Warrior15
17 WhereWouldIPutThis Warrior16
18 NoWay Warrior17
19 NotAChance Warrior18
20 IdNeverUseThis Warrior19
21 IdHaveToEquipThat Warrior20
22 ICantMoveThis Warrior21
23 ICantMoveThisYet Warrior22
24 ICantOpenThis Warrior23
25 ICantOpenThisYet Warrior24
26 ICantLiftThis Warrior25
27 ICantLiftThisYet Warrior26
28 ICantCastThatHere Warrior27
29 ICantCastThatYet Warrior28
30 ThatDidntDoAnything Warrior29
31 ICanAlreadyDoThat Warrior30
32 IDontNeedThat Warrior31
33 IDontNeedToDoThat Warrior32
34 IDontWantThat Warrior33
35 IDontHaveASpellReady Warrior34
36 NotEnoughMana Warrior35
37 ThatWouldKillMe Warrior36
38 ICantDoThat Warrior37
39 No Warrior38
40 Yes Warrior39
41 ThatWontWork Warrior40
42 ThatWontWorkHere Warrior41
43 ThatWontWorkYet Warrior42
44 ICantGetThereFromHere Warrior43
45 ItsTooHeavy Warrior44
46 ItsTooBig Warrior45
47 JustWhatIWasLookingFor Warrior46
48 IveGotABadFeelingAboutThis Warrior47
49 GotMilk Warrior48
50 ImNotThirsty Warrior49
51 ImNoMilkmaid Warrior50
52 ICouldBlowUpTheWholeVillage Warrior51
53 YepThatsACowAlright Warrior52
54 TooUghHeavy Warrior53
55 InSpirituSanctum Warrior54
56 PraedictumOtium Warrior55
57 EfficioObitusUtInimicus Warrior56
58 TheEnchantmentIsGone Warrior57
59 OhTooEasy Warrior58
60 BackToTheGrave Warrior59
61 TimeToDie Warrior60
62 ImNotImpressed Warrior61
63 ImSorryDidIBreakYourConcentration Warrior62
64 VengeanceIsMine Warrior63
65 Die Warrior64
66 Yeah Warrior65
67 Ah Warrior66
68 Phew Warrior67
69 Argh Warrior68
70 ArghClang Warrior69
71 Aaaaargh Warrior70
72 OofAh Warrior71
73 HeavyBreathing Warrior72
74 Oh Warrior73
75 Wow Warrior74
76 ThankTheLight Warrior75
77 WhatWasThat Warrior76
78 MmHmm Warrior77
79 Hmm Warrior78
80 UhHuh Warrior79
81 TheSpiritsOfTheDeadAreNowAvenged Warrior80
82 TheTownIsSafeFromTheseFoulSpawn Warrior81
83 RestWellLeoricIllFindYourSon Warrior82
84 YourMadnessEndsHereBetrayer Warrior83
85 YoullLureNoMoreMenToTheirDeaths Warrior84
86 ReturnToHeavenWarriorOfLight Warrior85
87 ICanSeeWhyTheyFearThisWeapon Warrior86
88 ThisMustBeWhatGriswoldWanted Warrior87
89 INeedToGetThisToLachdanan Warrior88
90 INeedToGetThisToGriswold Warrior89
91 IveNeverBeenHereBefore Warrior90
92 MayTheSpiritOfArkaineProtectMe Warrior91
93 ThisIsAPlaceOfGreatPower Warrior92
94 ThisBladeMustBeDestroyed Warrior93
95 YourReignOfPainHasEnded Warrior94
96 NowThatsOneBigMushroom Warrior95
97 TheSmellOfDeathSurroundsMe Warrior96b
98 TheSanctityOfThisPlaceHasBeenFouled Warrior97
99 ItsHotDownHere Warrior98
100 IMustBeGettingClose Warrior99
101 MaybeItsLockedFromTheInside Warrior100
102 LooksLikeItsRustedShut Warrior101
103 MaybeTheresAnotherWay Warrior102
104 AuughUh WarriorDeath

14
assets/txtdata/classes/warrior/sprites.tsv

@ -0,0 +1,14 @@
Variable Value
classPath warrior
classChar w
trn warrior
stand 96
walk 96
attack 128
bow 96
swHit 96
block 96
lightning 96
fire 96
magic 96
death 128
1 Variable Value
2 classPath warrior
3 classChar w
4 trn warrior
5 stand 96
6 walk 96
7 attack 128
8 bow 96
9 swHit 96
10 block 96
11 lightning 96
12 fire 96
13 magic 96
14 death 128

10
assets/txtdata/classes/warrior/starting_loadout.tsv

@ -0,0 +1,10 @@
Variable Value
skill ItemRepair
spell Null
spellLevel 0
item0 IDI_WARRIOR
item1 IDI_WARRSHLD
item2 IDI_WARRCLUB
item3 IDI_HEAL
item4 IDI_HEAL
gold 100
1 Variable Value
2 skill ItemRepair
3 spell Null
4 spellLevel 0
5 item0 IDI_WARRIOR
6 item1 IDI_WARRSHLD
7 item2 IDI_WARRCLUB
8 item3 IDI_HEAL
9 item4 IDI_HEAL
10 gold 100

7
mods/Hellfire/txtdata/classes/classdat.tsv

@ -0,0 +1,7 @@
className folderName portrait inv
Warrior warrior 0 inv
Rogue rogue 1 inv_rog
Sorcerer sorcerer 2 inv_sor
Monk monk 3 inv_sor
Bard bard 4 inv_rog
Barbarian barbarian 0 inv
1 className folderName portrait inv
2 Warrior warrior 0 inv
3 Rogue rogue 1 inv_rog
4 Sorcerer sorcerer 2 inv_sor
5 Monk monk 3 inv_sor
6 Bard bard 4 inv_rog
7 Barbarian barbarian 0 inv

10
mods/Hellfire/txtdata/classes/sorcerer/starting_loadout.tsv

@ -0,0 +1,10 @@
Variable Value
skill StaffRecharge
spell Firebolt
spellLevel 2
item0 IDI_SORCERER
item1 IDI_HEAL
item2 IDI_HEAL
item3 IDI_NONE
item4 IDI_NONE
gold 100
1 Variable Value
2 skill StaffRecharge
3 spell Firebolt
4 spellLevel 2
5 item0 IDI_SORCERER
6 item1 IDI_HEAL
7 item2 IDI_HEAL
8 item3 IDI_NONE
9 item4 IDI_NONE
10 gold 100

5
test/player_test.cpp

@ -84,6 +84,11 @@ BlockTestCase BlockData[] = {
TEST(Player, PM_DoGotHit)
{
LoadCoreArchives();
LoadGameArchives();
ASSERT_TRUE(HaveMainData());
LoadPlayerDataFiles();
Players.resize(1);
MyPlayer = &Players[0];
for (size_t i = 0; i < sizeof(BlockData) / sizeof(*BlockData); i++) {

9
tools/extract_translation_data.py

@ -6,6 +6,7 @@ root = pathlib.Path(__file__).resolve().parent.parent
translation_dummy_path = root.joinpath("Source/translation_dummy.cpp")
base_paths = {
"classdat": root.joinpath("assets/txtdata/classes/classdat.tsv"),
"monstdat": root.joinpath("assets/txtdata/monsters/monstdat.tsv"),
"unique_monstdat": root.joinpath("assets/txtdata/monsters/unique_monstdat.tsv"),
"itemdat": root.joinpath("assets/txtdata/items/itemdat.tsv"),
@ -40,6 +41,14 @@ def write_entry(temp_source, var_name, context, string_value, use_p):
temp_source.write(f'const char *{var_name} = N_("{string_value}");\n')
def process_files(paths, temp_source):
# Classes
if "classdat" in paths:
with open(paths["classdat"], 'r') as tsv:
reader = csv.DictReader(tsv, delimiter='\t')
for i, row in enumerate(reader):
var_name = 'CLASS_' + row['className'].upper().replace(' ', '_').replace('-', '_')
write_entry(temp_source, f'{var_name}_NAME', "default", row['className'], False)
# Monsters
with open(paths["monstdat"], 'r') as tsv:
reader = csv.DictReader(tsv, delimiter='\t')

Loading…
Cancel
Save