/** * @file monster.cpp * * Implementation of monster functionality, AI, actions, spawning, loading, etc. */ #include "all.h" #include "../3rdParty/Storm/Source/storm.h" /** Tracks which missile files are already loaded */ int MissileFileFlag; // BUGFIX: replace monstkills[MAXMONSTERS] with monstkills[NUM_MTYPES]. int monstkills[MAXMONSTERS]; int monstactive[MAXMONSTERS]; int nummonsters; BOOLEAN sgbSaveSoundOn; MonsterStruct monster[MAXMONSTERS]; int totalmonsters; CMonster Monsters[MAX_LVLMTYPES]; BYTE GraphicTable[NUMLEVELS][MAX_LVLMTYPES]; int monstimgtot; int uniquetrans; int nummtypes; const char plr2monst[9] = { 0, 5, 3, 7, 1, 4, 6, 0, 2 }; const BYTE counsmiss[4] = { MIS_FIREBOLT, MIS_CBOLT, MIS_LIGHTCTRL, MIS_FIREBALL }; /* data */ 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 } }; char animletter[7] = "nwahds"; int left[8] = { 7, 0, 1, 2, 3, 4, 5, 6 }; int right[8] = { 1, 2, 3, 4, 5, 6, 7, 0 }; int opposite[8] = { 4, 5, 6, 7, 0, 1, 2, 3 }; int offset_x[8] = { 1, 0, -1, -1, -1, 0, 1, 1 }; int offset_y[8] = { 1, 1, 1, 0, -1, -1, -1, 0 }; /** unused */ int rnd5[4] = { 5, 10, 15, 20 }; int rnd10[4] = { 10, 15, 20, 30 }; int rnd20[4] = { 20, 30, 40, 50 }; int rnd60[4] = { 60, 70, 80, 90 }; 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, }; 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(int type, int 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. int typelist[MAXMONSTERS]; int skeltypes[NUM_MTYPES]; int minl; // min level int maxl; // max level char mamask; const int numskeltypes = 19; int nt; // number of types #ifdef SPAWN mamask = 1; // monster availability mask #else mamask = 3; // monster availability mask #endif AddMonsterType(MT_GOLEM, 2); if (currlevel == 16) { AddMonsterType(MT_ADVOCATE, 1); AddMonsterType(MT_RBLACK, 1); AddMonsterType(MT_DIABLO, 2); return; } if (!setlevel) { if (QuestStatus(Q_BUTCHER)) AddMonsterType(MT_CLEAVER, 2); if (QuestStatus(Q_GARBUD)) AddMonsterType(UniqMonst[UMT_GARBUD].mtype, 4); if (QuestStatus(Q_ZHAR)) AddMonsterType(UniqMonst[UMT_ZHAR].mtype, 4); if (QuestStatus(Q_LTBANNER)) AddMonsterType(UniqMonst[UMT_SNOTSPIL].mtype, 4); if (QuestStatus(Q_VEIL)) AddMonsterType(UniqMonst[UMT_LACHDAN].mtype, 4); if (QuestStatus(Q_WARLORD)) AddMonsterType(UniqMonst[UMT_WARLORD].mtype, 4); if (gbMaxPlayers != 1 && currlevel == quests[Q_SKELKING]._qlevel) { AddMonsterType(MT_SKING, 4); 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++] = i; } } } } AddMonsterType(skeltypes[random_(88, nt)], 1); } nt = 0; for (i = 0; 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++] = i; } } } if (monstdebug) { for (i = 0; i < debugmonsttypes; i++) AddMonsterType(DebugMonsters[i], 1); } else { 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], 1); typelist[i] = typelist[--nt]; } } } } else { if (setlvlnum == SL_SKELKING) { AddMonsterType(MT_SKING, 4); } } } void InitMonsterGFX(int monst) { int mtype, anim, i; char strBuff[256]; BYTE *celBuf; mtype = Monsters[monst].mtype; for (anim = 0; anim < 6; anim++) { if ((animletter[anim] != 's' || monsterdata[mtype].has_special) && monsterdata[mtype].Frames[anim] > 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] = &celBuf[((int *)celBuf)[i]]; } } else { for (i = 0; i < 8; i++) { Monsters[monst].Anims[anim].Data[i] = celBuf; } } } // TODO: either the AnimStruct members have wrong naming or the MonsterData ones it seems Monsters[monst].Anims[anim].Frames = monsterdata[mtype].Frames[anim]; 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; 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_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 && !(MissileFileFlag & 0x10)) { MissileFileFlag |= 0x10; LoadMissileGFX(MFILE_ACIDBF); LoadMissileGFX(MFILE_ACIDSPLA); LoadMissileGFX(MFILE_ACIDPUD); } 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; if (monst->mtype == MT_DIABLO) { monster[i]._mmaxhp = (random_(88, 1) + 1666) << 6; } else { monster[i]._mmaxhp = (monst->mMinHP + random_(88, monst->mMaxHP - monst->mMinHP + 1)) << 6; } if (gbMaxPlayers == 1) { 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].field_18 = 0; monster[i]._pathcount = 0; monster[i]._mDelFlag = FALSE; monster[i]._uniqtype = 0; monster[i]._msquelch = 0; monster[i]._mRndSeed = GetRndSeed(); monster[i]._mAISeed = GetRndSeed(); monster[i].mWhoHit = 0; monster[i].mLevel = monst->MData->mLevel; 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 (gnDifficulty == DIFF_NIGHTMARE) { monster[i]._mmaxhp = 3 * 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 += 85; monster[i].mMinDamage = 2 * (monster[i].mMinDamage + 2); monster[i].mMaxDamage = 2 * (monster[i].mMaxDamage + 2); monster[i].mHit2 += 85; monster[i].mMinDamage2 = 2 * (monster[i].mMinDamage2 + 2); monster[i].mMaxDamage2 = 2 * (monster[i].mMaxDamage2 + 2); monster[i].mArmorClass += 50; } if (gnDifficulty == DIFF_HELL) { monster[i]._mmaxhp = 4 * 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 += 120; monster[i].mMinDamage = 4 * monster[i].mMinDamage + 6; monster[i].mMaxDamage = 4 * monster[i].mMaxDamage + 6; monster[i].mHit2 += 120; monster[i].mMinDamage2 = 4 * monster[i].mMinDamage2 + 6; monster[i].mMaxDamage2 = 4 * monster[i].mMaxDamage2 + 6; monster[i].mArmorClass += 80; 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 = 0; 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] || dPlayer[xp][yp]) { return FALSE; } f = dFlags[xp][yp]; if (f & BFLAG_VISIBLE) { return FALSE; } if (f & BFLAG_POPULATED) { return FALSE; } return !SolidLoc(xp, yp); } void PlaceMonster(int i, int mtype, int x, int y) { int rd; dMonster[x][y] = i + 1; rd = random_(90, 8); InitMonster(i, rd, mtype, x, y); } #ifndef SPAWN void PlaceUniqueMonst(int uniqindex, int miniontype, int unpackfilesize) { int xp, yp, x, y, i; int uniqtype; int count2; char filestr[64]; BOOL zharflag, done; 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 == TRUE) { zharflag = FALSE; xp = 2 * themeLoc[i].x + 20; yp = 2 * themeLoc[i].y + 20; } } } if (gbMaxPlayers == 1) { 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; } } } 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 (gbMaxPlayers == 1) { 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; Monst->mlid = AddLight(Monst->_mx, Monst->_my, 3); if (gbMaxPlayers != 1) { if (Monst->_mAi == AI_LAZHELP) Monst->mtalkmsg = 0; if (Monst->_mAi != AI_LAZURUS || quests[Q_BETRAYER]._qvar1 <= 3) { if (Monst->mtalkmsg) { Monst->_mgoal = MGOAL_INQUIRING; } } else { Monst->_mgoal = MGOAL_NORMAL; } } else if (Monst->mtalkmsg) Monst->_mgoal = MGOAL_INQUIRING; if (gnDifficulty == DIFF_NIGHTMARE) { Monst->_mmaxhp = 3 * 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); } if (gnDifficulty == DIFF_HELL) { Monst->_mmaxhp = 4 * 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 (Uniq->mUnqAttr & 8) { Monst->mArmorClass = Uniq->mUnqVar1; } nummonsters++; if (Uniq->mUnqAttr & 1) { PlaceGroup(miniontype, unpackfilesize, 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; } } void PlaceQuestMonsters() { int skeltype; BYTE *setp; if (!setlevel) { if (QuestStatus(Q_BUTCHER)) { PlaceUniqueMonst(UMT_BUTCHER, 0, 0); } if (currlevel == quests[Q_SKELKING]._qlevel && gbMaxPlayers != 1) { 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, 1); } if (QuestStatus(Q_VEIL)) { AddMonsterType(UniqMonst[UMT_LACHDAN].mtype, 1); } if (QuestStatus(Q_ZHAR) && zharlib == -1) { quests[Q_ZHAR]._qactive = QUEST_NOTAVAIL; } if (currlevel == quests[Q_BETRAYER]._qlevel && gbMaxPlayers != 1) { AddMonsterType(UniqMonst[UMT_LAZURUS].mtype, 4); AddMonsterType(UniqMonst[UMT_RED_VEX].mtype, 4); 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); } } else if (setlvlnum == SL_SKELKING) { PlaceUniqueMonst(UMT_SKELKING, 0, 0); } } #endif 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; } } #ifndef SPAWN 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); } #endif void InitMonsters() { int na, nt; int i, s, t; int numplacemonsters; int mtype; int numscattypes; int scattertypes[NUM_MTYPES]; numscattypes = 0; if (gbMaxPlayers != 1) CheckDungeonClear(); 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); } #ifndef SPAWN if (!setlevel && currlevel == 16) LoadDiabMonsts(); #endif 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); } } #ifndef SPAWN PlaceQuestMonsters(); #endif if (!setlevel) { #ifndef SPAWN PlaceUniques(); #endif na = 0; for (s = 16; s < 96; s++) for (t = 16; t < 96; t++) if (!SolidLoc(s, t)) na++; numplacemonsters = na / 30; if (gbMaxPlayers != 1) numplacemonsters += numplacemonsters >> 1; if (nummonsters + numplacemonsters > 190) numplacemonsters = 190 - nummonsters; totalmonsters = nummonsters + numplacemonsters; for (i = 0; i < nummtypes; i++) { if (Monsters[i].mPlaceFlags & 1) { 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) 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); } } } #ifndef SPAWN 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 SetMapMonsters(BYTE *pMap, int startx, int starty) { WORD rw, rh; WORD *lm; int i, j; int mtype; AddMonsterType(MT_GOLEM, 2); 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, 4); AddMonsterType(UniqMonst[UMT_RED_VEX].mtype, 4); AddMonsterType(UniqMonst[UMT_BLACKJADE].mtype, 4); PlaceUniqueMonst(UMT_LAZURUS, 0, 0); PlaceUniqueMonst(UMT_RED_VEX, 0, 0); PlaceUniqueMonst(UMT_BLACKJADE, 0, 0); } lm = (WORD *)pMap; rw = *lm; lm++; rh = *lm; lm += (rw * rh + 1); rw = rw << 1; rh = rh << 1; lm += rw * rh; for (j = 0; j < rh; j++) { for (i = 0; i < rw; i++) { if (*lm) { mtype = AddMonsterType(MonstConvTbl[(*lm) - 1], 2); PlaceMonster(nummonsters++, mtype, i + startx + 16, j + starty + 16); } lm++; } } } #endif 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 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_GOLEM)) { for (pnum = 0; pnum < MAX_PLRS; pnum++) { if (!plr[pnum].plractive || currlevel != plr[pnum].plrlevel || plr[pnum]._pLvlChanging || (plr[pnum]._pHitPoints == 0 && gbMaxPlayers != 1)) continue; if (dTransVal[Monst->_mx][Monst->_my] == dTransVal[plr[pnum]._px][plr[pnum]._py]) sameroom = TRUE; else sameroom = FALSE; if (abs(Monst->_mx - plr[pnum]._px) > abs(Monst->_my - plr[pnum]._py)) dist = Monst->_mx - plr[pnum]._px; else dist = Monst->_my - plr[pnum]._py; dist = abs(dist); 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]._mx == 1 && monster[mi]._my == 0) continue; if (M_Talker(mi) && monster[mi].mtalkmsg) continue; if (!(Monst->_mFlags & MFLAG_GOLEM) && ((abs(monster[mi]._mx - Monst->_mx) >= 2 || abs(monster[mi]._my - Monst->_my) >= 2) && !M_Ranged(i) || (!(Monst->_mFlags & MFLAG_GOLEM) && !(monster[mi]._mFlags & MFLAG_GOLEM)))) { continue; } sameroom = dTransVal[Monst->_mx][Monst->_my] == dTransVal[monster[mi]._mx][monster[mi]._my]; if (abs(Monst->_mx - monster[mi]._mx) > abs(Monst->_my - monster[mi]._my)) dist = Monst->_mx - monster[mi]._mx; else dist = Monst->_my - monster[mi]._my; dist = abs(dist); 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_CheckEFlag(int i) { int f, j; int x, y; WORD *m; x = monster[i]._mx - 1; y = monster[i]._my + 1; f = 0; // BUGFIX check (x > 0 && y < MAXDUNY) m = dpiece_defs_map_2[x][y].mt; if (m >= dpiece_defs_map_2[0][0].mt) { for (j = 2; j < 10; j++) { f |= m[j]; } } else { monster[i]._meflag = FALSE; return; } if (f | dSpecial[x][y]) monster[i]._meflag = TRUE; else { monster[i]._meflag = FALSE; } } 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_CheckEFlag(i); 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; M_CheckEFlag(i); } 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; M_CheckEFlag(i); } 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]._uniqtype != 0) 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; M_CheckEFlag(i); } 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]._uniqtype != 0) 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; M_CheckEFlag(i); } 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; M_CheckEFlag(i); } 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; M_CheckEFlag(i); } 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; M_CheckEFlag(i); } 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; M_CheckEFlag(i); } 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; M_CheckEFlag(i); } void M_ClearSquares(int i) { int x, y, mx, my, m1, m2; mx = monster[i]._moldx; my = monster[i]._moldy; m1 = -1 - i; 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; // BUGFIX useless assignment monster[i]._moldx = monster[i]._mx; monster[i]._moldy = monster[i]._my; M_CheckEFlag(i); 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); NetSendCmdParam2(FALSE, CMD_MONSTDAMAGE, 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]._mFlags &= ~MFLAG_TARGETS_MONSTER; monster[i]._menemy = pnum; monster[i]._menemyx = plr[pnum]._pfutx; monster[i]._menemyy = plr[pnum]._pfuty; 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]._mgoal = MGOAL_NORMAL; } 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_CheckEFlag(i); M_ClearSquares(i); dMonster[monster[i]._mx][monster[i]._my] = i + 1; } } } void M_DiabloDeath(int i, BOOL sendmsg) { MonsterStruct *Monst, *pmonster; int dist; int j, k; int _moldx, _moldy; Monst = monster + i; #ifndef SPAWN PlaySFX(USFX_DIABLOD); #endif quests[Q_DIABLO]._qactive = QUEST_DONE; if (sendmsg) NetSendCmdQuest(TRUE, Q_DIABLO); gbProcessPlayers = FALSE; sgbSaveSoundOn = gbSoundOn; for (j = 0; j < nummonsters; j++) { k = monstactive[j]; if (k == i || monster[i]._msquelch == 0) continue; pmonster = monster + k; NewMonsterAnim(k, pmonster->MType->Anims[MA_DEATH], pmonster->_mdir); monster[k]._mxoff = 0; monster[k]._myoff = 0; monster[k]._mVar1 = 0; _moldx = monster[k]._moldx; _moldy = monster[k]._moldy; monster[k]._my = _moldy; monster[k]._mfuty = _moldy; monster[k]._mmode = MM_DEATH; monster[k]._mx = _moldx; monster[k]._mfutx = _moldx; M_CheckEFlag(k); M_ClearSquares(k); dMonster[pmonster->_mx][pmonster->_my] = k + 1; } AddLight(Monst->_mx, Monst->_my, 8); DoVision(Monst->_mx, Monst->_my, 8, FALSE, TRUE); if (abs(ViewX - Monst->_mx) > abs(ViewY - Monst->_my)) dist = abs(ViewX - Monst->_mx); else dist = abs(ViewY - Monst->_my); if (dist > 20) dist = 20; j = ViewX << 16; k = ViewY << 16; Monst->_mVar3 = j; Monst->_mVar4 = k; Monst->_mVar5 = (int)((j - (Monst->_mx << 16)) / (double)dist); Monst->_mVar6 = (int)((k - (Monst->_my << 16)) / (double)dist); } void M2MStartHit(int mid, int i, int dam) { if ((DWORD)mid >= MAXMONSTERS) { app_fatal("Invalid monster %d getting hit by monster", mid); } if (monster[mid].MType == NULL) { app_fatal("Monster %d \"%s\" getting hit by monster: MType NULL", mid, monster[mid].mName); } if (i >= 0) monster[i].mWhoHit |= 1 << i; delta_monster_hp(mid, monster[mid]._mhitpoints, currlevel); NetSendCmdParam2(FALSE, CMD_MONSTDAMAGE, 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]._mgoal = MGOAL_NORMAL; } 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_CheckEFlag(mid); M_ClearSquares(mid); dMonster[monster[mid]._mx][monster[mid]._my] = mid + 1; } } } void MonstStartKill(int i, int pnum, BOOL sendmsg) { int md; MonsterStruct *Monst; if ((DWORD)i >= MAXMONSTERS) { app_fatal("MonstStartKill: Invalid monster %d", i); } if (!monster[i].MType) { app_fatal("MonstStartKill: Monster %d \"%s\" MType NULL", i, monster[i].mName); } Monst = &monster[i]; if (pnum >= 0) Monst->mWhoHit |= 1 << pnum; if (pnum < MAX_PLRS && i > MAX_PLRS) AddPlrMonstExper(Monst->mLevel, Monst->mExp, Monst->mWhoHit); monstkills[Monst->MType->mtype]++; Monst->_mhitpoints = 0; SetRndSeed(Monst->_mRndSeed); if (QuestStatus(Q_GARBUD) && Monst->mName == UniqMonst[UMT_GARBUD].mName) { CreateTypeItem(Monst->_mx + 1, Monst->_my + 1, TRUE, ITYPE_MACE, IMISC_NONE, TRUE, FALSE); } else if (i > MAX_PLRS - 1) { // Golems should not spawn items SpawnItem(i, Monst->_mx, Monst->_my, 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->_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_CheckEFlag(i); 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) AddMissile(Monst->_mx, Monst->_my, 0, 0, 0, MIS_ACIDPUD, 1, i, Monst->_mint + 1, 0); } void M2MStartKill(int i, int mid) { int md; if ((DWORD)i >= MAXMONSTERS) { app_fatal("M2MStartKill: Invalid monster (attacker) %d", i); } if ((DWORD)i >= MAXMONSTERS) { /// BUGFIX: should check `mid` app_fatal("M2MStartKill: Invalid monster (killed) %d", mid); } if (!monster[i].MType) app_fatal("M2MStartKill: Monster %d \"%s\" MType NULL", mid, monster[mid].mName); delta_kill_monster(mid, monster[mid]._mx, monster[mid]._my, currlevel); NetSendCmdLocParam1(FALSE, CMD_MONSTDEATH, monster[mid]._mx, monster[mid]._my, mid); monster[mid].mWhoHit |= 1 << i; if (i < 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); if (mid >= MAX_PLRS) SpawnItem(mid, monster[mid]._mx, monster[mid]._my, TRUE); if (monster[mid].MType->mtype == MT_DIABLO) M_DiabloDeath(mid, TRUE); else PlayEffect(i, 2); 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_CheckEFlag(mid); 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, 1, mid, monster[mid]._mint + 1, 0); } void M_StartKill(int i, int pnum) { if ((DWORD)i >= MAXMONSTERS) { app_fatal("M_StartKill: Invalid monster %d", 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) { if ((DWORD)i >= MAXMONSTERS) app_fatal("M_SyncStartKill: Invalid monster %d", 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) { if ((DWORD)i >= MAXMONSTERS) app_fatal("M_StartFadein: Invalid monster %d", i); if (monster[i].MType == NULL) app_fatal("M_StartFadein: Monster %d \"%s\" MType NULL", i, monster[i].mName); 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; M_CheckEFlag(i); 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) { if ((DWORD)i >= MAXMONSTERS) app_fatal("M_StartFadeout: Invalid monster %d", i); if (monster[i].MType == NULL) app_fatal("M_StartFadeout: Monster %d \"%s\" MType NULL", i, monster[i].mName); 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; M_CheckEFlag(i); 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("M_StartHeal: Invalid monster %d", i); if (monster[i].MType == NULL) app_fatal("M_StartHeal: Monster %d \"%s\" MType NULL", i, monster[i].mName); 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; if ((DWORD)monst >= MAXMONSTERS) app_fatal("M_ChangeLightOffset: Invalid monster %d", 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); ChangeLightOff(monster[monst].mlid, _mxoff, _myoff); } BOOL M_DoStand(int i) { MonsterStruct *Monst; if ((DWORD)i >= MAXMONSTERS) app_fatal("M_DoStand: Invalid monster %d", i); if (monster[i].MType == NULL) app_fatal("M_DoStand: Monster %d \"%s\" MType NULL", i, monster[i].mName); 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; } BOOL M_DoWalk(int i) { BOOL rv; if ((DWORD)i >= MAXMONSTERS) app_fatal("M_DoWalk: Invalid monster %d", i); if (monster[i].MType == NULL) app_fatal("M_DoWalk: Monster %d \"%s\" MType NULL", i, monster[i].mName); rv = FALSE; if (monster[i]._mVar8 == monster[i].MType->Anims[MA_WALK].Frames) { 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; if (monster[i]._uniqtype != 0) ChangeLightXY(monster[i].mlid, monster[i]._mx, monster[i]._my); M_StartStand(i, monster[i]._mdir); rv = TRUE; } else if (!monster[i]._mAnimCnt) { 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; } if (monster[i]._uniqtype != 0) M_ChangeLightOffset(i); return rv; } BOOL M_DoWalk2(int i) { BOOL rv; if ((DWORD)i >= MAXMONSTERS) app_fatal("M_DoWalk2: Invalid monster %d", i); if (monster[i].MType == NULL) app_fatal("M_DoWalk2: Monster %d \"%s\" MType NULL", i, monster[i].mName); if (monster[i]._mVar8 == monster[i].MType->Anims[MA_WALK].Frames) { dMonster[monster[i]._mVar1][monster[i]._mVar2] = 0; if (monster[i]._uniqtype != 0) ChangeLightXY(monster[i].mlid, monster[i]._mx, monster[i]._my); M_StartStand(i, monster[i]._mdir); rv = TRUE; } else { if (!monster[i]._mAnimCnt) { 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; } rv = FALSE; } if (monster[i]._uniqtype != 0) M_ChangeLightOffset(i); return rv; } BOOL M_DoWalk3(int i) { BOOL rv; if ((DWORD)i >= MAXMONSTERS) app_fatal("M_DoWalk3: Invalid monster %d", i); if (monster[i].MType == NULL) app_fatal("M_DoWalk3: Monster %d \"%s\" MType NULL", i, monster[i].mName); if (monster[i]._mVar8 == monster[i].MType->Anims[MA_WALK].Frames) { 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; if (monster[i]._uniqtype) ChangeLightXY(monster[i].mlid, monster[i]._mx, monster[i]._my); M_StartStand(i, monster[i]._mdir); rv = TRUE; } else { if (!monster[i]._mAnimCnt) { 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; } rv = FALSE; } if (monster[i]._uniqtype != 0) M_ChangeLightOffset(i); return rv; } void M_TryM2MHit(int i, int mid, int hper, int mind, int maxd) { BOOL ret; if ((DWORD)mid >= MAXMONSTERS) { app_fatal("M_TryM2MHit: Invalid monster %d", mid); } if (monster[mid].MType == NULL) app_fatal("M_TryM2MHit: Monster %d \"%s\" MType NULL", mid, monster[mid].mName); 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, ms_num, cur_ms_num, new_hp; if ((DWORD)i >= MAXMONSTERS) app_fatal("M_TryH2HHit: Invalid monster %d", i); if (monster[i].MType == NULL) app_fatal("M_TryH2HHit: Monster %d \"%s\" MType NULL", i, monster[i].mName); 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 hit = Hit + 2 * (monster[i].mLevel - plr[pnum]._pLevel) + 30 - plr[pnum]._pIBonusAC - plr[pnum]._pIAC - 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) { StartPlrBlock(pnum, GetDirection(plr[pnum]._px, plr[pnum]._py, monster[i]._mx, monster[i]._my)); return; } if (monster[i].MType->mtype == MT_YZOMBIE && pnum == myplr) { ms_num = -1; 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; else ms_num = misnum; } if (plr[pnum]._pMaxHP > 64) { if (plr[pnum]._pMaxHPBase > 64) { new_hp = plr[pnum]._pMaxHP - 64; plr[pnum]._pMaxHP = new_hp; if (plr[pnum]._pHitPoints > new_hp) { plr[pnum]._pHitPoints = new_hp; if (cur_ms_num >= 0) missile[cur_ms_num]._miVar1 = new_hp; } new_hp = plr[pnum]._pMaxHPBase - 64; plr[pnum]._pMaxHPBase = new_hp; if (plr[pnum]._pHPBase > new_hp) { plr[pnum]._pHPBase = new_hp; if (cur_ms_num >= 0) missile[cur_ms_num]._miVar2 = new_hp; } } } } dam = (MinDam << 6) + random_(99, (MaxDam - MinDam + 1) << 6); dam += (plr[pnum]._pIGetHit << 6); if (dam < 64) dam = 64; if (pnum == myplr) { 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 && gbMaxPlayers != 1) 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); 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("M_DoAttack: Invalid monster %d", i); Monst = &monster[i]; if (Monst->MType == NULL) app_fatal("M_DoAttack: Monster %d \"%s\" MType NULL", i, Monst->mName); if (Monst->MType == NULL) // BUGFIX: should check MData app_fatal("M_DoAttack: Monster %d \"%s\" MData NULL", i, Monst->mName); 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("M_DoRAttack: Invalid monster %d", i); if (monster[i].MType == NULL) app_fatal("M_DoRAttack: Monster %d \"%s\" MType NULL", i, monster[i].mName); if (monster[i].MType == NULL) // BUGFIX: should check MData app_fatal("M_DoRAttack: Monster %d \"%s\" MData NULL", i, monster[i].mName); if (monster[i]._mAnimFrame == monster[i].MData->mAFNum) { if (monster[i]._mVar1 != -1) { if (monster[i]._mVar1 != MIS_CBOLT) multimissiles = 1; else multimissiles = 3; for (mi = 0; mi < multimissiles; mi++) { AddMissile( monster[i]._mx, monster[i]._my, monster[i]._menemyx, monster[i]._menemyy, monster[i]._mdir, monster[i]._mVar1, 1, 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; } int M_DoRSpAttack(int i) { if ((DWORD)i >= MAXMONSTERS) app_fatal("M_DoRSpAttack: Invalid monster %d", i); if (monster[i].MType == NULL) app_fatal("M_DoRSpAttack: Monster %d \"%s\" MType NULL", i, monster[i].mName); if (monster[i].MType == NULL) // BUGFIX: should check MData app_fatal("M_DoRSpAttack: Monster %d \"%s\" MData NULL", i, monster[i].mName); if (monster[i]._mAnimFrame == monster[i].MData->mAFNum2 && !monster[i]._mAnimCnt) { AddMissile( monster[i]._mx, monster[i]._my, monster[i]._menemyx, monster[i]._menemyy, monster[i]._mdir, monster[i]._mVar1, 1, i, monster[i]._mVar3, 0); PlayEffect(i, 3); } if (monster[i]._mAi == AI_MEGA && monster[i]._mAnimFrame == 3) { int hadV2 = monster[i]._mVar2; monster[i]._mVar2++; if (hadV2 == 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) { if ((DWORD)i >= MAXMONSTERS) app_fatal("M_DoSAttack: Invalid monster %d", i); if (monster[i].MType == NULL) app_fatal("M_DoSAttack: Monster %d \"%s\" MType NULL", i, monster[i].mName); if (monster[i].MType == NULL) // BUGFIX: should check MData app_fatal("M_DoSAttack: Monster %d \"%s\" MData NULL", i, monster[i].mName); 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) { if ((DWORD)i >= MAXMONSTERS) app_fatal("M_DoFadein: Invalid monster %d", 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("M_DoFadeout: Invalid monster %d", 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; } int M_DoHeal(int i) { MonsterStruct *Monst; if ((DWORD)i >= MAXMONSTERS) app_fatal("M_DoHeal: Invalid monster %d", 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; } int M_DoTalk(int i) { MonsterStruct *Monst; int tren; if ((DWORD)i >= MAXMONSTERS) app_fatal("M_DoTalk: Invalid monster %d", i); Monst = monster + i; M_StartStand(i, Monst->_mdir); monster[i]._mgoal = MGOAL_TALKING; if (effect_is_playing(alltext[Monst->mtalkmsg].sfxnr)) return FALSE; InitQTextMsg(Monst->mtalkmsg); if (Monst->mName == UniqMonst[UMT_GARBUD].mName) { if (Monst->mtalkmsg == TEXT_GARBUD1) quests[Q_GARBUD]._qactive = QUEST_ACTIVE; quests[Q_GARBUD]._qlog = TRUE; if (Monst->mtalkmsg == TEXT_GARBUD2 && !(Monst->_mFlags & MFLAG_QUEST_COMPLETE)) { SpawnItem(i, Monst->_mx + 1, Monst->_my + 1, TRUE); Monst->_mFlags |= MFLAG_QUEST_COMPLETE; } } if (Monst->mName == UniqMonst[UMT_ZHAR].mName && Monst->mtalkmsg == TEXT_ZHAR1 && !(Monst->_mFlags & MFLAG_QUEST_COMPLETE)) { quests[Q_ZHAR]._qactive = QUEST_ACTIVE; quests[Q_ZHAR]._qlog = TRUE; CreateTypeItem(Monst->_mx + 1, Monst->_my + 1, FALSE, ITYPE_MISC, IMISC_BOOK, TRUE, FALSE); Monst->_mFlags |= MFLAG_QUEST_COMPLETE; } if (Monst->mName == UniqMonst[UMT_SNOTSPIL].mName) { if (Monst->mtalkmsg == TEXT_BANNER10 && !(Monst->_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; Monst->_mFlags |= MFLAG_QUEST_COMPLETE; } if (quests[Q_LTBANNER]._qvar1 < 2) { sprintf(tempstr, "SS Talk = %i, Flags = %i", Monst->mtalkmsg, Monst->_mFlags); app_fatal(tempstr); } } if (Monst->mName == UniqMonst[UMT_LACHDAN].mName) { if (Monst->mtalkmsg == TEXT_VEIL9) { quests[Q_VEIL]._qactive = QUEST_ACTIVE; quests[Q_VEIL]._qlog = TRUE; } if (Monst->mtalkmsg == TEXT_VEIL11 && !(Monst->_mFlags & MFLAG_QUEST_COMPLETE)) { SpawnUnique(UITEM_STEELVEIL, Monst->_mx + 1, Monst->_my + 1); Monst->_mFlags |= MFLAG_QUEST_COMPLETE; } } if (Monst->mName == UniqMonst[UMT_WARLORD].mName) quests[Q_WARLORD]._qvar1 = 2; if (Monst->mName == UniqMonst[UMT_LAZURUS].mName && gbMaxPlayers != 1) { Monst->_msquelch = UCHAR_MAX; Monst->mtalkmsg = 0; quests[Q_BETRAYER]._qvar1 = 6; Monst->_mgoal = MGOAL_NORMAL; } return FALSE; } void M_Teleport(int i) { BOOL tren; MonsterStruct *Monst; int k, j, x, y, _mx, _my, rx, ry; if ((DWORD)i >= MAXMONSTERS) app_fatal("M_Teleport: Invalid monster %d", i); tren = FALSE; Monst = &monster[i]; if (Monst->_mmode != MM_STONE) { _mx = Monst->_menemyx; _my = Monst->_menemyy; rx = 2 * random_(100, 2) - 1; ry = 2 * random_(100, 2) - 1; for (j = -1; j <= 1 && !tren; j++) { for (k = -1; k < 1 && !tren; 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)) tren = TRUE; } } } } } if (tren) { 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); M_CheckEFlag(i); } } BOOL M_DoGotHit(int i) { if ((DWORD)i >= MAXMONSTERS) app_fatal("M_DoGotHit: Invalid monster %d", i); if (monster[i].MType == NULL) app_fatal("M_DoGotHit: Monster %d \"%s\" MType NULL", i, monster[i].mName); 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("M_UpdateLeader: Invalid monster %d", 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 (gbMaxPlayers > 1) { SNetLeaveGame(0x40000004); } music_stop(); if (gbMaxPlayers > 1) { Sleep(1000); } #ifndef SPAWN if (plr[myplr]._pClass == PC_WARRIOR) { play_movie("gendata\\DiabVic2.smk", FALSE); } else if (plr[myplr]._pClass == PC_SORCERER) { 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; #endif } void PrepDoEnding() { int newKillLevel, i; DWORD *killLevel; gbSoundOn = sgbSaveSoundOn; gbRunGame = FALSE; deathflag = FALSE; cineflag = TRUE; killLevel = &plr[myplr].pDiabloKillLevel; newKillLevel = gnDifficulty + 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 (gbMaxPlayers > 1) { 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 var1; int x, y; if ((DWORD)i >= MAXMONSTERS) app_fatal("M_DoDeath: Invalid monster %d", i); if (monster[i].MType == NULL) app_fatal("M_DoDeath: Monster %d \"%s\" MType NULL", i, monster[i].mName); monster[i]._mVar1++; var1 = 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 (var1 == 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) { if ((DWORD)i >= MAXMONSTERS) app_fatal("M_DoSpStand: Invalid monster %d", i); if (monster[i].MType == NULL) app_fatal("M_DoSpStand: Monster %d \"%s\" MType NULL", i, monster[i].mName); 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 mVar2; int oFrame; if ((DWORD)i >= MAXMONSTERS) app_fatal("M_DoDelay: Invalid monster %d", i); if (monster[i].MType == NULL) app_fatal("M_DoDelay: Monster %d \"%s\" MType NULL", i, monster[i].mName); 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; } mVar2 = monster[i]._mVar2; monster[i]._mVar2--; if (!mVar2) { oFrame = monster[i]._mAnimFrame; M_StartStand(i, monster[i]._mdir); monster[i]._mAnimFrame = oFrame; return TRUE; } return FALSE; } BOOL M_DoStone(int i) { if ((DWORD)i >= MAXMONSTERS) app_fatal("M_DoStone: Invalid monster %d", 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("M_WalkDir: Invalid monster %d", 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("GroupUnity: Invalid monster %d", i); if (monster[i].leaderflag) { 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]._mmode = MM_SATTACK; monster[leader]._mFlags &= ~MFLAG_ALLOW_SPECIAL; } } return; } 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]._mmode = MM_SATTACK; monster[m]._mFlags &= ~MFLAG_ALLOW_SPECIAL; } } } } } } } BOOL M_CallWalk(int i, int md) { int mdtemp; BOOL ok; mdtemp = md; ok = DirOK(i, md); if (random_(101, 2)) 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)) 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) { char path[25]; BOOL(*Check) (int, int, int); if ((DWORD)i >= MAXMONSTERS) app_fatal("M_PathWalk: Invalid monster %d", 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]]); /* plr2monst is local */ 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)) { // 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, int &dir) { int mdtemp; BOOL ok; if (dir) md = left[left[md]]; else md = right[right[md]]; ok = DirOK(i, md); mdtemp = 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; int md, v; if ((DWORD)i >= MAXMONSTERS) { app_fatal("MAI_Zombie: Invalid monster %d", i); } Monst = &monster[i]; if (Monst->_mmode != MM_STAND) { return; } mx = Monst->_mx; my = Monst->_my; if (!(dFlags[mx][my] & BFLAG_VISIBLE)) { return; } mx = mx - Monst->_menemyx; my = my - Monst->_menemyy; md = Monst->_mdir; v = random_(103, 100); if (abs(mx) >= 2 || abs(my) >= 2) { if (v < 2 * Monst->_mint + 10) { if (abs(mx) >= 2 * Monst->_mint + 4 || abs(my) >= 2 * Monst->_mint + 4) { 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 if (v < 2 * Monst->_mint + 10) { 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_SkelSd: Invalid monster %d", 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_Path: Invalid monster %d", 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_SHOOT) 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; if ((DWORD)i >= MAXMONSTERS) { app_fatal("MAI_Snake: Invalid monster %d", 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_Bat: Invalid monster %d", 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)) 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, 1, 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_SkelBow: Invalid monster %d", 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_Fat: Invalid monster %d", 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; if ((DWORD)i >= MAXMONSTERS) { app_fatal("MAI_Sneak: Invalid monster %d", 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->_mgoalvar1 = 0; Monst->_mgoal = MGOAL_RETREAT; } else { if (abs(mx) >= dist + 3 || abs(my) >= dist + 3 || Monst->_mgoalvar1 > 8) { Monst->_mgoalvar1 = 0; Monst->_mgoal = MGOAL_NORMAL; } } if (Monst->_mgoal == MGOAL_RETREAT) { if (Monst->_mFlags & MFLAG_TARGETS_MONSTER) 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)) 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_Fireman: Invalid monster %d", 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_SHOOT; Monst->_mgoalvar1 = 0; } } else if (Monst->_mgoal == MGOAL_SHOOT) { 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_SHOOT; } 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_SHOOT; } } else if (!M_CallWalk(i, md) && (Monst->_mgoal == MGOAL_NORMAL || Monst->_mgoal == MGOAL_RETREAT)) { M_StartFadein(i, md, FALSE); Monst->_mgoal = MGOAL_SHOOT; } } void MAI_Fallen(int i) { int x, y, xpos, ypos; int m, rad, md; MonsterStruct *Monst; if ((DWORD)i >= MAXMONSTERS) { app_fatal("MAI_Fallen: Invalid monster %d", i); } if (monster[i]._mgoal == MGOAL_SHOOT) { if (monster[i]._mgoalvar1) monster[i]._mgoalvar1--; else monster[i]._mgoal = MGOAL_NORMAL; } Monst = &monster[i]; if (monster[i]._mmode != MM_STAND || monster[i]._msquelch == 0) { return; } if (Monst->_mgoal == MGOAL_RETREAT) { if (!Monst->_mgoalvar1--) { Monst->_mgoal = MGOAL_NORMAL; M_StartStand(i, opposite[Monst->_mdir]); } } if (Monst->_mAnimFrame == Monst->_mAnimLen) { if (random_(113, 4)) { return; } if (!(Monst->_mFlags & MFLAG_NOHEAL)) { M_StartSpStand(i, Monst->_mdir); rad = 2 * Monst->_mint + 2; if (Monst->_mmaxhp - rad >= Monst->_mhitpoints) Monst->_mhitpoints = rad + Monst->_mhitpoints; else Monst->_mhitpoints = Monst->_mmaxhp; } rad = 2 * Monst->_mint + 4; for (y = -rad; y <= rad; y++) { for (x = -rad; x <= rad; x++) { if (y >= 0 && y < MAXDUNY && x >= 0 && x < MAXDUNX) { m = dMonster[x + Monst->_mx][y + Monst->_my]; if (m > 0) { m--; if (monster[m]._mAi == AI_FALLEN) { monster[m]._mgoal = MGOAL_SHOOT; monster[m]._mgoalvar1 = 30 * Monst->_mint + 105; } } } } } } else if (Monst->_mgoal == MGOAL_RETREAT) { md = Monst->_mdir; M_CallWalk(i, md); } else if (Monst->_mgoal == MGOAL_SHOOT) { xpos = Monst->_mx - Monst->_menemyx; ypos = Monst->_my - Monst->_menemyy; if (abs(xpos) < 2 && abs(ypos) < 2) { M_StartAttack(i); } else { md = M_GetDir(i); M_CallWalk(i, md); } } else { MAI_SkelSd(i); } } void MAI_Cleaver(int i) { MonsterStruct *Monst; int x, y, mx, my, md; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_Cleaver: Invalid monster %d", 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_Round: Invalid monster %d", 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_Ranged: Invalid monster %d", 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_AcidUniq(int i) { MAI_Ranged(i, MIS_ACID, TRUE); } void MAI_Scav(int i) { BOOL done; int x, y; int _mx, _my; MonsterStruct *Monst; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_Scav: Invalid monster %d", i); Monst = monster + i; _mx = Monst->_mx; _my = Monst->_my; done = FALSE; if (monster[i]._mmode != MM_STAND) return; if (Monst->_mhitpoints < (Monst->_mmaxhp >> 1) && Monst->_mgoal != MGOAL_HEALING) { if (Monst->leaderflag) { 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]) { M_StartEat(i); if (!(Monst->_mFlags & MFLAG_NOHEAL)) Monst->_mhitpoints += 64; if (Monst->_mhitpoints >= (Monst->_mmaxhp >> 1) + (Monst->_mmaxhp >> 2)) { 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_Garg: Invalid monster %d", 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_mmaxhp>> 1 && !(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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_RoundRanged: Invalid monster %d", 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); 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) >= 3 || abs(my) >= 3) && random_(122, 4 << lessmissiles) == 0)) { if (Monst->_mgoal != MGOAL_MOVE) { Monst->_mgoalvar1 = 0; Monst->_mgoalvar2 = random_(123, 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)) { 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 (((abs(mx) >= 3 || abs(my) >= 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 (abs(mx) >= 2 || abs(my) >= 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_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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_RR2: Invalid monster %d", 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); 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) >= 3 || abs(my) >= 3)) { if (Monst->_mgoal != MGOAL_MOVE) { Monst->_mgoalvar1 = 0; Monst->_mgoalvar2 = random_(123, 2); } Monst->_mgoal = MGOAL_MOVE; Monst->_mgoalvar3 = 4; if (abs(mx) > abs(my)) { dist = abs(mx); } else { dist = abs(my); } 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 (((abs(mx) >= 3 || abs(my) >= 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 (abs(mx) >= 2 || abs(my) >= 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_Golum: Invalid monster %d", 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) { if (have_enemy && MAI_Path(i)) return; } else if (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]._msquelch = UCHAR_MAX; } } } M_StartAttack(i); return; } monster[i]._pathcount++; if (monster[i]._pathcount > 8) monster[i]._pathcount = 5; ok = M_CallWalk(i, plr[i]._pdir); if (!ok) { md = (md - 1) & 7; for (j = 0; j < 8 && !ok; j++) { md = (md + 1) & 7; ok = DirOK(i, md); } if (!ok) { return; } M_WalkDir(i, md); } } void MAI_SkelKing(int i) { MonsterStruct *Monst; int mx, my, fx, fy, nx, ny; int dist, v, md; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_SkelKing: Invalid monster %d", 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); 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) >= 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 (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 (gbMaxPlayers == 1 && ((abs(mx) >= 3 || abs(my) >= 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 (abs(mx) >= 2 || abs(my) >= 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_Rhino: Invalid monster %d", 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); if (abs(mx) >= 2 || abs(my) >= 2) { if (Monst->_mgoal == MGOAL_MOVE || (abs(mx) >= 5 || abs(my) >= 5) && random_(132, 4) != 0) { if (Monst->_mgoal != MGOAL_MOVE) { 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); } } } else Monst->_mgoal = MGOAL_NORMAL; if (Monst->_mgoal == MGOAL_NORMAL) { if ((abs(mx) >= 5 || abs(my) >= 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); Monst->_mmode = MM_CHARGE; dMonster[Monst->_mx][Monst->_my] = -1 - i; } } else { if (abs(mx) >= 2 || abs(my) >= 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_Counselor(int i) { int mx, my, fx, fy; int dist, md, v; MonsterStruct *Monst; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_Counselor: Invalid monster %d", i); if (monster[i]._mmode == MM_STAND && monster[i]._msquelch != 0) { Monst = monster + i; 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) { if (abs(mx) > abs(my)) dist = abs(mx); else dist = abs(my); if ((abs(mx) >= 2 || abs(my) >= 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, 1, i, 4, 0); AddMissile(Monst->_mx, Monst->_my, 0, 0, Monst->_mdir, MIS_FLASH2, 1, 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_Garbud: Invalid monster %d", i); Monst = &monster[i]; if (Monst->_mmode != MM_STAND) { return; } _mx = Monst->_mx; _my = Monst->_my; md = M_GetDir(i); if (Monst->mtalkmsg < TEXT_GARBUD4 && Monst->mtalkmsg > TEXT_DOOM10 && !(dFlags[_mx][_my] & BFLAG_VISIBLE) && Monst->_mgoal == MGOAL_TALKING) { Monst->_mgoal = MGOAL_INQUIRING; Monst->mtalkmsg++; } if (dFlags[_mx][_my] & BFLAG_VISIBLE) { #ifndef SPAWN 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; } } #endif } 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, _mx, _my, md; MonsterStruct *Monst; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_Zhar: Invalid monster %d", 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) { _mx = Monst->_mx - Monst->_menemyx; _my = Monst->_my - Monst->_menemyy; if (abs(_mx) > abs(_my)) abs(_mx); else abs(_my); #ifndef SPAWN 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; } } #endif } 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_SnotSpil: Invalid monster %d", 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) { #ifndef SPAWN 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; } } #endif if (quests[Q_LTBANNER]._qvar1 == 3) { if (Monst->_mgoal == MGOAL_NORMAL || Monst->_mgoal == MGOAL_SHOOT) 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_Lazurus: Invalid monster %d", 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 (gbMaxPlayers == 1) { if (Monst->mtalkmsg == TEXT_VILE13 && Monst->_mgoal == MGOAL_INQUIRING && plr[myplr]._px == TEXT_VILE13 && plr[myplr]._py == 46) { PlayInGameMovie("gendata\\fprst3.smk"); Monst->_mmode = MM_TALK; quests[Q_BETRAYER]._qvar1 = 5; } #ifndef SPAWN if (Monst->mtalkmsg == TEXT_VILE13 && !effect_is_playing(USFX_LAZ1) && Monst->_mgoal == MGOAL_TALKING) { ObjChangeMapResync(1, 18, 20, 24); RedoPlayerVision(); Monst->_msquelch = UCHAR_MAX; Monst->mtalkmsg = 0; quests[Q_BETRAYER]._qvar1 = 6; Monst->_mgoal = MGOAL_NORMAL; } #endif } if (gbMaxPlayers != 1 && 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) { 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_Lazhelp: Invalid monster %d", 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 (gbMaxPlayers == 1) { if (quests[Q_BETRAYER]._qvar1 <= 5) { Monst->_mgoal = MGOAL_INQUIRING; } else { Monst->mtalkmsg = 0; Monst->_mgoal = MGOAL_NORMAL; } } 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_Lachdanan: Invalid monster %d", i); Monst = &monster[i]; if (monster[i]._mmode != MM_STAND) { return; } _mx = Monst->_mx; _my = Monst->_my; md = M_GetDir(i); #ifndef SPAWN 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); } } } #endif 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("MAI_Warlord: Invalid monster %d", 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; #ifndef SPAWN 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; } #endif } 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: assert((DWORD)nummonsters <= MAXMONSTERS); for (i = 0; i < nummonsters; i++) { mi = monstactive[i]; Monst = &monster[mi]; raflag = FALSE; if (gbMaxPlayers > 1) { SetRndSeed(Monst->_mAISeed); Monst->_mAISeed = GetRndSeed(); } 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; #ifndef SPAWN if (dFlags[mx][my] & BFLAG_VISIBLE && Monst->_msquelch == 0) { if (Monst->MType->mtype == MT_CLEAVER) { PlaySFX(USFX_CLEAVER); } } #endif if (Monst->_mFlags & MFLAG_TARGETS_MONSTER) { _menemy = Monst->_menemy; if ((DWORD)_menemy >= MAXMONSTERS) { app_fatal("Illegal enemy monster %d for monster \"%s\"", _menemy, Monst->mName); } Monst->_lastx = monster[Monst->_menemy]._mfutx; Monst->_menemyx = Monst->_lastx; Monst->_lasty = monster[Monst->_menemy]._mfuty; Monst->_menemyy = Monst->_lasty; } else { _menemy = Monst->_menemy; if ((DWORD)_menemy >= MAX_PLRS) { app_fatal("Illegal enemy player %d for monster \"%s\"", _menemy, Monst->mName); } Monst->_menemyx = plr[Monst->_menemy]._pfutx; Monst->_menemyy = plr[Monst->_menemy]._pfuty; if (dFlags[mx][my] & BFLAG_VISIBLE) { Monst->_msquelch = 255; Monst->_lastx = plr[Monst->_menemy]._pfutx; Monst->_lasty = plr[Monst->_menemy]._pfuty; } else if (Monst->_msquelch != 0 && Monst->_mAi != 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: raflag = M_DoWalk(mi); break; case MM_WALK2: raflag = M_DoWalk2(mi); break; case MM_WALK3: raflag = M_DoWalk3(mi); 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("DirOK: Invalid monster %d", 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; } if (mdir == DIR_W) { if (SolidLoc(fx + 1, fy) || dFlags[fx + 1][fy] & BFLAG_MONSTLR) return FALSE; } if (mdir == DIR_N) { if (SolidLoc(fx + 1, fy) || SolidLoc(fx, fy + 1)) return FALSE; } 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--; 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]] == FALSE; } 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; 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; dincH = 2 * (dy - dx); dincD = 2 * dy; yincD = 1; } else { d = 2 * dy + dx; dincH = 2 * (dx + dy); dincD = 2 * dy; yincD = -1; } while (x1 != x2 || y1 != y2) { if ((d <= 0) ^ (yincD < 0)) { d += dincD; } else { d += dincH; y1 += yincD; } x1++; if ((x1 != xorg || y1 != yorg) && !Clear(x1, y1)) break; } } 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; dincH = 2 * (dx - dy); dincD = 2 * dx; xincD = 1; } else { d = 2 * dx + dy; dincH = 2 * (dy + dx); dincD = 2 * dx; xincD = -1; } while (y1 != y2 || x1 != x2) { if ((d <= 0) ^ (xincD < 0)) { d += dincD; } else { d += dincH; x1 += xincD; } y1++; if ((y1 != yorg || x1 != xorg) && !Clear(x1, y1)) break; } } 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 xorg, yorg; int dx, dy; int d; int xincD, yincD, dincD, dincH; int tmp; 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; dincH = 2 * (dy - dx); dincD = 2 * dy; yincD = 1; } else { d = 2 * dy + dx; dincH = 2 * (dx + dy); dincD = 2 * dy; yincD = -1; } while (x1 != x2 || y1 != y2) { if ((d <= 0) ^ (yincD < 0)) { d += dincD; } else { d += dincH; y1 += yincD; } x1++; if ((x1 != xorg || y1 != yorg) && !Clear(monst, x1, y1)) break; } } 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; dincH = 2 * (dx - dy); dincD = 2 * dx; xincD = 1; } else { d = 2 * dx + dy; dincH = 2 * (dy + dx); dincD = 2 * dx; xincD = -1; } while (y1 != y2 || x1 != x2) { if ((d <= 0) ^ (xincD < 0)) { d += dincD; } else { d += dincH; x1 += xincD; } y1++; if ((y1 != yorg || x1 != xorg) && !Clear(monst, x1, y1)) break; } } return x1 == x2 && y1 == y2; } void SyncMonsterAnim(int i) { MonsterData *MData; int _mdir; MonsterStruct *Monst; if ((DWORD)i >= MAXMONSTERS) app_fatal("SyncMonsterAnim: Invalid monster %d", i); Monst = monster + i; Monst->MType = Monsters + Monst->_mMTidx; MData = Monsters[Monst->_mMTidx].MData; Monst->MData = MData; if (Monst->_uniqtype != 0) Monst->mName = UniqMonst[Monst->_uniqtype - 1].mName; else Monst->mName = MData->mName; _mdir = monster[i]._mdir; switch (Monst->_mmode) { case MM_WALK: case MM_WALK2: case MM_WALK3: Monst->_mAnimData = Monst->MType->Anims[MA_WALK].Data[_mdir]; return; case MM_ATTACK: case MM_RATTACK: Monst->_mAnimData = Monst->MType->Anims[MA_ATTACK].Data[_mdir]; return; case MM_GOTHIT: Monst->_mAnimData = Monst->MType->Anims[MA_GOTHIT].Data[_mdir]; return; case MM_DEATH: Monst->_mAnimData = Monst->MType->Anims[MA_DEATH].Data[_mdir]; return; case MM_SATTACK: case MM_FADEIN: case MM_FADEOUT: Monst->_mAnimData = Monst->MType->Anims[MA_SPECIAL].Data[_mdir]; return; case MM_SPSTAND: case MM_RSPATTACK: Monst->_mAnimData = Monst->MType->Anims[MA_SPECIAL].Data[_mdir]; return; case MM_HEAL: Monst->_mAnimData = Monst->MType->Anims[MA_SPECIAL].Data[_mdir]; return; case MM_STAND: Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[_mdir]; return; case MM_DELAY: Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[_mdir]; return; case MM_TALK: Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[_mdir]; return; case MM_CHARGE: Monst->_mAnimData = Monst->MType->Anims[MA_ATTACK].Data[_mdir]; Monst->_mAnimFrame = 1; Monst->_mAnimLen = Monst->MType->Anims[MA_ATTACK].Frames; break; default: Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[_mdir]; Monst->_mAnimFrame = 1; Monst->_mAnimLen = Monst->MType->Anims[MA_STAND].Frames; break; } } void M_FallenFear(int x, int y) { int i, mi, rundist, aitype; for (i = 0; i < nummonsters; i++) { rundist = 0; mi = monstactive[i]; switch (monster[mi].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; } aitype = monster[mi]._mAi; if (aitype == AI_FALLEN && rundist && abs(x - monster[mi]._mx) < 5 && abs(y - monster[mi]._my) < 5 && monster[mi]._mhitpoints >> 6 > 0) { monster[mi]._mgoal = MGOAL_RETREAT; monster[mi]._mgoalvar1 = rundist; monster[mi]._mdir = GetDirection(x, y, monster[i]._mx, monster[i]._my); } } } void PrintMonstHistory(int mt) { int minHP, maxHP, res; sprintf(tempstr, "Total kills : %i", monstkills[mt]); AddPanelString(tempstr, TRUE); if (monstkills[mt] >= 30) { minHP = monsterdata[mt].mMinHP; maxHP = monsterdata[mt].mMaxHP; if (gbMaxPlayers == 1) { minHP = monsterdata[mt].mMinHP >> 1; maxHP = monsterdata[mt].mMaxHP >> 1; } if (minHP < 1) minHP = 1; if (maxHP < 1) maxHP = 1; if (gnDifficulty == DIFF_NIGHTMARE) { minHP = 3 * minHP + 1; maxHP = 3 * maxHP + 1; } if (gnDifficulty == DIFF_HELL) { minHP = 4 * minHP + 3; maxHP = 4 * maxHP + 3; } sprintf(tempstr, "Hit Points : %i-%i", minHP, maxHP); AddPanelString(tempstr, TRUE); } if (monstkills[mt] >= 15) { if (gnDifficulty != DIFF_HELL) res = monsterdata[mt].mMagicRes; else res = monsterdata[mt].mMagicRes2; res = res & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING | IMUNE_MAGIC | IMUNE_FIRE | IMUNE_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 & (IMUNE_MAGIC | IMUNE_FIRE | IMUNE_LIGHTNING)) { strcpy(tempstr, "Immune : "); if (res & IMUNE_MAGIC) strcat(tempstr, "Magic "); if (res & IMUNE_FIRE) strcat(tempstr, "Fire "); if (res & IMUNE_LIGHTNING) strcat(tempstr, "Lightning "); tempstr[strlen(tempstr) - 1] = '\0'; AddPanelString(tempstr, TRUE); } } } pinfoflag = TRUE; } void PrintUniqueHistory() { int res; res = monster[pcursmonst].mMagicRes & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING | IMUNE_MAGIC | IMUNE_FIRE | IMUNE_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 & (IMUNE_MAGIC | IMUNE_FIRE | IMUNE_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; if ((DWORD)i >= MAXMISSILES) app_fatal("MissToMonst: Invalid missile %d", i); Miss = &missile[i]; m = Miss->_misource; if ((DWORD)m >= MAXMONSTERS) app_fatal("MissToMonst: Invalid monster %d", 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 != 7 && plr[pnum]._pmode != 8) 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, mi, j; BOOL ret, fire; fire = FALSE; ret = !SolidLoc(x, y) && !dPlayer[x][y] && !dMonster[x][y]; if (ret && dObject[x][y]) { oi = dObject[x][y] > 0 ? dObject[x][y] - 1 : -(dObject[x][y] + 1); if (object[oi]._oSolidFlag) ret = FALSE; } if (ret && dMissile[x][y] && i >= 0) { mi = dMissile[x][y]; if (mi > 0) { if (missile[mi]._mitype == MIS_FIREWALL) { // BUGFIX: Change 'mi' to 'mi - 1' fire = TRUE; } else { for (j = 0; j < nummissiles; j++) { if (missile[missileactive[j]]._mitype == MIS_FIREWALL) fire = TRUE; } } } if (fire && (!(monster[i].mMagicRes & IMUNE_FIRE) || monster[i].MType->mtype == MT_DIABLO)) ret = FALSE; } return ret; } BOOL PosOkMonst2(int i, int x, int y) { int oi, mi, j; BOOL ret, fire; fire = FALSE; ret = !SolidLoc(x, y); if (ret && dObject[x][y]) { oi = dObject[x][y] > 0 ? dObject[x][y] - 1 : -(dObject[x][y] + 1); if (object[oi]._oSolidFlag) ret = FALSE; } if (ret && dMissile[x][y] && i >= 0) { mi = dMissile[x][y]; if (mi > 0) { if (missile[mi]._mitype == MIS_FIREWALL) { // BUGFIX: Change 'mi' to 'mi - 1' fire = TRUE; } else { for (j = 0; j < nummissiles; j++) { if (missile[missileactive[j]]._mitype == MIS_FIREWALL) fire = TRUE; } } } if (fire && (!(monster[i].mMagicRes & IMUNE_FIRE) || monster[i].MType->mtype == MT_DIABLO)) ret = FALSE; } return ret; } BOOL PosOkMonst3(int i, int x, int y) { int j, oi, objtype, mi; BOOL ret, fire, isdoor; fire = FALSE; 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 && dMissile[x][y] != 0 && i >= 0) { mi = dMissile[x][y]; if (mi > 0) { if (missile[mi]._mitype == MIS_FIREWALL) { // BUGFIX: Change 'mi' to 'mi - 1' fire = TRUE; } else { for (j = 0; j < nummissiles; j++) { mi = missileactive[j]; if (missile[mi]._mitype == MIS_FIREWALL) { fire = TRUE; } } } } if (fire && (!(monster[i].mMagicRes & IMUNE_FIRE) || monster[i].MType->mtype == MT_DIABLO)) { ret = FALSE; } } 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; if ((DWORD)i >= MAXMONSTERS) app_fatal("TalktoMonster: Invalid monster %d", 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) { if ((DWORD)i >= MAXMONSTERS) app_fatal("SpawnGolum: Invalid monster %d", 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]._mFlags |= MFLAG_GOLEM; monster[i].mArmorClass = 25; monster[i]._mmaxhp = 2 * (320 * missile[mi]._mispllvl + plr[i]._pMaxMana / 3); monster[i]._mhitpoints = monster[i]._mmaxhp; 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); 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) { if ((DWORD)m >= MAXMONSTERS) { app_fatal("CanTalkToMonst: Invalid monster %d", m); } if (monster[m]._mgoal == MGOAL_INQUIRING) { return TRUE; } return monster[m]._mgoal == MGOAL_TALKING; } BOOL CheckMonsterHit(int m, BOOL &ret) { if ((DWORD)m >= MAXMONSTERS) { app_fatal("CheckMonsterHit: Invalid monster %d", 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; } }