/**
 * @file monster.cpp
 *
 * Implementation of monster functionality, AI, actions, spawning, loading, etc.
 */
#include <algorithm>

#include "all.h"
#include "options.h"
#include "../3rdParty/Storm/Source/storm.h"

DEVILUTION_BEGIN_NAMESPACE

/** 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;
BOOLEAN sgbSaveSoundOn;
MonsterStruct monster[MAXMONSTERS];
int totalmonsters;
CMonster Monsters[MAX_LVLMTYPES];
int monstimgtot;
int uniquetrans;
int nummtypes;

/** Maps from monster intelligence factor to missile type. */
const BYTE counsmiss[4] = { MIS_FIREBOLT, MIS_CBOLT, MIS_LIGHTCTRL, MIS_FIREBALL };

/* data */

/** Maps from monster walk animation frame num to monster velocity. */
int MWVel[24][3] = {
	{ 256, 512, 1024 },
	{ 128, 256, 512 },
	{ 85, 170, 341 },
	{ 64, 128, 256 },
	{ 51, 102, 204 },
	{ 42, 85, 170 },
	{ 36, 73, 146 },
	{ 32, 64, 128 },
	{ 28, 56, 113 },
	{ 26, 51, 102 },
	{ 23, 46, 93 },
	{ 21, 42, 85 },
	{ 19, 39, 78 },
	{ 18, 36, 73 },
	{ 17, 34, 68 },
	{ 16, 32, 64 },
	{ 15, 30, 60 },
	{ 14, 28, 57 },
	{ 13, 26, 54 },
	{ 12, 25, 51 },
	{ 12, 24, 48 },
	{ 11, 23, 46 },
	{ 11, 22, 44 },
	{ 10, 21, 42 }
};
/** Maps from monster action to monster animation letter. */
char animletter[7] = "nwahds";
/** Maps from direction to a left turn from the direction. */
int left[8] = { 7, 0, 1, 2, 3, 4, 5, 6 };
/** Maps from direction to a right turn from the direction. */
int right[8] = { 1, 2, 3, 4, 5, 6, 7, 0 };
/** Maps from direction to the opposite direction. */
int opposite[8] = { 4, 5, 6, 7, 0, 1, 2, 3 };
/** Maps from direction to delta X-offset. */
int offset_x[8] = { 1, 0, -1, -1, -1, 0, 1, 1 };
/** Maps from direction to delta Y-offset. */
int offset_y[8] = { 1, 1, 1, 0, -1, -1, -1, 0 };

/** Maps from monster AI ID to monster AI function. */
void (*AiProc[])(int i) = {
	&MAI_Zombie,
	&MAI_Fat,
	&MAI_SkelSd,
	&MAI_SkelBow,
	&MAI_Scav,
	&MAI_Rhino,
	&MAI_GoatMc,
	&MAI_GoatBow,
	&MAI_Fallen,
	&MAI_Magma,
	&MAI_SkelKing,
	&MAI_Bat,
	&MAI_Garg,
	&MAI_Cleaver,
	&MAI_Succ,
	&MAI_Sneak,
	&MAI_Storm,
	&MAI_Fireman,
	&MAI_Garbud,
	&MAI_Acid,
	&MAI_AcidUniq,
	&MAI_Golum,
	&MAI_Zhar,
	&MAI_SnotSpil,
	&MAI_Snake,
	&MAI_Counselor,
	&MAI_Mega,
	&MAI_Diablo,
	&MAI_Lazurus,
	&MAI_Lazhelp,
	&MAI_Lachdanan,
	&MAI_Warlord,
	&MAI_Firebat,
	&MAI_Torchant,
	&MAI_HorkDemon,
	&MAI_Lich,
	&MAI_ArchLich,
	&MAI_Psychorb,
	&MAI_Necromorb,
	&MAI_BoneDemon
};

void InitMonsterTRN(int monst, BOOL special)
{
	BYTE *f;
	int i, n, j;

	f = Monsters[monst].trans_file;
	for (i = 0; i < 256; i++) {
		if (*f == 255) {
			*f = 0;
		}
		f++;
	}

	n = special ? 6 : 5;
	for (i = 0; i < n; i++) {
		if (i != 1 || Monsters[monst].mtype < MT_COUNSLR || Monsters[monst].mtype > MT_ADVOCATE) {
			for (j = 0; j < 8; j++) {
				Cl2ApplyTrans(
				    Monsters[monst].Anims[i].Data[j],
				    Monsters[monst].trans_file,
				    Monsters[monst].Anims[i].Frames);
			}
		}
	}
}

void InitLevelMonsters()
{
	int i;

	nummtypes = 0;
	monstimgtot = 0;
	MissileFileFlag = 0;

	for (i = 0; i < MAX_LVLMTYPES; i++) {
		Monsters[i].mPlaceFlags = 0;
	}

	ClrAllMonsters();
	nummonsters = 0;
	totalmonsters = MAXMONSTERS;

	for (i = 0; i < MAXMONSTERS; i++) {
		monstactive[i] = i;
	}

	uniquetrans = 0;
}

int AddMonsterType(_monster_id type, int placeflag)
{
	BOOL done = FALSE;
	int i;

	for (i = 0; i < nummtypes && !done; i++) {
		done = Monsters[i].mtype == type;
	}

	i--;

	if (!done) {
		i = nummtypes;
		nummtypes++;
		Monsters[i].mtype = type;
		monstimgtot += monsterdata[type].mImage;
		InitMonsterGFX(i);
		InitMonsterSND(i);
	}

	Monsters[i].mPlaceFlags |= placeflag;
	return i;
}

void GetLevelMTypes()
{
	int i;

	// this array is merged with skeltypes down below.
	_monster_id typelist[MAXMONSTERS];
	_monster_id skeltypes[NUM_MTYPES];

	int minl; // min level
	int maxl; // max level
	char mamask;
	const int numskeltypes = 19;

	int nt; // number of types

	if (gbIsSpawn)
		mamask = 1; // monster availability mask
	else
		mamask = 3; // monster availability mask

	AddMonsterType(MT_GOLEM, PLACE_SPECIAL);
	if (currlevel == 16) {
		AddMonsterType(MT_ADVOCATE, PLACE_SCATTER);
		AddMonsterType(MT_RBLACK, PLACE_SCATTER);
		AddMonsterType(MT_DIABLO, PLACE_SPECIAL);
		return;
	}

	if (currlevel == 18)
		AddMonsterType(MT_HORKSPWN, PLACE_SCATTER);
	if (currlevel == 19) {
		AddMonsterType(MT_HORKSPWN, PLACE_SCATTER);
		AddMonsterType(MT_HORKDMN, PLACE_UNIQUE);
	}
	if (currlevel == 20)
		AddMonsterType(MT_DEFILER, PLACE_UNIQUE);
	if (currlevel == 24) {
		AddMonsterType(MT_ARCHLICH, PLACE_SCATTER);
		AddMonsterType(MT_NAKRUL, PLACE_SPECIAL);
	}

	if (!setlevel) {
		if (QuestStatus(Q_BUTCHER))
			AddMonsterType(MT_CLEAVER, PLACE_SPECIAL);
		if (QuestStatus(Q_GARBUD))
			AddMonsterType(UniqMonst[UMT_GARBUD].mtype, PLACE_UNIQUE);
		if (QuestStatus(Q_ZHAR))
			AddMonsterType(UniqMonst[UMT_ZHAR].mtype, PLACE_UNIQUE);
		if (QuestStatus(Q_LTBANNER))
			AddMonsterType(UniqMonst[UMT_SNOTSPIL].mtype, PLACE_UNIQUE);
		if (QuestStatus(Q_VEIL))
			AddMonsterType(UniqMonst[UMT_LACHDAN].mtype, PLACE_UNIQUE);
		if (QuestStatus(Q_WARLORD))
			AddMonsterType(UniqMonst[UMT_WARLORD].mtype, PLACE_UNIQUE);

		if (gbIsMultiplayer && currlevel == quests[Q_SKELKING]._qlevel) {

			AddMonsterType(MT_SKING, PLACE_UNIQUE);

			nt = 0;
			for (i = MT_WSKELAX; i <= MT_WSKELAX + numskeltypes; i++) {
				if (IsSkel(i)) {
					minl = 15 * monsterdata[i].mMinDLvl / 30 + 1;
					maxl = 15 * monsterdata[i].mMaxDLvl / 30 + 1;

					if (currlevel >= minl && currlevel <= maxl) {
						if (MonstAvailTbl[i] & mamask) {
							skeltypes[nt++] = (_monster_id)i;
						}
					}
				}
			}
			AddMonsterType(skeltypes[random_(88, nt)], PLACE_SCATTER);
		}

		nt = 0;
		for (i = MT_NZOMBIE; i < NUM_MTYPES; i++) {
			minl = 15 * monsterdata[i].mMinDLvl / 30 + 1;
			maxl = 15 * monsterdata[i].mMaxDLvl / 30 + 1;

			if (currlevel >= minl && currlevel <= maxl) {
				if (MonstAvailTbl[i] & mamask) {
					typelist[nt++] = (_monster_id)i;
				}
			}
		}

#ifdef _DEBUG
		if (monstdebug) {
			for (i = 0; i < debugmonsttypes; i++)
				AddMonsterType(DebugMonsters[i], PLACE_SCATTER);
		} else
#endif
		{

			while (nt > 0 && nummtypes < MAX_LVLMTYPES && monstimgtot < 4000) {
				for (i = 0; i < nt;) {
					if (monsterdata[typelist[i]].mImage > 4000 - monstimgtot) {
						typelist[i] = typelist[--nt];
						continue;
					}

					i++;
				}

				if (nt != 0) {
					i = random_(88, nt);
					AddMonsterType(typelist[i], PLACE_SCATTER);
					typelist[i] = typelist[--nt];
				}
			}
		}

	} else {
		if (setlvlnum == SL_SKELKING) {
			AddMonsterType(MT_SKING, PLACE_UNIQUE);
		}
	}
}

void InitMonsterGFX(int monst)
{
	int mtype, anim, i;
	char strBuff[256];
	BYTE *celBuf;

	mtype = Monsters[monst].mtype;

	for (anim = 0; anim < 6; anim++) {
		int frames = monsterdata[mtype].Frames[anim];
		if (gbIsHellfire && mtype == MT_DIABLO && anim == 3)
			frames = 2;

		if ((animletter[anim] != 's' || monsterdata[mtype].has_special) && frames > 0) {
			sprintf(strBuff, monsterdata[mtype].GraphicType, animletter[anim]);

			celBuf = LoadFileInMem(strBuff, NULL);
			Monsters[monst].Anims[anim].CMem = celBuf;

			if (Monsters[monst].mtype != MT_GOLEM || (animletter[anim] != 's' && animletter[anim] != 'd')) {

				for (i = 0; i < 8; i++) {
					Monsters[monst].Anims[anim].Data[i] = CelGetFrameStart(celBuf, i);
				}
			} else {
				for (i = 0; i < 8; i++) {
					Monsters[monst].Anims[anim].Data[i] = celBuf;
				}
			}
		}

		// TODO: either the AnimStruct members have wrong naming or the MonsterData ones it seems
		Monsters[monst].Anims[anim].Frames = frames;
		Monsters[monst].Anims[anim].Rate = monsterdata[mtype].Rate[anim];
	}

	Monsters[monst].width = monsterdata[mtype].width;
	Monsters[monst].width2 = (monsterdata[mtype].width - 64) >> 1;
	Monsters[monst].mMinHP = monsterdata[mtype].mMinHP;
	Monsters[monst].mMaxHP = monsterdata[mtype].mMaxHP;
	if (!gbIsHellfire && mtype == MT_DIABLO) {
		Monsters[monst].mMinHP -= 2000;
		Monsters[monst].mMaxHP -= 2000;
	}
	Monsters[monst].has_special = monsterdata[mtype].has_special;
	Monsters[monst].mAFNum = monsterdata[mtype].mAFNum;
	Monsters[monst].MData = &monsterdata[mtype];

	if (monsterdata[mtype].has_trans) {
		Monsters[monst].trans_file = LoadFileInMem(monsterdata[mtype].TransFile, NULL);
		InitMonsterTRN(monst, monsterdata[mtype].has_special);
		MemFreeDbg(Monsters[monst].trans_file);
	}

	if (mtype >= MT_NMAGMA && mtype <= MT_WMAGMA && !(MissileFileFlag & 1)) {
		MissileFileFlag |= 1;
		LoadMissileGFX(MFILE_MAGBALL);
	}
	if (mtype >= MT_STORM && mtype <= MT_MAEL && !(MissileFileFlag & 2)) {
		MissileFileFlag |= 2;
		LoadMissileGFX(MFILE_THINLGHT);
	}
	if (mtype == MT_SUCCUBUS && !(MissileFileFlag & 4)) {
		MissileFileFlag |= 4;
		LoadMissileGFX(MFILE_FLARE);
		LoadMissileGFX(MFILE_FLAREEXP);
	}
	if (mtype >= MT_INCIN && mtype <= MT_HELLBURN && !(MissileFileFlag & 8)) {
		MissileFileFlag |= 8;
		LoadMissileGFX(MFILE_KRULL);
	}
	if (mtype == MT_SNOWWICH && !(MissileFileFlag & 0x20)) {
		MissileFileFlag |= 0x20;
		LoadMissileGFX(MFILE_SCUBMISB);
		LoadMissileGFX(MFILE_SCBSEXPB);
	}
	if (mtype == MT_HLSPWN && !(MissileFileFlag & 0x40)) {
		MissileFileFlag |= 0x40;
		LoadMissileGFX(MFILE_SCUBMISD);
		LoadMissileGFX(MFILE_SCBSEXPD);
	}
	if (mtype == MT_SOLBRNR && !(MissileFileFlag & 0x80)) {
		MissileFileFlag |= 0x80;
		LoadMissileGFX(MFILE_SCUBMISC);
		LoadMissileGFX(MFILE_SCBSEXPC);
	}
	if (mtype >= MT_INCIN && mtype <= MT_HELLBURN && !(MissileFileFlag & 8)) {
		MissileFileFlag |= 8;
		LoadMissileGFX(MFILE_KRULL);
	}
	if ((mtype >= MT_NACID && mtype <= MT_XACID || mtype == MT_SPIDLORD) && !(MissileFileFlag & 0x10)) {
		MissileFileFlag |= 0x10;
		LoadMissileGFX(MFILE_ACIDBF);
		LoadMissileGFX(MFILE_ACIDSPLA);
		LoadMissileGFX(MFILE_ACIDPUD);
	}
	if (mtype == MT_LICH && !(MissileFileFlag & 0x100)) {
		MissileFileFlag |= 0x100u;
		LoadMissileGFX(MFILE_LICH);
		LoadMissileGFX(MFILE_EXORA1);
	}
	if (mtype == MT_ARCHLICH && !(MissileFileFlag & 0x200)) {
		MissileFileFlag |= 0x200u;
		LoadMissileGFX(MFILE_ARCHLICH);
		LoadMissileGFX(MFILE_EXYEL2);
	}
	if ((mtype == MT_PSYCHORB || mtype == MT_BONEDEMN) && !(MissileFileFlag & 0x400)) {
		MissileFileFlag |= 0x400u;
		LoadMissileGFX(MFILE_BONEDEMON);
	}
	if (mtype == MT_NECRMORB && !(MissileFileFlag & 0x800)) {
		MissileFileFlag |= 0x800u;
		LoadMissileGFX(MFILE_NECROMORB);
		LoadMissileGFX(MFILE_EXRED3);
	}
	if (mtype == MT_PSYCHORB && !(MissileFileFlag & 0x1000)) {
		MissileFileFlag |= 0x1000u;
		LoadMissileGFX(MFILE_EXBL2);
	}
	if (mtype == MT_BONEDEMN && !(MissileFileFlag & 0x2000)) {
		MissileFileFlag |= 0x2000u;
		LoadMissileGFX(MFILE_EXBL3);
	}
	if (mtype == MT_DIABLO) {
		LoadMissileGFX(MFILE_FIREPLAR);
	}
}

void ClearMVars(int i)
{
	monster[i]._mVar1 = 0;
	monster[i]._mVar2 = 0;
	monster[i]._mVar3 = 0;
	monster[i]._mVar4 = 0;
	monster[i]._mVar5 = 0;
	monster[i]._mVar6 = 0;
	monster[i]._mVar7 = 0;
	monster[i]._mVar8 = 0;
}

void InitMonster(int i, int rd, int mtype, int x, int y)
{
	CMonster *monst = &Monsters[mtype];

	monster[i]._mdir = rd;
	monster[i]._mx = x;
	monster[i]._my = y;
	monster[i]._mfutx = x;
	monster[i]._mfuty = y;
	monster[i]._moldx = x;
	monster[i]._moldy = y;
	monster[i]._mMTidx = mtype;
	monster[i]._mmode = MM_STAND;
	monster[i].mName = monst->MData->mName;
	monster[i].MType = monst;
	monster[i].MData = monst->MData;
	monster[i]._mAnimData = monst->Anims[MA_STAND].Data[rd];
	monster[i]._mAnimDelay = monst->Anims[MA_STAND].Rate;
	monster[i]._mAnimCnt = random_(88, monster[i]._mAnimDelay - 1);
	monster[i]._mAnimLen = monst->Anims[MA_STAND].Frames;
	monster[i]._mAnimFrame = random_(88, monster[i]._mAnimLen - 1) + 1;

	monster[i].mLevel = monst->MData->mLevel;
	if (monst->mtype == MT_DIABLO) {
		monster[i]._mmaxhp = (random_(88, 1) + (gbIsHellfire ? 3333 : 1666)) << 6;
		if (!gbIsHellfire)
			monster[i].mLevel -= 15;
	} else {
		monster[i]._mmaxhp = (monst->mMinHP + random_(88, monst->mMaxHP - monst->mMinHP + 1)) << 6;
	}

	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 (gnDifficulty == 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 (gnDifficulty == 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 = 0;
		Monst->_mmode = MM_STAND;
		Monst->_mVar1 = 0;
		Monst->_mVar2 = 0;
		Monst->_mx = 0;
		Monst->_my = 0;
		Monst->_mfutx = 0;
		Monst->_mfuty = 0;
		Monst->_moldx = 0;
		Monst->_moldy = 0;
		Monst->_mdir = random_(89, 8);
		Monst->_mxvel = 0;
		Monst->_myvel = 0;
		Monst->_mAnimData = NULL;
		Monst->_mAnimDelay = 0;
		Monst->_mAnimCnt = 0;
		Monst->_mAnimLen = 0;
		Monst->_mAnimFrame = 0;
		Monst->_mFlags = 0;
		Monst->_mDelFlag = FALSE;
		Monst->_menemy = random_(89, gbActivePlayers);
		Monst->_menemyx = plr[Monst->_menemy]._pfutx;
		Monst->_menemyy = plr[Monst->_menemy]._pfuty;
	}
}

BOOL MonstPlace(int xp, int yp)
{
	char f;

	if (xp < 0 || xp >= MAXDUNX
	    || yp < 0 || yp >= MAXDUNY
	    || dMonster[xp][yp] != 0
	    || dPlayer[xp][yp] != 0) {
		return FALSE;
	}

	f = dFlags[xp][yp];

	if (f & BFLAG_VISIBLE) {
		return FALSE;
	}

	if (f & BFLAG_POPULATED) {
		return FALSE;
	}

	return !SolidLoc(xp, yp);
}

void monster_some_crypt()
{
	MonsterStruct *mon;
	int hp;

	if (currlevel == 24 && UberDiabloMonsterIndex >= 0 && UberDiabloMonsterIndex < nummonsters) {
		mon = &monster[UberDiabloMonsterIndex];
		PlayEffect(UberDiabloMonsterIndex, 2);
		quests[Q_NAKRUL]._qlog = 0;
		mon->mArmorClass -= 50;
		hp = mon->_mmaxhp / 2;
		mon->mMagicRes = 0;
		mon->_mhitpoints = hp;
		mon->_mmaxhp = hp;
	}
}

void PlaceMonster(int i, int mtype, int x, int y)
{
	int rd;

	if (Monsters[mtype].mtype == MT_NAKRUL) {
		for (int j = 0; j < nummonsters; j++) {
			if (monster[j]._mMTidx == mtype) {
				return;
			}
			if (monster[j].MType->mtype == MT_NAKRUL) {
				return;
			}
		}
	}
	dMonster[x][y] = i + 1;

	rd = random_(90, 8);
	InitMonster(i, rd, mtype, x, y);
}

void PlaceUniqueMonst(int uniqindex, int miniontype, int bosspacksize)
{
	int xp, yp, x, y, i;
	int uniqtype;
	int count2;
	char filestr[64];
	BOOL zharflag, done;
	const UniqMonstStruct *Uniq;
	MonsterStruct *Monst;
	int count;

	Monst = &monster[nummonsters];
	count = 0;
	Uniq = &UniqMonst[uniqindex];

	if ((uniquetrans + 19) << 8 >= LIGHTSIZE) {
		return;
	}

	for (uniqtype = 0; uniqtype < nummtypes; uniqtype++) {
		if (Monsters[uniqtype].mtype == UniqMonst[uniqindex].mtype) {
			break;
		}
	}

	while (1) {
		xp = random_(91, 80) + 16;
		yp = random_(91, 80) + 16;
		count2 = 0;
		for (x = xp - 3; x < xp + 3; x++) {
			for (y = yp - 3; y < yp + 3; y++) {
				if (y >= 0 && y < MAXDUNY && x >= 0 && x < MAXDUNX && MonstPlace(x, y)) {
					count2++;
				}
			}
		}

		if (count2 < 9) {
			count++;
			if (count < 1000) {
				continue;
			}
		}

		if (MonstPlace(xp, yp)) {
			break;
		}
	}

	if (uniqindex == UMT_SNOTSPIL) {
		xp = 2 * setpc_x + 24;
		yp = 2 * setpc_y + 28;
	}
	if (uniqindex == UMT_WARLORD) {
		xp = 2 * setpc_x + 22;
		yp = 2 * setpc_y + 23;
	}
	if (uniqindex == UMT_ZHAR) {
		zharflag = TRUE;
		for (i = 0; i < themeCount; i++) {
			if (i == zharlib && zharflag == TRUE) {
				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 (gnDifficulty == 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 (gnDifficulty == 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 (gnDifficulty == DIFF_NIGHTMARE) {
			Monst->mHit += NIGHTMARE_TO_HIT_BONUS;
			Monst->mHit2 += NIGHTMARE_TO_HIT_BONUS;
		} else if (gnDifficulty == DIFF_HELL) {
			Monst->mHit += HELL_TO_HIT_BONUS;
			Monst->mHit2 += HELL_TO_HIT_BONUS;
		}
	}
	if (Uniq->mUnqAttr & 8) {
		Monst->mArmorClass = Uniq->mUnqVar1;

		if (gnDifficulty == DIFF_NIGHTMARE) {
			Monst->mArmorClass += NIGHTMARE_AC_BONUS;
		} else if (gnDifficulty == DIFF_HELL) {
			Monst->mArmorClass += HELL_AC_BONUS;
		}
	}

	nummonsters++;

	if (Uniq->mUnqAttr & 1) {
		PlaceGroup(miniontype, bosspacksize, Uniq->mUnqAttr, nummonsters - 1);
	}

	if (Monst->_mAi != AI_GARG) {
		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir];
		Monst->_mAnimFrame = random_(88, Monst->_mAnimLen - 1) + 1;
		Monst->_mFlags &= ~MFLAG_ALLOW_SPECIAL;
		Monst->_mmode = MM_STAND;
	}
}

static void PlaceUniques()
{
	int u, mt;
	BOOL done;

	for (u = 0; UniqMonst[u].mtype != -1; u++) {
		if (UniqMonst[u].mlevel != currlevel)
			continue;
		done = FALSE;
		for (mt = 0; mt < nummtypes; mt++) {
			if (done)
				break;
			done = (Monsters[mt].mtype == UniqMonst[u].mtype);
		}
		mt--;
		if (u == UMT_GARBUD && quests[Q_GARBUD]._qactive == QUEST_NOTAVAIL)
			done = FALSE;
		if (u == UMT_ZHAR && quests[Q_ZHAR]._qactive == QUEST_NOTAVAIL)
			done = FALSE;
		if (u == UMT_SNOTSPIL && quests[Q_LTBANNER]._qactive == QUEST_NOTAVAIL)
			done = FALSE;
		if (u == UMT_LACHDAN && quests[Q_VEIL]._qactive == QUEST_NOTAVAIL)
			done = FALSE;
		if (u == UMT_WARLORD && quests[Q_WARLORD]._qactive == QUEST_NOTAVAIL)
			done = FALSE;
		if (done)
			PlaceUniqueMonst(u, mt, 8);
	}
}

void PlaceQuestMonsters()
{
	int skeltype;
	BYTE *setp;

	if (!setlevel) {
		if (QuestStatus(Q_BUTCHER)) {
			PlaceUniqueMonst(UMT_BUTCHER, 0, 0);
		}

		if (currlevel == quests[Q_SKELKING]._qlevel && gbIsMultiplayer) {
			skeltype = 0;

			for (skeltype = 0; skeltype < nummtypes; skeltype++) {
				if (IsSkel(Monsters[skeltype].mtype)) {
					break;
				}
			}

			PlaceUniqueMonst(UMT_SKELKING, skeltype, 30);
		}

		if (QuestStatus(Q_LTBANNER)) {
			setp = LoadFileInMem("Levels\\L1Data\\Banner1.DUN", NULL);
			SetMapMonsters(setp, 2 * setpc_x, 2 * setpc_y);
			mem_free_dbg(setp);
		}
		if (QuestStatus(Q_BLOOD)) {
			setp = LoadFileInMem("Levels\\L2Data\\Blood2.DUN", NULL);
			SetMapMonsters(setp, 2 * setpc_x, 2 * setpc_y);
			mem_free_dbg(setp);
		}
		if (QuestStatus(Q_BLIND)) {
			setp = LoadFileInMem("Levels\\L2Data\\Blind2.DUN", NULL);
			SetMapMonsters(setp, 2 * setpc_x, 2 * setpc_y);
			mem_free_dbg(setp);
		}
		if (QuestStatus(Q_ANVIL)) {
			setp = LoadFileInMem("Levels\\L3Data\\Anvil.DUN", NULL);
			SetMapMonsters(setp, 2 * setpc_x + 2, 2 * setpc_y + 2);
			mem_free_dbg(setp);
		}
		if (QuestStatus(Q_WARLORD)) {
			setp = LoadFileInMem("Levels\\L4Data\\Warlord.DUN", NULL);
			SetMapMonsters(setp, 2 * setpc_x, 2 * setpc_y);
			mem_free_dbg(setp);
			AddMonsterType(UniqMonst[UMT_WARLORD].mtype, PLACE_SCATTER);
		}
		if (QuestStatus(Q_VEIL)) {
			AddMonsterType(UniqMonst[UMT_LACHDAN].mtype, PLACE_SCATTER);
		}
		if (QuestStatus(Q_ZHAR) && zharlib == -1) {
			quests[Q_ZHAR]._qactive = QUEST_NOTAVAIL;
		}

		if (currlevel == quests[Q_BETRAYER]._qlevel && gbIsMultiplayer) {
			AddMonsterType(UniqMonst[UMT_LAZURUS].mtype, PLACE_UNIQUE);
			AddMonsterType(UniqMonst[UMT_RED_VEX].mtype, PLACE_UNIQUE);
			PlaceUniqueMonst(UMT_LAZURUS, 0, 0);
			PlaceUniqueMonst(UMT_RED_VEX, 0, 0);
			PlaceUniqueMonst(UMT_BLACKJADE, 0, 0);
			setp = LoadFileInMem("Levels\\L4Data\\Vile1.DUN", NULL);
			SetMapMonsters(setp, 2 * setpc_x, 2 * setpc_y);
			mem_free_dbg(setp);
		}

		if (currlevel == 24) {
			UberDiabloMonsterIndex = -1;
			int i1;
			for (i1 = 0; i1 < nummtypes; i1++) {
				if (Monsters[i1].mtype == UniqMonst[UMT_NAKRUL].mtype)
					break;
			}

			if (i1 < nummtypes) {
				for (int i2 = 0; i2 < nummonsters; i2++) {
					if (monster[i2]._uniqtype != 0 || monster[i2]._mMTidx == i1) {
						UberDiabloMonsterIndex = i2;
						break;
					}
				}
			}
			if (UberDiabloMonsterIndex == -1)
				PlaceUniqueMonst(UMT_NAKRUL, 0, 0);
		}
	} else if (setlvlnum == SL_SKELKING) {
		PlaceUniqueMonst(UMT_SKELKING, 0, 0);
	}
}

void PlaceGroup(int mtype, int num, int leaderf, int leader)
{
	int placed, try1, try2, j;
	int xp, yp, x1, y1;

	placed = 0;

	for (try1 = 0; try1 < 10; try1++) {
		while (placed) {
			nummonsters--;
			placed--;
			dMonster[monster[nummonsters]._mx][monster[nummonsters]._my] = 0;
		}

		if (leaderf & 1) {
			int offset = random_(92, 8);
			x1 = xp = monster[leader]._mx + offset_x[offset];
			y1 = yp = monster[leader]._my + offset_y[offset];
		} else {
			do {
				x1 = xp = random_(93, 80) + 16;
				y1 = yp = random_(93, 80) + 16;
			} while (!MonstPlace(xp, yp));
		}

		if (num + nummonsters > totalmonsters) {
			num = totalmonsters - nummonsters;
		}

		j = 0;
		for (try2 = 0; j < num && try2 < 100; xp += offset_x[random_(94, 8)], yp += offset_x[random_(94, 8)]) { /// BUGFIX: `yp += offset_y`
			if (!MonstPlace(xp, yp)
			    || (dTransVal[xp][yp] != dTransVal[x1][y1])
			    || (leaderf & 2) && ((abs(xp - x1) >= 4) || (abs(yp - y1) >= 4))) {
				try2++;
				continue;
			}

			PlaceMonster(nummonsters, mtype, xp, yp);
			if (leaderf & 1) {
				monster[nummonsters]._mmaxhp *= 2;
				monster[nummonsters]._mhitpoints = monster[nummonsters]._mmaxhp;
				monster[nummonsters]._mint = monster[leader]._mint;

				if (leaderf & 2) {
					monster[nummonsters].leader = leader;
					monster[nummonsters].leaderflag = 1;
					monster[nummonsters]._mAi = monster[leader]._mAi;
				}

				if (monster[nummonsters]._mAi != AI_GARG) {
					monster[nummonsters]._mAnimData = monster[nummonsters].MType->Anims[MA_STAND].Data[monster[nummonsters]._mdir];
					monster[nummonsters]._mAnimFrame = random_(88, monster[nummonsters]._mAnimLen - 1) + 1;
					monster[nummonsters]._mFlags &= ~MFLAG_ALLOW_SPECIAL;
					monster[nummonsters]._mmode = MM_STAND;
				}
			}
			nummonsters++;
			placed++;
			j++;
		}

		if (placed >= num) {
			break;
		}
	}

	if (leaderf & 2) {
		monster[leader].packsize = placed;
	}
}

void LoadDiabMonsts()
{
	BYTE *lpSetPiece;

	lpSetPiece = LoadFileInMem("Levels\\L4Data\\diab1.DUN", NULL);
	SetMapMonsters(lpSetPiece, 2 * diabquad1x, 2 * diabquad1y);
	mem_free_dbg(lpSetPiece);
	lpSetPiece = LoadFileInMem("Levels\\L4Data\\diab2a.DUN", NULL);
	SetMapMonsters(lpSetPiece, 2 * diabquad2x, 2 * diabquad2y);
	mem_free_dbg(lpSetPiece);
	lpSetPiece = LoadFileInMem("Levels\\L4Data\\diab3a.DUN", NULL);
	SetMapMonsters(lpSetPiece, 2 * diabquad3x, 2 * diabquad3y);
	mem_free_dbg(lpSetPiece);
	lpSetPiece = LoadFileInMem("Levels\\L4Data\\diab4a.DUN", NULL);
	SetMapMonsters(lpSetPiece, 2 * diabquad4x, 2 * diabquad4y);
	mem_free_dbg(lpSetPiece);
}

void InitMonsters()
{
	int na, nt;
	int i, s, t;
	int numplacemonsters;
	int mtype;
	int numscattypes;
	int scattertypes[NUM_MTYPES];

	numscattypes = 0;
#ifdef _DEBUG
	if (gbIsMultiplayer)
		CheckDungeonClear();
#endif
	if (!setlevel) {
		AddMonster(1, 0, 0, 0, FALSE);
		AddMonster(1, 0, 0, 0, FALSE);
		AddMonster(1, 0, 0, 0, FALSE);
		AddMonster(1, 0, 0, 0, FALSE);
	}

	if (!gbIsSpawn && !setlevel && currlevel == 16)
		LoadDiabMonsts();

	nt = numtrigs;
	if (currlevel == 15)
		nt = 1;
	for (i = 0; i < nt; i++) {
		for (s = -2; s < 2; s++) {
			for (t = -2; t < 2; t++)
				DoVision(s + trigs[i]._tx, t + trigs[i]._ty, 15, FALSE, FALSE);
		}
	}
	if (!gbIsSpawn)
		PlaceQuestMonsters();
	if (!setlevel) {
		if (!gbIsSpawn)
			PlaceUniques();
		na = 0;
		for (s = 16; s < 96; s++)
			for (t = 16; t < 96; t++)
				if (!SolidLoc(s, t))
					na++;
		numplacemonsters = na / 30;
		if (gbIsMultiplayer)
			numplacemonsters += numplacemonsters >> 1;
		if (nummonsters + numplacemonsters > MAXMONSTERS - 10)
			numplacemonsters = MAXMONSTERS - 10 - nummonsters;
		totalmonsters = nummonsters + numplacemonsters;
		for (i = 0; i < nummtypes; i++) {
			if (Monsters[i].mPlaceFlags & PLACE_SCATTER) {
				scattertypes[numscattypes] = i;
				numscattypes++;
			}
		}
		while (nummonsters < totalmonsters) {
			mtype = scattertypes[random_(95, numscattypes)];
			if (currlevel == 1 || random_(95, 2) == 0)
				na = 1;
			else if (currlevel == 2 || currlevel >= 21 && currlevel <= 24)
				na = random_(95, 2) + 2;
			else
				na = random_(95, 3) + 3;
			PlaceGroup(mtype, na, 0, 0);
		}
	}
	for (i = 0; i < nt; i++) {
		for (s = -2; s < 2; s++) {
			for (t = -2; t < 2; t++)
				DoUnVision(s + trigs[i]._tx, t + trigs[i]._ty, 15);
		}
	}
}

void SetMapMonsters(BYTE *pMap, int startx, int starty)
{
	WORD rw, rh;
	WORD *lm;
	int i, j;
	int mtype;

	AddMonsterType(MT_GOLEM, PLACE_SPECIAL);
	AddMonster(1, 0, 0, 0, FALSE);
	AddMonster(1, 0, 0, 0, FALSE);
	AddMonster(1, 0, 0, 0, FALSE);
	AddMonster(1, 0, 0, 0, FALSE);
	if (setlevel && setlvlnum == SL_VILEBETRAYER) {
		AddMonsterType(UniqMonst[UMT_LAZURUS].mtype, PLACE_UNIQUE);
		AddMonsterType(UniqMonst[UMT_RED_VEX].mtype, PLACE_UNIQUE);
		AddMonsterType(UniqMonst[UMT_BLACKJADE].mtype, PLACE_UNIQUE);
		PlaceUniqueMonst(UMT_LAZURUS, 0, 0);
		PlaceUniqueMonst(UMT_RED_VEX, 0, 0);
		PlaceUniqueMonst(UMT_BLACKJADE, 0, 0);
	}
	lm = (WORD *)pMap;
	rw = SDL_SwapLE16(*lm++);
	rh = SDL_SwapLE16(*lm++);
	lm += rw * rh;
	rw = rw << 1;
	rh = rh << 1;
	lm += rw * rh;

	for (j = 0; j < rh; j++) {
		for (i = 0; i < rw; i++) {
			if (*lm != 0) {
				mtype = AddMonsterType(MonstConvTbl[SDL_SwapLE16(*lm) - 1], PLACE_SPECIAL);
				PlaceMonster(nummonsters++, mtype, i + startx + 16, j + starty + 16);
			}
			lm++;
		}
	}
}

void DeleteMonster(int i)
{
	int temp;

	nummonsters--;
	temp = monstactive[nummonsters];
	monstactive[nummonsters] = monstactive[i];
	monstactive[i] = temp;
}

int AddMonster(int x, int y, int dir, int mtype, BOOL InMap)
{
	if (nummonsters < MAXMONSTERS) {
		int i = monstactive[nummonsters++];
		if (InMap)
			dMonster[x][y] = i + 1;
		InitMonster(i, dir, mtype, x, y);
		return i;
	}

	return -1;
}

void monster_43C785(int i)
{
	int x, y, d, j, oi, dir, mx, my;

	if (monster[i].MType) {
		mx = monster[i]._mx;
		my = monster[i]._my;
		dir = monster[i]._mdir;
		for (d = 0; d < 8; d++) {
			x = mx + offset_x[d];
			y = my + offset_y[d];
			if (!SolidLoc(x, y)) {
				if (dPlayer[x][y] == 0 && dMonster[x][y] == 0) {
					if (dObject[x][y] == 0)
						break;
					oi = dObject[x][y] > 0 ? dObject[x][y] - 1 : -(dObject[x][y] + 1);
					if (!object[oi]._oSolidFlag)
						break;
				}
			}
		}
		if (d < 8) {
			for (j = 0; j < MAX_LVLMTYPES; j++) {
				if (Monsters[j].mtype == monster[i].MType->mtype)
					break;
			}
			if (j < MAX_LVLMTYPES)
				AddMonster(x, y, dir, j, TRUE);
		}
	}
}

void NewMonsterAnim(int i, AnimStruct *anim, int md)
{
	MonsterStruct *Monst = &monster[i];
	Monst->_mAnimData = anim->Data[md];
	Monst->_mAnimLen = anim->Frames;
	Monst->_mAnimCnt = 0;
	Monst->_mAnimFrame = 1;
	Monst->_mAnimDelay = anim->Rate;
	Monst->_mFlags &= ~(MFLAG_LOCK_ANIMATION | MFLAG_ALLOW_SPECIAL);
	Monst->_mdir = md;
}

BOOL M_Ranged(int i)
{
	char ai = monster[i]._mAi;
	return ai == AI_SKELBOW || ai == AI_GOATBOW || ai == AI_SUCC || ai == AI_LAZHELP;
}

BOOL M_Talker(int i)
{
	char ai = monster[i]._mAi;
	return ai == AI_LAZURUS
	    || ai == AI_WARLORD
	    || ai == AI_GARBUD
	    || ai == AI_ZHAR
	    || ai == AI_SNOTSPIL
	    || ai == AI_LACHDAN
	    || ai == AI_LAZHELP;
}

void M_Enemy(int i)
{
	int j;
	int mi, pnum;
	int dist, best_dist;
	int _menemy;
	bool sameroom, bestsameroom;
	MonsterStruct *Monst;
	BYTE enemyx, enemyy;

	_menemy = -1;
	best_dist = -1;
	bestsameroom = 0;
	Monst = &monster[i];
	if (Monst->_mFlags & MFLAG_BERSERK || !(Monst->_mFlags & MFLAG_GOLEM)) {
		for (pnum = 0; pnum < MAX_PLRS; pnum++) {
			if (!plr[pnum].plractive || currlevel != plr[pnum].plrlevel || plr[pnum]._pLvlChanging
			    || ((plr[pnum]._pHitPoints >> 6) == 0) && gbIsMultiplayer)
				continue;
			sameroom = (dTransVal[Monst->_mx][Monst->_my] == dTransVal[plr[pnum]._px][plr[pnum]._py]);
			dist = std::max(abs(Monst->_mx - plr[pnum]._px), abs(Monst->_my - plr[pnum]._py));
			if ((sameroom && !bestsameroom)
			    || ((sameroom || !bestsameroom) && dist < best_dist)
			    || (_menemy == -1)) {
				Monst->_mFlags &= ~MFLAG_TARGETS_MONSTER;
				_menemy = pnum;
				enemyx = plr[pnum]._pfutx;
				enemyy = plr[pnum]._pfuty;
				best_dist = dist;
				bestsameroom = sameroom;
			}
		}
	}
	for (j = 0; j < nummonsters; j++) {
		mi = monstactive[j];
		if (mi == i)
			continue;
		if (!((monster[mi]._mhitpoints >> 6) > 0))
			continue;
		if (monster[mi]._mx == 1 && monster[mi]._my == 0)
			continue;
		if (M_Talker(mi) && monster[mi].mtalkmsg)
			continue;
		dist = std::max(abs(monster[mi]._mx - Monst->_mx), abs(monster[mi]._my - Monst->_my));
		if ((!(Monst->_mFlags & MFLAG_GOLEM)
		        && !(Monst->_mFlags & MFLAG_BERSERK)
		        && dist >= 2
		        && !M_Ranged(i))
		    || (!(Monst->_mFlags & MFLAG_GOLEM)
		        && !(Monst->_mFlags & MFLAG_BERSERK)
		        && !(monster[mi]._mFlags & MFLAG_GOLEM))) {
			continue;
		}
		sameroom = dTransVal[Monst->_mx][Monst->_my] == dTransVal[monster[mi]._mx][monster[mi]._my];
		if ((sameroom && !bestsameroom)
		    || ((sameroom || !bestsameroom) && dist < best_dist)
		    || (_menemy == -1)) {
			Monst->_mFlags |= MFLAG_TARGETS_MONSTER;
			_menemy = mi;
			enemyx = monster[mi]._mfutx;
			enemyy = monster[mi]._mfuty;
			best_dist = dist;
			bestsameroom = sameroom;
		}
	}
	if (_menemy != -1) {
		Monst->_mFlags &= ~MFLAG_NO_ENEMY;
		Monst->_menemy = _menemy;
		Monst->_menemyx = enemyx;
		Monst->_menemyy = enemyy;
	} else {
		Monst->_mFlags |= MFLAG_NO_ENEMY;
	}
}

int M_GetDir(int i)
{
	return GetDirection(monster[i]._mx, monster[i]._my, monster[i]._menemyx, monster[i]._menemyy);
}

void M_StartStand(int i, int md)
{
	ClearMVars(i);
	if (monster[i].MType->mtype == MT_GOLEM)
		NewMonsterAnim(i, &monster[i].MType->Anims[MA_WALK], md);
	else
		NewMonsterAnim(i, &monster[i].MType->Anims[MA_STAND], md);
	monster[i]._mVar1 = monster[i]._mmode;
	monster[i]._mVar2 = 0;
	monster[i]._mmode = MM_STAND;
	monster[i]._mxoff = 0;
	monster[i]._myoff = 0;
	monster[i]._mfutx = monster[i]._mx;
	monster[i]._mfuty = monster[i]._my;
	monster[i]._moldx = monster[i]._mx;
	monster[i]._moldy = monster[i]._my;
	monster[i]._mdir = md;
	M_Enemy(i);
}

void M_StartDelay(int i, int len)
{
	if (len <= 0) {
		return;
	}

	if (monster[i]._mAi != AI_LAZURUS) {
		monster[i]._mVar2 = len;
		monster[i]._mmode = MM_DELAY;
	}
}

void M_StartSpStand(int i, int md)
{
	NewMonsterAnim(i, &monster[i].MType->Anims[MA_SPECIAL], md);
	monster[i]._mmode = MM_SPSTAND;
	monster[i]._mxoff = 0;
	monster[i]._myoff = 0;
	monster[i]._mfutx = monster[i]._mx;
	monster[i]._mfuty = monster[i]._my;
	monster[i]._moldx = monster[i]._mx;
	monster[i]._moldy = monster[i]._my;
	monster[i]._mdir = md;
}

void M_StartWalk(int i, int xvel, int yvel, int xadd, int yadd, int EndDir)
{
	int fx = xadd + monster[i]._mx;
	int fy = yadd + monster[i]._my;

	dMonster[fx][fy] = -(i + 1);
	monster[i]._mmode = MM_WALK;
	monster[i]._moldx = monster[i]._mx;
	monster[i]._moldy = monster[i]._my;
	monster[i]._mfutx = fx;
	monster[i]._mfuty = fy;
	monster[i]._mxvel = xvel;
	monster[i]._myvel = yvel;
	monster[i]._mVar1 = xadd;
	monster[i]._mVar2 = yadd;
	monster[i]._mVar3 = EndDir;
	monster[i]._mdir = EndDir;
	NewMonsterAnim(i, &monster[i].MType->Anims[MA_WALK], EndDir);
	monster[i]._mVar6 = 0;
	monster[i]._mVar7 = 0;
	monster[i]._mVar8 = 0;
}

void M_StartWalk2(int i, int xvel, int yvel, int xoff, int yoff, int xadd, int yadd, int EndDir)
{
	int fx = xadd + monster[i]._mx;
	int fy = yadd + monster[i]._my;

	dMonster[monster[i]._mx][monster[i]._my] = -(i + 1);
	monster[i]._mVar1 = monster[i]._mx;
	monster[i]._mVar2 = monster[i]._my;
	monster[i]._moldx = monster[i]._mx;
	monster[i]._moldy = monster[i]._my;
	monster[i]._mx = fx;
	monster[i]._my = fy;
	monster[i]._mfutx = fx;
	monster[i]._mfuty = fy;
	dMonster[fx][fy] = i + 1;
	if (monster[i].mlid != NO_LIGHT)
		ChangeLightXY(monster[i].mlid, monster[i]._mx, monster[i]._my);
	monster[i]._mxoff = xoff;
	monster[i]._myoff = yoff;
	monster[i]._mmode = MM_WALK2;
	monster[i]._mxvel = xvel;
	monster[i]._myvel = yvel;
	monster[i]._mVar3 = EndDir;
	monster[i]._mdir = EndDir;
	NewMonsterAnim(i, &monster[i].MType->Anims[MA_WALK], EndDir);
	monster[i]._mVar6 = 16 * xoff;
	monster[i]._mVar7 = 16 * yoff;
	monster[i]._mVar8 = 0;
}

void M_StartWalk3(int i, int xvel, int yvel, int xoff, int yoff, int xadd, int yadd, int mapx, int mapy, int EndDir)
{
	int fx = xadd + monster[i]._mx;
	int fy = yadd + monster[i]._my;
	int x = mapx + monster[i]._mx;
	int y = mapy + monster[i]._my;

	if (monster[i].mlid != NO_LIGHT)
		ChangeLightXY(monster[i].mlid, x, y);

	dMonster[monster[i]._mx][monster[i]._my] = -(i + 1);
	dMonster[fx][fy] = -(i + 1);
	monster[i]._mVar4 = x;
	monster[i]._mVar5 = y;
	dFlags[x][y] |= BFLAG_MONSTLR;
	monster[i]._moldx = monster[i]._mx;
	monster[i]._moldy = monster[i]._my;
	monster[i]._mfutx = fx;
	monster[i]._mfuty = fy;
	monster[i]._mxoff = xoff;
	monster[i]._myoff = yoff;
	monster[i]._mmode = MM_WALK3;
	monster[i]._mxvel = xvel;
	monster[i]._myvel = yvel;
	monster[i]._mVar1 = fx;
	monster[i]._mVar2 = fy;
	monster[i]._mVar3 = EndDir;
	monster[i]._mdir = EndDir;
	NewMonsterAnim(i, &monster[i].MType->Anims[MA_WALK], EndDir);
	monster[i]._mVar6 = 16 * xoff;
	monster[i]._mVar7 = 16 * yoff;
	monster[i]._mVar8 = 0;
}

void M_StartAttack(int i)
{
	int md = M_GetDir(i);
	NewMonsterAnim(i, &monster[i].MType->Anims[MA_ATTACK], md);
	monster[i]._mmode = MM_ATTACK;
	monster[i]._mxoff = 0;
	monster[i]._myoff = 0;
	monster[i]._mfutx = monster[i]._mx;
	monster[i]._mfuty = monster[i]._my;
	monster[i]._moldx = monster[i]._mx;
	monster[i]._moldy = monster[i]._my;
	monster[i]._mdir = md;
}

void M_StartRAttack(int i, int missile_type, int dam)
{
	int md = M_GetDir(i);
	NewMonsterAnim(i, &monster[i].MType->Anims[MA_ATTACK], md);
	monster[i]._mmode = MM_RATTACK;
	monster[i]._mVar1 = missile_type;
	monster[i]._mVar2 = dam;
	monster[i]._mxoff = 0;
	monster[i]._myoff = 0;
	monster[i]._mfutx = monster[i]._mx;
	monster[i]._mfuty = monster[i]._my;
	monster[i]._moldx = monster[i]._mx;
	monster[i]._moldy = monster[i]._my;
	monster[i]._mdir = md;
}

void M_StartRSpAttack(int i, int missile_type, int dam)
{
	int md = M_GetDir(i);
	NewMonsterAnim(i, &monster[i].MType->Anims[MA_SPECIAL], md);
	monster[i]._mmode = MM_RSPATTACK;
	monster[i]._mVar1 = missile_type;
	monster[i]._mVar2 = 0;
	monster[i]._mVar3 = dam;
	monster[i]._mxoff = 0;
	monster[i]._myoff = 0;
	monster[i]._mfutx = monster[i]._mx;
	monster[i]._mfuty = monster[i]._my;
	monster[i]._moldx = monster[i]._mx;
	monster[i]._moldy = monster[i]._my;
	monster[i]._mdir = md;
}

void M_StartSpAttack(int i)
{
	int md = M_GetDir(i);
	NewMonsterAnim(i, &monster[i].MType->Anims[MA_SPECIAL], md);
	monster[i]._mmode = MM_SATTACK;
	monster[i]._mxoff = 0;
	monster[i]._myoff = 0;
	monster[i]._mfutx = monster[i]._mx;
	monster[i]._mfuty = monster[i]._my;
	monster[i]._moldx = monster[i]._mx;
	monster[i]._moldy = monster[i]._my;
	monster[i]._mdir = md;
}

void M_StartEat(int i)
{
	NewMonsterAnim(i, &monster[i].MType->Anims[MA_SPECIAL], monster[i]._mdir);
	monster[i]._mmode = MM_SATTACK;
	monster[i]._mxoff = 0;
	monster[i]._myoff = 0;
	monster[i]._mfutx = monster[i]._mx;
	monster[i]._mfuty = monster[i]._my;
	monster[i]._moldx = monster[i]._mx;
	monster[i]._moldy = monster[i]._my;
}

void M_ClearSquares(int i)
{
	int x, y, mx, my, m1, m2;

	mx = monster[i]._moldx;
	my = monster[i]._moldy;
	m1 = -(i + 1);
	m2 = i + 1;

	for (y = my - 1; y <= my + 1; y++) {
		if (y >= 0 && y < MAXDUNY) {
			for (x = mx - 1; x <= mx + 1; x++) {
				if (x >= 0 && x < MAXDUNX && (dMonster[x][y] == m1 || dMonster[x][y] == m2))
					dMonster[x][y] = 0;
			}
		}
	}

	if (mx + 1 < MAXDUNX)
		dFlags[mx + 1][my] &= ~BFLAG_MONSTLR;
	if (my + 1 < MAXDUNY)
		dFlags[mx][my + 1] &= ~BFLAG_MONSTLR;
}

void M_GetKnockback(int i)
{
	int d = (monster[i]._mdir - 4) & 7;
	if (DirOK(i, d)) {
		M_ClearSquares(i);
		monster[i]._moldx += offset_x[d];
		monster[i]._moldy += offset_y[d];
		NewMonsterAnim(i, &monster[i].MType->Anims[MA_GOTHIT], monster[i]._mdir);
		monster[i]._mmode = MM_GOTHIT;
		monster[i]._mxoff = 0;
		monster[i]._myoff = 0;
		monster[i]._mx = monster[i]._moldx;
		monster[i]._my = monster[i]._moldy;
		monster[i]._mfutx = monster[i]._mx;
		monster[i]._mfuty = monster[i]._my;
		monster[i]._moldx = monster[i]._mx; // CODEFIX: useless assignment
		monster[i]._moldy = monster[i]._my; // CODEFIX: useless assignment
		M_ClearSquares(i);
		dMonster[monster[i]._mx][monster[i]._my] = i + 1;
	}
}

void M_StartHit(int i, int pnum, int dam)
{
	if (pnum >= 0)
		monster[i].mWhoHit |= 1 << pnum;
	if (pnum == myplr) {
		delta_monster_hp(i, monster[i]._mhitpoints, currlevel);
		NetSendCmdMonDmg(FALSE, i, dam);
	}
	PlayEffect(i, 1);
	if (monster[i].MType->mtype >= MT_SNEAK && monster[i].MType->mtype <= MT_ILLWEAV || dam >> 6 >= monster[i].mLevel + 3) {
		if (pnum >= 0) {
			monster[i]._menemy = pnum;
			monster[i]._menemyx = plr[pnum]._pfutx;
			monster[i]._menemyy = plr[pnum]._pfuty;
			monster[i]._mFlags &= ~MFLAG_TARGETS_MONSTER;
			monster[i]._mdir = M_GetDir(i);
		}
		if (monster[i].MType->mtype == MT_BLINK) {
			M_Teleport(i);
		} else if ((monster[i].MType->mtype >= MT_NSCAV && monster[i].MType->mtype <= MT_YSCAV)
		    || monster[i].MType->mtype == MT_GRAVEDIG) {
			monster[i]._mgoal = MGOAL_NORMAL;
			monster[i]._mgoalvar1 = 0;
			monster[i]._mgoalvar2 = 0;
		}
		if (monster[i]._mmode != MM_STONE) {
			NewMonsterAnim(i, &monster[i].MType->Anims[MA_GOTHIT], monster[i]._mdir);
			monster[i]._mmode = MM_GOTHIT;
			monster[i]._mxoff = 0;
			monster[i]._myoff = 0;
			monster[i]._mx = monster[i]._moldx;
			monster[i]._my = monster[i]._moldy;
			monster[i]._mfutx = monster[i]._moldx;
			monster[i]._mfuty = monster[i]._moldy;
			M_ClearSquares(i);
			dMonster[monster[i]._mx][monster[i]._my] = i + 1;
		}
	}
}

void M_DiabloDeath(int i, BOOL sendmsg)
{
	MonsterStruct *Monst;
	int dist;
	int j, k;

	Monst = &monster[i];
	PlaySFX(USFX_DIABLOD);
	quests[Q_DIABLO]._qactive = QUEST_DONE;
	if (sendmsg)
		NetSendCmdQuest(TRUE, Q_DIABLO);
	sgbSaveSoundOn = gbSoundOn;
	gbProcessPlayers = FALSE;
	for (j = 0; j < nummonsters; j++) {
		k = monstactive[j];
		if (k == i || Monst->_msquelch == 0)
			continue;

		NewMonsterAnim(k, &monster[k].MType->Anims[MA_DEATH], monster[k]._mdir);
		monster[k]._mmode = MM_DEATH;
		monster[k]._mxoff = 0;
		monster[k]._myoff = 0;
		monster[k]._mVar1 = 0;
		monster[k]._mx = monster[k]._moldx;
		monster[k]._my = monster[k]._moldy;
		monster[k]._mfutx = monster[k]._mx;
		monster[k]._mfuty = monster[k]._my;
		M_ClearSquares(k);
		dMonster[monster[k]._mx][monster[k]._my] = k + 1;
	}
	AddLight(Monst->_mx, Monst->_my, 8);
	DoVision(Monst->_mx, Monst->_my, 8, FALSE, TRUE);
	dist = std::max(abs(ViewX - Monst->_mx), abs(ViewY - Monst->_my));
	if (dist > 20)
		dist = 20;
	Monst->_mVar3 = ViewX << 16;
	Monst->_mVar4 = ViewY << 16;
	Monst->_mVar5 = (int)((Monst->_mVar3 - (Monst->_mx << 16)) / (double)dist);
	Monst->_mVar6 = (int)((Monst->_mVar4 - (Monst->_my << 16)) / (double)dist);
}

void SpawnLoot(int i, BOOL sendmsg)
{
	int nSFX;
	MonsterStruct *Monst;

	Monst = &monster[i];
	if (QuestStatus(Q_GARBUD) && Monst->_uniqtype - 1 == UMT_GARBUD) {
		CreateTypeItem(Monst->_mx + 1, Monst->_my + 1, TRUE, ITYPE_MACE, IMISC_NONE, TRUE, FALSE);
	} else if (Monst->_uniqtype - 1 == UMT_DEFILER) {
		if (effect_is_playing(USFX_DEFILER8))
			stream_stop();
		quests[Q_DEFILER]._qlog = 0;
		SpawnMapOfDoom(Monst->_mx, Monst->_my);
	} else if (Monst->_uniqtype - 1 == UMT_HORKDMN) {
		if (gbTheoQuest) {
			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 (gbCowQuest)
			nSFX = USFX_NAKRUL6;
		if (effect_is_playing(nSFX))
			stream_stop();
		quests[Q_NAKRUL]._qlog = 0;
		UberDiabloMonsterIndex = -2;
		CreateMagicWeapon(Monst->_mx, Monst->_my, ITYPE_SWORD, ICURS_GREAT_SWORD, FALSE, TRUE);
		CreateMagicWeapon(Monst->_mx, Monst->_my, ITYPE_STAFF, ICURS_WAR_STAFF, FALSE, TRUE);
		CreateMagicWeapon(Monst->_mx, Monst->_my, ITYPE_BOW, ICURS_LONG_WAR_BOW, FALSE, TRUE);
		CreateSpellBook(Monst->_mx, Monst->_my, SPL_APOCA, FALSE, TRUE);
	} else if (i > MAX_PLRS - 1) { // Golems should not spawn loot
		SpawnItem(i, Monst->_mx, Monst->_my, sendmsg);
	}
}

void M2MStartHit(int mid, int i, int dam)
{
	assurance((DWORD)mid < MAXMONSTERS, mid);
	assurance(monster[mid].MType != NULL, mid);

	if (i >= 0 && i < MAX_PLRS)
		monster[mid].mWhoHit |= 1 << i;

	delta_monster_hp(mid, monster[mid]._mhitpoints, currlevel);
	NetSendCmdMonDmg(FALSE, mid, dam);
	PlayEffect(mid, 1);

	if (monster[mid].MType->mtype >= MT_SNEAK && monster[mid].MType->mtype <= MT_ILLWEAV || dam >> 6 >= monster[mid].mLevel + 3) {
		if (i >= 0)
			monster[mid]._mdir = (monster[i]._mdir - 4) & 7;

		if (monster[mid].MType->mtype == MT_BLINK) {
			M_Teleport(mid);
		} else if (monster[mid].MType->mtype >= MT_NSCAV && monster[mid].MType->mtype <= MT_YSCAV
		    || monster[mid].MType->mtype == MT_GRAVEDIG) {
			monster[mid]._mgoal = MGOAL_NORMAL;
			monster[mid]._mgoalvar1 = 0;
			monster[mid]._mgoalvar2 = 0;
		}

		if (monster[mid]._mmode != MM_STONE) {
			if (monster[mid].MType->mtype != MT_GOLEM) {
				NewMonsterAnim(mid, &monster[mid].MType->Anims[MA_GOTHIT], monster[mid]._mdir);
				monster[mid]._mmode = MM_GOTHIT;
			}

			monster[mid]._mxoff = 0;
			monster[mid]._myoff = 0;
			monster[mid]._mx = monster[mid]._moldx;
			monster[mid]._my = monster[mid]._moldy;
			monster[mid]._mfutx = monster[mid]._moldx;
			monster[mid]._mfuty = monster[mid]._moldy;
			M_ClearSquares(mid);
			dMonster[monster[mid]._mx][monster[mid]._my] = mid + 1;
		}
	}
}

void MonstStartKill(int i, int pnum, BOOL sendmsg)
{
	int md;
	MonsterStruct *Monst;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	assurance(Monst->MType != NULL, i);

	if (pnum >= 0)
		Monst->mWhoHit |= 1 << pnum;
	if (pnum < MAX_PLRS && i >= MAX_PLRS) /// BUGFIX: i >= MAX_PLRS (fixed)
		AddPlrMonstExper(Monst->mLevel, Monst->mExp, Monst->mWhoHit);
	monstkills[Monst->MType->mtype]++;
	Monst->_mhitpoints = 0;
	SetRndSeed(Monst->_mRndSeed);
	SpawnLoot(i, sendmsg);
	if (Monst->MType->mtype == MT_DIABLO)
		M_DiabloDeath(i, TRUE);
	else
		PlayEffect(i, 2);

	if (pnum >= 0)
		md = M_GetDir(i);
	else
		md = Monst->_mdir;
	Monst->_mdir = md;
	NewMonsterAnim(i, &Monst->MType->Anims[MA_DEATH], md);
	Monst->_mmode = MM_DEATH;
	Monst->_mgoal = 0;
	Monst->_mxoff = 0;
	Monst->_myoff = 0;
	Monst->_mVar1 = 0;
	Monst->_mx = Monst->_moldx;
	Monst->_my = Monst->_moldy;
	Monst->_mfutx = Monst->_moldx;
	Monst->_mfuty = Monst->_moldy;
	M_ClearSquares(i);
	dMonster[Monst->_mx][Monst->_my] = i + 1;
	CheckQuestKill(i, sendmsg);
	M_FallenFear(Monst->_mx, Monst->_my);
	if (Monst->MType->mtype >= MT_NACID && Monst->MType->mtype <= MT_XACID || Monst->MType->mtype == MT_SPIDLORD)
		AddMissile(Monst->_mx, Monst->_my, 0, 0, 0, MIS_ACIDPUD, TARGET_PLAYERS, i, Monst->_mint + 1, 0);
}

void M2MStartKill(int i, int mid)
{
	int md;

	assurance((DWORD)i < MAXMONSTERS, i);
	assurance((DWORD)mid < MAXMONSTERS, mid);
	assurance(monster[mid].MType != NULL, mid); /// BUGFIX: should check `mid` (fixed)

	delta_kill_monster(mid, monster[mid]._mx, monster[mid]._my, currlevel);
	NetSendCmdLocParam1(FALSE, CMD_MONSTDEATH, monster[mid]._mx, monster[mid]._my, mid);

	if (i < MAX_PLRS) {
		monster[mid].mWhoHit |= 1 << i;
		if (mid >= MAX_PLRS)
			AddPlrMonstExper(monster[mid].mLevel, monster[mid].mExp, monster[mid].mWhoHit);
	}

	monstkills[monster[mid].MType->mtype]++;
	monster[mid]._mhitpoints = 0;
	SetRndSeed(monster[mid]._mRndSeed);

	SpawnLoot(mid, TRUE);

	if (monster[mid].MType->mtype == MT_DIABLO)
		M_DiabloDeath(mid, TRUE);
	else
		PlayEffect(mid, 2);

	md = (monster[i]._mdir - 4) & 7;
	if (monster[mid].MType->mtype == MT_GOLEM)
		md = 0;

	monster[mid]._mdir = md;
	NewMonsterAnim(mid, &monster[mid].MType->Anims[MA_DEATH], md);
	monster[mid]._mmode = MM_DEATH;
	monster[mid]._mxoff = 0;
	monster[mid]._myoff = 0;
	monster[mid]._mx = monster[mid]._moldx;
	monster[mid]._my = monster[mid]._moldy;
	monster[mid]._mfutx = monster[mid]._moldx;
	monster[mid]._mfuty = monster[mid]._moldy;
	M_ClearSquares(mid);
	dMonster[monster[mid]._mx][monster[mid]._my] = mid + 1;
	CheckQuestKill(mid, TRUE);
	M_FallenFear(monster[mid]._mx, monster[mid]._my);
	if (monster[mid].MType->mtype >= MT_NACID && monster[mid].MType->mtype <= MT_XACID)
		AddMissile(monster[mid]._mx, monster[mid]._my, 0, 0, 0, MIS_ACIDPUD, TARGET_PLAYERS, mid, monster[mid]._mint + 1, 0);

	if (gbIsHellfire)
		M_StartStand(i, monster[i]._mdir);
}

void M_StartKill(int i, int pnum)
{
	assurance((DWORD)i < MAXMONSTERS, i);

	if (myplr == pnum) {
		delta_kill_monster(i, monster[i]._mx, monster[i]._my, currlevel);
		if (i != pnum) {
			NetSendCmdLocParam1(FALSE, CMD_MONSTDEATH, monster[i]._mx, monster[i]._my, i);
		} else {
			NetSendCmdLocParam1(FALSE, CMD_KILLGOLEM, monster[i]._mx, monster[i]._my, currlevel);
		}
	}

	MonstStartKill(i, pnum, TRUE);
}

void M_SyncStartKill(int i, int x, int y, int pnum)
{
	assurance((DWORD)i < MAXMONSTERS, i);

	if (monster[i]._mhitpoints == 0 || monster[i]._mmode == MM_DEATH) {
		return;
	}

	if (dMonster[x][y] == 0) {
		M_ClearSquares(i);
		monster[i]._mx = x;
		monster[i]._my = y;
		monster[i]._moldx = x;
		monster[i]._moldy = y;
	}

	if (monster[i]._mmode == MM_STONE) {
		MonstStartKill(i, pnum, FALSE);
		monster[i]._mmode = MM_STONE;
	} else {
		MonstStartKill(i, pnum, FALSE);
	}
}

void M_StartFadein(int i, int md, BOOL backwards)
{
	assurance((DWORD)i < MAXMONSTERS, i);
	assurance(monster[i].MType != NULL, i);

	NewMonsterAnim(i, &monster[i].MType->Anims[MA_SPECIAL], md);
	monster[i]._mmode = MM_FADEIN;
	monster[i]._mxoff = 0;
	monster[i]._myoff = 0;
	monster[i]._mfutx = monster[i]._mx;
	monster[i]._mfuty = monster[i]._my;
	monster[i]._moldx = monster[i]._mx;
	monster[i]._moldy = monster[i]._my;
	monster[i]._mdir = md;
	monster[i]._mFlags &= ~MFLAG_HIDDEN;
	if (backwards) {
		monster[i]._mFlags |= MFLAG_LOCK_ANIMATION;
		monster[i]._mAnimFrame = monster[i]._mAnimLen;
	}
}

void M_StartFadeout(int i, int md, BOOL backwards)
{
	assurance((DWORD)i < MAXMONSTERS, i);
	assurance(monster[i].MType != NULL, i);
	assurance(monster[i].MType != NULL, i);

	NewMonsterAnim(i, &monster[i].MType->Anims[MA_SPECIAL], md);
	monster[i]._mmode = MM_FADEOUT;
	monster[i]._mxoff = 0;
	monster[i]._myoff = 0;
	monster[i]._mfutx = monster[i]._mx;
	monster[i]._mfuty = monster[i]._my;
	monster[i]._moldx = monster[i]._mx;
	monster[i]._moldy = monster[i]._my;
	monster[i]._mdir = md;
	if (backwards) {
		monster[i]._mFlags |= MFLAG_LOCK_ANIMATION;
		monster[i]._mAnimFrame = monster[i]._mAnimLen;
	}
}

void M_StartHeal(int i)
{
	MonsterStruct *Monst;

	assurance((DWORD)i < MAXMONSTERS, i);
	assurance(monster[i].MType != NULL, i);

	Monst = &monster[i];
	Monst->_mAnimData = Monst->MType->Anims[MA_SPECIAL].Data[Monst->_mdir];
	Monst->_mAnimFrame = Monst->MType->Anims[MA_SPECIAL].Frames;
	Monst->_mFlags |= MFLAG_LOCK_ANIMATION;
	Monst->_mmode = MM_HEAL;
	Monst->_mVar1 = Monst->_mmaxhp / (16 * (random_(97, 5) + 4));
}

void M_ChangeLightOffset(int monst)
{
	int lx, ly, _mxoff, _myoff, sign;

	assurance((DWORD)monst < MAXMONSTERS, monst);

	lx = monster[monst]._mxoff + 2 * monster[monst]._myoff;
	ly = 2 * monster[monst]._myoff - monster[monst]._mxoff;

	if (lx < 0) {
		sign = -1;
		lx = -lx;
	} else {
		sign = 1;
	}

	_mxoff = sign * (lx >> 3);
	if (ly < 0) {
		_myoff = -1;
		ly = -ly;
	} else {
		_myoff = 1;
	}

	_myoff *= (ly >> 3);
	if (monster[monst].mlid != NO_LIGHT)
		ChangeLightOff(monster[monst].mlid, _mxoff, _myoff);
}

BOOL M_DoStand(int i)
{
	MonsterStruct *Monst;

	commitment((DWORD)i < MAXMONSTERS, i);
	commitment(monster[i].MType != NULL, i);

	Monst = &monster[i];
	if (Monst->MType->mtype == MT_GOLEM)
		Monst->_mAnimData = Monst->MType->Anims[MA_WALK].Data[Monst->_mdir];
	else
		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir];

	if (Monst->_mAnimFrame == Monst->_mAnimLen)
		M_Enemy(i);

	Monst->_mVar2++;

	return FALSE;
}

/**
 * @brief Continue movement towards new tile
 */
bool M_DoWalk(int i, int variant)
{
	bool returnValue;

	commitment((DWORD)i < MAXMONSTERS, i);
	commitment(monster[i].MType != NULL, i);

	//Check if we reached new tile
	if (monster[i]._mVar8 == monster[i].MType->Anims[MA_WALK].Frames) {
		switch (variant) {
		case MM_WALK:
			dMonster[monster[i]._mx][monster[i]._my] = 0;
			monster[i]._mx += monster[i]._mVar1;
			monster[i]._my += monster[i]._mVar2;
			dMonster[monster[i]._mx][monster[i]._my] = i + 1;
			break;
		case MM_WALK2:
			dMonster[monster[i]._mVar1][monster[i]._mVar2] = 0;
			break;
		case MM_WALK3:
			dMonster[monster[i]._mx][monster[i]._my] = 0;
			monster[i]._mx = monster[i]._mVar1;
			monster[i]._my = monster[i]._mVar2;
			dFlags[monster[i]._mVar4][monster[i]._mVar5] &= ~BFLAG_MONSTLR;
			dMonster[monster[i]._mx][monster[i]._my] = i + 1;
			break;
		}
		if (monster[i].mlid != NO_LIGHT)
			ChangeLightXY(monster[i].mlid, monster[i]._mx, monster[i]._my);
		M_StartStand(i, monster[i]._mdir);
		returnValue = TRUE;
	} else { //We didn't reach new tile so update monster's "sub-tile" position
		if (monster[i]._mAnimCnt == 0) {
			if (monster[i]._mVar8 == 0 && monster[i].MType->mtype == MT_FLESTHNG)
				PlayEffect(i, 3);
			monster[i]._mVar8++;
			monster[i]._mVar6 += monster[i]._mxvel;
			monster[i]._mVar7 += monster[i]._myvel;
			monster[i]._mxoff = monster[i]._mVar6 >> 4;
			monster[i]._myoff = monster[i]._mVar7 >> 4;
		}
		returnValue = FALSE;
	}

	if (monster[i].mlid != NO_LIGHT) // BUGFIX: change uniqtype check to mlid check like it is in all other places (fixed)
		M_ChangeLightOffset(i);

	return returnValue;
}

void M_TryM2MHit(int i, int mid, int hper, int mind, int maxd)
{
	BOOL ret;

	assurance((DWORD)mid < MAXMONSTERS, mid);
	assurance(monster[mid].MType != NULL, mid);
	if (monster[mid]._mhitpoints >> 6 > 0 && (monster[mid].MType->mtype != MT_ILLWEAV || monster[mid]._mgoal != MGOAL_RETREAT)) {
		int hit = random_(4, 100);
		if (monster[mid]._mmode == MM_STONE)
			hit = 0;
		if (!CheckMonsterHit(mid, &ret) && hit < hper) {
			int dam = (mind + random_(5, maxd - mind + 1)) << 6;
			monster[mid]._mhitpoints -= dam;
			if (monster[mid]._mhitpoints >> 6 <= 0) {
				if (monster[mid]._mmode == MM_STONE) {
					M2MStartKill(i, mid);
					monster[mid]._mmode = MM_STONE;
				} else {
					M2MStartKill(i, mid);
				}
			} else {
				if (monster[mid]._mmode == MM_STONE) {
					M2MStartHit(mid, i, dam);
					monster[mid]._mmode = MM_STONE;
				} else {
					M2MStartHit(mid, i, dam);
				}
			}
		}
	}
}

void M_TryH2HHit(int i, int pnum, int Hit, int MinDam, int MaxDam)
{
	int hit, hper;
	int dx, dy;
	int blk, blkper;
	int dam, mdam;
	int newx, newy;
	int j, misnum, ms_num, cur_ms_num, new_hp, dir, ac;

	assurance((DWORD)i < MAXMONSTERS, i);
	assurance(monster[i].MType != NULL, i);
	if (monster[i]._mFlags & MFLAG_TARGETS_MONSTER) {
		M_TryM2MHit(i, pnum, Hit, MinDam, MaxDam);
		return;
	}
	if (plr[pnum]._pHitPoints >> 6 <= 0 || plr[pnum]._pInvincible || plr[pnum]._pSpellFlags & 1)
		return;
	dx = abs(monster[i]._mx - plr[pnum]._px);
	dy = abs(monster[i]._my - plr[pnum]._py);
	if (dx >= 2 || dy >= 2)
		return;

	hper = random_(98, 100);
#ifdef _DEBUG
	if (debug_mode_dollar_sign || debug_mode_key_inverted_v)
		hper = 1000;
#endif
	ac = plr[pnum]._pIBonusAC + plr[pnum]._pIAC;
	if (plr[pnum].pDamAcFlags & 0x20 && monster[i].MData->mMonstClass == MC_DEMON)
		ac += 40;
	if (plr[pnum].pDamAcFlags & 0x40 && monster[i].MData->mMonstClass == MC_UNDEAD)
		ac += 20;
	hit = Hit
	    + 2 * (monster[i].mLevel - plr[pnum]._pLevel)
	    + 30
	    - ac
	    - plr[pnum]._pDexterity / 5;
	if (hit < 15)
		hit = 15;
	if (currlevel == 14 && hit < 20)
		hit = 20;
	if (currlevel == 15 && hit < 25)
		hit = 25;
	if (currlevel == 16 && hit < 30)
		hit = 30;
	if ((plr[pnum]._pmode == PM_STAND || plr[pnum]._pmode == PM_ATTACK) && plr[pnum]._pBlockFlag) {
		blkper = random_(98, 100);
	} else {
		blkper = 100;
	}
	blk = plr[pnum]._pDexterity
	    + plr[pnum]._pBaseToBlk
	    - (monster[i].mLevel << 1)
	    + (plr[pnum]._pLevel << 1);
	if (blk < 0)
		blk = 0;
	if (blk > 100)
		blk = 100;
	if (hper >= hit)
		return;
	if (blkper < blk) {
		direction dir = GetDirection(plr[pnum]._px, plr[pnum]._py, monster[i]._mx, monster[i]._my);
		StartPlrBlock(pnum, dir);
		if (pnum == myplr && plr[pnum].wReflections > 0) {
			plr[pnum].wReflections--;
			dam = random_(99, (MaxDam - MinDam + 1) << 6) + (MinDam << 6);
			dam += plr[pnum]._pIGetHit << 6;
			if (dam < 64)
				dam = 64;
			mdam = dam * (0.01 * (random_(100, 10) + 20));
			monster[i]._mhitpoints -= mdam;
			dam -= mdam;
			if (dam < 0)
				dam = 0;
			if (monster[i]._mhitpoints >> 6 <= 0)
				M_StartKill(i, pnum);
			else
				M_StartHit(i, pnum, mdam);
		}
		return;
	}
	if (monster[i].MType->mtype == MT_YZOMBIE && pnum == myplr) {
		ms_num = -1;
		cur_ms_num = -1;
		for (j = 0; j < nummissiles; j++) {
			misnum = missileactive[j];
			if (missile[misnum]._mitype != MIS_MANASHIELD)
				continue;
			if (missile[misnum]._misource == pnum)
				cur_ms_num = misnum;
			else
				ms_num = misnum;
		}
		if (plr[pnum]._pMaxHP > 64) {
			if (plr[pnum]._pMaxHPBase > 64) {
				plr[pnum]._pMaxHP -= 64;
				if (plr[pnum]._pHitPoints > plr[pnum]._pMaxHP) {
					plr[pnum]._pHitPoints = plr[pnum]._pMaxHP;
					if (cur_ms_num >= 0)
						missile[cur_ms_num]._miVar1 = plr[pnum]._pHitPoints;
				}
				plr[pnum]._pMaxHPBase -= 64;
				if (plr[pnum]._pHPBase > plr[pnum]._pMaxHPBase) {
					plr[pnum]._pHPBase = plr[pnum]._pMaxHPBase;
					if (cur_ms_num >= 0)
						missile[cur_ms_num]._miVar2 = plr[pnum]._pHPBase;
				}
			}
		}
	}
	dam = (MinDam << 6) + random_(99, (MaxDam - MinDam + 1) << 6);
	dam += (plr[pnum]._pIGetHit << 6);
	if (dam < 64)
		dam = 64;
	if (pnum == myplr) {
		if (plr[pnum].wReflections > 0) {
			plr[pnum].wReflections--;
			mdam = dam * (0.01 * (random_(100, 10) + 20));
			monster[i]._mhitpoints -= mdam;
			dam -= mdam;
			if (dam < 0)
				dam = 0;
			if (monster[i]._mhitpoints >> 6 <= 0)
				M_StartKill(i, pnum);
			else
				M_StartHit(i, pnum, mdam);
		}
		plr[pnum]._pHitPoints -= dam;
		plr[pnum]._pHPBase -= dam;
	}
	if (plr[pnum]._pIFlags & ISPL_THORNS) {
		mdam = (random_(99, 3) + 1) << 6;
		monster[i]._mhitpoints -= mdam;
		if (monster[i]._mhitpoints >> 6 <= 0)
			M_StartKill(i, pnum);
		else
			M_StartHit(i, pnum, mdam);
	}
	if (!(monster[i]._mFlags & MFLAG_NOLIFESTEAL) && monster[i].MType->mtype == MT_SKING && gbIsMultiplayer)
		monster[i]._mhitpoints += dam;
	if (plr[pnum]._pHitPoints > plr[pnum]._pMaxHP) {
		plr[pnum]._pHitPoints = plr[pnum]._pMaxHP;
		plr[pnum]._pHPBase = plr[pnum]._pMaxHPBase;
	}
	if (plr[pnum]._pHitPoints >> 6 <= 0) {
		SyncPlrKill(pnum, 0);
		if (gbIsHellfire)
			M_StartStand(i, monster[i]._mdir);
		return;
	}
	StartPlrHit(pnum, dam, FALSE);
	if (monster[i]._mFlags & MFLAG_KNOCKBACK) {
		if (plr[pnum]._pmode != PM_GOTHIT)
			StartPlrHit(pnum, 0, TRUE);
		newx = plr[pnum]._px + offset_x[monster[i]._mdir];
		newy = plr[pnum]._py + offset_y[monster[i]._mdir];
		if (PosOkPlayer(pnum, newx, newy)) {
			plr[pnum]._px = newx;
			plr[pnum]._py = newy;
			FixPlayerLocation(pnum, plr[pnum]._pdir);
			FixPlrWalkTags(pnum);
			dPlayer[newx][newy] = pnum + 1;
			SetPlayerOld(pnum);
		}
	}
}

BOOL M_DoAttack(int i)
{
	MonsterStruct *Monst;

	commitment((DWORD)i < MAXMONSTERS, i);
	Monst = &monster[i];
	commitment(Monst->MType != NULL, i);
	commitment(Monst->MData != NULL, i); // BUGFIX: should check MData (fixed)

	if (monster[i]._mAnimFrame == monster[i].MData->mAFNum) {
		M_TryH2HHit(i, monster[i]._menemy, monster[i].mHit, monster[i].mMinDamage, monster[i].mMaxDamage);
		if (monster[i]._mAi != AI_SNAKE)
			PlayEffect(i, 0);
	}
	if (monster[i].MType->mtype >= MT_NMAGMA && monster[i].MType->mtype <= MT_WMAGMA && monster[i]._mAnimFrame == 9) {
		M_TryH2HHit(i, monster[i]._menemy, monster[i].mHit + 10, monster[i].mMinDamage - 2, monster[i].mMaxDamage - 2);
		PlayEffect(i, 0);
	}
	if (monster[i].MType->mtype >= MT_STORM && monster[i].MType->mtype <= MT_MAEL && monster[i]._mAnimFrame == 13) {
		M_TryH2HHit(i, monster[i]._menemy, monster[i].mHit - 20, monster[i].mMinDamage + 4, monster[i].mMaxDamage + 4);
		PlayEffect(i, 0);
	}
	if (monster[i]._mAi == AI_SNAKE && monster[i]._mAnimFrame == 1)
		PlayEffect(i, 0);
	if (monster[i]._mAnimFrame == monster[i]._mAnimLen) {
		M_StartStand(i, monster[i]._mdir);
		return TRUE;
	}

	return FALSE;
}

BOOL M_DoRAttack(int i)
{
	int multimissiles, mi;

	commitment((DWORD)i < MAXMONSTERS, i);
	commitment(monster[i].MType != NULL, i);
	commitment(monster[i].MData != NULL, i);

	if (monster[i]._mAnimFrame == monster[i].MData->mAFNum) {
		if (monster[i]._mVar1 != -1) {
			if (monster[i]._mVar1 == MIS_CBOLT)
				multimissiles = 3;
			else
				multimissiles = 1;
			for (mi = 0; mi < multimissiles; mi++) {
				AddMissile(
				    monster[i]._mx + (gbIsHellfire ? offset_x[monster[i]._mdir] : 0),
				    monster[i]._my + (gbIsHellfire ? offset_y[monster[i]._mdir] : 0),
				    monster[i]._menemyx,
				    monster[i]._menemyy,
				    monster[i]._mdir,
				    monster[i]._mVar1,
				    TARGET_PLAYERS,
				    i,
				    monster[i]._mVar2,
				    0);
			}
		}
		PlayEffect(i, 0);
	}

	if (monster[i]._mAnimFrame == monster[i]._mAnimLen) {
		M_StartStand(i, monster[i]._mdir);
		return TRUE;
	}

	return FALSE;
}

BOOL M_DoRSpAttack(int i)
{
	commitment((DWORD)i < MAXMONSTERS, i);
	commitment(monster[i].MType != NULL, i);
	commitment(monster[i].MData != NULL, i); // BUGFIX: should check MData (fixed)

	if (monster[i]._mAnimFrame == monster[i].MData->mAFNum2 && monster[i]._mAnimCnt == 0) {
		AddMissile(
		    monster[i]._mx + (gbIsHellfire ? offset_x[monster[i]._mdir] : 0),
		    monster[i]._my + (gbIsHellfire ? offset_y[monster[i]._mdir] : 0),
		    monster[i]._menemyx,
		    monster[i]._menemyy,
		    monster[i]._mdir,
		    monster[i]._mVar1,
		    TARGET_PLAYERS,
		    i,
		    monster[i]._mVar3,
		    0);
		PlayEffect(i, 3);
	}

	if (monster[i]._mAi == AI_MEGA && monster[i]._mAnimFrame == 3) {
		if (monster[i]._mVar2++ == 0) {
			monster[i]._mFlags |= MFLAG_ALLOW_SPECIAL;
		} else if (monster[i]._mVar2 == 15) {
			monster[i]._mFlags &= ~MFLAG_ALLOW_SPECIAL;
		}
	}

	if (monster[i]._mAnimFrame == monster[i]._mAnimLen) {
		M_StartStand(i, monster[i]._mdir);
		return TRUE;
	}

	return FALSE;
}

BOOL M_DoSAttack(int i)
{
	commitment((DWORD)i < MAXMONSTERS, i);
	commitment(monster[i].MType != NULL, i);
	commitment(monster[i].MData != NULL, i);

	if (monster[i]._mAnimFrame == monster[i].MData->mAFNum2)
		M_TryH2HHit(i, monster[i]._menemy, monster[i].mHit2, monster[i].mMinDamage2, monster[i].mMaxDamage2);

	if (monster[i]._mAnimFrame == monster[i]._mAnimLen) {
		M_StartStand(i, monster[i]._mdir);
		return TRUE;
	}

	return FALSE;
}

BOOL M_DoFadein(int i)
{
	commitment((DWORD)i < MAXMONSTERS, i);

	if ((!(monster[i]._mFlags & MFLAG_LOCK_ANIMATION) || monster[i]._mAnimFrame != 1)
	    && (monster[i]._mFlags & MFLAG_LOCK_ANIMATION || monster[i]._mAnimFrame != monster[i]._mAnimLen)) {
		return FALSE;
	}

	M_StartStand(i, monster[i]._mdir);
	monster[i]._mFlags &= ~MFLAG_LOCK_ANIMATION;

	return TRUE;
}

BOOL M_DoFadeout(int i)
{
	int mt;

	commitment((DWORD)i < MAXMONSTERS, i);

	if ((!(monster[i]._mFlags & MFLAG_LOCK_ANIMATION) || monster[i]._mAnimFrame != 1)
	    && (monster[i]._mFlags & MFLAG_LOCK_ANIMATION || monster[i]._mAnimFrame != monster[i]._mAnimLen)) {
		return FALSE;
	}

	mt = monster[i].MType->mtype;
	if (mt < MT_INCIN || mt > MT_HELLBURN) {
		monster[i]._mFlags &= ~MFLAG_LOCK_ANIMATION;
		monster[i]._mFlags |= MFLAG_HIDDEN;
	} else {
		monster[i]._mFlags &= ~MFLAG_LOCK_ANIMATION;
	}

	M_StartStand(i, monster[i]._mdir);

	return TRUE;
}

BOOL M_DoHeal(int i)
{
	MonsterStruct *Monst;

	commitment((DWORD)i < MAXMONSTERS, i);
	Monst = &monster[i];
	if (monster[i]._mFlags & MFLAG_NOHEAL) {
		Monst->_mFlags &= ~MFLAG_ALLOW_SPECIAL;
		Monst->_mmode = MM_SATTACK;
		return FALSE;
	}

	if (Monst->_mAnimFrame == 1) {
		Monst->_mFlags &= ~MFLAG_LOCK_ANIMATION;
		Monst->_mFlags |= MFLAG_ALLOW_SPECIAL;
		if (Monst->_mVar1 + Monst->_mhitpoints < Monst->_mmaxhp) {
			Monst->_mhitpoints = Monst->_mVar1 + Monst->_mhitpoints;
		} else {
			Monst->_mhitpoints = Monst->_mmaxhp;
			Monst->_mFlags &= ~MFLAG_ALLOW_SPECIAL;
			Monst->_mmode = MM_SATTACK;
		}
	}
	return FALSE;
}

BOOL M_DoTalk(int i)
{
	MonsterStruct *Monst;
	int tren;

	commitment((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	M_StartStand(i, monster[i]._mdir);
	Monst->_mgoal = MGOAL_TALKING; // CODEFIX: apply Monst instead of monster[i] in the rest of the function
	if (effect_is_playing(alltext[monster[i].mtalkmsg].sfxnr))
		return FALSE;
	InitQTextMsg(monster[i].mtalkmsg);
	if (monster[i]._uniqtype - 1 == UMT_GARBUD) {
		if (monster[i].mtalkmsg == TEXT_GARBUD1) {
			quests[Q_GARBUD]._qactive = QUEST_ACTIVE;
			quests[Q_GARBUD]._qlog = TRUE; // BUGFIX: (?) for other quests qactive and qlog go together, maybe this should actually go into the if above (fixed)
		}
		if (monster[i].mtalkmsg == TEXT_GARBUD2 && !(monster[i]._mFlags & MFLAG_QUEST_COMPLETE)) {
			SpawnItem(i, monster[i]._mx + 1, monster[i]._my + 1, TRUE);
			monster[i]._mFlags |= MFLAG_QUEST_COMPLETE;
		}
	}
	if (monster[i]._uniqtype - 1 == UMT_ZHAR
	    && monster[i].mtalkmsg == TEXT_ZHAR1
	    && !(monster[i]._mFlags & MFLAG_QUEST_COMPLETE)) {
		quests[Q_ZHAR]._qactive = QUEST_ACTIVE;
		quests[Q_ZHAR]._qlog = TRUE;
		CreateTypeItem(monster[i]._mx + 1, monster[i]._my + 1, FALSE, ITYPE_MISC, IMISC_BOOK, TRUE, FALSE);
		monster[i]._mFlags |= MFLAG_QUEST_COMPLETE;
	}
	if (monster[i]._uniqtype - 1 == UMT_SNOTSPIL) {
		if (monster[i].mtalkmsg == TEXT_BANNER10 && !(monster[i]._mFlags & MFLAG_QUEST_COMPLETE)) {
			ObjChangeMap(setpc_x, setpc_y, (setpc_w >> 1) + setpc_x + 2, (setpc_h >> 1) + setpc_y - 2);
			tren = TransVal;
			TransVal = 9;
			DRLG_MRectTrans(setpc_x, setpc_y, (setpc_w >> 1) + setpc_x + 4, setpc_y + (setpc_h >> 1));
			TransVal = tren;
			quests[Q_LTBANNER]._qvar1 = 2;
			if (quests[Q_LTBANNER]._qactive == QUEST_INIT)
				quests[Q_LTBANNER]._qactive = QUEST_ACTIVE;
			monster[i]._mFlags |= MFLAG_QUEST_COMPLETE;
		}
		if (quests[Q_LTBANNER]._qvar1 < 2) {
			app_fatal("SS Talk = %i, Flags = %i", monster[i].mtalkmsg, monster[i]._mFlags);
		}
	}
	if (monster[i]._uniqtype - 1 == UMT_LACHDAN) {
		if (monster[i].mtalkmsg == TEXT_VEIL9) {
			quests[Q_VEIL]._qactive = QUEST_ACTIVE;
			quests[Q_VEIL]._qlog = TRUE;
		}
		if (monster[i].mtalkmsg == TEXT_VEIL11 && !(monster[i]._mFlags & MFLAG_QUEST_COMPLETE)) {
			SpawnUnique(UITEM_STEELVEIL, monster[i]._mx + 1, monster[i]._my + 1);
			monster[i]._mFlags |= MFLAG_QUEST_COMPLETE;
		}
	}
	if (monster[i]._uniqtype - 1 == UMT_WARLORD)
		quests[Q_WARLORD]._qvar1 = 2;
	if (monster[i]._uniqtype - 1 == UMT_LAZURUS && gbIsMultiplayer) {
		quests[Q_BETRAYER]._qvar1 = 6;
		monster[i]._mgoal = MGOAL_NORMAL;
		monster[i]._msquelch = UCHAR_MAX;
		monster[i].mtalkmsg = 0;
	}
	return FALSE;
}

void M_Teleport(int i)
{
	BOOL done;
	MonsterStruct *Monst;
	int k, j, x, y, _mx, _my, rx, ry;

	assurance((DWORD)i < MAXMONSTERS, i);

	done = FALSE;

	Monst = &monster[i];
	if (Monst->_mmode == MM_STONE)
		return;

	_mx = Monst->_menemyx;
	_my = Monst->_menemyy;
	rx = 2 * random_(100, 2) - 1;
	ry = 2 * random_(100, 2) - 1;

	for (j = -1; j <= 1 && !done; j++) {
		for (k = -1; k < 1 && !done; k++) {
			if (j != 0 || k != 0) {
				x = _mx + rx * j;
				y = _my + ry * k;
				if (y >= 0 && y < MAXDUNY && x >= 0 && x < MAXDUNX && x != Monst->_mx && y != Monst->_my) {
					if (PosOkMonst(i, x, y))
						done = TRUE;
				}
			}
		}
	}

	if (done) {
		M_ClearSquares(i);
		dMonster[Monst->_mx][Monst->_my] = 0;
		dMonster[x][y] = i + 1;
		Monst->_moldx = x;
		Monst->_moldy = y;
		Monst->_mdir = M_GetDir(i);
	}
}

BOOL M_DoGotHit(int i)
{
	commitment((DWORD)i < MAXMONSTERS, i);
	commitment(monster[i].MType != NULL, i);

	if (monster[i]._mAnimFrame == monster[i]._mAnimLen) {
		M_StartStand(i, monster[i]._mdir);

		return TRUE;
	}

	return FALSE;
}

void M_UpdateLeader(int i)
{
	int ma, j;

	assurance((DWORD)i < MAXMONSTERS, i);

	for (j = 0; j < nummonsters; j++) {
		ma = monstactive[j];
		if (monster[ma].leaderflag == 1 && monster[ma].leader == i)
			monster[ma].leaderflag = 0;
	}

	if (monster[i].leaderflag == 1) {
		monster[monster[i].leader].packsize--;
	}
}

void DoEnding()
{
	BOOL bMusicOn;
	int musicVolume;

	if (gbIsMultiplayer) {
		SNetLeaveGame(LEAVE_ENDING);
	}

	music_stop();

	if (gbIsMultiplayer) {
		SDL_Delay(1000);
	}

	if (gbIsSpawn)
		return;

	if (plr[myplr]._pClass == PC_WARRIOR || plr[myplr]._pClass == PC_BARBARIAN) {
		play_movie("gendata\\DiabVic2.smk", FALSE);
	} else if (plr[myplr]._pClass == PC_SORCERER) {
		play_movie("gendata\\DiabVic1.smk", FALSE);
	} else if (plr[myplr]._pClass == PC_MONK) {
		play_movie("gendata\\DiabVic1.smk", FALSE);
	} else {
		play_movie("gendata\\DiabVic3.smk", FALSE);
	}
	play_movie("gendata\\Diabend.smk", FALSE);

	bMusicOn = gbMusicOn;
	gbMusicOn = TRUE;

	musicVolume = sound_get_or_set_music_volume(1);
	sound_get_or_set_music_volume(0);

	music_start(TMUSIC_L2);
	loop_movie = TRUE;
	play_movie("gendata\\loopdend.smk", TRUE);
	loop_movie = FALSE;
	music_stop();

	sound_get_or_set_music_volume(musicVolume);
	gbMusicOn = bMusicOn;
}

void PrepDoEnding()
{
	int newKillLevel, i;
	DWORD *killLevel;

	gbSoundOn = sgbSaveSoundOn;
	gbRunGame = FALSE;
	deathflag = FALSE;
	cineflag = TRUE;

	killLevel = &plr[myplr].pDiabloKillLevel;
	newKillLevel = gnDifficulty + 1;
	if (*killLevel > newKillLevel)
		newKillLevel = *killLevel;
	plr[myplr].pDiabloKillLevel = newKillLevel;

	for (i = 0; i < MAX_PLRS; i++) {
		plr[i]._pmode = PM_QUIT;
		plr[i]._pInvincible = TRUE;
		if (gbIsMultiplayer) {
			if (plr[i]._pHitPoints >> 6 == 0)
				plr[i]._pHitPoints = 64;
			if (plr[i]._pMana >> 6 == 0)
				plr[i]._pMana = 64;
		}
	}
}

BOOL M_DoDeath(int i)
{
	int x, y;

	commitment((DWORD)i < MAXMONSTERS, i);
	commitment(monster[i].MType != NULL, i);

	monster[i]._mVar1++;
	if (monster[i].MType->mtype == MT_DIABLO) {
		x = monster[i]._mx - ViewX;
		if (x < 0)
			x = -1;
		else
			x = x > 0;
		ViewX += x;

		y = monster[i]._my - ViewY;
		if (y < 0) {
			y = -1;
		} else {
			y = y > 0;
		}
		ViewY += y;

		if (monster[i]._mVar1 == 140)
			PrepDoEnding();
	} else if (monster[i]._mAnimFrame == monster[i]._mAnimLen) {
		if (monster[i]._uniqtype == 0)
			AddDead(monster[i]._mx, monster[i]._my, monster[i].MType->mdeadval, (direction)monster[i]._mdir);
		else
			AddDead(monster[i]._mx, monster[i]._my, monster[i]._udeadval, (direction)monster[i]._mdir);

		dMonster[monster[i]._mx][monster[i]._my] = 0;
		monster[i]._mDelFlag = TRUE;

		M_UpdateLeader(i);
	}
	return FALSE;
}

BOOL M_DoSpStand(int i)
{
	commitment((DWORD)i < MAXMONSTERS, i);
	commitment(monster[i].MType != NULL, i);

	if (monster[i]._mAnimFrame == monster[i].MData->mAFNum2)
		PlayEffect(i, 3);

	if (monster[i]._mAnimFrame == monster[i]._mAnimLen) {
		M_StartStand(i, monster[i]._mdir);
		return TRUE;
	}

	return FALSE;
}

BOOL M_DoDelay(int i)
{
	int oFrame;

	commitment((DWORD)i < MAXMONSTERS, i);
	commitment(monster[i].MType != NULL, i);

	monster[i]._mAnimData = monster[i].MType->Anims[MA_STAND].Data[M_GetDir(i)];
	if (monster[i]._mAi == AI_LAZURUS) {
		if (monster[i]._mVar2 > 8 || monster[i]._mVar2 < 0)
			monster[i]._mVar2 = 8;
	}

	if (monster[i]._mVar2-- == 0) {
		oFrame = monster[i]._mAnimFrame;
		M_StartStand(i, monster[i]._mdir);
		monster[i]._mAnimFrame = oFrame;
		return TRUE;
	}

	return FALSE;
}

BOOL M_DoStone(int i)
{
	commitment((DWORD)i < MAXMONSTERS, i);

	if (!monster[i]._mhitpoints) {
		dMonster[monster[i]._mx][monster[i]._my] = 0;
		monster[i]._mDelFlag = TRUE;
	}

	return FALSE;
}

void M_WalkDir(int i, int md)
{
	int mwi;

	assurance((DWORD)i < MAXMONSTERS, i);

	mwi = monster[i].MType->Anims[MA_WALK].Frames - 1;
	switch (md) {
	case DIR_N:
		M_StartWalk(i, 0, -MWVel[mwi][1], -1, -1, DIR_N);
		break;
	case DIR_NE:
		M_StartWalk(i, MWVel[mwi][1], -MWVel[mwi][0], 0, -1, DIR_NE);
		break;
	case DIR_E:
		M_StartWalk3(i, MWVel[mwi][2], 0, -32, -16, 1, -1, 1, 0, DIR_E);
		break;
	case DIR_SE:
		M_StartWalk2(i, MWVel[mwi][1], MWVel[mwi][0], -32, -16, 1, 0, DIR_SE);
		break;
	case DIR_S:
		M_StartWalk2(i, 0, MWVel[mwi][1], 0, -32, 1, 1, DIR_S);
		break;
	case DIR_SW:
		M_StartWalk2(i, -MWVel[mwi][1], MWVel[mwi][0], 32, -16, 0, 1, DIR_SW);
		break;
	case DIR_W:
		M_StartWalk3(i, -MWVel[mwi][2], 0, 32, -16, -1, 1, 0, 1, DIR_W);
		break;
	case DIR_NW:
		M_StartWalk(i, -MWVel[mwi][1], -MWVel[mwi][0], -1, 0, DIR_NW);
		break;
	}
}

void GroupUnity(int i)
{
	int leader, m, j;
	BOOL clear;

	assurance((DWORD)i < MAXMONSTERS, i);

	if (monster[i].leaderflag != 0) {
		leader = monster[i].leader;
		clear = LineClearF(CheckNoSolid, monster[i]._mx, monster[i]._my, monster[leader]._mfutx, monster[leader]._mfuty);
		if (clear || monster[i].leaderflag != 1) {
			if (clear
			    && monster[i].leaderflag == 2
			    && abs(monster[i]._mx - monster[leader]._mfutx) < 4
			    && abs(monster[i]._my - monster[leader]._mfuty) < 4) {
				monster[leader].packsize++;
				monster[i].leaderflag = 1;
			}
		} else {
			monster[leader].packsize--;
			monster[i].leaderflag = 2;
		}
	}

	if (monster[i].leaderflag == 1) {
		if (monster[i]._msquelch > monster[leader]._msquelch) {
			monster[leader]._lastx = monster[i]._mx;
			monster[leader]._lasty = monster[i]._my;
			monster[leader]._msquelch = monster[i]._msquelch - 1;
		}
		if (monster[leader]._mAi == AI_GARG) {
			if (monster[leader]._mFlags & MFLAG_ALLOW_SPECIAL) {
				monster[leader]._mFlags &= ~MFLAG_ALLOW_SPECIAL;
				monster[leader]._mmode = MM_SATTACK;
			}
		}
	} else if (monster[i]._uniqtype != 0) {
		if (UniqMonst[monster[i]._uniqtype - 1].mUnqAttr & 2) {
			for (j = 0; j < nummonsters; j++) {
				m = monstactive[j];
				if (monster[m].leaderflag == 1 && monster[m].leader == i) {
					if (monster[i]._msquelch > monster[m]._msquelch) {
						monster[m]._lastx = monster[i]._mx;
						monster[m]._lasty = monster[i]._my;
						monster[m]._msquelch = monster[i]._msquelch - 1;
					}
					if (monster[m]._mAi == AI_GARG) {
						if (monster[m]._mFlags & MFLAG_ALLOW_SPECIAL) {
							monster[m]._mFlags &= ~MFLAG_ALLOW_SPECIAL;
							monster[m]._mmode = MM_SATTACK;
						}
					}
				}
			}
		}
	}
}

BOOL M_CallWalk(int i, int md)
{
	int mdtemp;
	BOOL ok;

	mdtemp = md;
	ok = DirOK(i, md);
	if (random_(101, 2) != 0)
		ok = ok || (md = left[mdtemp], DirOK(i, md)) || (md = right[mdtemp], DirOK(i, md));
	else
		ok = ok || (md = right[mdtemp], DirOK(i, md)) || (md = left[mdtemp], DirOK(i, md));
	if (random_(102, 2) != 0)
		ok = ok
		    || (md = right[right[mdtemp]], DirOK(i, md))
		    || (md = left[left[mdtemp]], DirOK(i, md));
	else
		ok = ok
		    || (md = left[left[mdtemp]], DirOK(i, md))
		    || (md = right[right[mdtemp]], DirOK(i, md));
	if (ok)
		M_WalkDir(i, md);
	return ok;
}

BOOL M_PathWalk(int i)
{
	Sint8 path[MAX_PATH_LENGTH];
	BOOL(*Check)
	(int, int, int);

	/** Maps from walking path step to facing direction. */
	const Sint8 plr2monst[9] = { 0, 5, 3, 7, 1, 4, 6, 0, 2 };

	commitment((DWORD)i < MAXMONSTERS, i);

	Check = PosOkMonst3;
	if (!(monster[i]._mFlags & MFLAG_CAN_OPEN_DOOR))
		Check = PosOkMonst;

	if (FindPath(Check, i, monster[i]._mx, monster[i]._my, monster[i]._menemyx, monster[i]._menemyy, path)) {
		M_CallWalk(i, plr2monst[path[0]]);
		return TRUE;
	}

	return FALSE;
}

BOOL M_CallWalk2(int i, int md)
{
	BOOL ok;
	int mdtemp;

	mdtemp = md;
	ok = DirOK(i, md);          // Can we continue in the same direction
	if (random_(101, 2) != 0) { // Randomly go left or right
		ok = ok || (mdtemp = left[md], DirOK(i, left[md])) || (mdtemp = right[md], DirOK(i, right[md]));
	} else {
		ok = ok || (mdtemp = right[md], DirOK(i, right[md])) || (mdtemp = left[md], DirOK(i, left[md]));
	}

	if (ok)
		M_WalkDir(i, mdtemp);

	return ok;
}

BOOL M_DumbWalk(int i, int md)
{
	BOOL ok;
	ok = DirOK(i, md);
	if (ok)
		M_WalkDir(i, md);

	return ok;
}

BOOL M_RoundWalk(int i, int md, Sint32 *dir)
{
	int mdtemp;
	BOOL ok;
	if (*dir)
		md = left[left[md]];
	else
		md = right[right[md]];

	mdtemp = md;
	ok = DirOK(i, md);
	if (!ok) {
		if (*dir) {
			md = right[mdtemp];
			ok = DirOK(i, md) || (md = right[right[mdtemp]], DirOK(i, md));
		} else {
			md = left[mdtemp];
			ok = (DirOK(i, md) || (md = left[left[mdtemp]], DirOK(i, md)));
		}
	}
	if (ok) {
		M_WalkDir(i, md);
	} else {
		*dir = !*dir;
		ok = M_CallWalk(i, opposite[mdtemp]);
	}
	return ok;
}

void MAI_Zombie(int i)
{
	MonsterStruct *Monst;
	int mx, my, md;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	if (Monst->_mmode != MM_STAND) {
		return;
	}

	mx = Monst->_mx;
	my = Monst->_my;
	if (!(dFlags[mx][my] & BFLAG_VISIBLE)) {
		return;
	}

	if (random_(103, 100) < 2 * Monst->_mint + 10) {
		md = std::max(abs(mx - Monst->_menemyx), abs(my - Monst->_menemyy));
		if (md >= 2) {
			if (md >= 2 * Monst->_mint + 4) {
				md = Monst->_mdir;
				if (random_(104, 100) < 2 * Monst->_mint + 20) {
					md = random_(104, 8);
				}
				M_DumbWalk(i, md);
			} else {
				md = M_GetDir(i);
				M_CallWalk(i, md);
			}
		} else {
			M_StartAttack(i);
		}
	}

	if (Monst->_mmode == MM_STAND)
		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir];
}

void MAI_SkelSd(int i)
{
	MonsterStruct *Monst;
	int mx, my, x, y, md;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) {
		return;
	}

	mx = Monst->_mx;
	my = Monst->_my;
	x = mx - Monst->_menemyx;
	y = my - Monst->_menemyy;
	md = GetDirection(mx, my, Monst->_lastx, Monst->_lasty);
	Monst->_mdir = md;
	if (abs(x) >= 2 || abs(y) >= 2) {
		if (Monst->_mVar1 == MM_DELAY || (random_(106, 100) >= 35 - 4 * Monst->_mint)) {
			M_CallWalk(i, md);
		} else {
			M_StartDelay(i, 15 - 2 * Monst->_mint + random_(106, 10));
		}
	} else {
		if (Monst->_mVar1 == MM_DELAY || (random_(105, 100) < 2 * Monst->_mint + 20)) {
			M_StartAttack(i);
		} else {
			M_StartDelay(i, 2 * (5 - Monst->_mint) + random_(105, 10));
		}
	}

	if (Monst->_mmode == MM_STAND)
		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
}

BOOL MAI_Path(int i)
{
	MonsterStruct *Monst;
	BOOL clear;

	commitment((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	if (Monst->MType->mtype != MT_GOLEM) {
		if (Monst->_msquelch == 0)
			return FALSE;
		if (Monst->_mmode != MM_STAND)
			return FALSE;
		if (Monst->_mgoal != MGOAL_NORMAL && Monst->_mgoal != MGOAL_MOVE && Monst->_mgoal != MGOAL_ATTACK2)
			return FALSE;
		if (Monst->_mx == 1 && Monst->_my == 0)
			return FALSE;
	}

	clear = LineClearF1(
	    PosOkMonst2,
	    i,
	    Monst->_mx,
	    Monst->_my,
	    Monst->_menemyx,
	    Monst->_menemyy);
	if (!clear || Monst->_pathcount >= 5 && Monst->_pathcount < 8) {
		if (Monst->_mFlags & MFLAG_CAN_OPEN_DOOR)
			MonstCheckDoors(i);
		Monst->_pathcount++;
		if (Monst->_pathcount < 5)
			return FALSE;
		if (M_PathWalk(i))
			return TRUE;
	}

	if (Monst->MType->mtype != MT_GOLEM)
		Monst->_pathcount = 0;

	return FALSE;
}

void MAI_Snake(int i)
{
	MonsterStruct *Monst;
	int fx, fy, mx, my, md;
	int pnum;
	int tmp;

	assurance((DWORD)i < MAXMONSTERS, i);
	char pattern[6] = { 1, 1, 0, -1, -1, 0 };
	Monst = &monster[i];
	pnum = Monst->_menemy;
	if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0)
		return;
	fx = Monst->_menemyx;
	fy = Monst->_menemyy;
	mx = Monst->_mx - fx;
	my = Monst->_my - fy;
	md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty);
	Monst->_mdir = md;
	if (abs(mx) >= 2 || abs(my) >= 2) {
		if (abs(mx) < 3 && abs(my) < 3 && LineClearF1(PosOkMonst, i, Monst->_mx, Monst->_my, fx, fy) && Monst->_mVar1 != MM_CHARGE) {
			if (AddMissile(Monst->_mx, Monst->_my, fx, fy, md, MIS_RHINO, pnum, i, 0, 0) != -1) {
				PlayEffect(i, 0);
				dMonster[Monst->_mx][Monst->_my] = -(i + 1);
				Monst->_mmode = MM_CHARGE;
			}
		} else if (Monst->_mVar1 == MM_DELAY || random_(106, 100) >= 35 - 2 * Monst->_mint) {
			if (md + pattern[Monst->_mgoalvar1] < 0) {
				tmp = md + pattern[Monst->_mgoalvar1] + 8;
			} else {
				tmp = md + pattern[Monst->_mgoalvar1] - 8;
				if (md + pattern[Monst->_mgoalvar1] < 8)
					tmp = md + pattern[Monst->_mgoalvar1];
			}
			Monst->_mgoalvar1++;
			if (Monst->_mgoalvar1 > 5)
				Monst->_mgoalvar1 = 0;
			if (tmp - Monst->_mgoalvar2 < 0) {
				md = tmp - Monst->_mgoalvar2 + 8;
			} else if (tmp - Monst->_mgoalvar2 >= 8) {
				md = tmp - Monst->_mgoalvar2 - 8;
			} else
				md = tmp - Monst->_mgoalvar2;
			if (md > 0) {
				if (md < 4) {
					if (Monst->_mgoalvar2 + 1 < 0) {
						md = Monst->_mgoalvar2 + 9;
					} else if (Monst->_mgoalvar2 + 1 >= 8) {
						md = Monst->_mgoalvar2 - 7;
					} else
						md = Monst->_mgoalvar2 + 1;
					Monst->_mgoalvar2 = md;
				} else if (md == 4) {
					Monst->_mgoalvar2 = tmp;
				} else {
					if (Monst->_mgoalvar2 - 1 < 0) {
						md = Monst->_mgoalvar2 + 7;
					} else if (Monst->_mgoalvar2 - 1 >= 8) {
						md = Monst->_mgoalvar2 - 9;
					} else
						md = Monst->_mgoalvar2 - 1;
					Monst->_mgoalvar2 = md;
				}
			}
			if (!M_DumbWalk(i, Monst->_mgoalvar2))
				M_CallWalk2(i, Monst->_mdir);
		} else {
			M_StartDelay(i, 15 - Monst->_mint + random_(106, 10));
		}
	} else {
		if (Monst->_mVar1 == MM_DELAY
		    || Monst->_mVar1 == MM_CHARGE
		    || (random_(105, 100) < Monst->_mint + 20)) {
			M_StartAttack(i);
		} else
			M_StartDelay(i, 10 - Monst->_mint + random_(105, 10));
	}
	if (Monst->_mmode == MM_STAND)
		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir];
}

void MAI_Bat(int i)
{
	MonsterStruct *Monst;
	int md, v, pnum;
	int fx, fy, xd, yd;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	pnum = Monst->_menemy;
	if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) {
		return;
	}

	xd = Monst->_mx - Monst->_menemyx;
	yd = Monst->_my - Monst->_menemyy;
	md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty);
	Monst->_mdir = md;
	v = random_(107, 100);
	if (Monst->_mgoal == MGOAL_RETREAT) {
		if (!Monst->_mgoalvar1) {
			M_CallWalk(i, opposite[md]);
			Monst->_mgoalvar1++;
		} else {
			if (random_(108, 2) != 0)
				M_CallWalk(i, left[md]);
			else
				M_CallWalk(i, right[md]);
			Monst->_mgoal = MGOAL_NORMAL;
		}
		return;
	}

	fx = Monst->_menemyx;
	fy = Monst->_menemyy;
	if (Monst->MType->mtype == MT_GLOOM
	    && (abs(xd) >= 5 || abs(yd) >= 5)
	    && v < 4 * Monst->_mint + 33
	    && LineClearF1(PosOkMonst, i, Monst->_mx, Monst->_my, fx, fy)) {
		if (AddMissile(Monst->_mx, Monst->_my, fx, fy, md, MIS_RHINO, pnum, i, 0, 0) != -1) {
			dMonster[Monst->_mx][Monst->_my] = -(i + 1);
			Monst->_mmode = MM_CHARGE;
		}
	} else if (abs(xd) >= 2 || abs(yd) >= 2) {
		if (Monst->_mVar2 > 20 && v < Monst->_mint + 13
		    || (Monst->_mVar1 == MM_WALK || Monst->_mVar1 == MM_WALK2 || Monst->_mVar1 == MM_WALK3)
		        && Monst->_mVar2 == 0
		        && v < Monst->_mint + 63) {
			M_CallWalk(i, md);
		}
	} else if (v < 4 * Monst->_mint + 8) {
		M_StartAttack(i);
		Monst->_mgoal = MGOAL_RETREAT;
		Monst->_mgoalvar1 = 0;
		if (Monst->MType->mtype == MT_FAMILIAR) {
			AddMissile(Monst->_menemyx, Monst->_menemyy, Monst->_menemyx + 1, 0, -1, MIS_LIGHTNING, TARGET_PLAYERS, i, random_(109, 10) + 1, 0);
		}
	}

	if (Monst->_mmode == MM_STAND)
		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
}

void MAI_SkelBow(int i)
{
	MonsterStruct *Monst;
	int mx, my, md, v;
	BOOL walking;

	walking = FALSE;
	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) {
		return;
	}

	mx = Monst->_mx - Monst->_menemyx;
	my = Monst->_my - Monst->_menemyy;

	md = M_GetDir(i);
	Monst->_mdir = md;
	v = random_(110, 100);

	if (abs(mx) < 4 && abs(my) < 4) {
		if (Monst->_mVar2 > 20 && v < 2 * Monst->_mint + 13
		    || (Monst->_mVar1 == MM_WALK || Monst->_mVar1 == MM_WALK2 || Monst->_mVar1 == MM_WALK3)
		        && Monst->_mVar2 == 0
		        && v < 2 * Monst->_mint + 63) {
			walking = M_DumbWalk(i, opposite[md]);
		}
	}

	mx = Monst->_menemyx;
	my = Monst->_menemyy;
	if (!walking) {
		if (random_(110, 100) < 2 * Monst->_mint + 3) {
			if (LineClear(Monst->_mx, Monst->_my, mx, my))
				M_StartRAttack(i, MIS_ARROW, 4);
		}
	}

	if (Monst->_mmode == MM_STAND)
		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
}

void MAI_Fat(int i)
{
	MonsterStruct *Monst;
	int mx, my, md, v;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) {
		return;
	}

	mx = Monst->_mx - Monst->_menemyx;
	my = Monst->_my - Monst->_menemyy;
	md = M_GetDir(i);
	Monst->_mdir = md;
	v = random_(111, 100);
	if (abs(mx) >= 2 || abs(my) >= 2) {
		if (Monst->_mVar2 > 20 && v < 4 * Monst->_mint + 20
		    || (Monst->_mVar1 == MM_WALK || Monst->_mVar1 == MM_WALK2 || Monst->_mVar1 == MM_WALK3)
		        && Monst->_mVar2 == 0
		        && v < 4 * Monst->_mint + 70) {
			M_CallWalk(i, md);
		}
	} else if (v < 4 * Monst->_mint + 15) {
		M_StartAttack(i);
	} else if (v < 4 * Monst->_mint + 20) {
		M_StartSpAttack(i);
	}

	if (Monst->_mmode == MM_STAND)
		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
}

void MAI_Sneak(int i)
{
	MonsterStruct *Monst;
	int mx, my, md;
	int dist, v;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	if (Monst->_mmode == MM_STAND) {
		mx = Monst->_mx;
		my = Monst->_my;
		if (dLight[mx][my] != lightmax) {
			mx -= Monst->_menemyx;
			my -= Monst->_menemyy;

			md = M_GetDir(i);
			dist = 5 - Monst->_mint;
			if (Monst->_mVar1 == MM_GOTHIT) {
				Monst->_mgoal = MGOAL_RETREAT;
				Monst->_mgoalvar1 = 0;
			} else {
				if (abs(mx) >= dist + 3 || abs(my) >= dist + 3 || Monst->_mgoalvar1 > 8) {
					Monst->_mgoal = MGOAL_NORMAL;
					Monst->_mgoalvar1 = 0;
				}
			}
			if (Monst->_mgoal == MGOAL_RETREAT && !(Monst->_mFlags & MFLAG_NO_ENEMY)) {
				if (Monst->_mFlags & MFLAG_TARGETS_MONSTER)
					md = GetDirection(Monst->_mx, Monst->_my, monster[Monst->_menemy]._mx, monster[Monst->_menemy]._my);
				else
					md = GetDirection(Monst->_mx, Monst->_my, plr[Monst->_menemy]._pownerx, plr[Monst->_menemy]._pownery);
				md = opposite[md];
				if (Monst->MType->mtype == MT_UNSEEN) {
					if (random_(112, 2) != 0)
						md = left[md];
					else
						md = right[md];
				}
			}
			Monst->_mdir = md;
			v = random_(112, 100);
			if (abs(mx) < dist && abs(my) < dist && Monst->_mFlags & MFLAG_HIDDEN) {
				M_StartFadein(i, md, FALSE);
			} else {
				if ((abs(mx) >= dist + 1 || abs(my) >= dist + 1) && !(Monst->_mFlags & MFLAG_HIDDEN)) {
					M_StartFadeout(i, md, TRUE);
				} else {
					if (Monst->_mgoal == MGOAL_RETREAT
					    || (abs(mx) >= 2 || abs(my) >= 2) && (Monst->_mVar2 > 20 && v < 4 * Monst->_mint + 14 || (Monst->_mVar1 == MM_WALK || Monst->_mVar1 == MM_WALK2 || Monst->_mVar1 == MM_WALK3) && Monst->_mVar2 == 0 && v < 4 * Monst->_mint + 64)) {
						Monst->_mgoalvar1++;
						M_CallWalk(i, md);
					}
				}
			}
			if (Monst->_mmode == MM_STAND) {
				if (abs(mx) >= 2 || abs(my) >= 2 || v >= 4 * Monst->_mint + 10)
					Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
				else
					M_StartAttack(i);
			}
		}
	}
}

void MAI_Fireman(int i)
{
	int xd, yd;
	int md, pnum;
	int fx, fy;
	MonsterStruct *Monst;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	if (monster[i]._mmode != MM_STAND || Monst->_msquelch == 0)
		return;

	pnum = monster[i]._menemy;
	fx = monster[i]._menemyx;
	fy = monster[i]._menemyy;
	xd = monster[i]._mx - fx;
	yd = monster[i]._my - fy;

	md = M_GetDir(i);
	if (Monst->_mgoal == MGOAL_NORMAL) {
		if (LineClear(Monst->_mx, Monst->_my, fx, fy)
		    && AddMissile(Monst->_mx, Monst->_my, fx, fy, md, MIS_FIREMAN, pnum, i, 0, 0) != -1) {
			Monst->_mmode = MM_CHARGE;
			Monst->_mgoal = MGOAL_ATTACK2;
			Monst->_mgoalvar1 = 0;
		}
	} else if (Monst->_mgoal == MGOAL_ATTACK2) {
		if (Monst->_mgoalvar1 == 3) {
			Monst->_mgoal = MGOAL_NORMAL;
			M_StartFadeout(i, md, TRUE);
		} else if (LineClear(Monst->_mx, Monst->_my, fx, fy)) {
			M_StartRAttack(i, MIS_KRULL, 4);
			Monst->_mgoalvar1++;
		} else {
			M_StartDelay(i, random_(112, 10) + 5);
			Monst->_mgoalvar1++;
		}
	} else if (Monst->_mgoal == MGOAL_RETREAT) {
		M_StartFadein(i, md, FALSE);
		Monst->_mgoal = MGOAL_ATTACK2;
	}
	Monst->_mdir = md;
	random_(112, 100);
	if (Monst->_mmode != MM_STAND)
		return;

	if (abs(xd) < 2 && abs(yd) < 2 && Monst->_mgoal == MGOAL_NORMAL) {
		M_TryH2HHit(i, monster[i]._menemy, monster[i].mHit, monster[i].mMinDamage, monster[i].mMaxDamage);
		Monst->_mgoal = MGOAL_RETREAT;
		if (!M_CallWalk(i, opposite[md])) {
			M_StartFadein(i, md, FALSE);
			Monst->_mgoal = MGOAL_ATTACK2;
		}
	} else if (!M_CallWalk(i, md) && (Monst->_mgoal == MGOAL_NORMAL || Monst->_mgoal == MGOAL_RETREAT)) {
		M_StartFadein(i, md, FALSE);
		Monst->_mgoal = MGOAL_ATTACK2;
	}
}

void MAI_Fallen(int i)
{
	int x, y, xpos, ypos;
	int m, rad;
	MonsterStruct *Monst;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	if (Monst->_mgoal == MGOAL_ATTACK2) {
		if (Monst->_mgoalvar1 != 0)
			Monst->_mgoalvar1--;
		else
			Monst->_mgoal = MGOAL_NORMAL;
	}
	if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) {
		return;
	}

	if (Monst->_mgoal == MGOAL_RETREAT) {
		if (Monst->_mgoalvar1-- == 0) {
			Monst->_mgoal = MGOAL_NORMAL;
			M_StartStand(i, opposite[Monst->_mdir]);
		}
	}

	if (Monst->_mAnimFrame == Monst->_mAnimLen) {
		if (random_(113, 4) != 0) {
			return;
		}
		if (!(monster[i]._mFlags & MFLAG_NOHEAL)) { // CODEFIX: - change to Monst-> in devilutionx
			M_StartSpStand(i, Monst->_mdir);
			if (Monst->_mmaxhp - (2 * Monst->_mint + 2) >= Monst->_mhitpoints)
				Monst->_mhitpoints += 2 * Monst->_mint + 2;
			else
				Monst->_mhitpoints = Monst->_mmaxhp;
		}
		rad = 2 * Monst->_mint + 4;
		for (y = -rad; y <= rad; y++) {
			for (x = -rad; x <= rad; x++) {
				xpos = Monst->_mx + x;
				ypos = Monst->_my + y;
				if (y >= 0 && y < MAXDUNY && x >= 0 && x < MAXDUNX) {
					m = dMonster[xpos][ypos];
					if (m > 0) {
						m--;
						if (monster[m]._mAi == AI_FALLEN) {
							monster[m]._mgoal = MGOAL_ATTACK2;
							monster[m]._mgoalvar1 = 30 * Monst->_mint + 105;
						}
					}
				}
			}
		}
	} else if (Monst->_mgoal == MGOAL_RETREAT) {
		M_CallWalk(i, Monst->_mdir);
	} else if (Monst->_mgoal == MGOAL_ATTACK2) {
		xpos = Monst->_mx - Monst->_menemyx;
		ypos = Monst->_my - Monst->_menemyy;
		if (abs(xpos) < 2 && abs(ypos) < 2)
			M_StartAttack(i);
		else
			M_CallWalk(i, M_GetDir(i));
	} else
		MAI_SkelSd(i);
}

void MAI_Cleaver(int i)
{
	MonsterStruct *Monst;
	int x, y, mx, my, md;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) {
		return;
	}

	mx = Monst->_mx;
	my = Monst->_my;
	x = mx - Monst->_menemyx;
	y = my - Monst->_menemyy;

	md = GetDirection(mx, my, Monst->_lastx, Monst->_lasty);
	Monst->_mdir = md;

	if (abs(x) >= 2 || abs(y) >= 2)
		M_CallWalk(i, md);
	else
		M_StartAttack(i);

	if (Monst->_mmode == MM_STAND)
		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
}

void MAI_Round(int i, BOOL special)
{
	MonsterStruct *Monst;
	int fx, fy;
	int mx, my, md;
	int dist, v;

	assurance((DWORD)i < MAXMONSTERS, i);
	Monst = &monster[i];
	if (Monst->_mmode == MM_STAND && Monst->_msquelch != 0) {
		fy = Monst->_menemyy;
		fx = Monst->_menemyx;
		mx = Monst->_mx - fx;
		my = Monst->_my - fy;
		md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty);
		if (Monst->_msquelch < UCHAR_MAX)
			MonstCheckDoors(i);
		v = random_(114, 100);
		if ((abs(mx) >= 2 || abs(my) >= 2) && Monst->_msquelch == UCHAR_MAX && dTransVal[Monst->_mx][Monst->_my] == dTransVal[fx][fy]) {
			if (Monst->_mgoal == MGOAL_MOVE || (abs(mx) >= 4 || abs(my) >= 4) && random_(115, 4) == 0) {
				if (Monst->_mgoal != MGOAL_MOVE) {
					Monst->_mgoalvar1 = 0;
					Monst->_mgoalvar2 = random_(116, 2);
				}
				Monst->_mgoal = MGOAL_MOVE;
				if (abs(mx) > abs(my))
					dist = abs(mx);
				else
					dist = abs(my);
				if (Monst->_mgoalvar1++ >= 2 * dist && DirOK(i, md) || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) {
					Monst->_mgoal = MGOAL_NORMAL;
				} else if (!M_RoundWalk(i, md, &Monst->_mgoalvar2)) {
					M_StartDelay(i, random_(125, 10) + 10);
				}
			}
		} else
			Monst->_mgoal = MGOAL_NORMAL;
		if (Monst->_mgoal == MGOAL_NORMAL) {
			if (abs(mx) >= 2 || abs(my) >= 2) {
				if (Monst->_mVar2 > 20 && v < 2 * Monst->_mint + 28
				    || (Monst->_mVar1 == MM_WALK || Monst->_mVar1 == MM_WALK2 || Monst->_mVar1 == MM_WALK3)
				        && Monst->_mVar2 == 0
				        && v < 2 * Monst->_mint + 78) {
					M_CallWalk(i, md);
				}
			} else if (v < 2 * Monst->_mint + 23) {
				Monst->_mdir = md;
				if (special && Monst->_mhitpoints < (Monst->_mmaxhp >> 1) && random_(117, 2) != 0)
					M_StartSpAttack(i);
				else
					M_StartAttack(i);
			}
		}
		if (Monst->_mmode == MM_STAND)
			Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
	}
}

void MAI_GoatMc(int i)
{
	MAI_Round(i, TRUE);
}

void MAI_Ranged(int i, int missile_type, BOOL special)
{
	int md;
	int fx, fy, mx, my;
	MonsterStruct *Monst;

	assurance((DWORD)i < MAXMONSTERS, i);

	if (monster[i]._mmode != MM_STAND) {
		return;
	}

	Monst = &monster[i];
	if (Monst->_msquelch == UCHAR_MAX || Monst->_mFlags & MFLAG_TARGETS_MONSTER) {
		fx = Monst->_menemyx;
		fy = Monst->_menemyy;
		mx = Monst->_mx - fx;
		my = Monst->_my - fy;
		md = M_GetDir(i);
		if (Monst->_msquelch < UCHAR_MAX)
			MonstCheckDoors(i);
		Monst->_mdir = md;
		if (Monst->_mVar1 == MM_RATTACK) {
			M_StartDelay(i, random_(118, 20));
		} else if (abs(mx) < 4 && abs(my) < 4) {
			if (random_(119, 100) < 10 * (Monst->_mint + 7))
				M_CallWalk(i, opposite[md]);
		}
		if (Monst->_mmode == MM_STAND) {
			if (LineClear(Monst->_mx, Monst->_my, fx, fy)) {
				if (special)
					M_StartRSpAttack(i, missile_type, 4);
				else
					M_StartRAttack(i, missile_type, 4);
			} else {
				Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
			}
		}
	} else if (Monst->_msquelch != 0) {
		fx = Monst->_lastx;
		fy = Monst->_lasty;
		md = GetDirection(Monst->_mx, Monst->_my, fx, fy);
		M_CallWalk(i, md);
	}
}

void MAI_GoatBow(int i)
{
	MAI_Ranged(i, MIS_ARROW, FALSE);
}

void MAI_Succ(int i)
{
	MAI_Ranged(i, MIS_FLARE, FALSE);
}

void MAI_Lich(int i)
{
	MAI_Ranged(i, MIS_LICH, FALSE);
}

void MAI_ArchLich(int i)
{
	MAI_Ranged(i, MIS_ARCHLICH, FALSE);
}

void MAI_Psychorb(int i)
{
	MAI_Ranged(i, MIS_PSYCHORB, FALSE);
}

void MAI_Necromorb(int i)
{
	MAI_Ranged(i, MIS_NECROMORB, FALSE);
}

void MAI_AcidUniq(int i)
{
	MAI_Ranged(i, MIS_ACID, TRUE);
}

void MAI_Firebat(int i)
{
	MAI_Ranged(i, MIS_FIREBOLT, FALSE);
}

void MAI_Torchant(int i)
{
	MAI_Ranged(i, MIS_FIREBALL, FALSE);
}

void MAI_Scav(int i)
{
	BOOL done;
	int x, y;
	MonsterStruct *Monst;

	assurance((DWORD)i < MAXMONSTERS, i);
	Monst = &monster[i];
	done = FALSE;
	if (monster[i]._mmode != MM_STAND)
		return;
	if (Monst->_mhitpoints < (Monst->_mmaxhp >> 1) && Monst->_mgoal != MGOAL_HEALING) {
		if (Monst->leaderflag != 0) {
			monster[Monst->leader].packsize--;
			Monst->leaderflag = 0;
		}
		Monst->_mgoal = MGOAL_HEALING;
		Monst->_mgoalvar3 = 10;
	}
	if (Monst->_mgoal == MGOAL_HEALING && Monst->_mgoalvar3 != 0) {
		Monst->_mgoalvar3--;
		if (dDead[Monst->_mx][Monst->_my] != 0) {
			M_StartEat(i);
			if (!(Monst->_mFlags & MFLAG_NOHEAL)) {
				if (gbIsHellfire) {
					int mMaxHP = Monst->_mmaxhp; // BUGFIX use _mmaxhp or we loose health when difficulty isn't normal (fixed)
					Monst->_mhitpoints += mMaxHP >> 3;
					if (Monst->_mhitpoints > Monst->_mmaxhp)
						Monst->_mhitpoints = Monst->_mmaxhp;
					if (Monst->_mgoalvar3 <= 0 || Monst->_mhitpoints == Monst->_mmaxhp)
						dDead[Monst->_mx][Monst->_my] = 0;
				} else {
					Monst->_mhitpoints += 64;
				}
			}
			int targetHealth = Monst->_mmaxhp;
			if (!gbIsHellfire)
				targetHealth = (Monst->_mmaxhp >> 1) + (Monst->_mmaxhp >> 2);
			if (Monst->_mhitpoints >= targetHealth) {
				Monst->_mgoal = MGOAL_NORMAL;
				Monst->_mgoalvar1 = 0;
				Monst->_mgoalvar2 = 0;
			}
		} else {
			if (Monst->_mgoalvar1 == 0) {
				if (random_(120, 2) != 0) {
					for (y = -4; y <= 4 && !done; y++) {
						for (x = -4; x <= 4 && !done; x++) {
							// BUGFIX: incorrect check of offset against limits of the dungeon
							if (y < 0 || y >= MAXDUNY || x < 0 || x >= MAXDUNX)
								continue;
							done = dDead[Monst->_mx + x][Monst->_my + y] != 0
							    && LineClearF(
							        CheckNoSolid,
							        Monst->_mx,
							        Monst->_my,
							        Monst->_mx + x,
							        Monst->_my + y);
						}
					}
					x--;
					y--;
				} else {
					for (y = 4; y >= -4 && !done; y--) {
						for (x = 4; x >= -4 && !done; x--) {
							// BUGFIX: incorrect check of offset against limits of the dungeon
							if (y < 0 || y >= MAXDUNY || x < 0 || x >= MAXDUNX)
								continue;
							done = dDead[Monst->_mx + x][Monst->_my + y] != 0
							    && LineClearF(
							        CheckNoSolid,
							        Monst->_mx,
							        Monst->_my,
							        Monst->_mx + x,
							        Monst->_my + y);
						}
					}
					x++;
					y++;
				}
				if (done) {
					Monst->_mgoalvar1 = x + Monst->_mx + 1;
					Monst->_mgoalvar2 = y + Monst->_my + 1;
				}
			}
			if (Monst->_mgoalvar1) {
				x = Monst->_mgoalvar1 - 1;
				y = Monst->_mgoalvar2 - 1;
				Monst->_mdir = GetDirection(Monst->_mx, Monst->_my, x, y);
				M_CallWalk(i, Monst->_mdir);
			}
		}
	}

	if (Monst->_mmode == MM_STAND)
		MAI_SkelSd(i);
}

void MAI_Garg(int i)
{
	MonsterStruct *Monst;
	int mx, my, dx, dy, md;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	dx = Monst->_mx - Monst->_lastx;
	dy = Monst->_my - Monst->_lasty;
	md = M_GetDir(i);
	if (Monst->_msquelch != 0 && Monst->_mFlags & MFLAG_ALLOW_SPECIAL) {
		M_Enemy(i);
		mx = Monst->_mx - Monst->_menemyx;
		my = Monst->_my - Monst->_menemyy;
		if (abs(mx) < Monst->_mint + 2 && abs(my) < Monst->_mint + 2) {
			Monst->_mFlags &= ~MFLAG_ALLOW_SPECIAL;
		}
		return;
	}

	if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) {
		return;
	}

	if (Monst->_mhitpoints < (Monst->_mmaxhp >> 1))
		if (!(Monst->_mFlags & MFLAG_NOHEAL))
			Monst->_mgoal = MGOAL_RETREAT;
	if (Monst->_mgoal == MGOAL_RETREAT) {
		if (abs(dx) >= Monst->_mint + 2 || abs(dy) >= Monst->_mint + 2) {
			Monst->_mgoal = MGOAL_NORMAL;
			M_StartHeal(i);
		} else if (!M_CallWalk(i, opposite[md])) {
			Monst->_mgoal = MGOAL_NORMAL;
		}
	}
	MAI_Round(i, FALSE);
}

void MAI_RoundRanged(int i, int missile_type, BOOL checkdoors, int dam, int lessmissiles)
{
	MonsterStruct *Monst;
	int mx, my;
	int fx, fy;
	int md, dist, v;

	assurance((DWORD)i < MAXMONSTERS, i);
	Monst = &monster[i];
	if (Monst->_mmode == MM_STAND && Monst->_msquelch != 0) {
		fx = Monst->_menemyx;
		fy = Monst->_menemyy;
		mx = Monst->_mx - fx;
		my = Monst->_my - fy;
		md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty);
		if (checkdoors && Monst->_msquelch < UCHAR_MAX)
			MonstCheckDoors(i);
		v = random_(121, 10000);
		dist = std::max(abs(mx), abs(my));
		if (dist >= 2 && Monst->_msquelch == UCHAR_MAX && dTransVal[Monst->_mx][Monst->_my] == dTransVal[fx][fy]) {
			if (Monst->_mgoal == MGOAL_MOVE || (dist >= 3 && random_(122, 4 << lessmissiles) == 0)) {
				if (Monst->_mgoal != MGOAL_MOVE) {
					Monst->_mgoalvar1 = 0;
					Monst->_mgoalvar2 = random_(123, 2);
				}
				Monst->_mgoal = MGOAL_MOVE;
				if (Monst->_mgoalvar1++ >= 2 * dist && DirOK(i, md)) {
					Monst->_mgoal = MGOAL_NORMAL;
				} else if (v < (500 * (Monst->_mint + 1) >> lessmissiles)
				    && (LineClear(Monst->_mx, Monst->_my, fx, fy))) {
					M_StartRSpAttack(i, missile_type, dam);
				} else {
					M_RoundWalk(i, md, &Monst->_mgoalvar2);
				}
			}
		} else {
			Monst->_mgoal = MGOAL_NORMAL;
		}
		if (Monst->_mgoal == MGOAL_NORMAL) {
			if ((dist >= 3 && v < ((500 * (Monst->_mint + 2)) >> lessmissiles)
			        || v < ((500 * (Monst->_mint + 1)) >> lessmissiles))
			    && LineClear(Monst->_mx, Monst->_my, fx, fy)) {
				M_StartRSpAttack(i, missile_type, dam);
			} else if (dist >= 2) {
				v = random_(124, 100);
				if (v < 1000 * (Monst->_mint + 5)
				    || (Monst->_mVar1 == MM_WALK || Monst->_mVar1 == MM_WALK2 || Monst->_mVar1 == MM_WALK3) && Monst->_mVar2 == 0 && v < 1000 * (Monst->_mint + 8)) {
					M_CallWalk(i, md);
				}
			} else if (v < 1000 * (Monst->_mint + 6)) {
				Monst->_mdir = md;
				M_StartAttack(i);
			}
		}
		if (Monst->_mmode == MM_STAND) {
			M_StartDelay(i, random_(125, 10) + 5);
		}
	}
}

void MAI_Magma(int i)
{
	MAI_RoundRanged(i, MIS_MAGMABALL, TRUE, 4, 0);
}

void MAI_Storm(int i)
{
	MAI_RoundRanged(i, MIS_LIGHTCTRL2, TRUE, 4, 0);
}

void MAI_BoneDemon(int i)
{
	MAI_RoundRanged(i, MIS_BONEDEMON, TRUE, 4, 0);
}

void MAI_Acid(int i)
{
	MAI_RoundRanged(i, MIS_ACID, FALSE, 4, 1);
}

void MAI_Diablo(int i)
{
	MAI_RoundRanged(i, MIS_DIABAPOCA, FALSE, 40, 0);
}

void MAI_RR2(int i, int mistype, int dam)
{
	MonsterStruct *Monst;
	int mx, my, fx, fy;
	int dist, v, md;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	mx = Monst->_mx - Monst->_menemyx;
	my = Monst->_my - Monst->_menemyy;
	if (abs(mx) >= 5 || abs(my) >= 5) {
		MAI_SkelSd(i);
		return;
	}

	if (Monst->_mmode == MM_STAND && Monst->_msquelch != 0) {
		fx = Monst->_menemyx;
		fy = Monst->_menemyy;
		mx = Monst->_mx - fx;
		my = Monst->_my - fy;
		md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty);
		if (Monst->_msquelch < UCHAR_MAX)
			MonstCheckDoors(i);
		v = random_(121, 100);
		dist = std::max(abs(mx), abs(my));
		if (dist >= 2 && Monst->_msquelch == UCHAR_MAX && dTransVal[Monst->_mx][Monst->_my] == dTransVal[fx][fy]) {
			if (Monst->_mgoal == MGOAL_MOVE || dist >= 3) {
				if (Monst->_mgoal != MGOAL_MOVE) {
					Monst->_mgoalvar1 = 0;
					Monst->_mgoalvar2 = random_(123, 2);
				}
				Monst->_mgoal = MGOAL_MOVE;
				Monst->_mgoalvar3 = 4;
				if (Monst->_mgoalvar1++ < 2 * dist || !DirOK(i, md)) {
					if (v < 5 * (Monst->_mint + 16))
						M_RoundWalk(i, md, &Monst->_mgoalvar2);
				} else
					Monst->_mgoal = MGOAL_NORMAL;
			}
		} else
			Monst->_mgoal = MGOAL_NORMAL;
		if (Monst->_mgoal == MGOAL_NORMAL) {
			if ((dist >= 3 && v < 5 * (Monst->_mint + 2) || v < 5 * (Monst->_mint + 1) || Monst->_mgoalvar3 == 4) && LineClear(Monst->_mx, Monst->_my, fx, fy)) {
				M_StartRSpAttack(i, mistype, dam);
			} else if (dist >= 2) {
				v = random_(124, 100);
				if (v < 2 * (5 * Monst->_mint + 25)
				    || (Monst->_mVar1 == MM_WALK || Monst->_mVar1 == MM_WALK2 || Monst->_mVar1 == MM_WALK3)
				        && Monst->_mVar2 == 0
				        && v < 2 * (5 * Monst->_mint + 40)) {
					M_CallWalk(i, md);
				}
			} else {
				if (random_(124, 100) < 10 * (Monst->_mint + 4)) {
					Monst->_mdir = md;
					if (random_(124, 2) != 0)
						M_StartAttack(i);
					else
						M_StartRSpAttack(i, mistype, dam);
				}
			}
			Monst->_mgoalvar3 = 1;
		}
		if (Monst->_mmode == MM_STAND) {
			M_StartDelay(i, random_(125, 10) + 5);
		}
	}
}

void MAI_Mega(int i)
{
	MAI_RR2(i, MIS_FLAMEC, 0);
}

void MAI_Golum(int i)
{
	int mx, my, _mex, _mey;
	int md, j, k, _menemy;
	MonsterStruct *Monst;
	BOOL have_enemy, ok;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	if (Monst->_mx == 1 && Monst->_my == 0) {
		return;
	}

	if (Monst->_mmode == MM_DEATH
	    || Monst->_mmode == MM_SPSTAND
	    || (Monst->_mmode >= MM_WALK && Monst->_mmode <= MM_WALK3)) {
		return;
	}

	if (!(Monst->_mFlags & MFLAG_TARGETS_MONSTER))
		M_Enemy(i);

	have_enemy = !(monster[i]._mFlags & MFLAG_NO_ENEMY);

	if (Monst->_mmode == MM_ATTACK) {
		return;
	}

	_menemy = monster[i]._menemy;

	mx = monster[i]._mx;
	my = monster[i]._my;
	_mex = mx - monster[_menemy]._mfutx;
	_mey = my - monster[_menemy]._mfuty;
	md = GetDirection(mx, my, monster[_menemy]._mx, monster[_menemy]._my);
	monster[i]._mdir = md;
	if (abs(_mex) < 2 && abs(_mey) < 2 && have_enemy) {
		_menemy = monster[i]._menemy;
		monster[i]._menemyx = monster[_menemy]._mx;
		monster[i]._menemyy = monster[_menemy]._my;
		if (monster[_menemy]._msquelch == 0) {
			monster[_menemy]._msquelch = UCHAR_MAX;
			monster[monster[i]._menemy]._lastx = monster[i]._mx;
			monster[monster[i]._menemy]._lasty = monster[i]._my;
			for (j = 0; j < 5; j++) {
				for (k = 0; k < 5; k++) {
					_menemy = dMonster[monster[i]._mx + k - 2][monster[i]._my + j - 2];
					if (_menemy > 0)
						monster[_menemy - 1]._msquelch = UCHAR_MAX; // BUGFIX: should be `monster[_menemy-1]`, not monster[_menemy]. (fixed)
				}
			}
		}
		M_StartAttack(i);
		return;
	}

	if (have_enemy && MAI_Path(i))
		return;

	monster[i]._pathcount++;
	if (monster[i]._pathcount > 8)
		monster[i]._pathcount = 5;

	ok = M_CallWalk(i, plr[i]._pdir);
	if (ok)
		return;

	md = (md - 1) & 7;
	for (j = 0; j < 8 && !ok; j++) {
		md = (md + 1) & 7;
		ok = DirOK(i, md);
	}
	if (ok)
		M_WalkDir(i, md);
}

void MAI_SkelKing(int i)
{
	MonsterStruct *Monst;
	int mx, my, fx, fy, nx, ny;
	int dist, v, md;

	assurance((DWORD)i < MAXMONSTERS, i);
	Monst = &monster[i];
	if (Monst->_mmode == MM_STAND && Monst->_msquelch != 0) {
		fx = Monst->_menemyx;
		fy = Monst->_menemyy;
		mx = Monst->_mx - fx;
		my = Monst->_my - fy;
		md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty);
		if (Monst->_msquelch < UCHAR_MAX)
			MonstCheckDoors(i);
		v = random_(126, 100);
		dist = std::max(abs(mx), abs(my));
		if (dist >= 2 && Monst->_msquelch == UCHAR_MAX && dTransVal[Monst->_mx][Monst->_my] == dTransVal[fx][fy]) {
			if (Monst->_mgoal == MGOAL_MOVE || (abs(mx) >= 3 || abs(my) >= 3) && random_(127, 4) == 0) {
				if (Monst->_mgoal != MGOAL_MOVE) {
					Monst->_mgoalvar1 = 0;
					Monst->_mgoalvar2 = random_(128, 2);
				}
				Monst->_mgoal = MGOAL_MOVE;
				if (Monst->_mgoalvar1++ >= 2 * dist && DirOK(i, md) || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) {
					Monst->_mgoal = MGOAL_NORMAL;
				} else if (!M_RoundWalk(i, md, &Monst->_mgoalvar2)) {
					M_StartDelay(i, random_(125, 10) + 10);
				}
			}
		} else
			Monst->_mgoal = MGOAL_NORMAL;
		if (Monst->_mgoal == MGOAL_NORMAL) {
			if (!gbIsMultiplayer
			    && (dist >= 3 && v < 4 * Monst->_mint + 35 || v < 6)
			    && LineClear(Monst->_mx, Monst->_my, fx, fy)) {
				nx = Monst->_mx + offset_x[md];
				ny = Monst->_my + offset_y[md];
				if (PosOkMonst(i, nx, ny) && nummonsters < MAXMONSTERS) {
					M_SpawnSkel(nx, ny, md);
					M_StartSpStand(i, md);
				}
			} else {
				if (dist >= 2) {
					v = random_(129, 100);
					if (v >= Monst->_mint + 25
					    && (Monst->_mVar1 != MM_WALK && Monst->_mVar1 != MM_WALK2 && Monst->_mVar1 != MM_WALK3 || Monst->_mVar2 != 0 || (v >= Monst->_mint + 75))) {
						M_StartDelay(i, random_(130, 10) + 10);
					} else {
						M_CallWalk(i, md);
					}
				} else if (v < Monst->_mint + 20) {
					Monst->_mdir = md;
					M_StartAttack(i);
				}
			}
		}
		if (Monst->_mmode == MM_STAND)
			Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
	}
}

void MAI_Rhino(int i)
{
	MonsterStruct *Monst;
	int mx, my, fx, fy;
	int v, dist, md;

	assurance((DWORD)i < MAXMONSTERS, i);
	Monst = &monster[i];
	if (Monst->_mmode == MM_STAND && Monst->_msquelch != 0) {
		fx = Monst->_menemyx;
		fy = Monst->_menemyy;
		mx = Monst->_mx - fx;
		my = Monst->_my - fy;
		md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty);
		if (Monst->_msquelch < UCHAR_MAX)
			MonstCheckDoors(i);
		v = random_(131, 100);
		dist = std::max(abs(mx), abs(my));
		if (dist >= 2) {
			if (Monst->_mgoal == MGOAL_MOVE || dist >= 5 && random_(132, 4) != 0) {
				if (Monst->_mgoal != MGOAL_MOVE) {
					Monst->_mgoalvar1 = 0;
					Monst->_mgoalvar2 = random_(133, 2);
				}
				Monst->_mgoal = MGOAL_MOVE;
				if (Monst->_mgoalvar1++ >= 2 * dist || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) {
					Monst->_mgoal = MGOAL_NORMAL;
				} else if (!M_RoundWalk(i, md, &Monst->_mgoalvar2)) {
					M_StartDelay(i, random_(125, 10) + 10);
				}
			}
		} else
			Monst->_mgoal = MGOAL_NORMAL;
		if (Monst->_mgoal == MGOAL_NORMAL) {
			if (dist >= 5
			    && v < 2 * Monst->_mint + 43
			    && LineClearF1(PosOkMonst, i, Monst->_mx, Monst->_my, fx, fy)) {
				if (AddMissile(Monst->_mx, Monst->_my, fx, fy, md, MIS_RHINO, Monst->_menemy, i, 0, 0) != -1) {
					if (Monst->MData->snd_special)
						PlayEffect(i, 3);
					dMonster[Monst->_mx][Monst->_my] = -(i + 1);
					Monst->_mmode = MM_CHARGE;
				}
			} else {
				if (dist >= 2) {
					v = random_(134, 100);
					if (v >= 2 * Monst->_mint + 33
					    && (Monst->_mVar1 != MM_WALK && Monst->_mVar1 != MM_WALK2 && Monst->_mVar1 != MM_WALK3
					        || Monst->_mVar2
					        || v >= 2 * Monst->_mint + 83)) {
						M_StartDelay(i, random_(135, 10) + 10);
					} else {
						M_CallWalk(i, md);
					}
				} else if (v < 2 * Monst->_mint + 28) {
					Monst->_mdir = md;
					M_StartAttack(i);
				}
			}
		}
		if (Monst->_mmode == MM_STAND)
			Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir];
	}
}

void MAI_HorkDemon(int i)
{
	MonsterStruct *Monst;
	int fx, fy, mx, my, md, v, dist;

	if ((DWORD)i >= MAXMONSTERS) {
		return;
	}

	Monst = &monster[i];
	if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) {
		return;
	}

	fx = Monst->_menemyx;
	fy = Monst->_menemyy;
	mx = Monst->_mx - fx;
	my = Monst->_my - fy;
	md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty);

	if (Monst->_msquelch < 255) {
		MonstCheckDoors(i);
	}

	v = random_(131, 100);

	if (abs(mx) < 2 && abs(my) < 2) {
		Monst->_mgoal = 1;
	} 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 = 4;
		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 = 1;
		} 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 == 1 || Monst->_mVar1 == 2 || Monst->_mVar1 == 3) && Monst->_mVar2 == 0 && v < 2 * Monst->_mint + 83) {
				M_CallWalk(i, md);
			} else {
				M_StartDelay(i, random_(135, 10) + 10);
			}
		}
	}
	if (Monst->_mmode == MM_STAND) {
		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir];
	}
}

void MAI_Counselor(int i)
{
	int mx, my, fx, fy;
	int dist, md, v;
	MonsterStruct *Monst;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	if (Monst->_mmode == MM_STAND && Monst->_msquelch != 0) {
		fx = Monst->_menemyx;
		fy = Monst->_menemyy;
		mx = Monst->_mx - fx;
		my = Monst->_my - fy;
		md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty);
		if (Monst->_msquelch < UCHAR_MAX)
			MonstCheckDoors(i);
		v = random_(121, 100);
		if (Monst->_mgoal == MGOAL_RETREAT) {
			if (Monst->_mgoalvar1++ <= 3)
				M_CallWalk(i, opposite[md]);
			else {
				Monst->_mgoal = MGOAL_NORMAL;
				M_StartFadein(i, md, TRUE);
			}
		} else if (Monst->_mgoal == MGOAL_MOVE) {
			dist = std::max(abs(mx), abs(my));
			if (dist >= 2 && Monst->_msquelch == UCHAR_MAX && dTransVal[Monst->_mx][Monst->_my] == dTransVal[fx][fy]) {
				if (Monst->_mgoalvar1++ < 2 * dist || !DirOK(i, md)) {
					M_RoundWalk(i, md, &Monst->_mgoalvar2);
				} else {
					Monst->_mgoal = MGOAL_NORMAL;
					M_StartFadein(i, md, TRUE);
				}
			} else {
				Monst->_mgoal = MGOAL_NORMAL;
				M_StartFadein(i, md, TRUE);
			}
		} else if (Monst->_mgoal == MGOAL_NORMAL) {
			if (abs(mx) >= 2 || abs(my) >= 2) {
				if (v < 5 * (Monst->_mint + 10) && LineClear(Monst->_mx, Monst->_my, fx, fy)) {
					M_StartRAttack(i, counsmiss[Monst->_mint], Monst->mMinDamage + random_(77, Monst->mMaxDamage - Monst->mMinDamage + 1));
				} else if (random_(124, 100) < 30) {
					Monst->_mgoal = MGOAL_MOVE;
					Monst->_mgoalvar1 = 0;
					M_StartFadeout(i, md, FALSE);
				} else
					M_StartDelay(i, random_(105, 10) + 2 * (5 - Monst->_mint));
			} else {
				Monst->_mdir = md;
				if (Monst->_mhitpoints < (Monst->_mmaxhp >> 1)) {
					Monst->_mgoal = MGOAL_RETREAT;
					Monst->_mgoalvar1 = 0;
					M_StartFadeout(i, md, FALSE);
				} else if (Monst->_mVar1 == MM_DELAY
				    || random_(105, 100) < 2 * Monst->_mint + 20) {
					M_StartRAttack(i, -1, 0);
					AddMissile(Monst->_mx, Monst->_my, 0, 0, Monst->_mdir, MIS_FLASH, TARGET_PLAYERS, i, 4, 0);
					AddMissile(Monst->_mx, Monst->_my, 0, 0, Monst->_mdir, MIS_FLASH2, TARGET_PLAYERS, i, 4, 0);
				} else
					M_StartDelay(i, random_(105, 10) + 2 * (5 - Monst->_mint));
			}
		}
		if (Monst->_mmode == MM_STAND) {
			M_StartDelay(i, random_(125, 10) + 5);
		}
	}
}

void MAI_Garbud(int i)
{
	int _mx, _my, md;
	MonsterStruct *Monst;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	if (Monst->_mmode != MM_STAND) {
		return;
	}

	_mx = Monst->_mx;
	_my = Monst->_my;
	md = M_GetDir(i);

	if (Monst->mtalkmsg >= TEXT_GARBUD1
	    && Monst->mtalkmsg <= TEXT_GARBUD3
	    && !(dFlags[_mx][_my] & BFLAG_VISIBLE)
	    && Monst->_mgoal == MGOAL_TALKING) {
		Monst->_mgoal = MGOAL_INQUIRING;
		switch (Monst->mtalkmsg) {
		case TEXT_GARBUD1:
			Monst->mtalkmsg = TEXT_GARBUD2;
			break;
		case TEXT_GARBUD2:
			Monst->mtalkmsg = TEXT_GARBUD3;
			break;
		case TEXT_GARBUD3:
			Monst->mtalkmsg = TEXT_GARBUD4;
			break;
		}
	}

	if (dFlags[_mx][_my] & BFLAG_VISIBLE) {
		if (Monst->mtalkmsg == TEXT_GARBUD4) {
			if (!effect_is_playing(USFX_GARBUD4) && Monst->_mgoal == MGOAL_TALKING) {
				Monst->_mgoal = MGOAL_NORMAL;
				Monst->_msquelch = UCHAR_MAX;
				Monst->mtalkmsg = 0;
			}
		}
	}

	if (Monst->_mgoal == MGOAL_NORMAL || Monst->_mgoal == MGOAL_MOVE)
		MAI_Round(i, TRUE);

	monster[i]._mdir = md;

	if (Monst->_mmode == MM_STAND)
		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
}

void MAI_Zhar(int i)
{
	int mx, my, md;
	MonsterStruct *Monst;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	if (monster[i]._mmode != MM_STAND) {
		return;
	}

	mx = Monst->_mx;
	my = Monst->_my;
	md = M_GetDir(i);
	if (Monst->mtalkmsg == TEXT_ZHAR1 && !(dFlags[mx][my] & BFLAG_VISIBLE) && Monst->_mgoal == MGOAL_TALKING) {
		Monst->mtalkmsg = TEXT_ZHAR2;
		Monst->_mgoal = MGOAL_INQUIRING;
	}

	if (dFlags[mx][my] & BFLAG_VISIBLE) {
		if (Monst->mtalkmsg == TEXT_ZHAR2) {
			if (!effect_is_playing(USFX_ZHAR2) && Monst->_mgoal == MGOAL_TALKING) {
				Monst->_msquelch = UCHAR_MAX;
				Monst->mtalkmsg = 0;
				Monst->_mgoal = MGOAL_NORMAL;
			}
		}
	}

	if (Monst->_mgoal == MGOAL_NORMAL || Monst->_mgoal == MGOAL_RETREAT || Monst->_mgoal == MGOAL_MOVE)
		MAI_Counselor(i);

	Monst->_mdir = md;

	if (monster[i]._mmode == MM_STAND)
		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
}

void MAI_SnotSpil(int i)
{
	int mx, my, md;
	MonsterStruct *Monst;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	if (monster[i]._mmode != MM_STAND) {
		return;
	}

	mx = Monst->_mx;
	my = Monst->_my;
	md = M_GetDir(i);

	if (Monst->mtalkmsg == TEXT_BANNER10 && !(dFlags[mx][my] & BFLAG_VISIBLE) && Monst->_mgoal == MGOAL_TALKING) {
		Monst->mtalkmsg = TEXT_BANNER11;
		Monst->_mgoal = MGOAL_INQUIRING;
	}

	if (Monst->mtalkmsg == TEXT_BANNER11 && quests[Q_LTBANNER]._qvar1 == 3) {
		Monst->mtalkmsg = 0;
		Monst->_mgoal = MGOAL_NORMAL;
	}

	if (dFlags[mx][my] & BFLAG_VISIBLE) {
		if (Monst->mtalkmsg == TEXT_BANNER12) {
			if (!effect_is_playing(USFX_SNOT3) && Monst->_mgoal == MGOAL_TALKING) {
				ObjChangeMap(setpc_x, setpc_y, setpc_x + setpc_w + 1, setpc_y + setpc_h + 1);
				quests[Q_LTBANNER]._qvar1 = 3;
				RedoPlayerVision();
				Monst->_msquelch = UCHAR_MAX;
				Monst->mtalkmsg = 0;
				Monst->_mgoal = MGOAL_NORMAL;
			}
		}
		if (quests[Q_LTBANNER]._qvar1 == 3) {
			if (Monst->_mgoal == MGOAL_NORMAL || Monst->_mgoal == MGOAL_ATTACK2)
				MAI_Fallen(i);
		}
	}

	Monst->_mdir = md;

	if (monster[i]._mmode == MM_STAND)
		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
}

void MAI_Lazurus(int i)
{
	int mx, my, md;
	MonsterStruct *Monst;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	if (monster[i]._mmode != MM_STAND) {
		return;
	}

	mx = Monst->_mx;
	my = Monst->_my;
	md = M_GetDir(i);
	if (dFlags[mx][my] & BFLAG_VISIBLE) {
		if (!gbIsMultiplayer) {
			if (Monst->mtalkmsg == TEXT_VILE13 && Monst->_mgoal == MGOAL_INQUIRING && plr[myplr]._px == 35 && plr[myplr]._py == 46) {
				PlayInGameMovie("gendata\\fprst3.smk");
				Monst->_mmode = MM_TALK;
				quests[Q_BETRAYER]._qvar1 = 5;
			}

			if (Monst->mtalkmsg == TEXT_VILE13 && !effect_is_playing(USFX_LAZ1) && Monst->_mgoal == MGOAL_TALKING) {
				ObjChangeMapResync(1, 18, 20, 24);
				RedoPlayerVision();
				quests[Q_BETRAYER]._qvar1 = 6;
				Monst->_mgoal = MGOAL_NORMAL;
				Monst->_msquelch = UCHAR_MAX;
				Monst->mtalkmsg = 0;
			}
		}

		if (gbIsMultiplayer && Monst->mtalkmsg == TEXT_VILE13 && Monst->_mgoal == MGOAL_INQUIRING && quests[Q_BETRAYER]._qvar1 <= 3) {
			Monst->_mmode = MM_TALK;
		}
	}

	if (Monst->_mgoal == MGOAL_NORMAL || Monst->_mgoal == MGOAL_RETREAT || Monst->_mgoal == MGOAL_MOVE) {
		if (!gbIsMultiplayer && quests[Q_BETRAYER]._qvar1 == 4 && Monst->mtalkmsg == 0) { // Fix save games affected by teleport bug
			ObjChangeMapResync(1, 18, 20, 24);
			RedoPlayerVision();
			quests[Q_BETRAYER]._qvar1 = 6;
		}
		Monst->mtalkmsg = 0;
		MAI_Counselor(i);
	}

	Monst->_mdir = md;

	if (monster[i]._mmode == MM_STAND || monster[i]._mmode == MM_TALK)
		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
}

void MAI_Lazhelp(int i)
{
	int _mx, _my;
	volatile int md; // BUGFIX: very questionable volatile
	MonsterStruct *Monst;

	assurance((DWORD)i < MAXMONSTERS, i);
	if (monster[i]._mmode != MM_STAND)
		return;

	Monst = &monster[i];
	_mx = Monst->_mx;
	_my = Monst->_my;
	md = M_GetDir(i);

	if (dFlags[_mx][_my] & BFLAG_VISIBLE) {
		if (!gbIsMultiplayer) {
			if (quests[Q_BETRAYER]._qvar1 <= 5) {
				Monst->_mgoal = MGOAL_INQUIRING;
			} else {
				Monst->_mgoal = MGOAL_NORMAL;
				Monst->mtalkmsg = 0;
			}
		} else
			Monst->_mgoal = MGOAL_NORMAL;
	}
	if (Monst->_mgoal == MGOAL_NORMAL)
		MAI_Succ(i);
	Monst->_mdir = md;
	if (monster[i]._mmode == MM_STAND)
		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
}

void MAI_Lachdanan(int i)
{
	int _mx, _my, md;
	MonsterStruct *Monst;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	if (monster[i]._mmode != MM_STAND) {
		return;
	}

	_mx = Monst->_mx;
	_my = Monst->_my;
	md = M_GetDir(i);

	if (Monst->mtalkmsg == TEXT_VEIL9 && !(dFlags[_mx][_my] & BFLAG_VISIBLE) && monster[i]._mgoal == MGOAL_TALKING) {
		Monst->mtalkmsg = TEXT_VEIL10;
		monster[i]._mgoal = MGOAL_INQUIRING;
	}

	if (dFlags[_mx][_my] & BFLAG_VISIBLE) {
		if (Monst->mtalkmsg == TEXT_VEIL11) {
			if (!effect_is_playing(USFX_LACH3) && Monst->_mgoal == MGOAL_TALKING) {
				Monst->mtalkmsg = 0;
				quests[Q_VEIL]._qactive = QUEST_DONE;
				M_StartKill(i, -1);
			}
		}
	}

	Monst->_mdir = md;

	if (monster[i]._mmode == MM_STAND)
		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
}

void MAI_Warlord(int i)
{
	MonsterStruct *Monst;
	int mx, my, md;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	if (monster[i]._mmode != MM_STAND) {
		return;
	}

	mx = Monst->_mx;
	my = Monst->_my;
	md = M_GetDir(i);
	if (dFlags[mx][my] & BFLAG_VISIBLE) {
		if (Monst->mtalkmsg == TEXT_WARLRD9 && Monst->_mgoal == MGOAL_INQUIRING)
			Monst->_mmode = MM_TALK;
		if (Monst->mtalkmsg == TEXT_WARLRD9 && !effect_is_playing(USFX_WARLRD1) && Monst->_mgoal == MGOAL_TALKING) {
			Monst->_msquelch = UCHAR_MAX;
			Monst->mtalkmsg = 0;
			Monst->_mgoal = MGOAL_NORMAL;
		}
	}

	if (Monst->_mgoal == MGOAL_NORMAL)
		MAI_SkelSd(i);

	Monst->_mdir = md;

	if (monster[i]._mmode == MM_STAND || monster[i]._mmode == MM_TALK)
		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir];
}

void DeleteMonsterList()
{
	int i;
	for (i = 0; i < MAX_PLRS; i++) {
		if (monster[i]._mDelFlag) {
			monster[i]._mx = 1;
			monster[i]._my = 0;
			monster[i]._mfutx = 0;
			monster[i]._mfuty = 0;
			monster[i]._moldx = 0;
			monster[i]._moldy = 0;
			monster[i]._mDelFlag = FALSE;
		}
	}

	i = MAX_PLRS;
	while (i < nummonsters) {
		if (monster[monstactive[i]]._mDelFlag) {
			DeleteMonster(i);
			i = 0; // TODO: check if this should be MAX_PLRS.
		} else {
			i++;
		}
	}
}

void ProcessMonsters()
{
	int i, mi, mx, my, _menemy;
	BOOL raflag;
	MonsterStruct *Monst;

	DeleteMonsterList();

	assert((DWORD)nummonsters <= MAXMONSTERS);
	for (i = 0; i < nummonsters; i++) {
		mi = monstactive[i];
		Monst = &monster[mi];
		raflag = FALSE;
		if (gbIsMultiplayer) {
			SetRndSeed(Monst->_mAISeed);
			Monst->_mAISeed = AdvanceRndSeed();
		}
		if (!(monster[mi]._mFlags & MFLAG_NOHEAL) && Monst->_mhitpoints < Monst->_mmaxhp && Monst->_mhitpoints >> 6 > 0) {
			if (Monst->mLevel > 1) {
				Monst->_mhitpoints += Monst->mLevel >> 1;
			} else {
				Monst->_mhitpoints += Monst->mLevel;
			}
		}
		mx = Monst->_mx;
		my = Monst->_my;

		if (dFlags[mx][my] & BFLAG_VISIBLE && Monst->_msquelch == 0) {
			if (Monst->MType->mtype == MT_CLEAVER) {
				PlaySFX(USFX_CLEAVER);
			}
			if (Monst->MType->mtype == MT_NAKRUL) {
				if (gbCowQuest) {
					PlaySFX(USFX_NAKRUL6);
				} else {
					if (IsUberRoomOpened)
						PlaySFX(USFX_NAKRUL4);
					else
						PlaySFX(USFX_NAKRUL5);
				}
			}
			if (Monst->MType->mtype == MT_DEFILER)
				PlaySFX(USFX_DEFILER8);
			M_Enemy(mi);
		}

		if (Monst->_mFlags & MFLAG_TARGETS_MONSTER) {
			_menemy = Monst->_menemy;
			assurance((DWORD)_menemy < MAXMONSTERS, _menemy);
			Monst->_lastx = monster[Monst->_menemy]._mfutx;
			Monst->_menemyx = Monst->_lastx;
			Monst->_lasty = monster[Monst->_menemy]._mfuty;
			Monst->_menemyy = Monst->_lasty;
		} else {
			_menemy = Monst->_menemy;
			assurance((DWORD)_menemy < MAX_PLRS, _menemy);
			Monst->_menemyx = plr[Monst->_menemy]._pfutx;
			Monst->_menemyy = plr[Monst->_menemy]._pfuty;
			if (dFlags[mx][my] & BFLAG_VISIBLE) {
				Monst->_msquelch = UCHAR_MAX;
				Monst->_lastx = plr[Monst->_menemy]._pfutx;
				Monst->_lasty = plr[Monst->_menemy]._pfuty;
			} else if (Monst->_msquelch != 0 && Monst->MType->mtype != MT_DIABLO) { /// BUGFIX: change '_mAi' to 'MType->mtype'
				Monst->_msquelch--;
			}
		}
		do {
			if (!(Monst->_mFlags & MFLAG_SEARCH)) {
				AiProc[Monst->_mAi](mi);
			} else if (!MAI_Path(mi)) {
				AiProc[Monst->_mAi](mi);
			}
			switch (Monst->_mmode) {
			case MM_STAND:
				raflag = M_DoStand(mi);
				break;
			case MM_WALK:
			case MM_WALK2:
			case MM_WALK3:
				raflag = M_DoWalk(mi, Monst->_mmode);
				break;
			case MM_ATTACK:
				raflag = M_DoAttack(mi);
				break;
			case MM_GOTHIT:
				raflag = M_DoGotHit(mi);
				break;
			case MM_DEATH:
				raflag = M_DoDeath(mi);
				break;
			case MM_SATTACK:
				raflag = M_DoSAttack(mi);
				break;
			case MM_FADEIN:
				raflag = M_DoFadein(mi);
				break;
			case MM_FADEOUT:
				raflag = M_DoFadeout(mi);
				break;
			case MM_RATTACK:
				raflag = M_DoRAttack(mi);
				break;
			case MM_SPSTAND:
				raflag = M_DoSpStand(mi);
				break;
			case MM_RSPATTACK:
				raflag = M_DoRSpAttack(mi);
				break;
			case MM_DELAY:
				raflag = M_DoDelay(mi);
				break;
			case MM_CHARGE:
				raflag = FALSE;
				break;
			case MM_STONE:
				raflag = M_DoStone(mi);
				break;
			case MM_HEAL:
				raflag = M_DoHeal(mi);
				break;
			case MM_TALK:
				raflag = M_DoTalk(mi);
				break;
			}
			if (raflag) {
				GroupUnity(mi);
			}
		} while (raflag);
		if (Monst->_mmode != MM_STONE) {
			Monst->_mAnimCnt++;
			if (!(Monst->_mFlags & MFLAG_ALLOW_SPECIAL) && Monst->_mAnimCnt >= Monst->_mAnimDelay) {
				Monst->_mAnimCnt = 0;
				if (Monst->_mFlags & MFLAG_LOCK_ANIMATION) {
					Monst->_mAnimFrame--;
					if (Monst->_mAnimFrame == 0) {
						Monst->_mAnimFrame = Monst->_mAnimLen;
					}
				} else {
					Monst->_mAnimFrame++;
					if (Monst->_mAnimFrame > Monst->_mAnimLen) {
						Monst->_mAnimFrame = 1;
					}
				}
			}
		}
	}

	DeleteMonsterList();
}

void FreeMonsters()
{
	int mtype;
	int i, j;

	for (i = 0; i < nummtypes; i++) {
		mtype = Monsters[i].mtype;
		for (j = 0; j < 6; j++) {
			if (animletter[j] != 's' || monsterdata[mtype].has_special) {
				MemFreeDbg(Monsters[i].Anims[j].CMem);
			}
		}
	}

	FreeMissiles2();
}

BOOL DirOK(int i, int mdir)
{
	int fx, fy;
	int x, y;
	int mcount, mi;

	commitment((DWORD)i < MAXMONSTERS, i);
	fx = monster[i]._mx + offset_x[mdir];
	fy = monster[i]._my + offset_y[mdir];
	if (fy < 0 || fy >= MAXDUNY || fx < 0 || fx >= MAXDUNX || !PosOkMonst(i, fx, fy))
		return FALSE;
	if (mdir == DIR_E) {
		if (SolidLoc(fx, fy + 1) || dFlags[fx][fy + 1] & BFLAG_MONSTLR)
			return FALSE;
	} else if (mdir == DIR_W) {
		if (SolidLoc(fx + 1, fy) || dFlags[fx + 1][fy] & BFLAG_MONSTLR)
			return FALSE;
	} else if (mdir == DIR_N) {
		if (SolidLoc(fx + 1, fy) || SolidLoc(fx, fy + 1))
			return FALSE;
	} else if (mdir == DIR_S)
		if (SolidLoc(fx - 1, fy) || SolidLoc(fx, fy - 1))
			return FALSE;
	if (monster[i].leaderflag == 1) {
		if (abs(fx - monster[monster[i].leader]._mfutx) >= 4
		    || abs(fy - monster[monster[i].leader]._mfuty) >= 4) {
			return FALSE;
		}
		return TRUE;
	}
	if (monster[i]._uniqtype == 0 || !(UniqMonst[monster[i]._uniqtype - 1].mUnqAttr & 2))
		return TRUE;
	mcount = 0;
	for (x = fx - 3; x <= fx + 3; x++) {
		for (y = fy - 3; y <= fy + 3; y++) {
			if (y < 0 || y >= MAXDUNY || x < 0 || x >= MAXDUNX)
				continue;
			mi = dMonster[x][y];
			if (mi < 0)
				mi = -mi;
			if (mi != 0)
				mi--;
			// BUGFIX: should only run pack member check if mi was non-zero prior to executing the body of the above if-statement.
			if (monster[mi].leaderflag == 1
			    && monster[mi].leader == i
			    && monster[mi]._mfutx == x
			    && monster[mi]._mfuty == y) {
				mcount++;
			}
		}
	}
	return mcount == monster[i].packsize;
}

BOOL PosOkMissile(int x, int y)
{
	return !nMissileTable[dPiece[x][y]] && !(dFlags[x][y] & BFLAG_MONSTLR);
}

BOOL CheckNoSolid(int x, int y)
{
	return nSolidTable[dPiece[x][y]] == FALSE;
}

BOOL LineClearF(BOOL (*Clear)(int, int), int x1, int y1, int x2, int y2)
{
	int xorg, yorg;
	int dx, dy;
	int d;
	int xincD, yincD, dincD, dincH;
	int tmp;
	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 (gnDifficulty == DIFF_NIGHTMARE) {
			minHP = 3 * minHP + hpBonusNightmare;
			maxHP = 3 * maxHP + hpBonusNightmare;
		} else if (gnDifficulty == 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 (gnDifficulty != DIFF_HELL)
			res = monsterdata[mt].mMagicRes;
		else
			res = monsterdata[mt].mMagicRes2;
		res = res & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING | IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING);
		if (!res) {
			strcpy(tempstr, "No magic resistance");
			AddPanelString(tempstr, TRUE);
		} else {
			if (res & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING)) {
				strcpy(tempstr, "Resists: ");
				if (res & RESIST_MAGIC)
					strcat(tempstr, "Magic ");
				if (res & RESIST_FIRE)
					strcat(tempstr, "Fire ");
				if (res & RESIST_LIGHTNING)
					strcat(tempstr, "Lightning ");
				tempstr[strlen(tempstr) - 1] = '\0';
				AddPanelString(tempstr, TRUE);
			}
			if (res & (IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING)) {
				strcpy(tempstr, "Immune: ");
				if (res & IMMUNE_MAGIC)
					strcat(tempstr, "Magic ");
				if (res & IMMUNE_FIRE)
					strcat(tempstr, "Fire ");
				if (res & IMMUNE_LIGHTNING)
					strcat(tempstr, "Lightning ");
				tempstr[strlen(tempstr) - 1] = '\0';
				AddPanelString(tempstr, TRUE);
			}
		}
	}
	pinfoflag = TRUE;
}

void PrintUniqueHistory()
{
	int res;

	if (sgOptions.Gameplay.bShowMonsterType) {
		sprintf(tempstr, "Type: %s", GetMonsterTypeText(*monster[pcursmonst].MData));
		AddPanelString(tempstr, TRUE);
	}

	res = monster[pcursmonst].mMagicRes & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING | IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING);
	if (!res) {
		strcpy(tempstr, "No resistances");
		AddPanelString(tempstr, TRUE);
		strcpy(tempstr, "No Immunities");
	} else {
		if (res & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING))
			strcpy(tempstr, "Some Magic Resistances");
		else
			strcpy(tempstr, "No resistances");
		AddPanelString(tempstr, TRUE);
		if (res & (IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING)) {
			strcpy(tempstr, "Some Magic Immunities");
		} else {
			strcpy(tempstr, "No Immunities");
		}
	}
	AddPanelString(tempstr, TRUE);
	pinfoflag = TRUE;
}

void MissToMonst(int i, int x, int y)
{
	int oldx, oldy;
	int newx, newy;
	int m, pnum;
	MissileStruct *Miss;
	MonsterStruct *Monst;

	assurance((DWORD)i < MAXMISSILES, i);

	Miss = &missile[i];
	m = Miss->_misource;

	assurance((DWORD)m < MAXMONSTERS, m);

	Monst = &monster[m];
	oldx = Miss->_mix;
	oldy = Miss->_miy;
	dMonster[x][y] = m + 1;
	Monst->_mdir = Miss->_mimfnum;
	Monst->_mx = x;
	Monst->_my = y;
	M_StartStand(m, Monst->_mdir);
	if (Monst->MType->mtype < MT_INCIN || Monst->MType->mtype > MT_HELLBURN) {
		if (!(Monst->_mFlags & MFLAG_TARGETS_MONSTER))
			M_StartHit(m, -1, 0);
		else
			M2MStartHit(m, -1, 0);
	} else {
		M_StartFadein(m, Monst->_mdir, FALSE);
	}

	if (!(Monst->_mFlags & MFLAG_TARGETS_MONSTER)) {
		pnum = dPlayer[oldx][oldy] - 1;
		if (dPlayer[oldx][oldy] > 0) {
			if (Monst->MType->mtype != MT_GLOOM && (Monst->MType->mtype < MT_INCIN || Monst->MType->mtype > MT_HELLBURN)) {
				M_TryH2HHit(m, dPlayer[oldx][oldy] - 1, 500, Monst->mMinDamage2, Monst->mMaxDamage2);
				if (pnum == dPlayer[oldx][oldy] - 1 && (Monst->MType->mtype < MT_NSNAKE || Monst->MType->mtype > MT_GSNAKE)) {
					if (plr[pnum]._pmode != PM_GOTHIT && plr[pnum]._pmode != PM_DEATH)
						StartPlrHit(pnum, 0, TRUE);
					newx = oldx + offset_x[Monst->_mdir];
					newy = oldy + offset_y[Monst->_mdir];
					if (PosOkPlayer(pnum, newx, newy)) {
						plr[pnum]._px = newx;
						plr[pnum]._py = newy;
						FixPlayerLocation(pnum, plr[pnum]._pdir);
						FixPlrWalkTags(pnum);
						dPlayer[newx][newy] = pnum + 1;
						SetPlayerOld(pnum);
					}
				}
			}
		}
	} else {
		if (dMonster[oldx][oldy] > 0) {
			if (Monst->MType->mtype != MT_GLOOM && (Monst->MType->mtype < MT_INCIN || Monst->MType->mtype > MT_HELLBURN)) {
				M_TryM2MHit(m, dMonster[oldx][oldy] - 1, 500, Monst->mMinDamage2, Monst->mMaxDamage2);
				if (Monst->MType->mtype < MT_NSNAKE || Monst->MType->mtype > MT_GSNAKE) {
					newx = oldx + offset_x[Monst->_mdir];
					newy = oldy + offset_y[Monst->_mdir];
					if (PosOkMonst(dMonster[oldx][oldy] - 1, newx, newy)) {
						m = dMonster[oldx][oldy];
						dMonster[newx][newy] = m;
						dMonster[oldx][oldy] = 0;
						m--;
						monster[m]._mx = newx;
						monster[m]._mfutx = newx;
						monster[m]._my = newy;
						monster[m]._mfuty = newy;
					}
				}
			}
		}
	}
}

BOOL PosOkMonst(int i, int x, int y)
{
	int oi, mi, j;
	BOOL ret, fire;

	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;
}

BOOLEAN monster_posok(int i, int x, int y)
{
	int mi, j;
	BOOLEAN 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, mi, j;
	BOOL ret, fire;

	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 j, oi, objtype, mi;
	bool ret, fire, isdoor;

	ret = TRUE;
	isdoor = FALSE;

	if (ret && dObject[x][y] != 0) {
		oi = dObject[x][y] > 0 ? dObject[x][y] - 1 : -(dObject[x][y] + 1);
		objtype = object[oi]._otype;
		isdoor = objtype == OBJ_L1LDOOR || objtype == OBJ_L1RDOOR
		    || objtype == OBJ_L2LDOOR || objtype == OBJ_L2RDOOR
		    || objtype == OBJ_L3LDOOR || objtype == OBJ_L3RDOOR;
		if (object[oi]._oSolidFlag && !isdoor) {
			ret = FALSE;
		}
	}
	if (ret) {
		ret = (!SolidLoc(x, y) || isdoor) && dPlayer[x][y] == 0 && dMonster[x][y] == 0;
	}
	if (ret)
		ret = monster_posok(i, x, y);

	return ret;
}

BOOL IsSkel(int mt)
{
	return mt >= MT_WSKELAX && mt <= MT_XSKELAX
	    || mt >= MT_WSKELBW && mt <= MT_XSKELBW
	    || mt >= MT_WSKELSD && mt <= MT_XSKELSD;
}

BOOL IsGoat(int mt)
{
	return mt >= MT_NGOATMC && mt <= MT_GGOATMC
	    || mt >= MT_NGOATBW && mt <= MT_GGOATBW;
}

int M_SpawnSkel(int x, int y, int dir)
{
	int i, j, skeltypes, skel;

	j = 0;
	for (i = 0; i < nummtypes; i++) {
		if (IsSkel(Monsters[i].mtype))
			j++;
	}

	if (j) {
		skeltypes = random_(136, j);
		j = 0;
		for (i = 0; i < nummtypes && j <= skeltypes; i++) {
			if (IsSkel(Monsters[i].mtype))
				j++;
		}
		skel = AddMonster(x, y, dir, i - 1, TRUE);
		if (skel != -1)
			M_StartSpStand(skel, dir);

		return skel;
	}

	return -1;
}

void ActivateSpawn(int i, int x, int y, int dir)
{
	dMonster[x][y] = i + 1;
	monster[i]._mx = x;
	monster[i]._my = y;
	monster[i]._mfutx = x;
	monster[i]._mfuty = y;
	monster[i]._moldx = x;
	monster[i]._moldy = y;
	M_StartSpStand(i, dir);
}

BOOL SpawnSkeleton(int ii, int x, int y)
{
	int dx, dy, xx, yy, dir, j, k, rs;
	BOOL savail;
	int monstok[3][3];

	if (ii == -1)
		return FALSE;

	if (PosOkMonst(-1, x, y)) {
		dir = GetDirection(x, y, x, y);
		ActivateSpawn(ii, x, y, dir);
		return TRUE;
	}

	savail = FALSE;
	yy = 0;
	for (j = y - 1; j <= y + 1; j++) {
		xx = 0;
		for (k = x - 1; k <= x + 1; k++) {
			monstok[xx][yy] = PosOkMonst(-1, k, j);
			savail |= monstok[xx][yy];
			xx++;
		}
		yy++;
	}
	if (!savail) {
		return FALSE;
	}

	rs = random_(137, 15) + 1;
	xx = 0;
	yy = 0;
	while (rs > 0) {
		if (monstok[xx][yy])
			rs--;
		if (rs > 0) {
			xx++;
			if (xx == 3) {
				xx = 0;
				yy++;
				if (yy == 3)
					yy = 0;
			}
		}
	}

	dx = x - 1 + xx;
	dy = y - 1 + yy;
	dir = GetDirection(dx, dy, x, y);
	ActivateSpawn(ii, dx, dy, dir);

	return TRUE;
}

int PreSpawnSkeleton()
{
	int i, j, skeltypes, skel;

	j = 0;

	for (i = 0; i < nummtypes; i++) {
		if (IsSkel(Monsters[i].mtype))
			j++;
	}

	if (j) {
		skeltypes = random_(136, j);
		j = 0;
		for (i = 0; i < nummtypes && j <= skeltypes; i++) {
			if (IsSkel(Monsters[i].mtype))
				j++;
		}
		skel = AddMonster(0, 0, 0, i - 1, FALSE);
		if (skel != -1)
			M_StartStand(skel, 0);

		return skel;
	}

	return -1;
}

void TalktoMonster(int i)
{
	MonsterStruct *Monst;
	int pnum, itm;

	assurance((DWORD)i < MAXMONSTERS, i);

	Monst = &monster[i];
	pnum = Monst->_menemy;
	Monst->_mmode = MM_TALK;
	if (Monst->_mAi == AI_SNOTSPIL || Monst->_mAi == AI_LACHDAN) {
		if (QuestStatus(Q_LTBANNER) && quests[Q_LTBANNER]._qvar1 == 2 && PlrHasItem(pnum, IDI_BANNER, &itm)) {
			RemoveInvItem(pnum, itm);
			quests[Q_LTBANNER]._qactive = QUEST_DONE;
			Monst->mtalkmsg = TEXT_BANNER12;
			Monst->_mgoal = MGOAL_INQUIRING;
		}
		if (QuestStatus(Q_VEIL) && Monst->mtalkmsg >= TEXT_VEIL9) {
			if (PlrHasItem(pnum, IDI_GLDNELIX, &itm)) {
				RemoveInvItem(pnum, itm);
				Monst->mtalkmsg = TEXT_VEIL11;
				Monst->_mgoal = MGOAL_INQUIRING;
			}
		}
	}
}

void SpawnGolum(int i, int x, int y, int mi)
{
	assurance((DWORD)i < MAXMONSTERS, i);

	dMonster[x][y] = i + 1;
	monster[i]._mx = x;
	monster[i]._my = y;
	monster[i]._mfutx = x;
	monster[i]._mfuty = y;
	monster[i]._moldx = x;
	monster[i]._moldy = y;
	monster[i]._pathcount = 0;
	monster[i]._mmaxhp = 2 * (320 * missile[mi]._mispllvl + plr[i]._pMaxMana / 3);
	monster[i]._mhitpoints = monster[i]._mmaxhp;
	monster[i].mArmorClass = 25;
	monster[i].mHit = 5 * (missile[mi]._mispllvl + 8) + 2 * plr[i]._pLevel;
	monster[i].mMinDamage = 2 * (missile[mi]._mispllvl + 4);
	monster[i].mMaxDamage = 2 * (missile[mi]._mispllvl + 8);
	monster[i]._mFlags |= MFLAG_GOLEM;
	M_StartSpStand(i, 0);
	M_Enemy(i);
	if (i == myplr) {
		NetSendCmdGolem(
		    monster[i]._mx,
		    monster[i]._my,
		    monster[i]._mdir,
		    monster[i]._menemy,
		    monster[i]._mhitpoints,
		    currlevel);
	}
}

BOOL CanTalkToMonst(int m)
{
	commitment((DWORD)m < MAXMONSTERS, m);

	if (monster[m]._mgoal == MGOAL_INQUIRING) {
		return TRUE;
	}

	return monster[m]._mgoal == MGOAL_TALKING;
}

BOOL CheckMonsterHit(int m, BOOL *ret)
{
	commitment((DWORD)m < MAXMONSTERS, m);

	if (monster[m]._mAi == AI_GARG && monster[m]._mFlags & MFLAG_ALLOW_SPECIAL) {
		monster[m]._mFlags &= ~MFLAG_ALLOW_SPECIAL;
		monster[m]._mmode = MM_SATTACK;
		*ret = TRUE;
		return TRUE;
	}

	if (monster[m].MType->mtype >= MT_COUNSLR && monster[m].MType->mtype <= MT_ADVOCATE) {
		if (monster[m]._mgoal != MGOAL_NORMAL) {
			*ret = FALSE;
			return TRUE;
		}
	}

	return FALSE;
}

int encode_enemy(int m)
{
	if (monster[m]._mFlags & MFLAG_TARGETS_MONSTER)
		return monster[m]._menemy + MAX_PLRS;
	else
		return monster[m]._menemy;
}

void decode_enemy(int m, int enemy)
{
	if (enemy < MAX_PLRS) {
		monster[m]._mFlags &= ~MFLAG_TARGETS_MONSTER;
		monster[m]._menemy = enemy;
		monster[m]._menemyx = plr[enemy]._pfutx;
		monster[m]._menemyy = plr[enemy]._pfuty;
	} else {
		monster[m]._mFlags |= MFLAG_TARGETS_MONSTER;
		enemy -= MAX_PLRS;
		monster[m]._menemy = enemy;
		monster[m]._menemyx = monster[enemy]._mfutx;
		monster[m]._menemyy = monster[enemy]._mfuty;
	}
}

DEVILUTION_END_NAMESPACE
