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