From 2eae12193131e0e7ef457ae3149f349be340914b Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Thu, 2 Nov 2023 12:59:28 +0000 Subject: [PATCH] Migrate monstdat to a data file We may want to migrate this to 1 file per monster but for now the migration is as close to the hard-coded version as possible. Sprites that are used by multiple monsters are only loaded from disk once. --- CMake/Assets.cmake | 1 + .../assets/txtdata/monsters/monstdat.tsv | 139 ++++ Source/data/file.cpp | 15 +- Source/data/file.hpp | 2 +- Source/data/iterators.hpp | 50 ++ Source/diablo.cpp | 4 + Source/lua/modules/dev/monsters.cpp | 2 +- Source/monstdat.cpp | 627 ++++++++++++------ Source/monstdat.h | 83 +-- Source/monster.cpp | 4 +- Source/translation_dummy.cpp | 146 ++++ test/pack_test.cpp | 2 + test/timedemo_test.cpp | 2 + tools/extract_translation_data.py | 23 + 14 files changed, 818 insertions(+), 282 deletions(-) create mode 100644 Packaging/resources/assets/txtdata/monsters/monstdat.tsv create mode 100644 Source/translation_dummy.cpp create mode 100755 tools/extract_translation_data.py diff --git a/CMake/Assets.cmake b/CMake/Assets.cmake index 903433a5e..5b0ccce21 100644 --- a/CMake/Assets.cmake +++ b/CMake/Assets.cmake @@ -155,6 +155,7 @@ set(devilutionx_assets txtdata/classes/rogue/attributes.tsv txtdata/classes/sorcerer/attributes.tsv txtdata/classes/warrior/attributes.tsv + txtdata/monsters/monstdat.tsv ui_art/diablo.pal ui_art/hellfire.pal ui_art/creditsw.clx diff --git a/Packaging/resources/assets/txtdata/monsters/monstdat.tsv b/Packaging/resources/assets/txtdata/monsters/monstdat.tsv new file mode 100644 index 000000000..2eb413075 --- /dev/null +++ b/Packaging/resources/assets/txtdata/monsters/monstdat.tsv @@ -0,0 +1,139 @@ +_monster_id name assetsSuffix soundSuffix trnFile availability width image hasSpecial hasSpecialSound frames[6] rate[6] minDunLvl maxDunLvl level hitPointsMinimum hitPointsMaximum ai abilityFlags intelligence toHit animFrameNum minDamage maxDamage toHitSpecial animFrameNumSpecial minDamageSpecial maxDamageSpecial armorClass monsterClass resistance resistanceHell selectionType treasure exp +MT_NZOMBIE Zombie zombie\zombie Always 128 799 false false 11,24,12,6,16,0 4,1,1,1,1,1 1 2 1 4 7 Zombie 0 10 8 2 5 0 0 0 0 5 Undead IMMUNE_MAGIC IMMUNE_MAGIC 3 54 +MT_BZOMBIE Ghoul zombie\zombie zombie\bluered Always 128 799 false false 11,24,12,6,16,0 4,1,1,1,1,1 2 3 2 7 11 Zombie 1 10 8 3 10 0 0 0 0 10 Undead IMMUNE_MAGIC IMMUNE_MAGIC 3 58 +MT_GZOMBIE Rotting Carcass zombie\zombie zombie\grey Always 128 799 false false 11,24,12,6,16,0 4,1,1,1,1,1 2 4 4 15 25 Zombie 2 25 8 5 15 0 0 0 0 15 Undead IMMUNE_MAGIC IMMUNE_MAGIC,RESIST_FIRE 3 136 +MT_YZOMBIE Black Death zombie\zombie zombie\yellow Always 128 799 false false 11,24,12,6,16,0 4,1,1,1,1,1 3 5 6 25 40 Zombie 3 30 8 6 22 0 0 0 0 20 Undead IMMUNE_MAGIC IMMUNE_MAGIC,RESIST_LIGHTNING 3 240 +MT_RFALLSP Fallen One falspear\phall falspear\fallent Always 128 543 true true 11,11,13,11,18,13 3,1,1,1,1,1 1 2 1 1 4 Fallen 0 15 7 1 3 0 5 0 0 0 Animal 3 46 +MT_DFALLSP Carver falspear\phall falspear\dark Always 128 543 true true 11,11,13,11,18,13 3,1,1,1,1,1 2 3 3 4 8 Fallen 2 20 7 2 5 0 5 0 0 5 Animal 3 80 +MT_YFALLSP Devil Kin falspear\phall Always 128 543 true true 11,11,13,11,18,13 3,1,1,1,1,1 2 4 5 12 24 Fallen 2 25 7 3 7 0 5 0 0 10 Animal RESIST_FIRE 3 155 +MT_BFALLSP Dark One falspear\phall falspear\blue Always 128 543 true true 11,11,13,11,18,13 3,1,1,1,1,1 3 5 7 20 36 Fallen 3 30 7 4 8 0 5 0 0 15 Animal RESIST_LIGHTNING 3 255 +MT_WSKELAX Skeleton skelaxe\sklax skelaxe\white Always 128 553 true false 12,8,13,6,17,16 5,1,1,1,1,1 1 2 1 2 4 SkeletonMelee 0 20 8 1 4 0 0 0 0 0 Undead IMMUNE_MAGIC IMMUNE_MAGIC 3 64 +MT_TSKELAX Corpse Axe skelaxe\sklax skelaxe\skelt Always 128 553 true false 12,8,13,6,17,16 4,1,1,1,1,1 2 3 2 4 7 SkeletonMelee 1 25 8 3 5 0 0 0 0 0 Undead IMMUNE_MAGIC IMMUNE_MAGIC 3 68 +MT_RSKELAX Burning Dead skelaxe\sklax Always 128 553 true false 12,8,13,6,17,16 2,1,1,1,1,1 2 4 4 8 12 SkeletonMelee 2 30 8 3 7 0 0 0 0 5 Undead IMMUNE_MAGIC,RESIST_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 3 154 +MT_XSKELAX Horror skelaxe\sklax skelaxe\black Always 128 553 true false 12,8,13,6,17,16 3,1,1,1,1,1 3 5 6 12 20 SkeletonMelee 3 35 8 4 9 0 0 0 0 15 Undead IMMUNE_MAGIC,RESIST_LIGHTNING IMMUNE_MAGIC,RESIST_LIGHTNING 3 264 +MT_RFALLSD Fallen One falsword\fall falsword\fallent Always 128 623 true true 12,12,13,11,14,15 3,1,1,1,1,1 1 2 1 2 5 Fallen 0 15 8 1 4 0 5 0 0 10 Animal 3 52 +MT_DFALLSD Carver falsword\fall falsword\dark Always 128 623 true true 12,12,13,11,14,15 3,1,1,1,1,1 2 3 3 5 9 Fallen 1 20 8 2 7 0 5 0 0 15 Animal 3 90 +MT_YFALLSD Devil Kin falsword\fall Always 128 623 true true 12,12,13,11,14,15 3,1,1,1,1,1 2 4 5 16 24 Fallen 2 25 8 4 10 0 5 0 0 20 Animal RESIST_FIRE 3 180 +MT_BFALLSD Dark One falsword\fall falsword\blue Always 128 623 true true 12,12,13,11,14,15 3,1,1,1,1,1 3 5 7 24 36 Fallen 3 30 8 4 12 0 5 0 0 25 Animal RESIST_LIGHTNING 3 280 +MT_NSCAV Scavenger scav\scav Always 128 410 true false 12,8,12,6,20,11 2,1,1,1,1,1 1 3 2 3 6 Scavenger 0 20 7 1 5 0 0 0 0 10 Animal RESIST_FIRE 3 80 +MT_BSCAV Plague Eater scav\scav scav\scavbr Always 128 410 true false 12,8,12,6,20,11 2,1,1,1,1,1 2 4 4 12 24 Scavenger 1 30 7 1 8 0 0 0 0 20 Animal RESIST_LIGHTNING 3 188 +MT_WSCAV Shadow Beast scav\scav scav\scavbe Always 128 410 true false 12,8,12,6,20,11 2,1,1,1,1,1 3 5 6 24 36 Scavenger 2 35 7 3 12 0 0 0 0 25 Animal RESIST_FIRE 3 375 +MT_YSCAV Bone Gasher scav\scav scav\scavw Always 128 410 true false 12,8,12,6,20,11 2,1,1,1,1,1 4 6 8 28 40 Scavenger 3 35 7 5 15 0 0 0 0 30 Animal RESIST_MAGIC RESIST_LIGHTNING 3 552 +MT_WSKELBW Skeleton skelbow\sklbw skelbow\white Always 128 567 true false 9,8,16,5,16,16 4,1,1,1,1,1 2 3 3 2 4 SkeletonRanged 0 15 12 1 2 0 0 0 0 0 Undead IMMUNE_MAGIC IMMUNE_MAGIC 3 110 +MT_TSKELBW Corpse Bow skelbow\sklbw skelbow\skelt Always 128 567 true false 9,8,16,5,16,16 4,1,1,1,1,1 2 4 5 8 16 SkeletonRanged 1 25 12 1 4 0 0 0 0 0 Undead IMMUNE_MAGIC IMMUNE_MAGIC 3 210 +MT_RSKELBW Burning Dead skelbow\sklbw Always 128 567 true false 9,8,16,5,16,16 2,1,1,1,1,1 3 5 7 10 24 SkeletonRanged 2 30 12 1 6 0 0 0 0 5 Undead IMMUNE_MAGIC,RESIST_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 3 364 +MT_XSKELBW Horror skelbow\sklbw skelbow\black Always 128 567 true false 9,8,16,5,16,16 3,1,1,1,1,1 4 6 9 15 45 SkeletonRanged 3 35 12 2 9 0 0 0 0 15 Undead IMMUNE_MAGIC,RESIST_LIGHTNING IMMUNE_MAGIC,RESIST_LIGHTNING 3 594 +MT_WSKELSD Skeleton Captain skelsd\sklsr skelsd\white Always 128 575 true true 13,8,12,7,15,16 4,1,1,1,1,1 1 3 2 3 6 SkeletonMelee 0 20 8 2 7 0 0 0 0 10 Undead IMMUNE_MAGIC IMMUNE_MAGIC 3 90 +MT_TSKELSD Corpse Captain skelsd\sklsr skelsd\skelt Always 128 575 true false 13,8,12,7,15,16 4,1,1,1,1,1 2 4 4 12 20 SkeletonMelee 1 30 8 3 9 0 0 0 0 5 Undead IMMUNE_MAGIC IMMUNE_MAGIC 3 200 +MT_RSKELSD Burning Dead Captain skelsd\sklsr Always 128 575 true false 13,8,12,7,15,16 4,1,1,1,1,1 3 5 6 16 30 SkeletonMelee 2 35 8 4 10 0 0 0 0 15 Undead IMMUNE_MAGIC,RESIST_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 3 393 +MT_XSKELSD Horror Captain skelsd\sklsr skelsd\black Always 128 575 true false 13,8,12,7,15,16 4,1,1,1,1,1 4 6 8 35 50 SkeletonMelee SEARCH 3 40 8 5 14 0 0 0 0 30 Undead IMMUNE_MAGIC,RESIST_LIGHTNING IMMUNE_MAGIC,RESIST_LIGHTNING 3 604 +MT_INVILORD Invisible Lord tsneak\tsneak Never 128 800 false false 13,13,15,11,16,0 2,1,1,1,1,1 19 20 14 278 278 SkeletonMelee SEARCH,CAN_OPEN_DOOR 3 65 8 16 30 0 0 0 0 60 Demon RESIST_MAGIC,RESIST_FIRE,RESIST_LIGHTNING RESIST_MAGIC,RESIST_FIRE,RESIST_LIGHTNING 3 2000 +MT_SNEAK Hidden sneak\sneak Retail 128 992 true false 16,8,12,8,24,15 2,1,1,1,1,1 2 5 5 8 24 Sneak HIDDEN 0 35 8 3 6 0 0 0 0 25 Demon 3 278 +MT_STALKER Stalker sneak\sneak sneak\sneakv2 Retail 128 992 true false 16,8,12,8,24,15 2,1,1,1,1,1 5 7 9 30 45 Sneak HIDDEN,SEARCH 1 40 8 8 16 0 0 0 0 30 Demon 3 630 +MT_UNSEEN Unseen sneak\sneak sneak\sneakv3 Retail 128 992 true false 16,8,12,8,24,15 2,1,1,1,1,1 6 8 11 35 50 Sneak HIDDEN,SEARCH 2 45 8 12 20 0 0 0 0 30 Demon RESIST_MAGIC IMMUNE_MAGIC 3 935 +MT_ILLWEAV Illusion Weaver sneak\sneak sneak\sneakv1 Retail 128 992 true false 16,8,12,8,24,15 2,1,1,1,1,1 8 10 13 40 60 Sneak HIDDEN,SEARCH 3 60 8 16 24 0 0 0 0 30 Demon RESIST_MAGIC,RESIST_FIRE IMMUNE_MAGIC,RESIST_FIRE 3 1500 +MT_LRDSAYTR Satyr Lord goatlord\goatl newsfx\satyr Retail 160 800 false false 13,13,14,9,16,0 2,1,1,1,1,1 21 22 28 160 200 SkeletonMelee SEARCH 3 90 8 20 30 0 0 0 0 70 Animal RESIST_FIRE,RESIST_LIGHTNING RESIST_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 3 2800 +MT_NGOATMC Flesh Clan goatmace\goat Retail 128 1030 true false 12,8,12,6,20,12 2,1,1,1,1,1 4 6 8 30 45 GoatMelee SEARCH,CAN_OPEN_DOOR 0 50 8 4 10 0 0 0 0 40 Demon 3 460 +MT_BGOATMC Stone Clan goatmace\goat goatmace\beige Retail 128 1030 true false 12,8,12,6,20,12 2,1,1,1,1,1 5 7 10 40 55 GoatMelee SEARCH,CAN_OPEN_DOOR 1 60 8 6 12 0 0 0 0 40 Demon RESIST_MAGIC IMMUNE_MAGIC 3 685 +MT_RGOATMC Fire Clan goatmace\goat goatmace\red Retail 128 1030 true false 12,8,12,6,20,12 2,1,1,1,1,1 6 8 12 50 65 GoatMelee SEARCH,CAN_OPEN_DOOR 2 70 8 8 16 0 0 0 0 45 Demon RESIST_FIRE IMMUNE_FIRE 3 906 +MT_GGOATMC Night Clan goatmace\goat goatmace\gray Retail 128 1030 true false 12,8,12,6,20,12 2,1,1,1,1,1 7 9 14 55 70 GoatMelee SEARCH,CAN_OPEN_DOOR 3 80 8 10 20 15 0 30 30 50 Demon RESIST_MAGIC IMMUNE_MAGIC 3 1190 +MT_FIEND Fiend bat\bat bat\red Always 96 364 false false 9,13,10,9,13,0 1,1,1,1,1,1 2 3 3 3 6 Bat 0 35 5 1 6 0 0 0 0 0 Animal 6 None 102 +MT_BLINK Blink bat\bat Always 96 364 false false 9,13,10,9,13,0 1,1,1,1,1,1 3 5 7 12 28 Bat 1 45 5 1 8 0 0 0 0 15 Animal 6 None 340 +MT_GLOOM Gloom bat\bat bat\grey Always 96 364 false false 9,13,10,9,13,0 1,1,1,1,1,1 4 6 9 28 36 Bat SEARCH 2 70 5 4 12 0 0 0 0 35 Animal RESIST_MAGIC RESIST_MAGIC 6 None 509 +MT_FAMILIAR Familiar bat\bat bat\orange Always 96 364 false false 9,13,10,9,13,0 1,1,1,1,1,1 6 8 13 20 35 Bat SEARCH 3 50 5 4 16 0 0 0 0 35 Demon RESIST_MAGIC,IMMUNE_LIGHTNING RESIST_MAGIC,IMMUNE_LIGHTNING 6 None 448 +MT_NGOATBW Flesh Clan goatbow\goatb Retail 128 1040 false false 12,8,16,6,20,0 3,1,1,1,1,1 4 6 8 20 35 GoatRanged CAN_OPEN_DOOR 0 35 13 1 7 0 0 0 0 35 Demon 3 448 +MT_BGOATBW Stone Clan goatbow\goatb goatbow\beige Retail 128 1040 false false 12,8,16,6,20,0 3,1,1,1,1,1 5 7 10 30 40 GoatRanged CAN_OPEN_DOOR 1 40 13 2 9 0 0 0 0 35 Demon RESIST_MAGIC IMMUNE_MAGIC 3 645 +MT_RGOATBW Fire Clan goatbow\goatb goatbow\red Retail 128 1040 false false 12,8,16,6,20,0 3,1,1,1,1,1 6 8 12 40 50 GoatRanged SEARCH,CAN_OPEN_DOOR 2 45 13 3 11 0 0 0 0 35 Demon RESIST_FIRE IMMUNE_FIRE 3 822 +MT_GGOATBW Night Clan goatbow\goatb goatbow\gray Retail 128 1040 false false 12,8,16,6,20,0 3,1,1,1,1,1 7 9 14 50 65 GoatRanged SEARCH,CAN_OPEN_DOOR 3 50 13 4 13 15 0 0 0 40 Demon RESIST_MAGIC IMMUNE_MAGIC 3 1092 +MT_NACID Acid Beast acid\acid Retail 128 716 true true 13,8,12,8,16,12 1,1,1,1,1,1 6 8 11 40 66 Acid 0 40 8 4 12 25 8 0 0 30 Animal IMMUNE_ACID IMMUNE_MAGIC,IMMUNE_ACID 3 846 +MT_RACID Poison Spitter acid\acid acid\acidblk Retail 128 716 true true 13,8,12,8,16,12 1,1,1,1,1,1 8 10 15 60 85 Acid 1 45 8 4 16 25 8 0 0 30 Animal IMMUNE_ACID IMMUNE_MAGIC,IMMUNE_ACID 3 1248 +MT_BACID Pit Beast acid\acid acid\acidb Retail 128 716 true true 13,8,12,8,16,12 1,1,1,1,1,1 10 12 21 80 110 Acid 2 55 8 8 18 35 8 0 0 35 Animal RESIST_MAGIC,IMMUNE_ACID IMMUNE_MAGIC,RESIST_LIGHTNING,IMMUNE_ACID 3 2060 +MT_XACID Lava Maw acid\acid acid\acidr Retail 128 716 true true 13,8,12,8,16,12 1,1,1,1,1,1 12 14 25 100 150 Acid 3 65 8 10 20 40 8 0 0 35 Animal RESIST_MAGIC,IMMUNE_FIRE,IMMUNE_ACID IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_ACID 3 2940 +MT_SKING Skeleton King sking\sking skelaxe\white Never 160 1010 true true 8,6,16,6,16,6 2,1,1,1,1,2 4 4 9 140 140 SkeletonKing SEARCH,CAN_OPEN_DOOR 3 60 8 6 16 0 0 0 0 70 Undead IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 7 Uniq(SKCROWN) 570 +MT_CLEAVER The Butcher fatc\fatc Never 128 980 false false 10,8,12,6,16,0 1,1,1,1,1,1 1 1 1 320 320 Butcher 3 50 8 6 12 0 0 0 0 50 Demon RESIST_FIRE,RESIST_LIGHTNING RESIST_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 3 Uniq(CLEAVER) 710 +MT_FAT Overlord fat\fat Retail 128 1130 true false 8,10,15,6,16,10 4,1,1,1,1,1 5 7 10 60 80 Fat 0 55 8 6 12 0 0 0 0 55 Demon RESIST_FIRE 3 635 +MT_MUDMAN Mud Man fat\fat fat\blue Retail 128 1130 true false 8,10,15,6,16,10 4,1,1,1,1,1 7 9 14 100 125 Fat SEARCH 1 60 8 8 16 0 0 0 0 60 Demon IMMUNE_LIGHTNING 3 1165 +MT_TOAD Toad Demon fat\fat fat\fatb Retail 128 1130 true false 8,10,15,6,16,10 4,1,1,1,1,1 8 10 16 135 160 Fat SEARCH 2 70 8 8 16 40 0 8 20 65 Demon IMMUNE_MAGIC IMMUNE_MAGIC,RESIST_LIGHTNING 3 1380 +MT_FLAYED Flayed One fat\fat fat\fatf Retail 128 1130 true false 8,10,15,6,16,10 4,1,1,1,1,1 10 12 20 160 200 Fat SEARCH 3 85 8 10 20 0 0 0 0 70 Demon RESIST_MAGIC,IMMUNE_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 3 2058 +MT_WYRM Wyrm worm\worm Never 160 2420 false false 13,13,13,11,19,0 1,1,1,1,1,1 5 7 11 60 90 SkeletonMelee 0 40 8 4 10 0 0 0 0 25 Animal RESIST_MAGIC RESIST_MAGIC 3 660 +MT_CAVSLUG Cave Slug worm\worm Never 160 2420 false false 13,13,13,11,19,0 1,1,1,1,1,1 6 8 13 75 110 SkeletonMelee 1 50 8 6 13 0 0 0 0 30 Animal RESIST_MAGIC RESIST_MAGIC 3 994 +MT_DVLWYRM Devil Wyrm worm\worm Never 160 2420 false false 13,13,13,11,19,0 1,1,1,1,1,1 7 9 15 100 140 SkeletonMelee 2 55 8 8 16 0 0 0 0 30 Animal RESIST_MAGIC,RESIST_FIRE RESIST_MAGIC,RESIST_FIRE 3 1320 +MT_DEVOUR Devourer worm\worm Never 160 2420 false false 13,13,13,11,19,0 1,1,1,1,1,1 8 10 17 125 200 SkeletonMelee 3 60 8 10 20 0 0 0 0 35 Animal RESIST_MAGIC,RESIST_FIRE RESIST_MAGIC,RESIST_FIRE 3 1827 +MT_NMAGMA Magma Demon magma\magma Retail 128 1680 true true 8,10,14,7,18,18 2,1,1,1,1,1 8 9 13 50 70 Magma SEARCH,CAN_OPEN_DOOR 0 45 4 2 10 50 13 0 0 45 Demon IMMUNE_MAGIC,RESIST_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 7 1076 +MT_YMAGMA Blood Stone magma\magma magma\yellow Retail 128 1680 true true 8,10,14,7,18,18 2,1,1,1,1,1 8 10 14 55 75 Magma SEARCH,CAN_OPEN_DOOR 1 50 4 2 12 50 14 0 0 45 Demon IMMUNE_MAGIC,IMMUNE_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 7 1309 +MT_BMAGMA Hell Stone magma\magma magma\blue Retail 128 1680 true true 8,10,14,7,18,18 2,1,1,1,1,1 9 11 16 60 80 Magma SEARCH,CAN_OPEN_DOOR 2 60 4 2 20 60 14 0 0 50 Demon IMMUNE_MAGIC,IMMUNE_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 7 1680 +MT_WMAGMA Lava Lord magma\magma magma\wierd Retail 128 1680 true true 8,10,14,7,18,18 2,1,1,1,1,1 9 11 18 70 85 Magma SEARCH,CAN_OPEN_DOOR 3 75 4 4 24 60 14 0 0 60 Demon IMMUNE_MAGIC,IMMUNE_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 7 2124 +MT_HORNED Horned Demon rhino\rhino Retail 160 1630 true true 8,8,14,6,16,6 2,1,1,1,1,1 7 9 13 40 80 Rhino SEARCH,CAN_OPEN_DOOR 0 60 7 2 16 100 0 5 32 40 Animal RESIST_FIRE 7 1172 +MT_MUDRUN Mud Runner rhino\rhino rhino\orange Retail 160 1630 true true 8,8,14,6,16,6 2,1,1,1,1,1 8 10 15 50 90 Rhino SEARCH,CAN_OPEN_DOOR 1 70 7 6 18 100 0 12 36 45 Animal RESIST_FIRE 7 1404 +MT_FROSTC Frost Charger rhino\rhino rhino\blue Retail 160 1630 true true 8,8,14,6,16,6 2,1,1,1,1,1 9 11 17 60 100 Rhino SEARCH,CAN_OPEN_DOOR 2 80 7 8 20 100 0 20 40 50 Animal IMMUNE_MAGIC,RESIST_LIGHTNING IMMUNE_MAGIC,RESIST_LIGHTNING 7 1720 +MT_OBLORD Obsidian Lord rhino\rhino rhino\rhinob Retail 160 1630 true true 8,8,14,6,16,6 2,1,1,1,1,1 10 12 19 70 110 Rhino SEARCH,CAN_OPEN_DOOR 3 90 7 10 22 100 0 20 50 55 Animal IMMUNE_MAGIC,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 7 1809 +MT_BONEDMN oldboned demskel\demskl Never 128 1740 true true 10,8,20,6,24,16 3,1,1,1,1,1 24 24 12 70 70 Storm 0 60 8 6 14 12 0 0 0 50 Demon IMMUNE_MAGIC IMMUNE_MAGIC 7 1344 +MT_REDDTH Red Death thin\thin thin\thinv3 Never 160 1740 true true 8,8,18,4,17,14 3,1,1,1,1,1 8 10 16 96 96 Storm 1 75 5 10 20 0 0 0 0 60 Demon IMMUNE_MAGIC,IMMUNE_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 7 2168 +MT_LTCHDMN Litch Demon thin\thin thin\thinv3 Never 160 1740 true true 8,8,18,4,17,14 3,1,1,1,1,1 9 11 18 110 110 Storm 2 80 5 10 24 0 0 0 0 45 Demon IMMUNE_MAGIC,IMMUNE_LIGHTNING IMMUNE_MAGIC,IMMUNE_LIGHTNING 7 2736 +MT_UDEDBLRG Undead Balrog thin\thin thin\thinv3 Never 160 1740 true true 8,8,18,4,17,14 3,1,1,1,1,1 11 13 22 130 130 Storm 3 85 5 12 30 0 0 0 0 65 Demon IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING 7 3575 +MT_INCIN Incinerator fireman\firem Never 128 1460 true false 14,19,20,8,14,23 1,1,1,1,1,1 21 22 16 30 45 FireMan 0 75 8 8 16 0 0 0 0 25 Demon IMMUNE_MAGIC,IMMUNE_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 3 1888 +MT_FLAMLRD Flame Lord fireman\firem Never 128 1460 true false 14,19,20,8,14,23 1,1,1,1,1,1 22 23 18 40 55 FireMan 1 75 8 10 20 0 0 0 0 25 Demon IMMUNE_MAGIC,IMMUNE_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 3 2250 +MT_DOOMFIRE Doom Fire fireman\firem Never 128 1460 true false 14,19,20,8,14,23 1,1,1,1,1,1 23 24 20 50 65 FireMan 2 80 8 12 24 0 0 0 0 30 Demon IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 3 2740 +MT_HELLBURN Hell Burner fireman\firem Never 128 1460 true false 14,19,20,8,14,23 1,1,1,1,1,1 24 24 22 60 80 FireMan 3 85 8 15 30 0 0 0 0 30 Demon IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 3 3355 +MT_STORM Red Storm thin\thin thin\thinv3 Retail 160 1740 true true 8,8,18,4,17,14 3,1,1,1,1,1 9 11 18 55 110 Storm SEARCH,CAN_OPEN_DOOR 0 80 5 8 18 75 8 4 16 30 Demon IMMUNE_MAGIC,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_LIGHTNING 7 2160 +MT_RSTORM Storm Rider thin\thin Retail 160 1740 true true 8,8,18,4,17,14 3,1,1,1,1,1 10 12 20 60 120 Storm SEARCH,CAN_OPEN_DOOR 1 80 5 8 18 80 8 4 16 30 Demon RESIST_MAGIC,IMMUNE_LIGHTNING IMMUNE_MAGIC,IMMUNE_LIGHTNING 7 2391 +MT_STORML Storm Lord thin\thin thin\thinv2 Retail 160 1740 true true 8,8,18,4,17,14 3,1,1,1,1,1 11 13 22 75 135 Storm SEARCH,CAN_OPEN_DOOR 2 85 5 12 24 75 8 4 16 35 Demon RESIST_MAGIC,IMMUNE_LIGHTNING IMMUNE_MAGIC,IMMUNE_LIGHTNING 7 2775 +MT_MAEL Maelstrom thin\thin thin\thinv1 Retail 160 1740 true true 8,8,18,4,17,14 3,1,1,1,1,1 12 14 24 90 150 Storm SEARCH,CAN_OPEN_DOOR 3 90 5 12 28 75 8 4 16 40 Demon RESIST_MAGIC,IMMUNE_LIGHTNING IMMUNE_MAGIC,IMMUNE_LIGHTNING 7 3177 +MT_BIGFALL Devil Kin Brute bigfall\fallg newsfx\kbrute Retail 128 800 true false 10,8,11,8,17,0 1,1,1,1,2,2 21 22 27 120 160 SkeletonMelee SEARCH,CAN_OPEN_DOOR 3 100 6 18 24 0 0 0 0 70 Animal RESIST_FIRE,RESIST_LIGHTNING RESIST_MAGIC,RESIST_FIRE,RESIST_LIGHTNING 3 2400 +MT_WINGED Winged-Demon gargoyle\gargo Retail 160 1650 true false 14,14,14,10,18,14 1,1,1,1,1,2 5 7 9 45 60 Gargoyle CAN_OPEN_DOOR 0 50 7 10 16 0 0 0 0 45 Demon IMMUNE_MAGIC,RESIST_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 6 662 +MT_GARGOYLE Gargoyle gargoyle\gargo gargoyle\gare Retail 160 1650 true false 14,14,14,10,18,14 1,1,1,1,1,2 7 9 13 60 90 Gargoyle CAN_OPEN_DOOR 1 65 7 10 16 0 0 0 0 45 Demon IMMUNE_MAGIC,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_LIGHTNING 6 1205 +MT_BLOODCLW Blood Claw gargoyle\gargo gargoyle\gargbr Retail 160 1650 true false 14,14,14,10,18,14 1,1,1,1,1,1 9 11 19 75 125 Gargoyle CAN_OPEN_DOOR 2 80 7 14 22 0 0 0 0 50 Demon IMMUNE_MAGIC,IMMUNE_FIRE IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 6 1873 +MT_DEATHW Death Wing gargoyle\gargo gargoyle\gargb Retail 160 1650 true false 14,14,14,10,18,14 1,1,1,1,1,1 10 12 23 90 150 Gargoyle CAN_OPEN_DOOR 3 95 7 16 28 0 0 0 0 60 Demon IMMUNE_MAGIC,IMMUNE_LIGHTNING IMMUNE_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING 6 2278 +MT_MEGA Slayer mega\mega Retail 160 2220 true true 6,7,14,1,24,5 3,1,1,1,2,1 10 12 20 120 140 Mega SEARCH,CAN_OPEN_DOOR 0 100 8 12 20 0 3 0 0 60 Demon RESIST_MAGIC,IMMUNE_FIRE RESIST_MAGIC,IMMUNE_FIRE 7 2300 +MT_GUARD Guardian mega\mega mega\guard Retail 160 2220 true true 6,7,14,1,24,5 3,1,1,1,2,1 11 13 22 140 160 Mega SEARCH,CAN_OPEN_DOOR 1 110 8 14 22 0 3 0 0 65 Demon RESIST_MAGIC,IMMUNE_FIRE RESIST_MAGIC,IMMUNE_FIRE 7 2714 +MT_VTEXLRD Vortex Lord mega\mega mega\vtexl Retail 160 2220 true true 6,7,14,1,24,5 3,1,1,1,2,1 12 14 24 160 180 Mega SEARCH,CAN_OPEN_DOOR 2 120 8 18 24 0 3 0 0 70 Demon RESIST_MAGIC,IMMUNE_FIRE RESIST_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 7 3252 +MT_BALROG Balrog mega\mega mega\balr Retail 160 2220 true true 6,7,14,1,24,5 3,1,1,1,2,1 13 15 26 180 200 Mega SEARCH,CAN_OPEN_DOOR 3 130 8 22 30 0 3 0 0 75 Demon RESIST_MAGIC,IMMUNE_FIRE RESIST_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 7 3643 +MT_NSNAKE Cave Viper snake\snake Retail 160 1270 false false 12,11,13,5,18,0 2,1,1,1,1,1 11 13 21 100 150 Snake SEARCH 0 90 8 8 20 0 0 0 0 60 Demon IMMUNE_MAGIC IMMUNE_MAGIC 7 2725 +MT_RSNAKE Fire Drake snake\snake snake\snakr Retail 160 1270 false false 12,11,13,5,18,0 2,1,1,1,1,1 12 14 23 120 170 Snake SEARCH 1 105 8 12 24 0 0 0 0 65 Demon IMMUNE_MAGIC,RESIST_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 7 3139 +MT_BSNAKE Gold Viper snake\snake snake\snakg Retail 160 1270 false false 12,11,13,5,18,0 2,1,1,1,1,1 13 14 25 140 180 Snake SEARCH 2 120 8 15 26 0 0 0 0 70 Demon IMMUNE_MAGIC,RESIST_LIGHTNING IMMUNE_MAGIC,RESIST_LIGHTNING 7 3540 +MT_GSNAKE Azure Drake snake\snake snake\snakb Retail 160 1270 false false 12,11,13,5,18,0 2,1,1,1,1,1 15 16 27 160 200 Snake SEARCH 3 130 8 18 30 0 0 0 0 75 Demon RESIST_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING 7 3791 +MT_NBLACK Black Knight black\black Retail 160 2120 false false 8,8,16,4,24,0 2,1,1,1,1,1 12 14 24 150 150 SkeletonMelee SEARCH 0 110 8 15 20 0 0 0 0 75 Demon RESIST_MAGIC,RESIST_LIGHTNING RESIST_MAGIC,IMMUNE_LIGHTNING 7 3360 +MT_RTBLACK Doom Guard black\black black\blkkntrt Retail 160 2120 false false 8,8,16,4,24,0 2,1,1,1,1,1 13 15 26 165 165 SkeletonMelee SEARCH 0 130 8 18 25 0 0 0 0 75 Demon RESIST_MAGIC,RESIST_FIRE RESIST_MAGIC,IMMUNE_FIRE 7 3650 +MT_BTBLACK Steel Lord black\black black\blkkntbt Retail 160 2120 false false 8,8,16,4,24,0 2,1,1,1,1,1 14 16 28 180 180 SkeletonMelee SEARCH 1 120 8 20 30 0 0 0 0 80 Demon RESIST_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 7 4252 +MT_RBLACK Blood Knight black\black black\blkkntbe Retail 160 2120 false false 8,8,16,4,24,0 2,1,1,1,1,1 13 14 30 200 200 SkeletonMelee SEARCH 1 130 8 25 35 0 0 0 0 85 Demon IMMUNE_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING IMMUNE_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING 7 5130 +MT_UNRAV The Shredded unrav\unrav newsfx\shred Retail 96 484 false false 10,10,12,5,16,0 1,1,1,1,1,1 17 18 23 70 90 SkeletonMelee 0 75 7 4 12 0 0 0 0 65 Undead RESIST_FIRE,RESIST_LIGHTNING RESIST_FIRE,RESIST_LIGHTNING 3 900 +MT_HOLOWONE Hollow One unrav\unrav acid\acid Never 96 484 false false 10,10,12,5,16,0 1,1,1,1,1,1 18 19 27 135 240 SkeletonMelee 1 75 7 12 24 0 0 0 0 75 Undead IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 3 4374 +MT_PAINMSTR Pain Master unrav\unrav acid\acid Never 96 484 false false 10,10,12,5,16,0 1,1,1,1,1,1 19 20 29 110 200 SkeletonMelee 2 80 7 16 30 0 0 0 0 80 Undead IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 3 5147 +MT_REALWEAV Reality Weaver unrav\unrav acid\acid Never 96 484 false false 10,10,12,5,16,0 1,1,1,1,1,1 20 20 30 135 240 SkeletonMelee 3 85 7 20 35 0 0 0 0 85 Undead RESIST_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING RESIST_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 3 5925 +MT_SUCCUBUS Succubus succ\scbs Retail 128 980 false false 14,8,16,7,24,0 1,1,1,1,1,1 12 14 24 120 150 Succubus CAN_OPEN_DOOR 0 100 10 1 20 0 0 0 0 60 Demon RESIST_MAGIC IMMUNE_MAGIC,RESIST_FIRE 3 3696 +MT_SNOWWICH Snow Witch succ\scbs succ\succb Retail 128 980 false false 14,8,16,7,24,0 1,1,1,1,1,1 13 15 26 135 175 Succubus CAN_OPEN_DOOR 1 110 10 1 24 0 0 0 0 65 Demon RESIST_LIGHTNING IMMUNE_MAGIC,RESIST_LIGHTNING 3 4084 +MT_HLSPWN Hell Spawn succ\scbs succ\succrw Retail 128 980 false false 14,8,16,7,24,0 1,1,1,1,1,1 14 16 28 150 200 Succubus SEARCH,CAN_OPEN_DOOR 2 115 10 1 30 0 0 0 0 75 Demon RESIST_MAGIC,IMMUNE_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 3 4480 +MT_SOLBRNR Soul Burner succ\scbs succ\succbw Retail 128 980 false false 14,8,16,7,24,0 1,1,1,1,1,1 15 16 30 140 225 Succubus SEARCH,CAN_OPEN_DOOR 3 120 10 1 35 0 0 0 0 85 Demon RESIST_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 3 4644 +MT_COUNSLR Counselor mage\mage Retail 128 2000 true false 12,1,20,8,28,20 1,1,1,1,1,1 13 14 25 70 70 Counselor CAN_OPEN_DOOR 0 90 8 8 20 0 0 0 0 0 Demon RESIST_MAGIC,RESIST_FIRE,RESIST_LIGHTNING RESIST_MAGIC,RESIST_FIRE,RESIST_LIGHTNING 7 4070 +MT_MAGISTR Magistrate mage\mage mage\cnselg Retail 128 2000 true false 12,1,20,8,28,20 1,1,1,1,1,1 14 15 27 85 85 Counselor CAN_OPEN_DOOR 1 100 8 10 24 0 0 0 0 0 Demon RESIST_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 7 4478 +MT_CABALIST Cabalist mage\mage mage\cnselgd Retail 128 2000 true false 12,1,20,8,28,20 1,1,1,1,1,1 15 16 29 120 120 Counselor CAN_OPEN_DOOR 2 110 8 14 30 0 0 0 0 0 Demon RESIST_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING IMMUNE_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING 7 4929 +MT_ADVOCATE Advocate mage\mage mage\cnselbk Retail 128 2000 true false 12,1,20,8,28,20 1,1,1,1,1,1 16 16 30 145 145 Counselor CAN_OPEN_DOOR 3 120 8 15 25 0 0 0 0 0 Demon IMMUNE_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 7 4968 +MT_GOLEM Golem golem\golem golem\golm Never 96 386 true false 0,16,12,0,12,20 1,1,1,1,1,1 1 1 12 1 1 Golem CAN_OPEN_DOOR 0 0 7 1 1 0 0 0 0 1 Demon 0 0 +MT_DIABLO The Dark Lord diablo\diablo Never 160 2000 true true 16,6,16,6,16,16 1,1,1,1,1,1 26 26 45 3333 3333 Diablo KNOCKBACK,SEARCH,CAN_OPEN_DOOR 3 220 4 30 60 0 11 0 0 90 Demon IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING 7 31666 +MT_DARKMAGE The Arch-Litch Malignus darkmage\dmage darkmage\dmag Never 128 1060 true false 6,1,21,6,23,18 1,1,1,1,1,1 21 21 30 160 160 Counselor CAN_OPEN_DOOR 3 120 8 20 40 0 0 0 0 70 Demon RESIST_MAGIC,RESIST_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 7 4968 +MT_HELLBOAR Hellboar fork\fork newsfx\hboar Retail 188 800 false false 10,10,15,6,16,0 2,1,1,1,1,1 17 18 23 80 100 SkeletonMelee KNOCKBACK,SEARCH 2 70 7 16 24 0 0 0 0 60 Demon RESIST_FIRE,RESIST_LIGHTNING 3 750 +MT_STINGER Stinger scorp\scorp newsfx\stingr Retail 64 305 false false 10,10,12,6,15,0 2,1,1,1,1,1 17 18 22 30 40 SkeletonMelee 3 85 8 1 20 0 0 0 0 50 Animal RESIST_LIGHTNING 1 500 +MT_PSYCHORB Psychorb eye\eye newsfx\psyco Retail 156 800 false false 12,13,13,7,21,0 2,1,1,1,1,1 17 18 22 20 30 Psychorb 3 80 8 10 10 0 0 0 0 40 Animal RESIST_FIRE 6 450 +MT_ARACHNON Arachnon spider\spider newsfx\slord Retail 148 800 false false 12,10,15,6,20,0 2,1,1,1,1,1 17 18 22 60 80 SkeletonMelee SEARCH 3 50 8 5 15 0 0 0 0 50 Animal RESIST_LIGHTNING 7 500 +MT_FELLTWIN Felltwin tsneak\tsneak newsfx\ftwin Retail 128 800 false false 13,13,15,11,16,0 2,1,1,1,1,1 17 18 22 50 70 SkeletonMelee SEARCH,CAN_OPEN_DOOR 3 70 8 10 18 0 0 0 0 50 Demon RESIST_FIRE,RESIST_LIGHTNING 3 600 +MT_HORKSPWN Hork Spawn spawn\spawn newsfx\hspawn Retail 164 520 false true 15,12,14,11,14,0 1,1,1,1,1,1 18 19 22 30 30 SkeletonMelee 3 60 8 10 25 0 0 0 0 25 Demon RESIST_MAGIC RESIST_MAGIC 3 250 +MT_VENMTAIL Venomtail wscorp\wscorp newsfx\stingr Retail 86 305 false false 10,10,12,6,15,0 2,1,1,1,1,1 19 20 24 40 50 SkeletonMelee 3 85 8 1 30 0 0 0 0 60 Animal RESIST_LIGHTNING IMMUNE_LIGHTNING 1 1000 +MT_NECRMORB Necromorb eye2\eye2 newsfx\psyco Retail 140 800 false false 12,13,13,7,21,0 2,1,1,1,1,1 19 20 24 30 40 Necromorb 3 80 8 20 20 0 0 0 0 50 Animal RESIST_FIRE IMMUNE_FIRE,RESIST_LIGHTNING 6 1100 +MT_SPIDLORD Spider Lord bspidr\bspidr newsfx\slord Retail 148 800 true true 12,10,15,6,20,10 2,1,1,1,1,1 19 20 24 80 100 Acid SEARCH 3 60 8 8 20 75 8 10 10 60 Animal RESIST_LIGHTNING RESIST_FIRE,IMMUNE_LIGHTNING 7 1250 +MT_LASHWORM Lashworm clasp\clasp newsfx\lworm Retail 176 800 false false 10,12,15,6,16,0 1,1,1,1,1,1 19 20 20 30 30 SkeletonMelee 3 90 8 12 20 0 0 0 0 50 Animal RESIST_FIRE 3 600 +MT_TORCHANT Torchant antworm\worm newsfx\tchant Retail 192 800 false false 14,12,12,6,20,0 2,1,1,1,1,1 19 20 22 60 80 Torchant 3 75 8 20 30 0 0 0 0 70 Animal IMMUNE_FIRE RESIST_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 7 1250 +MT_HORKDMN Hork Demon horkd\horkd newsfx\hdemon Never 138 800 true true 15,8,16,6,16,9 2,1,1,1,1,2 19 19 27 120 160 SkeletonMelee 3 60 8 20 35 80 8 0 0 80 Demon RESIST_LIGHTNING RESIST_MAGIC,IMMUNE_LIGHTNING 7 2000 +MT_DEFILER Hell Bug hellbug\hellbg newsfx\defile Never 198 800 true true 8,8,14,6,14,12 1,1,1,1,1,1 20 20 30 240 240 SkeletonMelee SEARCH 3 110 8 20 30 90 8 50 60 80 Demon RESIST_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING RESIST_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 7 5000 +MT_GRAVEDIG Gravedigger gravdg\gravdg newsfx\gdiggr Retail 124 800 true true 24,24,12,6,16,16 2,1,1,1,1,1 21 21 26 120 240 Scavenger CAN_OPEN_DOOR 3 80 6 2 12 0 0 0 0 20 Undead IMMUNE_LIGHTNING RESIST_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING 3 2000 +MT_TOMBRAT Tomb Rat rat\rat newsfx\tmbrat Retail 104 550 false false 11,8,12,6,20,0 2,1,1,1,1,1 21 22 24 80 120 SkeletonMelee 3 120 8 12 25 0 0 0 0 30 Animal RESIST_FIRE,RESIST_LIGHTNING 3 1800 +MT_FIREBAT Firebat hellbat\helbat newsfx\helbat Retail 96 550 false false 18,16,14,6,18,11 2,1,1,1,1,1 21 22 24 60 80 FireBat 3 100 8 15 20 0 0 0 0 70 Animal IMMUNE_FIRE RESIST_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 7 2400 +MT_SKLWING Skullwing demskel\demskl newsfx\swing skelaxe\skelt Retail 128 1740 true false 10,8,20,6,24,16 3,1,1,1,1,1 21 22 27 70 70 SkeletonMelee 0 75 7 15 20 75 9 15 20 80 Undead RESIST_FIRE,RESIST_LIGHTNING RESIST_FIRE,RESIST_LIGHTNING 7 3000 +MT_LICH Lich lich\lich newsfx\lich Retail 96 800 false true 12,10,10,7,21,0 2,1,1,1,2,1 21 22 25 80 100 Lich 3 100 8 15 20 0 0 0 0 60 Undead RESIST_LIGHTNING RESIST_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING 3 3000 +MT_CRYPTDMN Crypt Demon bubba\bubba newsfx\crypt Retail 154 800 false true 8,18,12,8,21,0 3,1,1,1,1,1 22 23 28 200 240 SkeletonMelee 3 100 8 20 40 0 0 0 0 85 Demon IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 3 3200 +MT_HELLBAT Hellbat hellbat2\bhelbt newsfx\helbat Retail 96 550 true false 18,16,14,6,18,11 2,1,1,1,1,1 23 24 29 100 140 Torchant 3 110 8 30 30 0 0 0 0 80 Demon RESIST_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING RESIST_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 7 3600 +MT_BONEDEMN Bone Demon demskel\demskl newsfx\swing Retail 128 1740 true true 10,8,20,6,24,16 3,1,1,1,1,1 23 24 30 240 280 BoneDemon 0 100 8 40 50 160 12 50 50 50 Undead IMMUNE_FIRE,IMMUNE_LIGHTNING IMMUNE_FIRE,IMMUNE_LIGHTNING 7 5000 +MT_ARCHLICH Arch Lich lich2\lich2 newsfx\lich Retail 136 800 false true 12,10,10,7,21,0 2,1,1,1,2,1 23 24 30 180 200 ArchLich 3 120 8 30 30 0 0 0 0 75 Undead RESIST_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 3 4000 +MT_BICLOPS Biclops byclps\byclps newsfx\biclop Retail 180 800 false false 10,11,16,6,16,0 2,1,1,1,2,1 23 24 30 200 240 SkeletonMelee KNOCKBACK,CAN_OPEN_DOOR 3 90 8 40 50 0 0 0 0 80 Demon RESIST_LIGHTNING RESIST_FIRE,RESIST_LIGHTNING 3 4000 +MT_FLESTHNG Flesh Thing flesh\flesh newsfx\flesht Retail 164 800 false true 15,24,15,6,16,0 1,1,1,1,1,1 23 24 28 300 400 SkeletonMelee 3 150 8 12 18 0 0 0 0 70 Demon RESIST_MAGIC,RESIST_FIRE,RESIST_LIGHTNING RESIST_MAGIC,RESIST_FIRE,RESIST_LIGHTNING 3 4000 +MT_REAPER Reaper reaper\reap newsfx\reaper Retail 180 800 false false 12,10,14,6,16,0 2,1,1,1,1,1 23 24 30 260 300 SkeletonMelee 3 120 8 30 35 0 0 0 0 90 Demon IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 3 6000 +MT_NAKRUL Na-Krul nkr\nkr newsfx\nakrul Never 226 1200 true true 2,6,16,3,16,16 1,1,1,1,1,1 31 31 40 1332 1332 SkeletonMelee KNOCKBACK,SEARCH,CAN_OPEN_DOOR 3 150 7 40 50 150 10 40 50 125 Demon IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 7 13333 diff --git a/Source/data/file.cpp b/Source/data/file.cpp index 5568dcb7e..2365ff92e 100644 --- a/Source/data/file.cpp +++ b/Source/data/file.cpp @@ -47,24 +47,31 @@ void DataFile::reportFatalError(Error code, std::string_view fileName) } } -void DataFile::reportFatalFieldError(DataFileField::Error code, std::string_view fileName, std::string_view fieldName, const DataFileField &field) +void DataFile::reportFatalFieldError(DataFileField::Error code, std::string_view fileName, std::string_view fieldName, const DataFileField &field, std::string_view details) { + std::string detailsStr; + if (!details.empty()) { + detailsStr = StrCat("\n", details); + } switch (code) { case DataFileField::Error::NotANumber: app_fatal(fmt::format(fmt::runtime(_( /* TRANSLATORS: Error message when parsing a data file and a text value is encountered when a number is expected. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} */ "Non-numeric value {0} for {1} in {2} at row {3} and column {4}")), - field.currentValue(), fieldName, fileName, field.row(), field.column())); + field.currentValue(), fieldName, fileName, field.row(), field.column()) + .append(detailsStr)); case DataFileField::Error::OutOfRange: app_fatal(fmt::format(fmt::runtime(_( /* TRANSLATORS: Error message when parsing a data file and we find a number larger than expected. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} */ "Out of range value {0} for {1} in {2} at row {3} and column {4}")), - field.currentValue(), fieldName, fileName, field.row(), field.column())); + field.currentValue(), fieldName, fileName, field.row(), field.column()) + .append(detailsStr)); case DataFileField::Error::InvalidValue: app_fatal(fmt::format(fmt::runtime(_( /* TRANSLATORS: Error message when we find an unrecognised value in a key column. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} */ "Invalid value {0} for {1} in {2} at row {3} and column {4}")), - field.currentValue(), fieldName, fileName, field.row(), field.column())); + field.currentValue(), fieldName, fileName, field.row(), field.column()) + .append(detailsStr)); } } diff --git a/Source/data/file.hpp b/Source/data/file.hpp index f687fa9b4..f01d47fe9 100644 --- a/Source/data/file.hpp +++ b/Source/data/file.hpp @@ -75,7 +75,7 @@ public: static tl::expected load(std::string_view path); static void reportFatalError(Error code, std::string_view fileName); - static void reportFatalFieldError(DataFileField::Error code, std::string_view fileName, std::string_view fieldName, const DataFileField &field); + static void reportFatalFieldError(DataFileField::Error code, std::string_view fileName, std::string_view fieldName, const DataFileField &field, std::string_view details = {}); void resetHeader() { diff --git a/Source/data/iterators.hpp b/Source/data/iterators.hpp index 83f00ee16..b76145e64 100644 --- a/Source/data/iterators.hpp +++ b/Source/data/iterators.hpp @@ -4,9 +4,11 @@ #include #include +#include #include "parser.hpp" #include "utils/parse_int.hpp" +#include "utils/str_split.hpp" namespace devilution { @@ -111,6 +113,54 @@ public: return mapError(result.ec); } + [[nodiscard]] tl::expected parseBool(bool &destination) + { + const std::string_view str = value(); + if (str == "true") { + destination = true; + return {}; + } + if (str == "false") { + destination = false; + return {}; + } + return tl::make_unexpected(DataFileField::Error::InvalidValue); + } + + template + [[nodiscard]] tl::expected parseIntArray(T (&destination)[N]) + { + size_t i = 0; + for (const std::string_view part : SplitByChar(value(), ',')) { + if (i == N) + return tl::make_unexpected(Error::InvalidValue); + const std::from_chars_result result + = std::from_chars(part.data(), part.data() + part.size(), destination[i]); + if (result.ec != std::errc()) + return mapError(result.ec); + ++i; + } + if (i != N) + return tl::make_unexpected(Error::InvalidValue); + return {}; + } + + template + [[nodiscard]] tl::expected parseEnumList(T &destination, ParseFn &&parseFn) + { + destination = {}; + const std::string_view str = value(); + if (str.empty()) + return {}; + for (const std::string_view part : SplitByChar(str, ',')) { + auto result = parseFn(part); + if (!result.has_value()) + return tl::make_unexpected(Error::InvalidValue); + destination |= result.value(); + } + return {}; + } + template [[nodiscard]] tl::expected asInt() { diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 468a32981..48e8f2df5 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -57,6 +57,7 @@ #include "menu.h" #include "minitext.h" #include "missiles.h" +#include "monstdat.h" #include "movie.h" #include "multi.h" #include "nthread.h" @@ -2474,6 +2475,9 @@ int DiabloMain(int argc, char **argv) // Load dynamic data before we go into the menu as we need to initialise player characters in memory pretty early. LoadPlayerDataFiles(); + // TODO: We can probably load this much later (when the game is starting). + LoadMonsterData(); + DiabloInit(); #ifdef __UWP__ onInitialized(); diff --git a/Source/lua/modules/dev/monsters.cpp b/Source/lua/modules/dev/monsters.cpp index c970bd5e8..1226bffc8 100644 --- a/Source/lua/modules/dev/monsters.cpp +++ b/Source/lua/modules/dev/monsters.cpp @@ -105,7 +105,7 @@ std::string DebugCmdSpawnMonster(std::string name, std::optional count for (int i = 0; i < NUM_MTYPES; i++) { auto mondata = MonstersData[i]; - const std::string monsterName = AsciiStrToLower(mondata.name); + const std::string monsterName = AsciiStrToLower(std::string_view(mondata.name)); if (monsterName.find(name) == std::string::npos) continue; mtype = i; diff --git a/Source/monstdat.cpp b/Source/monstdat.cpp index 1ce4adf62..f976f4d5a 100644 --- a/Source/monstdat.cpp +++ b/Source/monstdat.cpp @@ -6,7 +6,11 @@ #include "monstdat.h" #include +#include +#include + +#include "data/file.hpp" #include "items.h" #include "monster.h" #include "textdat.h" @@ -22,220 +26,17 @@ constexpr uint16_t Uniq(_unique_items item) return static_cast(T_UNIQ) + item; } -const char *const MonsterSpritePaths[] = { - "zombie\\zombie", // Zombie - "falspear\\phall", // FallenSpear - "falsword\\fall", // FallenSword - "skelaxe\\sklax", // SkeletonAxe - "skelbow\\sklbw", // SkeletonBow - "skelsd\\sklsr", // SkeletonCapt - "demskel\\demskl", // SkeletonDemo - "sking\\sking", // SkeletonKing - "scav\\scav", // Scavenger - "tsneak\\tsneak", // InvisibleLor - "sneak\\sneak", // Hidden - "goatmace\\goat", // GoatMace - "goatbow\\goatb", // GoatBow - "goatlord\\goatl", // GoatLord - "bat\\bat", // Bat - "acid\\acid", // AcidBeast - "fat\\fat", // Overlord - "fatc\\fatc", // Butcher - "worm\\worm", // Wyrm - "magma\\magma", // MagmaDemon - "rhino\\rhino", // HornedDemon - "thin\\thin", // StormRider - "fireman\\firem", // Incinerator - "bigfall\\fallg", // DevilKinBrut - "gargoyle\\gargo", // Gargoyle - "mega\\mega", // Slayer - "snake\\snake", // Viper - "black\\black", // BlackKnight - "unrav\\unrav", // Shredded - "succ\\scbs", // Succubus - "mage\\mage", // Counselor - "golem\\golem", // Golem - "diablo\\diablo", // Diablo - "darkmage\\dmage", // ArchLitch - "fork\\fork", // Hellboar - "scorp\\scorp", // Stinger - "eye\\eye", // Psychorb - "spider\\spider", // Arachnon - "spawn\\spawn", // HorkSpawn - "wscorp\\wscorp", // Venomtail - "eye2\\eye2", // Necromorb - "bspidr\\bspidr", // SpiderLord - "clasp\\clasp", // Lashworm - "antworm\\worm", // Torchant - "horkd\\horkd", // HorkDemon - "hellbug\\hellbg", // HellBug - "gravdg\\gravdg", // Gravedigger - "rat\\rat", // Rat - "hellbat\\helbat", // Firebat - "lich\\lich", // Lich - "bubba\\bubba", // CryptDemon - "hellbat2\\bhelbt", // Hellbat - "lich2\\lich2", // ArchLich - "byclps\\byclps", // Biclops - "flesh\\flesh", // FleshThing - "reaper\\reap", // Reaper - "nkr\\nkr", // NaKrul -}; +std::vector MonsterSpritePaths; } // namespace const char *MonsterData::spritePath() const { - return MonsterSpritePaths[static_cast(spriteId)]; + return MonsterSpritePaths[static_cast(spriteId)].c_str(); } /** Contains the data related to each monster ID. */ -const MonsterData MonstersData[] = { - // clang-format off -// _monster_id name, soundSuffix, trnFile, spriteId, availability, width, image, hasSpecial, hasSpecialSound, frames[6], rate[6], minDunLvl, maxDunLvl, level, hitPointsMinimum, hitPointsMaximum, ai, abilityFlags, intelligence, toHit, animFrameNum, minDamage, maxDamage, toHitSpecial, animFrameNumSpecial, minDamageSpecial, maxDamageSpecial, armorClass, monsterClass, resistance, resistanceHell, selectionType, treasure, exp - -// TRANSLATORS: Monster Block start -/* MT_NZOMBIE */ { P_("monster", "Zombie"), nullptr, nullptr, MonsterSpriteId::Zombie, MonsterAvailability::Always, 128, 799, false, false, { 11, 24, 12, 6, 16, 0 }, { 4, 1, 1, 1, 1, 1 }, 1, 2, 1, 4, 7, MonsterAIID::Zombie, 0, 0, 10, 8, 2, 5, 0, 0, 0, 0, 5, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC, 3, 0, 54 }, -/* MT_BZOMBIE */ { P_("monster", "Ghoul"), nullptr, "zombie\\bluered", MonsterSpriteId::Zombie, MonsterAvailability::Always, 128, 799, false, false, { 11, 24, 12, 6, 16, 0 }, { 4, 1, 1, 1, 1, 1 }, 2, 3, 2, 7, 11, MonsterAIID::Zombie, 0, 1, 10, 8, 3, 10, 0, 0, 0, 0, 10, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC, 3, 0, 58 }, -/* MT_GZOMBIE */ { P_("monster", "Rotting Carcass"), nullptr, "zombie\\grey", MonsterSpriteId::Zombie, MonsterAvailability::Always, 128, 799, false, false, { 11, 24, 12, 6, 16, 0 }, { 4, 1, 1, 1, 1, 1 }, 2, 4, 4, 15, 25, MonsterAIID::Zombie, 0, 2, 25, 8, 5, 15, 0, 0, 0, 0, 15, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC | RESIST_FIRE, 3, 0, 136 }, -/* MT_YZOMBIE */ { P_("monster", "Black Death"), nullptr, "zombie\\yellow", MonsterSpriteId::Zombie, MonsterAvailability::Always, 128, 799, false, false, { 11, 24, 12, 6, 16, 0 }, { 4, 1, 1, 1, 1, 1 }, 3, 5, 6, 25, 40, MonsterAIID::Zombie, 0, 3, 30, 8, 6, 22, 0, 0, 0, 0, 20, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC | RESIST_LIGHTNING, 3, 0, 240 }, -/* MT_RFALLSP */ { P_("monster", "Fallen One"), nullptr, "falspear\\fallent", MonsterSpriteId::FallenSpear, MonsterAvailability::Always, 128, 543, true, true, { 11, 11, 13, 11, 18, 13 }, { 3, 1, 1, 1, 1, 1 }, 1, 2, 1, 1, 4, MonsterAIID::Fallen, 0, 0, 15, 7, 1, 3, 0, 5, 0, 0, 0, MonsterClass::Animal, 0, 0, 3, 0, 46 }, -/* MT_DFALLSP */ { P_("monster", "Carver"), nullptr, "falspear\\dark", MonsterSpriteId::FallenSpear, MonsterAvailability::Always, 128, 543, true, true, { 11, 11, 13, 11, 18, 13 }, { 3, 1, 1, 1, 1, 1 }, 2, 3, 3, 4, 8, MonsterAIID::Fallen, 0, 2, 20, 7, 2, 5, 0, 5, 0, 0, 5, MonsterClass::Animal, 0, 0, 3, 0, 80 }, -/* MT_YFALLSP */ { P_("monster", "Devil Kin"), nullptr, nullptr, MonsterSpriteId::FallenSpear, MonsterAvailability::Always, 128, 543, true, true, { 11, 11, 13, 11, 18, 13 }, { 3, 1, 1, 1, 1, 1 }, 2, 4, 5, 12, 24, MonsterAIID::Fallen, 0, 2, 25, 7, 3, 7, 0, 5, 0, 0, 10, MonsterClass::Animal, 0, RESIST_FIRE, 3, 0, 155 }, -/* MT_BFALLSP */ { P_("monster", "Dark One"), nullptr, "falspear\\blue", MonsterSpriteId::FallenSpear, MonsterAvailability::Always, 128, 543, true, true, { 11, 11, 13, 11, 18, 13 }, { 3, 1, 1, 1, 1, 1 }, 3, 5, 7, 20, 36, MonsterAIID::Fallen, 0, 3, 30, 7, 4, 8, 0, 5, 0, 0, 15, MonsterClass::Animal, 0, RESIST_LIGHTNING, 3, 0, 255 }, -/* MT_WSKELAX */ { P_("monster", "Skeleton"), nullptr, "skelaxe\\white", MonsterSpriteId::SkeletonAxe, MonsterAvailability::Always, 128, 553, true, false, { 12, 8, 13, 6, 17, 16 }, { 5, 1, 1, 1, 1, 1 }, 1, 2, 1, 2, 4, MonsterAIID::SkeletonMelee, 0, 0, 20, 8, 1, 4, 0, 0, 0, 0, 0, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC, 3, 0, 64 }, -/* MT_TSKELAX */ { P_("monster", "Corpse Axe"), nullptr, "skelaxe\\skelt", MonsterSpriteId::SkeletonAxe, MonsterAvailability::Always, 128, 553, true, false, { 12, 8, 13, 6, 17, 16 }, { 4, 1, 1, 1, 1, 1 }, 2, 3, 2, 4, 7, MonsterAIID::SkeletonMelee, 0, 1, 25, 8, 3, 5, 0, 0, 0, 0, 0, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC, 3, 0, 68 }, -/* MT_RSKELAX */ { P_("monster", "Burning Dead"), nullptr, nullptr, MonsterSpriteId::SkeletonAxe, MonsterAvailability::Always, 128, 553, true, false, { 12, 8, 13, 6, 17, 16 }, { 2, 1, 1, 1, 1, 1 }, 2, 4, 4, 8, 12, MonsterAIID::SkeletonMelee, 0, 2, 30, 8, 3, 7, 0, 0, 0, 0, 5, MonsterClass::Undead, IMMUNE_MAGIC | RESIST_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 3, 0, 154 }, -/* MT_XSKELAX */ { P_("monster", "Horror"), nullptr, "skelaxe\\black", MonsterSpriteId::SkeletonAxe, MonsterAvailability::Always, 128, 553, true, false, { 12, 8, 13, 6, 17, 16 }, { 3, 1, 1, 1, 1, 1 }, 3, 5, 6, 12, 20, MonsterAIID::SkeletonMelee, 0, 3, 35, 8, 4, 9, 0, 0, 0, 0, 15, MonsterClass::Undead, IMMUNE_MAGIC | RESIST_LIGHTNING, IMMUNE_MAGIC | RESIST_LIGHTNING, 3, 0, 264 }, -/* MT_RFALLSD */ { P_("monster", "Fallen One"), nullptr, "falsword\\fallent", MonsterSpriteId::FallenSword, MonsterAvailability::Always, 128, 623, true, true, { 12, 12, 13, 11, 14, 15 }, { 3, 1, 1, 1, 1, 1 }, 1, 2, 1, 2, 5, MonsterAIID::Fallen, 0, 0, 15, 8, 1, 4, 0, 5, 0, 0, 10, MonsterClass::Animal, 0, 0, 3, 0, 52 }, -/* MT_DFALLSD */ { P_("monster", "Carver"), nullptr, "falsword\\dark", MonsterSpriteId::FallenSword, MonsterAvailability::Always, 128, 623, true, true, { 12, 12, 13, 11, 14, 15 }, { 3, 1, 1, 1, 1, 1 }, 2, 3, 3, 5, 9, MonsterAIID::Fallen, 0, 1, 20, 8, 2, 7, 0, 5, 0, 0, 15, MonsterClass::Animal, 0, 0, 3, 0, 90 }, -/* MT_YFALLSD */ { P_("monster", "Devil Kin"), nullptr, nullptr, MonsterSpriteId::FallenSword, MonsterAvailability::Always, 128, 623, true, true, { 12, 12, 13, 11, 14, 15 }, { 3, 1, 1, 1, 1, 1 }, 2, 4, 5, 16, 24, MonsterAIID::Fallen, 0, 2, 25, 8, 4, 10, 0, 5, 0, 0, 20, MonsterClass::Animal, 0, RESIST_FIRE, 3, 0, 180 }, -/* MT_BFALLSD */ { P_("monster", "Dark One"), nullptr, "falsword\\blue", MonsterSpriteId::FallenSword, MonsterAvailability::Always, 128, 623, true, true, { 12, 12, 13, 11, 14, 15 }, { 3, 1, 1, 1, 1, 1 }, 3, 5, 7, 24, 36, MonsterAIID::Fallen, 0, 3, 30, 8, 4, 12, 0, 5, 0, 0, 25, MonsterClass::Animal, 0, RESIST_LIGHTNING, 3, 0, 280 }, -/* MT_NSCAV */ { P_("monster", "Scavenger"), nullptr, nullptr, MonsterSpriteId::Scavenger, MonsterAvailability::Always, 128, 410, true, false, { 12, 8, 12, 6, 20, 11 }, { 2, 1, 1, 1, 1, 1 }, 1, 3, 2, 3, 6, MonsterAIID::Scavenger, 0, 0, 20, 7, 1, 5, 0, 0, 0, 0, 10, MonsterClass::Animal, 0, RESIST_FIRE, 3, 0, 80 }, -/* MT_BSCAV */ { P_("monster", "Plague Eater"), nullptr, "scav\\scavbr", MonsterSpriteId::Scavenger, MonsterAvailability::Always, 128, 410, true, false, { 12, 8, 12, 6, 20, 11 }, { 2, 1, 1, 1, 1, 1 }, 2, 4, 4, 12, 24, MonsterAIID::Scavenger, 0, 1, 30, 7, 1, 8, 0, 0, 0, 0, 20, MonsterClass::Animal, 0, RESIST_LIGHTNING, 3, 0, 188 }, -/* MT_WSCAV */ { P_("monster", "Shadow Beast"), nullptr, "scav\\scavbe", MonsterSpriteId::Scavenger, MonsterAvailability::Always, 128, 410, true, false, { 12, 8, 12, 6, 20, 11 }, { 2, 1, 1, 1, 1, 1 }, 3, 5, 6, 24, 36, MonsterAIID::Scavenger, 0, 2, 35, 7, 3, 12, 0, 0, 0, 0, 25, MonsterClass::Animal, 0, RESIST_FIRE, 3, 0, 375 }, -/* MT_YSCAV */ { P_("monster", "Bone Gasher"), nullptr, "scav\\scavw", MonsterSpriteId::Scavenger, MonsterAvailability::Always, 128, 410, true, false, { 12, 8, 12, 6, 20, 11 }, { 2, 1, 1, 1, 1, 1 }, 4, 6, 8, 28, 40, MonsterAIID::Scavenger, 0, 3, 35, 7, 5, 15, 0, 0, 0, 0, 30, MonsterClass::Animal, RESIST_MAGIC, RESIST_LIGHTNING, 3, 0, 552 }, -/* MT_WSKELBW */ { P_("monster", "Skeleton"), nullptr, "skelbow\\white", MonsterSpriteId::SkeletonBow, MonsterAvailability::Always, 128, 567, true, false, { 9, 8, 16, 5, 16, 16 }, { 4, 1, 1, 1, 1, 1 }, 2, 3, 3, 2, 4, MonsterAIID::SkeletonRanged, 0, 0, 15, 12, 1, 2, 0, 0, 0, 0, 0, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC, 3, 0, 110 }, -/* MT_TSKELBW */ { P_("monster", "Corpse Bow"), nullptr, "skelbow\\skelt", MonsterSpriteId::SkeletonBow, MonsterAvailability::Always, 128, 567, true, false, { 9, 8, 16, 5, 16, 16 }, { 4, 1, 1, 1, 1, 1 }, 2, 4, 5, 8, 16, MonsterAIID::SkeletonRanged, 0, 1, 25, 12, 1, 4, 0, 0, 0, 0, 0, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC, 3, 0, 210 }, -/* MT_RSKELBW */ { P_("monster", "Burning Dead"), nullptr, nullptr, MonsterSpriteId::SkeletonBow, MonsterAvailability::Always, 128, 567, true, false, { 9, 8, 16, 5, 16, 16 }, { 2, 1, 1, 1, 1, 1 }, 3, 5, 7, 10, 24, MonsterAIID::SkeletonRanged, 0, 2, 30, 12, 1, 6, 0, 0, 0, 0, 5, MonsterClass::Undead, IMMUNE_MAGIC | RESIST_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 3, 0, 364 }, -/* MT_XSKELBW */ { P_("monster", "Horror"), nullptr, "skelbow\\black", MonsterSpriteId::SkeletonBow, MonsterAvailability::Always, 128, 567, true, false, { 9, 8, 16, 5, 16, 16 }, { 3, 1, 1, 1, 1, 1 }, 4, 6, 9, 15, 45, MonsterAIID::SkeletonRanged, 0, 3, 35, 12, 2, 9, 0, 0, 0, 0, 15, MonsterClass::Undead, IMMUNE_MAGIC | RESIST_LIGHTNING, IMMUNE_MAGIC | RESIST_LIGHTNING, 3, 0, 594 }, -/* MT_WSKELSD */ { P_("monster", "Skeleton Captain"), nullptr, "skelsd\\white", MonsterSpriteId::SkeletonCaptain, MonsterAvailability::Always, 128, 575, true, true, { 13, 8, 12, 7, 15, 16 }, { 4, 1, 1, 1, 1, 1 }, 1, 3, 2, 3, 6, MonsterAIID::SkeletonMelee, 0, 0, 20, 8, 2, 7, 0, 0, 0, 0, 10, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC, 3, 0, 90 }, -/* MT_TSKELSD */ { P_("monster", "Corpse Captain"), nullptr, "skelsd\\skelt", MonsterSpriteId::SkeletonCaptain, MonsterAvailability::Always, 128, 575, true, false, { 13, 8, 12, 7, 15, 16 }, { 4, 1, 1, 1, 1, 1 }, 2, 4, 4, 12, 20, MonsterAIID::SkeletonMelee, 0, 1, 30, 8, 3, 9, 0, 0, 0, 0, 5, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC, 3, 0, 200 }, -/* MT_RSKELSD */ { P_("monster", "Burning Dead Captain"), nullptr, nullptr, MonsterSpriteId::SkeletonCaptain, MonsterAvailability::Always, 128, 575, true, false, { 13, 8, 12, 7, 15, 16 }, { 4, 1, 1, 1, 1, 1 }, 3, 5, 6, 16, 30, MonsterAIID::SkeletonMelee, 0, 2, 35, 8, 4, 10, 0, 0, 0, 0, 15, MonsterClass::Undead, IMMUNE_MAGIC | RESIST_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 3, 0, 393 }, -/* MT_XSKELSD */ { P_("monster", "Horror Captain"), nullptr, "skelsd\\black", MonsterSpriteId::SkeletonCaptain, MonsterAvailability::Always, 128, 575, true, false, { 13, 8, 12, 7, 15, 16 }, { 4, 1, 1, 1, 1, 1 }, 4, 6, 8, 35, 50, MonsterAIID::SkeletonMelee, MFLAG_SEARCH, 3, 40, 8, 5, 14, 0, 0, 0, 0, 30, MonsterClass::Undead, IMMUNE_MAGIC | RESIST_LIGHTNING, IMMUNE_MAGIC | RESIST_LIGHTNING, 3, 0, 604 }, -/* MT_INVILORD*/ { P_("monster", "Invisible Lord"), nullptr, nullptr, MonsterSpriteId::InvisibleLord, MonsterAvailability::Never, 128, 800, false, false, { 13, 13, 15, 11, 16, 0 }, { 2, 1, 1, 1, 1, 1 }, 19, 20, 14, 278, 278, MonsterAIID::SkeletonMelee, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 65, 8, 16, 30, 0, 0, 0, 0, 60, MonsterClass::Demon, RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, 3, 0, 2000 }, -/* MT_SNEAK */ { P_("monster", "Hidden"), nullptr, nullptr, MonsterSpriteId::Hidden, MonsterAvailability::Retail, 128, 992, true, false, { 16, 8, 12, 8, 24, 15 }, { 2, 1, 1, 1, 1, 1 }, 2, 5, 5, 8, 24, MonsterAIID::Sneak, MFLAG_HIDDEN, 0, 35, 8, 3, 6, 0, 0, 0, 0, 25, MonsterClass::Demon, 0, 0, 3, 0, 278 }, -/* MT_STALKER */ { P_("monster", "Stalker"), nullptr, "sneak\\sneakv2", MonsterSpriteId::Hidden, MonsterAvailability::Retail, 128, 992, true, false, { 16, 8, 12, 8, 24, 15 }, { 2, 1, 1, 1, 1, 1 }, 5, 7, 9, 30, 45, MonsterAIID::Sneak, MFLAG_HIDDEN | MFLAG_SEARCH, 1, 40, 8, 8, 16, 0, 0, 0, 0, 30, MonsterClass::Demon, 0, 0, 3, 0, 630 }, -/* MT_UNSEEN */ { P_("monster", "Unseen"), nullptr, "sneak\\sneakv3", MonsterSpriteId::Hidden, MonsterAvailability::Retail, 128, 992, true, false, { 16, 8, 12, 8, 24, 15 }, { 2, 1, 1, 1, 1, 1 }, 6, 8, 11, 35, 50, MonsterAIID::Sneak, MFLAG_HIDDEN | MFLAG_SEARCH, 2, 45, 8, 12, 20, 0, 0, 0, 0, 30, MonsterClass::Demon, RESIST_MAGIC, IMMUNE_MAGIC, 3, 0, 935 }, -/* MT_ILLWEAV */ { P_("monster", "Illusion Weaver"), nullptr, "sneak\\sneakv1", MonsterSpriteId::Hidden, MonsterAvailability::Retail, 128, 992, true, false, { 16, 8, 12, 8, 24, 15 }, { 2, 1, 1, 1, 1, 1 }, 8, 10, 13, 40, 60, MonsterAIID::Sneak, MFLAG_HIDDEN | MFLAG_SEARCH, 3, 60, 8, 16, 24, 0, 0, 0, 0, 30, MonsterClass::Demon, RESIST_MAGIC | RESIST_FIRE, IMMUNE_MAGIC | RESIST_FIRE, 3, 0, 1500 }, -/* MT_LRDSAYTR*/ { P_("monster", "Satyr Lord"), "newsfx\\satyr", nullptr, MonsterSpriteId::GoatLord, MonsterAvailability::Retail, 160, 800, false, false, { 13, 13, 14, 9, 16, 0 }, { 2, 1, 1, 1, 1, 1 }, 21, 22, 28, 160, 200, MonsterAIID::SkeletonMelee, MFLAG_SEARCH, 3, 90, 8, 20, 30, 0, 0, 0, 0, 70, MonsterClass::Animal, RESIST_FIRE | RESIST_LIGHTNING, RESIST_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 3, 0, 2800 }, -/* MT_NGOATMC */ { P_("monster", "Flesh Clan"), nullptr, nullptr, MonsterSpriteId::GoatMace, MonsterAvailability::Retail, 128, 1030, true, false, { 12, 8, 12, 6, 20, 12 }, { 2, 1, 1, 1, 1, 1 }, 4, 6, 8, 30, 45, MonsterAIID::GoatMelee, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 0, 50, 8, 4, 10, 0, 0, 0, 0, 40, MonsterClass::Demon, 0, 0, 3, 0, 460 }, -/* MT_BGOATMC */ { P_("monster", "Stone Clan"), nullptr, "goatmace\\beige", MonsterSpriteId::GoatMace, MonsterAvailability::Retail, 128, 1030, true, false, { 12, 8, 12, 6, 20, 12 }, { 2, 1, 1, 1, 1, 1 }, 5, 7, 10, 40, 55, MonsterAIID::GoatMelee, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 1, 60, 8, 6, 12, 0, 0, 0, 0, 40, MonsterClass::Demon, RESIST_MAGIC, IMMUNE_MAGIC, 3, 0, 685 }, -/* MT_RGOATMC */ { P_("monster", "Fire Clan"), nullptr, "goatmace\\red", MonsterSpriteId::GoatMace, MonsterAvailability::Retail, 128, 1030, true, false, { 12, 8, 12, 6, 20, 12 }, { 2, 1, 1, 1, 1, 1 }, 6, 8, 12, 50, 65, MonsterAIID::GoatMelee, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 2, 70, 8, 8, 16, 0, 0, 0, 0, 45, MonsterClass::Demon, RESIST_FIRE, IMMUNE_FIRE, 3, 0, 906 }, -/* MT_GGOATMC */ { P_("monster", "Night Clan"), nullptr, "goatmace\\gray", MonsterSpriteId::GoatMace, MonsterAvailability::Retail, 128, 1030, true, false, { 12, 8, 12, 6, 20, 12 }, { 2, 1, 1, 1, 1, 1 }, 7, 9, 14, 55, 70, MonsterAIID::GoatMelee, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 80, 8, 10, 20, 15, 0, 30, 30, 50, MonsterClass::Demon, RESIST_MAGIC, IMMUNE_MAGIC, 3, 0, 1190 }, -/* MT_FIEND */ { P_("monster", "Fiend"), nullptr, "bat\\red", MonsterSpriteId::Bat, MonsterAvailability::Always, 96, 364, false, false, { 9, 13, 10, 9, 13, 0 }, { 1, 1, 1, 1, 1, 1 }, 2, 3, 3, 3, 6, MonsterAIID::Bat, 0, 0, 35, 5, 1, 6, 0, 0, 0, 0, 0, MonsterClass::Animal, 0, 0, 6, T_NODROP, 102 }, -/* MT_BLINK */ { P_("monster", "Blink"), nullptr, nullptr, MonsterSpriteId::Bat, MonsterAvailability::Always, 96, 364, false, false, { 9, 13, 10, 9, 13, 0 }, { 1, 1, 1, 1, 1, 1 }, 3, 5, 7, 12, 28, MonsterAIID::Bat, 0, 1, 45, 5, 1, 8, 0, 0, 0, 0, 15, MonsterClass::Animal, 0, 0, 6, T_NODROP, 340 }, -/* MT_GLOOM */ { P_("monster", "Gloom"), nullptr, "bat\\grey", MonsterSpriteId::Bat, MonsterAvailability::Always, 96, 364, false, false, { 9, 13, 10, 9, 13, 0 }, { 1, 1, 1, 1, 1, 1 }, 4, 6, 9, 28, 36, MonsterAIID::Bat, MFLAG_SEARCH, 2, 70, 5, 4, 12, 0, 0, 0, 0, 35, MonsterClass::Animal, RESIST_MAGIC, RESIST_MAGIC, 6, T_NODROP, 509 }, -/* MT_FAMILIAR*/ { P_("monster", "Familiar"), nullptr, "bat\\orange", MonsterSpriteId::Bat, MonsterAvailability::Always, 96, 364, false, false, { 9, 13, 10, 9, 13, 0 }, { 1, 1, 1, 1, 1, 1 }, 6, 8, 13, 20, 35, MonsterAIID::Bat, MFLAG_SEARCH, 3, 50, 5, 4, 16, 0, 0, 0, 0, 35, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_LIGHTNING, RESIST_MAGIC | IMMUNE_LIGHTNING, 6, T_NODROP, 448 }, -/* MT_NGOATBW */ { P_("monster", "Flesh Clan"), nullptr, nullptr, MonsterSpriteId::GoatBow, MonsterAvailability::Retail, 128, 1040, false, false, { 12, 8, 16, 6, 20, 0 }, { 3, 1, 1, 1, 1, 1 }, 4, 6, 8, 20, 35, MonsterAIID::GoatRanged, MFLAG_CAN_OPEN_DOOR, 0, 35, 13, 1, 7, 0, 0, 0, 0, 35, MonsterClass::Demon, 0, 0, 3, 0, 448 }, -/* MT_BGOATBW */ { P_("monster", "Stone Clan"), nullptr, "goatbow\\beige", MonsterSpriteId::GoatBow, MonsterAvailability::Retail, 128, 1040, false, false, { 12, 8, 16, 6, 20, 0 }, { 3, 1, 1, 1, 1, 1 }, 5, 7, 10, 30, 40, MonsterAIID::GoatRanged, MFLAG_CAN_OPEN_DOOR, 1, 40, 13, 2, 9, 0, 0, 0, 0, 35, MonsterClass::Demon, RESIST_MAGIC, IMMUNE_MAGIC, 3, 0, 645 }, -/* MT_RGOATBW */ { P_("monster", "Fire Clan"), nullptr, "goatbow\\red", MonsterSpriteId::GoatBow, MonsterAvailability::Retail, 128, 1040, false, false, { 12, 8, 16, 6, 20, 0 }, { 3, 1, 1, 1, 1, 1 }, 6, 8, 12, 40, 50, MonsterAIID::GoatRanged, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 2, 45, 13, 3, 11, 0, 0, 0, 0, 35, MonsterClass::Demon, RESIST_FIRE, IMMUNE_FIRE, 3, 0, 822 }, -/* MT_GGOATBW */ { P_("monster", "Night Clan"), nullptr, "goatbow\\gray", MonsterSpriteId::GoatBow, MonsterAvailability::Retail, 128, 1040, false, false, { 12, 8, 16, 6, 20, 0 }, { 3, 1, 1, 1, 1, 1 }, 7, 9, 14, 50, 65, MonsterAIID::GoatRanged, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 50, 13, 4, 13, 15, 0, 0, 0, 40, MonsterClass::Demon, RESIST_MAGIC, IMMUNE_MAGIC, 3, 0, 1092 }, -/* MT_NACID */ { P_("monster", "Acid Beast"), nullptr, nullptr, MonsterSpriteId::AcidBeast, MonsterAvailability::Retail, 128, 716, true, true, { 13, 8, 12, 8, 16, 12 }, { 1, 1, 1, 1, 1, 1 }, 6, 8, 11, 40, 66, MonsterAIID::Acid, 0, 0, 40, 8, 4, 12, 25, 8, 0, 0, 30, MonsterClass::Animal, IMMUNE_ACID, IMMUNE_MAGIC | IMMUNE_ACID, 3, 0, 846 }, -/* MT_RACID */ { P_("monster", "Poison Spitter"), nullptr, "acid\\acidblk", MonsterSpriteId::AcidBeast, MonsterAvailability::Retail, 128, 716, true, true, { 13, 8, 12, 8, 16, 12 }, { 1, 1, 1, 1, 1, 1 }, 8, 10, 15, 60, 85, MonsterAIID::Acid, 0, 1, 45, 8, 4, 16, 25, 8, 0, 0, 30, MonsterClass::Animal, IMMUNE_ACID, IMMUNE_MAGIC | IMMUNE_ACID, 3, 0, 1248 }, -/* MT_BACID */ { P_("monster", "Pit Beast"), nullptr, "acid\\acidb", MonsterSpriteId::AcidBeast, MonsterAvailability::Retail, 128, 716, true, true, { 13, 8, 12, 8, 16, 12 }, { 1, 1, 1, 1, 1, 1 }, 10, 12, 21, 80, 110, MonsterAIID::Acid, 0, 2, 55, 8, 8, 18, 35, 8, 0, 0, 35, MonsterClass::Animal, RESIST_MAGIC | IMMUNE_ACID, IMMUNE_MAGIC | RESIST_LIGHTNING | IMMUNE_ACID, 3, 0, 2060 }, -/* MT_XACID */ { P_("monster", "Lava Maw"), nullptr, "acid\\acidr", MonsterSpriteId::AcidBeast, MonsterAvailability::Retail, 128, 716, true, true, { 13, 8, 12, 8, 16, 12 }, { 1, 1, 1, 1, 1, 1 }, 12, 14, 25, 100, 150, MonsterAIID::Acid, 0, 3, 65, 8, 10, 20, 40, 8, 0, 0, 35, MonsterClass::Animal, RESIST_MAGIC | IMMUNE_FIRE | IMMUNE_ACID, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_ACID, 3, 0, 2940 }, -/* MT_SKING */ { P_("monster", "Skeleton King"), nullptr, "skelaxe\\white", MonsterSpriteId::SkeletonKing, MonsterAvailability::Never, 160, 1010, true, true, { 8, 6, 16, 6, 16, 6 }, { 2, 1, 1, 1, 1, 2 }, 4, 4, 9, 140, 140, MonsterAIID::SkeletonKing, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 60, 8, 6, 16, 0, 0, 0, 0, 70, MonsterClass::Undead, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 7, Uniq(UITEM_SKCROWN), 570 }, -/* MT_CLEAVER */ { P_("monster", "The Butcher"), nullptr, nullptr, MonsterSpriteId::Butcher, MonsterAvailability::Never, 128, 980, false, false, { 10, 8, 12, 6, 16, 0 }, { 1, 1, 1, 1, 1, 1 }, 1, 1, 1, 320, 320, MonsterAIID::Butcher, 0, 3, 50, 8, 6, 12, 0, 0, 0, 0, 50, MonsterClass::Demon, RESIST_FIRE | RESIST_LIGHTNING, RESIST_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 3, Uniq(UITEM_CLEAVER), 710 }, -/* MT_FAT */ { P_("monster", "Overlord"), nullptr, nullptr, MonsterSpriteId::Overlord, MonsterAvailability::Retail, 128, 1130, true, false, { 8, 10, 15, 6, 16, 10 }, { 4, 1, 1, 1, 1, 1 }, 5, 7, 10, 60, 80, MonsterAIID::Fat, 0, 0, 55, 8, 6, 12, 0, 0, 0, 0, 55, MonsterClass::Demon, 0, RESIST_FIRE, 3, 0, 635 }, -/* MT_MUDMAN, */ { P_("monster", "Mud Man"), nullptr, "fat\\blue", MonsterSpriteId::Overlord, MonsterAvailability::Retail, 128, 1130, true, false, { 8, 10, 15, 6, 16, 10 }, { 4, 1, 1, 1, 1, 1 }, 7, 9, 14, 100, 125, MonsterAIID::Fat, MFLAG_SEARCH, 1, 60, 8, 8, 16, 0, 0, 0, 0, 60, MonsterClass::Demon, 0, IMMUNE_LIGHTNING, 3, 0, 1165 }, -/* MT_TOAD */ { P_("monster", "Toad Demon"), nullptr, "fat\\fatb", MonsterSpriteId::Overlord, MonsterAvailability::Retail, 128, 1130, true, false, { 8, 10, 15, 6, 16, 10 }, { 4, 1, 1, 1, 1, 1 }, 8, 10, 16, 135, 160, MonsterAIID::Fat, MFLAG_SEARCH, 2, 70, 8, 8, 16, 40, 0, 8, 20, 65, MonsterClass::Demon, IMMUNE_MAGIC, IMMUNE_MAGIC | RESIST_LIGHTNING, 3, 0, 1380 }, -/* MT_FLAYED */ { P_("monster", "Flayed One"), nullptr, "fat\\fatf", MonsterSpriteId::Overlord, MonsterAvailability::Retail, 128, 1130, true, false, { 8, 10, 15, 6, 16, 10 }, { 4, 1, 1, 1, 1, 1 }, 10, 12, 20, 160, 200, MonsterAIID::Fat, MFLAG_SEARCH, 3, 85, 8, 10, 20, 0, 0, 0, 0, 70, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 3, 0, 2058 }, -/* MT_WYRM */ { P_("monster", "Wyrm"), nullptr, nullptr, MonsterSpriteId::Wyrm, MonsterAvailability::Never, 160, 2420, false, false, { 13, 13, 13, 11, 19, 0 }, { 1, 1, 1, 1, 1, 1 }, 5, 7, 11, 60, 90, MonsterAIID::SkeletonMelee, 0, 0, 40, 8, 4, 10, 0, 0, 0, 0, 25, MonsterClass::Animal, RESIST_MAGIC, RESIST_MAGIC, 3, 0, 660 }, -/* MT_CAVSLUG */ { P_("monster", "Cave Slug"), nullptr, nullptr, MonsterSpriteId::Wyrm, MonsterAvailability::Never, 160, 2420, false, false, { 13, 13, 13, 11, 19, 0 }, { 1, 1, 1, 1, 1, 1 }, 6, 8, 13, 75, 110, MonsterAIID::SkeletonMelee, 0, 1, 50, 8, 6, 13, 0, 0, 0, 0, 30, MonsterClass::Animal, RESIST_MAGIC, RESIST_MAGIC, 3, 0, 994 }, -/* MT_DVLWYRM */ { P_("monster", "Devil Wyrm"), nullptr, nullptr, MonsterSpriteId::Wyrm, MonsterAvailability::Never, 160, 2420, false, false, { 13, 13, 13, 11, 19, 0 }, { 1, 1, 1, 1, 1, 1 }, 7, 9, 15, 100, 140, MonsterAIID::SkeletonMelee, 0, 2, 55, 8, 8, 16, 0, 0, 0, 0, 30, MonsterClass::Animal, RESIST_MAGIC | RESIST_FIRE, RESIST_MAGIC | RESIST_FIRE, 3, 0, 1320 }, -/* MT_DEVOUR */ { P_("monster", "Devourer"), nullptr, nullptr, MonsterSpriteId::Wyrm, MonsterAvailability::Never, 160, 2420, false, false, { 13, 13, 13, 11, 19, 0 }, { 1, 1, 1, 1, 1, 1 }, 8, 10, 17, 125, 200, MonsterAIID::SkeletonMelee, 0, 3, 60, 8, 10, 20, 0, 0, 0, 0, 35, MonsterClass::Animal, RESIST_MAGIC | RESIST_FIRE, RESIST_MAGIC | RESIST_FIRE, 3, 0, 1827 }, -/* MT_NMAGMA */ { P_("monster", "Magma Demon"), nullptr, nullptr, MonsterSpriteId::MagmaDemon, MonsterAvailability::Retail, 128, 1680, true, true, { 8, 10, 14, 7, 18, 18 }, { 2, 1, 1, 1, 1, 1 }, 8, 9, 13, 50, 70, MonsterAIID::Magma, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 0, 45, 4, 2, 10, 50, 13, 0, 0, 45, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 7, 0, 1076 }, -/* MT_YMAGMA */ { P_("monster", "Blood Stone"), nullptr, "magma\\yellow", MonsterSpriteId::MagmaDemon, MonsterAvailability::Retail, 128, 1680, true, true, { 8, 10, 14, 7, 18, 18 }, { 2, 1, 1, 1, 1, 1 }, 8, 10, 14, 55, 75, MonsterAIID::Magma, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 1, 50, 4, 2, 12, 50, 14, 0, 0, 45, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 7, 0, 1309 }, -/* MT_BMAGMA */ { P_("monster", "Hell Stone"), nullptr, "magma\\blue", MonsterSpriteId::MagmaDemon, MonsterAvailability::Retail, 128, 1680, true, true, { 8, 10, 14, 7, 18, 18 }, { 2, 1, 1, 1, 1, 1 }, 9, 11, 16, 60, 80, MonsterAIID::Magma, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 2, 60, 4, 2, 20, 60, 14, 0, 0, 50, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 7, 0, 1680 }, -/* MT_WMAGMA */ { P_("monster", "Lava Lord"), nullptr, "magma\\wierd", MonsterSpriteId::MagmaDemon, MonsterAvailability::Retail, 128, 1680, true, true, { 8, 10, 14, 7, 18, 18 }, { 2, 1, 1, 1, 1, 1 }, 9, 11, 18, 70, 85, MonsterAIID::Magma, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 75, 4, 4, 24, 60, 14, 0, 0, 60, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 7, 0, 2124 }, -/* MT_HORNED */ { P_("monster", "Horned Demon"), nullptr, nullptr, MonsterSpriteId::HornedDemon, MonsterAvailability::Retail, 160, 1630, true, true, { 8, 8, 14, 6, 16, 6 }, { 2, 1, 1, 1, 1, 1 }, 7, 9, 13, 40, 80, MonsterAIID::Rhino, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 0, 60, 7, 2, 16, 100, 0, 5, 32, 40, MonsterClass::Animal, 0, RESIST_FIRE, 7, 0, 1172 }, -/* MT_MUDRUN */ { P_("monster", "Mud Runner"), nullptr, "rhino\\orange", MonsterSpriteId::HornedDemon, MonsterAvailability::Retail, 160, 1630, true, true, { 8, 8, 14, 6, 16, 6 }, { 2, 1, 1, 1, 1, 1 }, 8, 10, 15, 50, 90, MonsterAIID::Rhino, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 1, 70, 7, 6, 18, 100, 0, 12, 36, 45, MonsterClass::Animal, 0, RESIST_FIRE, 7, 0, 1404 }, -/* MT_FROSTC */ { P_("monster", "Frost Charger"), nullptr, "rhino\\blue", MonsterSpriteId::HornedDemon, MonsterAvailability::Retail, 160, 1630, true, true, { 8, 8, 14, 6, 16, 6 }, { 2, 1, 1, 1, 1, 1 }, 9, 11, 17, 60, 100, MonsterAIID::Rhino, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 2, 80, 7, 8, 20, 100, 0, 20, 40, 50, MonsterClass::Animal, IMMUNE_MAGIC | RESIST_LIGHTNING, IMMUNE_MAGIC | RESIST_LIGHTNING, 7, 0, 1720 }, -/* MT_OBLORD */ { P_("monster", "Obsidian Lord"), nullptr, "rhino\\rhinob", MonsterSpriteId::HornedDemon, MonsterAvailability::Retail, 160, 1630, true, true, { 8, 8, 14, 6, 16, 6 }, { 2, 1, 1, 1, 1, 1 }, 10, 12, 19, 70, 110, MonsterAIID::Rhino, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 90, 7, 10, 22, 100, 0, 20, 50, 55, MonsterClass::Animal, IMMUNE_MAGIC | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 7, 0, 1809 }, -/* MT_BONEDMN */ { P_("monster", "oldboned"), nullptr, nullptr, MonsterSpriteId::SkeletonDemon, MonsterAvailability::Never, 128, 1740, true, true, { 10, 8, 20, 6, 24, 16 }, { 3, 1, 1, 1, 1, 1 }, 24, 24, 12, 70, 70, MonsterAIID::Storm, 0, 0, 60, 8, 6, 14, 12, 0, 0, 0, 50, MonsterClass::Demon, IMMUNE_MAGIC, IMMUNE_MAGIC, 7, 0, 1344 }, -/* MT_REDDTH */ { P_("monster", "Red Death"), nullptr, "thin\\thinv3", MonsterSpriteId::StormRider, MonsterAvailability::Never, 160, 1740, true, true, { 8, 8, 18, 4, 17, 14 }, { 3, 1, 1, 1, 1, 1 }, 8, 10, 16, 96, 96, MonsterAIID::Storm, 0, 1, 75, 5, 10, 20, 0, 0, 0, 0, 60, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 7, 0, 2168 }, -/* MT_LTCHDMN */ { P_("monster", "Litch Demon"), nullptr, "thin\\thinv3", MonsterSpriteId::StormRider, MonsterAvailability::Never, 160, 1740, true, true, { 8, 8, 18, 4, 17, 14 }, { 3, 1, 1, 1, 1, 1 }, 9, 11, 18, 110, 110, MonsterAIID::Storm, 0, 2, 80, 5, 10, 24, 0, 0, 0, 0, 45, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_LIGHTNING, IMMUNE_MAGIC | IMMUNE_LIGHTNING, 7, 0, 2736 }, -/* MT_UDEDBLRG*/ { P_("monster", "Undead Balrog"), nullptr, "thin\\thinv3", MonsterSpriteId::StormRider, MonsterAvailability::Never, 160, 1740, true, true, { 8, 8, 18, 4, 17, 14 }, { 3, 1, 1, 1, 1, 1 }, 11, 13, 22, 130, 130, MonsterAIID::Storm, 0, 3, 85, 5, 12, 30, 0, 0, 0, 0, 65, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, 7, 0, 3575 }, -/* MT_INCIN */ { P_("monster", "Incinerator"), nullptr, nullptr, MonsterSpriteId::Incinerator, MonsterAvailability::Never, 128, 1460, true, false, { 14, 19, 20, 8, 14, 23 }, { 1, 1, 1, 1, 1, 1 }, 21, 22, 16, 30, 45, MonsterAIID::FireMan, 0, 0, 75, 8, 8, 16, 0, 0, 0, 0, 25, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 3, 0, 1888 }, -/* MT_FLAMLRD */ { P_("monster", "Flame Lord"), nullptr, nullptr, MonsterSpriteId::Incinerator, MonsterAvailability::Never, 128, 1460, true, false, { 14, 19, 20, 8, 14, 23 }, { 1, 1, 1, 1, 1, 1 }, 22, 23, 18, 40, 55, MonsterAIID::FireMan, 0, 1, 75, 8, 10, 20, 0, 0, 0, 0, 25, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 3, 0, 2250 }, -/* MT_DOOMFIRE*/ { P_("monster", "Doom Fire"), nullptr, nullptr, MonsterSpriteId::Incinerator, MonsterAvailability::Never, 128, 1460, true, false, { 14, 19, 20, 8, 14, 23 }, { 1, 1, 1, 1, 1, 1 }, 23, 24, 20, 50, 65, MonsterAIID::FireMan, 0, 2, 80, 8, 12, 24, 0, 0, 0, 0, 30, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 3, 0, 2740 }, -/* MT_HELLBURN*/ { P_("monster", "Hell Burner"), nullptr, nullptr, MonsterSpriteId::Incinerator, MonsterAvailability::Never, 128, 1460, true, false, { 14, 19, 20, 8, 14, 23 }, { 1, 1, 1, 1, 1, 1 }, 24, 24, 22, 60, 80, MonsterAIID::FireMan, 0, 3, 85, 8, 15, 30, 0, 0, 0, 0, 30, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 3, 0, 3355 }, -/* MT_STORM */ { P_("monster", "Red Storm"), nullptr, "thin\\thinv3", MonsterSpriteId::StormRider, MonsterAvailability::Retail, 160, 1740, true, true, { 8, 8, 18, 4, 17, 14 }, { 3, 1, 1, 1, 1, 1 }, 9, 11, 18, 55, 110, MonsterAIID::Storm, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 0, 80, 5, 8, 18, 75, 8, 4, 16, 30, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_LIGHTNING, 7, 0, 2160 }, -/* MT_RSTORM */ { P_("monster", "Storm Rider"), nullptr, nullptr, MonsterSpriteId::StormRider, MonsterAvailability::Retail, 160, 1740, true, true, { 8, 8, 18, 4, 17, 14 }, { 3, 1, 1, 1, 1, 1 }, 10, 12, 20, 60, 120, MonsterAIID::Storm, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 1, 80, 5, 8, 18, 80, 8, 4, 16, 30, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_LIGHTNING, IMMUNE_MAGIC | IMMUNE_LIGHTNING, 7, 0, 2391 }, -/* MT_STORML */ { P_("monster", "Storm Lord"), nullptr, "thin\\thinv2", MonsterSpriteId::StormRider, MonsterAvailability::Retail, 160, 1740, true, true, { 8, 8, 18, 4, 17, 14 }, { 3, 1, 1, 1, 1, 1 }, 11, 13, 22, 75, 135, MonsterAIID::Storm, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 2, 85, 5, 12, 24, 75, 8, 4, 16, 35, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_LIGHTNING, IMMUNE_MAGIC | IMMUNE_LIGHTNING, 7, 0, 2775 }, -/* MT_MAEL */ { P_("monster", "Maelstrom"), nullptr, "thin\\thinv1", MonsterSpriteId::StormRider, MonsterAvailability::Retail, 160, 1740, true, true, { 8, 8, 18, 4, 17, 14 }, { 3, 1, 1, 1, 1, 1 }, 12, 14, 24, 90, 150, MonsterAIID::Storm, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 90, 5, 12, 28, 75, 8, 4, 16, 40, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_LIGHTNING, IMMUNE_MAGIC | IMMUNE_LIGHTNING, 7, 0, 3177 }, -/* MT_BIGFALL */ { P_("monster", "Devil Kin Brute"), "newsfx\\kbrute", nullptr, MonsterSpriteId::DevilKinBrute, MonsterAvailability::Retail, 128, 800, true, false, { 10, 8, 11, 8, 17, 0 }, { 1, 1, 1, 1, 2, 2 }, 21, 22, 27, 120, 160, MonsterAIID::SkeletonMelee, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 100, 6, 18, 24, 0, 0, 0, 0, 70, MonsterClass::Animal, RESIST_FIRE | RESIST_LIGHTNING, RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, 3, 0, 2400 }, -/* MT_WINGED */ { P_("monster", "Winged-Demon"), nullptr, nullptr, MonsterSpriteId::Gargoyle, MonsterAvailability::Retail, 160, 1650, true, false, { 14, 14, 14, 10, 18, 14 }, { 1, 1, 1, 1, 1, 2 }, 5, 7, 9, 45, 60, MonsterAIID::Gargoyle, MFLAG_CAN_OPEN_DOOR, 0, 50, 7, 10, 16, 0, 0, 0, 0, 45, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 6, 0, 662 }, -/* MT_GARGOYLE*/ { P_("monster", "Gargoyle"), nullptr, "gargoyle\\gare", MonsterSpriteId::Gargoyle, MonsterAvailability::Retail, 160, 1650, true, false, { 14, 14, 14, 10, 18, 14 }, { 1, 1, 1, 1, 1, 2 }, 7, 9, 13, 60, 90, MonsterAIID::Gargoyle, MFLAG_CAN_OPEN_DOOR, 1, 65, 7, 10, 16, 0, 0, 0, 0, 45, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_LIGHTNING, 6, 0, 1205 }, -/* MT_BLOODCLW*/ { P_("monster", "Blood Claw"), nullptr, "gargoyle\\gargbr", MonsterSpriteId::Gargoyle, MonsterAvailability::Retail, 160, 1650, true, false, { 14, 14, 14, 10, 18, 14 }, { 1, 1, 1, 1, 1, 1 }, 9, 11, 19, 75, 125, MonsterAIID::Gargoyle, MFLAG_CAN_OPEN_DOOR, 2, 80, 7, 14, 22, 0, 0, 0, 0, 50, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 6, 0, 1873 }, -/* MT_DEATHW */ { P_("monster", "Death Wing"), nullptr, "gargoyle\\gargb", MonsterSpriteId::Gargoyle, MonsterAvailability::Retail, 160, 1650, true, false, { 14, 14, 14, 10, 18, 14 }, { 1, 1, 1, 1, 1, 1 }, 10, 12, 23, 90, 150, MonsterAIID::Gargoyle, MFLAG_CAN_OPEN_DOOR, 3, 95, 7, 16, 28, 0, 0, 0, 0, 60, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_LIGHTNING, IMMUNE_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, 6, 0, 2278 }, -/* MT_MEGA */ { P_("monster", "Slayer"), nullptr, nullptr, MonsterSpriteId::Slayer, MonsterAvailability::Retail, 160, 2220, true, true, { 6, 7, 14, 1, 24, 5 }, { 3, 1, 1, 1, 2, 1 }, 10, 12, 20, 120, 140, MonsterAIID::Mega, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 0, 100, 8, 12, 20, 0, 3, 0, 0, 60, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_FIRE, RESIST_MAGIC | IMMUNE_FIRE, 7, 0, 2300 }, -/* MT_GUARD */ { P_("monster", "Guardian"), nullptr, "mega\\guard", MonsterSpriteId::Slayer, MonsterAvailability::Retail, 160, 2220, true, true, { 6, 7, 14, 1, 24, 5 }, { 3, 1, 1, 1, 2, 1 }, 11, 13, 22, 140, 160, MonsterAIID::Mega, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 1, 110, 8, 14, 22, 0, 3, 0, 0, 65, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_FIRE, RESIST_MAGIC | IMMUNE_FIRE, 7, 0, 2714 }, -/* MT_VTEXLRD */ { P_("monster", "Vortex Lord"), nullptr, "mega\\vtexl", MonsterSpriteId::Slayer, MonsterAvailability::Retail, 160, 2220, true, true, { 6, 7, 14, 1, 24, 5 }, { 3, 1, 1, 1, 2, 1 }, 12, 14, 24, 160, 180, MonsterAIID::Mega, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 2, 120, 8, 18, 24, 0, 3, 0, 0, 70, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_FIRE, RESIST_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 7, 0, 3252 }, -/* MT_BALROG */ { P_("monster", "Balrog"), nullptr, "mega\\balr", MonsterSpriteId::Slayer, MonsterAvailability::Retail, 160, 2220, true, true, { 6, 7, 14, 1, 24, 5 }, { 3, 1, 1, 1, 2, 1 }, 13, 15, 26, 180, 200, MonsterAIID::Mega, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 130, 8, 22, 30, 0, 3, 0, 0, 75, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_FIRE, RESIST_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 7, 0, 3643 }, -/* MT_NSNAKE */ { P_("monster", "Cave Viper"), nullptr, nullptr, MonsterSpriteId::Viper, MonsterAvailability::Retail, 160, 1270, false, false, { 12, 11, 13, 5, 18, 0 }, { 2, 1, 1, 1, 1, 1 }, 11, 13, 21, 100, 150, MonsterAIID::Snake, MFLAG_SEARCH, 0, 90, 8, 8, 20, 0, 0, 0, 0, 60, MonsterClass::Demon, IMMUNE_MAGIC, IMMUNE_MAGIC, 7, 0, 2725 }, -/* MT_RSNAKE */ { P_("monster", "Fire Drake"), nullptr, "snake\\snakr", MonsterSpriteId::Viper, MonsterAvailability::Retail, 160, 1270, false, false, { 12, 11, 13, 5, 18, 0 }, { 2, 1, 1, 1, 1, 1 }, 12, 14, 23, 120, 170, MonsterAIID::Snake, MFLAG_SEARCH, 1, 105, 8, 12, 24, 0, 0, 0, 0, 65, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 7, 0, 3139 }, -/* MT_BSNAKE */ { P_("monster", "Gold Viper"), nullptr, "snake\\snakg", MonsterSpriteId::Viper, MonsterAvailability::Retail, 160, 1270, false, false, { 12, 11, 13, 5, 18, 0 }, { 2, 1, 1, 1, 1, 1 }, 13, 14, 25, 140, 180, MonsterAIID::Snake, MFLAG_SEARCH, 2, 120, 8, 15, 26, 0, 0, 0, 0, 70, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_LIGHTNING, IMMUNE_MAGIC | RESIST_LIGHTNING, 7, 0, 3540 }, -/* MT_GSNAKE */ { P_("monster", "Azure Drake"), nullptr, "snake\\snakb", MonsterSpriteId::Viper, MonsterAvailability::Retail, 160, 1270, false, false, { 12, 11, 13, 5, 18, 0 }, { 2, 1, 1, 1, 1, 1 }, 15, 16, 27, 160, 200, MonsterAIID::Snake, MFLAG_SEARCH, 3, 130, 8, 18, 30, 0, 0, 0, 0, 75, MonsterClass::Demon, RESIST_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, 7, 0, 3791 }, -/* MT_NBLACK */ { P_("monster", "Black Knight"), nullptr, nullptr, MonsterSpriteId::BlackKnight, MonsterAvailability::Retail, 160, 2120, false, false, { 8, 8, 16, 4, 24, 0 }, { 2, 1, 1, 1, 1, 1 }, 12, 14, 24, 150, 150, MonsterAIID::SkeletonMelee, MFLAG_SEARCH, 0, 110, 8, 15, 20, 0, 0, 0, 0, 75, MonsterClass::Demon, RESIST_MAGIC | RESIST_LIGHTNING, RESIST_MAGIC | IMMUNE_LIGHTNING, 7, 0, 3360 }, -/* MT_RTBLACK */ { P_("monster", "Doom Guard"), nullptr, "black\\blkkntrt", MonsterSpriteId::BlackKnight, MonsterAvailability::Retail, 160, 2120, false, false, { 8, 8, 16, 4, 24, 0 }, { 2, 1, 1, 1, 1, 1 }, 13, 15, 26, 165, 165, MonsterAIID::SkeletonMelee, MFLAG_SEARCH, 0, 130, 8, 18, 25, 0, 0, 0, 0, 75, MonsterClass::Demon, RESIST_MAGIC | RESIST_FIRE, RESIST_MAGIC | IMMUNE_FIRE, 7, 0, 3650 }, -/* MT_BTBLACK */ { P_("monster", "Steel Lord"), nullptr, "black\\blkkntbt", MonsterSpriteId::BlackKnight, MonsterAvailability::Retail, 160, 2120, false, false, { 8, 8, 16, 4, 24, 0 }, { 2, 1, 1, 1, 1, 1 }, 14, 16, 28, 180, 180, MonsterAIID::SkeletonMelee, MFLAG_SEARCH, 1, 120, 8, 20, 30, 0, 0, 0, 0, 80, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 7, 0, 4252 }, -/* MT_RBLACK */ { P_("monster", "Blood Knight"), nullptr, "black\\blkkntbe", MonsterSpriteId::BlackKnight, MonsterAvailability::Retail, 160, 2120, false, false, { 8, 8, 16, 4, 24, 0 }, { 2, 1, 1, 1, 1, 1 }, 13, 14, 30, 200, 200, MonsterAIID::SkeletonMelee, MFLAG_SEARCH, 1, 130, 8, 25, 35, 0, 0, 0, 0, 85, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, IMMUNE_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, 7, 0, 5130 }, -/* MT_UNRAV */ { P_("monster", "The Shredded"), "newsfx\\shred", nullptr, MonsterSpriteId::Shredded, MonsterAvailability::Retail, 96, 484, false, false, { 10, 10, 12, 5, 16, 0 }, { 1, 1, 1, 1, 1, 1 }, 17, 18, 23, 70, 90, MonsterAIID::SkeletonMelee, 0, 0, 75, 7, 4, 12, 0, 0, 0, 0, 65, MonsterClass::Undead, RESIST_FIRE | RESIST_LIGHTNING, RESIST_FIRE | RESIST_LIGHTNING, 3, 0, 900 }, -/* MT_HOLOWONE*/ { P_("monster", "Hollow One"), "acid\\acid", nullptr, MonsterSpriteId::Shredded, MonsterAvailability::Never, 96, 484, false, false, { 10, 10, 12, 5, 16, 0 }, { 1, 1, 1, 1, 1, 1 }, 18, 19, 27, 135, 240, MonsterAIID::SkeletonMelee, 0, 1, 75, 7, 12, 24, 0, 0, 0, 0, 75, MonsterClass::Undead, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 3, 0, 4374 }, -/* MT_PAINMSTR*/ { P_("monster", "Pain Master"), "acid\\acid", nullptr, MonsterSpriteId::Shredded, MonsterAvailability::Never, 96, 484, false, false, { 10, 10, 12, 5, 16, 0 }, { 1, 1, 1, 1, 1, 1 }, 19, 20, 29, 110, 200, MonsterAIID::SkeletonMelee, 0, 2, 80, 7, 16, 30, 0, 0, 0, 0, 80, MonsterClass::Undead, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 3, 0, 5147 }, -/* MT_REALWEAV*/ { P_("monster", "Reality Weaver"), "acid\\acid", nullptr, MonsterSpriteId::Shredded, MonsterAvailability::Never, 96, 484, false, false, { 10, 10, 12, 5, 16, 0 }, { 1, 1, 1, 1, 1, 1 }, 20, 20, 30, 135, 240, MonsterAIID::SkeletonMelee, 0, 3, 85, 7, 20, 35, 0, 0, 0, 0, 85, MonsterClass::Undead, RESIST_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, RESIST_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 3, 0, 5925 }, -/* MT_SUCCUBUS*/ { P_("monster", "Succubus"), nullptr, nullptr, MonsterSpriteId::Succubus, MonsterAvailability::Retail, 128, 980, false, false, { 14, 8, 16, 7, 24, 0 }, { 1, 1, 1, 1, 1, 1 }, 12, 14, 24, 120, 150, MonsterAIID::Succubus, MFLAG_CAN_OPEN_DOOR, 0, 100, 10, 1, 20, 0, 0, 0, 0, 60, MonsterClass::Demon, RESIST_MAGIC, IMMUNE_MAGIC | RESIST_FIRE, 3, 0, 3696 }, -/* MT_SNOWWICH*/ { P_("monster", "Snow Witch"), nullptr, "succ\\succb", MonsterSpriteId::Succubus, MonsterAvailability::Retail, 128, 980, false, false, { 14, 8, 16, 7, 24, 0 }, { 1, 1, 1, 1, 1, 1 }, 13, 15, 26, 135, 175, MonsterAIID::Succubus, MFLAG_CAN_OPEN_DOOR, 1, 110, 10, 1, 24, 0, 0, 0, 0, 65, MonsterClass::Demon, RESIST_LIGHTNING, IMMUNE_MAGIC | RESIST_LIGHTNING, 3, 0, 4084 }, -/* MT_HLSPWN */ { P_("monster", "Hell Spawn"), nullptr, "succ\\succrw", MonsterSpriteId::Succubus, MonsterAvailability::Retail, 128, 980, false, false, { 14, 8, 16, 7, 24, 0 }, { 1, 1, 1, 1, 1, 1 }, 14, 16, 28, 150, 200, MonsterAIID::Succubus, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 2, 115, 10, 1, 30, 0, 0, 0, 0, 75, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 3, 0, 4480 }, -/* MT_SOLBRNR */ { P_("monster", "Soul Burner"), nullptr, "succ\\succbw", MonsterSpriteId::Succubus, MonsterAvailability::Retail, 128, 980, false, false, { 14, 8, 16, 7, 24, 0 }, { 1, 1, 1, 1, 1, 1 }, 15, 16, 30, 140, 225, MonsterAIID::Succubus, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 120, 10, 1, 35, 0, 0, 0, 0, 85, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 3, 0, 4644 }, -/* MT_COUNSLR */ { P_("monster", "Counselor"), nullptr, nullptr, MonsterSpriteId::Counselor, MonsterAvailability::Retail, 128, 2000, true, false, { 12, 1, 20, 8, 28, 20 }, { 1, 1, 1, 1, 1, 1 }, 13, 14, 25, 70, 70, MonsterAIID::Counselor, MFLAG_CAN_OPEN_DOOR, 0, 90, 8, 8, 20, 0, 0, 0, 0, 0, MonsterClass::Demon, RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, 7, 0, 4070 }, -/* MT_MAGISTR */ { P_("monster", "Magistrate"), nullptr, "mage\\cnselg", MonsterSpriteId::Counselor, MonsterAvailability::Retail, 128, 2000, true, false, { 12, 1, 20, 8, 28, 20 }, { 1, 1, 1, 1, 1, 1 }, 14, 15, 27, 85, 85, MonsterAIID::Counselor, MFLAG_CAN_OPEN_DOOR, 1, 100, 8, 10, 24, 0, 0, 0, 0, 0, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 7, 0, 4478 }, -/* MT_CABALIST*/ { P_("monster", "Cabalist"), nullptr, "mage\\cnselgd", MonsterSpriteId::Counselor, MonsterAvailability::Retail, 128, 2000, true, false, { 12, 1, 20, 8, 28, 20 }, { 1, 1, 1, 1, 1, 1 }, 15, 16, 29, 120, 120, MonsterAIID::Counselor, MFLAG_CAN_OPEN_DOOR, 2, 110, 8, 14, 30, 0, 0, 0, 0, 0, MonsterClass::Demon, RESIST_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, IMMUNE_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, 7, 0, 4929 }, -/* MT_ADVOCATE*/ { P_("monster", "Advocate"), nullptr, "mage\\cnselbk", MonsterSpriteId::Counselor, MonsterAvailability::Retail, 128, 2000, true, false, { 12, 1, 20, 8, 28, 20 }, { 1, 1, 1, 1, 1, 1 }, 16, 16, 30, 145, 145, MonsterAIID::Counselor, MFLAG_CAN_OPEN_DOOR, 3, 120, 8, 15, 25, 0, 0, 0, 0, 0, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 7, 0, 4968 }, -/* MT_GOLEM */ { P_("monster", "Golem"), "golem\\golm", nullptr, MonsterSpriteId::Golem, MonsterAvailability::Never, 96, 386, true, false, { 0, 16, 12, 0, 12, 20 }, { 1, 1, 1, 1, 1, 1 }, 1, 1, 12, 1, 1, MonsterAIID::Golem, MFLAG_CAN_OPEN_DOOR, 0, 0, 7, 1, 1, 0, 0, 0, 0, 1, MonsterClass::Demon, 0, 0, 0, 0, 0 }, -/* MT_DIABLO */ { P_("monster", "The Dark Lord"), nullptr, nullptr, MonsterSpriteId::Diablo, MonsterAvailability::Never, 160, 2000, true, true, { 16, 6, 16, 6, 16, 16 }, { 1, 1, 1, 1, 1, 1 }, 26, 26, 45, 3333, 3333, MonsterAIID::Diablo, MFLAG_KNOCKBACK | MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 220, 4, 30, 60, 0, 11, 0, 0, 90, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, 7, 0, 31666 }, -/* MT_DARKMAGE*/ { P_("monster", "The Arch-Litch Malignus"), "darkmage\\dmag", nullptr, MonsterSpriteId::ArchLitch, MonsterAvailability::Never, 128, 1060, true, false, { 6, 1, 21, 6, 23, 18 }, { 1, 1, 1, 1, 1, 1 }, 21, 21, 30, 160, 160, MonsterAIID::Counselor, MFLAG_CAN_OPEN_DOOR, 3, 120, 8, 20, 40, 0, 0, 0, 0, 70, MonsterClass::Demon, RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 7, 0, 4968 }, -/* MT_HELLBOAR*/ { P_("monster", "Hellboar"), "newsfx\\hboar", nullptr, MonsterSpriteId::Hellboar, MonsterAvailability::Retail, 188, 800, false, false, { 10, 10, 15, 6, 16, 0 }, { 2, 1, 1, 1, 1, 1 }, 17, 18, 23, 80, 100, MonsterAIID::SkeletonMelee, MFLAG_KNOCKBACK | MFLAG_SEARCH, 2, 70, 7, 16, 24, 0, 0, 0, 0, 60, MonsterClass::Demon, 0, RESIST_FIRE | RESIST_LIGHTNING, 3, 0, 750 }, -/* MT_STINGER */ { P_("monster", "Stinger"), "newsfx\\stingr", nullptr, MonsterSpriteId::Stinger, MonsterAvailability::Retail, 64, 305, false, false, { 10, 10, 12, 6, 15, 0 }, { 2, 1, 1, 1, 1, 1 }, 17, 18, 22, 30, 40, MonsterAIID::SkeletonMelee, 0, 3, 85, 8, 1, 20, 0, 0, 0, 0, 50, MonsterClass::Animal, 0, RESIST_LIGHTNING, 1, 0, 500 }, -/* MT_PSYCHORB*/ { P_("monster", "Psychorb"), "newsfx\\psyco", nullptr, MonsterSpriteId::Psychorb, MonsterAvailability::Retail, 156, 800, false, false, { 12, 13, 13, 7, 21, 0 }, { 2, 1, 1, 1, 1, 1 }, 17, 18, 22, 20, 30, MonsterAIID::Psychorb, 0, 3, 80, 8, 10, 10, 0, 0, 0, 0, 40, MonsterClass::Animal, 0, RESIST_FIRE, 6, 0, 450 }, -/* MT_ARACHNON*/ { P_("monster", "Arachnon"), "newsfx\\slord", nullptr, MonsterSpriteId::Arachnon, MonsterAvailability::Retail, 148, 800, false, false, { 12, 10, 15, 6, 20, 0 }, { 2, 1, 1, 1, 1, 1 }, 17, 18, 22, 60, 80, MonsterAIID::SkeletonMelee, MFLAG_SEARCH, 3, 50, 8, 5, 15, 0, 0, 0, 0, 50, MonsterClass::Animal, 0, RESIST_LIGHTNING, 7, 0, 500 }, -/* MT_FELLTWIN*/ { P_("monster", "Felltwin"), "newsfx\\ftwin", nullptr, MonsterSpriteId::InvisibleLord, MonsterAvailability::Retail, 128, 800, false, false, { 13, 13, 15, 11, 16, 0 }, { 2, 1, 1, 1, 1, 1 }, 17, 18, 22, 50, 70, MonsterAIID::SkeletonMelee, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 70, 8, 10, 18, 0, 0, 0, 0, 50, MonsterClass::Demon, 0, RESIST_FIRE | RESIST_LIGHTNING, 3, 0, 600 }, -/* MT_HORKSPWN*/ { P_("monster", "Hork Spawn"), "newsfx\\hspawn", nullptr, MonsterSpriteId::HorkSpawn, MonsterAvailability::Retail, 164, 520, false, true, { 15, 12, 14, 11, 14, 0 }, { 1, 1, 1, 1, 1, 1 }, 18, 19, 22, 30, 30, MonsterAIID::SkeletonMelee, 0, 3, 60, 8, 10, 25, 0, 0, 0, 0, 25, MonsterClass::Demon, RESIST_MAGIC, RESIST_MAGIC, 3, 0, 250 }, -/* MT_VENMTAIL*/ { P_("monster", "Venomtail"), "newsfx\\stingr", nullptr, MonsterSpriteId::Venomtail, MonsterAvailability::Retail, 86, 305, false, false, { 10, 10, 12, 6, 15, 0 }, { 2, 1, 1, 1, 1, 1 }, 19, 20, 24, 40, 50, MonsterAIID::SkeletonMelee, 0, 3, 85, 8, 1, 30, 0, 0, 0, 0, 60, MonsterClass::Animal, RESIST_LIGHTNING, IMMUNE_LIGHTNING, 1, 0, 1000 }, -/* MT_NECRMORB*/ { P_("monster", "Necromorb"), "newsfx\\psyco", nullptr, MonsterSpriteId::Necromorb, MonsterAvailability::Retail, 140, 800, false, false, { 12, 13, 13, 7, 21, 0 }, { 2, 1, 1, 1, 1, 1 }, 19, 20, 24, 30, 40, MonsterAIID::Necromorb, 0, 3, 80, 8, 20, 20, 0, 0, 0, 0, 50, MonsterClass::Animal, RESIST_FIRE, IMMUNE_FIRE | RESIST_LIGHTNING, 6, 0, 1100 }, -/* MT_SPIDLORD*/ { P_("monster", "Spider Lord"), "newsfx\\slord", nullptr, MonsterSpriteId::SpiderLord, MonsterAvailability::Retail, 148, 800, true, true, { 12, 10, 15, 6, 20, 10 }, { 2, 1, 1, 1, 1, 1 }, 19, 20, 24, 80, 100, MonsterAIID::Acid, MFLAG_SEARCH, 3, 60, 8, 8, 20, 75, 8, 10, 10, 60, MonsterClass::Animal, RESIST_LIGHTNING, RESIST_FIRE | IMMUNE_LIGHTNING, 7, 0, 1250 }, -/* MT_LASHWORM*/ { P_("monster", "Lashworm"), "newsfx\\lworm", nullptr, MonsterSpriteId::Lashworm, MonsterAvailability::Retail, 176, 800, false, false, { 10, 12, 15, 6, 16, 0 }, { 1, 1, 1, 1, 1, 1 }, 19, 20, 20, 30, 30, MonsterAIID::SkeletonMelee, 0, 3, 90, 8, 12, 20, 0, 0, 0, 0, 50, MonsterClass::Animal, 0, RESIST_FIRE, 3, 0, 600 }, -/* MT_TORCHANT*/ { P_("monster", "Torchant"), "newsfx\\tchant", nullptr, MonsterSpriteId::Torchant, MonsterAvailability::Retail, 192, 800, false, false, { 14, 12, 12, 6, 20, 0 }, { 2, 1, 1, 1, 1, 1 }, 19, 20, 22, 60, 80, MonsterAIID::Torchant, 0, 3, 75, 8, 20, 30, 0, 0, 0, 0, 70, MonsterClass::Animal, IMMUNE_FIRE, RESIST_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 7, 0, 1250 }, -/* MT_HORKDMN */ { P_("monster", "Hork Demon"), "newsfx\\hdemon", nullptr, MonsterSpriteId::HorkDemon, MonsterAvailability::Never, 138, 800, true, true, { 15, 8, 16, 6, 16, 9 }, { 2, 1, 1, 1, 1, 2 }, 19, 19, 27, 120, 160, MonsterAIID::SkeletonMelee, 0, 3, 60, 8, 20, 35, 80, 8, 0, 0, 80, MonsterClass::Demon, RESIST_LIGHTNING, RESIST_MAGIC | IMMUNE_LIGHTNING, 7, 0, 2000 }, -/* MT_DEFILER */ { P_("monster", "Hell Bug"), "newsfx\\defile", nullptr, MonsterSpriteId::HellBug, MonsterAvailability::Never, 198, 800, true, true, { 8, 8, 14, 6, 14, 12 }, { 1, 1, 1, 1, 1, 1 }, 20, 20, 30, 240, 240, MonsterAIID::SkeletonMelee, MFLAG_SEARCH, 3, 110, 8, 20, 30, 90, 8, 50, 60, 80, MonsterClass::Demon, RESIST_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, RESIST_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 7, 0, 5000 }, -/* MT_GRAVEDIG*/ { P_("monster", "Gravedigger"), "newsfx\\gdiggr", nullptr, MonsterSpriteId::Gravedigger, MonsterAvailability::Retail, 124, 800, true, true, { 24, 24, 12, 6, 16, 16 }, { 2, 1, 1, 1, 1, 1 }, 21, 21, 26, 120, 240, MonsterAIID::Scavenger, MFLAG_CAN_OPEN_DOOR, 3, 80, 6, 2, 12, 0, 0, 0, 0, 20, MonsterClass::Undead, IMMUNE_LIGHTNING, RESIST_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, 3, 0, 2000 }, -/* MT_TOMBRAT */ { P_("monster", "Tomb Rat"), "newsfx\\tmbrat", nullptr, MonsterSpriteId::Rat, MonsterAvailability::Retail, 104, 550, false, false, { 11, 8, 12, 6, 20, 0 }, { 2, 1, 1, 1, 1, 1 }, 21, 22, 24, 80, 120, MonsterAIID::SkeletonMelee, 0, 3, 120, 8, 12, 25, 0, 0, 0, 0, 30, MonsterClass::Animal, 0, RESIST_FIRE | RESIST_LIGHTNING, 3, 0, 1800 }, -/* MT_FIREBAT */ { P_("monster", "Firebat"), "newsfx\\helbat", nullptr, MonsterSpriteId::Firebat, MonsterAvailability::Retail, 96, 550, false, false, { 18, 16, 14, 6, 18, 11 }, { 2, 1, 1, 1, 1, 1 }, 21, 22, 24, 60, 80, MonsterAIID::FireBat, 0, 3, 100, 8, 15, 20, 0, 0, 0, 0, 70, MonsterClass::Animal, IMMUNE_FIRE, RESIST_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 7, 0, 2400 }, -/* MT_SKLWING */ { P_("monster", "Skullwing"), "newsfx\\swing", "skelaxe\\skelt", MonsterSpriteId::SkeletonDemon, MonsterAvailability::Retail, 128, 1740, true, false, { 10, 8, 20, 6, 24, 16 }, { 3, 1, 1, 1, 1, 1 }, 21, 22, 27, 70, 70, MonsterAIID::SkeletonMelee, 0, 0, 75, 7, 15, 20, 75, 9, 15, 20, 80, MonsterClass::Undead, RESIST_FIRE | RESIST_LIGHTNING, RESIST_FIRE | RESIST_LIGHTNING, 7, 0, 3000 }, -/* MT_LICH */ { P_("monster", "Lich"), "newsfx\\lich", nullptr, MonsterSpriteId::Lich, MonsterAvailability::Retail, 96, 800, false, true, { 12, 10, 10, 7, 21, 0 }, { 2, 1, 1, 1, 2, 1 }, 21, 22, 25, 80, 100, MonsterAIID::Lich, 0, 3, 100, 8, 15, 20, 0, 0, 0, 0, 60, MonsterClass::Undead, RESIST_LIGHTNING, RESIST_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, 3, 0, 3000 }, -/* MT_CRYPTDMN*/ { P_("monster", "Crypt Demon"), "newsfx\\crypt", nullptr, MonsterSpriteId::CryptDemon, MonsterAvailability::Retail, 154, 800, false, true, { 8, 18, 12, 8, 21, 0 }, { 3, 1, 1, 1, 1, 1 }, 22, 23, 28, 200, 240, MonsterAIID::SkeletonMelee, 0, 3, 100, 8, 20, 40, 0, 0, 0, 0, 85, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 3, 0, 3200 }, -/* MT_HELLBAT */ { P_("monster", "Hellbat"), "newsfx\\helbat", nullptr, MonsterSpriteId::Hellbat, MonsterAvailability::Retail, 96, 550, true, false, { 18, 16, 14, 6, 18, 11 }, { 2, 1, 1, 1, 1, 1 }, 23, 24, 29, 100, 140, MonsterAIID::Torchant, 0, 3, 110, 8, 30, 30, 0, 0, 0, 0, 80, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, RESIST_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 7, 0, 3600 }, -/* MT_BONEDEMN*/ { P_("monster", "Bone Demon"), "newsfx\\swing", nullptr, MonsterSpriteId::SkeletonDemon, MonsterAvailability::Retail, 128, 1740, true, true, { 10, 8, 20, 6, 24, 16 }, { 3, 1, 1, 1, 1, 1 }, 23, 24, 30, 240, 280, MonsterAIID::BoneDemon, 0, 0, 100, 8, 40, 50, 160, 12, 50, 50, 50, MonsterClass::Undead, IMMUNE_FIRE | IMMUNE_LIGHTNING, IMMUNE_FIRE | IMMUNE_LIGHTNING, 7, 0, 5000 }, -/* MT_ARCHLICH*/ { P_("monster", "Arch Lich"), "newsfx\\lich", nullptr, MonsterSpriteId::ArchLich, MonsterAvailability::Retail, 136, 800, false, true, { 12, 10, 10, 7, 21, 0 }, { 2, 1, 1, 1, 2, 1 }, 23, 24, 30, 180, 200, MonsterAIID::ArchLich, 0, 3, 120, 8, 30, 30, 0, 0, 0, 0, 75, MonsterClass::Undead, RESIST_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 3, 0, 4000 }, -/* MT_BICLOPS */ { P_("monster", "Biclops"), "newsfx\\biclop", nullptr, MonsterSpriteId::Biclops, MonsterAvailability::Retail, 180, 800, false, false, { 10, 11, 16, 6, 16, 0 }, { 2, 1, 1, 1, 2, 1 }, 23, 24, 30, 200, 240, MonsterAIID::SkeletonMelee, MFLAG_KNOCKBACK | MFLAG_CAN_OPEN_DOOR, 3, 90, 8, 40, 50, 0, 0, 0, 0, 80, MonsterClass::Demon, RESIST_LIGHTNING, RESIST_FIRE | RESIST_LIGHTNING, 3, 0, 4000 }, -/* MT_FLESTHNG*/ { P_("monster", "Flesh Thing"), "newsfx\\flesht", nullptr, MonsterSpriteId::FleshThing, MonsterAvailability::Retail, 164, 800, false, true, { 15, 24, 15, 6, 16, 0 }, { 1, 1, 1, 1, 1, 1 }, 23, 24, 28, 300, 400, MonsterAIID::SkeletonMelee, 0, 3, 150, 8, 12, 18, 0, 0, 0, 0, 70, MonsterClass::Demon, RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, 3, 0, 4000 }, -/* MT_REAPER */ { P_("monster", "Reaper"), "newsfx\\reaper", nullptr, MonsterSpriteId::Reaper, MonsterAvailability::Retail, 180, 800, false, false, { 12, 10, 14, 6, 16, 0 }, { 2, 1, 1, 1, 1, 1 }, 23, 24, 30, 260, 300, MonsterAIID::SkeletonMelee, 0, 3, 120, 8, 30, 35, 0, 0, 0, 0, 90, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 3, 0, 6000 }, -// TRANSLATORS: Monster Block end -/* MT_NAKRUL */ { P_("monster", "Na-Krul"), "newsfx\\nakrul", nullptr, MonsterSpriteId::NaKrul, MonsterAvailability::Never, 226, 1200, true, true, { 2, 6, 16, 3, 16, 16 }, { 1, 1, 1, 1, 1, 1 }, 31, 31, 40, 1332, 1332, MonsterAIID::SkeletonMelee, MFLAG_KNOCKBACK | MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 150, 7, 40, 50, 150, 10, 40, 50, 125, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 7, 0, 13333 }, - // clang-format on -}; +std::vector MonstersData; /** * Map between .DUN file value and monster type enum @@ -400,6 +201,420 @@ const _monster_id MonstConvTbl[] = { MT_LRDSAYTR, }; +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; + 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; + 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; + 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; + 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; + return tl::make_unexpected("Unknown enum value"); +} + +void LoadMonsterData() +{ + const std::string_view filename = "txtdata\\monsters\\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); + } + + MonstersData.clear(); + std::unordered_map spritePathToId; + for (DataFileRecord record : dataFile) { + FieldIterator fieldIt = record.begin(); + const FieldIterator endField = record.end(); + + MonstersData.emplace_back(); + MonsterData &monster = MonstersData.back(); + + const auto advance = [&]() { + ++fieldIt; + if (fieldIt == endField) { + DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename); + } + }; + + // Skip the first column (monster ID). + + // name + advance(); + monster.name = (*fieldIt).value(); + + // assetsSuffix + advance(); + { + std::string assetsSuffix { (*fieldIt).value() }; + const auto [it, inserted] = spritePathToId.emplace(assetsSuffix, spritePathToId.size()); + if (inserted) + MonsterSpritePaths.push_back(it->first); + monster.spriteId = it->second; + } + + // soundSuffix + advance(); + monster.soundSuffix = (*fieldIt).value(); + + // trnFile + advance(); + monster.trnFile = (*fieldIt).value(); + + // availability + advance(); + if (tl::expected result = ParseMonsterAvailability((*fieldIt).value()); result.has_value()) { + monster.availability = *std::move(result); + } else { + DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "availability", *fieldIt, result.error()); + } + + // width + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.width); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "width", *fieldIt); + } + + // image + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.image); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "image", *fieldIt); + } + + // hasSpecial + advance(); + if (tl::expected result = (*fieldIt).parseBool(monster.hasSpecial); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "hasSpecial", *fieldIt); + } + + // hasSpecialSound + advance(); + if (tl::expected result = (*fieldIt).parseBool(monster.hasSpecialSound); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "hasSpecialSound", *fieldIt); + } + + // frames[6] + advance(); + if (tl::expected result = (*fieldIt).parseIntArray(monster.frames); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "frames", *fieldIt); + } + + // rate[6] + advance(); + if (tl::expected result = (*fieldIt).parseIntArray(monster.rate); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "rate", *fieldIt); + } + + // minDunLvl + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.minDunLvl); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "minDunLvl", *fieldIt); + } + + // maxDunLvl + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.maxDunLvl); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "maxDunLvl", *fieldIt); + } + + // level + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.level); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "level", *fieldIt); + } + + // hitPointsMinimum + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.hitPointsMinimum); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "hitPointsMinimum", *fieldIt); + } + + // hitPointsMaximum + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.hitPointsMaximum); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "hitPointsMaximum", *fieldIt); + } + + // ai + advance(); + if (tl::expected result = ParseAiId((*fieldIt).value()); result.has_value()) { + monster.ai = *std::move(result); + } else { + DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "ai", *fieldIt, result.error()); + } + + // abilityFlags + advance(); + if (tl::expected result = (*fieldIt).parseEnumList(monster.abilityFlags, ParseMonsterFlag); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "abilityFlags", *fieldIt); + } + + // intelligence + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.intelligence); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "intelligence", *fieldIt); + } + + // toHit + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.toHit); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "toHit", *fieldIt); + } + + // animFrameNum + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.animFrameNum); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "animFrameNum", *fieldIt); + } + + // minDamage + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.minDamage); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "minDamage", *fieldIt); + } + + // maxDamage + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.maxDamage); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "maxDamage", *fieldIt); + } + + // toHitSpecial + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.toHitSpecial); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "toHitSpecial", *fieldIt); + } + + // animFrameNumSpecial + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.animFrameNumSpecial); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "animFrameNumSpecial", *fieldIt); + } + + // minDamageSpecial + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.minDamageSpecial); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "minDamageSpecial", *fieldIt); + } + + // maxDamageSpecial + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.maxDamageSpecial); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "maxDamageSpecial", *fieldIt); + } + + // armorClass + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.armorClass); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "armorClass", *fieldIt); + } + + // monsterClass + advance(); + if (tl::expected result = ParseMonsterClass((*fieldIt).value()); result.has_value()) { + monster.monsterClass = *std::move(result); + } else { + DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "monsterClass", *fieldIt, result.error()); + } + + // resistance + advance(); + if (tl::expected result = (*fieldIt).parseEnumList(monster.resistance, ParseMonsterResistance); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "resistance", *fieldIt); + } + + // resistanceHell + advance(); + if (tl::expected result = (*fieldIt).parseEnumList(monster.resistanceHell, ParseMonsterResistance); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "resistanceHell", *fieldIt); + } + + // selectionType + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.selectionType); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "selectionType", *fieldIt); + } + + // treasure + // TODO: Replace this hack with proper parsing once unique monsters have been migrated. + advance(); + { + const std::string_view value = (*fieldIt).value(); + if (value.empty()) { + monster.treasure = 0; + } else if (value == "None") { + monster.treasure = T_NODROP; + } else if (value == "Uniq(SKCROWN)") { + monster.treasure = Uniq(UITEM_SKCROWN); + } else if (value == "Uniq(CLEAVER)") { + monster.treasure = Uniq(UITEM_CLEAVER); + } else { + DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "treasure", *fieldIt, "NOTE: Parser is incomplete"); + } + } + + // exp + advance(); + if (tl::expected result = (*fieldIt).parseInt(monster.exp); !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "exp", *fieldIt); + } + } +} + +size_t GetNumMonsterSprites() +{ + return MonsterSpritePaths.size(); +} + /** Contains the data related to each unique monster ID. */ const UniqueMonsterData UniqueMonstersData[] = { // clang-format off diff --git a/Source/monstdat.h b/Source/monstdat.h index 2906703b1..9834e7a1f 100644 --- a/Source/monstdat.h +++ b/Source/monstdat.h @@ -87,73 +87,11 @@ enum class MonsterAvailability : uint8_t { Retail, }; -enum class MonsterSpriteId : uint8_t { - Zombie = 0, // "zombie\\zombie" - FallenSpear, // "falspear\\phall" - FallenSword, // "falsword\\fall" - SkeletonAxe, // "skelaxe\\sklax" - SkeletonBow, // "skelbow\\sklbw" - SkeletonCaptain, // "skelsd\\sklsr" - SkeletonDemon, // "demskel\\demskl" - SkeletonKing, // "sking\\sking" - Scavenger, // "scav\\scav" - InvisibleLord, // "tsneak\\tsneak" - Hidden, // "sneak\\sneak" - GoatMace, // "goatmace\\goat" - GoatBow, // "goatbow\\goatb" - GoatLord, // "goatlord\\goatl" - Bat, // "bat\\bat" - AcidBeast, // "acid\\acid" - Overlord, // "fat\\fat" - Butcher, // "fatc\\fatc" - Wyrm, // "worm\\worm" - MagmaDemon, // "magma\\magma" - HornedDemon, // "rhino\\rhino" - StormRider, // "thin\\thin" - Incinerator, // "fireman\\firem" - DevilKinBrute, // "bigfall\\fallg" - Gargoyle, // "gargoyle\\gargo" - Slayer, // "mega\\mega" - Viper, // "snake\\snake" - BlackKnight, // "black\\black" - Shredded, // "unrav\\unrav" - Succubus, // "succ\\scbs" - Counselor, // "mage\\mage" - Golem, // "golem\\golem" - Diablo, // "diablo\\diablo" - ArchLitch, // "darkmage\\dmage" - Hellboar, // "fork\\fork" - Stinger, // "scorp\\scorp" - Psychorb, // "eye\\eye" - Arachnon, // "spider\\spider" - HorkSpawn, // "spawn\\spawn" - Venomtail, // "wscorp\\wscorp" - Necromorb, // "eye2\\eye2" - SpiderLord, // "bspidr\\bspidr" - Lashworm, // "clasp\\clasp" - Torchant, // "antworm\\worm" - HorkDemon, // "horkd\\horkd" - HellBug, // "hellbug\\hellbg" - Gravedigger, // "gravdg\\gravdg" - Rat, // "rat\\rat" - Firebat, // "hellbat\\helbat" - Lich, // "lich\\lich" - CryptDemon, // "bubba\\bubba" - Hellbat, // "hellbat2\\bhelbt" - ArchLich, // "lich2\\lich2" - Biclops, // "byclps\\byclps" - FleshThing, // "flesh\\flesh" - Reaper, // "reaper\\reap" - NaKrul, // "nkr\\nkr" - FIRST = Zombie, - LAST = NaKrul -}; - struct MonsterData { - const char *name; - const char *soundSuffix; - const char *trnFile; - MonsterSpriteId spriteId; + std::string name; + std::string soundSuffix; + std::string trnFile; + uint16_t spriteId; MonsterAvailability availability; uint16_t width; uint16_t image; @@ -196,7 +134,7 @@ struct MonsterData { [[nodiscard]] const char *soundPath() const { - return soundSuffix != nullptr ? soundSuffix : spritePath(); + return !soundSuffix.empty() ? soundSuffix.c_str() : spritePath(); } [[nodiscard]] bool hasAnim(size_t index) const @@ -388,8 +326,17 @@ struct UniqueMonsterData { _speech_id mtalkmsg; }; -extern const MonsterData MonstersData[]; +extern std::vector MonstersData; extern const _monster_id MonstConvTbl[]; extern const UniqueMonsterData UniqueMonstersData[]; +void LoadMonsterData(); + +/** + * @brief Returns the number of the monster sprite files. + * + * Different monsters can use the same sprite with different TRNs, these count as 1. + */ +size_t GetNumMonsterSprites(); + } // namespace devilution diff --git a/Source/monster.cpp b/Source/monster.cpp index 9b5630ff6..e9e99f6ea 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -3425,7 +3425,7 @@ void InitMonsterGFX(CMonster &monsterType, MonsterSpritesData &&spritesData) ++j; } - if (monsterData.trnFile != nullptr) { + if (!monsterData.trnFile.empty()) { InitMonsterTRN(monsterType); } @@ -3478,7 +3478,7 @@ void InitAllMonsterGFX() return; using LevelMonsterTypeIndices = StaticVector; - std::array::value> monstersBySprite; + std::vector monstersBySprite(GetNumMonsterSprites()); for (size_t i = 0; i < LevelMonsterTypeCount; ++i) { monstersBySprite[static_cast(LevelMonsterTypes[i].data().spriteId)].emplace_back(i); } diff --git a/Source/translation_dummy.cpp b/Source/translation_dummy.cpp new file mode 100644 index 000000000..a0c14d10b --- /dev/null +++ b/Source/translation_dummy.cpp @@ -0,0 +1,146 @@ +/** + * @file translation_dummy.cpp + * + * Do not edit this file manually, it is automatically generated + * and updated by the extract_translation_data.py script. + */ +#include "utils/language.h + +const char *MT_NZOMBIE_NAME = P_("monster", "Zombie"); +const char *MT_BZOMBIE_NAME = P_("monster", "Ghoul"); +const char *MT_GZOMBIE_NAME = P_("monster", "Rotting Carcass"); +const char *MT_YZOMBIE_NAME = P_("monster", "Black Death"); +const char *MT_RFALLSP_NAME = P_("monster", "Fallen One"); +const char *MT_DFALLSP_NAME = P_("monster", "Carver"); +const char *MT_YFALLSP_NAME = P_("monster", "Devil Kin"); +const char *MT_BFALLSP_NAME = P_("monster", "Dark One"); +const char *MT_WSKELAX_NAME = P_("monster", "Skeleton"); +const char *MT_TSKELAX_NAME = P_("monster", "Corpse Axe"); +const char *MT_RSKELAX_NAME = P_("monster", "Burning Dead"); +const char *MT_XSKELAX_NAME = P_("monster", "Horror"); +const char *MT_RFALLSD_NAME = P_("monster", "Fallen One"); +const char *MT_DFALLSD_NAME = P_("monster", "Carver"); +const char *MT_YFALLSD_NAME = P_("monster", "Devil Kin"); +const char *MT_BFALLSD_NAME = P_("monster", "Dark One"); +const char *MT_NSCAV_NAME = P_("monster", "Scavenger"); +const char *MT_BSCAV_NAME = P_("monster", "Plague Eater"); +const char *MT_WSCAV_NAME = P_("monster", "Shadow Beast"); +const char *MT_YSCAV_NAME = P_("monster", "Bone Gasher"); +const char *MT_WSKELBW_NAME = P_("monster", "Skeleton"); +const char *MT_TSKELBW_NAME = P_("monster", "Corpse Bow"); +const char *MT_RSKELBW_NAME = P_("monster", "Burning Dead"); +const char *MT_XSKELBW_NAME = P_("monster", "Horror"); +const char *MT_WSKELSD_NAME = P_("monster", "Skeleton Captain"); +const char *MT_TSKELSD_NAME = P_("monster", "Corpse Captain"); +const char *MT_RSKELSD_NAME = P_("monster", "Burning Dead Captain"); +const char *MT_XSKELSD_NAME = P_("monster", "Horror Captain"); +const char *MT_INVILORD_NAME = P_("monster", "Invisible Lord"); +const char *MT_SNEAK_NAME = P_("monster", "Hidden"); +const char *MT_STALKER_NAME = P_("monster", "Stalker"); +const char *MT_UNSEEN_NAME = P_("monster", "Unseen"); +const char *MT_ILLWEAV_NAME = P_("monster", "Illusion Weaver"); +const char *MT_LRDSAYTR_NAME = P_("monster", "Satyr Lord"); +const char *MT_NGOATMC_NAME = P_("monster", "Flesh Clan"); +const char *MT_BGOATMC_NAME = P_("monster", "Stone Clan"); +const char *MT_RGOATMC_NAME = P_("monster", "Fire Clan"); +const char *MT_GGOATMC_NAME = P_("monster", "Night Clan"); +const char *MT_FIEND_NAME = P_("monster", "Fiend"); +const char *MT_BLINK_NAME = P_("monster", "Blink"); +const char *MT_GLOOM_NAME = P_("monster", "Gloom"); +const char *MT_FAMILIAR_NAME = P_("monster", "Familiar"); +const char *MT_NGOATBW_NAME = P_("monster", "Flesh Clan"); +const char *MT_BGOATBW_NAME = P_("monster", "Stone Clan"); +const char *MT_RGOATBW_NAME = P_("monster", "Fire Clan"); +const char *MT_GGOATBW_NAME = P_("monster", "Night Clan"); +const char *MT_NACID_NAME = P_("monster", "Acid Beast"); +const char *MT_RACID_NAME = P_("monster", "Poison Spitter"); +const char *MT_BACID_NAME = P_("monster", "Pit Beast"); +const char *MT_XACID_NAME = P_("monster", "Lava Maw"); +const char *MT_SKING_NAME = P_("monster", "Skeleton King"); +const char *MT_CLEAVER_NAME = P_("monster", "The Butcher"); +const char *MT_FAT_NAME = P_("monster", "Overlord"); +const char *MT_MUDMAN_NAME = P_("monster", "Mud Man"); +const char *MT_TOAD_NAME = P_("monster", "Toad Demon"); +const char *MT_FLAYED_NAME = P_("monster", "Flayed One"); +const char *MT_WYRM_NAME = P_("monster", "Wyrm"); +const char *MT_CAVSLUG_NAME = P_("monster", "Cave Slug"); +const char *MT_DVLWYRM_NAME = P_("monster", "Devil Wyrm"); +const char *MT_DEVOUR_NAME = P_("monster", "Devourer"); +const char *MT_NMAGMA_NAME = P_("monster", "Magma Demon"); +const char *MT_YMAGMA_NAME = P_("monster", "Blood Stone"); +const char *MT_BMAGMA_NAME = P_("monster", "Hell Stone"); +const char *MT_WMAGMA_NAME = P_("monster", "Lava Lord"); +const char *MT_HORNED_NAME = P_("monster", "Horned Demon"); +const char *MT_MUDRUN_NAME = P_("monster", "Mud Runner"); +const char *MT_FROSTC_NAME = P_("monster", "Frost Charger"); +const char *MT_OBLORD_NAME = P_("monster", "Obsidian Lord"); +const char *MT_BONEDMN_NAME = P_("monster", "oldboned"); +const char *MT_REDDTH_NAME = P_("monster", "Red Death"); +const char *MT_LTCHDMN_NAME = P_("monster", "Litch Demon"); +const char *MT_UDEDBLRG_NAME = P_("monster", "Undead Balrog"); +const char *MT_INCIN_NAME = P_("monster", "Incinerator"); +const char *MT_FLAMLRD_NAME = P_("monster", "Flame Lord"); +const char *MT_DOOMFIRE_NAME = P_("monster", "Doom Fire"); +const char *MT_HELLBURN_NAME = P_("monster", "Hell Burner"); +const char *MT_STORM_NAME = P_("monster", "Red Storm"); +const char *MT_RSTORM_NAME = P_("monster", "Storm Rider"); +const char *MT_STORML_NAME = P_("monster", "Storm Lord"); +const char *MT_MAEL_NAME = P_("monster", "Maelstrom"); +const char *MT_BIGFALL_NAME = P_("monster", "Devil Kin Brute"); +const char *MT_WINGED_NAME = P_("monster", "Winged-Demon"); +const char *MT_GARGOYLE_NAME = P_("monster", "Gargoyle"); +const char *MT_BLOODCLW_NAME = P_("monster", "Blood Claw"); +const char *MT_DEATHW_NAME = P_("monster", "Death Wing"); +const char *MT_MEGA_NAME = P_("monster", "Slayer"); +const char *MT_GUARD_NAME = P_("monster", "Guardian"); +const char *MT_VTEXLRD_NAME = P_("monster", "Vortex Lord"); +const char *MT_BALROG_NAME = P_("monster", "Balrog"); +const char *MT_NSNAKE_NAME = P_("monster", "Cave Viper"); +const char *MT_RSNAKE_NAME = P_("monster", "Fire Drake"); +const char *MT_BSNAKE_NAME = P_("monster", "Gold Viper"); +const char *MT_GSNAKE_NAME = P_("monster", "Azure Drake"); +const char *MT_NBLACK_NAME = P_("monster", "Black Knight"); +const char *MT_RTBLACK_NAME = P_("monster", "Doom Guard"); +const char *MT_BTBLACK_NAME = P_("monster", "Steel Lord"); +const char *MT_RBLACK_NAME = P_("monster", "Blood Knight"); +const char *MT_UNRAV_NAME = P_("monster", "The Shredded"); +const char *MT_HOLOWONE_NAME = P_("monster", "Hollow One"); +const char *MT_PAINMSTR_NAME = P_("monster", "Pain Master"); +const char *MT_REALWEAV_NAME = P_("monster", "Reality Weaver"); +const char *MT_SUCCUBUS_NAME = P_("monster", "Succubus"); +const char *MT_SNOWWICH_NAME = P_("monster", "Snow Witch"); +const char *MT_HLSPWN_NAME = P_("monster", "Hell Spawn"); +const char *MT_SOLBRNR_NAME = P_("monster", "Soul Burner"); +const char *MT_COUNSLR_NAME = P_("monster", "Counselor"); +const char *MT_MAGISTR_NAME = P_("monster", "Magistrate"); +const char *MT_CABALIST_NAME = P_("monster", "Cabalist"); +const char *MT_ADVOCATE_NAME = P_("monster", "Advocate"); +const char *MT_GOLEM_NAME = P_("monster", "Golem"); +const char *MT_DIABLO_NAME = P_("monster", "The Dark Lord"); +const char *MT_DARKMAGE_NAME = P_("monster", "The Arch-Litch Malignus"); +const char *MT_HELLBOAR_NAME = P_("monster", "Hellboar"); +const char *MT_STINGER_NAME = P_("monster", "Stinger"); +const char *MT_PSYCHORB_NAME = P_("monster", "Psychorb"); +const char *MT_ARACHNON_NAME = P_("monster", "Arachnon"); +const char *MT_FELLTWIN_NAME = P_("monster", "Felltwin"); +const char *MT_HORKSPWN_NAME = P_("monster", "Hork Spawn"); +const char *MT_VENMTAIL_NAME = P_("monster", "Venomtail"); +const char *MT_NECRMORB_NAME = P_("monster", "Necromorb"); +const char *MT_SPIDLORD_NAME = P_("monster", "Spider Lord"); +const char *MT_LASHWORM_NAME = P_("monster", "Lashworm"); +const char *MT_TORCHANT_NAME = P_("monster", "Torchant"); +const char *MT_HORKDMN_NAME = P_("monster", "Hork Demon"); +const char *MT_DEFILER_NAME = P_("monster", "Hell Bug"); +const char *MT_GRAVEDIG_NAME = P_("monster", "Gravedigger"); +const char *MT_TOMBRAT_NAME = P_("monster", "Tomb Rat"); +const char *MT_FIREBAT_NAME = P_("monster", "Firebat"); +const char *MT_SKLWING_NAME = P_("monster", "Skullwing"); +const char *MT_LICH_NAME = P_("monster", "Lich"); +const char *MT_CRYPTDMN_NAME = P_("monster", "Crypt Demon"); +const char *MT_HELLBAT_NAME = P_("monster", "Hellbat"); +const char *MT_BONEDEMN_NAME = P_("monster", "Bone Demon"); +const char *MT_ARCHLICH_NAME = P_("monster", "Arch Lich"); +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"); diff --git a/test/pack_test.cpp b/test/pack_test.cpp index 432efca6d..6271a2bc5 100644 --- a/test/pack_test.cpp +++ b/test/pack_test.cpp @@ -2,6 +2,7 @@ #include +#include "monstdat.h" #include "pack.h" #include "playerdat.hpp" #include "utils/paths.h" @@ -943,6 +944,7 @@ public: SwapLE(testPack); LoadPlayerDataFiles(); + LoadMonsterData(); UnPackPlayer(testPack, *MyPlayer); } }; diff --git a/test/timedemo_test.cpp b/test/timedemo_test.cpp index 49084ee49..9762e5c73 100644 --- a/test/timedemo_test.cpp +++ b/test/timedemo_test.cpp @@ -5,6 +5,7 @@ #include "diablo.h" #include "engine/demomode.h" #include "lua/lua.hpp" +#include "monstdat.h" #include "options.h" #include "pfile.h" #include "playerdat.hpp" @@ -52,6 +53,7 @@ void RunTimedemo(std::string timedemoFolderName) demo::InitPlayBack(demoNumber, true); LoadPlayerDataFiles(); + LoadMonsterData(); pfile_ui_set_hero_infos(Dummy_GetHeroInfo); gbLoadGame = true; diff --git a/tools/extract_translation_data.py b/tools/extract_translation_data.py new file mode 100755 index 000000000..f20493fc8 --- /dev/null +++ b/tools/extract_translation_data.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +import csv +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") + +with open(translation_dummy_path, 'w') as temp_source: + temp_source.write(f'/**\n') + temp_source.write(f' * @file translation_dummy.cpp\n') + temp_source.write(f' *\n') + temp_source.write(f' * Do not edit this file manually, it is automatically generated\n') + temp_source.write(f' * and updated by the extract_translation_data.py script.\n') + temp_source.write(f' */\n') + temp_source.write(f'#include "utils/language.h\n') + temp_source.write(f'\n') + 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')