You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
728 lines
45 KiB
728 lines
45 KiB
/** |
|
* @file monstdat.cpp |
|
* |
|
* Implementation of all monster data. |
|
*/ |
|
#include "monstdat.h" |
|
|
|
#include <cstdint> |
|
#include <unordered_map> |
|
|
|
#include <expected.hpp> |
|
|
|
#include "data/file.hpp" |
|
#include "items.h" |
|
#include "monster.h" |
|
#include "textdat.h" |
|
#include "utils/language.h" |
|
|
|
namespace devilution { |
|
|
|
namespace { |
|
|
|
// Returns a `treasure` value for the given item. |
|
constexpr uint16_t Uniq(_unique_items item) |
|
{ |
|
return static_cast<uint16_t>(T_UNIQ) + item; |
|
} |
|
|
|
std::vector<std::string> MonsterSpritePaths; |
|
|
|
} // namespace |
|
|
|
const char *MonsterData::spritePath() const |
|
{ |
|
return MonsterSpritePaths[static_cast<size_t>(spriteId)].c_str(); |
|
} |
|
|
|
/** Contains the data related to each monster ID. */ |
|
std::vector<MonsterData> MonstersData; |
|
|
|
/** |
|
* Map between .DUN file value and monster type enum |
|
*/ |
|
const _monster_id MonstConvTbl[] = { |
|
MT_NZOMBIE, |
|
MT_BZOMBIE, |
|
MT_GZOMBIE, |
|
MT_YZOMBIE, |
|
MT_RFALLSP, |
|
MT_DFALLSP, |
|
MT_YFALLSP, |
|
MT_BFALLSP, |
|
MT_WSKELAX, |
|
MT_TSKELAX, |
|
MT_RSKELAX, |
|
MT_XSKELAX, |
|
MT_RFALLSD, |
|
MT_DFALLSD, |
|
MT_YFALLSD, |
|
MT_BFALLSD, |
|
MT_NSCAV, |
|
MT_BSCAV, |
|
MT_WSCAV, |
|
MT_YSCAV, |
|
MT_WSKELBW, |
|
MT_TSKELBW, |
|
MT_RSKELBW, |
|
MT_XSKELBW, |
|
MT_WSKELSD, |
|
MT_TSKELSD, |
|
MT_RSKELSD, |
|
MT_XSKELSD, |
|
MT_SNEAK, |
|
MT_STALKER, |
|
MT_UNSEEN, |
|
MT_ILLWEAV, |
|
MT_NGOATMC, |
|
MT_BGOATMC, |
|
MT_RGOATMC, |
|
MT_GGOATMC, |
|
MT_FIEND, |
|
MT_GLOOM, |
|
MT_BLINK, |
|
MT_FAMILIAR, |
|
MT_NGOATBW, |
|
MT_BGOATBW, |
|
MT_RGOATBW, |
|
MT_GGOATBW, |
|
MT_NACID, |
|
MT_RACID, |
|
MT_BACID, |
|
MT_XACID, |
|
MT_SKING, |
|
MT_FAT, |
|
MT_MUDMAN, |
|
MT_TOAD, |
|
MT_FLAYED, |
|
MT_WYRM, |
|
MT_CAVSLUG, |
|
MT_DEVOUR, |
|
MT_DVLWYRM, |
|
MT_NMAGMA, |
|
MT_YMAGMA, |
|
MT_BMAGMA, |
|
MT_WMAGMA, |
|
MT_HORNED, |
|
MT_MUDRUN, |
|
MT_FROSTC, |
|
MT_OBLORD, |
|
MT_BONEDMN, |
|
MT_REDDTH, |
|
MT_LTCHDMN, |
|
MT_UDEDBLRG, |
|
MT_INVALID, |
|
MT_INVALID, |
|
MT_INVALID, |
|
MT_INVALID, |
|
MT_INCIN, |
|
MT_FLAMLRD, |
|
MT_DOOMFIRE, |
|
MT_HELLBURN, |
|
MT_INVALID, |
|
MT_INVALID, |
|
MT_INVALID, |
|
MT_INVALID, |
|
MT_RSTORM, |
|
MT_STORM, |
|
MT_STORML, |
|
MT_MAEL, |
|
MT_WINGED, |
|
MT_GARGOYLE, |
|
MT_BLOODCLW, |
|
MT_DEATHW, |
|
MT_MEGA, |
|
MT_GUARD, |
|
MT_VTEXLRD, |
|
MT_BALROG, |
|
MT_NSNAKE, |
|
MT_RSNAKE, |
|
MT_GSNAKE, |
|
MT_BSNAKE, |
|
MT_NBLACK, |
|
MT_RTBLACK, |
|
MT_BTBLACK, |
|
MT_RBLACK, |
|
MT_UNRAV, |
|
MT_HOLOWONE, |
|
MT_PAINMSTR, |
|
MT_REALWEAV, |
|
MT_SUCCUBUS, |
|
MT_SNOWWICH, |
|
MT_HLSPWN, |
|
MT_SOLBRNR, |
|
MT_COUNSLR, |
|
MT_MAGISTR, |
|
MT_CABALIST, |
|
MT_ADVOCATE, |
|
MT_INVALID, |
|
MT_DIABLO, |
|
MT_INVALID, |
|
MT_GOLEM, |
|
MT_INVALID, |
|
MT_INVALID, |
|
MT_INVALID, // Monster from blood1.dun and blood2.dun |
|
MT_INVALID, |
|
MT_INVALID, |
|
MT_INVALID, |
|
MT_INVALID, // Snotspill from banner2.dun |
|
MT_INVALID, |
|
MT_INVALID, |
|
MT_BIGFALL, |
|
MT_DARKMAGE, |
|
MT_HELLBOAR, |
|
MT_STINGER, |
|
MT_PSYCHORB, |
|
MT_ARACHNON, |
|
MT_FELLTWIN, |
|
MT_HORKSPWN, |
|
MT_VENMTAIL, |
|
MT_NECRMORB, |
|
MT_SPIDLORD, |
|
MT_LASHWORM, |
|
MT_TORCHANT, |
|
MT_HORKDMN, |
|
MT_DEFILER, |
|
MT_GRAVEDIG, |
|
MT_TOMBRAT, |
|
MT_FIREBAT, |
|
MT_SKLWING, |
|
MT_LICH, |
|
MT_CRYPTDMN, |
|
MT_HELLBAT, |
|
MT_BONEDEMN, |
|
MT_LICH, |
|
MT_BICLOPS, |
|
MT_FLESTHNG, |
|
MT_REAPER, |
|
MT_NAKRUL, |
|
MT_CLEAVER, |
|
MT_INVILORD, |
|
MT_LRDSAYTR, |
|
}; |
|
|
|
tl::expected<MonsterAvailability, std::string> 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<MonsterAIID, std::string> 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<monster_flag, std::string> 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<MonsterClass, std::string> 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<monster_resistance, std::string> 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<DataFile, DataFile::Error> dataFileResult = DataFile::load(filename); |
|
if (!dataFileResult.has_value()) { |
|
DataFile::reportFatalError(dataFileResult.error(), filename); |
|
} |
|
|
|
DataFile &dataFile = dataFileResult.value(); |
|
if (tl::expected<void, DataFile::Error> result = dataFile.skipHeader(); |
|
!result.has_value()) { |
|
DataFile::reportFatalError(result.error(), filename); |
|
} |
|
|
|
MonstersData.clear(); |
|
std::unordered_map<std::string, size_t> 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<MonsterAvailability, std::string> 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<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.width); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "width", *fieldIt); |
|
} |
|
|
|
// image |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.image); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "image", *fieldIt); |
|
} |
|
|
|
// hasSpecial |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseBool(monster.hasSpecial); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "hasSpecial", *fieldIt); |
|
} |
|
|
|
// hasSpecialSound |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseBool(monster.hasSpecialSound); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "hasSpecialSound", *fieldIt); |
|
} |
|
|
|
// frames[6] |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseIntArray(monster.frames); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "frames", *fieldIt); |
|
} |
|
|
|
// rate[6] |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseIntArray(monster.rate); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "rate", *fieldIt); |
|
} |
|
|
|
// minDunLvl |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.minDunLvl); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "minDunLvl", *fieldIt); |
|
} |
|
|
|
// maxDunLvl |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.maxDunLvl); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "maxDunLvl", *fieldIt); |
|
} |
|
|
|
// level |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.level); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "level", *fieldIt); |
|
} |
|
|
|
// hitPointsMinimum |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.hitPointsMinimum); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "hitPointsMinimum", *fieldIt); |
|
} |
|
|
|
// hitPointsMaximum |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.hitPointsMaximum); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "hitPointsMaximum", *fieldIt); |
|
} |
|
|
|
// ai |
|
advance(); |
|
if (tl::expected<MonsterAIID, std::string> result = ParseAiId((*fieldIt).value()); result.has_value()) { |
|
monster.ai = *std::move(result); |
|
} else { |
|
DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "ai", *fieldIt, result.error()); |
|
} |
|
|
|
// abilityFlags |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseEnumList(monster.abilityFlags, ParseMonsterFlag); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "abilityFlags", *fieldIt); |
|
} |
|
|
|
// intelligence |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.intelligence); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "intelligence", *fieldIt); |
|
} |
|
|
|
// toHit |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.toHit); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "toHit", *fieldIt); |
|
} |
|
|
|
// animFrameNum |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.animFrameNum); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "animFrameNum", *fieldIt); |
|
} |
|
|
|
// minDamage |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.minDamage); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "minDamage", *fieldIt); |
|
} |
|
|
|
// maxDamage |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.maxDamage); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "maxDamage", *fieldIt); |
|
} |
|
|
|
// toHitSpecial |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.toHitSpecial); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "toHitSpecial", *fieldIt); |
|
} |
|
|
|
// animFrameNumSpecial |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.animFrameNumSpecial); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "animFrameNumSpecial", *fieldIt); |
|
} |
|
|
|
// minDamageSpecial |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.minDamageSpecial); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "minDamageSpecial", *fieldIt); |
|
} |
|
|
|
// maxDamageSpecial |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.maxDamageSpecial); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "maxDamageSpecial", *fieldIt); |
|
} |
|
|
|
// armorClass |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.armorClass); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "armorClass", *fieldIt); |
|
} |
|
|
|
// monsterClass |
|
advance(); |
|
if (tl::expected<MonsterClass, std::string> result = ParseMonsterClass((*fieldIt).value()); result.has_value()) { |
|
monster.monsterClass = *std::move(result); |
|
} else { |
|
DataFile::reportFatalFieldError(DataFileField::Error::InvalidValue, filename, "monsterClass", *fieldIt, result.error()); |
|
} |
|
|
|
// resistance |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseEnumList(monster.resistance, ParseMonsterResistance); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "resistance", *fieldIt); |
|
} |
|
|
|
// resistanceHell |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseEnumList(monster.resistanceHell, ParseMonsterResistance); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "resistanceHell", *fieldIt); |
|
} |
|
|
|
// selectionType |
|
advance(); |
|
if (tl::expected<void, DataFileField::Error> result = (*fieldIt).parseInt(monster.selectionType); !result.has_value()) { |
|
DataFile::reportFatalFieldError(result.error(), filename, "selectionType", *fieldIt); |
|
} |
|
|
|
// treasure |
|
// 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<void, DataFileField::Error> 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 |
|
// mtype, mName, mTrnName, mlevel, mmaxhp, mAi, mint, mMinDamage, mMaxDamage, mMagicRes, monsterPack, customToHit, customArmorClass, mtalkmsg |
|
// TRANSLATORS: Unique Monster Block start |
|
{ MT_NGOATMC, P_("monster", "Gharbad the Weak"), "bsdb", 4, 120, MonsterAIID::Gharbad, 3, 8, 16, IMMUNE_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_GARBUD1 }, |
|
{ MT_SKING, P_("monster", "Skeleton King"), "genrl", 0, 240, MonsterAIID::SkeletonKing, 3, 6, 16, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Independent, 0, 0, TEXT_NONE }, |
|
{ MT_COUNSLR, P_("monster", "Zhar the Mad"), "general", 8, 360, MonsterAIID::Zhar, 3, 16, 40, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_ZHAR1 }, |
|
{ MT_BFALLSP, P_("monster", "Snotspill"), "bng", 4, 220, MonsterAIID::Snotspill, 3, 10, 18, RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_BANNER10 }, |
|
{ MT_ADVOCATE, P_("monster", "Arch-Bishop Lazarus"), "general", 0, 600, MonsterAIID::Lazarus, 3, 30, 50, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_VILE13 }, |
|
{ MT_HLSPWN, P_("monster", "Red Vex"), "redv", 0, 400, MonsterAIID::LazarusSuccubus, 3, 30, 50, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_VILE13 }, |
|
{ MT_HLSPWN, P_("monster", "Black Jade"), "blkjd", 0, 400, MonsterAIID::LazarusSuccubus, 3, 30, 50, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_VILE13 }, |
|
{ MT_RBLACK, P_("monster", "Lachdanan"), "bhka", 14, 500, MonsterAIID::Lachdanan, 3, 0, 0, 0, UniqueMonsterPack::None, 0, 0, TEXT_VEIL9 }, |
|
{ MT_BTBLACK, P_("monster", "Warlord of Blood"), "general", 13, 850, MonsterAIID::Warlord, 3, 35, 50, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_WARLRD9 }, |
|
{ MT_CLEAVER, P_("monster", "The Butcher"), "genrl", 0, 220, MonsterAIID::Butcher, 3, 6, 12, RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_HORKDMN, P_("monster", "Hork Demon"), "genrl", 19, 300, MonsterAIID::HorkDemon, 3, 20, 35, RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_DEFILER, P_("monster", "The Defiler"), "genrl", 20, 480, MonsterAIID::SkeletonMelee, 3, 30, 40, RESIST_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_NAKRUL, P_("monster", "Na-Krul"), "genrl", 0, 1332, MonsterAIID::SkeletonMelee, 3, 40, 50, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_TSKELAX, P_("monster", "Bonehead Keenaxe"), "bhka", 2, 91, MonsterAIID::SkeletonMelee, 2, 4, 10, IMMUNE_MAGIC, UniqueMonsterPack::Leashed, 100, 0, TEXT_NONE }, |
|
{ MT_RFALLSD, P_("monster", "Bladeskin the Slasher"), "bsts", 2, 51, MonsterAIID::Fallen, 0, 6, 18, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 45, TEXT_NONE }, |
|
{ MT_NZOMBIE, P_("monster", "Soulpus"), "general", 2, 133, MonsterAIID::Zombie, 0, 4, 8, RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_RFALLSP, P_("monster", "Pukerat the Unclean"), "ptu", 2, 77, MonsterAIID::Fallen, 3, 1, 5, RESIST_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_WSKELAX, P_("monster", "Boneripper"), "br", 2, 54, MonsterAIID::Bat, 0, 6, 15, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_NZOMBIE, P_("monster", "Rotfeast the Hungry"), "eth", 2, 85, MonsterAIID::SkeletonMelee, 3, 4, 12, IMMUNE_MAGIC, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_DFALLSD, P_("monster", "Gutshank the Quick"), "gtq", 3, 66, MonsterAIID::Bat, 2, 6, 16, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_TSKELSD, P_("monster", "Brokenhead Bangshield"), "bhbs", 3, 108, MonsterAIID::SkeletonMelee, 3, 12, 20, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_YFALLSP, P_("monster", "Bongo"), "bng", 3, 178, MonsterAIID::Fallen, 3, 9, 21, 0, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_BZOMBIE, P_("monster", "Rotcarnage"), "rcrn", 3, 102, MonsterAIID::Zombie, 3, 9, 24, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 45, TEXT_NONE }, |
|
{ MT_NSCAV, P_("monster", "Shadowbite"), "shbt", 2, 60, MonsterAIID::SkeletonMelee, 3, 3, 20, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_WSKELBW, P_("monster", "Deadeye"), "de", 2, 49, MonsterAIID::GoatRanged, 0, 6, 9, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_RSKELAX, P_("monster", "Madeye the Dead"), "mtd", 4, 75, MonsterAIID::Bat, 0, 9, 21, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 30, TEXT_NONE }, |
|
{ MT_BSCAV, P_("monster", "El Chupacabras"), "general", 3, 120, MonsterAIID::GoatMelee, 0, 10, 18, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_TSKELBW, P_("monster", "Skullfire"), "skfr", 3, 125, MonsterAIID::GoatRanged, 1, 6, 10, IMMUNE_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_SNEAK, P_("monster", "Warpskull"), "tspo", 3, 117, MonsterAIID::Sneak, 2, 6, 18, RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_GZOMBIE, P_("monster", "Goretongue"), "pmr", 3, 156, MonsterAIID::SkeletonMelee, 1, 15, 30, IMMUNE_MAGIC, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_WSCAV, P_("monster", "Pulsecrawler"), "bhka", 4, 150, MonsterAIID::Scavenger, 0, 16, 20, IMMUNE_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 45, TEXT_NONE }, |
|
{ MT_BLINK, P_("monster", "Moonbender"), "general", 4, 135, MonsterAIID::Bat, 0, 9, 27, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_BLINK, P_("monster", "Wrathraven"), "general", 5, 135, MonsterAIID::Bat, 2, 9, 22, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_YSCAV, P_("monster", "Spineeater"), "general", 4, 180, MonsterAIID::Scavenger, 1, 18, 25, IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_RSKELBW, P_("monster", "Blackash the Burning"), "bashtb", 4, 120, MonsterAIID::GoatRanged, 0, 6, 16, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_BFALLSD, P_("monster", "Shadowcrow"), "general", 5, 270, MonsterAIID::Sneak, 2, 12, 25, 0, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_LRDSAYTR, P_("monster", "Blightstone the Weak"), "bhka", 4, 360, MonsterAIID::SkeletonMelee, 0, 4, 12, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 70, 0, TEXT_NONE }, |
|
{ MT_FAT, P_("monster", "Bilefroth the Pit Master"), "bftp", 6, 210, MonsterAIID::Bat, 1, 16, 23, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_NGOATBW, P_("monster", "Bloodskin Darkbow"), "bsdb", 5, 207, MonsterAIID::GoatRanged, 0, 3, 16, RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 55, TEXT_NONE }, |
|
{ MT_GLOOM, P_("monster", "Foulwing"), "db", 5, 246, MonsterAIID::Rhino, 3, 12, 28, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_XSKELSD, P_("monster", "Shadowdrinker"), "shdr", 5, 300, MonsterAIID::Sneak, 1, 18, 26, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 45, TEXT_NONE }, |
|
{ MT_UNSEEN, P_("monster", "Hazeshifter"), "bhka", 5, 285, MonsterAIID::Sneak, 3, 18, 30, IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_NACID, P_("monster", "Deathspit"), "bfds", 6, 303, MonsterAIID::AcidUnique, 0, 12, 32, RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_RGOATMC, P_("monster", "Bloodgutter"), "bgbl", 6, 315, MonsterAIID::Bat, 1, 24, 34, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_BGOATMC, P_("monster", "Deathshade Fleshmaul"), "dsfm", 6, 276, MonsterAIID::Rhino, 0, 12, 24, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::None, 0, 65, TEXT_NONE }, |
|
{ MT_WYRM, P_("monster", "Warmaggot the Mad"), "general", 6, 246, MonsterAIID::Bat, 3, 15, 30, RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_STORM, P_("monster", "Glasskull the Jagged"), "bhka", 7, 354, MonsterAIID::Storm, 0, 18, 30, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_RGOATBW, P_("monster", "Blightfire"), "blf", 7, 321, MonsterAIID::Succubus, 2, 13, 21, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_GARGOYLE, P_("monster", "Nightwing the Cold"), "general", 7, 342, MonsterAIID::Bat, 1, 18, 26, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_GGOATBW, P_("monster", "Gorestone"), "general", 7, 303, MonsterAIID::GoatRanged, 1, 15, 28, RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 70, 0, TEXT_NONE }, |
|
{ MT_BMAGMA, P_("monster", "Bronzefist Firestone"), "general", 8, 360, MonsterAIID::Magma, 0, 30, 36, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_INCIN, P_("monster", "Wrathfire the Doomed"), "wftd", 8, 270, MonsterAIID::SkeletonMelee, 2, 20, 30, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_NMAGMA, P_("monster", "Firewound the Grim"), "bhka", 8, 303, MonsterAIID::Magma, 0, 18, 22, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_MUDMAN, P_("monster", "Baron Sludge"), "bsm", 8, 315, MonsterAIID::Sneak, 3, 25, 34, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 75, TEXT_NONE }, |
|
{ MT_GGOATMC, P_("monster", "Blighthorn Steelmace"), "bhsm", 7, 250, MonsterAIID::Rhino, 0, 20, 28, RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 45, TEXT_NONE }, |
|
{ MT_RACID, P_("monster", "Chaoshowler"), "general", 8, 240, MonsterAIID::AcidUnique, 0, 12, 20, 0, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_REDDTH, P_("monster", "Doomgrin the Rotting"), "general", 8, 405, MonsterAIID::Storm, 3, 25, 50, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_FLAMLRD, P_("monster", "Madburner"), "general", 9, 270, MonsterAIID::Storm, 0, 20, 40, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_LTCHDMN, P_("monster", "Bonesaw the Litch"), "general", 9, 495, MonsterAIID::Storm, 2, 30, 55, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_MUDRUN, P_("monster", "Breakspine"), "general", 9, 351, MonsterAIID::Rhino, 0, 25, 34, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_REDDTH, P_("monster", "Devilskull Sharpbone"), "general", 9, 444, MonsterAIID::Storm, 1, 25, 40, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_STORM, P_("monster", "Brokenstorm"), "general", 9, 411, MonsterAIID::Storm, 2, 25, 36, IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_RSTORM, P_("monster", "Stormbane"), "general", 9, 555, MonsterAIID::Storm, 3, 30, 30, IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_TOAD, P_("monster", "Oozedrool"), "general", 9, 483, MonsterAIID::Fat, 3, 25, 30, RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_BLOODCLW, P_("monster", "Goldblight of the Flame"), "general", 10, 405, MonsterAIID::Gargoyle, 0, 15, 35, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 80, TEXT_NONE }, |
|
{ MT_OBLORD, P_("monster", "Blackstorm"), "general", 10, 525, MonsterAIID::Rhino, 3, 20, 40, IMMUNE_MAGIC | IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 90, TEXT_NONE }, |
|
{ MT_RACID, P_("monster", "Plaguewrath"), "general", 10, 450, MonsterAIID::AcidUnique, 2, 20, 30, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_RSTORM, P_("monster", "The Flayer"), "general", 10, 501, MonsterAIID::Storm, 1, 20, 35, RESIST_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_FROSTC, P_("monster", "Bluehorn"), "general", 11, 477, MonsterAIID::Rhino, 1, 25, 30, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 90, TEXT_NONE }, |
|
{ MT_HELLBURN, P_("monster", "Warpfire Hellspawn"), "general", 11, 525, MonsterAIID::FireMan, 3, 10, 40, RESIST_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_NSNAKE, P_("monster", "Fangspeir"), "general", 11, 444, MonsterAIID::SkeletonMelee, 1, 15, 32, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_UDEDBLRG, P_("monster", "Festerskull"), "general", 11, 600, MonsterAIID::Storm, 2, 15, 30, IMMUNE_MAGIC, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_NBLACK, P_("monster", "Lionskull the Bent"), "general", 12, 525, MonsterAIID::SkeletonMelee, 2, 25, 25, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_COUNSLR, P_("monster", "Blacktongue"), "general", 12, 360, MonsterAIID::Counselor, 3, 15, 30, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_DEATHW, P_("monster", "Viletouch"), "general", 12, 525, MonsterAIID::Gargoyle, 3, 20, 40, IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_RSNAKE, P_("monster", "Viperflame"), "general", 12, 570, MonsterAIID::SkeletonMelee, 1, 25, 35, IMMUNE_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_BSNAKE, P_("monster", "Fangskin"), "bhka", 14, 681, MonsterAIID::SkeletonMelee, 2, 15, 50, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_SUCCUBUS, P_("monster", "Witchfire the Unholy"), "general", 12, 444, MonsterAIID::Succubus, 3, 10, 20, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_BALROG, P_("monster", "Blackskull"), "bhka", 13, 750, MonsterAIID::SkeletonMelee, 3, 25, 40, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_UNRAV, P_("monster", "Soulslash"), "general", 12, 450, MonsterAIID::SkeletonMelee, 0, 25, 25, IMMUNE_MAGIC, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_VTEXLRD, P_("monster", "Windspawn"), "general", 12, 711, MonsterAIID::SkeletonMelee, 1, 35, 40, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_GSNAKE, P_("monster", "Lord of the Pit"), "general", 13, 762, MonsterAIID::SkeletonMelee, 2, 25, 42, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_RTBLACK, P_("monster", "Rustweaver"), "general", 13, 400, MonsterAIID::SkeletonMelee, 3, 1, 60, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_HOLOWONE, P_("monster", "Howlingire the Shade"), "general", 13, 450, MonsterAIID::SkeletonMelee, 2, 40, 75, RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_MAEL, P_("monster", "Doomcloud"), "general", 13, 612, MonsterAIID::Storm, 1, 1, 60, RESIST_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_PAINMSTR, P_("monster", "Bloodmoon Soulfire"), "general", 13, 684, MonsterAIID::SkeletonMelee, 1, 15, 40, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_SNOWWICH, P_("monster", "Witchmoon"), "general", 13, 310, MonsterAIID::Succubus, 3, 30, 40, RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_VTEXLRD, P_("monster", "Gorefeast"), "general", 13, 771, MonsterAIID::SkeletonMelee, 3, 20, 55, RESIST_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_RTBLACK, P_("monster", "Graywar the Slayer"), "general", 14, 672, MonsterAIID::SkeletonMelee, 1, 30, 50, RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_MAGISTR, P_("monster", "Dreadjudge"), "general", 14, 540, MonsterAIID::Counselor, 1, 30, 40, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_HLSPWN, P_("monster", "Stareye the Witch"), "general", 14, 726, MonsterAIID::Succubus, 2, 30, 50, IMMUNE_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_BTBLACK, P_("monster", "Steelskull the Hunter"), "general", 14, 831, MonsterAIID::SkeletonMelee, 3, 40, 50, RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_RBLACK, P_("monster", "Sir Gorash"), "general", 16, 1050, MonsterAIID::SkeletonMelee, 1, 20, 60, 0, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_CABALIST, P_("monster", "The Vizier"), "general", 15, 850, MonsterAIID::Counselor, 2, 25, 40, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_REALWEAV, P_("monster", "Zamphir"), "general", 15, 891, MonsterAIID::SkeletonMelee, 2, 30, 50, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_HLSPWN, P_("monster", "Bloodlust"), "general", 15, 825, MonsterAIID::Succubus, 1, 20, 55, IMMUNE_MAGIC | IMMUNE_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_HLSPWN, P_("monster", "Webwidow"), "general", 16, 774, MonsterAIID::Succubus, 1, 20, 50, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_SOLBRNR, P_("monster", "Fleshdancer"), "general", 16, 999, MonsterAIID::Succubus, 3, 30, 50, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
{ MT_OBLORD, P_("monster", "Grimspike"), "general", 19, 534, MonsterAIID::Sneak, 1, 25, 40, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
// TRANSLATORS: Unique Monster Block end |
|
{ MT_STORML, P_("monster", "Doomlock"), "general", 28, 534, MonsterAIID::Sneak, 1, 35, 55, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, |
|
{ MT_INVALID, nullptr, nullptr, 0, 0, MonsterAIID::Invalid, 0, 0, 0, 0, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, |
|
// clang-format on |
|
}; |
|
|
|
} // namespace devilution
|
|
|