/** * @file monster.cpp * * Implementation of monster functionality, AI, actions, spawning, loading, etc. */ #include #include #include "control.h" #include "cursor.h" #include "dead.h" #ifdef _DEBUG #include "debug.h" #endif #include "drlg_l1.h" #include "drlg_l4.h" #include "init.h" #include "lighting.h" #include "minitext.h" #include "missiles.h" #include "movie.h" #include "options.h" #include "sound.h" #include "storm/storm.h" #include "themes.h" #include "towners.h" #include "trigs.h" namespace devilution { #define NIGHTMARE_TO_HIT_BONUS 85 #define HELL_TO_HIT_BONUS 120 #define NIGHTMARE_AC_BONUS 50 #define HELL_AC_BONUS 80 /** Tracks which missile files are already loaded */ int MissileFileFlag; // BUGFIX: replace monstkills[MAXMONSTERS] with monstkills[NUM_MTYPES]. /** Tracks the total number of monsters killed per monster_id. */ int monstkills[MAXMONSTERS]; int monstactive[MAXMONSTERS]; int nummonsters; bool sgbSaveSoundOn; MonsterStruct monster[MAXMONSTERS]; int totalmonsters; CMonster Monsters[MAX_LVLMTYPES]; int monstimgtot; int uniquetrans; int nummtypes; /** Maps from monster intelligence factor to missile type. */ const BYTE counsmiss[4] = { MIS_FIREBOLT, MIS_CBOLT, MIS_LIGHTCTRL, MIS_FIREBALL }; /* data */ /** Maps from monster walk animation frame num to monster velocity. */ int MWVel[24][3] = { { 256, 512, 1024 }, { 128, 256, 512 }, { 85, 170, 341 }, { 64, 128, 256 }, { 51, 102, 204 }, { 42, 85, 170 }, { 36, 73, 146 }, { 32, 64, 128 }, { 28, 56, 113 }, { 26, 51, 102 }, { 23, 46, 93 }, { 21, 42, 85 }, { 19, 39, 78 }, { 18, 36, 73 }, { 17, 34, 68 }, { 16, 32, 64 }, { 15, 30, 60 }, { 14, 28, 57 }, { 13, 26, 54 }, { 12, 25, 51 }, { 12, 24, 48 }, { 11, 23, 46 }, { 11, 22, 44 }, { 10, 21, 42 } }; /** Maps from monster action to monster animation letter. */ char animletter[7] = "nwahds"; /** Maps from direction to a left turn from the direction. */ int left[8] = { 7, 0, 1, 2, 3, 4, 5, 6 }; /** Maps from direction to a right turn from the direction. */ int right[8] = { 1, 2, 3, 4, 5, 6, 7, 0 }; /** Maps from direction to the opposite direction. */ int opposite[8] = { 4, 5, 6, 7, 0, 1, 2, 3 }; /** Maps from direction to delta X-offset. */ int offset_x[8] = { 1, 0, -1, -1, -1, 0, 1, 1 }; /** Maps from direction to delta Y-offset. */ int offset_y[8] = { 1, 1, 1, 0, -1, -1, -1, 0 }; /** 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(int monst, bool special) { BYTE *f; int i, n, j; f = Monsters[monst].trans_file; for (i = 0; i < 256; i++) { if (*f == 255) { *f = 0; } f++; } n = special ? 6 : 5; for (i = 0; i < n; i++) { if (i != 1 || Monsters[monst].mtype < MT_COUNSLR || Monsters[monst].mtype > MT_ADVOCATE) { for (j = 0; j < 8; j++) { Cl2ApplyTrans( Monsters[monst].Anims[i].Data[j], Monsters[monst].trans_file, Monsters[monst].Anims[i].Frames); } } } } void InitLevelMonsters() { int i; nummtypes = 0; monstimgtot = 0; MissileFileFlag = 0; for (i = 0; i < MAX_LVLMTYPES; i++) { Monsters[i].mPlaceFlags = 0; } ClrAllMonsters(); nummonsters = 0; totalmonsters = MAXMONSTERS; for (i = 0; i < MAXMONSTERS; i++) { monstactive[i] = i; } uniquetrans = 0; } int AddMonsterType(_monster_id type, placeflag placeflag) { bool done = false; int i; for (i = 0; i < nummtypes && !done; i++) { done = Monsters[i].mtype == type; } i--; if (!done) { i = nummtypes; nummtypes++; Monsters[i].mtype = type; monstimgtot += monsterdata[type].mImage; InitMonsterGFX(i); InitMonsterSND(i); } Monsters[i].mPlaceFlags |= placeflag; return i; } void GetLevelMTypes() { int i; // 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 (!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 (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) { skeltypes[nt++] = (_monster_id)i; } } } } AddMonsterType(skeltypes[random_(88, nt)], PLACE_SCATTER); } nt = 0; for (i = MT_NZOMBIE; i < NUM_MTYPES; i++) { minl = 15 * monsterdata[i].mMinDLvl / 30 + 1; maxl = 15 * monsterdata[i].mMaxDLvl / 30 + 1; if (currlevel >= minl && currlevel <= maxl) { if (MonstAvailTbl[i] & mamask) { typelist[nt++] = (_monster_id)i; } } } #ifdef _DEBUG if (monstdebug) { for (i = 0; i < debugmonsttypes; i++) AddMonsterType(DebugMonsters[i], PLACE_SCATTER); } else #endif { while (nt > 0 && nummtypes < MAX_LVLMTYPES && monstimgtot < 4000) { for (i = 0; i < nt;) { if (monsterdata[typelist[i]].mImage > 4000 - monstimgtot) { typelist[i] = typelist[--nt]; continue; } i++; } if (nt != 0) { i = random_(88, nt); AddMonsterType(typelist[i], PLACE_SCATTER); typelist[i] = typelist[--nt]; } } } } else { if (setlvlnum == SL_SKELKING) { AddMonsterType(MT_SKING, PLACE_UNIQUE); } } } void InitMonsterGFX(int monst) { int mtype, anim, i; char strBuff[256]; BYTE *celBuf; mtype = Monsters[monst].mtype; for (anim = 0; anim < 6; anim++) { int frames = monsterdata[mtype].Frames[anim]; if (gbIsHellfire && mtype == MT_DIABLO && anim == 3) frames = 2; if ((animletter[anim] != 's' || monsterdata[mtype].has_special) && frames > 0) { sprintf(strBuff, monsterdata[mtype].GraphicType, animletter[anim]); celBuf = LoadFileInMem(strBuff, NULL); Monsters[monst].Anims[anim].CMem = celBuf; if (Monsters[monst].mtype != MT_GOLEM || (animletter[anim] != 's' && animletter[anim] != 'd')) { for (i = 0; i < 8; i++) { Monsters[monst].Anims[anim].Data[i] = CelGetFrameStart(celBuf, i); } } else { for (i = 0; i < 8; i++) { Monsters[monst].Anims[anim].Data[i] = celBuf; } } } Monsters[monst].Anims[anim].Frames = frames; Monsters[monst].Anims[anim].Rate = monsterdata[mtype].Rate[anim]; } Monsters[monst].width = monsterdata[mtype].width; Monsters[monst].width2 = (monsterdata[mtype].width - 64) >> 1; Monsters[monst].mMinHP = monsterdata[mtype].mMinHP; Monsters[monst].mMaxHP = monsterdata[mtype].mMaxHP; if (!gbIsHellfire && mtype == MT_DIABLO) { Monsters[monst].mMinHP -= 2000; Monsters[monst].mMaxHP -= 2000; } Monsters[monst].has_special = monsterdata[mtype].has_special; Monsters[monst].mAFNum = monsterdata[mtype].mAFNum; Monsters[monst].MData = &monsterdata[mtype]; if (monsterdata[mtype].has_trans) { Monsters[monst].trans_file = LoadFileInMem(monsterdata[mtype].TransFile, NULL); InitMonsterTRN(monst, monsterdata[mtype].has_special); MemFreeDbg(Monsters[monst].trans_file); } if (mtype >= MT_NMAGMA && mtype <= MT_WMAGMA && !(MissileFileFlag & 1)) { MissileFileFlag |= 1; LoadMissileGFX(MFILE_MAGBALL); } if (mtype >= MT_STORM && mtype <= MT_MAEL && !(MissileFileFlag & 2)) { MissileFileFlag |= 2; LoadMissileGFX(MFILE_THINLGHT); } if (mtype == MT_SUCCUBUS && !(MissileFileFlag & 4)) { MissileFileFlag |= 4; LoadMissileGFX(MFILE_FLARE); LoadMissileGFX(MFILE_FLAREEXP); } if (mtype >= MT_INCIN && mtype <= MT_HELLBURN && !(MissileFileFlag & 8)) { MissileFileFlag |= 8; LoadMissileGFX(MFILE_KRULL); } if (mtype == MT_SNOWWICH && !(MissileFileFlag & 0x20)) { MissileFileFlag |= 0x20; LoadMissileGFX(MFILE_SCUBMISB); LoadMissileGFX(MFILE_SCBSEXPB); } if (mtype == MT_HLSPWN && !(MissileFileFlag & 0x40)) { MissileFileFlag |= 0x40; LoadMissileGFX(MFILE_SCUBMISD); LoadMissileGFX(MFILE_SCBSEXPD); } if (mtype == MT_SOLBRNR && !(MissileFileFlag & 0x80)) { MissileFileFlag |= 0x80; LoadMissileGFX(MFILE_SCUBMISC); LoadMissileGFX(MFILE_SCBSEXPC); } if (mtype >= MT_INCIN && mtype <= MT_HELLBURN && !(MissileFileFlag & 8)) { MissileFileFlag |= 8; LoadMissileGFX(MFILE_KRULL); } if (((mtype >= MT_NACID && mtype <= MT_XACID) || mtype == MT_SPIDLORD) && !(MissileFileFlag & 0x10)) { MissileFileFlag |= 0x10; LoadMissileGFX(MFILE_ACIDBF); LoadMissileGFX(MFILE_ACIDSPLA); LoadMissileGFX(MFILE_ACIDPUD); } if (mtype == MT_LICH && !(MissileFileFlag & 0x100)) { MissileFileFlag |= 0x100u; LoadMissileGFX(MFILE_LICH); LoadMissileGFX(MFILE_EXORA1); } if (mtype == MT_ARCHLICH && !(MissileFileFlag & 0x200)) { MissileFileFlag |= 0x200u; LoadMissileGFX(MFILE_ARCHLICH); LoadMissileGFX(MFILE_EXYEL2); } if ((mtype == MT_PSYCHORB || mtype == MT_BONEDEMN) && !(MissileFileFlag & 0x400)) { MissileFileFlag |= 0x400u; LoadMissileGFX(MFILE_BONEDEMON); } if (mtype == MT_NECRMORB && !(MissileFileFlag & 0x800)) { MissileFileFlag |= 0x800u; LoadMissileGFX(MFILE_NECROMORB); LoadMissileGFX(MFILE_EXRED3); } if (mtype == MT_PSYCHORB && !(MissileFileFlag & 0x1000)) { MissileFileFlag |= 0x1000u; LoadMissileGFX(MFILE_EXBL2); } if (mtype == MT_BONEDEMN && !(MissileFileFlag & 0x2000)) { MissileFileFlag |= 0x2000u; LoadMissileGFX(MFILE_EXBL3); } if (mtype == MT_DIABLO) { LoadMissileGFX(MFILE_FIREPLAR); } } void ClearMVars(int i) { monster[i]._mVar1 = 0; monster[i]._mVar2 = 0; monster[i]._mVar3 = 0; monster[i]._mVar4 = 0; monster[i]._mVar5 = 0; monster[i]._mVar6 = 0; monster[i]._mVar7 = 0; monster[i]._mVar8 = 0; } void InitMonster(int i, int rd, int mtype, int x, int y) { CMonster *monst = &Monsters[mtype]; monster[i]._mdir = rd; monster[i]._mx = x; monster[i]._my = y; monster[i]._mfutx = x; monster[i]._mfuty = y; monster[i]._moldx = x; monster[i]._moldy = y; monster[i]._mMTidx = mtype; monster[i]._mmode = MM_STAND; monster[i].mName = monst->MData->mName; monster[i].MType = monst; monster[i].MData = monst->MData; monster[i]._mAnimData = monst->Anims[MA_STAND].Data[rd]; monster[i]._mAnimDelay = monst->Anims[MA_STAND].Rate; monster[i]._mAnimCnt = random_(88, monster[i]._mAnimDelay - 1); monster[i]._mAnimLen = monst->Anims[MA_STAND].Frames; monster[i]._mAnimFrame = random_(88, monster[i]._mAnimLen - 1) + 1; monster[i].mLevel = monst->MData->mLevel; monster[i]._mmaxhp = (monst->mMinHP + random_(88, monst->mMaxHP - monst->mMinHP + 1)) << 6; if (monst->mtype == MT_DIABLO && !gbIsHellfire) { monster[i]._mmaxhp /= 2; monster[i].mLevel -= 15; } if (!gbIsMultiplayer) { monster[i]._mmaxhp >>= 1; if (monster[i]._mmaxhp < 64) { monster[i]._mmaxhp = 64; } } monster[i]._mhitpoints = monster[i]._mmaxhp; monster[i]._mAi = monst->MData->mAi; monster[i]._mint = monst->MData->mInt; monster[i]._mgoal = MGOAL_NORMAL; monster[i]._mgoalvar1 = 0; monster[i]._mgoalvar2 = 0; monster[i]._mgoalvar3 = 0; monster[i]._pathcount = 0; monster[i]._mDelFlag = false; monster[i]._uniqtype = 0; monster[i]._msquelch = 0; monster[i].mlid = NO_LIGHT; // BUGFIX monsters initial light id should be -1 (fixed) monster[i]._mRndSeed = AdvanceRndSeed(); monster[i]._mAISeed = AdvanceRndSeed(); monster[i].mWhoHit = 0; monster[i].mExp = monst->MData->mExp; monster[i].mHit = monst->MData->mHit; monster[i].mMinDamage = monst->MData->mMinDamage; monster[i].mMaxDamage = monst->MData->mMaxDamage; monster[i].mHit2 = monst->MData->mHit2; monster[i].mMinDamage2 = monst->MData->mMinDamage2; monster[i].mMaxDamage2 = monst->MData->mMaxDamage2; monster[i].mArmorClass = monst->MData->mArmorClass; monster[i].mMagicRes = monst->MData->mMagicRes; monster[i].leader = 0; monster[i].leaderflag = 0; monster[i]._mFlags = monst->MData->mFlags; monster[i].mtalkmsg = 0; if (monster[i]._mAi == AI_GARG) { monster[i]._mAnimData = monst->Anims[MA_SPECIAL].Data[rd]; monster[i]._mAnimFrame = 1; monster[i]._mFlags |= MFLAG_ALLOW_SPECIAL; monster[i]._mmode = MM_SATTACK; } if (sgGameInitInfo.nDifficulty == DIFF_NIGHTMARE) { monster[i]._mmaxhp = 3 * monster[i]._mmaxhp; if (gbIsHellfire) monster[i]._mmaxhp += (gbIsMultiplayer ? 100 : 50) << 6; else monster[i]._mmaxhp += 64; monster[i]._mhitpoints = monster[i]._mmaxhp; monster[i].mLevel += 15; monster[i].mExp = 2 * (monster[i].mExp + 1000); monster[i].mHit += NIGHTMARE_TO_HIT_BONUS; monster[i].mMinDamage = 2 * (monster[i].mMinDamage + 2); monster[i].mMaxDamage = 2 * (monster[i].mMaxDamage + 2); monster[i].mHit2 += NIGHTMARE_TO_HIT_BONUS; monster[i].mMinDamage2 = 2 * (monster[i].mMinDamage2 + 2); monster[i].mMaxDamage2 = 2 * (monster[i].mMaxDamage2 + 2); monster[i].mArmorClass += NIGHTMARE_AC_BONUS; } else if (sgGameInitInfo.nDifficulty == DIFF_HELL) { monster[i]._mmaxhp = 4 * monster[i]._mmaxhp; if (gbIsHellfire) monster[i]._mmaxhp += (gbIsMultiplayer ? 200 : 100) << 6; else monster[i]._mmaxhp += 192; monster[i]._mhitpoints = monster[i]._mmaxhp; monster[i].mLevel += 30; monster[i].mExp = 4 * (monster[i].mExp + 1000); monster[i].mHit += HELL_TO_HIT_BONUS; monster[i].mMinDamage = 4 * monster[i].mMinDamage + 6; monster[i].mMaxDamage = 4 * monster[i].mMaxDamage + 6; monster[i].mHit2 += HELL_TO_HIT_BONUS; monster[i].mMinDamage2 = 4 * monster[i].mMinDamage2 + 6; monster[i].mMaxDamage2 = 4 * monster[i].mMaxDamage2 + 6; monster[i].mArmorClass += HELL_AC_BONUS; monster[i].mMagicRes = monst->MData->mMagicRes2; } } void ClrAllMonsters() { int i; MonsterStruct *Monst; for (i = 0; i < MAXMONSTERS; i++) { Monst = &monster[i]; ClearMVars(i); Monst->mName = "Invalid Monster"; Monst->_mgoal = MGOAL_NONE; Monst->_mmode = MM_STAND; Monst->_mVar1 = 0; Monst->_mVar2 = 0; Monst->_mx = 0; Monst->_my = 0; Monst->_mfutx = 0; Monst->_mfuty = 0; Monst->_moldx = 0; Monst->_moldy = 0; Monst->_mdir = random_(89, 8); Monst->_mxvel = 0; Monst->_myvel = 0; Monst->_mAnimData = NULL; Monst->_mAnimDelay = 0; Monst->_mAnimCnt = 0; Monst->_mAnimLen = 0; Monst->_mAnimFrame = 0; Monst->_mFlags = 0; Monst->_mDelFlag = false; Monst->_menemy = random_(89, gbActivePlayers); Monst->_menemyx = plr[Monst->_menemy]._pfutx; Monst->_menemyy = plr[Monst->_menemy]._pfuty; } } bool MonstPlace(int xp, int yp) { char f; if (xp < 0 || xp >= MAXDUNX || yp < 0 || yp >= MAXDUNY || dMonster[xp][yp] != 0 || dPlayer[xp][yp] != 0) { return false; } f = dFlags[xp][yp]; if (f & BFLAG_VISIBLE) { return false; } if (f & BFLAG_POPULATED) { return false; } return !SolidLoc(xp, yp); } void monster_some_crypt() { MonsterStruct *mon; int hp; if (currlevel == 24 && UberDiabloMonsterIndex >= 0 && UberDiabloMonsterIndex < nummonsters) { mon = &monster[UberDiabloMonsterIndex]; PlayEffect(UberDiabloMonsterIndex, 2); quests[Q_NAKRUL]._qlog = 0; mon->mArmorClass -= 50; hp = mon->_mmaxhp / 2; mon->mMagicRes = 0; mon->_mhitpoints = hp; mon->_mmaxhp = hp; } } void PlaceMonster(int i, int mtype, int x, int y) { int rd; if (Monsters[mtype].mtype == MT_NAKRUL) { for (int j = 0; j < nummonsters; j++) { if (monster[j]._mMTidx == mtype) { return; } if (monster[j].MType->mtype == MT_NAKRUL) { return; } } } dMonster[x][y] = i + 1; rd = random_(90, 8); InitMonster(i, rd, mtype, x, y); } void PlaceUniqueMonst(int uniqindex, int miniontype, int bosspacksize) { int xp, yp, x, y, i; int uniqtype; int count2; char filestr[64]; bool zharflag, done; const UniqMonstStruct *Uniq; MonsterStruct *Monst; int count; Monst = &monster[nummonsters]; count = 0; Uniq = &UniqMonst[uniqindex]; if ((uniquetrans + 19) << 8 >= LIGHTSIZE) { return; } for (uniqtype = 0; uniqtype < nummtypes; uniqtype++) { if (Monsters[uniqtype].mtype == UniqMonst[uniqindex].mtype) { break; } } while (1) { xp = random_(91, 80) + 16; yp = random_(91, 80) + 16; count2 = 0; for (x = xp - 3; x < xp + 3; x++) { for (y = yp - 3; y < yp + 3; y++) { if (y >= 0 && y < MAXDUNY && x >= 0 && x < MAXDUNX && MonstPlace(x, y)) { count2++; } } } if (count2 < 9) { count++; if (count < 1000) { continue; } } if (MonstPlace(xp, yp)) { break; } } 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) { zharflag = true; for (i = 0; i < themeCount; i++) { if (i == zharlib && zharflag) { zharflag = false; xp = 2 * themeLoc[i].x + 20; yp = 2 * themeLoc[i].y + 20; } } } 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 (uniqindex == UMT_BUTCHER) { done = false; for (yp = 0; yp < MAXDUNY && !done; yp++) { for (xp = 0; xp < MAXDUNX && !done; xp++) { done = dPiece[xp][yp] == 367; } } } if (uniqindex == UMT_NAKRUL) { if (UberRow == 0 || UberCol == 0) { UberDiabloMonsterIndex = -1; return; } xp = UberRow - 2; yp = UberCol; UberDiabloMonsterIndex = nummonsters; } PlaceMonster(nummonsters, uniqtype, xp, yp); Monst->_uniqtype = uniqindex + 1; if (Uniq->mlevel) { Monst->mLevel = 2 * Uniq->mlevel; } else { Monst->mLevel += 5; } Monst->mExp *= 2; Monst->mName = Uniq->mName; Monst->_mmaxhp = Uniq->mmaxhp << 6; if (!gbIsMultiplayer) { Monst->_mmaxhp = Monst->_mmaxhp >> 1; if (Monst->_mmaxhp < 64) { Monst->_mmaxhp = 64; } } 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->_mx, Monst->_my, 3); if (gbIsMultiplayer) { if (Monst->_mAi == AI_LAZHELP) Monst->mtalkmsg = 0; if (Monst->_mAi == AI_LAZURUS && quests[Q_BETRAYER]._qvar1 > 3) { Monst->_mgoal = MGOAL_NORMAL; } else if (Monst->mtalkmsg) { Monst->_mgoal = MGOAL_INQUIRING; } } else if (Monst->mtalkmsg) { 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; } sprintf(filestr, "Monsters\\Monsters\\%s.TRN", Uniq->mTrnName); LoadFileWithMem(filestr, &pLightTbl[256 * (uniquetrans + 19)]); Monst->_uniqtrans = uniquetrans++; if (Uniq->mUnqAttr & 4) { 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) { 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; } } nummonsters++; if (Uniq->mUnqAttr & 1) { PlaceGroup(miniontype, bosspacksize, Uniq->mUnqAttr, nummonsters - 1); } if (Monst->_mAi != AI_GARG) { Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir]; Monst->_mAnimFrame = random_(88, Monst->_mAnimLen - 1) + 1; Monst->_mFlags &= ~MFLAG_ALLOW_SPECIAL; Monst->_mmode = MM_STAND; } } static void PlaceUniques() { int u, mt; bool done; for (u = 0; UniqMonst[u].mtype != -1; u++) { if (UniqMonst[u].mlevel != currlevel) continue; done = false; for (mt = 0; mt < nummtypes; mt++) { if (done) break; done = (Monsters[mt].mtype == UniqMonst[u].mtype); } mt--; 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; BYTE *setp; if (!setlevel) { if (QuestStatus(Q_BUTCHER)) { PlaceUniqueMonst(UMT_BUTCHER, 0, 0); } if (currlevel == quests[Q_SKELKING]._qlevel && gbIsMultiplayer) { skeltype = 0; for (skeltype = 0; skeltype < nummtypes; skeltype++) { if (IsSkel(Monsters[skeltype].mtype)) { break; } } PlaceUniqueMonst(UMT_SKELKING, skeltype, 30); } if (QuestStatus(Q_LTBANNER)) { setp = LoadFileInMem("Levels\\L1Data\\Banner1.DUN", NULL); SetMapMonsters(setp, 2 * setpc_x, 2 * setpc_y); mem_free_dbg(setp); } if (QuestStatus(Q_BLOOD)) { setp = LoadFileInMem("Levels\\L2Data\\Blood2.DUN", NULL); SetMapMonsters(setp, 2 * setpc_x, 2 * setpc_y); mem_free_dbg(setp); } if (QuestStatus(Q_BLIND)) { setp = LoadFileInMem("Levels\\L2Data\\Blind2.DUN", NULL); SetMapMonsters(setp, 2 * setpc_x, 2 * setpc_y); mem_free_dbg(setp); } if (QuestStatus(Q_ANVIL)) { setp = LoadFileInMem("Levels\\L3Data\\Anvil.DUN", NULL); SetMapMonsters(setp, 2 * setpc_x + 2, 2 * setpc_y + 2); mem_free_dbg(setp); } if (QuestStatus(Q_WARLORD)) { setp = LoadFileInMem("Levels\\L4Data\\Warlord.DUN", NULL); SetMapMonsters(setp, 2 * setpc_x, 2 * setpc_y); mem_free_dbg(setp); 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); setp = LoadFileInMem("Levels\\L4Data\\Vile1.DUN", NULL); SetMapMonsters(setp, 2 * setpc_x, 2 * setpc_y); mem_free_dbg(setp); } if (currlevel == 24) { UberDiabloMonsterIndex = -1; int i1; for (i1 = 0; i1 < nummtypes; i1++) { if (Monsters[i1].mtype == UniqMonst[UMT_NAKRUL].mtype) break; } if (i1 < nummtypes) { for (int i2 = 0; i2 < nummonsters; i2++) { if (monster[i2]._uniqtype != 0 || monster[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, try1, try2, j; int xp, yp, x1, y1; placed = 0; for (try1 = 0; try1 < 10; try1++) { while (placed) { nummonsters--; placed--; dMonster[monster[nummonsters]._mx][monster[nummonsters]._my] = 0; } if (leaderf & 1) { int offset = random_(92, 8); x1 = xp = monster[leader]._mx + offset_x[offset]; y1 = yp = monster[leader]._my + offset_y[offset]; } else { do { x1 = xp = random_(93, 80) + 16; y1 = yp = random_(93, 80) + 16; } while (!MonstPlace(xp, yp)); } if (num + nummonsters > totalmonsters) { num = totalmonsters - nummonsters; } j = 0; for (try2 = 0; j < num && try2 < 100; xp += offset_x[random_(94, 8)], yp += offset_x[random_(94, 8)]) { /// BUGFIX: `yp += offset_y` if (!MonstPlace(xp, yp) || (dTransVal[xp][yp] != dTransVal[x1][y1]) || ((leaderf & 2) && ((abs(xp - x1) >= 4) || (abs(yp - y1) >= 4)))) { try2++; continue; } PlaceMonster(nummonsters, mtype, xp, yp); if (leaderf & 1) { monster[nummonsters]._mmaxhp *= 2; monster[nummonsters]._mhitpoints = monster[nummonsters]._mmaxhp; monster[nummonsters]._mint = monster[leader]._mint; if (leaderf & 2) { monster[nummonsters].leader = leader; monster[nummonsters].leaderflag = 1; monster[nummonsters]._mAi = monster[leader]._mAi; } if (monster[nummonsters]._mAi != AI_GARG) { monster[nummonsters]._mAnimData = monster[nummonsters].MType->Anims[MA_STAND].Data[monster[nummonsters]._mdir]; monster[nummonsters]._mAnimFrame = random_(88, monster[nummonsters]._mAnimLen - 1) + 1; monster[nummonsters]._mFlags &= ~MFLAG_ALLOW_SPECIAL; monster[nummonsters]._mmode = MM_STAND; } } nummonsters++; placed++; j++; } if (placed >= num) { break; } } if (leaderf & 2) { monster[leader].packsize = placed; } } void LoadDiabMonsts() { BYTE *lpSetPiece; lpSetPiece = LoadFileInMem("Levels\\L4Data\\diab1.DUN", NULL); SetMapMonsters(lpSetPiece, 2 * diabquad1x, 2 * diabquad1y); mem_free_dbg(lpSetPiece); lpSetPiece = LoadFileInMem("Levels\\L4Data\\diab2a.DUN", NULL); SetMapMonsters(lpSetPiece, 2 * diabquad2x, 2 * diabquad2y); mem_free_dbg(lpSetPiece); lpSetPiece = LoadFileInMem("Levels\\L4Data\\diab3a.DUN", NULL); SetMapMonsters(lpSetPiece, 2 * diabquad3x, 2 * diabquad3y); mem_free_dbg(lpSetPiece); lpSetPiece = LoadFileInMem("Levels\\L4Data\\diab4a.DUN", NULL); SetMapMonsters(lpSetPiece, 2 * diabquad4x, 2 * diabquad4y); mem_free_dbg(lpSetPiece); } void InitMonsters() { int na, nt; int i, s, t; int numplacemonsters; int mtype; int numscattypes; int scattertypes[NUM_MTYPES]; numscattypes = 0; #ifdef _DEBUG if (gbIsMultiplayer) CheckDungeonClear(); #endif if (!setlevel) { AddMonster(1, 0, 0, 0, false); AddMonster(1, 0, 0, 0, false); AddMonster(1, 0, 0, 0, false); AddMonster(1, 0, 0, 0, false); } if (!gbIsSpawn && !setlevel && currlevel == 16) LoadDiabMonsts(); nt = numtrigs; if (currlevel == 15) nt = 1; for (i = 0; i < nt; i++) { for (s = -2; s < 2; s++) { for (t = -2; t < 2; t++) DoVision(s + trigs[i]._tx, t + trigs[i]._ty, 15, false, false); } } if (!gbIsSpawn) PlaceQuestMonsters(); if (!setlevel) { if (!gbIsSpawn) PlaceUniques(); na = 0; for (s = 16; s < 96; s++) for (t = 16; t < 96; t++) if (!SolidLoc(s, t)) na++; numplacemonsters = na / 30; if (gbIsMultiplayer) numplacemonsters += numplacemonsters >> 1; if (nummonsters + numplacemonsters > MAXMONSTERS - 10) numplacemonsters = MAXMONSTERS - 10 - nummonsters; totalmonsters = nummonsters + numplacemonsters; for (i = 0; i < nummtypes; i++) { if (Monsters[i].mPlaceFlags & PLACE_SCATTER) { scattertypes[numscattypes] = i; numscattypes++; } } while (nummonsters < totalmonsters) { mtype = scattertypes[random_(95, numscattypes)]; if (currlevel == 1 || random_(95, 2) == 0) na = 1; else if (currlevel == 2 || (currlevel >= 21 && currlevel <= 24)) na = random_(95, 2) + 2; else na = random_(95, 3) + 3; PlaceGroup(mtype, na, 0, 0); } } for (i = 0; i < nt; i++) { for (s = -2; s < 2; s++) { for (t = -2; t < 2; t++) DoUnVision(s + trigs[i]._tx, t + trigs[i]._ty, 15); } } } void SetMapMonsters(BYTE *pMap, int startx, int starty) { WORD rw, rh; WORD *lm; int i, j; int mtype; AddMonsterType(MT_GOLEM, PLACE_SPECIAL); AddMonster(1, 0, 0, 0, false); AddMonster(1, 0, 0, 0, false); AddMonster(1, 0, 0, 0, false); AddMonster(1, 0, 0, 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); } lm = (WORD *)pMap; rw = SDL_SwapLE16(*lm++); rh = SDL_SwapLE16(*lm++); lm += rw * rh; rw = rw << 1; rh = rh << 1; lm += rw * rh; for (j = 0; j < rh; j++) { for (i = 0; i < rw; i++) { if (*lm != 0) { mtype = AddMonsterType(MonstConvTbl[SDL_SwapLE16(*lm) - 1], PLACE_SPECIAL); PlaceMonster(nummonsters++, mtype, i + startx + 16, j + starty + 16); } lm++; } } } void DeleteMonster(int i) { int temp; nummonsters--; temp = monstactive[nummonsters]; monstactive[nummonsters] = monstactive[i]; monstactive[i] = temp; } int AddMonster(int x, int y, int dir, int mtype, bool InMap) { if (nummonsters < MAXMONSTERS) { int i = monstactive[nummonsters++]; if (InMap) dMonster[x][y] = i + 1; InitMonster(i, dir, mtype, x, y); return i; } return -1; } void monster_43C785(int i) { int x, y, d, j, oi, dir, mx, my; if (monster[i].MType) { mx = monster[i]._mx; my = monster[i]._my; dir = monster[i]._mdir; for (d = 0; d < 8; d++) { x = mx + offset_x[d]; y = my + offset_y[d]; if (!SolidLoc(x, y)) { if (dPlayer[x][y] == 0 && dMonster[x][y] == 0) { if (dObject[x][y] == 0) break; oi = dObject[x][y] > 0 ? dObject[x][y] - 1 : -(dObject[x][y] + 1); if (!object[oi]._oSolidFlag) break; } } } if (d < 8) { for (j = 0; j < MAX_LVLMTYPES; j++) { if (Monsters[j].mtype == monster[i].MType->mtype) break; } if (j < MAX_LVLMTYPES) AddMonster(x, y, dir, j, true); } } } void NewMonsterAnim(int i, AnimStruct *anim, int md) { MonsterStruct *Monst = &monster[i]; Monst->_mAnimData = anim->Data[md]; Monst->_mAnimLen = anim->Frames; Monst->_mAnimCnt = 0; Monst->_mAnimFrame = 1; Monst->_mAnimDelay = anim->Rate; Monst->_mFlags &= ~(MFLAG_LOCK_ANIMATION | MFLAG_ALLOW_SPECIAL); Monst->_mdir = md; } bool M_Ranged(int i) { char ai = monster[i]._mAi; return ai == AI_SKELBOW || ai == AI_GOATBOW || ai == AI_SUCC || ai == AI_LAZHELP; } bool M_Talker(int i) { char ai = monster[i]._mAi; return ai == AI_LAZURUS || ai == AI_WARLORD || ai == AI_GARBUD || ai == AI_ZHAR || ai == AI_SNOTSPIL || ai == AI_LACHDAN || ai == AI_LAZHELP; } void M_Enemy(int i) { int j; int mi, pnum; int dist, best_dist; int _menemy; bool sameroom, bestsameroom; MonsterStruct *Monst; BYTE enemyx, enemyy; _menemy = -1; best_dist = -1; bestsameroom = 0; Monst = &monster[i]; if (Monst->_mFlags & MFLAG_BERSERK || !(Monst->_mFlags & MFLAG_GOLEM)) { for (pnum = 0; pnum < MAX_PLRS; pnum++) { if (!plr[pnum].plractive || currlevel != plr[pnum].plrlevel || plr[pnum]._pLvlChanging || (((plr[pnum]._pHitPoints >> 6) == 0) && gbIsMultiplayer)) continue; sameroom = (dTransVal[Monst->_mx][Monst->_my] == dTransVal[plr[pnum]._px][plr[pnum]._py]); dist = std::max(abs(Monst->_mx - plr[pnum]._px), abs(Monst->_my - plr[pnum]._py)); if ((sameroom && !bestsameroom) || ((sameroom || !bestsameroom) && dist < best_dist) || (_menemy == -1)) { Monst->_mFlags &= ~MFLAG_TARGETS_MONSTER; _menemy = pnum; enemyx = plr[pnum]._pfutx; enemyy = plr[pnum]._pfuty; best_dist = dist; bestsameroom = sameroom; } } } for (j = 0; j < nummonsters; j++) { mi = monstactive[j]; if (mi == i) continue; if (!((monster[mi]._mhitpoints >> 6) > 0)) continue; if (monster[mi]._mx == 1 && monster[mi]._my == 0) continue; if (M_Talker(mi) && monster[mi].mtalkmsg) continue; dist = std::max(abs(monster[mi]._mx - Monst->_mx), abs(monster[mi]._my - Monst->_my)); if ((!(Monst->_mFlags & MFLAG_GOLEM) && !(Monst->_mFlags & MFLAG_BERSERK) && dist >= 2 && !M_Ranged(i)) || (!(Monst->_mFlags & MFLAG_GOLEM) && !(Monst->_mFlags & MFLAG_BERSERK) && !(monster[mi]._mFlags & MFLAG_GOLEM))) { continue; } sameroom = dTransVal[Monst->_mx][Monst->_my] == dTransVal[monster[mi]._mx][monster[mi]._my]; if ((sameroom && !bestsameroom) || ((sameroom || !bestsameroom) && dist < best_dist) || (_menemy == -1)) { Monst->_mFlags |= MFLAG_TARGETS_MONSTER; _menemy = mi; enemyx = monster[mi]._mfutx; enemyy = monster[mi]._mfuty; best_dist = dist; bestsameroom = sameroom; } } if (_menemy != -1) { Monst->_mFlags &= ~MFLAG_NO_ENEMY; Monst->_menemy = _menemy; Monst->_menemyx = enemyx; Monst->_menemyy = enemyy; } else { Monst->_mFlags |= MFLAG_NO_ENEMY; } } int M_GetDir(int i) { return GetDirection(monster[i]._mx, monster[i]._my, monster[i]._menemyx, monster[i]._menemyy); } void M_StartStand(int i, int md) { ClearMVars(i); if (monster[i].MType->mtype == MT_GOLEM) NewMonsterAnim(i, &monster[i].MType->Anims[MA_WALK], md); else NewMonsterAnim(i, &monster[i].MType->Anims[MA_STAND], md); monster[i]._mVar1 = monster[i]._mmode; monster[i]._mVar2 = 0; monster[i]._mmode = MM_STAND; monster[i]._mxoff = 0; monster[i]._myoff = 0; monster[i]._mfutx = monster[i]._mx; monster[i]._mfuty = monster[i]._my; monster[i]._moldx = monster[i]._mx; monster[i]._moldy = monster[i]._my; monster[i]._mdir = md; M_Enemy(i); } void M_StartDelay(int i, int len) { if (len <= 0) { return; } if (monster[i]._mAi != AI_LAZURUS) { monster[i]._mVar2 = len; monster[i]._mmode = MM_DELAY; } } void M_StartSpStand(int i, int md) { NewMonsterAnim(i, &monster[i].MType->Anims[MA_SPECIAL], md); monster[i]._mmode = MM_SPSTAND; monster[i]._mxoff = 0; monster[i]._myoff = 0; monster[i]._mfutx = monster[i]._mx; monster[i]._mfuty = monster[i]._my; monster[i]._moldx = monster[i]._mx; monster[i]._moldy = monster[i]._my; monster[i]._mdir = md; } void M_StartWalk(int i, int xvel, int yvel, int xadd, int yadd, int EndDir) { int fx = xadd + monster[i]._mx; int fy = yadd + monster[i]._my; dMonster[fx][fy] = -(i + 1); monster[i]._mmode = MM_WALK; monster[i]._moldx = monster[i]._mx; monster[i]._moldy = monster[i]._my; monster[i]._mfutx = fx; monster[i]._mfuty = fy; monster[i]._mxvel = xvel; monster[i]._myvel = yvel; monster[i]._mVar1 = xadd; monster[i]._mVar2 = yadd; monster[i]._mVar3 = EndDir; monster[i]._mdir = EndDir; NewMonsterAnim(i, &monster[i].MType->Anims[MA_WALK], EndDir); monster[i]._mVar6 = 0; monster[i]._mVar7 = 0; monster[i]._mVar8 = 0; } void M_StartWalk2(int i, int xvel, int yvel, int xoff, int yoff, int xadd, int yadd, int EndDir) { int fx = xadd + monster[i]._mx; int fy = yadd + monster[i]._my; dMonster[monster[i]._mx][monster[i]._my] = -(i + 1); monster[i]._mVar1 = monster[i]._mx; monster[i]._mVar2 = monster[i]._my; monster[i]._moldx = monster[i]._mx; monster[i]._moldy = monster[i]._my; monster[i]._mx = fx; monster[i]._my = fy; monster[i]._mfutx = fx; monster[i]._mfuty = fy; dMonster[fx][fy] = i + 1; if (monster[i].mlid != NO_LIGHT) ChangeLightXY(monster[i].mlid, monster[i]._mx, monster[i]._my); monster[i]._mxoff = xoff; monster[i]._myoff = yoff; monster[i]._mmode = MM_WALK2; monster[i]._mxvel = xvel; monster[i]._myvel = yvel; monster[i]._mVar3 = EndDir; monster[i]._mdir = EndDir; NewMonsterAnim(i, &monster[i].MType->Anims[MA_WALK], EndDir); monster[i]._mVar6 = 16 * xoff; monster[i]._mVar7 = 16 * yoff; monster[i]._mVar8 = 0; } void M_StartWalk3(int i, int xvel, int yvel, int xoff, int yoff, int xadd, int yadd, int mapx, int mapy, int EndDir) { int fx = xadd + monster[i]._mx; int fy = yadd + monster[i]._my; int x = mapx + monster[i]._mx; int y = mapy + monster[i]._my; if (monster[i].mlid != NO_LIGHT) ChangeLightXY(monster[i].mlid, x, y); dMonster[monster[i]._mx][monster[i]._my] = -(i + 1); dMonster[fx][fy] = -(i + 1); monster[i]._mVar4 = x; monster[i]._mVar5 = y; dFlags[x][y] |= BFLAG_MONSTLR; monster[i]._moldx = monster[i]._mx; monster[i]._moldy = monster[i]._my; monster[i]._mfutx = fx; monster[i]._mfuty = fy; monster[i]._mxoff = xoff; monster[i]._myoff = yoff; monster[i]._mmode = MM_WALK3; monster[i]._mxvel = xvel; monster[i]._myvel = yvel; monster[i]._mVar1 = fx; monster[i]._mVar2 = fy; monster[i]._mVar3 = EndDir; monster[i]._mdir = EndDir; NewMonsterAnim(i, &monster[i].MType->Anims[MA_WALK], EndDir); monster[i]._mVar6 = 16 * xoff; monster[i]._mVar7 = 16 * yoff; monster[i]._mVar8 = 0; } void M_StartAttack(int i) { int md = M_GetDir(i); NewMonsterAnim(i, &monster[i].MType->Anims[MA_ATTACK], md); monster[i]._mmode = MM_ATTACK; monster[i]._mxoff = 0; monster[i]._myoff = 0; monster[i]._mfutx = monster[i]._mx; monster[i]._mfuty = monster[i]._my; monster[i]._moldx = monster[i]._mx; monster[i]._moldy = monster[i]._my; monster[i]._mdir = md; } void M_StartRAttack(int i, int missile_type, int dam) { int md = M_GetDir(i); NewMonsterAnim(i, &monster[i].MType->Anims[MA_ATTACK], md); monster[i]._mmode = MM_RATTACK; monster[i]._mVar1 = missile_type; monster[i]._mVar2 = dam; monster[i]._mxoff = 0; monster[i]._myoff = 0; monster[i]._mfutx = monster[i]._mx; monster[i]._mfuty = monster[i]._my; monster[i]._moldx = monster[i]._mx; monster[i]._moldy = monster[i]._my; monster[i]._mdir = md; } void M_StartRSpAttack(int i, int missile_type, int dam) { int md = M_GetDir(i); NewMonsterAnim(i, &monster[i].MType->Anims[MA_SPECIAL], md); monster[i]._mmode = MM_RSPATTACK; monster[i]._mVar1 = missile_type; monster[i]._mVar2 = 0; monster[i]._mVar3 = dam; monster[i]._mxoff = 0; monster[i]._myoff = 0; monster[i]._mfutx = monster[i]._mx; monster[i]._mfuty = monster[i]._my; monster[i]._moldx = monster[i]._mx; monster[i]._moldy = monster[i]._my; monster[i]._mdir = md; } void M_StartSpAttack(int i) { int md = M_GetDir(i); NewMonsterAnim(i, &monster[i].MType->Anims[MA_SPECIAL], md); monster[i]._mmode = MM_SATTACK; monster[i]._mxoff = 0; monster[i]._myoff = 0; monster[i]._mfutx = monster[i]._mx; monster[i]._mfuty = monster[i]._my; monster[i]._moldx = monster[i]._mx; monster[i]._moldy = monster[i]._my; monster[i]._mdir = md; } void M_StartEat(int i) { NewMonsterAnim(i, &monster[i].MType->Anims[MA_SPECIAL], monster[i]._mdir); monster[i]._mmode = MM_SATTACK; monster[i]._mxoff = 0; monster[i]._myoff = 0; monster[i]._mfutx = monster[i]._mx; monster[i]._mfuty = monster[i]._my; monster[i]._moldx = monster[i]._mx; monster[i]._moldy = monster[i]._my; } void M_ClearSquares(int i) { int x, y, mx, my, m1, m2; mx = monster[i]._moldx; my = monster[i]._moldy; m1 = -(i + 1); m2 = i + 1; for (y = my - 1; y <= my + 1; y++) { if (y >= 0 && y < MAXDUNY) { for (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) { int d = (monster[i]._mdir - 4) & 7; if (DirOK(i, d)) { M_ClearSquares(i); monster[i]._moldx += offset_x[d]; monster[i]._moldy += offset_y[d]; NewMonsterAnim(i, &monster[i].MType->Anims[MA_GOTHIT], monster[i]._mdir); monster[i]._mmode = MM_GOTHIT; monster[i]._mxoff = 0; monster[i]._myoff = 0; monster[i]._mx = monster[i]._moldx; monster[i]._my = monster[i]._moldy; monster[i]._mfutx = monster[i]._mx; monster[i]._mfuty = monster[i]._my; monster[i]._moldx = monster[i]._mx; // CODEFIX: useless assignment monster[i]._moldy = monster[i]._my; // CODEFIX: useless assignment M_ClearSquares(i); dMonster[monster[i]._mx][monster[i]._my] = i + 1; } } void M_StartHit(int i, int pnum, int dam) { if (pnum >= 0) monster[i].mWhoHit |= 1 << pnum; if (pnum == myplr) { delta_monster_hp(i, monster[i]._mhitpoints, currlevel); NetSendCmdMonDmg(false, i, dam); } PlayEffect(i, 1); if ((monster[i].MType->mtype >= MT_SNEAK && monster[i].MType->mtype <= MT_ILLWEAV) || dam >> 6 >= monster[i].mLevel + 3) { if (pnum >= 0) { monster[i]._menemy = pnum; monster[i]._menemyx = plr[pnum]._pfutx; monster[i]._menemyy = plr[pnum]._pfuty; monster[i]._mFlags &= ~MFLAG_TARGETS_MONSTER; monster[i]._mdir = M_GetDir(i); } if (monster[i].MType->mtype == MT_BLINK) { M_Teleport(i); } else if ((monster[i].MType->mtype >= MT_NSCAV && monster[i].MType->mtype <= MT_YSCAV) || monster[i].MType->mtype == MT_GRAVEDIG) { monster[i]._mgoal = MGOAL_NORMAL; monster[i]._mgoalvar1 = 0; monster[i]._mgoalvar2 = 0; } if (monster[i]._mmode != MM_STONE) { NewMonsterAnim(i, &monster[i].MType->Anims[MA_GOTHIT], monster[i]._mdir); monster[i]._mmode = MM_GOTHIT; monster[i]._mxoff = 0; monster[i]._myoff = 0; monster[i]._mx = monster[i]._moldx; monster[i]._my = monster[i]._moldy; monster[i]._mfutx = monster[i]._moldx; monster[i]._mfuty = monster[i]._moldy; M_ClearSquares(i); dMonster[monster[i]._mx][monster[i]._my] = i + 1; } } } void M_DiabloDeath(int i, bool sendmsg) { MonsterStruct *Monst; int dist; int j, k; Monst = &monster[i]; PlaySFX(USFX_DIABLOD); quests[Q_DIABLO]._qactive = QUEST_DONE; if (sendmsg) NetSendCmdQuest(true, Q_DIABLO); sgbSaveSoundOn = gbSoundOn; gbProcessPlayers = false; for (j = 0; j < nummonsters; j++) { k = monstactive[j]; if (k == i || Monst->_msquelch == 0) continue; NewMonsterAnim(k, &monster[k].MType->Anims[MA_DEATH], monster[k]._mdir); monster[k]._mmode = MM_DEATH; monster[k]._mxoff = 0; monster[k]._myoff = 0; monster[k]._mVar1 = 0; monster[k]._mx = monster[k]._moldx; monster[k]._my = monster[k]._moldy; monster[k]._mfutx = monster[k]._mx; monster[k]._mfuty = monster[k]._my; M_ClearSquares(k); dMonster[monster[k]._mx][monster[k]._my] = k + 1; } AddLight(Monst->_mx, Monst->_my, 8); DoVision(Monst->_mx, Monst->_my, 8, false, true); dist = std::max(abs(ViewX - Monst->_mx), abs(ViewY - Monst->_my)); if (dist > 20) dist = 20; Monst->_mVar3 = ViewX << 16; Monst->_mVar4 = ViewY << 16; Monst->_mVar5 = (int)((Monst->_mVar3 - (Monst->_mx << 16)) / (double)dist); Monst->_mVar6 = (int)((Monst->_mVar4 - (Monst->_my << 16)) / (double)dist); } void SpawnLoot(int i, bool sendmsg) { int nSFX; MonsterStruct *Monst; Monst = &monster[i]; if (QuestStatus(Q_GARBUD) && Monst->_uniqtype - 1 == UMT_GARBUD) { CreateTypeItem(Monst->_mx + 1, Monst->_my + 1, true, ITYPE_MACE, IMISC_NONE, true, false); } else if (Monst->_uniqtype - 1 == UMT_DEFILER) { if (effect_is_playing(USFX_DEFILER8)) stream_stop(); quests[Q_DEFILER]._qlog = 0; SpawnMapOfDoom(Monst->_mx, Monst->_my); } else if (Monst->_uniqtype - 1 == UMT_HORKDMN) { if (sgGameInitInfo.bTheoQuest) { SpawnTheodore(Monst->_mx, Monst->_my); } else { CreateAmulet(Monst->_mx, Monst->_my, 13, false, true); } } else if (Monst->MType->mtype == MT_HORKSPWN) { } else if (Monst->MType->mtype == MT_NAKRUL) { nSFX = IsUberRoomOpened ? USFX_NAKRUL4 : USFX_NAKRUL5; if (sgGameInitInfo.bCowQuest) nSFX = USFX_NAKRUL6; if (effect_is_playing(nSFX)) stream_stop(); quests[Q_NAKRUL]._qlog = 0; UberDiabloMonsterIndex = -2; CreateMagicWeapon(Monst->_mx, Monst->_my, ITYPE_SWORD, ICURS_GREAT_SWORD, false, true); CreateMagicWeapon(Monst->_mx, Monst->_my, ITYPE_STAFF, ICURS_WAR_STAFF, false, true); CreateMagicWeapon(Monst->_mx, Monst->_my, ITYPE_BOW, ICURS_LONG_WAR_BOW, false, true); CreateSpellBook(Monst->_mx, Monst->_my, SPL_APOCA, false, true); } else if (i > MAX_PLRS - 1) { // Golems should not spawn loot SpawnItem(i, Monst->_mx, Monst->_my, sendmsg); } } void M2MStartHit(int mid, int i, int dam) { assurance((DWORD)mid < MAXMONSTERS, mid); assurance(monster[mid].MType != NULL, mid); if (i >= 0 && i < MAX_PLRS) monster[mid].mWhoHit |= 1 << i; delta_monster_hp(mid, monster[mid]._mhitpoints, currlevel); NetSendCmdMonDmg(false, mid, dam); PlayEffect(mid, 1); if ((monster[mid].MType->mtype >= MT_SNEAK && monster[mid].MType->mtype <= MT_ILLWEAV) || dam >> 6 >= monster[mid].mLevel + 3) { if (i >= 0) monster[mid]._mdir = (monster[i]._mdir - 4) & 7; if (monster[mid].MType->mtype == MT_BLINK) { M_Teleport(mid); } else if ((monster[mid].MType->mtype >= MT_NSCAV && monster[mid].MType->mtype <= MT_YSCAV) || monster[mid].MType->mtype == MT_GRAVEDIG) { monster[mid]._mgoal = MGOAL_NORMAL; monster[mid]._mgoalvar1 = 0; monster[mid]._mgoalvar2 = 0; } if (monster[mid]._mmode != MM_STONE) { if (monster[mid].MType->mtype != MT_GOLEM) { NewMonsterAnim(mid, &monster[mid].MType->Anims[MA_GOTHIT], monster[mid]._mdir); monster[mid]._mmode = MM_GOTHIT; } monster[mid]._mxoff = 0; monster[mid]._myoff = 0; monster[mid]._mx = monster[mid]._moldx; monster[mid]._my = monster[mid]._moldy; monster[mid]._mfutx = monster[mid]._moldx; monster[mid]._mfuty = monster[mid]._moldy; M_ClearSquares(mid); dMonster[monster[mid]._mx][monster[mid]._my] = mid + 1; } } } void MonstStartKill(int i, int pnum, bool sendmsg) { int md; MonsterStruct *Monst; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; assurance(Monst->MType != NULL, i); if (pnum >= 0) Monst->mWhoHit |= 1 << pnum; if (pnum < MAX_PLRS && i >= MAX_PLRS) /// BUGFIX: i >= MAX_PLRS (fixed) AddPlrMonstExper(Monst->mLevel, Monst->mExp, Monst->mWhoHit); monstkills[Monst->MType->mtype]++; Monst->_mhitpoints = 0; SetRndSeed(Monst->_mRndSeed); SpawnLoot(i, sendmsg); if (Monst->MType->mtype == MT_DIABLO) M_DiabloDeath(i, true); else PlayEffect(i, 2); if (pnum >= 0) md = M_GetDir(i); else md = Monst->_mdir; Monst->_mdir = md; NewMonsterAnim(i, &Monst->MType->Anims[MA_DEATH], md); Monst->_mmode = MM_DEATH; Monst->_mgoal = MGOAL_NONE; Monst->_mxoff = 0; Monst->_myoff = 0; Monst->_mVar1 = 0; Monst->_mx = Monst->_moldx; Monst->_my = Monst->_moldy; Monst->_mfutx = Monst->_moldx; Monst->_mfuty = Monst->_moldy; M_ClearSquares(i); dMonster[Monst->_mx][Monst->_my] = i + 1; CheckQuestKill(i, sendmsg); M_FallenFear(Monst->_mx, Monst->_my); if ((Monst->MType->mtype >= MT_NACID && Monst->MType->mtype <= MT_XACID) || Monst->MType->mtype == MT_SPIDLORD) AddMissile(Monst->_mx, Monst->_my, 0, 0, 0, MIS_ACIDPUD, TARGET_PLAYERS, i, Monst->_mint + 1, 0); } void M2MStartKill(int i, int mid) { int md; assurance((DWORD)i < MAXMONSTERS, i); assurance((DWORD)mid < MAXMONSTERS, mid); assurance(monster[mid].MType != NULL, mid); /// BUGFIX: should check `mid` (fixed) delta_kill_monster(mid, monster[mid]._mx, monster[mid]._my, currlevel); NetSendCmdLocParam1(false, CMD_MONSTDEATH, monster[mid]._mx, monster[mid]._my, mid); if (i < MAX_PLRS) { monster[mid].mWhoHit |= 1 << i; if (mid >= MAX_PLRS) AddPlrMonstExper(monster[mid].mLevel, monster[mid].mExp, monster[mid].mWhoHit); } monstkills[monster[mid].MType->mtype]++; monster[mid]._mhitpoints = 0; SetRndSeed(monster[mid]._mRndSeed); SpawnLoot(mid, true); if (monster[mid].MType->mtype == MT_DIABLO) M_DiabloDeath(mid, true); else PlayEffect(mid, 2); md = (monster[i]._mdir - 4) & 7; if (monster[mid].MType->mtype == MT_GOLEM) md = 0; monster[mid]._mdir = md; NewMonsterAnim(mid, &monster[mid].MType->Anims[MA_DEATH], md); monster[mid]._mmode = MM_DEATH; monster[mid]._mxoff = 0; monster[mid]._myoff = 0; monster[mid]._mx = monster[mid]._moldx; monster[mid]._my = monster[mid]._moldy; monster[mid]._mfutx = monster[mid]._moldx; monster[mid]._mfuty = monster[mid]._moldy; M_ClearSquares(mid); dMonster[monster[mid]._mx][monster[mid]._my] = mid + 1; CheckQuestKill(mid, true); M_FallenFear(monster[mid]._mx, monster[mid]._my); if (monster[mid].MType->mtype >= MT_NACID && monster[mid].MType->mtype <= MT_XACID) AddMissile(monster[mid]._mx, monster[mid]._my, 0, 0, 0, MIS_ACIDPUD, TARGET_PLAYERS, mid, monster[mid]._mint + 1, 0); if (gbIsHellfire) M_StartStand(i, monster[i]._mdir); } void M_StartKill(int i, int pnum) { assurance((DWORD)i < MAXMONSTERS, i); if (myplr == pnum) { delta_kill_monster(i, monster[i]._mx, monster[i]._my, currlevel); if (i != pnum) { NetSendCmdLocParam1(false, CMD_MONSTDEATH, monster[i]._mx, monster[i]._my, i); } else { NetSendCmdLocParam1(false, CMD_KILLGOLEM, monster[i]._mx, monster[i]._my, currlevel); } } MonstStartKill(i, pnum, true); } void M_SyncStartKill(int i, int x, int y, int pnum) { assurance((DWORD)i < MAXMONSTERS, i); if (monster[i]._mhitpoints == 0 || monster[i]._mmode == MM_DEATH) { return; } if (dMonster[x][y] == 0) { M_ClearSquares(i); monster[i]._mx = x; monster[i]._my = y; monster[i]._moldx = x; monster[i]._moldy = y; } if (monster[i]._mmode == MM_STONE) { MonstStartKill(i, pnum, false); monster[i]._mmode = MM_STONE; } else { MonstStartKill(i, pnum, false); } } void M_StartFadein(int i, int md, bool backwards) { assurance((DWORD)i < MAXMONSTERS, i); assurance(monster[i].MType != NULL, i); NewMonsterAnim(i, &monster[i].MType->Anims[MA_SPECIAL], md); monster[i]._mmode = MM_FADEIN; monster[i]._mxoff = 0; monster[i]._myoff = 0; monster[i]._mfutx = monster[i]._mx; monster[i]._mfuty = monster[i]._my; monster[i]._moldx = monster[i]._mx; monster[i]._moldy = monster[i]._my; monster[i]._mdir = md; monster[i]._mFlags &= ~MFLAG_HIDDEN; if (backwards) { monster[i]._mFlags |= MFLAG_LOCK_ANIMATION; monster[i]._mAnimFrame = monster[i]._mAnimLen; } } void M_StartFadeout(int i, int md, bool backwards) { assurance((DWORD)i < MAXMONSTERS, i); assurance(monster[i].MType != NULL, i); assurance(monster[i].MType != NULL, i); NewMonsterAnim(i, &monster[i].MType->Anims[MA_SPECIAL], md); monster[i]._mmode = MM_FADEOUT; monster[i]._mxoff = 0; monster[i]._myoff = 0; monster[i]._mfutx = monster[i]._mx; monster[i]._mfuty = monster[i]._my; monster[i]._moldx = monster[i]._mx; monster[i]._moldy = monster[i]._my; monster[i]._mdir = md; if (backwards) { monster[i]._mFlags |= MFLAG_LOCK_ANIMATION; monster[i]._mAnimFrame = monster[i]._mAnimLen; } } void M_StartHeal(int i) { MonsterStruct *Monst; assurance((DWORD)i < MAXMONSTERS, i); assurance(monster[i].MType != NULL, i); Monst = &monster[i]; Monst->_mAnimData = Monst->MType->Anims[MA_SPECIAL].Data[Monst->_mdir]; Monst->_mAnimFrame = Monst->MType->Anims[MA_SPECIAL].Frames; Monst->_mFlags |= MFLAG_LOCK_ANIMATION; Monst->_mmode = MM_HEAL; Monst->_mVar1 = Monst->_mmaxhp / (16 * (random_(97, 5) + 4)); } void M_ChangeLightOffset(int monst) { int lx, ly, _mxoff, _myoff, sign; assurance((DWORD)monst < MAXMONSTERS, monst); lx = monster[monst]._mxoff + 2 * monster[monst]._myoff; ly = 2 * monster[monst]._myoff - monster[monst]._mxoff; if (lx < 0) { sign = -1; lx = -lx; } else { sign = 1; } _mxoff = sign * (lx >> 3); if (ly < 0) { _myoff = -1; ly = -ly; } else { _myoff = 1; } _myoff *= (ly >> 3); if (monster[monst].mlid != NO_LIGHT) ChangeLightOff(monster[monst].mlid, _mxoff, _myoff); } bool M_DoStand(int i) { MonsterStruct *Monst; commitment((DWORD)i < MAXMONSTERS, i); commitment(monster[i].MType != NULL, i); Monst = &monster[i]; if (Monst->MType->mtype == MT_GOLEM) Monst->_mAnimData = Monst->MType->Anims[MA_WALK].Data[Monst->_mdir]; else Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir]; if (Monst->_mAnimFrame == Monst->_mAnimLen) M_Enemy(i); Monst->_mVar2++; return false; } /** * @brief Continue movement towards new tile */ bool M_DoWalk(int i, int variant) { bool returnValue; commitment((DWORD)i < MAXMONSTERS, i); commitment(monster[i].MType != NULL, i); //Check if we reached new tile if (monster[i]._mVar8 == monster[i].MType->Anims[MA_WALK].Frames) { switch (variant) { case MM_WALK: dMonster[monster[i]._mx][monster[i]._my] = 0; monster[i]._mx += monster[i]._mVar1; monster[i]._my += monster[i]._mVar2; dMonster[monster[i]._mx][monster[i]._my] = i + 1; break; case MM_WALK2: dMonster[monster[i]._mVar1][monster[i]._mVar2] = 0; break; case MM_WALK3: dMonster[monster[i]._mx][monster[i]._my] = 0; monster[i]._mx = monster[i]._mVar1; monster[i]._my = monster[i]._mVar2; dFlags[monster[i]._mVar4][monster[i]._mVar5] &= ~BFLAG_MONSTLR; dMonster[monster[i]._mx][monster[i]._my] = i + 1; break; } if (monster[i].mlid != NO_LIGHT) ChangeLightXY(monster[i].mlid, monster[i]._mx, monster[i]._my); M_StartStand(i, monster[i]._mdir); returnValue = true; } else { //We didn't reach new tile so update monster's "sub-tile" position if (monster[i]._mAnimCnt == 0) { if (monster[i]._mVar8 == 0 && monster[i].MType->mtype == MT_FLESTHNG) PlayEffect(i, 3); monster[i]._mVar8++; monster[i]._mVar6 += monster[i]._mxvel; monster[i]._mVar7 += monster[i]._myvel; monster[i]._mxoff = monster[i]._mVar6 >> 4; monster[i]._myoff = monster[i]._mVar7 >> 4; } returnValue = false; } if (monster[i].mlid != NO_LIGHT) // BUGFIX: change uniqtype check to mlid check like it is in all other places (fixed) M_ChangeLightOffset(i); return returnValue; } void M_TryM2MHit(int i, int mid, int hper, int mind, int maxd) { bool ret; assurance((DWORD)mid < MAXMONSTERS, mid); assurance(monster[mid].MType != NULL, mid); if (monster[mid]._mhitpoints >> 6 > 0 && (monster[mid].MType->mtype != MT_ILLWEAV || monster[mid]._mgoal != MGOAL_RETREAT)) { int hit = random_(4, 100); if (monster[mid]._mmode == MM_STONE) hit = 0; if (!CheckMonsterHit(mid, &ret) && hit < hper) { int dam = (mind + random_(5, maxd - mind + 1)) << 6; monster[mid]._mhitpoints -= dam; if (monster[mid]._mhitpoints >> 6 <= 0) { if (monster[mid]._mmode == MM_STONE) { M2MStartKill(i, mid); monster[mid]._mmode = MM_STONE; } else { M2MStartKill(i, mid); } } else { if (monster[mid]._mmode == MM_STONE) { M2MStartHit(mid, i, dam); monster[mid]._mmode = MM_STONE; } else { M2MStartHit(mid, i, dam); } } } } } void M_TryH2HHit(int i, int pnum, int Hit, int MinDam, int MaxDam) { int hit, hper; int dx, dy; int blk, blkper; int dam, mdam; int newx, newy; int j, misnum, cur_ms_num, ac; assurance((DWORD)i < MAXMONSTERS, i); assurance(monster[i].MType != NULL, i); if (monster[i]._mFlags & MFLAG_TARGETS_MONSTER) { M_TryM2MHit(i, pnum, Hit, MinDam, MaxDam); return; } if (plr[pnum]._pHitPoints >> 6 <= 0 || plr[pnum]._pInvincible || plr[pnum]._pSpellFlags & 1) return; dx = abs(monster[i]._mx - plr[pnum]._px); dy = abs(monster[i]._my - plr[pnum]._py); if (dx >= 2 || dy >= 2) return; hper = random_(98, 100); #ifdef _DEBUG if (debug_mode_dollar_sign || debug_mode_key_inverted_v) hper = 1000; #endif ac = plr[pnum]._pIBonusAC + plr[pnum]._pIAC; if (plr[pnum].pDamAcFlags & 0x20 && monster[i].MData->mMonstClass == MC_DEMON) ac += 40; if (plr[pnum].pDamAcFlags & 0x40 && monster[i].MData->mMonstClass == MC_UNDEAD) ac += 20; hit = Hit + 2 * (monster[i].mLevel - plr[pnum]._pLevel) + 30 - ac - plr[pnum]._pDexterity / 5; if (hit < 15) hit = 15; if (currlevel == 14 && hit < 20) hit = 20; if (currlevel == 15 && hit < 25) hit = 25; if (currlevel == 16 && hit < 30) hit = 30; if ((plr[pnum]._pmode == PM_STAND || plr[pnum]._pmode == PM_ATTACK) && plr[pnum]._pBlockFlag) { blkper = random_(98, 100); } else { blkper = 100; } blk = plr[pnum]._pDexterity + plr[pnum]._pBaseToBlk - (monster[i].mLevel << 1) + (plr[pnum]._pLevel << 1); if (blk < 0) blk = 0; if (blk > 100) blk = 100; if (hper >= hit) return; if (blkper < blk) { direction dir = GetDirection(plr[pnum]._px, plr[pnum]._py, monster[i]._mx, monster[i]._my); StartPlrBlock(pnum, dir); if (pnum == myplr && plr[pnum].wReflections > 0) { plr[pnum].wReflections--; dam = random_(99, (MaxDam - MinDam + 1) << 6) + (MinDam << 6); dam += plr[pnum]._pIGetHit << 6; if (dam < 64) dam = 64; mdam = dam * (0.01 * (random_(100, 10) + 20)); monster[i]._mhitpoints -= mdam; dam -= mdam; if (dam < 0) dam = 0; if (monster[i]._mhitpoints >> 6 <= 0) M_StartKill(i, pnum); else M_StartHit(i, pnum, mdam); } return; } if (monster[i].MType->mtype == MT_YZOMBIE && pnum == myplr) { cur_ms_num = -1; for (j = 0; j < nummissiles; j++) { misnum = missileactive[j]; if (missile[misnum]._mitype != MIS_MANASHIELD) continue; if (missile[misnum]._misource == pnum) cur_ms_num = misnum; } if (plr[pnum]._pMaxHP > 64) { if (plr[pnum]._pMaxHPBase > 64) { plr[pnum]._pMaxHP -= 64; if (plr[pnum]._pHitPoints > plr[pnum]._pMaxHP) { plr[pnum]._pHitPoints = plr[pnum]._pMaxHP; if (cur_ms_num >= 0) missile[cur_ms_num]._miVar1 = plr[pnum]._pHitPoints; } plr[pnum]._pMaxHPBase -= 64; if (plr[pnum]._pHPBase > plr[pnum]._pMaxHPBase) { plr[pnum]._pHPBase = plr[pnum]._pMaxHPBase; if (cur_ms_num >= 0) missile[cur_ms_num]._miVar2 = plr[pnum]._pHPBase; } } } } dam = (MinDam << 6) + random_(99, (MaxDam - MinDam + 1) << 6); dam += (plr[pnum]._pIGetHit << 6); if (dam < 64) dam = 64; if (pnum == myplr) { if (plr[pnum].wReflections > 0) { plr[pnum].wReflections--; mdam = dam * (0.01 * (random_(100, 10) + 20)); monster[i]._mhitpoints -= mdam; dam -= mdam; if (dam < 0) dam = 0; if (monster[i]._mhitpoints >> 6 <= 0) M_StartKill(i, pnum); else M_StartHit(i, pnum, mdam); } plr[pnum]._pHitPoints -= dam; plr[pnum]._pHPBase -= dam; } if (plr[pnum]._pIFlags & ISPL_THORNS) { mdam = (random_(99, 3) + 1) << 6; monster[i]._mhitpoints -= mdam; if (monster[i]._mhitpoints >> 6 <= 0) M_StartKill(i, pnum); else M_StartHit(i, pnum, mdam); } if (!(monster[i]._mFlags & MFLAG_NOLIFESTEAL) && monster[i].MType->mtype == MT_SKING && gbIsMultiplayer) monster[i]._mhitpoints += dam; if (plr[pnum]._pHitPoints > plr[pnum]._pMaxHP) { plr[pnum]._pHitPoints = plr[pnum]._pMaxHP; plr[pnum]._pHPBase = plr[pnum]._pMaxHPBase; } if (plr[pnum]._pHitPoints >> 6 <= 0) { SyncPlrKill(pnum, 0); if (gbIsHellfire) M_StartStand(i, monster[i]._mdir); return; } StartPlrHit(pnum, dam, false); if (monster[i]._mFlags & MFLAG_KNOCKBACK) { if (plr[pnum]._pmode != PM_GOTHIT) StartPlrHit(pnum, 0, true); newx = plr[pnum]._px + offset_x[monster[i]._mdir]; newy = plr[pnum]._py + offset_y[monster[i]._mdir]; if (PosOkPlayer(pnum, newx, newy)) { plr[pnum]._px = newx; plr[pnum]._py = newy; FixPlayerLocation(pnum, plr[pnum]._pdir); FixPlrWalkTags(pnum); dPlayer[newx][newy] = pnum + 1; SetPlayerOld(pnum); } } } bool M_DoAttack(int i) { MonsterStruct *Monst; commitment((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; commitment(Monst->MType != NULL, i); commitment(Monst->MData != NULL, i); // BUGFIX: should check MData (fixed) if (monster[i]._mAnimFrame == monster[i].MData->mAFNum) { M_TryH2HHit(i, monster[i]._menemy, monster[i].mHit, monster[i].mMinDamage, monster[i].mMaxDamage); if (monster[i]._mAi != AI_SNAKE) PlayEffect(i, 0); } if (monster[i].MType->mtype >= MT_NMAGMA && monster[i].MType->mtype <= MT_WMAGMA && monster[i]._mAnimFrame == 9) { M_TryH2HHit(i, monster[i]._menemy, monster[i].mHit + 10, monster[i].mMinDamage - 2, monster[i].mMaxDamage - 2); PlayEffect(i, 0); } if (monster[i].MType->mtype >= MT_STORM && monster[i].MType->mtype <= MT_MAEL && monster[i]._mAnimFrame == 13) { M_TryH2HHit(i, monster[i]._menemy, monster[i].mHit - 20, monster[i].mMinDamage + 4, monster[i].mMaxDamage + 4); PlayEffect(i, 0); } if (monster[i]._mAi == AI_SNAKE && monster[i]._mAnimFrame == 1) PlayEffect(i, 0); if (monster[i]._mAnimFrame == monster[i]._mAnimLen) { M_StartStand(i, monster[i]._mdir); return true; } return false; } bool M_DoRAttack(int i) { int multimissiles, mi; commitment((DWORD)i < MAXMONSTERS, i); commitment(monster[i].MType != NULL, i); commitment(monster[i].MData != NULL, i); if (monster[i]._mAnimFrame == monster[i].MData->mAFNum) { if (monster[i]._mVar1 != -1) { if (monster[i]._mVar1 == MIS_CBOLT) multimissiles = 3; else multimissiles = 1; for (mi = 0; mi < multimissiles; mi++) { AddMissile( monster[i]._mx + (gbIsHellfire ? offset_x[monster[i]._mdir] : 0), monster[i]._my + (gbIsHellfire ? offset_y[monster[i]._mdir] : 0), monster[i]._menemyx, monster[i]._menemyy, monster[i]._mdir, monster[i]._mVar1, TARGET_PLAYERS, i, monster[i]._mVar2, 0); } } PlayEffect(i, 0); } if (monster[i]._mAnimFrame == monster[i]._mAnimLen) { M_StartStand(i, monster[i]._mdir); return true; } return false; } bool M_DoRSpAttack(int i) { commitment((DWORD)i < MAXMONSTERS, i); commitment(monster[i].MType != NULL, i); commitment(monster[i].MData != NULL, i); // BUGFIX: should check MData (fixed) if (monster[i]._mAnimFrame == monster[i].MData->mAFNum2 && monster[i]._mAnimCnt == 0) { AddMissile( monster[i]._mx + (gbIsHellfire ? offset_x[monster[i]._mdir] : 0), monster[i]._my + (gbIsHellfire ? offset_y[monster[i]._mdir] : 0), monster[i]._menemyx, monster[i]._menemyy, monster[i]._mdir, monster[i]._mVar1, TARGET_PLAYERS, i, monster[i]._mVar3, 0); PlayEffect(i, 3); } if (monster[i]._mAi == AI_MEGA && monster[i]._mAnimFrame == 3) { if (monster[i]._mVar2++ == 0) { monster[i]._mFlags |= MFLAG_ALLOW_SPECIAL; } else if (monster[i]._mVar2 == 15) { monster[i]._mFlags &= ~MFLAG_ALLOW_SPECIAL; } } if (monster[i]._mAnimFrame == monster[i]._mAnimLen) { M_StartStand(i, monster[i]._mdir); return true; } return false; } bool M_DoSAttack(int i) { commitment((DWORD)i < MAXMONSTERS, i); commitment(monster[i].MType != NULL, i); commitment(monster[i].MData != NULL, i); if (monster[i]._mAnimFrame == monster[i].MData->mAFNum2) M_TryH2HHit(i, monster[i]._menemy, monster[i].mHit2, monster[i].mMinDamage2, monster[i].mMaxDamage2); if (monster[i]._mAnimFrame == monster[i]._mAnimLen) { M_StartStand(i, monster[i]._mdir); return true; } return false; } bool M_DoFadein(int i) { commitment((DWORD)i < MAXMONSTERS, i); if ((!(monster[i]._mFlags & MFLAG_LOCK_ANIMATION) || monster[i]._mAnimFrame != 1) && (monster[i]._mFlags & MFLAG_LOCK_ANIMATION || monster[i]._mAnimFrame != monster[i]._mAnimLen)) { return false; } M_StartStand(i, monster[i]._mdir); monster[i]._mFlags &= ~MFLAG_LOCK_ANIMATION; return true; } bool M_DoFadeout(int i) { int mt; commitment((DWORD)i < MAXMONSTERS, i); if ((!(monster[i]._mFlags & MFLAG_LOCK_ANIMATION) || monster[i]._mAnimFrame != 1) && (monster[i]._mFlags & MFLAG_LOCK_ANIMATION || monster[i]._mAnimFrame != monster[i]._mAnimLen)) { return false; } mt = monster[i].MType->mtype; if (mt < MT_INCIN || mt > MT_HELLBURN) { monster[i]._mFlags &= ~MFLAG_LOCK_ANIMATION; monster[i]._mFlags |= MFLAG_HIDDEN; } else { monster[i]._mFlags &= ~MFLAG_LOCK_ANIMATION; } M_StartStand(i, monster[i]._mdir); return true; } bool M_DoHeal(int i) { MonsterStruct *Monst; commitment((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (monster[i]._mFlags & MFLAG_NOHEAL) { Monst->_mFlags &= ~MFLAG_ALLOW_SPECIAL; Monst->_mmode = MM_SATTACK; return false; } if (Monst->_mAnimFrame == 1) { Monst->_mFlags &= ~MFLAG_LOCK_ANIMATION; Monst->_mFlags |= MFLAG_ALLOW_SPECIAL; if (Monst->_mVar1 + Monst->_mhitpoints < Monst->_mmaxhp) { Monst->_mhitpoints = Monst->_mVar1 + Monst->_mhitpoints; } else { Monst->_mhitpoints = Monst->_mmaxhp; Monst->_mFlags &= ~MFLAG_ALLOW_SPECIAL; Monst->_mmode = MM_SATTACK; } } return false; } bool M_DoTalk(int i) { MonsterStruct *Monst; int tren; commitment((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; M_StartStand(i, monster[i]._mdir); Monst->_mgoal = MGOAL_TALKING; // CODEFIX: apply Monst instead of monster[i] in the rest of the function if (effect_is_playing(alltext[monster[i].mtalkmsg].sfxnr)) return false; InitQTextMsg(monster[i].mtalkmsg); if (monster[i]._uniqtype - 1 == UMT_GARBUD) { if (monster[i].mtalkmsg == TEXT_GARBUD1) { quests[Q_GARBUD]._qactive = QUEST_ACTIVE; quests[Q_GARBUD]._qlog = true; // BUGFIX: (?) for other quests qactive and qlog go together, maybe this should actually go into the if above (fixed) } if (monster[i].mtalkmsg == TEXT_GARBUD2 && !(monster[i]._mFlags & MFLAG_QUEST_COMPLETE)) { SpawnItem(i, monster[i]._mx + 1, monster[i]._my + 1, true); monster[i]._mFlags |= MFLAG_QUEST_COMPLETE; } } if (monster[i]._uniqtype - 1 == UMT_ZHAR && monster[i].mtalkmsg == TEXT_ZHAR1 && !(monster[i]._mFlags & MFLAG_QUEST_COMPLETE)) { quests[Q_ZHAR]._qactive = QUEST_ACTIVE; quests[Q_ZHAR]._qlog = true; CreateTypeItem(monster[i]._mx + 1, monster[i]._my + 1, false, ITYPE_MISC, IMISC_BOOK, true, false); monster[i]._mFlags |= MFLAG_QUEST_COMPLETE; } if (monster[i]._uniqtype - 1 == UMT_SNOTSPIL) { if (monster[i].mtalkmsg == TEXT_BANNER10 && !(monster[i]._mFlags & MFLAG_QUEST_COMPLETE)) { ObjChangeMap(setpc_x, setpc_y, (setpc_w >> 1) + setpc_x + 2, (setpc_h >> 1) + setpc_y - 2); tren = TransVal; TransVal = 9; DRLG_MRectTrans(setpc_x, setpc_y, (setpc_w >> 1) + setpc_x + 4, setpc_y + (setpc_h >> 1)); TransVal = tren; quests[Q_LTBANNER]._qvar1 = 2; if (quests[Q_LTBANNER]._qactive == QUEST_INIT) quests[Q_LTBANNER]._qactive = QUEST_ACTIVE; monster[i]._mFlags |= MFLAG_QUEST_COMPLETE; } if (quests[Q_LTBANNER]._qvar1 < 2) { app_fatal("SS Talk = %i, Flags = %i", monster[i].mtalkmsg, monster[i]._mFlags); } } if (monster[i]._uniqtype - 1 == UMT_LACHDAN) { if (monster[i].mtalkmsg == TEXT_VEIL9) { quests[Q_VEIL]._qactive = QUEST_ACTIVE; quests[Q_VEIL]._qlog = true; } if (monster[i].mtalkmsg == TEXT_VEIL11 && !(monster[i]._mFlags & MFLAG_QUEST_COMPLETE)) { SpawnUnique(UITEM_STEELVEIL, monster[i]._mx + 1, monster[i]._my + 1); monster[i]._mFlags |= MFLAG_QUEST_COMPLETE; } } if (monster[i]._uniqtype - 1 == UMT_WARLORD) quests[Q_WARLORD]._qvar1 = 2; if (monster[i]._uniqtype - 1 == UMT_LAZURUS && gbIsMultiplayer) { quests[Q_BETRAYER]._qvar1 = 6; monster[i]._mgoal = MGOAL_NORMAL; monster[i]._msquelch = UCHAR_MAX; monster[i].mtalkmsg = 0; } return false; } void M_Teleport(int i) { bool done; MonsterStruct *Monst; int k, j, x, y, _mx, _my, rx, ry; assurance((DWORD)i < MAXMONSTERS, i); done = false; Monst = &monster[i]; if (Monst->_mmode == MM_STONE) return; _mx = Monst->_menemyx; _my = Monst->_menemyy; rx = 2 * random_(100, 2) - 1; ry = 2 * random_(100, 2) - 1; for (j = -1; j <= 1 && !done; j++) { for (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->_mx && y != Monst->_my) { if (PosOkMonst(i, x, y)) done = true; } } } } if (done) { M_ClearSquares(i); dMonster[Monst->_mx][Monst->_my] = 0; dMonster[x][y] = i + 1; Monst->_moldx = x; Monst->_moldy = y; Monst->_mdir = M_GetDir(i); } } bool M_DoGotHit(int i) { commitment((DWORD)i < MAXMONSTERS, i); commitment(monster[i].MType != NULL, i); if (monster[i]._mAnimFrame == monster[i]._mAnimLen) { M_StartStand(i, monster[i]._mdir); return true; } return false; } void M_UpdateLeader(int i) { int ma, j; assurance((DWORD)i < MAXMONSTERS, i); for (j = 0; j < nummonsters; j++) { ma = monstactive[j]; if (monster[ma].leaderflag == 1 && monster[ma].leader == i) monster[ma].leaderflag = 0; } if (monster[i].leaderflag == 1) { monster[monster[i].leader].packsize--; } } void DoEnding() { bool bMusicOn; int musicVolume; if (gbIsMultiplayer) { SNetLeaveGame(LEAVE_ENDING); } music_stop(); if (gbIsMultiplayer) { SDL_Delay(1000); } if (gbIsSpawn) return; if (plr[myplr]._pClass == HeroClass::Warrior || plr[myplr]._pClass == HeroClass::Barbarian) { play_movie("gendata\\DiabVic2.smk", false); } else if (plr[myplr]._pClass == HeroClass::Sorcerer) { play_movie("gendata\\DiabVic1.smk", false); } else if (plr[myplr]._pClass == HeroClass::Monk) { play_movie("gendata\\DiabVic1.smk", false); } else { play_movie("gendata\\DiabVic3.smk", false); } play_movie("gendata\\Diabend.smk", false); bMusicOn = gbMusicOn; gbMusicOn = true; musicVolume = sound_get_or_set_music_volume(1); sound_get_or_set_music_volume(0); music_start(TMUSIC_L2); loop_movie = true; play_movie("gendata\\loopdend.smk", true); loop_movie = false; music_stop(); sound_get_or_set_music_volume(musicVolume); gbMusicOn = bMusicOn; } void PrepDoEnding() { int newKillLevel, i; DWORD *killLevel; gbSoundOn = sgbSaveSoundOn; gbRunGame = false; deathflag = false; cineflag = true; killLevel = &plr[myplr].pDiabloKillLevel; newKillLevel = sgGameInitInfo.nDifficulty + 1; if (*killLevel > newKillLevel) newKillLevel = *killLevel; plr[myplr].pDiabloKillLevel = newKillLevel; for (i = 0; i < MAX_PLRS; i++) { plr[i]._pmode = PM_QUIT; plr[i]._pInvincible = true; if (gbIsMultiplayer) { if (plr[i]._pHitPoints >> 6 == 0) plr[i]._pHitPoints = 64; if (plr[i]._pMana >> 6 == 0) plr[i]._pMana = 64; } } } bool M_DoDeath(int i) { int x, y; commitment((DWORD)i < MAXMONSTERS, i); commitment(monster[i].MType != NULL, i); monster[i]._mVar1++; if (monster[i].MType->mtype == MT_DIABLO) { x = monster[i]._mx - ViewX; if (x < 0) x = -1; else x = x > 0; ViewX += x; y = monster[i]._my - ViewY; if (y < 0) { y = -1; } else { y = y > 0; } ViewY += y; if (monster[i]._mVar1 == 140) PrepDoEnding(); } else if (monster[i]._mAnimFrame == monster[i]._mAnimLen) { if (monster[i]._uniqtype == 0) AddDead(monster[i]._mx, monster[i]._my, monster[i].MType->mdeadval, (direction)monster[i]._mdir); else AddDead(monster[i]._mx, monster[i]._my, monster[i]._udeadval, (direction)monster[i]._mdir); dMonster[monster[i]._mx][monster[i]._my] = 0; monster[i]._mDelFlag = true; M_UpdateLeader(i); } return false; } bool M_DoSpStand(int i) { commitment((DWORD)i < MAXMONSTERS, i); commitment(monster[i].MType != NULL, i); if (monster[i]._mAnimFrame == monster[i].MData->mAFNum2) PlayEffect(i, 3); if (monster[i]._mAnimFrame == monster[i]._mAnimLen) { M_StartStand(i, monster[i]._mdir); return true; } return false; } bool M_DoDelay(int i) { int oFrame; commitment((DWORD)i < MAXMONSTERS, i); commitment(monster[i].MType != NULL, i); monster[i]._mAnimData = monster[i].MType->Anims[MA_STAND].Data[M_GetDir(i)]; if (monster[i]._mAi == AI_LAZURUS) { if (monster[i]._mVar2 > 8 || monster[i]._mVar2 < 0) monster[i]._mVar2 = 8; } if (monster[i]._mVar2-- == 0) { oFrame = monster[i]._mAnimFrame; M_StartStand(i, monster[i]._mdir); monster[i]._mAnimFrame = oFrame; return true; } return false; } bool M_DoStone(int i) { commitment((DWORD)i < MAXMONSTERS, i); if (!monster[i]._mhitpoints) { dMonster[monster[i]._mx][monster[i]._my] = 0; monster[i]._mDelFlag = true; } return false; } void M_WalkDir(int i, int md) { int mwi; assurance((DWORD)i < MAXMONSTERS, i); mwi = monster[i].MType->Anims[MA_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; } } void GroupUnity(int i) { int leader, m, j; bool clear; assurance((DWORD)i < MAXMONSTERS, i); if (monster[i].leaderflag != 0) { leader = monster[i].leader; clear = LineClearF(CheckNoSolid, monster[i]._mx, monster[i]._my, monster[leader]._mfutx, monster[leader]._mfuty); if (clear || monster[i].leaderflag != 1) { if (clear && monster[i].leaderflag == 2 && abs(monster[i]._mx - monster[leader]._mfutx) < 4 && abs(monster[i]._my - monster[leader]._mfuty) < 4) { monster[leader].packsize++; monster[i].leaderflag = 1; } } else { monster[leader].packsize--; monster[i].leaderflag = 2; } } if (monster[i].leaderflag == 1) { if (monster[i]._msquelch > monster[leader]._msquelch) { monster[leader]._lastx = monster[i]._mx; monster[leader]._lasty = monster[i]._my; monster[leader]._msquelch = monster[i]._msquelch - 1; } if (monster[leader]._mAi == AI_GARG) { if (monster[leader]._mFlags & MFLAG_ALLOW_SPECIAL) { monster[leader]._mFlags &= ~MFLAG_ALLOW_SPECIAL; monster[leader]._mmode = MM_SATTACK; } } } else if (monster[i]._uniqtype != 0) { if (UniqMonst[monster[i]._uniqtype - 1].mUnqAttr & 2) { for (j = 0; j < nummonsters; j++) { m = monstactive[j]; if (monster[m].leaderflag == 1 && monster[m].leader == i) { if (monster[i]._msquelch > monster[m]._msquelch) { monster[m]._lastx = monster[i]._mx; monster[m]._lasty = monster[i]._my; monster[m]._msquelch = monster[i]._msquelch - 1; } if (monster[m]._mAi == AI_GARG) { if (monster[m]._mFlags & MFLAG_ALLOW_SPECIAL) { monster[m]._mFlags &= ~MFLAG_ALLOW_SPECIAL; monster[m]._mmode = MM_SATTACK; } } } } } } } bool M_CallWalk(int i, int md) { int mdtemp; bool ok; mdtemp = md; ok = DirOK(i, md); if (random_(101, 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 (random_(102, 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; } bool M_PathWalk(int i) { Sint8 path[MAX_PATH_LENGTH]; bool (*Check)(int, int, int); /** Maps from walking path step to facing direction. */ const Sint8 plr2monst[9] = { 0, 5, 3, 7, 1, 4, 6, 0, 2 }; commitment((DWORD)i < MAXMONSTERS, i); Check = PosOkMonst3; if (!(monster[i]._mFlags & MFLAG_CAN_OPEN_DOOR)) Check = PosOkMonst; if (FindPath(Check, i, monster[i]._mx, monster[i]._my, monster[i]._menemyx, monster[i]._menemyy, path)) { M_CallWalk(i, plr2monst[path[0]]); return true; } return false; } bool M_CallWalk2(int i, int md) { bool ok; int mdtemp; mdtemp = md; ok = DirOK(i, md); // Can we continue in the same direction if (random_(101, 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 (ok) M_WalkDir(i, mdtemp); return ok; } bool M_DumbWalk(int i, int md) { bool ok; ok = DirOK(i, md); if (ok) M_WalkDir(i, md); return ok; } bool M_RoundWalk(int i, int md, Sint32 *dir) { int mdtemp; bool ok; if (*dir) md = left[left[md]]; else md = right[right[md]]; mdtemp = md; ok = DirOK(i, md); if (!ok) { if (*dir) { md = right[mdtemp]; ok = DirOK(i, md) || (md = right[right[mdtemp]], DirOK(i, md)); } else { md = left[mdtemp]; ok = (DirOK(i, md) || (md = left[left[mdtemp]], DirOK(i, md))); } } if (ok) { M_WalkDir(i, md); } else { *dir = !*dir; ok = M_CallWalk(i, opposite[mdtemp]); } return ok; } void MAI_Zombie(int i) { MonsterStruct *Monst; int mx, my, md; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (Monst->_mmode != MM_STAND) { return; } mx = Monst->_mx; my = Monst->_my; if (!(dFlags[mx][my] & BFLAG_VISIBLE)) { return; } if (random_(103, 100) < 2 * Monst->_mint + 10) { md = std::max(abs(mx - Monst->_menemyx), abs(my - Monst->_menemyy)); if (md >= 2) { if (md >= 2 * Monst->_mint + 4) { md = Monst->_mdir; if (random_(104, 100) < 2 * Monst->_mint + 20) { md = random_(104, 8); } M_DumbWalk(i, md); } else { md = M_GetDir(i); M_CallWalk(i, md); } } else { M_StartAttack(i); } } if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir]; } void MAI_SkelSd(int i) { MonsterStruct *Monst; int mx, my, x, y, md; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) { return; } mx = Monst->_mx; my = Monst->_my; x = mx - Monst->_menemyx; y = my - Monst->_menemyy; md = GetDirection(mx, my, Monst->_lastx, Monst->_lasty); Monst->_mdir = md; if (abs(x) >= 2 || abs(y) >= 2) { if (Monst->_mVar1 == MM_DELAY || (random_(106, 100) >= 35 - 4 * Monst->_mint)) { M_CallWalk(i, md); } else { M_StartDelay(i, 15 - 2 * Monst->_mint + random_(106, 10)); } } else { if (Monst->_mVar1 == MM_DELAY || (random_(105, 100) < 2 * Monst->_mint + 20)) { M_StartAttack(i); } else { M_StartDelay(i, 2 * (5 - Monst->_mint) + random_(105, 10)); } } if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md]; } bool MAI_Path(int i) { MonsterStruct *Monst; bool clear; commitment((DWORD)i < MAXMONSTERS, i); Monst = &monster[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->_mx == 1 && Monst->_my == 0) return false; } clear = LineClearF1( PosOkMonst2, i, Monst->_mx, Monst->_my, Monst->_menemyx, Monst->_menemyy); if (!clear || (Monst->_pathcount >= 5 && Monst->_pathcount < 8)) { if (Monst->_mFlags & MFLAG_CAN_OPEN_DOOR) 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; } void MAI_Snake(int i) { MonsterStruct *Monst; int fx, fy, mx, my, md; int pnum; int tmp; assurance((DWORD)i < MAXMONSTERS, i); char pattern[6] = { 1, 1, 0, -1, -1, 0 }; Monst = &monster[i]; pnum = Monst->_menemy; if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) return; fx = Monst->_menemyx; fy = Monst->_menemyy; mx = Monst->_mx - fx; my = Monst->_my - fy; md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty); Monst->_mdir = md; if (abs(mx) >= 2 || abs(my) >= 2) { if (abs(mx) < 3 && abs(my) < 3 && LineClearF1(PosOkMonst, i, Monst->_mx, Monst->_my, fx, fy) && Monst->_mVar1 != MM_CHARGE) { if (AddMissile(Monst->_mx, Monst->_my, fx, fy, md, MIS_RHINO, pnum, i, 0, 0) != -1) { PlayEffect(i, 0); dMonster[Monst->_mx][Monst->_my] = -(i + 1); Monst->_mmode = MM_CHARGE; } } else if (Monst->_mVar1 == MM_DELAY || random_(106, 100) >= 35 - 2 * Monst->_mint) { if (md + pattern[Monst->_mgoalvar1] < 0) { tmp = md + pattern[Monst->_mgoalvar1] + 8; } else { tmp = md + pattern[Monst->_mgoalvar1] - 8; if (md + pattern[Monst->_mgoalvar1] < 8) tmp = md + pattern[Monst->_mgoalvar1]; } Monst->_mgoalvar1++; if (Monst->_mgoalvar1 > 5) Monst->_mgoalvar1 = 0; if (tmp - Monst->_mgoalvar2 < 0) { md = tmp - Monst->_mgoalvar2 + 8; } else if (tmp - Monst->_mgoalvar2 >= 8) { md = tmp - Monst->_mgoalvar2 - 8; } else md = tmp - Monst->_mgoalvar2; if (md > 0) { if (md < 4) { if (Monst->_mgoalvar2 + 1 < 0) { md = Monst->_mgoalvar2 + 9; } else if (Monst->_mgoalvar2 + 1 >= 8) { md = Monst->_mgoalvar2 - 7; } else md = Monst->_mgoalvar2 + 1; Monst->_mgoalvar2 = md; } else if (md == 4) { Monst->_mgoalvar2 = tmp; } else { if (Monst->_mgoalvar2 - 1 < 0) { md = Monst->_mgoalvar2 + 7; } else if (Monst->_mgoalvar2 - 1 >= 8) { md = Monst->_mgoalvar2 - 9; } else md = Monst->_mgoalvar2 - 1; Monst->_mgoalvar2 = md; } } if (!M_DumbWalk(i, Monst->_mgoalvar2)) M_CallWalk2(i, Monst->_mdir); } else { M_StartDelay(i, 15 - Monst->_mint + random_(106, 10)); } } else { if (Monst->_mVar1 == MM_DELAY || Monst->_mVar1 == MM_CHARGE || (random_(105, 100) < Monst->_mint + 20)) { M_StartAttack(i); } else M_StartDelay(i, 10 - Monst->_mint + random_(105, 10)); } if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir]; } void MAI_Bat(int i) { MonsterStruct *Monst; int md, v, pnum; int fx, fy, xd, yd; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; pnum = Monst->_menemy; if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) { return; } xd = Monst->_mx - Monst->_menemyx; yd = Monst->_my - Monst->_menemyy; md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty); Monst->_mdir = md; v = random_(107, 100); if (Monst->_mgoal == MGOAL_RETREAT) { if (!Monst->_mgoalvar1) { M_CallWalk(i, opposite[md]); Monst->_mgoalvar1++; } else { if (random_(108, 2) != 0) M_CallWalk(i, left[md]); else M_CallWalk(i, right[md]); Monst->_mgoal = MGOAL_NORMAL; } return; } fx = Monst->_menemyx; fy = Monst->_menemyy; if (Monst->MType->mtype == MT_GLOOM && (abs(xd) >= 5 || abs(yd) >= 5) && v < 4 * Monst->_mint + 33 && LineClearF1(PosOkMonst, i, Monst->_mx, Monst->_my, fx, fy)) { if (AddMissile(Monst->_mx, Monst->_my, fx, fy, md, MIS_RHINO, pnum, i, 0, 0) != -1) { dMonster[Monst->_mx][Monst->_my] = -(i + 1); Monst->_mmode = MM_CHARGE; } } else if (abs(xd) >= 2 || abs(yd) >= 2) { if ((Monst->_mVar2 > 20 && v < Monst->_mint + 13) || ((Monst->_mVar1 == MM_WALK || Monst->_mVar1 == MM_WALK2 || Monst->_mVar1 == MM_WALK3) && Monst->_mVar2 == 0 && v < Monst->_mint + 63)) { M_CallWalk(i, md); } } else if (v < 4 * Monst->_mint + 8) { M_StartAttack(i); Monst->_mgoal = MGOAL_RETREAT; Monst->_mgoalvar1 = 0; if (Monst->MType->mtype == MT_FAMILIAR) { AddMissile(Monst->_menemyx, Monst->_menemyy, Monst->_menemyx + 1, 0, -1, MIS_LIGHTNING, TARGET_PLAYERS, i, random_(109, 10) + 1, 0); } } if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md]; } void MAI_SkelBow(int i) { MonsterStruct *Monst; int mx, my, md, v; bool walking; walking = false; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) { return; } mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; md = M_GetDir(i); Monst->_mdir = md; v = random_(110, 100); 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]); } } mx = Monst->_menemyx; my = Monst->_menemyy; if (!walking) { if (random_(110, 100) < 2 * Monst->_mint + 3) { if (LineClear(Monst->_mx, Monst->_my, mx, my)) M_StartRAttack(i, MIS_ARROW, 4); } } if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md]; } void MAI_Fat(int i) { MonsterStruct *Monst; int mx, my, md, v; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) { return; } mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; md = M_GetDir(i); Monst->_mdir = md; v = random_(111, 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); } if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md]; } void MAI_Sneak(int i) { MonsterStruct *Monst; int mx, my, md; int dist, v; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (Monst->_mmode == MM_STAND) { mx = Monst->_mx; my = Monst->_my; if (dLight[mx][my] != lightmax) { mx -= Monst->_menemyx; my -= Monst->_menemyy; md = M_GetDir(i); dist = 5 - Monst->_mint; if (Monst->_mVar1 == MM_GOTHIT) { Monst->_mgoal = MGOAL_RETREAT; Monst->_mgoalvar1 = 0; } else { if (abs(mx) >= dist + 3 || abs(my) >= dist + 3 || Monst->_mgoalvar1 > 8) { Monst->_mgoal = MGOAL_NORMAL; Monst->_mgoalvar1 = 0; } } if (Monst->_mgoal == MGOAL_RETREAT && !(Monst->_mFlags & MFLAG_NO_ENEMY)) { if (Monst->_mFlags & MFLAG_TARGETS_MONSTER) md = GetDirection(Monst->_mx, Monst->_my, monster[Monst->_menemy]._mx, monster[Monst->_menemy]._my); else md = GetDirection(Monst->_mx, Monst->_my, plr[Monst->_menemy]._pownerx, plr[Monst->_menemy]._pownery); md = opposite[md]; if (Monst->MType->mtype == MT_UNSEEN) { if (random_(112, 2) != 0) md = left[md]; else md = right[md]; } } Monst->_mdir = md; v = random_(112, 100); if (abs(mx) < dist && abs(my) < dist && Monst->_mFlags & MFLAG_HIDDEN) { M_StartFadein(i, md, false); } else { if ((abs(mx) >= dist + 1 || abs(my) >= dist + 1) && !(Monst->_mFlags & MFLAG_HIDDEN)) { M_StartFadeout(i, md, true); } else { if (Monst->_mgoal == MGOAL_RETREAT || ((abs(mx) >= 2 || abs(my) >= 2) && ((Monst->_mVar2 > 20 && v < 4 * Monst->_mint + 14) || ((Monst->_mVar1 == MM_WALK || Monst->_mVar1 == MM_WALK2 || Monst->_mVar1 == MM_WALK3) && Monst->_mVar2 == 0 && v < 4 * Monst->_mint + 64)))) { Monst->_mgoalvar1++; M_CallWalk(i, md); } } } if (Monst->_mmode == MM_STAND) { if (abs(mx) >= 2 || abs(my) >= 2 || v >= 4 * Monst->_mint + 10) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md]; else M_StartAttack(i); } } } } void MAI_Fireman(int i) { int xd, yd; int md, pnum; int fx, fy; MonsterStruct *Monst; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (monster[i]._mmode != MM_STAND || Monst->_msquelch == 0) return; pnum = monster[i]._menemy; fx = monster[i]._menemyx; fy = monster[i]._menemyy; xd = monster[i]._mx - fx; yd = monster[i]._my - fy; md = M_GetDir(i); if (Monst->_mgoal == MGOAL_NORMAL) { if (LineClear(Monst->_mx, Monst->_my, fx, fy) && AddMissile(Monst->_mx, Monst->_my, 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 (LineClear(Monst->_mx, Monst->_my, fx, fy)) { M_StartRAttack(i, MIS_KRULL, 4); Monst->_mgoalvar1++; } else { M_StartDelay(i, random_(112, 10) + 5); Monst->_mgoalvar1++; } } else if (Monst->_mgoal == MGOAL_RETREAT) { M_StartFadein(i, md, false); Monst->_mgoal = MGOAL_ATTACK2; } Monst->_mdir = md; random_(112, 100); if (Monst->_mmode != MM_STAND) return; if (abs(xd) < 2 && abs(yd) < 2 && Monst->_mgoal == MGOAL_NORMAL) { M_TryH2HHit(i, monster[i]._menemy, monster[i].mHit, monster[i].mMinDamage, monster[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_Fallen(int i) { int x, y, xpos, ypos; int m, rad; MonsterStruct *Monst; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[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; } if (Monst->_mgoal == MGOAL_RETREAT) { if (Monst->_mgoalvar1-- == 0) { Monst->_mgoal = MGOAL_NORMAL; M_StartStand(i, opposite[Monst->_mdir]); } } if (Monst->_mAnimFrame == Monst->_mAnimLen) { if (random_(113, 4) != 0) { return; } if (!(monster[i]._mFlags & MFLAG_NOHEAL)) { // 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; } rad = 2 * Monst->_mint + 4; for (y = -rad; y <= rad; y++) { for (x = -rad; x <= rad; x++) { xpos = Monst->_mx + x; ypos = Monst->_my + y; if (y >= 0 && y < MAXDUNY && x >= 0 && x < MAXDUNX) { m = dMonster[xpos][ypos]; if (m > 0) { m--; if (monster[m]._mAi == AI_FALLEN) { monster[m]._mgoal = MGOAL_ATTACK2; monster[m]._mgoalvar1 = 30 * Monst->_mint + 105; } } } } } } else if (Monst->_mgoal == MGOAL_RETREAT) { M_CallWalk(i, Monst->_mdir); } else if (Monst->_mgoal == MGOAL_ATTACK2) { xpos = Monst->_mx - Monst->_menemyx; ypos = Monst->_my - Monst->_menemyy; if (abs(xpos) < 2 && abs(ypos) < 2) M_StartAttack(i); else M_CallWalk(i, M_GetDir(i)); } else MAI_SkelSd(i); } void MAI_Cleaver(int i) { MonsterStruct *Monst; int x, y, mx, my, md; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) { return; } mx = Monst->_mx; my = Monst->_my; x = mx - Monst->_menemyx; y = my - Monst->_menemyy; md = GetDirection(mx, my, Monst->_lastx, Monst->_lasty); Monst->_mdir = md; if (abs(x) >= 2 || abs(y) >= 2) M_CallWalk(i, md); else M_StartAttack(i); if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md]; } void MAI_Round(int i, bool special) { MonsterStruct *Monst; int fx, fy; int mx, my, md; int dist, v; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (Monst->_mmode == MM_STAND && Monst->_msquelch != 0) { fy = Monst->_menemyy; fx = Monst->_menemyx; mx = Monst->_mx - fx; my = Monst->_my - fy; md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty); if (Monst->_msquelch < UCHAR_MAX) MonstCheckDoors(i); v = random_(114, 100); if ((abs(mx) >= 2 || abs(my) >= 2) && Monst->_msquelch == UCHAR_MAX && dTransVal[Monst->_mx][Monst->_my] == dTransVal[fx][fy]) { if (Monst->_mgoal == MGOAL_MOVE || ((abs(mx) >= 4 || abs(my) >= 4) && random_(115, 4) == 0)) { if (Monst->_mgoal != MGOAL_MOVE) { Monst->_mgoalvar1 = 0; Monst->_mgoalvar2 = random_(116, 2); } Monst->_mgoal = MGOAL_MOVE; if (abs(mx) > abs(my)) dist = abs(mx); else dist = abs(my); if ((Monst->_mgoalvar1++ >= 2 * dist && DirOK(i, md)) || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) { Monst->_mgoal = MGOAL_NORMAL; } else if (!M_RoundWalk(i, md, &Monst->_mgoalvar2)) { M_StartDelay(i, random_(125, 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 >> 1) && random_(117, 2) != 0) M_StartSpAttack(i); else M_StartAttack(i); } } if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md]; } } void MAI_GoatMc(int i) { MAI_Round(i, true); } void MAI_Ranged(int i, int missile_type, bool special) { int md; int fx, fy, mx, my; MonsterStruct *Monst; assurance((DWORD)i < MAXMONSTERS, i); if (monster[i]._mmode != MM_STAND) { return; } Monst = &monster[i]; if (Monst->_msquelch == UCHAR_MAX || Monst->_mFlags & MFLAG_TARGETS_MONSTER) { fx = Monst->_menemyx; fy = Monst->_menemyy; mx = Monst->_mx - fx; my = Monst->_my - fy; md = M_GetDir(i); if (Monst->_msquelch < UCHAR_MAX) MonstCheckDoors(i); Monst->_mdir = md; if (Monst->_mVar1 == MM_RATTACK) { M_StartDelay(i, random_(118, 20)); } else if (abs(mx) < 4 && abs(my) < 4) { if (random_(119, 100) < 10 * (Monst->_mint + 7)) M_CallWalk(i, opposite[md]); } if (Monst->_mmode == MM_STAND) { if (LineClear(Monst->_mx, Monst->_my, fx, fy)) { if (special) M_StartRSpAttack(i, missile_type, 4); else M_StartRAttack(i, missile_type, 4); } else { Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md]; } } } else if (Monst->_msquelch != 0) { fx = Monst->_lastx; fy = Monst->_lasty; md = GetDirection(Monst->_mx, Monst->_my, fx, fy); M_CallWalk(i, md); } } 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) { MAI_Ranged(i, MIS_FIREBOLT, false); } void MAI_Torchant(int i) { MAI_Ranged(i, MIS_FIREBALL, false); } void MAI_Scav(int i) { bool done; int x, y; MonsterStruct *Monst; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; done = false; if (monster[i]._mmode != MM_STAND) return; if (Monst->_mhitpoints < (Monst->_mmaxhp >> 1) && Monst->_mgoal != MGOAL_HEALING) { if (Monst->leaderflag != 0) { monster[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->_mx][Monst->_my] != 0) { M_StartEat(i); if (!(Monst->_mFlags & MFLAG_NOHEAL)) { if (gbIsHellfire) { int mMaxHP = Monst->_mmaxhp; // BUGFIX use _mmaxhp or we loose health when difficulty isn't normal (fixed) Monst->_mhitpoints += mMaxHP >> 3; if (Monst->_mhitpoints > Monst->_mmaxhp) Monst->_mhitpoints = Monst->_mmaxhp; if (Monst->_mgoalvar3 <= 0 || Monst->_mhitpoints == Monst->_mmaxhp) dDead[Monst->_mx][Monst->_my] = 0; } else { Monst->_mhitpoints += 64; } } int targetHealth = Monst->_mmaxhp; if (!gbIsHellfire) targetHealth = (Monst->_mmaxhp >> 1) + (Monst->_mmaxhp >> 2); if (Monst->_mhitpoints >= targetHealth) { Monst->_mgoal = MGOAL_NORMAL; Monst->_mgoalvar1 = 0; Monst->_mgoalvar2 = 0; } } else { if (Monst->_mgoalvar1 == 0) { if (random_(120, 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->_mx + x][Monst->_my + y] != 0 && LineClearF( CheckNoSolid, Monst->_mx, Monst->_my, Monst->_mx + x, Monst->_my + 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->_mx + x][Monst->_my + y] != 0 && LineClearF( CheckNoSolid, Monst->_mx, Monst->_my, Monst->_mx + x, Monst->_my + y); } } x++; y++; } if (done) { Monst->_mgoalvar1 = x + Monst->_mx + 1; Monst->_mgoalvar2 = y + Monst->_my + 1; } } if (Monst->_mgoalvar1) { x = Monst->_mgoalvar1 - 1; y = Monst->_mgoalvar2 - 1; Monst->_mdir = GetDirection(Monst->_mx, Monst->_my, x, y); M_CallWalk(i, Monst->_mdir); } } } if (Monst->_mmode == MM_STAND) MAI_SkelSd(i); } void MAI_Garg(int i) { MonsterStruct *Monst; int mx, my, dx, dy, md; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; dx = Monst->_mx - Monst->_lastx; dy = Monst->_my - Monst->_lasty; md = M_GetDir(i); if (Monst->_msquelch != 0 && Monst->_mFlags & MFLAG_ALLOW_SPECIAL) { M_Enemy(i); mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; if (abs(mx) < Monst->_mint + 2 && abs(my) < Monst->_mint + 2) { Monst->_mFlags &= ~MFLAG_ALLOW_SPECIAL; } return; } if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) { return; } if (Monst->_mhitpoints < (Monst->_mmaxhp >> 1)) if (!(Monst->_mFlags & MFLAG_NOHEAL)) 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; } } MAI_Round(i, false); } void MAI_RoundRanged(int i, int missile_type, bool checkdoors, int dam, int lessmissiles) { MonsterStruct *Monst; int mx, my; int fx, fy; int md, dist, v; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (Monst->_mmode == MM_STAND && Monst->_msquelch != 0) { fx = Monst->_menemyx; fy = Monst->_menemyy; mx = Monst->_mx - fx; my = Monst->_my - fy; md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty); if (checkdoors && Monst->_msquelch < UCHAR_MAX) MonstCheckDoors(i); v = random_(121, 10000); dist = std::max(abs(mx), abs(my)); if (dist >= 2 && Monst->_msquelch == UCHAR_MAX && dTransVal[Monst->_mx][Monst->_my] == dTransVal[fx][fy]) { if (Monst->_mgoal == MGOAL_MOVE || (dist >= 3 && random_(122, 4 << lessmissiles) == 0)) { if (Monst->_mgoal != MGOAL_MOVE) { Monst->_mgoalvar1 = 0; Monst->_mgoalvar2 = random_(123, 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) && (LineClear(Monst->_mx, Monst->_my, fx, fy))) { M_StartRSpAttack(i, missile_type, 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)) && LineClear(Monst->_mx, Monst->_my, fx, fy)) { M_StartRSpAttack(i, missile_type, dam); } else if (dist >= 2) { v = random_(124, 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, random_(125, 10) + 5); } } } void MAI_Magma(int i) { MAI_RoundRanged(i, MIS_MAGMABALL, true, 4, 0); } void MAI_Storm(int i) { MAI_RoundRanged(i, MIS_LIGHTCTRL2, true, 4, 0); } void MAI_BoneDemon(int i) { MAI_RoundRanged(i, MIS_BONEDEMON, true, 4, 0); } void MAI_Acid(int i) { MAI_RoundRanged(i, MIS_ACID, false, 4, 1); } void MAI_Diablo(int i) { MAI_RoundRanged(i, MIS_DIABAPOCA, false, 40, 0); } void MAI_RR2(int i, int mistype, int dam) { MonsterStruct *Monst; int mx, my, fx, fy; int dist, v, md; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; if (abs(mx) >= 5 || abs(my) >= 5) { MAI_SkelSd(i); return; } if (Monst->_mmode == MM_STAND && Monst->_msquelch != 0) { fx = Monst->_menemyx; fy = Monst->_menemyy; mx = Monst->_mx - fx; my = Monst->_my - fy; md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty); if (Monst->_msquelch < UCHAR_MAX) MonstCheckDoors(i); v = random_(121, 100); dist = std::max(abs(mx), abs(my)); if (dist >= 2 && Monst->_msquelch == UCHAR_MAX && dTransVal[Monst->_mx][Monst->_my] == dTransVal[fx][fy]) { if (Monst->_mgoal == MGOAL_MOVE || dist >= 3) { if (Monst->_mgoal != MGOAL_MOVE) { Monst->_mgoalvar1 = 0; Monst->_mgoalvar2 = random_(123, 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 (Monst->_mgoal == MGOAL_NORMAL) { if (((dist >= 3 && v < 5 * (Monst->_mint + 2)) || v < 5 * (Monst->_mint + 1) || Monst->_mgoalvar3 == 4) && LineClear(Monst->_mx, Monst->_my, fx, fy)) { M_StartRSpAttack(i, mistype, dam); } else if (dist >= 2) { v = random_(124, 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 (random_(124, 100) < 10 * (Monst->_mint + 4)) { Monst->_mdir = md; if (random_(124, 2) != 0) M_StartAttack(i); else M_StartRSpAttack(i, mistype, dam); } } Monst->_mgoalvar3 = 1; } if (Monst->_mmode == MM_STAND) { M_StartDelay(i, random_(125, 10) + 5); } } } void MAI_Mega(int i) { MAI_RR2(i, MIS_FLAMEC, 0); } void MAI_Golum(int i) { int mx, my, _mex, _mey; int md, j, k, _menemy; MonsterStruct *Monst; bool have_enemy, ok; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (Monst->_mx == 1 && Monst->_my == 0) { return; } if (Monst->_mmode == MM_DEATH || Monst->_mmode == MM_SPSTAND || (Monst->_mmode >= MM_WALK && Monst->_mmode <= MM_WALK3)) { return; } if (!(Monst->_mFlags & MFLAG_TARGETS_MONSTER)) M_Enemy(i); have_enemy = !(monster[i]._mFlags & MFLAG_NO_ENEMY); if (Monst->_mmode == MM_ATTACK) { return; } _menemy = monster[i]._menemy; mx = monster[i]._mx; my = monster[i]._my; _mex = mx - monster[_menemy]._mfutx; _mey = my - monster[_menemy]._mfuty; md = GetDirection(mx, my, monster[_menemy]._mx, monster[_menemy]._my); monster[i]._mdir = md; if (abs(_mex) < 2 && abs(_mey) < 2 && have_enemy) { _menemy = monster[i]._menemy; monster[i]._menemyx = monster[_menemy]._mx; monster[i]._menemyy = monster[_menemy]._my; if (monster[_menemy]._msquelch == 0) { monster[_menemy]._msquelch = UCHAR_MAX; monster[monster[i]._menemy]._lastx = monster[i]._mx; monster[monster[i]._menemy]._lasty = monster[i]._my; for (j = 0; j < 5; j++) { for (k = 0; k < 5; k++) { _menemy = dMonster[monster[i]._mx + k - 2][monster[i]._my + j - 2]; if (_menemy > 0) monster[_menemy - 1]._msquelch = UCHAR_MAX; // BUGFIX: should be `monster[_menemy-1]`, not monster[_menemy]. (fixed) } } } M_StartAttack(i); return; } if (have_enemy && MAI_Path(i)) return; monster[i]._pathcount++; if (monster[i]._pathcount > 8) monster[i]._pathcount = 5; ok = M_CallWalk(i, plr[i]._pdir); if (ok) return; md = (md - 1) & 7; for (j = 0; j < 8 && !ok; j++) { md = (md + 1) & 7; ok = DirOK(i, md); } if (ok) M_WalkDir(i, md); } void MAI_SkelKing(int i) { MonsterStruct *Monst; int mx, my, fx, fy, nx, ny; int dist, v, md; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (Monst->_mmode == MM_STAND && Monst->_msquelch != 0) { fx = Monst->_menemyx; fy = Monst->_menemyy; mx = Monst->_mx - fx; my = Monst->_my - fy; md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty); if (Monst->_msquelch < UCHAR_MAX) MonstCheckDoors(i); v = random_(126, 100); dist = std::max(abs(mx), abs(my)); if (dist >= 2 && Monst->_msquelch == UCHAR_MAX && dTransVal[Monst->_mx][Monst->_my] == dTransVal[fx][fy]) { if (Monst->_mgoal == MGOAL_MOVE || ((abs(mx) >= 3 || abs(my) >= 3) && random_(127, 4) == 0)) { if (Monst->_mgoal != MGOAL_MOVE) { Monst->_mgoalvar1 = 0; Monst->_mgoalvar2 = random_(128, 2); } Monst->_mgoal = MGOAL_MOVE; if ((Monst->_mgoalvar1++ >= 2 * dist && DirOK(i, md)) || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) { Monst->_mgoal = MGOAL_NORMAL; } else if (!M_RoundWalk(i, md, &Monst->_mgoalvar2)) { M_StartDelay(i, random_(125, 10) + 10); } } } else Monst->_mgoal = MGOAL_NORMAL; if (Monst->_mgoal == MGOAL_NORMAL) { if (!gbIsMultiplayer && ((dist >= 3 && v < 4 * Monst->_mint + 35) || v < 6) && LineClear(Monst->_mx, Monst->_my, fx, fy)) { nx = Monst->_mx + offset_x[md]; ny = Monst->_my + offset_y[md]; if (PosOkMonst(i, nx, ny) && nummonsters < MAXMONSTERS) { M_SpawnSkel(nx, ny, md); M_StartSpStand(i, md); } } else { if (dist >= 2) { v = random_(129, 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, random_(130, 10) + 10); } else { M_CallWalk(i, md); } } else if (v < Monst->_mint + 20) { Monst->_mdir = md; M_StartAttack(i); } } } if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md]; } } void MAI_Rhino(int i) { MonsterStruct *Monst; int mx, my, fx, fy; int v, dist, md; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (Monst->_mmode == MM_STAND && Monst->_msquelch != 0) { fx = Monst->_menemyx; fy = Monst->_menemyy; mx = Monst->_mx - fx; my = Monst->_my - fy; md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty); if (Monst->_msquelch < UCHAR_MAX) MonstCheckDoors(i); v = random_(131, 100); dist = std::max(abs(mx), abs(my)); if (dist >= 2) { if (Monst->_mgoal == MGOAL_MOVE || (dist >= 5 && random_(132, 4) != 0)) { if (Monst->_mgoal != MGOAL_MOVE) { Monst->_mgoalvar1 = 0; Monst->_mgoalvar2 = random_(133, 2); } Monst->_mgoal = MGOAL_MOVE; if (Monst->_mgoalvar1++ >= 2 * dist || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) { Monst->_mgoal = MGOAL_NORMAL; } else if (!M_RoundWalk(i, md, &Monst->_mgoalvar2)) { M_StartDelay(i, random_(125, 10) + 10); } } } else Monst->_mgoal = MGOAL_NORMAL; if (Monst->_mgoal == MGOAL_NORMAL) { if (dist >= 5 && v < 2 * Monst->_mint + 43 && LineClearF1(PosOkMonst, i, Monst->_mx, Monst->_my, fx, fy)) { if (AddMissile(Monst->_mx, Monst->_my, fx, fy, md, MIS_RHINO, Monst->_menemy, i, 0, 0) != -1) { if (Monst->MData->snd_special) PlayEffect(i, 3); dMonster[Monst->_mx][Monst->_my] = -(i + 1); Monst->_mmode = MM_CHARGE; } } else { if (dist >= 2) { v = random_(134, 100); if (v >= 2 * Monst->_mint + 33 && ((Monst->_mVar1 != MM_WALK && Monst->_mVar1 != MM_WALK2 && Monst->_mVar1 != MM_WALK3) || Monst->_mVar2 || v >= 2 * Monst->_mint + 83)) { M_StartDelay(i, random_(135, 10) + 10); } else { M_CallWalk(i, md); } } else if (v < 2 * Monst->_mint + 28) { Monst->_mdir = md; M_StartAttack(i); } } } if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir]; } } void MAI_HorkDemon(int i) { MonsterStruct *Monst; int fx, fy, mx, my, md, v, dist; if ((DWORD)i >= MAXMONSTERS) { return; } Monst = &monster[i]; if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) { return; } fx = Monst->_menemyx; fy = Monst->_menemyy; mx = Monst->_mx - fx; my = Monst->_my - fy; md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty); if (Monst->_msquelch < 255) { MonstCheckDoors(i); } v = random_(131, 100); if (abs(mx) < 2 && abs(my) < 2) { Monst->_mgoal = MGOAL_NORMAL; } else if (Monst->_mgoal == 4 || ((abs(mx) >= 5 || abs(my) >= 5) && random_(132, 4) != 0)) { if (Monst->_mgoal != 4) { Monst->_mgoalvar1 = 0; Monst->_mgoalvar2 = random_(133, 2); } Monst->_mgoal = MGOAL_MOVE; if (abs(mx) > abs(my)) { dist = abs(mx); } else { dist = abs(my); } if (Monst->_mgoalvar1++ >= 2 * dist || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) { Monst->_mgoal = MGOAL_NORMAL; } else if (!M_RoundWalk(i, md, &Monst->_mgoalvar2)) { M_StartDelay(i, random_(125, 10) + 10); } } if (Monst->_mgoal == 1) { if ((abs(mx) >= 3 || abs(my) >= 3) && v < 2 * Monst->_mint + 43) { if (PosOkMonst(i, Monst->_mx + offset_x[Monst->_mdir], Monst->_my + offset_y[Monst->_mdir]) && nummonsters < 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 = random_(134, 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, random_(135, 10) + 10); } } } if (Monst->_mmode == MM_STAND) { Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir]; } } void MAI_Counselor(int i) { int mx, my, fx, fy; int dist, md, v; MonsterStruct *Monst; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (Monst->_mmode == MM_STAND && Monst->_msquelch != 0) { fx = Monst->_menemyx; fy = Monst->_menemyy; mx = Monst->_mx - fx; my = Monst->_my - fy; md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty); if (Monst->_msquelch < UCHAR_MAX) MonstCheckDoors(i); v = random_(121, 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) { dist = std::max(abs(mx), abs(my)); if (dist >= 2 && Monst->_msquelch == UCHAR_MAX && dTransVal[Monst->_mx][Monst->_my] == 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 { Monst->_mgoal = MGOAL_NORMAL; M_StartFadein(i, md, true); } } else if (Monst->_mgoal == MGOAL_NORMAL) { if (abs(mx) >= 2 || abs(my) >= 2) { if (v < 5 * (Monst->_mint + 10) && LineClear(Monst->_mx, Monst->_my, fx, fy)) { M_StartRAttack(i, counsmiss[Monst->_mint], Monst->mMinDamage + random_(77, Monst->mMaxDamage - Monst->mMinDamage + 1)); } else if (random_(124, 100) < 30) { Monst->_mgoal = MGOAL_MOVE; Monst->_mgoalvar1 = 0; M_StartFadeout(i, md, false); } else M_StartDelay(i, random_(105, 10) + 2 * (5 - Monst->_mint)); } else { Monst->_mdir = md; if (Monst->_mhitpoints < (Monst->_mmaxhp >> 1)) { Monst->_mgoal = MGOAL_RETREAT; Monst->_mgoalvar1 = 0; M_StartFadeout(i, md, false); } else if (Monst->_mVar1 == MM_DELAY || random_(105, 100) < 2 * Monst->_mint + 20) { M_StartRAttack(i, -1, 0); AddMissile(Monst->_mx, Monst->_my, 0, 0, Monst->_mdir, MIS_FLASH, TARGET_PLAYERS, i, 4, 0); AddMissile(Monst->_mx, Monst->_my, 0, 0, Monst->_mdir, MIS_FLASH2, TARGET_PLAYERS, i, 4, 0); } else M_StartDelay(i, random_(105, 10) + 2 * (5 - Monst->_mint)); } } if (Monst->_mmode == MM_STAND) { M_StartDelay(i, random_(125, 10) + 5); } } } void MAI_Garbud(int i) { int _mx, _my, md; MonsterStruct *Monst; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (Monst->_mmode != MM_STAND) { return; } _mx = Monst->_mx; _my = Monst->_my; md = M_GetDir(i); if (Monst->mtalkmsg >= TEXT_GARBUD1 && Monst->mtalkmsg <= TEXT_GARBUD3 && !(dFlags[_mx][_my] & BFLAG_VISIBLE) && 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; } } if (dFlags[_mx][_my] & BFLAG_VISIBLE) { if (Monst->mtalkmsg == TEXT_GARBUD4) { if (!effect_is_playing(USFX_GARBUD4) && Monst->_mgoal == MGOAL_TALKING) { Monst->_mgoal = MGOAL_NORMAL; Monst->_msquelch = UCHAR_MAX; Monst->mtalkmsg = 0; } } } if (Monst->_mgoal == MGOAL_NORMAL || Monst->_mgoal == MGOAL_MOVE) MAI_Round(i, true); monster[i]._mdir = md; if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md]; } void MAI_Zhar(int i) { int mx, my, md; MonsterStruct *Monst; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (monster[i]._mmode != MM_STAND) { return; } mx = Monst->_mx; my = Monst->_my; md = M_GetDir(i); if (Monst->mtalkmsg == TEXT_ZHAR1 && !(dFlags[mx][my] & BFLAG_VISIBLE) && Monst->_mgoal == MGOAL_TALKING) { Monst->mtalkmsg = TEXT_ZHAR2; Monst->_mgoal = MGOAL_INQUIRING; } if (dFlags[mx][my] & BFLAG_VISIBLE) { if (Monst->mtalkmsg == TEXT_ZHAR2) { if (!effect_is_playing(USFX_ZHAR2) && Monst->_mgoal == MGOAL_TALKING) { Monst->_msquelch = UCHAR_MAX; Monst->mtalkmsg = 0; Monst->_mgoal = MGOAL_NORMAL; } } } if (Monst->_mgoal == MGOAL_NORMAL || Monst->_mgoal == MGOAL_RETREAT || Monst->_mgoal == MGOAL_MOVE) MAI_Counselor(i); Monst->_mdir = md; if (monster[i]._mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md]; } void MAI_SnotSpil(int i) { int mx, my, md; MonsterStruct *Monst; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (monster[i]._mmode != MM_STAND) { return; } mx = Monst->_mx; my = Monst->_my; md = M_GetDir(i); if (Monst->mtalkmsg == TEXT_BANNER10 && !(dFlags[mx][my] & BFLAG_VISIBLE) && Monst->_mgoal == MGOAL_TALKING) { Monst->mtalkmsg = TEXT_BANNER11; Monst->_mgoal = MGOAL_INQUIRING; } if (Monst->mtalkmsg == TEXT_BANNER11 && quests[Q_LTBANNER]._qvar1 == 3) { Monst->mtalkmsg = 0; Monst->_mgoal = MGOAL_NORMAL; } if (dFlags[mx][my] & BFLAG_VISIBLE) { 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 = UCHAR_MAX; Monst->mtalkmsg = 0; Monst->_mgoal = MGOAL_NORMAL; } } if (quests[Q_LTBANNER]._qvar1 == 3) { if (Monst->_mgoal == MGOAL_NORMAL || Monst->_mgoal == MGOAL_ATTACK2) MAI_Fallen(i); } } Monst->_mdir = md; if (monster[i]._mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md]; } void MAI_Lazurus(int i) { int mx, my, md; MonsterStruct *Monst; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (monster[i]._mmode != MM_STAND) { return; } mx = Monst->_mx; my = Monst->_my; md = M_GetDir(i); if (dFlags[mx][my] & BFLAG_VISIBLE) { if (!gbIsMultiplayer) { if (Monst->mtalkmsg == TEXT_VILE13 && Monst->_mgoal == MGOAL_INQUIRING && plr[myplr]._px == 35 && plr[myplr]._py == 46) { PlayInGameMovie("gendata\\fprst3.smk"); Monst->_mmode = MM_TALK; quests[Q_BETRAYER]._qvar1 = 5; } 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 = UCHAR_MAX; Monst->mtalkmsg = 0; } } 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 == 0) { // Fix save games affected by teleport bug ObjChangeMapResync(1, 18, 20, 24); RedoPlayerVision(); quests[Q_BETRAYER]._qvar1 = 6; } Monst->mtalkmsg = 0; MAI_Counselor(i); } Monst->_mdir = md; if (monster[i]._mmode == MM_STAND || monster[i]._mmode == MM_TALK) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md]; } void MAI_Lazhelp(int i) { int _mx, _my; volatile int md; // BUGFIX: very questionable volatile MonsterStruct *Monst; assurance((DWORD)i < MAXMONSTERS, i); if (monster[i]._mmode != MM_STAND) return; Monst = &monster[i]; _mx = Monst->_mx; _my = Monst->_my; md = M_GetDir(i); if (dFlags[_mx][_my] & BFLAG_VISIBLE) { if (!gbIsMultiplayer) { if (quests[Q_BETRAYER]._qvar1 <= 5) { Monst->_mgoal = MGOAL_INQUIRING; } else { Monst->_mgoal = MGOAL_NORMAL; Monst->mtalkmsg = 0; } } else Monst->_mgoal = MGOAL_NORMAL; } if (Monst->_mgoal == MGOAL_NORMAL) MAI_Succ(i); Monst->_mdir = md; if (monster[i]._mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md]; } void MAI_Lachdanan(int i) { int _mx, _my, md; MonsterStruct *Monst; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (monster[i]._mmode != MM_STAND) { return; } _mx = Monst->_mx; _my = Monst->_my; md = M_GetDir(i); if (Monst->mtalkmsg == TEXT_VEIL9 && !(dFlags[_mx][_my] & BFLAG_VISIBLE) && monster[i]._mgoal == MGOAL_TALKING) { Monst->mtalkmsg = TEXT_VEIL10; monster[i]._mgoal = MGOAL_INQUIRING; } if (dFlags[_mx][_my] & BFLAG_VISIBLE) { if (Monst->mtalkmsg == TEXT_VEIL11) { if (!effect_is_playing(USFX_LACH3) && Monst->_mgoal == MGOAL_TALKING) { Monst->mtalkmsg = 0; quests[Q_VEIL]._qactive = QUEST_DONE; M_StartKill(i, -1); } } } Monst->_mdir = md; if (monster[i]._mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md]; } void MAI_Warlord(int i) { MonsterStruct *Monst; int mx, my, md; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; if (monster[i]._mmode != MM_STAND) { return; } mx = Monst->_mx; my = Monst->_my; md = M_GetDir(i); if (dFlags[mx][my] & BFLAG_VISIBLE) { 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 = UCHAR_MAX; Monst->mtalkmsg = 0; Monst->_mgoal = MGOAL_NORMAL; } } if (Monst->_mgoal == MGOAL_NORMAL) MAI_SkelSd(i); Monst->_mdir = md; if (monster[i]._mmode == MM_STAND || monster[i]._mmode == MM_TALK) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir]; } void DeleteMonsterList() { int i; for (i = 0; i < MAX_PLRS; i++) { if (monster[i]._mDelFlag) { monster[i]._mx = 1; monster[i]._my = 0; monster[i]._mfutx = 0; monster[i]._mfuty = 0; monster[i]._moldx = 0; monster[i]._moldy = 0; monster[i]._mDelFlag = false; } } i = MAX_PLRS; while (i < nummonsters) { if (monster[monstactive[i]]._mDelFlag) { DeleteMonster(i); i = 0; // TODO: check if this should be MAX_PLRS. } else { i++; } } } void ProcessMonsters() { int i, mi, mx, my, _menemy; bool raflag; MonsterStruct *Monst; DeleteMonsterList(); assert((DWORD)nummonsters <= MAXMONSTERS); for (i = 0; i < nummonsters; i++) { mi = monstactive[i]; Monst = &monster[mi]; raflag = false; if (gbIsMultiplayer) { SetRndSeed(Monst->_mAISeed); Monst->_mAISeed = AdvanceRndSeed(); } if (!(monster[mi]._mFlags & MFLAG_NOHEAL) && Monst->_mhitpoints < Monst->_mmaxhp && Monst->_mhitpoints >> 6 > 0) { if (Monst->mLevel > 1) { Monst->_mhitpoints += Monst->mLevel >> 1; } else { Monst->_mhitpoints += Monst->mLevel; } } mx = Monst->_mx; my = Monst->_my; if (dFlags[mx][my] & BFLAG_VISIBLE && Monst->_msquelch == 0) { if (Monst->MType->mtype == MT_CLEAVER) { PlaySFX(USFX_CLEAVER); } if (Monst->MType->mtype == MT_NAKRUL) { if (sgGameInitInfo.bCowQuest) { PlaySFX(USFX_NAKRUL6); } else { if (IsUberRoomOpened) PlaySFX(USFX_NAKRUL4); else PlaySFX(USFX_NAKRUL5); } } if (Monst->MType->mtype == MT_DEFILER) PlaySFX(USFX_DEFILER8); M_Enemy(mi); } if (Monst->_mFlags & MFLAG_TARGETS_MONSTER) { _menemy = Monst->_menemy; assurance((DWORD)_menemy < MAXMONSTERS, _menemy); Monst->_lastx = monster[Monst->_menemy]._mfutx; Monst->_menemyx = Monst->_lastx; Monst->_lasty = monster[Monst->_menemy]._mfuty; Monst->_menemyy = Monst->_lasty; } else { _menemy = Monst->_menemy; assurance((DWORD)_menemy < MAX_PLRS, _menemy); Monst->_menemyx = plr[Monst->_menemy]._pfutx; Monst->_menemyy = plr[Monst->_menemy]._pfuty; if (dFlags[mx][my] & BFLAG_VISIBLE) { Monst->_msquelch = UCHAR_MAX; Monst->_lastx = plr[Monst->_menemy]._pfutx; Monst->_lasty = plr[Monst->_menemy]._pfuty; } else if (Monst->_msquelch != 0 && Monst->MType->mtype != MT_DIABLO) { /// BUGFIX: change '_mAi' to 'MType->mtype' Monst->_msquelch--; } } do { if (!(Monst->_mFlags & MFLAG_SEARCH)) { AiProc[Monst->_mAi](mi); } else if (!MAI_Path(mi)) { AiProc[Monst->_mAi](mi); } switch (Monst->_mmode) { case MM_STAND: raflag = M_DoStand(mi); break; case MM_WALK: case MM_WALK2: case MM_WALK3: raflag = M_DoWalk(mi, Monst->_mmode); break; case MM_ATTACK: raflag = M_DoAttack(mi); break; case MM_GOTHIT: raflag = M_DoGotHit(mi); break; case MM_DEATH: raflag = M_DoDeath(mi); break; case MM_SATTACK: raflag = M_DoSAttack(mi); break; case MM_FADEIN: raflag = M_DoFadein(mi); break; case MM_FADEOUT: raflag = M_DoFadeout(mi); break; case MM_RATTACK: raflag = M_DoRAttack(mi); break; case MM_SPSTAND: raflag = M_DoSpStand(mi); break; case MM_RSPATTACK: raflag = M_DoRSpAttack(mi); break; case MM_DELAY: raflag = M_DoDelay(mi); break; case MM_CHARGE: raflag = false; break; case MM_STONE: raflag = M_DoStone(mi); break; case MM_HEAL: raflag = M_DoHeal(mi); break; case MM_TALK: raflag = M_DoTalk(mi); break; } if (raflag) { GroupUnity(mi); } } while (raflag); if (Monst->_mmode != MM_STONE) { Monst->_mAnimCnt++; if (!(Monst->_mFlags & MFLAG_ALLOW_SPECIAL) && Monst->_mAnimCnt >= Monst->_mAnimDelay) { Monst->_mAnimCnt = 0; if (Monst->_mFlags & MFLAG_LOCK_ANIMATION) { Monst->_mAnimFrame--; if (Monst->_mAnimFrame == 0) { Monst->_mAnimFrame = Monst->_mAnimLen; } } else { Monst->_mAnimFrame++; if (Monst->_mAnimFrame > Monst->_mAnimLen) { Monst->_mAnimFrame = 1; } } } } } DeleteMonsterList(); } void FreeMonsters() { int mtype; int i, j; for (i = 0; i < nummtypes; i++) { mtype = Monsters[i].mtype; for (j = 0; j < 6; j++) { if (animletter[j] != 's' || monsterdata[mtype].has_special) { MemFreeDbg(Monsters[i].Anims[j].CMem); } } } FreeMissiles2(); } bool DirOK(int i, int mdir) { int fx, fy; int x, y; int mcount, mi; commitment((DWORD)i < MAXMONSTERS, i); fx = monster[i]._mx + offset_x[mdir]; fy = monster[i]._my + offset_y[mdir]; if (fy < 0 || fy >= MAXDUNY || fx < 0 || fx >= MAXDUNX || !PosOkMonst(i, fx, fy)) return false; if (mdir == DIR_E) { if (SolidLoc(fx, fy + 1) || dFlags[fx][fy + 1] & BFLAG_MONSTLR) return false; } else if (mdir == DIR_W) { if (SolidLoc(fx + 1, fy) || dFlags[fx + 1][fy] & BFLAG_MONSTLR) return false; } else if (mdir == DIR_N) { if (SolidLoc(fx + 1, fy) || SolidLoc(fx, fy + 1)) return false; } else if (mdir == DIR_S) if (SolidLoc(fx - 1, fy) || SolidLoc(fx, fy - 1)) return false; if (monster[i].leaderflag == 1) { if (abs(fx - monster[monster[i].leader]._mfutx) >= 4 || abs(fy - monster[monster[i].leader]._mfuty) >= 4) { return false; } return true; } if (monster[i]._uniqtype == 0 || !(UniqMonst[monster[i]._uniqtype - 1].mUnqAttr & 2)) return true; mcount = 0; for (x = fx - 3; x <= fx + 3; x++) { for (y = fy - 3; y <= fy + 3; y++) { if (y < 0 || y >= MAXDUNY || x < 0 || x >= MAXDUNX) continue; mi = dMonster[x][y]; if (mi < 0) mi = -mi; if (mi != 0) mi--; // BUGFIX: should only run pack member check if mi was non-zero prior to executing the body of the above if-statement. if (monster[mi].leaderflag == 1 && monster[mi].leader == i && monster[mi]._mfutx == x && monster[mi]._mfuty == y) { mcount++; } } } return mcount == monster[i].packsize; } bool PosOkMissile(int x, int y) { return !nMissileTable[dPiece[x][y]] && !(dFlags[x][y] & BFLAG_MONSTLR); } bool CheckNoSolid(int x, int y) { return !nSolidTable[dPiece[x][y]]; } bool LineClearF(bool (*Clear)(int, int), int x1, int y1, int x2, int y2) { int xorg, yorg; int dx, dy; int d; int xincD, yincD, dincD, dincH; int tmp; bool done = false; xorg = x1; yorg = y1; dx = x2 - x1; dy = y2 - y1; if (abs(dx) > abs(dy)) { if (dx < 0) { tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; dx = -dx; dy = -dy; } if (dy > 0) { d = 2 * dy - dx; dincD = 2 * dy; dincH = 2 * (dy - dx); yincD = 1; } else { d = 2 * dy + dx; dincD = 2 * dy; dincH = 2 * (dx + dy); yincD = -1; } while (!done && (x1 != x2 || y1 != y2)) { if ((d <= 0) ^ (yincD < 0)) { d += dincD; } else { d += dincH; y1 += yincD; } x1++; done = ((x1 != xorg || y1 != yorg) && !Clear(x1, y1)); } } else { if (dy < 0) { tmp = y1; y1 = y2; y2 = tmp; tmp = x1; x1 = x2; x2 = tmp; dy = -dy; dx = -dx; } if (dx > 0) { d = 2 * dx - dy; dincD = 2 * dx; dincH = 2 * (dx - dy); xincD = 1; } else { d = 2 * dx + dy; dincD = 2 * dx; dincH = 2 * (dy + dx); xincD = -1; } while (!done && (y1 != y2 || x1 != x2)) { if ((d <= 0) ^ (xincD < 0)) { d += dincD; } else { d += dincH; x1 += xincD; } y1++; done = ((y1 != yorg || x1 != xorg) && !Clear(x1, y1)); } } return x1 == x2 && y1 == y2; } bool LineClear(int x1, int y1, int x2, int y2) { return LineClearF(PosOkMissile, x1, y1, x2, y2); } bool LineClearF1(bool (*Clear)(int, int, int), int monst, int x1, int y1, int x2, int y2) { int dx, dy; int d; int xorg, yorg; int xincD, yincD, dincD, dincH; int tmp; bool done = false; xorg = x1; yorg = y1; dx = x2 - x1; dy = y2 - y1; if (abs(dx) > abs(dy)) { if (dx < 0) { tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; dx = -dx; dy = -dy; } if (dy > 0) { d = 2 * dy - dx; dincD = 2 * dy; dincH = 2 * (dy - dx); yincD = 1; } else { d = 2 * dy + dx; dincD = 2 * dy; dincH = 2 * (dx + dy); yincD = -1; } while (!done && (x1 != x2 || y1 != y2)) { if ((d <= 0) ^ (yincD < 0)) { d += dincD; } else { d += dincH; y1 += yincD; } x1++; done = ((x1 != xorg || y1 != yorg) && !Clear(monst, x1, y1)); } } else { if (dy < 0) { tmp = y1; y1 = y2; y2 = tmp; tmp = x1; x1 = x2; x2 = tmp; dy = -dy; dx = -dx; } if (dx > 0) { d = 2 * dx - dy; dincD = 2 * dx; dincH = 2 * (dx - dy); xincD = 1; } else { d = 2 * dx + dy; dincD = 2 * dx; dincH = 2 * (dy + dx); xincD = -1; } while (!done && (y1 != y2 || x1 != x2)) { if ((d <= 0) ^ (xincD < 0)) { d += dincD; } else { d += dincH; x1 += xincD; } y1++; done = ((y1 != yorg || x1 != xorg) && !Clear(monst, x1, y1)); } } return x1 == x2 && y1 == y2; } void SyncMonsterAnim(int i) { int _mdir; assurance((DWORD)i < MAXMONSTERS || i < 0, i); monster[i].MType = &Monsters[monster[i]._mMTidx]; monster[i].MData = Monsters[monster[i]._mMTidx].MData; if (monster[i]._uniqtype != 0) monster[i].mName = UniqMonst[monster[i]._uniqtype - 1].mName; else monster[i].mName = monster[i].MData->mName; _mdir = monster[i]._mdir; switch (monster[i]._mmode) { case MM_STAND: monster[i]._mAnimData = monster[i].MType->Anims[MA_STAND].Data[_mdir]; break; case MM_WALK: case MM_WALK2: case MM_WALK3: monster[i]._mAnimData = monster[i].MType->Anims[MA_WALK].Data[_mdir]; break; case MM_ATTACK: case MM_RATTACK: monster[i]._mAnimData = monster[i].MType->Anims[MA_ATTACK].Data[_mdir]; break; case MM_GOTHIT: monster[i]._mAnimData = monster[i].MType->Anims[MA_GOTHIT].Data[_mdir]; break; case MM_DEATH: monster[i]._mAnimData = monster[i].MType->Anims[MA_DEATH].Data[_mdir]; break; case MM_SATTACK: case MM_FADEIN: case MM_FADEOUT: monster[i]._mAnimData = monster[i].MType->Anims[MA_SPECIAL].Data[_mdir]; break; case MM_SPSTAND: case MM_RSPATTACK: monster[i]._mAnimData = monster[i].MType->Anims[MA_SPECIAL].Data[_mdir]; break; case MM_HEAL: monster[i]._mAnimData = monster[i].MType->Anims[MA_SPECIAL].Data[_mdir]; break; case MM_DELAY: monster[i]._mAnimData = monster[i].MType->Anims[MA_STAND].Data[_mdir]; break; case MM_TALK: monster[i]._mAnimData = monster[i].MType->Anims[MA_STAND].Data[_mdir]; break; case MM_CHARGE: monster[i]._mAnimData = monster[i].MType->Anims[MA_ATTACK].Data[_mdir]; monster[i]._mAnimFrame = 1; monster[i]._mAnimLen = monster[i].MType->Anims[MA_ATTACK].Frames; break; default: monster[i]._mAnimData = monster[i].MType->Anims[MA_STAND].Data[_mdir]; monster[i]._mAnimFrame = 1; monster[i]._mAnimLen = monster[i].MType->Anims[MA_STAND].Frames; break; } } void M_FallenFear(int x, int y) { MonsterStruct *m; int i, rundist; for (i = 0; i < nummonsters; i++) { m = &monster[monstactive[i]]; switch (m->MType->mtype) { case MT_RFALLSP: case MT_RFALLSD: rundist = 7; break; case MT_DFALLSP: case MT_DFALLSD: rundist = 5; break; case MT_YFALLSP: case MT_YFALLSD: rundist = 3; break; case MT_BFALLSP: case MT_BFALLSD: rundist = 2; break; default: continue; } if (m->_mAi == AI_FALLEN && abs(x - m->_mx) < 5 && abs(y - m->_my) < 5 && m->_mhitpoints >> 6 > 0) { m->_mgoal = MGOAL_RETREAT; m->_mgoalvar1 = rundist; m->_mdir = GetDirection(x, y, m->_mx, m->_my); } } } const char *GetMonsterTypeText(const MonsterData &monsterData) { switch (monsterData.mMonstClass) { case MC_ANIMAL: return "Animal"; case MC_DEMON: return "Demon"; case MC_UNDEAD: return "Undead"; } app_fatal("Unknown mMonstClass %d", monsterData.mMonstClass); } void PrintMonstHistory(int mt) { int minHP, maxHP, res; if (sgOptions.Gameplay.bShowMonsterType) { sprintf(tempstr, "Type: %s Kills: %i", GetMonsterTypeText(monsterdata[mt]), monstkills[mt]); } else { sprintf(tempstr, "Total kills: %i", monstkills[mt]); } AddPanelString(tempstr, true); if (monstkills[mt] >= 30) { minHP = monsterdata[mt].mMinHP; maxHP = monsterdata[mt].mMaxHP; if (!gbIsHellfire && mt == MT_DIABLO) { minHP -= 2000; maxHP -= 2000; } if (!gbIsMultiplayer) { minHP >>= 1; maxHP >>= 1; } if (minHP < 1) minHP = 1; if (maxHP < 1) maxHP = 1; int hpBonusNightmare = 1; int hpBonusHell = 3; if (gbIsHellfire) { hpBonusNightmare = (!gbIsMultiplayer ? 50 : 100); hpBonusHell = (!gbIsMultiplayer ? 100 : 200); } if (sgGameInitInfo.nDifficulty == DIFF_NIGHTMARE) { minHP = 3 * minHP + hpBonusNightmare; maxHP = 3 * maxHP + hpBonusNightmare; } else if (sgGameInitInfo.nDifficulty == DIFF_HELL) { minHP = 4 * minHP + hpBonusHell; maxHP = 4 * maxHP + hpBonusHell; } sprintf(tempstr, "Hit Points: %i-%i", minHP, maxHP); AddPanelString(tempstr, true); } if (monstkills[mt] >= 15) { if (sgGameInitInfo.nDifficulty != DIFF_HELL) res = monsterdata[mt].mMagicRes; else res = monsterdata[mt].mMagicRes2; res = res & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING | IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING); if (!res) { strcpy(tempstr, "No magic resistance"); AddPanelString(tempstr, true); } else { if (res & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING)) { strcpy(tempstr, "Resists: "); if (res & RESIST_MAGIC) strcat(tempstr, "Magic "); if (res & RESIST_FIRE) strcat(tempstr, "Fire "); if (res & RESIST_LIGHTNING) strcat(tempstr, "Lightning "); tempstr[strlen(tempstr) - 1] = '\0'; AddPanelString(tempstr, true); } if (res & (IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING)) { strcpy(tempstr, "Immune: "); if (res & IMMUNE_MAGIC) strcat(tempstr, "Magic "); if (res & IMMUNE_FIRE) strcat(tempstr, "Fire "); if (res & IMMUNE_LIGHTNING) strcat(tempstr, "Lightning "); tempstr[strlen(tempstr) - 1] = '\0'; AddPanelString(tempstr, true); } } } pinfoflag = true; } void PrintUniqueHistory() { int res; if (sgOptions.Gameplay.bShowMonsterType) { sprintf(tempstr, "Type: %s", GetMonsterTypeText(*monster[pcursmonst].MData)); AddPanelString(tempstr, true); } res = monster[pcursmonst].mMagicRes & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING | IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING); if (!res) { strcpy(tempstr, "No resistances"); AddPanelString(tempstr, true); strcpy(tempstr, "No Immunities"); } else { if (res & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING)) strcpy(tempstr, "Some Magic Resistances"); else strcpy(tempstr, "No resistances"); AddPanelString(tempstr, true); if (res & (IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING)) { strcpy(tempstr, "Some Magic Immunities"); } else { strcpy(tempstr, "No Immunities"); } } AddPanelString(tempstr, true); pinfoflag = true; } void MissToMonst(int i, int x, int y) { int oldx, oldy; int newx, newy; int m, pnum; MissileStruct *Miss; MonsterStruct *Monst; assurance((DWORD)i < MAXMISSILES, i); Miss = &missile[i]; m = Miss->_misource; assurance((DWORD)m < MAXMONSTERS, m); Monst = &monster[m]; oldx = Miss->_mix; oldy = Miss->_miy; dMonster[x][y] = m + 1; Monst->_mdir = Miss->_mimfnum; Monst->_mx = x; Monst->_my = y; M_StartStand(m, Monst->_mdir); if (Monst->MType->mtype < MT_INCIN || Monst->MType->mtype > MT_HELLBURN) { if (!(Monst->_mFlags & MFLAG_TARGETS_MONSTER)) M_StartHit(m, -1, 0); else M2MStartHit(m, -1, 0); } else { M_StartFadein(m, Monst->_mdir, false); } if (!(Monst->_mFlags & MFLAG_TARGETS_MONSTER)) { pnum = dPlayer[oldx][oldy] - 1; if (dPlayer[oldx][oldy] > 0) { if (Monst->MType->mtype != MT_GLOOM && (Monst->MType->mtype < MT_INCIN || Monst->MType->mtype > MT_HELLBURN)) { M_TryH2HHit(m, dPlayer[oldx][oldy] - 1, 500, Monst->mMinDamage2, Monst->mMaxDamage2); if (pnum == dPlayer[oldx][oldy] - 1 && (Monst->MType->mtype < MT_NSNAKE || Monst->MType->mtype > MT_GSNAKE)) { if (plr[pnum]._pmode != PM_GOTHIT && plr[pnum]._pmode != PM_DEATH) StartPlrHit(pnum, 0, true); newx = oldx + offset_x[Monst->_mdir]; newy = oldy + offset_y[Monst->_mdir]; if (PosOkPlayer(pnum, newx, newy)) { plr[pnum]._px = newx; plr[pnum]._py = newy; FixPlayerLocation(pnum, plr[pnum]._pdir); FixPlrWalkTags(pnum); dPlayer[newx][newy] = pnum + 1; SetPlayerOld(pnum); } } } } } else { if (dMonster[oldx][oldy] > 0) { if (Monst->MType->mtype != MT_GLOOM && (Monst->MType->mtype < MT_INCIN || Monst->MType->mtype > MT_HELLBURN)) { M_TryM2MHit(m, dMonster[oldx][oldy] - 1, 500, Monst->mMinDamage2, Monst->mMaxDamage2); if (Monst->MType->mtype < MT_NSNAKE || Monst->MType->mtype > MT_GSNAKE) { newx = oldx + offset_x[Monst->_mdir]; newy = oldy + offset_y[Monst->_mdir]; if (PosOkMonst(dMonster[oldx][oldy] - 1, newx, newy)) { m = dMonster[oldx][oldy]; dMonster[newx][newy] = m; dMonster[oldx][oldy] = 0; m--; monster[m]._mx = newx; monster[m]._mfutx = newx; monster[m]._my = newy; monster[m]._mfuty = newy; } } } } } } bool PosOkMonst(int i, int x, int y) { int oi; bool ret; ret = !SolidLoc(x, y) && dPlayer[x][y] == 0 && dMonster[x][y] == 0; if (ret && dObject[x][y] != 0) { oi = dObject[x][y] > 0 ? dObject[x][y] - 1 : -(dObject[x][y] + 1); if (object[oi]._oSolidFlag) ret = false; } if (ret) ret = monster_posok(i, x, y); return ret; } bool monster_posok(int i, int x, int y) { int mi, j; bool ret, fire, lightning; ret = true; mi = dMissile[x][y]; if (mi != 0 && i >= 0) { fire = false; lightning = false; if (mi > 0) { if (missile[mi - 1]._mitype == MIS_FIREWALL) { // BUGFIX: Change 'mi' to 'mi - 1' (fixed) fire = true; } else if (missile[mi - 1]._mitype == MIS_LIGHTWALL) { // BUGFIX: Change 'mi' to 'mi - 1' (fixed) lightning = true; } } else { for (j = 0; j < nummissiles; j++) { mi = missileactive[j]; if (missile[mi]._mix == x && missile[mi]._miy == y) { if (missile[mi]._mitype == MIS_FIREWALL) { fire = true; break; } if (missile[mi]._mitype == MIS_LIGHTWALL) { lightning = true; break; } } } } if (fire && (!(monster[i].mMagicRes & IMMUNE_FIRE) || monster[i].MType->mtype == MT_DIABLO)) ret = false; if (lightning && (!(monster[i].mMagicRes & IMMUNE_LIGHTNING) || monster[i].MType->mtype == MT_DIABLO)) ret = false; } return ret; } bool PosOkMonst2(int i, int x, int y) { int oi; bool ret; ret = !SolidLoc(x, y); if (ret && dObject[x][y] != 0) { oi = dObject[x][y] > 0 ? dObject[x][y] - 1 : -(dObject[x][y] + 1); if (object[oi]._oSolidFlag) ret = false; } if (ret) ret = monster_posok(i, x, y); return ret; } bool PosOkMonst3(int i, int x, int y) { int oi, objtype; bool ret, isdoor; ret = true; isdoor = false; if (ret && dObject[x][y] != 0) { oi = dObject[x][y] > 0 ? dObject[x][y] - 1 : -(dObject[x][y] + 1); objtype = object[oi]._otype; isdoor = objtype == OBJ_L1LDOOR || objtype == OBJ_L1RDOOR || objtype == OBJ_L2LDOOR || objtype == OBJ_L2RDOOR || objtype == OBJ_L3LDOOR || objtype == OBJ_L3RDOOR; if (object[oi]._oSolidFlag && !isdoor) { ret = false; } } if (ret) { ret = (!SolidLoc(x, y) || isdoor) && dPlayer[x][y] == 0 && dMonster[x][y] == 0; } if (ret) ret = monster_posok(i, x, y); return ret; } bool IsSkel(int mt) { return (mt >= MT_WSKELAX && mt <= MT_XSKELAX) || (mt >= MT_WSKELBW && mt <= MT_XSKELBW) || (mt >= MT_WSKELSD && mt <= MT_XSKELSD); } bool IsGoat(int mt) { return (mt >= MT_NGOATMC && mt <= MT_GGOATMC) || (mt >= MT_NGOATBW && mt <= MT_GGOATBW); } int M_SpawnSkel(int x, int y, int dir) { int i, j, skeltypes, skel; j = 0; for (i = 0; i < nummtypes; i++) { if (IsSkel(Monsters[i].mtype)) j++; } if (j) { skeltypes = random_(136, j); j = 0; for (i = 0; i < nummtypes && j <= skeltypes; i++) { if (IsSkel(Monsters[i].mtype)) j++; } skel = AddMonster(x, y, dir, i - 1, true); if (skel != -1) M_StartSpStand(skel, dir); return skel; } return -1; } void ActivateSpawn(int i, int x, int y, int dir) { dMonster[x][y] = i + 1; monster[i]._mx = x; monster[i]._my = y; monster[i]._mfutx = x; monster[i]._mfuty = y; monster[i]._moldx = x; monster[i]._moldy = y; M_StartSpStand(i, dir); } bool SpawnSkeleton(int ii, int x, int y) { int dx, dy, xx, yy, dir, j, k, rs; bool savail; int monstok[3][3]; if (ii == -1) return false; if (PosOkMonst(-1, x, y)) { dir = GetDirection(x, y, x, y); ActivateSpawn(ii, x, y, dir); return true; } savail = false; yy = 0; for (j = y - 1; j <= y + 1; j++) { xx = 0; for (k = x - 1; k <= x + 1; k++) { monstok[xx][yy] = PosOkMonst(-1, k, j); savail |= monstok[xx][yy]; xx++; } yy++; } if (!savail) { return false; } rs = random_(137, 15) + 1; xx = 0; yy = 0; while (rs > 0) { if (monstok[xx][yy]) rs--; if (rs > 0) { xx++; if (xx == 3) { xx = 0; yy++; if (yy == 3) yy = 0; } } } dx = x - 1 + xx; dy = y - 1 + yy; dir = GetDirection(dx, dy, x, y); ActivateSpawn(ii, dx, dy, dir); return true; } int PreSpawnSkeleton() { int i, j, skeltypes, skel; j = 0; for (i = 0; i < nummtypes; i++) { if (IsSkel(Monsters[i].mtype)) j++; } if (j) { skeltypes = random_(136, j); j = 0; for (i = 0; i < nummtypes && j <= skeltypes; i++) { if (IsSkel(Monsters[i].mtype)) j++; } skel = AddMonster(0, 0, 0, i - 1, false); if (skel != -1) M_StartStand(skel, 0); return skel; } return -1; } void TalktoMonster(int i) { MonsterStruct *Monst; int pnum, itm; assurance((DWORD)i < MAXMONSTERS, i); Monst = &monster[i]; pnum = Monst->_menemy; Monst->_mmode = MM_TALK; if (Monst->_mAi == AI_SNOTSPIL || Monst->_mAi == AI_LACHDAN) { if (QuestStatus(Q_LTBANNER) && quests[Q_LTBANNER]._qvar1 == 2 && PlrHasItem(pnum, IDI_BANNER, &itm)) { RemoveInvItem(pnum, itm); quests[Q_LTBANNER]._qactive = QUEST_DONE; Monst->mtalkmsg = TEXT_BANNER12; Monst->_mgoal = MGOAL_INQUIRING; } if (QuestStatus(Q_VEIL) && Monst->mtalkmsg >= TEXT_VEIL9) { if (PlrHasItem(pnum, IDI_GLDNELIX, &itm)) { RemoveInvItem(pnum, itm); Monst->mtalkmsg = TEXT_VEIL11; Monst->_mgoal = MGOAL_INQUIRING; } } } } void SpawnGolum(int i, int x, int y, int mi) { assurance((DWORD)i < MAXMONSTERS, i); dMonster[x][y] = i + 1; monster[i]._mx = x; monster[i]._my = y; monster[i]._mfutx = x; monster[i]._mfuty = y; monster[i]._moldx = x; monster[i]._moldy = y; monster[i]._pathcount = 0; monster[i]._mmaxhp = 2 * (320 * missile[mi]._mispllvl + plr[i]._pMaxMana / 3); monster[i]._mhitpoints = monster[i]._mmaxhp; monster[i].mArmorClass = 25; monster[i].mHit = 5 * (missile[mi]._mispllvl + 8) + 2 * plr[i]._pLevel; monster[i].mMinDamage = 2 * (missile[mi]._mispllvl + 4); monster[i].mMaxDamage = 2 * (missile[mi]._mispllvl + 8); monster[i]._mFlags |= MFLAG_GOLEM; M_StartSpStand(i, 0); M_Enemy(i); if (i == myplr) { NetSendCmdGolem( monster[i]._mx, monster[i]._my, monster[i]._mdir, monster[i]._menemy, monster[i]._mhitpoints, currlevel); } } bool CanTalkToMonst(int m) { commitment((DWORD)m < MAXMONSTERS, m); if (monster[m]._mgoal == MGOAL_INQUIRING) { return true; } return monster[m]._mgoal == MGOAL_TALKING; } bool CheckMonsterHit(int m, bool *ret) { commitment((DWORD)m < MAXMONSTERS, m); if (monster[m]._mAi == AI_GARG && monster[m]._mFlags & MFLAG_ALLOW_SPECIAL) { monster[m]._mFlags &= ~MFLAG_ALLOW_SPECIAL; monster[m]._mmode = MM_SATTACK; *ret = true; return true; } if (monster[m].MType->mtype >= MT_COUNSLR && monster[m].MType->mtype <= MT_ADVOCATE) { if (monster[m]._mgoal != MGOAL_NORMAL) { *ret = false; return true; } } return false; } int encode_enemy(int m) { if (monster[m]._mFlags & MFLAG_TARGETS_MONSTER) return monster[m]._menemy + MAX_PLRS; else return monster[m]._menemy; } void decode_enemy(int m, int enemy) { if (enemy < MAX_PLRS) { monster[m]._mFlags &= ~MFLAG_TARGETS_MONSTER; monster[m]._menemy = enemy; monster[m]._menemyx = plr[enemy]._pfutx; monster[m]._menemyy = plr[enemy]._pfuty; } else { monster[m]._mFlags |= MFLAG_TARGETS_MONSTER; enemy -= MAX_PLRS; monster[m]._menemy = enemy; monster[m]._menemyx = monster[enemy]._mfutx; monster[m]._menemyy = monster[enemy]._mfuty; } } } // namespace devilution