/*-----------------------------------------------------------------------** ** Diablo ** ** Monsters file ** ** (C)1995 Condor, Inc. All rights reserved. ** **-----------------------------------------------------------------------** ** $Header: /Diablo/MONSTER.CPP 3 1/23/97 7:22p Rseis $ **-----------------------------------------------------------------------** ** ** File Routines **-----------------------------------------------------------------------*/ #include "diablo.h" #pragma hdrstop #include "storm/h/storm.h" #include "sound.h" #include "debug.h" #include "monster.h" #include "monstdat.h" #include "engine.h" #include "gendung.h" #include "lighting.h" #include "items.h" #include "itemdat.h" #include "player.h" #include "effects.h" #include "dead.h" #include "missiles.h" #include "misdat.h" #include "control.h" #include "objects.h" #include "objdat.h" #include "path.h" #include "quests.h" #include "minitext.h" #include "textdat.h" #include "monstint.h" #include "msg.h" #include "inv.h" #include "multi.h" #include "trigs.h" #include "themes.h" #include "drlg_l4.h" #include "towners.h" #include "setmaps.h" #include "palette.h" #include "scrollrt.h" #include "cursor.h" #include "drlg_l1.h" #include "spells.h" /*-----------------------------------------------------------------------* ** Function Prototypes **-----------------------------------------------------------------------*/ BOOL LineClear(int x1, int y1, int x2, int y2); int M_GetDir(int i); typedef BOOL (*CHECKFUNC)(int x, int y); typedef BOOL (*CHECKFUNC1)(int arg1, int x, int y); BOOL LineClearF(CHECKFUNC Clear, int x1, int y1, int x2, int y2); BOOL LineClearF1(CHECKFUNC1 Clear, int arg, int x1, int y1, int x2, int y2); BOOL CheckNoSolid(int x, int y); BOOL PosOkMissile(int x, int y); int M_SpawnSkel(int x, int y, int dir); void M_Teleport(int i); void PlaceGroup(int, int, BOOL, int); void ClrAllMonsters(); BOOL PosOkMonst2(int i, int x, int y); BOOL PosOkMonst3(int i, int x, int y); BOOL effect_is_playing(int nSFX); void SpawnMap(int x, int y); void SpawnBear(int x, int y); extern const TextDataStruct alltext[]; //void DaveMonstMap(BOOL initupdate, int pnum); /*-----------------------------------------------------------------------* ** Global Variables **-----------------------------------------------------------------------*/ MonsterStruct monster[MAXMONSTERS]; long nummonsters; int totalmonsters; // monstactive -- indexes into monster[] int monstactive[MAXMONSTERS]; // [0..nummonsters-1] -- active monsters // [nummonsters..MAXMONSTERS-1] -- available monsters int nummtypes = 0; long monstimgtot; int gfxflags; int uniquetrans; // index to next available unique monster palette translation int mleveltypes[NUMLEVELS][MAX_LVLMTYPES]; long monstkills[MONSTERTYPES]; static char sgszInvalidMonstName[] = "Invalid Monster"; /*-----------------------------------------------------------------------* ** Defines **-----------------------------------------------------------------------*/ #define PACK_MEMBER 1 #define PACK_NOMEMBER 2 #define INITMONSTRAD 15 // Initial vision radius for no monsters /*-----------------------------------------------------------------------* ** Macros **-----------------------------------------------------------------------*/ #define InBounds(x,y) (0 <= (y) && (y) < MAXDUNY && 0 <= (x) && (x) < MAXDUNX) #define WALKMODE(m) ((m) == MM_WALK || (m) == MM_WALK2 || (m) == MM_WALK3) #define Sign(x) ((x) < 0 ? -1 : (x) > 0 ? 1 : 0) #define Mod(val, x) ((val) < 0 ? (val)+(x) : (val) >= (x) ? (val)-(x) : (val)) /*-----------------------------------------------------------------------* ** File Variables **-----------------------------------------------------------------------*/ // I had to add this variable, because _mVar2 seems like // it must have been trashed, since sound never came on again -- pjw static BYTE sgbSaveSoundOn; // 16 32 64 int MWVel[24][3] = { {0x100,0x200,0x400}, // 1 frame {0x80,0x100,0x200}, // 2 frame {0x55,0xaa,0x155}, // 3 frame {0x40,0x80,0x100}, // 4 frame {0x33,0x66,0xcc}, // 5 frame {0x2a,0x55,0xaa}, // 6 frame {0x24,0x49,0x92}, // 7 frame {0x20,0x40,0x80}, // 8 frame {0x1c,0x38,0x71}, // 9 frame {0x1a,0x33,0x66}, // 10 frame {0x17,0x2e,0x5d}, // 11 frame {0x15,0x2a,0x55}, // 12 frame {0x13,0x27,0x4e}, // 13 frame {0x12,0x24,0x49}, // 14 frame {0x11,0x22,0x44}, // 15 frame {0x10,0x20,0x40}, // 16 frame {0x0f,0x1e,0x3c}, // 17 frame {0x0e,0x1c,0x39}, // 18 frame {0x0d,0x1a,0x36}, // 19 frame {0x0c,0x19,0x33}, // 20 frame {0x0c,0x18,0x30}, // 21 frame {0x0b,0x17,0x2e}, // 22 frame {0x0b,0x16,0x2c}, // 23 frame {0x0a,0x15,0x2a} }; // 24 frame // Muli-player monster multiplier % /*int MPMM[4] = { 100, // 1 player 150, // 2 player 175, // 3 player 200 }; // 4 player*/ CMonster Monsters[MAX_LVLMTYPES]; char animletter[] = "nwahds"; int left[] = {7,0,1,2,3,4,5,6}; int right[] = {1,2,3,4,5,6,7,0}; int opposite[] = {4,5,6,7,0,1,2,3}; int offset_x[] = {1,0,-1,-1,-1,0,1,1}; int offset_y[] = {1,1,1,0,-1,-1,-1,0}; static const int infront_x[] = { 1, 0,-1, -1, -1, 0, 1, 1}; static const int infront_y[] = { 1, 1, 1, 0, -1, -1, -1, 0}; int rnd5[] = { 5, 10, 15, 20 }; int rnd10[] = { 10, 15, 20, 30 }; int rnd20[] = { 20, 30, 40, 50 }; int rnd60[] = { 60, 70, 80, 90 }; void MAI_Zombie(int); void MAI_Fat(int); void MAI_SkelSd(int); void MAI_SkelBow(int); void MAI_Scav(int); void MAI_Rhino(int); void MAI_GoatMc(int); void MAI_GoatBow(int); void MAI_Fallen(int); void MAI_Magma(int); void MAI_SkelKing(int); void MAI_Bat(int); void MAI_Garg(int); void MAI_Cleaver(int); void MAI_Succ(int); void MAI_Lich(int); void MAI_ArchLich(int); void MAI_Psychorb(int); void MAI_Necromorb(int); void MAI_Sneak(int); void MAI_Storm(int); void MAI_Fireman(int i); void MAI_Garbud(int); void MAI_Acid(int); void MAI_AcidUniq(int); void MAI_Golum(int); void MAI_Zhar(int); void MAI_SnotSpil(int); void MAI_Snake(int); void MAI_Counselor(int); void MAI_Mega(int); void MAI_Diablo(int); void MAI_Lazurus(int); void MAI_Lazhelp(int); void MAI_Lachdanan(int); void MAI_Warlord(int); void MAI_Firebat(int); void MAI_Hellbat(int); void MAI_HorkDemon(int); void MAI_BoneDemon(int); void (*AiProc[NUM_AI])(int) = { MAI_Zombie, MAI_Fat, MAI_SkelSd, MAI_SkelBow, MAI_Scav, MAI_Rhino, MAI_GoatMc, MAI_GoatBow, MAI_Fallen, MAI_Magma, MAI_SkelKing, MAI_Bat, MAI_Garg, MAI_Cleaver, MAI_Succ, MAI_Sneak, MAI_Storm, MAI_Fireman, MAI_Garbud, MAI_Acid, MAI_AcidUniq, MAI_Golum, MAI_Zhar, MAI_SnotSpil, MAI_Snake, MAI_Counselor, MAI_Mega, MAI_Diablo, MAI_Lazurus, MAI_Lazhelp, MAI_Lachdanan, MAI_Warlord, MAI_Firebat, MAI_Hellbat, MAI_HorkDemon, MAI_Lich, MAI_ArchLich, MAI_Psychorb, MAI_Necromorb, MAI_BoneDemon, }; /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void TranslateMonsterGFX(int monst, BOOL HasSpecial) { app_assert((DWORD)monst < MAX_LVLMTYPES); BYTE * pData = Monsters[monst].pTrans; for (int n = 256; n--; ++pData) { if (*pData == 255) *pData = 0; } int const nf = HasSpecial ? 6 : 5; for (int j = 0; j < nf; ++j) { if ((j == 1) && (Monsters[monst].mtype >= MT_COUNSLR) && (Monsters[monst].mtype <= MT_ADVOCATE)) continue; for (int i = 0; i < 8; ++i) TranslateCels(Monsters[monst].Anims[j].Cels[i], Monsters[monst].pTrans, Monsters[monst].Anims[j].Frames); } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ #if 0 // UNUSED static BOOL MonstTaken(int i) { int lev; BOOL found = FALSE; int m; int templvl; // JKE // Temp hack for monsters JKE 7/30 templvl = currlevel; // if (templvl > 20) templvl -= 8; // else if (templvl > 16) templvl -= 4; // for(lev = 0; lev <= currlevel && !found; ++lev) // JKE for(lev = 0; lev <= templvl && !found; ++lev) { for(m = 0; m < MAX_LVLMTYPES && mleveltypes[m] && !found; ++m) found = ((i < mleveltypes[lev][m]-1) && !strcmp(monsterdata[i].filename, monsterdata[mleveltypes[lev][m]-1].filename)); } return found; } #endif // UNUSED /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void InitLevelMonsters() { int i; nummtypes = 0; monstimgtot = 0; gfxflags = 0; for (i = 0; i < MAX_LVLMTYPES; ++i) Monsters[i].mPlaceFlags = 0; ClrAllMonsters(); nummonsters = 0; totalmonsters = MAXMONSTERS; // this value gets further restricted later on // active monsters list for (i = 0; i < MAXMONSTERS; ++i) { monstactive[i] = i; } uniquetrans = 0; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int AddMonsterType(int type, int placeflag) { int i; BOOL done = FALSE; // check if this type is already being loaded app_assert((DWORD)nummtypes <= MAX_LVLMTYPES); for (i = 0; i < nummtypes && !done; ++i) done = Monsters[i].mtype == type; --i; if (!done) { i = nummtypes++; Monsters[i].mtype = type; monstimgtot += monsterdata[type].mImgSize; InitMonsterGFX(i); InitMonsterSND(i); } Monsters[i].mPlaceFlags |= placeflag; return i; } /*-----------------------------------------------------------------------* * GetLevelMTypes() * * Pick the monsters types to be placed in this level **-----------------------------------------------------------------------*/ void GetLevelMTypes() { int typelist[MONSTERTYPES]; int nt, i; int tidx, mt; int minl,maxl; char mamask; int templvl; // temp hack JKE #if IS_VERSION(SHAREWARE) mamask = MAT_SW; #else mamask = MAT_SW | MAT_YES; #endif // Certain monsters are required for certain levels AddMonsterType(MT_GOLEM, MPFLAG_DONT); if (currlevel == 16) { AddMonsterType(MT_ADVOCATE, MPFLAG_SCATTER); AddMonsterType(MT_RBLACK, MPFLAG_SCATTER); AddMonsterType(MT_DIABLO, MPFLAG_DONT); return; } if (currlevel == HIVESTART + 1) { AddMonsterType(MT_SPAWN, MPFLAG_SCATTER); } if (currlevel == HIVESTART + 2) { AddMonsterType(MT_SPAWN, MPFLAG_SCATTER); AddMonsterType(MT_HORKD, MPFLAG_UNIQ); } if (currlevel == HIVEEND) { AddMonsterType(MT_BUG, MPFLAG_UNIQ); } if (currlevel == NA_KRUL_LEVEL /*CRYPTEND*/) { AddMonsterType(MT_LICH2, MPFLAG_SCATTER); AddMonsterType(MT_NKR, MPFLAG_DONT); } // Set levels load their own monster types if (!setlevel) { if (QuestStatus(Q_BUTCHER)) { AddMonsterType(MT_CLEAVER, MPFLAG_DONT); } if (QuestStatus(Q_GARBUD)) { AddMonsterType(UniqMonst[MU_GARBUD].mtype, MPFLAG_UNIQ); } if (QuestStatus(Q_ZHAR)) { AddMonsterType(UniqMonst[MU_ZHAR].mtype, MPFLAG_UNIQ); } if (QuestStatus(Q_LTBANNER)) { AddMonsterType(UniqMonst[MU_SNOTSPIL].mtype, MPFLAG_UNIQ); } if (QuestStatus(Q_VEIL)) { AddMonsterType(UniqMonst[MU_LACHDA].mtype, MPFLAG_UNIQ); } if (QuestStatus(Q_WARLORD)) { AddMonsterType(UniqMonst[MU_WARLORD].mtype, MPFLAG_UNIQ); } if (gbMaxPlayers != 1 && currlevel == quests[Q_SKELKING]._qlevel) { AddMonsterType(MT_SKING, MPFLAG_UNIQ); // Skelking requires skeleton minions int skeltypes[LASTMT]; int numskeltypes = 0; for (i = MT_WSKELAX; i <= MT_XSKELSD; ++i) { if (IsSkel(i)) { minl = ((monsterdata[i].mMinDLvl * 15) / 30) + 1; maxl = ((monsterdata[i].mMaxDLvl * 15) / 30) + 1; if ((currlevel >= minl) && (currlevel <= maxl) && (MonstAvailTbl[i] & mamask)) skeltypes[numskeltypes++] = i; } } AddMonsterType(skeltypes[random(88,numskeltypes)], MPFLAG_SCATTER); } // Pick general monsters // Make list of available monster types for this level // Temp hack monster JKE templvl = currlevel; // if (templvl > 20) templvl -=8; // else if (templvl > 16) templvl -= 4; nt = 0; for (i = 0; i < LASTMT; ++i) { minl = ((monsterdata[i].mMinDLvl * 15) / 30) + 1; maxl = ((monsterdata[i].mMaxDLvl * 15) / 30) + 1; // if ((currlevel >= minl) && (currlevel <= maxl) && (MonstAvailTbl[i] & mamask)) { // typelist[nt] = i; // ++nt; // } // use templvl to patch over current level if ((templvl >= minl) && (templvl <= maxl) && (MonstAvailTbl[i] & mamask)) { typelist[nt] = i; ++nt; } } if (monstdebug) { for(i = 0; i < debugmonsttypes; ++i) AddMonsterType(DebugMonsters[i], MPFLAG_SCATTER); } else { while ((nt > 0) && (nummtypes < MAX_LVLMTYPES) && (monstimgtot < IMG_MAX)) { // prune excessively large monsters for (i = 0; i < nt; ) { if (monsterdata[typelist[i]].mImgSize > IMG_MAX - monstimgtot) typelist[i] = typelist[--nt]; else ++i; } if (nt) { tidx = random(88, nt); mt = typelist[tidx]; AddMonsterType(mt, MPFLAG_SCATTER); typelist[tidx] = typelist[--nt]; } } } } else if (setlvlnum == SL_SKELKING) { AddMonsterType(MT_SKING, MPFLAG_UNIQ); } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void InitMonsterGFX (int monst) { int anim; long i; char strBuff[256]; int mtype; BYTE *p; app_assert((DWORD)monst < MAX_LVLMTYPES); mtype = Monsters[monst].mtype; for(anim = 0; anim < MAX_ANIMTYPE; ++anim) { if((!(animletter[anim] == 's' && !monsterdata[mtype].has_special)) && monsterdata[mtype].Frames[anim] > 0) { sprintf(strBuff, monsterdata[mtype].filename, animletter[anim]); app_assert(! Monsters[monst].Anims[anim].CMem); Monsters[monst].Anims[anim].CMem = LoadFileInMemSig(strBuff,NULL,'MONS'); p = Monsters[monst].Anims[anim].CMem; if (Monsters[monst].mtype == MT_GOLEM && (animletter[anim] == 's' || animletter[anim] == 'd')) { for(i=0; i<8; ++i) Monsters[monst].Anims[anim].Cels[i] = p; } else { for(i=0; i<8; ++i) Monsters[monst].Anims[anim].Cels[i] = p + *(DWORD *)(p + (i<<2)); } } Monsters[monst].Anims[anim].Frames = monsterdata[mtype].Frames[anim]; Monsters[monst].Anims[anim].Rate = monsterdata[mtype].Rate[anim]; } Monsters[monst].mAnimWidth = monsterdata[mtype].mAnimWidth; Monsters[monst].mAnimWidth2 = (monsterdata[mtype].mAnimWidth - 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].transflag) { Monsters[monst].pTrans = LoadFileInMemSig(monsterdata[mtype].TransFile,NULL,'MONS'); TranslateMonsterGFX(monst, monsterdata[mtype].has_special); DiabloFreePtr(Monsters[monst].pTrans); } if (EquivMonst(mtype, MT_NMAGMA) && ((gfxflags & 0x0001) == 0)) { gfxflags |= 0x0001; ILoadMissileGFX(MF_MAGBALL); } if (EquivMonst(mtype, MT_STORM) && ((gfxflags & 0x0002) == 0)) { gfxflags |= 0x0002; ILoadMissileGFX(MF_THINLIGHT); } if ((mtype == MT_SUCCUBUS) && ((gfxflags & 0x0004) == 0)) { gfxflags |= 0x0004; // ILoadMissileGFX(MF_FLARE); already used for Blood Star // ILoadMissileGFX(MF_FLAREXP); } if (EquivMonst(mtype, MT_INCIN) && ((gfxflags & 0x0008) == 0)) { gfxflags |= 0x0008; ILoadMissileGFX(MF_KRULL); } if ((EquivMonst(mtype, MT_NACID) || mtype == MT_SPIDER2) && ((gfxflags & 0x0010) == 0)) { gfxflags |= 0x0010; ILoadMissileGFX(MF_ACID); ILoadMissileGFX(MF_ACIDSPLAT); ILoadMissileGFX(MF_ACIDPUD); } if ((mtype == MT_SNOWWICH) && ((gfxflags & 0x0020) == 0)) { gfxflags |= 0x0020; ILoadMissileGFX(MF_BFLARE); ILoadMissileGFX(MF_BFLAREXP); } if ((mtype == MT_HLSPWN) && ((gfxflags & 0x0040) == 0)) { gfxflags |= 0x0040; ILoadMissileGFX(MF_DFLARE); ILoadMissileGFX(MF_DFLAREXP); } if ((mtype == MT_SOLBRNR) && ((gfxflags & 0x0080) == 0)) { gfxflags |= 0x0080; ILoadMissileGFX(MF_CFLARE); ILoadMissileGFX(MF_CFLAREXP); } if ((mtype == MT_LICH) && ((gfxflags & 0x0100) == 0)) { gfxflags |= 0x0100; ILoadMissileGFX(MF_ORANGEFLARE); ILoadMissileGFX(MF_ORANGEEXPLOSION); } if ((mtype == MT_LICH2) && ((gfxflags & 0x0200) == 0)) { gfxflags |= 0x0200; ILoadMissileGFX(MF_YELLOWFLARE); ILoadMissileGFX(MF_YELLOWEXPLOSION); } if ((mtype == MT_EYE || mtype == MT_BONED2) && ((gfxflags & 0x0400) == 0)) { gfxflags |= 0x0400; ILoadMissileGFX(MF_BLUE2FLARE); } if ((mtype == MT_EYE2) && ((gfxflags & 0x0800) == 0)) { gfxflags |= 0x0800; ILoadMissileGFX(MF_REDFLARE); ILoadMissileGFX(MF_REDEXPLOSION); } if ((mtype == MT_EYE) && ((gfxflags & 0x1000) == 0)) { gfxflags |= 0x1000; ILoadMissileGFX(MF_BLUEEXPLOSION); } if ((mtype == MT_BONED2) && ((gfxflags & 0x2000) == 0)) { gfxflags |= 0x2000; ILoadMissileGFX(MF_BLUE2EXPLOSION); } if (mtype == MT_DIABLO) ILoadMissileGFX(MF_FIREPLAR); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void ClearMVars(int i) { app_assert((DWORD)i < MAXMONSTERS); 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) { app_assert((DWORD)i < MAXMONSTERS); app_assert((DWORD)mtype < MAX_LVLMTYPES); CMonster *monst = &Monsters[mtype]; app_assert(monst->MData != NULL); 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].Cels[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, 1666 - 1666 + 1) + 1666) << HP_SHIFT; monster[i]._mmaxhp = (random(88, 3333 - 3333 + 1) + 3333) << HP_SHIFT; } else { monster[i]._mmaxhp = (random(88, monst->mMaxHP - monst->mMinHP + 1) + monst->mMinHP) << HP_SHIFT; } /* if (gbMaxPlayers != 1) monster[i]._mmaxhp = (monster[i]._mmaxhp * MPMM[gbActivePlayers-1]) / 100; else monster[i]._mmaxhp = monster[i]._mmaxhp >> 1; if (monster[i]._mmaxhp < (1 << HP_SHIFT)) monster[i]._mmaxhp = 1 << HP_SHIFT;*/ if (gbMaxPlayers == 1) { monster[i]._mmaxhp = monster[i]._mmaxhp >> 1; if (monster[i]._mmaxhp < (1 << HP_SHIFT)) monster[i]._mmaxhp = 1 << HP_SHIFT; } monster[i]._mhitpoints = monster[i]._mmaxhp; monster[i]._mAi = monst->MData->mAi; monster[i]._mint = monst->MData->mInt; monster[i]._mgoal = MG_ATTACK; monster[i]._mgoalvar1 = 0; monster[i]._mgoalvar2 = 0; monster[i]._mgoalvar3 = 0; monster[i]._mgoalvar4 = 0; monster[i]._pathcount = 0; monster[i]._mDelFlag = FALSE; monster[i]._uniqtype = 0; monster[i]._msquelch = 0; monster[i].mlid = 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].Cels[rd]; monster[i]._mAnimFrame = 1; monster[i]._mFlags |= MFLAG_STILL; monster[i]._mmode = MM_SATTACK; } if (gnDifficulty == D_NIGHTMARE) { monster[i]._mmaxhp = (monster[i]._mmaxhp * 3) + (((gbMaxPlayers == 1) ? 50 : 100) << HP_SHIFT); monster[i]._mhitpoints = monster[i]._mmaxhp; monster[i].mLevel += 15; monster[i].mExp = (monster[i].mExp << 1) + 2000; monster[i].mHit += 85; monster[i].mMinDamage = (monster[i].mMinDamage * 2) + 4; monster[i].mMaxDamage = (monster[i].mMaxDamage * 2) + 4; monster[i].mHit2 += 85; monster[i].mMinDamage2 = (monster[i].mMinDamage2 * 2) + 4; monster[i].mMaxDamage2 = (monster[i].mMaxDamage2 * 2) + 4; monster[i].mArmorClass += 50; } else if (gnDifficulty == D_HELL) { monster[i]._mmaxhp = (monster[i]._mmaxhp * 4) + (((gbMaxPlayers == 1) ? 100 : 200) << HP_SHIFT); monster[i]._mhitpoints = monster[i]._mmaxhp; monster[i].mLevel += 30; monster[i].mExp = (monster[i].mExp << 2) + 4000; monster[i].mHit += 120; monster[i].mMinDamage = (monster[i].mMinDamage * 4) + 6; monster[i].mMaxDamage = (monster[i].mMaxDamage * 4) + 6; monster[i].mHit2 += 120; monster[i].mMinDamage2 = (monster[i].mMinDamage2 * 4) + 6; monster[i].mMaxDamage2 = (monster[i].mMaxDamage2 * 4) + 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); //track down freeloaders from previous levels by making them obvious Monst->mName = sgszInvalidMonstName; Monst->_mgoal = 0; Monst->_mmode = MM_STAND; Monst->_mVar1 = MM_STAND; 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) { if (xp < 0 || xp >= DMAXX || yp < 0 || yp >= DMAXY) return FALSE; if (dMonster[xp][yp] != 0) return FALSE; if (dPlayer[xp][yp] != 0) return FALSE; if (dFlags[xp][yp] & BFLAG_MONSTACTIVE) return FALSE; if (dFlags[xp][yp] & BFLAG_SETPC) return FALSE; if (SolidLoc(xp,yp)) return FALSE; return TRUE; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void PlaceMonster(int i, int mtype, int x, int y) { int rd; // stoopid hack to NEVER get duplicate Na-Kruls if (Monsters[mtype].mtype == MT_NKR) { int monstindex; for (monstindex = 0; monstindex < nummonsters; ++monstindex) if (monster[monstindex]._mMTidx == mtype || monster[monstindex].MType->mtype == MT_NKR) return; } dMonster[x][y] = i + 1; rd = random(90, 8); InitMonster(i, rd, mtype, x, y); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void LoadUniMonstTrans(int uid) { } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void PlaceUniqueMonst(int uniqindex, int miniontype, int packsize) { app_assert((DWORD)nummonsters < MAXMONSTERS); UniqMonstStruct *Uniq = &UniqMonst[uniqindex]; MonsterStruct *Monst = &monster[nummonsters]; int xp,yp; int x,y; BOOL done; int count, count2; char filestr[64]; done = FALSE; count2 = 0; int uniqtype; // Check if too many uniques. Limit is unique palette translation table size if((uniquetrans << 8) + 4864 >= LIGHTSIZE) return; // find the index of the monster graphics app_assert((DWORD)nummtypes <= MAX_LVLMTYPES); for (uniqtype = 0; uniqtype < nummtypes; ++uniqtype) if (Monsters[uniqtype].mtype == UniqMonst[uniqindex].mtype) break; app_assert(uniqtype < nummtypes); do { xp = random(91, DMAXX - DIRTEDGE) + (DIRTEDGED2); yp = random(91, DMAXY - DIRTEDGE) + (DIRTEDGED2); count = 0; for(x = xp-3; x < xp+3; ++x) for(y = yp-3; y < yp+3; ++y) if(InBounds(x,y) && MonstPlace(x,y)) ++count; } while((count < 9 // try finding a clear spot && ++count2 < 1000) // avoid infinite loop || !MonstPlace(xp,yp)); // make sure there's at least // a spot for the leader if(uniqindex == MU_SNOTSPIL) { xp = (setpc_x << 1) + DIRTEDGED2 + 8; yp = (setpc_y << 1) + DIRTEDGED2 + 12; } if(uniqindex == MU_WARLORD) { xp = (setpc_x << 1) + DIRTEDGED2 + 6; yp = (setpc_y << 1) + DIRTEDGED2 + 7; } if(uniqindex == MU_ZHAR) { int i; BOOL zharflag = TRUE; for (i = 0; i < themeCount; ++i) { if ((i == zharlib) && (zharflag == TRUE)) { zharflag = FALSE; xp = ((themeLoc[i].x << 1) + DIRTEDGED2)+4; yp = ((themeLoc[i].y << 1) + DIRTEDGED2)+4; } } } if (gbMaxPlayers == 1) { if(uniqindex == MU_LAZARUS) { xp = 32; yp = 46; } if(uniqindex == MU_REDVEX) { xp = 40; yp = 45; } if(uniqindex == MU_BLKJADE) { xp = 38; yp = 49; } if(uniqindex == MU_SKELKING) { xp = 35; yp = 47; } } else { if(uniqindex == MU_LAZARUS) { xp = (setpc_x << 1) + DIRTEDGED2 + 3; yp = (setpc_y << 1) + DIRTEDGED2 + 6; } if(uniqindex == MU_REDVEX) { xp = (setpc_x << 1) + DIRTEDGED2 + 5; yp = (setpc_y << 1) + DIRTEDGED2 + 3; } if(uniqindex == MU_BLKJADE) { xp = (setpc_x << 1) + DIRTEDGED2 + 5; yp = (setpc_y << 1) + DIRTEDGED2 + 9; } } if (uniqindex == MU_CLEAVER) { done = FALSE; // Find certain tile where Butcher goes for (yp = 0; yp < DMAXY && !done; ++yp) for (xp = 0; xp < DMAXX && !done; ++xp) done = (dPiece[xp][yp] == 367); // NOTE: xp and yp get incremented an extra time after done=TRUE, but // coincidentally, that's where we want to place butcher } if (uniqindex == MU_NAKRUL) { if (Na_Krul.x == 0 || Na_Krul.y == 0) { Na_Krul.MIndex = -1; return; } xp = Na_Krul.x - 2; yp = Na_Krul.y; Na_Krul.MIndex = nummonsters; } PlaceMonster(nummonsters, uniqtype, xp, yp); Monst->_uniqtype = uniqindex+1; if (Uniq->mlevel != 0) Monst->mLevel = Uniq->mlevel*2; else Monst->mLevel += 5; Monst->mExp = (Monst->mExp*2); Monst->mName = Uniq->mName; Monst->_mmaxhp = Uniq->mmaxhp << HP_SHIFT; /* if (gbMaxPlayers != 1) Monst->_mmaxhp = (Monst->_mmaxhp * MPMM[gbActivePlayers-1]) / 100; else Monst->_mmaxhp = Monst->_mmaxhp >> 1; if (Monst->_mmaxhp < (1 << HP_SHIFT)) Monst->_mmaxhp = 1 << HP_SHIFT;*/ if (gbMaxPlayers == 1) { Monst->_mmaxhp = Monst->_mmaxhp >> 1; if (Monst->_mmaxhp < (1 << HP_SHIFT)) Monst->_mmaxhp = 1 << HP_SHIFT; } 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; // Don't light up the hork demon, it washes him out. if ( uniqindex == MU_HORKDEMON) { Monst->mlid = 0; } else { Monst->mlid = AddLight(Monst->_mx, Monst->_my, 3); } if ((gbMaxPlayers != 1) && (Monst->_mAi == AI_LAZHELP)) Monst->mtalkmsg = 0; if (Monst->mtalkmsg != 0) Monst->_mgoal = MG_TALK; if (gnDifficulty == D_NIGHTMARE) { Monst->_mmaxhp = (Monst->_mmaxhp * 3) + (((gbMaxPlayers == 1) ? 50 : 100) << HP_SHIFT); Monst->_mhitpoints = Monst->_mmaxhp; Monst->mLevel += 15; Monst->mExp = (Monst->mExp << 1) + 2000; Monst->mMinDamage = (Monst->mMinDamage * 2) + 4; Monst->mMaxDamage = (Monst->mMaxDamage * 2) + 4; Monst->mMinDamage2 = (Monst->mMinDamage2 * 2) + 4; Monst->mMaxDamage2 = (Monst->mMaxDamage2 * 2) + 4; } else if (gnDifficulty == D_HELL) { Monst->_mmaxhp = (Monst->_mmaxhp * 4) + (((gbMaxPlayers == 1) ? 100 : 200) << HP_SHIFT); Monst->_mhitpoints = Monst->_mmaxhp; Monst->mLevel += 30; Monst->mExp = (Monst->mExp << 2) + 4000; Monst->mMinDamage = (Monst->mMinDamage * 4) + 6; Monst->mMaxDamage = (Monst->mMaxDamage * 4) + 6; Monst->mMinDamage2 = (Monst->mMinDamage2 * 4) + 6; Monst->mMaxDamage2 = (Monst->mMaxDamage2 * 4) + 6; } // Load unique monster translations sprintf(filestr, "Monsters\\Monsters\\%s.TRN", Uniq->mTFile); app_assert((uniquetrans << 8) + 4864 < LIGHTSIZE); LoadFileWithMem(filestr, pLightTbl + (uniquetrans << 8) + 4864); Monst->_uniqtrans = uniquetrans; ++uniquetrans; if(Uniq->mUnqAttr & UN_H) { Monst->mHit = Uniq->mUnqVar1; Monst->mHit2 = Uniq->mUnqVar1; } if(Uniq->mUnqAttr & UN_A) { Monst->mArmorClass = Uniq->mUnqVar1; } ++nummonsters; if(Uniq->mUnqAttr & UN_PACK) { PlaceGroup(miniontype, packsize, Uniq->mUnqAttr, nummonsters-1); } // quick fix for monsters that were gargoyles before they became unique if (Monst->_mAi != AI_GARG) { Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[Monst->_mdir]; Monst->_mAnimFrame = random(88, Monst->_mAnimLen - 1) + 1; Monst->_mFlags &= ~MFLAG_STILL; Monst->_mmode = MM_STAND; } } void Hose_NaKrul() { if (currlevel == NA_KRUL_LEVEL) { int mi = Na_Krul.MIndex; if (mi < 0 || mi >= nummonsters) return; MonsterStruct *Monst = &monster[mi]; PlayEffect(mi, MS_DEATH); quests[Q_NA_KRUL]._qlog = FALSE; Monst->_mmaxhp = Monst->_mmaxhp / 2; Monst->_mhitpoints = Monst->_mmaxhp; Monst->mArmorClass -= 50; Monst->mMagicRes = 0x0000; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ #if !IS_VERSION(SHAREWARE) static void PlaceUniques() { int u; int mt; BOOL done; //uniquetrans = 0; for(u = 0; UniqMonst[u].mtype != -1; ++u) { #if CHEATS if ((UniqMonst[u].mlevel == currlevel) || (UniqMonst[u].mlevel && davedebug)) { #else if (UniqMonst[u].mlevel == currlevel) { #endif done = FALSE; app_assert((DWORD)nummtypes <= MAX_LVLMTYPES); for(mt = 0; mt < nummtypes && !done; ++mt) done = Monsters[mt].mtype == UniqMonst[u].mtype; --mt; if ((u == MU_GARBUD) && (quests[Q_GARBUD]._qactive == QUEST_NOTAVAIL)) done = FALSE; if ((u == MU_ZHAR) && (quests[Q_ZHAR]._qactive == QUEST_NOTAVAIL)) done = FALSE; if ((u == MU_SNOTSPIL) && (quests[Q_LTBANNER]._qactive == QUEST_NOTAVAIL)) done = FALSE; if ((u == MU_LACHDA) && (quests[Q_VEIL]._qactive == QUEST_NOTAVAIL)) done = FALSE; if ((u == MU_WARLORD) && (quests[Q_WARLORD]._qactive == QUEST_NOTAVAIL)) done = FALSE; if(done) { PlaceUniqueMonst(u, mt, 8); } } } } #endif /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ #if !IS_VERSION(SHAREWARE) static void PlaceQuestMonsters() { int skeltype; byte *setp; if (!setlevel) { if (QuestStatus(Q_BUTCHER)) PlaceUniqueMonst(MU_CLEAVER, 0, 0); if ((currlevel == quests[Q_SKELKING]._qlevel) && (gbMaxPlayers != 1)) { app_assert((DWORD)nummtypes <= MAX_LVLMTYPES); for (skeltype = 0; skeltype < nummtypes; ++skeltype) if (IsSkel(Monsters[skeltype].mtype)) break; app_assert(skeltype < nummtypes); PlaceUniqueMonst(MU_SKELKING, skeltype, 30); } if (QuestStatus(Q_LTBANNER)) { setp = LoadFileInMemSig("Levels\\L1Data\\Banner1.DUN",NULL,'MONS'); SetMapMonsters(setp, (setpc_x << 1), (setpc_y << 1)); DiabloFreePtr(setp); } if (QuestStatus(Q_BLOOD)) { setp = LoadFileInMemSig("Levels\\L2Data\\Blood2.DUN", NULL, 'MONS'); SetMapMonsters(setp, (setpc_x << 1), (setpc_y << 1)); DiabloFreePtr(setp); } if (QuestStatus(Q_BLIND)) { setp = LoadFileInMemSig("Levels\\L2Data\\Blind2.DUN", NULL, 'MONS'); SetMapMonsters(setp, (setpc_x << 1), (setpc_y << 1)); DiabloFreePtr(setp); } if (QuestStatus(Q_ANVIL)) { setp = LoadFileInMemSig("Levels\\L3Data\\Anvil.DUN",NULL,'MONS'); SetMapMonsters(setp, ((setpc_x+1) << 1), ((setpc_y+1) << 1)); DiabloFreePtr(setp); } if (QuestStatus(Q_WARLORD)) { setp = LoadFileInMemSig("Levels\\L4Data\\Warlord.DUN",NULL,'MONS'); SetMapMonsters(setp, (setpc_x << 1), (setpc_y << 1)); DiabloFreePtr(setp); AddMonsterType(UniqMonst[MU_WARLORD].mtype, MPFLAG_SCATTER); // to allow scatterable monst of mtype } if (QuestStatus(Q_VEIL)) { AddMonsterType(UniqMonst[MU_LACHDA].mtype, MPFLAG_SCATTER); // to allow scatterable monst of mtype } if (QuestStatus(Q_ZHAR)) { if (zharlib == -1) quests[Q_ZHAR]._qactive = QUEST_NOTAVAIL; } if ((currlevel == quests[Q_BETRAYER]._qlevel) && (gbMaxPlayers != 1)) { AddMonsterType(UniqMonst[MU_LAZARUS].mtype, MPFLAG_UNIQ); AddMonsterType(UniqMonst[MU_REDVEX].mtype, MPFLAG_UNIQ); PlaceUniqueMonst(MU_LAZARUS, 0, 0); PlaceUniqueMonst(MU_REDVEX, 0, 0); PlaceUniqueMonst(MU_BLKJADE, 0, 0); setp = LoadFileInMemSig("Levels\\L4Data\\Vile1.DUN",NULL,'MONS'); SetMapMonsters(setp, (setpc_x << 1), (setpc_y << 1)); DiabloFreePtr(setp); } if (currlevel == NA_KRUL_LEVEL) { //if ((quests[Q_NA_KRUL]._qactive != QUEST_NOTAVAIL) && // (quests[Q_NA_KRUL]._qlog)) { int numminions = 2; if (gnDifficulty == D_NIGHTMARE) numminions = 4; else if (gnDifficulty == D_HELL) numminions = 6; // see if we already have a Na-Krul Na_Krul.MIndex = -1; int uniqtype; for (uniqtype = 0; uniqtype < nummtypes; ++uniqtype) if (Monsters[uniqtype].mtype == UniqMonst[MU_NAKRUL].mtype) break; if (uniqtype < nummtypes) { int monstindex; for (monstindex = 0; monstindex < nummonsters; ++monstindex) { if (monster[monstindex]._uniqtype != 0 || monster[monstindex]._mMTidx == uniqtype) { Na_Krul.MIndex = monstindex; break; } } } if (Na_Krul.MIndex == -1) // didn't find { PlaceUniqueMonst(MU_NAKRUL, 0, 0); } } } } else if (setlvlnum == SL_SKELKING) { PlaceUniqueMonst(MU_SKELKING, 0, 0); } } #endif /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void PlaceGroup(int mtype, int num, BOOL leaderf, int leader) { int xp, yp; int x1, y1; int j; int placed = 0; int try1 = 0; int try2; int rd; app_assert((DWORD)leader < MAXMONSTERS); do { // Clear out the subset placed last time through the loop while(placed) { --nummonsters; --placed; app_assert((DWORD)nummonsters < MAXMONSTERS); app_assert((DWORD)monster[nummonsters]._mx < MAXDUNX); app_assert((DWORD)monster[nummonsters]._my < MAXDUNY); dMonster[monster[nummonsters]._mx][monster[nummonsters]._my] = 0; } if(leaderf & UN_PACK) { rd = random(92, 8); x1 = xp = monster[leader]._mx + offset_x[rd]; y1 = yp = monster[leader]._my + offset_y[rd]; } else { do { x1 = xp = random(93, DMAXX - DIRTEDGE) + (DIRTEDGED2); y1 = yp = random(93, DMAXY - DIRTEDGE) + (DIRTEDGED2); } while (!MonstPlace(xp, yp)); } if ((nummonsters + num) > totalmonsters) num = totalmonsters - nummonsters; j = 0; try2 = 0; while (j < num && try2 < 100) { if (MonstPlace(xp, yp) && (dTransVal[xp][yp] == dTransVal[x1][y1]) && !((leaderf & UN_STICK) && !DIST(xp-x1,yp-y1,4))) { PlaceMonster(nummonsters, mtype, xp, yp); if(leaderf & UN_PACK) { monster[nummonsters]._mmaxhp *= 2; monster[nummonsters]._mhitpoints = monster[nummonsters]._mmaxhp; monster[nummonsters]._mint = monster[leader]._mint; if (leaderf & UN_STICK) { monster[nummonsters].leader = leader; monster[nummonsters].leaderflag = PACK_MEMBER; monster[nummonsters]._mAi = monster[leader]._mAi; } // quick fix for monsters that were gargoyles before they became unique if (monster[nummonsters]._mAi != AI_GARG) { monster[nummonsters]._mAnimData = monster[nummonsters].MType->Anims[MA_STAND].Cels[monster[nummonsters]._mdir]; monster[nummonsters]._mAnimFrame = random(88, monster[nummonsters]._mAnimLen - 1) + 1; monster[nummonsters]._mFlags &= ~MFLAG_STILL; monster[nummonsters]._mmode = MM_STAND; } } ++nummonsters; ++placed; ++j; } else ++try2; xp += offset_x[random(94, 8)]; yp += offset_x[random(94, 8)]; } } while((placed < num) && (++try1 < 10)); if(leaderf & UN_STICK) monster[leader].packsize = placed; } /*-----------------------------------------------------------------------** **-----------------------------------------------------------------------*/ #if !IS_VERSION(SHAREWARE) void LoadDiabMonsts() { BYTE *pSetPiece; int xx, yy; pSetPiece = LoadFileInMemSig("Levels\\L4Data\\diab1.DUN",NULL,'STPC'); xx = (diabquad1x << 1); yy = (diabquad1y << 1); SetMapMonsters(pSetPiece, xx, yy); DiabloFreePtr(pSetPiece); pSetPiece = LoadFileInMemSig("Levels\\L4Data\\diab2a.DUN",NULL,'STPC'); xx = (diabquad2x << 1); yy = (diabquad2y << 1); SetMapMonsters(pSetPiece, xx, yy); DiabloFreePtr(pSetPiece); pSetPiece = LoadFileInMemSig("Levels\\L4Data\\diab3a.DUN",NULL,'STPC'); xx = (diabquad3x << 1); yy = (diabquad3y << 1); SetMapMonsters(pSetPiece, xx, yy); DiabloFreePtr(pSetPiece); pSetPiece = LoadFileInMemSig("Levels\\L4Data\\diab4a.DUN",NULL,'STPC'); xx = (diabquad4x << 1); yy = (diabquad4y << 1); SetMapMonsters(pSetPiece, xx, yy); DiabloFreePtr(pSetPiece); } #endif /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void InitMonsters () { int i, mtype; int na, nt; int scattertypes[LASTMT]; int numscattypes = 0; long fv,j; int numplacemonsters; int s,t; void DaveCheck2(); if (gbMaxPlayers != 1) DaveCheck2(); // add 4 golem places first // WARNING!! IF YOU CHANGE THIS YOU MUST TEST POSION WATER QUEST!!! 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); } // We will place the monsters seperately for 16 #if !IS_VERSION(SHAREWARE) if (!setlevel && currlevel == 16) { LoadDiabMonsts(); } #endif // Monsters are not placed where bFlags[][] & BFLAG_MONSTACTIVE is set // This prevents: 1) Monsters attacking players as they enter dungeon // 2) Monsters setting off traps 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(trigs[i]._tx + s, trigs[i]._ty + t, INITMONSTRAD, FALSE, FALSE); } #if !IS_VERSION(SHAREWARE) PlaceQuestMonsters(); #endif if (!setlevel) { #if !IS_VERSION(SHAREWARE) PlaceUniques(); // Place uniques before any other monsters #endif // Calc a volume of monsters fv = 0; for (i = DIRTEDGED2; i < (DMAXY - (DIRTEDGED2)); ++i) { for (j = DIRTEDGED2; j < (DMAXX - (DIRTEDGED2)); ++j) { if (!SolidLoc(i,j)) ++fv; } } numplacemonsters = fv / MONSTDENSITY; #if 0 // Debugging = 1, normal = 0 numplacemonsters = 1; #else if (gbMaxPlayers != 1) numplacemonsters += (numplacemonsters >> 1); #endif if (numplacemonsters + nummonsters > MAXMONSTERS - 10) numplacemonsters = MAXMONSTERS - 10 - nummonsters; totalmonsters = nummonsters + numplacemonsters; #ifndef PACKS_ONLY // switch for debugging pack AI // Place scattered monsters app_assert((DWORD)nummtypes <= MAX_LVLMTYPES); for (i = 0; i < nummtypes; ++i) { if (Monsters[i].mPlaceFlags & MPFLAG_SCATTER) scattertypes[numscattypes++] = i; } while (nummonsters < totalmonsters) { mtype = scattertypes[random(95, numscattypes)]; if (currlevel != 1 // no groups on level 1 && random(95, 2)) { if (currlevel == 2 || // half-size groups on level 2 (currlevel >= CRYPTSTART && currlevel <= CRYPTEND)) na = random(95, 2) + 2; else na = random(95, 3) + 3; } else na = 1; PlaceGroup(mtype, na, FALSE, NULL); } #endif // PACKS_ONLY } for (i = 0; i < nt; ++i) { for (s = -2; s < 2; ++s) for (t = -2; t < 2; ++t) DoUnVision(trigs[i]._tx + s, trigs[i]._ty + t, INITMONSTRAD); } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void SetMapMonsters(BYTE *pMap, int startx, int starty) { int i,j; WORD rw,rh; WORD *lm; int mt,mx,my; int mtype; // add 4 golem places first // WARNING! IF YOU CHANGE THIS YOU MUST TEST THE POISON WATER QUEST!!!! AddMonsterType(MT_GOLEM, MPFLAG_DONT); 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[MU_LAZARUS].mtype, MPFLAG_UNIQ); AddMonsterType(UniqMonst[MU_REDVEX].mtype, MPFLAG_UNIQ); AddMonsterType(UniqMonst[MU_BLKJADE].mtype, MPFLAG_UNIQ); PlaceUniqueMonst(MU_LAZARUS, 0, 0); PlaceUniqueMonst(MU_REDVEX, 0, 0); PlaceUniqueMonst(MU_BLKJADE, 0, 0); } lm = (WORD *)pMap; rw = *(lm++); rh = *(lm++); // Skip map lm += rw * rh; // Convert to index mini tile level instead of mega rw = rw << 1; rh = rh << 1; // Skip treasure map lm += rw * rh; for (j = 0; j < rh; ++j) { for (i = 0; i < rw; ++i) { if (*lm != 0) { mt = *lm; mt = MonstConvTbl[mt-1]; mtype = AddMonsterType(mt, MPFLAG_DONT); mx = i + DIRTEDGED2 + startx; my = j + DIRTEDGED2 + starty; PlaceMonster(nummonsters++, mtype, mx, my); } ++lm; } } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void DeleteMonster(int i) { int temp; app_assert((DWORD)(nummonsters-1) < MAXMONSTERS); app_assert((DWORD)i < MAXMONSTERS); temp = monstactive[--nummonsters]; monstactive[nummonsters] = monstactive[i]; monstactive[i] = temp; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int AddMonster(int x, int y, int dir, int mtype, BOOL InMap) { int i; if (nummonsters < MAXMONSTERS) { i = monstactive[nummonsters++]; if (InMap) dMonster[x][y] = i + 1; InitMonster(i, dir, mtype, x, y); return i; } else return -1; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void CloneMonster(int m) { if (monster[m].MType == NULL) return; int oldx = monster[m]._mx; int oldy = monster[m]._my; int dir = monster[m]._mdir; // try to find a place to put a new monster, next to old monster int x, y, theta; for (theta = 0; theta < 8; ++theta) { x = oldx + offset_x[theta]; y = oldy + offset_y[theta]; if (SolidLoc(x, y) || dPlayer[x][y] || dMonster[x][y]) continue; if (!dObject[x][y]) break; int oi = (dObject[x][y] > 0) ? (dObject[x][y] - 1) : -(dObject[x][y] + 1); if (!object[oi]._oSolidFlag) break; } if (theta >= 8) // no space return; int mtype = monster[m].MType->mtype; for (int i=0; i < MAX_LVLMTYPES; ++i) { if (Monsters[i].mtype == mtype) break; } if (i < MAX_LVLMTYPES) AddMonster(x, y, dir, i, TRUE); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void NewMonsterAnim(int i, AnimStruct &anim, int md) { app_assert((DWORD)i < MAXMONSTERS); app_assert((DWORD)md < 8); MonsterStruct *Monst = &monster[i]; Monst->_mAnimData = anim.Cels[md]; Monst->_mAnimLen = anim.Frames; Monst->_mAnimFrame = 1; Monst->_mAnimCnt = 0; Monst->_mAnimDelay = anim.Rate; Monst->_mdir = md; Monst->_mFlags &= ~(MFLAG_BACKWARDS|MFLAG_STILL); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ BOOL M_Ranged (int i) { app_assert((DWORD)i < MAXMONSTERS); if (monster[i]._mAi == AI_SKELBOW || monster[i]._mAi == AI_GOATBOW || monster[i]._mAi == AI_SUCC || monster[i]._mAi == AI_LAZHELP) return TRUE; return FALSE; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ BOOL M_Talker (int i) { app_assert((DWORD)i < MAXMONSTERS); if (monster[i]._mAi == AI_LAZURUS || monster[i]._mAi == AI_WARLORD || monster[i]._mAi == AI_GARBUD || monster[i]._mAi == AI_ZHAR || monster[i]._mAi == AI_SNOTSPIL || monster[i]._mAi == AI_LACHDANAN || monster[i]._mAi == AI_LAZHELP) return TRUE; return FALSE; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void M_Enemy (int i) { int j; int pnum, closest; int bestdist; bool bestsameroom; app_assert((DWORD)i < MAXMONSTERS); MonsterStruct *Monst = &monster[i]; BYTE enemyx,enemyy; closest = -1; bestdist = -1; bestsameroom = false; if (Monst->_mFlags & MFLAG_BERSERK || !(Monst->_mFlags & MFLAG_MKILLER) ) { for(pnum = 0; pnum < MAX_PLRS; ++pnum) { if (!plr[pnum].plractive || currlevel != plr[pnum].plrlevel || plr[pnum]._pLvlChanging || ((plr[pnum]._pHitPoints >> HP_SHIFT) == 0) ) continue; bool const sameroom = (dTransVal[Monst->_mx][Monst->_my] == dTransVal[plr[pnum]._px][plr[pnum]._py]); int const dist = max(abs(Monst->_mx - plr[pnum]._px), abs(Monst->_my - plr[pnum]._py)); if ((sameroom && !bestsameroom) || ((sameroom || !bestsameroom) && dist < bestdist) || closest == -1) { Monst->_mFlags &= ~MFLAG_MID; // Monst->_mFlags &= ~MFLAG_MKILLER; closest = pnum; enemyx = plr[pnum]._pfutx; enemyy = plr[pnum]._pfuty; bestdist = dist; bestsameroom = sameroom; } } } app_assert((DWORD)nummonsters <= MAXMONSTERS); for (j = 0; j < nummonsters; ++j) { int const mi = monstactive[j]; if ((mi != i) && ((monster[mi]._mhitpoints >> HP_SHIFT) > 0) && ((monster[mi]._mx != 1) || (monster[mi]._my != 0)) && // special confus-o-matic code for inactive golum ((!M_Talker(mi)) || monster[mi].mtalkmsg == 0) && /*(DIST((monster[mi]._mx - Monst->_mx),(monster[mi]._my - Monst->_my),2)) &&*/ ((Monst->_mFlags & MFLAG_MKILLER) || (Monst->_mFlags & MFLAG_BERSERK) || ((DIST((monster[mi]._mx - Monst->_mx),(monster[mi]._my - Monst->_my),2)) || (M_Ranged(i)))) && (Monst->_mFlags & MFLAG_MKILLER || (Monst->_mFlags & MFLAG_BERSERK) || monster[mi]._mFlags & MFLAG_MKILLER)) { bool const sameroom = (dTransVal[Monst->_mx][Monst->_my] == dTransVal[monster[mi]._mx][monster[mi]._my]); int const dist = max(abs(Monst->_mx - monster[mi]._mx), abs(Monst->_my - monster[mi]._my)); if ((sameroom && !bestsameroom) || ((sameroom || !bestsameroom) && dist < bestdist) || closest == -1) { Monst->_mFlags |= MFLAG_MID; closest = mi; enemyx = monster[mi]._mfutx; enemyy = monster[mi]._mfuty; bestdist = dist; bestsameroom = sameroom; } } } // everyone dead? if (closest != -1) { Monst->_menemy = closest; Monst->_menemyx = enemyx; Monst->_menemyy = enemyy; Monst->_mFlags &= ~MFLAG_NOENEMY; } else Monst->_mFlags |= MFLAG_NOENEMY; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static int M_GetDir(int i) { app_assert((DWORD)i < MAXMONSTERS); return GetDirection(monster[i]._mx, monster[i]._my, monster[i]._menemyx, monster[i]._menemyy); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void M_CheckEFlag(int i) { int tx,ty,tv; int t; WORD *mt; app_assert((DWORD)i < MAXMONSTERS); tx = monster[i]._mx - 1; ty = monster[i]._my + 1; tv = 0; mt = &(dMT[tx][ty].mt[0]); for(t = 2; t < 10; ++t) tv |= mt[t]; tv |= dSpecial[tx][ty]; if (tv != 0) monster[i]._meflag = 1; else monster[i]._meflag = 0; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void M_StartStand(int i, int md) { app_assert((DWORD)i < MAXMONSTERS); app_assert(monster[i].MType != NULL); 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; // count how long he's been standing 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); // Pick nearest enemy M_Enemy(i); } /*-----------------------------------------------------------------------* * M_StartDelay * * Keeps monster in Stand animation for len frames * Monster must be in Stand mode before entering Delay mode **-----------------------------------------------------------------------*/ static void M_StartDelay(int i, int len) { if(len > 0) { if (monster[i]._mAi == AI_LAZURUS) return; app_assert((DWORD)i < MAXMONSTERS); monster[i]._mVar2 = len; monster[i]._mmode = MM_DELAY; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void M_StartSpStand(int i, int md) { app_assert((DWORD)i < MAXMONSTERS); app_assert(monster[i].MType != NULL); 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); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void M_StartWalk(int i, int xvel, int yvel, int xadd, int yadd, int EndDir) { long fx,fy; int pn; app_assert((DWORD)i < MAXMONSTERS); app_assert(monster[i].MType != NULL); fx = monster[i]._mx + xadd; fy = monster[i]._my + yadd; app_assert((DWORD)fx < MAXDUNX); app_assert((DWORD)fy < MAXDUNY); pn = dPiece[fx][fy] - 1; dMonster[fx][fy] = -1 - i; 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); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void M_StartWalk2(int i, int xvel, int yvel, int xoff, int yoff, int xadd, int yadd, int EndDir) { long fx,fy; int pn; app_assert((DWORD)i < MAXMONSTERS); app_assert(monster[i].MType != NULL); fx = monster[i]._mx + xadd; fy = monster[i]._my + yadd; app_assert((DWORD)fx < MAXDUNX); app_assert((DWORD)fy < MAXDUNY); pn = dPiece[fx][fy] - 1; app_assert((DWORD)monster[i]._mx < MAXDUNX); app_assert((DWORD)monster[i]._my < MAXDUNY); dMonster[monster[i]._mx][monster[i]._my] = -1 - i; 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]._mFlags & MFLAG_INVISIBLE) && monster[i].mlid != 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 = xoff << 4; monster[i]._mVar7 = yoff << 4; monster[i]._mVar8 = 0; M_CheckEFlag(i); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void M_StartWalk3(int i, int xvel, int yvel, int xoff, int yoff, int xadd, int yadd, int txa, int tya, int EndDir) { long fx,fy; long tx,ty; int pn, pn2; app_assert((DWORD)i < MAXMONSTERS); app_assert(monster[i].MType != NULL); fx = monster[i]._mx + xadd; fy = monster[i]._my + yadd; tx = monster[i]._mx + txa; // Temp location for drawing ty = monster[i]._my + tya; if( !(monster[i]._mFlags & MFLAG_INVISIBLE) && monster[i].mlid != 0) { ChangeLightXY(monster[i].mlid, tx, ty); } app_assert((DWORD)fx < MAXDUNX); app_assert((DWORD)fy < MAXDUNY); pn = dPiece[fx][fy] - 1; pn2 = dPiece[tx][ty] - 1; app_assert((DWORD)monster[i]._mx < MAXDUNX); app_assert((DWORD)monster[i]._my < MAXDUNY); dMonster[monster[i]._mx][monster[i]._my] = -1 - i; dMonster[fx][fy] = -1 - i; monster[i]._mVar4 = tx; monster[i]._mVar5 = ty; dFlags[tx][ty] |= 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 = xoff << 4; monster[i]._mVar7 = yoff << 4; monster[i]._mVar8 = 0; M_CheckEFlag(i); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void M_StartAttack(int i) { int md; app_assert((DWORD)i < MAXMONSTERS); app_assert(monster[i].MType != NULL); 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); } /*-----------------------------------------------------------------------* ** Ranged weapons **-----------------------------------------------------------------------*/ static void M_StartRAttack(int i, int missile_type, int dam) { int md; app_assert((DWORD)i < MAXMONSTERS); app_assert(monster[i].MType != NULL); 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); } /*-----------------------------------------------------------------------* ** Ranged Special weapons **-----------------------------------------------------------------------*/ static void M_StartRSpAttack(int i, int missile_type, int dam) { int md; app_assert((DWORD)i < MAXMONSTERS); app_assert(monster[i].MType != NULL); md = M_GetDir(i); NewMonsterAnim(i, monster[i].MType->Anims[MA_SPECIAL], md); monster[i]._mmode = MM_RSATTACK; 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); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void M_StartSpAttack(int i) { int md; app_assert((DWORD)i < MAXMONSTERS); app_assert(monster[i].MType != NULL); 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); } /*-----------------------------------------------------------------------* * M_StartEat is the same as M_StartSpAttack except mdir isn't affected **-----------------------------------------------------------------------*/ static void M_StartEat(int i) { app_assert((DWORD)i < MAXMONSTERS); app_assert(monster[i].MType != NULL); 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) { app_assert((DWORD)i < MAXMONSTERS); int const mx = monster[i]._moldx; int const my = monster[i]._moldy; int const mt = -1 - i; int const mt2 = i + 1; app_assert((DWORD)(mx+1) < MAXDUNX); app_assert((DWORD)(my+1) < MAXDUNY); for (int y = my-1; y <= my+1; ++y) { for (int x = mx-1; x <= mx+1; ++x) { if (dMonster[x][y] == mt || dMonster[x][y] == mt2) { dMonster[x][y] = 0; } } } dFlags[mx+1][my+0] &= BFMASK_MONSTLR; dFlags[mx+0][my+1] &= BFMASK_MONSTLR; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void M_GetKnockback(int i) { app_assert((DWORD)i < MAXMONSTERS); app_assert(monster[i].MType != NULL); // knock back enemy if you can int const d = (monster[i]._mdir + 4) & 0x7; if (DirOK(i, d)) { M_ClearSquares(i); monster[i]._moldx += offset_x[d]; monster[i]._moldy += offset_y[d]; NewMonsterAnim(i, monster[i].MType->Anims[MA_GOTHIT], monster[i]._mdir); monster[i]._mmode = MM_GOTHIT; monster[i]._mxoff = 0; monster[i]._myoff = 0; monster[i]._mx = monster[i]._moldx; monster[i]._my = monster[i]._moldy; monster[i]._mfutx = monster[i]._mx; monster[i]._mfuty = monster[i]._my; monster[i]._moldx = monster[i]._mx; monster[i]._moldy = monster[i]._my; M_CheckEFlag(i); M_ClearSquares(i); app_assert((DWORD)monster[i]._mx < MAXDUNX); app_assert((DWORD)monster[i]._my < MAXDUNY); dMonster[monster[i]._mx][monster[i]._my] = i + 1; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void M_StartHit(int i, int pnum, int dam) { app_assert((DWORD)i < MAXMONSTERS); app_assert(monster[i].MType != NULL); if (pnum >= 0) monster[i].mWhoHit |= (1 << pnum); // Who damaged me if (pnum == myplr) { extern void delta_monster_hp(int mi,long hp,BYTE bLevel); delta_monster_hp(i,monster[i]._mhitpoints,currlevel); NetSendCmdMonstDamage(FALSE,i,dam); } PlayEffect(i, MS_GOTHIT); // Sneaky demons always get hit if (!EquivMonst(monster[i].MType->mtype, MT_SNEAK)) // Other monsters only get hit if damage is severe //if (((dam >> HP_SHIFT) < (monster[i].mLevel + 3)) && (!(plr[pnum]._pIFlags & IAF_KNOCKBACK))) if ((dam >> HP_SHIFT) < (monster[i].mLevel + 3)) return; // Set monster's enemy to be this player if (pnum >= 0) { monster[i]._menemy = pnum; monster[i]._menemyx = plr[pnum]._pfutx; monster[i]._menemyy = plr[pnum]._pfuty; monster[i]._mFlags &= ~MFLAG_MID; monster[i]._mdir = M_GetDir(i); } // Check for special hits/teleports if(monster[i].MType->mtype == MT_BLINK) { M_Teleport(i); } else { if (EquivMonst(monster[i].MType->mtype, MT_NSCAV) || monster[i].MType->mtype == MT_GRAVDG) { // allow it to seek food again. monster[i]._mgoal = MG_ATTACK; monster[i]._mgoalvar1 = 0; monster[i]._mgoalvar2 = 0; } } if (monster[i]._mmode != MM_STONE) { NewMonsterAnim(i, monster[i].MType->Anims[MA_GOTHIT], monster[i]._mdir); monster[i]._mmode = MM_GOTHIT; monster[i]._mxoff = 0; monster[i]._myoff = 0; monster[i]._mx = monster[i]._moldx; monster[i]._my = monster[i]._moldy; monster[i]._mfutx = monster[i]._mx; monster[i]._mfuty = monster[i]._my; 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; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void M_DiabloDeath(int i, BOOL sendmsg) { MonsterStruct *Monst = &monster[i]; // disable any other sounds from playing, for dramatic effect #if !IS_VERSION(SHAREWARE) PlaySFX(USFX_DIABLOD); #endif quests[Q_DIABLO]._qactive = QUEST_DONE; if (sendmsg) NetSendCmdQuest(TRUE, Q_DIABLO); sgbSaveSoundOn = gbSoundOn; gbSoundOn = FALSE; // disable player processing, so we can take over screen scrolling gbProcessPlayers = FALSE; // kill all other monsters on level app_assert((DWORD)nummonsters <= MAXMONSTERS); for (int j = 0; j < nummonsters; ++j) { int const k = monstactive[j]; if (k != i && monster[i]._msquelch) { NewMonsterAnim(k, monster[k].MType->Anims[MA_DEATH], monster[k]._mdir); monster[k]._mmode = MM_DEATH; monster[k]._mxoff = 0; monster[k]._myoff = 0; monster[k]._mVar1 = 0; monster[k]._mx = monster[k]._moldx; monster[k]._my = monster[k]._moldy; monster[k]._mfutx = monster[k]._mx; monster[k]._mfuty = monster[k]._my; monster[k]._moldx = monster[k]._mx; monster[k]._moldy = monster[k]._my; M_CheckEFlag(k); M_ClearSquares(k); dMonster[monster[k]._mx][monster[k]._my] = k + 1; } } AddLight(Monst->_mx, Monst->_my, 8); DoVision(Monst->_mx, Monst->_my, 8, FALSE, TRUE); // set up scrolling vars int steps = max(abs(ViewX-Monst->_mx),abs(ViewY-Monst->_my)); steps = min(20, steps); Monst->_mVar3 = ViewX << 16; // convert to fixed-point Monst->_mVar4 = ViewY << 16; // calculate scroll offsets Monst->_mVar5 = (int)((double)(Monst->_mVar3 - (Monst->_mx << 16))/(double)steps); Monst->_mVar6 = (int)((double)(Monst->_mVar4 - (Monst->_my << 16))/(double)steps); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void M2MStartHit(int mid, int i, int dam) { if ((DWORD)mid >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("Invalid monster %d getting hit by monster",mid); #else return; #endif } if (monster[mid].MType == NULL) { #if defined(_DEBUG) app_fatal("Monster %d \"%s\" getting hit by monster: MType NULL",mid,monster[mid].mName); #else return; #endif } if (i >= 0) monster[i].mWhoHit |= (1 << i); // Who damaged me extern void delta_monster_hp(int mi,long hp, BYTE bLevel); delta_monster_hp(mid, monster[mid]._mhitpoints, currlevel); NetSendCmdMonstDamage(FALSE, mid, dam); PlayEffect(mid, MS_GOTHIT); // Sneaky demons always get hit if (!EquivMonst(monster[mid].MType->mtype, MT_SNEAK)) // Other monsters only get hit if damage is severe if ((dam >> HP_SHIFT) < (monster[mid].mLevel + 3)) return; // face plr who hit me if (i >= 0) monster[mid]._mdir = (monster[i]._mdir + 4) &0x07; // Check for special hits/teleports if(monster[mid].MType->mtype == MT_BLINK) { M_Teleport(mid); } else { if (EquivMonst(monster[mid].MType->mtype, MT_NSCAV) || monster[mid].MType->mtype == MT_GRAVDG) { // allow it to seek food again. monster[mid]._mgoal = MG_ATTACK; monster[mid]._mgoalvar1 = 0; monster[mid]._mgoalvar2 = 0; } } if (monster[mid]._mmode != MM_STONE) { if (monster[mid].MType->mtype != MT_GOLEM) { NewMonsterAnim(mid, monster[mid].MType->Anims[MA_GOTHIT], monster[mid]._mdir); monster[mid]._mmode = MM_GOTHIT; } monster[mid]._mxoff = 0; monster[mid]._myoff = 0; monster[mid]._mx = monster[mid]._moldx; monster[mid]._my = monster[mid]._moldy; monster[mid]._mfutx = monster[mid]._mx; monster[mid]._mfuty = monster[mid]._my; monster[mid]._moldx = monster[mid]._mx; monster[mid]._moldy = monster[mid]._my; M_CheckEFlag(mid); M_ClearSquares(mid); dMonster[monster[mid]._mx][monster[mid]._my] = mid + 1; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void MonstKillEffect(int i, BOOL sendmsg) { MonsterStruct *Monst = &monster[i]; if (QuestStatus(Q_GARBUD) && (Monst->mName == UniqMonst[MU_GARBUD].mName)) CreateTypeItem(Monst->_mx+1, Monst->_my+1, TRUE, IT_MACE, IMID_NONE, TRUE, FALSE); else if (Monst->mName == UniqMonst[MU_DEFILER].mName) { if (effect_is_playing(HSFX_DEFILER5)) stream_stop(); quests[Q_DEFILER]._qlog = FALSE; SpawnMap(Monst->_mx, Monst->_my); } else if (Monst->mName == UniqMonst[MU_HORKDEMON].mName) { if (gbTheo) SpawnBear(Monst->_mx, Monst->_my); else CreateAmulet(Monst->_mx, Monst->_my, 13, FALSE, TRUE); } else if (Monst->MType->mtype == MT_SPAWN) { // do nothing } else if (Monst->MType->mtype == MT_NKR) { int whichEffect = (Na_Krul.Books)?HSFX_NA_KRUL4:HSFX_NA_KRUL5; if (gbCowsuit) // override -- funny whichEffect = HSFX_NA_KRUL6; if (effect_is_playing(whichEffect)) stream_stop(); quests[Q_NA_KRUL]._qlog = FALSE; Na_Krul.MIndex = -2; CreateMagicWeapon(Monst->_mx, Monst->_my, IT_SWORD, ITEM_GRTSWORD, FALSE, TRUE); CreateMagicWeapon(Monst->_mx, Monst->_my, IT_STAFF, ITEM_STLSTAFF, FALSE, TRUE); CreateMagicWeapon(Monst->_mx, Monst->_my, IT_BOW, ITEM_STLLONGBOW, FALSE, TRUE); CreateSpellBook (Monst->_mx, Monst->_my, SPL_APOCA, FALSE, TRUE); } else if (i > 3) { SpawnItem(i, Monst->_mx, Monst->_my, sendmsg); } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void MonstStartKill(int i, int pnum, BOOL sendmsg) { if ((DWORD)i >= MAXMONSTERS) { #if defined (_DEBUG) app_fatal("MonstStartKill: Invalid monster %d",i); #else return; #endif } if (monster[i].MType == NULL) { //app_fatal("MonstStartKill: Monster %d \"%s\" MType NULL",i,monster[i].mName); return; } int md; MonsterStruct *Monst = &monster[i]; // killer gets credit too, and divvy up exper if (pnum >= 0) Monst->mWhoHit |= (1 << pnum); // Who killed me // rjs.patch1.start.1/23/97 - fixes golem giving exp when killed //old. if (pnum < MAX_PLRS) AddPlrMonstExper(Monst->mLevel, Monst->mExp, Monst->mWhoHit); if ((pnum < MAX_PLRS) && (i > MAX_PLRS)) AddPlrMonstExper(Monst->mLevel, Monst->mExp, Monst->mWhoHit); // rjs.patch1.end.1/23/97 // Kill the monster (for myplr or other plr) ++monstkills[Monst->MType->mtype]; Monst->_mhitpoints = 0; SetRndSeed(Monst->_mRndSeed); MonstKillEffect(i, sendmsg); #if 0 if (QuestStatus(Q_GARBUD) && (Monst->mName == UniqMonst[MU_GARBUD].mName)) CreateTypeItem(Monst->_mx+1, Monst->_my+1, TRUE, IT_MACE, IMID_NONE, TRUE, FALSE); else if (Monst->mName == UniqMonst[MU_DEFILER].mName) { quests[Q_DEFILER]._qlog = FALSE; SpawnMap(Monst->_mx, Monst->_my); } else if (Monst->mName == UniqMonst[MU_HORKDEMON].mName) if (gbTheo) SpawnBear(Monst->_mx, Monst->_my); else if (Monst->MType->mtype == MT_SPAWN) { // do nothing } else if (Monst->MType->mtype == MT_NKR) { quests[Q_NA_KRUL]._qlog = FALSE; } else if (i > 3) SpawnItem(i, Monst->_mx, Monst->_my, sendmsg); #endif if (Monst->MType->mtype == MT_DIABLO) { M_DiabloDeath(i, TRUE); } else PlayEffect(i, MS_DEATH); if (pnum >= 0) md = M_GetDir(i); else md = Monst->_mdir; Monst->_mdir = md; NewMonsterAnim(i, Monst->MType->Anims[MA_DEATH], md); Monst->_mmode = MM_DEATH; Monst->_mgoal = 0; Monst->_mxoff = 0; Monst->_myoff = 0; Monst->_mVar1 = 0; Monst->_mx = Monst->_moldx; Monst->_my = Monst->_moldy; Monst->_mfutx = Monst->_mx; Monst->_mfuty = Monst->_my; Monst->_moldx = Monst->_mx; Monst->_moldy = Monst->_my; M_CheckEFlag(i); M_ClearSquares(i); dMonster[Monst->_mx][Monst->_my] = i + 1; CheckQuestKill(i, sendmsg); // Send Fallen Ones running in fear M_FallenFear(Monst->_mx, Monst->_my); // Acid demon emits acid pool if ((EquivMonst(Monst->MType->mtype, MT_NACID)) || (Monst->MType->mtype == MT_SPIDER2)) AddMissile(Monst->_mx, Monst->_my, 0, 0, 0, MIT_ACIDPUD, MI_ENEMYPLR, i, Monst->_mint + 1, 0); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void M2MStartKill(int i, int mid) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M2MStartKill: Invalid monster (attacker) %d",i); #else return; #endif } if ((DWORD)mid >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M2MStartKill: Invalid monster (killed) %d",mid); #else return; #endif } if (monster[i].MType == NULL) { //app_fatal("M2MStartKill: Monster %d \"%s\" MType NULL",mid,monster[mid].mName); return; } int md; void delta_kill_monster(int mi, BYTE x, BYTE y, BYTE bLevel); 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); // Who killed me 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); MonstKillEffect(mid, TRUE); #if 0 // @@@ needs to be fixed for multiplayer (death not synced, etc) if (mid >= MAX_PLRS) SpawnItem(mid, monster[mid]._mx, monster[mid]._my, TRUE); #endif if (monster[mid].MType->mtype == MT_DIABLO) { M_DiabloDeath(mid, TRUE); } else PlayEffect(mid, MS_DEATH); //PlayEffect(mid, MS_DEATH); md = (monster[i]._mdir + 4) & 0x07; 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]._mx; monster[mid]._mfuty = monster[mid]._my; monster[mid]._moldx = monster[mid]._mx; monster[mid]._moldy = monster[mid]._my; M_CheckEFlag(mid); M_ClearSquares(mid); dMonster[monster[mid]._mx][monster[mid]._my] = mid + 1; CheckQuestKill(mid, TRUE); // Send Fallen Ones running in fear M_FallenFear(monster[mid]._mx, monster[mid]._my); // Acid demon emits acid pool if(EquivMonst(monster[mid].MType->mtype, MT_NACID)) AddMissile(monster[mid]._mx, monster[mid]._my, 0, 0, 0, MIT_ACIDPUD, MI_ENEMYPLR, mid, monster[mid]._mint + 1, 0); M_StartStand(i, monster[i]._mdir); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void M_StartKill(int i, int pnum) { if ((DWORD)i >= MAXMONSTERS) { #if defined (_DEBUG) app_fatal("M_StartKill: Invalid monster %d",i); #else return; #endif } // Send a message to everyone saying I killed the monster if (myplr == pnum) { void delta_kill_monster(int mi, BYTE x, BYTE y, BYTE bLevel); 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) { #if defined(_DEBUG) app_fatal("M_SyncStartKill: Invalid monster %d",i); #else return; #endif } // Already dead? if (monster[i]._mhitpoints == 0) return; if (monster[i]._mmode == MM_DEATH) return; app_assert(pnum != myplr); if (!dMonster[x][y]) { M_ClearSquares(i); monster[i]._mx = x; monster[i]._my = y; monster[i]._moldx = x; monster[i]._moldy = y; } MonstStartKill(i, pnum, FALSE); // app_assert(dMonster[monster[i]._mx][monster[i]._my] == i+1 // || dMonster[monster[i]._mx][monster[i]._my] == -(i+1)); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void M_StartFadein(int i, int md, BOOL backwards) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_StartFadein: Invalid monster %d",i); #else return; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_StartFadein: Monster %d \"%s\" MType NULL",i,monster[i].mName); #else return; #endif } 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_INVISIBLE; if (backwards) { monster[i]._mFlags |= MFLAG_BACKWARDS; monster[i]._mAnimFrame = monster[i]._mAnimLen; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void M_StartFadeout(int i, int md, BOOL backwards) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_StartFadeout: Invalid monster %d",i); #else return; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_StartFadeout: Monster %d \"%s\" MType NULL",i,monster[i].mName); #else return; #endif } 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_BACKWARDS; monster[i]._mAnimFrame = monster[i]._mAnimLen; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void M_StartHeal(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_StartHeal: Invalid monster %d",i); #else return; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_StartHeal: Monster %d \"%s\" MType NULL",i,monster[i].mName); #else return; #endif } MonsterStruct *Monst = &monster[i]; Monst->_mAnimData = Monst->MType->Anims[MA_SPECIAL].Cels[Monst->_mdir]; Monst->_mAnimFrame = Monst->MType->Anims[MA_SPECIAL].Frames; Monst->_mFlags |= MFLAG_BACKWARDS; Monst->_mmode = MM_HEAL; Monst->_mVar1 = Monst->_mmaxhp / (16*(random(97, 5)+4)); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void M_ChangeLightOffset(int monst) { if ((DWORD)monst >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_ChangeLightOffset: Invalid monster %d",monst); #else return; #endif } int lx,ly; int sign; lx = (monster[monst]._mxoff + (monster[monst]._myoff << 1)); ly = ((monster[monst]._myoff << 1) - monster[monst]._mxoff); // Divide these values by 8, because lighting offsets have // 8 subdivisions per tile. if (lx < 0) { sign = -1; lx = -lx; } else sign = 1; lx = lx >> 3; lx *= sign; if (ly < 0) { sign = -1; ly = -ly; } else sign = 1; ly = ly >> 3; ly *= sign; if (monster[monst].mlid != 0) { ChangeLightOff(monster[monst].mlid, lx, ly); } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int M_DoStand(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_DoStand: Invalid monster %d",i); #else return RUN_DONE; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_DoStand: Monster %d \"%s\" MType NULL",i,monster[i].mName); #else return RUN_DONE; #endif } MonsterStruct *Monst = &monster[i]; if (Monst->MType->mtype == MT_GOLEM) Monst->_mAnimData = Monst->MType->Anims[MA_WALK].Cels[Monst->_mdir]; else Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[Monst->_mdir]; if (Monst->_mAnimFrame == Monst->_mAnimLen) { M_Enemy(i); } ++Monst->_mVar2; return RUN_DONE; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int M_DoWalk(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_DoWalk: Invalid monster %d",i); #else return RUN_DONE; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_DoWalk: Monster %d \"%s\" MType NULL",i,monster[i].mName); #else return RUN_DONE; #endif } int rv; 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]._mFlags & MFLAG_INVISIBLE) && monster[i].mlid != 0) { ChangeLightXY(monster[i].mlid, monster[i]._mx, monster[i]._my); } M_StartStand(i, monster[i]._mdir); rv = RUN_AGAIN; // DaveMonstMap(FALSE, 0); } else { if (monster[i]._mAnimCnt == 0) { if (monster[i]._mVar8 == 0 && monster[i].MType->mtype == MT_FLESH) { PlayEffect(i, MS_SATTACK); } ++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 = RUN_DONE; } if( !(monster[i]._mFlags & MFLAG_INVISIBLE) && monster[i].mlid != 0) M_ChangeLightOffset(i); return (rv); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int M_DoWalk2(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_DoWalk2: Invalid monster %d",i); #else return RUN_DONE; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_DoWalk2: Monster %d \"%s\" MType NULL",i,monster[i].mName); #else return RUN_DONE; #endif } int rv; if (monster[i]._mVar8 == monster[i].MType->Anims[MA_WALK].Frames) { dMonster[monster[i]._mVar1][monster[i]._mVar2] = 0; if( !(monster[i]._mFlags & MFLAG_INVISIBLE) && monster[i].mlid != 0) { ChangeLightXY(monster[i].mlid, monster[i]._mx, monster[i]._my); } M_StartStand(i, monster[i]._mdir); rv = RUN_AGAIN; // DaveMonstMap(FALSE, 0); } else { if (monster[i]._mAnimCnt == 0) { if (monster[i]._mVar8 == 0 && monster[i].MType->mtype == MT_FLESH) { PlayEffect(i, MS_SATTACK); } ++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 = RUN_DONE; } if( !(monster[i]._mFlags & MFLAG_INVISIBLE) && monster[i].mlid != 0) M_ChangeLightOffset(i); return(rv); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int M_DoWalk3(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_DoWalk3: Invalid monster %d",i); #else return RUN_DONE; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_DoWalk3: Monster %d \"%s\" MType NULL",i,monster[i].mName); #else return RUN_DONE; #endif } int rv; 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] &= BFMASK_MONSTLR; dMonster[monster[i]._mx][monster[i]._my] = i + 1; if( !(monster[i]._mFlags & MFLAG_INVISIBLE) && monster[i].mlid != 0) { ChangeLightXY(monster[i].mlid, monster[i]._mx, monster[i]._my); } M_StartStand(i, monster[i]._mdir); rv = RUN_AGAIN; // DaveMonstMap(FALSE, 0); } else { if (monster[i]._mAnimCnt == 0) { if (monster[i]._mVar8 == 0 && monster[i].MType->mtype == MT_FLESH) { PlayEffect(i, MS_SATTACK); } ++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 = RUN_DONE; } if(monster[i]._uniqtype && !(monster[i]._mFlags & MFLAG_INVISIBLE)) M_ChangeLightOffset(i); return(rv); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void M_TryM2MHit(int i, int mid, int hper, int mind, int maxd) { int hit, dam; BOOL ret; if ((DWORD)mid >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_TryM2MHit: Invalid monster %d",mid); #else return; #endif } if (monster[mid].MType == NULL) { #if defined(_DEBUG) app_fatal("M_TryM2MHit: Monster %d \"%s\" MType NULL",mid,monster[mid].mName); #else return; #endif } if ((monster[mid]._mhitpoints >> HP_SHIFT) <= 0) return; if (monster[mid].MType->mtype == MT_ILLWEAV && monster[mid]._mgoal == MG_RUN_AWAY) return; hit = random(4, 100); if (monster[mid]._mmode == MM_STONE) hit = 0; if (CheckMonsterHit(mid, ret)) { return; } else { if (hit < hper) { dam = random(5, maxd - mind + 1) + mind; dam = dam << HP_SHIFT; monster[mid]._mhitpoints -= dam; //rjs - x2 stone dam fix - if (monster[mid]._mmode == MM_STONE) monster[mid]._mhitpoints -= dam; if ((monster[mid]._mhitpoints >> HP_SHIFT) <= 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); } } } return; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static void M_TryH2HHit(int i, int pnum, int Hit, int MinDam, int MaxDam) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_TryH2HHit: Invalid monster %d",i); #else return; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_TryH2HHit: Monster %d \"%s\" MType NULL",i,monster[i].mName); #else return; #endif } int hit, hper, tac; long dam; int dx, dy; int blk, blkper, blkdir; int mdam; if ((monster[i]._mFlags & MFLAG_MID) != 0) { M_TryM2MHit(i, pnum, Hit, MinDam, MaxDam); return; } app_assert((DWORD)pnum < MAX_PLRS); if ((plr[pnum]._pHitPoints >> HP_SHIFT) <= 0) return; if (plr[pnum]._pInvincible) return; if ((plr[pnum]._pSpellFlags & SF_ETHER) != 0) return; dx = abs(monster[i]._mx - plr[pnum]._px); dy = abs(monster[i]._my - plr[pnum]._py); if ((dx < 2) && (dy < 2)) { // Did I hit? hit = random(98, 100); #if CHEATS if (simplecheat || cheatflag) hit = 1000; // TEMP! #endif //rjs tac = (byte)plr[pnum]._pArmorClass + plr[pnum]._pIAC + plr[pnum]._pIBonusAC; tac = plr[pnum]._pIAC + plr[pnum]._pIBonusAC; if ((plr[pnum]._pIFlags2 & IAF2_DEMONAC) && (monster[i].MData->mMonstClass == MC_DEMON)) tac += 40; if ((plr[pnum]._pIFlags2 & IAF2_UNDEADAC) && (monster[i].MData->mMonstClass == MC_UNDEAD)) tac += 20; tac += (plr[pnum]._pDexterity / 5); hper = 30 + Hit - tac + ((monster[i].mLevel - plr[pnum]._pLevel) << 1); if (hper < 15) hper = 15; if ((currlevel == 14) && (hper < 20)) hper = 20; if ((currlevel == 15) && (hper < 25)) hper = 25; if ((currlevel == 16) && (hper < 30)) hper = 30; if (((plr[pnum]._pmode == PM_STAND) || (plr[pnum]._pmode == PM_ATTACK)) && (plr[pnum]._pBlockFlag)) blk = random(98, 100); else blk = 100; blkper = plr[pnum]._pBaseToBlk + plr[pnum]._pDexterity - ((monster[i].mLevel - plr[pnum]._pLevel) << 1); if (blkper < 0) blkper = 0; if (blkper > 100) blkper = 100; if (hit < hper) { if (blk < blkper) { blkdir = GetDirection(plr[pnum]._px, plr[pnum]._py, monster[i]._mx, monster[i]._my); StartPlrBlock(pnum, blkdir); if (pnum == myplr && plr[pnum]._pReflectCount > 0) { --plr[pnum]._pReflectCount; // Reflect back with 20->30% of the damage. dam = (MaxDam - MinDam + 1) << HP_SHIFT; dam = random(99, dam) + (MinDam << HP_SHIFT); dam += (plr[pnum]._pIGetHit << HP_SHIFT); if (dam < (1 << HP_SHIFT)) dam = (1 << HP_SHIFT); mdam = static_cast(dam * (0.01 * (random(100, 10) + 20))); monster[i]._mhitpoints -= mdam; dam -= mdam; if (dam < 0) { dam = 0; } if (monster[i]._mhitpoints >> HP_SHIFT <= 0) { M_StartKill (i, pnum); } else { M_StartHit(i, pnum, mdam); } } } else { if (monster[i].MType->mtype == MT_YZOMBIE && pnum == myplr) { // find manashield int k; int mi; int msi = -1; for (k = 0; k < nummissiles; ++k) { mi = missileactive[k]; if (missile[mi]._mitype == MIT_MANASHIELD && missile[mi]._misource == pnum) { msi = mi; } } if(plr[pnum]._pMaxHP > 1 << HP_SHIFT ) { plr[pnum]._pMaxHP -= 1 << HP_SHIFT; if(plr[pnum]._pHitPoints > plr[pnum]._pMaxHP) { plr[pnum]._pHitPoints = plr[pnum]._pMaxHP; if( msi >= 0 ) missile[msi]._miVar1 = plr[pnum]._pHitPoints; } plr[pnum]._pMaxHPBase -= 1 << HP_SHIFT; if (plr[pnum]._pHPBase > plr[pnum]._pMaxHPBase) { plr[pnum]._pHPBase = plr[pnum]._pMaxHPBase; if( msi >= 0 ) missile[msi]._miVar2 = plr[pnum]._pHPBase; } } } dam = (MaxDam - MinDam + 1) << HP_SHIFT; dam = random(99, dam) + (MinDam << HP_SHIFT); dam += (plr[pnum]._pIGetHit << HP_SHIFT); if (dam < (1 << HP_SHIFT)) dam = (1 << HP_SHIFT); if (pnum == myplr) { if (plr[pnum]._pReflectCount > 0) { --plr[pnum]._pReflectCount; // Reflect back with 20->30% of the damage. mdam = static_cast(dam * (0.01 * (random(100, 10) + 20))); monster[i]._mhitpoints -= mdam; dam -= mdam; if (dam < 0) { dam = 0; } if (monster[i]._mhitpoints >> HP_SHIFT <= 0) { M_StartKill (i, pnum); } else { M_StartHit(i, pnum, mdam); } } plr[pnum]._pHitPoints -= dam; plr[pnum]._pHPBase -= dam; } if (plr[pnum]._pIFlags & IAF_THORN) { mdam = ((random (99, 3) + 1) << HP_SHIFT); monster[i]._mhitpoints -= mdam; if (monster[i]._mhitpoints >> HP_SHIFT <= 0) { M_StartKill (i, pnum); //(old) AddPlrExperience(pnum, monster[i].mLevel, monster[i].mExp); //(new, but moved) AddPlrMonstExper(monster[i].mLevel, monster[i].mExp, monster[i].mWhoHit); } else { M_StartHit(i, pnum, mdam); } } if (!(monster[i]._mFlags & IAF_MNOHEAL)) { if (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 >> HP_SHIFT) <= 0) { // rjs - manashld fix? - plr[pnum]._pHitPoints = 0; StartPlrKill(pnum, FALSE); M_StartStand(i, monster[i]._mdir); } else { StartPlrHit(pnum, dam, FALSE); if (monster[i]._mFlags & MFLAG_KNOCKBACK) { // make sure player is doing a hit animation if (plr[pnum]._pmode != PM_GOTHIT) StartPlrHit(pnum, 0, TRUE); // knock back enemy one square int newx,newy,oldx,oldy; oldx = plr[pnum]._px; oldy = plr[pnum]._py; newx = oldx + offset_x[monster[i]._mdir]; newy = oldy + 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); } } } } } } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int M_DoAttack(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_DoAttack: Invalid monster %d",i); #else return RUN_DONE; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_DoAttack: Monster %d \"%s\" MType NULL",i,monster[i].mName); #else return RUN_DONE; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_DoAttack: Monster %d \"%s\" MData NULL",i,monster[i].mName); #else return RUN_DONE; #endif } MonsterStruct *Monst = &monster[i]; if (Monst->_mAnimFrame == Monst->MData->mAFNum) { M_TryH2HHit(i, Monst->_menemy, Monst->mHit, Monst->mMinDamage, Monst->mMaxDamage); if (Monst->_mAi != AI_SNAKE) PlayEffect(i, MS_ATTACK); } // Special code for Magma -- second punch if (EquivMonst(Monst->MType->mtype, MT_NMAGMA) && Monst->_mAnimFrame == 9) { M_TryH2HHit(i, Monst->_menemy, Monst->mHit+10, Monst->mMinDamage-2, Monst->mMaxDamage-2); PlayEffect(i, MS_ATTACK); } // Special code for Storm -- second punch if (EquivMonst(Monst->MType->mtype, MT_STORM) && Monst->_mAnimFrame == 13) { M_TryH2HHit(i, Monst->_menemy, Monst->mHit-20, Monst->mMinDamage+4, Monst->mMaxDamage+4); PlayEffect(i, MS_ATTACK); } // Special code for Snake -- play sound before attack frame if (Monst->_mAi == AI_SNAKE && Monst->_mAnimFrame == 1) PlayEffect(i, MS_ATTACK); if (Monst->_mAnimFrame == Monst->_mAnimLen) { M_StartStand(i, Monst->_mdir); return(RUN_AGAIN); } else return (RUN_DONE); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int M_DoRAttack(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_DoRAttack: Invalid monster %d",i); #else return RUN_DONE; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_DoRAttack: Monster %d \"%s\" MType NULL",i,monster[i].mName); #else return RUN_DONE; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_DoRAttack: Monster %d \"%s\" MData NULL",i,monster[i].mName); #else return RUN_DONE; #endif } int multimissiles; int mi; if (monster[i]._mAnimFrame == monster[i].MData->mAFNum) { if (monster[i]._mVar1 != -1) { if (monster[i]._mVar1 == MIT_CBOLT) multimissiles = 3; else multimissiles = 1; for (mi = 0; mi < multimissiles; ++mi) // special loop code to handle CBOLT, which must be cast 3 times AddMissile(monster[i]._mx + infront_x[monster[i]._mdir], monster[i]._my + infront_y[monster[i]._mdir], monster[i]._menemyx, monster[i]._menemyy, monster[i]._mdir, monster[i]._mVar1, MI_ENEMYPLR, i, monster[i]._mVar2, 0); } PlayEffect(i, MS_ATTACK); } if (monster[i]._mAnimFrame == monster[i]._mAnimLen) { M_StartStand(i, monster[i]._mdir); return(RUN_AGAIN); } else return (RUN_DONE); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int M_DoRSpAttack(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_DoRSpAttack: Invalid monster %d",i); #else return RUN_DONE; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_DoRSpAttack: Monster %d \"%s\" MType NULL",i,monster[i].mName); #else return RUN_DONE; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_DoRSpAttack: Monster %d \"%s\" MData NULL",i,monster[i].mName); #else return RUN_DONE; #endif } if (monster[i]._mAnimFrame == monster[i].MData->mAFNum2 && !monster[i]._mAnimCnt) { AddMissile(monster[i]._mx + infront_x[monster[i]._mdir], monster[i]._my + infront_y[monster[i]._mdir], monster[i]._menemyx, monster[i]._menemyy, monster[i]._mdir, monster[i]._mVar1, MI_ENEMYPLR, i, monster[i]._mVar3, 0); if (Monsters[i].Snds[MS_SATTACK].effect[0] != 0){ PlayEffect(i, MS_SATTACK); } } // special code for Mega demon -- hold attack animation frame if (monster[i]._mAi == AI_MEGA && monster[i]._mAnimFrame == 3) { if (!monster[i]._mVar2++) monster[i]._mFlags |= MFLAG_STILL; else if (monster[i]._mVar2 == 15) monster[i]._mFlags &= ~MFLAG_STILL; } if (monster[i]._mAnimFrame == monster[i]._mAnimLen) { M_StartStand(i, monster[i]._mdir); return(RUN_AGAIN); } else return (RUN_DONE); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int M_DoSAttack(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_DoSAttack: Invalid monster %d",i); #else return RUN_DONE; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_DoSAttack: Monster %d \"%s\" MType NULL",i,monster[i].mName); #else return RUN_DONE; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_DoSAttack: Monster %d \"%s\" MData NULL",i,monster[i].mName); #else return RUN_DONE; #endif } 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(RUN_AGAIN); } else return(RUN_DONE); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int M_DoFadein(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_DoFadein: Invalid monster %d",i); #else return RUN_DONE; #endif } if ((monster[i]._mFlags & MFLAG_BACKWARDS && monster[i]._mAnimFrame == 1) || (!(monster[i]._mFlags & MFLAG_BACKWARDS) && monster[i]._mAnimFrame == monster[i]._mAnimLen)) { M_StartStand(i, monster[i]._mdir); monster[i]._mFlags &= ~MFLAG_BACKWARDS; return(RUN_AGAIN); } else return(RUN_DONE); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int M_DoFadeout(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_DoFadeout: Invalid monster %d",i); #else return RUN_DONE; #endif } int mtype; if ((monster[i]._mFlags & MFLAG_BACKWARDS && monster[i]._mAnimFrame == 1) || (!(monster[i]._mFlags & MFLAG_BACKWARDS) && monster[i]._mAnimFrame == monster[i]._mAnimLen)) { app_assert(monster[i].MType != NULL); mtype = monster[i].MType->mtype; if (EquivMonst(mtype, MT_INCIN)) monster[i]._mFlags &= ~MFLAG_BACKWARDS; else { monster[i]._mFlags &= ~MFLAG_BACKWARDS; monster[i]._mFlags |= MFLAG_INVISIBLE; } M_StartStand(i, monster[i]._mdir); return(RUN_AGAIN); } else return(RUN_DONE); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int M_DoHeal(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_DoHeal: Invalid monster %d",i); #else return RUN_DONE; #endif } MonsterStruct *Monst = &monster[i]; if (monster[i]._mFlags & MFLAG_NOHEAL) return RUN_DONE; // Gargoyle turning back to stone if(Monst->_mAnimFrame == 1) { Monst->_mFlags &= ~MFLAG_BACKWARDS; Monst->_mFlags |= MFLAG_STILL; if(Monst->_mhitpoints + Monst->_mVar1 < Monst->_mmaxhp) Monst->_mhitpoints += Monst->_mVar1; else { Monst->_mhitpoints = Monst->_mmaxhp; Monst->_mFlags &= ~MFLAG_STILL; Monst->_mmode = MM_SATTACK; } } return RUN_DONE; } /*-----------------------------------------------------------------------** **-----------------------------------------------------------------------*/ int M_DoTalk(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_DoTalk: Invalid monster %d",i); #else return RUN_DONE; #endif } MonsterStruct *Monst = &monster[i]; M_StartStand(i, monster[i]._mdir); Monst->_mgoal=MG_WAITTOTALK; if (effect_is_playing(alltext[monster[i].mtalkmsg].sfxnr)) return RUN_DONE; InitQTextMsg(monster[i].mtalkmsg); if (monster[i].mName == UniqMonst[MU_GARBUD].mName) { if (monster[i].mtalkmsg == TXT_GARB1) quests[Q_GARBUD]._qactive = QUEST_NOTDONE; quests[Q_GARBUD]._qlog = TRUE; if (monster[i].mtalkmsg == TXT_GARB2) { if(!(monster[i]._mFlags & MFLAG_DROP)) { SpawnItem(i, monster[i]._mx+1, monster[i]._my+1, TRUE); monster[i]._mFlags |= MFLAG_DROP; } } } if (monster[i].mName == UniqMonst[MU_ZHAR].mName) { if (monster[i].mtalkmsg == TXT_ZHAR1) { if(!(monster[i]._mFlags & MFLAG_DROP)) { quests[Q_ZHAR]._qactive = QUEST_NOTDONE; quests[Q_ZHAR]._qlog = TRUE; CreateTypeItem(monster[i]._mx+1, monster[i]._my+1, FALSE, IT_MISC, IMID_BOOK, TRUE, FALSE); monster[i]._mFlags |= MFLAG_DROP; } } } if (monster[i].mName == UniqMonst[MU_SNOTSPIL].mName) { if ((monster[i].mtalkmsg == TXT_BOL1) && (!(monster[i]._mFlags & MFLAG_DROP))) { app_assert(setpc_x != 0); ObjChangeMap(setpc_x, setpc_y, setpc_x+(setpc_w>>1)+2, setpc_y+(setpc_h>>1)-2); int const tren = TransVal; TransVal = 9; DRLG_MRectTrans(setpc_x, setpc_y, setpc_x+(setpc_w>>1)+4, setpc_y+(setpc_h>>1)); TransVal = tren; quests[Q_LTBANNER]._qvar1 = 2; if (quests[Q_LTBANNER]._qactive == QUEST_NOTACTIVE) quests[Q_LTBANNER]._qactive = QUEST_NOTDONE; monster[i]._mFlags |= MFLAG_DROP; } if (quests[Q_LTBANNER]._qvar1 < 2) { sprintf(tempstr, "SS Talk = %i, Flags = %i", monster[i].mtalkmsg, monster[i]._mFlags); app_fatal(tempstr); } } if (monster[i].mName == UniqMonst[MU_LACHDA].mName) { if (monster[i].mtalkmsg == TXT_VEIL1) { quests[Q_VEIL]._qactive = QUEST_NOTDONE; quests[Q_VEIL]._qlog = TRUE; } if ((monster[i].mtalkmsg == TXT_VEIL3) && (!(monster[i]._mFlags & MFLAG_DROP))) { SpawnUnique(UID_STEELVEIL, monster[i]._mx+1, monster[i]._my+1); monster[i]._mFlags |= MFLAG_DROP; } } if (monster[i].mName == UniqMonst[MU_WARLORD].mName) { app_assert(gbMaxPlayers == 1); quests[Q_WARLORD]._qvar1 = 2; } if ((monster[i].mName == UniqMonst[MU_LAZARUS].mName) && (gbMaxPlayers != 1)) { quests[Q_BETRAYER]._qvar1 = 6; monster[i]._mgoal = MG_ATTACK; monster[i]._msquelch = 255; monster[i].mtalkmsg = 0; } return RUN_DONE; } /*-----------------------------------------------------------------------* * M_Teleport * * Teleports monster i to one of it's enemy's 8 adjacent squares. **----------------------------------------------------------------------*/ void M_Teleport(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_Teleport: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; BOOL done = FALSE; int mulx,muly; int x,y; int a,b; int px,py; if (Monst->_mmode == MM_STONE) return; px = Monst->_menemyx; py = Monst->_menemyy; // randomize search direction mulx = random(100,2)*2-1; muly = random(100,2)*2-1; for(a = -1; a <= 1 && !done; ++a) for(b = -1; b < 1 && !done; ++b) if(a || b) { x = px + a*mulx; y = py + b*muly; if(InBounds(x,y) && x != Monst->_mx && y != Monst->_my) if(PosOkMonst(i,x,y)) done = TRUE; } if(done) { M_ClearSquares(i); app_assert((DWORD)Monst->_mx < MAXDUNX); app_assert((DWORD)Monst->_my < MAXDUNY); dMonster[Monst->_mx][Monst->_my] = 0; dMonster[x][y] = i+1; // M_StartHit knocks monster back to oldx,oldy, so those are the members to set Monst->_moldx = x; Monst->_moldy = y; Monst->_mdir = M_GetDir(i); M_CheckEFlag(i); } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int M_DoGotHit(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_DoGotHit: Invalid monster %d",i); #else return RUN_DONE; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_DoGotHit: Monster %d \"%s\" MType NULL",i,monster[i].mName); #else return RUN_DONE; #endif } if (monster[i]._mAnimFrame == monster[i]._mAnimLen) { M_StartStand(i, monster[i]._mdir); return(RUN_AGAIN); } else return(RUN_DONE); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void M_UpdateLeader(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_UpdateLeader: Invalid monster %d",i); #else return; #endif } int x,tmp; // check for pack leader if(monster[i]._uniqtype && (UniqMonst[monster[i]._uniqtype-1].mUnqAttr & UN_STICK)) // free the pack app_assert((DWORD)nummonsters <= MAXMONSTERS); for(x = 0; x < nummonsters; ++x) { if(monster[tmp=monstactive[x]].leaderflag == PACK_MEMBER && monster[tmp].leader == i) { monster[tmp].leaderflag = 0; } } // check for pack member if(monster[i].leaderflag == PACK_MEMBER) --monster[monster[i].leader].packsize; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void play_movie(const char * pszMovie,BOOL bAllowCancel); extern BOOL gbRunGame; extern BOOL deathflag; extern BYTE gbDoEnding; void DoEnding() { // tell other players to view victory if (gbMaxPlayers > 1) SNetLeaveGame(SNET_EXIT_PLAYERWON); //SetCursor(NO_CURSOR); //FullBlit(TRUE); //PaletteFadeOut(FADE_FAST); music_stop(); // give SNet some more time to field messages if (gbMaxPlayers > 1) Sleep(1000); #if !IS_VERSION(SHAREWARE) LONG lMusicVol; BOOL bMusicOn; if (plr[myplr]._pClass == CLASS_WARRIOR || plr[myplr]._pClass == CLASS_BARBARIAN) play_movie("gendata\\DiabVic2.smk",FALSE); else if (plr[myplr]._pClass == CLASS_SORCEROR) play_movie("gendata\\DiabVic1.smk",FALSE); else if (plr[myplr]._pClass == CLASS_MONK) play_movie("gendata\\DiabVic1.smk",FALSE); else play_movie("gendata\\DiabVic3.smk",FALSE); play_movie("gendata\\Diabend.smk",FALSE); // turn music to full volume bMusicOn = gbMusicOn; gbMusicOn = TRUE; lMusicVol = music_volume(VOLUME_READ); music_volume(VOLUME_MAX); music_start(MUSIC_L2); extern BOOL gbLoopMovie; gbLoopMovie = TRUE; play_movie("gendata\\loopdend.smk",TRUE); gbLoopMovie = FALSE; #endif //ClrDraw(); //FullBlit(FALSE); //PaletteFadeOut(FADE_FAST); // restore music volume #if !IS_VERSION(SHAREWARE) music_stop(); music_volume(lMusicVol); gbMusicOn = bMusicOn; #endif } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void PrepDoEnding() { // enable sound again gbSoundOn = sgbSaveSoundOn; gbDoEnding = TRUE; gbRunGame = FALSE; deathflag = FALSE; app_assert((DWORD)myplr < MAX_PLRS); plr[myplr].pDiabloKillLevel = max( plr[myplr].pDiabloKillLevel, (DWORD) gnDifficulty + 1 ); for (int i = 0; i < MAX_PLRS; ++i) { plr[i]._pmode = PM_QUIT; plr[i]._pInvincible = TRUE; if (gbMaxPlayers > 1) { if ((plr[i]._pHitPoints >> HP_SHIFT) == 0) plr[i]._pHitPoints = 1 << HP_SHIFT; if ((plr[i]._pMana >> MANA_SHIFT) == 0) plr[i]._pMana = 1 << MANA_SHIFT; } } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int M_DoDeath(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_DoDeath: Invalid monster %d",i); #else return RUN_DONE; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_DoDeath: Monster %d \"%s\" MType NULL",i,monster[i].mName); #else return RUN_DONE; #endif } ++monster[i]._mVar1; if (monster[i].MType->mtype == MT_DIABLO) { ViewX += Sign(monster[i]._mx - ViewX); ViewY += Sign(monster[i]._my - ViewY); if (monster[i]._mVar1 == 140) { PrepDoEnding(); } } else { if (monster[i]._mAnimFrame == monster[i]._mAnimLen) { if (monster[i]._uniqtype == 0) AddDead(monster[i]._mx, monster[i]._my, monster[i].MType->mdeadval, monster[i]._mdir); else AddDead(monster[i]._mx, monster[i]._my, monster[i]._udeadval, monster[i]._mdir); app_assert(!(dFlags[monster[i]._mx+1][monster[i]._my] & BFLAG_MONSTLR) && !(dFlags[monster[i]._mx][monster[i]._my+1] & BFLAG_MONSTLR)); dMonster[monster[i]._mx][monster[i]._my] = 0; monster[i]._mDelFlag = TRUE; M_UpdateLeader(i); } } return(RUN_DONE); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int M_DoSpStand(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_DoSpStand: Invalid monster %d",i); #else return RUN_DONE; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_DoSpStand: Monster %d \"%s\" MType NULL",i,monster[i].mName); #else return RUN_DONE; #endif } if (monster[i]._mAnimFrame == monster[i].MData->mAFNum2) { PlayEffect(i, MS_SATTACK); } if (monster[i]._mAnimFrame == monster[i]._mAnimLen) { M_StartStand(i, monster[i]._mdir); return(RUN_AGAIN); } else return(RUN_DONE); } int M_DoDelay(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_DoDelay: Invalid monster %d",i); #else return RUN_DONE; #endif } if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_DoDelay: Monster %d \"%s\" MType NULL",i,monster[i].mName); #else return RUN_DONE; #endif } int md; md = M_GetDir(i); monster[i]._mAnimData = monster[i].MType->Anims[MA_STAND].Cels[md]; if (monster[i]._mAi == AI_LAZURUS) { if ((monster[i]._mVar2 > 8) || (monster[i]._mVar2 < 0)) monster[i]._mVar2 = 8; } if(!monster[i]._mVar2--) { int tmp = monster[i]._mAnimFrame; M_StartStand(i, monster[i]._mdir); // StartStand sets AnimFrame to 0 monster[i]._mAnimFrame = tmp; // restore AnimFrame to avoid pop // in case monster continues standing. return(RUN_AGAIN); } else return(RUN_DONE); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int M_DoStone(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_DoStone: Invalid monster %d",i); #else return RUN_DONE; #endif } if (monster[i]._mhitpoints == 0) { dMonster[monster[i]._mx][monster[i]._my] = 0; monster[i]._mDelFlag = TRUE; } return(RUN_DONE); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void M_WalkDir(int i, int md) { int mwi; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_WalkDir: Invalid monster %d",i); #else return; #endif } mwi = monster[i].MType->Anims[MA_WALK].Frames - 1; switch (md) { case M_DIRU : M_StartWalk(i, 0, -MWVel[mwi][1], -1, -1, M_DIRU); break; case M_DIRUR : M_StartWalk(i, MWVel[mwi][1], -MWVel[mwi][0], 0, -1, M_DIRUR); break; case M_DIRR : M_StartWalk3(i, MWVel[mwi][2], 0, -32, -16, 1, -1, 1, 0, M_DIRR); break; case M_DIRDR : M_StartWalk2(i, MWVel[mwi][1], MWVel[mwi][0], -32, -16, 1, 0, M_DIRDR); break; case M_DIRD : M_StartWalk2(i, 0, MWVel[mwi][1], 0, -32, 1, 1, M_DIRD); break; case M_DIRDL : M_StartWalk2(i, -MWVel[mwi][1], MWVel[mwi][0], 32, -16, 0, 1, M_DIRDL); break; case M_DIRL : M_StartWalk3(i, -MWVel[mwi][2], 0, 32, -16, -1, 1, 0, 1, M_DIRL); break; case M_DIRUL : M_StartWalk(i, -MWVel[mwi][1], -MWVel[mwi][0], -1 ,0, M_DIRUL); break; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void GroupUnity(int i) { int leader; int tmp; int m; // // IMPLEMENT GROUP COHESION // if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("GroupUnity: Invalid monster %d",i); #else return; #endif } if(monster[i].leaderflag) { leader = monster[i].leader; // test if there's a wall between monst and pack leader tmp = LineClearF(CheckNoSolid, monster[i]._mx, monster[i]._my, monster[leader]._mfutx, monster[leader]._mfuty); if(!tmp && monster[i].leaderflag == PACK_MEMBER) { // have to leave pack --monster[leader].packsize; monster[i].leaderflag = PACK_NOMEMBER; } else if(tmp && monster[i].leaderflag == PACK_NOMEMBER && DIST(monster[i]._mx - monster[leader]._mfutx, monster[i]._my - monster[leader]._mfuty, 4)) { // rejoin pack ++monster[leader].packsize; monster[i].leaderflag = PACK_MEMBER; } } if(monster[i].leaderflag == PACK_MEMBER) { // make sure leader is active 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 && (monster[leader]._mFlags & MFLAG_STILL)) { monster[leader]._mFlags &= ~MFLAG_STILL; monster[leader]._mmode = MM_SATTACK; } } else if(monster[i]._uniqtype && (UniqMonst[monster[i]._uniqtype-1].mUnqAttr & UN_STICK)) { // make sure all of pack is activated app_assert((DWORD)nummonsters <= MAXMONSTERS); for(m = 0; m < nummonsters; ++m) { if(monster[tmp=monstactive[m]].leaderflag == PACK_MEMBER && monster[tmp].leader == i) { if(monster[i]._msquelch > monster[tmp]._msquelch) { monster[tmp]._lastx = monster[i]._mx; monster[tmp]._lasty = monster[i]._my; monster[tmp]._msquelch = monster[i]._msquelch-1; } if (monster[tmp]._mAi == AI_GARG && (monster[tmp]._mFlags & MFLAG_STILL)) { monster[tmp]._mFlags &= ~MFLAG_STILL; monster[tmp]._mmode = MM_SATTACK; } } } } // END OF GROUP COHESION } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ BOOL M_CallWalk(int i, int md) { int mdtemp = md; BOOL ok = FALSE; ok = DirOK(i,md); if(random(101,2)) ok = ok || DirOK(i, md = left[mdtemp]) || DirOK(i, md = right[mdtemp]); else ok = ok || DirOK(i, md = right[mdtemp]) || DirOK(i, md = left[mdtemp]); if(random(102,2)) ok = ok || DirOK(i, md = right[right[mdtemp]]) || DirOK(i, md = left[left[mdtemp]]); else ok = ok || DirOK(i, md = left[left[mdtemp]]) || DirOK(i, md = right[right[mdtemp]]); if(ok) M_WalkDir(i, md); return ok; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ BOOL M_PathWalk(int i) { char path[MAXPATHLEN]; static const char plr2monst[] = { 0, 5, 3, 7, 1, 4, 6, 0, 2 }; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_PathWalk: Invalid monster %d",i); #else return FALSE; #endif } CHECKFUNC1 Check = (monster[i]._mFlags & MFLAG_CHECKDOORS) ? PosOkMonst3:PosOkMonst; int pathlen = FindPath(Check, i, monster[i]._mx, monster[i]._my, monster[i]._menemyx, monster[i]._menemyy, path); if(pathlen) { M_CallWalk(i, plr2monst[path[0]]); return TRUE; } else return FALSE; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ BOOL M_CallWalk2(int i, int md) { int mdtemp = md; BOOL ok = FALSE; ok = DirOK(i,md); if(random(101,2)) ok = ok || DirOK(i, md = left[mdtemp]) || DirOK(i, md = right[mdtemp]); else ok = ok || DirOK(i, md = right[mdtemp]) || DirOK(i, md = left[mdtemp]); if(ok) M_WalkDir(i, md); 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 = FALSE; if(dir) md = left[left[md]]; else md = right[right[md]]; mdtemp = md; ok = DirOK(i,md); if(!ok) { // Note: The use of || below is a trick: the second condition is not evaluated // (and therefore md is not re-set) if the first condition evaluates TRUE. if(dir) ok = DirOK(i, md = right[mdtemp]) || DirOK(i, md = right[right[mdtemp]]); else ok = DirOK(i, md = left[mdtemp]) || DirOK(i, md = left[left[mdtemp]]); } if(ok) M_WalkDir(i,md); else { // Note: The value of md here is set according to the second condition of the // || operators above. Therefore, md is, by design, the direction towards // the player. dir = !dir; ok = M_CallWalk(i,opposite[mdtemp]); } return ok; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void M_Face(int i) { int md; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("M_Face: Invalid monster %d",i); #else return; #endif } if (monster[i]._msquelch) { md = M_GetDir(i); monster[i]._mdir = md; if (monster[i].MType == NULL) { #if defined(_DEBUG) app_fatal("M_Face: Monster %d \"%s\" MType NULL",i,monster[i].mName); #else return; #endif } monster[i]._mAnimData = monster[i].MType->Anims[MA_STAND].Cels[md]; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Zombie(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Zombie: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; int mx, my, md, v; if (Monst->_mmode == MM_STAND) { mx = Monst->_mx; my = Monst->_my; if (dFlags[mx][my] & BFLAG_MONSTACTIVE) { mx -= Monst->_menemyx; my -= Monst->_menemyy; md = Monst->_mdir; v = random(103,100); // Try attack if (DIST(mx,my,2)) { if (v < (10 + 2*Monst->_mint)) M_StartAttack(i); } else { // Try walk if (v < (10 + 2*Monst->_mint)) { // Check on distance from player if(DIST(mx,my,4+2*Monst->_mint)) { md = M_GetDir(i); M_CallWalk(i, md); } else { if(random(104,100) < (20 + 2*Monst->_mint)) md = random(104,8); M_DumbWalk(i, md); } } } // face dir if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[Monst->_mdir]; } } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_SkelSd(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_SkelSd: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; int mx, my, md; if ((Monst->_mmode == MM_STAND) && (Monst->_msquelch)) { mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty); Monst->_mdir = md; // Try attack if (DIST(mx,my,2)) { if (Monst->_mVar1 == MM_DELAY || random(105,100) < 20 + 2*Monst->_mint) M_StartAttack(i); else M_StartDelay(i, random(105,10)+10 - 2*Monst->_mint); } else { // Try walk -- more likely to walk if already walking if ((Monst->_mVar1 != MM_DELAY) && random(106,100) < 35 - 4*Monst->_mint) M_StartDelay(i, random(106,10) + 15 - 2*Monst->_mint); else M_CallWalk(i, md); } // face dir if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[md]; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ BOOL MAI_Path(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Path: Invalid monster %d",i); #else return FALSE; #endif } MonsterStruct *Monst = &monster[i]; if ((Monst->MType->mtype != MT_GOLEM) // golum always ready to walk && ((!Monst->_msquelch) || (Monst->_mmode != MM_STAND) || !(Monst->_mgoal == MG_ATTACK || Monst->_mgoal == MG_WALK_AROUND1 || Monst->_mgoal == MG_ATTACK2) || (Monst->_mx == 1 && Monst->_my == 0))) // inactive golum return FALSE; if (!LineClearF1(PosOkMonst2, i,Monst->_mx,Monst->_my,Monst->_menemyx,Monst->_menemyy) || (Monst->_pathcount >= 5 && Monst->_pathcount < 8)) { // Try Opening Door if (Monst->_mFlags & MFLAG_CHECKDOORS) MonstCheckDoors(i); if (++Monst->_pathcount >= 5) { if (M_PathWalk(i)) return TRUE; } else // don't want to zero pathcount return FALSE; } if (Monst->MType->mtype != MT_GOLEM) Monst->_pathcount = 0; //path count for golum handled in ai return FALSE; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Snake(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Snake: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; int fx, fy, mx, my, md, pnum; char pattern[] = { +1, +1, 0, -1, -1 , 0}; int tmp; pnum = Monst->_menemy; if ((Monst->_mmode == MM_STAND) && (Monst->_msquelch)) { 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; // Try attack if (DIST(mx,my,2)) { if (Monst->_mVar1 == MM_DELAY || Monst->_mVar1 == MM_MISSILE || random(105,100) < 20 + 1*Monst->_mint) M_StartAttack(i); else M_StartDelay(i, random(105,10)+10 - 1*Monst->_mint); } else if (DIST(mx,my,3) && LineClearF1(PosOkMonst, i,Monst->_mx,Monst->_my,fx,fy) && Monst->_mVar1 != MM_MISSILE) { // Launch snake if (AddMissile(Monst->_mx, Monst->_my, fx,fy, md, MIT_RHINO, pnum, i, 0, 0) != -1) { PlayEffect(i, MS_ATTACK); dMonster[Monst->_mx][Monst->_my] = -(i+1); Monst->_mmode = MM_MISSILE; } } else { // Try walk -- more likely to walk if already walking if ((Monst->_mVar1 != MM_DELAY) && random(106,100) < 35 - 2*Monst->_mint) M_StartDelay(i, random(106,10) + 15 - 1*Monst->_mint); else { md = Mod(md + pattern[Monst->_mgoalvar1], 8); if (++Monst->_mgoalvar1 > 5) Monst->_mgoalvar1 = 0; tmp = Mod(md - Monst->_mgoalvar2, 8); if (tmp > 0) { if (tmp < 4) Monst->_mgoalvar2 = Mod(Monst->_mgoalvar2 + 1, 8); else if (tmp == 4) Monst->_mgoalvar2 = md; else Monst->_mgoalvar2 = Mod(Monst->_mgoalvar2 - 1, 8); } app_assert(Monst->_mgoalvar2 >= 0 && Monst->_mgoalvar2 < 8); if (!M_DumbWalk(i, Monst->_mgoalvar2)) M_CallWalk2(i, Monst->_mdir); } } // face dir if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[Monst->_mdir]; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Bat(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Bat: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; int mx, my, md, v, pnum; int fx, fy; pnum = Monst->_menemy; if ((Monst->_mmode == MM_STAND) && (Monst->_msquelch)) { mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty); Monst->_mdir = md; v = random(107,100); if(Monst->_mgoal == MG_RUN_AWAY) { 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 = MG_ATTACK; } } else { fx = Monst->_menemyx; fy = Monst->_menemyy; // Try attack if ((Monst->MType->mtype == MT_GLOOM) && (!DIST(mx,my,5) && v < 33 + 4*Monst->_mint) && LineClearF1(PosOkMonst, i,Monst->_mx,Monst->_my,fx,fy)) { // Turn into missile if (AddMissile(Monst->_mx, Monst->_my, fx, fy, md, MIT_RHINO, pnum, i, 0, 0) != -1) { dMonster[Monst->_mx][Monst->_my] = -(i+1); Monst->_mmode = MM_MISSILE; } } else if (DIST(mx,my,2)) { if (v < 8 + 4*Monst->_mint) { M_StartAttack(i); // After attacking, fly away Monst->_mgoal = MG_RUN_AWAY; Monst->_mgoalvar1 = 0; if(Monst->MType->mtype == MT_FAMILIAR) { // Note: arg 5 is a signal to the lightning code. // The code checks if this is != to arg 1, and if so, checks for // collision. Normally, collision is only checked for when // lightning moves, but in this case it won't move! AddMissile(Monst->_menemyx, Monst->_menemyy, Monst->_menemyx+1, 0, -1, MIT_LIGHTNING, MI_ENEMYPLR, i, random(109,10)+1, 0); } } } else { // Try walk if ((Monst->_mVar2 > 20 && v < (13 + Monst->_mint)) || (WALKMODE(Monst->_mVar1) && Monst->_mVar2 == 0 && v < (63 + Monst->_mint))) { M_CallWalk(i, md); } } // face dir if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[md]; } } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_SkelBow(int i) { int mx, my, md, fx, fy; BOOL walking = FALSE; int v; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_SkelBow: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; if ((Monst->_mmode == MM_STAND) && (Monst->_msquelch)) { mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; md = M_GetDir(i); Monst->_mdir = md; // Try walk v = random(110,100); if (DIST(mx,my,4) && ((Monst->_mVar2 > 20 && v < 13 + 2*Monst->_mint) || (WALKMODE(Monst->_mVar1) && Monst->_mVar2 == 0 && v < 63 + 2*Monst->_mint))) { walking = M_DumbWalk(i, opposite[md]); } fx = Monst->_menemyx; fy = Monst->_menemyy; if (!walking && (random(110,100) < 3+2*Monst->_mint) && LineClear(Monst->_mx,Monst->_my,fx, fy)) { // Try attack M_StartRAttack(i,MIT_ARROW,4); } // face dir if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[md]; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Fat(int i) { int mx, my, md, v; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Fat: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; if ((Monst->_mmode == MM_STAND) && (Monst->_msquelch)) { mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; md = M_GetDir(i); Monst->_mdir = md; v = random(111,100); // Try attack if (DIST(mx,my,2)) { if (v < (15 + 4*Monst->_mint)) M_StartAttack(i); else if (v < (20 + 4*Monst->_mint)) M_StartSpAttack(i); } else { // Try walk if ((Monst->_mVar2 > 20 && v < (20 + 4*Monst->_mint)) || (WALKMODE(Monst->_mVar1) && Monst->_mVar2 == 0 && v < (70 + 4*Monst->_mint))) { M_CallWalk(i, md); } } // face dir if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[md]; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Sneak(int i) { int mx, my, md, v; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Sneak: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; int dist; 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 gothit, run away if(Monst->_mVar1 == MM_GOTHIT) { Monst->_mgoal = MG_RUN_AWAY; Monst->_mgoalvar1 = 0; // count run_away moves } // if far away enough, stop running away else if(!DIST(mx,my,dist+3) || Monst->_mgoalvar1 > 8) { Monst->_mgoal = MG_ATTACK; Monst->_mgoalvar1 = 0; } if(Monst->_mgoal == MG_RUN_AWAY && !(Monst->_mFlags & MFLAG_NOENEMY)) { // Use special "owner location", i.e. the location of the player // according to the computer which owns the player // This is an attempt to reduce the amount of monster warping due to // diverging paths on different computers if (Monst->_mFlags & MFLAG_MID) { md = GetDirection(Monst->_mx, Monst->_my, monster[Monst->_menemy]._mx, monster[Monst->_menemy]._my); } else { md = GetDirection(Monst->_mx, Monst->_my, plr[Monst->_menemy]._pownerx, plr[Monst->_menemy]._pownery); } md = opposite[md]; if(Monst->MType->mtype == MT_UNSEEN) { if(random(112,2)) md = left[md]; else md = right[md]; } } Monst->_mdir = md; v = random(112,100); // become visible if(DIST(mx,my,dist) && (Monst->_mFlags & MFLAG_INVISIBLE)) M_StartFadein(i,md,FALSE); // become invisible else if(!DIST(mx,my,dist+1) && !(Monst->_mFlags & MFLAG_INVISIBLE)) M_StartFadeout(i,md,TRUE); // Try attack else if ((Monst->_mgoal == MG_RUN_AWAY) || ( !DIST(mx,my,2) && ((Monst->_mVar2 > 20 && v < (14 + 4*Monst->_mint)) || (WALKMODE(Monst->_mVar1) && Monst->_mVar2 == 0 && v < (64 + 4*Monst->_mint))))) { // Try walk ++Monst->_mgoalvar1; M_CallWalk(i, md); } // face dir if (Monst->_mmode == MM_STAND) { if (DIST(mx,my,2) && (v < (10 + 4*Monst->_mint))) { M_StartAttack(i); } else { Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[md]; } } } } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Fireman(int i) { int mx, my, md, v, pnum; int fx, fy; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Fireman: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; if (Monst->_mmode == MM_STAND && Monst->_msquelch) { pnum = Monst->_menemy; fx = Monst->_menemyx; fy = Monst->_menemyy; mx = Monst->_mx - fx; my = Monst->_my - fy; md = M_GetDir(i); if(Monst->_mgoal == MG_ATTACK) { if(LineClear(Monst->_mx,Monst->_my,fx,fy)) { // Launch monster missile if (AddMissile(Monst->_mx, Monst->_my, fx,fy, md, MIT_FIREMAN, pnum, i, 0, 0) != -1) { Monst->_mmode = MM_MISSILE; Monst->_mgoal = MG_ATTACK2; Monst->_mgoalvar1 = 0; } } } else if(Monst->_mgoal == MG_ATTACK2) { if(Monst->_mgoalvar1 == 3) { Monst->_mgoal = MG_ATTACK; M_StartFadeout(i,md,TRUE); } else if(LineClear(Monst->_mx,Monst->_my,fx,fy)) { // Try attack M_StartRAttack(i,MIT_KRULL,4); ++Monst->_mgoalvar1; } else { M_StartDelay(i, random(112,10) + 5); ++Monst->_mgoalvar1; } } else if(Monst->_mgoal == MG_RUN_AWAY) { M_StartFadein(i, md,FALSE); Monst->_mgoal = MG_ATTACK2; } Monst->_mdir = md; v = random(112,100); if(Monst->_mmode == MM_STAND) { if(DIST(mx,my,2) && Monst->_mgoal == MG_ATTACK) { M_TryH2HHit(i, monster[i]._menemy, monster[i].mHit, monster[i].mMinDamage, monster[i].mMaxDamage); Monst->_mgoal = MG_RUN_AWAY; if(!M_CallWalk(i, opposite[md])) { M_StartFadein(i, md,FALSE); Monst->_mgoal = MG_ATTACK2; } } else { if(!M_CallWalk(i, md)) { if(Monst->_mgoal == MG_ATTACK || Monst->_mgoal == MG_RUN_AWAY) { M_StartFadein(i, md, FALSE); Monst->_mgoal = MG_ATTACK2; } } } } } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Fallen(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Fallen: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; int x,y; int xpos,ypos; int m; int rad; int mx,my; int aitype; if (Monst->_mgoal == MG_ATTACK2) { if(Monst->_mgoalvar1) --Monst->_mgoalvar1; else Monst->_mgoal = MG_ATTACK; } if ((Monst->_mmode == MM_STAND) && (Monst->_msquelch)) { if (Monst->_mgoal == MG_RUN_AWAY && Monst->_mgoalvar1-- == 0) { Monst->_mgoal = MG_ATTACK; M_StartStand(i, opposite[Monst->_mdir]); } if (Monst->_mAnimFrame == Monst->_mAnimLen) { if (random(113,4) == 0) { // start 'dance' if (!(monster[i]._mFlags & MFLAG_NOHEAL)) { M_StartSpStand(i, Monst->_mdir); if(Monst->_mmaxhp - (2*Monst->_mint+2) >= Monst->_mhitpoints) Monst->_mhitpoints += 2*Monst->_mint+2; else Monst->_mhitpoints = Monst->_mmaxhp; } // set surrounding monsters into relentless attack rad = 2*Monst->_mint+4; for(y = -rad; y <= rad; ++y) { for(x = -rad; x <= rad; ++x) { xpos = Monst->_mx + x; ypos = Monst->_my + y; if(InBounds(x,y)) { m = dMonster[xpos][ypos]; if(m > 0) { --m; aitype = monster[m]._mAi; if(aitype == AI_FALLEN) { monster[m]._mgoal = MG_ATTACK2; // Set # frames for crazy attack mode monster[m]._mgoalvar1 = (2*Monst->_mint+7)*15; } } } } } } } else if (Monst->_mgoal == MG_RUN_AWAY) { M_CallWalk(i,Monst->_mdir); } else if (Monst->_mgoal == MG_ATTACK2) { // attack relentlessly mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; if(DIST(mx,my,2)) M_StartAttack(i); else M_CallWalk(i, M_GetDir(i)); } else MAI_SkelSd(i); } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Cleaver(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Cleaver: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; int mx, my, md; if (Monst->_mmode == MM_STAND && Monst->_msquelch) { mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty); Monst->_mdir = md; // If close, attack if (DIST(mx,my,2)) { M_StartAttack(i); } else { // Try walk M_CallWalk(i, md); } // face dir if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[md]; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Round(int i, BOOL special) { int mx, my, md, v; int fx, fy, dist; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Round: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; if ((Monst->_mmode == MM_STAND) && (Monst->_msquelch)) { fx = Monst->_menemyx; fy = Monst->_menemyy; mx = Monst->_mx - fx; my = Monst->_my - fy; md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty); if(Monst->_msquelch < 255) MonstCheckDoors(i); v = random(114,100); if (DIST(mx,my,2) || Monst->_msquelch != 255 || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) { Monst->_mgoal = MG_ATTACK; } else if(Monst->_mgoal == MG_WALK_AROUND1 || (!DIST(mx,my,4) && !random(115,4))) { if(Monst->_mgoal != MG_WALK_AROUND1) { Monst->_mgoalvar1 = 0; // reset counter // Pick direction to walk around player Monst->_mgoalvar2 = random(116,2); } // MODE CHANGE: Walk Around player Monst->_mgoal = MG_WALK_AROUND1; // Calculate distance to player dist = max(abs(mx),abs(my)); if( (Monst->_mgoalvar1++ >= (dist << 1) && DirOK(i,md)) || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) { Monst->_mgoal = MG_ATTACK; } else if(!M_RoundWalk(i,md,Monst->_mgoalvar2)) M_StartDelay(i, random(125,10)+10); } if(Monst->_mgoal == MG_ATTACK) { // Try attack if (DIST(mx,my,2)) { if (v < 23 + 2*Monst->_mint) { Monst->_mdir = md; if(special && Monst->_mhitpoints < (Monst->_mmaxhp >> 1) && random(117,2)) M_StartSpAttack(i); else M_StartAttack(i); } } else if ((Monst->_mVar2 > 20 && v < (28 + 2*Monst->_mint)) || (WALKMODE(Monst->_mVar1) && Monst->_mVar2 == 0 && v < (78 + 2*Monst->_mint))) { M_CallWalk(i, md); } } // face dir if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[md]; } } void MAI_GoatMc(int i) { MAI_Round(i, TRUE); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Ranged(int i, int missile_type, BOOL special) { int fx, fy, mx, my, md; BOOL walking = FALSE; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Ranged: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; if (Monst->_mmode == MM_STAND) { if (Monst->_msquelch == 255 || monster[i]._mFlags & MFLAG_MID) { fx = Monst->_menemyx; fy = Monst->_menemyy; mx = Monst->_mx - fx; my = Monst->_my - fy; md = M_GetDir(i); if(Monst->_msquelch < 255) MonstCheckDoors(i); Monst->_mdir = md; if (Monst->_mVar1 == MM_RATTACK) { // pause after each arrow M_StartDelay(i, random(118,20)); } else if (DIST(mx,my,4) && random(119,100) < 70 + 10*Monst->_mint) { // retreat walking = M_CallWalk(i, opposite[md]); } if (Monst->_mmode == MM_STAND) { if (LineClear(Monst->_mx,Monst->_my,fx,fy)) { // Try attack if(special) M_StartRSpAttack(i,missile_type,4); else M_StartRAttack(i,missile_type,4); } else // face dir Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[md]; } } else if(Monst->_msquelch // 0 < msquelch < 255 && !(monster[i]._mFlags & MFLAG_MID)) // temp. golum fix { mx = Monst->_lastx; my = Monst->_lasty; md = GetDirection(Monst->_mx, Monst->_my, mx, my); M_CallWalk(i, md); } } } void MAI_GoatBow(int i) { MAI_Ranged(i,MIT_ARROW, FALSE); } void MAI_Succ(int i) { MAI_Ranged(i,MIT_FLARE, FALSE); } void MAI_Lich(int i) { MAI_Ranged(i,MIT_ORANGEFLARE, FALSE); } void MAI_ArchLich(int i) { MAI_Ranged(i,MIT_YELLOWFLARE, FALSE); } void MAI_Psychorb(int i) { MAI_Ranged(i,MIT_BLUEFLARE, FALSE); } void MAI_Necromorb(int i) { MAI_Ranged(i,MIT_REDFLARE, FALSE); } void MAI_AcidUniq(int i) { MAI_Ranged(i,MIT_ACID, TRUE); } void MAI_Firebat(int i) { MAI_Ranged(i,MIT_FIREBOLT, FALSE); } void MAI_Hellbat(int i) { MAI_Ranged(i,MIT_FIREBALL, FALSE); } /*-----------------------------------------------------------------------* * MAI_Scav * * MG_EAT is the scavenger's eat goal. * _mgoalvar1 : =0 ==> no goal dest * >0 ==> _mgoalvar1-1 is x coord of food * _mgoalvar2 : _mgoalvar2-1 is y coord of food * **-----------------------------------------------------------------------*/ void MAI_Scav(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Scav: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; int x,y; BOOL done = FALSE; if (Monst->_mmode == MM_STAND) { if(Monst->_mhitpoints < (Monst->_mmaxhp >> 1) && Monst->_mgoal != MG_EAT) { if(monster[i].leaderflag) { // leave pack permanently --monster[monster[i].leader].packsize; monster[i].leaderflag = 0; } Monst->_mgoal = MG_EAT; Monst->_mgoalvar3 = 10; } if (Monst->_mgoal == MG_EAT && Monst->_mgoalvar3) { --Monst->_mgoalvar3; // Are we on top of food? if (dDead[Monst->_mx][Monst->_my]) { M_StartEat(i); if (!(monster[i]._mFlags & MFLAG_NOHEAL)) { int maxhp = (Monst->MType->mMaxHP << HP_SHIFT); if (gbMaxPlayers == 1) maxhp >>= 1; int healamt = maxhp >> 3; Monst->_mhitpoints += healamt; // by eating, the monster can get to its species maximum // in hit points... if (Monst->_mhitpoints > maxhp) Monst->_mhitpoints = maxhp; if (Monst->_mmaxhp < Monst->_mhitpoints) Monst->_mmaxhp = Monst->_mhitpoints; if (Monst->_mgoalvar3 <= 0 || // done eating Monst->_mhitpoints == maxhp) { dDead[Monst->_mx][Monst->_my] = 0; // monster buried } } // if ((Monst->_mhitpoints) >= (Monst->_mmaxhp >> 1) + (Monst->_mmaxhp >> 2)) if ((Monst->_mhitpoints) == (Monst->_mmaxhp)) { Monst->_mgoal = MG_ATTACK; Monst->_mgoalvar1 = 0; Monst->_mgoalvar2 = 0; } } else { if (!Monst->_mgoalvar1) { // Find food! // Randomize search direction if(random(120,2)) { for(y = -4; y <= 4 && !done; ++y) for(x = -4; x <= 4 && !done; ++x) if(InBounds(x,y)) done = dDead[Monst->_mx + x][Monst->_my + y] && 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) if(InBounds(x,y)) done = dDead[Monst->_mx + x][Monst->_my + y] && LineClearF(CheckNoSolid,Monst->_mx,Monst->_my,Monst->_mx + x,Monst->_my + y); ++x; ++y; } if(done) { // Note: 1 is added to the following vars because // 0 is reserved as a flag meaning undefined. Monst->_mgoalvar1 = Monst->_mx + x +1; Monst->_mgoalvar2 = Monst->_my + y +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); } } } else // if(Monst->_mmode == MM_STAND) // none of the above cases applied, so resort to default behavior MAI_SkelSd(i); } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Garg(int i) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Garg: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; int mx, my; int md; mx = Monst->_mx - Monst->_lastx; my = Monst->_my - Monst->_lasty; md = M_GetDir(i); if(Monst->_msquelch && (Monst->_mFlags & MFLAG_STILL)) { M_Enemy(i); mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; if(DIST(mx,my,Monst->_mint+2)) Monst->_mFlags &= ~MFLAG_STILL; } else if ((Monst->_mmode == MM_STAND) && (Monst->_msquelch)) { if(Monst->_mhitpoints < (Monst->_mmaxhp >> 1)) { // run away to get healed. Monst->_mgoal = MG_RUN_AWAY; } if(Monst->_mgoal == MG_RUN_AWAY) { if(DIST(mx,my,Monst->_mint+2)) { if(!M_CallWalk(i,opposite[md])) Monst->_mgoal = MG_ATTACK; } else { Monst->_mgoal = MG_ATTACK; M_StartHeal(i); } } MAI_Round(i, FALSE); } } /*-----------------------------------------------------------------------* * MAI_RoundRanged * * Similar to MAI_Round, but does ranged attacks (throws lava) * **-----------------------------------------------------------------------*/ void MAI_RoundRanged(int i, int missile_type, BOOL checkdoors, int dam, BOOL lessmissiles) { int fx, fy, mx, my, md, v, pnum; int dist; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_RoundRanged: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; if ((Monst->_mmode == MM_STAND) && (Monst->_msquelch)) { pnum = Monst->_menemy; 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 < 255) MonstCheckDoors(i); // Using random(10000) instead of random(100) for increased precision // for small numbers, e.g. random(100) < (5 >> 1) v = random(121,10000); if (DIST(mx,my,2) || Monst->_msquelch != 255 || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) { Monst->_mgoal = MG_ATTACK; } else if(Monst->_mgoal == MG_WALK_AROUND1 || (!DIST(mx,my,3) && !random(122,4 << lessmissiles))) { if(Monst->_mgoal != MG_WALK_AROUND1) { Monst->_mgoalvar1 = 0; // reset counter // Pick direction to walk around player Monst->_mgoalvar2 = random(123,2); } // MODE CHANGE: Walk Around player Monst->_mgoal = MG_WALK_AROUND1; // Calculate distance to player dist = max(abs(mx),abs(my)); if(Monst->_mgoalvar1++ >= (dist << 1) && DirOK(i,md)) { Monst->_mgoal = MG_ATTACK; } else if(v < (500 + 500*Monst->_mint) >> lessmissiles && LineClear(Monst->_mx, Monst->_my, fx, fy)) { // Missile Attack M_StartRSpAttack(i,missile_type,dam); } else M_RoundWalk(i,md,Monst->_mgoalvar2); } if(Monst->_mgoal == MG_ATTACK) { // Try attack if (((!DIST(mx,my,3) && v < (1000 + 500*Monst->_mint) >> lessmissiles) || v < (500 + 500*Monst->_mint) >> lessmissiles) && LineClear(Monst->_mx,Monst->_my,fx,fy)) { // Missile Attack M_StartRSpAttack(i,missile_type,dam); } else if (DIST(mx,my,2)) { if (v < 6000 + 1000*Monst->_mint) { Monst->_mdir = md; M_StartAttack(i); } } else if (((v=random(124,100)) < (5000 + 1000*Monst->_mint)) || (WALKMODE(Monst->_mVar1) && Monst->_mVar2 == 0 && v < (8000 + 1000*Monst->_mint))) { M_CallWalk(i, md); } } // face dir if (Monst->_mmode == MM_STAND) M_StartDelay(i, random(125,10)+5); } } void MAI_Magma(int i) { MAI_RoundRanged(i, MIT_MAGMABALL, TRUE,4, FALSE); } void MAI_Storm(int i) { MAI_RoundRanged(i, MIT_THINLIGHTCTRL, TRUE,4, FALSE); } void MAI_BoneDemon(int i) { MAI_RoundRanged(i, MIT_BLUE2FLARE, TRUE,4, FALSE); } void MAI_Acid(int i) { MAI_RoundRanged(i, MIT_ACID, FALSE,4, TRUE); } void MAI_Diablo(int i) { MAI_RoundRanged(i, MIT_DIABAPOCA, FALSE, 40, FALSE); } void MAI_RR2(int i, int mistype, int dam) { int fx, fy, mx, my, md, v, pnum; int dist; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_RR2: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; if (!DIST(mx, my, 5)) MAI_SkelSd(i); else if ((Monst->_mmode == MM_STAND) && (Monst->_msquelch)) { pnum = Monst->_menemy; fx = Monst->_menemyx; fy = Monst->_menemyy; mx = Monst->_mx - fx; my = Monst->_my - fy; md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty); if(Monst->_msquelch < 255) MonstCheckDoors(i); v = random(121,100); if (DIST(mx,my,2) || Monst->_msquelch != 255 || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) { Monst->_mgoal = MG_ATTACK; } else if(Monst->_mgoal == MG_WALK_AROUND1 || !DIST(mx,my,3)) { if(Monst->_mgoal != MG_WALK_AROUND1) { Monst->_mgoalvar1 = 0; // reset counter // Pick direction to walk around player Monst->_mgoalvar2 = random(123,2); } // MODE CHANGE: Walk Around player Monst->_mgoal = MG_WALK_AROUND1; Monst->_mgoalvar3 = MG_WALK_AROUND1; // Calculate distance to player dist = max(abs(mx),abs(my)); if(Monst->_mgoalvar1++ >= (dist << 1) && DirOK(i,md)) { Monst->_mgoal = MG_ATTACK; } else if (v < 80 + 5*Monst->_mint) M_RoundWalk(i,md,Monst->_mgoalvar2); } if(Monst->_mgoal == MG_ATTACK) { // Try attack if (((!DIST(mx,my,3) && v < 10 + 5*Monst->_mint) || v < 5 + 5*Monst->_mint || Monst->_mgoalvar3 == MG_WALK_AROUND1) && LineClear(Monst->_mx,Monst->_my,fx,fy)) { // Missile Attack M_StartRSpAttack(i,mistype,dam); } else if (DIST(mx,my,2)) { if (random(124,100) < 40 + 10*Monst->_mint) { Monst->_mdir = md; if (random(124, 2)) M_StartAttack(i); else // Missile Attack M_StartRSpAttack(i,mistype,dam); } } else if (((v=random(124,100)) < (50 + 10*Monst->_mint)) || (WALKMODE(Monst->_mVar1) && Monst->_mVar2 == 0 && v < (80 + 10*Monst->_mint))) { M_CallWalk(i, md); } Monst->_mgoalvar3 = MG_ATTACK; } if (Monst->_mmode == MM_STAND) M_StartDelay(i, random(125,10)+5); } } void MAI_Mega(int i) { MAI_RR2(i, MIT_FLAMEC,0); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Golum(int i) { int ok, j, k, mid; int mx, my, md; BOOL have_enemy; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Golum: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; if ((Monst->_mx == 1) && (Monst->_my == 0)) return; if (Monst->_mmode == MM_DEATH) return; if (Monst->_mmode == MM_SPSTAND) return; if (Monst->_mmode >= MM_WALK && Monst->_mmode <= MM_WALK3) return; if (!(monster[i]._mFlags & MFLAG_MID)) M_Enemy(i); have_enemy = !(Monst->_mFlags & MFLAG_NOENEMY); if (Monst->_mmode != MM_ATTACK) { mx = Monst->_mx - monster[Monst->_menemy]._mfutx; my = Monst->_my - monster[Monst->_menemy]._mfuty; md = GetDirection(Monst->_mx, Monst->_my, monster[Monst->_menemy]._mx, monster[Monst->_menemy]._my); Monst->_mdir = md; if (DIST(mx,my,2) && have_enemy) { Monst->_menemyx = monster[Monst->_menemy]._mx; Monst->_menemyy = monster[Monst->_menemy]._my; if(!monster[Monst->_menemy]._msquelch) { monster[Monst->_menemy]._msquelch = 255; monster[Monst->_menemy]._lastx = Monst->_mx; monster[Monst->_menemy]._lasty = Monst->_my; for (j = 0; j < 5; ++j) { for (k = 0; k < 5; ++k) { mid = dMonster[monster[i]._mx - 2 + k][monster[i]._my - 2 + j]; if (mid > 0) monster[mid]._msquelch = 255; } } } M_StartAttack(i); } else { if (!have_enemy || !MAI_Path(i)) { ++Monst->_pathcount; if (Monst->_pathcount > 8) Monst->_pathcount = 5; ok = M_CallWalk(i, plr[i]._pdir); if (ok == FALSE) { md = (md - 1) & 0x07; for (j = 0; j < 8 && ok == FALSE; ++j) { md = (md + 1) & 0x07; ok = DirOK(i, md); } if (ok) M_WalkDir(i, md); } } } } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_SkelKing(int i) { int fx, fy, mx, my, md, v; int dist; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_SkelKing: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; int nx,ny; // location for new skel spawn int skel; if ((Monst->_mmode == MM_STAND) && (Monst->_msquelch)) { fx = Monst->_menemyx; fy = Monst->_menemyy; mx = Monst->_mx - fx; my = Monst->_my - fy; md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty); if(Monst->_msquelch < 255) MonstCheckDoors(i); v = random(126,100); if (DIST(mx,my,2) || Monst->_msquelch != 255 || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) { Monst->_mgoal = MG_ATTACK; } else if(Monst->_mgoal == MG_WALK_AROUND1 || (!DIST(mx,my,3) && !random(127,4))) { if(Monst->_mgoal != MG_WALK_AROUND1) { Monst->_mgoalvar1 = 0; // reset counter // Pick direction to walk around player Monst->_mgoalvar2 = random(128,2); } // MODE CHANGE: Walk Around player Monst->_mgoal = MG_WALK_AROUND1; // Calculate distance to player dist = max(abs(mx),abs(my)); if( (Monst->_mgoalvar1++ >= (dist << 1) && DirOK(i,md)) || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) { Monst->_mgoal = MG_ATTACK; } else if(!M_RoundWalk(i,md,Monst->_mgoalvar2)) M_StartDelay(i, random(125,10)+10); } if(Monst->_mgoal == MG_ATTACK) { // Try attack if (gbMaxPlayers == 1 // no spawning in multi-player && ((!DIST(mx,my,3) && v < 35 + 4*Monst->_mint) || v < 6) && LineClear(Monst->_mx,Monst->_my,fx,fy)) { // Spawn new Skeleton nx = Monst->_mx + offset_x[md]; ny = Monst->_my + offset_y[md]; if (PosOkMonst(i,nx,ny) && nummonsters < MAXMONSTERS) { // skel = AddMonster(nx, ny, md, 1, TRUE); // M_StartSpStand(skel, md); skel = M_SpawnSkel(nx, ny, md); M_StartSpStand(i, md); } } else if (DIST(mx,my,2)) { if (v < 20 + Monst->_mint) { Monst->_mdir = md; M_StartAttack(i); } } else if (((v=random(129,100)) < (25 + Monst->_mint)) || (WALKMODE(Monst->_mVar1) && Monst->_mVar2 == 0 && v < (75 + Monst->_mint))) { M_CallWalk(i, md); } else M_StartDelay(i, random(130,10)+10); } // face dir if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[md]; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Rhino(int i) { int fx, fy, mx, my, md, v; int dist; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Rhino: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; if (Monst->_mmode == MM_STAND && Monst->_msquelch) { fx = Monst->_menemyx; fy = Monst->_menemyy; mx = Monst->_mx - fx; my = Monst->_my - fy; md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty); if(Monst->_msquelch < 255) MonstCheckDoors(i); v = random(131,100); if (DIST(mx,my,2)) { Monst->_mgoal = MG_ATTACK; } else if(Monst->_mgoal == MG_WALK_AROUND1 || (!DIST(mx,my,5) && random(132,4))) { if(Monst->_mgoal != MG_WALK_AROUND1) { Monst->_mgoalvar1 = 0; // reset counter // Pick direction to walk around player Monst->_mgoalvar2 = random(133,2); } // MODE CHANGE: Walk Around player Monst->_mgoal = MG_WALK_AROUND1; // Calculate distance to player dist = max(abs(mx),abs(my)); if( (Monst->_mgoalvar1++ >= (dist << 1)) || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) { Monst->_mgoal = MG_ATTACK; } else if(!M_RoundWalk(i,md,Monst->_mgoalvar2)) M_StartDelay(i, random(125,10)+10); } if(Monst->_mgoal == MG_ATTACK) { // Try attack if ((!DIST(mx,my,5) && v < 43 + 2*Monst->_mint) && LineClearF1(PosOkMonst, i,Monst->_mx,Monst->_my,fx,fy)) { // Launch rhino if (AddMissile(Monst->_mx, Monst->_my, fx,fy, md, MIT_RHINO, Monst->_menemy, i, 0, 0) != -1) { if (Monst->MData->snd_special) PlayEffect(i, MS_SATTACK); dMonster[Monst->_mx][Monst->_my] = -(i+1); Monst->_mmode = MM_MISSILE; } } else if (DIST(mx,my,2)) { if (v < 28 + 2*Monst->_mint) { Monst->_mdir = md; M_StartAttack(i); } } else if (((v=random(134,100)) < (33 + 2*Monst->_mint)) || (WALKMODE(Monst->_mVar1) && Monst->_mVar2 == 0 && v < (83 + 2*Monst->_mint))) { M_CallWalk(i, md); } else M_StartDelay(i, random(135,10)+10); } // face dir if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[Monst->_mdir]; } } void MAI_HorkDemon(int i) { int v; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_HorkDemon: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; if (Monst->_mmode == MM_STAND && Monst->_msquelch) { int const fx = Monst->_menemyx; int const fy = Monst->_menemyy; int const mx = Monst->_mx - fx; int const my = Monst->_my - fy; int const md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty); if(Monst->_msquelch < 255) MonstCheckDoors(i); v = random(131,100); if (DIST(mx,my,2)) { Monst->_mgoal = MG_ATTACK; } else if(Monst->_mgoal == MG_WALK_AROUND1 || (!DIST(mx,my,5) && random(132,4))) { if(Monst->_mgoal != MG_WALK_AROUND1) { Monst->_mgoalvar1 = 0; // reset counter // Pick direction to walk around player Monst->_mgoalvar2 = random(133,2); } // MODE CHANGE: Walk Around player Monst->_mgoal = MG_WALK_AROUND1; // Calculate distance to player int const dist = max(abs(mx),abs(my)); if( (Monst->_mgoalvar1++ >= (dist << 1)) || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) { Monst->_mgoal = MG_ATTACK; } else if(!M_RoundWalk(i,md,Monst->_mgoalvar2)) M_StartDelay(i, random(125,10)+10); } if(Monst->_mgoal == MG_ATTACK) { // Try attack if ((!DIST(mx,my,3) && v < 43 + 2*Monst->_mint)) { // Launch Hork Spawn int const Enemyd = Monst->_mdir; int const nx = Monst->_mx + infront_x[Enemyd]; int const ny = Monst->_my + infront_y[Enemyd]; if (PosOkMonst(i,nx,ny) && nummonsters < MAXMONSTERS) { M_StartRSpAttack(i, MIT_HORKSPAWN, 0); } } else if (DIST(mx,my,2)) { if (v < 28 + 2*Monst->_mint) { Monst->_mdir = md; M_StartAttack(i); } } else if (((v=random(134,100)) < (33 + 2*Monst->_mint)) || (WALKMODE(Monst->_mVar1) && Monst->_mVar2 == 0 && v < (83 + 2*Monst->_mint))) { M_CallWalk(i, md); } else M_StartDelay(i, random(135,10)+10); } // face dir if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[Monst->_mdir]; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Counselor(int i) { int fx, fy, mx, my, md, v; int dist; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Counselor: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; static const BYTE counsmiss[] = { MIT_FIREBOLT, MIT_CBOLT, MIT_LIGHTCTRL, MIT_FIREBALL }; if ((Monst->_mmode == MM_STAND) && (Monst->_msquelch)) { fx = Monst->_menemyx; fy = Monst->_menemyy; mx = Monst->_mx - fx; my = Monst->_my - fy; md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty); if(Monst->_msquelch < 255) MonstCheckDoors(i); v = random(121,100); if (Monst->_mgoal == MG_RUN_AWAY) { if (Monst->_mgoalvar1++ > 3) { Monst->_mgoal = MG_ATTACK; M_StartFadein(i, md, TRUE); } else M_CallWalk(i, opposite[md]); } else if(Monst->_mgoal == MG_WALK_AROUND1) { // Calculate distance to player dist = max(abs(mx),abs(my)); if (DIST(mx,my,2) || Monst->_msquelch != 255 || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) { Monst->_mgoal = MG_ATTACK; M_StartFadein(i, md, TRUE); } else if(Monst->_mgoalvar1++ >= (dist << 1) && DirOK(i,md)) { Monst->_mgoal = MG_ATTACK; M_StartFadein(i, md, TRUE); } else M_RoundWalk(i,md,Monst->_mgoalvar2); } else if(Monst->_mgoal == MG_ATTACK) { // Try attack if (DIST(mx,my,2)) { Monst->_mdir = md; if (Monst->_mhitpoints < (Monst->_mmaxhp >> 1)) { // run away! Monst->_mgoal = MG_RUN_AWAY; Monst->_mgoalvar1 = 0; M_StartFadeout(i, md, FALSE); } else if (Monst->_mVar1 == MM_DELAY || random(105,100) < 20 + 2*Monst->_mint) { M_StartRAttack(i,-1,0); AddMissile(monster[i]._mx, monster[i]._my, 0, 0, monster[i]._mdir, MIT_FLASH, MI_ENEMYPLR, i, 4, 0); AddMissile(monster[i]._mx, monster[i]._my, 0, 0, monster[i]._mdir, MIT_FLASH2, MI_ENEMYPLR, i, 4, 0); } else M_StartDelay(i, random(105,10)+10 - 2*Monst->_mint); } else if (v < 50 + 5*Monst->_mint && LineClear(Monst->_mx,Monst->_my,fx,fy)) { // Missile Attack M_StartRAttack(i, counsmiss[Monst->_mint], random(77, Monst->mMaxDamage - Monst->mMinDamage + 1) + Monst->mMinDamage); } else if (random(124, 100) < 30) { Monst->_mgoal = MG_WALK_AROUND1; Monst->_mgoalvar1 = 0; M_StartFadeout(i, md, FALSE); } else M_StartDelay(i, random(105,10)+10 - 2*Monst->_mint); } // face dir if (Monst->_mmode == MM_STAND) M_StartDelay(i, random(125,10)+5); } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Garbud(int i) { int mx, my, md; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Garbud: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; if (Monst->_mmode == MM_STAND) { mx = Monst->_mx; my = Monst->_my; md = M_GetDir(i); if ((Monst->mtalkmsg < TXT_GARB4) && (Monst->mtalkmsg > (TXT_GARB1-1)) && !(dFlags[mx][my] & BFLAG_MONSTACTIVE) && (Monst->_mgoal == MG_WAITTOTALK)) { ++Monst->mtalkmsg; Monst->_mgoal = MG_TALK; } if (dFlags[mx][my] & BFLAG_MONSTACTIVE) { mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; #if !IS_VERSION(SHAREWARE) if ((Monst->mtalkmsg == TXT_GARB4) && !(effect_is_playing(USFX_GARBUD4)) && (Monst->_mgoal == MG_WAITTOTALK)) { Monst->_mgoal = MG_ATTACK; Monst->_msquelch = 255; Monst->mtalkmsg = 0; } #endif } if ((Monst->_mgoal == MG_ATTACK) || (Monst->_mgoal == MG_WALK_AROUND1)) { MAI_Round(i, TRUE); } monster[i]._mdir = md; if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[md]; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Zhar(int i) { int mx, my, md, dist; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Zhar: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; if (Monst->_mmode == MM_STAND) { mx = Monst->_mx; my = Monst->_my; md = M_GetDir(i); if ((Monst->mtalkmsg == TXT_ZHAR1) && !(dFlags[mx][my] & BFLAG_MONSTACTIVE) && (Monst->_mgoal == MG_WAITTOTALK)) { ++Monst->mtalkmsg; Monst->_mgoal = MG_TALK; } if (dFlags[mx][my] & BFLAG_MONSTACTIVE) { mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; dist = max(abs(mx), abs(my)); #if !IS_VERSION(SHAREWARE) if ((Monst->mtalkmsg == TXT_ZHAR2) && !(effect_is_playing(USFX_ZHAR2)) && (Monst->_mgoal == MG_WAITTOTALK)) { Monst->_mgoal = MG_ATTACK; Monst->_msquelch = 255; Monst->mtalkmsg = 0; } #endif } if ((Monst->_mgoal == MG_ATTACK) || (Monst->_mgoal == MG_RUN_AWAY) || (Monst->_mgoal == MG_WALK_AROUND1)) { MAI_Counselor(i); } monster[i]._mdir = md; if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[md]; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_SnotSpil(int i) { int mx, my, md, pnum; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_SnotSpil: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; pnum = Monst->_menemy; if (Monst->_mmode == MM_STAND) { mx = Monst->_mx; my = Monst->_my; md = M_GetDir(i); if ((Monst->mtalkmsg == TXT_BOL1) && !(dFlags[mx][my] & BFLAG_MONSTACTIVE) && (Monst->_mgoal == MG_WAITTOTALK)) { Monst->mtalkmsg = TXT_BOL2; Monst->_mgoal = MG_TALK; } if ((Monst->mtalkmsg == TXT_BOL2) && (quests[Q_LTBANNER]._qvar1 == 3)) { Monst->mtalkmsg = 0; Monst->_mgoal = MG_ATTACK; } if (dFlags[mx][my] & BFLAG_MONSTACTIVE) { mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; #if !IS_VERSION(SHAREWARE) if ((Monst->mtalkmsg == TXT_BOL3) && !(effect_is_playing(USFX_SNOT3)) && (Monst->_mgoal == MG_WAITTOTALK)) { int i = plr[pnum]._pvid; ObjChangeMap(setpc_x, setpc_y, setpc_x + setpc_w + 1, setpc_y + setpc_h + 1); quests[Q_LTBANNER]._qvar1 = 3; extern void RedoPlayerVision(); RedoPlayerVision(); Monst->_mgoal = MG_ATTACK; Monst->_msquelch = 255; Monst->mtalkmsg = 0; } #endif if (quests[Q_LTBANNER]._qvar1 == 3) { if ((Monst->_mgoal == MG_ATTACK) || (Monst->_mgoal == MG_ATTACK2)) { MAI_Fallen(i); } } } monster[i]._mdir = md; if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[md]; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Lazurus(int i) { int mx, my, md; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Lazurus: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; if (Monst->_mmode == MM_STAND) { mx = Monst->_mx; my = Monst->_my; md = M_GetDir(i); if (dFlags[mx][my] & BFLAG_MONSTACTIVE) { mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; if (gbMaxPlayers == 1) { if ((Monst->mtalkmsg == TXT_VB1) && (Monst->_mgoal == MG_TALK) && (plr[myplr]._px == 35) && (plr[myplr]._py == 46)) { PlayInGameMovie("gendata\\fprst3.smk"); Monst->_mmode = MM_TALK; quests[Q_BETRAYER]._qvar1 = 5; } #if !IS_VERSION(SHAREWARE) if ((Monst->mtalkmsg == TXT_VB1) && !(effect_is_playing(USFX_LAZ1)) && (Monst->_mgoal == MG_WAITTOTALK)) { ObjChangeMapResync(1, 18, 20, 24); extern void RedoPlayerVision(); RedoPlayerVision(); quests[Q_BETRAYER]._qvar1 = 6; Monst->_mgoal = MG_ATTACK; Monst->_msquelch = 255; Monst->mtalkmsg = 0; } #endif } if (gbMaxPlayers != 1) { #if !IS_VERSION(SHAREWARE) if ((Monst->mtalkmsg == TXT_VB1) && (Monst->_mgoal == MG_TALK) && (quests[Q_BETRAYER]._qvar1 <= 3)) { Monst->_mmode = MM_TALK; } #endif } } if ((Monst->_mgoal == MG_ATTACK) || (Monst->_mgoal == MG_RUN_AWAY) || (Monst->_mgoal == MG_WALK_AROUND1)) { MAI_Counselor(i); } monster[i]._mdir = md; if ((Monst->_mmode == MM_STAND) || (Monst->_mmode == MM_TALK)) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[md]; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Lazhelp(int i) { int mx, my, md; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Lazhelp: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; if (Monst->_mmode == MM_STAND) { mx = Monst->_mx; my = Monst->_my; md = M_GetDir(i); if (dFlags[mx][my] & BFLAG_MONSTACTIVE) { mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; if (gbMaxPlayers == 1) { if (quests[Q_BETRAYER]._qvar1 <= 5) Monst->_mgoal = MG_TALK; else { Monst->_mgoal = MG_ATTACK; Monst->mtalkmsg = 0; } } else if (gbMaxPlayers != 1) { Monst->_mgoal = MG_ATTACK; } } if (Monst->_mgoal == MG_ATTACK) { MAI_Succ(i); } monster[i]._mdir = md; } if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[md]; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Lachdanan(int i) { int mx, my, md, pnum; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Lachdanan: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; pnum = Monst->_menemy; if (Monst->_mmode == MM_STAND) { mx = Monst->_mx; my = Monst->_my; md = M_GetDir(i); #if !IS_VERSION(SHAREWARE) if ((Monst->mtalkmsg == TXT_VEIL1) && !(dFlags[mx][my] & BFLAG_MONSTACTIVE) && (Monst->_mgoal == MG_WAITTOTALK)) { ++Monst->mtalkmsg; Monst->_mgoal = MG_TALK; } #endif if (dFlags[mx][my] & BFLAG_MONSTACTIVE) { mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; #if !IS_VERSION(SHAREWARE) if ((Monst->mtalkmsg == TXT_VEIL3) && !(effect_is_playing(USFX_LACH3)) && (Monst->_mgoal == MG_WAITTOTALK)) { Monst->mtalkmsg = 0; quests[Q_VEIL]._qactive = QUEST_DONE; M_StartKill(i, -1); } #endif } monster[i]._mdir = md; if (Monst->_mmode == MM_STAND) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[md]; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void MAI_Warlord(int i) { int mx, my, md; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MAI_Warlord: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; if (Monst->_mmode == MM_STAND) { mx = Monst->_mx; my = Monst->_my; md = M_GetDir(i); if (dFlags[mx][my] & BFLAG_MONSTACTIVE) { mx = Monst->_mx - Monst->_menemyx; my = Monst->_my - Monst->_menemyy; if ((Monst->mtalkmsg == TXT_WARLRD1) && (Monst->_mgoal == MG_TALK)) { Monst->_mmode = MM_TALK; } #if !IS_VERSION(SHAREWARE) BOOL effect_is_playing(int nSFX); if ((Monst->mtalkmsg == TXT_WARLRD1) && !(effect_is_playing(USFX_WARLRD1)) && (Monst->_mgoal == MG_WAITTOTALK)) { Monst->_mgoal = MG_ATTACK; Monst->_msquelch = 255; Monst->mtalkmsg = 0; } #endif } if (Monst->_mgoal == MG_ATTACK) { MAI_SkelSd(i); } monster[i]._mdir = md; if ((Monst->_mmode == MM_STAND) || (Monst->_mmode == MM_TALK)) Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Cels[md]; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void DeleteMonsterList() { int i, mi; // pseudo delete dead golems for (i = 0; i < 4; ++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 = 4; // this is to skip the golems app_assert((DWORD)nummonsters <= MAXMONSTERS); while (i < nummonsters) { mi = monstactive[i]; if (monster[mi]._mDelFlag) { DeleteMonster(i); i = 0; } else ++i; } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void ProcessMonsters () { int i, mi; int raflag; int mx, my; MonsterStruct *Monst; int oldmode; DeleteMonsterList(); app_assert((DWORD)nummonsters <= MAXMONSTERS); for (i = 0; i < nummonsters; ++i) { mi = monstactive[i]; Monst = &monster[mi]; raflag = RUN_DONE; // Try and run same AI's as best as possible in multiplayer if (gbMaxPlayers > 1) { SetRndSeed(Monst->_mAISeed); Monst->_mAISeed = GetRndSeed(); } // Turn regen if (!(monster[mi]._mFlags & MFLAG_NOHEAL)) { if ((Monst->_mhitpoints < Monst->_mmaxhp) && ((Monst->_mhitpoints >> HP_SHIFT) > 0)) { if (Monst->mLevel > 1) Monst->_mhitpoints += Monst->mLevel >> 1; else Monst->_mhitpoints += Monst->mLevel; } } // Check if just activating mx = Monst->_mx; my = Monst->_my; if ((dFlags[mx][my] & BFLAG_MONSTACTIVE) && (Monst->_msquelch == 0)) { #if !IS_VERSION(SHAREWARE) if (Monst->MType->mtype == MT_CLEAVER) { //if (gbMaxPlayers == 1) PlayInGameMovie("gendata\\fbutch3.smk"); PlaySFX(USFX_CLEAVER); } if (Monst->MType->mtype == MT_NKR) { if (gbCowsuit) PlaySFX(HSFX_NA_KRUL6); else if (Na_Krul.Books) PlaySFX(HSFX_NA_KRUL4); else PlaySFX(HSFX_NA_KRUL5); } if (Monst->MType->mtype == MT_BUG) { //if (gbMaxPlayers == 1) PlayInGameMovie("gendata\\fbutch3.smk"); PlaySFX(HSFX_DEFILER5); } #endif M_Enemy(mi); } // Check if monster's enemy if (Monst->_mFlags & MFLAG_MID) { if ((DWORD)Monst->_menemy >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("Illegal enemy monster %d for monster \"%s\"",Monst->_menemy,Monst->mName); #else return; #endif } Monst->_menemyx = Monst->_lastx = monster[Monst->_menemy]._mfutx; Monst->_menemyy = Monst->_lasty = monster[Monst->_menemy]._mfuty; } else { // Deal with keeping a monster active for a while after its visibility flag // goes off if ((DWORD)Monst->_menemy >= MAX_PLRS) { #if defined(_DEBUG) app_fatal("Illegal enemy player %d for monster \"%s\"",Monst->_menemy,Monst->mName); #else return; #endif } Monst->_menemyx = plr[Monst->_menemy]._pfutx; Monst->_menemyy = plr[Monst->_menemy]._pfuty; if (dFlags[mx][my] & BFLAG_MONSTACTIVE) { Monst->_msquelch = 255; Monst->_lastx = plr[Monst->_menemy]._pfutx; Monst->_lasty = plr[Monst->_menemy]._pfuty; } else if(Monst->_msquelch && (Monst->_mAi != MT_DIABLO)) --Monst->_msquelch; } do { // Run Monster's AI // Try Path Mode if (Monst->_mFlags & MFLAG_PATH) { if (!MAI_Path(mi)) AiProc[Monst->_mAi](mi); } else AiProc[Monst->_mAi](mi); // Run Monster Mode switch (oldmode = 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_RATTACK: raflag = M_DoRAttack(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_SPSTAND: raflag = M_DoSpStand(mi); break; case MM_RSATTACK: raflag = M_DoRSpAttack(mi); break; case MM_DELAY: raflag = M_DoDelay(mi); break; case MM_MISSILE: raflag = RUN_DONE; 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; } /* for (int mm = 0; mm < nummonsters; ++mm) { int mmi = monstactive[mm]; app_assert(monster[mmi]._mDelFlag || monster[mmi]._mmode == MM_MISSILE || !monster[mmi]._msquelch || mmi < 4 || dMonster[monster[mmi]._mx][monster[mmi]._my] == mmi+1 || dMonster[monster[mmi]._mx][monster[mmi]._my] == -(mmi+1)); }*/ if(raflag != RUN_DONE) GroupUnity(mi); // if (Monst->_msquelch) DaveMonstMap(FALSE, 0); } while (raflag != RUN_DONE); // Animate Monster if (Monst->_mmode != MM_STONE) { ++Monst->_mAnimCnt; if (!(Monst->_mFlags & MFLAG_STILL)) { if (Monst->_mAnimCnt >= Monst->_mAnimDelay) { Monst->_mAnimCnt = 0; if(Monst->_mFlags & MFLAG_BACKWARDS) { --Monst->_mAnimFrame; if (!Monst->_mAnimFrame) Monst->_mAnimFrame = Monst->_mAnimLen; } else { ++Monst->_mAnimFrame; if (Monst->_mAnimFrame > Monst->_mAnimLen) Monst->_mAnimFrame = 1; } } } } } DeleteMonsterList(); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void FreeMonsterGFX() { app_assert((DWORD)nummtypes <= MAX_LVLMTYPES); for (int monst = 0; monst < nummtypes; ++monst) { int mtype = Monsters[monst].mtype; for (int anim = 0; anim < MAX_ANIMTYPE; ++anim) { if(!(animletter[anim] == 's' && !monsterdata[mtype].has_special)) { DiabloFreePtr(Monsters[monst].Anims[anim].CMem); } } } IFreeMissileGFX(); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ BOOL DirOK(int i, int mdir) { long fx, fy; int tmp; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("DirOK: Invalid monster %d",i); #else return FALSE; #endif } fx = monster[i]._mx + offset_x[mdir]; fy = monster[i]._my + offset_y[mdir]; if(! InBounds(fx,fy)) return FALSE; if (! PosOkMonst(i, fx, fy)) return FALSE; if (mdir == M_DIRR) { if (SolidLoc(fx+0,fy+1)) return FALSE; if (dFlags[fx+0][fy+1] & BFLAG_MONSTLR) return FALSE; } else if(mdir == M_DIRL) { if (SolidLoc(fx+1,fy+0)) return FALSE; if (dFlags[fx+1][fy+0] & BFLAG_MONSTLR) return FALSE; } else if (mdir == M_DIRU) { if (SolidLoc(fx+1,fy+0)) return FALSE; if (SolidLoc(fx+0,fy+1)) return FALSE; } else if (mdir == M_DIRD) { if (SolidLoc(fx-1,fy+0)) return FALSE; if (SolidLoc(fx+0,fy-1)) return FALSE; } // check for group cohesion if(monster[i].leaderflag == PACK_MEMBER) { // make sure monster is close to leader return DIST( fx-monster[monster[i].leader]._mfutx, fy-monster[monster[i].leader]._mfuty, 4 ); } if (monster[i]._uniqtype && (UniqMonst[monster[i]._uniqtype-1].mUnqAttr & UN_STICK)) { int mcount = 0; for (int x = fx - 3; x <= fx + 3; ++x) { for (int y = fy - 3; y <= fy + 3; ++y) { if (! InBounds(x,y)) continue; if((tmp = dMonster[x][y]) < 0) tmp = -tmp; if (tmp != 0) --tmp; app_assert(tmp >= 0); if (monster[tmp].leaderflag == PACK_MEMBER && monster[tmp].leader == i && monster[tmp]._mfutx == x && monster[tmp]._mfuty == y) ++mcount; } } return (mcount == monster[i].packsize); } return TRUE; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ BOOL PosOkMissile(int x, int y) { return !(nMissileTable[dPiece[x][y]] || (dFlags[x][y] & BFLAG_MONSTLR)); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ BOOL CheckNoSolid(int x, int y) { return !nSolidTable[dPiece[x][y]]; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------* BOOL LineClearF(CHECKFUNC Clear, int x1, int y1, int x2, int y2) { int md; BOOL done = FALSE; do { md = GetDirection(x1,y1,x2,y2); x1 += offset_x[md]; y1 += offset_y[md]; done = !(*Clear)(x1, y1); } while(!done && !(x1==x2 && y1==y2)); return (x1 == x2) && (y1 == y2); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ BOOL LineClearF(CHECKFUNC Clear, int x1, int y1, int x2, int y2) { // Bresenham line algorithm // See Foley/van Dam, "Computer Graphics: Principles and Practice" int dx,dy; int d; // test variable int dincH; // Horizontal increment for d int dincD; // Diagonal increment for d int xincD,yincD; // Diagonal increments for minor axis int xorg, yorg; BOOL done = FALSE; int tmp; xorg = x1; yorg = y1; dx = x2 - x1; dy = y2 - y1; if(abs(dx) > abs(dy)) { // X is the major axis if(dx < 0) { // swap endpoints tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; dx = -dx; dy = -dy; } if(dy > 0) { // pos. slope d = 2*dy - dx; dincH = 2*dy; dincD = 2*(dy - dx); yincD = 1; } else { // neg. slope d = 2*dy + dx; dincH = 2*dy; dincD = 2*(dy + dx); yincD = -1; } // done = (x1 != xorg || y1 != yorg) && !(*Clear)(x1, y1); while(!done && !(x1 == x2 && y1 == y2)) { if((d <= 0) ^ (yincD < 0)) { d += dincH; } else { d += dincD; y1 += yincD; } ++x1; done = (x1 != xorg || y1 != yorg) && !(*Clear)(x1, y1); } } else { // Y is the major axis if(dy < 0) { // swap endpoints tmp = y1; y1 = y2; y2 = tmp; tmp = x1; x1 = x2; x2 = tmp; dy = -dy; dx = -dx; } if(dx > 0) { // pos. slope d = 2*dx - dy; dincH = 2*dx; dincD = 2*(dx - dy); xincD = 1; } else { // neg. slope d = 2*dx + dy; dincH = 2*dx; dincD = 2*(dx + dy); xincD = -1; } // done = (y1 != yorg || x1 != xorg) && !(*Clear)(x1, y1); while(!done && !(y1 == y2 && x1 == x2)) { if((d <= 0) ^ (xincD < 0)) { d += dincH; } else { d += dincD; x1 += xincD; } ++y1; done = (y1 != yorg || x1 != xorg) && !(*Clear)(x1, y1); } } return x1 == x2 && y1 == y2; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ BOOL LineClear(int x1, int y1, int x2, int y2) { return LineClearF(PosOkMissile, x1, y1, x2, y2); } /*-----------------------------------------------------------------------* * LineClearF1 * * This is exactly the same routine as LineClearF, except its Check * routine requires an extra argument **-----------------------------------------------------------------------*/ BOOL LineClearF1(CHECKFUNC1 Clear, int monst, int x1, int y1, int x2, int y2) { // Bresenham line algorithm // See Foley/van Dam, "Computer Graphics: Principles and Practice" int dx,dy; int d; // test variable int dincH; // Horizontal increment for d int dincD; // Diagonal increment for d int xincD,yincD; // Diagonal increments for minor axis int xorg, yorg; BOOL done = FALSE; int tmp; xorg = x1; yorg = y1; dx = x2 - x1; dy = y2 - y1; if(abs(dx) > abs(dy)) { // X is the major axis if(dx < 0) { // swap endpoints tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; dx = -dx; dy = -dy; } if(dy > 0) { // pos. slope d = 2*dy - dx; dincH = 2*dy; dincD = 2*(dy - dx); yincD = 1; } else { // neg. slope d = 2*dy + dx; dincH = 2*dy; dincD = 2*(dy + dx); yincD = -1; } while(!done && !(x1 == x2 && y1 == y2)) { if((d <= 0) ^ (yincD < 0)) { d += dincH; } else { d += dincD; y1 += yincD; } ++x1; done = (x1 != xorg || y1 != yorg) && !Clear(monst, x1, y1); } } else { // Y is the major axis if(dy < 0) { // swap endpoints tmp = y1; y1 = y2; y2 = tmp; tmp = x1; x1 = x2; x2 = tmp; dy = -dy; dx = -dx; } if(dx > 0) { // pos. slope d = 2*dx - dy; dincH = 2*dx; dincD = 2*(dx - dy); xincD = 1; } else { // neg. slope d = 2*dx + dy; dincH = 2*dx; dincD = 2*(dx + dy); xincD = -1; } while(!done && !(y1 == y2 && x1 == x2)) { if((d <= 0) ^ (xincD < 0)) { d += dincH; } else { d += dincD; x1 += xincD; } ++y1; done = (y1 != yorg || x1 != xorg) && !Clear(monst, x1, y1); } } return x1 == x2 && y1 == y2; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void SyncMonsterAnim(int m) { int dir; if ((DWORD)m >= MAXMONSTERS || m < 0) { #if defined(_DEBUG) app_fatal("SyncMonsterAnim: Invalid monster %d",m); #else return; #endif } app_assert(monster[m]._mMTidx < MAX_LVLMTYPES && monster[m]._mMTidx >= 0); monster[m].MType = &Monsters[monster[m]._mMTidx]; monster[m].MData = Monsters[monster[m]._mMTidx].MData; if(monster[m]._uniqtype) monster[m].mName = UniqMonst[monster[m]._uniqtype-1].mName; else { app_assert(monster[m].MData != 0); monster[m].mName = monster[m].MData->mName; } dir = monster[m]._mdir; switch (monster[m]._mmode) { case MM_STAND : monster[m]._mAnimData = monster[m].MType->Anims[MA_STAND].Cels[dir]; break; case MM_WALK : monster[m]._mAnimData = monster[m].MType->Anims[MA_WALK].Cels[dir]; break; case MM_WALK2 : monster[m]._mAnimData = monster[m].MType->Anims[MA_WALK].Cels[dir]; break; case MM_WALK3: monster[m]._mAnimData = monster[m].MType->Anims[MA_WALK].Cels[dir]; break; case MM_ATTACK: monster[m]._mAnimData = monster[m].MType->Anims[MA_ATTACK].Cels[dir]; break; case MM_RATTACK: monster[m]._mAnimData = monster[m].MType->Anims[MA_ATTACK].Cels[dir]; break; case MM_GOTHIT: monster[m]._mAnimData = monster[m].MType->Anims[MA_GOTHIT].Cels[dir]; break; case MM_DEATH: monster[m]._mAnimData = monster[m].MType->Anims[MA_DEATH].Cels[dir]; break; case MM_SATTACK: monster[m]._mAnimData = monster[m].MType->Anims[MA_SPECIAL].Cels[dir]; break; case MM_FADEIN: monster[m]._mAnimData = monster[m].MType->Anims[MA_SPECIAL].Cels[dir]; break; case MM_FADEOUT: monster[m]._mAnimData = monster[m].MType->Anims[MA_SPECIAL].Cels[dir]; break; case MM_SPSTAND: monster[m]._mAnimData = monster[m].MType->Anims[MA_SPECIAL].Cels[dir]; break; case MM_RSATTACK: monster[m]._mAnimData = monster[m].MType->Anims[MA_SPECIAL].Cels[dir]; break; case MM_DELAY: monster[m]._mAnimData = monster[m].MType->Anims[MA_STAND].Cels[dir]; break; case MM_HEAL: monster[m]._mAnimData = monster[m].MType->Anims[MA_SPECIAL].Cels[dir]; break; case MM_TALK: monster[m]._mAnimData = monster[m].MType->Anims[MA_STAND].Cels[dir]; break; case MM_STONE: monster[m]._mAnimData = monster[m].MType->Anims[MA_STAND].Cels[dir]; monster[m]._mAnimFrame = 1; monster[m]._mAnimLen = monster[m].MType->Anims[MA_STAND].Frames; break; case MM_MISSILE: monster[m]._mAnimData = monster[m].MType->Anims[MA_ATTACK].Cels[dir]; monster[m]._mAnimFrame = 1; monster[m]._mAnimLen = monster[m].MType->Anims[MA_ATTACK].Frames; break; default: monster[m]._mAnimData = monster[m].MType->Anims[MA_STAND].Cels[dir]; monster[m]._mAnimFrame = 1; monster[m]._mAnimLen = monster[m].MType->Anims[MA_STAND].Frames; break; } } /*-----------------------------------------------------------------------* * When monster gets killed, make the fallen run in fear **-----------------------------------------------------------------------*/ void M_FallenFear(int x, int y) { int i, mi; int rundist; int aitype; app_assert((DWORD)nummonsters <= MAXMONSTERS); for (i = 0; i < nummonsters; ++i) { mi = monstactive[i]; rundist = 0; switch(monster[mi].MType->mtype) { case MT_RFALLSD : case MT_RFALLSP : rundist = 7; break; case MT_DFALLSD : case MT_DFALLSP : rundist = 5; break; case MT_YFALLSD : case MT_YFALLSP : rundist = 3; break; case MT_BFALLSD : case MT_BFALLSP : rundist = 2; break; } aitype = monster[mi]._mAi; if(aitype == AI_FALLEN) { if (rundist && DIST(x-monster[mi]._mx, y-monster[mi]._my, 5) && ((monster[mi]._mhitpoints >> HP_SHIFT) > 0)) { monster[mi]._mgoal = MG_RUN_AWAY; monster[mi]._mgoalvar1 = rundist; // run away for 'rundist' squares // next direction will be away from monster that got hit monster[mi]._mdir = GetDirection(x, y, monster[i]._mx, monster[i]._my); } } } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void PrintMonstHistory(int mt) { int res, minhp, maxhp; sprintf(tempstr, "Total kills : %i", monstkills[mt]); AddPanelString(tempstr, TEXT_CENTER); if (monstkills[mt] >= 30) { minhp = monsterdata[mt].mMinHP; maxhp = monsterdata[mt].mMaxHP; if (gbMaxPlayers == 1) { minhp = minhp >> 1; maxhp = maxhp >> 1; } if (minhp < 1) minhp = 1; if (maxhp < 1) maxhp = 1; if (gnDifficulty == D_NIGHTMARE) { minhp = (minhp * 3) + ((gbMaxPlayers == 1) ? 50 : 100); maxhp = (maxhp * 3) + ((gbMaxPlayers == 1) ? 50 : 100); } else if (gnDifficulty == D_HELL) { minhp = (minhp * 4) + ((gbMaxPlayers == 1) ? 100 : 200); maxhp = (maxhp * 4) + ((gbMaxPlayers == 1) ? 100 : 200); } sprintf(tempstr, "Hit Points : %i-%i", minhp, maxhp); AddPanelString(tempstr, TEXT_CENTER); } /*minhp = monster[cursmonst]._mhitpoints >> HP_SHIFT; maxhp = monster[cursmonst]._mmaxhp >> HP_SHIFT; sprintf(tempstr, "Hit Points : %i of %i", minhp, maxhp); AddPanelString(tempstr, TEXT_CENTER);*/ if (monstkills[mt] >= 15) { if (gnDifficulty != D_HELL) res = monsterdata[mt].mMagicRes; else res = monsterdata[mt].mMagicRes2; res &= (M_RM|M_RF|M_RL|M_IM|M_IF|M_IL); if (res == M_NONE) { strcpy(tempstr, "No magic resistance"); AddPanelString(tempstr, TEXT_CENTER); } else { if (res & (M_RM|M_RF|M_RL)) { strcpy(tempstr, "Resists : "); if (res & M_RM) strcat(tempstr, "Magic "); if (res & M_RF) strcat(tempstr, "Fire "); if (res & M_RL) strcat(tempstr, "Lightning "); tempstr[strlen(tempstr)-1] = 0; AddPanelString(tempstr, TEXT_CENTER); } if (res & (M_IM|M_IF|M_IL)) { strcpy(tempstr, "Immune : "); if (res & M_IM) strcat(tempstr, "Magic "); if (res & M_IF) strcat(tempstr, "Fire "); if (res & M_IL) strcat(tempstr, "Lightning "); tempstr[strlen(tempstr)-1] = 0; AddPanelString(tempstr, TEXT_CENTER); } } } pinfoflag = TRUE; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void PrintUniqueHistory() { int res; /*int minhp = monster[cursmonst]._mhitpoints >> HP_SHIFT; int maxhp = monster[cursmonst]._mmaxhp >> HP_SHIFT; sprintf(tempstr, "Hit Points : %i of %i", minhp, maxhp); AddPanelString(tempstr, TEXT_CENTER);*/ res = monster[cursmonst].mMagicRes; res &= (M_RM|M_RF|M_RL|M_IM|M_IF|M_IL); if (res == M_NONE) { strcpy(tempstr, "No resistances"); AddPanelString(tempstr, TEXT_CENTER); strcpy(tempstr, "No Immunities"); AddPanelString(tempstr, TEXT_CENTER); } else { if (res & (M_RM|M_RF|M_RL)) strcpy(tempstr, "Some Magic Resistances"); else strcpy(tempstr, "No resistances"); AddPanelString(tempstr, TEXT_CENTER); if (res & (M_IM|M_IF|M_IL)) strcpy(tempstr, "Some Magic Immunities"); else strcpy(tempstr, "No Immunities"); AddPanelString(tempstr, TEXT_CENTER); } pinfoflag = TRUE; } /*-----------------------------------------------------------------------* * MissToMonst * * Used when Rhino charges and collides into something. **-----------------------------------------------------------------------*/ void MissToMonst(int i, int x, int y) { int oldx,oldy; int newx,newy; if ((DWORD)i >= MAXMISSILES) { #if defined(_DEBUG) app_fatal("MissToMonst: Invalid missile %d",i); #else return; #endif } MissileStruct *Miss = &missile[i]; int m = Miss->_misource; if ((DWORD)m >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("MissToMonst: Invalid monster %d",m); #else return; #endif } MonsterStruct *Monst = &monster[m]; int pnum; app_assert(Monst->_mmode == MM_MISSILE); 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(EquivMonst(Monst->MType->mtype, MT_INCIN)) M_StartFadein(m, Monst->_mdir, FALSE); else { if ((Monst->_mFlags & MFLAG_MID) == 0) M_StartHit(m,-1,0); else M2MStartHit(m,-1,0); } if ((Monst->_mFlags & MFLAG_MID) == 0) { pnum = dPlayer[oldx][oldy]-1; if((dPlayer[oldx][oldy] > 0) && (Monst->MType->mtype != MT_GLOOM) && !EquivMonst(Monst->MType->mtype, MT_INCIN)) { M_TryH2HHit(m, dPlayer[oldx][oldy]-1, 500, Monst->mMinDamage2, Monst->mMaxDamage2); // make sure player didn't go change location during h2hhit if (pnum == dPlayer[oldx][oldy]-1 && !EquivMonst(Monst->MType->mtype, MT_NSNAKE)) { // make sure player is doing a hit animation if (plr[pnum]._pmode != PM_GOTHIT && plr[pnum]._pmode != PM_DEATH) StartPlrHit(pnum, 0, TRUE); // knock opponent back one square 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) && (Monst->MType->mtype != MT_GLOOM) && !EquivMonst(Monst->MType->mtype, MT_INCIN)) { M_TryM2MHit(m, dMonster[oldx][oldy]-1, 500, Monst->mMinDamage2, Monst->mMaxDamage2); if (!EquivMonst(Monst->MType->mtype, MT_NSNAKE)) { // knock opponent back one square newx = oldx + offset_x[Monst->_mdir]; newy = oldy + offset_y[Monst->_mdir]; if(PosOkMonst(dMonster[oldx][oldy]-1, newx, newy)) { // This assumes that monster got hit (since we use a 500% hit above) // so we don't have to clean up his area, and we assume pnum is positive pnum = dMonster[newx][newy] = dMonster[oldx][oldy]; dMonster[oldx][oldy] = 0; --pnum; monster[pnum]._mfutx = monster[pnum]._mx = newx; monster[pnum]._mfuty = monster[pnum]._my = newy; } } } } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ static bool CheckForMissileWalls(int i, int x, int y) { bool ret = true; int mi = dMissile[x][y]; if(mi && i >= 0) { bool fire = false; bool lightwall = false; if(mi > 0) { if(missile[mi]._mitype == MIT_FIREWALL) fire = true; else if ( missile[mi]._mitype == MIT_LIGHTWALL) lightwall = true; } else { for(mi = 0; mi < nummissiles; ++mi) { int const mx = missileactive[mi]; if (missile[mx]._mix == x && missile[mx]._miy == y) { if(missile[mx]._mitype == MIT_FIREWALL) { fire = true; break; } if (missile[mx]._mitype == MIT_LIGHTWALL) { lightwall = true; break; } } } } if ((fire && !(monster[i].mMagicRes & M_IF)) || (fire && (monster[i].MType->mtype == MT_DIABLO))) ret = FALSE; if ((lightwall && !(monster[i].mMagicRes & M_IL)) || (lightwall && (monster[i].MType->mtype == MT_DIABLO))) ret = FALSE; } return ret; } /*-----------------------------------------------------------------------* * PosOkMonst * * Map position (x,y) is ok for placement of a monster. **-----------------------------------------------------------------------*/ BOOL PosOkMonst(int i, int x, int y) { BOOL ret = TRUE; ret = !SolidLoc(x, y) && !dPlayer[x][y] && !dMonster[x][y]; int oi = dObject[x][y]; if(ret && oi) { if (oi > 0) oi -= 1; else oi = -(oi + 1); if (object[oi]._oSolidFlag) ret = FALSE; } if(ret) { ret = CheckForMissileWalls(i, x, y); } return ret; } /*-----------------------------------------------------------------------* * PosOkMonst2 * * Map position (x,y) is ok for placement of a monster. * Same as PosOkMonst, except ignores other monsters and players **-----------------------------------------------------------------------*/ BOOL PosOkMonst2(int i, int x, int y) { BOOL ret = TRUE; int oi = dObject[x][y]; ret = !SolidLoc(x, y); if(ret && oi) { if (oi > 0) oi -= 1; else oi = -(oi + 1); if (object[oi]._oSolidFlag) ret = FALSE; } if(ret) { ret = CheckForMissileWalls(i, x, y); } return ret; } /*-----------------------------------------------------------------------* * PosOkMonst * * Map position (x,y) is ok for placement of a monster. * Same as PosOkMonst3, except ignores doors **-----------------------------------------------------------------------*/ BOOL PosOkMonst3(int i, int x, int y) { BOOL ret = TRUE; bool isdoor = false; int oi = dObject[x][y]; if(oi) { if (oi > 0) oi -= 1; else oi = -(oi + 1); int const objtype = object[oi]._otype; isdoor = (objtype == OBJ_L1DOORL || objtype == OBJ_L1DOORR || objtype == OBJ_L2DOORL || objtype == OBJ_L2DOORR || objtype == OBJ_L3DOORL || objtype == OBJ_L3DOORR); if (object[oi]._oSolidFlag && !isdoor) ret = FALSE; } if (ret) ret = (!SolidLoc(x, y) || isdoor) && !dPlayer[x][y] && !dMonster[x][y]; if(ret) { ret = CheckForMissileWalls(i, x, y); } return ret; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ BOOL IsSkel(int mt) { return EquivMonst(mt, MT_WSKELAX) || EquivMonst(mt, MT_WSKELBW) || EquivMonst(mt, MT_WSKELSD); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ BOOL IsGoat(int mt) { return EquivMonst(mt, MT_NGOATMC) || EquivMonst(mt, MT_NGOATBW); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int M_SpawnSkel(int x, int y, int dir) { int i,j; int skeltypes = 0; int skel; app_assert((DWORD)nummtypes <= MAX_LVLMTYPES); for (i = 0; i < nummtypes; ++i) if (IsSkel(Monsters[i].mtype)) ++skeltypes; if (skeltypes) { j = random(136,skeltypes); skeltypes = 0; for (i = 0; i < nummtypes && skeltypes <= j; ++i) if(IsSkel(Monsters[i].mtype)) ++skeltypes; --i; skel = AddMonster(x, y, dir, i, TRUE); if (skel != -1) M_StartSpStand(skel, dir); return skel; } else 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 monstok[3][3]; int i,j,xx,yy,rs; BOOL savail; if (ii == -1) return FALSE; // Try location first if (PosOkMonst(-1, x, y)) { ActivateSpawn(ii, x, y, GetDirection(x, y, x, y)); } else { // Try surrounding squares savail = FALSE; yy = 0; for (j = (y-1); j <= (y+1); ++j) { xx = 0; for (i = (x-1); i <= (x+1); ++i) { monstok[xx][yy] = PosOkMonst(-1, i,j); savail |= monstok[xx][yy]; ++xx; } ++yy; } // No fit, no good if (!savail) return(FALSE); // Place skeleton 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; } } } xx = xx + x - 1; yy = yy + y - 1; ActivateSpawn(ii, xx, yy, GetDirection(xx, yy, x, y)); } return(TRUE); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ int PreSpawnSkeleton() { int i,j; int skeltypes = 0; int skel; app_assert((DWORD)nummtypes <= MAX_LVLMTYPES); for (i = 0; i < nummtypes; ++i) if (IsSkel(Monsters[i].mtype)) ++skeltypes; if (skeltypes) { j = random(136,skeltypes); skeltypes = 0; for (i = 0; i < nummtypes && skeltypes <= j; ++i) { if(IsSkel(Monsters[i].mtype)) ++skeltypes; } --i; skel = AddMonster(0, 0, 0, i, FALSE); if (skel != -1) M_StartStand(skel, 0); return skel; } else return -1; } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void TalktoMonster(int i) { int pnum, itm; if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("TalktoMonster: Invalid monster %d",i); #else return; #endif } MonsterStruct *Monst = &monster[i]; pnum = Monst->_menemy; Monst->_mmode = MM_TALK; if ((Monst->_mAi != AI_SNOTSPIL) && (Monst->_mAi != AI_LACHDANAN)) return; if (QuestStatus(Q_LTBANNER)) { if ((quests[Q_LTBANNER]._qvar1 == 2) && (PlrHasItem(pnum, IDI_BANNER, itm))) { RemoveInvItem(pnum, itm); quests[Q_LTBANNER]._qactive = QUEST_DONE; Monst->mtalkmsg = TXT_BOL3; Monst->_mgoal = MG_TALK; } } if (QuestStatus(Q_VEIL)) { if ((Monst->mtalkmsg >= TXT_VEIL1) && (PlrHasItem(pnum, IDI_GLDNELIX, itm))) { RemoveInvItem(pnum, itm); Monst->mtalkmsg = TXT_VEIL3; Monst->_mgoal = MG_TALK; } } } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ void SpawnGolum(int i, int x, int y, int mi) { if ((DWORD)i >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("SpawnGolum: Invalid monster %d",i); #else return; #endif } dMonster[x][y] = i + 1; monster[i]._mx = x; monster[i]._my = y; monster[i]._mfutx = x; monster[i]._mfuty = y; monster[i]._moldx = x; monster[i]._moldy = y; monster[i]._pathcount = 0; monster[i]._mmaxhp = ((plr[i]._pMaxMana/3)<<1) + (((missile[mi]._mispllvl << HP_SHIFT) << 3) + ((missile[mi]._mispllvl << HP_SHIFT) << 1)); monster[i]._mhitpoints = monster[i]._mmaxhp; monster[i].mArmorClass = 25; monster[i].mHit = 40 + (plr[i]._pLevel << 1) + ((missile[mi]._mispllvl << 2) + (missile[mi]._mispllvl)); monster[i].mMinDamage = 8 + (missile[mi]._mispllvl << 1); monster[i].mMaxDamage = 16 + (missile[mi]._mispllvl << 1); monster[i]._mFlags |= MFLAG_MKILLER; M_StartSpStand(i, 0); M_Enemy(i); void NetSendCmdGolem(BYTE, BYTE, BYTE, BYTE, long, BYTE); 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) { #if defined(_DEBUG) app_fatal("CanTalkToMonst: Invalid monster %d",m); #else return FALSE; #endif } if (monster[m]._mgoal == MG_TALK) return(TRUE); if (monster[m]._mgoal == MG_WAITTOTALK) return(TRUE); return(FALSE); } /*-----------------------------------------------------------------------* **-----------------------------------------------------------------------*/ BOOL CheckMonsterHit(int m, BOOL &ret) { if ((DWORD)m >= MAXMONSTERS) { #if defined(_DEBUG) app_fatal("CheckMonsterHit: Invalid monster %d",m); #else return FALSE; #endif } if (monster[m]._mAi == AI_GARG && (monster[m]._mFlags & MFLAG_STILL)) { monster[m]._mFlags &= ~MFLAG_STILL; monster[m]._mmode = MM_SATTACK; ret = TRUE; return(TRUE); } else if(EquivMonst(monster[m].MType->mtype, MT_COUNSLR) && (monster[m]._mgoal != MG_ATTACK)) { ret = FALSE; return TRUE; } return FALSE; } //****************************************************************** //****************************************************************** int encode_enemy(int m) { if (monster[m]._mFlags & MFLAG_MID) return monster[m]._menemy + MAX_PLRS; else // enemy is player, guaranteed < MAX_PLRS return monster[m]._menemy; } //****************************************************************** //****************************************************************** void decode_enemy(int m, int enemy) { if (enemy < MAX_PLRS) { monster[m]._mFlags &= ~MFLAG_MID; monster[m]._menemy = enemy; monster[m]._menemyx = plr[enemy]._pfutx; monster[m]._menemyy = plr[enemy]._pfuty; } else { monster[m]._mFlags |= MFLAG_MID; enemy -= MAX_PLRS; monster[m]._menemy = enemy; monster[m]._menemyx = monster[enemy]._mfutx; monster[m]._menemyy = monster[enemy]._mfuty; } }