diff --git a/CMake/Assets.cmake b/CMake/Assets.cmake index d09a4f28d..38f604781 100644 --- a/CMake/Assets.cmake +++ b/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 diff --git a/CMake/Mods.cmake b/CMake/Mods.cmake index 2a5a84224..354a96401 100644 --- a/CMake/Mods.cmake +++ b/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 diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 935758f59..0b4f25939 100644 --- a/Source/CMakeLists.txt +++ b/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 diff --git a/Source/DiabloUI/diabloui.cpp b/Source/DiabloUI/diabloui.cpp index f394e1d50..a083811aa 100644 --- a/Source/DiabloUI/diabloui.cpp +++ b/Source/DiabloUI/diabloui.cpp @@ -82,8 +82,8 @@ std::size_t SelectedItem = 0; namespace { OptionalOwnedClxSpriteList ArtHero; -std::array::value + 1> ArtHeroPortraitOrder; -std::array::value + 1> ArtHeroOverrides; +std::vector ArtHeroPortraitOrder; +std::vector 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(HeroClass::Monk)] = 3; - ArtHeroPortraitOrder[static_cast(HeroClass::Bard)] = 4; - ArtHeroPortraitOrder[enum_size::value] = 5; + ArtHeroPortraitOrder.resize(GetNumPlayerClasses() + 1); + for (size_t i = 0; i < GetNumPlayerClasses(); ++i) { + const PlayerData &playerClassData = GetPlayerDataForClass(static_cast(i)); + ArtHeroPortraitOrder[i] = playerClassData.portrait; } - if (numPortraits >= 7) { - ArtHeroPortraitOrder[static_cast(HeroClass::Barbarian)] = 6; + ArtHeroPortraitOrder.back() = 3; + if (numPortraits >= 6) { + ArtHeroPortraitOrder.back() = 5; } - for (size_t i = 0; i <= enum_size::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); diff --git a/Source/DiabloUI/hero/selhero.cpp b/Source/DiabloUI/hero/selhero.cpp index 1f3f5516d..d1d222e91 100644 --- a/Source/DiabloUI/hero/selhero.cpp +++ b/Source/DiabloUI/hero/selhero.cpp @@ -143,7 +143,7 @@ void SelheroListFocus(size_t value) return; } - SELHERO_DIALOG_HERO_IMG->setSprite(UiGetHeroDialogSprite(enum_size::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(_("Warrior"), static_cast(HeroClass::Warrior))); - vecSelHeroDlgItems.push_back(std::make_unique(_("Rogue"), static_cast(HeroClass::Rogue))); - vecSelHeroDlgItems.push_back(std::make_unique(_("Sorcerer"), static_cast(HeroClass::Sorcerer))); - if (gbIsHellfire) { - vecSelHeroDlgItems.push_back(std::make_unique(_("Monk"), static_cast(HeroClass::Monk))); - if (HaveBardAssets() || *GetOptions().Gameplay.testBard) { - vecSelHeroDlgItems.push_back(std::make_unique(_("Bard"), static_cast(HeroClass::Bard))); + for (size_t i = 0; i < GetNumPlayerClasses(); ++i) { + const HeroClass heroClass = static_cast(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(_("Barbarian"), static_cast(HeroClass::Barbarian))); + + if (heroClass == HeroClass::Barbarian && !HaveBarbarianAssets() && !(*GetOptions().Gameplay.testBarbarian)) { + continue; } + + const PlayerData &playerData = GetPlayerDataForClass(heroClass); + vecSelHeroDlgItems.push_back(std::make_unique(std::string_view(playerData.className), static_cast(heroClass))); } if (vecSelHeroDlgItems.size() > 4) itemH = 26; - const int itemY = static_cast(246 + (176 - vecSelHeroDlgItems.size() * itemH) / 2); - vecSelDlgItems.push_back(std::make_unique(vecSelHeroDlgItems, vecSelHeroDlgItems.size(), uiPosition.x + 264, (uiPosition.y + itemY), 320, itemH, UiFlags::AlignCenter | UiFlags::FontSize24 | UiFlags::ColorUiGold)); + const int itemY = static_cast(246 + (176 - std::min(vecSelHeroDlgItems.size(), 6) * itemH) / 2); + vecSelDlgItems.push_back(std::make_unique(vecSelHeroDlgItems, std::min(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((*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(_("OK"), &UiFocusNavigationSelect, rect2, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiGold)); diff --git a/Source/data/value_reader.cpp b/Source/data/value_reader.cpp new file mode 100644 index 000000000..b72c5f670 --- /dev/null +++ b/Source/data/value_reader.cpp @@ -0,0 +1,37 @@ +#include "data/value_reader.hpp" + +#include + +#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 diff --git a/Source/data/value_reader.hpp b/Source/data/value_reader.hpp new file mode 100644 index 000000000..808912abb --- /dev/null +++ b/Source/data/value_reader.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#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 + void readValue(std::string_view expectedKey, T &outValue, F &&readFn) + { + DataFileField valueField = getValueField(expectedKey); + if (const tl::expected result = readFn(valueField, outValue); + !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename_, "Value", valueField); + } + ++it_; + } + + template + void read(std::string_view expectedKey, T &outValue, F &&parseFn) + { + readValue(expectedKey, outValue, [&parseFn](DataFileField &valueField, T &outValue) -> tl::expected { + const auto result = parseFn(valueField.value()); + if (!result.has_value()) { + return tl::make_unexpected(devilution::DataFileField::Error::InvalidValue); + } + + outValue = result.value(); + return {}; + }); + } + + template + typename std::enable_if_t, void> + readInt(std::string_view expectedKey, T &outValue) + { + readValue(expectedKey, outValue, [](DataFileField &valueField, T &outValue) { + return valueField.parseInt(outValue); + }); + } + + template + typename std::enable_if_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 readChar(std::string_view expectedKey, char &outValue) + { + readValue(expectedKey, outValue, [](DataFileField &valueField, char &outValue) -> tl::expected { + 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 diff --git a/Source/discord/discord.cpp b/Source/discord/discord.cpp index 52f227a78..95b022946 100644 --- a/Source/discord/discord.cpp +++ b/Source/discord/discord.cpp @@ -124,7 +124,7 @@ std::string GetTooltipString() std::string GetPlayerAssetString() { char chars[5] { - CharChar[static_cast(MyPlayer->_pClass)], + GetPlayerSpriteDataForClass(MyPlayer->_pClass).classChar, ArmourChar[tracked_data.playerGfx >> 4], WepChar[tracked_data.playerGfx & 0xF], 'a', diff --git a/Source/effects.cpp b/Source/effects.cpp index 38cb7cd9d..703cb6362 100644 --- a/Source/effects.cpp +++ b/Source/effects.cpp @@ -278,7 +278,14 @@ void sound_init() mask |= sfx_MONK; break; default: - app_fatal("effects:1"); + if (static_cast(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 ParseHeroSpeech(std::string_view value) +{ + const std::optional enumValueOpt = magic_enum::enum_cast(value); + if (enumValueOpt.has_value()) { + return enumValueOpt.value(); + } + return tl::make_unexpected("Unknown enum value."); +} + tl::expected ParseSfxId(std::string_view value) { const std::optional enumValueOpt = magic_enum::enum_cast(value); diff --git a/Source/effects.h b/Source/effects.h index a6967c982..dd1aa258b 100644 --- a/Source/effects.h +++ b/Source/effects.h @@ -37,6 +37,7 @@ void ui_sound_init(); void effects_play_sound(SfxID); int GetSFXLength(SfxID nSFX); +tl::expected ParseHeroSpeech(std::string_view value); tl::expected ParseSfxId(std::string_view value); } // namespace devilution diff --git a/Source/effects_stubs.cpp b/Source/effects_stubs.cpp index b424dc3ae..950796bfd 100644 --- a/Source/effects_stubs.cpp +++ b/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 ParseHeroSpeech(std::string_view value) +{ + const std::optional enumValueOpt = magic_enum::enum_cast(value); + if (enumValueOpt.has_value()) { + return enumValueOpt.value(); + } + return tl::make_unexpected("Unknown enum value."); +} + tl::expected ParseSfxId(std::string_view value) { const std::optional enumValueOpt = magic_enum::enum_cast(value); diff --git a/Source/engine/trn.cpp b/Source/engine/trn.cpp index 86be4d7ee..e4121b120 100644 --- a/Source/engine/trn.cpp +++ b/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> GetClassTRN(Player &player) { std::array 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)) { diff --git a/Source/inv.cpp b/Source/inv.cpp index 23ec0eaf9..168285b25 100644 --- a/Source/inv.cpp +++ b/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(SidePanelSize.width)); - break; - case HeroClass::Rogue: - case HeroClass::Bard: - pInvCels = LoadCel("data\\inv\\inv_rog", static_cast(SidePanelSize.width)); - break; - case HeroClass::Sorcerer: - pInvCels = LoadCel("data\\inv\\inv_sor", static_cast(SidePanelSize.width)); - break; - case HeroClass::Monk: - pInvCels = LoadCel(!gbIsSpawn ? "data\\inv\\inv_sor" : "data\\inv\\inv", static_cast(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(SidePanelSize.width)); } void DrawInv(const Surface &out) diff --git a/Source/itemdat.cpp b/Source/itemdat.cpp index 7e003f8cc..619193765 100644 --- a/Source/itemdat.cpp +++ b/Source/itemdat.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include "data/file.hpp" @@ -42,6 +41,15 @@ std::vector ItemPrefixes; /** Contains the data related to each item suffix. */ std::vector 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 ParseItemClass(std::string_view value) diff --git a/Source/itemdat.h b/Source/itemdat.h index 914db668c..146516fcd 100644 --- a/Source/itemdat.h +++ b/Source/itemdat.h @@ -10,6 +10,8 @@ #include #include +#include +#include #include "objdat.h" #include "spelldat.h" @@ -654,8 +656,15 @@ extern std::vector ItemSuffixes; extern DVL_API_FOR_TEST std::vector UniqueItems; extern ankerl::unordered_dense::map 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 { + static constexpr int min = devilution::_item_indexes::IDI_NONE; + static constexpr int max = devilution::_item_indexes::IDI_NUM_DEFAULT_ITEMS; +}; diff --git a/Source/items.cpp b/Source/items.cpp index fff24343d..87219d541 100644 --- a/Source/items.cpp +++ b/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); } diff --git a/Source/minitext.cpp b/Source/minitext.cpp index ce0cb412e..1b25b0db2 100644 --- a/Source/minitext.cpp +++ b/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(MyPlayer->_pClass)]; switch (sfxnr) { case SfxID::Warrior1: - sfxnr = classSounds[static_cast(HeroSpeech::ChamberOfBoneLore)]; + sfxnr = GetHeroSound(MyPlayer->_pClass, HeroSpeech::ChamberOfBoneLore); break; case SfxID::Warrior10: - sfxnr = classSounds[static_cast(HeroSpeech::ValorLore)]; + sfxnr = GetHeroSound(MyPlayer->_pClass, HeroSpeech::ValorLore); break; case SfxID::Warrior11: - sfxnr = classSounds[static_cast(HeroSpeech::HallsOfTheBlindLore)]; + sfxnr = GetHeroSound(MyPlayer->_pClass, HeroSpeech::HallsOfTheBlindLore); break; case SfxID::Warrior12: - sfxnr = classSounds[static_cast(HeroSpeech::WarlordOfBloodLore)]; + sfxnr = GetHeroSound(MyPlayer->_pClass, HeroSpeech::WarlordOfBloodLore); break; case SfxID::Warrior54: - sfxnr = classSounds[static_cast(HeroSpeech::InSpirituSanctum)]; + sfxnr = GetHeroSound(MyPlayer->_pClass, HeroSpeech::InSpirituSanctum); break; case SfxID::Warrior55: - sfxnr = classSounds[static_cast(HeroSpeech::PraedictumOtium)]; + sfxnr = GetHeroSound(MyPlayer->_pClass, HeroSpeech::PraedictumOtium); break; case SfxID::Warrior56: - sfxnr = classSounds[static_cast(HeroSpeech::EfficioObitusUtInimicus)]; + sfxnr = GetHeroSound(MyPlayer->_pClass, HeroSpeech::EfficioObitusUtInimicus); break; default: break; diff --git a/Source/multi.cpp b/Source/multi.cpp index 8994993a3..07ed97284 100644 --- a/Source/multi.cpp +++ b/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(player._pClass) < enum_size::value + return static_cast(player._pClass) < GetNumPlayerClasses() && player.plrlevel < NUMLEVELS && InDungeonBounds(player.position.tile) && !std::string_view(player._pName).empty(); diff --git a/Source/pack.cpp b/Source/pack.cpp index 5aec1cd70..e06c7ff47 100644 --- a/Source/pack.cpp +++ b/Source/pack.cpp @@ -359,7 +359,7 @@ void UnPackPlayer(const PlayerPack &packed, Player &player) player.position.future = position; player.setLevel(std::clamp(packed.plrlevel, 0, NUMLEVELS)); - player._pClass = static_cast(std::clamp(packed.pClass, 0, enum_size::value - 1)); + player._pClass = static_cast(std::clamp(packed.pClass, 0, static_cast(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::value); + ValidateField(packed.pClass, packed.pClass < GetNumPlayerClasses()); player._pClass = static_cast(packed.pClass); const Point position { packed.px, packed.py }; diff --git a/Source/player.cpp b/Source/player.cpp index e49c18e06..69e36dd95 100644 --- a/Source/player.cpp +++ b/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(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(_pClass)][static_cast(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(_pClass)][static_cast(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(_pClass)][static_cast(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(player._pgfxnum & 0xF)); - const char *path = PlayersSpriteData[static_cast(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(cls)], ArmourChar[player._pgfxnum >> 4], WepChar[static_cast(animWeaponId)] }; + char prefix[3] = { spriteData.classChar, ArmourChar[player._pgfxnum >> 4], WepChar[static_cast(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(player._pgfxnum & 0xF)); - const char *path = PlayersSpriteData[static_cast(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(cls)], ArmourChar[player._pgfxnum >> 4], WepChar[static_cast(animWeaponId)] }; + char prefix[3] = { spriteData.classChar, ArmourChar[player._pgfxnum >> 4], WepChar[static_cast(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(pc)]; + const PlayerAnimData &plrAtkAnimData = GetPlayerAnimDataForClass(pc); auto gn = static_cast(player._pgfxnum & 0xFU); if (leveltype == DTYPE_TOWN) { diff --git a/Source/player.h b/Source/player.h index cea07355f..597109349 100644 --- a/Source/player.h +++ b/Source/player.h @@ -183,16 +183,6 @@ constexpr std::array WepChar = { 't', // staff }; -/** Maps from player class to letter used in graphic files. */ -constexpr std::array CharChar = { - 'w', // warrior - 'r', // rogue - 's', // sorcerer - 'm', // monk - 'b', // bard - 'c', // barbarian -}; - /** * @brief Contains Data (CelSprites) for a player graphic (player_graphic) */ diff --git a/Source/playerdat.cpp b/Source/playerdat.cpp index fe6cd8215..6195b441d 100644 --- a/Source/playerdat.cpp +++ b/Source/playerdat.cpp @@ -15,8 +15,11 @@ #include #include +#include #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 dataFileResult = DataFile::load(filename); - if (!dataFileResult.has_value()) { - DataFile::reportFatalError(dataFileResult.error(), filename); - } + tl::expected 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 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 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 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 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 &sounds) +{ + const std::string filename = StrCat("txtdata\\classes\\", classPath, "\\sounds.tsv"); + tl::expected 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 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([&](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 PlayersData; + std::vector ClassAttributesPerClass; std::vector PlayersCombatData; +std::vector PlayersStartingLoadoutData; + +/** Contains the data related to each player class. */ +std::vector PlayersSpriteData; + +std::vector PlayersAnimData; + +std::vector> 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(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(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::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(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(playerClass)]; + return PlayersData.size(); } -const SfxID herosounds[enum_size::value][enum_size::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(playerClass); + assert(playerClassIndex < PlayersData.size()); + return PlayersData[playerClassIndex]; +} const PlayerCombatData &GetPlayerCombatDataForClass(HeroClass pClass) { - return PlayersCombatData[static_cast(pClass)]; + const size_t playerClassIndex = static_cast(pClass); + assert(playerClassIndex < PlayersCombatData.size()); + return PlayersCombatData[playerClassIndex]; } const PlayerStartingLoadoutData &GetPlayerStartingLoadoutForClass(HeroClass pClass) { - return PlayersStartingLoadoutData[static_cast(pClass)]; + const size_t playerClassIndex = static_cast(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(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(pClass); + assert(playerClassIndex < PlayersAnimData.size()); + return PlayersAnimData[playerClassIndex]; +} } // namespace devilution diff --git a/Source/playerdat.hpp b/Source/playerdat.hpp index 7b4ff3e9a..b7c934171 100644 --- a/Source/playerdat.hpp +++ b/Source/playerdat.hpp @@ -22,14 +22,20 @@ enum class HeroClass : uint8_t { Bard, Barbarian, - LAST = Barbarian + NUM_MAX_CLASSES = std::numeric_limits::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 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::value][enum_size::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 diff --git a/assets/txtdata/classes/barbarian/animations.tsv b/assets/txtdata/classes/barbarian/animations.tsv new file mode 100644 index 000000000..8db3b9e27 --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/barbarian/sounds.tsv b/assets/txtdata/classes/barbarian/sounds.tsv new file mode 100644 index 000000000..27ece85df --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/barbarian/sprites.tsv b/assets/txtdata/classes/barbarian/sprites.tsv new file mode 100644 index 000000000..2b4ef461b --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/barbarian/starting_loadout.tsv b/assets/txtdata/classes/barbarian/starting_loadout.tsv new file mode 100644 index 000000000..fe78508e2 --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/bard/animations.tsv b/assets/txtdata/classes/bard/animations.tsv new file mode 100644 index 000000000..5daf2976d --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/bard/sounds.tsv b/assets/txtdata/classes/bard/sounds.tsv new file mode 100644 index 000000000..830fac9f3 --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/bard/sprites.tsv b/assets/txtdata/classes/bard/sprites.tsv new file mode 100644 index 000000000..c34c15a95 --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/bard/starting_loadout.tsv b/assets/txtdata/classes/bard/starting_loadout.tsv new file mode 100644 index 000000000..9f3c34423 --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/classdat.tsv b/assets/txtdata/classes/classdat.tsv new file mode 100644 index 000000000..0032b30da --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/monk/animations.tsv b/assets/txtdata/classes/monk/animations.tsv new file mode 100644 index 000000000..931628e86 --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/monk/sounds.tsv b/assets/txtdata/classes/monk/sounds.tsv new file mode 100644 index 000000000..45f39ae44 --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/monk/sprites.tsv b/assets/txtdata/classes/monk/sprites.tsv new file mode 100644 index 000000000..ad75f6503 --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/monk/starting_loadout.tsv b/assets/txtdata/classes/monk/starting_loadout.tsv new file mode 100644 index 000000000..40b2ab791 --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/rogue/animations.tsv b/assets/txtdata/classes/rogue/animations.tsv new file mode 100644 index 000000000..22f69bf72 --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/rogue/sounds.tsv b/assets/txtdata/classes/rogue/sounds.tsv new file mode 100644 index 000000000..830fac9f3 --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/rogue/sprites.tsv b/assets/txtdata/classes/rogue/sprites.tsv new file mode 100644 index 000000000..be4afcd29 --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/rogue/starting_loadout.tsv b/assets/txtdata/classes/rogue/starting_loadout.tsv new file mode 100644 index 000000000..2a11c3830 --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/sorcerer/animations.tsv b/assets/txtdata/classes/sorcerer/animations.tsv new file mode 100644 index 000000000..e5938c346 --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/sorcerer/sounds.tsv b/assets/txtdata/classes/sorcerer/sounds.tsv new file mode 100644 index 000000000..2a2c7e7da --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/sorcerer/sprites.tsv b/assets/txtdata/classes/sorcerer/sprites.tsv new file mode 100644 index 000000000..eb8d96c0c --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/sorcerer/starting_loadout.tsv b/assets/txtdata/classes/sorcerer/starting_loadout.tsv new file mode 100644 index 000000000..cf9c3aa70 --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/warrior/animations.tsv b/assets/txtdata/classes/warrior/animations.tsv new file mode 100644 index 000000000..5770f7274 --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/warrior/sounds.tsv b/assets/txtdata/classes/warrior/sounds.tsv new file mode 100644 index 000000000..18765d307 --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/warrior/sprites.tsv b/assets/txtdata/classes/warrior/sprites.tsv new file mode 100644 index 000000000..39fe0360b --- /dev/null +++ b/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 diff --git a/assets/txtdata/classes/warrior/starting_loadout.tsv b/assets/txtdata/classes/warrior/starting_loadout.tsv new file mode 100644 index 000000000..52a722c72 --- /dev/null +++ b/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 diff --git a/mods/Hellfire/txtdata/classes/classdat.tsv b/mods/Hellfire/txtdata/classes/classdat.tsv new file mode 100644 index 000000000..3f69dbada --- /dev/null +++ b/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 diff --git a/mods/Hellfire/txtdata/classes/sorcerer/starting_loadout.tsv b/mods/Hellfire/txtdata/classes/sorcerer/starting_loadout.tsv new file mode 100644 index 000000000..0c7c29d80 --- /dev/null +++ b/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 diff --git a/test/player_test.cpp b/test/player_test.cpp index 541eb12a9..fb5ed31d9 100644 --- a/test/player_test.cpp +++ b/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++) { diff --git a/tools/extract_translation_data.py b/tools/extract_translation_data.py index 3b4ea358e..4dbdda068 100755 --- a/tools/extract_translation_data.py +++ b/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')