From 4bf4e42230d26e1864e3ab2226675cf7b5a725f8 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Sun, 11 Jul 2021 21:13:00 +0200 Subject: [PATCH] Move local symbols to anonymous namespace --- Source/lighting.cpp | 65 +- Source/lighting.h | 6 +- Source/loadsave.cpp | 845 +++---- Source/mainmenu.cpp | 4 +- Source/missiles.cpp | 1309 +++++------ Source/missiles.h | 1 - Source/monster.cpp | 5478 +++++++++++++++++++++---------------------- Source/monster.h | 54 - 8 files changed, 3850 insertions(+), 3912 deletions(-) diff --git a/Source/lighting.cpp b/Source/lighting.cpp index 0b0236835..4e6b5d6d0 100644 --- a/Source/lighting.cpp +++ b/Source/lighting.cpp @@ -13,18 +13,15 @@ namespace devilution { LightStruct VisionList[MAXVISION]; -uint8_t ActiveLights[MAXLIGHTS]; +int VisionCount; +int VisionId; LightStruct Lights[MAXLIGHTS]; +uint8_t ActiveLights[MAXLIGHTS]; int ActiveLightCount; -uint8_t lightradius[16][128]; -bool dovision; -int VisionCount; char LightsMax; -bool UpdateLighting; -uint8_t lightblock[64][16][16]; -int VisionId; std::array LightTables; bool DisableLighting; +bool UpdateLighting; /** * CrawlTable specifies X- and Y-coordinate deltas from a missile target coordinate. @@ -49,7 +46,7 @@ bool DisableLighting; * | 526 * +-------> x */ -const char CrawlTable[2749] = { +const int8_t CrawlTable[2749] = { // clang-format off 1, // Table 0, offset 0 0, 0, @@ -447,6 +444,12 @@ const uint8_t VisionCrawlTable[23][30] = { // clang-format on }; +namespace { + +uint8_t lightradius[16][128]; +bool dovision; +uint8_t lightblock[64][16][16]; + /** RadiusAdj maps from VisionCrawlTable index to lighting vision radius adjustment. */ const BYTE RadiusAdj[23] = { 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 4, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0 }; @@ -491,6 +494,30 @@ char GetLight(Point position) return dLight[position.x][position.y]; } +void DoUnLight(int nXPos, int nYPos, int nRadius) +{ + nRadius++; + + int minX = nXPos - nRadius; + int maxX = nXPos + nRadius; + int minY = nYPos - nRadius; + int maxY = nYPos + nRadius; + + minX = std::max(minX, 0); + maxX = std::max(maxX, MAXDUNX); + minY = std::max(minY, 0); + maxY = std::max(maxY, MAXDUNY); + + for (int y = minY; y < maxY; y++) { + for (int x = minX; x < maxX; x++) { + if (x >= 0 && x < MAXDUNX && y >= 0 && y < MAXDUNY) + dLight[x][y] = dPreLight[x][y]; + } + } +} + +} // namespace + void DoLighting(Point position, int nRadius, int lnum) { int xoff = 0; @@ -598,28 +625,6 @@ void DoLighting(Point position, int nRadius, int lnum) } } -void DoUnLight(int nXPos, int nYPos, int nRadius) -{ - nRadius++; - - int minX = nXPos - nRadius; - int maxX = nXPos + nRadius; - int minY = nYPos - nRadius; - int maxY = nYPos + nRadius; - - minX = std::max(minX, 0); - maxX = std::max(maxX, MAXDUNX); - minY = std::max(minY, 0); - maxY = std::max(maxY, MAXDUNY); - - for (int y = minY; y < maxY; y++) { - for (int x = minX; x < maxX; x++) { - if (x >= 0 && x < MAXDUNX && y >= 0 && y < MAXDUNY) - dLight[x][y] = dPreLight[x][y]; - } - } -} - void DoUnVision(Point position, int nRadius) { nRadius++; diff --git a/Source/lighting.h b/Source/lighting.h index 437d19472..c90595605 100644 --- a/Source/lighting.h +++ b/Source/lighting.h @@ -43,7 +43,7 @@ extern LightStruct Lights[MAXLIGHTS]; extern uint8_t ActiveLights[MAXLIGHTS]; extern int ActiveLightCount; extern char LightsMax; -extern std::array LightTables; +extern std::array LightTables; extern bool DisableLighting; extern bool UpdateLighting; @@ -73,7 +73,7 @@ void lighting_color_cycling(); /* rdata */ -extern const char CrawlTable[2749]; -extern const BYTE VisionCrawlTable[23][30]; +extern const int8_t CrawlTable[2749]; +extern const uint8_t VisionCrawlTable[23][30]; } // namespace devilution diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 257172a4f..aa3df0ff1 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -33,11 +33,12 @@ namespace devilution { bool gbIsHellfireSaveGame; uint8_t giNumberOfLevels; -uint8_t giNumberQuests; -uint8_t giNumberOfSmithPremiumItems; namespace { +uint8_t giNumberQuests; +uint8_t giNumberOfSmithPremiumItems; + template T SwapLE(T in) { @@ -206,25 +207,6 @@ public: } }; -} // namespace - -void RemoveInvalidItem(ItemStruct *pItem) -{ - bool isInvalid = !IsItemAvailable(pItem->IDidx) || !IsUniqueAvailable(pItem->_iUid); - - if (!gbIsHellfire) { - isInvalid = isInvalid || (pItem->_itype == ITYPE_STAFF && GetSpellStaffLevel(pItem->_iSpell) == -1); - isInvalid = isInvalid || (pItem->_iMiscId == IMISC_BOOK && GetSpellBookLevel(pItem->_iSpell) == -1); - isInvalid = isInvalid || pItem->_iDamAcFlags != 0; - isInvalid = isInvalid || pItem->_iPrePower > IDI_LASTDIABLO; - isInvalid = isInvalid || pItem->_iSufPower > IDI_LASTDIABLO; - } - - if (isInvalid) { - pItem->_itype = ITYPE_NONE; - } -} - static void LoadItemData(LoadHelper *file, ItemStruct *pItem) { pItem->_iSeed = file->NextLE(); @@ -817,133 +799,6 @@ static void LoadPortal(LoadHelper *file, int i) pPortal->setlvl = file->NextBool32(); } -_item_indexes RemapItemIdxFromDiablo(_item_indexes i) -{ - constexpr auto GetItemIdValue = [](int i) -> int { - if (i == IDI_SORCERER) { - return 166; - } - if (i >= 156) { - i += 5; // Hellfire exclusive items - } - if (i >= 88) { - i += 1; // Scroll of Search - } - if (i >= 83) { - i += 4; // Oils - } - - return i; - }; - - return static_cast<_item_indexes>(GetItemIdValue(i)); -} - -_item_indexes RemapItemIdxToDiablo(_item_indexes i) -{ - constexpr auto GetItemIdValue = [](int i) -> int { - if (i == 166) { - return IDI_SORCERER; - } - if ((i >= 83 && i <= 86) || i == 92 || i >= 161) { - return -1; // Hellfire exclusive items - } - if (i >= 93) { - i -= 1; // Scroll of Search - } - if (i >= 87) { - i -= 4; // Oils - } - - return i; - }; - - return static_cast<_item_indexes>(GetItemIdValue(i)); -} - -_item_indexes RemapItemIdxFromSpawn(_item_indexes i) -{ - constexpr auto GetItemIdValue = [](int i) { - if (i >= 62) { - i += 9; // Medium and heavy armors - } - if (i >= 96) { - i += 1; // Scroll of Stone Curse - } - if (i >= 98) { - i += 1; // Scroll of Guardian - } - if (i >= 99) { - i += 1; // Scroll of ... - } - if (i >= 101) { - i += 1; // Scroll of Golem - } - if (i >= 102) { - i += 1; // Scroll of None - } - if (i >= 104) { - i += 1; // Scroll of Apocalypse - } - - return i; - }; - - return static_cast<_item_indexes>(GetItemIdValue(i)); -} - -_item_indexes RemapItemIdxToSpawn(_item_indexes i) -{ - constexpr auto GetItemIdValue = [](int i) { - if (i >= 104) { - i -= 1; // Scroll of Apocalypse - } - if (i >= 102) { - i -= 1; // Scroll of None - } - if (i >= 101) { - i -= 1; // Scroll of Golem - } - if (i >= 99) { - i -= 1; // Scroll of ... - } - if (i >= 98) { - i -= 1; // Scroll of Guardian - } - if (i >= 96) { - i -= 1; // Scroll of Stone Curse - } - if (i >= 71) { - i -= 9; // Medium and heavy armors - } - - return i; - }; - - return static_cast<_item_indexes>(GetItemIdValue(i)); -} - -bool IsHeaderValid(uint32_t magicNumber) -{ - gbIsHellfireSaveGame = false; - if (magicNumber == LoadLE32("SHAR")) { - return true; - } - if (magicNumber == LoadLE32("SHLF")) { - gbIsHellfireSaveGame = true; - return true; - } - if (!gbIsSpawn && magicNumber == LoadLE32("RETL")) { - return true; - } - if (!gbIsSpawn && magicNumber == LoadLE32("HELF")) { - gbIsHellfireSaveGame = true; - return true; - } - - return false; -} - static void ConvertLevels() { // Backup current level state @@ -994,43 +849,6 @@ static void ConvertLevels() leveltype = tmpLeveltype; } -void LoadHotkeys() -{ - LoadHelper file("hotkeys"); - if (!file.IsValid()) - return; - - auto &myPlayer = Players[MyPlayerId]; - - for (auto &spellId : myPlayer._pSplHotKey) { - spellId = static_cast(file.NextLE()); - } - for (auto &spellType : myPlayer._pSplTHotKey) { - spellType = static_cast(file.NextLE()); - } - myPlayer._pRSpell = static_cast(file.NextLE()); - myPlayer._pRSplType = static_cast(file.NextLE()); -} - -void SaveHotkeys() -{ - auto &myPlayer = Players[MyPlayerId]; - - const size_t nHotkeyTypes = sizeof(myPlayer._pSplHotKey) / sizeof(myPlayer._pSplHotKey[0]); - const size_t nHotkeySpells = sizeof(myPlayer._pSplTHotKey) / sizeof(myPlayer._pSplTHotKey[0]); - - SaveHelper file("hotkeys", (nHotkeyTypes * 4) + nHotkeySpells + 4 + 1); - - for (auto &spellId : myPlayer._pSplHotKey) { - file.WriteLE(spellId); - } - for (auto &spellType : myPlayer._pSplTHotKey) { - file.WriteLE(spellType); - } - file.WriteLE(myPlayer._pRSpell); - file.WriteLE(myPlayer._pRSplType); -} - static void LoadMatchingItems(LoadHelper *file, const int n, ItemStruct *pItem) { ItemStruct tempItem; @@ -1045,31 +863,6 @@ static void LoadMatchingItems(LoadHelper *file, const int n, ItemStruct *pItem) } } -void LoadHeroItems(PlayerStruct &player) -{ - LoadHelper file("heroitems"); - if (!file.IsValid()) - return; - - gbIsHellfireSaveGame = file.NextBool8(); - - LoadMatchingItems(&file, NUM_INVLOC, player.InvBody); - LoadMatchingItems(&file, NUM_INV_GRID_ELEM, player.InvList); - LoadMatchingItems(&file, MAXBELTITEMS, player.SpdList); - - gbIsHellfireSaveGame = gbIsHellfire; -} - -void RemoveEmptyInventory(PlayerStruct &player) -{ - for (int i = NUM_INV_GRID_ELEM; i > 0; i--) { - int idx = player.InvGrid[i - 1]; - if (idx > 0 && player.InvList[idx - 1].isEmpty()) { - player.RemoveInvItem(idx - 1); - } - } -} - void RemoveEmptyLevelItems() { for (int i = ActiveItemCount; i > 0; i--) { @@ -1081,219 +874,17 @@ void RemoveEmptyLevelItems() } } -/** - * @brief Load game state - * @param firstflag Can be set to false if we are simply reloading the current game - */ -void LoadGame(bool firstflag) +static void SaveItem(SaveHelper *file, ItemStruct *pItem) { - FreeGameMem(); - pfile_remove_temp_files(); - - LoadHelper file("game"); - if (!file.IsValid()) - app_fatal("%s", _("Unable to open save file archive")); - - if (!IsHeaderValid(file.NextLE())) - app_fatal("%s", _("Invalid save file")); - - if (gbIsHellfireSaveGame) { - giNumberOfLevels = 25; - giNumberQuests = 24; - giNumberOfSmithPremiumItems = 15; - } else { - // Todo initialize additional levels and quests if we are running Hellfire - giNumberOfLevels = 17; - giNumberQuests = 16; - giNumberOfSmithPremiumItems = 6; - } - - setlevel = file.NextBool8(); - setlvlnum = static_cast<_setlevels>(file.NextBE()); - currlevel = file.NextBE(); - leveltype = static_cast(file.NextBE()); - if (!setlevel) - leveltype = gnLevelTypeTbl[currlevel]; - int viewX = file.NextBE(); - int viewY = file.NextBE(); - invflag = file.NextBool8(); - chrflag = file.NextBool8(); - int tmpNummonsters = file.NextBE(); - int tmpNumitems = file.NextBE(); - int tmpNummissiles = file.NextBE(); - int tmpNobjects = file.NextBE(); - - if (!gbIsHellfire && currlevel > 17) - app_fatal("%s", _("Player is on a Hellfire only level")); - - for (uint8_t i = 0; i < giNumberOfLevels; i++) { - glSeedTbl[i] = file.NextBE(); - file.Skip(4); // Skip loading gnLevelTypeTbl - } - - LoadPlayer(&file, MyPlayerId); - - sgGameInitInfo.nDifficulty = Players[MyPlayerId].pDifficulty; - if (sgGameInitInfo.nDifficulty < DIFF_NORMAL || sgGameInitInfo.nDifficulty > DIFF_HELL) - sgGameInitInfo.nDifficulty = DIFF_NORMAL; - - for (int i = 0; i < giNumberQuests; i++) - LoadQuest(&file, i); - for (int i = 0; i < MAXPORTAL; i++) - LoadPortal(&file, i); - - if (gbIsHellfireSaveGame != gbIsHellfire) { - ConvertLevels(); - RemoveEmptyInventory(Players[MyPlayerId]); - } - - LoadGameLevel(firstflag, ENTRY_LOAD); - SyncInitPlr(MyPlayerId); - SyncPlrAnim(MyPlayerId); - - ViewX = viewX; - ViewY = viewY; - ActiveMonsterCount = tmpNummonsters; - ActiveItemCount = tmpNumitems; - ActiveMissileCount = tmpNummissiles; - ActiveObjectCount = tmpNobjects; - - for (int &monstkill : MonsterKillCounts) - monstkill = file.NextBE(); - - if (leveltype != DTYPE_TOWN) { - for (int &monsterId : ActiveMonsters) - monsterId = file.NextBE(); - for (int i = 0; i < ActiveMonsterCount; i++) - LoadMonster(&file, ActiveMonsters[i]); - for (int &missileId : ActiveMissiles) - missileId = file.NextLE(); - for (int &missileId : AvailableMissiles) - missileId = file.NextLE(); - for (int i = 0; i < ActiveMissileCount; i++) - LoadMissile(&file, ActiveMissiles[i]); - for (int &objectId : ActiveObjects) - objectId = file.NextLE(); - for (int &objectId : AvailableObjects) - objectId = file.NextLE(); - for (int i = 0; i < ActiveObjectCount; i++) - LoadObject(&file, ActiveObjects[i]); - for (int i = 0; i < ActiveObjectCount; i++) - SyncObjectAnim(Objects[ActiveObjects[i]]); - - ActiveLightCount = file.NextBE(); - - for (uint8_t &lightId : ActiveLights) - lightId = file.NextLE(); - for (int i = 0; i < ActiveLightCount; i++) - LoadLighting(&file, &Lights[ActiveLights[i]]); - - VisionId = file.NextBE(); - VisionCount = file.NextBE(); - - for (int i = 0; i < VisionCount; i++) - LoadLighting(&file, &VisionList[i]); - } - - for (int &itemId : ActiveItems) - itemId = file.NextLE(); - for (int &itemId : AvailableItems) - itemId = file.NextLE(); - for (int i = 0; i < ActiveItemCount; i++) - LoadItem(&file, ActiveItems[i]); - for (bool &uniqueItemFlag : UniqueItemFlags) - uniqueItemFlag = file.NextBool8(); - - for (int j = 0; j < MAXDUNY; j++) { - for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) - dLight[i][j] = file.NextLE(); - } - for (int j = 0; j < MAXDUNY; j++) { - for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) - dFlags[i][j] = file.NextLE(); - } - for (int j = 0; j < MAXDUNY; j++) { - for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) - dPlayer[i][j] = file.NextLE(); - } - for (int j = 0; j < MAXDUNY; j++) { - for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) - dItem[i][j] = file.NextLE(); - } - - if (leveltype != DTYPE_TOWN) { - for (int j = 0; j < MAXDUNY; j++) { - for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) - dMonster[i][j] = file.NextBE(); - } - for (int j = 0; j < MAXDUNY; j++) { - for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) - dDead[i][j] = file.NextLE(); - } - for (int j = 0; j < MAXDUNY; j++) { - for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) - dObject[i][j] = file.NextLE(); - } - for (int j = 0; j < MAXDUNY; j++) { - for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) - dLight[i][j] = file.NextLE(); - } - for (int j = 0; j < MAXDUNY; j++) { - for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) - dPreLight[i][j] = file.NextLE(); - } - for (int j = 0; j < DMAXY; j++) { - for (int i = 0; i < DMAXX; i++) // NOLINT(modernize-loop-convert) - AutomapView[i][j] = file.NextBool8(); - } - for (int j = 0; j < MAXDUNY; j++) { - for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) - dMissile[i][j] = file.NextLE(); - } - } - - numpremium = file.NextBE(); - premiumlevel = file.NextBE(); - - for (int i = 0; i < giNumberOfSmithPremiumItems; i++) - LoadPremium(&file, i); - if (gbIsHellfire && !gbIsHellfireSaveGame) - SpawnPremium(MyPlayerId); - - AutomapActive = file.NextBool8(); - AutoMapScale = file.NextBE(); - AutomapZoomReset(); - ResyncQuests(); - - if (leveltype != DTYPE_TOWN) - ProcessLightList(); - - RedoPlayerVision(); - ProcessVisionList(); - missiles_process_charge(); - ResetPal(); - NewCursor(CURSOR_HAND); - gbProcessPlayers = true; - - if (gbIsHellfireSaveGame != gbIsHellfire) { - RemoveEmptyLevelItems(); - SaveGame(); - } - - gbIsHellfireSaveGame = gbIsHellfire; -} - -static void SaveItem(SaveHelper *file, ItemStruct *pItem) -{ - auto idx = pItem->IDidx; - if (!gbIsHellfire) - idx = RemapItemIdxToDiablo(idx); - if (gbIsSpawn) - idx = RemapItemIdxToSpawn(idx); - int iType = pItem->_itype; - if (idx == -1) { - idx = _item_indexes::IDI_GOLD; - iType = ITYPE_NONE; + auto idx = pItem->IDidx; + if (!gbIsHellfire) + idx = RemapItemIdxToDiablo(idx); + if (gbIsSpawn) + idx = RemapItemIdxToSpawn(idx); + int iType = pItem->_itype; + if (idx == -1) { + idx = _item_indexes::IDI_GOLD; + iType = ITYPE_NONE; } file->WriteLE(pItem->_iSeed); @@ -1856,6 +1447,416 @@ static void SavePortal(SaveHelper *file, int i) const int DiabloItemSaveSize = 368; const int HellfireItemSaveSize = 372; +} // namespace + +void RemoveInvalidItem(ItemStruct *pItem) +{ + bool isInvalid = !IsItemAvailable(pItem->IDidx) || !IsUniqueAvailable(pItem->_iUid); + + if (!gbIsHellfire) { + isInvalid = isInvalid || (pItem->_itype == ITYPE_STAFF && GetSpellStaffLevel(pItem->_iSpell) == -1); + isInvalid = isInvalid || (pItem->_iMiscId == IMISC_BOOK && GetSpellBookLevel(pItem->_iSpell) == -1); + isInvalid = isInvalid || pItem->_iDamAcFlags != 0; + isInvalid = isInvalid || pItem->_iPrePower > IDI_LASTDIABLO; + isInvalid = isInvalid || pItem->_iSufPower > IDI_LASTDIABLO; + } + + if (isInvalid) { + pItem->_itype = ITYPE_NONE; + } +} + +_item_indexes RemapItemIdxFromDiablo(_item_indexes i) +{ + constexpr auto GetItemIdValue = [](int i) -> int { + if (i == IDI_SORCERER) { + return 166; + } + if (i >= 156) { + i += 5; // Hellfire exclusive items + } + if (i >= 88) { + i += 1; // Scroll of Search + } + if (i >= 83) { + i += 4; // Oils + } + + return i; + }; + + return static_cast<_item_indexes>(GetItemIdValue(i)); +} + +_item_indexes RemapItemIdxToDiablo(_item_indexes i) +{ + constexpr auto GetItemIdValue = [](int i) -> int { + if (i == 166) { + return IDI_SORCERER; + } + if ((i >= 83 && i <= 86) || i == 92 || i >= 161) { + return -1; // Hellfire exclusive items + } + if (i >= 93) { + i -= 1; // Scroll of Search + } + if (i >= 87) { + i -= 4; // Oils + } + + return i; + }; + + return static_cast<_item_indexes>(GetItemIdValue(i)); +} + +_item_indexes RemapItemIdxFromSpawn(_item_indexes i) +{ + constexpr auto GetItemIdValue = [](int i) { + if (i >= 62) { + i += 9; // Medium and heavy armors + } + if (i >= 96) { + i += 1; // Scroll of Stone Curse + } + if (i >= 98) { + i += 1; // Scroll of Guardian + } + if (i >= 99) { + i += 1; // Scroll of ... + } + if (i >= 101) { + i += 1; // Scroll of Golem + } + if (i >= 102) { + i += 1; // Scroll of None + } + if (i >= 104) { + i += 1; // Scroll of Apocalypse + } + + return i; + }; + + return static_cast<_item_indexes>(GetItemIdValue(i)); +} + +_item_indexes RemapItemIdxToSpawn(_item_indexes i) +{ + constexpr auto GetItemIdValue = [](int i) { + if (i >= 104) { + i -= 1; // Scroll of Apocalypse + } + if (i >= 102) { + i -= 1; // Scroll of None + } + if (i >= 101) { + i -= 1; // Scroll of Golem + } + if (i >= 99) { + i -= 1; // Scroll of ... + } + if (i >= 98) { + i -= 1; // Scroll of Guardian + } + if (i >= 96) { + i -= 1; // Scroll of Stone Curse + } + if (i >= 71) { + i -= 9; // Medium and heavy armors + } + + return i; + }; + + return static_cast<_item_indexes>(GetItemIdValue(i)); +} + +bool IsHeaderValid(uint32_t magicNumber) +{ + gbIsHellfireSaveGame = false; + if (magicNumber == LoadLE32("SHAR")) { + return true; + } + if (magicNumber == LoadLE32("SHLF")) { + gbIsHellfireSaveGame = true; + return true; + } + if (!gbIsSpawn && magicNumber == LoadLE32("RETL")) { + return true; + } + if (!gbIsSpawn && magicNumber == LoadLE32("HELF")) { + gbIsHellfireSaveGame = true; + return true; + } + + return false; +} + +void LoadHotkeys() +{ + LoadHelper file("hotkeys"); + if (!file.IsValid()) + return; + + auto &myPlayer = Players[MyPlayerId]; + + for (auto &spellId : myPlayer._pSplHotKey) { + spellId = static_cast(file.NextLE()); + } + for (auto &spellType : myPlayer._pSplTHotKey) { + spellType = static_cast(file.NextLE()); + } + myPlayer._pRSpell = static_cast(file.NextLE()); + myPlayer._pRSplType = static_cast(file.NextLE()); +} + +void SaveHotkeys() +{ + auto &myPlayer = Players[MyPlayerId]; + + const size_t nHotkeyTypes = sizeof(myPlayer._pSplHotKey) / sizeof(myPlayer._pSplHotKey[0]); + const size_t nHotkeySpells = sizeof(myPlayer._pSplTHotKey) / sizeof(myPlayer._pSplTHotKey[0]); + + SaveHelper file("hotkeys", (nHotkeyTypes * 4) + nHotkeySpells + 4 + 1); + + for (auto &spellId : myPlayer._pSplHotKey) { + file.WriteLE(spellId); + } + for (auto &spellType : myPlayer._pSplTHotKey) { + file.WriteLE(spellType); + } + file.WriteLE(myPlayer._pRSpell); + file.WriteLE(myPlayer._pRSplType); +} + +void LoadHeroItems(PlayerStruct &player) +{ + LoadHelper file("heroitems"); + if (!file.IsValid()) + return; + + gbIsHellfireSaveGame = file.NextBool8(); + + LoadMatchingItems(&file, NUM_INVLOC, player.InvBody); + LoadMatchingItems(&file, NUM_INV_GRID_ELEM, player.InvList); + LoadMatchingItems(&file, MAXBELTITEMS, player.SpdList); + + gbIsHellfireSaveGame = gbIsHellfire; +} + +void RemoveEmptyInventory(PlayerStruct &player) +{ + for (int i = NUM_INV_GRID_ELEM; i > 0; i--) { + int idx = player.InvGrid[i - 1]; + if (idx > 0 && player.InvList[idx - 1].isEmpty()) { + player.RemoveInvItem(idx - 1); + } + } +} + +/** + * @brief Load game state + * @param firstflag Can be set to false if we are simply reloading the current game + */ +void LoadGame(bool firstflag) +{ + FreeGameMem(); + pfile_remove_temp_files(); + + LoadHelper file("game"); + if (!file.IsValid()) + app_fatal("%s", _("Unable to open save file archive")); + + if (!IsHeaderValid(file.NextLE())) + app_fatal("%s", _("Invalid save file")); + + if (gbIsHellfireSaveGame) { + giNumberOfLevels = 25; + giNumberQuests = 24; + giNumberOfSmithPremiumItems = 15; + } else { + // Todo initialize additional levels and quests if we are running Hellfire + giNumberOfLevels = 17; + giNumberQuests = 16; + giNumberOfSmithPremiumItems = 6; + } + + setlevel = file.NextBool8(); + setlvlnum = static_cast<_setlevels>(file.NextBE()); + currlevel = file.NextBE(); + leveltype = static_cast(file.NextBE()); + if (!setlevel) + leveltype = gnLevelTypeTbl[currlevel]; + int viewX = file.NextBE(); + int viewY = file.NextBE(); + invflag = file.NextBool8(); + chrflag = file.NextBool8(); + int tmpNummonsters = file.NextBE(); + int tmpNumitems = file.NextBE(); + int tmpNummissiles = file.NextBE(); + int tmpNobjects = file.NextBE(); + + if (!gbIsHellfire && currlevel > 17) + app_fatal("%s", _("Player is on a Hellfire only level")); + + for (uint8_t i = 0; i < giNumberOfLevels; i++) { + glSeedTbl[i] = file.NextBE(); + file.Skip(4); // Skip loading gnLevelTypeTbl + } + + LoadPlayer(&file, MyPlayerId); + + sgGameInitInfo.nDifficulty = Players[MyPlayerId].pDifficulty; + if (sgGameInitInfo.nDifficulty < DIFF_NORMAL || sgGameInitInfo.nDifficulty > DIFF_HELL) + sgGameInitInfo.nDifficulty = DIFF_NORMAL; + + for (int i = 0; i < giNumberQuests; i++) + LoadQuest(&file, i); + for (int i = 0; i < MAXPORTAL; i++) + LoadPortal(&file, i); + + if (gbIsHellfireSaveGame != gbIsHellfire) { + ConvertLevels(); + RemoveEmptyInventory(Players[MyPlayerId]); + } + + LoadGameLevel(firstflag, ENTRY_LOAD); + SyncInitPlr(MyPlayerId); + SyncPlrAnim(MyPlayerId); + + ViewX = viewX; + ViewY = viewY; + ActiveMonsterCount = tmpNummonsters; + ActiveItemCount = tmpNumitems; + ActiveMissileCount = tmpNummissiles; + ActiveObjectCount = tmpNobjects; + + for (int &monstkill : MonsterKillCounts) + monstkill = file.NextBE(); + + if (leveltype != DTYPE_TOWN) { + for (int &monsterId : ActiveMonsters) + monsterId = file.NextBE(); + for (int i = 0; i < ActiveMonsterCount; i++) + LoadMonster(&file, ActiveMonsters[i]); + for (int &missileId : ActiveMissiles) + missileId = file.NextLE(); + for (int &missileId : AvailableMissiles) + missileId = file.NextLE(); + for (int i = 0; i < ActiveMissileCount; i++) + LoadMissile(&file, ActiveMissiles[i]); + for (int &objectId : ActiveObjects) + objectId = file.NextLE(); + for (int &objectId : AvailableObjects) + objectId = file.NextLE(); + for (int i = 0; i < ActiveObjectCount; i++) + LoadObject(&file, ActiveObjects[i]); + for (int i = 0; i < ActiveObjectCount; i++) + SyncObjectAnim(Objects[ActiveObjects[i]]); + + ActiveLightCount = file.NextBE(); + + for (uint8_t &lightId : ActiveLights) + lightId = file.NextLE(); + for (int i = 0; i < ActiveLightCount; i++) + LoadLighting(&file, &Lights[ActiveLights[i]]); + + VisionId = file.NextBE(); + VisionCount = file.NextBE(); + + for (int i = 0; i < VisionCount; i++) + LoadLighting(&file, &VisionList[i]); + } + + for (int &itemId : ActiveItems) + itemId = file.NextLE(); + for (int &itemId : AvailableItems) + itemId = file.NextLE(); + for (int i = 0; i < ActiveItemCount; i++) + LoadItem(&file, ActiveItems[i]); + for (bool &uniqueItemFlag : UniqueItemFlags) + uniqueItemFlag = file.NextBool8(); + + for (int j = 0; j < MAXDUNY; j++) { + for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) + dLight[i][j] = file.NextLE(); + } + for (int j = 0; j < MAXDUNY; j++) { + for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) + dFlags[i][j] = file.NextLE(); + } + for (int j = 0; j < MAXDUNY; j++) { + for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) + dPlayer[i][j] = file.NextLE(); + } + for (int j = 0; j < MAXDUNY; j++) { + for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) + dItem[i][j] = file.NextLE(); + } + + if (leveltype != DTYPE_TOWN) { + for (int j = 0; j < MAXDUNY; j++) { + for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) + dMonster[i][j] = file.NextBE(); + } + for (int j = 0; j < MAXDUNY; j++) { + for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) + dDead[i][j] = file.NextLE(); + } + for (int j = 0; j < MAXDUNY; j++) { + for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) + dObject[i][j] = file.NextLE(); + } + for (int j = 0; j < MAXDUNY; j++) { + for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) + dLight[i][j] = file.NextLE(); + } + for (int j = 0; j < MAXDUNY; j++) { + for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) + dPreLight[i][j] = file.NextLE(); + } + for (int j = 0; j < DMAXY; j++) { + for (int i = 0; i < DMAXX; i++) // NOLINT(modernize-loop-convert) + AutomapView[i][j] = file.NextBool8(); + } + for (int j = 0; j < MAXDUNY; j++) { + for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) + dMissile[i][j] = file.NextLE(); + } + } + + numpremium = file.NextBE(); + premiumlevel = file.NextBE(); + + for (int i = 0; i < giNumberOfSmithPremiumItems; i++) + LoadPremium(&file, i); + if (gbIsHellfire && !gbIsHellfireSaveGame) + SpawnPremium(MyPlayerId); + + AutomapActive = file.NextBool8(); + AutoMapScale = file.NextBE(); + AutomapZoomReset(); + ResyncQuests(); + + if (leveltype != DTYPE_TOWN) + ProcessLightList(); + + RedoPlayerVision(); + ProcessVisionList(); + missiles_process_charge(); + ResetPal(); + NewCursor(CURSOR_HAND); + gbProcessPlayers = true; + + if (gbIsHellfireSaveGame != gbIsHellfire) { + RemoveEmptyLevelItems(); + SaveGame(); + } + + gbIsHellfireSaveGame = gbIsHellfire; +} + void SaveHeroItems(PlayerStruct &player) { size_t itemCount = NUM_INVLOC + NUM_INV_GRID_ELEM + MAXBELTITEMS; diff --git a/Source/mainmenu.cpp b/Source/mainmenu.cpp index 190737606..52341a336 100644 --- a/Source/mainmenu.cpp +++ b/Source/mainmenu.cpp @@ -16,7 +16,7 @@ namespace devilution { char gszHero[16]; -/* data */ +namespace { /** The active music track id for the main menu. */ uint8_t menu_music_track_id = TMUSIC_INTRO; @@ -76,6 +76,8 @@ static void MainmenuPlayIntro() mainmenu_refresh_music(); } +} // namespace + bool mainmenu_select_hero_dialog(GameData *gameData) { _selhero_selections dlgresult = SELHERO_NEW_DUNGEON; diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 5e401872d..f50b2668e 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -25,8 +25,11 @@ int ActiveMissiles[MAXMISSILES]; int AvailableMissiles[MAXMISSILES]; MissileStruct Missiles[MAXMISSILES]; int ActiveMissileCount; -ChainStruct chain[MAXMISSILES]; bool MissilePreFlag; + +namespace { + +ChainStruct chain[MAXMISSILES]; int numchains; const int CrawlNum[19] = { 0, 3, 12, 45, 94, 159, 240, 337, 450, 579, 724, 885, 1062, 1255, 1464, 1689, 1930, 2187, 2460 }; @@ -65,128 +68,6 @@ int GenerateRndSum(int range, int iterations) return value; } -void GetDamageAmt(int i, int *mind, int *maxd) -{ - assert(MyPlayerId >= 0 && MyPlayerId < MAX_PLRS); - assert(i >= 0 && i < 64); - - auto &myPlayer = Players[MyPlayerId]; - - int sl = myPlayer._pSplLvl[i] + myPlayer._pISplLvlAdd; - - switch (i) { - case SPL_FIREBOLT: - *mind = (myPlayer._pMagic / 8) + sl + 1; - *maxd = *mind + 9; - break; - case SPL_HEAL: - case SPL_HEALOTHER: - /// BUGFIX: healing calculation is unused - *mind = AddClassHealingBonus(myPlayer._pLevel + sl + 1, myPlayer._pClass) - 1; - *maxd = AddClassHealingBonus((4 * myPlayer._pLevel) + (6 * sl) + 10, myPlayer._pClass) - 1; - break; - case SPL_LIGHTNING: - case SPL_RUNELIGHT: - *mind = 2; - *maxd = 2 + myPlayer._pLevel; - break; - case SPL_FLASH: - *mind = ScaleSpellEffect(myPlayer._pLevel, sl); - *mind += *mind / 2; - *maxd = *mind * 2; - break; - case SPL_IDENTIFY: - case SPL_TOWN: - case SPL_STONE: - case SPL_INFRA: - case SPL_RNDTELEPORT: - case SPL_MANASHIELD: - case SPL_DOOMSERP: - case SPL_BLODRIT: - case SPL_INVISIBIL: - case SPL_BLODBOIL: - case SPL_TELEPORT: - case SPL_ETHEREALIZE: - case SPL_REPAIR: - case SPL_RECHARGE: - case SPL_DISARM: - case SPL_RESURRECT: - case SPL_TELEKINESIS: - case SPL_BONESPIRIT: - case SPL_WARP: - case SPL_REFLECT: - case SPL_BERSERK: - case SPL_SEARCH: - case SPL_RUNESTONE: - *mind = -1; - *maxd = -1; - break; - case SPL_FIREWALL: - case SPL_LIGHTWALL: - case SPL_FIRERING: - *mind = 2 * myPlayer._pLevel + 4; - *maxd = *mind + 36; - break; - case SPL_FIREBALL: - case SPL_RUNEFIRE: { - int base = (2 * myPlayer._pLevel) + 4; - *mind = ScaleSpellEffect(base, sl); - *maxd = ScaleSpellEffect(base + 36, sl); - } break; - case SPL_GUARDIAN: { - int base = (myPlayer._pLevel / 2) + 1; - *mind = ScaleSpellEffect(base, sl); - *maxd = ScaleSpellEffect(base + 9, sl); - } break; - case SPL_CHAIN: - *mind = 4; - *maxd = 4 + (2 * myPlayer._pLevel); - break; - case SPL_WAVE: - *mind = 6 * (myPlayer._pLevel + 1); - *maxd = *mind + 54; - break; - case SPL_NOVA: - case SPL_IMMOLAT: - case SPL_RUNEIMMOLAT: - case SPL_RUNENOVA: - *mind = ScaleSpellEffect((myPlayer._pLevel + 5) / 2, sl) * 5; - *maxd = ScaleSpellEffect((myPlayer._pLevel + 30) / 2, sl) * 5; - break; - case SPL_FLAME: - *mind = 3; - *maxd = myPlayer._pLevel + 4; - *maxd += *maxd / 2; - break; - case SPL_GOLEM: - *mind = 11; - *maxd = 17; - break; - case SPL_APOCA: - *mind = myPlayer._pLevel; - *maxd = *mind * 6; - break; - case SPL_ELEMENT: - *mind = ScaleSpellEffect(2 * myPlayer._pLevel + 4, sl); - /// BUGFIX: add here '*mind /= 2;' - *maxd = ScaleSpellEffect(2 * myPlayer._pLevel + 40, sl); - /// BUGFIX: add here '*maxd /= 2;' - break; - case SPL_CBOLT: - *mind = 1; - *maxd = *mind + (myPlayer._pMagic / 4); - break; - case SPL_HBOLT: - *mind = myPlayer._pLevel + 9; - *maxd = *mind + 9; - break; - case SPL_FLARE: - *mind = (myPlayer._pMagic / 2) + 3 * sl - (myPlayer._pMagic / 8); - *maxd = *mind; - break; - } -} - static bool CheckBlock(Point from, Point to) { while (from != to) { @@ -224,16 +105,6 @@ static int FindClosest(Point source, int rad) return -1; } -int GetSpellLevel(int playerId, spell_id sn) -{ - auto &player = Players[playerId]; - - if (playerId != MyPlayerId) - return 1; // BUGFIX spell level will be wrong in multiplayer - - return std::max(player._pISplLvlAdd + player._pSplLvl[sn], 0); -} - constexpr Direction16 Direction16Flip(Direction16 x, Direction16 pivot) { unsigned ret = (2 * pivot + 16 - x) % 16; @@ -241,76 +112,6 @@ constexpr Direction16 Direction16Flip(Direction16 x, Direction16 pivot) return static_cast(ret); } -/** - * @brief Returns the direction a vector from p1(x1, y1) to p2(x2, y2) is pointing to. - * - * W sW SW Sw S - * ^ - * nW | Se - * | - * NW ------+-----> SE - * | - * Nw | sE - * | - * N Ne NE nE E - * - * @param x1 the x coordinate of p1 - * @param y1 the y coordinate of p1 - * @param x2 the x coordinate of p2 - * @param y2 the y coordinate of p2 - * @return the direction of the p1->p2 vector - */ -Direction16 GetDirection16(Point p1, Point p2) -{ - Displacement offset = p2 - p1; - Displacement absolute = abs(offset); - - bool flipY = offset.deltaX != absolute.deltaX; - bool flipX = offset.deltaY != absolute.deltaY; - - bool flipMedian = false; - if (absolute.deltaX > absolute.deltaY) { - std::swap(absolute.deltaX, absolute.deltaY); - flipMedian = true; - } - - Direction16 ret = DIR16_S; - if (3 * absolute.deltaX <= (absolute.deltaY * 2)) { // mx/my <= 2/3, approximation of tan(33.75) - if (5 * absolute.deltaX < absolute.deltaY) // mx/my < 0.2, approximation of tan(11.25) - ret = DIR16_SW; - else - ret = DIR16_Sw; - } - - Direction16 medianPivot = DIR16_S; - if (flipY) { - ret = Direction16Flip(ret, DIR16_SW); - medianPivot = Direction16Flip(medianPivot, DIR16_SW); - } - if (flipX) { - ret = Direction16Flip(ret, DIR16_SE); - medianPivot = Direction16Flip(medianPivot, DIR16_SE); - } - if (flipMedian) - ret = Direction16Flip(ret, medianPivot); - return ret; -} - -void DeleteMissile(int mi, int i) -{ - if (Missiles[mi]._mitype == MIS_MANASHIELD) { - int src = Missiles[mi]._misource; - if (src == MyPlayerId) - NetSendCmd(true, CMD_REMSHIELD); - Players[src].pManaShield = false; - } - - AvailableMissiles[MAXMISSILES - ActiveMissileCount] = mi; - ActiveMissileCount--; - if (ActiveMissileCount > 0 && i != ActiveMissileCount) - ActiveMissiles[i] = ActiveMissiles[ActiveMissileCount]; -} - static void UpdateMissileVel(int i, Point source, Point destination, int v) { Missiles[i].position.velocity = { 0, 0 }; @@ -395,13 +196,12 @@ void MoveMissilePos(int i) } } -bool MonsterTrapHit(int m, int mindam, int maxdam, int dist, int t, bool shift) +bool MonsterMHit(int pnum, int m, int mindam, int maxdam, int dist, int t, bool shift) { bool resist = false; - if (Monsters[m].mtalkmsg != TEXT_NONE) { - return false; - } - if (Monsters[m]._mhitpoints >> 6 <= 0) { + if (Monsters[m].mtalkmsg != TEXT_NONE + || Monsters[m]._mhitpoints >> 6 <= 0 + || (t == MIS_HBOLT && Monsters[m].MType->mtype != MT_DIABLO && Monsters[m].MData->mMonstClass != MC_UNDEAD)) { return false; } if (Monsters[m].MType->mtype == MT_ILLWEAV && Monsters[m]._mgoal == MGOAL_RETREAT) @@ -409,98 +209,21 @@ bool MonsterTrapHit(int m, int mindam, int maxdam, int dist, int t, bool shift) if (Monsters[m]._mmode == MM_CHARGE) return false; + uint8_t mor = Monsters[m].mMagicRes; missile_resistance mir = MissileData[t].mResist; - int mor = Monsters[m].mMagicRes; + if (((mor & IMMUNE_MAGIC) != 0 && mir == MISR_MAGIC) || ((mor & IMMUNE_FIRE) != 0 && mir == MISR_FIRE) - || ((mor & IMMUNE_LIGHTNING) != 0 && mir == MISR_LIGHTNING)) { + || ((mor & IMMUNE_LIGHTNING) != 0 && mir == MISR_LIGHTNING) + || ((mor & IMMUNE_ACID) != 0 && mir == MISR_ACID)) return false; - } if (((mor & RESIST_MAGIC) != 0 && mir == MISR_MAGIC) || ((mor & RESIST_FIRE) != 0 && mir == MISR_FIRE) - || ((mor & RESIST_LIGHTNING) != 0 && mir == MISR_LIGHTNING)) { - resist = true; - } - - int hit = GenerateRnd(100); - int hper = 90 - (BYTE)Monsters[m].mArmorClass - dist; - if (hper < 5) - hper = 5; - if (hper > 95) - hper = 95; - bool ret; - if (CheckMonsterHit(m, &ret)) { - return ret; - } -#ifdef _DEBUG - if (hit < hper || debug_mode_dollar_sign || debug_mode_key_inverted_v || Monsters[m]._mmode == MM_STONE) { -#else - if (hit < hper || Monsters[m]._mmode == MM_STONE) { -#endif - int dam = mindam + GenerateRnd(maxdam - mindam + 1); - if (!shift) - dam <<= 6; - if (resist) - Monsters[m]._mhitpoints -= dam / 4; - else - Monsters[m]._mhitpoints -= dam; -#ifdef _DEBUG - if (debug_mode_dollar_sign || debug_mode_key_inverted_v) - Monsters[m]._mhitpoints = 0; -#endif - if (Monsters[m]._mhitpoints >> 6 <= 0) { - if (Monsters[m]._mmode == MM_STONE) { - M_StartKill(m, -1); - Monsters[m].Petrify(); - } else { - M_StartKill(m, -1); - } - } else { - if (resist) { - PlayEffect(m, 1); - } else if (Monsters[m]._mmode == MM_STONE) { - if (m > MAX_PLRS - 1) - M_StartHit(m, -1, dam); - Monsters[m].Petrify(); - } else { - if (m > MAX_PLRS - 1) - M_StartHit(m, -1, dam); - } - } - return true; - } - return false; -} - -bool MonsterMHit(int pnum, int m, int mindam, int maxdam, int dist, int t, bool shift) -{ - bool resist = false; - if (Monsters[m].mtalkmsg != TEXT_NONE - || Monsters[m]._mhitpoints >> 6 <= 0 - || (t == MIS_HBOLT && Monsters[m].MType->mtype != MT_DIABLO && Monsters[m].MData->mMonstClass != MC_UNDEAD)) { - return false; - } - if (Monsters[m].MType->mtype == MT_ILLWEAV && Monsters[m]._mgoal == MGOAL_RETREAT) - return false; - if (Monsters[m]._mmode == MM_CHARGE) - return false; - - uint8_t mor = Monsters[m].mMagicRes; - missile_resistance mir = MissileData[t].mResist; - - if (((mor & IMMUNE_MAGIC) != 0 && mir == MISR_MAGIC) - || ((mor & IMMUNE_FIRE) != 0 && mir == MISR_FIRE) - || ((mor & IMMUNE_LIGHTNING) != 0 && mir == MISR_LIGHTNING) - || ((mor & IMMUNE_ACID) != 0 && mir == MISR_ACID)) - return false; - - if (((mor & RESIST_MAGIC) != 0 && mir == MISR_MAGIC) - || ((mor & RESIST_FIRE) != 0 && mir == MISR_FIRE) - || ((mor & RESIST_LIGHTNING) != 0 && mir == MISR_LIGHTNING)) - resist = true; - - if (gbIsHellfire && t == MIS_HBOLT && (Monsters[m].MType->mtype == MT_DIABLO || Monsters[m].MType->mtype == MT_BONEDEMN)) + || ((mor & RESIST_LIGHTNING) != 0 && mir == MISR_LIGHTNING)) + resist = true; + + if (gbIsHellfire && t == MIS_HBOLT && (Monsters[m].MType->mtype == MT_DIABLO || Monsters[m].MType->mtype == MT_BONEDEMN)) resist = true; int hit = GenerateRnd(100); @@ -605,150 +328,6 @@ bool MonsterMHit(int pnum, int m, int mindam, int maxdam, int dist, int t, bool return true; } -bool PlayerMHit(int pnum, int m, int dist, int mind, int maxd, int mtype, bool shift, int earflag, bool *blocked) -{ - *blocked = false; - - auto &player = Players[pnum]; - - if (player._pHitPoints >> 6 <= 0) { - return false; - } - - if (player._pInvincible) { - return false; - } - - if ((player._pSpellFlags & 1) != 0 && MissileData[mtype].mType == 0) { - return false; - } - - int hit = GenerateRnd(100); -#ifdef _DEBUG - if (debug_mode_dollar_sign || debug_mode_key_inverted_v) - hit = 1000; -#endif - int hper = 40; - if (MissileData[mtype].mType == 0) { - int tac = player._pIAC + player._pIBonusAC + player._pDexterity / 5; - if (m != -1) { - hper = Monsters[m].mHit - + ((Monsters[m].mLevel - player._pLevel) * 2) - + 30 - - (dist * 2) - tac; - } else { - hper = 100 - (tac / 2) - (dist * 2); - } - } else if (m != -1) { - hper += (Monsters[m].mLevel * 2) - (player._pLevel * 2) - (dist * 2); - } - - if (hper < 10) - hper = 10; - if (currlevel == 14 && hper < 20) { - hper = 20; - } - if (currlevel == 15 && hper < 25) { - hper = 25; - } - if (currlevel == 16 && hper < 30) { - hper = 30; - } - - int blk = 100; - if ((player._pmode == PM_STAND || player._pmode == PM_ATTACK) && player._pBlockFlag) { - blk = GenerateRnd(100); - } - - if (shift) - blk = 100; - if (mtype == MIS_ACIDPUD) - blk = 100; - - int blkper = player._pBaseToBlk + player._pDexterity; - if (m != -1) - blkper -= (Monsters[m].mLevel - player._pLevel) * 2; - - if (blkper < 0) - blkper = 0; - if (blkper > 100) - blkper = 100; - - int8_t resper; - switch (MissileData[mtype].mResist) { - case MISR_FIRE: - resper = player._pFireResist; - break; - case MISR_LIGHTNING: - resper = player._pLghtResist; - break; - case MISR_MAGIC: - case MISR_ACID: - resper = player._pMagResist; - break; - default: - resper = 0; - break; - } - - if (hit >= hper) { - return false; - } - - int dam; - if (mtype == MIS_BONESPIRIT) { - dam = player._pHitPoints / 3; - } else { - if (!shift) { - dam = (mind << 6) + GenerateRnd((maxd - mind + 1) << 6); - if (m == -1) - if ((player._pIFlags & ISPL_ABSHALFTRAP) != 0) - dam /= 2; - dam += (player._pIGetHit << 6); - } else { - dam = mind + GenerateRnd(maxd - mind + 1); - if (m == -1) - if ((player._pIFlags & ISPL_ABSHALFTRAP) != 0) - dam /= 2; - dam += player._pIGetHit; - } - - dam = std::max(dam, 64); - } - - if ((resper <= 0 || gbIsHellfire) && blk < blkper) { - Direction dir = player._pdir; - if (m != -1) { - dir = GetDirection(player.position.tile, Monsters[m].position.tile); - } - *blocked = true; - StartPlrBlock(pnum, dir); - return true; - } - - if (resper > 0) { - dam -= dam * resper / 100; - if (pnum == MyPlayerId) { - ApplyPlrDamage(pnum, 0, 0, dam, earflag); - } - - if (player._pHitPoints >> 6 > 0) { - player.Say(HeroSpeech::ArghClang); - } - return true; - } - - if (pnum == MyPlayerId) { - ApplyPlrDamage(pnum, 0, 0, dam, earflag); - } - - if (player._pHitPoints >> 6 > 0) { - StartPlrHit(pnum, dam, false); - } - - return true; -} - bool Plr2PlrMHit(int pnum, int p, int mindam, int maxdam, int dist, int mtype, bool shift, bool *blocked) { if (sgGameInitInfo.bFriendlyFire == 0 && gbFriendlyMode) @@ -1012,42 +591,643 @@ void CheckMissileCol(int i, int mindam, int maxdam, bool shift, Point position, } } } - if (dObject[mx][my] != 0) { - int oi = dObject[mx][my] > 0 ? dObject[mx][my] - 1 : -(dObject[mx][my] + 1); - if (!Objects[oi]._oMissFlag) { - if (Objects[oi]._oBreak == 1) - BreakObject(-1, oi); - if (!nodel) - Missiles[i]._mirange = 0; - Missiles[i]._miHitFlag = false; + if (dObject[mx][my] != 0) { + int oi = dObject[mx][my] > 0 ? dObject[mx][my] - 1 : -(dObject[mx][my] + 1); + if (!Objects[oi]._oMissFlag) { + if (Objects[oi]._oBreak == 1) + BreakObject(-1, oi); + if (!nodel) + Missiles[i]._mirange = 0; + Missiles[i]._miHitFlag = false; + } + } + if (nMissileTable[dPiece[mx][my]]) { + if (!nodel) + Missiles[i]._mirange = 0; + Missiles[i]._miHitFlag = false; + } + if (Missiles[i]._mirange == 0 && MissileData[Missiles[i]._mitype].miSFX != -1) + PlaySfxLoc(MissileData[Missiles[i]._mitype].miSFX, Missiles[i].position.tile); +} + +void SetMissAnim(int mi, int animtype) +{ + int dir = Missiles[mi]._mimfnum; + + if (animtype > MFILE_NONE) { + animtype = MFILE_NONE; + } + + Missiles[mi]._miAnimType = animtype; + Missiles[mi]._miAnimFlags = MissileSpriteData[animtype].mFlags; + Missiles[mi]._miAnimData = MissileSpriteData[animtype].mAnimData[dir]; + Missiles[mi]._miAnimDelay = MissileSpriteData[animtype].mAnimDelay[dir]; + Missiles[mi]._miAnimLen = MissileSpriteData[animtype].mAnimLen[dir]; + Missiles[mi]._miAnimWidth = MissileSpriteData[animtype].mAnimWidth[dir]; + Missiles[mi]._miAnimWidth2 = MissileSpriteData[animtype].mAnimWidth2[dir]; + Missiles[mi]._miAnimCnt = 0; + Missiles[mi]._miAnimFrame = 1; +} + +void FreeMissileGFX(int mi) +{ + if ((MissileSpriteData[mi].mFlags & MFLAG_ALLOW_SPECIAL) != 0) { + if (MissileSpriteData[mi].mAnimData[0] != nullptr) { + auto *p = (DWORD *)MissileSpriteData[mi].mAnimData[0]; + p -= MissileSpriteData[mi].mAnimFAmt; + delete[] p; + MissileSpriteData[mi].mAnimData[0] = nullptr; + } + return; + } + + for (int i = 0; i < MissileSpriteData[mi].mAnimFAmt; i++) { + if (MissileSpriteData[mi].mAnimData[i] != nullptr) { + delete[] MissileSpriteData[mi].mAnimData[i]; + MissileSpriteData[mi].mAnimData[i] = nullptr; + } + } +} + +static bool MissilesFoundTarget(int mi, Point *position, int rad) +{ + rad = std::min(rad, 19); + + for (int i = 0; i < rad; i++) { + int k = CrawlNum[i]; + int ck = k + 2; + for (auto j = static_cast(CrawlTable[k]); j > 0; j--, ck += 2) { + int tx = position->x + CrawlTable[ck - 1]; + int ty = position->y + CrawlTable[ck]; + if (!InDungeonBounds({ tx, ty })) + continue; + + int dp = dPiece[tx][ty]; + if (nSolidTable[dp] || dObject[tx][ty] != 0 || dMissile[tx][ty] != 0) + continue; + + Missiles[mi].position.tile = { tx, ty }; + *position = { tx, ty }; + return true; + } + } + return false; +} + +bool CheckIfTrig(Point position) +{ + for (int i = 0; i < numtrigs; i++) { + if (trigs[i].position.WalkingDistance(position) < 2) + return true; + } + return false; +} + +static int Sentfire(int i, Point src) +{ + int ex = 0; + if (LineClearMissile(Missiles[i].position.tile, src)) { + if (dMonster[src.x][src.y] > 0 && Monsters[dMonster[src.x][src.y] - 1]._mhitpoints >> 6 > 0 && dMonster[src.x][src.y] - 1 > MAX_PLRS - 1) { + Direction dir = GetDirection(Missiles[i].position.tile, src); + Missiles[i]._miVar3 = AvailableMissiles[0]; + AddMissile(Missiles[i].position.tile, src, dir, MIS_FIREBOLT, TARGET_MONSTERS, Missiles[i]._misource, Missiles[i]._midam, GetSpellLevel(Missiles[i]._misource, SPL_FIREBOLT)); + ex = -1; + } + } + if (ex == -1) { + SetMissDir(i, 2); + Missiles[i]._miVar2 = 3; + } + + return ex; +} + +static void FireballUpdate(int i, Displacement offset, bool alwaysDelete) +{ + Missiles[i]._mirange--; + + int id = Missiles[i]._misource; + Point p = (Missiles[i]._micaster == TARGET_MONSTERS) ? Players[id].position.tile : Monsters[id].position.tile; + + if (Missiles[i]._miAnimType == MFILE_BIGEXP) { + if (Missiles[i]._mirange == 0) { + Missiles[i]._miDelFlag = true; + AddUnLight(Missiles[i]._mlid); + } + } else { + int dam = Missiles[i]._midam; + Missiles[i].position.traveled += offset; + UpdateMissilePos(i); + if (Missiles[i].position.tile != Missiles[i].position.start) + CheckMissileCol(i, dam, dam, false, Missiles[i].position.tile, false); + if (Missiles[i]._mirange == 0) { + Point m = Missiles[i].position.tile; + ChangeLight(Missiles[i]._mlid, Missiles[i].position.tile, Missiles[i]._miAnimFrame); + + constexpr Displacement Pattern[] = { { 0, 0 }, { 0, 1 }, { 0, -1 }, { 1, 0 }, { 1, -1 }, { 1, 1 }, { -1, 0 }, { -1, 1 }, { -1, -1 } }; + for (auto shift : Pattern) { + if (!CheckBlock(p, m + shift)) + CheckMissileCol(i, dam, dam, false, m + shift, true); + } + + if (!TransList[dTransVal[m.x][m.y]] + || (Missiles[i].position.velocity.deltaX < 0 && ((TransList[dTransVal[m.x][m.y + 1]] && nSolidTable[dPiece[m.x][m.y + 1]]) || (TransList[dTransVal[m.x][m.y - 1]] && nSolidTable[dPiece[m.x][m.y - 1]])))) { + Missiles[i].position.tile.x++; + Missiles[i].position.tile.y++; + Missiles[i].position.offset.deltaY -= 32; + } + if (Missiles[i].position.velocity.deltaY > 0 + && ((TransList[dTransVal[m.x + 1][m.y]] && nSolidTable[dPiece[m.x + 1][m.y]]) + || (TransList[dTransVal[m.x - 1][m.y]] && nSolidTable[dPiece[m.x - 1][m.y]]))) { + Missiles[i].position.offset.deltaY -= 32; + } + if (Missiles[i].position.velocity.deltaX > 0 + && ((TransList[dTransVal[m.x][m.y + 1]] && nSolidTable[dPiece[m.x][m.y + 1]]) + || (TransList[dTransVal[m.x][m.y - 1]] && nSolidTable[dPiece[m.x][m.y - 1]]))) { + Missiles[i].position.offset.deltaX -= 32; + } + Missiles[i]._mimfnum = 0; + SetMissAnim(i, MFILE_BIGEXP); + Missiles[i]._mirange = Missiles[i]._miAnimLen - 1; + } else if (Missiles[i].position.tile.x != Missiles[i]._miVar1 || Missiles[i].position.tile.y != Missiles[i]._miVar2) { + Missiles[i]._miVar1 = Missiles[i].position.tile.x; + Missiles[i]._miVar2 = Missiles[i].position.tile.y; + ChangeLight(Missiles[i]._mlid, Missiles[i].position.tile, 8); + } + if (alwaysDelete) + Missiles[i]._miDelFlag = true; + } + + PutMissile(i); +} + +static void MissileRing(int i, int type) +{ + Missiles[i]._miDelFlag = true; + int8_t src = Missiles[i]._micaster; + uint8_t lvl = src > 0 ? Players[src]._pLevel : currlevel; + int dmg = 16 * (GenerateRndSum(10, 2) + lvl + 2) / 2; + + int k = CrawlNum[3]; + int ck = k + 2; + for (auto j = static_cast(CrawlTable[k]); j > 0; j--, ck += 2) { + int tx = Missiles[i]._miVar1 + CrawlTable[ck - 1]; + int ty = Missiles[i]._miVar2 + CrawlTable[ck]; + if (!InDungeonBounds({ tx, ty })) + continue; + int dp = dPiece[tx][ty]; + if (nSolidTable[dp]) + continue; + if (dObject[tx][ty] != 0) + continue; + if (!LineClearMissile(Missiles[i].position.tile, { tx, ty })) + continue; + if (nMissileTable[dp] || Missiles[i].limitReached) { + Missiles[i].limitReached = true; + continue; + } + + AddMissile({ tx, ty }, { tx, ty }, 0, type, TARGET_BOTH, src, dmg, Missiles[i]._mispllvl); + } +} + +bool GrowWall(int playerId, Point position, Point target, missile_id type, int spellLevel, int damage) +{ + int dp = dPiece[position.x][position.y]; + assert(dp <= MAXTILES && dp >= 0); + + if (nMissileTable[dp] || !InDungeonBounds(target)) { + return false; + } + + AddMissile(position, position, Players[playerId]._pdir, type, TARGET_BOTH, playerId, damage, spellLevel); + return true; +} + +} // namespace + +void GetDamageAmt(int i, int *mind, int *maxd) +{ + assert(MyPlayerId >= 0 && MyPlayerId < MAX_PLRS); + assert(i >= 0 && i < 64); + + auto &myPlayer = Players[MyPlayerId]; + + int sl = myPlayer._pSplLvl[i] + myPlayer._pISplLvlAdd; + + switch (i) { + case SPL_FIREBOLT: + *mind = (myPlayer._pMagic / 8) + sl + 1; + *maxd = *mind + 9; + break; + case SPL_HEAL: + case SPL_HEALOTHER: + /// BUGFIX: healing calculation is unused + *mind = AddClassHealingBonus(myPlayer._pLevel + sl + 1, myPlayer._pClass) - 1; + *maxd = AddClassHealingBonus((4 * myPlayer._pLevel) + (6 * sl) + 10, myPlayer._pClass) - 1; + break; + case SPL_LIGHTNING: + case SPL_RUNELIGHT: + *mind = 2; + *maxd = 2 + myPlayer._pLevel; + break; + case SPL_FLASH: + *mind = ScaleSpellEffect(myPlayer._pLevel, sl); + *mind += *mind / 2; + *maxd = *mind * 2; + break; + case SPL_IDENTIFY: + case SPL_TOWN: + case SPL_STONE: + case SPL_INFRA: + case SPL_RNDTELEPORT: + case SPL_MANASHIELD: + case SPL_DOOMSERP: + case SPL_BLODRIT: + case SPL_INVISIBIL: + case SPL_BLODBOIL: + case SPL_TELEPORT: + case SPL_ETHEREALIZE: + case SPL_REPAIR: + case SPL_RECHARGE: + case SPL_DISARM: + case SPL_RESURRECT: + case SPL_TELEKINESIS: + case SPL_BONESPIRIT: + case SPL_WARP: + case SPL_REFLECT: + case SPL_BERSERK: + case SPL_SEARCH: + case SPL_RUNESTONE: + *mind = -1; + *maxd = -1; + break; + case SPL_FIREWALL: + case SPL_LIGHTWALL: + case SPL_FIRERING: + *mind = 2 * myPlayer._pLevel + 4; + *maxd = *mind + 36; + break; + case SPL_FIREBALL: + case SPL_RUNEFIRE: { + int base = (2 * myPlayer._pLevel) + 4; + *mind = ScaleSpellEffect(base, sl); + *maxd = ScaleSpellEffect(base + 36, sl); + } break; + case SPL_GUARDIAN: { + int base = (myPlayer._pLevel / 2) + 1; + *mind = ScaleSpellEffect(base, sl); + *maxd = ScaleSpellEffect(base + 9, sl); + } break; + case SPL_CHAIN: + *mind = 4; + *maxd = 4 + (2 * myPlayer._pLevel); + break; + case SPL_WAVE: + *mind = 6 * (myPlayer._pLevel + 1); + *maxd = *mind + 54; + break; + case SPL_NOVA: + case SPL_IMMOLAT: + case SPL_RUNEIMMOLAT: + case SPL_RUNENOVA: + *mind = ScaleSpellEffect((myPlayer._pLevel + 5) / 2, sl) * 5; + *maxd = ScaleSpellEffect((myPlayer._pLevel + 30) / 2, sl) * 5; + break; + case SPL_FLAME: + *mind = 3; + *maxd = myPlayer._pLevel + 4; + *maxd += *maxd / 2; + break; + case SPL_GOLEM: + *mind = 11; + *maxd = 17; + break; + case SPL_APOCA: + *mind = myPlayer._pLevel; + *maxd = *mind * 6; + break; + case SPL_ELEMENT: + *mind = ScaleSpellEffect(2 * myPlayer._pLevel + 4, sl); + /// BUGFIX: add here '*mind /= 2;' + *maxd = ScaleSpellEffect(2 * myPlayer._pLevel + 40, sl); + /// BUGFIX: add here '*maxd /= 2;' + break; + case SPL_CBOLT: + *mind = 1; + *maxd = *mind + (myPlayer._pMagic / 4); + break; + case SPL_HBOLT: + *mind = myPlayer._pLevel + 9; + *maxd = *mind + 9; + break; + case SPL_FLARE: + *mind = (myPlayer._pMagic / 2) + 3 * sl - (myPlayer._pMagic / 8); + *maxd = *mind; + break; + } +} + +int GetSpellLevel(int playerId, spell_id sn) +{ + auto &player = Players[playerId]; + + if (playerId != MyPlayerId) + return 1; // BUGFIX spell level will be wrong in multiplayer + + return std::max(player._pISplLvlAdd + player._pSplLvl[sn], 0); +} + +/** + * @brief Returns the direction a vector from p1(x1, y1) to p2(x2, y2) is pointing to. + * + * W sW SW Sw S + * ^ + * nW | Se + * | + * NW ------+-----> SE + * | + * Nw | sE + * | + * N Ne NE nE E + * + * @param x1 the x coordinate of p1 + * @param y1 the y coordinate of p1 + * @param x2 the x coordinate of p2 + * @param y2 the y coordinate of p2 + * @return the direction of the p1->p2 vector + */ +Direction16 GetDirection16(Point p1, Point p2) +{ + Displacement offset = p2 - p1; + Displacement absolute = abs(offset); + + bool flipY = offset.deltaX != absolute.deltaX; + bool flipX = offset.deltaY != absolute.deltaY; + + bool flipMedian = false; + if (absolute.deltaX > absolute.deltaY) { + std::swap(absolute.deltaX, absolute.deltaY); + flipMedian = true; + } + + Direction16 ret = DIR16_S; + if (3 * absolute.deltaX <= (absolute.deltaY * 2)) { // mx/my <= 2/3, approximation of tan(33.75) + if (5 * absolute.deltaX < absolute.deltaY) // mx/my < 0.2, approximation of tan(11.25) + ret = DIR16_SW; + else + ret = DIR16_Sw; + } + + Direction16 medianPivot = DIR16_S; + if (flipY) { + ret = Direction16Flip(ret, DIR16_SW); + medianPivot = Direction16Flip(medianPivot, DIR16_SW); + } + if (flipX) { + ret = Direction16Flip(ret, DIR16_SE); + medianPivot = Direction16Flip(medianPivot, DIR16_SE); + } + if (flipMedian) + ret = Direction16Flip(ret, medianPivot); + return ret; +} + +void DeleteMissile(int mi, int i) +{ + if (Missiles[mi]._mitype == MIS_MANASHIELD) { + int src = Missiles[mi]._misource; + if (src == MyPlayerId) + NetSendCmd(true, CMD_REMSHIELD); + Players[src].pManaShield = false; + } + + AvailableMissiles[MAXMISSILES - ActiveMissileCount] = mi; + ActiveMissileCount--; + if (ActiveMissileCount > 0 && i != ActiveMissileCount) + ActiveMissiles[i] = ActiveMissiles[ActiveMissileCount]; +} + +bool MonsterTrapHit(int m, int mindam, int maxdam, int dist, int t, bool shift) +{ + bool resist = false; + if (Monsters[m].mtalkmsg != TEXT_NONE) { + return false; + } + if (Monsters[m]._mhitpoints >> 6 <= 0) { + return false; + } + if (Monsters[m].MType->mtype == MT_ILLWEAV && Monsters[m]._mgoal == MGOAL_RETREAT) + return false; + if (Monsters[m]._mmode == MM_CHARGE) + return false; + + missile_resistance mir = MissileData[t].mResist; + int mor = Monsters[m].mMagicRes; + if (((mor & IMMUNE_MAGIC) != 0 && mir == MISR_MAGIC) + || ((mor & IMMUNE_FIRE) != 0 && mir == MISR_FIRE) + || ((mor & IMMUNE_LIGHTNING) != 0 && mir == MISR_LIGHTNING)) { + return false; + } + + if (((mor & RESIST_MAGIC) != 0 && mir == MISR_MAGIC) + || ((mor & RESIST_FIRE) != 0 && mir == MISR_FIRE) + || ((mor & RESIST_LIGHTNING) != 0 && mir == MISR_LIGHTNING)) { + resist = true; + } + + int hit = GenerateRnd(100); + int hper = 90 - (BYTE)Monsters[m].mArmorClass - dist; + if (hper < 5) + hper = 5; + if (hper > 95) + hper = 95; + bool ret; + if (CheckMonsterHit(m, &ret)) { + return ret; + } +#ifdef _DEBUG + if (hit < hper || debug_mode_dollar_sign || debug_mode_key_inverted_v || Monsters[m]._mmode == MM_STONE) { +#else + if (hit < hper || Monsters[m]._mmode == MM_STONE) { +#endif + int dam = mindam + GenerateRnd(maxdam - mindam + 1); + if (!shift) + dam <<= 6; + if (resist) + Monsters[m]._mhitpoints -= dam / 4; + else + Monsters[m]._mhitpoints -= dam; +#ifdef _DEBUG + if (debug_mode_dollar_sign || debug_mode_key_inverted_v) + Monsters[m]._mhitpoints = 0; +#endif + if (Monsters[m]._mhitpoints >> 6 <= 0) { + if (Monsters[m]._mmode == MM_STONE) { + M_StartKill(m, -1); + Monsters[m].Petrify(); + } else { + M_StartKill(m, -1); + } + } else { + if (resist) { + PlayEffect(m, 1); + } else if (Monsters[m]._mmode == MM_STONE) { + if (m > MAX_PLRS - 1) + M_StartHit(m, -1, dam); + Monsters[m].Petrify(); + } else { + if (m > MAX_PLRS - 1) + M_StartHit(m, -1, dam); + } + } + return true; + } + return false; +} + +bool PlayerMHit(int pnum, int m, int dist, int mind, int maxd, int mtype, bool shift, int earflag, bool *blocked) +{ + *blocked = false; + + auto &player = Players[pnum]; + + if (player._pHitPoints >> 6 <= 0) { + return false; + } + + if (player._pInvincible) { + return false; + } + + if ((player._pSpellFlags & 1) != 0 && MissileData[mtype].mType == 0) { + return false; + } + + int hit = GenerateRnd(100); +#ifdef _DEBUG + if (debug_mode_dollar_sign || debug_mode_key_inverted_v) + hit = 1000; +#endif + int hper = 40; + if (MissileData[mtype].mType == 0) { + int tac = player._pIAC + player._pIBonusAC + player._pDexterity / 5; + if (m != -1) { + hper = Monsters[m].mHit + + ((Monsters[m].mLevel - player._pLevel) * 2) + + 30 + - (dist * 2) - tac; + } else { + hper = 100 - (tac / 2) - (dist * 2); + } + } else if (m != -1) { + hper += (Monsters[m].mLevel * 2) - (player._pLevel * 2) - (dist * 2); + } + + if (hper < 10) + hper = 10; + if (currlevel == 14 && hper < 20) { + hper = 20; + } + if (currlevel == 15 && hper < 25) { + hper = 25; + } + if (currlevel == 16 && hper < 30) { + hper = 30; + } + + int blk = 100; + if ((player._pmode == PM_STAND || player._pmode == PM_ATTACK) && player._pBlockFlag) { + blk = GenerateRnd(100); + } + + if (shift) + blk = 100; + if (mtype == MIS_ACIDPUD) + blk = 100; + + int blkper = player._pBaseToBlk + player._pDexterity; + if (m != -1) + blkper -= (Monsters[m].mLevel - player._pLevel) * 2; + + if (blkper < 0) + blkper = 0; + if (blkper > 100) + blkper = 100; + + int8_t resper; + switch (MissileData[mtype].mResist) { + case MISR_FIRE: + resper = player._pFireResist; + break; + case MISR_LIGHTNING: + resper = player._pLghtResist; + break; + case MISR_MAGIC: + case MISR_ACID: + resper = player._pMagResist; + break; + default: + resper = 0; + break; + } + + if (hit >= hper) { + return false; + } + + int dam; + if (mtype == MIS_BONESPIRIT) { + dam = player._pHitPoints / 3; + } else { + if (!shift) { + dam = (mind << 6) + GenerateRnd((maxd - mind + 1) << 6); + if (m == -1) + if ((player._pIFlags & ISPL_ABSHALFTRAP) != 0) + dam /= 2; + dam += (player._pIGetHit << 6); + } else { + dam = mind + GenerateRnd(maxd - mind + 1); + if (m == -1) + if ((player._pIFlags & ISPL_ABSHALFTRAP) != 0) + dam /= 2; + dam += player._pIGetHit; + } + + dam = std::max(dam, 64); + } + + if ((resper <= 0 || gbIsHellfire) && blk < blkper) { + Direction dir = player._pdir; + if (m != -1) { + dir = GetDirection(player.position.tile, Monsters[m].position.tile); } + *blocked = true; + StartPlrBlock(pnum, dir); + return true; } - if (nMissileTable[dPiece[mx][my]]) { - if (!nodel) - Missiles[i]._mirange = 0; - Missiles[i]._miHitFlag = false; + + if (resper > 0) { + dam -= dam * resper / 100; + if (pnum == MyPlayerId) { + ApplyPlrDamage(pnum, 0, 0, dam, earflag); + } + + if (player._pHitPoints >> 6 > 0) { + player.Say(HeroSpeech::ArghClang); + } + return true; } - if (Missiles[i]._mirange == 0 && MissileData[Missiles[i]._mitype].miSFX != -1) - PlaySfxLoc(MissileData[Missiles[i]._mitype].miSFX, Missiles[i].position.tile); -} -void SetMissAnim(int mi, int animtype) -{ - int dir = Missiles[mi]._mimfnum; + if (pnum == MyPlayerId) { + ApplyPlrDamage(pnum, 0, 0, dam, earflag); + } - if (animtype > MFILE_NONE) { - animtype = MFILE_NONE; + if (player._pHitPoints >> 6 > 0) { + StartPlrHit(pnum, dam, false); } - Missiles[mi]._miAnimType = animtype; - Missiles[mi]._miAnimFlags = MissileSpriteData[animtype].mFlags; - Missiles[mi]._miAnimData = MissileSpriteData[animtype].mAnimData[dir]; - Missiles[mi]._miAnimDelay = MissileSpriteData[animtype].mAnimDelay[dir]; - Missiles[mi]._miAnimLen = MissileSpriteData[animtype].mAnimLen[dir]; - Missiles[mi]._miAnimWidth = MissileSpriteData[animtype].mAnimWidth[dir]; - Missiles[mi]._miAnimWidth2 = MissileSpriteData[animtype].mAnimWidth2[dir]; - Missiles[mi]._miAnimCnt = 0; - Missiles[mi]._miAnimFrame = 1; + return true; } void SetMissDir(int mi, int dir) @@ -1089,26 +1269,6 @@ void InitMissileGFX() } } -void FreeMissileGFX(int mi) -{ - if ((MissileSpriteData[mi].mFlags & MFLAG_ALLOW_SPECIAL) != 0) { - if (MissileSpriteData[mi].mAnimData[0] != nullptr) { - auto *p = (DWORD *)MissileSpriteData[mi].mAnimData[0]; - p -= MissileSpriteData[mi].mAnimFAmt; - delete[] p; - MissileSpriteData[mi].mAnimData[0] = nullptr; - } - return; - } - - for (int i = 0; i < MissileSpriteData[mi].mAnimFAmt; i++) { - if (MissileSpriteData[mi].mAnimData[i] != nullptr) { - delete[] MissileSpriteData[mi].mAnimData[i]; - MissileSpriteData[mi].mAnimData[i] = nullptr; - } - } -} - void FreeMissiles() { for (int mi = 0; MissileSpriteData[mi].mAnimFAmt != 0; mi++) { @@ -1186,31 +1346,6 @@ void AddHiveExplosion(int mi, Point /*src*/, Point /*dst*/, int midir, int8_t mi Missiles[mi]._miDelFlag = true; } -static bool MissilesFoundTarget(int mi, Point *position, int rad) -{ - rad = std::min(rad, 19); - - for (int i = 0; i < rad; i++) { - int k = CrawlNum[i]; - int ck = k + 2; - for (auto j = static_cast(CrawlTable[k]); j > 0; j--, ck += 2) { - int tx = position->x + CrawlTable[ck - 1]; - int ty = position->y + CrawlTable[ck]; - if (!InDungeonBounds({ tx, ty })) - continue; - - int dp = dPiece[tx][ty]; - if (nSolidTable[dp] || dObject[tx][ty] != 0 || dMissile[tx][ty] != 0) - continue; - - Missiles[mi].position.tile = { tx, ty }; - *position = { tx, ty }; - return true; - } - } - return false; -} - void AddFireRune(int mi, Point src, Point dst, int /*midir*/, int8_t /*mienemy*/, int id, int /*dam*/) { if (LineClearMissile(src, dst)) { @@ -2125,15 +2260,6 @@ void AddWeapexp(int mi, Point src, Point dst, int /*midir*/, int8_t /*mienemy*/, Missiles[mi]._mirange = Missiles[mi]._miAnimLen - 1; } -bool CheckIfTrig(Point position) -{ - for (int i = 0; i < numtrigs; i++) { - if (trigs[i].position.WalkingDistance(position) < 2) - return true; - } - return false; -} - void AddTown(int mi, Point /*src*/, Point dst, int /*midir*/, int8_t /*mienemy*/, int id, int /*dam*/) { int tx = dst.x; @@ -2992,25 +3118,6 @@ int AddMissile(Point src, Point dst, int midir, int mitype, int8_t micaster, int return mi; } -static int Sentfire(int i, Point src) -{ - int ex = 0; - if (LineClearMissile(Missiles[i].position.tile, src)) { - if (dMonster[src.x][src.y] > 0 && Monsters[dMonster[src.x][src.y] - 1]._mhitpoints >> 6 > 0 && dMonster[src.x][src.y] - 1 > MAX_PLRS - 1) { - Direction dir = GetDirection(Missiles[i].position.tile, src); - Missiles[i]._miVar3 = AvailableMissiles[0]; - AddMissile(Missiles[i].position.tile, src, dir, MIS_FIREBOLT, TARGET_MONSTERS, Missiles[i]._misource, Missiles[i]._midam, GetSpellLevel(Missiles[i]._misource, SPL_FIREBOLT)); - ex = -1; - } - } - if (ex == -1) { - SetMissDir(i, 2); - Missiles[i]._miVar2 = 3; - } - - return ex; -} - void MI_Dummy(int i) { } @@ -3023,7 +3130,7 @@ void MI_Golem(int mi) int k = CrawlNum[i]; int ck = k + 2; for (auto j = static_cast(CrawlTable[k]); j > 0; j--, ck += 2) { - const char *ct = &CrawlTable[ck]; + const int8_t *ct = &CrawlTable[ck]; int tx = Missiles[mi]._miVar4 + *(ct - 1); int ty = Missiles[mi]._miVar5 + *ct; if (0 < tx && tx < MAXDUNX && 0 < ty && ty < MAXDUNY) { @@ -3327,65 +3434,6 @@ void MI_Firewall(int i) PutMissile(i); } -static void FireballUpdate(int i, Displacement offset, bool alwaysDelete) -{ - Missiles[i]._mirange--; - - int id = Missiles[i]._misource; - Point p = (Missiles[i]._micaster == TARGET_MONSTERS) ? Players[id].position.tile : Monsters[id].position.tile; - - if (Missiles[i]._miAnimType == MFILE_BIGEXP) { - if (Missiles[i]._mirange == 0) { - Missiles[i]._miDelFlag = true; - AddUnLight(Missiles[i]._mlid); - } - } else { - int dam = Missiles[i]._midam; - Missiles[i].position.traveled += offset; - UpdateMissilePos(i); - if (Missiles[i].position.tile != Missiles[i].position.start) - CheckMissileCol(i, dam, dam, false, Missiles[i].position.tile, false); - if (Missiles[i]._mirange == 0) { - Point m = Missiles[i].position.tile; - ChangeLight(Missiles[i]._mlid, Missiles[i].position.tile, Missiles[i]._miAnimFrame); - - constexpr Displacement Pattern[] = { { 0, 0 }, { 0, 1 }, { 0, -1 }, { 1, 0 }, { 1, -1 }, { 1, 1 }, { -1, 0 }, { -1, 1 }, { -1, -1 } }; - for (auto shift : Pattern) { - if (!CheckBlock(p, m + shift)) - CheckMissileCol(i, dam, dam, false, m + shift, true); - } - - if (!TransList[dTransVal[m.x][m.y]] - || (Missiles[i].position.velocity.deltaX < 0 && ((TransList[dTransVal[m.x][m.y + 1]] && nSolidTable[dPiece[m.x][m.y + 1]]) || (TransList[dTransVal[m.x][m.y - 1]] && nSolidTable[dPiece[m.x][m.y - 1]])))) { - Missiles[i].position.tile.x++; - Missiles[i].position.tile.y++; - Missiles[i].position.offset.deltaY -= 32; - } - if (Missiles[i].position.velocity.deltaY > 0 - && ((TransList[dTransVal[m.x + 1][m.y]] && nSolidTable[dPiece[m.x + 1][m.y]]) - || (TransList[dTransVal[m.x - 1][m.y]] && nSolidTable[dPiece[m.x - 1][m.y]]))) { - Missiles[i].position.offset.deltaY -= 32; - } - if (Missiles[i].position.velocity.deltaX > 0 - && ((TransList[dTransVal[m.x][m.y + 1]] && nSolidTable[dPiece[m.x][m.y + 1]]) - || (TransList[dTransVal[m.x][m.y - 1]] && nSolidTable[dPiece[m.x][m.y - 1]]))) { - Missiles[i].position.offset.deltaX -= 32; - } - Missiles[i]._mimfnum = 0; - SetMissAnim(i, MFILE_BIGEXP); - Missiles[i]._mirange = Missiles[i]._miAnimLen - 1; - } else if (Missiles[i].position.tile.x != Missiles[i]._miVar1 || Missiles[i].position.tile.y != Missiles[i]._miVar2) { - Missiles[i]._miVar1 = Missiles[i].position.tile.x; - Missiles[i]._miVar2 = Missiles[i].position.tile.y; - ChangeLight(Missiles[i]._mlid, Missiles[i].position.tile, 8); - } - if (alwaysDelete) - Missiles[i]._miDelFlag = true; - } - - PutMissile(i); -} - void MI_Fireball(int i) { FireballUpdate(i, Missiles[i].position.velocity, false); @@ -3564,26 +3612,6 @@ void MI_LightningArrow(int i) } } -void MI_FlashFront(int i) -{ - int src = Missiles[i]._misource; - if (Missiles[i]._micaster == TARGET_MONSTERS && src != -1) { - Missiles[i].position.tile = Players[src].position.tile; - Missiles[i].position.traveled.deltaX = Players[src].position.offset.deltaX << 16; - Missiles[i].position.traveled.deltaY = Players[src].position.offset.deltaY << 16; - } - Missiles[i]._mirange--; - if (Missiles[i]._mirange == 0) { - Missiles[i]._miDelFlag = true; - if (Missiles[i]._micaster == TARGET_MONSTERS) { - src = Missiles[i]._misource; - if (src != -1) - Players[src]._pBaseToBlk -= 50; - } - } - PutMissile(i); -} - void MI_Reflect(int i) { int src = Missiles[i]._misource; @@ -3596,36 +3624,6 @@ void MI_Reflect(int i) PutMissile(i); } -static void MissileRing(int i, int type) -{ - Missiles[i]._miDelFlag = true; - int8_t src = Missiles[i]._micaster; - uint8_t lvl = src > 0 ? Players[src]._pLevel : currlevel; - int dmg = 16 * (GenerateRndSum(10, 2) + lvl + 2) / 2; - - int k = CrawlNum[3]; - int ck = k + 2; - for (auto j = static_cast(CrawlTable[k]); j > 0; j--, ck += 2) { - int tx = Missiles[i]._miVar1 + CrawlTable[ck - 1]; - int ty = Missiles[i]._miVar2 + CrawlTable[ck]; - if (!InDungeonBounds({ tx, ty })) - continue; - int dp = dPiece[tx][ty]; - if (nSolidTable[dp]) - continue; - if (dObject[tx][ty] != 0) - continue; - if (!LineClearMissile(Missiles[i].position.tile, { tx, ty })) - continue; - if (nMissileTable[dp] || Missiles[i].limitReached) { - Missiles[i].limitReached = true; - continue; - } - - AddMissile({ tx, ty }, { tx, ty }, 0, type, TARGET_BOTH, src, dmg, Missiles[i]._mispllvl); - } -} - void MI_FireRing(int i) { MissileRing(i, MIS_FIREWALL); @@ -3647,19 +3645,6 @@ void MI_Search(int i) AutoMapShowItems = false; } -bool GrowWall(int playerId, Point position, Point target, missile_id type, int spellLevel, int damage) -{ - int dp = dPiece[position.x][position.y]; - assert(dp <= MAXTILES && dp >= 0); - - if (nMissileTable[dp] || !InDungeonBounds(target)) { - return false; - } - - AddMissile(position, position, Players[playerId]._pdir, type, TARGET_BOTH, playerId, damage, spellLevel); - return true; -} - void MI_LightningWallC(int i) { Missiles[i]._mirange--; diff --git a/Source/missiles.h b/Source/missiles.h index 7f9600acc..c050e8c1f 100644 --- a/Source/missiles.h +++ b/Source/missiles.h @@ -129,7 +129,6 @@ Direction16 GetDirection16(Point p1, Point p2); void DeleteMissile(int mi, int i); bool MonsterTrapHit(int m, int mindam, int maxdam, int dist, int t, bool shift); bool PlayerMHit(int pnum, int m, int dist, int mind, int maxd, int mtype, bool shift, int earflag, bool *blocked); -void SetMissAnim(int mi, int animtype); void SetMissDir(int mi, int dir); void LoadMissileGFX(BYTE mi); void InitMissileGFX(); diff --git a/Source/monster.cpp b/Source/monster.cpp index 5c522c379..a583d6e4a 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -39,34 +39,24 @@ namespace devilution { -namespace { - -void NewMonsterAnim(MonsterStruct &monster, MonsterGraphic graphic, Direction md, AnimationDistributionFlags flags = AnimationDistributionFlags::None, int numSkippedFrames = 0, int distributeFramesBeforeFrame = 0) -{ - auto &animData = monster.MType->GetAnimData(graphic); - auto *pCelSprite = &*animData.CelSpritesForDirections[md]; - monster.AnimInfo.SetNewAnimation(pCelSprite, animData.Frames, animData.Rate, flags, numSkippedFrames, distributeFramesBeforeFrame); - monster._mFlags &= ~(MFLAG_LOCK_ANIMATION | MFLAG_ALLOW_SPECIAL); - monster._mdir = md; -} +CMonster LevelMonsterTypes[MAX_LVLMTYPES]; +int LevelMonsterTypeCount; +MonsterStruct Monsters[MAXMONSTERS]; +int ActiveMonsters[MAXMONSTERS]; +int ActiveMonsterCount; +// BUGFIX: replace MonsterKillCounts[MAXMONSTERS] with MonsterKillCounts[NUM_MTYPES]. +/** Tracks the total number of monsters killed per monster_id. */ +int MonsterKillCounts[MAXMONSTERS]; +bool sgbSaveSoundOn; -void StartMonsterGotHit(int monsterId) -{ - auto &monster = Monsters[monsterId]; - if (monster.MType->mtype != MT_GOLEM) { - auto animationFlags = gGameLogicStep < GameLogicStep::ProcessMonsters ? AnimationDistributionFlags::ProcessAnimationPending : AnimationDistributionFlags::None; - int numSkippedFrames = (gbIsHellfire && monster.MType->mtype == MT_DIABLO) ? 4 : 0; - NewMonsterAnim(monster, MonsterGraphic::GotHit, monster._mdir, animationFlags, numSkippedFrames); - monster._mmode = MM_GOTHIT; - } - monster.position.offset = { 0, 0 }; - monster.position.tile = monster.position.old; - monster.position.future = monster.position.old; - M_ClearSquares(monsterId); - dMonster[monster.position.tile.x][monster.position.tile.y] = monsterId + 1; -} +/** Maps from direction to a left turn from the direction. */ +Direction left[8] = { DIR_SE, DIR_S, DIR_SW, DIR_W, DIR_NW, DIR_N, DIR_NE, DIR_E }; +/** Maps from direction to a right turn from the direction. */ +Direction right[8] = { DIR_SW, DIR_W, DIR_NW, DIR_N, DIR_NE, DIR_E, DIR_SE, DIR_S }; +/** Maps from direction to the opposite direction. */ +Direction opposite[8] = { DIR_N, DIR_NE, DIR_E, DIR_SE, DIR_S, DIR_SW, DIR_W, DIR_NW }; -} // namespace +namespace { #define NIGHTMARE_TO_HIT_BONUS 85 #define HELL_TO_HIT_BONUS 120 @@ -76,21 +66,9 @@ void StartMonsterGotHit(int monsterId) /** Tracks which missile files are already loaded */ int MissileFileFlag; - -// BUGFIX: replace MonsterKillCounts[MAXMONSTERS] with MonsterKillCounts[NUM_MTYPES]. -/** Tracks the total number of monsters killed per monster_id. */ -int MonsterKillCounts[MAXMONSTERS]; -int ActiveMonsters[MAXMONSTERS]; -int ActiveMonsterCount; -bool sgbSaveSoundOn; -MonsterStruct Monsters[MAXMONSTERS]; int totalmonsters; -CMonster LevelMonsterTypes[MAX_LVLMTYPES]; int monstimgtot; int uniquetrans; -int LevelMonsterTypeCount; - -/* data */ // BUGFIX: MWVel velocity values are not rounded consistently. The correct // formula for monster walk velocity is calculated as follows (for 16, 32 and 64 @@ -158,56 +136,6 @@ int MWVel[24][3] = { }; /** Maps from monster action to monster animation letter. */ char animletter[7] = "nwahds"; -/** Maps from direction to a left turn from the direction. */ -Direction left[8] = { DIR_SE, DIR_S, DIR_SW, DIR_W, DIR_NW, DIR_N, DIR_NE, DIR_E }; -/** Maps from direction to a right turn from the direction. */ -Direction right[8] = { DIR_SW, DIR_W, DIR_NW, DIR_N, DIR_NE, DIR_E, DIR_SE, DIR_S }; -/** Maps from direction to the opposite direction. */ -Direction opposite[8] = { DIR_N, DIR_NE, DIR_E, DIR_SE, DIR_S, DIR_SW, DIR_W, DIR_NW }; - -/** Maps from monster AI ID to monster AI function. */ -void (*AiProc[])(int i) = { - &MAI_Zombie, - &MAI_Fat, - &MAI_SkelSd, - &MAI_SkelBow, - &MAI_Scav, - &MAI_Rhino, - &MAI_GoatMc, - &MAI_GoatBow, - &MAI_Fallen, - &MAI_Magma, - &MAI_SkelKing, - &MAI_Bat, - &MAI_Garg, - &MAI_Cleaver, - &MAI_Succ, - &MAI_Sneak, - &MAI_Storm, - &MAI_Fireman, - &MAI_Garbud, - &MAI_Acid, - &MAI_AcidUniq, - &MAI_Golum, - &MAI_Zhar, - &MAI_SnotSpil, - &MAI_Snake, - &MAI_Counselor, - &MAI_Mega, - &MAI_Diablo, - &MAI_Lazurus, - &MAI_Lazhelp, - &MAI_Lachdanan, - &MAI_Warlord, - &MAI_Firebat, - &MAI_Torchant, - &MAI_HorkDemon, - &MAI_Lich, - &MAI_ArchLich, - &MAI_Psychorb, - &MAI_Necromorb, - &MAI_BoneDemon -}; void InitMonsterTRN(CMonster &monst) { @@ -231,289 +159,477 @@ void InitMonsterTRN(CMonster &monst) } } -void InitLevelMonsters() +void InitMonster(int i, Direction rd, int mtype, Point position) { - LevelMonsterTypeCount = 0; - monstimgtot = 0; - MissileFileFlag = 0; + CMonster *monst = &LevelMonsterTypes[mtype]; - for (auto &levelMonsterType : LevelMonsterTypes) { - levelMonsterType.mPlaceFlags = 0; + auto &animData = monst->GetAnimData(MonsterGraphic::Stand); + + Monsters[i]._mdir = rd; + Monsters[i].position.tile = position; + Monsters[i].position.future = position; + Monsters[i].position.old = position; + Monsters[i]._mMTidx = mtype; + Monsters[i]._mmode = MM_STAND; + Monsters[i].mName = _(monst->MData->mName); + Monsters[i].MType = monst; + Monsters[i].MData = monst->MData; + Monsters[i].AnimInfo = {}; + Monsters[i].AnimInfo.pCelSprite = animData.CelSpritesForDirections[rd] ? &*animData.CelSpritesForDirections[rd] : nullptr; + Monsters[i].AnimInfo.TicksPerFrame = animData.Rate; + Monsters[i].AnimInfo.TickCounterOfCurrentFrame = GenerateRnd(Monsters[i].AnimInfo.TicksPerFrame - 1); + Monsters[i].AnimInfo.NumberOfFrames = animData.Frames; + Monsters[i].AnimInfo.CurrentFrame = GenerateRnd(Monsters[i].AnimInfo.NumberOfFrames - 1) + 1; + + Monsters[i].mLevel = monst->MData->mLevel; + Monsters[i]._mmaxhp = (monst->mMinHP + GenerateRnd(monst->mMaxHP - monst->mMinHP + 1)) << 6; + if (monst->mtype == MT_DIABLO && !gbIsHellfire) { + Monsters[i]._mmaxhp /= 2; + Monsters[i].mLevel -= 15; } - ClrAllMonsters(); - ActiveMonsterCount = 0; - totalmonsters = MAXMONSTERS; + if (!gbIsMultiplayer) { + Monsters[i]._mmaxhp /= 2; + if (Monsters[i]._mmaxhp < 64) { + Monsters[i]._mmaxhp = 64; + } + } - for (int i = 0; i < MAXMONSTERS; i++) { - ActiveMonsters[i] = i; + Monsters[i]._mhitpoints = Monsters[i]._mmaxhp; + Monsters[i]._mAi = monst->MData->mAi; + Monsters[i]._mint = monst->MData->mInt; + Monsters[i]._mgoal = MGOAL_NORMAL; + Monsters[i]._mgoalvar1 = 0; + Monsters[i]._mgoalvar2 = 0; + Monsters[i]._mgoalvar3 = 0; + Monsters[i]._pathcount = 0; + Monsters[i]._mDelFlag = false; + Monsters[i]._uniqtype = 0; + Monsters[i]._msquelch = 0; + Monsters[i].mlid = NO_LIGHT; // BUGFIX monsters initial light id should be -1 (fixed) + Monsters[i]._mRndSeed = AdvanceRndSeed(); + Monsters[i]._mAISeed = AdvanceRndSeed(); + Monsters[i].mWhoHit = 0; + Monsters[i].mExp = monst->MData->mExp; + Monsters[i].mHit = monst->MData->mHit; + Monsters[i].mMinDamage = monst->MData->mMinDamage; + Monsters[i].mMaxDamage = monst->MData->mMaxDamage; + Monsters[i].mHit2 = monst->MData->mHit2; + Monsters[i].mMinDamage2 = monst->MData->mMinDamage2; + Monsters[i].mMaxDamage2 = monst->MData->mMaxDamage2; + Monsters[i].mArmorClass = monst->MData->mArmorClass; + Monsters[i].mMagicRes = monst->MData->mMagicRes; + Monsters[i].leader = 0; + Monsters[i].leaderflag = 0; + Monsters[i]._mFlags = monst->MData->mFlags; + Monsters[i].mtalkmsg = TEXT_NONE; + + if (Monsters[i]._mAi == AI_GARG) { + Monsters[i].AnimInfo.pCelSprite = &*monst->GetAnimData(MonsterGraphic::Special).CelSpritesForDirections[rd]; + Monsters[i].AnimInfo.CurrentFrame = 1; + Monsters[i]._mFlags |= MFLAG_ALLOW_SPECIAL; + Monsters[i]._mmode = MM_SATTACK; } - uniquetrans = 0; + if (sgGameInitInfo.nDifficulty == DIFF_NIGHTMARE) { + Monsters[i]._mmaxhp = 3 * Monsters[i]._mmaxhp; + if (gbIsHellfire) + Monsters[i]._mmaxhp += (gbIsMultiplayer ? 100 : 50) << 6; + else + Monsters[i]._mmaxhp += 64; + Monsters[i]._mhitpoints = Monsters[i]._mmaxhp; + Monsters[i].mLevel += 15; + Monsters[i].mExp = 2 * (Monsters[i].mExp + 1000); + Monsters[i].mHit += NIGHTMARE_TO_HIT_BONUS; + Monsters[i].mMinDamage = 2 * (Monsters[i].mMinDamage + 2); + Monsters[i].mMaxDamage = 2 * (Monsters[i].mMaxDamage + 2); + Monsters[i].mHit2 += NIGHTMARE_TO_HIT_BONUS; + Monsters[i].mMinDamage2 = 2 * (Monsters[i].mMinDamage2 + 2); + Monsters[i].mMaxDamage2 = 2 * (Monsters[i].mMaxDamage2 + 2); + Monsters[i].mArmorClass += NIGHTMARE_AC_BONUS; + } else if (sgGameInitInfo.nDifficulty == DIFF_HELL) { + Monsters[i]._mmaxhp = 4 * Monsters[i]._mmaxhp; + if (gbIsHellfire) + Monsters[i]._mmaxhp += (gbIsMultiplayer ? 200 : 100) << 6; + else + Monsters[i]._mmaxhp += 192; + Monsters[i]._mhitpoints = Monsters[i]._mmaxhp; + Monsters[i].mLevel += 30; + Monsters[i].mExp = 4 * (Monsters[i].mExp + 1000); + Monsters[i].mHit += HELL_TO_HIT_BONUS; + Monsters[i].mMinDamage = 4 * Monsters[i].mMinDamage + 6; + Monsters[i].mMaxDamage = 4 * Monsters[i].mMaxDamage + 6; + Monsters[i].mHit2 += HELL_TO_HIT_BONUS; + Monsters[i].mMinDamage2 = 4 * Monsters[i].mMinDamage2 + 6; + Monsters[i].mMaxDamage2 = 4 * Monsters[i].mMaxDamage2 + 6; + Monsters[i].mArmorClass += HELL_AC_BONUS; + Monsters[i].mMagicRes = monst->MData->mMagicRes2; + } } -int AddMonsterType(_monster_id type, placeflag placeflag) +bool MonstPlace(int xp, int yp) { - bool done = false; - int i; + char f; - for (i = 0; i < LevelMonsterTypeCount && !done; i++) { - done = LevelMonsterTypes[i].mtype == type; + if (xp < 0 || xp >= MAXDUNX + || yp < 0 || yp >= MAXDUNY + || dMonster[xp][yp] != 0 + || dPlayer[xp][yp] != 0) { + return false; } - i--; + f = dFlags[xp][yp]; - if (!done) { - i = LevelMonsterTypeCount; - LevelMonsterTypeCount++; - LevelMonsterTypes[i].mtype = type; - monstimgtot += MonsterData[type].mImage; - InitMonsterGFX(i); - InitMonsterSND(i); + if ((f & BFLAG_VISIBLE) != 0) { + return false; } - LevelMonsterTypes[i].mPlaceFlags |= placeflag; - return i; + if ((f & BFLAG_POPULATED) != 0) { + return false; + } + + return !SolidLoc({ xp, yp }); } -void GetLevelMTypes() +void PlaceMonster(int i, int mtype, int x, int y) { - // this array is merged with skeltypes down below. - _monster_id typelist[MAXMONSTERS]; - _monster_id skeltypes[NUM_MTYPES]; - - int minl; // min level - int maxl; // max level - char mamask; - const int numskeltypes = 19; - - int nt; // number of types - - if (gbIsSpawn) - mamask = 1; // monster availability mask - else - mamask = 3; // monster availability mask - - AddMonsterType(MT_GOLEM, PLACE_SPECIAL); - if (currlevel == 16) { - AddMonsterType(MT_ADVOCATE, PLACE_SCATTER); - AddMonsterType(MT_RBLACK, PLACE_SCATTER); - AddMonsterType(MT_DIABLO, PLACE_SPECIAL); - return; - } - - if (currlevel == 18) - AddMonsterType(MT_HORKSPWN, PLACE_SCATTER); - if (currlevel == 19) { - AddMonsterType(MT_HORKSPWN, PLACE_SCATTER); - AddMonsterType(MT_HORKDMN, PLACE_UNIQUE); - } - if (currlevel == 20) - AddMonsterType(MT_DEFILER, PLACE_UNIQUE); - if (currlevel == 24) { - AddMonsterType(MT_ARCHLICH, PLACE_SCATTER); - AddMonsterType(MT_NAKRUL, PLACE_SPECIAL); + if (LevelMonsterTypes[mtype].mtype == MT_NAKRUL) { + for (int j = 0; j < ActiveMonsterCount; j++) { + if (Monsters[j]._mMTidx == mtype) { + return; + } + if (Monsters[j].MType->mtype == MT_NAKRUL) { + return; + } + } } + dMonster[x][y] = i + 1; - if (!setlevel) { - if (QuestStatus(Q_BUTCHER)) - AddMonsterType(MT_CLEAVER, PLACE_SPECIAL); - if (QuestStatus(Q_GARBUD)) - AddMonsterType(UniqMonst[UMT_GARBUD].mtype, PLACE_UNIQUE); - if (QuestStatus(Q_ZHAR)) - AddMonsterType(UniqMonst[UMT_ZHAR].mtype, PLACE_UNIQUE); - if (QuestStatus(Q_LTBANNER)) - AddMonsterType(UniqMonst[UMT_SNOTSPIL].mtype, PLACE_UNIQUE); - if (QuestStatus(Q_VEIL)) - AddMonsterType(UniqMonst[UMT_LACHDAN].mtype, PLACE_UNIQUE); - if (QuestStatus(Q_WARLORD)) - AddMonsterType(UniqMonst[UMT_WARLORD].mtype, PLACE_UNIQUE); - - if (gbIsMultiplayer && currlevel == Quests[Q_SKELKING]._qlevel) { - - AddMonsterType(MT_SKING, PLACE_UNIQUE); + auto rd = static_cast(GenerateRnd(8)); + InitMonster(i, rd, mtype, { x, y }); +} - nt = 0; - for (int i = MT_WSKELAX; i <= MT_WSKELAX + numskeltypes; i++) { - if (IsSkel(i)) { - minl = 15 * MonsterData[i].mMinDLvl / 30 + 1; - maxl = 15 * MonsterData[i].mMaxDLvl / 30 + 1; +void PlaceGroup(int mtype, int num, int leaderf, int leader) +{ + int placed = 0; - if (currlevel >= minl && currlevel <= maxl) { - if ((MonstAvailTbl[i] & mamask) != 0) { - skeltypes[nt++] = (_monster_id)i; - } - } - } - } - AddMonsterType(skeltypes[GenerateRnd(nt)], PLACE_SCATTER); + for (int try1 = 0; try1 < 10; try1++) { + while (placed != 0) { + ActiveMonsterCount--; + placed--; + dMonster[Monsters[ActiveMonsterCount].position.tile.x][Monsters[ActiveMonsterCount].position.tile.y] = 0; } - nt = 0; - for (int i = MT_NZOMBIE; i < NUM_MTYPES; i++) { - minl = 15 * MonsterData[i].mMinDLvl / 30 + 1; - maxl = 15 * MonsterData[i].mMaxDLvl / 30 + 1; + int xp; + int yp; + if ((leaderf & 1) != 0) { + int offset = GenerateRnd(8); + auto position = Monsters[leader].position.tile + static_cast(offset); + xp = position.x; + yp = position.y; + } else { + do { + xp = GenerateRnd(80) + 16; + yp = GenerateRnd(80) + 16; + } while (!MonstPlace(xp, yp)); + } + int x1 = xp; + int y1 = yp; - if (currlevel >= minl && currlevel <= maxl) { - if ((MonstAvailTbl[i] & mamask) != 0) { - typelist[nt++] = (_monster_id)i; - } - } + if (num + ActiveMonsterCount > totalmonsters) { + num = totalmonsters - ActiveMonsterCount; } -#ifdef _DEBUG - if (monstdebug) { - for (int i = 0; i < debugmonsttypes; i++) - AddMonsterType(DebugMonsters[i], PLACE_SCATTER); - } else -#endif - { + int j = 0; + for (int try2 = 0; j < num && try2 < 100; xp += Displacement::fromDirection(static_cast(GenerateRnd(8))).deltaX, yp += Displacement::fromDirection(static_cast(GenerateRnd(8))).deltaX) { /// BUGFIX: `yp += Point.y` + if (!MonstPlace(xp, yp) + || (dTransVal[xp][yp] != dTransVal[x1][y1]) + || ((leaderf & 2) != 0 && (abs(xp - x1) >= 4 || abs(yp - y1) >= 4))) { + try2++; + continue; + } - while (nt > 0 && LevelMonsterTypeCount < MAX_LVLMTYPES && monstimgtot < 4000) { - for (int i = 0; i < nt;) { - if (MonsterData[typelist[i]].mImage > 4000 - monstimgtot) { - typelist[i] = typelist[--nt]; - continue; - } + PlaceMonster(ActiveMonsterCount, mtype, xp, yp); + if ((leaderf & 1) != 0) { + Monsters[ActiveMonsterCount]._mmaxhp *= 2; + Monsters[ActiveMonsterCount]._mhitpoints = Monsters[ActiveMonsterCount]._mmaxhp; + Monsters[ActiveMonsterCount]._mint = Monsters[leader]._mint; - i++; + if ((leaderf & 2) != 0) { + Monsters[ActiveMonsterCount].leader = leader; + Monsters[ActiveMonsterCount].leaderflag = 1; + Monsters[ActiveMonsterCount]._mAi = Monsters[leader]._mAi; } - if (nt != 0) { - int i = GenerateRnd(nt); - AddMonsterType(typelist[i], PLACE_SCATTER); - typelist[i] = typelist[--nt]; + if (Monsters[ActiveMonsterCount]._mAi != AI_GARG) { + Monsters[ActiveMonsterCount].AnimInfo.pCelSprite = &*Monsters[ActiveMonsterCount].MType->GetAnimData(MonsterGraphic::Stand).CelSpritesForDirections[Monsters[ActiveMonsterCount]._mdir]; + Monsters[ActiveMonsterCount].AnimInfo.CurrentFrame = GenerateRnd(Monsters[ActiveMonsterCount].AnimInfo.NumberOfFrames - 1) + 1; + Monsters[ActiveMonsterCount]._mFlags &= ~MFLAG_ALLOW_SPECIAL; + Monsters[ActiveMonsterCount]._mmode = MM_STAND; } } + ActiveMonsterCount++; + placed++; + j++; } - } else { - if (setlvlnum == SL_SKELKING) { - AddMonsterType(MT_SKING, PLACE_UNIQUE); + if (placed >= num) { + break; } } + + if ((leaderf & 2) != 0) { + Monsters[leader].packsize = placed; + } } -void InitMonsterGFX(int monst) +void PlaceUniqueMonst(int uniqindex, int miniontype, int bosspacksize) { - int mtype = LevelMonsterTypes[monst].mtype; - int width = MonsterData[mtype].width; - - for (int anim = 0; anim < 6; anim++) { - int frames = MonsterData[mtype].Frames[anim]; + MonsterStruct *monst = &Monsters[ActiveMonsterCount]; + const UniqMonstStruct *uniq = &UniqMonst[uniqindex]; - if ((animletter[anim] != 's' || MonsterData[mtype].has_special) && frames > 0) { - char strBuff[256]; - sprintf(strBuff, MonsterData[mtype].GraphicType, animletter[anim]); + if ((uniquetrans + 19) * 256 >= LIGHTSIZE) { + return; + } - byte *celBuf; - { - auto celData = LoadFileInMem(strBuff); - celBuf = celData.get(); - LevelMonsterTypes[monst].Anims[anim].CMem = std::move(celData); - } + int uniqtype; + for (uniqtype = 0; uniqtype < LevelMonsterTypeCount; uniqtype++) { + if (LevelMonsterTypes[uniqtype].mtype == UniqMonst[uniqindex].mtype) { + break; + } + } - if (LevelMonsterTypes[monst].mtype != MT_GOLEM || (animletter[anim] != 's' && animletter[anim] != 'd')) { - for (int i = 0; i < 8; i++) { - byte *pCelStart = CelGetFrame(celBuf, i); - LevelMonsterTypes[monst].Anims[anim].CelSpritesForDirections[i].emplace(pCelStart, width); - } - } else { - for (int i = 0; i < 8; i++) { - LevelMonsterTypes[monst].Anims[anim].CelSpritesForDirections[i].emplace(celBuf, width); + int count = 0; + int xp; + int yp; + while (true) { + xp = GenerateRnd(80) + 16; + yp = GenerateRnd(80) + 16; + int count2 = 0; + for (int x = xp - 3; x < xp + 3; x++) { + for (int y = yp - 3; y < yp + 3; y++) { + if (y >= 0 && y < MAXDUNY && x >= 0 && x < MAXDUNX && MonstPlace(x, y)) { + count2++; } } } - LevelMonsterTypes[monst].Anims[anim].Frames = frames; - LevelMonsterTypes[monst].Anims[anim].Rate = MonsterData[mtype].Rate[anim]; - } - - LevelMonsterTypes[monst].mMinHP = MonsterData[mtype].mMinHP; - LevelMonsterTypes[monst].mMaxHP = MonsterData[mtype].mMaxHP; - if (!gbIsHellfire && mtype == MT_DIABLO) { - LevelMonsterTypes[monst].mMinHP -= 2000; - LevelMonsterTypes[monst].mMaxHP -= 2000; - } - LevelMonsterTypes[monst].mAFNum = MonsterData[mtype].mAFNum; - LevelMonsterTypes[monst].MData = &MonsterData[mtype]; + if (count2 < 9) { + count++; + if (count < 1000) { + continue; + } + } - if (MonsterData[mtype].has_trans) { - InitMonsterTRN(LevelMonsterTypes[monst]); + if (MonstPlace(xp, yp)) { + break; + } } - if (mtype >= MT_NMAGMA && mtype <= MT_WMAGMA && (MissileFileFlag & 1) == 0) { - MissileFileFlag |= 1; - LoadMissileGFX(MFILE_MAGBALL); - } - if (mtype >= MT_STORM && mtype <= MT_MAEL && (MissileFileFlag & 2) == 0) { - MissileFileFlag |= 2; - LoadMissileGFX(MFILE_THINLGHT); - } - if (mtype == MT_SUCCUBUS && (MissileFileFlag & 4) == 0) { - MissileFileFlag |= 4; - LoadMissileGFX(MFILE_FLARE); - LoadMissileGFX(MFILE_FLAREEXP); + if (uniqindex == UMT_SNOTSPIL) { + xp = 2 * setpc_x + 24; + yp = 2 * setpc_y + 28; } - if (mtype >= MT_INCIN && mtype <= MT_HELLBURN && (MissileFileFlag & 8) == 0) { - MissileFileFlag |= 8; - LoadMissileGFX(MFILE_KRULL); + if (uniqindex == UMT_WARLORD) { + xp = 2 * setpc_x + 22; + yp = 2 * setpc_y + 23; } - if (mtype == MT_SNOWWICH && (MissileFileFlag & 0x20) == 0) { - MissileFileFlag |= 0x20; - LoadMissileGFX(MFILE_SCUBMISB); - LoadMissileGFX(MFILE_SCBSEXPB); + if (uniqindex == UMT_ZHAR) { + for (int i = 0; i < themeCount; i++) { + if (i == zharlib) { + xp = 2 * themeLoc[i].x + 20; + yp = 2 * themeLoc[i].y + 20; + break; + } + } } - if (mtype == MT_HLSPWN && (MissileFileFlag & 0x40) == 0) { - MissileFileFlag |= 0x40; - LoadMissileGFX(MFILE_SCUBMISD); - LoadMissileGFX(MFILE_SCBSEXPD); + if (!gbIsMultiplayer) { + if (uniqindex == UMT_LAZURUS) { + xp = 32; + yp = 46; + } + if (uniqindex == UMT_RED_VEX) { + xp = 40; + yp = 45; + } + if (uniqindex == UMT_BLACKJADE) { + xp = 38; + yp = 49; + } + if (uniqindex == UMT_SKELKING) { + xp = 35; + yp = 47; + } + } else { + if (uniqindex == UMT_LAZURUS) { + xp = 2 * setpc_x + 19; + yp = 2 * setpc_y + 22; + } + if (uniqindex == UMT_RED_VEX) { + xp = 2 * setpc_x + 21; + yp = 2 * setpc_y + 19; + } + if (uniqindex == UMT_BLACKJADE) { + xp = 2 * setpc_x + 21; + yp = 2 * setpc_y + 25; + } } - if (mtype == MT_SOLBRNR && (MissileFileFlag & 0x80) == 0) { - MissileFileFlag |= 0x80; - LoadMissileGFX(MFILE_SCUBMISC); - LoadMissileGFX(MFILE_SCBSEXPC); + if (uniqindex == UMT_BUTCHER) { + bool done = false; + for (yp = 0; yp < MAXDUNY && !done; yp++) { + for (xp = 0; xp < MAXDUNX && !done; xp++) { + done = dPiece[xp][yp] == 367; + } + } } - if (mtype >= MT_INCIN && mtype <= MT_HELLBURN && (MissileFileFlag & 8) == 0) { - MissileFileFlag |= 8; - LoadMissileGFX(MFILE_KRULL); + + if (uniqindex == UMT_NAKRUL) { + if (UberRow == 0 || UberCol == 0) { + UberDiabloMonsterIndex = -1; + return; + } + xp = UberRow - 2; + yp = UberCol; + UberDiabloMonsterIndex = ActiveMonsterCount; } - if (((mtype >= MT_NACID && mtype <= MT_XACID) || mtype == MT_SPIDLORD) && (MissileFileFlag & 0x10) == 0) { - MissileFileFlag |= 0x10; - LoadMissileGFX(MFILE_ACIDBF); - LoadMissileGFX(MFILE_ACIDSPLA); - LoadMissileGFX(MFILE_ACIDPUD); + PlaceMonster(ActiveMonsterCount, uniqtype, xp, yp); + monst->_uniqtype = uniqindex + 1; + + if (uniq->mlevel != 0) { + monst->mLevel = 2 * uniq->mlevel; + } else { + monst->mLevel += 5; } - if (mtype == MT_LICH && (MissileFileFlag & 0x100) == 0) { - MissileFileFlag |= 0x100; - LoadMissileGFX(MFILE_LICH); - LoadMissileGFX(MFILE_EXORA1); + + monst->mExp *= 2; + monst->mName = _(uniq->mName); + monst->_mmaxhp = uniq->mmaxhp << 6; + + if (!gbIsMultiplayer) { + monst->_mmaxhp = monst->_mmaxhp / 2; + if (monst->_mmaxhp < 64) { + monst->_mmaxhp = 64; + } } - if (mtype == MT_ARCHLICH && (MissileFileFlag & 0x200) == 0) { - MissileFileFlag |= 0x200; - LoadMissileGFX(MFILE_ARCHLICH); - LoadMissileGFX(MFILE_EXYEL2); + + monst->_mhitpoints = monst->_mmaxhp; + monst->_mAi = uniq->mAi; + monst->_mint = uniq->mint; + monst->mMinDamage = uniq->mMinDamage; + monst->mMaxDamage = uniq->mMaxDamage; + monst->mMinDamage2 = uniq->mMinDamage; + monst->mMaxDamage2 = uniq->mMaxDamage; + monst->mMagicRes = uniq->mMagicRes; + monst->mtalkmsg = uniq->mtalkmsg; + if (uniqindex == UMT_HORKDMN) + monst->mlid = NO_LIGHT; // BUGFIX monsters initial light id should be -1 (fixed) + else + monst->mlid = AddLight(monst->position.tile, 3); + + if (gbIsMultiplayer) { + if (monst->_mAi == AI_LAZHELP) + monst->mtalkmsg = TEXT_NONE; + if (monst->_mAi == AI_LAZURUS && Quests[Q_BETRAYER]._qvar1 > 3) { + monst->_mgoal = MGOAL_NORMAL; + } else if (monst->mtalkmsg != TEXT_NONE) { + monst->_mgoal = MGOAL_INQUIRING; + } + } else if (monst->mtalkmsg != TEXT_NONE) { + monst->_mgoal = MGOAL_INQUIRING; } - if ((mtype == MT_PSYCHORB || mtype == MT_BONEDEMN) && (MissileFileFlag & 0x400) == 0) { - MissileFileFlag |= 0x400; - LoadMissileGFX(MFILE_BONEDEMON); + + if (sgGameInitInfo.nDifficulty == DIFF_NIGHTMARE) { + monst->_mmaxhp = 3 * monst->_mmaxhp; + if (gbIsHellfire) + monst->_mmaxhp += (gbIsMultiplayer ? 100 : 50) << 6; + else + monst->_mmaxhp += 64; + monst->mLevel += 15; + monst->_mhitpoints = monst->_mmaxhp; + monst->mExp = 2 * (monst->mExp + 1000); + monst->mMinDamage = 2 * (monst->mMinDamage + 2); + monst->mMaxDamage = 2 * (monst->mMaxDamage + 2); + monst->mMinDamage2 = 2 * (monst->mMinDamage2 + 2); + monst->mMaxDamage2 = 2 * (monst->mMaxDamage2 + 2); + } else if (sgGameInitInfo.nDifficulty == DIFF_HELL) { + monst->_mmaxhp = 4 * monst->_mmaxhp; + if (gbIsHellfire) + monst->_mmaxhp += (gbIsMultiplayer ? 200 : 100) << 6; + else + monst->_mmaxhp += 192; + monst->mLevel += 30; + monst->_mhitpoints = monst->_mmaxhp; + monst->mExp = 4 * (monst->mExp + 1000); + monst->mMinDamage = 4 * monst->mMinDamage + 6; + monst->mMaxDamage = 4 * monst->mMaxDamage + 6; + monst->mMinDamage2 = 4 * monst->mMinDamage2 + 6; + monst->mMaxDamage2 = 4 * monst->mMaxDamage2 + 6; } - if (mtype == MT_NECRMORB && (MissileFileFlag & 0x800) == 0) { - MissileFileFlag |= 0x800; - LoadMissileGFX(MFILE_NECROMORB); - LoadMissileGFX(MFILE_EXRED3); + + char filestr[64]; + sprintf(filestr, "Monsters\\Monsters\\%s.TRN", uniq->mTrnName); + LoadFileInMem(filestr, &LightTables[256 * (uniquetrans + 19)], 256); + + monst->_uniqtrans = uniquetrans++; + + if ((uniq->mUnqAttr & 4) != 0) { + monst->mHit = uniq->mUnqVar1; + monst->mHit2 = uniq->mUnqVar1; + + if (sgGameInitInfo.nDifficulty == DIFF_NIGHTMARE) { + monst->mHit += NIGHTMARE_TO_HIT_BONUS; + monst->mHit2 += NIGHTMARE_TO_HIT_BONUS; + } else if (sgGameInitInfo.nDifficulty == DIFF_HELL) { + monst->mHit += HELL_TO_HIT_BONUS; + monst->mHit2 += HELL_TO_HIT_BONUS; + } } - if (mtype == MT_PSYCHORB && (MissileFileFlag & 0x1000) == 0) { - MissileFileFlag |= 0x1000; - LoadMissileGFX(MFILE_EXBL2); + if ((uniq->mUnqAttr & 8) != 0) { + monst->mArmorClass = uniq->mUnqVar1; + + if (sgGameInitInfo.nDifficulty == DIFF_NIGHTMARE) { + monst->mArmorClass += NIGHTMARE_AC_BONUS; + } else if (sgGameInitInfo.nDifficulty == DIFF_HELL) { + monst->mArmorClass += HELL_AC_BONUS; + } } - if (mtype == MT_BONEDEMN && (MissileFileFlag & 0x2000) == 0) { - MissileFileFlag |= 0x2000; - LoadMissileGFX(MFILE_EXBL3); + + ActiveMonsterCount++; + + if ((uniq->mUnqAttr & 1) != 0) { + PlaceGroup(miniontype, bosspacksize, uniq->mUnqAttr, ActiveMonsterCount - 1); } - if (mtype == MT_DIABLO) { - LoadMissileGFX(MFILE_FIREPLAR); + + if (monst->_mAi != AI_GARG) { + monst->AnimInfo.pCelSprite = &*monst->MType->GetAnimData(MonsterGraphic::Stand).CelSpritesForDirections[monst->_mdir]; + monst->AnimInfo.CurrentFrame = GenerateRnd(monst->AnimInfo.NumberOfFrames - 1) + 1; + monst->_mFlags &= ~MFLAG_ALLOW_SPECIAL; + monst->_mmode = MM_STAND; + } +} + +int AddMonsterType(_monster_id type, placeflag placeflag) +{ + bool done = false; + int i; + + for (i = 0; i < LevelMonsterTypeCount && !done; i++) { + done = LevelMonsterTypes[i].mtype == type; + } + + i--; + + if (!done) { + i = LevelMonsterTypeCount; + LevelMonsterTypeCount++; + LevelMonsterTypes[i].mtype = type; + monstimgtot += MonsterData[type].mImage; + InitMonsterGFX(i); + InitMonsterSND(i); } + + LevelMonsterTypes[i].mPlaceFlags |= placeflag; + return i; } void ClearMVars(int i) @@ -525,913 +641,297 @@ void ClearMVars(int i) Monsters[i].position.offset2 = { 0, 0 }; } -void InitMonster(int i, Direction rd, int mtype, Point position) +void ClrAllMonsters() { - CMonster *monst = &LevelMonsterTypes[mtype]; - - auto &animData = monst->GetAnimData(MonsterGraphic::Stand); - - Monsters[i]._mdir = rd; - Monsters[i].position.tile = position; - Monsters[i].position.future = position; - Monsters[i].position.old = position; - Monsters[i]._mMTidx = mtype; - Monsters[i]._mmode = MM_STAND; - Monsters[i].mName = _(monst->MData->mName); - Monsters[i].MType = monst; - Monsters[i].MData = monst->MData; - Monsters[i].AnimInfo = {}; - Monsters[i].AnimInfo.pCelSprite = animData.CelSpritesForDirections[rd] ? &*animData.CelSpritesForDirections[rd] : nullptr; - Monsters[i].AnimInfo.TicksPerFrame = animData.Rate; - Monsters[i].AnimInfo.TickCounterOfCurrentFrame = GenerateRnd(Monsters[i].AnimInfo.TicksPerFrame - 1); - Monsters[i].AnimInfo.NumberOfFrames = animData.Frames; - Monsters[i].AnimInfo.CurrentFrame = GenerateRnd(Monsters[i].AnimInfo.NumberOfFrames - 1) + 1; + for (int i = 0; i < MAXMONSTERS; i++) { + MonsterStruct *monst = &Monsters[i]; + ClearMVars(i); + monst->mName = "Invalid Monster"; + monst->_mgoal = MGOAL_NONE; + monst->_mmode = MM_STAND; + monst->_mVar1 = 0; + monst->_mVar2 = 0; + monst->position.tile = { 0, 0 }; + monst->position.future = { 0, 0 }; + monst->position.old = { 0, 0 }; + monst->_mdir = static_cast(GenerateRnd(8)); + monst->position.velocity = { 0, 0 }; + monst->AnimInfo = {}; + monst->_mFlags = 0; + monst->_mDelFlag = false; + monst->_menemy = GenerateRnd(gbActivePlayers); + monst->enemyPosition = Players[monst->_menemy].position.future; + } +} - Monsters[i].mLevel = monst->MData->mLevel; - Monsters[i]._mmaxhp = (monst->mMinHP + GenerateRnd(monst->mMaxHP - monst->mMinHP + 1)) << 6; - if (monst->mtype == MT_DIABLO && !gbIsHellfire) { - Monsters[i]._mmaxhp /= 2; - Monsters[i].mLevel -= 15; - } - - if (!gbIsMultiplayer) { - Monsters[i]._mmaxhp /= 2; - if (Monsters[i]._mmaxhp < 64) { - Monsters[i]._mmaxhp = 64; - } - } - - Monsters[i]._mhitpoints = Monsters[i]._mmaxhp; - Monsters[i]._mAi = monst->MData->mAi; - Monsters[i]._mint = monst->MData->mInt; - Monsters[i]._mgoal = MGOAL_NORMAL; - Monsters[i]._mgoalvar1 = 0; - Monsters[i]._mgoalvar2 = 0; - Monsters[i]._mgoalvar3 = 0; - Monsters[i]._pathcount = 0; - Monsters[i]._mDelFlag = false; - Monsters[i]._uniqtype = 0; - Monsters[i]._msquelch = 0; - Monsters[i].mlid = NO_LIGHT; // BUGFIX monsters initial light id should be -1 (fixed) - Monsters[i]._mRndSeed = AdvanceRndSeed(); - Monsters[i]._mAISeed = AdvanceRndSeed(); - Monsters[i].mWhoHit = 0; - Monsters[i].mExp = monst->MData->mExp; - Monsters[i].mHit = monst->MData->mHit; - Monsters[i].mMinDamage = monst->MData->mMinDamage; - Monsters[i].mMaxDamage = monst->MData->mMaxDamage; - Monsters[i].mHit2 = monst->MData->mHit2; - Monsters[i].mMinDamage2 = monst->MData->mMinDamage2; - Monsters[i].mMaxDamage2 = monst->MData->mMaxDamage2; - Monsters[i].mArmorClass = monst->MData->mArmorClass; - Monsters[i].mMagicRes = monst->MData->mMagicRes; - Monsters[i].leader = 0; - Monsters[i].leaderflag = 0; - Monsters[i]._mFlags = monst->MData->mFlags; - Monsters[i].mtalkmsg = TEXT_NONE; - - if (Monsters[i]._mAi == AI_GARG) { - Monsters[i].AnimInfo.pCelSprite = &*monst->GetAnimData(MonsterGraphic::Special).CelSpritesForDirections[rd]; - Monsters[i].AnimInfo.CurrentFrame = 1; - Monsters[i]._mFlags |= MFLAG_ALLOW_SPECIAL; - Monsters[i]._mmode = MM_SATTACK; - } - - if (sgGameInitInfo.nDifficulty == DIFF_NIGHTMARE) { - Monsters[i]._mmaxhp = 3 * Monsters[i]._mmaxhp; - if (gbIsHellfire) - Monsters[i]._mmaxhp += (gbIsMultiplayer ? 100 : 50) << 6; - else - Monsters[i]._mmaxhp += 64; - Monsters[i]._mhitpoints = Monsters[i]._mmaxhp; - Monsters[i].mLevel += 15; - Monsters[i].mExp = 2 * (Monsters[i].mExp + 1000); - Monsters[i].mHit += NIGHTMARE_TO_HIT_BONUS; - Monsters[i].mMinDamage = 2 * (Monsters[i].mMinDamage + 2); - Monsters[i].mMaxDamage = 2 * (Monsters[i].mMaxDamage + 2); - Monsters[i].mHit2 += NIGHTMARE_TO_HIT_BONUS; - Monsters[i].mMinDamage2 = 2 * (Monsters[i].mMinDamage2 + 2); - Monsters[i].mMaxDamage2 = 2 * (Monsters[i].mMaxDamage2 + 2); - Monsters[i].mArmorClass += NIGHTMARE_AC_BONUS; - } else if (sgGameInitInfo.nDifficulty == DIFF_HELL) { - Monsters[i]._mmaxhp = 4 * Monsters[i]._mmaxhp; - if (gbIsHellfire) - Monsters[i]._mmaxhp += (gbIsMultiplayer ? 200 : 100) << 6; - else - Monsters[i]._mmaxhp += 192; - Monsters[i]._mhitpoints = Monsters[i]._mmaxhp; - Monsters[i].mLevel += 30; - Monsters[i].mExp = 4 * (Monsters[i].mExp + 1000); - Monsters[i].mHit += HELL_TO_HIT_BONUS; - Monsters[i].mMinDamage = 4 * Monsters[i].mMinDamage + 6; - Monsters[i].mMaxDamage = 4 * Monsters[i].mMaxDamage + 6; - Monsters[i].mHit2 += HELL_TO_HIT_BONUS; - Monsters[i].mMinDamage2 = 4 * Monsters[i].mMinDamage2 + 6; - Monsters[i].mMaxDamage2 = 4 * Monsters[i].mMaxDamage2 + 6; - Monsters[i].mArmorClass += HELL_AC_BONUS; - Monsters[i].mMagicRes = monst->MData->mMagicRes2; - } -} - -void ClrAllMonsters() +static void PlaceUniques() { - for (int i = 0; i < MAXMONSTERS; i++) { - MonsterStruct *monst = &Monsters[i]; - ClearMVars(i); - monst->mName = "Invalid Monster"; - monst->_mgoal = MGOAL_NONE; - monst->_mmode = MM_STAND; - monst->_mVar1 = 0; - monst->_mVar2 = 0; - monst->position.tile = { 0, 0 }; - monst->position.future = { 0, 0 }; - monst->position.old = { 0, 0 }; - monst->_mdir = static_cast(GenerateRnd(8)); - monst->position.velocity = { 0, 0 }; - monst->AnimInfo = {}; - monst->_mFlags = 0; - monst->_mDelFlag = false; - monst->_menemy = GenerateRnd(gbActivePlayers); - monst->enemyPosition = Players[monst->_menemy].position.future; + for (int u = 0; UniqMonst[u].mtype != -1; u++) { + if (UniqMonst[u].mlevel != currlevel) + continue; + bool done = false; + int mt; + for (mt = 0; mt < LevelMonsterTypeCount; mt++) { + done = (LevelMonsterTypes[mt].mtype == UniqMonst[u].mtype); + if (done) + break; + } + if (u == UMT_GARBUD && Quests[Q_GARBUD]._qactive == QUEST_NOTAVAIL) + done = false; + if (u == UMT_ZHAR && Quests[Q_ZHAR]._qactive == QUEST_NOTAVAIL) + done = false; + if (u == UMT_SNOTSPIL && Quests[Q_LTBANNER]._qactive == QUEST_NOTAVAIL) + done = false; + if (u == UMT_LACHDAN && Quests[Q_VEIL]._qactive == QUEST_NOTAVAIL) + done = false; + if (u == UMT_WARLORD && Quests[Q_WARLORD]._qactive == QUEST_NOTAVAIL) + done = false; + if (done) + PlaceUniqueMonst(u, mt, 8); } } -bool MonstPlace(int xp, int yp) +void PlaceQuestMonsters() { - char f; - - if (xp < 0 || xp >= MAXDUNX - || yp < 0 || yp >= MAXDUNY - || dMonster[xp][yp] != 0 - || dPlayer[xp][yp] != 0) { - return false; - } + int skeltype; - f = dFlags[xp][yp]; + if (!setlevel) { + if (QuestStatus(Q_BUTCHER)) { + PlaceUniqueMonst(UMT_BUTCHER, 0, 0); + } - if ((f & BFLAG_VISIBLE) != 0) { - return false; - } + if (currlevel == Quests[Q_SKELKING]._qlevel && gbIsMultiplayer) { + skeltype = 0; - if ((f & BFLAG_POPULATED) != 0) { - return false; - } + for (skeltype = 0; skeltype < LevelMonsterTypeCount; skeltype++) { + if (IsSkel(LevelMonsterTypes[skeltype].mtype)) { + break; + } + } - return !SolidLoc({ xp, yp }); -} + PlaceUniqueMonst(UMT_SKELKING, skeltype, 30); + } -void monster_some_crypt() -{ - MonsterStruct *mon; - int hp; + if (QuestStatus(Q_LTBANNER)) { + auto dunData = LoadFileInMem("Levels\\L1Data\\Banner1.DUN"); + SetMapMonsters(dunData.get(), Point { setpc_x, setpc_y } * 2); + } + if (QuestStatus(Q_BLOOD)) { + auto dunData = LoadFileInMem("Levels\\L2Data\\Blood2.DUN"); + SetMapMonsters(dunData.get(), Point { setpc_x, setpc_y } * 2); + } + if (QuestStatus(Q_BLIND)) { + auto dunData = LoadFileInMem("Levels\\L2Data\\Blind2.DUN"); + SetMapMonsters(dunData.get(), Point { setpc_x, setpc_y } * 2); + } + if (QuestStatus(Q_ANVIL)) { + auto dunData = LoadFileInMem("Levels\\L3Data\\Anvil.DUN"); + SetMapMonsters(dunData.get(), Point { setpc_x + 2, setpc_y + 2 } * 2); + } + if (QuestStatus(Q_WARLORD)) { + auto dunData = LoadFileInMem("Levels\\L4Data\\Warlord.DUN"); + SetMapMonsters(dunData.get(), Point { setpc_x, setpc_y } * 2); + AddMonsterType(UniqMonst[UMT_WARLORD].mtype, PLACE_SCATTER); + } + if (QuestStatus(Q_VEIL)) { + AddMonsterType(UniqMonst[UMT_LACHDAN].mtype, PLACE_SCATTER); + } + if (QuestStatus(Q_ZHAR) && zharlib == -1) { + Quests[Q_ZHAR]._qactive = QUEST_NOTAVAIL; + } - if (currlevel == 24 && UberDiabloMonsterIndex >= 0 && UberDiabloMonsterIndex < ActiveMonsterCount) { - mon = &Monsters[UberDiabloMonsterIndex]; - PlayEffect(UberDiabloMonsterIndex, 2); - Quests[Q_NAKRUL]._qlog = false; - mon->mArmorClass -= 50; - hp = mon->_mmaxhp / 2; - mon->mMagicRes = 0; - mon->_mhitpoints = hp; - mon->_mmaxhp = hp; - } -} + if (currlevel == Quests[Q_BETRAYER]._qlevel && gbIsMultiplayer) { + AddMonsterType(UniqMonst[UMT_LAZURUS].mtype, PLACE_UNIQUE); + AddMonsterType(UniqMonst[UMT_RED_VEX].mtype, PLACE_UNIQUE); + PlaceUniqueMonst(UMT_LAZURUS, 0, 0); + PlaceUniqueMonst(UMT_RED_VEX, 0, 0); + PlaceUniqueMonst(UMT_BLACKJADE, 0, 0); + auto dunData = LoadFileInMem("Levels\\L4Data\\Vile1.DUN"); + SetMapMonsters(dunData.get(), Point { setpc_x, setpc_y } * 2); + } -void PlaceMonster(int i, int mtype, int x, int y) -{ - if (LevelMonsterTypes[mtype].mtype == MT_NAKRUL) { - for (int j = 0; j < ActiveMonsterCount; j++) { - if (Monsters[j]._mMTidx == mtype) { - return; + if (currlevel == 24) { + UberDiabloMonsterIndex = -1; + int i1; + for (i1 = 0; i1 < LevelMonsterTypeCount; i1++) { + if (LevelMonsterTypes[i1].mtype == UniqMonst[UMT_NAKRUL].mtype) + break; } - if (Monsters[j].MType->mtype == MT_NAKRUL) { - return; + + if (i1 < LevelMonsterTypeCount) { + for (int i2 = 0; i2 < ActiveMonsterCount; i2++) { + if (Monsters[i2]._uniqtype != 0 || Monsters[i2]._mMTidx == i1) { + UberDiabloMonsterIndex = i2; + break; + } + } } + if (UberDiabloMonsterIndex == -1) + PlaceUniqueMonst(UMT_NAKRUL, 0, 0); } + } else if (setlvlnum == SL_SKELKING) { + PlaceUniqueMonst(UMT_SKELKING, 0, 0); } - dMonster[x][y] = i + 1; - - auto rd = static_cast(GenerateRnd(8)); - InitMonster(i, rd, mtype, { x, y }); } -void PlaceUniqueMonst(int uniqindex, int miniontype, int bosspacksize) +void LoadDiabMonsts() { - MonsterStruct *monst = &Monsters[ActiveMonsterCount]; - const UniqMonstStruct *uniq = &UniqMonst[uniqindex]; - - if ((uniquetrans + 19) * 256 >= LIGHTSIZE) { - return; + { + auto dunData = LoadFileInMem("Levels\\L4Data\\diab1.DUN"); + SetMapMonsters(dunData.get(), Point { diabquad1x, diabquad1y } * 2); } - - int uniqtype; - for (uniqtype = 0; uniqtype < LevelMonsterTypeCount; uniqtype++) { - if (LevelMonsterTypes[uniqtype].mtype == UniqMonst[uniqindex].mtype) { - break; - } + { + auto dunData = LoadFileInMem("Levels\\L4Data\\diab2a.DUN"); + SetMapMonsters(dunData.get(), Point { diabquad2x, diabquad2y } * 2); + } + { + auto dunData = LoadFileInMem("Levels\\L4Data\\diab3a.DUN"); + SetMapMonsters(dunData.get(), Point { diabquad3x, diabquad3y } * 2); + } + { + auto dunData = LoadFileInMem("Levels\\L4Data\\diab4a.DUN"); + SetMapMonsters(dunData.get(), Point { diabquad4x, diabquad4y } * 2); } +} - int count = 0; - int xp; - int yp; - while (true) { - xp = GenerateRnd(80) + 16; - yp = GenerateRnd(80) + 16; - int count2 = 0; - for (int x = xp - 3; x < xp + 3; x++) { - for (int y = yp - 3; y < yp + 3; y++) { - if (y >= 0 && y < MAXDUNY && x >= 0 && x < MAXDUNX && MonstPlace(x, y)) { - count2++; - } - } - } +void DeleteMonster(int i) +{ + ActiveMonsterCount--; + ActiveMonsters[i] = ActiveMonsters[ActiveMonsterCount]; +} - if (count2 < 9) { - count++; - if (count < 1000) { - continue; - } - } +void NewMonsterAnim(MonsterStruct &monster, MonsterGraphic graphic, Direction md, AnimationDistributionFlags flags = AnimationDistributionFlags::None, int numSkippedFrames = 0, int distributeFramesBeforeFrame = 0) +{ + auto &animData = monster.MType->GetAnimData(graphic); + auto *pCelSprite = &*animData.CelSpritesForDirections[md]; + monster.AnimInfo.SetNewAnimation(pCelSprite, animData.Frames, animData.Rate, flags, numSkippedFrames, distributeFramesBeforeFrame); + monster._mFlags &= ~(MFLAG_LOCK_ANIMATION | MFLAG_ALLOW_SPECIAL); + monster._mdir = md; +} - if (MonstPlace(xp, yp)) { - break; - } +void StartMonsterGotHit(int monsterId) +{ + auto &monster = Monsters[monsterId]; + if (monster.MType->mtype != MT_GOLEM) { + auto animationFlags = gGameLogicStep < GameLogicStep::ProcessMonsters ? AnimationDistributionFlags::ProcessAnimationPending : AnimationDistributionFlags::None; + int numSkippedFrames = (gbIsHellfire && monster.MType->mtype == MT_DIABLO) ? 4 : 0; + NewMonsterAnim(monster, MonsterGraphic::GotHit, monster._mdir, animationFlags, numSkippedFrames); + monster._mmode = MM_GOTHIT; } + monster.position.offset = { 0, 0 }; + monster.position.tile = monster.position.old; + monster.position.future = monster.position.old; + M_ClearSquares(monsterId); + dMonster[monster.position.tile.x][monster.position.tile.y] = monsterId + 1; +} - if (uniqindex == UMT_SNOTSPIL) { - xp = 2 * setpc_x + 24; - yp = 2 * setpc_y + 28; - } - if (uniqindex == UMT_WARLORD) { - xp = 2 * setpc_x + 22; - yp = 2 * setpc_y + 23; - } - if (uniqindex == UMT_ZHAR) { - for (int i = 0; i < themeCount; i++) { - if (i == zharlib) { - xp = 2 * themeLoc[i].x + 20; - yp = 2 * themeLoc[i].y + 20; - break; +bool M_Ranged(int i) +{ + return IsAnyOf(Monsters[i]._mAi, AI_SKELBOW, AI_GOATBOW, AI_SUCC, AI_LAZHELP); +} + +void M_Enemy(int i) +{ + BYTE enemyx; + BYTE enemyy; + + int menemy = -1; + int bestDist = -1; + bool bestsameroom = false; + MonsterStruct *monst = &Monsters[i]; + if ((monst->_mFlags & MFLAG_BERSERK) != 0 || (monst->_mFlags & MFLAG_GOLEM) == 0) { + for (int pnum = 0; pnum < MAX_PLRS; pnum++) { + if (!Players[pnum].plractive || currlevel != Players[pnum].plrlevel || Players[pnum]._pLvlChanging + || (((Players[pnum]._pHitPoints >> 6) == 0) && gbIsMultiplayer)) + continue; + bool sameroom = (dTransVal[monst->position.tile.x][monst->position.tile.y] == dTransVal[Players[pnum].position.tile.x][Players[pnum].position.tile.y]); + int dist = monst->position.tile.WalkingDistance(Players[pnum].position.tile); + if ((sameroom && !bestsameroom) + || ((sameroom || !bestsameroom) && dist < bestDist) + || (menemy == -1)) { + monst->_mFlags &= ~MFLAG_TARGETS_MONSTER; + menemy = pnum; + enemyx = Players[pnum].position.future.x; + enemyy = Players[pnum].position.future.y; + bestDist = dist; + bestsameroom = sameroom; } } } - if (!gbIsMultiplayer) { - if (uniqindex == UMT_LAZURUS) { - xp = 32; - yp = 46; - } - if (uniqindex == UMT_RED_VEX) { - xp = 40; - yp = 45; - } - if (uniqindex == UMT_BLACKJADE) { - xp = 38; - yp = 49; - } - if (uniqindex == UMT_SKELKING) { - xp = 35; - yp = 47; - } - } else { - if (uniqindex == UMT_LAZURUS) { - xp = 2 * setpc_x + 19; - yp = 2 * setpc_y + 22; - } - if (uniqindex == UMT_RED_VEX) { - xp = 2 * setpc_x + 21; - yp = 2 * setpc_y + 19; + for (int j = 0; j < ActiveMonsterCount; j++) { + int mi = ActiveMonsters[j]; + if (mi == i) + continue; + if ((Monsters[mi]._mhitpoints >> 6) <= 0) + continue; + if (Monsters[mi].position.tile.x == 1 && Monsters[mi].position.tile.y == 0) + continue; + if (M_Talker(mi) && Monsters[mi].mtalkmsg != TEXT_NONE) + continue; + if ((monst->_mFlags & MFLAG_GOLEM) != 0 && (Monsters[mi]._mFlags & MFLAG_GOLEM) != 0) // prevent golems from fighting each other + continue; + + int dist = Monsters[mi].position.tile.WalkingDistance(monst->position.tile); + if (((monst->_mFlags & MFLAG_GOLEM) == 0 + && (monst->_mFlags & MFLAG_BERSERK) == 0 + && dist >= 2 + && !M_Ranged(i)) + || ((monst->_mFlags & MFLAG_GOLEM) == 0 + && (monst->_mFlags & MFLAG_BERSERK) == 0 + && (Monsters[mi]._mFlags & MFLAG_GOLEM) == 0)) { + continue; } - if (uniqindex == UMT_BLACKJADE) { - xp = 2 * setpc_x + 21; - yp = 2 * setpc_y + 25; + bool sameroom = dTransVal[monst->position.tile.x][monst->position.tile.y] == dTransVal[Monsters[mi].position.tile.x][Monsters[mi].position.tile.y]; + if ((sameroom && !bestsameroom) + || ((sameroom || !bestsameroom) && dist < bestDist) + || (menemy == -1)) { + monst->_mFlags |= MFLAG_TARGETS_MONSTER; + menemy = mi; + enemyx = Monsters[mi].position.future.x; + enemyy = Monsters[mi].position.future.y; + bestDist = dist; + bestsameroom = sameroom; } } - if (uniqindex == UMT_BUTCHER) { - bool done = false; - for (yp = 0; yp < MAXDUNY && !done; yp++) { - for (xp = 0; xp < MAXDUNX && !done; xp++) { - done = dPiece[xp][yp] == 367; - } - } + if (menemy != -1) { + monst->_mFlags &= ~MFLAG_NO_ENEMY; + monst->_menemy = menemy; + monst->enemyPosition = { enemyx, enemyy }; + } else { + monst->_mFlags |= MFLAG_NO_ENEMY; } +} - if (uniqindex == UMT_NAKRUL) { - if (UberRow == 0 || UberCol == 0) { - UberDiabloMonsterIndex = -1; - return; - } - xp = UberRow - 2; - yp = UberCol; - UberDiabloMonsterIndex = ActiveMonsterCount; +void M_StartDelay(int i, int len) +{ + if (len <= 0) { + return; } - PlaceMonster(ActiveMonsterCount, uniqtype, xp, yp); - monst->_uniqtype = uniqindex + 1; - if (uniq->mlevel != 0) { - monst->mLevel = 2 * uniq->mlevel; - } else { - monst->mLevel += 5; + if (Monsters[i]._mAi != AI_LAZURUS) { + Monsters[i]._mVar2 = len; + Monsters[i]._mmode = MM_DELAY; } +} - monst->mExp *= 2; - monst->mName = _(uniq->mName); - monst->_mmaxhp = uniq->mmaxhp << 6; - - if (!gbIsMultiplayer) { - monst->_mmaxhp = monst->_mmaxhp / 2; - if (monst->_mmaxhp < 64) { - monst->_mmaxhp = 64; - } - } +Direction M_GetDir(int i) +{ + return GetDirection(Monsters[i].position.tile, Monsters[i].enemyPosition); +} - monst->_mhitpoints = monst->_mmaxhp; - monst->_mAi = uniq->mAi; - monst->_mint = uniq->mint; - monst->mMinDamage = uniq->mMinDamage; - monst->mMaxDamage = uniq->mMaxDamage; - monst->mMinDamage2 = uniq->mMinDamage; - monst->mMaxDamage2 = uniq->mMaxDamage; - monst->mMagicRes = uniq->mMagicRes; - monst->mtalkmsg = uniq->mtalkmsg; - if (uniqindex == UMT_HORKDMN) - monst->mlid = NO_LIGHT; // BUGFIX monsters initial light id should be -1 (fixed) - else - monst->mlid = AddLight(monst->position.tile, 3); +void M_StartSpStand(int i, Direction md) +{ + NewMonsterAnim(Monsters[i], MonsterGraphic::Special, md); + Monsters[i]._mmode = MM_SPSTAND; + Monsters[i].position.offset = { 0, 0 }; + Monsters[i].position.future = Monsters[i].position.tile; + Monsters[i].position.old = Monsters[i].position.tile; +} - if (gbIsMultiplayer) { - if (monst->_mAi == AI_LAZHELP) - monst->mtalkmsg = TEXT_NONE; - if (monst->_mAi == AI_LAZURUS && Quests[Q_BETRAYER]._qvar1 > 3) { - monst->_mgoal = MGOAL_NORMAL; - } else if (monst->mtalkmsg != TEXT_NONE) { - monst->_mgoal = MGOAL_INQUIRING; - } - } else if (monst->mtalkmsg != TEXT_NONE) { - monst->_mgoal = MGOAL_INQUIRING; - } - - if (sgGameInitInfo.nDifficulty == DIFF_NIGHTMARE) { - monst->_mmaxhp = 3 * monst->_mmaxhp; - if (gbIsHellfire) - monst->_mmaxhp += (gbIsMultiplayer ? 100 : 50) << 6; - else - monst->_mmaxhp += 64; - monst->mLevel += 15; - monst->_mhitpoints = monst->_mmaxhp; - monst->mExp = 2 * (monst->mExp + 1000); - monst->mMinDamage = 2 * (monst->mMinDamage + 2); - monst->mMaxDamage = 2 * (monst->mMaxDamage + 2); - monst->mMinDamage2 = 2 * (monst->mMinDamage2 + 2); - monst->mMaxDamage2 = 2 * (monst->mMaxDamage2 + 2); - } else if (sgGameInitInfo.nDifficulty == DIFF_HELL) { - monst->_mmaxhp = 4 * monst->_mmaxhp; - if (gbIsHellfire) - monst->_mmaxhp += (gbIsMultiplayer ? 200 : 100) << 6; - else - monst->_mmaxhp += 192; - monst->mLevel += 30; - monst->_mhitpoints = monst->_mmaxhp; - monst->mExp = 4 * (monst->mExp + 1000); - monst->mMinDamage = 4 * monst->mMinDamage + 6; - monst->mMaxDamage = 4 * monst->mMaxDamage + 6; - monst->mMinDamage2 = 4 * monst->mMinDamage2 + 6; - monst->mMaxDamage2 = 4 * monst->mMaxDamage2 + 6; - } - - char filestr[64]; - sprintf(filestr, "Monsters\\Monsters\\%s.TRN", uniq->mTrnName); - LoadFileInMem(filestr, &LightTables[256 * (uniquetrans + 19)], 256); - - monst->_uniqtrans = uniquetrans++; - - if ((uniq->mUnqAttr & 4) != 0) { - monst->mHit = uniq->mUnqVar1; - monst->mHit2 = uniq->mUnqVar1; - - if (sgGameInitInfo.nDifficulty == DIFF_NIGHTMARE) { - monst->mHit += NIGHTMARE_TO_HIT_BONUS; - monst->mHit2 += NIGHTMARE_TO_HIT_BONUS; - } else if (sgGameInitInfo.nDifficulty == DIFF_HELL) { - monst->mHit += HELL_TO_HIT_BONUS; - monst->mHit2 += HELL_TO_HIT_BONUS; - } - } - if ((uniq->mUnqAttr & 8) != 0) { - monst->mArmorClass = uniq->mUnqVar1; - - if (sgGameInitInfo.nDifficulty == DIFF_NIGHTMARE) { - monst->mArmorClass += NIGHTMARE_AC_BONUS; - } else if (sgGameInitInfo.nDifficulty == DIFF_HELL) { - monst->mArmorClass += HELL_AC_BONUS; - } - } - - ActiveMonsterCount++; - - if ((uniq->mUnqAttr & 1) != 0) { - PlaceGroup(miniontype, bosspacksize, uniq->mUnqAttr, ActiveMonsterCount - 1); - } - - if (monst->_mAi != AI_GARG) { - monst->AnimInfo.pCelSprite = &*monst->MType->GetAnimData(MonsterGraphic::Stand).CelSpritesForDirections[monst->_mdir]; - monst->AnimInfo.CurrentFrame = GenerateRnd(monst->AnimInfo.NumberOfFrames - 1) + 1; - monst->_mFlags &= ~MFLAG_ALLOW_SPECIAL; - monst->_mmode = MM_STAND; - } -} - -static void PlaceUniques() -{ - for (int u = 0; UniqMonst[u].mtype != -1; u++) { - if (UniqMonst[u].mlevel != currlevel) - continue; - bool done = false; - int mt; - for (mt = 0; mt < LevelMonsterTypeCount; mt++) { - done = (LevelMonsterTypes[mt].mtype == UniqMonst[u].mtype); - if (done) - break; - } - if (u == UMT_GARBUD && Quests[Q_GARBUD]._qactive == QUEST_NOTAVAIL) - done = false; - if (u == UMT_ZHAR && Quests[Q_ZHAR]._qactive == QUEST_NOTAVAIL) - done = false; - if (u == UMT_SNOTSPIL && Quests[Q_LTBANNER]._qactive == QUEST_NOTAVAIL) - done = false; - if (u == UMT_LACHDAN && Quests[Q_VEIL]._qactive == QUEST_NOTAVAIL) - done = false; - if (u == UMT_WARLORD && Quests[Q_WARLORD]._qactive == QUEST_NOTAVAIL) - done = false; - if (done) - PlaceUniqueMonst(u, mt, 8); - } -} - -void PlaceQuestMonsters() -{ - int skeltype; - - if (!setlevel) { - if (QuestStatus(Q_BUTCHER)) { - PlaceUniqueMonst(UMT_BUTCHER, 0, 0); - } - - if (currlevel == Quests[Q_SKELKING]._qlevel && gbIsMultiplayer) { - skeltype = 0; - - for (skeltype = 0; skeltype < LevelMonsterTypeCount; skeltype++) { - if (IsSkel(LevelMonsterTypes[skeltype].mtype)) { - break; - } - } - - PlaceUniqueMonst(UMT_SKELKING, skeltype, 30); - } - - if (QuestStatus(Q_LTBANNER)) { - auto dunData = LoadFileInMem("Levels\\L1Data\\Banner1.DUN"); - SetMapMonsters(dunData.get(), Point { setpc_x, setpc_y } * 2); - } - if (QuestStatus(Q_BLOOD)) { - auto dunData = LoadFileInMem("Levels\\L2Data\\Blood2.DUN"); - SetMapMonsters(dunData.get(), Point { setpc_x, setpc_y } * 2); - } - if (QuestStatus(Q_BLIND)) { - auto dunData = LoadFileInMem("Levels\\L2Data\\Blind2.DUN"); - SetMapMonsters(dunData.get(), Point { setpc_x, setpc_y } * 2); - } - if (QuestStatus(Q_ANVIL)) { - auto dunData = LoadFileInMem("Levels\\L3Data\\Anvil.DUN"); - SetMapMonsters(dunData.get(), Point { setpc_x + 2, setpc_y + 2 } * 2); - } - if (QuestStatus(Q_WARLORD)) { - auto dunData = LoadFileInMem("Levels\\L4Data\\Warlord.DUN"); - SetMapMonsters(dunData.get(), Point { setpc_x, setpc_y } * 2); - AddMonsterType(UniqMonst[UMT_WARLORD].mtype, PLACE_SCATTER); - } - if (QuestStatus(Q_VEIL)) { - AddMonsterType(UniqMonst[UMT_LACHDAN].mtype, PLACE_SCATTER); - } - if (QuestStatus(Q_ZHAR) && zharlib == -1) { - Quests[Q_ZHAR]._qactive = QUEST_NOTAVAIL; - } - - if (currlevel == Quests[Q_BETRAYER]._qlevel && gbIsMultiplayer) { - AddMonsterType(UniqMonst[UMT_LAZURUS].mtype, PLACE_UNIQUE); - AddMonsterType(UniqMonst[UMT_RED_VEX].mtype, PLACE_UNIQUE); - PlaceUniqueMonst(UMT_LAZURUS, 0, 0); - PlaceUniqueMonst(UMT_RED_VEX, 0, 0); - PlaceUniqueMonst(UMT_BLACKJADE, 0, 0); - auto dunData = LoadFileInMem("Levels\\L4Data\\Vile1.DUN"); - SetMapMonsters(dunData.get(), Point { setpc_x, setpc_y } * 2); - } - - if (currlevel == 24) { - UberDiabloMonsterIndex = -1; - int i1; - for (i1 = 0; i1 < LevelMonsterTypeCount; i1++) { - if (LevelMonsterTypes[i1].mtype == UniqMonst[UMT_NAKRUL].mtype) - break; - } - - if (i1 < LevelMonsterTypeCount) { - for (int i2 = 0; i2 < ActiveMonsterCount; i2++) { - if (Monsters[i2]._uniqtype != 0 || Monsters[i2]._mMTidx == i1) { - UberDiabloMonsterIndex = i2; - break; - } - } - } - if (UberDiabloMonsterIndex == -1) - PlaceUniqueMonst(UMT_NAKRUL, 0, 0); - } - } else if (setlvlnum == SL_SKELKING) { - PlaceUniqueMonst(UMT_SKELKING, 0, 0); - } -} - -void PlaceGroup(int mtype, int num, int leaderf, int leader) -{ - int placed = 0; - - for (int try1 = 0; try1 < 10; try1++) { - while (placed != 0) { - ActiveMonsterCount--; - placed--; - dMonster[Monsters[ActiveMonsterCount].position.tile.x][Monsters[ActiveMonsterCount].position.tile.y] = 0; - } - - int xp; - int yp; - if ((leaderf & 1) != 0) { - int offset = GenerateRnd(8); - auto position = Monsters[leader].position.tile + static_cast(offset); - xp = position.x; - yp = position.y; - } else { - do { - xp = GenerateRnd(80) + 16; - yp = GenerateRnd(80) + 16; - } while (!MonstPlace(xp, yp)); - } - int x1 = xp; - int y1 = yp; - - if (num + ActiveMonsterCount > totalmonsters) { - num = totalmonsters - ActiveMonsterCount; - } - - int j = 0; - for (int try2 = 0; j < num && try2 < 100; xp += Displacement::fromDirection(static_cast(GenerateRnd(8))).deltaX, yp += Displacement::fromDirection(static_cast(GenerateRnd(8))).deltaX) { /// BUGFIX: `yp += Point.y` - if (!MonstPlace(xp, yp) - || (dTransVal[xp][yp] != dTransVal[x1][y1]) - || ((leaderf & 2) != 0 && (abs(xp - x1) >= 4 || abs(yp - y1) >= 4))) { - try2++; - continue; - } - - PlaceMonster(ActiveMonsterCount, mtype, xp, yp); - if ((leaderf & 1) != 0) { - Monsters[ActiveMonsterCount]._mmaxhp *= 2; - Monsters[ActiveMonsterCount]._mhitpoints = Monsters[ActiveMonsterCount]._mmaxhp; - Monsters[ActiveMonsterCount]._mint = Monsters[leader]._mint; - - if ((leaderf & 2) != 0) { - Monsters[ActiveMonsterCount].leader = leader; - Monsters[ActiveMonsterCount].leaderflag = 1; - Monsters[ActiveMonsterCount]._mAi = Monsters[leader]._mAi; - } - - if (Monsters[ActiveMonsterCount]._mAi != AI_GARG) { - Monsters[ActiveMonsterCount].AnimInfo.pCelSprite = &*Monsters[ActiveMonsterCount].MType->GetAnimData(MonsterGraphic::Stand).CelSpritesForDirections[Monsters[ActiveMonsterCount]._mdir]; - Monsters[ActiveMonsterCount].AnimInfo.CurrentFrame = GenerateRnd(Monsters[ActiveMonsterCount].AnimInfo.NumberOfFrames - 1) + 1; - Monsters[ActiveMonsterCount]._mFlags &= ~MFLAG_ALLOW_SPECIAL; - Monsters[ActiveMonsterCount]._mmode = MM_STAND; - } - } - ActiveMonsterCount++; - placed++; - j++; - } - - if (placed >= num) { - break; - } - } - - if ((leaderf & 2) != 0) { - Monsters[leader].packsize = placed; - } -} - -void LoadDiabMonsts() -{ - { - auto dunData = LoadFileInMem("Levels\\L4Data\\diab1.DUN"); - SetMapMonsters(dunData.get(), Point { diabquad1x, diabquad1y } * 2); - } - { - auto dunData = LoadFileInMem("Levels\\L4Data\\diab2a.DUN"); - SetMapMonsters(dunData.get(), Point { diabquad2x, diabquad2y } * 2); - } - { - auto dunData = LoadFileInMem("Levels\\L4Data\\diab3a.DUN"); - SetMapMonsters(dunData.get(), Point { diabquad3x, diabquad3y } * 2); - } - { - auto dunData = LoadFileInMem("Levels\\L4Data\\diab4a.DUN"); - SetMapMonsters(dunData.get(), Point { diabquad4x, diabquad4y } * 2); - } -} - -void InitMonsters() -{ -#ifdef _DEBUG - if (gbIsMultiplayer) - CheckDungeonClear(); -#endif - if (!setlevel) { - AddMonster({ 1, 0 }, DIR_S, 0, false); - AddMonster({ 1, 0 }, DIR_S, 0, false); - AddMonster({ 1, 0 }, DIR_S, 0, false); - AddMonster({ 1, 0 }, DIR_S, 0, false); - } - - if (!gbIsSpawn && !setlevel && currlevel == 16) - LoadDiabMonsts(); - - int nt = numtrigs; - if (currlevel == 15) - nt = 1; - for (int i = 0; i < nt; i++) { - for (int s = -2; s < 2; s++) { - for (int t = -2; t < 2; t++) - DoVision(trigs[i].position + Displacement { s, t }, 15, false, false); - } - } - if (!gbIsSpawn) - PlaceQuestMonsters(); - if (!setlevel) { - if (!gbIsSpawn) - PlaceUniques(); - int na = 0; - for (int s = 16; s < 96; s++) { - for (int t = 16; t < 96; t++) { - if (!SolidLoc({ s, t })) - na++; - } - } - int numplacemonsters = na / 30; - if (gbIsMultiplayer) - numplacemonsters += numplacemonsters / 2; - if (ActiveMonsterCount + numplacemonsters > MAXMONSTERS - 10) - numplacemonsters = MAXMONSTERS - 10 - ActiveMonsterCount; - totalmonsters = ActiveMonsterCount + numplacemonsters; - int numscattypes = 0; - int scattertypes[NUM_MTYPES]; - for (int i = 0; i < LevelMonsterTypeCount; i++) { - if ((LevelMonsterTypes[i].mPlaceFlags & PLACE_SCATTER) != 0) { - scattertypes[numscattypes] = i; - numscattypes++; - } - } - while (ActiveMonsterCount < totalmonsters) { - int mtype = scattertypes[GenerateRnd(numscattypes)]; - if (currlevel == 1 || GenerateRnd(2) == 0) - na = 1; - else if (currlevel == 2 || (currlevel >= 21 && currlevel <= 24)) - na = GenerateRnd(2) + 2; - else - na = GenerateRnd(3) + 3; - PlaceGroup(mtype, na, 0, 0); - } - } - for (int i = 0; i < nt; i++) { - for (int s = -2; s < 2; s++) { - for (int t = -2; t < 2; t++) - DoUnVision(trigs[i].position + Displacement { s, t }, 15); - } - } -} - -void SetMapMonsters(const uint16_t *dunData, Point startPosition) -{ - AddMonsterType(MT_GOLEM, PLACE_SPECIAL); - AddMonster({ 1, 0 }, DIR_S, 0, false); - AddMonster({ 1, 0 }, DIR_S, 0, false); - AddMonster({ 1, 0 }, DIR_S, 0, false); - AddMonster({ 1, 0 }, DIR_S, 0, false); - if (setlevel && setlvlnum == SL_VILEBETRAYER) { - AddMonsterType(UniqMonst[UMT_LAZURUS].mtype, PLACE_UNIQUE); - AddMonsterType(UniqMonst[UMT_RED_VEX].mtype, PLACE_UNIQUE); - AddMonsterType(UniqMonst[UMT_BLACKJADE].mtype, PLACE_UNIQUE); - PlaceUniqueMonst(UMT_LAZURUS, 0, 0); - PlaceUniqueMonst(UMT_RED_VEX, 0, 0); - PlaceUniqueMonst(UMT_BLACKJADE, 0, 0); - } - - int width = SDL_SwapLE16(dunData[0]); - int height = SDL_SwapLE16(dunData[1]); - - int layer2Offset = 2 + width * height; - - // The rest of the layers are at dPiece scale - width *= 2; - height *= 2; - - const uint16_t *monsterLayer = &dunData[layer2Offset + width * height]; - - for (int j = 0; j < height; j++) { - for (int i = 0; i < width; i++) { - uint8_t monsterId = SDL_SwapLE16(monsterLayer[j * width + i]); - if (monsterId != 0) { - int mtype = AddMonsterType(MonstConvTbl[monsterId - 1], PLACE_SPECIAL); - PlaceMonster(ActiveMonsterCount++, mtype, i + startPosition.x + 16, j + startPosition.y + 16); - } - } - } -} - -void DeleteMonster(int i) -{ - ActiveMonsterCount--; - ActiveMonsters[i] = ActiveMonsters[ActiveMonsterCount]; -} - -int AddMonster(Point position, Direction dir, int mtype, bool inMap) -{ - if (ActiveMonsterCount < MAXMONSTERS) { - int i = ActiveMonsters[ActiveMonsterCount++]; - if (inMap) - dMonster[position.x][position.y] = i + 1; - InitMonster(i, dir, mtype, position); - return i; - } - - return -1; -} - -void AddDoppelganger(MonsterStruct &monster) -{ - if (monster.MType == nullptr) { - return; - } - - Point target = { 0, 0 }; - for (int d = 0; d < 8; d++) { - const Point position = monster.position.tile + static_cast(d); - if (!SolidLoc(position)) { - if (dPlayer[position.x][position.y] == 0 && dMonster[position.x][position.y] == 0) { - if (dObject[position.x][position.y] == 0) { - target = position; - break; - } - int oi = dObject[position.x][position.y] > 0 ? dObject[position.x][position.y] - 1 : -(dObject[position.x][position.y] + 1); - if (!Objects[oi]._oSolidFlag) { - target = position; - break; - } - } - } - } - if (target != Point { 0, 0 }) { - for (int j = 0; j < MAX_LVLMTYPES; j++) { - if (LevelMonsterTypes[j].mtype == monster.MType->mtype) { - AddMonster(target, monster._mdir, j, true); - break; - } - } - } -} - -bool M_Ranged(int i) -{ - return IsAnyOf(Monsters[i]._mAi, AI_SKELBOW, AI_GOATBOW, AI_SUCC, AI_LAZHELP); -} - -bool M_Talker(int i) -{ - return IsAnyOf(Monsters[i]._mAi, AI_LAZURUS, AI_WARLORD, AI_GARBUD, AI_ZHAR, AI_SNOTSPIL, AI_LACHDAN, AI_LAZHELP); -} - -void M_Enemy(int i) -{ - BYTE enemyx; - BYTE enemyy; - - int menemy = -1; - int bestDist = -1; - bool bestsameroom = false; - MonsterStruct *monst = &Monsters[i]; - if ((monst->_mFlags & MFLAG_BERSERK) != 0 || (monst->_mFlags & MFLAG_GOLEM) == 0) { - for (int pnum = 0; pnum < MAX_PLRS; pnum++) { - if (!Players[pnum].plractive || currlevel != Players[pnum].plrlevel || Players[pnum]._pLvlChanging - || (((Players[pnum]._pHitPoints >> 6) == 0) && gbIsMultiplayer)) - continue; - bool sameroom = (dTransVal[monst->position.tile.x][monst->position.tile.y] == dTransVal[Players[pnum].position.tile.x][Players[pnum].position.tile.y]); - int dist = monst->position.tile.WalkingDistance(Players[pnum].position.tile); - if ((sameroom && !bestsameroom) - || ((sameroom || !bestsameroom) && dist < bestDist) - || (menemy == -1)) { - monst->_mFlags &= ~MFLAG_TARGETS_MONSTER; - menemy = pnum; - enemyx = Players[pnum].position.future.x; - enemyy = Players[pnum].position.future.y; - bestDist = dist; - bestsameroom = sameroom; - } - } - } - for (int j = 0; j < ActiveMonsterCount; j++) { - int mi = ActiveMonsters[j]; - if (mi == i) - continue; - if ((Monsters[mi]._mhitpoints >> 6) <= 0) - continue; - if (Monsters[mi].position.tile.x == 1 && Monsters[mi].position.tile.y == 0) - continue; - if (M_Talker(mi) && Monsters[mi].mtalkmsg != TEXT_NONE) - continue; - if ((monst->_mFlags & MFLAG_GOLEM) != 0 && (Monsters[mi]._mFlags & MFLAG_GOLEM) != 0) // prevent golems from fighting each other - continue; - - int dist = Monsters[mi].position.tile.WalkingDistance(monst->position.tile); - if (((monst->_mFlags & MFLAG_GOLEM) == 0 - && (monst->_mFlags & MFLAG_BERSERK) == 0 - && dist >= 2 - && !M_Ranged(i)) - || ((monst->_mFlags & MFLAG_GOLEM) == 0 - && (monst->_mFlags & MFLAG_BERSERK) == 0 - && (Monsters[mi]._mFlags & MFLAG_GOLEM) == 0)) { - continue; - } - bool sameroom = dTransVal[monst->position.tile.x][monst->position.tile.y] == dTransVal[Monsters[mi].position.tile.x][Monsters[mi].position.tile.y]; - if ((sameroom && !bestsameroom) - || ((sameroom || !bestsameroom) && dist < bestDist) - || (menemy == -1)) { - monst->_mFlags |= MFLAG_TARGETS_MONSTER; - menemy = mi; - enemyx = Monsters[mi].position.future.x; - enemyy = Monsters[mi].position.future.y; - bestDist = dist; - bestsameroom = sameroom; - } - } - if (menemy != -1) { - monst->_mFlags &= ~MFLAG_NO_ENEMY; - monst->_menemy = menemy; - monst->enemyPosition = { enemyx, enemyy }; - } else { - monst->_mFlags |= MFLAG_NO_ENEMY; - } -} - -Direction M_GetDir(int i) -{ - return GetDirection(Monsters[i].position.tile, Monsters[i].enemyPosition); -} - -void M_StartStand(int i, Direction md) -{ - ClearMVars(i); - if (Monsters[i].MType->mtype == MT_GOLEM) - NewMonsterAnim(Monsters[i], MonsterGraphic::Walk, md); - else - NewMonsterAnim(Monsters[i], MonsterGraphic::Stand, md); - Monsters[i]._mVar1 = Monsters[i]._mmode; - Monsters[i]._mVar2 = 0; - Monsters[i]._mmode = MM_STAND; - Monsters[i].position.offset = { 0, 0 }; - Monsters[i].position.future = Monsters[i].position.tile; - Monsters[i].position.old = Monsters[i].position.tile; - M_Enemy(i); -} - -void M_StartDelay(int i, int len) -{ - if (len <= 0) { - return; - } - - if (Monsters[i]._mAi != AI_LAZURUS) { - Monsters[i]._mVar2 = len; - Monsters[i]._mmode = MM_DELAY; - } -} - -void M_StartSpStand(int i, Direction md) -{ - NewMonsterAnim(Monsters[i], MonsterGraphic::Special, md); - Monsters[i]._mmode = MM_SPSTAND; - Monsters[i].position.offset = { 0, 0 }; - Monsters[i].position.future = Monsters[i].position.tile; - Monsters[i].position.old = Monsters[i].position.tile; -} - -void M_StartWalk(int i, int xvel, int yvel, int xadd, int yadd, Direction endDir) -{ - int fx = xadd + Monsters[i].position.tile.x; - int fy = yadd + Monsters[i].position.tile.y; +void M_StartWalk(int i, int xvel, int yvel, int xadd, int yadd, Direction endDir) +{ + int fx = xadd + Monsters[i].position.tile.x; + int fy = yadd + Monsters[i].position.tile.y; dMonster[fx][fy] = -(i + 1); Monsters[i]._mmode = MM_WALK; @@ -1526,92 +1026,28 @@ void M_StartRSpAttack(int i, missile_id missileType, int dam) Monsters[i]._mVar1 = missileType; Monsters[i]._mVar2 = 0; Monsters[i]._mVar3 = dam; - Monsters[i].position.offset = { 0, 0 }; - Monsters[i].position.future = Monsters[i].position.tile; - Monsters[i].position.old = Monsters[i].position.tile; -} - -void M_StartSpAttack(int i) -{ - Direction md = M_GetDir(i); - NewMonsterAnim(Monsters[i], MonsterGraphic::Special, md); - Monsters[i]._mmode = MM_SATTACK; - Monsters[i].position.offset = { 0, 0 }; - Monsters[i].position.future = Monsters[i].position.tile; - Monsters[i].position.old = Monsters[i].position.tile; -} - -void M_StartEat(int i) -{ - NewMonsterAnim(Monsters[i], MonsterGraphic::Special, Monsters[i]._mdir); - Monsters[i]._mmode = MM_SATTACK; - Monsters[i].position.offset = { 0, 0 }; - Monsters[i].position.future = Monsters[i].position.tile; - Monsters[i].position.old = Monsters[i].position.tile; -} - -void M_ClearSquares(int i) -{ - int mx = Monsters[i].position.old.x; - int my = Monsters[i].position.old.y; - int m1 = -(i + 1); - int m2 = i + 1; - - for (int y = my - 1; y <= my + 1; y++) { - if (y >= 0 && y < MAXDUNY) { - for (int x = mx - 1; x <= mx + 1; x++) { - if (x >= 0 && x < MAXDUNX && (dMonster[x][y] == m1 || dMonster[x][y] == m2)) - dMonster[x][y] = 0; - } - } - } - - if (mx + 1 < MAXDUNX) - dFlags[mx + 1][my] &= ~BFLAG_MONSTLR; - if (my + 1 < MAXDUNY) - dFlags[mx][my + 1] &= ~BFLAG_MONSTLR; -} - -void M_GetKnockback(int i) -{ - Direction d = opposite[Monsters[i]._mdir]; - if (!DirOK(i, d)) { - return; - } + Monsters[i].position.offset = { 0, 0 }; + Monsters[i].position.future = Monsters[i].position.tile; + Monsters[i].position.old = Monsters[i].position.tile; +} - M_ClearSquares(i); - Monsters[i].position.old += d; - StartMonsterGotHit(i); +void M_StartSpAttack(int i) +{ + Direction md = M_GetDir(i); + NewMonsterAnim(Monsters[i], MonsterGraphic::Special, md); + Monsters[i]._mmode = MM_SATTACK; + Monsters[i].position.offset = { 0, 0 }; + Monsters[i].position.future = Monsters[i].position.tile; + Monsters[i].position.old = Monsters[i].position.tile; } -void M_StartHit(int i, int pnum, int dam) +void M_StartEat(int i) { - if (pnum >= 0) - Monsters[i].mWhoHit |= 1 << pnum; - if (pnum == MyPlayerId) { - delta_monster_hp(i, Monsters[i]._mhitpoints, currlevel); - NetSendCmdMonDmg(false, i, dam); - } - PlayEffect(i, 1); - if ((Monsters[i].MType->mtype >= MT_SNEAK && Monsters[i].MType->mtype <= MT_ILLWEAV) || dam >> 6 >= Monsters[i].mLevel + 3) { - if (pnum >= 0) { - Monsters[i]._menemy = pnum; - Monsters[i].enemyPosition = Players[pnum].position.future; - Monsters[i]._mFlags &= ~MFLAG_TARGETS_MONSTER; - Monsters[i]._mdir = M_GetDir(i); - } - if (Monsters[i].MType->mtype == MT_BLINK) { - M_Teleport(i); - } else if ((Monsters[i].MType->mtype >= MT_NSCAV && Monsters[i].MType->mtype <= MT_YSCAV) - || Monsters[i].MType->mtype == MT_GRAVEDIG) { - Monsters[i]._mgoal = MGOAL_NORMAL; - Monsters[i]._mgoalvar1 = 0; - Monsters[i]._mgoalvar2 = 0; - } - if (Monsters[i]._mmode != MM_STONE) { - StartMonsterGotHit(i); - } - } + NewMonsterAnim(Monsters[i], MonsterGraphic::Special, Monsters[i]._mdir); + Monsters[i]._mmode = MM_SATTACK; + Monsters[i].position.offset = { 0, 0 }; + Monsters[i].position.future = Monsters[i].position.tile; + Monsters[i].position.old = Monsters[i].position.tile; } void M_DiabloDeath(int i, bool sendmsg) @@ -1682,6 +1118,45 @@ void SpawnLoot(int i, bool sendmsg) } } +void M_Teleport(int i) +{ + assurance((DWORD)i < MAXMONSTERS, i); + + MonsterStruct *monst = &Monsters[i]; + if (monst->_mmode == MM_STONE) + return; + + int mx = monst->enemyPosition.x; + int my = monst->enemyPosition.y; + int rx = 2 * GenerateRnd(2) - 1; + int ry = 2 * GenerateRnd(2) - 1; + + bool done = false; + + int x; + int y; + for (int j = -1; j <= 1 && !done; j++) { + for (int k = -1; k < 1 && !done; k++) { + if (j != 0 || k != 0) { + x = mx + rx * j; + y = my + ry * k; + if (y >= 0 && y < MAXDUNY && x >= 0 && x < MAXDUNX && x != monst->position.tile.x && y != monst->position.tile.y) { + if (PosOkMonst(i, { x, y })) + done = true; + } + } + } + } + + if (done) { + M_ClearSquares(i); + dMonster[monst->position.tile.x][monst->position.tile.y] = 0; + dMonster[x][y] = i + 1; + monst->position.old = { x, y }; + monst->_mdir = M_GetDir(i); + } +} + void M2MStartHit(int mid, int i, int dam) { assurance((DWORD)mid < MAXMONSTERS, mid); @@ -1795,44 +1270,6 @@ void M2MStartKill(int i, int mid) M_StartStand(i, Monsters[i]._mdir); } -void M_StartKill(int i, int pnum) -{ - assurance((DWORD)i < MAXMONSTERS, i); - - if (MyPlayerId == pnum) { - delta_kill_monster(i, Monsters[i].position.tile, currlevel); - if (i != pnum) { - NetSendCmdLocParam1(false, CMD_MONSTDEATH, Monsters[i].position.tile, i); - } else { - NetSendCmdLocParam1(false, CMD_KILLGOLEM, Monsters[i].position.tile, currlevel); - } - } - - MonstStartKill(i, pnum, true); -} - -void M_SyncStartKill(int i, Point position, int pnum) -{ - assurance((DWORD)i < MAXMONSTERS, i); - - if (Monsters[i]._mhitpoints > 0 || Monsters[i]._mmode == MM_DEATH) { - return; - } - - if (dMonster[position.x][position.y] == 0) { - M_ClearSquares(i); - Monsters[i].position.tile = position; - Monsters[i].position.old = position; - } - - if (Monsters[i]._mmode == MM_STONE) { - MonstStartKill(i, pnum, false); - Monsters[i].Petrify(); - } else { - MonstStartKill(i, pnum, false); - } -} - void M_StartFadein(int i, Direction md, bool backwards) { assurance((DWORD)i < MAXMONSTERS, i); @@ -2388,577 +1825,986 @@ bool M_DoTalk(int i) return false; } -void M_Teleport(int i) +bool M_DoGotHit(int i) { - assurance((DWORD)i < MAXMONSTERS, i); + commitment((DWORD)i < MAXMONSTERS, i); + commitment(Monsters[i].MType != nullptr, i); - MonsterStruct *monst = &Monsters[i]; - if (monst->_mmode == MM_STONE) - return; + if (Monsters[i].AnimInfo.CurrentFrame == Monsters[i].AnimInfo.NumberOfFrames) { + M_StartStand(i, Monsters[i]._mdir); - int mx = monst->enemyPosition.x; - int my = monst->enemyPosition.y; - int rx = 2 * GenerateRnd(2) - 1; - int ry = 2 * GenerateRnd(2) - 1; + return true; + } - bool done = false; + return false; +} - int x; - int y; - for (int j = -1; j <= 1 && !done; j++) { - for (int k = -1; k < 1 && !done; k++) { - if (j != 0 || k != 0) { - x = mx + rx * j; - y = my + ry * k; - if (y >= 0 && y < MAXDUNY && x >= 0 && x < MAXDUNX && x != monst->position.tile.x && y != monst->position.tile.y) { - if (PosOkMonst(i, { x, y })) - done = true; - } - } +bool M_DoDeath(int i) +{ + commitment((DWORD)i < MAXMONSTERS, i); + commitment(Monsters[i].MType != nullptr, i); + + Monsters[i]._mVar1++; + if (Monsters[i].MType->mtype == MT_DIABLO) { + if (Monsters[i].position.tile.x < ViewX) { + ViewX--; + } else if (Monsters[i].position.tile.x > ViewX) { + ViewX++; + } + + if (Monsters[i].position.tile.y < ViewY) { + ViewY--; + } else if (Monsters[i].position.tile.y > ViewY) { + ViewY++; } + + if (Monsters[i]._mVar1 == 140) + PrepDoEnding(); + } else if (Monsters[i].AnimInfo.CurrentFrame == Monsters[i].AnimInfo.NumberOfFrames) { + if (Monsters[i]._uniqtype == 0) + AddDead(Monsters[i].position.tile, Monsters[i].MType->mdeadval, Monsters[i]._mdir); + else + AddDead(Monsters[i].position.tile, Monsters[i]._udeadval, Monsters[i]._mdir); + + dMonster[Monsters[i].position.tile.x][Monsters[i].position.tile.y] = 0; + Monsters[i]._mDelFlag = true; + + M_UpdateLeader(i); } + return false; +} - if (done) { - M_ClearSquares(i); - dMonster[monst->position.tile.x][monst->position.tile.y] = 0; - dMonster[x][y] = i + 1; - monst->position.old = { x, y }; - monst->_mdir = M_GetDir(i); +bool M_DoSpStand(int i) +{ + commitment((DWORD)i < MAXMONSTERS, i); + commitment(Monsters[i].MType != nullptr, i); + + if (Monsters[i].AnimInfo.CurrentFrame == Monsters[i].MData->mAFNum2) + PlayEffect(i, 3); + + if (Monsters[i].AnimInfo.CurrentFrame == Monsters[i].AnimInfo.NumberOfFrames) { + M_StartStand(i, Monsters[i]._mdir); + return true; + } + + return false; +} + +bool M_DoDelay(int i) +{ + commitment((DWORD)i < MAXMONSTERS, i); + commitment(Monsters[i].MType != nullptr, i); + + Monsters[i].AnimInfo.pCelSprite = &*Monsters[i].MType->GetAnimData(MonsterGraphic::Stand).CelSpritesForDirections[M_GetDir(i)]; + if (Monsters[i]._mAi == AI_LAZURUS) { + if (Monsters[i]._mVar2 > 8 || Monsters[i]._mVar2 < 0) + Monsters[i]._mVar2 = 8; + } + + if (Monsters[i]._mVar2-- == 0) { + int oFrame = Monsters[i].AnimInfo.CurrentFrame; + M_StartStand(i, Monsters[i]._mdir); + Monsters[i].AnimInfo.CurrentFrame = oFrame; + return true; + } + + return false; +} + +bool M_DoStone(int i) +{ + commitment((DWORD)i < MAXMONSTERS, i); + + if (Monsters[i]._mhitpoints <= 0) { + dMonster[Monsters[i].position.tile.x][Monsters[i].position.tile.y] = 0; + Monsters[i]._mDelFlag = true; + } + + return false; +} + +static int AddSkeleton(Point position, Direction dir, bool inMap) +{ + int j = 0; + for (int i = 0; i < LevelMonsterTypeCount; i++) { + if (IsSkel(LevelMonsterTypes[i].mtype)) + j++; + } + + if (j == 0) { + return -1; + } + + int skeltypes = GenerateRnd(j); + int m = 0; + for (int i = 0; m < LevelMonsterTypeCount && i <= skeltypes; m++) { + if (IsSkel(LevelMonsterTypes[m].mtype)) + i++; } + return AddMonster(position, dir, m - 1, inMap); } -bool M_DoGotHit(int i) +int M_SpawnSkel(Point position, Direction dir) { - commitment((DWORD)i < MAXMONSTERS, i); - commitment(Monsters[i].MType != nullptr, i); + int skel = AddSkeleton(position, dir, true); + if (skel != -1) + M_StartSpStand(skel, dir); - if (Monsters[i].AnimInfo.CurrentFrame == Monsters[i].AnimInfo.NumberOfFrames) { - M_StartStand(i, Monsters[i]._mdir); + return skel; +} - return true; - } +bool CheckNoSolid(int /*entity*/, Point position) +{ + return !nSolidTable[dPiece[position.x][position.y]]; +} - return false; +bool LineClearSolid(Point startPoint, Point endPoint) +{ + return LineClear(CheckNoSolid, 0, startPoint, endPoint); } -void M_UpdateLeader(int i) +void GroupUnity(int i) { assurance((DWORD)i < MAXMONSTERS, i); - for (int j = 0; j < ActiveMonsterCount; j++) { - int ma = ActiveMonsters[j]; - if (Monsters[ma].leaderflag == 1 && Monsters[ma].leader == i) - Monsters[ma].leaderflag = 0; + int leader; + if (Monsters[i].leaderflag != 0) { + leader = Monsters[i].leader; + bool clear = LineClearSolid(Monsters[i].position.tile, Monsters[leader].position.future); + if (clear || Monsters[i].leaderflag != 1) { + if (clear + && Monsters[i].leaderflag == 2 + && Monsters[i].position.tile.WalkingDistance(Monsters[leader].position.future) < 4) { + Monsters[leader].packsize++; + Monsters[i].leaderflag = 1; + } + } else { + Monsters[leader].packsize--; + Monsters[i].leaderflag = 2; + } } if (Monsters[i].leaderflag == 1) { - Monsters[Monsters[i].leader].packsize--; + if (Monsters[i]._msquelch > Monsters[leader]._msquelch) { + Monsters[leader].position.last = Monsters[i].position.tile; + Monsters[leader]._msquelch = Monsters[i]._msquelch - 1; + } + if (Monsters[leader]._mAi == AI_GARG) { + if ((Monsters[leader]._mFlags & MFLAG_ALLOW_SPECIAL) != 0) { + Monsters[leader]._mFlags &= ~MFLAG_ALLOW_SPECIAL; + Monsters[leader]._mmode = MM_SATTACK; + } + } + } else if (Monsters[i]._uniqtype != 0) { + if ((UniqMonst[Monsters[i]._uniqtype - 1].mUnqAttr & 2) != 0) { + for (int j = 0; j < ActiveMonsterCount; j++) { + int m = ActiveMonsters[j]; + if (Monsters[m].leaderflag == 1 && Monsters[m].leader == i) { + if (Monsters[i]._msquelch > Monsters[m]._msquelch) { + Monsters[m].position.last = Monsters[i].position.tile; + Monsters[m]._msquelch = Monsters[i]._msquelch - 1; + } + if (Monsters[m]._mAi == AI_GARG) { + if ((Monsters[m]._mFlags & MFLAG_ALLOW_SPECIAL) != 0) { + Monsters[m]._mFlags &= ~MFLAG_ALLOW_SPECIAL; + Monsters[m]._mmode = MM_SATTACK; + } + } + } + } + } } } -void DoEnding() +bool M_CallWalk(int i, Direction md) { - if (gbIsMultiplayer) { - SNetLeaveGame(LEAVE_ENDING); + Direction mdtemp = md; + bool ok = DirOK(i, md); + if (GenerateRnd(2) != 0) + ok = ok || (md = left[mdtemp], DirOK(i, md)) || (md = right[mdtemp], DirOK(i, md)); + else + ok = ok || (md = right[mdtemp], DirOK(i, md)) || (md = left[mdtemp], DirOK(i, md)); + if (GenerateRnd(2) != 0) { + ok = ok + || (md = right[right[mdtemp]], DirOK(i, md)) + || (md = left[left[mdtemp]], DirOK(i, md)); + } else { + ok = ok + || (md = left[left[mdtemp]], DirOK(i, md)) + || (md = right[right[mdtemp]], DirOK(i, md)); } + if (ok) + M_WalkDir(i, md); + return ok; +} - music_stop(); - - if (gbIsMultiplayer) { - SDL_Delay(1000); +bool M_CallWalk2(int i, Direction md) +{ + Direction mdtemp = md; + bool ok = DirOK(i, md); // Can we continue in the same direction + if (GenerateRnd(2) != 0) { // Randomly go left or right + ok = ok || (mdtemp = left[md], DirOK(i, left[md])) || (mdtemp = right[md], DirOK(i, right[md])); + } else { + ok = ok || (mdtemp = right[md], DirOK(i, right[md])) || (mdtemp = left[md], DirOK(i, left[md])); } - if (gbIsSpawn) - return; + if (ok) + M_WalkDir(i, mdtemp); - switch (Players[MyPlayerId]._pClass) { - case HeroClass::Sorcerer: - case HeroClass::Monk: - play_movie("gendata\\DiabVic1.smk", false); - break; - case HeroClass::Warrior: - case HeroClass::Barbarian: - play_movie("gendata\\DiabVic2.smk", false); - break; - default: - play_movie("gendata\\DiabVic3.smk", false); - break; + return ok; +} + +bool monster_posok(int i, Point position) +{ + int8_t mi = dMissile[position.x][position.y]; + if (mi == 0 || i < 0) { + return true; } - play_movie("gendata\\Diabend.smk", false); - bool bMusicOn = gbMusicOn; - gbMusicOn = true; + bool fire = false; + bool lightning = false; + if (mi > 0) { + if (Missiles[mi - 1]._mitype == MIS_FIREWALL) { // BUGFIX: Change 'mi' to 'mi - 1' (fixed) + fire = true; + } else if (Missiles[mi - 1]._mitype == MIS_LIGHTWALL) { // BUGFIX: Change 'mi' to 'mi - 1' (fixed) + lightning = true; + } + } else { + for (int j = 0; j < ActiveMissileCount; j++) { + mi = ActiveMissiles[j]; + if (Missiles[mi].position.tile == position) { + if (Missiles[mi]._mitype == MIS_FIREWALL) { + fire = true; + break; + } + if (Missiles[mi]._mitype == MIS_LIGHTWALL) { + lightning = true; + break; + } + } + } + } + if (fire && ((Monsters[i].mMagicRes & IMMUNE_FIRE) == 0 || Monsters[i].MType->mtype == MT_DIABLO)) + return false; + if (lightning && ((Monsters[i].mMagicRes & IMMUNE_LIGHTNING) == 0 || Monsters[i].MType->mtype == MT_DIABLO)) + return false; - int musicVolume = sound_get_or_set_music_volume(1); - sound_get_or_set_music_volume(0); + return true; +} - music_start(TMUSIC_L2); - loop_movie = true; - play_movie("gendata\\loopdend.smk", true); - loop_movie = false; - music_stop(); +bool PosOkMonst2(int i, Point position) +{ + if (SolidLoc(position)) + return false; - sound_get_or_set_music_volume(musicVolume); - gbMusicOn = bMusicOn; + if (dObject[position.x][position.y] != 0) { + int oi = dObject[position.x][position.y] > 0 ? dObject[position.x][position.y] - 1 : -(dObject[position.x][position.y] + 1); + if (Objects[oi]._oSolidFlag) + return false; + } + + return monster_posok(i, position); } -void PrepDoEnding() +bool PosOkMonst3(int i, Point position) { - gbSoundOn = sgbSaveSoundOn; - gbRunGame = false; - MyPlayerIsDead = false; - cineflag = true; + bool isdoor = false; + if (dObject[position.x][position.y] != 0) { + int oi = dObject[position.x][position.y] > 0 ? dObject[position.x][position.y] - 1 : -(dObject[position.x][position.y] + 1); + isdoor = IsAnyOf(Objects[oi]._otype, OBJ_L1LDOOR, OBJ_L1RDOOR, OBJ_L2LDOOR, OBJ_L2RDOOR, OBJ_L3LDOOR, OBJ_L3RDOOR); + if (Objects[oi]._oSolidFlag && !isdoor) + return false; + } - Players[MyPlayerId].pDiabloKillLevel = std::max(Players[MyPlayerId].pDiabloKillLevel, static_cast(sgGameInitInfo.nDifficulty + 1)); + if ((SolidLoc(position) && !isdoor) || dPlayer[position.x][position.y] != 0 || dMonster[position.x][position.y] != 0) + return false; - for (auto &player : Players) { - player._pmode = PM_QUIT; - player._pInvincible = true; - if (gbIsMultiplayer) { - if (player._pHitPoints >> 6 == 0) - player._pHitPoints = 64; - if (player._pMana >> 6 == 0) - player._pMana = 64; - } - } + return monster_posok(i, position); } -bool M_DoDeath(int i) +bool M_PathWalk(int i) { + int8_t path[MAX_PATH_LENGTH]; + bool (*check)(int, Point); + + /** Maps from walking path step to facing direction. */ + const Direction plr2monst[9] = { DIR_S, DIR_NE, DIR_NW, DIR_SE, DIR_SW, DIR_N, DIR_E, DIR_S, DIR_W }; + commitment((DWORD)i < MAXMONSTERS, i); - commitment(Monsters[i].MType != nullptr, i); - Monsters[i]._mVar1++; - if (Monsters[i].MType->mtype == MT_DIABLO) { - if (Monsters[i].position.tile.x < ViewX) { - ViewX--; - } else if (Monsters[i].position.tile.x > ViewX) { - ViewX++; - } + check = PosOkMonst3; + if ((Monsters[i]._mFlags & MFLAG_CAN_OPEN_DOOR) == 0) + check = PosOkMonst; - if (Monsters[i].position.tile.y < ViewY) { - ViewY--; - } else if (Monsters[i].position.tile.y > ViewY) { - ViewY++; - } + if (FindPath(check, i, Monsters[i].position.tile.x, Monsters[i].position.tile.y, Monsters[i].enemyPosition.x, Monsters[i].enemyPosition.y, path) == 0) { + return false; + } - if (Monsters[i]._mVar1 == 140) - PrepDoEnding(); - } else if (Monsters[i].AnimInfo.CurrentFrame == Monsters[i].AnimInfo.NumberOfFrames) { - if (Monsters[i]._uniqtype == 0) - AddDead(Monsters[i].position.tile, Monsters[i].MType->mdeadval, Monsters[i]._mdir); - else - AddDead(Monsters[i].position.tile, Monsters[i]._udeadval, Monsters[i]._mdir); + M_CallWalk(i, plr2monst[path[0]]); + return true; +} + +bool M_DumbWalk(int i, Direction md) +{ + bool ok = DirOK(i, md); + if (ok) + M_WalkDir(i, md); - dMonster[Monsters[i].position.tile.x][Monsters[i].position.tile.y] = 0; - Monsters[i]._mDelFlag = true; + return ok; +} - M_UpdateLeader(i); - } - return false; +static Direction Turn(Direction direction, bool turnLeft) +{ + return turnLeft ? left[direction] : right[direction]; } -bool M_DoSpStand(int i) +bool M_RoundWalk(int i, Direction direction, int *dir) { - commitment((DWORD)i < MAXMONSTERS, i); - commitment(Monsters[i].MType != nullptr, i); + Direction turn45deg = Turn(direction, *dir != 0); + Direction turn90deg = Turn(turn45deg, *dir != 0); - if (Monsters[i].AnimInfo.CurrentFrame == Monsters[i].MData->mAFNum2) - PlayEffect(i, 3); + if (DirOK(i, turn90deg)) { + // Turn 90 degrees + M_WalkDir(i, turn90deg); + return true; + } - if (Monsters[i].AnimInfo.CurrentFrame == Monsters[i].AnimInfo.NumberOfFrames) { - M_StartStand(i, Monsters[i]._mdir); + if (DirOK(i, turn45deg)) { + // Only do a small turn + M_WalkDir(i, turn45deg); return true; } - return false; + if (DirOK(i, direction)) { + // Continue straight + M_WalkDir(i, direction); + return true; + } + + // Try 90 degrees in the opposite than desired direction + *dir = (*dir == 0) ? 1 : 0; + return M_CallWalk(i, opposite[turn90deg]); } -bool M_DoDelay(int i) +bool MAI_Path(int i) { commitment((DWORD)i < MAXMONSTERS, i); - commitment(Monsters[i].MType != nullptr, i); - Monsters[i].AnimInfo.pCelSprite = &*Monsters[i].MType->GetAnimData(MonsterGraphic::Stand).CelSpritesForDirections[M_GetDir(i)]; - if (Monsters[i]._mAi == AI_LAZURUS) { - if (Monsters[i]._mVar2 > 8 || Monsters[i]._mVar2 < 0) - Monsters[i]._mVar2 = 8; + MonsterStruct *monst = &Monsters[i]; + if (monst->MType->mtype != MT_GOLEM) { + if (monst->_msquelch == 0) + return false; + if (monst->_mmode != MM_STAND) + return false; + if (monst->_mgoal != MGOAL_NORMAL && monst->_mgoal != MGOAL_MOVE && monst->_mgoal != MGOAL_ATTACK2) + return false; + if (monst->position.tile.x == 1 && monst->position.tile.y == 0) + return false; } - if (Monsters[i]._mVar2-- == 0) { - int oFrame = Monsters[i].AnimInfo.CurrentFrame; - M_StartStand(i, Monsters[i]._mdir); - Monsters[i].AnimInfo.CurrentFrame = oFrame; - return true; + bool clear = LineClear( + PosOkMonst2, + i, + monst->position.tile, + monst->enemyPosition); + if (!clear || (monst->_pathcount >= 5 && monst->_pathcount < 8)) { + if ((monst->_mFlags & MFLAG_CAN_OPEN_DOOR) != 0) + MonstCheckDoors(i); + monst->_pathcount++; + if (monst->_pathcount < 5) + return false; + if (M_PathWalk(i)) + return true; } + if (monst->MType->mtype != MT_GOLEM) + monst->_pathcount = 0; + return false; } -bool M_DoStone(int i) +void MAI_Round(int i, bool special) { - commitment((DWORD)i < MAXMONSTERS, i); + assurance((DWORD)i < MAXMONSTERS, i); + MonsterStruct *monst = &Monsters[i]; + if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { + return; + } - if (Monsters[i]._mhitpoints <= 0) { - dMonster[Monsters[i].position.tile.x][Monsters[i].position.tile.y] = 0; - Monsters[i]._mDelFlag = true; + int fx = monst->enemyPosition.x; + int fy = monst->enemyPosition.y; + int mx = monst->position.tile.x - fx; + int my = monst->position.tile.y - fy; + Direction md = GetDirection(monst->position.tile, monst->position.last); + if (monst->_msquelch < UINT8_MAX) + MonstCheckDoors(i); + int v = GenerateRnd(100); + if ((abs(mx) >= 2 || abs(my) >= 2) && monst->_msquelch == UINT8_MAX && dTransVal[monst->position.tile.x][monst->position.tile.y] == dTransVal[fx][fy]) { + if (monst->_mgoal == MGOAL_MOVE || ((abs(mx) >= 4 || abs(my) >= 4) && GenerateRnd(4) == 0)) { + if (monst->_mgoal != MGOAL_MOVE) { + monst->_mgoalvar1 = 0; + monst->_mgoalvar2 = GenerateRnd(2); + } + monst->_mgoal = MGOAL_MOVE; + int dist = std::max(abs(mx), abs(my)); + if ((monst->_mgoalvar1++ >= 2 * dist && DirOK(i, md)) || dTransVal[monst->position.tile.x][monst->position.tile.y] != dTransVal[fx][fy]) { + monst->_mgoal = MGOAL_NORMAL; + } else if (!M_RoundWalk(i, md, &monst->_mgoalvar2)) { + M_StartDelay(i, GenerateRnd(10) + 10); + } + } + } else { + monst->_mgoal = MGOAL_NORMAL; + } + if (monst->_mgoal == MGOAL_NORMAL) { + if (abs(mx) >= 2 || abs(my) >= 2) { + if ((monst->_mVar2 > 20 && v < 2 * monst->_mint + 28) + || ((monst->_mVar1 == MM_WALK || monst->_mVar1 == MM_WALK2 || monst->_mVar1 == MM_WALK3) + && monst->_mVar2 == 0 + && v < 2 * monst->_mint + 78)) { + M_CallWalk(i, md); + } + } else if (v < 2 * monst->_mint + 23) { + monst->_mdir = md; + if (special && monst->_mhitpoints < (monst->_mmaxhp / 2) && GenerateRnd(2) != 0) + M_StartSpAttack(i); + else + M_StartAttack(i); + } } - return false; + monst->CheckStandAnimationIsLoaded(md); } -void M_WalkDir(int i, Direction md) +void MAI_Ranged(int i, missile_id missileType, bool special) { assurance((DWORD)i < MAXMONSTERS, i); - int mwi = Monsters[i].MType->GetAnimData(MonsterGraphic::Walk).Frames - 1; - switch (md) { - case DIR_N: - M_StartWalk(i, 0, -MWVel[mwi][1], -1, -1, DIR_N); - break; - case DIR_NE: - M_StartWalk(i, MWVel[mwi][1], -MWVel[mwi][0], 0, -1, DIR_NE); - break; - case DIR_E: - M_StartWalk3(i, MWVel[mwi][2], 0, -32, -16, 1, -1, 1, 0, DIR_E); - break; - case DIR_SE: - M_StartWalk2(i, MWVel[mwi][1], MWVel[mwi][0], -32, -16, 1, 0, DIR_SE); - break; - case DIR_S: - M_StartWalk2(i, 0, MWVel[mwi][1], 0, -32, 1, 1, DIR_S); - break; - case DIR_SW: - M_StartWalk2(i, -MWVel[mwi][1], MWVel[mwi][0], 32, -16, 0, 1, DIR_SW); - break; - case DIR_W: - M_StartWalk3(i, -MWVel[mwi][2], 0, 32, -16, -1, 1, 0, 1, DIR_W); - break; - case DIR_NW: - M_StartWalk(i, -MWVel[mwi][1], -MWVel[mwi][0], -1, 0, DIR_NW); - break; - case DIR_OMNI: - break; + if (Monsters[i]._mmode != MM_STAND) { + return; + } + + MonsterStruct *monst = &Monsters[i]; + if (monst->_msquelch == UINT8_MAX || (monst->_mFlags & MFLAG_TARGETS_MONSTER) != 0) { + int fx = monst->enemyPosition.x; + int fy = monst->enemyPosition.y; + int mx = monst->position.tile.x - fx; + int my = monst->position.tile.y - fy; + Direction md = M_GetDir(i); + if (monst->_msquelch < UINT8_MAX) + MonstCheckDoors(i); + monst->_mdir = md; + if (monst->_mVar1 == MM_RATTACK) { + M_StartDelay(i, GenerateRnd(20)); + } else if (abs(mx) < 4 && abs(my) < 4) { + if (GenerateRnd(100) < 10 * (monst->_mint + 7)) + M_CallWalk(i, opposite[md]); + } + if (monst->_mmode == MM_STAND) { + if (LineClearMissile(monst->position.tile, { fx, fy })) { + if (special) + M_StartRSpAttack(i, missileType, 4); + else + M_StartRAttack(i, missileType, 4); + } else { + monst->CheckStandAnimationIsLoaded(md); + } + } + return; + } + + if (monst->_msquelch != 0) { + int fx = monst->position.last.x; + int fy = monst->position.last.y; + Direction md = GetDirection(monst->position.tile, { fx, fy }); + M_CallWalk(i, md); } } -void GroupUnity(int i) +void MAI_RoundRanged(int i, missile_id missileType, bool checkdoors, int dam, int lessmissiles) { assurance((DWORD)i < MAXMONSTERS, i); + MonsterStruct *monst = &Monsters[i]; + if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { + return; + } - int leader; - if (Monsters[i].leaderflag != 0) { - leader = Monsters[i].leader; - bool clear = LineClearSolid(Monsters[i].position.tile, Monsters[leader].position.future); - if (clear || Monsters[i].leaderflag != 1) { - if (clear - && Monsters[i].leaderflag == 2 - && Monsters[i].position.tile.WalkingDistance(Monsters[leader].position.future) < 4) { - Monsters[leader].packsize++; - Monsters[i].leaderflag = 1; + int fx = monst->enemyPosition.x; + int fy = monst->enemyPosition.y; + int mx = monst->position.tile.x - fx; + int my = monst->position.tile.y - fy; + Direction md = GetDirection(monst->position.tile, monst->position.last); + if (checkdoors && monst->_msquelch < UINT8_MAX) + MonstCheckDoors(i); + int v = GenerateRnd(10000); + int dist = std::max(abs(mx), abs(my)); + if (dist >= 2 && monst->_msquelch == UINT8_MAX && dTransVal[monst->position.tile.x][monst->position.tile.y] == dTransVal[fx][fy]) { + if (monst->_mgoal == MGOAL_MOVE || (dist >= 3 && GenerateRnd(4 << lessmissiles) == 0)) { + if (monst->_mgoal != MGOAL_MOVE) { + monst->_mgoalvar1 = 0; + monst->_mgoalvar2 = GenerateRnd(2); } - } else { - Monsters[leader].packsize--; - Monsters[i].leaderflag = 2; + monst->_mgoal = MGOAL_MOVE; + if (monst->_mgoalvar1++ >= 2 * dist && DirOK(i, md)) { + monst->_mgoal = MGOAL_NORMAL; + } else if (v < (500 * (monst->_mint + 1) >> lessmissiles) + && (LineClearMissile(monst->position.tile, { fx, fy }))) { + M_StartRSpAttack(i, missileType, dam); + } else { + M_RoundWalk(i, md, &monst->_mgoalvar2); + } + } + } else { + monst->_mgoal = MGOAL_NORMAL; + } + if (monst->_mgoal == MGOAL_NORMAL) { + if (((dist >= 3 && v < ((500 * (monst->_mint + 2)) >> lessmissiles)) + || v < ((500 * (monst->_mint + 1)) >> lessmissiles)) + && LineClearMissile(monst->position.tile, { fx, fy })) { + M_StartRSpAttack(i, missileType, dam); + } else if (dist >= 2) { + v = GenerateRnd(100); + if (v < 1000 * (monst->_mint + 5) + || ((monst->_mVar1 == MM_WALK || monst->_mVar1 == MM_WALK2 || monst->_mVar1 == MM_WALK3) && monst->_mVar2 == 0 && v < 1000 * (monst->_mint + 8))) { + M_CallWalk(i, md); + } + } else if (v < 1000 * (monst->_mint + 6)) { + monst->_mdir = md; + M_StartAttack(i); } } + if (monst->_mmode == MM_STAND) { + M_StartDelay(i, GenerateRnd(10) + 5); + } +} - if (Monsters[i].leaderflag == 1) { - if (Monsters[i]._msquelch > Monsters[leader]._msquelch) { - Monsters[leader].position.last = Monsters[i].position.tile; - Monsters[leader]._msquelch = Monsters[i]._msquelch - 1; - } - if (Monsters[leader]._mAi == AI_GARG) { - if ((Monsters[leader]._mFlags & MFLAG_ALLOW_SPECIAL) != 0) { - Monsters[leader]._mFlags &= ~MFLAG_ALLOW_SPECIAL; - Monsters[leader]._mmode = MM_SATTACK; - } - } - } else if (Monsters[i]._uniqtype != 0) { - if ((UniqMonst[Monsters[i]._uniqtype - 1].mUnqAttr & 2) != 0) { - for (int j = 0; j < ActiveMonsterCount; j++) { - int m = ActiveMonsters[j]; - if (Monsters[m].leaderflag == 1 && Monsters[m].leader == i) { - if (Monsters[i]._msquelch > Monsters[m]._msquelch) { - Monsters[m].position.last = Monsters[i].position.tile; - Monsters[m]._msquelch = Monsters[i]._msquelch - 1; - } - if (Monsters[m]._mAi == AI_GARG) { - if ((Monsters[m]._mFlags & MFLAG_ALLOW_SPECIAL) != 0) { - Monsters[m]._mFlags &= ~MFLAG_ALLOW_SPECIAL; - Monsters[m]._mmode = MM_SATTACK; - } - } +void MAI_Zombie(int i) +{ + assurance((DWORD)i < MAXMONSTERS, i); + + MonsterStruct *monst = &Monsters[i]; + if (monst->_mmode != MM_STAND) { + return; + } + + int mx = monst->position.tile.x; + int my = monst->position.tile.y; + if ((dFlags[mx][my] & BFLAG_VISIBLE) == 0) { + return; + } + + if (GenerateRnd(100) < 2 * monst->_mint + 10) { + int dist = monst->enemyPosition.WalkingDistance({ mx, my }); + if (dist >= 2) { + if (dist >= 2 * monst->_mint + 4) { + Direction md = monst->_mdir; + if (GenerateRnd(100) < 2 * monst->_mint + 20) { + md = static_cast(GenerateRnd(8)); } + M_DumbWalk(i, md); + } else { + M_CallWalk(i, M_GetDir(i)); } + } else { + M_StartAttack(i); } } -} -bool M_CallWalk(int i, Direction md) -{ - Direction mdtemp = md; - bool ok = DirOK(i, md); - if (GenerateRnd(2) != 0) - ok = ok || (md = left[mdtemp], DirOK(i, md)) || (md = right[mdtemp], DirOK(i, md)); - else - ok = ok || (md = right[mdtemp], DirOK(i, md)) || (md = left[mdtemp], DirOK(i, md)); - if (GenerateRnd(2) != 0) { - ok = ok - || (md = right[right[mdtemp]], DirOK(i, md)) - || (md = left[left[mdtemp]], DirOK(i, md)); - } else { - ok = ok - || (md = left[left[mdtemp]], DirOK(i, md)) - || (md = right[right[mdtemp]], DirOK(i, md)); - } - if (ok) - M_WalkDir(i, md); - return ok; + monst->CheckStandAnimationIsLoaded(monst->_mdir); } -bool M_PathWalk(int i) +void MAI_Fat(int i) { - int8_t path[MAX_PATH_LENGTH]; - bool (*check)(int, Point); - - /** Maps from walking path step to facing direction. */ - const Direction plr2monst[9] = { DIR_S, DIR_NE, DIR_NW, DIR_SE, DIR_SW, DIR_N, DIR_E, DIR_S, DIR_W }; - - commitment((DWORD)i < MAXMONSTERS, i); + assurance((DWORD)i < MAXMONSTERS, i); - check = PosOkMonst3; - if ((Monsters[i]._mFlags & MFLAG_CAN_OPEN_DOOR) == 0) - check = PosOkMonst; + MonsterStruct *monst = &Monsters[i]; + if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { + return; + } - if (FindPath(check, i, Monsters[i].position.tile.x, Monsters[i].position.tile.y, Monsters[i].enemyPosition.x, Monsters[i].enemyPosition.y, path) == 0) { - return false; + int mx = monst->position.tile.x - monst->enemyPosition.x; + int my = monst->position.tile.y - monst->enemyPosition.y; + Direction md = M_GetDir(i); + monst->_mdir = md; + int v = GenerateRnd(100); + if (abs(mx) >= 2 || abs(my) >= 2) { + if ((monst->_mVar2 > 20 && v < 4 * monst->_mint + 20) + || ((monst->_mVar1 == MM_WALK || monst->_mVar1 == MM_WALK2 || monst->_mVar1 == MM_WALK3) + && monst->_mVar2 == 0 + && v < 4 * monst->_mint + 70)) { + M_CallWalk(i, md); + } + } else if (v < 4 * monst->_mint + 15) { + M_StartAttack(i); + } else if (v < 4 * monst->_mint + 20) { + M_StartSpAttack(i); } - M_CallWalk(i, plr2monst[path[0]]); - return true; + monst->CheckStandAnimationIsLoaded(md); } -bool M_CallWalk2(int i, Direction md) +void MAI_SkelSd(int i) { - Direction mdtemp = md; - bool ok = DirOK(i, md); // Can we continue in the same direction - if (GenerateRnd(2) != 0) { // Randomly go left or right - ok = ok || (mdtemp = left[md], DirOK(i, left[md])) || (mdtemp = right[md], DirOK(i, right[md])); - } else { - ok = ok || (mdtemp = right[md], DirOK(i, right[md])) || (mdtemp = left[md], DirOK(i, left[md])); + assurance((DWORD)i < MAXMONSTERS, i); + + MonsterStruct *monst = &Monsters[i]; + if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { + return; } - if (ok) - M_WalkDir(i, mdtemp); + int x = monst->position.tile.x - monst->enemyPosition.x; + int y = monst->position.tile.y - monst->enemyPosition.y; + Direction md = GetDirection(monst->position.tile, monst->position.last); + monst->_mdir = md; + if (abs(x) >= 2 || abs(y) >= 2) { + if (monst->_mVar1 == MM_DELAY || (GenerateRnd(100) >= 35 - 4 * monst->_mint)) { + M_CallWalk(i, md); + } else { + M_StartDelay(i, 15 - 2 * monst->_mint + GenerateRnd(10)); + } + } else { + if (monst->_mVar1 == MM_DELAY || (GenerateRnd(100) < 2 * monst->_mint + 20)) { + M_StartAttack(i); + } else { + M_StartDelay(i, 2 * (5 - monst->_mint) + GenerateRnd(10)); + } + } - return ok; + monst->CheckStandAnimationIsLoaded(md); } -bool M_DumbWalk(int i, Direction md) +void MAI_SkelBow(int i) { - bool ok = DirOK(i, md); - if (ok) - M_WalkDir(i, md); + assurance((DWORD)i < MAXMONSTERS, i); - return ok; -} + MonsterStruct *monst = &Monsters[i]; + if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { + return; + } -static Direction Turn(Direction direction, bool turnLeft) -{ - return turnLeft ? left[direction] : right[direction]; -} + int mx = monst->position.tile.x - monst->enemyPosition.x; + int my = monst->position.tile.y - monst->enemyPosition.y; -bool M_RoundWalk(int i, Direction direction, int *dir) -{ - Direction turn45deg = Turn(direction, *dir != 0); - Direction turn90deg = Turn(turn45deg, *dir != 0); + Direction md = M_GetDir(i); + monst->_mdir = md; + int v = GenerateRnd(100); - if (DirOK(i, turn90deg)) { - // Turn 90 degrees - M_WalkDir(i, turn90deg); - return true; + bool walking = false; + + if (abs(mx) < 4 && abs(my) < 4) { + if ((monst->_mVar2 > 20 && v < 2 * monst->_mint + 13) + || ((monst->_mVar1 == MM_WALK || monst->_mVar1 == MM_WALK2 || monst->_mVar1 == MM_WALK3) + && monst->_mVar2 == 0 + && v < 2 * monst->_mint + 63)) { + walking = M_DumbWalk(i, opposite[md]); + } } - if (DirOK(i, turn45deg)) { - // Only do a small turn - M_WalkDir(i, turn45deg); - return true; + if (!walking) { + if (GenerateRnd(100) < 2 * monst->_mint + 3) { + if (LineClearMissile(monst->position.tile, monst->enemyPosition)) + M_StartRAttack(i, MIS_ARROW, 4); + } } - if (DirOK(i, direction)) { - // Continue straight - M_WalkDir(i, direction); - return true; + monst->CheckStandAnimationIsLoaded(md); +} + +void MAI_Scav(int i) +{ + assurance((DWORD)i < MAXMONSTERS, i); + + MonsterStruct *monst = &Monsters[i]; + if (Monsters[i]._mmode != MM_STAND) + return; + if (monst->_mhitpoints < (monst->_mmaxhp / 2) && monst->_mgoal != MGOAL_HEALING) { + if (monst->leaderflag != 0) { + Monsters[monst->leader].packsize--; + monst->leaderflag = 0; + } + monst->_mgoal = MGOAL_HEALING; + monst->_mgoalvar3 = 10; + } + if (monst->_mgoal == MGOAL_HEALING && monst->_mgoalvar3 != 0) { + monst->_mgoalvar3--; + if (dDead[monst->position.tile.x][monst->position.tile.y] != 0) { + M_StartEat(i); + if ((monst->_mFlags & MFLAG_NOHEAL) == 0) { + if (gbIsHellfire) { + int mMaxHP = monst->_mmaxhp; // BUGFIX use _mmaxhp or we loose health when difficulty isn't normal (fixed) + monst->_mhitpoints += mMaxHP / 8; + if (monst->_mhitpoints > monst->_mmaxhp) + monst->_mhitpoints = monst->_mmaxhp; + if (monst->_mgoalvar3 <= 0 || monst->_mhitpoints == monst->_mmaxhp) + dDead[monst->position.tile.x][monst->position.tile.y] = 0; + } else { + monst->_mhitpoints += 64; + } + } + int targetHealth = monst->_mmaxhp; + if (!gbIsHellfire) + targetHealth = (monst->_mmaxhp / 2) + (monst->_mmaxhp / 4); + if (monst->_mhitpoints >= targetHealth) { + monst->_mgoal = MGOAL_NORMAL; + monst->_mgoalvar1 = 0; + monst->_mgoalvar2 = 0; + } + } else { + if (monst->_mgoalvar1 == 0) { + bool done = false; + int x; + int y; + if (GenerateRnd(2) != 0) { + for (y = -4; y <= 4 && !done; y++) { + for (x = -4; x <= 4 && !done; x++) { + // BUGFIX: incorrect check of offset against limits of the dungeon + if (y < 0 || y >= MAXDUNY || x < 0 || x >= MAXDUNX) + continue; + done = dDead[monst->position.tile.x + x][monst->position.tile.y + y] != 0 + && LineClearSolid( + monst->position.tile, + monst->position.tile + Displacement { x, y }); + } + } + x--; + y--; + } else { + for (y = 4; y >= -4 && !done; y--) { + for (x = 4; x >= -4 && !done; x--) { + // BUGFIX: incorrect check of offset against limits of the dungeon + if (y < 0 || y >= MAXDUNY || x < 0 || x >= MAXDUNX) + continue; + done = dDead[monst->position.tile.x + x][monst->position.tile.y + y] != 0 + && LineClearSolid( + monst->position.tile, + monst->position.tile + Displacement { x, y }); + } + } + x++; + y++; + } + if (done) { + monst->_mgoalvar1 = x + monst->position.tile.x + 1; + monst->_mgoalvar2 = y + monst->position.tile.y + 1; + } + } + if (monst->_mgoalvar1 != 0) { + int x = monst->_mgoalvar1 - 1; + int y = monst->_mgoalvar2 - 1; + monst->_mdir = GetDirection(monst->position.tile, { x, y }); + M_CallWalk(i, monst->_mdir); + } + } } - // Try 90 degrees in the opposite than desired direction - *dir = (*dir == 0) ? 1 : 0; - return M_CallWalk(i, opposite[turn90deg]); + if (monst->_mmode == MM_STAND) + MAI_SkelSd(i); } -void MAI_Zombie(int i) +void MAI_Rhino(int i) { assurance((DWORD)i < MAXMONSTERS, i); - MonsterStruct *monst = &Monsters[i]; - if (monst->_mmode != MM_STAND) { + if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { return; } - int mx = monst->position.tile.x; - int my = monst->position.tile.y; - if ((dFlags[mx][my] & BFLAG_VISIBLE) == 0) { - return; + int fx = monst->enemyPosition.x; + int fy = monst->enemyPosition.y; + int mx = monst->position.tile.x - fx; + int my = monst->position.tile.y - fy; + Direction md = GetDirection(monst->position.tile, monst->position.last); + if (monst->_msquelch < UINT8_MAX) + MonstCheckDoors(i); + int v = GenerateRnd(100); + int dist = std::max(abs(mx), abs(my)); + if (dist >= 2) { + if (monst->_mgoal == MGOAL_MOVE || (dist >= 5 && GenerateRnd(4) != 0)) { + if (monst->_mgoal != MGOAL_MOVE) { + monst->_mgoalvar1 = 0; + monst->_mgoalvar2 = GenerateRnd(2); + } + monst->_mgoal = MGOAL_MOVE; + if (monst->_mgoalvar1++ >= 2 * dist || dTransVal[monst->position.tile.x][monst->position.tile.y] != dTransVal[fx][fy]) { + monst->_mgoal = MGOAL_NORMAL; + } else if (!M_RoundWalk(i, md, &monst->_mgoalvar2)) { + M_StartDelay(i, GenerateRnd(10) + 10); + } + } + } else { + monst->_mgoal = MGOAL_NORMAL; } - - if (GenerateRnd(100) < 2 * monst->_mint + 10) { - int dist = monst->enemyPosition.WalkingDistance({ mx, my }); - if (dist >= 2) { - if (dist >= 2 * monst->_mint + 4) { - Direction md = monst->_mdir; - if (GenerateRnd(100) < 2 * monst->_mint + 20) { - md = static_cast(GenerateRnd(8)); - } - M_DumbWalk(i, md); - } else { - M_CallWalk(i, M_GetDir(i)); + if (monst->_mgoal == MGOAL_NORMAL) { + if (dist >= 5 + && v < 2 * monst->_mint + 43 + && LineClear(PosOkMonst, i, monst->position.tile, { fx, fy })) { + if (AddMissile(monst->position.tile, { fx, fy }, md, MIS_RHINO, monst->_menemy, i, 0, 0) != -1) { + if (monst->MData->snd_special) + PlayEffect(i, 3); + dMonster[monst->position.tile.x][monst->position.tile.y] = -(i + 1); + monst->_mmode = MM_CHARGE; } } else { - M_StartAttack(i); + if (dist >= 2) { + v = GenerateRnd(100); + if (v >= 2 * monst->_mint + 33 + && ((monst->_mVar1 != MM_WALK && monst->_mVar1 != MM_WALK2 && monst->_mVar1 != MM_WALK3) + || monst->_mVar2 != 0 + || v >= 2 * monst->_mint + 83)) { + M_StartDelay(i, GenerateRnd(10) + 10); + } else { + M_CallWalk(i, md); + } + } else if (v < 2 * monst->_mint + 28) { + monst->_mdir = md; + M_StartAttack(i); + } } } monst->CheckStandAnimationIsLoaded(monst->_mdir); } -void MAI_SkelSd(int i) +void MAI_GoatMc(int i) +{ + MAI_Round(i, true); +} + +void MAI_GoatBow(int i) +{ + MAI_Ranged(i, MIS_ARROW, false); +} + +void MAI_Fallen(int i) { assurance((DWORD)i < MAXMONSTERS, i); MonsterStruct *monst = &Monsters[i]; + if (monst->_mgoal == MGOAL_ATTACK2) { + if (monst->_mgoalvar1 != 0) + monst->_mgoalvar1--; + else + monst->_mgoal = MGOAL_NORMAL; + } if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { return; } - int x = monst->position.tile.x - monst->enemyPosition.x; - int y = monst->position.tile.y - monst->enemyPosition.y; - Direction md = GetDirection(monst->position.tile, monst->position.last); - monst->_mdir = md; - if (abs(x) >= 2 || abs(y) >= 2) { - if (monst->_mVar1 == MM_DELAY || (GenerateRnd(100) >= 35 - 4 * monst->_mint)) { - M_CallWalk(i, md); - } else { - M_StartDelay(i, 15 - 2 * monst->_mint + GenerateRnd(10)); - } - } else { - if (monst->_mVar1 == MM_DELAY || (GenerateRnd(100) < 2 * monst->_mint + 20)) { - M_StartAttack(i); - } else { - M_StartDelay(i, 2 * (5 - monst->_mint) + GenerateRnd(10)); + if (monst->_mgoal == MGOAL_RETREAT) { + if (monst->_mgoalvar1-- == 0) { + monst->_mgoal = MGOAL_NORMAL; + M_StartStand(i, opposite[monst->_mgoalvar2]); } } - monst->CheckStandAnimationIsLoaded(md); + if (monst->AnimInfo.CurrentFrame == monst->AnimInfo.NumberOfFrames) { + if (GenerateRnd(4) != 0) { + return; + } + if ((Monsters[i]._mFlags & MFLAG_NOHEAL) == 0) { // CODEFIX: - change to Monst-> in devilutionx + M_StartSpStand(i, monst->_mdir); + if (monst->_mmaxhp - (2 * monst->_mint + 2) >= monst->_mhitpoints) + monst->_mhitpoints += 2 * monst->_mint + 2; + else + monst->_mhitpoints = monst->_mmaxhp; + } + int rad = 2 * monst->_mint + 4; + for (int y = -rad; y <= rad; y++) { + for (int x = -rad; x <= rad; x++) { + int xpos = monst->position.tile.x + x; + int ypos = monst->position.tile.y + y; + if (y >= 0 && y < MAXDUNY && x >= 0 && x < MAXDUNX) { + int m = dMonster[xpos][ypos]; + if (m > 0) { + m--; + if (Monsters[m]._mAi == AI_FALLEN) { + Monsters[m]._mgoal = MGOAL_ATTACK2; + Monsters[m]._mgoalvar1 = 30 * monst->_mint + 105; + } + } + } + } + } + } else if (monst->_mgoal == MGOAL_RETREAT) { + M_CallWalk(i, monst->_mdir); + } else if (monst->_mgoal == MGOAL_ATTACK2) { + int xpos = monst->position.tile.x - monst->enemyPosition.x; + int ypos = monst->position.tile.y - monst->enemyPosition.y; + if (abs(xpos) < 2 && abs(ypos) < 2) + M_StartAttack(i); + else + M_CallWalk(i, M_GetDir(i)); + } else + MAI_SkelSd(i); } -bool MAI_Path(int i) +void MAI_Magma(int i) { - commitment((DWORD)i < MAXMONSTERS, i); - - MonsterStruct *monst = &Monsters[i]; - if (monst->MType->mtype != MT_GOLEM) { - if (monst->_msquelch == 0) - return false; - if (monst->_mmode != MM_STAND) - return false; - if (monst->_mgoal != MGOAL_NORMAL && monst->_mgoal != MGOAL_MOVE && monst->_mgoal != MGOAL_ATTACK2) - return false; - if (monst->position.tile.x == 1 && monst->position.tile.y == 0) - return false; - } - - bool clear = LineClear( - PosOkMonst2, - i, - monst->position.tile, - monst->enemyPosition); - if (!clear || (monst->_pathcount >= 5 && monst->_pathcount < 8)) { - if ((monst->_mFlags & MFLAG_CAN_OPEN_DOOR) != 0) - MonstCheckDoors(i); - monst->_pathcount++; - if (monst->_pathcount < 5) - return false; - if (M_PathWalk(i)) - return true; - } - - if (monst->MType->mtype != MT_GOLEM) - monst->_pathcount = 0; - - return false; + MAI_RoundRanged(i, MIS_MAGMABALL, true, 4, 0); } -void MAI_Snake(int i) +void MAI_SkelKing(int i) { assurance((DWORD)i < MAXMONSTERS, i); - char pattern[6] = { 1, 1, 0, -1, -1, 0 }; - MonsterStruct *monst = &Monsters[i]; - int pnum = monst->_menemy; - if (monst->_mmode != MM_STAND || monst->_msquelch == 0) - return; - int fx = monst->enemyPosition.x; - int fy = monst->enemyPosition.y; - int mx = monst->position.tile.x - fx; - int my = monst->position.tile.y - fy; - Direction md = GetDirection(monst->position.tile, monst->position.last); - monst->_mdir = md; - if (abs(mx) >= 2 || abs(my) >= 2) { - if (abs(mx) < 3 && abs(my) < 3 && LineClear(PosOkMonst, i, monst->position.tile, { fx, fy }) && monst->_mVar1 != MM_CHARGE) { - if (AddMissile(monst->position.tile, { fx, fy }, md, MIS_RHINO, pnum, i, 0, 0) != -1) { - PlayEffect(i, 0); - dMonster[monst->position.tile.x][monst->position.tile.y] = -(i + 1); - monst->_mmode = MM_CHARGE; - } - } else if (monst->_mVar1 == MM_DELAY || GenerateRnd(100) >= 35 - 2 * monst->_mint) { - if (pattern[monst->_mgoalvar1] == -1) - md = left[md]; - else if (pattern[monst->_mgoalvar1] == 1) - md = right[md]; - - monst->_mgoalvar1++; - if (monst->_mgoalvar1 > 5) - monst->_mgoalvar1 = 0; - - if (md != monst->_mgoalvar2) { - int drift = md - monst->_mgoalvar2; - if (drift < 0) - drift += 8; + MonsterStruct *monst = &Monsters[i]; + if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { + return; + } - if (drift < 4) - md = right[monst->_mgoalvar2]; - else if (drift > 4) - md = left[monst->_mgoalvar2]; - monst->_mgoalvar2 = md; + int fx = monst->enemyPosition.x; + int fy = monst->enemyPosition.y; + int mx = monst->position.tile.x - fx; + int my = monst->position.tile.y - fy; + Direction md = GetDirection(monst->position.tile, monst->position.last); + if (monst->_msquelch < UINT8_MAX) + MonstCheckDoors(i); + int v = GenerateRnd(100); + int dist = std::max(abs(mx), abs(my)); + if (dist >= 2 && monst->_msquelch == UINT8_MAX && dTransVal[monst->position.tile.x][monst->position.tile.y] == dTransVal[fx][fy]) { + if (monst->_mgoal == MGOAL_MOVE || ((abs(mx) >= 3 || abs(my) >= 3) && GenerateRnd(4) == 0)) { + if (monst->_mgoal != MGOAL_MOVE) { + monst->_mgoalvar1 = 0; + monst->_mgoalvar2 = GenerateRnd(2); + } + monst->_mgoal = MGOAL_MOVE; + if ((monst->_mgoalvar1++ >= 2 * dist && DirOK(i, md)) || dTransVal[monst->position.tile.x][monst->position.tile.y] != dTransVal[fx][fy]) { + monst->_mgoal = MGOAL_NORMAL; + } else if (!M_RoundWalk(i, md, &monst->_mgoalvar2)) { + M_StartDelay(i, GenerateRnd(10) + 10); } - - if (!M_DumbWalk(i, md)) - M_CallWalk2(i, monst->_mdir); - } else { - M_StartDelay(i, 15 - monst->_mint + GenerateRnd(10)); } } else { - if (monst->_mVar1 == MM_DELAY - || monst->_mVar1 == MM_CHARGE - || (GenerateRnd(100) < monst->_mint + 20)) { - M_StartAttack(i); - } else - M_StartDelay(i, 10 - monst->_mint + GenerateRnd(10)); + monst->_mgoal = MGOAL_NORMAL; + } + if (monst->_mgoal == MGOAL_NORMAL) { + if (!gbIsMultiplayer + && ((dist >= 3 && v < 4 * monst->_mint + 35) || v < 6) + && LineClearMissile(monst->position.tile, { fx, fy })) { + Point newPosition = monst->position.tile + md; + if (PosOkMonst(i, newPosition) && ActiveMonsterCount < MAXMONSTERS) { + M_SpawnSkel(newPosition, md); + M_StartSpStand(i, md); + } + } else { + if (dist >= 2) { + v = GenerateRnd(100); + if (v >= monst->_mint + 25 + && ((monst->_mVar1 != MM_WALK && monst->_mVar1 != MM_WALK2 && monst->_mVar1 != MM_WALK3) || monst->_mVar2 != 0 || (v >= monst->_mint + 75))) { + M_StartDelay(i, GenerateRnd(10) + 10); + } else { + M_CallWalk(i, md); + } + } else if (v < monst->_mint + 20) { + monst->_mdir = md; + M_StartAttack(i); + } + } } - monst->CheckStandAnimationIsLoaded(monst->_mdir); + monst->CheckStandAnimationIsLoaded(md); } + void MAI_Bat(int i) { assurance((DWORD)i < MAXMONSTERS, i); @@ -3017,44 +2863,43 @@ void MAI_Bat(int i) monst->CheckStandAnimationIsLoaded(md); } -void MAI_SkelBow(int i) +void MAI_Garg(int i) { assurance((DWORD)i < MAXMONSTERS, i); MonsterStruct *monst = &Monsters[i]; - if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { + int dx = monst->position.tile.x - monst->position.last.x; + int dy = monst->position.tile.y - monst->position.last.y; + Direction md = M_GetDir(i); + if (monst->_msquelch != 0 && (monst->_mFlags & MFLAG_ALLOW_SPECIAL) != 0) { + M_Enemy(i); + int mx = monst->position.tile.x - monst->enemyPosition.x; + int my = monst->position.tile.y - monst->enemyPosition.y; + if (abs(mx) < monst->_mint + 2 && abs(my) < monst->_mint + 2) { + monst->_mFlags &= ~MFLAG_ALLOW_SPECIAL; + } return; } - int mx = monst->position.tile.x - monst->enemyPosition.x; - int my = monst->position.tile.y - monst->enemyPosition.y; - - Direction md = M_GetDir(i); - monst->_mdir = md; - int v = GenerateRnd(100); - - bool walking = false; - - if (abs(mx) < 4 && abs(my) < 4) { - if ((monst->_mVar2 > 20 && v < 2 * monst->_mint + 13) - || ((monst->_mVar1 == MM_WALK || monst->_mVar1 == MM_WALK2 || monst->_mVar1 == MM_WALK3) - && monst->_mVar2 == 0 - && v < 2 * monst->_mint + 63)) { - walking = M_DumbWalk(i, opposite[md]); - } + if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { + return; } - if (!walking) { - if (GenerateRnd(100) < 2 * monst->_mint + 3) { - if (LineClearMissile(monst->position.tile, monst->enemyPosition)) - M_StartRAttack(i, MIS_ARROW, 4); + if (monst->_mhitpoints < (monst->_mmaxhp / 2)) + if ((monst->_mFlags & MFLAG_NOHEAL) == 0) + monst->_mgoal = MGOAL_RETREAT; + if (monst->_mgoal == MGOAL_RETREAT) { + if (abs(dx) >= monst->_mint + 2 || abs(dy) >= monst->_mint + 2) { + monst->_mgoal = MGOAL_NORMAL; + M_StartHeal(i); + } else if (!M_CallWalk(i, opposite[md])) { + monst->_mgoal = MGOAL_NORMAL; } } - - monst->CheckStandAnimationIsLoaded(md); + MAI_Round(i, false); } -void MAI_Fat(int i) +void MAI_Cleaver(int i) { assurance((DWORD)i < MAXMONSTERS, i); @@ -3063,27 +2908,27 @@ void MAI_Fat(int i) return; } - int mx = monst->position.tile.x - monst->enemyPosition.x; - int my = monst->position.tile.y - monst->enemyPosition.y; - Direction md = M_GetDir(i); + int mx = monst->position.tile.x; + int my = monst->position.tile.y; + int x = mx - monst->enemyPosition.x; + int y = my - monst->enemyPosition.y; + + Direction md = GetDirection({ mx, my }, monst->position.last); monst->_mdir = md; - int v = GenerateRnd(100); - if (abs(mx) >= 2 || abs(my) >= 2) { - if ((monst->_mVar2 > 20 && v < 4 * monst->_mint + 20) - || ((monst->_mVar1 == MM_WALK || monst->_mVar1 == MM_WALK2 || monst->_mVar1 == MM_WALK3) - && monst->_mVar2 == 0 - && v < 4 * monst->_mint + 70)) { - M_CallWalk(i, md); - } - } else if (v < 4 * monst->_mint + 15) { + + if (abs(x) >= 2 || abs(y) >= 2) + M_CallWalk(i, md); + else M_StartAttack(i); - } else if (v < 4 * monst->_mint + 20) { - M_StartSpAttack(i); - } monst->CheckStandAnimationIsLoaded(md); } +void MAI_Succ(int i) +{ + MAI_Ranged(i, MIS_FLARE, false); +} + void MAI_Sneak(int i) { assurance((DWORD)i < MAXMONSTERS, i); @@ -3145,427 +2990,555 @@ void MAI_Sneak(int i) } } -void MAI_Fireman(int i) +void MAI_Storm(int i) +{ + MAI_RoundRanged(i, MIS_LIGHTCTRL2, true, 4, 0); +} + +void MAI_Fireman(int i) +{ + assurance((DWORD)i < MAXMONSTERS, i); + + MonsterStruct *monst = &Monsters[i]; + if (Monsters[i]._mmode != MM_STAND || monst->_msquelch == 0) + return; + + int pnum = Monsters[i]._menemy; + int fx = Monsters[i].enemyPosition.x; + int fy = Monsters[i].enemyPosition.y; + int xd = Monsters[i].position.tile.x - fx; + int yd = Monsters[i].position.tile.y - fy; + + Direction md = M_GetDir(i); + if (monst->_mgoal == MGOAL_NORMAL) { + if (LineClearMissile(monst->position.tile, { fx, fy }) + && AddMissile(monst->position.tile, { fx, fy }, md, MIS_FIREMAN, pnum, i, 0, 0) != -1) { + monst->_mmode = MM_CHARGE; + monst->_mgoal = MGOAL_ATTACK2; + monst->_mgoalvar1 = 0; + } + } else if (monst->_mgoal == MGOAL_ATTACK2) { + if (monst->_mgoalvar1 == 3) { + monst->_mgoal = MGOAL_NORMAL; + M_StartFadeout(i, md, true); + } else if (LineClearMissile(monst->position.tile, { fx, fy })) { + M_StartRAttack(i, MIS_KRULL, 4); + monst->_mgoalvar1++; + } else { + M_StartDelay(i, GenerateRnd(10) + 5); + monst->_mgoalvar1++; + } + } else if (monst->_mgoal == MGOAL_RETREAT) { + M_StartFadein(i, md, false); + monst->_mgoal = MGOAL_ATTACK2; + } + monst->_mdir = md; + AdvanceRndSeed(); + if (monst->_mmode != MM_STAND) + return; + + if (abs(xd) < 2 && abs(yd) < 2 && monst->_mgoal == MGOAL_NORMAL) { + M_TryH2HHit(i, Monsters[i]._menemy, Monsters[i].mHit, Monsters[i].mMinDamage, Monsters[i].mMaxDamage); + monst->_mgoal = MGOAL_RETREAT; + if (!M_CallWalk(i, opposite[md])) { + M_StartFadein(i, md, false); + monst->_mgoal = MGOAL_ATTACK2; + } + } else if (!M_CallWalk(i, md) && (monst->_mgoal == MGOAL_NORMAL || monst->_mgoal == MGOAL_RETREAT)) { + M_StartFadein(i, md, false); + monst->_mgoal = MGOAL_ATTACK2; + } +} + +void MAI_Garbud(int i) +{ + assurance((DWORD)i < MAXMONSTERS, i); + + MonsterStruct *monst = &Monsters[i]; + if (monst->_mmode != MM_STAND) { + return; + } + + int mx = monst->position.tile.x; + int my = monst->position.tile.y; + Direction md = M_GetDir(i); + + if (monst->mtalkmsg >= TEXT_GARBUD1 + && monst->mtalkmsg <= TEXT_GARBUD3 + && (dFlags[mx][my] & BFLAG_VISIBLE) == 0 + && monst->_mgoal == MGOAL_TALKING) { + monst->_mgoal = MGOAL_INQUIRING; + switch (monst->mtalkmsg) { + case TEXT_GARBUD1: + monst->mtalkmsg = TEXT_GARBUD2; + break; + case TEXT_GARBUD2: + monst->mtalkmsg = TEXT_GARBUD3; + break; + case TEXT_GARBUD3: + monst->mtalkmsg = TEXT_GARBUD4; + break; + default: + break; + } + } + + if ((dFlags[mx][my] & BFLAG_VISIBLE) != 0) { + if (monst->mtalkmsg == TEXT_GARBUD4) { + if (!effect_is_playing(USFX_GARBUD4) && monst->_mgoal == MGOAL_TALKING) { + monst->_mgoal = MGOAL_NORMAL; + monst->_msquelch = UINT8_MAX; + monst->mtalkmsg = TEXT_NONE; + } + } + } + + if (monst->_mgoal == MGOAL_NORMAL || monst->_mgoal == MGOAL_MOVE) + MAI_Round(i, true); + + monst->CheckStandAnimationIsLoaded(md); +} + +void MAI_Acid(int i) +{ + MAI_RoundRanged(i, MIS_ACID, false, 4, 1); +} + +void MAI_AcidUniq(int i) +{ + MAI_Ranged(i, MIS_ACID, true); +} + +void MAI_SnotSpil(int i) { assurance((DWORD)i < MAXMONSTERS, i); MonsterStruct *monst = &Monsters[i]; - if (Monsters[i]._mmode != MM_STAND || monst->_msquelch == 0) + if (Monsters[i]._mmode != MM_STAND) { return; + } - int pnum = Monsters[i]._menemy; - int fx = Monsters[i].enemyPosition.x; - int fy = Monsters[i].enemyPosition.y; - int xd = Monsters[i].position.tile.x - fx; - int yd = Monsters[i].position.tile.y - fy; - + int mx = monst->position.tile.x; + int my = monst->position.tile.y; Direction md = M_GetDir(i); - if (monst->_mgoal == MGOAL_NORMAL) { - if (LineClearMissile(monst->position.tile, { fx, fy }) - && AddMissile(monst->position.tile, { fx, fy }, md, MIS_FIREMAN, pnum, i, 0, 0) != -1) { - monst->_mmode = MM_CHARGE; - monst->_mgoal = MGOAL_ATTACK2; - monst->_mgoalvar1 = 0; + + if (monst->mtalkmsg == TEXT_BANNER10 && (dFlags[mx][my] & BFLAG_VISIBLE) == 0 && monst->_mgoal == MGOAL_TALKING) { + monst->mtalkmsg = TEXT_BANNER11; + monst->_mgoal = MGOAL_INQUIRING; + } + + if (monst->mtalkmsg == TEXT_BANNER11 && Quests[Q_LTBANNER]._qvar1 == 3) { + monst->mtalkmsg = TEXT_NONE; + monst->_mgoal = MGOAL_NORMAL; + } + + if ((dFlags[mx][my] & BFLAG_VISIBLE) != 0) { + if (monst->mtalkmsg == TEXT_BANNER12) { + if (!effect_is_playing(USFX_SNOT3) && monst->_mgoal == MGOAL_TALKING) { + ObjChangeMap(setpc_x, setpc_y, setpc_x + setpc_w + 1, setpc_y + setpc_h + 1); + Quests[Q_LTBANNER]._qvar1 = 3; + RedoPlayerVision(); + monst->_msquelch = UINT8_MAX; + monst->mtalkmsg = TEXT_NONE; + monst->_mgoal = MGOAL_NORMAL; + } } - } else if (monst->_mgoal == MGOAL_ATTACK2) { - if (monst->_mgoalvar1 == 3) { - monst->_mgoal = MGOAL_NORMAL; - M_StartFadeout(i, md, true); - } else if (LineClearMissile(monst->position.tile, { fx, fy })) { - M_StartRAttack(i, MIS_KRULL, 4); - monst->_mgoalvar1++; - } else { - M_StartDelay(i, GenerateRnd(10) + 5); - monst->_mgoalvar1++; + if (Quests[Q_LTBANNER]._qvar1 == 3) { + if (monst->_mgoal == MGOAL_NORMAL || monst->_mgoal == MGOAL_ATTACK2) + MAI_Fallen(i); } - } else if (monst->_mgoal == MGOAL_RETREAT) { - M_StartFadein(i, md, false); - monst->_mgoal = MGOAL_ATTACK2; } - monst->_mdir = md; - AdvanceRndSeed(); - if (monst->_mmode != MM_STAND) + + monst->CheckStandAnimationIsLoaded(md); +} + +void MAI_Snake(int i) +{ + assurance((DWORD)i < MAXMONSTERS, i); + char pattern[6] = { 1, 1, 0, -1, -1, 0 }; + MonsterStruct *monst = &Monsters[i]; + int pnum = monst->_menemy; + if (monst->_mmode != MM_STAND || monst->_msquelch == 0) return; + int fx = monst->enemyPosition.x; + int fy = monst->enemyPosition.y; + int mx = monst->position.tile.x - fx; + int my = monst->position.tile.y - fy; + Direction md = GetDirection(monst->position.tile, monst->position.last); + monst->_mdir = md; + if (abs(mx) >= 2 || abs(my) >= 2) { + if (abs(mx) < 3 && abs(my) < 3 && LineClear(PosOkMonst, i, monst->position.tile, { fx, fy }) && monst->_mVar1 != MM_CHARGE) { + if (AddMissile(monst->position.tile, { fx, fy }, md, MIS_RHINO, pnum, i, 0, 0) != -1) { + PlayEffect(i, 0); + dMonster[monst->position.tile.x][monst->position.tile.y] = -(i + 1); + monst->_mmode = MM_CHARGE; + } + } else if (monst->_mVar1 == MM_DELAY || GenerateRnd(100) >= 35 - 2 * monst->_mint) { + if (pattern[monst->_mgoalvar1] == -1) + md = left[md]; + else if (pattern[monst->_mgoalvar1] == 1) + md = right[md]; - if (abs(xd) < 2 && abs(yd) < 2 && monst->_mgoal == MGOAL_NORMAL) { - M_TryH2HHit(i, Monsters[i]._menemy, Monsters[i].mHit, Monsters[i].mMinDamage, Monsters[i].mMaxDamage); - monst->_mgoal = MGOAL_RETREAT; - if (!M_CallWalk(i, opposite[md])) { - M_StartFadein(i, md, false); - monst->_mgoal = MGOAL_ATTACK2; + monst->_mgoalvar1++; + if (monst->_mgoalvar1 > 5) + monst->_mgoalvar1 = 0; + + if (md != monst->_mgoalvar2) { + int drift = md - monst->_mgoalvar2; + if (drift < 0) + drift += 8; + + if (drift < 4) + md = right[monst->_mgoalvar2]; + else if (drift > 4) + md = left[monst->_mgoalvar2]; + monst->_mgoalvar2 = md; + } + + if (!M_DumbWalk(i, md)) + M_CallWalk2(i, monst->_mdir); + } else { + M_StartDelay(i, 15 - monst->_mint + GenerateRnd(10)); } - } else if (!M_CallWalk(i, md) && (monst->_mgoal == MGOAL_NORMAL || monst->_mgoal == MGOAL_RETREAT)) { - M_StartFadein(i, md, false); - monst->_mgoal = MGOAL_ATTACK2; + } else { + if (monst->_mVar1 == MM_DELAY + || monst->_mVar1 == MM_CHARGE + || (GenerateRnd(100) < monst->_mint + 20)) { + M_StartAttack(i); + } else + M_StartDelay(i, 10 - monst->_mint + GenerateRnd(10)); } + + monst->CheckStandAnimationIsLoaded(monst->_mdir); } -void MAI_Fallen(int i) +void MAI_Counselor(int i) { assurance((DWORD)i < MAXMONSTERS, i); MonsterStruct *monst = &Monsters[i]; - if (monst->_mgoal == MGOAL_ATTACK2) { - if (monst->_mgoalvar1 != 0) - monst->_mgoalvar1--; - else - monst->_mgoal = MGOAL_NORMAL; - } if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { return; } - + int fx = monst->enemyPosition.x; + int fy = monst->enemyPosition.y; + int mx = monst->position.tile.x - fx; + int my = monst->position.tile.y - fy; + Direction md = GetDirection(monst->position.tile, monst->position.last); + if (monst->_msquelch < UINT8_MAX) + MonstCheckDoors(i); + int v = GenerateRnd(100); if (monst->_mgoal == MGOAL_RETREAT) { - if (monst->_mgoalvar1-- == 0) { + if (monst->_mgoalvar1++ <= 3) + M_CallWalk(i, opposite[md]); + else { monst->_mgoal = MGOAL_NORMAL; - M_StartStand(i, opposite[monst->_mgoalvar2]); - } - } - - if (monst->AnimInfo.CurrentFrame == monst->AnimInfo.NumberOfFrames) { - if (GenerateRnd(4) != 0) { - return; - } - if ((Monsters[i]._mFlags & MFLAG_NOHEAL) == 0) { // CODEFIX: - change to Monst-> in devilutionx - M_StartSpStand(i, monst->_mdir); - if (monst->_mmaxhp - (2 * monst->_mint + 2) >= monst->_mhitpoints) - monst->_mhitpoints += 2 * monst->_mint + 2; - else - monst->_mhitpoints = monst->_mmaxhp; + M_StartFadein(i, md, true); } - int rad = 2 * monst->_mint + 4; - for (int y = -rad; y <= rad; y++) { - for (int x = -rad; x <= rad; x++) { - int xpos = monst->position.tile.x + x; - int ypos = monst->position.tile.y + y; - if (y >= 0 && y < MAXDUNY && x >= 0 && x < MAXDUNX) { - int m = dMonster[xpos][ypos]; - if (m > 0) { - m--; - if (Monsters[m]._mAi == AI_FALLEN) { - Monsters[m]._mgoal = MGOAL_ATTACK2; - Monsters[m]._mgoalvar1 = 30 * monst->_mint + 105; - } - } - } + } else if (monst->_mgoal == MGOAL_MOVE) { + int dist = std::max(abs(mx), abs(my)); + if (dist >= 2 && monst->_msquelch == UINT8_MAX && dTransVal[monst->position.tile.x][monst->position.tile.y] == dTransVal[fx][fy]) { + if (monst->_mgoalvar1++ < 2 * dist || !DirOK(i, md)) { + M_RoundWalk(i, md, &monst->_mgoalvar2); + } else { + monst->_mgoal = MGOAL_NORMAL; + M_StartFadein(i, md, true); } } - } else if (monst->_mgoal == MGOAL_RETREAT) { - M_CallWalk(i, monst->_mdir); - } else if (monst->_mgoal == MGOAL_ATTACK2) { - int xpos = monst->position.tile.x - monst->enemyPosition.x; - int ypos = monst->position.tile.y - monst->enemyPosition.y; - if (abs(xpos) < 2 && abs(ypos) < 2) - M_StartAttack(i); - else - M_CallWalk(i, M_GetDir(i)); - } else - MAI_SkelSd(i); + } else if (monst->_mgoal == MGOAL_NORMAL) { + if (abs(mx) >= 2 || abs(my) >= 2) { + if (v < 5 * (monst->_mint + 10) && LineClearMissile(monst->position.tile, { fx, fy })) { + constexpr missile_id MissileTypes[4] = { MIS_FIREBOLT, MIS_CBOLT, MIS_LIGHTCTRL, MIS_FIREBALL }; + M_StartRAttack(i, MissileTypes[monst->_mint], monst->mMinDamage + GenerateRnd(monst->mMaxDamage - monst->mMinDamage + 1)); + } else if (GenerateRnd(100) < 30) { + monst->_mgoal = MGOAL_MOVE; + monst->_mgoalvar1 = 0; + M_StartFadeout(i, md, false); + } else + M_StartDelay(i, GenerateRnd(10) + 2 * (5 - monst->_mint)); + } else { + monst->_mdir = md; + if (monst->_mhitpoints < (monst->_mmaxhp / 2)) { + monst->_mgoal = MGOAL_RETREAT; + monst->_mgoalvar1 = 0; + M_StartFadeout(i, md, false); + } else if (monst->_mVar1 == MM_DELAY + || GenerateRnd(100) < 2 * monst->_mint + 20) { + M_StartRAttack(i, MIS_NULL, 0); + AddMissile(monst->position.tile, { 0, 0 }, monst->_mdir, MIS_FLASH, TARGET_PLAYERS, i, 4, 0); + AddMissile(monst->position.tile, { 0, 0 }, monst->_mdir, MIS_FLASH2, TARGET_PLAYERS, i, 4, 0); + } else + M_StartDelay(i, GenerateRnd(10) + 2 * (5 - monst->_mint)); + } + } + if (monst->_mmode == MM_STAND) { + M_StartDelay(i, GenerateRnd(10) + 5); + } } -void MAI_Cleaver(int i) +void MAI_Zhar(int i) { assurance((DWORD)i < MAXMONSTERS, i); MonsterStruct *monst = &Monsters[i]; - if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { + if (Monsters[i]._mmode != MM_STAND) { return; } int mx = monst->position.tile.x; int my = monst->position.tile.y; - int x = mx - monst->enemyPosition.x; - int y = my - monst->enemyPosition.y; + Direction md = M_GetDir(i); + if (monst->mtalkmsg == TEXT_ZHAR1 && (dFlags[mx][my] & BFLAG_VISIBLE) == 0 && monst->_mgoal == MGOAL_TALKING) { + monst->mtalkmsg = TEXT_ZHAR2; + monst->_mgoal = MGOAL_INQUIRING; + } - Direction md = GetDirection({ mx, my }, monst->position.last); - monst->_mdir = md; + if ((dFlags[mx][my] & BFLAG_VISIBLE) != 0) { + if (monst->mtalkmsg == TEXT_ZHAR2) { + if (!effect_is_playing(USFX_ZHAR2) && monst->_mgoal == MGOAL_TALKING) { + monst->_msquelch = UINT8_MAX; + monst->mtalkmsg = TEXT_NONE; + monst->_mgoal = MGOAL_NORMAL; + } + } + } - if (abs(x) >= 2 || abs(y) >= 2) - M_CallWalk(i, md); - else - M_StartAttack(i); + if (monst->_mgoal == MGOAL_NORMAL || monst->_mgoal == MGOAL_RETREAT || monst->_mgoal == MGOAL_MOVE) + MAI_Counselor(i); monst->CheckStandAnimationIsLoaded(md); } -void MAI_Round(int i, bool special) +void MAI_RR2(int i, missile_id mistype, int dam) { assurance((DWORD)i < MAXMONSTERS, i); + MonsterStruct *monst = &Monsters[i]; + int mx = monst->position.tile.x - monst->enemyPosition.x; + int my = monst->position.tile.y - monst->enemyPosition.y; + if (abs(mx) >= 5 || abs(my) >= 5) { + MAI_SkelSd(i); + return; + } + if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { return; } int fx = monst->enemyPosition.x; int fy = monst->enemyPosition.y; - int mx = monst->position.tile.x - fx; - int my = monst->position.tile.y - fy; + mx = monst->position.tile.x - fx; + my = monst->position.tile.y - fy; Direction md = GetDirection(monst->position.tile, monst->position.last); if (monst->_msquelch < UINT8_MAX) MonstCheckDoors(i); int v = GenerateRnd(100); - if ((abs(mx) >= 2 || abs(my) >= 2) && monst->_msquelch == UINT8_MAX && dTransVal[monst->position.tile.x][monst->position.tile.y] == dTransVal[fx][fy]) { - if (monst->_mgoal == MGOAL_MOVE || ((abs(mx) >= 4 || abs(my) >= 4) && GenerateRnd(4) == 0)) { + int dist = std::max(abs(mx), abs(my)); + if (dist >= 2 && monst->_msquelch == UINT8_MAX && dTransVal[monst->position.tile.x][monst->position.tile.y] == dTransVal[fx][fy]) { + if (monst->_mgoal == MGOAL_MOVE || dist >= 3) { if (monst->_mgoal != MGOAL_MOVE) { monst->_mgoalvar1 = 0; monst->_mgoalvar2 = GenerateRnd(2); } monst->_mgoal = MGOAL_MOVE; - int dist = std::max(abs(mx), abs(my)); - if ((monst->_mgoalvar1++ >= 2 * dist && DirOK(i, md)) || dTransVal[monst->position.tile.x][monst->position.tile.y] != dTransVal[fx][fy]) { + monst->_mgoalvar3 = 4; + if (monst->_mgoalvar1++ < 2 * dist || !DirOK(i, md)) { + if (v < 5 * (monst->_mint + 16)) + M_RoundWalk(i, md, &monst->_mgoalvar2); + } else monst->_mgoal = MGOAL_NORMAL; - } else if (!M_RoundWalk(i, md, &monst->_mgoalvar2)) { - M_StartDelay(i, GenerateRnd(10) + 10); - } } } else { monst->_mgoal = MGOAL_NORMAL; } if (monst->_mgoal == MGOAL_NORMAL) { - if (abs(mx) >= 2 || abs(my) >= 2) { - if ((monst->_mVar2 > 20 && v < 2 * monst->_mint + 28) + if (((dist >= 3 && v < 5 * (monst->_mint + 2)) || v < 5 * (monst->_mint + 1) || monst->_mgoalvar3 == 4) && LineClearMissile(monst->position.tile, { fx, fy })) { + M_StartRSpAttack(i, mistype, dam); + } else if (dist >= 2) { + v = GenerateRnd(100); + if (v < 2 * (5 * monst->_mint + 25) || ((monst->_mVar1 == MM_WALK || monst->_mVar1 == MM_WALK2 || monst->_mVar1 == MM_WALK3) && monst->_mVar2 == 0 - && v < 2 * monst->_mint + 78)) { + && v < 2 * (5 * monst->_mint + 40))) { M_CallWalk(i, md); } - } else if (v < 2 * monst->_mint + 23) { - monst->_mdir = md; - if (special && monst->_mhitpoints < (monst->_mmaxhp / 2) && GenerateRnd(2) != 0) - M_StartSpAttack(i); - else - M_StartAttack(i); - } - } - - monst->CheckStandAnimationIsLoaded(md); -} - -void MAI_GoatMc(int i) -{ - MAI_Round(i, true); -} - -void MAI_Ranged(int i, missile_id missileType, bool special) -{ - assurance((DWORD)i < MAXMONSTERS, i); - - if (Monsters[i]._mmode != MM_STAND) { - return; - } - - MonsterStruct *monst = &Monsters[i]; - if (monst->_msquelch == UINT8_MAX || (monst->_mFlags & MFLAG_TARGETS_MONSTER) != 0) { - int fx = monst->enemyPosition.x; - int fy = monst->enemyPosition.y; - int mx = monst->position.tile.x - fx; - int my = monst->position.tile.y - fy; - Direction md = M_GetDir(i); - if (monst->_msquelch < UINT8_MAX) - MonstCheckDoors(i); - monst->_mdir = md; - if (monst->_mVar1 == MM_RATTACK) { - M_StartDelay(i, GenerateRnd(20)); - } else if (abs(mx) < 4 && abs(my) < 4) { - if (GenerateRnd(100) < 10 * (monst->_mint + 7)) - M_CallWalk(i, opposite[md]); - } - if (monst->_mmode == MM_STAND) { - if (LineClearMissile(monst->position.tile, { fx, fy })) { - if (special) - M_StartRSpAttack(i, missileType, 4); + } else { + if (GenerateRnd(100) < 10 * (monst->_mint + 4)) { + monst->_mdir = md; + if (GenerateRnd(2) != 0) + M_StartAttack(i); else - M_StartRAttack(i, missileType, 4); - } else { - monst->CheckStandAnimationIsLoaded(md); + M_StartRSpAttack(i, mistype, dam); } } - return; + monst->_mgoalvar3 = 1; } - - if (monst->_msquelch != 0) { - int fx = monst->position.last.x; - int fy = monst->position.last.y; - Direction md = GetDirection(monst->position.tile, { fx, fy }); - M_CallWalk(i, md); + if (monst->_mmode == MM_STAND) { + M_StartDelay(i, GenerateRnd(10) + 5); } } -void MAI_GoatBow(int i) -{ - MAI_Ranged(i, MIS_ARROW, false); -} - -void MAI_Succ(int i) -{ - MAI_Ranged(i, MIS_FLARE, false); -} - -void MAI_Lich(int i) -{ - MAI_Ranged(i, MIS_LICH, false); -} - -void MAI_ArchLich(int i) -{ - MAI_Ranged(i, MIS_ARCHLICH, false); -} - -void MAI_Psychorb(int i) -{ - MAI_Ranged(i, MIS_PSYCHORB, false); -} - -void MAI_Necromorb(int i) -{ - MAI_Ranged(i, MIS_NECROMORB, false); -} - -void MAI_AcidUniq(int i) -{ - MAI_Ranged(i, MIS_ACID, true); -} - -void MAI_Firebat(int i) +void MAI_Mega(int i) { - MAI_Ranged(i, MIS_FIREBOLT, false); + MAI_RR2(i, MIS_FLAMEC, 0); } -void MAI_Torchant(int i) +void MAI_Diablo(int i) { - MAI_Ranged(i, MIS_FIREBALL, false); + MAI_RoundRanged(i, MIS_DIABAPOCA, false, 40, 0); } -void MAI_Scav(int i) +void MAI_Lazurus(int i) { assurance((DWORD)i < MAXMONSTERS, i); MonsterStruct *monst = &Monsters[i]; - if (Monsters[i]._mmode != MM_STAND) + if (Monsters[i]._mmode != MM_STAND) { return; - if (monst->_mhitpoints < (monst->_mmaxhp / 2) && monst->_mgoal != MGOAL_HEALING) { - if (monst->leaderflag != 0) { - Monsters[monst->leader].packsize--; - monst->leaderflag = 0; - } - monst->_mgoal = MGOAL_HEALING; - monst->_mgoalvar3 = 10; } - if (monst->_mgoal == MGOAL_HEALING && monst->_mgoalvar3 != 0) { - monst->_mgoalvar3--; - if (dDead[monst->position.tile.x][monst->position.tile.y] != 0) { - M_StartEat(i); - if ((monst->_mFlags & MFLAG_NOHEAL) == 0) { - if (gbIsHellfire) { - int mMaxHP = monst->_mmaxhp; // BUGFIX use _mmaxhp or we loose health when difficulty isn't normal (fixed) - monst->_mhitpoints += mMaxHP / 8; - if (monst->_mhitpoints > monst->_mmaxhp) - monst->_mhitpoints = monst->_mmaxhp; - if (monst->_mgoalvar3 <= 0 || monst->_mhitpoints == monst->_mmaxhp) - dDead[monst->position.tile.x][monst->position.tile.y] = 0; - } else { - monst->_mhitpoints += 64; - } + + int mx = monst->position.tile.x; + int my = monst->position.tile.y; + Direction md = M_GetDir(i); + if ((dFlags[mx][my] & BFLAG_VISIBLE) != 0) { + if (!gbIsMultiplayer) { + if (monst->mtalkmsg == TEXT_VILE13 && monst->_mgoal == MGOAL_INQUIRING && Players[MyPlayerId].position.tile.x == 35 && Players[MyPlayerId].position.tile.y == 46) { + PlayInGameMovie("gendata\\fprst3.smk"); + monst->_mmode = MM_TALK; + Quests[Q_BETRAYER]._qvar1 = 5; } - int targetHealth = monst->_mmaxhp; - if (!gbIsHellfire) - targetHealth = (monst->_mmaxhp / 2) + (monst->_mmaxhp / 4); - if (monst->_mhitpoints >= targetHealth) { + + if (monst->mtalkmsg == TEXT_VILE13 && !effect_is_playing(USFX_LAZ1) && monst->_mgoal == MGOAL_TALKING) { + ObjChangeMapResync(1, 18, 20, 24); + RedoPlayerVision(); + Quests[Q_BETRAYER]._qvar1 = 6; monst->_mgoal = MGOAL_NORMAL; - monst->_mgoalvar1 = 0; - monst->_mgoalvar2 = 0; - } - } else { - if (monst->_mgoalvar1 == 0) { - bool done = false; - int x; - int y; - if (GenerateRnd(2) != 0) { - for (y = -4; y <= 4 && !done; y++) { - for (x = -4; x <= 4 && !done; x++) { - // BUGFIX: incorrect check of offset against limits of the dungeon - if (y < 0 || y >= MAXDUNY || x < 0 || x >= MAXDUNX) - continue; - done = dDead[monst->position.tile.x + x][monst->position.tile.y + y] != 0 - && LineClearSolid( - monst->position.tile, - monst->position.tile + Displacement { x, y }); - } - } - x--; - y--; - } else { - for (y = 4; y >= -4 && !done; y--) { - for (x = 4; x >= -4 && !done; x--) { - // BUGFIX: incorrect check of offset against limits of the dungeon - if (y < 0 || y >= MAXDUNY || x < 0 || x >= MAXDUNX) - continue; - done = dDead[monst->position.tile.x + x][monst->position.tile.y + y] != 0 - && LineClearSolid( - monst->position.tile, - monst->position.tile + Displacement { x, y }); - } - } - x++; - y++; - } - if (done) { - monst->_mgoalvar1 = x + monst->position.tile.x + 1; - monst->_mgoalvar2 = y + monst->position.tile.y + 1; - } - } - if (monst->_mgoalvar1 != 0) { - int x = monst->_mgoalvar1 - 1; - int y = monst->_mgoalvar2 - 1; - monst->_mdir = GetDirection(monst->position.tile, { x, y }); - M_CallWalk(i, monst->_mdir); + monst->_msquelch = UINT8_MAX; + monst->mtalkmsg = TEXT_NONE; } } + + if (gbIsMultiplayer && monst->mtalkmsg == TEXT_VILE13 && monst->_mgoal == MGOAL_INQUIRING && Quests[Q_BETRAYER]._qvar1 <= 3) { + monst->_mmode = MM_TALK; + } + } + + if (monst->_mgoal == MGOAL_NORMAL || monst->_mgoal == MGOAL_RETREAT || monst->_mgoal == MGOAL_MOVE) { + if (!gbIsMultiplayer && Quests[Q_BETRAYER]._qvar1 == 4 && monst->mtalkmsg == TEXT_NONE) { // Fix save games affected by teleport bug + ObjChangeMapResync(1, 18, 20, 24); + RedoPlayerVision(); + Quests[Q_BETRAYER]._qvar1 = 6; + } + monst->mtalkmsg = TEXT_NONE; + MAI_Counselor(i); } - if (monst->_mmode == MM_STAND) - MAI_SkelSd(i); + monst->CheckStandAnimationIsLoaded(md); } -void MAI_Garg(int i) +void MAI_Lazhelp(int i) { assurance((DWORD)i < MAXMONSTERS, i); + if (Monsters[i]._mmode != MM_STAND) + return; MonsterStruct *monst = &Monsters[i]; - int dx = monst->position.tile.x - monst->position.last.x; - int dy = monst->position.tile.y - monst->position.last.y; + int mx = monst->position.tile.x; + int my = monst->position.tile.y; Direction md = M_GetDir(i); - if (monst->_msquelch != 0 && (monst->_mFlags & MFLAG_ALLOW_SPECIAL) != 0) { - M_Enemy(i); - int mx = monst->position.tile.x - monst->enemyPosition.x; - int my = monst->position.tile.y - monst->enemyPosition.y; - if (abs(mx) < monst->_mint + 2 && abs(my) < monst->_mint + 2) { - monst->_mFlags &= ~MFLAG_ALLOW_SPECIAL; - } + + if ((dFlags[mx][my] & BFLAG_VISIBLE) != 0) { + if (!gbIsMultiplayer) { + if (Quests[Q_BETRAYER]._qvar1 <= 5) { + monst->_mgoal = MGOAL_INQUIRING; + } else { + monst->_mgoal = MGOAL_NORMAL; + monst->mtalkmsg = TEXT_NONE; + } + } else + monst->_mgoal = MGOAL_NORMAL; + } + if (monst->_mgoal == MGOAL_NORMAL) + MAI_Succ(i); + + monst->CheckStandAnimationIsLoaded(md); +} + +void MAI_Lachdanan(int i) +{ + assurance((DWORD)i < MAXMONSTERS, i); + + MonsterStruct *monst = &Monsters[i]; + if (Monsters[i]._mmode != MM_STAND) { return; } - if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { + int mx = monst->position.tile.x; + int my = monst->position.tile.y; + Direction md = M_GetDir(i); + + if (monst->mtalkmsg == TEXT_VEIL9 && (dFlags[mx][my] & BFLAG_VISIBLE) == 0 && Monsters[i]._mgoal == MGOAL_TALKING) { + monst->mtalkmsg = TEXT_VEIL10; + Monsters[i]._mgoal = MGOAL_INQUIRING; + } + + if ((dFlags[mx][my] & BFLAG_VISIBLE) != 0) { + if (monst->mtalkmsg == TEXT_VEIL11) { + if (!effect_is_playing(USFX_LACH3) && monst->_mgoal == MGOAL_TALKING) { + monst->mtalkmsg = TEXT_NONE; + Quests[Q_VEIL]._qactive = QUEST_DONE; + M_StartKill(i, -1); + } + } + } + + monst->CheckStandAnimationIsLoaded(md); +} + +void MAI_Warlord(int i) +{ + assurance((DWORD)i < MAXMONSTERS, i); + + MonsterStruct *monst = &Monsters[i]; + if (Monsters[i]._mmode != MM_STAND) { return; } - if (monst->_mhitpoints < (monst->_mmaxhp / 2)) - if ((monst->_mFlags & MFLAG_NOHEAL) == 0) - monst->_mgoal = MGOAL_RETREAT; - if (monst->_mgoal == MGOAL_RETREAT) { - if (abs(dx) >= monst->_mint + 2 || abs(dy) >= monst->_mint + 2) { - monst->_mgoal = MGOAL_NORMAL; - M_StartHeal(i); - } else if (!M_CallWalk(i, opposite[md])) { + int mx = monst->position.tile.x; + int my = monst->position.tile.y; + Direction md = M_GetDir(i); + if ((dFlags[mx][my] & BFLAG_VISIBLE) != 0) { + if (monst->mtalkmsg == TEXT_WARLRD9 && monst->_mgoal == MGOAL_INQUIRING) + monst->_mmode = MM_TALK; + if (monst->mtalkmsg == TEXT_WARLRD9 && !effect_is_playing(USFX_WARLRD1) && monst->_mgoal == MGOAL_TALKING) { + monst->_msquelch = UINT8_MAX; + monst->mtalkmsg = TEXT_NONE; monst->_mgoal = MGOAL_NORMAL; } } - MAI_Round(i, false); + + if (monst->_mgoal == MGOAL_NORMAL) + MAI_SkelSd(i); + + monst->CheckStandAnimationIsLoaded(md); } -void MAI_RoundRanged(int i, missile_id missileType, bool checkdoors, int dam, int lessmissiles) +void MAI_Firebat(int i) { - assurance((DWORD)i < MAXMONSTERS, i); + MAI_Ranged(i, MIS_FIREBOLT, false); +} + +void MAI_Torchant(int i) +{ + MAI_Ranged(i, MIS_FIREBALL, false); +} + +void MAI_HorkDemon(int i) +{ + if ((DWORD)i >= MAXMONSTERS) { + return; + } + MonsterStruct *monst = &Monsters[i]; if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { return; @@ -3576,728 +3549,886 @@ void MAI_RoundRanged(int i, missile_id missileType, bool checkdoors, int dam, in int mx = monst->position.tile.x - fx; int my = monst->position.tile.y - fy; Direction md = GetDirection(monst->position.tile, monst->position.last); - if (checkdoors && monst->_msquelch < UINT8_MAX) + + if (monst->_msquelch < 255) { MonstCheckDoors(i); - int v = GenerateRnd(10000); - int dist = std::max(abs(mx), abs(my)); - if (dist >= 2 && monst->_msquelch == UINT8_MAX && dTransVal[monst->position.tile.x][monst->position.tile.y] == dTransVal[fx][fy]) { - if (monst->_mgoal == MGOAL_MOVE || (dist >= 3 && GenerateRnd(4 << lessmissiles) == 0)) { - if (monst->_mgoal != MGOAL_MOVE) { - monst->_mgoalvar1 = 0; - monst->_mgoalvar2 = GenerateRnd(2); - } - monst->_mgoal = MGOAL_MOVE; - if (monst->_mgoalvar1++ >= 2 * dist && DirOK(i, md)) { - monst->_mgoal = MGOAL_NORMAL; - } else if (v < (500 * (monst->_mint + 1) >> lessmissiles) - && (LineClearMissile(monst->position.tile, { fx, fy }))) { - M_StartRSpAttack(i, missileType, dam); - } else { - M_RoundWalk(i, md, &monst->_mgoalvar2); - } - } - } else { + } + + int v = GenerateRnd(100); + + if (abs(mx) < 2 && abs(my) < 2) { monst->_mgoal = MGOAL_NORMAL; + } else if (monst->_mgoal == 4 || ((abs(mx) >= 5 || abs(my) >= 5) && GenerateRnd(4) != 0)) { + if (monst->_mgoal != 4) { + monst->_mgoalvar1 = 0; + monst->_mgoalvar2 = GenerateRnd(2); + } + monst->_mgoal = MGOAL_MOVE; + int dist = std::max(abs(mx), abs(my)); + if (monst->_mgoalvar1++ >= 2 * dist || dTransVal[monst->position.tile.x][monst->position.tile.y] != dTransVal[fx][fy]) { + monst->_mgoal = MGOAL_NORMAL; + } else if (!M_RoundWalk(i, md, &monst->_mgoalvar2)) { + M_StartDelay(i, GenerateRnd(10) + 10); + } } - if (monst->_mgoal == MGOAL_NORMAL) { - if (((dist >= 3 && v < ((500 * (monst->_mint + 2)) >> lessmissiles)) - || v < ((500 * (monst->_mint + 1)) >> lessmissiles)) - && LineClearMissile(monst->position.tile, { fx, fy })) { - M_StartRSpAttack(i, missileType, dam); - } else if (dist >= 2) { + + if (monst->_mgoal == 1) { + if ((abs(mx) >= 3 || abs(my) >= 3) && v < 2 * monst->_mint + 43) { + Point position = monst->position.tile + monst->_mdir; + if (PosOkMonst(i, position) && ActiveMonsterCount < MAXMONSTERS) { + M_StartRSpAttack(i, MIS_HORKDMN, 0); + } + } else if (abs(mx) < 2 && abs(my) < 2) { + if (v < 2 * monst->_mint + 28) { + monst->_mdir = md; + M_StartAttack(i); + } + } else { v = GenerateRnd(100); - if (v < 1000 * (monst->_mint + 5) - || ((monst->_mVar1 == MM_WALK || monst->_mVar1 == MM_WALK2 || monst->_mVar1 == MM_WALK3) && monst->_mVar2 == 0 && v < 1000 * (monst->_mint + 8))) { + if (v < 2 * monst->_mint + 33 + || ((monst->_mVar1 == MM_WALK || monst->_mVar1 == MM_WALK2 || monst->_mVar1 == MM_WALK3) && monst->_mVar2 == 0 && v < 2 * monst->_mint + 83)) { M_CallWalk(i, md); + } else { + M_StartDelay(i, GenerateRnd(10) + 10); } - } else if (v < 1000 * (monst->_mint + 6)) { - monst->_mdir = md; - M_StartAttack(i); } } - if (monst->_mmode == MM_STAND) { - M_StartDelay(i, GenerateRnd(10) + 5); + + monst->CheckStandAnimationIsLoaded(monst->_mdir); +} + +void MAI_Lich(int i) +{ + MAI_Ranged(i, MIS_LICH, false); +} + +void MAI_ArchLich(int i) +{ + MAI_Ranged(i, MIS_ARCHLICH, false); +} + +void MAI_Psychorb(int i) +{ + MAI_Ranged(i, MIS_PSYCHORB, false); +} + +void MAI_Necromorb(int i) +{ + MAI_Ranged(i, MIS_NECROMORB, false); +} + +void MAI_BoneDemon(int i) +{ + MAI_RoundRanged(i, MIS_BONEDEMON, true, 4, 0); +} + +const char *GetMonsterTypeText(const MonsterDataStruct &monsterData) +{ + switch (monsterData.mMonstClass) { + case MC_ANIMAL: + return _("Animal"); + case MC_DEMON: + return _("Demon"); + case MC_UNDEAD: + return _("Undead"); } + + app_fatal("Unknown mMonstClass %i", monsterData.mMonstClass); } -void MAI_Magma(int i) -{ - MAI_RoundRanged(i, MIS_MAGMABALL, true, 4, 0); -} +void ActivateSpawn(int i, int x, int y, Direction dir) +{ + dMonster[x][y] = i + 1; + Monsters[i].position.tile = { x, y }; + Monsters[i].position.future = { x, y }; + Monsters[i].position.old = { x, y }; + M_StartSpStand(i, dir); +} + +/** Maps from monster AI ID to monster AI function. */ +void (*AiProc[])(int i) = { + &MAI_Zombie, + &MAI_Fat, + &MAI_SkelSd, + &MAI_SkelBow, + &MAI_Scav, + &MAI_Rhino, + &MAI_GoatMc, + &MAI_GoatBow, + &MAI_Fallen, + &MAI_Magma, + &MAI_SkelKing, + &MAI_Bat, + &MAI_Garg, + &MAI_Cleaver, + &MAI_Succ, + &MAI_Sneak, + &MAI_Storm, + &MAI_Fireman, + &MAI_Garbud, + &MAI_Acid, + &MAI_AcidUniq, + &MAI_Golum, + &MAI_Zhar, + &MAI_SnotSpil, + &MAI_Snake, + &MAI_Counselor, + &MAI_Mega, + &MAI_Diablo, + &MAI_Lazurus, + &MAI_Lazhelp, + &MAI_Lachdanan, + &MAI_Warlord, + &MAI_Firebat, + &MAI_Torchant, + &MAI_HorkDemon, + &MAI_Lich, + &MAI_ArchLich, + &MAI_Psychorb, + &MAI_Necromorb, + &MAI_BoneDemon +}; + +} // namespace -void MAI_Storm(int i) +void InitLevelMonsters() { - MAI_RoundRanged(i, MIS_LIGHTCTRL2, true, 4, 0); -} + LevelMonsterTypeCount = 0; + monstimgtot = 0; + MissileFileFlag = 0; -void MAI_BoneDemon(int i) -{ - MAI_RoundRanged(i, MIS_BONEDEMON, true, 4, 0); -} + for (auto &levelMonsterType : LevelMonsterTypes) { + levelMonsterType.mPlaceFlags = 0; + } -void MAI_Acid(int i) -{ - MAI_RoundRanged(i, MIS_ACID, false, 4, 1); -} + ClrAllMonsters(); + ActiveMonsterCount = 0; + totalmonsters = MAXMONSTERS; -void MAI_Diablo(int i) -{ - MAI_RoundRanged(i, MIS_DIABAPOCA, false, 40, 0); + for (int i = 0; i < MAXMONSTERS; i++) { + ActiveMonsters[i] = i; + } + + uniquetrans = 0; } -void MAI_RR2(int i, missile_id mistype, int dam) +void GetLevelMTypes() { - assurance((DWORD)i < MAXMONSTERS, i); + // this array is merged with skeltypes down below. + _monster_id typelist[MAXMONSTERS]; + _monster_id skeltypes[NUM_MTYPES]; - MonsterStruct *monst = &Monsters[i]; - int mx = monst->position.tile.x - monst->enemyPosition.x; - int my = monst->position.tile.y - monst->enemyPosition.y; - if (abs(mx) >= 5 || abs(my) >= 5) { - MAI_SkelSd(i); - return; - } + int minl; // min level + int maxl; // max level + char mamask; + const int numskeltypes = 19; - if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { + int nt; // number of types + + if (gbIsSpawn) + mamask = 1; // monster availability mask + else + mamask = 3; // monster availability mask + + AddMonsterType(MT_GOLEM, PLACE_SPECIAL); + if (currlevel == 16) { + AddMonsterType(MT_ADVOCATE, PLACE_SCATTER); + AddMonsterType(MT_RBLACK, PLACE_SCATTER); + AddMonsterType(MT_DIABLO, PLACE_SPECIAL); return; } - int fx = monst->enemyPosition.x; - int fy = monst->enemyPosition.y; - mx = monst->position.tile.x - fx; - my = monst->position.tile.y - fy; - Direction md = GetDirection(monst->position.tile, monst->position.last); - if (monst->_msquelch < UINT8_MAX) - MonstCheckDoors(i); - int v = GenerateRnd(100); - int dist = std::max(abs(mx), abs(my)); - if (dist >= 2 && monst->_msquelch == UINT8_MAX && dTransVal[monst->position.tile.x][monst->position.tile.y] == dTransVal[fx][fy]) { - if (monst->_mgoal == MGOAL_MOVE || dist >= 3) { - if (monst->_mgoal != MGOAL_MOVE) { - monst->_mgoalvar1 = 0; - monst->_mgoalvar2 = GenerateRnd(2); - } - monst->_mgoal = MGOAL_MOVE; - monst->_mgoalvar3 = 4; - if (monst->_mgoalvar1++ < 2 * dist || !DirOK(i, md)) { - if (v < 5 * (monst->_mint + 16)) - M_RoundWalk(i, md, &monst->_mgoalvar2); - } else - monst->_mgoal = MGOAL_NORMAL; - } - } else { - monst->_mgoal = MGOAL_NORMAL; + if (currlevel == 18) + AddMonsterType(MT_HORKSPWN, PLACE_SCATTER); + if (currlevel == 19) { + AddMonsterType(MT_HORKSPWN, PLACE_SCATTER); + AddMonsterType(MT_HORKDMN, PLACE_UNIQUE); } - if (monst->_mgoal == MGOAL_NORMAL) { - if (((dist >= 3 && v < 5 * (monst->_mint + 2)) || v < 5 * (monst->_mint + 1) || monst->_mgoalvar3 == 4) && LineClearMissile(monst->position.tile, { fx, fy })) { - M_StartRSpAttack(i, mistype, dam); - } else if (dist >= 2) { - v = GenerateRnd(100); - if (v < 2 * (5 * monst->_mint + 25) - || ((monst->_mVar1 == MM_WALK || monst->_mVar1 == MM_WALK2 || monst->_mVar1 == MM_WALK3) - && monst->_mVar2 == 0 - && v < 2 * (5 * monst->_mint + 40))) { - M_CallWalk(i, md); - } - } else { - if (GenerateRnd(100) < 10 * (monst->_mint + 4)) { - monst->_mdir = md; - if (GenerateRnd(2) != 0) - M_StartAttack(i); - else - M_StartRSpAttack(i, mistype, dam); + if (currlevel == 20) + AddMonsterType(MT_DEFILER, PLACE_UNIQUE); + if (currlevel == 24) { + AddMonsterType(MT_ARCHLICH, PLACE_SCATTER); + AddMonsterType(MT_NAKRUL, PLACE_SPECIAL); + } + + if (!setlevel) { + if (QuestStatus(Q_BUTCHER)) + AddMonsterType(MT_CLEAVER, PLACE_SPECIAL); + if (QuestStatus(Q_GARBUD)) + AddMonsterType(UniqMonst[UMT_GARBUD].mtype, PLACE_UNIQUE); + if (QuestStatus(Q_ZHAR)) + AddMonsterType(UniqMonst[UMT_ZHAR].mtype, PLACE_UNIQUE); + if (QuestStatus(Q_LTBANNER)) + AddMonsterType(UniqMonst[UMT_SNOTSPIL].mtype, PLACE_UNIQUE); + if (QuestStatus(Q_VEIL)) + AddMonsterType(UniqMonst[UMT_LACHDAN].mtype, PLACE_UNIQUE); + if (QuestStatus(Q_WARLORD)) + AddMonsterType(UniqMonst[UMT_WARLORD].mtype, PLACE_UNIQUE); + + if (gbIsMultiplayer && currlevel == Quests[Q_SKELKING]._qlevel) { + + AddMonsterType(MT_SKING, PLACE_UNIQUE); + + nt = 0; + for (int i = MT_WSKELAX; i <= MT_WSKELAX + numskeltypes; i++) { + if (IsSkel(i)) { + minl = 15 * MonsterData[i].mMinDLvl / 30 + 1; + maxl = 15 * MonsterData[i].mMaxDLvl / 30 + 1; + + if (currlevel >= minl && currlevel <= maxl) { + if ((MonstAvailTbl[i] & mamask) != 0) { + skeltypes[nt++] = (_monster_id)i; + } + } + } } + AddMonsterType(skeltypes[GenerateRnd(nt)], PLACE_SCATTER); } - monst->_mgoalvar3 = 1; - } - if (monst->_mmode == MM_STAND) { - M_StartDelay(i, GenerateRnd(10) + 5); - } -} -void MAI_Mega(int i) -{ - MAI_RR2(i, MIS_FLAMEC, 0); -} + nt = 0; + for (int i = MT_NZOMBIE; i < NUM_MTYPES; i++) { + minl = 15 * MonsterData[i].mMinDLvl / 30 + 1; + maxl = 15 * MonsterData[i].mMaxDLvl / 30 + 1; -void MAI_Golum(int i) -{ - assurance((DWORD)i < MAXMONSTERS, i); + if (currlevel >= minl && currlevel <= maxl) { + if ((MonstAvailTbl[i] & mamask) != 0) { + typelist[nt++] = (_monster_id)i; + } + } + } - MonsterStruct *monst = &Monsters[i]; - if (monst->position.tile.x == 1 && monst->position.tile.y == 0) { - return; - } +#ifdef _DEBUG + if (monstdebug) { + for (int i = 0; i < debugmonsttypes; i++) + AddMonsterType(DebugMonsters[i], PLACE_SCATTER); + } else +#endif + { - if (monst->_mmode == MM_DEATH - || monst->_mmode == MM_SPSTAND - || (monst->_mmode >= MM_WALK && monst->_mmode <= MM_WALK3)) { - return; - } + while (nt > 0 && LevelMonsterTypeCount < MAX_LVLMTYPES && monstimgtot < 4000) { + for (int i = 0; i < nt;) { + if (MonsterData[typelist[i]].mImage > 4000 - monstimgtot) { + typelist[i] = typelist[--nt]; + continue; + } - if ((monst->_mFlags & MFLAG_TARGETS_MONSTER) == 0) - M_Enemy(i); + i++; + } - bool haveEnemy = (Monsters[i]._mFlags & MFLAG_NO_ENEMY) == 0; + if (nt != 0) { + int i = GenerateRnd(nt); + AddMonsterType(typelist[i], PLACE_SCATTER); + typelist[i] = typelist[--nt]; + } + } + } - if (monst->_mmode == MM_ATTACK) { - return; + } else { + if (setlvlnum == SL_SKELKING) { + AddMonsterType(MT_SKING, PLACE_UNIQUE); + } } +} - int menemy = Monsters[i]._menemy; +void InitMonsterGFX(int monst) +{ + int mtype = LevelMonsterTypes[monst].mtype; + int width = MonsterData[mtype].width; - int mex = Monsters[i].position.tile.x - Monsters[menemy].position.future.x; - int mey = Monsters[i].position.tile.y - Monsters[menemy].position.future.y; - Direction md = GetDirection(Monsters[i].position.tile, Monsters[menemy].position.tile); - Monsters[i]._mdir = md; - if (abs(mex) < 2 && abs(mey) < 2 && haveEnemy) { - menemy = Monsters[i]._menemy; - Monsters[i].enemyPosition = Monsters[menemy].position.tile; - if (Monsters[menemy]._msquelch == 0) { - Monsters[menemy]._msquelch = UINT8_MAX; - Monsters[Monsters[i]._menemy].position.last = Monsters[i].position.tile; - for (int j = 0; j < 5; j++) { - for (int k = 0; k < 5; k++) { - menemy = dMonster[Monsters[i].position.tile.x + k - 2][Monsters[i].position.tile.y + j - 2]; // BUGFIX: Check if indexes are between 0 and 112 - if (menemy > 0) - Monsters[menemy - 1]._msquelch = UINT8_MAX; // BUGFIX: should be `Monsters[_menemy-1]`, not Monsters[_menemy]. (fixed) + for (int anim = 0; anim < 6; anim++) { + int frames = MonsterData[mtype].Frames[anim]; + + if ((animletter[anim] != 's' || MonsterData[mtype].has_special) && frames > 0) { + char strBuff[256]; + sprintf(strBuff, MonsterData[mtype].GraphicType, animletter[anim]); + + byte *celBuf; + { + auto celData = LoadFileInMem(strBuff); + celBuf = celData.get(); + LevelMonsterTypes[monst].Anims[anim].CMem = std::move(celData); + } + + if (LevelMonsterTypes[monst].mtype != MT_GOLEM || (animletter[anim] != 's' && animletter[anim] != 'd')) { + for (int i = 0; i < 8; i++) { + byte *pCelStart = CelGetFrame(celBuf, i); + LevelMonsterTypes[monst].Anims[anim].CelSpritesForDirections[i].emplace(pCelStart, width); + } + } else { + for (int i = 0; i < 8; i++) { + LevelMonsterTypes[monst].Anims[anim].CelSpritesForDirections[i].emplace(celBuf, width); } } } - M_StartAttack(i); - return; - } - if (haveEnemy && MAI_Path(i)) - return; + LevelMonsterTypes[monst].Anims[anim].Frames = frames; + LevelMonsterTypes[monst].Anims[anim].Rate = MonsterData[mtype].Rate[anim]; + } - Monsters[i]._pathcount++; - if (Monsters[i]._pathcount > 8) - Monsters[i]._pathcount = 5; + LevelMonsterTypes[monst].mMinHP = MonsterData[mtype].mMinHP; + LevelMonsterTypes[monst].mMaxHP = MonsterData[mtype].mMaxHP; + if (!gbIsHellfire && mtype == MT_DIABLO) { + LevelMonsterTypes[monst].mMinHP -= 2000; + LevelMonsterTypes[monst].mMaxHP -= 2000; + } + LevelMonsterTypes[monst].mAFNum = MonsterData[mtype].mAFNum; + LevelMonsterTypes[monst].MData = &MonsterData[mtype]; - bool ok = M_CallWalk(i, Players[i]._pdir); - if (ok) - return; + if (MonsterData[mtype].has_trans) { + InitMonsterTRN(LevelMonsterTypes[monst]); + } - md = left[md]; - for (int j = 0; j < 8 && !ok; j++) { - md = right[md]; - ok = DirOK(i, md); + if (mtype >= MT_NMAGMA && mtype <= MT_WMAGMA && (MissileFileFlag & 1) == 0) { + MissileFileFlag |= 1; + LoadMissileGFX(MFILE_MAGBALL); + } + if (mtype >= MT_STORM && mtype <= MT_MAEL && (MissileFileFlag & 2) == 0) { + MissileFileFlag |= 2; + LoadMissileGFX(MFILE_THINLGHT); + } + if (mtype == MT_SUCCUBUS && (MissileFileFlag & 4) == 0) { + MissileFileFlag |= 4; + LoadMissileGFX(MFILE_FLARE); + LoadMissileGFX(MFILE_FLAREEXP); + } + if (mtype >= MT_INCIN && mtype <= MT_HELLBURN && (MissileFileFlag & 8) == 0) { + MissileFileFlag |= 8; + LoadMissileGFX(MFILE_KRULL); + } + if (mtype == MT_SNOWWICH && (MissileFileFlag & 0x20) == 0) { + MissileFileFlag |= 0x20; + LoadMissileGFX(MFILE_SCUBMISB); + LoadMissileGFX(MFILE_SCBSEXPB); + } + if (mtype == MT_HLSPWN && (MissileFileFlag & 0x40) == 0) { + MissileFileFlag |= 0x40; + LoadMissileGFX(MFILE_SCUBMISD); + LoadMissileGFX(MFILE_SCBSEXPD); + } + if (mtype == MT_SOLBRNR && (MissileFileFlag & 0x80) == 0) { + MissileFileFlag |= 0x80; + LoadMissileGFX(MFILE_SCUBMISC); + LoadMissileGFX(MFILE_SCBSEXPC); + } + if (mtype >= MT_INCIN && mtype <= MT_HELLBURN && (MissileFileFlag & 8) == 0) { + MissileFileFlag |= 8; + LoadMissileGFX(MFILE_KRULL); + } + if (((mtype >= MT_NACID && mtype <= MT_XACID) || mtype == MT_SPIDLORD) && (MissileFileFlag & 0x10) == 0) { + MissileFileFlag |= 0x10; + LoadMissileGFX(MFILE_ACIDBF); + LoadMissileGFX(MFILE_ACIDSPLA); + LoadMissileGFX(MFILE_ACIDPUD); + } + if (mtype == MT_LICH && (MissileFileFlag & 0x100) == 0) { + MissileFileFlag |= 0x100; + LoadMissileGFX(MFILE_LICH); + LoadMissileGFX(MFILE_EXORA1); + } + if (mtype == MT_ARCHLICH && (MissileFileFlag & 0x200) == 0) { + MissileFileFlag |= 0x200; + LoadMissileGFX(MFILE_ARCHLICH); + LoadMissileGFX(MFILE_EXYEL2); + } + if ((mtype == MT_PSYCHORB || mtype == MT_BONEDEMN) && (MissileFileFlag & 0x400) == 0) { + MissileFileFlag |= 0x400; + LoadMissileGFX(MFILE_BONEDEMON); + } + if (mtype == MT_NECRMORB && (MissileFileFlag & 0x800) == 0) { + MissileFileFlag |= 0x800; + LoadMissileGFX(MFILE_NECROMORB); + LoadMissileGFX(MFILE_EXRED3); + } + if (mtype == MT_PSYCHORB && (MissileFileFlag & 0x1000) == 0) { + MissileFileFlag |= 0x1000; + LoadMissileGFX(MFILE_EXBL2); + } + if (mtype == MT_BONEDEMN && (MissileFileFlag & 0x2000) == 0) { + MissileFileFlag |= 0x2000; + LoadMissileGFX(MFILE_EXBL3); + } + if (mtype == MT_DIABLO) { + LoadMissileGFX(MFILE_FIREPLAR); } - if (ok) - M_WalkDir(i, md); } -void MAI_SkelKing(int i) +void monster_some_crypt() { - assurance((DWORD)i < MAXMONSTERS, i); - MonsterStruct *monst = &Monsters[i]; - if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { - return; - } + MonsterStruct *mon; + int hp; - int fx = monst->enemyPosition.x; - int fy = monst->enemyPosition.y; - int mx = monst->position.tile.x - fx; - int my = monst->position.tile.y - fy; - Direction md = GetDirection(monst->position.tile, monst->position.last); - if (monst->_msquelch < UINT8_MAX) - MonstCheckDoors(i); - int v = GenerateRnd(100); - int dist = std::max(abs(mx), abs(my)); - if (dist >= 2 && monst->_msquelch == UINT8_MAX && dTransVal[monst->position.tile.x][monst->position.tile.y] == dTransVal[fx][fy]) { - if (monst->_mgoal == MGOAL_MOVE || ((abs(mx) >= 3 || abs(my) >= 3) && GenerateRnd(4) == 0)) { - if (monst->_mgoal != MGOAL_MOVE) { - monst->_mgoalvar1 = 0; - monst->_mgoalvar2 = GenerateRnd(2); - } - monst->_mgoal = MGOAL_MOVE; - if ((monst->_mgoalvar1++ >= 2 * dist && DirOK(i, md)) || dTransVal[monst->position.tile.x][monst->position.tile.y] != dTransVal[fx][fy]) { - monst->_mgoal = MGOAL_NORMAL; - } else if (!M_RoundWalk(i, md, &monst->_mgoalvar2)) { - M_StartDelay(i, GenerateRnd(10) + 10); - } - } - } else { - monst->_mgoal = MGOAL_NORMAL; - } - if (monst->_mgoal == MGOAL_NORMAL) { - if (!gbIsMultiplayer - && ((dist >= 3 && v < 4 * monst->_mint + 35) || v < 6) - && LineClearMissile(monst->position.tile, { fx, fy })) { - Point newPosition = monst->position.tile + md; - if (PosOkMonst(i, newPosition) && ActiveMonsterCount < MAXMONSTERS) { - M_SpawnSkel(newPosition, md); - M_StartSpStand(i, md); - } - } else { - if (dist >= 2) { - v = GenerateRnd(100); - if (v >= monst->_mint + 25 - && ((monst->_mVar1 != MM_WALK && monst->_mVar1 != MM_WALK2 && monst->_mVar1 != MM_WALK3) || monst->_mVar2 != 0 || (v >= monst->_mint + 75))) { - M_StartDelay(i, GenerateRnd(10) + 10); - } else { - M_CallWalk(i, md); - } - } else if (v < monst->_mint + 20) { - monst->_mdir = md; - M_StartAttack(i); - } - } + if (currlevel == 24 && UberDiabloMonsterIndex >= 0 && UberDiabloMonsterIndex < ActiveMonsterCount) { + mon = &Monsters[UberDiabloMonsterIndex]; + PlayEffect(UberDiabloMonsterIndex, 2); + Quests[Q_NAKRUL]._qlog = false; + mon->mArmorClass -= 50; + hp = mon->_mmaxhp / 2; + mon->mMagicRes = 0; + mon->_mhitpoints = hp; + mon->_mmaxhp = hp; } - - monst->CheckStandAnimationIsLoaded(md); } -void MAI_Rhino(int i) +void InitMonsters() { - assurance((DWORD)i < MAXMONSTERS, i); - MonsterStruct *monst = &Monsters[i]; - if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { - return; +#ifdef _DEBUG + if (gbIsMultiplayer) + CheckDungeonClear(); +#endif + if (!setlevel) { + AddMonster({ 1, 0 }, DIR_S, 0, false); + AddMonster({ 1, 0 }, DIR_S, 0, false); + AddMonster({ 1, 0 }, DIR_S, 0, false); + AddMonster({ 1, 0 }, DIR_S, 0, false); } - int fx = monst->enemyPosition.x; - int fy = monst->enemyPosition.y; - int mx = monst->position.tile.x - fx; - int my = monst->position.tile.y - fy; - Direction md = GetDirection(monst->position.tile, monst->position.last); - if (monst->_msquelch < UINT8_MAX) - MonstCheckDoors(i); - int v = GenerateRnd(100); - int dist = std::max(abs(mx), abs(my)); - if (dist >= 2) { - if (monst->_mgoal == MGOAL_MOVE || (dist >= 5 && GenerateRnd(4) != 0)) { - if (monst->_mgoal != MGOAL_MOVE) { - monst->_mgoalvar1 = 0; - monst->_mgoalvar2 = GenerateRnd(2); + if (!gbIsSpawn && !setlevel && currlevel == 16) + LoadDiabMonsts(); + + int nt = numtrigs; + if (currlevel == 15) + nt = 1; + for (int i = 0; i < nt; i++) { + for (int s = -2; s < 2; s++) { + for (int t = -2; t < 2; t++) + DoVision(trigs[i].position + Displacement { s, t }, 15, false, false); + } + } + if (!gbIsSpawn) + PlaceQuestMonsters(); + if (!setlevel) { + if (!gbIsSpawn) + PlaceUniques(); + int na = 0; + for (int s = 16; s < 96; s++) { + for (int t = 16; t < 96; t++) { + if (!SolidLoc({ s, t })) + na++; } - monst->_mgoal = MGOAL_MOVE; - if (monst->_mgoalvar1++ >= 2 * dist || dTransVal[monst->position.tile.x][monst->position.tile.y] != dTransVal[fx][fy]) { - monst->_mgoal = MGOAL_NORMAL; - } else if (!M_RoundWalk(i, md, &monst->_mgoalvar2)) { - M_StartDelay(i, GenerateRnd(10) + 10); + } + int numplacemonsters = na / 30; + if (gbIsMultiplayer) + numplacemonsters += numplacemonsters / 2; + if (ActiveMonsterCount + numplacemonsters > MAXMONSTERS - 10) + numplacemonsters = MAXMONSTERS - 10 - ActiveMonsterCount; + totalmonsters = ActiveMonsterCount + numplacemonsters; + int numscattypes = 0; + int scattertypes[NUM_MTYPES]; + for (int i = 0; i < LevelMonsterTypeCount; i++) { + if ((LevelMonsterTypes[i].mPlaceFlags & PLACE_SCATTER) != 0) { + scattertypes[numscattypes] = i; + numscattypes++; } } - } else { - monst->_mgoal = MGOAL_NORMAL; + while (ActiveMonsterCount < totalmonsters) { + int mtype = scattertypes[GenerateRnd(numscattypes)]; + if (currlevel == 1 || GenerateRnd(2) == 0) + na = 1; + else if (currlevel == 2 || (currlevel >= 21 && currlevel <= 24)) + na = GenerateRnd(2) + 2; + else + na = GenerateRnd(3) + 3; + PlaceGroup(mtype, na, 0, 0); + } } - if (monst->_mgoal == MGOAL_NORMAL) { - if (dist >= 5 - && v < 2 * monst->_mint + 43 - && LineClear(PosOkMonst, i, monst->position.tile, { fx, fy })) { - if (AddMissile(monst->position.tile, { fx, fy }, md, MIS_RHINO, monst->_menemy, i, 0, 0) != -1) { - if (monst->MData->snd_special) - PlayEffect(i, 3); - dMonster[monst->position.tile.x][monst->position.tile.y] = -(i + 1); - monst->_mmode = MM_CHARGE; - } - } else { - if (dist >= 2) { - v = GenerateRnd(100); - if (v >= 2 * monst->_mint + 33 - && ((monst->_mVar1 != MM_WALK && monst->_mVar1 != MM_WALK2 && monst->_mVar1 != MM_WALK3) - || monst->_mVar2 != 0 - || v >= 2 * monst->_mint + 83)) { - M_StartDelay(i, GenerateRnd(10) + 10); - } else { - M_CallWalk(i, md); - } - } else if (v < 2 * monst->_mint + 28) { - monst->_mdir = md; - M_StartAttack(i); - } + for (int i = 0; i < nt; i++) { + for (int s = -2; s < 2; s++) { + for (int t = -2; t < 2; t++) + DoUnVision(trigs[i].position + Displacement { s, t }, 15); } } - - monst->CheckStandAnimationIsLoaded(monst->_mdir); } -void MAI_HorkDemon(int i) +void SetMapMonsters(const uint16_t *dunData, Point startPosition) { - if ((DWORD)i >= MAXMONSTERS) { - return; + AddMonsterType(MT_GOLEM, PLACE_SPECIAL); + AddMonster({ 1, 0 }, DIR_S, 0, false); + AddMonster({ 1, 0 }, DIR_S, 0, false); + AddMonster({ 1, 0 }, DIR_S, 0, false); + AddMonster({ 1, 0 }, DIR_S, 0, false); + if (setlevel && setlvlnum == SL_VILEBETRAYER) { + AddMonsterType(UniqMonst[UMT_LAZURUS].mtype, PLACE_UNIQUE); + AddMonsterType(UniqMonst[UMT_RED_VEX].mtype, PLACE_UNIQUE); + AddMonsterType(UniqMonst[UMT_BLACKJADE].mtype, PLACE_UNIQUE); + PlaceUniqueMonst(UMT_LAZURUS, 0, 0); + PlaceUniqueMonst(UMT_RED_VEX, 0, 0); + PlaceUniqueMonst(UMT_BLACKJADE, 0, 0); } - MonsterStruct *monst = &Monsters[i]; - if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { - return; - } + int width = SDL_SwapLE16(dunData[0]); + int height = SDL_SwapLE16(dunData[1]); - int fx = monst->enemyPosition.x; - int fy = monst->enemyPosition.y; - int mx = monst->position.tile.x - fx; - int my = monst->position.tile.y - fy; - Direction md = GetDirection(monst->position.tile, monst->position.last); + int layer2Offset = 2 + width * height; - if (monst->_msquelch < 255) { - MonstCheckDoors(i); - } + // The rest of the layers are at dPiece scale + width *= 2; + height *= 2; - int v = GenerateRnd(100); + const uint16_t *monsterLayer = &dunData[layer2Offset + width * height]; - if (abs(mx) < 2 && abs(my) < 2) { - monst->_mgoal = MGOAL_NORMAL; - } else if (monst->_mgoal == 4 || ((abs(mx) >= 5 || abs(my) >= 5) && GenerateRnd(4) != 0)) { - if (monst->_mgoal != 4) { - monst->_mgoalvar1 = 0; - monst->_mgoalvar2 = GenerateRnd(2); - } - monst->_mgoal = MGOAL_MOVE; - int dist = std::max(abs(mx), abs(my)); - if (monst->_mgoalvar1++ >= 2 * dist || dTransVal[monst->position.tile.x][monst->position.tile.y] != dTransVal[fx][fy]) { - monst->_mgoal = MGOAL_NORMAL; - } else if (!M_RoundWalk(i, md, &monst->_mgoalvar2)) { - M_StartDelay(i, GenerateRnd(10) + 10); + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + uint8_t monsterId = SDL_SwapLE16(monsterLayer[j * width + i]); + if (monsterId != 0) { + int mtype = AddMonsterType(MonstConvTbl[monsterId - 1], PLACE_SPECIAL); + PlaceMonster(ActiveMonsterCount++, mtype, i + startPosition.x + 16, j + startPosition.y + 16); + } } } +} - if (monst->_mgoal == 1) { - if ((abs(mx) >= 3 || abs(my) >= 3) && v < 2 * monst->_mint + 43) { - Point position = monst->position.tile + monst->_mdir; - if (PosOkMonst(i, position) && ActiveMonsterCount < MAXMONSTERS) { - M_StartRSpAttack(i, MIS_HORKDMN, 0); - } - } else if (abs(mx) < 2 && abs(my) < 2) { - if (v < 2 * monst->_mint + 28) { - monst->_mdir = md; - M_StartAttack(i); - } - } else { - v = GenerateRnd(100); - if (v < 2 * monst->_mint + 33 - || ((monst->_mVar1 == MM_WALK || monst->_mVar1 == MM_WALK2 || monst->_mVar1 == MM_WALK3) && monst->_mVar2 == 0 && v < 2 * monst->_mint + 83)) { - M_CallWalk(i, md); - } else { - M_StartDelay(i, GenerateRnd(10) + 10); - } - } +int AddMonster(Point position, Direction dir, int mtype, bool inMap) +{ + if (ActiveMonsterCount < MAXMONSTERS) { + int i = ActiveMonsters[ActiveMonsterCount++]; + if (inMap) + dMonster[position.x][position.y] = i + 1; + InitMonster(i, dir, mtype, position); + return i; } - monst->CheckStandAnimationIsLoaded(monst->_mdir); + return -1; } -void MAI_Counselor(int i) +void AddDoppelganger(MonsterStruct &monster) { - assurance((DWORD)i < MAXMONSTERS, i); - - MonsterStruct *monst = &Monsters[i]; - if (monst->_mmode != MM_STAND || monst->_msquelch == 0) { + if (monster.MType == nullptr) { return; } - int fx = monst->enemyPosition.x; - int fy = monst->enemyPosition.y; - int mx = monst->position.tile.x - fx; - int my = monst->position.tile.y - fy; - Direction md = GetDirection(monst->position.tile, monst->position.last); - if (monst->_msquelch < UINT8_MAX) - MonstCheckDoors(i); - int v = GenerateRnd(100); - if (monst->_mgoal == MGOAL_RETREAT) { - if (monst->_mgoalvar1++ <= 3) - M_CallWalk(i, opposite[md]); - else { - monst->_mgoal = MGOAL_NORMAL; - M_StartFadein(i, md, true); - } - } else if (monst->_mgoal == MGOAL_MOVE) { - int dist = std::max(abs(mx), abs(my)); - if (dist >= 2 && monst->_msquelch == UINT8_MAX && dTransVal[monst->position.tile.x][monst->position.tile.y] == dTransVal[fx][fy]) { - if (monst->_mgoalvar1++ < 2 * dist || !DirOK(i, md)) { - M_RoundWalk(i, md, &monst->_mgoalvar2); - } else { - monst->_mgoal = MGOAL_NORMAL; - M_StartFadein(i, md, true); + + Point target = { 0, 0 }; + for (int d = 0; d < 8; d++) { + const Point position = monster.position.tile + static_cast(d); + if (!SolidLoc(position)) { + if (dPlayer[position.x][position.y] == 0 && dMonster[position.x][position.y] == 0) { + if (dObject[position.x][position.y] == 0) { + target = position; + break; + } + int oi = dObject[position.x][position.y] > 0 ? dObject[position.x][position.y] - 1 : -(dObject[position.x][position.y] + 1); + if (!Objects[oi]._oSolidFlag) { + target = position; + break; + } } } - } else if (monst->_mgoal == MGOAL_NORMAL) { - if (abs(mx) >= 2 || abs(my) >= 2) { - if (v < 5 * (monst->_mint + 10) && LineClearMissile(monst->position.tile, { fx, fy })) { - constexpr missile_id MissileTypes[4] = { MIS_FIREBOLT, MIS_CBOLT, MIS_LIGHTCTRL, MIS_FIREBALL }; - M_StartRAttack(i, MissileTypes[monst->_mint], monst->mMinDamage + GenerateRnd(monst->mMaxDamage - monst->mMinDamage + 1)); - } else if (GenerateRnd(100) < 30) { - monst->_mgoal = MGOAL_MOVE; - monst->_mgoalvar1 = 0; - M_StartFadeout(i, md, false); - } else - M_StartDelay(i, GenerateRnd(10) + 2 * (5 - monst->_mint)); - } else { - monst->_mdir = md; - if (monst->_mhitpoints < (monst->_mmaxhp / 2)) { - monst->_mgoal = MGOAL_RETREAT; - monst->_mgoalvar1 = 0; - M_StartFadeout(i, md, false); - } else if (monst->_mVar1 == MM_DELAY - || GenerateRnd(100) < 2 * monst->_mint + 20) { - M_StartRAttack(i, MIS_NULL, 0); - AddMissile(monst->position.tile, { 0, 0 }, monst->_mdir, MIS_FLASH, TARGET_PLAYERS, i, 4, 0); - AddMissile(monst->position.tile, { 0, 0 }, monst->_mdir, MIS_FLASH2, TARGET_PLAYERS, i, 4, 0); - } else - M_StartDelay(i, GenerateRnd(10) + 2 * (5 - monst->_mint)); - } } - if (monst->_mmode == MM_STAND) { - M_StartDelay(i, GenerateRnd(10) + 5); + if (target != Point { 0, 0 }) { + for (int j = 0; j < MAX_LVLMTYPES; j++) { + if (LevelMonsterTypes[j].mtype == monster.MType->mtype) { + AddMonster(target, monster._mdir, j, true); + break; + } + } } } -void MAI_Garbud(int i) +bool M_Talker(int i) { - assurance((DWORD)i < MAXMONSTERS, i); + return IsAnyOf(Monsters[i]._mAi, AI_LAZURUS, AI_WARLORD, AI_GARBUD, AI_ZHAR, AI_SNOTSPIL, AI_LACHDAN, AI_LAZHELP); +} + +void M_StartStand(int i, Direction md) +{ + ClearMVars(i); + if (Monsters[i].MType->mtype == MT_GOLEM) + NewMonsterAnim(Monsters[i], MonsterGraphic::Walk, md); + else + NewMonsterAnim(Monsters[i], MonsterGraphic::Stand, md); + Monsters[i]._mVar1 = Monsters[i]._mmode; + Monsters[i]._mVar2 = 0; + Monsters[i]._mmode = MM_STAND; + Monsters[i].position.offset = { 0, 0 }; + Monsters[i].position.future = Monsters[i].position.tile; + Monsters[i].position.old = Monsters[i].position.tile; + M_Enemy(i); +} + +void M_ClearSquares(int i) +{ + int mx = Monsters[i].position.old.x; + int my = Monsters[i].position.old.y; + int m1 = -(i + 1); + int m2 = i + 1; + + for (int y = my - 1; y <= my + 1; y++) { + if (y >= 0 && y < MAXDUNY) { + for (int x = mx - 1; x <= mx + 1; x++) { + if (x >= 0 && x < MAXDUNX && (dMonster[x][y] == m1 || dMonster[x][y] == m2)) + dMonster[x][y] = 0; + } + } + } + + if (mx + 1 < MAXDUNX) + dFlags[mx + 1][my] &= ~BFLAG_MONSTLR; + if (my + 1 < MAXDUNY) + dFlags[mx][my + 1] &= ~BFLAG_MONSTLR; +} - MonsterStruct *monst = &Monsters[i]; - if (monst->_mmode != MM_STAND) { +void M_GetKnockback(int i) +{ + Direction d = opposite[Monsters[i]._mdir]; + if (!DirOK(i, d)) { return; } - int mx = monst->position.tile.x; - int my = monst->position.tile.y; - Direction md = M_GetDir(i); + M_ClearSquares(i); + Monsters[i].position.old += d; + StartMonsterGotHit(i); +} - if (monst->mtalkmsg >= TEXT_GARBUD1 - && monst->mtalkmsg <= TEXT_GARBUD3 - && (dFlags[mx][my] & BFLAG_VISIBLE) == 0 - && monst->_mgoal == MGOAL_TALKING) { - monst->_mgoal = MGOAL_INQUIRING; - switch (monst->mtalkmsg) { - case TEXT_GARBUD1: - monst->mtalkmsg = TEXT_GARBUD2; - break; - case TEXT_GARBUD2: - monst->mtalkmsg = TEXT_GARBUD3; - break; - case TEXT_GARBUD3: - monst->mtalkmsg = TEXT_GARBUD4; - break; - default: - break; +void M_StartHit(int i, int pnum, int dam) +{ + if (pnum >= 0) + Monsters[i].mWhoHit |= 1 << pnum; + if (pnum == MyPlayerId) { + delta_monster_hp(i, Monsters[i]._mhitpoints, currlevel); + NetSendCmdMonDmg(false, i, dam); + } + PlayEffect(i, 1); + if ((Monsters[i].MType->mtype >= MT_SNEAK && Monsters[i].MType->mtype <= MT_ILLWEAV) || dam >> 6 >= Monsters[i].mLevel + 3) { + if (pnum >= 0) { + Monsters[i]._menemy = pnum; + Monsters[i].enemyPosition = Players[pnum].position.future; + Monsters[i]._mFlags &= ~MFLAG_TARGETS_MONSTER; + Monsters[i]._mdir = M_GetDir(i); + } + if (Monsters[i].MType->mtype == MT_BLINK) { + M_Teleport(i); + } else if ((Monsters[i].MType->mtype >= MT_NSCAV && Monsters[i].MType->mtype <= MT_YSCAV) + || Monsters[i].MType->mtype == MT_GRAVEDIG) { + Monsters[i]._mgoal = MGOAL_NORMAL; + Monsters[i]._mgoalvar1 = 0; + Monsters[i]._mgoalvar2 = 0; + } + if (Monsters[i]._mmode != MM_STONE) { + StartMonsterGotHit(i); } } +} - if ((dFlags[mx][my] & BFLAG_VISIBLE) != 0) { - if (monst->mtalkmsg == TEXT_GARBUD4) { - if (!effect_is_playing(USFX_GARBUD4) && monst->_mgoal == MGOAL_TALKING) { - monst->_mgoal = MGOAL_NORMAL; - monst->_msquelch = UINT8_MAX; - monst->mtalkmsg = TEXT_NONE; - } +void M_StartKill(int i, int pnum) +{ + assurance((DWORD)i < MAXMONSTERS, i); + + if (MyPlayerId == pnum) { + delta_kill_monster(i, Monsters[i].position.tile, currlevel); + if (i != pnum) { + NetSendCmdLocParam1(false, CMD_MONSTDEATH, Monsters[i].position.tile, i); + } else { + NetSendCmdLocParam1(false, CMD_KILLGOLEM, Monsters[i].position.tile, currlevel); } } - if (monst->_mgoal == MGOAL_NORMAL || monst->_mgoal == MGOAL_MOVE) - MAI_Round(i, true); - - monst->CheckStandAnimationIsLoaded(md); + MonstStartKill(i, pnum, true); } -void MAI_Zhar(int i) +void M_SyncStartKill(int i, Point position, int pnum) { assurance((DWORD)i < MAXMONSTERS, i); - MonsterStruct *monst = &Monsters[i]; - if (Monsters[i]._mmode != MM_STAND) { + if (Monsters[i]._mhitpoints > 0 || Monsters[i]._mmode == MM_DEATH) { return; } - int mx = monst->position.tile.x; - int my = monst->position.tile.y; - Direction md = M_GetDir(i); - if (monst->mtalkmsg == TEXT_ZHAR1 && (dFlags[mx][my] & BFLAG_VISIBLE) == 0 && monst->_mgoal == MGOAL_TALKING) { - monst->mtalkmsg = TEXT_ZHAR2; - monst->_mgoal = MGOAL_INQUIRING; + if (dMonster[position.x][position.y] == 0) { + M_ClearSquares(i); + Monsters[i].position.tile = position; + Monsters[i].position.old = position; } - if ((dFlags[mx][my] & BFLAG_VISIBLE) != 0) { - if (monst->mtalkmsg == TEXT_ZHAR2) { - if (!effect_is_playing(USFX_ZHAR2) && monst->_mgoal == MGOAL_TALKING) { - monst->_msquelch = UINT8_MAX; - monst->mtalkmsg = TEXT_NONE; - monst->_mgoal = MGOAL_NORMAL; - } - } + if (Monsters[i]._mmode == MM_STONE) { + MonstStartKill(i, pnum, false); + Monsters[i].Petrify(); + } else { + MonstStartKill(i, pnum, false); } - - if (monst->_mgoal == MGOAL_NORMAL || monst->_mgoal == MGOAL_RETREAT || monst->_mgoal == MGOAL_MOVE) - MAI_Counselor(i); - - monst->CheckStandAnimationIsLoaded(md); } -void MAI_SnotSpil(int i) +void M_UpdateLeader(int i) { assurance((DWORD)i < MAXMONSTERS, i); - MonsterStruct *monst = &Monsters[i]; - if (Monsters[i]._mmode != MM_STAND) { - return; + for (int j = 0; j < ActiveMonsterCount; j++) { + int ma = ActiveMonsters[j]; + if (Monsters[ma].leaderflag == 1 && Monsters[ma].leader == i) + Monsters[ma].leaderflag = 0; } - int mx = monst->position.tile.x; - int my = monst->position.tile.y; - Direction md = M_GetDir(i); + if (Monsters[i].leaderflag == 1) { + Monsters[Monsters[i].leader].packsize--; + } +} - if (monst->mtalkmsg == TEXT_BANNER10 && (dFlags[mx][my] & BFLAG_VISIBLE) == 0 && monst->_mgoal == MGOAL_TALKING) { - monst->mtalkmsg = TEXT_BANNER11; - monst->_mgoal = MGOAL_INQUIRING; +void DoEnding() +{ + if (gbIsMultiplayer) { + SNetLeaveGame(LEAVE_ENDING); } - if (monst->mtalkmsg == TEXT_BANNER11 && Quests[Q_LTBANNER]._qvar1 == 3) { - monst->mtalkmsg = TEXT_NONE; - monst->_mgoal = MGOAL_NORMAL; + music_stop(); + + if (gbIsMultiplayer) { + SDL_Delay(1000); } - if ((dFlags[mx][my] & BFLAG_VISIBLE) != 0) { - if (monst->mtalkmsg == TEXT_BANNER12) { - if (!effect_is_playing(USFX_SNOT3) && monst->_mgoal == MGOAL_TALKING) { - ObjChangeMap(setpc_x, setpc_y, setpc_x + setpc_w + 1, setpc_y + setpc_h + 1); - Quests[Q_LTBANNER]._qvar1 = 3; - RedoPlayerVision(); - monst->_msquelch = UINT8_MAX; - monst->mtalkmsg = TEXT_NONE; - monst->_mgoal = MGOAL_NORMAL; - } - } - if (Quests[Q_LTBANNER]._qvar1 == 3) { - if (monst->_mgoal == MGOAL_NORMAL || monst->_mgoal == MGOAL_ATTACK2) - MAI_Fallen(i); - } + if (gbIsSpawn) + return; + + switch (Players[MyPlayerId]._pClass) { + case HeroClass::Sorcerer: + case HeroClass::Monk: + play_movie("gendata\\DiabVic1.smk", false); + break; + case HeroClass::Warrior: + case HeroClass::Barbarian: + play_movie("gendata\\DiabVic2.smk", false); + break; + default: + play_movie("gendata\\DiabVic3.smk", false); + break; } + play_movie("gendata\\Diabend.smk", false); - monst->CheckStandAnimationIsLoaded(md); -} + bool bMusicOn = gbMusicOn; + gbMusicOn = true; -void MAI_Lazurus(int i) -{ - assurance((DWORD)i < MAXMONSTERS, i); + int musicVolume = sound_get_or_set_music_volume(1); + sound_get_or_set_music_volume(0); - MonsterStruct *monst = &Monsters[i]; - if (Monsters[i]._mmode != MM_STAND) { - return; - } + music_start(TMUSIC_L2); + loop_movie = true; + play_movie("gendata\\loopdend.smk", true); + loop_movie = false; + music_stop(); - int mx = monst->position.tile.x; - int my = monst->position.tile.y; - Direction md = M_GetDir(i); - if ((dFlags[mx][my] & BFLAG_VISIBLE) != 0) { - if (!gbIsMultiplayer) { - if (monst->mtalkmsg == TEXT_VILE13 && monst->_mgoal == MGOAL_INQUIRING && Players[MyPlayerId].position.tile.x == 35 && Players[MyPlayerId].position.tile.y == 46) { - PlayInGameMovie("gendata\\fprst3.smk"); - monst->_mmode = MM_TALK; - Quests[Q_BETRAYER]._qvar1 = 5; - } + sound_get_or_set_music_volume(musicVolume); + gbMusicOn = bMusicOn; +} - if (monst->mtalkmsg == TEXT_VILE13 && !effect_is_playing(USFX_LAZ1) && monst->_mgoal == MGOAL_TALKING) { - ObjChangeMapResync(1, 18, 20, 24); - RedoPlayerVision(); - Quests[Q_BETRAYER]._qvar1 = 6; - monst->_mgoal = MGOAL_NORMAL; - monst->_msquelch = UINT8_MAX; - monst->mtalkmsg = TEXT_NONE; - } - } +void PrepDoEnding() +{ + gbSoundOn = sgbSaveSoundOn; + gbRunGame = false; + MyPlayerIsDead = false; + cineflag = true; - if (gbIsMultiplayer && monst->mtalkmsg == TEXT_VILE13 && monst->_mgoal == MGOAL_INQUIRING && Quests[Q_BETRAYER]._qvar1 <= 3) { - monst->_mmode = MM_TALK; + Players[MyPlayerId].pDiabloKillLevel = std::max(Players[MyPlayerId].pDiabloKillLevel, static_cast(sgGameInitInfo.nDifficulty + 1)); + + for (auto &player : Players) { + player._pmode = PM_QUIT; + player._pInvincible = true; + if (gbIsMultiplayer) { + if (player._pHitPoints >> 6 == 0) + player._pHitPoints = 64; + if (player._pMana >> 6 == 0) + player._pMana = 64; } } +} - if (monst->_mgoal == MGOAL_NORMAL || monst->_mgoal == MGOAL_RETREAT || monst->_mgoal == MGOAL_MOVE) { - if (!gbIsMultiplayer && Quests[Q_BETRAYER]._qvar1 == 4 && monst->mtalkmsg == TEXT_NONE) { // Fix save games affected by teleport bug - ObjChangeMapResync(1, 18, 20, 24); - RedoPlayerVision(); - Quests[Q_BETRAYER]._qvar1 = 6; - } - monst->mtalkmsg = TEXT_NONE; - MAI_Counselor(i); +void M_WalkDir(int i, Direction md) +{ + assurance((DWORD)i < MAXMONSTERS, i); + + int mwi = Monsters[i].MType->GetAnimData(MonsterGraphic::Walk).Frames - 1; + switch (md) { + case DIR_N: + M_StartWalk(i, 0, -MWVel[mwi][1], -1, -1, DIR_N); + break; + case DIR_NE: + M_StartWalk(i, MWVel[mwi][1], -MWVel[mwi][0], 0, -1, DIR_NE); + break; + case DIR_E: + M_StartWalk3(i, MWVel[mwi][2], 0, -32, -16, 1, -1, 1, 0, DIR_E); + break; + case DIR_SE: + M_StartWalk2(i, MWVel[mwi][1], MWVel[mwi][0], -32, -16, 1, 0, DIR_SE); + break; + case DIR_S: + M_StartWalk2(i, 0, MWVel[mwi][1], 0, -32, 1, 1, DIR_S); + break; + case DIR_SW: + M_StartWalk2(i, -MWVel[mwi][1], MWVel[mwi][0], 32, -16, 0, 1, DIR_SW); + break; + case DIR_W: + M_StartWalk3(i, -MWVel[mwi][2], 0, 32, -16, -1, 1, 0, 1, DIR_W); + break; + case DIR_NW: + M_StartWalk(i, -MWVel[mwi][1], -MWVel[mwi][0], -1, 0, DIR_NW); + break; + case DIR_OMNI: + break; } - - monst->CheckStandAnimationIsLoaded(md); } -void MAI_Lazhelp(int i) +void MAI_Golum(int i) { assurance((DWORD)i < MAXMONSTERS, i); - if (Monsters[i]._mmode != MM_STAND) - return; MonsterStruct *monst = &Monsters[i]; - int mx = monst->position.tile.x; - int my = monst->position.tile.y; - Direction md = M_GetDir(i); + if (monst->position.tile.x == 1 && monst->position.tile.y == 0) { + return; + } - if ((dFlags[mx][my] & BFLAG_VISIBLE) != 0) { - if (!gbIsMultiplayer) { - if (Quests[Q_BETRAYER]._qvar1 <= 5) { - monst->_mgoal = MGOAL_INQUIRING; - } else { - monst->_mgoal = MGOAL_NORMAL; - monst->mtalkmsg = TEXT_NONE; - } - } else - monst->_mgoal = MGOAL_NORMAL; + if (monst->_mmode == MM_DEATH + || monst->_mmode == MM_SPSTAND + || (monst->_mmode >= MM_WALK && monst->_mmode <= MM_WALK3)) { + return; } - if (monst->_mgoal == MGOAL_NORMAL) - MAI_Succ(i); - monst->CheckStandAnimationIsLoaded(md); -} + if ((monst->_mFlags & MFLAG_TARGETS_MONSTER) == 0) + M_Enemy(i); -void MAI_Lachdanan(int i) -{ - assurance((DWORD)i < MAXMONSTERS, i); + bool haveEnemy = (Monsters[i]._mFlags & MFLAG_NO_ENEMY) == 0; - MonsterStruct *monst = &Monsters[i]; - if (Monsters[i]._mmode != MM_STAND) { + if (monst->_mmode == MM_ATTACK) { return; } - int mx = monst->position.tile.x; - int my = monst->position.tile.y; - Direction md = M_GetDir(i); - - if (monst->mtalkmsg == TEXT_VEIL9 && (dFlags[mx][my] & BFLAG_VISIBLE) == 0 && Monsters[i]._mgoal == MGOAL_TALKING) { - monst->mtalkmsg = TEXT_VEIL10; - Monsters[i]._mgoal = MGOAL_INQUIRING; - } + int menemy = Monsters[i]._menemy; - if ((dFlags[mx][my] & BFLAG_VISIBLE) != 0) { - if (monst->mtalkmsg == TEXT_VEIL11) { - if (!effect_is_playing(USFX_LACH3) && monst->_mgoal == MGOAL_TALKING) { - monst->mtalkmsg = TEXT_NONE; - Quests[Q_VEIL]._qactive = QUEST_DONE; - M_StartKill(i, -1); + int mex = Monsters[i].position.tile.x - Monsters[menemy].position.future.x; + int mey = Monsters[i].position.tile.y - Monsters[menemy].position.future.y; + Direction md = GetDirection(Monsters[i].position.tile, Monsters[menemy].position.tile); + Monsters[i]._mdir = md; + if (abs(mex) < 2 && abs(mey) < 2 && haveEnemy) { + menemy = Monsters[i]._menemy; + Monsters[i].enemyPosition = Monsters[menemy].position.tile; + if (Monsters[menemy]._msquelch == 0) { + Monsters[menemy]._msquelch = UINT8_MAX; + Monsters[Monsters[i]._menemy].position.last = Monsters[i].position.tile; + for (int j = 0; j < 5; j++) { + for (int k = 0; k < 5; k++) { + menemy = dMonster[Monsters[i].position.tile.x + k - 2][Monsters[i].position.tile.y + j - 2]; // BUGFIX: Check if indexes are between 0 and 112 + if (menemy > 0) + Monsters[menemy - 1]._msquelch = UINT8_MAX; // BUGFIX: should be `Monsters[_menemy-1]`, not Monsters[_menemy]. (fixed) + } } } + M_StartAttack(i); + return; } - monst->CheckStandAnimationIsLoaded(md); -} + if (haveEnemy && MAI_Path(i)) + return; -void MAI_Warlord(int i) -{ - assurance((DWORD)i < MAXMONSTERS, i); + Monsters[i]._pathcount++; + if (Monsters[i]._pathcount > 8) + Monsters[i]._pathcount = 5; - MonsterStruct *monst = &Monsters[i]; - if (Monsters[i]._mmode != MM_STAND) { + bool ok = M_CallWalk(i, Players[i]._pdir); + if (ok) return; - } - int mx = monst->position.tile.x; - int my = monst->position.tile.y; - Direction md = M_GetDir(i); - if ((dFlags[mx][my] & BFLAG_VISIBLE) != 0) { - if (monst->mtalkmsg == TEXT_WARLRD9 && monst->_mgoal == MGOAL_INQUIRING) - monst->_mmode = MM_TALK; - if (monst->mtalkmsg == TEXT_WARLRD9 && !effect_is_playing(USFX_WARLRD1) && monst->_mgoal == MGOAL_TALKING) { - monst->_msquelch = UINT8_MAX; - monst->mtalkmsg = TEXT_NONE; - monst->_mgoal = MGOAL_NORMAL; - } + md = left[md]; + for (int j = 0; j < 8 && !ok; j++) { + md = right[md]; + ok = DirOK(i, md); } - - if (monst->_mgoal == MGOAL_NORMAL) - MAI_SkelSd(i); - - monst->CheckStandAnimationIsLoaded(md); + if (ok) + M_WalkDir(i, md); } void DeleteMonsterList() @@ -4511,16 +4642,6 @@ bool PosOkMissile(int /*entity*/, Point position) return !nMissileTable[dPiece[position.x][position.y]] && (dFlags[position.x][position.y] & BFLAG_MONSTLR) == 0; } -bool CheckNoSolid(int /*entity*/, Point position) -{ - return !nSolidTable[dPiece[position.x][position.y]]; -} - -bool LineClearSolid(Point startPoint, Point endPoint) -{ - return LineClear(CheckNoSolid, 0, startPoint, endPoint); -} - bool LineClearMissile(Point startPoint, Point endPoint) { return LineClear(PosOkMissile, 0, startPoint, endPoint); @@ -4694,20 +4815,6 @@ void M_FallenFear(Point position) } } -const char *GetMonsterTypeText(const MonsterDataStruct &monsterData) -{ - switch (monsterData.mMonstClass) { - case MC_ANIMAL: - return _("Animal"); - case MC_DEMON: - return _("Demon"); - case MC_UNDEAD: - return _("Undead"); - } - - app_fatal("Unknown mMonstClass %i", monsterData.mMonstClass); -} - void PrintMonstHistory(int mt) { if (sgOptions.Gameplay.bShowMonsterType) { @@ -4882,74 +4989,6 @@ bool PosOkMonst(int i, Point position) return PosOkMonst2(i, position); } -bool monster_posok(int i, Point position) -{ - int8_t mi = dMissile[position.x][position.y]; - if (mi == 0 || i < 0) { - return true; - } - - bool fire = false; - bool lightning = false; - if (mi > 0) { - if (Missiles[mi - 1]._mitype == MIS_FIREWALL) { // BUGFIX: Change 'mi' to 'mi - 1' (fixed) - fire = true; - } else if (Missiles[mi - 1]._mitype == MIS_LIGHTWALL) { // BUGFIX: Change 'mi' to 'mi - 1' (fixed) - lightning = true; - } - } else { - for (int j = 0; j < ActiveMissileCount; j++) { - mi = ActiveMissiles[j]; - if (Missiles[mi].position.tile == position) { - if (Missiles[mi]._mitype == MIS_FIREWALL) { - fire = true; - break; - } - if (Missiles[mi]._mitype == MIS_LIGHTWALL) { - lightning = true; - break; - } - } - } - } - if (fire && ((Monsters[i].mMagicRes & IMMUNE_FIRE) == 0 || Monsters[i].MType->mtype == MT_DIABLO)) - return false; - if (lightning && ((Monsters[i].mMagicRes & IMMUNE_LIGHTNING) == 0 || Monsters[i].MType->mtype == MT_DIABLO)) - return false; - - return true; -} - -bool PosOkMonst2(int i, Point position) -{ - if (SolidLoc(position)) - return false; - - if (dObject[position.x][position.y] != 0) { - int oi = dObject[position.x][position.y] > 0 ? dObject[position.x][position.y] - 1 : -(dObject[position.x][position.y] + 1); - if (Objects[oi]._oSolidFlag) - return false; - } - - return monster_posok(i, position); -} - -bool PosOkMonst3(int i, Point position) -{ - bool isdoor = false; - if (dObject[position.x][position.y] != 0) { - int oi = dObject[position.x][position.y] > 0 ? dObject[position.x][position.y] - 1 : -(dObject[position.x][position.y] + 1); - isdoor = IsAnyOf(Objects[oi]._otype, OBJ_L1LDOOR, OBJ_L1RDOOR, OBJ_L2LDOOR, OBJ_L2RDOOR, OBJ_L3LDOOR, OBJ_L3RDOOR); - if (Objects[oi]._oSolidFlag && !isdoor) - return false; - } - - if ((SolidLoc(position) && !isdoor) || dPlayer[position.x][position.y] != 0 || dMonster[position.x][position.y] != 0) - return false; - - return monster_posok(i, position); -} - bool IsSkel(int mt) { return (mt >= MT_WSKELAX && mt <= MT_XSKELAX) @@ -4963,45 +5002,6 @@ bool IsGoat(int mt) || (mt >= MT_NGOATBW && mt <= MT_GGOATBW); } -static int AddSkeleton(Point position, Direction dir, bool inMap) -{ - int j = 0; - for (int i = 0; i < LevelMonsterTypeCount; i++) { - if (IsSkel(LevelMonsterTypes[i].mtype)) - j++; - } - - if (j == 0) { - return -1; - } - - int skeltypes = GenerateRnd(j); - int m = 0; - for (int i = 0; m < LevelMonsterTypeCount && i <= skeltypes; m++) { - if (IsSkel(LevelMonsterTypes[m].mtype)) - i++; - } - return AddMonster(position, dir, m - 1, inMap); -} - -int M_SpawnSkel(Point position, Direction dir) -{ - int skel = AddSkeleton(position, dir, true); - if (skel != -1) - M_StartSpStand(skel, dir); - - return skel; -} - -void ActivateSpawn(int i, int x, int y, Direction dir) -{ - dMonster[x][y] = i + 1; - Monsters[i].position.tile = { x, y }; - Monsters[i].position.future = { x, y }; - Monsters[i].position.old = { x, y }; - M_StartSpStand(i, dir); -} - bool SpawnSkeleton(int ii, Point position) { if (ii == -1) diff --git a/Source/monster.h b/Source/monster.h index 286e7a39e..55c14f972 100644 --- a/Source/monster.h +++ b/Source/monster.h @@ -228,13 +228,9 @@ extern bool sgbSaveSoundOn; void InitLevelMonsters(); void GetLevelMTypes(); void InitMonsterGFX(int monst); -void InitMonster(int i, Direction rd, int mtype, Point position); -void ClrAllMonsters(); void monster_some_crypt(); -void PlaceGroup(int mtype, int num, int leaderf, int leader); void InitMonsters(); void SetMapMonsters(const uint16_t *dunData, Point startPosition); -void DeleteMonster(int i); int AddMonster(Point position, Direction dir, int mtype, bool InMap); void AddDoppelganger(MonsterStruct &monster); bool M_Talker(int i); @@ -244,60 +240,16 @@ void M_GetKnockback(int i); void M_StartHit(int i, int pnum, int dam); void M_StartKill(int i, int pnum); void M_SyncStartKill(int i, Point position, int pnum); -void M_Teleport(int i); void M_UpdateLeader(int i); void DoEnding(); void PrepDoEnding(); void M_WalkDir(int i, Direction md); -void MAI_Zombie(int i); -void MAI_SkelSd(int i); -void MAI_Snake(int i); -void MAI_Bat(int i); -void MAI_SkelBow(int i); -void MAI_Fat(int i); -void MAI_Sneak(int i); -void MAI_Fireman(int i); -void MAI_Fallen(int i); -void MAI_Cleaver(int i); -void MAI_Round(int i, bool special); -void MAI_GoatMc(int i); -void MAI_Ranged(int i, missile_id missileType, bool special); -void MAI_GoatBow(int i); -void MAI_Succ(int i); -void MAI_Lich(int i); -void MAI_ArchLich(int i); -void MAI_Psychorb(int i); -void MAI_Necromorb(int i); -void MAI_AcidUniq(int i); -void MAI_Firebat(int i); -void MAI_Torchant(int i); -void MAI_Scav(int i); -void MAI_Garg(int i); -void MAI_RoundRanged(int i, missile_id missileType, bool checkdoors, int dam, int lessmissiles); -void MAI_Magma(int i); -void MAI_Storm(int i); -void MAI_BoneDemon(int i); -void MAI_Acid(int i); -void MAI_Diablo(int i); -void MAI_Mega(int i); void MAI_Golum(int i); -void MAI_SkelKing(int i); -void MAI_Rhino(int i); -void MAI_HorkDemon(int i); -void MAI_Counselor(int i); -void MAI_Garbud(int i); -void MAI_Zhar(int i); -void MAI_SnotSpil(int i); -void MAI_Lazurus(int i); -void MAI_Lazhelp(int i); -void MAI_Lachdanan(int i); -void MAI_Warlord(int i); void DeleteMonsterList(); void ProcessMonsters(); void FreeMonsters(); bool DirOK(int i, Direction mdir); bool PosOkMissile(int entity, Point position); -bool LineClearSolid(Point startPoint, Point endPoint); bool LineClearMissile(Point startPoint, Point endPoint); bool LineClear(bool (*Clear)(int, Point), int entity, Point startPoint, Point endPoint); void SyncMonsterAnim(int i); @@ -306,12 +258,8 @@ void PrintMonstHistory(int mt); void PrintUniqueHistory(); void MissToMonst(int i, Point position); bool PosOkMonst(int i, Point position); -bool monster_posok(int i, Point position); -bool PosOkMonst2(int i, Point position); -bool PosOkMonst3(int i, Point position); bool IsSkel(int mt); bool IsGoat(int mt); -int M_SpawnSkel(Point position, Direction dir); bool SpawnSkeleton(int ii, Point position); int PreSpawnSkeleton(); void TalktoMonster(int i); @@ -321,8 +269,6 @@ bool CheckMonsterHit(int m, bool *ret); int encode_enemy(int m); void decode_enemy(int m, int enemy); -/* data */ - extern Direction left[8]; extern Direction right[8]; extern Direction opposite[8];