diff --git a/.editorconfig b/.editorconfig index 4278da51c..3d7d00f2e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -58,6 +58,9 @@ end_of_line = lf [*.txt] end_of_line = crlf +[*.tsv] +trim_trailing_whitespace = false + [AppRun] end_of_line = lf diff --git a/CMake/Assets.cmake b/CMake/Assets.cmake index 5b0ccce21..9d6532b66 100644 --- a/CMake/Assets.cmake +++ b/CMake/Assets.cmake @@ -156,6 +156,7 @@ set(devilutionx_assets txtdata/classes/sorcerer/attributes.tsv txtdata/classes/warrior/attributes.tsv txtdata/monsters/monstdat.tsv + txtdata/monsters/unique_monstdat.tsv ui_art/diablo.pal ui_art/hellfire.pal ui_art/creditsw.clx diff --git a/Packaging/resources/assets/txtdata/monsters/unique_monstdat.tsv b/Packaging/resources/assets/txtdata/monsters/unique_monstdat.tsv new file mode 100644 index 000000000..841b86097 --- /dev/null +++ b/Packaging/resources/assets/txtdata/monsters/unique_monstdat.tsv @@ -0,0 +1,101 @@ +type name trn level maxHp ai intelligence minDamage maxDamage resistance monsterPack customToHit customArmorClass talkMessage +MT_NGOATMC Gharbad the Weak bsdb 4 120 Gharbad 3 8 16 IMMUNE_LIGHTNING None 0 0 TEXT_GARBUD1 +MT_SKING Skeleton King genrl 0 240 SkeletonKing 3 6 16 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING Independent 0 0 +MT_COUNSLR Zhar the Mad general 8 360 Zhar 3 16 40 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING None 0 0 TEXT_ZHAR1 +MT_BFALLSP Snotspill bng 4 220 Snotspill 3 10 18 RESIST_LIGHTNING None 0 0 TEXT_BANNER10 +MT_ADVOCATE Arch-Bishop Lazarus general 0 600 Lazarus 3 30 50 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING None 0 0 TEXT_VILE13 +MT_HLSPWN Red Vex redv 0 400 LazarusSuccubus 3 30 50 IMMUNE_MAGIC,RESIST_FIRE None 0 0 TEXT_VILE13 +MT_HLSPWN Black Jade blkjd 0 400 LazarusSuccubus 3 30 50 IMMUNE_MAGIC,RESIST_LIGHTNING None 0 0 TEXT_VILE13 +MT_RBLACK Lachdanan bhka 14 500 Lachdanan 3 0 0 None 0 0 TEXT_VEIL9 +MT_BTBLACK Warlord of Blood general 13 850 Warlord 3 35 50 IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING None 0 0 TEXT_WARLRD9 +MT_CLEAVER The Butcher genrl 0 220 Butcher 3 6 12 RESIST_FIRE,RESIST_LIGHTNING None 0 0 +MT_HORKDMN Hork Demon genrl 19 300 HorkDemon 3 20 35 RESIST_LIGHTNING None 0 0 +MT_DEFILER The Defiler genrl 20 480 SkeletonMelee 3 30 40 RESIST_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING None 0 0 +MT_NAKRUL Na-Krul genrl 0 1332 SkeletonMelee 3 40 50 IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING Leashed 0 0 +MT_TSKELAX Bonehead Keenaxe bhka 2 91 SkeletonMelee 2 4 10 IMMUNE_MAGIC Leashed 100 0 +MT_RFALLSD Bladeskin the Slasher bsts 2 51 Fallen 0 6 18 RESIST_FIRE Leashed 0 45 +MT_NZOMBIE Soulpus general 2 133 Zombie 0 4 8 RESIST_FIRE,RESIST_LIGHTNING None 0 0 +MT_RFALLSP Pukerat the Unclean ptu 2 77 Fallen 3 1 5 RESIST_FIRE None 0 0 +MT_WSKELAX Boneripper br 2 54 Bat 0 6 15 IMMUNE_MAGIC,IMMUNE_FIRE Leashed 0 0 +MT_NZOMBIE Rotfeast the Hungry eth 2 85 SkeletonMelee 3 4 12 IMMUNE_MAGIC Leashed 0 0 +MT_DFALLSD Gutshank the Quick gtq 3 66 Bat 2 6 16 RESIST_FIRE Leashed 0 0 +MT_TSKELSD Brokenhead Bangshield bhbs 3 108 SkeletonMelee 3 12 20 IMMUNE_MAGIC,RESIST_LIGHTNING Leashed 0 0 +MT_YFALLSP Bongo bng 3 178 Fallen 3 9 21 Leashed 0 0 +MT_BZOMBIE Rotcarnage rcrn 3 102 Zombie 3 9 24 IMMUNE_MAGIC,RESIST_LIGHTNING Leashed 0 45 +MT_NSCAV Shadowbite shbt 2 60 SkeletonMelee 3 3 20 IMMUNE_FIRE Leashed 0 0 +MT_WSKELBW Deadeye de 2 49 GoatRanged 0 6 9 IMMUNE_MAGIC,RESIST_FIRE None 0 0 +MT_RSKELAX Madeye the Dead mtd 4 75 Bat 0 9 21 IMMUNE_MAGIC,IMMUNE_FIRE Leashed 0 30 +MT_BSCAV El Chupacabras general 3 120 GoatMelee 0 10 18 RESIST_FIRE Leashed 0 0 +MT_TSKELBW Skullfire skfr 3 125 GoatRanged 1 6 10 IMMUNE_FIRE None 0 0 +MT_SNEAK Warpskull tspo 3 117 Sneak 2 6 18 RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_GZOMBIE Goretongue pmr 3 156 SkeletonMelee 1 15 30 IMMUNE_MAGIC None 0 0 +MT_WSCAV Pulsecrawler bhka 4 150 Scavenger 0 16 20 IMMUNE_FIRE,RESIST_LIGHTNING Leashed 0 45 +MT_BLINK Moonbender general 4 135 Bat 0 9 27 IMMUNE_FIRE Leashed 0 0 +MT_BLINK Wrathraven general 5 135 Bat 2 9 22 IMMUNE_FIRE Leashed 0 0 +MT_YSCAV Spineeater general 4 180 Scavenger 1 18 25 IMMUNE_LIGHTNING Leashed 0 0 +MT_RSKELBW Blackash the Burning bashtb 4 120 GoatRanged 0 6 16 IMMUNE_MAGIC,IMMUNE_FIRE Leashed 0 0 +MT_BFALLSD Shadowcrow general 5 270 Sneak 2 12 25 Leashed 0 0 +MT_LRDSAYTR Blightstone the Weak bhka 4 360 SkeletonMelee 0 4 12 IMMUNE_MAGIC,RESIST_LIGHTNING Leashed 70 0 +MT_FAT Bilefroth the Pit Master bftp 6 210 Bat 1 16 23 IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_NGOATBW Bloodskin Darkbow bsdb 5 207 GoatRanged 0 3 16 RESIST_FIRE,RESIST_LIGHTNING Leashed 0 55 +MT_GLOOM Foulwing db 5 246 Rhino 3 12 28 RESIST_FIRE Leashed 0 0 +MT_XSKELSD Shadowdrinker shdr 5 300 Sneak 1 18 26 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING None 0 45 +MT_UNSEEN Hazeshifter bhka 5 285 Sneak 3 18 30 IMMUNE_LIGHTNING Leashed 0 0 +MT_NACID Deathspit bfds 6 303 AcidUnique 0 12 32 RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_RGOATMC Bloodgutter bgbl 6 315 Bat 1 24 34 IMMUNE_FIRE Leashed 0 0 +MT_BGOATMC Deathshade Fleshmaul dsfm 6 276 Rhino 0 12 24 IMMUNE_MAGIC,RESIST_FIRE None 0 65 +MT_WYRM Warmaggot the Mad general 6 246 Bat 3 15 30 RESIST_LIGHTNING Leashed 0 0 +MT_STORM Glasskull the Jagged bhka 7 354 Storm 0 18 30 IMMUNE_MAGIC,IMMUNE_FIRE Leashed 0 0 +MT_RGOATBW Blightfire blf 7 321 Succubus 2 13 21 IMMUNE_FIRE Leashed 0 0 +MT_GARGOYLE Nightwing the Cold general 7 342 Bat 1 18 26 IMMUNE_MAGIC,RESIST_LIGHTNING Leashed 0 0 +MT_GGOATBW Gorestone general 7 303 GoatRanged 1 15 28 RESIST_LIGHTNING Leashed 70 0 +MT_BMAGMA Bronzefist Firestone general 8 360 Magma 0 30 36 IMMUNE_MAGIC,RESIST_FIRE Leashed 0 0 +MT_INCIN Wrathfire the Doomed wftd 8 270 SkeletonMelee 2 20 30 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_NMAGMA Firewound the Grim bhka 8 303 Magma 0 18 22 IMMUNE_MAGIC,RESIST_FIRE Leashed 0 0 +MT_MUDMAN Baron Sludge bsm 8 315 Sneak 3 25 34 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING Leashed 0 75 +MT_GGOATMC Blighthorn Steelmace bhsm 7 250 Rhino 0 20 28 RESIST_LIGHTNING Leashed 0 45 +MT_RACID Chaoshowler general 8 240 AcidUnique 0 12 20 Leashed 0 0 +MT_REDDTH Doomgrin the Rotting general 8 405 Storm 3 25 50 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_FLAMLRD Madburner general 9 270 Storm 0 20 40 IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING Leashed 0 0 +MT_LTCHDMN Bonesaw the Litch general 9 495 Storm 2 30 55 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_MUDRUN Breakspine general 9 351 Rhino 0 25 34 RESIST_FIRE Leashed 0 0 +MT_REDDTH Devilskull Sharpbone general 9 444 Storm 1 25 40 IMMUNE_FIRE Leashed 0 0 +MT_STORM Brokenstorm general 9 411 Storm 2 25 36 IMMUNE_LIGHTNING Leashed 0 0 +MT_RSTORM Stormbane general 9 555 Storm 3 30 30 IMMUNE_LIGHTNING Leashed 0 0 +MT_TOAD Oozedrool general 9 483 Fat 3 25 30 RESIST_LIGHTNING Leashed 0 0 +MT_BLOODCLW Goldblight of the Flame general 10 405 Gargoyle 0 15 35 IMMUNE_MAGIC,IMMUNE_FIRE Leashed 0 80 +MT_OBLORD Blackstorm general 10 525 Rhino 3 20 40 IMMUNE_MAGIC,IMMUNE_LIGHTNING Leashed 0 90 +MT_RACID Plaguewrath general 10 450 AcidUnique 2 20 30 IMMUNE_MAGIC,RESIST_FIRE Leashed 0 0 +MT_RSTORM The Flayer general 10 501 Storm 1 20 35 RESIST_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING Leashed 0 0 +MT_FROSTC Bluehorn general 11 477 Rhino 1 25 30 IMMUNE_MAGIC,RESIST_FIRE Leashed 0 90 +MT_HELLBURN Warpfire Hellspawn general 11 525 FireMan 3 10 40 RESIST_MAGIC,IMMUNE_FIRE Leashed 0 0 +MT_NSNAKE Fangspeir general 11 444 SkeletonMelee 1 15 32 IMMUNE_FIRE Leashed 0 0 +MT_UDEDBLRG Festerskull general 11 600 Storm 2 15 30 IMMUNE_MAGIC Leashed 0 0 +MT_NBLACK Lionskull the Bent general 12 525 SkeletonMelee 2 25 25 IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING Leashed 0 0 +MT_COUNSLR Blacktongue general 12 360 Counselor 3 15 30 RESIST_FIRE Leashed 0 0 +MT_DEATHW Viletouch general 12 525 Gargoyle 3 20 40 IMMUNE_LIGHTNING Leashed 0 0 +MT_RSNAKE Viperflame general 12 570 SkeletonMelee 1 25 35 IMMUNE_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_BSNAKE Fangskin bhka 14 681 SkeletonMelee 2 15 50 IMMUNE_MAGIC,RESIST_LIGHTNING Leashed 0 0 +MT_SUCCUBUS Witchfire the Unholy general 12 444 Succubus 3 10 20 IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_BALROG Blackskull bhka 13 750 SkeletonMelee 3 25 40 IMMUNE_MAGIC,RESIST_LIGHTNING Leashed 0 0 +MT_UNRAV Soulslash general 12 450 SkeletonMelee 0 25 25 IMMUNE_MAGIC Leashed 0 0 +MT_VTEXLRD Windspawn general 12 711 SkeletonMelee 1 35 40 IMMUNE_MAGIC,IMMUNE_FIRE Leashed 0 0 +MT_GSNAKE Lord of the Pit general 13 762 SkeletonMelee 2 25 42 RESIST_FIRE Leashed 0 0 +MT_RTBLACK Rustweaver general 13 400 SkeletonMelee 3 1 60 IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING None 0 0 +MT_HOLOWONE Howlingire the Shade general 13 450 SkeletonMelee 2 40 75 RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_MAEL Doomcloud general 13 612 Storm 1 1 60 RESIST_FIRE,IMMUNE_LIGHTNING None 0 0 +MT_PAINMSTR Bloodmoon Soulfire general 13 684 SkeletonMelee 1 15 40 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_SNOWWICH Witchmoon general 13 310 Succubus 3 30 40 RESIST_LIGHTNING None 0 0 +MT_VTEXLRD Gorefeast general 13 771 SkeletonMelee 3 20 55 RESIST_FIRE None 0 0 +MT_RTBLACK Graywar the Slayer general 14 672 SkeletonMelee 1 30 50 RESIST_LIGHTNING None 0 0 +MT_MAGISTR Dreadjudge general 14 540 Counselor 1 30 40 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_HLSPWN Stareye the Witch general 14 726 Succubus 2 30 50 IMMUNE_FIRE None 0 0 +MT_BTBLACK Steelskull the Hunter general 14 831 SkeletonMelee 3 40 50 RESIST_LIGHTNING None 0 0 +MT_RBLACK Sir Gorash general 16 1050 SkeletonMelee 1 20 60 None 0 0 +MT_CABALIST The Vizier general 15 850 Counselor 2 25 40 IMMUNE_FIRE Leashed 0 0 +MT_REALWEAV Zamphir general 15 891 SkeletonMelee 2 30 50 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_HLSPWN Bloodlust general 15 825 Succubus 1 20 55 IMMUNE_MAGIC,IMMUNE_LIGHTNING None 0 0 +MT_HLSPWN Webwidow general 16 774 Succubus 1 20 50 IMMUNE_MAGIC,IMMUNE_FIRE None 0 0 +MT_SOLBRNR Fleshdancer general 16 999 Succubus 3 30 50 IMMUNE_MAGIC,RESIST_FIRE None 0 0 +MT_OBLORD Grimspike general 19 534 Sneak 1 25 40 IMMUNE_MAGIC,RESIST_FIRE Leashed 0 0 +MT_STORML Doomlock general 28 534 Sneak 1 35 55 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 diff --git a/Source/data/file.cpp b/Source/data/file.cpp index 2365ff92e..01d0a0525 100644 --- a/Source/data/file.cpp +++ b/Source/data/file.cpp @@ -3,6 +3,7 @@ #include #include "engine/assets.hpp" +#include "utils/algorithm/container.hpp" #include "utils/language.h" namespace devilution { @@ -134,4 +135,12 @@ tl::expected DataFile::skipHeader() return {}; } +[[nodiscard]] size_t DataFile::numRecords() const +{ + if (content_.empty()) return 0; + const auto numNewlines = static_cast(c_count(content_, '\n') + (content_.back() == '\n' ? 0 : 1)); + if (numNewlines < 2) return 0; + return static_cast(numNewlines - 1); +} + } // namespace devilution diff --git a/Source/data/file.hpp b/Source/data/file.hpp index f01d47fe9..aadd5d7ba 100644 --- a/Source/data/file.hpp +++ b/Source/data/file.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -119,6 +120,9 @@ public: return {}; } + // Assumes a header + [[nodiscard]] size_t numRecords() const; + [[nodiscard]] const char *data() const { return content_.data(); diff --git a/Source/lua/modules/dev/monsters.cpp b/Source/lua/modules/dev/monsters.cpp index 1226bffc8..da4ea4a1e 100644 --- a/Source/lua/modules/dev/monsters.cpp +++ b/Source/lua/modules/dev/monsters.cpp @@ -30,9 +30,9 @@ std::string DebugCmdSpawnUniqueMonster(std::string name, std::optional int mtype = -1; UniqueMonsterType uniqueIndex = UniqueMonsterType::None; - for (size_t i = 0; UniqueMonstersData[i].mtype != MT_INVALID; i++) { + for (size_t i = 0; UniqueMonstersData.size(); ++i) { auto mondata = UniqueMonstersData[i]; - const std::string monsterName = AsciiStrToLower(mondata.mName); + const std::string monsterName = AsciiStrToLower(std::string_view(mondata.mName)); if (monsterName.find(name) == std::string::npos) continue; mtype = mondata.mtype; diff --git a/Source/monstdat.cpp b/Source/monstdat.cpp index f976f4d5a..45e9b9a24 100644 --- a/Source/monstdat.cpp +++ b/Source/monstdat.cpp @@ -38,6 +38,9 @@ const char *MonsterData::spritePath() const /** Contains the data related to each monster ID. */ std::vector MonstersData; +/** Contains the data related to each unique monster ID. */ +std::vector UniqueMonstersData; + /** * Map between .DUN file value and monster type enum */ @@ -201,162 +204,269 @@ const _monster_id MonstConvTbl[] = { MT_LRDSAYTR, }; +namespace { + +tl::expected<_monster_id, std::string> ParseMonsterId(std::string_view value) +{ + if (value == "MT_NZOMBIE") return MT_NZOMBIE; + if (value == "MT_BZOMBIE") return MT_BZOMBIE; + if (value == "MT_GZOMBIE") return MT_GZOMBIE; + if (value == "MT_YZOMBIE") return MT_YZOMBIE; + if (value == "MT_RFALLSP") return MT_RFALLSP; + if (value == "MT_DFALLSP") return MT_DFALLSP; + if (value == "MT_YFALLSP") return MT_YFALLSP; + if (value == "MT_BFALLSP") return MT_BFALLSP; + if (value == "MT_WSKELAX") return MT_WSKELAX; + if (value == "MT_TSKELAX") return MT_TSKELAX; + if (value == "MT_RSKELAX") return MT_RSKELAX; + if (value == "MT_XSKELAX") return MT_XSKELAX; + if (value == "MT_RFALLSD") return MT_RFALLSD; + if (value == "MT_DFALLSD") return MT_DFALLSD; + if (value == "MT_YFALLSD") return MT_YFALLSD; + if (value == "MT_BFALLSD") return MT_BFALLSD; + if (value == "MT_NSCAV") return MT_NSCAV; + if (value == "MT_BSCAV") return MT_BSCAV; + if (value == "MT_WSCAV") return MT_WSCAV; + if (value == "MT_YSCAV") return MT_YSCAV; + if (value == "MT_WSKELBW") return MT_WSKELBW; + if (value == "MT_TSKELBW") return MT_TSKELBW; + if (value == "MT_RSKELBW") return MT_RSKELBW; + if (value == "MT_XSKELBW") return MT_XSKELBW; + if (value == "MT_WSKELSD") return MT_WSKELSD; + if (value == "MT_TSKELSD") return MT_TSKELSD; + if (value == "MT_RSKELSD") return MT_RSKELSD; + if (value == "MT_XSKELSD") return MT_XSKELSD; + if (value == "MT_SNEAK") return MT_SNEAK; + if (value == "MT_STALKER") return MT_STALKER; + if (value == "MT_UNSEEN") return MT_UNSEEN; + if (value == "MT_ILLWEAV") return MT_ILLWEAV; + if (value == "MT_NGOATMC") return MT_NGOATMC; + if (value == "MT_BGOATMC") return MT_BGOATMC; + if (value == "MT_RGOATMC") return MT_RGOATMC; + if (value == "MT_GGOATMC") return MT_GGOATMC; + if (value == "MT_FIEND") return MT_FIEND; + if (value == "MT_GLOOM") return MT_GLOOM; + if (value == "MT_BLINK") return MT_BLINK; + if (value == "MT_FAMILIAR") return MT_FAMILIAR; + if (value == "MT_NGOATBW") return MT_NGOATBW; + if (value == "MT_BGOATBW") return MT_BGOATBW; + if (value == "MT_RGOATBW") return MT_RGOATBW; + if (value == "MT_GGOATBW") return MT_GGOATBW; + if (value == "MT_NACID") return MT_NACID; + if (value == "MT_RACID") return MT_RACID; + if (value == "MT_BACID") return MT_BACID; + if (value == "MT_XACID") return MT_XACID; + if (value == "MT_SKING") return MT_SKING; + if (value == "MT_FAT") return MT_FAT; + if (value == "MT_MUDMAN") return MT_MUDMAN; + if (value == "MT_TOAD") return MT_TOAD; + if (value == "MT_FLAYED") return MT_FLAYED; + if (value == "MT_WYRM") return MT_WYRM; + if (value == "MT_CAVSLUG") return MT_CAVSLUG; + if (value == "MT_DEVOUR") return MT_DEVOUR; + if (value == "MT_DVLWYRM") return MT_DVLWYRM; + if (value == "MT_NMAGMA") return MT_NMAGMA; + if (value == "MT_YMAGMA") return MT_YMAGMA; + if (value == "MT_BMAGMA") return MT_BMAGMA; + if (value == "MT_WMAGMA") return MT_WMAGMA; + if (value == "MT_HORNED") return MT_HORNED; + if (value == "MT_MUDRUN") return MT_MUDRUN; + if (value == "MT_FROSTC") return MT_FROSTC; + if (value == "MT_OBLORD") return MT_OBLORD; + if (value == "MT_BONEDMN") return MT_BONEDMN; + if (value == "MT_REDDTH") return MT_REDDTH; + if (value == "MT_LTCHDMN") return MT_LTCHDMN; + if (value == "MT_UDEDBLRG") return MT_UDEDBLRG; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INCIN") return MT_INCIN; + if (value == "MT_FLAMLRD") return MT_FLAMLRD; + if (value == "MT_DOOMFIRE") return MT_DOOMFIRE; + if (value == "MT_HELLBURN") return MT_HELLBURN; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_RSTORM") return MT_RSTORM; + if (value == "MT_STORM") return MT_STORM; + if (value == "MT_STORML") return MT_STORML; + if (value == "MT_MAEL") return MT_MAEL; + if (value == "MT_WINGED") return MT_WINGED; + if (value == "MT_GARGOYLE") return MT_GARGOYLE; + if (value == "MT_BLOODCLW") return MT_BLOODCLW; + if (value == "MT_DEATHW") return MT_DEATHW; + if (value == "MT_MEGA") return MT_MEGA; + if (value == "MT_GUARD") return MT_GUARD; + if (value == "MT_VTEXLRD") return MT_VTEXLRD; + if (value == "MT_BALROG") return MT_BALROG; + if (value == "MT_NSNAKE") return MT_NSNAKE; + if (value == "MT_RSNAKE") return MT_RSNAKE; + if (value == "MT_GSNAKE") return MT_GSNAKE; + if (value == "MT_BSNAKE") return MT_BSNAKE; + if (value == "MT_NBLACK") return MT_NBLACK; + if (value == "MT_RTBLACK") return MT_RTBLACK; + if (value == "MT_BTBLACK") return MT_BTBLACK; + if (value == "MT_RBLACK") return MT_RBLACK; + if (value == "MT_UNRAV") return MT_UNRAV; + if (value == "MT_HOLOWONE") return MT_HOLOWONE; + if (value == "MT_PAINMSTR") return MT_PAINMSTR; + if (value == "MT_REALWEAV") return MT_REALWEAV; + if (value == "MT_SUCCUBUS") return MT_SUCCUBUS; + if (value == "MT_SNOWWICH") return MT_SNOWWICH; + if (value == "MT_HLSPWN") return MT_HLSPWN; + if (value == "MT_SOLBRNR") return MT_SOLBRNR; + if (value == "MT_COUNSLR") return MT_COUNSLR; + if (value == "MT_MAGISTR") return MT_MAGISTR; + if (value == "MT_CABALIST") return MT_CABALIST; + if (value == "MT_ADVOCATE") return MT_ADVOCATE; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_DIABLO") return MT_DIABLO; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_GOLEM") return MT_GOLEM; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_BIGFALL") return MT_BIGFALL; + if (value == "MT_DARKMAGE") return MT_DARKMAGE; + if (value == "MT_HELLBOAR") return MT_HELLBOAR; + if (value == "MT_STINGER") return MT_STINGER; + if (value == "MT_PSYCHORB") return MT_PSYCHORB; + if (value == "MT_ARACHNON") return MT_ARACHNON; + if (value == "MT_FELLTWIN") return MT_FELLTWIN; + if (value == "MT_HORKSPWN") return MT_HORKSPWN; + if (value == "MT_VENMTAIL") return MT_VENMTAIL; + if (value == "MT_NECRMORB") return MT_NECRMORB; + if (value == "MT_SPIDLORD") return MT_SPIDLORD; + if (value == "MT_LASHWORM") return MT_LASHWORM; + if (value == "MT_TORCHANT") return MT_TORCHANT; + if (value == "MT_HORKDMN") return MT_HORKDMN; + if (value == "MT_DEFILER") return MT_DEFILER; + if (value == "MT_GRAVEDIG") return MT_GRAVEDIG; + if (value == "MT_TOMBRAT") return MT_TOMBRAT; + if (value == "MT_FIREBAT") return MT_FIREBAT; + if (value == "MT_SKLWING") return MT_SKLWING; + if (value == "MT_LICH") return MT_LICH; + if (value == "MT_CRYPTDMN") return MT_CRYPTDMN; + if (value == "MT_HELLBAT") return MT_HELLBAT; + if (value == "MT_BONEDEMN") return MT_BONEDEMN; + if (value == "MT_LICH") return MT_LICH; + if (value == "MT_BICLOPS") return MT_BICLOPS; + if (value == "MT_FLESTHNG") return MT_FLESTHNG; + if (value == "MT_REAPER") return MT_REAPER; + if (value == "MT_NAKRUL") return MT_NAKRUL; + if (value == "MT_CLEAVER") return MT_CLEAVER; + if (value == "MT_INVILORD") return MT_INVILORD; + if (value == "MT_LRDSAYTR") return MT_LRDSAYTR; + return tl::make_unexpected("Unknown enum value"); +} + tl::expected ParseMonsterAvailability(std::string_view value) { - if (value == "Always") - return MonsterAvailability::Always; - if (value == "Never") - return MonsterAvailability::Never; - if (value == "Retail") - return MonsterAvailability::Retail; + if (value == "Always") return MonsterAvailability::Always; + if (value == "Never") return MonsterAvailability::Never; + if (value == "Retail") return MonsterAvailability::Retail; return tl::make_unexpected("Expected one of: Always, Never, or Retail"); } tl::expected ParseAiId(std::string_view value) { - if (value == "Zombie") - return MonsterAIID::Zombie; - if (value == "Fat") - return MonsterAIID::Fat; - if (value == "SkeletonMelee") - return MonsterAIID::SkeletonMelee; - if (value == "SkeletonRanged") - return MonsterAIID::SkeletonRanged; - if (value == "Scavenger") - return MonsterAIID::Scavenger; - if (value == "Rhino") - return MonsterAIID::Rhino; - if (value == "GoatMelee") - return MonsterAIID::GoatMelee; - if (value == "GoatRanged") - return MonsterAIID::GoatRanged; - if (value == "Fallen") - return MonsterAIID::Fallen; - if (value == "Magma") - return MonsterAIID::Magma; - if (value == "SkeletonKing") - return MonsterAIID::SkeletonKing; - if (value == "Bat") - return MonsterAIID::Bat; - if (value == "Gargoyle") - return MonsterAIID::Gargoyle; - if (value == "Butcher") - return MonsterAIID::Butcher; - if (value == "Succubus") - return MonsterAIID::Succubus; - if (value == "Sneak") - return MonsterAIID::Sneak; - if (value == "Storm") - return MonsterAIID::Storm; - if (value == "FireMan") - return MonsterAIID::FireMan; - if (value == "Gharbad") - return MonsterAIID::Gharbad; - if (value == "Acid") - return MonsterAIID::Acid; - if (value == "AcidUnique") - return MonsterAIID::AcidUnique; - if (value == "Golem") - return MonsterAIID::Golem; - if (value == "Zhar") - return MonsterAIID::Zhar; - if (value == "Snotspill") - return MonsterAIID::Snotspill; - if (value == "Snake") - return MonsterAIID::Snake; - if (value == "Counselor") - return MonsterAIID::Counselor; - if (value == "Mega") - return MonsterAIID::Mega; - if (value == "Diablo") - return MonsterAIID::Diablo; - if (value == "Lazarus") - return MonsterAIID::Lazarus; - if (value == "LazarusSuccubus") - return MonsterAIID::LazarusSuccubus; - if (value == "Lachdanan") - return MonsterAIID::Lachdanan; - if (value == "Warlord") - return MonsterAIID::Warlord; - if (value == "FireBat") - return MonsterAIID::FireBat; - if (value == "Torchant") - return MonsterAIID::Torchant; - if (value == "HorkDemon") - return MonsterAIID::HorkDemon; - if (value == "Lich") - return MonsterAIID::Lich; - if (value == "ArchLich") - return MonsterAIID::ArchLich; - if (value == "Psychorb") - return MonsterAIID::Psychorb; - if (value == "Necromorb") - return MonsterAIID::Necromorb; - if (value == "BoneDemon") - return MonsterAIID::BoneDemon; + if (value == "Zombie") return MonsterAIID::Zombie; + if (value == "Fat") return MonsterAIID::Fat; + if (value == "SkeletonMelee") return MonsterAIID::SkeletonMelee; + if (value == "SkeletonRanged") return MonsterAIID::SkeletonRanged; + if (value == "Scavenger") return MonsterAIID::Scavenger; + if (value == "Rhino") return MonsterAIID::Rhino; + if (value == "GoatMelee") return MonsterAIID::GoatMelee; + if (value == "GoatRanged") return MonsterAIID::GoatRanged; + if (value == "Fallen") return MonsterAIID::Fallen; + if (value == "Magma") return MonsterAIID::Magma; + if (value == "SkeletonKing") return MonsterAIID::SkeletonKing; + if (value == "Bat") return MonsterAIID::Bat; + if (value == "Gargoyle") return MonsterAIID::Gargoyle; + if (value == "Butcher") return MonsterAIID::Butcher; + if (value == "Succubus") return MonsterAIID::Succubus; + if (value == "Sneak") return MonsterAIID::Sneak; + if (value == "Storm") return MonsterAIID::Storm; + if (value == "FireMan") return MonsterAIID::FireMan; + if (value == "Gharbad") return MonsterAIID::Gharbad; + if (value == "Acid") return MonsterAIID::Acid; + if (value == "AcidUnique") return MonsterAIID::AcidUnique; + if (value == "Golem") return MonsterAIID::Golem; + if (value == "Zhar") return MonsterAIID::Zhar; + if (value == "Snotspill") return MonsterAIID::Snotspill; + if (value == "Snake") return MonsterAIID::Snake; + if (value == "Counselor") return MonsterAIID::Counselor; + if (value == "Mega") return MonsterAIID::Mega; + if (value == "Diablo") return MonsterAIID::Diablo; + if (value == "Lazarus") return MonsterAIID::Lazarus; + if (value == "LazarusSuccubus") return MonsterAIID::LazarusSuccubus; + if (value == "Lachdanan") return MonsterAIID::Lachdanan; + if (value == "Warlord") return MonsterAIID::Warlord; + if (value == "FireBat") return MonsterAIID::FireBat; + if (value == "Torchant") return MonsterAIID::Torchant; + if (value == "HorkDemon") return MonsterAIID::HorkDemon; + if (value == "Lich") return MonsterAIID::Lich; + if (value == "ArchLich") return MonsterAIID::ArchLich; + if (value == "Psychorb") return MonsterAIID::Psychorb; + if (value == "Necromorb") return MonsterAIID::Necromorb; + if (value == "BoneDemon") return MonsterAIID::BoneDemon; return tl::make_unexpected("Unknown enum value"); } tl::expected ParseMonsterFlag(std::string_view value) { - if (value == "HIDDEN") - return MFLAG_HIDDEN; - if (value == "LOCK_ANIMATION") - return MFLAG_LOCK_ANIMATION; - if (value == "ALLOW_SPECIAL") - return MFLAG_ALLOW_SPECIAL; - if (value == "TARGETS_MONSTER") - return MFLAG_TARGETS_MONSTER; - if (value == "GOLEM") - return MFLAG_GOLEM; - if (value == "QUEST_COMPLETE") - return MFLAG_QUEST_COMPLETE; - if (value == "KNOCKBACK") - return MFLAG_KNOCKBACK; - if (value == "SEARCH") - return MFLAG_SEARCH; - if (value == "CAN_OPEN_DOOR") - return MFLAG_CAN_OPEN_DOOR; - if (value == "NO_ENEMY") - return MFLAG_NO_ENEMY; - if (value == "BERSERK") - return MFLAG_BERSERK; - if (value == "NOLIFESTEAL") - return MFLAG_NOLIFESTEAL; + if (value == "HIDDEN") return MFLAG_HIDDEN; + if (value == "LOCK_ANIMATION") return MFLAG_LOCK_ANIMATION; + if (value == "ALLOW_SPECIAL") return MFLAG_ALLOW_SPECIAL; + if (value == "TARGETS_MONSTER") return MFLAG_TARGETS_MONSTER; + if (value == "GOLEM") return MFLAG_GOLEM; + if (value == "QUEST_COMPLETE") return MFLAG_QUEST_COMPLETE; + if (value == "KNOCKBACK") return MFLAG_KNOCKBACK; + if (value == "SEARCH") return MFLAG_SEARCH; + if (value == "CAN_OPEN_DOOR") return MFLAG_CAN_OPEN_DOOR; + if (value == "NO_ENEMY") return MFLAG_NO_ENEMY; + if (value == "BERSERK") return MFLAG_BERSERK; + if (value == "NOLIFESTEAL") return MFLAG_NOLIFESTEAL; return tl::make_unexpected("Unknown enum value"); } tl::expected ParseMonsterClass(std::string_view value) { - if (value == "Undead") - return MonsterClass::Undead; - if (value == "Demon") - return MonsterClass::Demon; - if (value == "Animal") - return MonsterClass::Animal; + if (value == "Undead") return MonsterClass::Undead; + if (value == "Demon") return MonsterClass::Demon; + if (value == "Animal") return MonsterClass::Animal; return tl::make_unexpected("Unknown enum value"); } tl::expected ParseMonsterResistance(std::string_view value) { - if (value == "RESIST_MAGIC") - return RESIST_MAGIC; - if (value == "RESIST_FIRE") - return RESIST_FIRE; - if (value == "RESIST_LIGHTNING") - return RESIST_LIGHTNING; - if (value == "IMMUNE_MAGIC") - return IMMUNE_MAGIC; - if (value == "IMMUNE_FIRE") - return IMMUNE_FIRE; - if (value == "IMMUNE_LIGHTNING") - return IMMUNE_LIGHTNING; - if (value == "IMMUNE_ACID") - return IMMUNE_ACID; + if (value == "RESIST_MAGIC") return RESIST_MAGIC; + if (value == "RESIST_FIRE") return RESIST_FIRE; + if (value == "RESIST_LIGHTNING") return RESIST_LIGHTNING; + if (value == "IMMUNE_MAGIC") return IMMUNE_MAGIC; + if (value == "IMMUNE_FIRE") return IMMUNE_FIRE; + if (value == "IMMUNE_LIGHTNING") return IMMUNE_LIGHTNING; + if (value == "IMMUNE_ACID") return IMMUNE_ACID; return tl::make_unexpected("Unknown enum value"); } -void LoadMonsterData() +tl::expected ParseUniqueMonsterPack(std::string_view value) +{ + if (value == "None") return UniqueMonsterPack::None; + if (value == "Independent") return UniqueMonsterPack::Independent; + if (value == "Leashed") return UniqueMonsterPack::Leashed; + return tl::make_unexpected("Unknown enum value"); +} + +void LoadMonstDat() { const std::string_view filename = "txtdata\\monsters\\monstdat.tsv"; tl::expected dataFileResult = DataFile::load(filename); @@ -371,6 +481,7 @@ void LoadMonsterData() } MonstersData.clear(); + MonstersData.reserve(dataFile.numRecords()); std::unordered_map spritePathToId; for (DataFileRecord record : dataFile) { FieldIterator fieldIt = record.begin(); @@ -585,7 +696,7 @@ void LoadMonsterData() } // treasure - // TODO: Replace this hack with proper parsing once unique monsters have been migrated. + // TODO: Replace this hack with proper parsing once items have been migrated to data files. advance(); { const std::string_view value = (*fieldIt).value(); @@ -608,6 +719,153 @@ void LoadMonsterData() DataFile::reportFatalFieldError(result.error(), filename, "exp", *fieldIt); } } + + MonstersData.shrink_to_fit(); +} + +void LoadUniqueMonstDat() +{ + const std::string_view filename = "txtdata\\monsters\\unique_monstdat.tsv"; + tl::expected dataFileResult = DataFile::load(filename); + if (!dataFileResult.has_value()) { + DataFile::reportFatalError(dataFileResult.error(), filename); + } + + DataFile &dataFile = dataFileResult.value(); + if (tl::expected result = dataFile.skipHeader(); + !result.has_value()) { + DataFile::reportFatalError(result.error(), filename); + } + + UniqueMonstersData.clear(); + UniqueMonstersData.reserve(dataFile.numRecords()); + for (DataFileRecord record : dataFile) { + FieldIterator fieldIt = record.begin(); + const FieldIterator endField = record.end(); + + UniqueMonstersData.emplace_back(); + UniqueMonsterData &monster = UniqueMonstersData.back(); + + const auto advance = [&]() { + ++fieldIt; + if (fieldIt == endField) { + DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename); + } + }; + + // type + if (tl::expected<_monster_id, std::string> result = ParseMonsterId((*fieldIt).value()); result.has_value()) { + monster.mtype = *std::move(result); + } else { + DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "type", *fieldIt, result.error()); + } + + // name + advance(); + monster.mName = (*fieldIt).value(); + + // trn + advance(); + monster.mTrnName = (*fieldIt).value(); + + // level + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.mlevel); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "level", *fieldIt); + } + + // maxHp + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.mmaxhp); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "maxHp", *fieldIt); + } + + // ai + advance(); + if (tl::expected result = ParseAiId((*fieldIt).value()); result.has_value()) { + monster.mAi = *std::move(result); + } else { + DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "ai", *fieldIt, result.error()); + } + + // intelligence + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.mint); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "intelligence", *fieldIt); + } + + // minDamage + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.mMinDamage); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "minDamage", *fieldIt); + } + + // maxDamage + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.mMaxDamage); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "maxDamage", *fieldIt); + } + + // resistance + advance(); + if (tl::expected result = (*fieldIt).parseEnumList(monster.mMagicRes, ParseMonsterResistance); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "resistance", *fieldIt); + } + + // monsterPack + advance(); + if (tl::expected result = ParseUniqueMonsterPack((*fieldIt).value()); result.has_value()) { + monster.monsterPack = *std::move(result); + } else { + DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "monsterPack", *fieldIt, result.error()); + } + + // customToHit + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.customToHit); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "customToHit", *fieldIt); + } + + // customArmorClass + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.customArmorClass); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "customArmorClass", *fieldIt); + } + + // talkMessage + // TODO: Replace this hack with proper parsing once messages have been migrated to data files. + advance(); + { + const std::string_view value = (*fieldIt).value(); + if (value.empty()) { + monster.mtalkmsg = TEXT_NONE; + } else if (value == "TEXT_GARBUD1") { + monster.mtalkmsg = TEXT_GARBUD1; + } else if (value == "TEXT_ZHAR1") { + monster.mtalkmsg = TEXT_ZHAR1; + } else if (value == "TEXT_BANNER10") { + monster.mtalkmsg = TEXT_BANNER10; + } else if (value == "TEXT_VILE13") { + monster.mtalkmsg = TEXT_VILE13; + } else if (value == "TEXT_VEIL9") { + monster.mtalkmsg = TEXT_VEIL9; + } else if (value == "TEXT_WARLRD9") { + monster.mtalkmsg = TEXT_WARLRD9; + } else { + DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "talkMessage", *fieldIt, "NOTE: Parser is incomplete"); + } + } + } + + UniqueMonstersData.shrink_to_fit(); +} + +} // namespace + +void LoadMonsterData() +{ + LoadMonstDat(); + LoadUniqueMonstDat(); } size_t GetNumMonsterSprites() @@ -615,114 +873,4 @@ size_t GetNumMonsterSprites() return MonsterSpritePaths.size(); } -/** Contains the data related to each unique monster ID. */ -const UniqueMonsterData UniqueMonstersData[] = { - // clang-format off -// mtype, mName, mTrnName, mlevel, mmaxhp, mAi, mint, mMinDamage, mMaxDamage, mMagicRes, monsterPack, customToHit, customArmorClass, mtalkmsg - // TRANSLATORS: Unique Monster Block start -{ MT_NGOATMC, P_("monster", "Gharbad the Weak"), "bsdb", 4, 120, MonsterAIID::Gharbad, 3, 8, 16, IMMUNE_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_GARBUD1 }, -{ MT_SKING, P_("monster", "Skeleton King"), "genrl", 0, 240, MonsterAIID::SkeletonKing, 3, 6, 16, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Independent, 0, 0, TEXT_NONE }, -{ MT_COUNSLR, P_("monster", "Zhar the Mad"), "general", 8, 360, MonsterAIID::Zhar, 3, 16, 40, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_ZHAR1 }, -{ MT_BFALLSP, P_("monster", "Snotspill"), "bng", 4, 220, MonsterAIID::Snotspill, 3, 10, 18, RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_BANNER10 }, -{ MT_ADVOCATE, P_("monster", "Arch-Bishop Lazarus"), "general", 0, 600, MonsterAIID::Lazarus, 3, 30, 50, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_VILE13 }, -{ MT_HLSPWN, P_("monster", "Red Vex"), "redv", 0, 400, MonsterAIID::LazarusSuccubus, 3, 30, 50, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_VILE13 }, -{ MT_HLSPWN, P_("monster", "Black Jade"), "blkjd", 0, 400, MonsterAIID::LazarusSuccubus, 3, 30, 50, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_VILE13 }, -{ MT_RBLACK, P_("monster", "Lachdanan"), "bhka", 14, 500, MonsterAIID::Lachdanan, 3, 0, 0, 0, UniqueMonsterPack::None, 0, 0, TEXT_VEIL9 }, -{ MT_BTBLACK, P_("monster", "Warlord of Blood"), "general", 13, 850, MonsterAIID::Warlord, 3, 35, 50, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_WARLRD9 }, -{ MT_CLEAVER, P_("monster", "The Butcher"), "genrl", 0, 220, MonsterAIID::Butcher, 3, 6, 12, RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_HORKDMN, P_("monster", "Hork Demon"), "genrl", 19, 300, MonsterAIID::HorkDemon, 3, 20, 35, RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_DEFILER, P_("monster", "The Defiler"), "genrl", 20, 480, MonsterAIID::SkeletonMelee, 3, 30, 40, RESIST_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_NAKRUL, P_("monster", "Na-Krul"), "genrl", 0, 1332, MonsterAIID::SkeletonMelee, 3, 40, 50, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_TSKELAX, P_("monster", "Bonehead Keenaxe"), "bhka", 2, 91, MonsterAIID::SkeletonMelee, 2, 4, 10, IMMUNE_MAGIC, UniqueMonsterPack::Leashed, 100, 0, TEXT_NONE }, -{ MT_RFALLSD, P_("monster", "Bladeskin the Slasher"), "bsts", 2, 51, MonsterAIID::Fallen, 0, 6, 18, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 45, TEXT_NONE }, -{ MT_NZOMBIE, P_("monster", "Soulpus"), "general", 2, 133, MonsterAIID::Zombie, 0, 4, 8, RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_RFALLSP, P_("monster", "Pukerat the Unclean"), "ptu", 2, 77, MonsterAIID::Fallen, 3, 1, 5, RESIST_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_WSKELAX, P_("monster", "Boneripper"), "br", 2, 54, MonsterAIID::Bat, 0, 6, 15, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_NZOMBIE, P_("monster", "Rotfeast the Hungry"), "eth", 2, 85, MonsterAIID::SkeletonMelee, 3, 4, 12, IMMUNE_MAGIC, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_DFALLSD, P_("monster", "Gutshank the Quick"), "gtq", 3, 66, MonsterAIID::Bat, 2, 6, 16, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_TSKELSD, P_("monster", "Brokenhead Bangshield"), "bhbs", 3, 108, MonsterAIID::SkeletonMelee, 3, 12, 20, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_YFALLSP, P_("monster", "Bongo"), "bng", 3, 178, MonsterAIID::Fallen, 3, 9, 21, 0, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_BZOMBIE, P_("monster", "Rotcarnage"), "rcrn", 3, 102, MonsterAIID::Zombie, 3, 9, 24, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 45, TEXT_NONE }, -{ MT_NSCAV, P_("monster", "Shadowbite"), "shbt", 2, 60, MonsterAIID::SkeletonMelee, 3, 3, 20, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_WSKELBW, P_("monster", "Deadeye"), "de", 2, 49, MonsterAIID::GoatRanged, 0, 6, 9, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_RSKELAX, P_("monster", "Madeye the Dead"), "mtd", 4, 75, MonsterAIID::Bat, 0, 9, 21, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 30, TEXT_NONE }, -{ MT_BSCAV, P_("monster", "El Chupacabras"), "general", 3, 120, MonsterAIID::GoatMelee, 0, 10, 18, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_TSKELBW, P_("monster", "Skullfire"), "skfr", 3, 125, MonsterAIID::GoatRanged, 1, 6, 10, IMMUNE_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_SNEAK, P_("monster", "Warpskull"), "tspo", 3, 117, MonsterAIID::Sneak, 2, 6, 18, RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_GZOMBIE, P_("monster", "Goretongue"), "pmr", 3, 156, MonsterAIID::SkeletonMelee, 1, 15, 30, IMMUNE_MAGIC, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_WSCAV, P_("monster", "Pulsecrawler"), "bhka", 4, 150, MonsterAIID::Scavenger, 0, 16, 20, IMMUNE_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 45, TEXT_NONE }, -{ MT_BLINK, P_("monster", "Moonbender"), "general", 4, 135, MonsterAIID::Bat, 0, 9, 27, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_BLINK, P_("monster", "Wrathraven"), "general", 5, 135, MonsterAIID::Bat, 2, 9, 22, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_YSCAV, P_("monster", "Spineeater"), "general", 4, 180, MonsterAIID::Scavenger, 1, 18, 25, IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_RSKELBW, P_("monster", "Blackash the Burning"), "bashtb", 4, 120, MonsterAIID::GoatRanged, 0, 6, 16, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_BFALLSD, P_("monster", "Shadowcrow"), "general", 5, 270, MonsterAIID::Sneak, 2, 12, 25, 0, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_LRDSAYTR, P_("monster", "Blightstone the Weak"), "bhka", 4, 360, MonsterAIID::SkeletonMelee, 0, 4, 12, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 70, 0, TEXT_NONE }, -{ MT_FAT, P_("monster", "Bilefroth the Pit Master"), "bftp", 6, 210, MonsterAIID::Bat, 1, 16, 23, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_NGOATBW, P_("monster", "Bloodskin Darkbow"), "bsdb", 5, 207, MonsterAIID::GoatRanged, 0, 3, 16, RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 55, TEXT_NONE }, -{ MT_GLOOM, P_("monster", "Foulwing"), "db", 5, 246, MonsterAIID::Rhino, 3, 12, 28, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_XSKELSD, P_("monster", "Shadowdrinker"), "shdr", 5, 300, MonsterAIID::Sneak, 1, 18, 26, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 45, TEXT_NONE }, -{ MT_UNSEEN, P_("monster", "Hazeshifter"), "bhka", 5, 285, MonsterAIID::Sneak, 3, 18, 30, IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_NACID, P_("monster", "Deathspit"), "bfds", 6, 303, MonsterAIID::AcidUnique, 0, 12, 32, RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_RGOATMC, P_("monster", "Bloodgutter"), "bgbl", 6, 315, MonsterAIID::Bat, 1, 24, 34, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_BGOATMC, P_("monster", "Deathshade Fleshmaul"), "dsfm", 6, 276, MonsterAIID::Rhino, 0, 12, 24, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::None, 0, 65, TEXT_NONE }, -{ MT_WYRM, P_("monster", "Warmaggot the Mad"), "general", 6, 246, MonsterAIID::Bat, 3, 15, 30, RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_STORM, P_("monster", "Glasskull the Jagged"), "bhka", 7, 354, MonsterAIID::Storm, 0, 18, 30, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_RGOATBW, P_("monster", "Blightfire"), "blf", 7, 321, MonsterAIID::Succubus, 2, 13, 21, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_GARGOYLE, P_("monster", "Nightwing the Cold"), "general", 7, 342, MonsterAIID::Bat, 1, 18, 26, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_GGOATBW, P_("monster", "Gorestone"), "general", 7, 303, MonsterAIID::GoatRanged, 1, 15, 28, RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 70, 0, TEXT_NONE }, -{ MT_BMAGMA, P_("monster", "Bronzefist Firestone"), "general", 8, 360, MonsterAIID::Magma, 0, 30, 36, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_INCIN, P_("monster", "Wrathfire the Doomed"), "wftd", 8, 270, MonsterAIID::SkeletonMelee, 2, 20, 30, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_NMAGMA, P_("monster", "Firewound the Grim"), "bhka", 8, 303, MonsterAIID::Magma, 0, 18, 22, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_MUDMAN, P_("monster", "Baron Sludge"), "bsm", 8, 315, MonsterAIID::Sneak, 3, 25, 34, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 75, TEXT_NONE }, -{ MT_GGOATMC, P_("monster", "Blighthorn Steelmace"), "bhsm", 7, 250, MonsterAIID::Rhino, 0, 20, 28, RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 45, TEXT_NONE }, -{ MT_RACID, P_("monster", "Chaoshowler"), "general", 8, 240, MonsterAIID::AcidUnique, 0, 12, 20, 0, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_REDDTH, P_("monster", "Doomgrin the Rotting"), "general", 8, 405, MonsterAIID::Storm, 3, 25, 50, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_FLAMLRD, P_("monster", "Madburner"), "general", 9, 270, MonsterAIID::Storm, 0, 20, 40, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_LTCHDMN, P_("monster", "Bonesaw the Litch"), "general", 9, 495, MonsterAIID::Storm, 2, 30, 55, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_MUDRUN, P_("monster", "Breakspine"), "general", 9, 351, MonsterAIID::Rhino, 0, 25, 34, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_REDDTH, P_("monster", "Devilskull Sharpbone"), "general", 9, 444, MonsterAIID::Storm, 1, 25, 40, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_STORM, P_("monster", "Brokenstorm"), "general", 9, 411, MonsterAIID::Storm, 2, 25, 36, IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_RSTORM, P_("monster", "Stormbane"), "general", 9, 555, MonsterAIID::Storm, 3, 30, 30, IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_TOAD, P_("monster", "Oozedrool"), "general", 9, 483, MonsterAIID::Fat, 3, 25, 30, RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_BLOODCLW, P_("monster", "Goldblight of the Flame"), "general", 10, 405, MonsterAIID::Gargoyle, 0, 15, 35, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 80, TEXT_NONE }, -{ MT_OBLORD, P_("monster", "Blackstorm"), "general", 10, 525, MonsterAIID::Rhino, 3, 20, 40, IMMUNE_MAGIC | IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 90, TEXT_NONE }, -{ MT_RACID, P_("monster", "Plaguewrath"), "general", 10, 450, MonsterAIID::AcidUnique, 2, 20, 30, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_RSTORM, P_("monster", "The Flayer"), "general", 10, 501, MonsterAIID::Storm, 1, 20, 35, RESIST_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_FROSTC, P_("monster", "Bluehorn"), "general", 11, 477, MonsterAIID::Rhino, 1, 25, 30, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 90, TEXT_NONE }, -{ MT_HELLBURN, P_("monster", "Warpfire Hellspawn"), "general", 11, 525, MonsterAIID::FireMan, 3, 10, 40, RESIST_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_NSNAKE, P_("monster", "Fangspeir"), "general", 11, 444, MonsterAIID::SkeletonMelee, 1, 15, 32, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_UDEDBLRG, P_("monster", "Festerskull"), "general", 11, 600, MonsterAIID::Storm, 2, 15, 30, IMMUNE_MAGIC, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_NBLACK, P_("monster", "Lionskull the Bent"), "general", 12, 525, MonsterAIID::SkeletonMelee, 2, 25, 25, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_COUNSLR, P_("monster", "Blacktongue"), "general", 12, 360, MonsterAIID::Counselor, 3, 15, 30, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_DEATHW, P_("monster", "Viletouch"), "general", 12, 525, MonsterAIID::Gargoyle, 3, 20, 40, IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_RSNAKE, P_("monster", "Viperflame"), "general", 12, 570, MonsterAIID::SkeletonMelee, 1, 25, 35, IMMUNE_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_BSNAKE, P_("monster", "Fangskin"), "bhka", 14, 681, MonsterAIID::SkeletonMelee, 2, 15, 50, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_SUCCUBUS, P_("monster", "Witchfire the Unholy"), "general", 12, 444, MonsterAIID::Succubus, 3, 10, 20, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_BALROG, P_("monster", "Blackskull"), "bhka", 13, 750, MonsterAIID::SkeletonMelee, 3, 25, 40, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_UNRAV, P_("monster", "Soulslash"), "general", 12, 450, MonsterAIID::SkeletonMelee, 0, 25, 25, IMMUNE_MAGIC, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_VTEXLRD, P_("monster", "Windspawn"), "general", 12, 711, MonsterAIID::SkeletonMelee, 1, 35, 40, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_GSNAKE, P_("monster", "Lord of the Pit"), "general", 13, 762, MonsterAIID::SkeletonMelee, 2, 25, 42, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_RTBLACK, P_("monster", "Rustweaver"), "general", 13, 400, MonsterAIID::SkeletonMelee, 3, 1, 60, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_HOLOWONE, P_("monster", "Howlingire the Shade"), "general", 13, 450, MonsterAIID::SkeletonMelee, 2, 40, 75, RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_MAEL, P_("monster", "Doomcloud"), "general", 13, 612, MonsterAIID::Storm, 1, 1, 60, RESIST_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_PAINMSTR, P_("monster", "Bloodmoon Soulfire"), "general", 13, 684, MonsterAIID::SkeletonMelee, 1, 15, 40, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_SNOWWICH, P_("monster", "Witchmoon"), "general", 13, 310, MonsterAIID::Succubus, 3, 30, 40, RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_VTEXLRD, P_("monster", "Gorefeast"), "general", 13, 771, MonsterAIID::SkeletonMelee, 3, 20, 55, RESIST_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_RTBLACK, P_("monster", "Graywar the Slayer"), "general", 14, 672, MonsterAIID::SkeletonMelee, 1, 30, 50, RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_MAGISTR, P_("monster", "Dreadjudge"), "general", 14, 540, MonsterAIID::Counselor, 1, 30, 40, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_HLSPWN, P_("monster", "Stareye the Witch"), "general", 14, 726, MonsterAIID::Succubus, 2, 30, 50, IMMUNE_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_BTBLACK, P_("monster", "Steelskull the Hunter"), "general", 14, 831, MonsterAIID::SkeletonMelee, 3, 40, 50, RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_RBLACK, P_("monster", "Sir Gorash"), "general", 16, 1050, MonsterAIID::SkeletonMelee, 1, 20, 60, 0, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_CABALIST, P_("monster", "The Vizier"), "general", 15, 850, MonsterAIID::Counselor, 2, 25, 40, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_REALWEAV, P_("monster", "Zamphir"), "general", 15, 891, MonsterAIID::SkeletonMelee, 2, 30, 50, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_HLSPWN, P_("monster", "Bloodlust"), "general", 15, 825, MonsterAIID::Succubus, 1, 20, 55, IMMUNE_MAGIC | IMMUNE_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_HLSPWN, P_("monster", "Webwidow"), "general", 16, 774, MonsterAIID::Succubus, 1, 20, 50, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_SOLBRNR, P_("monster", "Fleshdancer"), "general", 16, 999, MonsterAIID::Succubus, 3, 30, 50, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_OBLORD, P_("monster", "Grimspike"), "general", 19, 534, MonsterAIID::Sneak, 1, 25, 40, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -// TRANSLATORS: Unique Monster Block end -{ MT_STORML, P_("monster", "Doomlock"), "general", 28, 534, MonsterAIID::Sneak, 1, 35, 55, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_INVALID, nullptr, nullptr, 0, 0, MonsterAIID::Invalid, 0, 0, 0, 0, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, - // clang-format on -}; - } // namespace devilution diff --git a/Source/monstdat.h b/Source/monstdat.h index 9834e7a1f..69fc29cb5 100644 --- a/Source/monstdat.h +++ b/Source/monstdat.h @@ -306,8 +306,8 @@ enum class UniqueMonsterPack : uint8_t { struct UniqueMonsterData { _monster_id mtype; - const char *mName; - const char *mTrnName; + std::string mName; + std::string mTrnName; uint8_t mlevel; uint16_t mmaxhp; MonsterAIID mAi; @@ -328,7 +328,7 @@ struct UniqueMonsterData { extern std::vector MonstersData; extern const _monster_id MonstConvTbl[]; -extern const UniqueMonsterData UniqueMonstersData[]; +extern std::vector UniqueMonstersData; void LoadMonsterData(); diff --git a/Source/monster.cpp b/Source/monster.cpp index e9e99f6ea..756b45579 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -479,7 +479,7 @@ void ClrAllMonsters() void PlaceUniqueMonsters() { - for (size_t u = 0; UniqueMonstersData[u].mtype != -1; u++) { + for (size_t u = 0; u < UniqueMonstersData.size(); ++u) { if (UniqueMonstersData[u].mlevel != currlevel) continue; diff --git a/Source/pack.cpp b/Source/pack.cpp index 1bde01ccf..631678c4c 100644 --- a/Source/pack.cpp +++ b/Source/pack.cpp @@ -118,8 +118,7 @@ bool IsUniqueMonsterItemValid(uint16_t iCreateInfo, uint32_t dwBuff) const uint8_t level = iCreateInfo & CF_LEVEL; // Check all unique monster levels to see if they match the item level - for (int i = 0; UniqueMonstersData[i].mName != nullptr; i++) { - const auto &uniqueMonsterData = UniqueMonstersData[i]; + for (const UniqueMonsterData &uniqueMonsterData : UniqueMonstersData) { const auto &uniqueMonsterLevel = static_cast(MonstersData[uniqueMonsterData.mtype].level); if (IsAnyOf(uniqueMonsterData.mtype, MT_DEFILER, MT_NAKRUL, MT_HORKDMN)) { diff --git a/Source/translation_dummy.cpp b/Source/translation_dummy.cpp index a0c14d10b..c06ccf1c7 100644 --- a/Source/translation_dummy.cpp +++ b/Source/translation_dummy.cpp @@ -144,3 +144,103 @@ const char *MT_BICLOPS_NAME = P_("monster", "Biclops"); const char *MT_FLESTHNG_NAME = P_("monster", "Flesh Thing"); const char *MT_REAPER_NAME = P_("monster", "Reaper"); const char *MT_NAKRUL_NAME = P_("monster", "Na-Krul"); +const char *GHARBAD_THE_WEAK_NAME = P_("monster", "Gharbad the Weak"); +const char *SKELETON_KING_NAME = P_("monster", "Skeleton King"); +const char *ZHAR_THE_MAD_NAME = P_("monster", "Zhar the Mad"); +const char *SNOTSPILL_NAME = P_("monster", "Snotspill"); +const char *ARCH_BISHOP_LAZARUS_NAME = P_("monster", "Arch-Bishop Lazarus"); +const char *RED_VEX_NAME = P_("monster", "Red Vex"); +const char *BLACK_JADE_NAME = P_("monster", "Black Jade"); +const char *LACHDANAN_NAME = P_("monster", "Lachdanan"); +const char *WARLORD_OF_BLOOD_NAME = P_("monster", "Warlord of Blood"); +const char *THE_BUTCHER_NAME = P_("monster", "The Butcher"); +const char *HORK_DEMON_NAME = P_("monster", "Hork Demon"); +const char *THE_DEFILER_NAME = P_("monster", "The Defiler"); +const char *NA_KRUL_NAME = P_("monster", "Na-Krul"); +const char *BONEHEAD_KEENAXE_NAME = P_("monster", "Bonehead Keenaxe"); +const char *BLADESKIN_THE_SLASHER_NAME = P_("monster", "Bladeskin the Slasher"); +const char *SOULPUS_NAME = P_("monster", "Soulpus"); +const char *PUKERAT_THE_UNCLEAN_NAME = P_("monster", "Pukerat the Unclean"); +const char *BONERIPPER_NAME = P_("monster", "Boneripper"); +const char *ROTFEAST_THE_HUNGRY_NAME = P_("monster", "Rotfeast the Hungry"); +const char *GUTSHANK_THE_QUICK_NAME = P_("monster", "Gutshank the Quick"); +const char *BROKENHEAD_BANGSHIELD_NAME = P_("monster", "Brokenhead Bangshield"); +const char *BONGO_NAME = P_("monster", "Bongo"); +const char *ROTCARNAGE_NAME = P_("monster", "Rotcarnage"); +const char *SHADOWBITE_NAME = P_("monster", "Shadowbite"); +const char *DEADEYE_NAME = P_("monster", "Deadeye"); +const char *MADEYE_THE_DEAD_NAME = P_("monster", "Madeye the Dead"); +const char *EL_CHUPACABRAS_NAME = P_("monster", "El Chupacabras"); +const char *SKULLFIRE_NAME = P_("monster", "Skullfire"); +const char *WARPSKULL_NAME = P_("monster", "Warpskull"); +const char *GORETONGUE_NAME = P_("monster", "Goretongue"); +const char *PULSECRAWLER_NAME = P_("monster", "Pulsecrawler"); +const char *MOONBENDER_NAME = P_("monster", "Moonbender"); +const char *WRATHRAVEN_NAME = P_("monster", "Wrathraven"); +const char *SPINEEATER_NAME = P_("monster", "Spineeater"); +const char *BLACKASH_THE_BURNING_NAME = P_("monster", "Blackash the Burning"); +const char *SHADOWCROW_NAME = P_("monster", "Shadowcrow"); +const char *BLIGHTSTONE_THE_WEAK_NAME = P_("monster", "Blightstone the Weak"); +const char *BILEFROTH_THE_PIT_MASTER_NAME = P_("monster", "Bilefroth the Pit Master"); +const char *BLOODSKIN_DARKBOW_NAME = P_("monster", "Bloodskin Darkbow"); +const char *FOULWING_NAME = P_("monster", "Foulwing"); +const char *SHADOWDRINKER_NAME = P_("monster", "Shadowdrinker"); +const char *HAZESHIFTER_NAME = P_("monster", "Hazeshifter"); +const char *DEATHSPIT_NAME = P_("monster", "Deathspit"); +const char *BLOODGUTTER_NAME = P_("monster", "Bloodgutter"); +const char *DEATHSHADE_FLESHMAUL_NAME = P_("monster", "Deathshade Fleshmaul"); +const char *WARMAGGOT_THE_MAD_NAME = P_("monster", "Warmaggot the Mad"); +const char *GLASSKULL_THE_JAGGED_NAME = P_("monster", "Glasskull the Jagged"); +const char *BLIGHTFIRE_NAME = P_("monster", "Blightfire"); +const char *NIGHTWING_THE_COLD_NAME = P_("monster", "Nightwing the Cold"); +const char *GORESTONE_NAME = P_("monster", "Gorestone"); +const char *BRONZEFIST_FIRESTONE_NAME = P_("monster", "Bronzefist Firestone"); +const char *WRATHFIRE_THE_DOOMED_NAME = P_("monster", "Wrathfire the Doomed"); +const char *FIREWOUND_THE_GRIM_NAME = P_("monster", "Firewound the Grim"); +const char *BARON_SLUDGE_NAME = P_("monster", "Baron Sludge"); +const char *BLIGHTHORN_STEELMACE_NAME = P_("monster", "Blighthorn Steelmace"); +const char *CHAOSHOWLER_NAME = P_("monster", "Chaoshowler"); +const char *DOOMGRIN_THE_ROTTING_NAME = P_("monster", "Doomgrin the Rotting"); +const char *MADBURNER_NAME = P_("monster", "Madburner"); +const char *BONESAW_THE_LITCH_NAME = P_("monster", "Bonesaw the Litch"); +const char *BREAKSPINE_NAME = P_("monster", "Breakspine"); +const char *DEVILSKULL_SHARPBONE_NAME = P_("monster", "Devilskull Sharpbone"); +const char *BROKENSTORM_NAME = P_("monster", "Brokenstorm"); +const char *STORMBANE_NAME = P_("monster", "Stormbane"); +const char *OOZEDROOL_NAME = P_("monster", "Oozedrool"); +const char *GOLDBLIGHT_OF_THE_FLAME_NAME = P_("monster", "Goldblight of the Flame"); +const char *BLACKSTORM_NAME = P_("monster", "Blackstorm"); +const char *PLAGUEWRATH_NAME = P_("monster", "Plaguewrath"); +const char *THE_FLAYER_NAME = P_("monster", "The Flayer"); +const char *BLUEHORN_NAME = P_("monster", "Bluehorn"); +const char *WARPFIRE_HELLSPAWN_NAME = P_("monster", "Warpfire Hellspawn"); +const char *FANGSPEIR_NAME = P_("monster", "Fangspeir"); +const char *FESTERSKULL_NAME = P_("monster", "Festerskull"); +const char *LIONSKULL_THE_BENT_NAME = P_("monster", "Lionskull the Bent"); +const char *BLACKTONGUE_NAME = P_("monster", "Blacktongue"); +const char *VILETOUCH_NAME = P_("monster", "Viletouch"); +const char *VIPERFLAME_NAME = P_("monster", "Viperflame"); +const char *FANGSKIN_NAME = P_("monster", "Fangskin"); +const char *WITCHFIRE_THE_UNHOLY_NAME = P_("monster", "Witchfire the Unholy"); +const char *BLACKSKULL_NAME = P_("monster", "Blackskull"); +const char *SOULSLASH_NAME = P_("monster", "Soulslash"); +const char *WINDSPAWN_NAME = P_("monster", "Windspawn"); +const char *LORD_OF_THE_PIT_NAME = P_("monster", "Lord of the Pit"); +const char *RUSTWEAVER_NAME = P_("monster", "Rustweaver"); +const char *HOWLINGIRE_THE_SHADE_NAME = P_("monster", "Howlingire the Shade"); +const char *DOOMCLOUD_NAME = P_("monster", "Doomcloud"); +const char *BLOODMOON_SOULFIRE_NAME = P_("monster", "Bloodmoon Soulfire"); +const char *WITCHMOON_NAME = P_("monster", "Witchmoon"); +const char *GOREFEAST_NAME = P_("monster", "Gorefeast"); +const char *GRAYWAR_THE_SLAYER_NAME = P_("monster", "Graywar the Slayer"); +const char *DREADJUDGE_NAME = P_("monster", "Dreadjudge"); +const char *STAREYE_THE_WITCH_NAME = P_("monster", "Stareye the Witch"); +const char *STEELSKULL_THE_HUNTER_NAME = P_("monster", "Steelskull the Hunter"); +const char *SIR_GORASH_NAME = P_("monster", "Sir Gorash"); +const char *THE_VIZIER_NAME = P_("monster", "The Vizier"); +const char *ZAMPHIR_NAME = P_("monster", "Zamphir"); +const char *BLOODLUST_NAME = P_("monster", "Bloodlust"); +const char *WEBWIDOW_NAME = P_("monster", "Webwidow"); +const char *FLESHDANCER_NAME = P_("monster", "Fleshdancer"); +const char *GRIMSPIKE_NAME = P_("monster", "Grimspike"); +const char *DOOMLOCK_NAME = P_("monster", "Doomlock"); diff --git a/tools/extract_translation_data.py b/tools/extract_translation_data.py index f20493fc8..a2cd93fec 100755 --- a/tools/extract_translation_data.py +++ b/tools/extract_translation_data.py @@ -5,6 +5,7 @@ import pathlib root = pathlib.Path(__file__).resolve().parent.parent translation_dummy_path = root.joinpath("Source/translation_dummy.cpp") monstdat_path = root.joinpath("Packaging/resources/assets/txtdata/monsters/monstdat.tsv") +unique_monstdat_path = root.joinpath("Packaging/resources/assets/txtdata/monsters/unique_monstdat.tsv") with open(translation_dummy_path, 'w') as temp_source: temp_source.write(f'/**\n') @@ -18,6 +19,12 @@ with open(translation_dummy_path, 'w') as temp_source: with open(monstdat_path, 'r') as tsv: reader = csv.DictReader(tsv, delimiter='\t') for row in reader: - translation_key = row['_monster_id'] + "_NAME" - name = row['name']; - temp_source.write(f'const char *' + translation_key + ' = P_("monster", "' + name + '");\n') + name = row['name'] + var_name = row['_monster_id'] + "_NAME" + temp_source.write(f'const char *' + var_name + ' = P_("monster", "' + name + '");\n') + with open(unique_monstdat_path, 'r') as tsv: + reader = csv.DictReader(tsv, delimiter='\t') + for row in reader: + name = row['name'] + var_name = name.upper().replace(' ', '_').replace('-', '_') + "_NAME" + temp_source.write(f'const char *' + var_name + ' = P_("monster", "' + name + '");\n')