From 1337ff6ea2fdb0593d82a60e35d723d105f6fd16 Mon Sep 17 00:00:00 2001 From: ephphatha Date: Wed, 13 Jul 2022 11:38:26 +1000 Subject: [PATCH] Add check if a monster potentially has leashed minions --- Source/control.cpp | 2 +- Source/dead.cpp | 4 +- Source/debug.cpp | 8 +- Source/engine/render/scrollrt.cpp | 2 +- Source/items.cpp | 6 +- Source/loadsave.cpp | 6 +- Source/missiles.cpp | 8 +- Source/monster.cpp | 144 +++++++++++++++--------------- Source/monster.h | 44 +++++---- Source/msg.cpp | 6 +- Source/objects.cpp | 2 +- Source/player.cpp | 2 +- Source/qol/monhealthbar.cpp | 4 +- Source/quests.cpp | 8 +- 14 files changed, 130 insertions(+), 116 deletions(-) diff --git a/Source/control.cpp b/Source/control.cpp index e4c18b562..863c2f22e 100644 --- a/Source/control.cpp +++ b/Source/control.cpp @@ -909,7 +909,7 @@ void DrawInfoBox(const Surface &out) InfoColor = UiFlags::ColorWhite; InfoString = string_view(monster.name); ClearPanel(); - if (monster.uniqType != 0) { + if (monster.isUnique()) { InfoColor = UiFlags::ColorWhitegold; PrintUniqueHistory(); } else { diff --git a/Source/dead.cpp b/Source/dead.cpp index 90694fdd7..0288cd1c6 100644 --- a/Source/dead.cpp +++ b/Source/dead.cpp @@ -57,7 +57,7 @@ void InitCorpses() for (size_t i = 0; i < ActiveMonsterCount; i++) { auto &monster = Monsters[ActiveMonsters[i]]; - if (monster.uniqType != 0) { + if (monster.isUnique()) { InitDeadAnimationFromMonster(Corpses[nd], monster.type()); Corpses[nd].translationPaletteIndex = ActiveMonsters[i] + 1; nd++; @@ -78,7 +78,7 @@ void SyncUniqDead() { for (size_t i = 0; i < ActiveMonsterCount; i++) { auto &monster = Monsters[ActiveMonsters[i]]; - if (monster.uniqType == 0) + if (!monster.isUnique()) continue; for (int dx = 0; dx < MAXDUNX; dx++) { for (int dy = 0; dy < MAXDUNY; dy++) { diff --git a/Source/debug.cpp b/Source/debug.cpp index 3d50039fc..b41b120da 100644 --- a/Source/debug.cpp +++ b/Source/debug.cpp @@ -677,15 +677,15 @@ std::string DebugCmdSpawnUniqueMonster(const string_view parameter) std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { return std::tolower(c); }); int mtype = -1; - int uniqueIndex = -1; - for (int i = 0; UniqueMonstersData[i].mtype != MT_INVALID; i++) { + UniqueMonsterType uniqueIndex = UniqueMonsterType::None; + for (size_t i = 0; UniqueMonstersData[i].mtype != MT_INVALID; i++) { auto mondata = UniqueMonstersData[i]; std::string monsterName(mondata.mName); std::transform(monsterName.begin(), monsterName.end(), monsterName.begin(), [](unsigned char c) { return std::tolower(c); }); if (monsterName.find(name) == std::string::npos) continue; mtype = mondata.mtype; - uniqueIndex = i; + uniqueIndex = static_cast(i); if (monsterName == name) // to support partial name matching but always choose the correct monster if full name is given break; } @@ -726,7 +726,7 @@ std::string DebugCmdSpawnUniqueMonster(const string_view parameter) Monster *monster = AddMonster(pos, myPlayer._pdir, id, true); if (monster == nullptr) return StrCat("I could only summon ", spawnedMonster, " Monsters. The rest strike for shorter working hours."); - PrepareUniqueMonst(*monster, uniqueIndex, 0, 0, UniqueMonstersData[uniqueIndex]); + PrepareUniqueMonst(*monster, uniqueIndex, 0, 0, UniqueMonstersData[static_cast(uniqueIndex)]); monster->corpseId = 1; spawnedMonster += 1; diff --git a/Source/engine/render/scrollrt.cpp b/Source/engine/render/scrollrt.cpp index ede7074aa..a664334a9 100644 --- a/Source/engine/render/scrollrt.cpp +++ b/Source/engine/render/scrollrt.cpp @@ -452,7 +452,7 @@ void DrawMonster(const Surface &out, Point tilePosition, Point targetBufferPosit return; } uint8_t *trn = nullptr; - if (monster.uniqType != 0) + if (monster.isUnique()) trn = monster.uniqueMonsterTRN.get(); if (monster.mode == MonsterMode::Petrified) trn = GetStoneTRN(); diff --git a/Source/items.cpp b/Source/items.cpp index 4301711ad..b98c1960a 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -3073,7 +3073,7 @@ void SpawnItem(Monster &monster, Point position, bool sendmsg) int idx; bool onlygood = true; - if (monster.uniqType != 0 || ((monster.data().mTreasure & T_UNIQ) != 0 && gbIsMultiplayer)) { + if (monster.isUnique() || ((monster.data().mTreasure & T_UNIQ) != 0 && gbIsMultiplayer)) { idx = RndUItem(&monster); if (idx < 0) { SpawnUnique((_unique_items) - (idx + 1), position); @@ -3102,7 +3102,7 @@ void SpawnItem(Monster &monster, Point position, bool sendmsg) int ii = AllocateItem(); auto &item = Items[ii]; GetSuperItemSpace(position, ii); - int uper = monster.uniqType != 0 ? 15 : 1; + int uper = monster.isUnique() ? 15 : 1; int8_t mLevel = monster.data().mLevel; if (!gbIsHellfire && monster.type().type == MT_DIABLO) @@ -4452,7 +4452,7 @@ std::string DebugSpawnItem(std::string itemName) uint32_t begin = SDL_GetTicks(); Monster fake_m; fake_m.levelType = 0; - fake_m.uniqType = 0; + fake_m.uniqueType = UniqueMonsterType::None; int i = 0; for (;; i++) { diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index e66e521bf..bf771ea77 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -632,7 +632,7 @@ void LoadMonster(LoadHelper *file, Monster &monster) monster.aiSeed = file->NextLE(); file->Skip(4); // Unused - monster.uniqType = file->NextLE(); + monster.uniqueType = static_cast(file->NextLE() - 1); monster.uniqTrans = file->NextLE(); monster.corpseId = file->NextLE(); @@ -680,7 +680,7 @@ void LoadMonster(LoadHelper *file, Monster &monster) */ void SyncPackSize(Monster &leader) { - if (leader.uniqType == 0) + if (!leader.isUnique()) return; if (leader.ai != AI_SCAV) return; @@ -1382,7 +1382,7 @@ void SaveMonster(SaveHelper *file, Monster &monster) file->WriteLE(monster.aiSeed); file->Skip(4); // Unused - file->WriteLE(monster.uniqType); + file->WriteLE(static_cast(monster.uniqueType) + 1); file->WriteLE(monster.uniqTrans); file->WriteLE(monster.corpseId); diff --git a/Source/missiles.cpp b/Source/missiles.cpp index f6fc738bd..adc0736dd 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -1143,7 +1143,7 @@ void AddBerserk(Missile &missile, const AddMissileParameter ¶meter) return false; if ((monster.flags & MFLAG_BERSERK) != 0) return false; - if (monster.uniqType != 0 || monster.ai == AI_DIABLO) + if (monster.isUnique() || monster.ai == AI_DIABLO) return false; if (IsAnyOf(monster.mode, MonsterMode::FadeIn, MonsterMode::FadeOut, MonsterMode::Charge)) return false; @@ -2038,7 +2038,7 @@ void AddRhino(Missile &missile, const AddMissileParameter ¶meter) InitMissileAnimationFromMonster(missile, parameter.midir, monster, graphic); if (IsAnyOf(monster.type().type, MT_NSNAKE, MT_RSNAKE, MT_BSNAKE, MT_GSNAKE)) missile._miAnimFrame = 7; - if (monster.uniqType != 0) { + if (monster.isUnique()) { missile._mlid = monster.lightId; } PutMissile(missile); @@ -2584,7 +2584,7 @@ Missile *AddMissile(Point src, Point dst, Direction midir, missile_id mitype, mi if (!missile.IsTrap() && micaster == TARGET_PLAYERS) { Monster &monster = Monsters[id]; - if (monster.uniqType != 0) { + if (monster.isUnique()) { missile._miUniqTrans = monster.uniqTrans + 1; } } @@ -3564,7 +3564,7 @@ void MI_Rhino(Missile &missile) monster.position.old = newPos; monster.position.tile = newPos; dMonster[newPos.x][newPos.y] = -(monst + 1); - if (monster.uniqType != 0) + if (monster.isUnique()) ChangeLightXY(missile._mlid, newPos); MoveMissilePos(missile); PutMissile(missile); diff --git a/Source/monster.cpp b/Source/monster.cpp index f117a0540..8108a94c6 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -226,7 +226,7 @@ void InitMonster(Monster &monster, Direction rd, int mtype, Point position) monster.goalVar3 = 0; monster.pathCount = 0; monster.isInvalid = false; - monster.uniqType = 0; + monster.uniqueType = UniqueMonsterType::None; monster.activeForTicks = 0; monster.lightId = NO_LIGHT; // BUGFIX monsters initial light id should be -1 (fixed) monster.rndItemSeed = AdvanceRndSeed(); @@ -391,10 +391,10 @@ void PlaceGroup(int mtype, unsigned num, Monster *leader = nullptr, bool leashed } } -void PlaceUniqueMonst(int uniqindex, int miniontype, int bosspacksize) +void PlaceUniqueMonst(UniqueMonsterType uniqindex, int miniontype, int bosspacksize) { auto &monster = Monsters[ActiveMonsterCount]; - const auto &uniqueMonsterData = UniqueMonstersData[uniqindex]; + const auto &uniqueMonsterData = UniqueMonstersData[static_cast(uniqindex)]; size_t uniqtype; for (uniqtype = 0; uniqtype < LevelMonsterTypeCount; uniqtype++) { @@ -428,13 +428,13 @@ void PlaceUniqueMonst(int uniqindex, int miniontype, int bosspacksize) } } - if (uniqindex == UMT_SNOTSPIL) { + if (uniqindex == UniqueMonsterType::SnotSpill) { position = SetPiece.position.megaToWorld() + Displacement { 8, 12 }; } - if (uniqindex == UMT_WARLORD) { + if (uniqindex == UniqueMonsterType::WarlordOfBlood) { position = SetPiece.position.megaToWorld() + Displacement { 6, 7 }; } - if (uniqindex == UMT_ZHAR) { + if (uniqindex == UniqueMonsterType::Zhar) { for (int i = 0; i < themeCount; i++) { if (i == zharlib) { position = themeLoc[i].room.position.megaToWorld() + Displacement { 4, 4 }; @@ -443,34 +443,34 @@ void PlaceUniqueMonst(int uniqindex, int miniontype, int bosspacksize) } } if (setlevel) { - if (uniqindex == UMT_LAZARUS) { + if (uniqindex == UniqueMonsterType::Lazarus) { position = { 32, 46 }; } - if (uniqindex == UMT_RED_VEX) { + if (uniqindex == UniqueMonsterType::RedVex) { position = { 40, 45 }; } - if (uniqindex == UMT_BLACKJADE) { + if (uniqindex == UniqueMonsterType::BlackJade) { position = { 38, 49 }; } - if (uniqindex == UMT_SKELKING) { + if (uniqindex == UniqueMonsterType::SkeletonKing) { position = { 35, 47 }; } } else { - if (uniqindex == UMT_LAZARUS) { + if (uniqindex == UniqueMonsterType::Lazarus) { position = SetPiece.position.megaToWorld() + Displacement { 3, 6 }; } - if (uniqindex == UMT_RED_VEX) { + if (uniqindex == UniqueMonsterType::RedVex) { position = SetPiece.position.megaToWorld() + Displacement { 5, 3 }; } - if (uniqindex == UMT_BLACKJADE) { + if (uniqindex == UniqueMonsterType::BlackJade) { position = SetPiece.position.megaToWorld() + Displacement { 5, 9 }; } } - if (uniqindex == UMT_BUTCHER) { + if (uniqindex == UniqueMonsterType::Butcher) { position = SetPiece.position.megaToWorld() + Displacement { 4, 4 }; } - if (uniqindex == UMT_NAKRUL) { + if (uniqindex == UniqueMonsterType::NaKrul) { if (UberRow == 0 || UberCol == 0) { UberDiabloMonsterIndex = -1; return; @@ -542,7 +542,7 @@ void ClrAllMonsters() void PlaceUniqueMonsters() { - for (int u = 0; UniqueMonstersData[u].mtype != -1; u++) { + for (size_t u = 0; UniqueMonstersData[u].mtype != -1; u++) { if (UniqueMonstersData[u].mlevel != currlevel) continue; @@ -550,18 +550,19 @@ void PlaceUniqueMonsters() if (mt == LevelMonsterTypeCount) continue; - if (u == UMT_GARBUD && Quests[Q_GARBUD]._qactive == QUEST_NOTAVAIL) + UniqueMonsterType uniqueType = static_cast(u); + if (uniqueType == UniqueMonsterType::Garbud && Quests[Q_GARBUD]._qactive == QUEST_NOTAVAIL) continue; - if (u == UMT_ZHAR && Quests[Q_ZHAR]._qactive == QUEST_NOTAVAIL) + if (uniqueType == UniqueMonsterType::Zhar && Quests[Q_ZHAR]._qactive == QUEST_NOTAVAIL) continue; - if (u == UMT_SNOTSPIL && Quests[Q_LTBANNER]._qactive == QUEST_NOTAVAIL) + if (uniqueType == UniqueMonsterType::SnotSpill && Quests[Q_LTBANNER]._qactive == QUEST_NOTAVAIL) continue; - if (u == UMT_LACHDAN && Quests[Q_VEIL]._qactive == QUEST_NOTAVAIL) + if (uniqueType == UniqueMonsterType::Lachdan && Quests[Q_VEIL]._qactive == QUEST_NOTAVAIL) continue; - if (u == UMT_WARLORD && Quests[Q_WARLORD]._qactive == QUEST_NOTAVAIL) + if (uniqueType == UniqueMonsterType::WarlordOfBlood && Quests[Q_WARLORD]._qactive == QUEST_NOTAVAIL) continue; - PlaceUniqueMonst(u, mt, 8); + PlaceUniqueMonst(uniqueType, mt, 8); } } @@ -569,13 +570,13 @@ void PlaceQuestMonsters() { if (!setlevel) { if (Quests[Q_BUTCHER].IsAvailable()) { - PlaceUniqueMonst(UMT_BUTCHER, 0, 0); + PlaceUniqueMonst(UniqueMonsterType::Butcher, 0, 0); } if (currlevel == Quests[Q_SKELKING]._qlevel && gbIsMultiplayer) { for (size_t i = 0; i < LevelMonsterTypeCount; i++) { if (IsSkel(LevelMonsterTypes[i].type)) { - PlaceUniqueMonst(UMT_SKELKING, i, 30); + PlaceUniqueMonst(UniqueMonsterType::SkeletonKing, i, 30); break; } } @@ -600,21 +601,21 @@ void PlaceQuestMonsters() if (Quests[Q_WARLORD].IsAvailable()) { auto dunData = LoadFileInMem("Levels\\L4Data\\Warlord.DUN"); SetMapMonsters(dunData.get(), SetPiece.position.megaToWorld()); - AddMonsterType(UniqueMonstersData[UMT_WARLORD].mtype, PLACE_SCATTER); + AddMonsterType(UniqueMonstersData[static_cast(UniqueMonsterType::WarlordOfBlood)].mtype, PLACE_SCATTER); } if (Quests[Q_VEIL].IsAvailable()) { - AddMonsterType(UniqueMonstersData[UMT_LACHDAN].mtype, PLACE_SCATTER); + AddMonsterType(UniqueMonstersData[static_cast(UniqueMonsterType::Lachdan)].mtype, PLACE_SCATTER); } if (Quests[Q_ZHAR].IsAvailable() && zharlib == -1) { Quests[Q_ZHAR]._qactive = QUEST_NOTAVAIL; } if (currlevel == Quests[Q_BETRAYER]._qlevel && gbIsMultiplayer) { - AddMonsterType(UniqueMonstersData[UMT_LAZARUS].mtype, PLACE_UNIQUE); - AddMonsterType(UniqueMonstersData[UMT_RED_VEX].mtype, PLACE_UNIQUE); - PlaceUniqueMonst(UMT_LAZARUS, 0, 0); - PlaceUniqueMonst(UMT_RED_VEX, 0, 0); - PlaceUniqueMonst(UMT_BLACKJADE, 0, 0); + AddMonsterType(UniqueMonstersData[static_cast(UniqueMonsterType::Lazarus)].mtype, PLACE_UNIQUE); + AddMonsterType(UniqueMonstersData[static_cast(UniqueMonsterType::RedVex)].mtype, PLACE_UNIQUE); + PlaceUniqueMonst(UniqueMonsterType::Lazarus, 0, 0); + PlaceUniqueMonst(UniqueMonsterType::RedVex, 0, 0); + PlaceUniqueMonst(UniqueMonsterType::BlackJade, 0, 0); auto dunData = LoadFileInMem("Levels\\L4Data\\Vile1.DUN"); SetMapMonsters(dunData.get(), SetPiece.position.megaToWorld()); } @@ -623,24 +624,24 @@ void PlaceQuestMonsters() UberDiabloMonsterIndex = -1; size_t i1; for (i1 = 0; i1 < LevelMonsterTypeCount; i1++) { - if (LevelMonsterTypes[i1].type == UniqueMonstersData[UMT_NAKRUL].mtype) + if (LevelMonsterTypes[i1].type == UniqueMonstersData[static_cast(UniqueMonsterType::NaKrul)].mtype) break; } if (i1 < LevelMonsterTypeCount) { for (size_t i2 = 0; i2 < ActiveMonsterCount; i2++) { auto &monster = Monsters[i2]; - if (monster.uniqType != 0 || monster.levelType == i1) { + if (monster.isUnique() || monster.levelType == i1) { UberDiabloMonsterIndex = static_cast(i2); break; } } } if (UberDiabloMonsterIndex == -1) - PlaceUniqueMonst(UMT_NAKRUL, 0, 0); + PlaceUniqueMonst(UniqueMonsterType::NaKrul, 0, 0); } } else if (setlvlnum == SL_SKELKING) { - PlaceUniqueMonst(UMT_SKELKING, 0, 0); + PlaceUniqueMonst(UniqueMonsterType::SkeletonKing, 0, 0); } } @@ -972,14 +973,14 @@ void SpawnLoot(Monster &monster, bool sendmsg) return; } - if (Quests[Q_GARBUD].IsAvailable() && monster.uniqType - 1 == UMT_GARBUD) { + if (Quests[Q_GARBUD].IsAvailable() && monster.uniqueType == UniqueMonsterType::Garbud) { CreateTypeItem(monster.position.tile + Displacement { 1, 1 }, true, ItemType::Mace, IMISC_NONE, sendmsg, false); - } else if (monster.uniqType - 1 == UMT_DEFILER) { + } else if (monster.uniqueType == UniqueMonsterType::Defiler) { if (effect_is_playing(USFX_DEFILER8)) stream_stop(); Quests[Q_DEFILER]._qlog = false; SpawnMapOfDoom(monster.position.tile, sendmsg); - } else if (monster.uniqType - 1 == UMT_HORKDMN) { + } else if (monster.uniqueType == UniqueMonsterType::HorkDemon) { if (sgGameInitInfo.bTheoQuest != 0) { SpawnTheodore(monster.position.tile, sendmsg); } else { @@ -1579,7 +1580,7 @@ void MonsterTalk(Monster &monster) if (effect_is_playing(Speeches[monster.talkMsg].sfxnr)) return; InitQTextMsg(monster.talkMsg); - if (monster.uniqType - 1 == UMT_GARBUD) { + if (monster.uniqueType == UniqueMonsterType::Garbud) { if (monster.talkMsg == TEXT_GARBUD1) { Quests[Q_GARBUD]._qactive = QUEST_ACTIVE; Quests[Q_GARBUD]._qlog = true; // BUGFIX: (?) for other quests qactive and qlog go together, maybe this should actually go into the if above (fixed) @@ -1589,7 +1590,7 @@ void MonsterTalk(Monster &monster) monster.flags |= MFLAG_QUEST_COMPLETE; } } - if (monster.uniqType - 1 == UMT_ZHAR + if (monster.uniqueType == UniqueMonsterType::Zhar && monster.talkMsg == TEXT_ZHAR1 && (monster.flags & MFLAG_QUEST_COMPLETE) == 0) { Quests[Q_ZHAR]._qactive = QUEST_ACTIVE; @@ -1597,7 +1598,7 @@ void MonsterTalk(Monster &monster) CreateTypeItem(monster.position.tile + Displacement { 1, 1 }, false, ItemType::Misc, IMISC_BOOK, true, false); monster.flags |= MFLAG_QUEST_COMPLETE; } - if (monster.uniqType - 1 == UMT_SNOTSPIL) { + if (monster.uniqueType == UniqueMonsterType::SnotSpill) { if (monster.talkMsg == TEXT_BANNER10 && (monster.flags & MFLAG_QUEST_COMPLETE) == 0) { ObjChangeMap(SetPiece.position.x, SetPiece.position.y, SetPiece.position.x + (SetPiece.size.width / 2) + 2, SetPiece.position.y + (SetPiece.size.height / 2) - 2); auto tren = TransVal; @@ -1613,7 +1614,7 @@ void MonsterTalk(Monster &monster) app_fatal(StrCat("SS Talk = ", monster.talkMsg, ", Flags = ", monster.flags)); } } - if (monster.uniqType - 1 == UMT_LACHDAN) { + if (monster.uniqueType == UniqueMonsterType::Lachdan) { if (monster.talkMsg == TEXT_VEIL9) { Quests[Q_VEIL]._qactive = QUEST_ACTIVE; Quests[Q_VEIL]._qlog = true; @@ -1623,9 +1624,9 @@ void MonsterTalk(Monster &monster) monster.flags |= MFLAG_QUEST_COMPLETE; } } - if (monster.uniqType - 1 == UMT_WARLORD) + if (monster.uniqueType == UniqueMonsterType::WarlordOfBlood) Quests[Q_WARLORD]._qvar1 = 2; - if (monster.uniqType - 1 == UMT_LAZARUS && gbIsMultiplayer) { + if (monster.uniqueType == UniqueMonsterType::Lazarus && gbIsMultiplayer) { Quests[Q_BETRAYER]._qvar1 = 6; monster.goal = MonsterGoal::Normal; monster.activeForTicks = UINT8_MAX; @@ -1680,10 +1681,10 @@ void MonsterDeath(Monster &monster) if (monster.var1 == 140) PrepDoEnding(); } else if (monster.animInfo.currentFrame == monster.animInfo.numberOfFrames - 1) { - if (monster.uniqType == 0) - AddCorpse(monster.position.tile, monster.type().corpseId, monster.direction); - else + if (monster.isUnique()) AddCorpse(monster.position.tile, monster.corpseId, monster.direction); + else + AddCorpse(monster.position.tile, monster.type().corpseId, monster.direction); dMonster[monster.position.tile.x][monster.position.tile.y] = 0; monster.isInvalid = true; @@ -1786,7 +1787,7 @@ void GroupUnity(Monster &monster) return; // No unique monster would be a minion of someone else! - assert(monster.uniqType == 0); + assert(!monster.isUnique()); // Someone with a leaderRelation should have a leader, if we end up trying to access a nullptr then the relation was already broken... @@ -3430,13 +3431,13 @@ bool UpdateModeStance(int monsterId) void InitTRNForUniqueMonster(Monster &monster) { char filestr[64]; - *BufCopy(filestr, R"(Monsters\Monsters\)", UniqueMonstersData[monster.uniqType - 1].mTrnName, ".TRN") = '\0'; + *BufCopy(filestr, R"(Monsters\Monsters\)", UniqueMonstersData[static_cast(monster.uniqueType)].mTrnName, ".TRN") = '\0'; monster.uniqueMonsterTRN = LoadFileInMem(filestr); } -void PrepareUniqueMonst(Monster &monster, int uniqindex, int miniontype, int bosspacksize, const UniqueMonsterData &uniqueMonsterData) +void PrepareUniqueMonst(Monster &monster, UniqueMonsterType monsterType, int miniontype, int bosspacksize, const UniqueMonsterData &uniqueMonsterData) { - monster.uniqType = uniqindex + 1; + monster.uniqueType = monsterType; if (uniqueMonsterData.mlevel != 0) { monster.level = 2 * uniqueMonsterData.mlevel; @@ -3460,7 +3461,7 @@ void PrepareUniqueMonst(Monster &monster, int uniqindex, int miniontype, int bos monster.maxDamage2 = uniqueMonsterData.mMaxDamage; monster.magicResistance = uniqueMonsterData.mMagicRes; monster.talkMsg = uniqueMonsterData.mtalkmsg; - if (uniqindex == UMT_HORKDMN) + if (monsterType == UniqueMonsterType::HorkDemon) monster.lightId = NO_LIGHT; // BUGFIX monsters initial light id should be -1 (fixed) else monster.lightId = AddLight(monster.position.tile, 3); @@ -3589,15 +3590,15 @@ void GetLevelMTypes() if (Quests[Q_BUTCHER].IsAvailable()) AddMonsterType(MT_CLEAVER, PLACE_SPECIAL); if (Quests[Q_GARBUD].IsAvailable()) - AddMonsterType(UniqueMonstersData[UMT_GARBUD].mtype, PLACE_UNIQUE); + AddMonsterType(UniqueMonstersData[static_cast(UniqueMonsterType::Garbud)].mtype, PLACE_UNIQUE); if (Quests[Q_ZHAR].IsAvailable()) - AddMonsterType(UniqueMonstersData[UMT_ZHAR].mtype, PLACE_UNIQUE); + AddMonsterType(UniqueMonstersData[static_cast(UniqueMonsterType::Zhar)].mtype, PLACE_UNIQUE); if (Quests[Q_LTBANNER].IsAvailable()) - AddMonsterType(UniqueMonstersData[UMT_SNOTSPIL].mtype, PLACE_UNIQUE); + AddMonsterType(UniqueMonstersData[static_cast(UniqueMonsterType::SnotSpill)].mtype, PLACE_UNIQUE); if (Quests[Q_VEIL].IsAvailable()) - AddMonsterType(UniqueMonstersData[UMT_LACHDAN].mtype, PLACE_UNIQUE); + AddMonsterType(UniqueMonstersData[static_cast(UniqueMonsterType::Lachdan)].mtype, PLACE_UNIQUE); if (Quests[Q_WARLORD].IsAvailable()) - AddMonsterType(UniqueMonstersData[UMT_WARLORD].mtype, PLACE_UNIQUE); + AddMonsterType(UniqueMonstersData[static_cast(UniqueMonsterType::WarlordOfBlood)].mtype, PLACE_UNIQUE); if (gbIsMultiplayer && currlevel == Quests[Q_SKELKING]._qlevel) { @@ -3828,12 +3829,12 @@ void SetMapMonsters(const uint16_t *dunData, Point startPosition) AddMonster(GolemHoldingCell, Direction::South, 0, false); if (setlevel && setlvlnum == SL_VILEBETRAYER) { - AddMonsterType(UniqueMonstersData[UMT_LAZARUS].mtype, PLACE_UNIQUE); - AddMonsterType(UniqueMonstersData[UMT_RED_VEX].mtype, PLACE_UNIQUE); - AddMonsterType(UniqueMonstersData[UMT_BLACKJADE].mtype, PLACE_UNIQUE); - PlaceUniqueMonst(UMT_LAZARUS, 0, 0); - PlaceUniqueMonst(UMT_RED_VEX, 0, 0); - PlaceUniqueMonst(UMT_BLACKJADE, 0, 0); + AddMonsterType(UniqueMonstersData[static_cast(UniqueMonsterType::Lazarus)].mtype, PLACE_UNIQUE); + AddMonsterType(UniqueMonstersData[static_cast(UniqueMonsterType::RedVex)].mtype, PLACE_UNIQUE); + AddMonsterType(UniqueMonstersData[static_cast(UniqueMonsterType::BlackJade)].mtype, PLACE_UNIQUE); + PlaceUniqueMonst(UniqueMonsterType::Lazarus, 0, 0); + PlaceUniqueMonst(UniqueMonsterType::RedVex, 0, 0); + PlaceUniqueMonst(UniqueMonsterType::BlackJade, 0, 0); } int width = SDL_SwapLE16(dunData[0]); @@ -4011,7 +4012,9 @@ void M_SyncStartKill(int monsterId, Point position, int pnum) void M_UpdateRelations(const Monster &monster) { - ReleaseMinions(monster); + if (monster.hasLeashedMinions()) + ReleaseMinions(monster); + ShrinkLeaderPacksize(monster); } @@ -4297,7 +4300,7 @@ bool DirOK(int monsterId, Direction mdir) if (monster.leaderRelation == LeaderRelation::Leashed) { return futurePosition.WalkingDistance(monster.getLeader()->position.future) < 4; } - if (monster.uniqType == 0 || UniqueMonstersData[monster.uniqType - 1].monsterPack != UniqueMonsterPack::Leashed) + if (!monster.hasLeashedMinions()) return true; int mcount = 0; for (int x = futurePosition.x - 3; x <= futurePosition.x + 3; x++) { @@ -4410,13 +4413,12 @@ void SyncMonsterAnim(Monster &monster) LevelMonsterTypes[monster.levelType].corpseId = 1; } #endif - if (monster.uniqType != 0) - monster.name = pgettext("monster", UniqueMonstersData[monster.uniqType - 1].mName).data(); - else - monster.name = pgettext("monster", monster.data().mName).data(); - - if (monster.uniqType != 0) + if (monster.isUnique()) { + monster.name = pgettext("monster", UniqueMonstersData[static_cast(monster.uniqueType)].mName).data(); InitTRNForUniqueMonster(monster); + } else { + monster.name = pgettext("monster", monster.data().mName).data(); + } MonsterGraphic graphic = MonsterGraphic::Stand; diff --git a/Source/monster.h b/Source/monster.h index e39bfea1e..e8cd75423 100644 --- a/Source/monster.h +++ b/Source/monster.h @@ -48,20 +48,21 @@ enum monster_flag : uint16_t { }; /** Indexes from UniqueMonstersData array for special unique monsters (usually quest related) */ -enum : uint8_t { - UMT_GARBUD, - UMT_SKELKING, - UMT_ZHAR, - UMT_SNOTSPIL, - UMT_LAZARUS, - UMT_RED_VEX, - UMT_BLACKJADE, - UMT_LACHDAN, - UMT_WARLORD, - UMT_BUTCHER, - UMT_HORKDMN, - UMT_DEFILER, - UMT_NAKRUL, +enum class UniqueMonsterType : uint8_t { + Garbud, + SkeletonKing, + Zhar, + SnotSpill, + Lazarus, + RedVex, + BlackJade, + Lachdan, + WarlordOfBlood, + Butcher, + HorkDemon, + Defiler, + NaKrul, + None = static_cast(-1), }; enum class MonsterMode : uint8_t { @@ -224,7 +225,7 @@ struct Monster { // note: missing field _mAFNum uint8_t intelligence; /** Stores information for how many ticks the monster will remain active */ uint8_t activeForTicks; - uint8_t uniqType; + UniqueMonsterType uniqueType; uint8_t uniqTrans; int8_t corpseId; int8_t whoHit; @@ -294,6 +295,11 @@ struct Monster { // note: missing field _mAFNum [[nodiscard]] Monster *getLeader() const; void setLeader(const Monster *leader); + [[nodiscard]] bool hasLeashedMinions() const + { + return isUnique() && UniqueMonstersData[static_cast(uniqueType)].monsterPack == UniqueMonsterPack::Leashed; + } + /** * @brief Is the monster currently walking? */ @@ -301,6 +307,12 @@ struct Monster { // note: missing field _mAFNum bool isImmune(missile_id mitype) const; bool isResistant(missile_id mitype) const; bool isPossibleToHit() const; + + [[nodiscard]] bool isUnique() const + { + return uniqueType != UniqueMonsterType::None; + } + bool tryLiftGargoyle(); }; @@ -311,7 +323,7 @@ extern size_t ActiveMonsterCount; extern int MonsterKillCounts[MaxMonsters]; extern bool sgbSaveSoundOn; -void PrepareUniqueMonst(Monster &monster, int uniqindex, int miniontype, int bosspacksize, const UniqueMonsterData &uniqueMonsterData); +void PrepareUniqueMonst(Monster &monster, UniqueMonsterType monsterType, int miniontype, int bosspacksize, const UniqueMonsterData &uniqueMonsterData); void InitLevelMonsters(); void GetLevelMTypes(); void InitMonsterGFX(size_t monsterTypeIndex); diff --git a/Source/msg.cpp b/Source/msg.cpp index 32472a801..532315271 100644 --- a/Source/msg.cpp +++ b/Source/msg.cpp @@ -2464,10 +2464,10 @@ void DeltaLoadLevel() if (deltaLevel.monster[i].hitPoints == 0) { M_ClearSquares(monster); if (monster.ai != AI_DIABLO) { - if (monster.uniqType == 0) { - AddCorpse(monster.position.tile, monster.type().corpseId, monster.direction); - } else { + if (monster.isUnique()) { AddCorpse(monster.position.tile, monster.corpseId, monster.direction); + } else { + AddCorpse(monster.position.tile, monster.type().corpseId, monster.direction); } } monster.isInvalid = true; diff --git a/Source/objects.cpp b/Source/objects.cpp index 5b360b61e..e16c549d8 100644 --- a/Source/objects.cpp +++ b/Source/objects.cpp @@ -3437,7 +3437,7 @@ void OperateBookCase(int i, bool sendmsg, bool sendLootMsg) if (Quests[Q_ZHAR].IsAvailable()) { auto &zhar = Monsters[MAX_PLRS]; if (zhar.mode == MonsterMode::Stand // prevents playing the "angry" message for the second time if zhar got aggroed by losing vision and talking again - && zhar.uniqType - 1 == UMT_ZHAR + && zhar.uniqueType == UniqueMonsterType::Zhar && zhar.activeForTicks == UINT8_MAX && zhar.hitPoints > 0) { zhar.talkMsg = TEXT_ZHAR2; diff --git a/Source/player.cpp b/Source/player.cpp index cdf1d393c..62b6836de 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -875,7 +875,7 @@ bool PlrHitMonst(int pnum, size_t monsterId, bool adjacentDamage = false) dam *= 3; } - if (HasAnyOf(player.pDamAcFlags, ItemSpecialEffectHf::Doppelganger) && monster.type().type != MT_DIABLO && monster.uniqType == 0 && GenerateRnd(100) < 10) { + if (HasAnyOf(player.pDamAcFlags, ItemSpecialEffectHf::Doppelganger) && monster.type().type != MT_DIABLO && !monster.isUnique() && GenerateRnd(100) < 10) { AddDoppelganger(monster); } diff --git a/Source/qol/monhealthbar.cpp b/Source/qol/monhealthbar.cpp index 13df75c5e..bcb87f623 100644 --- a/Source/qol/monhealthbar.cpp +++ b/Source/qol/monhealthbar.cpp @@ -134,7 +134,7 @@ void DrawMonsterHealthBar(const Surface &out) UiFlags style = UiFlags::AlignCenter | UiFlags::VerticalCenter; DrawString(out, monster.name, { position + Displacement { -1, 1 }, { width, height } }, style | UiFlags::ColorBlack); - if (monster.uniqType != 0) + if (monster.isUnique()) style |= UiFlags::ColorWhitegold; else if (monster.leader != Monster::NoLeader) style |= UiFlags::ColorBlue; @@ -144,7 +144,7 @@ void DrawMonsterHealthBar(const Surface &out) if (multiplier > 0) DrawString(out, StrCat("x", multiplier), { position, { width - 2, height } }, UiFlags::ColorWhite | UiFlags::AlignRight | UiFlags::VerticalCenter); - if (monster.uniqType != 0 || MonsterKillCounts[monster.type().type] >= 15) { + if (monster.isUnique() || MonsterKillCounts[monster.type().type] >= 15) { monster_resistance immunes[] = { IMMUNE_MAGIC, IMMUNE_FIRE, IMMUNE_LIGHTNING }; monster_resistance resists[] = { RESIST_MAGIC, RESIST_FIRE, RESIST_LIGHTNING }; diff --git a/Source/quests.cpp b/Source/quests.cpp index 01adcecbb..fccb121e3 100644 --- a/Source/quests.cpp +++ b/Source/quests.cpp @@ -415,13 +415,13 @@ void CheckQuestKill(const Monster &monster, bool sendmsg) myPlayer.Say(HeroSpeech::TheSpiritsOfTheDeadAreNowAvenged, 30); if (sendmsg) NetSendCmdQuest(true, quest); - } else if (monster.uniqType - 1 == UMT_GARBUD) { //"Gharbad the Weak" + } else if (monster.uniqueType == UniqueMonsterType::Garbud) { //"Gharbad the Weak" Quests[Q_GARBUD]._qactive = QUEST_DONE; myPlayer.Say(HeroSpeech::ImNotImpressed, 30); - } else if (monster.uniqType - 1 == UMT_ZHAR) { //"Zhar the Mad" + } else if (monster.uniqueType == UniqueMonsterType::Zhar) { //"Zhar the Mad" Quests[Q_ZHAR]._qactive = QUEST_DONE; myPlayer.Say(HeroSpeech::ImSorryDidIBreakYourConcentration, 30); - } else if (monster.uniqType - 1 == UMT_LAZARUS) { //"Arch-Bishop Lazarus" + } else if (monster.uniqueType == UniqueMonsterType::Lazarus) { //"Arch-Bishop Lazarus" auto &betrayerQuest = Quests[Q_BETRAYER]; betrayerQuest._qactive = QUEST_DONE; myPlayer.Say(HeroSpeech::YourMadnessEndsHereBetrayer, 30); @@ -448,7 +448,7 @@ void CheckQuestKill(const Monster &monster, bool sendmsg) betrayerQuest._qvar2 = 4; AddMissile({ 35, 32 }, { 35, 32 }, Direction::South, MIS_RPORTAL, TARGET_MONSTERS, MyPlayerId, 0, 0); } - } else if (monster.uniqType - 1 == UMT_WARLORD) { //"Warlord of Blood" + } else if (monster.uniqueType == UniqueMonsterType::WarlordOfBlood) { Quests[Q_WARLORD]._qactive = QUEST_DONE; myPlayer.Say(HeroSpeech::YourReignOfPainHasEnded, 30); }