1 /**
2  * @file monster.cpp
3  *
4  * Implementation of monster functionality, AI, actions, spawning, loading, etc.
5  */
6 #include <algorithm>
7 
8 #include "all.h"
9 #include "options.h"
10 #include "../3rdParty/Storm/Source/storm.h"
11 
12 DEVILUTION_BEGIN_NAMESPACE
13 
14 /** Tracks which missile files are already loaded */
15 int MissileFileFlag;
16 
17 // BUGFIX: replace monstkills[MAXMONSTERS] with monstkills[NUM_MTYPES].
18 /** Tracks the total number of monsters killed per monster_id. */
19 int monstkills[MAXMONSTERS];
20 int monstactive[MAXMONSTERS];
21 int nummonsters;
22 BOOLEAN sgbSaveSoundOn;
23 MonsterStruct monster[MAXMONSTERS];
24 int totalmonsters;
25 CMonster Monsters[MAX_LVLMTYPES];
26 int monstimgtot;
27 int uniquetrans;
28 int nummtypes;
29 
30 /** Maps from monster intelligence factor to missile type. */
31 const BYTE counsmiss[4] = { MIS_FIREBOLT, MIS_CBOLT, MIS_LIGHTCTRL, MIS_FIREBALL };
32 
33 /* data */
34 
35 /** Maps from monster walk animation frame num to monster velocity. */
36 int MWVel[24][3] = {
37 	{ 256, 512, 1024 },
38 	{ 128, 256, 512 },
39 	{ 85, 170, 341 },
40 	{ 64, 128, 256 },
41 	{ 51, 102, 204 },
42 	{ 42, 85, 170 },
43 	{ 36, 73, 146 },
44 	{ 32, 64, 128 },
45 	{ 28, 56, 113 },
46 	{ 26, 51, 102 },
47 	{ 23, 46, 93 },
48 	{ 21, 42, 85 },
49 	{ 19, 39, 78 },
50 	{ 18, 36, 73 },
51 	{ 17, 34, 68 },
52 	{ 16, 32, 64 },
53 	{ 15, 30, 60 },
54 	{ 14, 28, 57 },
55 	{ 13, 26, 54 },
56 	{ 12, 25, 51 },
57 	{ 12, 24, 48 },
58 	{ 11, 23, 46 },
59 	{ 11, 22, 44 },
60 	{ 10, 21, 42 }
61 };
62 /** Maps from monster action to monster animation letter. */
63 char animletter[7] = "nwahds";
64 /** Maps from direction to a left turn from the direction. */
65 int left[8] = { 7, 0, 1, 2, 3, 4, 5, 6 };
66 /** Maps from direction to a right turn from the direction. */
67 int right[8] = { 1, 2, 3, 4, 5, 6, 7, 0 };
68 /** Maps from direction to the opposite direction. */
69 int opposite[8] = { 4, 5, 6, 7, 0, 1, 2, 3 };
70 /** Maps from direction to delta X-offset. */
71 int offset_x[8] = { 1, 0, -1, -1, -1, 0, 1, 1 };
72 /** Maps from direction to delta Y-offset. */
73 int offset_y[8] = { 1, 1, 1, 0, -1, -1, -1, 0 };
74 
75 /** Maps from monster AI ID to monster AI function. */
76 void (*AiProc[])(int i) = {
77 	&MAI_Zombie,
78 	&MAI_Fat,
79 	&MAI_SkelSd,
80 	&MAI_SkelBow,
81 	&MAI_Scav,
82 	&MAI_Rhino,
83 	&MAI_GoatMc,
84 	&MAI_GoatBow,
85 	&MAI_Fallen,
86 	&MAI_Magma,
87 	&MAI_SkelKing,
88 	&MAI_Bat,
89 	&MAI_Garg,
90 	&MAI_Cleaver,
91 	&MAI_Succ,
92 	&MAI_Sneak,
93 	&MAI_Storm,
94 	&MAI_Fireman,
95 	&MAI_Garbud,
96 	&MAI_Acid,
97 	&MAI_AcidUniq,
98 	&MAI_Golum,
99 	&MAI_Zhar,
100 	&MAI_SnotSpil,
101 	&MAI_Snake,
102 	&MAI_Counselor,
103 	&MAI_Mega,
104 	&MAI_Diablo,
105 	&MAI_Lazurus,
106 	&MAI_Lazhelp,
107 	&MAI_Lachdanan,
108 	&MAI_Warlord,
109 	&MAI_Firebat,
110 	&MAI_Torchant,
111 	&MAI_HorkDemon,
112 	&MAI_Lich,
113 	&MAI_ArchLich,
114 	&MAI_Psychorb,
115 	&MAI_Necromorb,
116 	&MAI_BoneDemon
117 };
118 
InitMonsterTRN(int monst,BOOL special)119 void InitMonsterTRN(int monst, BOOL special)
120 {
121 	BYTE *f;
122 	int i, n, j;
123 
124 	f = Monsters[monst].trans_file;
125 	for (i = 0; i < 256; i++) {
126 		if (*f == 255) {
127 			*f = 0;
128 		}
129 		f++;
130 	}
131 
132 	n = special ? 6 : 5;
133 	for (i = 0; i < n; i++) {
134 		if (i != 1 || Monsters[monst].mtype < MT_COUNSLR || Monsters[monst].mtype > MT_ADVOCATE) {
135 			for (j = 0; j < 8; j++) {
136 				Cl2ApplyTrans(
137 				    Monsters[monst].Anims[i].Data[j],
138 				    Monsters[monst].trans_file,
139 				    Monsters[monst].Anims[i].Frames);
140 			}
141 		}
142 	}
143 }
144 
InitLevelMonsters()145 void InitLevelMonsters()
146 {
147 	int i;
148 
149 	nummtypes = 0;
150 	monstimgtot = 0;
151 	MissileFileFlag = 0;
152 
153 	for (i = 0; i < MAX_LVLMTYPES; i++) {
154 		Monsters[i].mPlaceFlags = 0;
155 	}
156 
157 	ClrAllMonsters();
158 	nummonsters = 0;
159 	totalmonsters = MAXMONSTERS;
160 
161 	for (i = 0; i < MAXMONSTERS; i++) {
162 		monstactive[i] = i;
163 	}
164 
165 	uniquetrans = 0;
166 }
167 
AddMonsterType(_monster_id type,int placeflag)168 int AddMonsterType(_monster_id type, int placeflag)
169 {
170 	BOOL done = FALSE;
171 	int i;
172 
173 	for (i = 0; i < nummtypes && !done; i++) {
174 		done = Monsters[i].mtype == type;
175 	}
176 
177 	i--;
178 
179 	if (!done) {
180 		i = nummtypes;
181 		nummtypes++;
182 		Monsters[i].mtype = type;
183 		monstimgtot += monsterdata[type].mImage;
184 		InitMonsterGFX(i);
185 		InitMonsterSND(i);
186 	}
187 
188 	Monsters[i].mPlaceFlags |= placeflag;
189 	return i;
190 }
191 
GetLevelMTypes()192 void GetLevelMTypes()
193 {
194 	int i;
195 
196 	// this array is merged with skeltypes down below.
197 	_monster_id typelist[MAXMONSTERS];
198 	_monster_id skeltypes[NUM_MTYPES];
199 
200 	int minl; // min level
201 	int maxl; // max level
202 	char mamask;
203 	const int numskeltypes = 19;
204 
205 	int nt; // number of types
206 
207 	if (gbIsSpawn)
208 		mamask = 1; // monster availability mask
209 	else
210 		mamask = 3; // monster availability mask
211 
212 	AddMonsterType(MT_GOLEM, PLACE_SPECIAL);
213 	if (currlevel == 16) {
214 		AddMonsterType(MT_ADVOCATE, PLACE_SCATTER);
215 		AddMonsterType(MT_RBLACK, PLACE_SCATTER);
216 		AddMonsterType(MT_DIABLO, PLACE_SPECIAL);
217 		return;
218 	}
219 
220 	if (currlevel == 18)
221 		AddMonsterType(MT_HORKSPWN, PLACE_SCATTER);
222 	if (currlevel == 19) {
223 		AddMonsterType(MT_HORKSPWN, PLACE_SCATTER);
224 		AddMonsterType(MT_HORKDMN, PLACE_UNIQUE);
225 	}
226 	if (currlevel == 20)
227 		AddMonsterType(MT_DEFILER, PLACE_UNIQUE);
228 	if (currlevel == 24) {
229 		AddMonsterType(MT_ARCHLICH, PLACE_SCATTER);
230 		AddMonsterType(MT_NAKRUL, PLACE_SPECIAL);
231 	}
232 
233 	if (!setlevel) {
234 		if (QuestStatus(Q_BUTCHER))
235 			AddMonsterType(MT_CLEAVER, PLACE_SPECIAL);
236 		if (QuestStatus(Q_GARBUD))
237 			AddMonsterType(UniqMonst[UMT_GARBUD].mtype, PLACE_UNIQUE);
238 		if (QuestStatus(Q_ZHAR))
239 			AddMonsterType(UniqMonst[UMT_ZHAR].mtype, PLACE_UNIQUE);
240 		if (QuestStatus(Q_LTBANNER))
241 			AddMonsterType(UniqMonst[UMT_SNOTSPIL].mtype, PLACE_UNIQUE);
242 		if (QuestStatus(Q_VEIL))
243 			AddMonsterType(UniqMonst[UMT_LACHDAN].mtype, PLACE_UNIQUE);
244 		if (QuestStatus(Q_WARLORD))
245 			AddMonsterType(UniqMonst[UMT_WARLORD].mtype, PLACE_UNIQUE);
246 
247 		if (gbIsMultiplayer && currlevel == quests[Q_SKELKING]._qlevel) {
248 
249 			AddMonsterType(MT_SKING, PLACE_UNIQUE);
250 
251 			nt = 0;
252 			for (i = MT_WSKELAX; i <= MT_WSKELAX + numskeltypes; i++) {
253 				if (IsSkel(i)) {
254 					minl = 15 * monsterdata[i].mMinDLvl / 30 + 1;
255 					maxl = 15 * monsterdata[i].mMaxDLvl / 30 + 1;
256 
257 					if (currlevel >= minl && currlevel <= maxl) {
258 						if (MonstAvailTbl[i] & mamask) {
259 							skeltypes[nt++] = (_monster_id)i;
260 						}
261 					}
262 				}
263 			}
264 			AddMonsterType(skeltypes[random_(88, nt)], PLACE_SCATTER);
265 		}
266 
267 		nt = 0;
268 		for (i = MT_NZOMBIE; i < NUM_MTYPES; i++) {
269 			minl = 15 * monsterdata[i].mMinDLvl / 30 + 1;
270 			maxl = 15 * monsterdata[i].mMaxDLvl / 30 + 1;
271 
272 			if (currlevel >= minl && currlevel <= maxl) {
273 				if (MonstAvailTbl[i] & mamask) {
274 					typelist[nt++] = (_monster_id)i;
275 				}
276 			}
277 		}
278 
279 #ifdef _DEBUG
280 		if (monstdebug) {
281 			for (i = 0; i < debugmonsttypes; i++)
282 				AddMonsterType(DebugMonsters[i], PLACE_SCATTER);
283 		} else
284 #endif
285 		{
286 
287 			while (nt > 0 && nummtypes < MAX_LVLMTYPES && monstimgtot < 4000) {
288 				for (i = 0; i < nt;) {
289 					if (monsterdata[typelist[i]].mImage > 4000 - monstimgtot) {
290 						typelist[i] = typelist[--nt];
291 						continue;
292 					}
293 
294 					i++;
295 				}
296 
297 				if (nt != 0) {
298 					i = random_(88, nt);
299 					AddMonsterType(typelist[i], PLACE_SCATTER);
300 					typelist[i] = typelist[--nt];
301 				}
302 			}
303 		}
304 
305 	} else {
306 		if (setlvlnum == SL_SKELKING) {
307 			AddMonsterType(MT_SKING, PLACE_UNIQUE);
308 		}
309 	}
310 }
311 
InitMonsterGFX(int monst)312 void InitMonsterGFX(int monst)
313 {
314 	int mtype, anim, i;
315 	char strBuff[256];
316 	BYTE *celBuf;
317 
318 	mtype = Monsters[monst].mtype;
319 
320 	for (anim = 0; anim < 6; anim++) {
321 		int frames = monsterdata[mtype].Frames[anim];
322 		if (gbIsHellfire && mtype == MT_DIABLO && anim == 3)
323 			frames = 2;
324 
325 		if ((animletter[anim] != 's' || monsterdata[mtype].has_special) && frames > 0) {
326 			sprintf(strBuff, monsterdata[mtype].GraphicType, animletter[anim]);
327 
328 			celBuf = LoadFileInMem(strBuff, NULL);
329 			Monsters[monst].Anims[anim].CMem = celBuf;
330 
331 			if (Monsters[monst].mtype != MT_GOLEM || (animletter[anim] != 's' && animletter[anim] != 'd')) {
332 
333 				for (i = 0; i < 8; i++) {
334 					Monsters[monst].Anims[anim].Data[i] = CelGetFrameStart(celBuf, i);
335 				}
336 			} else {
337 				for (i = 0; i < 8; i++) {
338 					Monsters[monst].Anims[anim].Data[i] = celBuf;
339 				}
340 			}
341 		}
342 
343 		// TODO: either the AnimStruct members have wrong naming or the MonsterData ones it seems
344 		Monsters[monst].Anims[anim].Frames = frames;
345 		Monsters[monst].Anims[anim].Rate = monsterdata[mtype].Rate[anim];
346 	}
347 
348 	Monsters[monst].width = monsterdata[mtype].width;
349 	Monsters[monst].width2 = (monsterdata[mtype].width - 64) >> 1;
350 	Monsters[monst].mMinHP = monsterdata[mtype].mMinHP;
351 	Monsters[monst].mMaxHP = monsterdata[mtype].mMaxHP;
352 	if (!gbIsHellfire && mtype == MT_DIABLO) {
353 		Monsters[monst].mMinHP -= 2000;
354 		Monsters[monst].mMaxHP -= 2000;
355 	}
356 	Monsters[monst].has_special = monsterdata[mtype].has_special;
357 	Monsters[monst].mAFNum = monsterdata[mtype].mAFNum;
358 	Monsters[monst].MData = &monsterdata[mtype];
359 
360 	if (monsterdata[mtype].has_trans) {
361 		Monsters[monst].trans_file = LoadFileInMem(monsterdata[mtype].TransFile, NULL);
362 		InitMonsterTRN(monst, monsterdata[mtype].has_special);
363 		MemFreeDbg(Monsters[monst].trans_file);
364 	}
365 
366 	if (mtype >= MT_NMAGMA && mtype <= MT_WMAGMA && !(MissileFileFlag & 1)) {
367 		MissileFileFlag |= 1;
368 		LoadMissileGFX(MFILE_MAGBALL);
369 	}
370 	if (mtype >= MT_STORM && mtype <= MT_MAEL && !(MissileFileFlag & 2)) {
371 		MissileFileFlag |= 2;
372 		LoadMissileGFX(MFILE_THINLGHT);
373 	}
374 	if (mtype == MT_SUCCUBUS && !(MissileFileFlag & 4)) {
375 		MissileFileFlag |= 4;
376 		LoadMissileGFX(MFILE_FLARE);
377 		LoadMissileGFX(MFILE_FLAREEXP);
378 	}
379 	if (mtype >= MT_INCIN && mtype <= MT_HELLBURN && !(MissileFileFlag & 8)) {
380 		MissileFileFlag |= 8;
381 		LoadMissileGFX(MFILE_KRULL);
382 	}
383 	if (mtype == MT_SNOWWICH && !(MissileFileFlag & 0x20)) {
384 		MissileFileFlag |= 0x20;
385 		LoadMissileGFX(MFILE_SCUBMISB);
386 		LoadMissileGFX(MFILE_SCBSEXPB);
387 	}
388 	if (mtype == MT_HLSPWN && !(MissileFileFlag & 0x40)) {
389 		MissileFileFlag |= 0x40;
390 		LoadMissileGFX(MFILE_SCUBMISD);
391 		LoadMissileGFX(MFILE_SCBSEXPD);
392 	}
393 	if (mtype == MT_SOLBRNR && !(MissileFileFlag & 0x80)) {
394 		MissileFileFlag |= 0x80;
395 		LoadMissileGFX(MFILE_SCUBMISC);
396 		LoadMissileGFX(MFILE_SCBSEXPC);
397 	}
398 	if (mtype >= MT_INCIN && mtype <= MT_HELLBURN && !(MissileFileFlag & 8)) {
399 		MissileFileFlag |= 8;
400 		LoadMissileGFX(MFILE_KRULL);
401 	}
402 	if ((mtype >= MT_NACID && mtype <= MT_XACID || mtype == MT_SPIDLORD) && !(MissileFileFlag & 0x10)) {
403 		MissileFileFlag |= 0x10;
404 		LoadMissileGFX(MFILE_ACIDBF);
405 		LoadMissileGFX(MFILE_ACIDSPLA);
406 		LoadMissileGFX(MFILE_ACIDPUD);
407 	}
408 	if (mtype == MT_LICH && !(MissileFileFlag & 0x100)) {
409 		MissileFileFlag |= 0x100u;
410 		LoadMissileGFX(MFILE_LICH);
411 		LoadMissileGFX(MFILE_EXORA1);
412 	}
413 	if (mtype == MT_ARCHLICH && !(MissileFileFlag & 0x200)) {
414 		MissileFileFlag |= 0x200u;
415 		LoadMissileGFX(MFILE_ARCHLICH);
416 		LoadMissileGFX(MFILE_EXYEL2);
417 	}
418 	if ((mtype == MT_PSYCHORB || mtype == MT_BONEDEMN) && !(MissileFileFlag & 0x400)) {
419 		MissileFileFlag |= 0x400u;
420 		LoadMissileGFX(MFILE_BONEDEMON);
421 	}
422 	if (mtype == MT_NECRMORB && !(MissileFileFlag & 0x800)) {
423 		MissileFileFlag |= 0x800u;
424 		LoadMissileGFX(MFILE_NECROMORB);
425 		LoadMissileGFX(MFILE_EXRED3);
426 	}
427 	if (mtype == MT_PSYCHORB && !(MissileFileFlag & 0x1000)) {
428 		MissileFileFlag |= 0x1000u;
429 		LoadMissileGFX(MFILE_EXBL2);
430 	}
431 	if (mtype == MT_BONEDEMN && !(MissileFileFlag & 0x2000)) {
432 		MissileFileFlag |= 0x2000u;
433 		LoadMissileGFX(MFILE_EXBL3);
434 	}
435 	if (mtype == MT_DIABLO) {
436 		LoadMissileGFX(MFILE_FIREPLAR);
437 	}
438 }
439 
ClearMVars(int i)440 void ClearMVars(int i)
441 {
442 	monster[i]._mVar1 = 0;
443 	monster[i]._mVar2 = 0;
444 	monster[i]._mVar3 = 0;
445 	monster[i]._mVar4 = 0;
446 	monster[i]._mVar5 = 0;
447 	monster[i]._mVar6 = 0;
448 	monster[i]._mVar7 = 0;
449 	monster[i]._mVar8 = 0;
450 }
451 
InitMonster(int i,int rd,int mtype,int x,int y)452 void InitMonster(int i, int rd, int mtype, int x, int y)
453 {
454 	CMonster *monst = &Monsters[mtype];
455 
456 	monster[i]._mdir = rd;
457 	monster[i]._mx = x;
458 	monster[i]._my = y;
459 	monster[i]._mfutx = x;
460 	monster[i]._mfuty = y;
461 	monster[i]._moldx = x;
462 	monster[i]._moldy = y;
463 	monster[i]._mMTidx = mtype;
464 	monster[i]._mmode = MM_STAND;
465 	monster[i].mName = monst->MData->mName;
466 	monster[i].MType = monst;
467 	monster[i].MData = monst->MData;
468 	monster[i]._mAnimData = monst->Anims[MA_STAND].Data[rd];
469 	monster[i]._mAnimDelay = monst->Anims[MA_STAND].Rate;
470 	monster[i]._mAnimCnt = random_(88, monster[i]._mAnimDelay - 1);
471 	monster[i]._mAnimLen = monst->Anims[MA_STAND].Frames;
472 	monster[i]._mAnimFrame = random_(88, monster[i]._mAnimLen - 1) + 1;
473 
474 	monster[i].mLevel = monst->MData->mLevel;
475 	if (monst->mtype == MT_DIABLO) {
476 		monster[i]._mmaxhp = (random_(88, 1) + (gbIsHellfire ? 3333 : 1666)) << 6;
477 		if (!gbIsHellfire)
478 			monster[i].mLevel -= 15;
479 	} else {
480 		monster[i]._mmaxhp = (monst->mMinHP + random_(88, monst->mMaxHP - monst->mMinHP + 1)) << 6;
481 	}
482 
483 	if (!gbIsMultiplayer) {
484 		monster[i]._mmaxhp >>= 1;
485 		if (monster[i]._mmaxhp < 64) {
486 			monster[i]._mmaxhp = 64;
487 		}
488 	}
489 
490 	monster[i]._mhitpoints = monster[i]._mmaxhp;
491 	monster[i]._mAi = monst->MData->mAi;
492 	monster[i]._mint = monst->MData->mInt;
493 	monster[i]._mgoal = MGOAL_NORMAL;
494 	monster[i]._mgoalvar1 = 0;
495 	monster[i]._mgoalvar2 = 0;
496 	monster[i]._mgoalvar3 = 0;
497 	monster[i]._pathcount = 0;
498 	monster[i]._mDelFlag = FALSE;
499 	monster[i]._uniqtype = 0;
500 	monster[i]._msquelch = 0;
501 	monster[i].mlid = NO_LIGHT; // BUGFIX monsters initial light id should be -1 (fixed)
502 	monster[i]._mRndSeed = AdvanceRndSeed();
503 	monster[i]._mAISeed = AdvanceRndSeed();
504 	monster[i].mWhoHit = 0;
505 	monster[i].mExp = monst->MData->mExp;
506 	monster[i].mHit = monst->MData->mHit;
507 	monster[i].mMinDamage = monst->MData->mMinDamage;
508 	monster[i].mMaxDamage = monst->MData->mMaxDamage;
509 	monster[i].mHit2 = monst->MData->mHit2;
510 	monster[i].mMinDamage2 = monst->MData->mMinDamage2;
511 	monster[i].mMaxDamage2 = monst->MData->mMaxDamage2;
512 	monster[i].mArmorClass = monst->MData->mArmorClass;
513 	monster[i].mMagicRes = monst->MData->mMagicRes;
514 	monster[i].leader = 0;
515 	monster[i].leaderflag = 0;
516 	monster[i]._mFlags = monst->MData->mFlags;
517 	monster[i].mtalkmsg = 0;
518 
519 	if (monster[i]._mAi == AI_GARG) {
520 		monster[i]._mAnimData = monst->Anims[MA_SPECIAL].Data[rd];
521 		monster[i]._mAnimFrame = 1;
522 		monster[i]._mFlags |= MFLAG_ALLOW_SPECIAL;
523 		monster[i]._mmode = MM_SATTACK;
524 	}
525 
526 	if (gnDifficulty == DIFF_NIGHTMARE) {
527 		monster[i]._mmaxhp = 3 * monster[i]._mmaxhp;
528 		if (gbIsHellfire)
529 			monster[i]._mmaxhp += (gbIsMultiplayer ? 100 : 50) << 6;
530 		else
531 			monster[i]._mmaxhp += 64;
532 		monster[i]._mhitpoints = monster[i]._mmaxhp;
533 		monster[i].mLevel += 15;
534 		monster[i].mExp = 2 * (monster[i].mExp + 1000);
535 		monster[i].mHit += NIGHTMARE_TO_HIT_BONUS;
536 		monster[i].mMinDamage = 2 * (monster[i].mMinDamage + 2);
537 		monster[i].mMaxDamage = 2 * (monster[i].mMaxDamage + 2);
538 		monster[i].mHit2 += NIGHTMARE_TO_HIT_BONUS;
539 		monster[i].mMinDamage2 = 2 * (monster[i].mMinDamage2 + 2);
540 		monster[i].mMaxDamage2 = 2 * (monster[i].mMaxDamage2 + 2);
541 		monster[i].mArmorClass += NIGHTMARE_AC_BONUS;
542 	} else if (gnDifficulty == DIFF_HELL) {
543 		monster[i]._mmaxhp = 4 * monster[i]._mmaxhp;
544 		if (gbIsHellfire)
545 			monster[i]._mmaxhp += (gbIsMultiplayer ? 200 : 100) << 6;
546 		else
547 			monster[i]._mmaxhp += 192;
548 		monster[i]._mhitpoints = monster[i]._mmaxhp;
549 		monster[i].mLevel += 30;
550 		monster[i].mExp = 4 * (monster[i].mExp + 1000);
551 		monster[i].mHit += HELL_TO_HIT_BONUS;
552 		monster[i].mMinDamage = 4 * monster[i].mMinDamage + 6;
553 		monster[i].mMaxDamage = 4 * monster[i].mMaxDamage + 6;
554 		monster[i].mHit2 += HELL_TO_HIT_BONUS;
555 		monster[i].mMinDamage2 = 4 * monster[i].mMinDamage2 + 6;
556 		monster[i].mMaxDamage2 = 4 * monster[i].mMaxDamage2 + 6;
557 		monster[i].mArmorClass += HELL_AC_BONUS;
558 		monster[i].mMagicRes = monst->MData->mMagicRes2;
559 	}
560 }
561 
ClrAllMonsters()562 void ClrAllMonsters()
563 {
564 	int i;
565 	MonsterStruct *Monst;
566 
567 	for (i = 0; i < MAXMONSTERS; i++) {
568 		Monst = &monster[i];
569 		ClearMVars(i);
570 		Monst->mName = "Invalid Monster";
571 		Monst->_mgoal = 0;
572 		Monst->_mmode = MM_STAND;
573 		Monst->_mVar1 = 0;
574 		Monst->_mVar2 = 0;
575 		Monst->_mx = 0;
576 		Monst->_my = 0;
577 		Monst->_mfutx = 0;
578 		Monst->_mfuty = 0;
579 		Monst->_moldx = 0;
580 		Monst->_moldy = 0;
581 		Monst->_mdir = random_(89, 8);
582 		Monst->_mxvel = 0;
583 		Monst->_myvel = 0;
584 		Monst->_mAnimData = NULL;
585 		Monst->_mAnimDelay = 0;
586 		Monst->_mAnimCnt = 0;
587 		Monst->_mAnimLen = 0;
588 		Monst->_mAnimFrame = 0;
589 		Monst->_mFlags = 0;
590 		Monst->_mDelFlag = FALSE;
591 		Monst->_menemy = random_(89, gbActivePlayers);
592 		Monst->_menemyx = plr[Monst->_menemy]._pfutx;
593 		Monst->_menemyy = plr[Monst->_menemy]._pfuty;
594 	}
595 }
596 
MonstPlace(int xp,int yp)597 BOOL MonstPlace(int xp, int yp)
598 {
599 	char f;
600 
601 	if (xp < 0 || xp >= MAXDUNX
602 	    || yp < 0 || yp >= MAXDUNY
603 	    || dMonster[xp][yp] != 0
604 	    || dPlayer[xp][yp] != 0) {
605 		return FALSE;
606 	}
607 
608 	f = dFlags[xp][yp];
609 
610 	if (f & BFLAG_VISIBLE) {
611 		return FALSE;
612 	}
613 
614 	if (f & BFLAG_POPULATED) {
615 		return FALSE;
616 	}
617 
618 	return !SolidLoc(xp, yp);
619 }
620 
monster_some_crypt()621 void monster_some_crypt()
622 {
623 	MonsterStruct *mon;
624 	int hp;
625 
626 	if (currlevel == 24 && UberDiabloMonsterIndex >= 0 && UberDiabloMonsterIndex < nummonsters) {
627 		mon = &monster[UberDiabloMonsterIndex];
628 		PlayEffect(UberDiabloMonsterIndex, 2);
629 		quests[Q_NAKRUL]._qlog = 0;
630 		mon->mArmorClass -= 50;
631 		hp = mon->_mmaxhp / 2;
632 		mon->mMagicRes = 0;
633 		mon->_mhitpoints = hp;
634 		mon->_mmaxhp = hp;
635 	}
636 }
637 
PlaceMonster(int i,int mtype,int x,int y)638 void PlaceMonster(int i, int mtype, int x, int y)
639 {
640 	int rd;
641 
642 	if (Monsters[mtype].mtype == MT_NAKRUL) {
643 		for (int j = 0; j < nummonsters; j++) {
644 			if (monster[j]._mMTidx == mtype) {
645 				return;
646 			}
647 			if (monster[j].MType->mtype == MT_NAKRUL) {
648 				return;
649 			}
650 		}
651 	}
652 	dMonster[x][y] = i + 1;
653 
654 	rd = random_(90, 8);
655 	InitMonster(i, rd, mtype, x, y);
656 }
657 
PlaceUniqueMonst(int uniqindex,int miniontype,int bosspacksize)658 void PlaceUniqueMonst(int uniqindex, int miniontype, int bosspacksize)
659 {
660 	int xp, yp, x, y, i;
661 	int uniqtype;
662 	int count2;
663 	char filestr[64];
664 	BOOL zharflag, done;
665 	const UniqMonstStruct *Uniq;
666 	MonsterStruct *Monst;
667 	int count;
668 
669 	Monst = &monster[nummonsters];
670 	count = 0;
671 	Uniq = &UniqMonst[uniqindex];
672 
673 	if ((uniquetrans + 19) << 8 >= LIGHTSIZE) {
674 		return;
675 	}
676 
677 	for (uniqtype = 0; uniqtype < nummtypes; uniqtype++) {
678 		if (Monsters[uniqtype].mtype == UniqMonst[uniqindex].mtype) {
679 			break;
680 		}
681 	}
682 
683 	while (1) {
684 		xp = random_(91, 80) + 16;
685 		yp = random_(91, 80) + 16;
686 		count2 = 0;
687 		for (x = xp - 3; x < xp + 3; x++) {
688 			for (y = yp - 3; y < yp + 3; y++) {
689 				if (y >= 0 && y < MAXDUNY && x >= 0 && x < MAXDUNX && MonstPlace(x, y)) {
690 					count2++;
691 				}
692 			}
693 		}
694 
695 		if (count2 < 9) {
696 			count++;
697 			if (count < 1000) {
698 				continue;
699 			}
700 		}
701 
702 		if (MonstPlace(xp, yp)) {
703 			break;
704 		}
705 	}
706 
707 	if (uniqindex == UMT_SNOTSPIL) {
708 		xp = 2 * setpc_x + 24;
709 		yp = 2 * setpc_y + 28;
710 	}
711 	if (uniqindex == UMT_WARLORD) {
712 		xp = 2 * setpc_x + 22;
713 		yp = 2 * setpc_y + 23;
714 	}
715 	if (uniqindex == UMT_ZHAR) {
716 		zharflag = TRUE;
717 		for (i = 0; i < themeCount; i++) {
718 			if (i == zharlib && zharflag == TRUE) {
719 				zharflag = FALSE;
720 				xp = 2 * themeLoc[i].x + 20;
721 				yp = 2 * themeLoc[i].y + 20;
722 			}
723 		}
724 	}
725 	if (!gbIsMultiplayer) {
726 		if (uniqindex == UMT_LAZURUS) {
727 			xp = 32;
728 			yp = 46;
729 		}
730 		if (uniqindex == UMT_RED_VEX) {
731 			xp = 40;
732 			yp = 45;
733 		}
734 		if (uniqindex == UMT_BLACKJADE) {
735 			xp = 38;
736 			yp = 49;
737 		}
738 		if (uniqindex == UMT_SKELKING) {
739 			xp = 35;
740 			yp = 47;
741 		}
742 	} else {
743 		if (uniqindex == UMT_LAZURUS) {
744 			xp = 2 * setpc_x + 19;
745 			yp = 2 * setpc_y + 22;
746 		}
747 		if (uniqindex == UMT_RED_VEX) {
748 			xp = 2 * setpc_x + 21;
749 			yp = 2 * setpc_y + 19;
750 		}
751 		if (uniqindex == UMT_BLACKJADE) {
752 			xp = 2 * setpc_x + 21;
753 			yp = 2 * setpc_y + 25;
754 		}
755 	}
756 	if (uniqindex == UMT_BUTCHER) {
757 		done = FALSE;
758 		for (yp = 0; yp < MAXDUNY && !done; yp++) {
759 			for (xp = 0; xp < MAXDUNX && !done; xp++) {
760 				done = dPiece[xp][yp] == 367;
761 			}
762 		}
763 	}
764 
765 	if (uniqindex == UMT_NAKRUL) {
766 		if (UberRow == 0 || UberCol == 0) {
767 			UberDiabloMonsterIndex = -1;
768 			return;
769 		}
770 		xp = UberRow - 2;
771 		yp = UberCol;
772 		UberDiabloMonsterIndex = nummonsters;
773 	}
774 	PlaceMonster(nummonsters, uniqtype, xp, yp);
775 	Monst->_uniqtype = uniqindex + 1;
776 
777 	if (Uniq->mlevel) {
778 		Monst->mLevel = 2 * Uniq->mlevel;
779 	} else {
780 		Monst->mLevel += 5;
781 	}
782 
783 	Monst->mExp *= 2;
784 	Monst->mName = Uniq->mName;
785 	Monst->_mmaxhp = Uniq->mmaxhp << 6;
786 
787 	if (!gbIsMultiplayer) {
788 		Monst->_mmaxhp = Monst->_mmaxhp >> 1;
789 		if (Monst->_mmaxhp < 64) {
790 			Monst->_mmaxhp = 64;
791 		}
792 	}
793 
794 	Monst->_mhitpoints = Monst->_mmaxhp;
795 	Monst->_mAi = Uniq->mAi;
796 	Monst->_mint = Uniq->mint;
797 	Monst->mMinDamage = Uniq->mMinDamage;
798 	Monst->mMaxDamage = Uniq->mMaxDamage;
799 	Monst->mMinDamage2 = Uniq->mMinDamage;
800 	Monst->mMaxDamage2 = Uniq->mMaxDamage;
801 	Monst->mMagicRes = Uniq->mMagicRes;
802 	Monst->mtalkmsg = Uniq->mtalkmsg;
803 	if (uniqindex == UMT_HORKDMN)
804 		Monst->mlid = NO_LIGHT; // BUGFIX monsters initial light id should be -1 (fixed)
805 	else
806 		Monst->mlid = AddLight(Monst->_mx, Monst->_my, 3);
807 
808 	if (gbIsMultiplayer) {
809 		if (Monst->_mAi == AI_LAZHELP)
810 			Monst->mtalkmsg = 0;
811 		if (Monst->_mAi == AI_LAZURUS && quests[Q_BETRAYER]._qvar1 > 3) {
812 			Monst->_mgoal = MGOAL_NORMAL;
813 		} else if (Monst->mtalkmsg) {
814 			Monst->_mgoal = MGOAL_INQUIRING;
815 		}
816 	} else if (Monst->mtalkmsg) {
817 		Monst->_mgoal = MGOAL_INQUIRING;
818 	}
819 
820 	if (gnDifficulty == DIFF_NIGHTMARE) {
821 		Monst->_mmaxhp = 3 * Monst->_mmaxhp;
822 		if (gbIsHellfire)
823 			Monst->_mmaxhp += (gbIsMultiplayer ? 100 : 50) << 6;
824 		else
825 			Monst->_mmaxhp += 64;
826 		Monst->mLevel += 15;
827 		Monst->_mhitpoints = Monst->_mmaxhp;
828 		Monst->mExp = 2 * (Monst->mExp + 1000);
829 		Monst->mMinDamage = 2 * (Monst->mMinDamage + 2);
830 		Monst->mMaxDamage = 2 * (Monst->mMaxDamage + 2);
831 		Monst->mMinDamage2 = 2 * (Monst->mMinDamage2 + 2);
832 		Monst->mMaxDamage2 = 2 * (Monst->mMaxDamage2 + 2);
833 	} else if (gnDifficulty == DIFF_HELL) {
834 		Monst->_mmaxhp = 4 * Monst->_mmaxhp;
835 		if (gbIsHellfire)
836 			Monst->_mmaxhp += (gbIsMultiplayer ? 200 : 100) << 6;
837 		else
838 			Monst->_mmaxhp += 192;
839 		Monst->mLevel += 30;
840 		Monst->_mhitpoints = Monst->_mmaxhp;
841 		Monst->mExp = 4 * (Monst->mExp + 1000);
842 		Monst->mMinDamage = 4 * Monst->mMinDamage + 6;
843 		Monst->mMaxDamage = 4 * Monst->mMaxDamage + 6;
844 		Monst->mMinDamage2 = 4 * Monst->mMinDamage2 + 6;
845 		Monst->mMaxDamage2 = 4 * Monst->mMaxDamage2 + 6;
846 	}
847 
848 	sprintf(filestr, "Monsters\\Monsters\\%s.TRN", Uniq->mTrnName);
849 	LoadFileWithMem(filestr, &pLightTbl[256 * (uniquetrans + 19)]);
850 
851 	Monst->_uniqtrans = uniquetrans++;
852 
853 	if (Uniq->mUnqAttr & 4) {
854 		Monst->mHit = Uniq->mUnqVar1;
855 		Monst->mHit2 = Uniq->mUnqVar1;
856 
857 		if (gnDifficulty == DIFF_NIGHTMARE) {
858 			Monst->mHit += NIGHTMARE_TO_HIT_BONUS;
859 			Monst->mHit2 += NIGHTMARE_TO_HIT_BONUS;
860 		} else if (gnDifficulty == DIFF_HELL) {
861 			Monst->mHit += HELL_TO_HIT_BONUS;
862 			Monst->mHit2 += HELL_TO_HIT_BONUS;
863 		}
864 	}
865 	if (Uniq->mUnqAttr & 8) {
866 		Monst->mArmorClass = Uniq->mUnqVar1;
867 
868 		if (gnDifficulty == DIFF_NIGHTMARE) {
869 			Monst->mArmorClass += NIGHTMARE_AC_BONUS;
870 		} else if (gnDifficulty == DIFF_HELL) {
871 			Monst->mArmorClass += HELL_AC_BONUS;
872 		}
873 	}
874 
875 	nummonsters++;
876 
877 	if (Uniq->mUnqAttr & 1) {
878 		PlaceGroup(miniontype, bosspacksize, Uniq->mUnqAttr, nummonsters - 1);
879 	}
880 
881 	if (Monst->_mAi != AI_GARG) {
882 		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir];
883 		Monst->_mAnimFrame = random_(88, Monst->_mAnimLen - 1) + 1;
884 		Monst->_mFlags &= ~MFLAG_ALLOW_SPECIAL;
885 		Monst->_mmode = MM_STAND;
886 	}
887 }
888 
PlaceUniques()889 static void PlaceUniques()
890 {
891 	int u, mt;
892 	BOOL done;
893 
894 	for (u = 0; UniqMonst[u].mtype != -1; u++) {
895 		if (UniqMonst[u].mlevel != currlevel)
896 			continue;
897 		done = FALSE;
898 		for (mt = 0; mt < nummtypes; mt++) {
899 			if (done)
900 				break;
901 			done = (Monsters[mt].mtype == UniqMonst[u].mtype);
902 		}
903 		mt--;
904 		if (u == UMT_GARBUD && quests[Q_GARBUD]._qactive == QUEST_NOTAVAIL)
905 			done = FALSE;
906 		if (u == UMT_ZHAR && quests[Q_ZHAR]._qactive == QUEST_NOTAVAIL)
907 			done = FALSE;
908 		if (u == UMT_SNOTSPIL && quests[Q_LTBANNER]._qactive == QUEST_NOTAVAIL)
909 			done = FALSE;
910 		if (u == UMT_LACHDAN && quests[Q_VEIL]._qactive == QUEST_NOTAVAIL)
911 			done = FALSE;
912 		if (u == UMT_WARLORD && quests[Q_WARLORD]._qactive == QUEST_NOTAVAIL)
913 			done = FALSE;
914 		if (done)
915 			PlaceUniqueMonst(u, mt, 8);
916 	}
917 }
918 
PlaceQuestMonsters()919 void PlaceQuestMonsters()
920 {
921 	int skeltype;
922 	BYTE *setp;
923 
924 	if (!setlevel) {
925 		if (QuestStatus(Q_BUTCHER)) {
926 			PlaceUniqueMonst(UMT_BUTCHER, 0, 0);
927 		}
928 
929 		if (currlevel == quests[Q_SKELKING]._qlevel && gbIsMultiplayer) {
930 			skeltype = 0;
931 
932 			for (skeltype = 0; skeltype < nummtypes; skeltype++) {
933 				if (IsSkel(Monsters[skeltype].mtype)) {
934 					break;
935 				}
936 			}
937 
938 			PlaceUniqueMonst(UMT_SKELKING, skeltype, 30);
939 		}
940 
941 		if (QuestStatus(Q_LTBANNER)) {
942 			setp = LoadFileInMem("Levels\\L1Data\\Banner1.DUN", NULL);
943 			SetMapMonsters(setp, 2 * setpc_x, 2 * setpc_y);
944 			mem_free_dbg(setp);
945 		}
946 		if (QuestStatus(Q_BLOOD)) {
947 			setp = LoadFileInMem("Levels\\L2Data\\Blood2.DUN", NULL);
948 			SetMapMonsters(setp, 2 * setpc_x, 2 * setpc_y);
949 			mem_free_dbg(setp);
950 		}
951 		if (QuestStatus(Q_BLIND)) {
952 			setp = LoadFileInMem("Levels\\L2Data\\Blind2.DUN", NULL);
953 			SetMapMonsters(setp, 2 * setpc_x, 2 * setpc_y);
954 			mem_free_dbg(setp);
955 		}
956 		if (QuestStatus(Q_ANVIL)) {
957 			setp = LoadFileInMem("Levels\\L3Data\\Anvil.DUN", NULL);
958 			SetMapMonsters(setp, 2 * setpc_x + 2, 2 * setpc_y + 2);
959 			mem_free_dbg(setp);
960 		}
961 		if (QuestStatus(Q_WARLORD)) {
962 			setp = LoadFileInMem("Levels\\L4Data\\Warlord.DUN", NULL);
963 			SetMapMonsters(setp, 2 * setpc_x, 2 * setpc_y);
964 			mem_free_dbg(setp);
965 			AddMonsterType(UniqMonst[UMT_WARLORD].mtype, PLACE_SCATTER);
966 		}
967 		if (QuestStatus(Q_VEIL)) {
968 			AddMonsterType(UniqMonst[UMT_LACHDAN].mtype, PLACE_SCATTER);
969 		}
970 		if (QuestStatus(Q_ZHAR) && zharlib == -1) {
971 			quests[Q_ZHAR]._qactive = QUEST_NOTAVAIL;
972 		}
973 
974 		if (currlevel == quests[Q_BETRAYER]._qlevel && gbIsMultiplayer) {
975 			AddMonsterType(UniqMonst[UMT_LAZURUS].mtype, PLACE_UNIQUE);
976 			AddMonsterType(UniqMonst[UMT_RED_VEX].mtype, PLACE_UNIQUE);
977 			PlaceUniqueMonst(UMT_LAZURUS, 0, 0);
978 			PlaceUniqueMonst(UMT_RED_VEX, 0, 0);
979 			PlaceUniqueMonst(UMT_BLACKJADE, 0, 0);
980 			setp = LoadFileInMem("Levels\\L4Data\\Vile1.DUN", NULL);
981 			SetMapMonsters(setp, 2 * setpc_x, 2 * setpc_y);
982 			mem_free_dbg(setp);
983 		}
984 
985 		if (currlevel == 24) {
986 			UberDiabloMonsterIndex = -1;
987 			int i1;
988 			for (i1 = 0; i1 < nummtypes; i1++) {
989 				if (Monsters[i1].mtype == UniqMonst[UMT_NAKRUL].mtype)
990 					break;
991 			}
992 
993 			if (i1 < nummtypes) {
994 				for (int i2 = 0; i2 < nummonsters; i2++) {
995 					if (monster[i2]._uniqtype != 0 || monster[i2]._mMTidx == i1) {
996 						UberDiabloMonsterIndex = i2;
997 						break;
998 					}
999 				}
1000 			}
1001 			if (UberDiabloMonsterIndex == -1)
1002 				PlaceUniqueMonst(UMT_NAKRUL, 0, 0);
1003 		}
1004 	} else if (setlvlnum == SL_SKELKING) {
1005 		PlaceUniqueMonst(UMT_SKELKING, 0, 0);
1006 	}
1007 }
1008 
PlaceGroup(int mtype,int num,int leaderf,int leader)1009 void PlaceGroup(int mtype, int num, int leaderf, int leader)
1010 {
1011 	int placed, try1, try2, j;
1012 	int xp, yp, x1, y1;
1013 
1014 	placed = 0;
1015 
1016 	for (try1 = 0; try1 < 10; try1++) {
1017 		while (placed) {
1018 			nummonsters--;
1019 			placed--;
1020 			dMonster[monster[nummonsters]._mx][monster[nummonsters]._my] = 0;
1021 		}
1022 
1023 		if (leaderf & 1) {
1024 			int offset = random_(92, 8);
1025 			x1 = xp = monster[leader]._mx + offset_x[offset];
1026 			y1 = yp = monster[leader]._my + offset_y[offset];
1027 		} else {
1028 			do {
1029 				x1 = xp = random_(93, 80) + 16;
1030 				y1 = yp = random_(93, 80) + 16;
1031 			} while (!MonstPlace(xp, yp));
1032 		}
1033 
1034 		if (num + nummonsters > totalmonsters) {
1035 			num = totalmonsters - nummonsters;
1036 		}
1037 
1038 		j = 0;
1039 		for (try2 = 0; j < num && try2 < 100; xp += offset_x[random_(94, 8)], yp += offset_x[random_(94, 8)]) { /// BUGFIX: `yp += offset_y`
1040 			if (!MonstPlace(xp, yp)
1041 			    || (dTransVal[xp][yp] != dTransVal[x1][y1])
1042 			    || (leaderf & 2) && ((abs(xp - x1) >= 4) || (abs(yp - y1) >= 4))) {
1043 				try2++;
1044 				continue;
1045 			}
1046 
1047 			PlaceMonster(nummonsters, mtype, xp, yp);
1048 			if (leaderf & 1) {
1049 				monster[nummonsters]._mmaxhp *= 2;
1050 				monster[nummonsters]._mhitpoints = monster[nummonsters]._mmaxhp;
1051 				monster[nummonsters]._mint = monster[leader]._mint;
1052 
1053 				if (leaderf & 2) {
1054 					monster[nummonsters].leader = leader;
1055 					monster[nummonsters].leaderflag = 1;
1056 					monster[nummonsters]._mAi = monster[leader]._mAi;
1057 				}
1058 
1059 				if (monster[nummonsters]._mAi != AI_GARG) {
1060 					monster[nummonsters]._mAnimData = monster[nummonsters].MType->Anims[MA_STAND].Data[monster[nummonsters]._mdir];
1061 					monster[nummonsters]._mAnimFrame = random_(88, monster[nummonsters]._mAnimLen - 1) + 1;
1062 					monster[nummonsters]._mFlags &= ~MFLAG_ALLOW_SPECIAL;
1063 					monster[nummonsters]._mmode = MM_STAND;
1064 				}
1065 			}
1066 			nummonsters++;
1067 			placed++;
1068 			j++;
1069 		}
1070 
1071 		if (placed >= num) {
1072 			break;
1073 		}
1074 	}
1075 
1076 	if (leaderf & 2) {
1077 		monster[leader].packsize = placed;
1078 	}
1079 }
1080 
LoadDiabMonsts()1081 void LoadDiabMonsts()
1082 {
1083 	BYTE *lpSetPiece;
1084 
1085 	lpSetPiece = LoadFileInMem("Levels\\L4Data\\diab1.DUN", NULL);
1086 	SetMapMonsters(lpSetPiece, 2 * diabquad1x, 2 * diabquad1y);
1087 	mem_free_dbg(lpSetPiece);
1088 	lpSetPiece = LoadFileInMem("Levels\\L4Data\\diab2a.DUN", NULL);
1089 	SetMapMonsters(lpSetPiece, 2 * diabquad2x, 2 * diabquad2y);
1090 	mem_free_dbg(lpSetPiece);
1091 	lpSetPiece = LoadFileInMem("Levels\\L4Data\\diab3a.DUN", NULL);
1092 	SetMapMonsters(lpSetPiece, 2 * diabquad3x, 2 * diabquad3y);
1093 	mem_free_dbg(lpSetPiece);
1094 	lpSetPiece = LoadFileInMem("Levels\\L4Data\\diab4a.DUN", NULL);
1095 	SetMapMonsters(lpSetPiece, 2 * diabquad4x, 2 * diabquad4y);
1096 	mem_free_dbg(lpSetPiece);
1097 }
1098 
InitMonsters()1099 void InitMonsters()
1100 {
1101 	int na, nt;
1102 	int i, s, t;
1103 	int numplacemonsters;
1104 	int mtype;
1105 	int numscattypes;
1106 	int scattertypes[NUM_MTYPES];
1107 
1108 	numscattypes = 0;
1109 #ifdef _DEBUG
1110 	if (gbIsMultiplayer)
1111 		CheckDungeonClear();
1112 #endif
1113 	if (!setlevel) {
1114 		AddMonster(1, 0, 0, 0, FALSE);
1115 		AddMonster(1, 0, 0, 0, FALSE);
1116 		AddMonster(1, 0, 0, 0, FALSE);
1117 		AddMonster(1, 0, 0, 0, FALSE);
1118 	}
1119 
1120 	if (!gbIsSpawn && !setlevel && currlevel == 16)
1121 		LoadDiabMonsts();
1122 
1123 	nt = numtrigs;
1124 	if (currlevel == 15)
1125 		nt = 1;
1126 	for (i = 0; i < nt; i++) {
1127 		for (s = -2; s < 2; s++) {
1128 			for (t = -2; t < 2; t++)
1129 				DoVision(s + trigs[i]._tx, t + trigs[i]._ty, 15, FALSE, FALSE);
1130 		}
1131 	}
1132 	if (!gbIsSpawn)
1133 		PlaceQuestMonsters();
1134 	if (!setlevel) {
1135 		if (!gbIsSpawn)
1136 			PlaceUniques();
1137 		na = 0;
1138 		for (s = 16; s < 96; s++)
1139 			for (t = 16; t < 96; t++)
1140 				if (!SolidLoc(s, t))
1141 					na++;
1142 		numplacemonsters = na / 30;
1143 		if (gbIsMultiplayer)
1144 			numplacemonsters += numplacemonsters >> 1;
1145 		if (nummonsters + numplacemonsters > MAXMONSTERS - 10)
1146 			numplacemonsters = MAXMONSTERS - 10 - nummonsters;
1147 		totalmonsters = nummonsters + numplacemonsters;
1148 		for (i = 0; i < nummtypes; i++) {
1149 			if (Monsters[i].mPlaceFlags & PLACE_SCATTER) {
1150 				scattertypes[numscattypes] = i;
1151 				numscattypes++;
1152 			}
1153 		}
1154 		while (nummonsters < totalmonsters) {
1155 			mtype = scattertypes[random_(95, numscattypes)];
1156 			if (currlevel == 1 || random_(95, 2) == 0)
1157 				na = 1;
1158 			else if (currlevel == 2 || currlevel >= 21 && currlevel <= 24)
1159 				na = random_(95, 2) + 2;
1160 			else
1161 				na = random_(95, 3) + 3;
1162 			PlaceGroup(mtype, na, 0, 0);
1163 		}
1164 	}
1165 	for (i = 0; i < nt; i++) {
1166 		for (s = -2; s < 2; s++) {
1167 			for (t = -2; t < 2; t++)
1168 				DoUnVision(s + trigs[i]._tx, t + trigs[i]._ty, 15);
1169 		}
1170 	}
1171 }
1172 
SetMapMonsters(BYTE * pMap,int startx,int starty)1173 void SetMapMonsters(BYTE *pMap, int startx, int starty)
1174 {
1175 	WORD rw, rh;
1176 	WORD *lm;
1177 	int i, j;
1178 	int mtype;
1179 
1180 	AddMonsterType(MT_GOLEM, PLACE_SPECIAL);
1181 	AddMonster(1, 0, 0, 0, FALSE);
1182 	AddMonster(1, 0, 0, 0, FALSE);
1183 	AddMonster(1, 0, 0, 0, FALSE);
1184 	AddMonster(1, 0, 0, 0, FALSE);
1185 	if (setlevel && setlvlnum == SL_VILEBETRAYER) {
1186 		AddMonsterType(UniqMonst[UMT_LAZURUS].mtype, PLACE_UNIQUE);
1187 		AddMonsterType(UniqMonst[UMT_RED_VEX].mtype, PLACE_UNIQUE);
1188 		AddMonsterType(UniqMonst[UMT_BLACKJADE].mtype, PLACE_UNIQUE);
1189 		PlaceUniqueMonst(UMT_LAZURUS, 0, 0);
1190 		PlaceUniqueMonst(UMT_RED_VEX, 0, 0);
1191 		PlaceUniqueMonst(UMT_BLACKJADE, 0, 0);
1192 	}
1193 	lm = (WORD *)pMap;
1194 	rw = SDL_SwapLE16(*lm++);
1195 	rh = SDL_SwapLE16(*lm++);
1196 	lm += rw * rh;
1197 	rw = rw << 1;
1198 	rh = rh << 1;
1199 	lm += rw * rh;
1200 
1201 	for (j = 0; j < rh; j++) {
1202 		for (i = 0; i < rw; i++) {
1203 			if (*lm != 0) {
1204 				mtype = AddMonsterType(MonstConvTbl[SDL_SwapLE16(*lm) - 1], PLACE_SPECIAL);
1205 				PlaceMonster(nummonsters++, mtype, i + startx + 16, j + starty + 16);
1206 			}
1207 			lm++;
1208 		}
1209 	}
1210 }
1211 
DeleteMonster(int i)1212 void DeleteMonster(int i)
1213 {
1214 	int temp;
1215 
1216 	nummonsters--;
1217 	temp = monstactive[nummonsters];
1218 	monstactive[nummonsters] = monstactive[i];
1219 	monstactive[i] = temp;
1220 }
1221 
AddMonster(int x,int y,int dir,int mtype,BOOL InMap)1222 int AddMonster(int x, int y, int dir, int mtype, BOOL InMap)
1223 {
1224 	if (nummonsters < MAXMONSTERS) {
1225 		int i = monstactive[nummonsters++];
1226 		if (InMap)
1227 			dMonster[x][y] = i + 1;
1228 		InitMonster(i, dir, mtype, x, y);
1229 		return i;
1230 	}
1231 
1232 	return -1;
1233 }
1234 
monster_43C785(int i)1235 void monster_43C785(int i)
1236 {
1237 	int x, y, d, j, oi, dir, mx, my;
1238 
1239 	if (monster[i].MType) {
1240 		mx = monster[i]._mx;
1241 		my = monster[i]._my;
1242 		dir = monster[i]._mdir;
1243 		for (d = 0; d < 8; d++) {
1244 			x = mx + offset_x[d];
1245 			y = my + offset_y[d];
1246 			if (!SolidLoc(x, y)) {
1247 				if (dPlayer[x][y] == 0 && dMonster[x][y] == 0) {
1248 					if (dObject[x][y] == 0)
1249 						break;
1250 					oi = dObject[x][y] > 0 ? dObject[x][y] - 1 : -(dObject[x][y] + 1);
1251 					if (!object[oi]._oSolidFlag)
1252 						break;
1253 				}
1254 			}
1255 		}
1256 		if (d < 8) {
1257 			for (j = 0; j < MAX_LVLMTYPES; j++) {
1258 				if (Monsters[j].mtype == monster[i].MType->mtype)
1259 					break;
1260 			}
1261 			if (j < MAX_LVLMTYPES)
1262 				AddMonster(x, y, dir, j, TRUE);
1263 		}
1264 	}
1265 }
1266 
NewMonsterAnim(int i,AnimStruct * anim,int md)1267 void NewMonsterAnim(int i, AnimStruct *anim, int md)
1268 {
1269 	MonsterStruct *Monst = &monster[i];
1270 	Monst->_mAnimData = anim->Data[md];
1271 	Monst->_mAnimLen = anim->Frames;
1272 	Monst->_mAnimCnt = 0;
1273 	Monst->_mAnimFrame = 1;
1274 	Monst->_mAnimDelay = anim->Rate;
1275 	Monst->_mFlags &= ~(MFLAG_LOCK_ANIMATION | MFLAG_ALLOW_SPECIAL);
1276 	Monst->_mdir = md;
1277 }
1278 
M_Ranged(int i)1279 BOOL M_Ranged(int i)
1280 {
1281 	char ai = monster[i]._mAi;
1282 	return ai == AI_SKELBOW || ai == AI_GOATBOW || ai == AI_SUCC || ai == AI_LAZHELP;
1283 }
1284 
M_Talker(int i)1285 BOOL M_Talker(int i)
1286 {
1287 	char ai = monster[i]._mAi;
1288 	return ai == AI_LAZURUS
1289 	    || ai == AI_WARLORD
1290 	    || ai == AI_GARBUD
1291 	    || ai == AI_ZHAR
1292 	    || ai == AI_SNOTSPIL
1293 	    || ai == AI_LACHDAN
1294 	    || ai == AI_LAZHELP;
1295 }
1296 
M_Enemy(int i)1297 void M_Enemy(int i)
1298 {
1299 	int j;
1300 	int mi, pnum;
1301 	int dist, best_dist;
1302 	int _menemy;
1303 	bool sameroom, bestsameroom;
1304 	MonsterStruct *Monst;
1305 	BYTE enemyx, enemyy;
1306 
1307 	_menemy = -1;
1308 	best_dist = -1;
1309 	bestsameroom = 0;
1310 	Monst = &monster[i];
1311 	if (Monst->_mFlags & MFLAG_BERSERK || !(Monst->_mFlags & MFLAG_GOLEM)) {
1312 		for (pnum = 0; pnum < MAX_PLRS; pnum++) {
1313 			if (!plr[pnum].plractive || currlevel != plr[pnum].plrlevel || plr[pnum]._pLvlChanging
1314 			    || ((plr[pnum]._pHitPoints >> 6) == 0) && gbIsMultiplayer)
1315 				continue;
1316 			sameroom = (dTransVal[Monst->_mx][Monst->_my] == dTransVal[plr[pnum]._px][plr[pnum]._py]);
1317 			dist = std::max(abs(Monst->_mx - plr[pnum]._px), abs(Monst->_my - plr[pnum]._py));
1318 			if ((sameroom && !bestsameroom)
1319 			    || ((sameroom || !bestsameroom) && dist < best_dist)
1320 			    || (_menemy == -1)) {
1321 				Monst->_mFlags &= ~MFLAG_TARGETS_MONSTER;
1322 				_menemy = pnum;
1323 				enemyx = plr[pnum]._pfutx;
1324 				enemyy = plr[pnum]._pfuty;
1325 				best_dist = dist;
1326 				bestsameroom = sameroom;
1327 			}
1328 		}
1329 	}
1330 	for (j = 0; j < nummonsters; j++) {
1331 		mi = monstactive[j];
1332 		if (mi == i)
1333 			continue;
1334 		if (!((monster[mi]._mhitpoints >> 6) > 0))
1335 			continue;
1336 		if (monster[mi]._mx == 1 && monster[mi]._my == 0)
1337 			continue;
1338 		if (M_Talker(mi) && monster[mi].mtalkmsg)
1339 			continue;
1340 		dist = std::max(abs(monster[mi]._mx - Monst->_mx), abs(monster[mi]._my - Monst->_my));
1341 		if ((!(Monst->_mFlags & MFLAG_GOLEM)
1342 		        && !(Monst->_mFlags & MFLAG_BERSERK)
1343 		        && dist >= 2
1344 		        && !M_Ranged(i))
1345 		    || (!(Monst->_mFlags & MFLAG_GOLEM)
1346 		        && !(Monst->_mFlags & MFLAG_BERSERK)
1347 		        && !(monster[mi]._mFlags & MFLAG_GOLEM))) {
1348 			continue;
1349 		}
1350 		sameroom = dTransVal[Monst->_mx][Monst->_my] == dTransVal[monster[mi]._mx][monster[mi]._my];
1351 		if ((sameroom && !bestsameroom)
1352 		    || ((sameroom || !bestsameroom) && dist < best_dist)
1353 		    || (_menemy == -1)) {
1354 			Monst->_mFlags |= MFLAG_TARGETS_MONSTER;
1355 			_menemy = mi;
1356 			enemyx = monster[mi]._mfutx;
1357 			enemyy = monster[mi]._mfuty;
1358 			best_dist = dist;
1359 			bestsameroom = sameroom;
1360 		}
1361 	}
1362 	if (_menemy != -1) {
1363 		Monst->_mFlags &= ~MFLAG_NO_ENEMY;
1364 		Monst->_menemy = _menemy;
1365 		Monst->_menemyx = enemyx;
1366 		Monst->_menemyy = enemyy;
1367 	} else {
1368 		Monst->_mFlags |= MFLAG_NO_ENEMY;
1369 	}
1370 }
1371 
M_GetDir(int i)1372 int M_GetDir(int i)
1373 {
1374 	return GetDirection(monster[i]._mx, monster[i]._my, monster[i]._menemyx, monster[i]._menemyy);
1375 }
1376 
M_StartStand(int i,int md)1377 void M_StartStand(int i, int md)
1378 {
1379 	ClearMVars(i);
1380 	if (monster[i].MType->mtype == MT_GOLEM)
1381 		NewMonsterAnim(i, &monster[i].MType->Anims[MA_WALK], md);
1382 	else
1383 		NewMonsterAnim(i, &monster[i].MType->Anims[MA_STAND], md);
1384 	monster[i]._mVar1 = monster[i]._mmode;
1385 	monster[i]._mVar2 = 0;
1386 	monster[i]._mmode = MM_STAND;
1387 	monster[i]._mxoff = 0;
1388 	monster[i]._myoff = 0;
1389 	monster[i]._mfutx = monster[i]._mx;
1390 	monster[i]._mfuty = monster[i]._my;
1391 	monster[i]._moldx = monster[i]._mx;
1392 	monster[i]._moldy = monster[i]._my;
1393 	monster[i]._mdir = md;
1394 	M_Enemy(i);
1395 }
1396 
M_StartDelay(int i,int len)1397 void M_StartDelay(int i, int len)
1398 {
1399 	if (len <= 0) {
1400 		return;
1401 	}
1402 
1403 	if (monster[i]._mAi != AI_LAZURUS) {
1404 		monster[i]._mVar2 = len;
1405 		monster[i]._mmode = MM_DELAY;
1406 	}
1407 }
1408 
M_StartSpStand(int i,int md)1409 void M_StartSpStand(int i, int md)
1410 {
1411 	NewMonsterAnim(i, &monster[i].MType->Anims[MA_SPECIAL], md);
1412 	monster[i]._mmode = MM_SPSTAND;
1413 	monster[i]._mxoff = 0;
1414 	monster[i]._myoff = 0;
1415 	monster[i]._mfutx = monster[i]._mx;
1416 	monster[i]._mfuty = monster[i]._my;
1417 	monster[i]._moldx = monster[i]._mx;
1418 	monster[i]._moldy = monster[i]._my;
1419 	monster[i]._mdir = md;
1420 }
1421 
M_StartWalk(int i,int xvel,int yvel,int xadd,int yadd,int EndDir)1422 void M_StartWalk(int i, int xvel, int yvel, int xadd, int yadd, int EndDir)
1423 {
1424 	int fx = xadd + monster[i]._mx;
1425 	int fy = yadd + monster[i]._my;
1426 
1427 	dMonster[fx][fy] = -(i + 1);
1428 	monster[i]._mmode = MM_WALK;
1429 	monster[i]._moldx = monster[i]._mx;
1430 	monster[i]._moldy = monster[i]._my;
1431 	monster[i]._mfutx = fx;
1432 	monster[i]._mfuty = fy;
1433 	monster[i]._mxvel = xvel;
1434 	monster[i]._myvel = yvel;
1435 	monster[i]._mVar1 = xadd;
1436 	monster[i]._mVar2 = yadd;
1437 	monster[i]._mVar3 = EndDir;
1438 	monster[i]._mdir = EndDir;
1439 	NewMonsterAnim(i, &monster[i].MType->Anims[MA_WALK], EndDir);
1440 	monster[i]._mVar6 = 0;
1441 	monster[i]._mVar7 = 0;
1442 	monster[i]._mVar8 = 0;
1443 }
1444 
M_StartWalk2(int i,int xvel,int yvel,int xoff,int yoff,int xadd,int yadd,int EndDir)1445 void M_StartWalk2(int i, int xvel, int yvel, int xoff, int yoff, int xadd, int yadd, int EndDir)
1446 {
1447 	int fx = xadd + monster[i]._mx;
1448 	int fy = yadd + monster[i]._my;
1449 
1450 	dMonster[monster[i]._mx][monster[i]._my] = -(i + 1);
1451 	monster[i]._mVar1 = monster[i]._mx;
1452 	monster[i]._mVar2 = monster[i]._my;
1453 	monster[i]._moldx = monster[i]._mx;
1454 	monster[i]._moldy = monster[i]._my;
1455 	monster[i]._mx = fx;
1456 	monster[i]._my = fy;
1457 	monster[i]._mfutx = fx;
1458 	monster[i]._mfuty = fy;
1459 	dMonster[fx][fy] = i + 1;
1460 	if (monster[i].mlid != NO_LIGHT)
1461 		ChangeLightXY(monster[i].mlid, monster[i]._mx, monster[i]._my);
1462 	monster[i]._mxoff = xoff;
1463 	monster[i]._myoff = yoff;
1464 	monster[i]._mmode = MM_WALK2;
1465 	monster[i]._mxvel = xvel;
1466 	monster[i]._myvel = yvel;
1467 	monster[i]._mVar3 = EndDir;
1468 	monster[i]._mdir = EndDir;
1469 	NewMonsterAnim(i, &monster[i].MType->Anims[MA_WALK], EndDir);
1470 	monster[i]._mVar6 = 16 * xoff;
1471 	monster[i]._mVar7 = 16 * yoff;
1472 	monster[i]._mVar8 = 0;
1473 }
1474 
M_StartWalk3(int i,int xvel,int yvel,int xoff,int yoff,int xadd,int yadd,int mapx,int mapy,int EndDir)1475 void M_StartWalk3(int i, int xvel, int yvel, int xoff, int yoff, int xadd, int yadd, int mapx, int mapy, int EndDir)
1476 {
1477 	int fx = xadd + monster[i]._mx;
1478 	int fy = yadd + monster[i]._my;
1479 	int x = mapx + monster[i]._mx;
1480 	int y = mapy + monster[i]._my;
1481 
1482 	if (monster[i].mlid != NO_LIGHT)
1483 		ChangeLightXY(monster[i].mlid, x, y);
1484 
1485 	dMonster[monster[i]._mx][monster[i]._my] = -(i + 1);
1486 	dMonster[fx][fy] = -(i + 1);
1487 	monster[i]._mVar4 = x;
1488 	monster[i]._mVar5 = y;
1489 	dFlags[x][y] |= BFLAG_MONSTLR;
1490 	monster[i]._moldx = monster[i]._mx;
1491 	monster[i]._moldy = monster[i]._my;
1492 	monster[i]._mfutx = fx;
1493 	monster[i]._mfuty = fy;
1494 	monster[i]._mxoff = xoff;
1495 	monster[i]._myoff = yoff;
1496 	monster[i]._mmode = MM_WALK3;
1497 	monster[i]._mxvel = xvel;
1498 	monster[i]._myvel = yvel;
1499 	monster[i]._mVar1 = fx;
1500 	monster[i]._mVar2 = fy;
1501 	monster[i]._mVar3 = EndDir;
1502 	monster[i]._mdir = EndDir;
1503 	NewMonsterAnim(i, &monster[i].MType->Anims[MA_WALK], EndDir);
1504 	monster[i]._mVar6 = 16 * xoff;
1505 	monster[i]._mVar7 = 16 * yoff;
1506 	monster[i]._mVar8 = 0;
1507 }
1508 
M_StartAttack(int i)1509 void M_StartAttack(int i)
1510 {
1511 	int md = M_GetDir(i);
1512 	NewMonsterAnim(i, &monster[i].MType->Anims[MA_ATTACK], md);
1513 	monster[i]._mmode = MM_ATTACK;
1514 	monster[i]._mxoff = 0;
1515 	monster[i]._myoff = 0;
1516 	monster[i]._mfutx = monster[i]._mx;
1517 	monster[i]._mfuty = monster[i]._my;
1518 	monster[i]._moldx = monster[i]._mx;
1519 	monster[i]._moldy = monster[i]._my;
1520 	monster[i]._mdir = md;
1521 }
1522 
M_StartRAttack(int i,int missile_type,int dam)1523 void M_StartRAttack(int i, int missile_type, int dam)
1524 {
1525 	int md = M_GetDir(i);
1526 	NewMonsterAnim(i, &monster[i].MType->Anims[MA_ATTACK], md);
1527 	monster[i]._mmode = MM_RATTACK;
1528 	monster[i]._mVar1 = missile_type;
1529 	monster[i]._mVar2 = dam;
1530 	monster[i]._mxoff = 0;
1531 	monster[i]._myoff = 0;
1532 	monster[i]._mfutx = monster[i]._mx;
1533 	monster[i]._mfuty = monster[i]._my;
1534 	monster[i]._moldx = monster[i]._mx;
1535 	monster[i]._moldy = monster[i]._my;
1536 	monster[i]._mdir = md;
1537 }
1538 
M_StartRSpAttack(int i,int missile_type,int dam)1539 void M_StartRSpAttack(int i, int missile_type, int dam)
1540 {
1541 	int md = M_GetDir(i);
1542 	NewMonsterAnim(i, &monster[i].MType->Anims[MA_SPECIAL], md);
1543 	monster[i]._mmode = MM_RSPATTACK;
1544 	monster[i]._mVar1 = missile_type;
1545 	monster[i]._mVar2 = 0;
1546 	monster[i]._mVar3 = dam;
1547 	monster[i]._mxoff = 0;
1548 	monster[i]._myoff = 0;
1549 	monster[i]._mfutx = monster[i]._mx;
1550 	monster[i]._mfuty = monster[i]._my;
1551 	monster[i]._moldx = monster[i]._mx;
1552 	monster[i]._moldy = monster[i]._my;
1553 	monster[i]._mdir = md;
1554 }
1555 
M_StartSpAttack(int i)1556 void M_StartSpAttack(int i)
1557 {
1558 	int md = M_GetDir(i);
1559 	NewMonsterAnim(i, &monster[i].MType->Anims[MA_SPECIAL], md);
1560 	monster[i]._mmode = MM_SATTACK;
1561 	monster[i]._mxoff = 0;
1562 	monster[i]._myoff = 0;
1563 	monster[i]._mfutx = monster[i]._mx;
1564 	monster[i]._mfuty = monster[i]._my;
1565 	monster[i]._moldx = monster[i]._mx;
1566 	monster[i]._moldy = monster[i]._my;
1567 	monster[i]._mdir = md;
1568 }
1569 
M_StartEat(int i)1570 void M_StartEat(int i)
1571 {
1572 	NewMonsterAnim(i, &monster[i].MType->Anims[MA_SPECIAL], monster[i]._mdir);
1573 	monster[i]._mmode = MM_SATTACK;
1574 	monster[i]._mxoff = 0;
1575 	monster[i]._myoff = 0;
1576 	monster[i]._mfutx = monster[i]._mx;
1577 	monster[i]._mfuty = monster[i]._my;
1578 	monster[i]._moldx = monster[i]._mx;
1579 	monster[i]._moldy = monster[i]._my;
1580 }
1581 
M_ClearSquares(int i)1582 void M_ClearSquares(int i)
1583 {
1584 	int x, y, mx, my, m1, m2;
1585 
1586 	mx = monster[i]._moldx;
1587 	my = monster[i]._moldy;
1588 	m1 = -(i + 1);
1589 	m2 = i + 1;
1590 
1591 	for (y = my - 1; y <= my + 1; y++) {
1592 		if (y >= 0 && y < MAXDUNY) {
1593 			for (x = mx - 1; x <= mx + 1; x++) {
1594 				if (x >= 0 && x < MAXDUNX && (dMonster[x][y] == m1 || dMonster[x][y] == m2))
1595 					dMonster[x][y] = 0;
1596 			}
1597 		}
1598 	}
1599 
1600 	if (mx + 1 < MAXDUNX)
1601 		dFlags[mx + 1][my] &= ~BFLAG_MONSTLR;
1602 	if (my + 1 < MAXDUNY)
1603 		dFlags[mx][my + 1] &= ~BFLAG_MONSTLR;
1604 }
1605 
M_GetKnockback(int i)1606 void M_GetKnockback(int i)
1607 {
1608 	int d = (monster[i]._mdir - 4) & 7;
1609 	if (DirOK(i, d)) {
1610 		M_ClearSquares(i);
1611 		monster[i]._moldx += offset_x[d];
1612 		monster[i]._moldy += offset_y[d];
1613 		NewMonsterAnim(i, &monster[i].MType->Anims[MA_GOTHIT], monster[i]._mdir);
1614 		monster[i]._mmode = MM_GOTHIT;
1615 		monster[i]._mxoff = 0;
1616 		monster[i]._myoff = 0;
1617 		monster[i]._mx = monster[i]._moldx;
1618 		monster[i]._my = monster[i]._moldy;
1619 		monster[i]._mfutx = monster[i]._mx;
1620 		monster[i]._mfuty = monster[i]._my;
1621 		monster[i]._moldx = monster[i]._mx; // CODEFIX: useless assignment
1622 		monster[i]._moldy = monster[i]._my; // CODEFIX: useless assignment
1623 		M_ClearSquares(i);
1624 		dMonster[monster[i]._mx][monster[i]._my] = i + 1;
1625 	}
1626 }
1627 
M_StartHit(int i,int pnum,int dam)1628 void M_StartHit(int i, int pnum, int dam)
1629 {
1630 	if (pnum >= 0)
1631 		monster[i].mWhoHit |= 1 << pnum;
1632 	if (pnum == myplr) {
1633 		delta_monster_hp(i, monster[i]._mhitpoints, currlevel);
1634 		NetSendCmdMonDmg(FALSE, i, dam);
1635 	}
1636 	PlayEffect(i, 1);
1637 	if (monster[i].MType->mtype >= MT_SNEAK && monster[i].MType->mtype <= MT_ILLWEAV || dam >> 6 >= monster[i].mLevel + 3) {
1638 		if (pnum >= 0) {
1639 			monster[i]._menemy = pnum;
1640 			monster[i]._menemyx = plr[pnum]._pfutx;
1641 			monster[i]._menemyy = plr[pnum]._pfuty;
1642 			monster[i]._mFlags &= ~MFLAG_TARGETS_MONSTER;
1643 			monster[i]._mdir = M_GetDir(i);
1644 		}
1645 		if (monster[i].MType->mtype == MT_BLINK) {
1646 			M_Teleport(i);
1647 		} else if ((monster[i].MType->mtype >= MT_NSCAV && monster[i].MType->mtype <= MT_YSCAV)
1648 		    || monster[i].MType->mtype == MT_GRAVEDIG) {
1649 			monster[i]._mgoal = MGOAL_NORMAL;
1650 			monster[i]._mgoalvar1 = 0;
1651 			monster[i]._mgoalvar2 = 0;
1652 		}
1653 		if (monster[i]._mmode != MM_STONE) {
1654 			NewMonsterAnim(i, &monster[i].MType->Anims[MA_GOTHIT], monster[i]._mdir);
1655 			monster[i]._mmode = MM_GOTHIT;
1656 			monster[i]._mxoff = 0;
1657 			monster[i]._myoff = 0;
1658 			monster[i]._mx = monster[i]._moldx;
1659 			monster[i]._my = monster[i]._moldy;
1660 			monster[i]._mfutx = monster[i]._moldx;
1661 			monster[i]._mfuty = monster[i]._moldy;
1662 			M_ClearSquares(i);
1663 			dMonster[monster[i]._mx][monster[i]._my] = i + 1;
1664 		}
1665 	}
1666 }
1667 
M_DiabloDeath(int i,BOOL sendmsg)1668 void M_DiabloDeath(int i, BOOL sendmsg)
1669 {
1670 	MonsterStruct *Monst;
1671 	int dist;
1672 	int j, k;
1673 
1674 	Monst = &monster[i];
1675 	PlaySFX(USFX_DIABLOD);
1676 	quests[Q_DIABLO]._qactive = QUEST_DONE;
1677 	if (sendmsg)
1678 		NetSendCmdQuest(TRUE, Q_DIABLO);
1679 	sgbSaveSoundOn = gbSoundOn;
1680 	gbProcessPlayers = FALSE;
1681 	for (j = 0; j < nummonsters; j++) {
1682 		k = monstactive[j];
1683 		if (k == i || Monst->_msquelch == 0)
1684 			continue;
1685 
1686 		NewMonsterAnim(k, &monster[k].MType->Anims[MA_DEATH], monster[k]._mdir);
1687 		monster[k]._mmode = MM_DEATH;
1688 		monster[k]._mxoff = 0;
1689 		monster[k]._myoff = 0;
1690 		monster[k]._mVar1 = 0;
1691 		monster[k]._mx = monster[k]._moldx;
1692 		monster[k]._my = monster[k]._moldy;
1693 		monster[k]._mfutx = monster[k]._mx;
1694 		monster[k]._mfuty = monster[k]._my;
1695 		M_ClearSquares(k);
1696 		dMonster[monster[k]._mx][monster[k]._my] = k + 1;
1697 	}
1698 	AddLight(Monst->_mx, Monst->_my, 8);
1699 	DoVision(Monst->_mx, Monst->_my, 8, FALSE, TRUE);
1700 	dist = std::max(abs(ViewX - Monst->_mx), abs(ViewY - Monst->_my));
1701 	if (dist > 20)
1702 		dist = 20;
1703 	Monst->_mVar3 = ViewX << 16;
1704 	Monst->_mVar4 = ViewY << 16;
1705 	Monst->_mVar5 = (int)((Monst->_mVar3 - (Monst->_mx << 16)) / (double)dist);
1706 	Monst->_mVar6 = (int)((Monst->_mVar4 - (Monst->_my << 16)) / (double)dist);
1707 }
1708 
SpawnLoot(int i,BOOL sendmsg)1709 void SpawnLoot(int i, BOOL sendmsg)
1710 {
1711 	int nSFX;
1712 	MonsterStruct *Monst;
1713 
1714 	Monst = &monster[i];
1715 	if (QuestStatus(Q_GARBUD) && Monst->_uniqtype - 1 == UMT_GARBUD) {
1716 		CreateTypeItem(Monst->_mx + 1, Monst->_my + 1, TRUE, ITYPE_MACE, IMISC_NONE, TRUE, FALSE);
1717 	} else if (Monst->_uniqtype - 1 == UMT_DEFILER) {
1718 		if (effect_is_playing(USFX_DEFILER8))
1719 			stream_stop();
1720 		quests[Q_DEFILER]._qlog = 0;
1721 		SpawnMapOfDoom(Monst->_mx, Monst->_my);
1722 	} else if (Monst->_uniqtype - 1 == UMT_HORKDMN) {
1723 		if (gbTheoQuest) {
1724 			SpawnTheodore(Monst->_mx, Monst->_my);
1725 		} else {
1726 			CreateAmulet(Monst->_mx, Monst->_my, 13, FALSE, TRUE);
1727 		}
1728 	} else if (Monst->MType->mtype == MT_HORKSPWN) {
1729 	} else if (Monst->MType->mtype == MT_NAKRUL) {
1730 		nSFX = IsUberRoomOpened ? USFX_NAKRUL4 : USFX_NAKRUL5;
1731 		if (gbCowQuest)
1732 			nSFX = USFX_NAKRUL6;
1733 		if (effect_is_playing(nSFX))
1734 			stream_stop();
1735 		quests[Q_NAKRUL]._qlog = 0;
1736 		UberDiabloMonsterIndex = -2;
1737 		CreateMagicWeapon(Monst->_mx, Monst->_my, ITYPE_SWORD, ICURS_GREAT_SWORD, FALSE, TRUE);
1738 		CreateMagicWeapon(Monst->_mx, Monst->_my, ITYPE_STAFF, ICURS_WAR_STAFF, FALSE, TRUE);
1739 		CreateMagicWeapon(Monst->_mx, Monst->_my, ITYPE_BOW, ICURS_LONG_WAR_BOW, FALSE, TRUE);
1740 		CreateSpellBook(Monst->_mx, Monst->_my, SPL_APOCA, FALSE, TRUE);
1741 	} else if (i > MAX_PLRS - 1) { // Golems should not spawn loot
1742 		SpawnItem(i, Monst->_mx, Monst->_my, sendmsg);
1743 	}
1744 }
1745 
M2MStartHit(int mid,int i,int dam)1746 void M2MStartHit(int mid, int i, int dam)
1747 {
1748 	assurance((DWORD)mid < MAXMONSTERS, mid);
1749 	assurance(monster[mid].MType != NULL, mid);
1750 
1751 	if (i >= 0 && i < MAX_PLRS)
1752 		monster[mid].mWhoHit |= 1 << i;
1753 
1754 	delta_monster_hp(mid, monster[mid]._mhitpoints, currlevel);
1755 	NetSendCmdMonDmg(FALSE, mid, dam);
1756 	PlayEffect(mid, 1);
1757 
1758 	if (monster[mid].MType->mtype >= MT_SNEAK && monster[mid].MType->mtype <= MT_ILLWEAV || dam >> 6 >= monster[mid].mLevel + 3) {
1759 		if (i >= 0)
1760 			monster[mid]._mdir = (monster[i]._mdir - 4) & 7;
1761 
1762 		if (monster[mid].MType->mtype == MT_BLINK) {
1763 			M_Teleport(mid);
1764 		} else if (monster[mid].MType->mtype >= MT_NSCAV && monster[mid].MType->mtype <= MT_YSCAV
1765 		    || monster[mid].MType->mtype == MT_GRAVEDIG) {
1766 			monster[mid]._mgoal = MGOAL_NORMAL;
1767 			monster[mid]._mgoalvar1 = 0;
1768 			monster[mid]._mgoalvar2 = 0;
1769 		}
1770 
1771 		if (monster[mid]._mmode != MM_STONE) {
1772 			if (monster[mid].MType->mtype != MT_GOLEM) {
1773 				NewMonsterAnim(mid, &monster[mid].MType->Anims[MA_GOTHIT], monster[mid]._mdir);
1774 				monster[mid]._mmode = MM_GOTHIT;
1775 			}
1776 
1777 			monster[mid]._mxoff = 0;
1778 			monster[mid]._myoff = 0;
1779 			monster[mid]._mx = monster[mid]._moldx;
1780 			monster[mid]._my = monster[mid]._moldy;
1781 			monster[mid]._mfutx = monster[mid]._moldx;
1782 			monster[mid]._mfuty = monster[mid]._moldy;
1783 			M_ClearSquares(mid);
1784 			dMonster[monster[mid]._mx][monster[mid]._my] = mid + 1;
1785 		}
1786 	}
1787 }
1788 
MonstStartKill(int i,int pnum,BOOL sendmsg)1789 void MonstStartKill(int i, int pnum, BOOL sendmsg)
1790 {
1791 	int md;
1792 	MonsterStruct *Monst;
1793 
1794 	assurance((DWORD)i < MAXMONSTERS, i);
1795 
1796 	Monst = &monster[i];
1797 	assurance(Monst->MType != NULL, i);
1798 
1799 	if (pnum >= 0)
1800 		Monst->mWhoHit |= 1 << pnum;
1801 	if (pnum < MAX_PLRS && i >= MAX_PLRS) /// BUGFIX: i >= MAX_PLRS (fixed)
1802 		AddPlrMonstExper(Monst->mLevel, Monst->mExp, Monst->mWhoHit);
1803 	monstkills[Monst->MType->mtype]++;
1804 	Monst->_mhitpoints = 0;
1805 	SetRndSeed(Monst->_mRndSeed);
1806 	SpawnLoot(i, sendmsg);
1807 	if (Monst->MType->mtype == MT_DIABLO)
1808 		M_DiabloDeath(i, TRUE);
1809 	else
1810 		PlayEffect(i, 2);
1811 
1812 	if (pnum >= 0)
1813 		md = M_GetDir(i);
1814 	else
1815 		md = Monst->_mdir;
1816 	Monst->_mdir = md;
1817 	NewMonsterAnim(i, &Monst->MType->Anims[MA_DEATH], md);
1818 	Monst->_mmode = MM_DEATH;
1819 	Monst->_mgoal = 0;
1820 	Monst->_mxoff = 0;
1821 	Monst->_myoff = 0;
1822 	Monst->_mVar1 = 0;
1823 	Monst->_mx = Monst->_moldx;
1824 	Monst->_my = Monst->_moldy;
1825 	Monst->_mfutx = Monst->_moldx;
1826 	Monst->_mfuty = Monst->_moldy;
1827 	M_ClearSquares(i);
1828 	dMonster[Monst->_mx][Monst->_my] = i + 1;
1829 	CheckQuestKill(i, sendmsg);
1830 	M_FallenFear(Monst->_mx, Monst->_my);
1831 	if (Monst->MType->mtype >= MT_NACID && Monst->MType->mtype <= MT_XACID || Monst->MType->mtype == MT_SPIDLORD)
1832 		AddMissile(Monst->_mx, Monst->_my, 0, 0, 0, MIS_ACIDPUD, TARGET_PLAYERS, i, Monst->_mint + 1, 0);
1833 }
1834 
M2MStartKill(int i,int mid)1835 void M2MStartKill(int i, int mid)
1836 {
1837 	int md;
1838 
1839 	assurance((DWORD)i < MAXMONSTERS, i);
1840 	assurance((DWORD)mid < MAXMONSTERS, mid);
1841 	assurance(monster[mid].MType != NULL, mid); /// BUGFIX: should check `mid` (fixed)
1842 
1843 	delta_kill_monster(mid, monster[mid]._mx, monster[mid]._my, currlevel);
1844 	NetSendCmdLocParam1(FALSE, CMD_MONSTDEATH, monster[mid]._mx, monster[mid]._my, mid);
1845 
1846 	if (i < MAX_PLRS) {
1847 		monster[mid].mWhoHit |= 1 << i;
1848 		if (mid >= MAX_PLRS)
1849 			AddPlrMonstExper(monster[mid].mLevel, monster[mid].mExp, monster[mid].mWhoHit);
1850 	}
1851 
1852 	monstkills[monster[mid].MType->mtype]++;
1853 	monster[mid]._mhitpoints = 0;
1854 	SetRndSeed(monster[mid]._mRndSeed);
1855 
1856 	SpawnLoot(mid, TRUE);
1857 
1858 	if (monster[mid].MType->mtype == MT_DIABLO)
1859 		M_DiabloDeath(mid, TRUE);
1860 	else
1861 		PlayEffect(mid, 2);
1862 
1863 	md = (monster[i]._mdir - 4) & 7;
1864 	if (monster[mid].MType->mtype == MT_GOLEM)
1865 		md = 0;
1866 
1867 	monster[mid]._mdir = md;
1868 	NewMonsterAnim(mid, &monster[mid].MType->Anims[MA_DEATH], md);
1869 	monster[mid]._mmode = MM_DEATH;
1870 	monster[mid]._mxoff = 0;
1871 	monster[mid]._myoff = 0;
1872 	monster[mid]._mx = monster[mid]._moldx;
1873 	monster[mid]._my = monster[mid]._moldy;
1874 	monster[mid]._mfutx = monster[mid]._moldx;
1875 	monster[mid]._mfuty = monster[mid]._moldy;
1876 	M_ClearSquares(mid);
1877 	dMonster[monster[mid]._mx][monster[mid]._my] = mid + 1;
1878 	CheckQuestKill(mid, TRUE);
1879 	M_FallenFear(monster[mid]._mx, monster[mid]._my);
1880 	if (monster[mid].MType->mtype >= MT_NACID && monster[mid].MType->mtype <= MT_XACID)
1881 		AddMissile(monster[mid]._mx, monster[mid]._my, 0, 0, 0, MIS_ACIDPUD, TARGET_PLAYERS, mid, monster[mid]._mint + 1, 0);
1882 
1883 	if (gbIsHellfire)
1884 		M_StartStand(i, monster[i]._mdir);
1885 }
1886 
M_StartKill(int i,int pnum)1887 void M_StartKill(int i, int pnum)
1888 {
1889 	assurance((DWORD)i < MAXMONSTERS, i);
1890 
1891 	if (myplr == pnum) {
1892 		delta_kill_monster(i, monster[i]._mx, monster[i]._my, currlevel);
1893 		if (i != pnum) {
1894 			NetSendCmdLocParam1(FALSE, CMD_MONSTDEATH, monster[i]._mx, monster[i]._my, i);
1895 		} else {
1896 			NetSendCmdLocParam1(FALSE, CMD_KILLGOLEM, monster[i]._mx, monster[i]._my, currlevel);
1897 		}
1898 	}
1899 
1900 	MonstStartKill(i, pnum, TRUE);
1901 }
1902 
M_SyncStartKill(int i,int x,int y,int pnum)1903 void M_SyncStartKill(int i, int x, int y, int pnum)
1904 {
1905 	assurance((DWORD)i < MAXMONSTERS, i);
1906 
1907 	if (monster[i]._mhitpoints == 0 || monster[i]._mmode == MM_DEATH) {
1908 		return;
1909 	}
1910 
1911 	if (dMonster[x][y] == 0) {
1912 		M_ClearSquares(i);
1913 		monster[i]._mx = x;
1914 		monster[i]._my = y;
1915 		monster[i]._moldx = x;
1916 		monster[i]._moldy = y;
1917 	}
1918 
1919 	if (monster[i]._mmode == MM_STONE) {
1920 		MonstStartKill(i, pnum, FALSE);
1921 		monster[i]._mmode = MM_STONE;
1922 	} else {
1923 		MonstStartKill(i, pnum, FALSE);
1924 	}
1925 }
1926 
M_StartFadein(int i,int md,BOOL backwards)1927 void M_StartFadein(int i, int md, BOOL backwards)
1928 {
1929 	assurance((DWORD)i < MAXMONSTERS, i);
1930 	assurance(monster[i].MType != NULL, i);
1931 
1932 	NewMonsterAnim(i, &monster[i].MType->Anims[MA_SPECIAL], md);
1933 	monster[i]._mmode = MM_FADEIN;
1934 	monster[i]._mxoff = 0;
1935 	monster[i]._myoff = 0;
1936 	monster[i]._mfutx = monster[i]._mx;
1937 	monster[i]._mfuty = monster[i]._my;
1938 	monster[i]._moldx = monster[i]._mx;
1939 	monster[i]._moldy = monster[i]._my;
1940 	monster[i]._mdir = md;
1941 	monster[i]._mFlags &= ~MFLAG_HIDDEN;
1942 	if (backwards) {
1943 		monster[i]._mFlags |= MFLAG_LOCK_ANIMATION;
1944 		monster[i]._mAnimFrame = monster[i]._mAnimLen;
1945 	}
1946 }
1947 
M_StartFadeout(int i,int md,BOOL backwards)1948 void M_StartFadeout(int i, int md, BOOL backwards)
1949 {
1950 	assurance((DWORD)i < MAXMONSTERS, i);
1951 	assurance(monster[i].MType != NULL, i);
1952 	assurance(monster[i].MType != NULL, i);
1953 
1954 	NewMonsterAnim(i, &monster[i].MType->Anims[MA_SPECIAL], md);
1955 	monster[i]._mmode = MM_FADEOUT;
1956 	monster[i]._mxoff = 0;
1957 	monster[i]._myoff = 0;
1958 	monster[i]._mfutx = monster[i]._mx;
1959 	monster[i]._mfuty = monster[i]._my;
1960 	monster[i]._moldx = monster[i]._mx;
1961 	monster[i]._moldy = monster[i]._my;
1962 	monster[i]._mdir = md;
1963 	if (backwards) {
1964 		monster[i]._mFlags |= MFLAG_LOCK_ANIMATION;
1965 		monster[i]._mAnimFrame = monster[i]._mAnimLen;
1966 	}
1967 }
1968 
M_StartHeal(int i)1969 void M_StartHeal(int i)
1970 {
1971 	MonsterStruct *Monst;
1972 
1973 	assurance((DWORD)i < MAXMONSTERS, i);
1974 	assurance(monster[i].MType != NULL, i);
1975 
1976 	Monst = &monster[i];
1977 	Monst->_mAnimData = Monst->MType->Anims[MA_SPECIAL].Data[Monst->_mdir];
1978 	Monst->_mAnimFrame = Monst->MType->Anims[MA_SPECIAL].Frames;
1979 	Monst->_mFlags |= MFLAG_LOCK_ANIMATION;
1980 	Monst->_mmode = MM_HEAL;
1981 	Monst->_mVar1 = Monst->_mmaxhp / (16 * (random_(97, 5) + 4));
1982 }
1983 
M_ChangeLightOffset(int monst)1984 void M_ChangeLightOffset(int monst)
1985 {
1986 	int lx, ly, _mxoff, _myoff, sign;
1987 
1988 	assurance((DWORD)monst < MAXMONSTERS, monst);
1989 
1990 	lx = monster[monst]._mxoff + 2 * monster[monst]._myoff;
1991 	ly = 2 * monster[monst]._myoff - monster[monst]._mxoff;
1992 
1993 	if (lx < 0) {
1994 		sign = -1;
1995 		lx = -lx;
1996 	} else {
1997 		sign = 1;
1998 	}
1999 
2000 	_mxoff = sign * (lx >> 3);
2001 	if (ly < 0) {
2002 		_myoff = -1;
2003 		ly = -ly;
2004 	} else {
2005 		_myoff = 1;
2006 	}
2007 
2008 	_myoff *= (ly >> 3);
2009 	if (monster[monst].mlid != NO_LIGHT)
2010 		ChangeLightOff(monster[monst].mlid, _mxoff, _myoff);
2011 }
2012 
M_DoStand(int i)2013 BOOL M_DoStand(int i)
2014 {
2015 	MonsterStruct *Monst;
2016 
2017 	commitment((DWORD)i < MAXMONSTERS, i);
2018 	commitment(monster[i].MType != NULL, i);
2019 
2020 	Monst = &monster[i];
2021 	if (Monst->MType->mtype == MT_GOLEM)
2022 		Monst->_mAnimData = Monst->MType->Anims[MA_WALK].Data[Monst->_mdir];
2023 	else
2024 		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir];
2025 
2026 	if (Monst->_mAnimFrame == Monst->_mAnimLen)
2027 		M_Enemy(i);
2028 
2029 	Monst->_mVar2++;
2030 
2031 	return FALSE;
2032 }
2033 
2034 /**
2035  * @brief Continue movement towards new tile
2036  */
M_DoWalk(int i,int variant)2037 bool M_DoWalk(int i, int variant)
2038 {
2039 	bool returnValue;
2040 
2041 	commitment((DWORD)i < MAXMONSTERS, i);
2042 	commitment(monster[i].MType != NULL, i);
2043 
2044 	//Check if we reached new tile
2045 	if (monster[i]._mVar8 == monster[i].MType->Anims[MA_WALK].Frames) {
2046 		switch (variant) {
2047 		case MM_WALK:
2048 			dMonster[monster[i]._mx][monster[i]._my] = 0;
2049 			monster[i]._mx += monster[i]._mVar1;
2050 			monster[i]._my += monster[i]._mVar2;
2051 			dMonster[monster[i]._mx][monster[i]._my] = i + 1;
2052 			break;
2053 		case MM_WALK2:
2054 			dMonster[monster[i]._mVar1][monster[i]._mVar2] = 0;
2055 			break;
2056 		case MM_WALK3:
2057 			dMonster[monster[i]._mx][monster[i]._my] = 0;
2058 			monster[i]._mx = monster[i]._mVar1;
2059 			monster[i]._my = monster[i]._mVar2;
2060 			dFlags[monster[i]._mVar4][monster[i]._mVar5] &= ~BFLAG_MONSTLR;
2061 			dMonster[monster[i]._mx][monster[i]._my] = i + 1;
2062 			break;
2063 		}
2064 		if (monster[i].mlid != NO_LIGHT)
2065 			ChangeLightXY(monster[i].mlid, monster[i]._mx, monster[i]._my);
2066 		M_StartStand(i, monster[i]._mdir);
2067 		returnValue = TRUE;
2068 	} else { //We didn't reach new tile so update monster's "sub-tile" position
2069 		if (monster[i]._mAnimCnt == 0) {
2070 			if (monster[i]._mVar8 == 0 && monster[i].MType->mtype == MT_FLESTHNG)
2071 				PlayEffect(i, 3);
2072 			monster[i]._mVar8++;
2073 			monster[i]._mVar6 += monster[i]._mxvel;
2074 			monster[i]._mVar7 += monster[i]._myvel;
2075 			monster[i]._mxoff = monster[i]._mVar6 >> 4;
2076 			monster[i]._myoff = monster[i]._mVar7 >> 4;
2077 		}
2078 		returnValue = FALSE;
2079 	}
2080 
2081 	if (monster[i].mlid != NO_LIGHT) // BUGFIX: change uniqtype check to mlid check like it is in all other places (fixed)
2082 		M_ChangeLightOffset(i);
2083 
2084 	return returnValue;
2085 }
2086 
M_TryM2MHit(int i,int mid,int hper,int mind,int maxd)2087 void M_TryM2MHit(int i, int mid, int hper, int mind, int maxd)
2088 {
2089 	BOOL ret;
2090 
2091 	assurance((DWORD)mid < MAXMONSTERS, mid);
2092 	assurance(monster[mid].MType != NULL, mid);
2093 	if (monster[mid]._mhitpoints >> 6 > 0 && (monster[mid].MType->mtype != MT_ILLWEAV || monster[mid]._mgoal != MGOAL_RETREAT)) {
2094 		int hit = random_(4, 100);
2095 		if (monster[mid]._mmode == MM_STONE)
2096 			hit = 0;
2097 		if (!CheckMonsterHit(mid, &ret) && hit < hper) {
2098 			int dam = (mind + random_(5, maxd - mind + 1)) << 6;
2099 			monster[mid]._mhitpoints -= dam;
2100 			if (monster[mid]._mhitpoints >> 6 <= 0) {
2101 				if (monster[mid]._mmode == MM_STONE) {
2102 					M2MStartKill(i, mid);
2103 					monster[mid]._mmode = MM_STONE;
2104 				} else {
2105 					M2MStartKill(i, mid);
2106 				}
2107 			} else {
2108 				if (monster[mid]._mmode == MM_STONE) {
2109 					M2MStartHit(mid, i, dam);
2110 					monster[mid]._mmode = MM_STONE;
2111 				} else {
2112 					M2MStartHit(mid, i, dam);
2113 				}
2114 			}
2115 		}
2116 	}
2117 }
2118 
M_TryH2HHit(int i,int pnum,int Hit,int MinDam,int MaxDam)2119 void M_TryH2HHit(int i, int pnum, int Hit, int MinDam, int MaxDam)
2120 {
2121 	int hit, hper;
2122 	int dx, dy;
2123 	int blk, blkper;
2124 	int dam, mdam;
2125 	int newx, newy;
2126 	int j, misnum, ms_num, cur_ms_num, new_hp, dir, ac;
2127 
2128 	assurance((DWORD)i < MAXMONSTERS, i);
2129 	assurance(monster[i].MType != NULL, i);
2130 	if (monster[i]._mFlags & MFLAG_TARGETS_MONSTER) {
2131 		M_TryM2MHit(i, pnum, Hit, MinDam, MaxDam);
2132 		return;
2133 	}
2134 	if (plr[pnum]._pHitPoints >> 6 <= 0 || plr[pnum]._pInvincible || plr[pnum]._pSpellFlags & 1)
2135 		return;
2136 	dx = abs(monster[i]._mx - plr[pnum]._px);
2137 	dy = abs(monster[i]._my - plr[pnum]._py);
2138 	if (dx >= 2 || dy >= 2)
2139 		return;
2140 
2141 	hper = random_(98, 100);
2142 #ifdef _DEBUG
2143 	if (debug_mode_dollar_sign || debug_mode_key_inverted_v)
2144 		hper = 1000;
2145 #endif
2146 	ac = plr[pnum]._pIBonusAC + plr[pnum]._pIAC;
2147 	if (plr[pnum].pDamAcFlags & 0x20 && monster[i].MData->mMonstClass == MC_DEMON)
2148 		ac += 40;
2149 	if (plr[pnum].pDamAcFlags & 0x40 && monster[i].MData->mMonstClass == MC_UNDEAD)
2150 		ac += 20;
2151 	hit = Hit
2152 	    + 2 * (monster[i].mLevel - plr[pnum]._pLevel)
2153 	    + 30
2154 	    - ac
2155 	    - plr[pnum]._pDexterity / 5;
2156 	if (hit < 15)
2157 		hit = 15;
2158 	if (currlevel == 14 && hit < 20)
2159 		hit = 20;
2160 	if (currlevel == 15 && hit < 25)
2161 		hit = 25;
2162 	if (currlevel == 16 && hit < 30)
2163 		hit = 30;
2164 	if ((plr[pnum]._pmode == PM_STAND || plr[pnum]._pmode == PM_ATTACK) && plr[pnum]._pBlockFlag) {
2165 		blkper = random_(98, 100);
2166 	} else {
2167 		blkper = 100;
2168 	}
2169 	blk = plr[pnum]._pDexterity
2170 	    + plr[pnum]._pBaseToBlk
2171 	    - (monster[i].mLevel << 1)
2172 	    + (plr[pnum]._pLevel << 1);
2173 	if (blk < 0)
2174 		blk = 0;
2175 	if (blk > 100)
2176 		blk = 100;
2177 	if (hper >= hit)
2178 		return;
2179 	if (blkper < blk) {
2180 		direction dir = GetDirection(plr[pnum]._px, plr[pnum]._py, monster[i]._mx, monster[i]._my);
2181 		StartPlrBlock(pnum, dir);
2182 		if (pnum == myplr && plr[pnum].wReflections > 0) {
2183 			plr[pnum].wReflections--;
2184 			dam = random_(99, (MaxDam - MinDam + 1) << 6) + (MinDam << 6);
2185 			dam += plr[pnum]._pIGetHit << 6;
2186 			if (dam < 64)
2187 				dam = 64;
2188 			mdam = dam * (0.01 * (random_(100, 10) + 20));
2189 			monster[i]._mhitpoints -= mdam;
2190 			dam -= mdam;
2191 			if (dam < 0)
2192 				dam = 0;
2193 			if (monster[i]._mhitpoints >> 6 <= 0)
2194 				M_StartKill(i, pnum);
2195 			else
2196 				M_StartHit(i, pnum, mdam);
2197 		}
2198 		return;
2199 	}
2200 	if (monster[i].MType->mtype == MT_YZOMBIE && pnum == myplr) {
2201 		ms_num = -1;
2202 		cur_ms_num = -1;
2203 		for (j = 0; j < nummissiles; j++) {
2204 			misnum = missileactive[j];
2205 			if (missile[misnum]._mitype != MIS_MANASHIELD)
2206 				continue;
2207 			if (missile[misnum]._misource == pnum)
2208 				cur_ms_num = misnum;
2209 			else
2210 				ms_num = misnum;
2211 		}
2212 		if (plr[pnum]._pMaxHP > 64) {
2213 			if (plr[pnum]._pMaxHPBase > 64) {
2214 				plr[pnum]._pMaxHP -= 64;
2215 				if (plr[pnum]._pHitPoints > plr[pnum]._pMaxHP) {
2216 					plr[pnum]._pHitPoints = plr[pnum]._pMaxHP;
2217 					if (cur_ms_num >= 0)
2218 						missile[cur_ms_num]._miVar1 = plr[pnum]._pHitPoints;
2219 				}
2220 				plr[pnum]._pMaxHPBase -= 64;
2221 				if (plr[pnum]._pHPBase > plr[pnum]._pMaxHPBase) {
2222 					plr[pnum]._pHPBase = plr[pnum]._pMaxHPBase;
2223 					if (cur_ms_num >= 0)
2224 						missile[cur_ms_num]._miVar2 = plr[pnum]._pHPBase;
2225 				}
2226 			}
2227 		}
2228 	}
2229 	dam = (MinDam << 6) + random_(99, (MaxDam - MinDam + 1) << 6);
2230 	dam += (plr[pnum]._pIGetHit << 6);
2231 	if (dam < 64)
2232 		dam = 64;
2233 	if (pnum == myplr) {
2234 		if (plr[pnum].wReflections > 0) {
2235 			plr[pnum].wReflections--;
2236 			mdam = dam * (0.01 * (random_(100, 10) + 20));
2237 			monster[i]._mhitpoints -= mdam;
2238 			dam -= mdam;
2239 			if (dam < 0)
2240 				dam = 0;
2241 			if (monster[i]._mhitpoints >> 6 <= 0)
2242 				M_StartKill(i, pnum);
2243 			else
2244 				M_StartHit(i, pnum, mdam);
2245 		}
2246 		plr[pnum]._pHitPoints -= dam;
2247 		plr[pnum]._pHPBase -= dam;
2248 	}
2249 	if (plr[pnum]._pIFlags & ISPL_THORNS) {
2250 		mdam = (random_(99, 3) + 1) << 6;
2251 		monster[i]._mhitpoints -= mdam;
2252 		if (monster[i]._mhitpoints >> 6 <= 0)
2253 			M_StartKill(i, pnum);
2254 		else
2255 			M_StartHit(i, pnum, mdam);
2256 	}
2257 	if (!(monster[i]._mFlags & MFLAG_NOLIFESTEAL) && monster[i].MType->mtype == MT_SKING && gbIsMultiplayer)
2258 		monster[i]._mhitpoints += dam;
2259 	if (plr[pnum]._pHitPoints > plr[pnum]._pMaxHP) {
2260 		plr[pnum]._pHitPoints = plr[pnum]._pMaxHP;
2261 		plr[pnum]._pHPBase = plr[pnum]._pMaxHPBase;
2262 	}
2263 	if (plr[pnum]._pHitPoints >> 6 <= 0) {
2264 		SyncPlrKill(pnum, 0);
2265 		if (gbIsHellfire)
2266 			M_StartStand(i, monster[i]._mdir);
2267 		return;
2268 	}
2269 	StartPlrHit(pnum, dam, FALSE);
2270 	if (monster[i]._mFlags & MFLAG_KNOCKBACK) {
2271 		if (plr[pnum]._pmode != PM_GOTHIT)
2272 			StartPlrHit(pnum, 0, TRUE);
2273 		newx = plr[pnum]._px + offset_x[monster[i]._mdir];
2274 		newy = plr[pnum]._py + offset_y[monster[i]._mdir];
2275 		if (PosOkPlayer(pnum, newx, newy)) {
2276 			plr[pnum]._px = newx;
2277 			plr[pnum]._py = newy;
2278 			FixPlayerLocation(pnum, plr[pnum]._pdir);
2279 			FixPlrWalkTags(pnum);
2280 			dPlayer[newx][newy] = pnum + 1;
2281 			SetPlayerOld(pnum);
2282 		}
2283 	}
2284 }
2285 
M_DoAttack(int i)2286 BOOL M_DoAttack(int i)
2287 {
2288 	MonsterStruct *Monst;
2289 
2290 	commitment((DWORD)i < MAXMONSTERS, i);
2291 	Monst = &monster[i];
2292 	commitment(Monst->MType != NULL, i);
2293 	commitment(Monst->MData != NULL, i); // BUGFIX: should check MData (fixed)
2294 
2295 	if (monster[i]._mAnimFrame == monster[i].MData->mAFNum) {
2296 		M_TryH2HHit(i, monster[i]._menemy, monster[i].mHit, monster[i].mMinDamage, monster[i].mMaxDamage);
2297 		if (monster[i]._mAi != AI_SNAKE)
2298 			PlayEffect(i, 0);
2299 	}
2300 	if (monster[i].MType->mtype >= MT_NMAGMA && monster[i].MType->mtype <= MT_WMAGMA && monster[i]._mAnimFrame == 9) {
2301 		M_TryH2HHit(i, monster[i]._menemy, monster[i].mHit + 10, monster[i].mMinDamage - 2, monster[i].mMaxDamage - 2);
2302 		PlayEffect(i, 0);
2303 	}
2304 	if (monster[i].MType->mtype >= MT_STORM && monster[i].MType->mtype <= MT_MAEL && monster[i]._mAnimFrame == 13) {
2305 		M_TryH2HHit(i, monster[i]._menemy, monster[i].mHit - 20, monster[i].mMinDamage + 4, monster[i].mMaxDamage + 4);
2306 		PlayEffect(i, 0);
2307 	}
2308 	if (monster[i]._mAi == AI_SNAKE && monster[i]._mAnimFrame == 1)
2309 		PlayEffect(i, 0);
2310 	if (monster[i]._mAnimFrame == monster[i]._mAnimLen) {
2311 		M_StartStand(i, monster[i]._mdir);
2312 		return TRUE;
2313 	}
2314 
2315 	return FALSE;
2316 }
2317 
M_DoRAttack(int i)2318 BOOL M_DoRAttack(int i)
2319 {
2320 	int multimissiles, mi;
2321 
2322 	commitment((DWORD)i < MAXMONSTERS, i);
2323 	commitment(monster[i].MType != NULL, i);
2324 	commitment(monster[i].MData != NULL, i);
2325 
2326 	if (monster[i]._mAnimFrame == monster[i].MData->mAFNum) {
2327 		if (monster[i]._mVar1 != -1) {
2328 			if (monster[i]._mVar1 == MIS_CBOLT)
2329 				multimissiles = 3;
2330 			else
2331 				multimissiles = 1;
2332 			for (mi = 0; mi < multimissiles; mi++) {
2333 				AddMissile(
2334 				    monster[i]._mx + (gbIsHellfire ? offset_x[monster[i]._mdir] : 0),
2335 				    monster[i]._my + (gbIsHellfire ? offset_y[monster[i]._mdir] : 0),
2336 				    monster[i]._menemyx,
2337 				    monster[i]._menemyy,
2338 				    monster[i]._mdir,
2339 				    monster[i]._mVar1,
2340 				    TARGET_PLAYERS,
2341 				    i,
2342 				    monster[i]._mVar2,
2343 				    0);
2344 			}
2345 		}
2346 		PlayEffect(i, 0);
2347 	}
2348 
2349 	if (monster[i]._mAnimFrame == monster[i]._mAnimLen) {
2350 		M_StartStand(i, monster[i]._mdir);
2351 		return TRUE;
2352 	}
2353 
2354 	return FALSE;
2355 }
2356 
M_DoRSpAttack(int i)2357 BOOL M_DoRSpAttack(int i)
2358 {
2359 	commitment((DWORD)i < MAXMONSTERS, i);
2360 	commitment(monster[i].MType != NULL, i);
2361 	commitment(monster[i].MData != NULL, i); // BUGFIX: should check MData (fixed)
2362 
2363 	if (monster[i]._mAnimFrame == monster[i].MData->mAFNum2 && monster[i]._mAnimCnt == 0) {
2364 		AddMissile(
2365 		    monster[i]._mx + (gbIsHellfire ? offset_x[monster[i]._mdir] : 0),
2366 		    monster[i]._my + (gbIsHellfire ? offset_y[monster[i]._mdir] : 0),
2367 		    monster[i]._menemyx,
2368 		    monster[i]._menemyy,
2369 		    monster[i]._mdir,
2370 		    monster[i]._mVar1,
2371 		    TARGET_PLAYERS,
2372 		    i,
2373 		    monster[i]._mVar3,
2374 		    0);
2375 		PlayEffect(i, 3);
2376 	}
2377 
2378 	if (monster[i]._mAi == AI_MEGA && monster[i]._mAnimFrame == 3) {
2379 		if (monster[i]._mVar2++ == 0) {
2380 			monster[i]._mFlags |= MFLAG_ALLOW_SPECIAL;
2381 		} else if (monster[i]._mVar2 == 15) {
2382 			monster[i]._mFlags &= ~MFLAG_ALLOW_SPECIAL;
2383 		}
2384 	}
2385 
2386 	if (monster[i]._mAnimFrame == monster[i]._mAnimLen) {
2387 		M_StartStand(i, monster[i]._mdir);
2388 		return TRUE;
2389 	}
2390 
2391 	return FALSE;
2392 }
2393 
M_DoSAttack(int i)2394 BOOL M_DoSAttack(int i)
2395 {
2396 	commitment((DWORD)i < MAXMONSTERS, i);
2397 	commitment(monster[i].MType != NULL, i);
2398 	commitment(monster[i].MData != NULL, i);
2399 
2400 	if (monster[i]._mAnimFrame == monster[i].MData->mAFNum2)
2401 		M_TryH2HHit(i, monster[i]._menemy, monster[i].mHit2, monster[i].mMinDamage2, monster[i].mMaxDamage2);
2402 
2403 	if (monster[i]._mAnimFrame == monster[i]._mAnimLen) {
2404 		M_StartStand(i, monster[i]._mdir);
2405 		return TRUE;
2406 	}
2407 
2408 	return FALSE;
2409 }
2410 
M_DoFadein(int i)2411 BOOL M_DoFadein(int i)
2412 {
2413 	commitment((DWORD)i < MAXMONSTERS, i);
2414 
2415 	if ((!(monster[i]._mFlags & MFLAG_LOCK_ANIMATION) || monster[i]._mAnimFrame != 1)
2416 	    && (monster[i]._mFlags & MFLAG_LOCK_ANIMATION || monster[i]._mAnimFrame != monster[i]._mAnimLen)) {
2417 		return FALSE;
2418 	}
2419 
2420 	M_StartStand(i, monster[i]._mdir);
2421 	monster[i]._mFlags &= ~MFLAG_LOCK_ANIMATION;
2422 
2423 	return TRUE;
2424 }
2425 
M_DoFadeout(int i)2426 BOOL M_DoFadeout(int i)
2427 {
2428 	int mt;
2429 
2430 	commitment((DWORD)i < MAXMONSTERS, i);
2431 
2432 	if ((!(monster[i]._mFlags & MFLAG_LOCK_ANIMATION) || monster[i]._mAnimFrame != 1)
2433 	    && (monster[i]._mFlags & MFLAG_LOCK_ANIMATION || monster[i]._mAnimFrame != monster[i]._mAnimLen)) {
2434 		return FALSE;
2435 	}
2436 
2437 	mt = monster[i].MType->mtype;
2438 	if (mt < MT_INCIN || mt > MT_HELLBURN) {
2439 		monster[i]._mFlags &= ~MFLAG_LOCK_ANIMATION;
2440 		monster[i]._mFlags |= MFLAG_HIDDEN;
2441 	} else {
2442 		monster[i]._mFlags &= ~MFLAG_LOCK_ANIMATION;
2443 	}
2444 
2445 	M_StartStand(i, monster[i]._mdir);
2446 
2447 	return TRUE;
2448 }
2449 
M_DoHeal(int i)2450 BOOL M_DoHeal(int i)
2451 {
2452 	MonsterStruct *Monst;
2453 
2454 	commitment((DWORD)i < MAXMONSTERS, i);
2455 	Monst = &monster[i];
2456 	if (monster[i]._mFlags & MFLAG_NOHEAL) {
2457 		Monst->_mFlags &= ~MFLAG_ALLOW_SPECIAL;
2458 		Monst->_mmode = MM_SATTACK;
2459 		return FALSE;
2460 	}
2461 
2462 	if (Monst->_mAnimFrame == 1) {
2463 		Monst->_mFlags &= ~MFLAG_LOCK_ANIMATION;
2464 		Monst->_mFlags |= MFLAG_ALLOW_SPECIAL;
2465 		if (Monst->_mVar1 + Monst->_mhitpoints < Monst->_mmaxhp) {
2466 			Monst->_mhitpoints = Monst->_mVar1 + Monst->_mhitpoints;
2467 		} else {
2468 			Monst->_mhitpoints = Monst->_mmaxhp;
2469 			Monst->_mFlags &= ~MFLAG_ALLOW_SPECIAL;
2470 			Monst->_mmode = MM_SATTACK;
2471 		}
2472 	}
2473 	return FALSE;
2474 }
2475 
M_DoTalk(int i)2476 BOOL M_DoTalk(int i)
2477 {
2478 	MonsterStruct *Monst;
2479 	int tren;
2480 
2481 	commitment((DWORD)i < MAXMONSTERS, i);
2482 
2483 	Monst = &monster[i];
2484 	M_StartStand(i, monster[i]._mdir);
2485 	Monst->_mgoal = MGOAL_TALKING; // CODEFIX: apply Monst instead of monster[i] in the rest of the function
2486 	if (effect_is_playing(alltext[monster[i].mtalkmsg].sfxnr))
2487 		return FALSE;
2488 	InitQTextMsg(monster[i].mtalkmsg);
2489 	if (monster[i]._uniqtype - 1 == UMT_GARBUD) {
2490 		if (monster[i].mtalkmsg == TEXT_GARBUD1) {
2491 			quests[Q_GARBUD]._qactive = QUEST_ACTIVE;
2492 			quests[Q_GARBUD]._qlog = TRUE; // BUGFIX: (?) for other quests qactive and qlog go together, maybe this should actually go into the if above (fixed)
2493 		}
2494 		if (monster[i].mtalkmsg == TEXT_GARBUD2 && !(monster[i]._mFlags & MFLAG_QUEST_COMPLETE)) {
2495 			SpawnItem(i, monster[i]._mx + 1, monster[i]._my + 1, TRUE);
2496 			monster[i]._mFlags |= MFLAG_QUEST_COMPLETE;
2497 		}
2498 	}
2499 	if (monster[i]._uniqtype - 1 == UMT_ZHAR
2500 	    && monster[i].mtalkmsg == TEXT_ZHAR1
2501 	    && !(monster[i]._mFlags & MFLAG_QUEST_COMPLETE)) {
2502 		quests[Q_ZHAR]._qactive = QUEST_ACTIVE;
2503 		quests[Q_ZHAR]._qlog = TRUE;
2504 		CreateTypeItem(monster[i]._mx + 1, monster[i]._my + 1, FALSE, ITYPE_MISC, IMISC_BOOK, TRUE, FALSE);
2505 		monster[i]._mFlags |= MFLAG_QUEST_COMPLETE;
2506 	}
2507 	if (monster[i]._uniqtype - 1 == UMT_SNOTSPIL) {
2508 		if (monster[i].mtalkmsg == TEXT_BANNER10 && !(monster[i]._mFlags & MFLAG_QUEST_COMPLETE)) {
2509 			ObjChangeMap(setpc_x, setpc_y, (setpc_w >> 1) + setpc_x + 2, (setpc_h >> 1) + setpc_y - 2);
2510 			tren = TransVal;
2511 			TransVal = 9;
2512 			DRLG_MRectTrans(setpc_x, setpc_y, (setpc_w >> 1) + setpc_x + 4, setpc_y + (setpc_h >> 1));
2513 			TransVal = tren;
2514 			quests[Q_LTBANNER]._qvar1 = 2;
2515 			if (quests[Q_LTBANNER]._qactive == QUEST_INIT)
2516 				quests[Q_LTBANNER]._qactive = QUEST_ACTIVE;
2517 			monster[i]._mFlags |= MFLAG_QUEST_COMPLETE;
2518 		}
2519 		if (quests[Q_LTBANNER]._qvar1 < 2) {
2520 			app_fatal("SS Talk = %i, Flags = %i", monster[i].mtalkmsg, monster[i]._mFlags);
2521 		}
2522 	}
2523 	if (monster[i]._uniqtype - 1 == UMT_LACHDAN) {
2524 		if (monster[i].mtalkmsg == TEXT_VEIL9) {
2525 			quests[Q_VEIL]._qactive = QUEST_ACTIVE;
2526 			quests[Q_VEIL]._qlog = TRUE;
2527 		}
2528 		if (monster[i].mtalkmsg == TEXT_VEIL11 && !(monster[i]._mFlags & MFLAG_QUEST_COMPLETE)) {
2529 			SpawnUnique(UITEM_STEELVEIL, monster[i]._mx + 1, monster[i]._my + 1);
2530 			monster[i]._mFlags |= MFLAG_QUEST_COMPLETE;
2531 		}
2532 	}
2533 	if (monster[i]._uniqtype - 1 == UMT_WARLORD)
2534 		quests[Q_WARLORD]._qvar1 = 2;
2535 	if (monster[i]._uniqtype - 1 == UMT_LAZURUS && gbIsMultiplayer) {
2536 		quests[Q_BETRAYER]._qvar1 = 6;
2537 		monster[i]._mgoal = MGOAL_NORMAL;
2538 		monster[i]._msquelch = UCHAR_MAX;
2539 		monster[i].mtalkmsg = 0;
2540 	}
2541 	return FALSE;
2542 }
2543 
M_Teleport(int i)2544 void M_Teleport(int i)
2545 {
2546 	BOOL done;
2547 	MonsterStruct *Monst;
2548 	int k, j, x, y, _mx, _my, rx, ry;
2549 
2550 	assurance((DWORD)i < MAXMONSTERS, i);
2551 
2552 	done = FALSE;
2553 
2554 	Monst = &monster[i];
2555 	if (Monst->_mmode == MM_STONE)
2556 		return;
2557 
2558 	_mx = Monst->_menemyx;
2559 	_my = Monst->_menemyy;
2560 	rx = 2 * random_(100, 2) - 1;
2561 	ry = 2 * random_(100, 2) - 1;
2562 
2563 	for (j = -1; j <= 1 && !done; j++) {
2564 		for (k = -1; k < 1 && !done; k++) {
2565 			if (j != 0 || k != 0) {
2566 				x = _mx + rx * j;
2567 				y = _my + ry * k;
2568 				if (y >= 0 && y < MAXDUNY && x >= 0 && x < MAXDUNX && x != Monst->_mx && y != Monst->_my) {
2569 					if (PosOkMonst(i, x, y))
2570 						done = TRUE;
2571 				}
2572 			}
2573 		}
2574 	}
2575 
2576 	if (done) {
2577 		M_ClearSquares(i);
2578 		dMonster[Monst->_mx][Monst->_my] = 0;
2579 		dMonster[x][y] = i + 1;
2580 		Monst->_moldx = x;
2581 		Monst->_moldy = y;
2582 		Monst->_mdir = M_GetDir(i);
2583 	}
2584 }
2585 
M_DoGotHit(int i)2586 BOOL M_DoGotHit(int i)
2587 {
2588 	commitment((DWORD)i < MAXMONSTERS, i);
2589 	commitment(monster[i].MType != NULL, i);
2590 
2591 	if (monster[i]._mAnimFrame == monster[i]._mAnimLen) {
2592 		M_StartStand(i, monster[i]._mdir);
2593 
2594 		return TRUE;
2595 	}
2596 
2597 	return FALSE;
2598 }
2599 
M_UpdateLeader(int i)2600 void M_UpdateLeader(int i)
2601 {
2602 	int ma, j;
2603 
2604 	assurance((DWORD)i < MAXMONSTERS, i);
2605 
2606 	for (j = 0; j < nummonsters; j++) {
2607 		ma = monstactive[j];
2608 		if (monster[ma].leaderflag == 1 && monster[ma].leader == i)
2609 			monster[ma].leaderflag = 0;
2610 	}
2611 
2612 	if (monster[i].leaderflag == 1) {
2613 		monster[monster[i].leader].packsize--;
2614 	}
2615 }
2616 
DoEnding()2617 void DoEnding()
2618 {
2619 	BOOL bMusicOn;
2620 	int musicVolume;
2621 
2622 	if (gbIsMultiplayer) {
2623 		SNetLeaveGame(LEAVE_ENDING);
2624 	}
2625 
2626 	music_stop();
2627 
2628 	if (gbIsMultiplayer) {
2629 		SDL_Delay(1000);
2630 	}
2631 
2632 	if (gbIsSpawn)
2633 		return;
2634 
2635 	if (plr[myplr]._pClass == PC_WARRIOR || plr[myplr]._pClass == PC_BARBARIAN) {
2636 		play_movie("gendata\\DiabVic2.smk", FALSE);
2637 	} else if (plr[myplr]._pClass == PC_SORCERER) {
2638 		play_movie("gendata\\DiabVic1.smk", FALSE);
2639 	} else if (plr[myplr]._pClass == PC_MONK) {
2640 		play_movie("gendata\\DiabVic1.smk", FALSE);
2641 	} else {
2642 		play_movie("gendata\\DiabVic3.smk", FALSE);
2643 	}
2644 	play_movie("gendata\\Diabend.smk", FALSE);
2645 
2646 	bMusicOn = gbMusicOn;
2647 	gbMusicOn = TRUE;
2648 
2649 	musicVolume = sound_get_or_set_music_volume(1);
2650 	sound_get_or_set_music_volume(0);
2651 
2652 	music_start(TMUSIC_L2);
2653 	loop_movie = TRUE;
2654 	play_movie("gendata\\loopdend.smk", TRUE);
2655 	loop_movie = FALSE;
2656 	music_stop();
2657 
2658 	sound_get_or_set_music_volume(musicVolume);
2659 	gbMusicOn = bMusicOn;
2660 }
2661 
PrepDoEnding()2662 void PrepDoEnding()
2663 {
2664 	int newKillLevel, i;
2665 	DWORD *killLevel;
2666 
2667 	gbSoundOn = sgbSaveSoundOn;
2668 	gbRunGame = FALSE;
2669 	deathflag = FALSE;
2670 	cineflag = TRUE;
2671 
2672 	killLevel = &plr[myplr].pDiabloKillLevel;
2673 	newKillLevel = gnDifficulty + 1;
2674 	if (*killLevel > newKillLevel)
2675 		newKillLevel = *killLevel;
2676 	plr[myplr].pDiabloKillLevel = newKillLevel;
2677 
2678 	for (i = 0; i < MAX_PLRS; i++) {
2679 		plr[i]._pmode = PM_QUIT;
2680 		plr[i]._pInvincible = TRUE;
2681 		if (gbIsMultiplayer) {
2682 			if (plr[i]._pHitPoints >> 6 == 0)
2683 				plr[i]._pHitPoints = 64;
2684 			if (plr[i]._pMana >> 6 == 0)
2685 				plr[i]._pMana = 64;
2686 		}
2687 	}
2688 }
2689 
M_DoDeath(int i)2690 BOOL M_DoDeath(int i)
2691 {
2692 	int x, y;
2693 
2694 	commitment((DWORD)i < MAXMONSTERS, i);
2695 	commitment(monster[i].MType != NULL, i);
2696 
2697 	monster[i]._mVar1++;
2698 	if (monster[i].MType->mtype == MT_DIABLO) {
2699 		x = monster[i]._mx - ViewX;
2700 		if (x < 0)
2701 			x = -1;
2702 		else
2703 			x = x > 0;
2704 		ViewX += x;
2705 
2706 		y = monster[i]._my - ViewY;
2707 		if (y < 0) {
2708 			y = -1;
2709 		} else {
2710 			y = y > 0;
2711 		}
2712 		ViewY += y;
2713 
2714 		if (monster[i]._mVar1 == 140)
2715 			PrepDoEnding();
2716 	} else if (monster[i]._mAnimFrame == monster[i]._mAnimLen) {
2717 		if (monster[i]._uniqtype == 0)
2718 			AddDead(monster[i]._mx, monster[i]._my, monster[i].MType->mdeadval, (direction)monster[i]._mdir);
2719 		else
2720 			AddDead(monster[i]._mx, monster[i]._my, monster[i]._udeadval, (direction)monster[i]._mdir);
2721 
2722 		dMonster[monster[i]._mx][monster[i]._my] = 0;
2723 		monster[i]._mDelFlag = TRUE;
2724 
2725 		M_UpdateLeader(i);
2726 	}
2727 	return FALSE;
2728 }
2729 
M_DoSpStand(int i)2730 BOOL M_DoSpStand(int i)
2731 {
2732 	commitment((DWORD)i < MAXMONSTERS, i);
2733 	commitment(monster[i].MType != NULL, i);
2734 
2735 	if (monster[i]._mAnimFrame == monster[i].MData->mAFNum2)
2736 		PlayEffect(i, 3);
2737 
2738 	if (monster[i]._mAnimFrame == monster[i]._mAnimLen) {
2739 		M_StartStand(i, monster[i]._mdir);
2740 		return TRUE;
2741 	}
2742 
2743 	return FALSE;
2744 }
2745 
M_DoDelay(int i)2746 BOOL M_DoDelay(int i)
2747 {
2748 	int oFrame;
2749 
2750 	commitment((DWORD)i < MAXMONSTERS, i);
2751 	commitment(monster[i].MType != NULL, i);
2752 
2753 	monster[i]._mAnimData = monster[i].MType->Anims[MA_STAND].Data[M_GetDir(i)];
2754 	if (monster[i]._mAi == AI_LAZURUS) {
2755 		if (monster[i]._mVar2 > 8 || monster[i]._mVar2 < 0)
2756 			monster[i]._mVar2 = 8;
2757 	}
2758 
2759 	if (monster[i]._mVar2-- == 0) {
2760 		oFrame = monster[i]._mAnimFrame;
2761 		M_StartStand(i, monster[i]._mdir);
2762 		monster[i]._mAnimFrame = oFrame;
2763 		return TRUE;
2764 	}
2765 
2766 	return FALSE;
2767 }
2768 
M_DoStone(int i)2769 BOOL M_DoStone(int i)
2770 {
2771 	commitment((DWORD)i < MAXMONSTERS, i);
2772 
2773 	if (!monster[i]._mhitpoints) {
2774 		dMonster[monster[i]._mx][monster[i]._my] = 0;
2775 		monster[i]._mDelFlag = TRUE;
2776 	}
2777 
2778 	return FALSE;
2779 }
2780 
M_WalkDir(int i,int md)2781 void M_WalkDir(int i, int md)
2782 {
2783 	int mwi;
2784 
2785 	assurance((DWORD)i < MAXMONSTERS, i);
2786 
2787 	mwi = monster[i].MType->Anims[MA_WALK].Frames - 1;
2788 	switch (md) {
2789 	case DIR_N:
2790 		M_StartWalk(i, 0, -MWVel[mwi][1], -1, -1, DIR_N);
2791 		break;
2792 	case DIR_NE:
2793 		M_StartWalk(i, MWVel[mwi][1], -MWVel[mwi][0], 0, -1, DIR_NE);
2794 		break;
2795 	case DIR_E:
2796 		M_StartWalk3(i, MWVel[mwi][2], 0, -32, -16, 1, -1, 1, 0, DIR_E);
2797 		break;
2798 	case DIR_SE:
2799 		M_StartWalk2(i, MWVel[mwi][1], MWVel[mwi][0], -32, -16, 1, 0, DIR_SE);
2800 		break;
2801 	case DIR_S:
2802 		M_StartWalk2(i, 0, MWVel[mwi][1], 0, -32, 1, 1, DIR_S);
2803 		break;
2804 	case DIR_SW:
2805 		M_StartWalk2(i, -MWVel[mwi][1], MWVel[mwi][0], 32, -16, 0, 1, DIR_SW);
2806 		break;
2807 	case DIR_W:
2808 		M_StartWalk3(i, -MWVel[mwi][2], 0, 32, -16, -1, 1, 0, 1, DIR_W);
2809 		break;
2810 	case DIR_NW:
2811 		M_StartWalk(i, -MWVel[mwi][1], -MWVel[mwi][0], -1, 0, DIR_NW);
2812 		break;
2813 	}
2814 }
2815 
GroupUnity(int i)2816 void GroupUnity(int i)
2817 {
2818 	int leader, m, j;
2819 	BOOL clear;
2820 
2821 	assurance((DWORD)i < MAXMONSTERS, i);
2822 
2823 	if (monster[i].leaderflag != 0) {
2824 		leader = monster[i].leader;
2825 		clear = LineClearF(CheckNoSolid, monster[i]._mx, monster[i]._my, monster[leader]._mfutx, monster[leader]._mfuty);
2826 		if (clear || monster[i].leaderflag != 1) {
2827 			if (clear
2828 			    && monster[i].leaderflag == 2
2829 			    && abs(monster[i]._mx - monster[leader]._mfutx) < 4
2830 			    && abs(monster[i]._my - monster[leader]._mfuty) < 4) {
2831 				monster[leader].packsize++;
2832 				monster[i].leaderflag = 1;
2833 			}
2834 		} else {
2835 			monster[leader].packsize--;
2836 			monster[i].leaderflag = 2;
2837 		}
2838 	}
2839 
2840 	if (monster[i].leaderflag == 1) {
2841 		if (monster[i]._msquelch > monster[leader]._msquelch) {
2842 			monster[leader]._lastx = monster[i]._mx;
2843 			monster[leader]._lasty = monster[i]._my;
2844 			monster[leader]._msquelch = monster[i]._msquelch - 1;
2845 		}
2846 		if (monster[leader]._mAi == AI_GARG) {
2847 			if (monster[leader]._mFlags & MFLAG_ALLOW_SPECIAL) {
2848 				monster[leader]._mFlags &= ~MFLAG_ALLOW_SPECIAL;
2849 				monster[leader]._mmode = MM_SATTACK;
2850 			}
2851 		}
2852 	} else if (monster[i]._uniqtype != 0) {
2853 		if (UniqMonst[monster[i]._uniqtype - 1].mUnqAttr & 2) {
2854 			for (j = 0; j < nummonsters; j++) {
2855 				m = monstactive[j];
2856 				if (monster[m].leaderflag == 1 && monster[m].leader == i) {
2857 					if (monster[i]._msquelch > monster[m]._msquelch) {
2858 						monster[m]._lastx = monster[i]._mx;
2859 						monster[m]._lasty = monster[i]._my;
2860 						monster[m]._msquelch = monster[i]._msquelch - 1;
2861 					}
2862 					if (monster[m]._mAi == AI_GARG) {
2863 						if (monster[m]._mFlags & MFLAG_ALLOW_SPECIAL) {
2864 							monster[m]._mFlags &= ~MFLAG_ALLOW_SPECIAL;
2865 							monster[m]._mmode = MM_SATTACK;
2866 						}
2867 					}
2868 				}
2869 			}
2870 		}
2871 	}
2872 }
2873 
M_CallWalk(int i,int md)2874 BOOL M_CallWalk(int i, int md)
2875 {
2876 	int mdtemp;
2877 	BOOL ok;
2878 
2879 	mdtemp = md;
2880 	ok = DirOK(i, md);
2881 	if (random_(101, 2) != 0)
2882 		ok = ok || (md = left[mdtemp], DirOK(i, md)) || (md = right[mdtemp], DirOK(i, md));
2883 	else
2884 		ok = ok || (md = right[mdtemp], DirOK(i, md)) || (md = left[mdtemp], DirOK(i, md));
2885 	if (random_(102, 2) != 0)
2886 		ok = ok
2887 		    || (md = right[right[mdtemp]], DirOK(i, md))
2888 		    || (md = left[left[mdtemp]], DirOK(i, md));
2889 	else
2890 		ok = ok
2891 		    || (md = left[left[mdtemp]], DirOK(i, md))
2892 		    || (md = right[right[mdtemp]], DirOK(i, md));
2893 	if (ok)
2894 		M_WalkDir(i, md);
2895 	return ok;
2896 }
2897 
M_PathWalk(int i)2898 BOOL M_PathWalk(int i)
2899 {
2900 	Sint8 path[MAX_PATH_LENGTH];
2901 	BOOL(*Check)
2902 	(int, int, int);
2903 
2904 	/** Maps from walking path step to facing direction. */
2905 	const Sint8 plr2monst[9] = { 0, 5, 3, 7, 1, 4, 6, 0, 2 };
2906 
2907 	commitment((DWORD)i < MAXMONSTERS, i);
2908 
2909 	Check = PosOkMonst3;
2910 	if (!(monster[i]._mFlags & MFLAG_CAN_OPEN_DOOR))
2911 		Check = PosOkMonst;
2912 
2913 	if (FindPath(Check, i, monster[i]._mx, monster[i]._my, monster[i]._menemyx, monster[i]._menemyy, path)) {
2914 		M_CallWalk(i, plr2monst[path[0]]);
2915 		return TRUE;
2916 	}
2917 
2918 	return FALSE;
2919 }
2920 
M_CallWalk2(int i,int md)2921 BOOL M_CallWalk2(int i, int md)
2922 {
2923 	BOOL ok;
2924 	int mdtemp;
2925 
2926 	mdtemp = md;
2927 	ok = DirOK(i, md);          // Can we continue in the same direction
2928 	if (random_(101, 2) != 0) { // Randomly go left or right
2929 		ok = ok || (mdtemp = left[md], DirOK(i, left[md])) || (mdtemp = right[md], DirOK(i, right[md]));
2930 	} else {
2931 		ok = ok || (mdtemp = right[md], DirOK(i, right[md])) || (mdtemp = left[md], DirOK(i, left[md]));
2932 	}
2933 
2934 	if (ok)
2935 		M_WalkDir(i, mdtemp);
2936 
2937 	return ok;
2938 }
2939 
M_DumbWalk(int i,int md)2940 BOOL M_DumbWalk(int i, int md)
2941 {
2942 	BOOL ok;
2943 	ok = DirOK(i, md);
2944 	if (ok)
2945 		M_WalkDir(i, md);
2946 
2947 	return ok;
2948 }
2949 
M_RoundWalk(int i,int md,Sint32 * dir)2950 BOOL M_RoundWalk(int i, int md, Sint32 *dir)
2951 {
2952 	int mdtemp;
2953 	BOOL ok;
2954 	if (*dir)
2955 		md = left[left[md]];
2956 	else
2957 		md = right[right[md]];
2958 
2959 	mdtemp = md;
2960 	ok = DirOK(i, md);
2961 	if (!ok) {
2962 		if (*dir) {
2963 			md = right[mdtemp];
2964 			ok = DirOK(i, md) || (md = right[right[mdtemp]], DirOK(i, md));
2965 		} else {
2966 			md = left[mdtemp];
2967 			ok = (DirOK(i, md) || (md = left[left[mdtemp]], DirOK(i, md)));
2968 		}
2969 	}
2970 	if (ok) {
2971 		M_WalkDir(i, md);
2972 	} else {
2973 		*dir = !*dir;
2974 		ok = M_CallWalk(i, opposite[mdtemp]);
2975 	}
2976 	return ok;
2977 }
2978 
MAI_Zombie(int i)2979 void MAI_Zombie(int i)
2980 {
2981 	MonsterStruct *Monst;
2982 	int mx, my, md;
2983 
2984 	assurance((DWORD)i < MAXMONSTERS, i);
2985 
2986 	Monst = &monster[i];
2987 	if (Monst->_mmode != MM_STAND) {
2988 		return;
2989 	}
2990 
2991 	mx = Monst->_mx;
2992 	my = Monst->_my;
2993 	if (!(dFlags[mx][my] & BFLAG_VISIBLE)) {
2994 		return;
2995 	}
2996 
2997 	if (random_(103, 100) < 2 * Monst->_mint + 10) {
2998 		md = std::max(abs(mx - Monst->_menemyx), abs(my - Monst->_menemyy));
2999 		if (md >= 2) {
3000 			if (md >= 2 * Monst->_mint + 4) {
3001 				md = Monst->_mdir;
3002 				if (random_(104, 100) < 2 * Monst->_mint + 20) {
3003 					md = random_(104, 8);
3004 				}
3005 				M_DumbWalk(i, md);
3006 			} else {
3007 				md = M_GetDir(i);
3008 				M_CallWalk(i, md);
3009 			}
3010 		} else {
3011 			M_StartAttack(i);
3012 		}
3013 	}
3014 
3015 	if (Monst->_mmode == MM_STAND)
3016 		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir];
3017 }
3018 
MAI_SkelSd(int i)3019 void MAI_SkelSd(int i)
3020 {
3021 	MonsterStruct *Monst;
3022 	int mx, my, x, y, md;
3023 
3024 	assurance((DWORD)i < MAXMONSTERS, i);
3025 
3026 	Monst = &monster[i];
3027 	if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) {
3028 		return;
3029 	}
3030 
3031 	mx = Monst->_mx;
3032 	my = Monst->_my;
3033 	x = mx - Monst->_menemyx;
3034 	y = my - Monst->_menemyy;
3035 	md = GetDirection(mx, my, Monst->_lastx, Monst->_lasty);
3036 	Monst->_mdir = md;
3037 	if (abs(x) >= 2 || abs(y) >= 2) {
3038 		if (Monst->_mVar1 == MM_DELAY || (random_(106, 100) >= 35 - 4 * Monst->_mint)) {
3039 			M_CallWalk(i, md);
3040 		} else {
3041 			M_StartDelay(i, 15 - 2 * Monst->_mint + random_(106, 10));
3042 		}
3043 	} else {
3044 		if (Monst->_mVar1 == MM_DELAY || (random_(105, 100) < 2 * Monst->_mint + 20)) {
3045 			M_StartAttack(i);
3046 		} else {
3047 			M_StartDelay(i, 2 * (5 - Monst->_mint) + random_(105, 10));
3048 		}
3049 	}
3050 
3051 	if (Monst->_mmode == MM_STAND)
3052 		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
3053 }
3054 
MAI_Path(int i)3055 BOOL MAI_Path(int i)
3056 {
3057 	MonsterStruct *Monst;
3058 	BOOL clear;
3059 
3060 	commitment((DWORD)i < MAXMONSTERS, i);
3061 
3062 	Monst = &monster[i];
3063 	if (Monst->MType->mtype != MT_GOLEM) {
3064 		if (Monst->_msquelch == 0)
3065 			return FALSE;
3066 		if (Monst->_mmode != MM_STAND)
3067 			return FALSE;
3068 		if (Monst->_mgoal != MGOAL_NORMAL && Monst->_mgoal != MGOAL_MOVE && Monst->_mgoal != MGOAL_ATTACK2)
3069 			return FALSE;
3070 		if (Monst->_mx == 1 && Monst->_my == 0)
3071 			return FALSE;
3072 	}
3073 
3074 	clear = LineClearF1(
3075 	    PosOkMonst2,
3076 	    i,
3077 	    Monst->_mx,
3078 	    Monst->_my,
3079 	    Monst->_menemyx,
3080 	    Monst->_menemyy);
3081 	if (!clear || Monst->_pathcount >= 5 && Monst->_pathcount < 8) {
3082 		if (Monst->_mFlags & MFLAG_CAN_OPEN_DOOR)
3083 			MonstCheckDoors(i);
3084 		Monst->_pathcount++;
3085 		if (Monst->_pathcount < 5)
3086 			return FALSE;
3087 		if (M_PathWalk(i))
3088 			return TRUE;
3089 	}
3090 
3091 	if (Monst->MType->mtype != MT_GOLEM)
3092 		Monst->_pathcount = 0;
3093 
3094 	return FALSE;
3095 }
3096 
MAI_Snake(int i)3097 void MAI_Snake(int i)
3098 {
3099 	MonsterStruct *Monst;
3100 	int fx, fy, mx, my, md;
3101 	int pnum;
3102 	int tmp;
3103 
3104 	assurance((DWORD)i < MAXMONSTERS, i);
3105 	char pattern[6] = { 1, 1, 0, -1, -1, 0 };
3106 	Monst = &monster[i];
3107 	pnum = Monst->_menemy;
3108 	if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0)
3109 		return;
3110 	fx = Monst->_menemyx;
3111 	fy = Monst->_menemyy;
3112 	mx = Monst->_mx - fx;
3113 	my = Monst->_my - fy;
3114 	md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty);
3115 	Monst->_mdir = md;
3116 	if (abs(mx) >= 2 || abs(my) >= 2) {
3117 		if (abs(mx) < 3 && abs(my) < 3 && LineClearF1(PosOkMonst, i, Monst->_mx, Monst->_my, fx, fy) && Monst->_mVar1 != MM_CHARGE) {
3118 			if (AddMissile(Monst->_mx, Monst->_my, fx, fy, md, MIS_RHINO, pnum, i, 0, 0) != -1) {
3119 				PlayEffect(i, 0);
3120 				dMonster[Monst->_mx][Monst->_my] = -(i + 1);
3121 				Monst->_mmode = MM_CHARGE;
3122 			}
3123 		} else if (Monst->_mVar1 == MM_DELAY || random_(106, 100) >= 35 - 2 * Monst->_mint) {
3124 			if (md + pattern[Monst->_mgoalvar1] < 0) {
3125 				tmp = md + pattern[Monst->_mgoalvar1] + 8;
3126 			} else {
3127 				tmp = md + pattern[Monst->_mgoalvar1] - 8;
3128 				if (md + pattern[Monst->_mgoalvar1] < 8)
3129 					tmp = md + pattern[Monst->_mgoalvar1];
3130 			}
3131 			Monst->_mgoalvar1++;
3132 			if (Monst->_mgoalvar1 > 5)
3133 				Monst->_mgoalvar1 = 0;
3134 			if (tmp - Monst->_mgoalvar2 < 0) {
3135 				md = tmp - Monst->_mgoalvar2 + 8;
3136 			} else if (tmp - Monst->_mgoalvar2 >= 8) {
3137 				md = tmp - Monst->_mgoalvar2 - 8;
3138 			} else
3139 				md = tmp - Monst->_mgoalvar2;
3140 			if (md > 0) {
3141 				if (md < 4) {
3142 					if (Monst->_mgoalvar2 + 1 < 0) {
3143 						md = Monst->_mgoalvar2 + 9;
3144 					} else if (Monst->_mgoalvar2 + 1 >= 8) {
3145 						md = Monst->_mgoalvar2 - 7;
3146 					} else
3147 						md = Monst->_mgoalvar2 + 1;
3148 					Monst->_mgoalvar2 = md;
3149 				} else if (md == 4) {
3150 					Monst->_mgoalvar2 = tmp;
3151 				} else {
3152 					if (Monst->_mgoalvar2 - 1 < 0) {
3153 						md = Monst->_mgoalvar2 + 7;
3154 					} else if (Monst->_mgoalvar2 - 1 >= 8) {
3155 						md = Monst->_mgoalvar2 - 9;
3156 					} else
3157 						md = Monst->_mgoalvar2 - 1;
3158 					Monst->_mgoalvar2 = md;
3159 				}
3160 			}
3161 			if (!M_DumbWalk(i, Monst->_mgoalvar2))
3162 				M_CallWalk2(i, Monst->_mdir);
3163 		} else {
3164 			M_StartDelay(i, 15 - Monst->_mint + random_(106, 10));
3165 		}
3166 	} else {
3167 		if (Monst->_mVar1 == MM_DELAY
3168 		    || Monst->_mVar1 == MM_CHARGE
3169 		    || (random_(105, 100) < Monst->_mint + 20)) {
3170 			M_StartAttack(i);
3171 		} else
3172 			M_StartDelay(i, 10 - Monst->_mint + random_(105, 10));
3173 	}
3174 	if (Monst->_mmode == MM_STAND)
3175 		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir];
3176 }
3177 
MAI_Bat(int i)3178 void MAI_Bat(int i)
3179 {
3180 	MonsterStruct *Monst;
3181 	int md, v, pnum;
3182 	int fx, fy, xd, yd;
3183 
3184 	assurance((DWORD)i < MAXMONSTERS, i);
3185 
3186 	Monst = &monster[i];
3187 	pnum = Monst->_menemy;
3188 	if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) {
3189 		return;
3190 	}
3191 
3192 	xd = Monst->_mx - Monst->_menemyx;
3193 	yd = Monst->_my - Monst->_menemyy;
3194 	md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty);
3195 	Monst->_mdir = md;
3196 	v = random_(107, 100);
3197 	if (Monst->_mgoal == MGOAL_RETREAT) {
3198 		if (!Monst->_mgoalvar1) {
3199 			M_CallWalk(i, opposite[md]);
3200 			Monst->_mgoalvar1++;
3201 		} else {
3202 			if (random_(108, 2) != 0)
3203 				M_CallWalk(i, left[md]);
3204 			else
3205 				M_CallWalk(i, right[md]);
3206 			Monst->_mgoal = MGOAL_NORMAL;
3207 		}
3208 		return;
3209 	}
3210 
3211 	fx = Monst->_menemyx;
3212 	fy = Monst->_menemyy;
3213 	if (Monst->MType->mtype == MT_GLOOM
3214 	    && (abs(xd) >= 5 || abs(yd) >= 5)
3215 	    && v < 4 * Monst->_mint + 33
3216 	    && LineClearF1(PosOkMonst, i, Monst->_mx, Monst->_my, fx, fy)) {
3217 		if (AddMissile(Monst->_mx, Monst->_my, fx, fy, md, MIS_RHINO, pnum, i, 0, 0) != -1) {
3218 			dMonster[Monst->_mx][Monst->_my] = -(i + 1);
3219 			Monst->_mmode = MM_CHARGE;
3220 		}
3221 	} else if (abs(xd) >= 2 || abs(yd) >= 2) {
3222 		if (Monst->_mVar2 > 20 && v < Monst->_mint + 13
3223 		    || (Monst->_mVar1 == MM_WALK || Monst->_mVar1 == MM_WALK2 || Monst->_mVar1 == MM_WALK3)
3224 		        && Monst->_mVar2 == 0
3225 		        && v < Monst->_mint + 63) {
3226 			M_CallWalk(i, md);
3227 		}
3228 	} else if (v < 4 * Monst->_mint + 8) {
3229 		M_StartAttack(i);
3230 		Monst->_mgoal = MGOAL_RETREAT;
3231 		Monst->_mgoalvar1 = 0;
3232 		if (Monst->MType->mtype == MT_FAMILIAR) {
3233 			AddMissile(Monst->_menemyx, Monst->_menemyy, Monst->_menemyx + 1, 0, -1, MIS_LIGHTNING, TARGET_PLAYERS, i, random_(109, 10) + 1, 0);
3234 		}
3235 	}
3236 
3237 	if (Monst->_mmode == MM_STAND)
3238 		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
3239 }
3240 
MAI_SkelBow(int i)3241 void MAI_SkelBow(int i)
3242 {
3243 	MonsterStruct *Monst;
3244 	int mx, my, md, v;
3245 	BOOL walking;
3246 
3247 	walking = FALSE;
3248 	assurance((DWORD)i < MAXMONSTERS, i);
3249 
3250 	Monst = &monster[i];
3251 	if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) {
3252 		return;
3253 	}
3254 
3255 	mx = Monst->_mx - Monst->_menemyx;
3256 	my = Monst->_my - Monst->_menemyy;
3257 
3258 	md = M_GetDir(i);
3259 	Monst->_mdir = md;
3260 	v = random_(110, 100);
3261 
3262 	if (abs(mx) < 4 && abs(my) < 4) {
3263 		if (Monst->_mVar2 > 20 && v < 2 * Monst->_mint + 13
3264 		    || (Monst->_mVar1 == MM_WALK || Monst->_mVar1 == MM_WALK2 || Monst->_mVar1 == MM_WALK3)
3265 		        && Monst->_mVar2 == 0
3266 		        && v < 2 * Monst->_mint + 63) {
3267 			walking = M_DumbWalk(i, opposite[md]);
3268 		}
3269 	}
3270 
3271 	mx = Monst->_menemyx;
3272 	my = Monst->_menemyy;
3273 	if (!walking) {
3274 		if (random_(110, 100) < 2 * Monst->_mint + 3) {
3275 			if (LineClear(Monst->_mx, Monst->_my, mx, my))
3276 				M_StartRAttack(i, MIS_ARROW, 4);
3277 		}
3278 	}
3279 
3280 	if (Monst->_mmode == MM_STAND)
3281 		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
3282 }
3283 
MAI_Fat(int i)3284 void MAI_Fat(int i)
3285 {
3286 	MonsterStruct *Monst;
3287 	int mx, my, md, v;
3288 
3289 	assurance((DWORD)i < MAXMONSTERS, i);
3290 
3291 	Monst = &monster[i];
3292 	if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) {
3293 		return;
3294 	}
3295 
3296 	mx = Monst->_mx - Monst->_menemyx;
3297 	my = Monst->_my - Monst->_menemyy;
3298 	md = M_GetDir(i);
3299 	Monst->_mdir = md;
3300 	v = random_(111, 100);
3301 	if (abs(mx) >= 2 || abs(my) >= 2) {
3302 		if (Monst->_mVar2 > 20 && v < 4 * Monst->_mint + 20
3303 		    || (Monst->_mVar1 == MM_WALK || Monst->_mVar1 == MM_WALK2 || Monst->_mVar1 == MM_WALK3)
3304 		        && Monst->_mVar2 == 0
3305 		        && v < 4 * Monst->_mint + 70) {
3306 			M_CallWalk(i, md);
3307 		}
3308 	} else if (v < 4 * Monst->_mint + 15) {
3309 		M_StartAttack(i);
3310 	} else if (v < 4 * Monst->_mint + 20) {
3311 		M_StartSpAttack(i);
3312 	}
3313 
3314 	if (Monst->_mmode == MM_STAND)
3315 		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
3316 }
3317 
MAI_Sneak(int i)3318 void MAI_Sneak(int i)
3319 {
3320 	MonsterStruct *Monst;
3321 	int mx, my, md;
3322 	int dist, v;
3323 
3324 	assurance((DWORD)i < MAXMONSTERS, i);
3325 
3326 	Monst = &monster[i];
3327 	if (Monst->_mmode == MM_STAND) {
3328 		mx = Monst->_mx;
3329 		my = Monst->_my;
3330 		if (dLight[mx][my] != lightmax) {
3331 			mx -= Monst->_menemyx;
3332 			my -= Monst->_menemyy;
3333 
3334 			md = M_GetDir(i);
3335 			dist = 5 - Monst->_mint;
3336 			if (Monst->_mVar1 == MM_GOTHIT) {
3337 				Monst->_mgoal = MGOAL_RETREAT;
3338 				Monst->_mgoalvar1 = 0;
3339 			} else {
3340 				if (abs(mx) >= dist + 3 || abs(my) >= dist + 3 || Monst->_mgoalvar1 > 8) {
3341 					Monst->_mgoal = MGOAL_NORMAL;
3342 					Monst->_mgoalvar1 = 0;
3343 				}
3344 			}
3345 			if (Monst->_mgoal == MGOAL_RETREAT && !(Monst->_mFlags & MFLAG_NO_ENEMY)) {
3346 				if (Monst->_mFlags & MFLAG_TARGETS_MONSTER)
3347 					md = GetDirection(Monst->_mx, Monst->_my, monster[Monst->_menemy]._mx, monster[Monst->_menemy]._my);
3348 				else
3349 					md = GetDirection(Monst->_mx, Monst->_my, plr[Monst->_menemy]._pownerx, plr[Monst->_menemy]._pownery);
3350 				md = opposite[md];
3351 				if (Monst->MType->mtype == MT_UNSEEN) {
3352 					if (random_(112, 2) != 0)
3353 						md = left[md];
3354 					else
3355 						md = right[md];
3356 				}
3357 			}
3358 			Monst->_mdir = md;
3359 			v = random_(112, 100);
3360 			if (abs(mx) < dist && abs(my) < dist && Monst->_mFlags & MFLAG_HIDDEN) {
3361 				M_StartFadein(i, md, FALSE);
3362 			} else {
3363 				if ((abs(mx) >= dist + 1 || abs(my) >= dist + 1) && !(Monst->_mFlags & MFLAG_HIDDEN)) {
3364 					M_StartFadeout(i, md, TRUE);
3365 				} else {
3366 					if (Monst->_mgoal == MGOAL_RETREAT
3367 					    || (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)) {
3368 						Monst->_mgoalvar1++;
3369 						M_CallWalk(i, md);
3370 					}
3371 				}
3372 			}
3373 			if (Monst->_mmode == MM_STAND) {
3374 				if (abs(mx) >= 2 || abs(my) >= 2 || v >= 4 * Monst->_mint + 10)
3375 					Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
3376 				else
3377 					M_StartAttack(i);
3378 			}
3379 		}
3380 	}
3381 }
3382 
MAI_Fireman(int i)3383 void MAI_Fireman(int i)
3384 {
3385 	int xd, yd;
3386 	int md, pnum;
3387 	int fx, fy;
3388 	MonsterStruct *Monst;
3389 
3390 	assurance((DWORD)i < MAXMONSTERS, i);
3391 
3392 	Monst = &monster[i];
3393 	if (monster[i]._mmode != MM_STAND || Monst->_msquelch == 0)
3394 		return;
3395 
3396 	pnum = monster[i]._menemy;
3397 	fx = monster[i]._menemyx;
3398 	fy = monster[i]._menemyy;
3399 	xd = monster[i]._mx - fx;
3400 	yd = monster[i]._my - fy;
3401 
3402 	md = M_GetDir(i);
3403 	if (Monst->_mgoal == MGOAL_NORMAL) {
3404 		if (LineClear(Monst->_mx, Monst->_my, fx, fy)
3405 		    && AddMissile(Monst->_mx, Monst->_my, fx, fy, md, MIS_FIREMAN, pnum, i, 0, 0) != -1) {
3406 			Monst->_mmode = MM_CHARGE;
3407 			Monst->_mgoal = MGOAL_ATTACK2;
3408 			Monst->_mgoalvar1 = 0;
3409 		}
3410 	} else if (Monst->_mgoal == MGOAL_ATTACK2) {
3411 		if (Monst->_mgoalvar1 == 3) {
3412 			Monst->_mgoal = MGOAL_NORMAL;
3413 			M_StartFadeout(i, md, TRUE);
3414 		} else if (LineClear(Monst->_mx, Monst->_my, fx, fy)) {
3415 			M_StartRAttack(i, MIS_KRULL, 4);
3416 			Monst->_mgoalvar1++;
3417 		} else {
3418 			M_StartDelay(i, random_(112, 10) + 5);
3419 			Monst->_mgoalvar1++;
3420 		}
3421 	} else if (Monst->_mgoal == MGOAL_RETREAT) {
3422 		M_StartFadein(i, md, FALSE);
3423 		Monst->_mgoal = MGOAL_ATTACK2;
3424 	}
3425 	Monst->_mdir = md;
3426 	random_(112, 100);
3427 	if (Monst->_mmode != MM_STAND)
3428 		return;
3429 
3430 	if (abs(xd) < 2 && abs(yd) < 2 && Monst->_mgoal == MGOAL_NORMAL) {
3431 		M_TryH2HHit(i, monster[i]._menemy, monster[i].mHit, monster[i].mMinDamage, monster[i].mMaxDamage);
3432 		Monst->_mgoal = MGOAL_RETREAT;
3433 		if (!M_CallWalk(i, opposite[md])) {
3434 			M_StartFadein(i, md, FALSE);
3435 			Monst->_mgoal = MGOAL_ATTACK2;
3436 		}
3437 	} else if (!M_CallWalk(i, md) && (Monst->_mgoal == MGOAL_NORMAL || Monst->_mgoal == MGOAL_RETREAT)) {
3438 		M_StartFadein(i, md, FALSE);
3439 		Monst->_mgoal = MGOAL_ATTACK2;
3440 	}
3441 }
3442 
MAI_Fallen(int i)3443 void MAI_Fallen(int i)
3444 {
3445 	int x, y, xpos, ypos;
3446 	int m, rad;
3447 	MonsterStruct *Monst;
3448 
3449 	assurance((DWORD)i < MAXMONSTERS, i);
3450 
3451 	Monst = &monster[i];
3452 	if (Monst->_mgoal == MGOAL_ATTACK2) {
3453 		if (Monst->_mgoalvar1 != 0)
3454 			Monst->_mgoalvar1--;
3455 		else
3456 			Monst->_mgoal = MGOAL_NORMAL;
3457 	}
3458 	if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) {
3459 		return;
3460 	}
3461 
3462 	if (Monst->_mgoal == MGOAL_RETREAT) {
3463 		if (Monst->_mgoalvar1-- == 0) {
3464 			Monst->_mgoal = MGOAL_NORMAL;
3465 			M_StartStand(i, opposite[Monst->_mdir]);
3466 		}
3467 	}
3468 
3469 	if (Monst->_mAnimFrame == Monst->_mAnimLen) {
3470 		if (random_(113, 4) != 0) {
3471 			return;
3472 		}
3473 		if (!(monster[i]._mFlags & MFLAG_NOHEAL)) { // CODEFIX: - change to Monst-> in devilutionx
3474 			M_StartSpStand(i, Monst->_mdir);
3475 			if (Monst->_mmaxhp - (2 * Monst->_mint + 2) >= Monst->_mhitpoints)
3476 				Monst->_mhitpoints += 2 * Monst->_mint + 2;
3477 			else
3478 				Monst->_mhitpoints = Monst->_mmaxhp;
3479 		}
3480 		rad = 2 * Monst->_mint + 4;
3481 		for (y = -rad; y <= rad; y++) {
3482 			for (x = -rad; x <= rad; x++) {
3483 				xpos = Monst->_mx + x;
3484 				ypos = Monst->_my + y;
3485 				if (y >= 0 && y < MAXDUNY && x >= 0 && x < MAXDUNX) {
3486 					m = dMonster[xpos][ypos];
3487 					if (m > 0) {
3488 						m--;
3489 						if (monster[m]._mAi == AI_FALLEN) {
3490 							monster[m]._mgoal = MGOAL_ATTACK2;
3491 							monster[m]._mgoalvar1 = 30 * Monst->_mint + 105;
3492 						}
3493 					}
3494 				}
3495 			}
3496 		}
3497 	} else if (Monst->_mgoal == MGOAL_RETREAT) {
3498 		M_CallWalk(i, Monst->_mdir);
3499 	} else if (Monst->_mgoal == MGOAL_ATTACK2) {
3500 		xpos = Monst->_mx - Monst->_menemyx;
3501 		ypos = Monst->_my - Monst->_menemyy;
3502 		if (abs(xpos) < 2 && abs(ypos) < 2)
3503 			M_StartAttack(i);
3504 		else
3505 			M_CallWalk(i, M_GetDir(i));
3506 	} else
3507 		MAI_SkelSd(i);
3508 }
3509 
MAI_Cleaver(int i)3510 void MAI_Cleaver(int i)
3511 {
3512 	MonsterStruct *Monst;
3513 	int x, y, mx, my, md;
3514 
3515 	assurance((DWORD)i < MAXMONSTERS, i);
3516 
3517 	Monst = &monster[i];
3518 	if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) {
3519 		return;
3520 	}
3521 
3522 	mx = Monst->_mx;
3523 	my = Monst->_my;
3524 	x = mx - Monst->_menemyx;
3525 	y = my - Monst->_menemyy;
3526 
3527 	md = GetDirection(mx, my, Monst->_lastx, Monst->_lasty);
3528 	Monst->_mdir = md;
3529 
3530 	if (abs(x) >= 2 || abs(y) >= 2)
3531 		M_CallWalk(i, md);
3532 	else
3533 		M_StartAttack(i);
3534 
3535 	if (Monst->_mmode == MM_STAND)
3536 		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
3537 }
3538 
MAI_Round(int i,BOOL special)3539 void MAI_Round(int i, BOOL special)
3540 {
3541 	MonsterStruct *Monst;
3542 	int fx, fy;
3543 	int mx, my, md;
3544 	int dist, v;
3545 
3546 	assurance((DWORD)i < MAXMONSTERS, i);
3547 	Monst = &monster[i];
3548 	if (Monst->_mmode == MM_STAND && Monst->_msquelch != 0) {
3549 		fy = Monst->_menemyy;
3550 		fx = Monst->_menemyx;
3551 		mx = Monst->_mx - fx;
3552 		my = Monst->_my - fy;
3553 		md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty);
3554 		if (Monst->_msquelch < UCHAR_MAX)
3555 			MonstCheckDoors(i);
3556 		v = random_(114, 100);
3557 		if ((abs(mx) >= 2 || abs(my) >= 2) && Monst->_msquelch == UCHAR_MAX && dTransVal[Monst->_mx][Monst->_my] == dTransVal[fx][fy]) {
3558 			if (Monst->_mgoal == MGOAL_MOVE || (abs(mx) >= 4 || abs(my) >= 4) && random_(115, 4) == 0) {
3559 				if (Monst->_mgoal != MGOAL_MOVE) {
3560 					Monst->_mgoalvar1 = 0;
3561 					Monst->_mgoalvar2 = random_(116, 2);
3562 				}
3563 				Monst->_mgoal = MGOAL_MOVE;
3564 				if (abs(mx) > abs(my))
3565 					dist = abs(mx);
3566 				else
3567 					dist = abs(my);
3568 				if (Monst->_mgoalvar1++ >= 2 * dist && DirOK(i, md) || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) {
3569 					Monst->_mgoal = MGOAL_NORMAL;
3570 				} else if (!M_RoundWalk(i, md, &Monst->_mgoalvar2)) {
3571 					M_StartDelay(i, random_(125, 10) + 10);
3572 				}
3573 			}
3574 		} else
3575 			Monst->_mgoal = MGOAL_NORMAL;
3576 		if (Monst->_mgoal == MGOAL_NORMAL) {
3577 			if (abs(mx) >= 2 || abs(my) >= 2) {
3578 				if (Monst->_mVar2 > 20 && v < 2 * Monst->_mint + 28
3579 				    || (Monst->_mVar1 == MM_WALK || Monst->_mVar1 == MM_WALK2 || Monst->_mVar1 == MM_WALK3)
3580 				        && Monst->_mVar2 == 0
3581 				        && v < 2 * Monst->_mint + 78) {
3582 					M_CallWalk(i, md);
3583 				}
3584 			} else if (v < 2 * Monst->_mint + 23) {
3585 				Monst->_mdir = md;
3586 				if (special && Monst->_mhitpoints < (Monst->_mmaxhp >> 1) && random_(117, 2) != 0)
3587 					M_StartSpAttack(i);
3588 				else
3589 					M_StartAttack(i);
3590 			}
3591 		}
3592 		if (Monst->_mmode == MM_STAND)
3593 			Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
3594 	}
3595 }
3596 
MAI_GoatMc(int i)3597 void MAI_GoatMc(int i)
3598 {
3599 	MAI_Round(i, TRUE);
3600 }
3601 
MAI_Ranged(int i,int missile_type,BOOL special)3602 void MAI_Ranged(int i, int missile_type, BOOL special)
3603 {
3604 	int md;
3605 	int fx, fy, mx, my;
3606 	MonsterStruct *Monst;
3607 
3608 	assurance((DWORD)i < MAXMONSTERS, i);
3609 
3610 	if (monster[i]._mmode != MM_STAND) {
3611 		return;
3612 	}
3613 
3614 	Monst = &monster[i];
3615 	if (Monst->_msquelch == UCHAR_MAX || Monst->_mFlags & MFLAG_TARGETS_MONSTER) {
3616 		fx = Monst->_menemyx;
3617 		fy = Monst->_menemyy;
3618 		mx = Monst->_mx - fx;
3619 		my = Monst->_my - fy;
3620 		md = M_GetDir(i);
3621 		if (Monst->_msquelch < UCHAR_MAX)
3622 			MonstCheckDoors(i);
3623 		Monst->_mdir = md;
3624 		if (Monst->_mVar1 == MM_RATTACK) {
3625 			M_StartDelay(i, random_(118, 20));
3626 		} else if (abs(mx) < 4 && abs(my) < 4) {
3627 			if (random_(119, 100) < 10 * (Monst->_mint + 7))
3628 				M_CallWalk(i, opposite[md]);
3629 		}
3630 		if (Monst->_mmode == MM_STAND) {
3631 			if (LineClear(Monst->_mx, Monst->_my, fx, fy)) {
3632 				if (special)
3633 					M_StartRSpAttack(i, missile_type, 4);
3634 				else
3635 					M_StartRAttack(i, missile_type, 4);
3636 			} else {
3637 				Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
3638 			}
3639 		}
3640 	} else if (Monst->_msquelch != 0) {
3641 		fx = Monst->_lastx;
3642 		fy = Monst->_lasty;
3643 		md = GetDirection(Monst->_mx, Monst->_my, fx, fy);
3644 		M_CallWalk(i, md);
3645 	}
3646 }
3647 
MAI_GoatBow(int i)3648 void MAI_GoatBow(int i)
3649 {
3650 	MAI_Ranged(i, MIS_ARROW, FALSE);
3651 }
3652 
MAI_Succ(int i)3653 void MAI_Succ(int i)
3654 {
3655 	MAI_Ranged(i, MIS_FLARE, FALSE);
3656 }
3657 
MAI_Lich(int i)3658 void MAI_Lich(int i)
3659 {
3660 	MAI_Ranged(i, MIS_LICH, FALSE);
3661 }
3662 
MAI_ArchLich(int i)3663 void MAI_ArchLich(int i)
3664 {
3665 	MAI_Ranged(i, MIS_ARCHLICH, FALSE);
3666 }
3667 
MAI_Psychorb(int i)3668 void MAI_Psychorb(int i)
3669 {
3670 	MAI_Ranged(i, MIS_PSYCHORB, FALSE);
3671 }
3672 
MAI_Necromorb(int i)3673 void MAI_Necromorb(int i)
3674 {
3675 	MAI_Ranged(i, MIS_NECROMORB, FALSE);
3676 }
3677 
MAI_AcidUniq(int i)3678 void MAI_AcidUniq(int i)
3679 {
3680 	MAI_Ranged(i, MIS_ACID, TRUE);
3681 }
3682 
MAI_Firebat(int i)3683 void MAI_Firebat(int i)
3684 {
3685 	MAI_Ranged(i, MIS_FIREBOLT, FALSE);
3686 }
3687 
MAI_Torchant(int i)3688 void MAI_Torchant(int i)
3689 {
3690 	MAI_Ranged(i, MIS_FIREBALL, FALSE);
3691 }
3692 
MAI_Scav(int i)3693 void MAI_Scav(int i)
3694 {
3695 	BOOL done;
3696 	int x, y;
3697 	MonsterStruct *Monst;
3698 
3699 	assurance((DWORD)i < MAXMONSTERS, i);
3700 	Monst = &monster[i];
3701 	done = FALSE;
3702 	if (monster[i]._mmode != MM_STAND)
3703 		return;
3704 	if (Monst->_mhitpoints < (Monst->_mmaxhp >> 1) && Monst->_mgoal != MGOAL_HEALING) {
3705 		if (Monst->leaderflag != 0) {
3706 			monster[Monst->leader].packsize--;
3707 			Monst->leaderflag = 0;
3708 		}
3709 		Monst->_mgoal = MGOAL_HEALING;
3710 		Monst->_mgoalvar3 = 10;
3711 	}
3712 	if (Monst->_mgoal == MGOAL_HEALING && Monst->_mgoalvar3 != 0) {
3713 		Monst->_mgoalvar3--;
3714 		if (dDead[Monst->_mx][Monst->_my] != 0) {
3715 			M_StartEat(i);
3716 			if (!(Monst->_mFlags & MFLAG_NOHEAL)) {
3717 				if (gbIsHellfire) {
3718 					int mMaxHP = Monst->_mmaxhp; // BUGFIX use _mmaxhp or we loose health when difficulty isn't normal (fixed)
3719 					Monst->_mhitpoints += mMaxHP >> 3;
3720 					if (Monst->_mhitpoints > Monst->_mmaxhp)
3721 						Monst->_mhitpoints = Monst->_mmaxhp;
3722 					if (Monst->_mgoalvar3 <= 0 || Monst->_mhitpoints == Monst->_mmaxhp)
3723 						dDead[Monst->_mx][Monst->_my] = 0;
3724 				} else {
3725 					Monst->_mhitpoints += 64;
3726 				}
3727 			}
3728 			int targetHealth = Monst->_mmaxhp;
3729 			if (!gbIsHellfire)
3730 				targetHealth = (Monst->_mmaxhp >> 1) + (Monst->_mmaxhp >> 2);
3731 			if (Monst->_mhitpoints >= targetHealth) {
3732 				Monst->_mgoal = MGOAL_NORMAL;
3733 				Monst->_mgoalvar1 = 0;
3734 				Monst->_mgoalvar2 = 0;
3735 			}
3736 		} else {
3737 			if (Monst->_mgoalvar1 == 0) {
3738 				if (random_(120, 2) != 0) {
3739 					for (y = -4; y <= 4 && !done; y++) {
3740 						for (x = -4; x <= 4 && !done; x++) {
3741 							// BUGFIX: incorrect check of offset against limits of the dungeon
3742 							if (y < 0 || y >= MAXDUNY || x < 0 || x >= MAXDUNX)
3743 								continue;
3744 							done = dDead[Monst->_mx + x][Monst->_my + y] != 0
3745 							    && LineClearF(
3746 							        CheckNoSolid,
3747 							        Monst->_mx,
3748 							        Monst->_my,
3749 							        Monst->_mx + x,
3750 							        Monst->_my + y);
3751 						}
3752 					}
3753 					x--;
3754 					y--;
3755 				} else {
3756 					for (y = 4; y >= -4 && !done; y--) {
3757 						for (x = 4; x >= -4 && !done; x--) {
3758 							// BUGFIX: incorrect check of offset against limits of the dungeon
3759 							if (y < 0 || y >= MAXDUNY || x < 0 || x >= MAXDUNX)
3760 								continue;
3761 							done = dDead[Monst->_mx + x][Monst->_my + y] != 0
3762 							    && LineClearF(
3763 							        CheckNoSolid,
3764 							        Monst->_mx,
3765 							        Monst->_my,
3766 							        Monst->_mx + x,
3767 							        Monst->_my + y);
3768 						}
3769 					}
3770 					x++;
3771 					y++;
3772 				}
3773 				if (done) {
3774 					Monst->_mgoalvar1 = x + Monst->_mx + 1;
3775 					Monst->_mgoalvar2 = y + Monst->_my + 1;
3776 				}
3777 			}
3778 			if (Monst->_mgoalvar1) {
3779 				x = Monst->_mgoalvar1 - 1;
3780 				y = Monst->_mgoalvar2 - 1;
3781 				Monst->_mdir = GetDirection(Monst->_mx, Monst->_my, x, y);
3782 				M_CallWalk(i, Monst->_mdir);
3783 			}
3784 		}
3785 	}
3786 
3787 	if (Monst->_mmode == MM_STAND)
3788 		MAI_SkelSd(i);
3789 }
3790 
MAI_Garg(int i)3791 void MAI_Garg(int i)
3792 {
3793 	MonsterStruct *Monst;
3794 	int mx, my, dx, dy, md;
3795 
3796 	assurance((DWORD)i < MAXMONSTERS, i);
3797 
3798 	Monst = &monster[i];
3799 	dx = Monst->_mx - Monst->_lastx;
3800 	dy = Monst->_my - Monst->_lasty;
3801 	md = M_GetDir(i);
3802 	if (Monst->_msquelch != 0 && Monst->_mFlags & MFLAG_ALLOW_SPECIAL) {
3803 		M_Enemy(i);
3804 		mx = Monst->_mx - Monst->_menemyx;
3805 		my = Monst->_my - Monst->_menemyy;
3806 		if (abs(mx) < Monst->_mint + 2 && abs(my) < Monst->_mint + 2) {
3807 			Monst->_mFlags &= ~MFLAG_ALLOW_SPECIAL;
3808 		}
3809 		return;
3810 	}
3811 
3812 	if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) {
3813 		return;
3814 	}
3815 
3816 	if (Monst->_mhitpoints < (Monst->_mmaxhp >> 1))
3817 		if (!(Monst->_mFlags & MFLAG_NOHEAL))
3818 			Monst->_mgoal = MGOAL_RETREAT;
3819 	if (Monst->_mgoal == MGOAL_RETREAT) {
3820 		if (abs(dx) >= Monst->_mint + 2 || abs(dy) >= Monst->_mint + 2) {
3821 			Monst->_mgoal = MGOAL_NORMAL;
3822 			M_StartHeal(i);
3823 		} else if (!M_CallWalk(i, opposite[md])) {
3824 			Monst->_mgoal = MGOAL_NORMAL;
3825 		}
3826 	}
3827 	MAI_Round(i, FALSE);
3828 }
3829 
MAI_RoundRanged(int i,int missile_type,BOOL checkdoors,int dam,int lessmissiles)3830 void MAI_RoundRanged(int i, int missile_type, BOOL checkdoors, int dam, int lessmissiles)
3831 {
3832 	MonsterStruct *Monst;
3833 	int mx, my;
3834 	int fx, fy;
3835 	int md, dist, v;
3836 
3837 	assurance((DWORD)i < MAXMONSTERS, i);
3838 	Monst = &monster[i];
3839 	if (Monst->_mmode == MM_STAND && Monst->_msquelch != 0) {
3840 		fx = Monst->_menemyx;
3841 		fy = Monst->_menemyy;
3842 		mx = Monst->_mx - fx;
3843 		my = Monst->_my - fy;
3844 		md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty);
3845 		if (checkdoors && Monst->_msquelch < UCHAR_MAX)
3846 			MonstCheckDoors(i);
3847 		v = random_(121, 10000);
3848 		dist = std::max(abs(mx), abs(my));
3849 		if (dist >= 2 && Monst->_msquelch == UCHAR_MAX && dTransVal[Monst->_mx][Monst->_my] == dTransVal[fx][fy]) {
3850 			if (Monst->_mgoal == MGOAL_MOVE || (dist >= 3 && random_(122, 4 << lessmissiles) == 0)) {
3851 				if (Monst->_mgoal != MGOAL_MOVE) {
3852 					Monst->_mgoalvar1 = 0;
3853 					Monst->_mgoalvar2 = random_(123, 2);
3854 				}
3855 				Monst->_mgoal = MGOAL_MOVE;
3856 				if (Monst->_mgoalvar1++ >= 2 * dist && DirOK(i, md)) {
3857 					Monst->_mgoal = MGOAL_NORMAL;
3858 				} else if (v < (500 * (Monst->_mint + 1) >> lessmissiles)
3859 				    && (LineClear(Monst->_mx, Monst->_my, fx, fy))) {
3860 					M_StartRSpAttack(i, missile_type, dam);
3861 				} else {
3862 					M_RoundWalk(i, md, &Monst->_mgoalvar2);
3863 				}
3864 			}
3865 		} else {
3866 			Monst->_mgoal = MGOAL_NORMAL;
3867 		}
3868 		if (Monst->_mgoal == MGOAL_NORMAL) {
3869 			if ((dist >= 3 && v < ((500 * (Monst->_mint + 2)) >> lessmissiles)
3870 			        || v < ((500 * (Monst->_mint + 1)) >> lessmissiles))
3871 			    && LineClear(Monst->_mx, Monst->_my, fx, fy)) {
3872 				M_StartRSpAttack(i, missile_type, dam);
3873 			} else if (dist >= 2) {
3874 				v = random_(124, 100);
3875 				if (v < 1000 * (Monst->_mint + 5)
3876 				    || (Monst->_mVar1 == MM_WALK || Monst->_mVar1 == MM_WALK2 || Monst->_mVar1 == MM_WALK3) && Monst->_mVar2 == 0 && v < 1000 * (Monst->_mint + 8)) {
3877 					M_CallWalk(i, md);
3878 				}
3879 			} else if (v < 1000 * (Monst->_mint + 6)) {
3880 				Monst->_mdir = md;
3881 				M_StartAttack(i);
3882 			}
3883 		}
3884 		if (Monst->_mmode == MM_STAND) {
3885 			M_StartDelay(i, random_(125, 10) + 5);
3886 		}
3887 	}
3888 }
3889 
MAI_Magma(int i)3890 void MAI_Magma(int i)
3891 {
3892 	MAI_RoundRanged(i, MIS_MAGMABALL, TRUE, 4, 0);
3893 }
3894 
MAI_Storm(int i)3895 void MAI_Storm(int i)
3896 {
3897 	MAI_RoundRanged(i, MIS_LIGHTCTRL2, TRUE, 4, 0);
3898 }
3899 
MAI_BoneDemon(int i)3900 void MAI_BoneDemon(int i)
3901 {
3902 	MAI_RoundRanged(i, MIS_BONEDEMON, TRUE, 4, 0);
3903 }
3904 
MAI_Acid(int i)3905 void MAI_Acid(int i)
3906 {
3907 	MAI_RoundRanged(i, MIS_ACID, FALSE, 4, 1);
3908 }
3909 
MAI_Diablo(int i)3910 void MAI_Diablo(int i)
3911 {
3912 	MAI_RoundRanged(i, MIS_DIABAPOCA, FALSE, 40, 0);
3913 }
3914 
MAI_RR2(int i,int mistype,int dam)3915 void MAI_RR2(int i, int mistype, int dam)
3916 {
3917 	MonsterStruct *Monst;
3918 	int mx, my, fx, fy;
3919 	int dist, v, md;
3920 
3921 	assurance((DWORD)i < MAXMONSTERS, i);
3922 
3923 	Monst = &monster[i];
3924 	mx = Monst->_mx - Monst->_menemyx;
3925 	my = Monst->_my - Monst->_menemyy;
3926 	if (abs(mx) >= 5 || abs(my) >= 5) {
3927 		MAI_SkelSd(i);
3928 		return;
3929 	}
3930 
3931 	if (Monst->_mmode == MM_STAND && Monst->_msquelch != 0) {
3932 		fx = Monst->_menemyx;
3933 		fy = Monst->_menemyy;
3934 		mx = Monst->_mx - fx;
3935 		my = Monst->_my - fy;
3936 		md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty);
3937 		if (Monst->_msquelch < UCHAR_MAX)
3938 			MonstCheckDoors(i);
3939 		v = random_(121, 100);
3940 		dist = std::max(abs(mx), abs(my));
3941 		if (dist >= 2 && Monst->_msquelch == UCHAR_MAX && dTransVal[Monst->_mx][Monst->_my] == dTransVal[fx][fy]) {
3942 			if (Monst->_mgoal == MGOAL_MOVE || dist >= 3) {
3943 				if (Monst->_mgoal != MGOAL_MOVE) {
3944 					Monst->_mgoalvar1 = 0;
3945 					Monst->_mgoalvar2 = random_(123, 2);
3946 				}
3947 				Monst->_mgoal = MGOAL_MOVE;
3948 				Monst->_mgoalvar3 = 4;
3949 				if (Monst->_mgoalvar1++ < 2 * dist || !DirOK(i, md)) {
3950 					if (v < 5 * (Monst->_mint + 16))
3951 						M_RoundWalk(i, md, &Monst->_mgoalvar2);
3952 				} else
3953 					Monst->_mgoal = MGOAL_NORMAL;
3954 			}
3955 		} else
3956 			Monst->_mgoal = MGOAL_NORMAL;
3957 		if (Monst->_mgoal == MGOAL_NORMAL) {
3958 			if ((dist >= 3 && v < 5 * (Monst->_mint + 2) || v < 5 * (Monst->_mint + 1) || Monst->_mgoalvar3 == 4) && LineClear(Monst->_mx, Monst->_my, fx, fy)) {
3959 				M_StartRSpAttack(i, mistype, dam);
3960 			} else if (dist >= 2) {
3961 				v = random_(124, 100);
3962 				if (v < 2 * (5 * Monst->_mint + 25)
3963 				    || (Monst->_mVar1 == MM_WALK || Monst->_mVar1 == MM_WALK2 || Monst->_mVar1 == MM_WALK3)
3964 				        && Monst->_mVar2 == 0
3965 				        && v < 2 * (5 * Monst->_mint + 40)) {
3966 					M_CallWalk(i, md);
3967 				}
3968 			} else {
3969 				if (random_(124, 100) < 10 * (Monst->_mint + 4)) {
3970 					Monst->_mdir = md;
3971 					if (random_(124, 2) != 0)
3972 						M_StartAttack(i);
3973 					else
3974 						M_StartRSpAttack(i, mistype, dam);
3975 				}
3976 			}
3977 			Monst->_mgoalvar3 = 1;
3978 		}
3979 		if (Monst->_mmode == MM_STAND) {
3980 			M_StartDelay(i, random_(125, 10) + 5);
3981 		}
3982 	}
3983 }
3984 
MAI_Mega(int i)3985 void MAI_Mega(int i)
3986 {
3987 	MAI_RR2(i, MIS_FLAMEC, 0);
3988 }
3989 
MAI_Golum(int i)3990 void MAI_Golum(int i)
3991 {
3992 	int mx, my, _mex, _mey;
3993 	int md, j, k, _menemy;
3994 	MonsterStruct *Monst;
3995 	BOOL have_enemy, ok;
3996 
3997 	assurance((DWORD)i < MAXMONSTERS, i);
3998 
3999 	Monst = &monster[i];
4000 	if (Monst->_mx == 1 && Monst->_my == 0) {
4001 		return;
4002 	}
4003 
4004 	if (Monst->_mmode == MM_DEATH
4005 	    || Monst->_mmode == MM_SPSTAND
4006 	    || (Monst->_mmode >= MM_WALK && Monst->_mmode <= MM_WALK3)) {
4007 		return;
4008 	}
4009 
4010 	if (!(Monst->_mFlags & MFLAG_TARGETS_MONSTER))
4011 		M_Enemy(i);
4012 
4013 	have_enemy = !(monster[i]._mFlags & MFLAG_NO_ENEMY);
4014 
4015 	if (Monst->_mmode == MM_ATTACK) {
4016 		return;
4017 	}
4018 
4019 	_menemy = monster[i]._menemy;
4020 
4021 	mx = monster[i]._mx;
4022 	my = monster[i]._my;
4023 	_mex = mx - monster[_menemy]._mfutx;
4024 	_mey = my - monster[_menemy]._mfuty;
4025 	md = GetDirection(mx, my, monster[_menemy]._mx, monster[_menemy]._my);
4026 	monster[i]._mdir = md;
4027 	if (abs(_mex) < 2 && abs(_mey) < 2 && have_enemy) {
4028 		_menemy = monster[i]._menemy;
4029 		monster[i]._menemyx = monster[_menemy]._mx;
4030 		monster[i]._menemyy = monster[_menemy]._my;
4031 		if (monster[_menemy]._msquelch == 0) {
4032 			monster[_menemy]._msquelch = UCHAR_MAX;
4033 			monster[monster[i]._menemy]._lastx = monster[i]._mx;
4034 			monster[monster[i]._menemy]._lasty = monster[i]._my;
4035 			for (j = 0; j < 5; j++) {
4036 				for (k = 0; k < 5; k++) {
4037 					_menemy = dMonster[monster[i]._mx + k - 2][monster[i]._my + j - 2];
4038 					if (_menemy > 0)
4039 						monster[_menemy - 1]._msquelch = UCHAR_MAX; // BUGFIX: should be `monster[_menemy-1]`, not monster[_menemy]. (fixed)
4040 				}
4041 			}
4042 		}
4043 		M_StartAttack(i);
4044 		return;
4045 	}
4046 
4047 	if (have_enemy && MAI_Path(i))
4048 		return;
4049 
4050 	monster[i]._pathcount++;
4051 	if (monster[i]._pathcount > 8)
4052 		monster[i]._pathcount = 5;
4053 
4054 	ok = M_CallWalk(i, plr[i]._pdir);
4055 	if (ok)
4056 		return;
4057 
4058 	md = (md - 1) & 7;
4059 	for (j = 0; j < 8 && !ok; j++) {
4060 		md = (md + 1) & 7;
4061 		ok = DirOK(i, md);
4062 	}
4063 	if (ok)
4064 		M_WalkDir(i, md);
4065 }
4066 
MAI_SkelKing(int i)4067 void MAI_SkelKing(int i)
4068 {
4069 	MonsterStruct *Monst;
4070 	int mx, my, fx, fy, nx, ny;
4071 	int dist, v, md;
4072 
4073 	assurance((DWORD)i < MAXMONSTERS, i);
4074 	Monst = &monster[i];
4075 	if (Monst->_mmode == MM_STAND && Monst->_msquelch != 0) {
4076 		fx = Monst->_menemyx;
4077 		fy = Monst->_menemyy;
4078 		mx = Monst->_mx - fx;
4079 		my = Monst->_my - fy;
4080 		md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty);
4081 		if (Monst->_msquelch < UCHAR_MAX)
4082 			MonstCheckDoors(i);
4083 		v = random_(126, 100);
4084 		dist = std::max(abs(mx), abs(my));
4085 		if (dist >= 2 && Monst->_msquelch == UCHAR_MAX && dTransVal[Monst->_mx][Monst->_my] == dTransVal[fx][fy]) {
4086 			if (Monst->_mgoal == MGOAL_MOVE || (abs(mx) >= 3 || abs(my) >= 3) && random_(127, 4) == 0) {
4087 				if (Monst->_mgoal != MGOAL_MOVE) {
4088 					Monst->_mgoalvar1 = 0;
4089 					Monst->_mgoalvar2 = random_(128, 2);
4090 				}
4091 				Monst->_mgoal = MGOAL_MOVE;
4092 				if (Monst->_mgoalvar1++ >= 2 * dist && DirOK(i, md) || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) {
4093 					Monst->_mgoal = MGOAL_NORMAL;
4094 				} else if (!M_RoundWalk(i, md, &Monst->_mgoalvar2)) {
4095 					M_StartDelay(i, random_(125, 10) + 10);
4096 				}
4097 			}
4098 		} else
4099 			Monst->_mgoal = MGOAL_NORMAL;
4100 		if (Monst->_mgoal == MGOAL_NORMAL) {
4101 			if (!gbIsMultiplayer
4102 			    && (dist >= 3 && v < 4 * Monst->_mint + 35 || v < 6)
4103 			    && LineClear(Monst->_mx, Monst->_my, fx, fy)) {
4104 				nx = Monst->_mx + offset_x[md];
4105 				ny = Monst->_my + offset_y[md];
4106 				if (PosOkMonst(i, nx, ny) && nummonsters < MAXMONSTERS) {
4107 					M_SpawnSkel(nx, ny, md);
4108 					M_StartSpStand(i, md);
4109 				}
4110 			} else {
4111 				if (dist >= 2) {
4112 					v = random_(129, 100);
4113 					if (v >= Monst->_mint + 25
4114 					    && (Monst->_mVar1 != MM_WALK && Monst->_mVar1 != MM_WALK2 && Monst->_mVar1 != MM_WALK3 || Monst->_mVar2 != 0 || (v >= Monst->_mint + 75))) {
4115 						M_StartDelay(i, random_(130, 10) + 10);
4116 					} else {
4117 						M_CallWalk(i, md);
4118 					}
4119 				} else if (v < Monst->_mint + 20) {
4120 					Monst->_mdir = md;
4121 					M_StartAttack(i);
4122 				}
4123 			}
4124 		}
4125 		if (Monst->_mmode == MM_STAND)
4126 			Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
4127 	}
4128 }
4129 
MAI_Rhino(int i)4130 void MAI_Rhino(int i)
4131 {
4132 	MonsterStruct *Monst;
4133 	int mx, my, fx, fy;
4134 	int v, dist, md;
4135 
4136 	assurance((DWORD)i < MAXMONSTERS, i);
4137 	Monst = &monster[i];
4138 	if (Monst->_mmode == MM_STAND && Monst->_msquelch != 0) {
4139 		fx = Monst->_menemyx;
4140 		fy = Monst->_menemyy;
4141 		mx = Monst->_mx - fx;
4142 		my = Monst->_my - fy;
4143 		md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty);
4144 		if (Monst->_msquelch < UCHAR_MAX)
4145 			MonstCheckDoors(i);
4146 		v = random_(131, 100);
4147 		dist = std::max(abs(mx), abs(my));
4148 		if (dist >= 2) {
4149 			if (Monst->_mgoal == MGOAL_MOVE || dist >= 5 && random_(132, 4) != 0) {
4150 				if (Monst->_mgoal != MGOAL_MOVE) {
4151 					Monst->_mgoalvar1 = 0;
4152 					Monst->_mgoalvar2 = random_(133, 2);
4153 				}
4154 				Monst->_mgoal = MGOAL_MOVE;
4155 				if (Monst->_mgoalvar1++ >= 2 * dist || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) {
4156 					Monst->_mgoal = MGOAL_NORMAL;
4157 				} else if (!M_RoundWalk(i, md, &Monst->_mgoalvar2)) {
4158 					M_StartDelay(i, random_(125, 10) + 10);
4159 				}
4160 			}
4161 		} else
4162 			Monst->_mgoal = MGOAL_NORMAL;
4163 		if (Monst->_mgoal == MGOAL_NORMAL) {
4164 			if (dist >= 5
4165 			    && v < 2 * Monst->_mint + 43
4166 			    && LineClearF1(PosOkMonst, i, Monst->_mx, Monst->_my, fx, fy)) {
4167 				if (AddMissile(Monst->_mx, Monst->_my, fx, fy, md, MIS_RHINO, Monst->_menemy, i, 0, 0) != -1) {
4168 					if (Monst->MData->snd_special)
4169 						PlayEffect(i, 3);
4170 					dMonster[Monst->_mx][Monst->_my] = -(i + 1);
4171 					Monst->_mmode = MM_CHARGE;
4172 				}
4173 			} else {
4174 				if (dist >= 2) {
4175 					v = random_(134, 100);
4176 					if (v >= 2 * Monst->_mint + 33
4177 					    && (Monst->_mVar1 != MM_WALK && Monst->_mVar1 != MM_WALK2 && Monst->_mVar1 != MM_WALK3
4178 					        || Monst->_mVar2
4179 					        || v >= 2 * Monst->_mint + 83)) {
4180 						M_StartDelay(i, random_(135, 10) + 10);
4181 					} else {
4182 						M_CallWalk(i, md);
4183 					}
4184 				} else if (v < 2 * Monst->_mint + 28) {
4185 					Monst->_mdir = md;
4186 					M_StartAttack(i);
4187 				}
4188 			}
4189 		}
4190 		if (Monst->_mmode == MM_STAND)
4191 			Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir];
4192 	}
4193 }
4194 
MAI_HorkDemon(int i)4195 void MAI_HorkDemon(int i)
4196 {
4197 	MonsterStruct *Monst;
4198 	int fx, fy, mx, my, md, v, dist;
4199 
4200 	if ((DWORD)i >= MAXMONSTERS) {
4201 		return;
4202 	}
4203 
4204 	Monst = &monster[i];
4205 	if (Monst->_mmode != MM_STAND || Monst->_msquelch == 0) {
4206 		return;
4207 	}
4208 
4209 	fx = Monst->_menemyx;
4210 	fy = Monst->_menemyy;
4211 	mx = Monst->_mx - fx;
4212 	my = Monst->_my - fy;
4213 	md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty);
4214 
4215 	if (Monst->_msquelch < 255) {
4216 		MonstCheckDoors(i);
4217 	}
4218 
4219 	v = random_(131, 100);
4220 
4221 	if (abs(mx) < 2 && abs(my) < 2) {
4222 		Monst->_mgoal = 1;
4223 	} else if (Monst->_mgoal == 4 || (abs(mx) >= 5 || abs(my) >= 5) && random_(132, 4) != 0) {
4224 		if (Monst->_mgoal != 4) {
4225 			Monst->_mgoalvar1 = 0;
4226 			Monst->_mgoalvar2 = random_(133, 2);
4227 		}
4228 		Monst->_mgoal = 4;
4229 		if (abs(mx) > abs(my)) {
4230 			dist = abs(mx);
4231 		} else {
4232 			dist = abs(my);
4233 		}
4234 		if (Monst->_mgoalvar1++ >= 2 * dist || dTransVal[Monst->_mx][Monst->_my] != dTransVal[fx][fy]) {
4235 			Monst->_mgoal = 1;
4236 		} else if (!M_RoundWalk(i, md, &Monst->_mgoalvar2)) {
4237 			M_StartDelay(i, random_(125, 10) + 10);
4238 		}
4239 	}
4240 
4241 	if (Monst->_mgoal == 1) {
4242 		if ((abs(mx) >= 3 || abs(my) >= 3) && v < 2 * Monst->_mint + 43) {
4243 			if (PosOkMonst(i, Monst->_mx + offset_x[Monst->_mdir], Monst->_my + offset_y[Monst->_mdir]) && nummonsters < MAXMONSTERS) {
4244 				M_StartRSpAttack(i, MIS_HORKDMN, 0);
4245 			}
4246 		} else if (abs(mx) < 2 && abs(my) < 2) {
4247 			if (v < 2 * Monst->_mint + 28) {
4248 				Monst->_mdir = md;
4249 				M_StartAttack(i);
4250 			}
4251 		} else {
4252 			v = random_(134, 100);
4253 			if (v < 2 * Monst->_mint + 33
4254 			    || (Monst->_mVar1 == 1 || Monst->_mVar1 == 2 || Monst->_mVar1 == 3) && Monst->_mVar2 == 0 && v < 2 * Monst->_mint + 83) {
4255 				M_CallWalk(i, md);
4256 			} else {
4257 				M_StartDelay(i, random_(135, 10) + 10);
4258 			}
4259 		}
4260 	}
4261 	if (Monst->_mmode == MM_STAND) {
4262 		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir];
4263 	}
4264 }
4265 
MAI_Counselor(int i)4266 void MAI_Counselor(int i)
4267 {
4268 	int mx, my, fx, fy;
4269 	int dist, md, v;
4270 	MonsterStruct *Monst;
4271 
4272 	assurance((DWORD)i < MAXMONSTERS, i);
4273 
4274 	Monst = &monster[i];
4275 	if (Monst->_mmode == MM_STAND && Monst->_msquelch != 0) {
4276 		fx = Monst->_menemyx;
4277 		fy = Monst->_menemyy;
4278 		mx = Monst->_mx - fx;
4279 		my = Monst->_my - fy;
4280 		md = GetDirection(Monst->_mx, Monst->_my, Monst->_lastx, Monst->_lasty);
4281 		if (Monst->_msquelch < UCHAR_MAX)
4282 			MonstCheckDoors(i);
4283 		v = random_(121, 100);
4284 		if (Monst->_mgoal == MGOAL_RETREAT) {
4285 			if (Monst->_mgoalvar1++ <= 3)
4286 				M_CallWalk(i, opposite[md]);
4287 			else {
4288 				Monst->_mgoal = MGOAL_NORMAL;
4289 				M_StartFadein(i, md, TRUE);
4290 			}
4291 		} else if (Monst->_mgoal == MGOAL_MOVE) {
4292 			dist = std::max(abs(mx), abs(my));
4293 			if (dist >= 2 && Monst->_msquelch == UCHAR_MAX && dTransVal[Monst->_mx][Monst->_my] == dTransVal[fx][fy]) {
4294 				if (Monst->_mgoalvar1++ < 2 * dist || !DirOK(i, md)) {
4295 					M_RoundWalk(i, md, &Monst->_mgoalvar2);
4296 				} else {
4297 					Monst->_mgoal = MGOAL_NORMAL;
4298 					M_StartFadein(i, md, TRUE);
4299 				}
4300 			} else {
4301 				Monst->_mgoal = MGOAL_NORMAL;
4302 				M_StartFadein(i, md, TRUE);
4303 			}
4304 		} else if (Monst->_mgoal == MGOAL_NORMAL) {
4305 			if (abs(mx) >= 2 || abs(my) >= 2) {
4306 				if (v < 5 * (Monst->_mint + 10) && LineClear(Monst->_mx, Monst->_my, fx, fy)) {
4307 					M_StartRAttack(i, counsmiss[Monst->_mint], Monst->mMinDamage + random_(77, Monst->mMaxDamage - Monst->mMinDamage + 1));
4308 				} else if (random_(124, 100) < 30) {
4309 					Monst->_mgoal = MGOAL_MOVE;
4310 					Monst->_mgoalvar1 = 0;
4311 					M_StartFadeout(i, md, FALSE);
4312 				} else
4313 					M_StartDelay(i, random_(105, 10) + 2 * (5 - Monst->_mint));
4314 			} else {
4315 				Monst->_mdir = md;
4316 				if (Monst->_mhitpoints < (Monst->_mmaxhp >> 1)) {
4317 					Monst->_mgoal = MGOAL_RETREAT;
4318 					Monst->_mgoalvar1 = 0;
4319 					M_StartFadeout(i, md, FALSE);
4320 				} else if (Monst->_mVar1 == MM_DELAY
4321 				    || random_(105, 100) < 2 * Monst->_mint + 20) {
4322 					M_StartRAttack(i, -1, 0);
4323 					AddMissile(Monst->_mx, Monst->_my, 0, 0, Monst->_mdir, MIS_FLASH, TARGET_PLAYERS, i, 4, 0);
4324 					AddMissile(Monst->_mx, Monst->_my, 0, 0, Monst->_mdir, MIS_FLASH2, TARGET_PLAYERS, i, 4, 0);
4325 				} else
4326 					M_StartDelay(i, random_(105, 10) + 2 * (5 - Monst->_mint));
4327 			}
4328 		}
4329 		if (Monst->_mmode == MM_STAND) {
4330 			M_StartDelay(i, random_(125, 10) + 5);
4331 		}
4332 	}
4333 }
4334 
MAI_Garbud(int i)4335 void MAI_Garbud(int i)
4336 {
4337 	int _mx, _my, md;
4338 	MonsterStruct *Monst;
4339 
4340 	assurance((DWORD)i < MAXMONSTERS, i);
4341 
4342 	Monst = &monster[i];
4343 	if (Monst->_mmode != MM_STAND) {
4344 		return;
4345 	}
4346 
4347 	_mx = Monst->_mx;
4348 	_my = Monst->_my;
4349 	md = M_GetDir(i);
4350 
4351 	if (Monst->mtalkmsg >= TEXT_GARBUD1
4352 	    && Monst->mtalkmsg <= TEXT_GARBUD3
4353 	    && !(dFlags[_mx][_my] & BFLAG_VISIBLE)
4354 	    && Monst->_mgoal == MGOAL_TALKING) {
4355 		Monst->_mgoal = MGOAL_INQUIRING;
4356 		switch (Monst->mtalkmsg) {
4357 		case TEXT_GARBUD1:
4358 			Monst->mtalkmsg = TEXT_GARBUD2;
4359 			break;
4360 		case TEXT_GARBUD2:
4361 			Monst->mtalkmsg = TEXT_GARBUD3;
4362 			break;
4363 		case TEXT_GARBUD3:
4364 			Monst->mtalkmsg = TEXT_GARBUD4;
4365 			break;
4366 		}
4367 	}
4368 
4369 	if (dFlags[_mx][_my] & BFLAG_VISIBLE) {
4370 		if (Monst->mtalkmsg == TEXT_GARBUD4) {
4371 			if (!effect_is_playing(USFX_GARBUD4) && Monst->_mgoal == MGOAL_TALKING) {
4372 				Monst->_mgoal = MGOAL_NORMAL;
4373 				Monst->_msquelch = UCHAR_MAX;
4374 				Monst->mtalkmsg = 0;
4375 			}
4376 		}
4377 	}
4378 
4379 	if (Monst->_mgoal == MGOAL_NORMAL || Monst->_mgoal == MGOAL_MOVE)
4380 		MAI_Round(i, TRUE);
4381 
4382 	monster[i]._mdir = md;
4383 
4384 	if (Monst->_mmode == MM_STAND)
4385 		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
4386 }
4387 
MAI_Zhar(int i)4388 void MAI_Zhar(int i)
4389 {
4390 	int mx, my, md;
4391 	MonsterStruct *Monst;
4392 
4393 	assurance((DWORD)i < MAXMONSTERS, i);
4394 
4395 	Monst = &monster[i];
4396 	if (monster[i]._mmode != MM_STAND) {
4397 		return;
4398 	}
4399 
4400 	mx = Monst->_mx;
4401 	my = Monst->_my;
4402 	md = M_GetDir(i);
4403 	if (Monst->mtalkmsg == TEXT_ZHAR1 && !(dFlags[mx][my] & BFLAG_VISIBLE) && Monst->_mgoal == MGOAL_TALKING) {
4404 		Monst->mtalkmsg = TEXT_ZHAR2;
4405 		Monst->_mgoal = MGOAL_INQUIRING;
4406 	}
4407 
4408 	if (dFlags[mx][my] & BFLAG_VISIBLE) {
4409 		if (Monst->mtalkmsg == TEXT_ZHAR2) {
4410 			if (!effect_is_playing(USFX_ZHAR2) && Monst->_mgoal == MGOAL_TALKING) {
4411 				Monst->_msquelch = UCHAR_MAX;
4412 				Monst->mtalkmsg = 0;
4413 				Monst->_mgoal = MGOAL_NORMAL;
4414 			}
4415 		}
4416 	}
4417 
4418 	if (Monst->_mgoal == MGOAL_NORMAL || Monst->_mgoal == MGOAL_RETREAT || Monst->_mgoal == MGOAL_MOVE)
4419 		MAI_Counselor(i);
4420 
4421 	Monst->_mdir = md;
4422 
4423 	if (monster[i]._mmode == MM_STAND)
4424 		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
4425 }
4426 
MAI_SnotSpil(int i)4427 void MAI_SnotSpil(int i)
4428 {
4429 	int mx, my, md;
4430 	MonsterStruct *Monst;
4431 
4432 	assurance((DWORD)i < MAXMONSTERS, i);
4433 
4434 	Monst = &monster[i];
4435 	if (monster[i]._mmode != MM_STAND) {
4436 		return;
4437 	}
4438 
4439 	mx = Monst->_mx;
4440 	my = Monst->_my;
4441 	md = M_GetDir(i);
4442 
4443 	if (Monst->mtalkmsg == TEXT_BANNER10 && !(dFlags[mx][my] & BFLAG_VISIBLE) && Monst->_mgoal == MGOAL_TALKING) {
4444 		Monst->mtalkmsg = TEXT_BANNER11;
4445 		Monst->_mgoal = MGOAL_INQUIRING;
4446 	}
4447 
4448 	if (Monst->mtalkmsg == TEXT_BANNER11 && quests[Q_LTBANNER]._qvar1 == 3) {
4449 		Monst->mtalkmsg = 0;
4450 		Monst->_mgoal = MGOAL_NORMAL;
4451 	}
4452 
4453 	if (dFlags[mx][my] & BFLAG_VISIBLE) {
4454 		if (Monst->mtalkmsg == TEXT_BANNER12) {
4455 			if (!effect_is_playing(USFX_SNOT3) && Monst->_mgoal == MGOAL_TALKING) {
4456 				ObjChangeMap(setpc_x, setpc_y, setpc_x + setpc_w + 1, setpc_y + setpc_h + 1);
4457 				quests[Q_LTBANNER]._qvar1 = 3;
4458 				RedoPlayerVision();
4459 				Monst->_msquelch = UCHAR_MAX;
4460 				Monst->mtalkmsg = 0;
4461 				Monst->_mgoal = MGOAL_NORMAL;
4462 			}
4463 		}
4464 		if (quests[Q_LTBANNER]._qvar1 == 3) {
4465 			if (Monst->_mgoal == MGOAL_NORMAL || Monst->_mgoal == MGOAL_ATTACK2)
4466 				MAI_Fallen(i);
4467 		}
4468 	}
4469 
4470 	Monst->_mdir = md;
4471 
4472 	if (monster[i]._mmode == MM_STAND)
4473 		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
4474 }
4475 
MAI_Lazurus(int i)4476 void MAI_Lazurus(int i)
4477 {
4478 	int mx, my, md;
4479 	MonsterStruct *Monst;
4480 
4481 	assurance((DWORD)i < MAXMONSTERS, i);
4482 
4483 	Monst = &monster[i];
4484 	if (monster[i]._mmode != MM_STAND) {
4485 		return;
4486 	}
4487 
4488 	mx = Monst->_mx;
4489 	my = Monst->_my;
4490 	md = M_GetDir(i);
4491 	if (dFlags[mx][my] & BFLAG_VISIBLE) {
4492 		if (!gbIsMultiplayer) {
4493 			if (Monst->mtalkmsg == TEXT_VILE13 && Monst->_mgoal == MGOAL_INQUIRING && plr[myplr]._px == 35 && plr[myplr]._py == 46) {
4494 				PlayInGameMovie("gendata\\fprst3.smk");
4495 				Monst->_mmode = MM_TALK;
4496 				quests[Q_BETRAYER]._qvar1 = 5;
4497 			}
4498 
4499 			if (Monst->mtalkmsg == TEXT_VILE13 && !effect_is_playing(USFX_LAZ1) && Monst->_mgoal == MGOAL_TALKING) {
4500 				ObjChangeMapResync(1, 18, 20, 24);
4501 				RedoPlayerVision();
4502 				quests[Q_BETRAYER]._qvar1 = 6;
4503 				Monst->_mgoal = MGOAL_NORMAL;
4504 				Monst->_msquelch = UCHAR_MAX;
4505 				Monst->mtalkmsg = 0;
4506 			}
4507 		}
4508 
4509 		if (gbIsMultiplayer && Monst->mtalkmsg == TEXT_VILE13 && Monst->_mgoal == MGOAL_INQUIRING && quests[Q_BETRAYER]._qvar1 <= 3) {
4510 			Monst->_mmode = MM_TALK;
4511 		}
4512 	}
4513 
4514 	if (Monst->_mgoal == MGOAL_NORMAL || Monst->_mgoal == MGOAL_RETREAT || Monst->_mgoal == MGOAL_MOVE) {
4515 		if (!gbIsMultiplayer && quests[Q_BETRAYER]._qvar1 == 4 && Monst->mtalkmsg == 0) { // Fix save games affected by teleport bug
4516 			ObjChangeMapResync(1, 18, 20, 24);
4517 			RedoPlayerVision();
4518 			quests[Q_BETRAYER]._qvar1 = 6;
4519 		}
4520 		Monst->mtalkmsg = 0;
4521 		MAI_Counselor(i);
4522 	}
4523 
4524 	Monst->_mdir = md;
4525 
4526 	if (monster[i]._mmode == MM_STAND || monster[i]._mmode == MM_TALK)
4527 		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
4528 }
4529 
MAI_Lazhelp(int i)4530 void MAI_Lazhelp(int i)
4531 {
4532 	int _mx, _my;
4533 	volatile int md; // BUGFIX: very questionable volatile
4534 	MonsterStruct *Monst;
4535 
4536 	assurance((DWORD)i < MAXMONSTERS, i);
4537 	if (monster[i]._mmode != MM_STAND)
4538 		return;
4539 
4540 	Monst = &monster[i];
4541 	_mx = Monst->_mx;
4542 	_my = Monst->_my;
4543 	md = M_GetDir(i);
4544 
4545 	if (dFlags[_mx][_my] & BFLAG_VISIBLE) {
4546 		if (!gbIsMultiplayer) {
4547 			if (quests[Q_BETRAYER]._qvar1 <= 5) {
4548 				Monst->_mgoal = MGOAL_INQUIRING;
4549 			} else {
4550 				Monst->_mgoal = MGOAL_NORMAL;
4551 				Monst->mtalkmsg = 0;
4552 			}
4553 		} else
4554 			Monst->_mgoal = MGOAL_NORMAL;
4555 	}
4556 	if (Monst->_mgoal == MGOAL_NORMAL)
4557 		MAI_Succ(i);
4558 	Monst->_mdir = md;
4559 	if (monster[i]._mmode == MM_STAND)
4560 		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
4561 }
4562 
MAI_Lachdanan(int i)4563 void MAI_Lachdanan(int i)
4564 {
4565 	int _mx, _my, md;
4566 	MonsterStruct *Monst;
4567 
4568 	assurance((DWORD)i < MAXMONSTERS, i);
4569 
4570 	Monst = &monster[i];
4571 	if (monster[i]._mmode != MM_STAND) {
4572 		return;
4573 	}
4574 
4575 	_mx = Monst->_mx;
4576 	_my = Monst->_my;
4577 	md = M_GetDir(i);
4578 
4579 	if (Monst->mtalkmsg == TEXT_VEIL9 && !(dFlags[_mx][_my] & BFLAG_VISIBLE) && monster[i]._mgoal == MGOAL_TALKING) {
4580 		Monst->mtalkmsg = TEXT_VEIL10;
4581 		monster[i]._mgoal = MGOAL_INQUIRING;
4582 	}
4583 
4584 	if (dFlags[_mx][_my] & BFLAG_VISIBLE) {
4585 		if (Monst->mtalkmsg == TEXT_VEIL11) {
4586 			if (!effect_is_playing(USFX_LACH3) && Monst->_mgoal == MGOAL_TALKING) {
4587 				Monst->mtalkmsg = 0;
4588 				quests[Q_VEIL]._qactive = QUEST_DONE;
4589 				M_StartKill(i, -1);
4590 			}
4591 		}
4592 	}
4593 
4594 	Monst->_mdir = md;
4595 
4596 	if (monster[i]._mmode == MM_STAND)
4597 		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[md];
4598 }
4599 
MAI_Warlord(int i)4600 void MAI_Warlord(int i)
4601 {
4602 	MonsterStruct *Monst;
4603 	int mx, my, md;
4604 
4605 	assurance((DWORD)i < MAXMONSTERS, i);
4606 
4607 	Monst = &monster[i];
4608 	if (monster[i]._mmode != MM_STAND) {
4609 		return;
4610 	}
4611 
4612 	mx = Monst->_mx;
4613 	my = Monst->_my;
4614 	md = M_GetDir(i);
4615 	if (dFlags[mx][my] & BFLAG_VISIBLE) {
4616 		if (Monst->mtalkmsg == TEXT_WARLRD9 && Monst->_mgoal == MGOAL_INQUIRING)
4617 			Monst->_mmode = MM_TALK;
4618 		if (Monst->mtalkmsg == TEXT_WARLRD9 && !effect_is_playing(USFX_WARLRD1) && Monst->_mgoal == MGOAL_TALKING) {
4619 			Monst->_msquelch = UCHAR_MAX;
4620 			Monst->mtalkmsg = 0;
4621 			Monst->_mgoal = MGOAL_NORMAL;
4622 		}
4623 	}
4624 
4625 	if (Monst->_mgoal == MGOAL_NORMAL)
4626 		MAI_SkelSd(i);
4627 
4628 	Monst->_mdir = md;
4629 
4630 	if (monster[i]._mmode == MM_STAND || monster[i]._mmode == MM_TALK)
4631 		Monst->_mAnimData = Monst->MType->Anims[MA_STAND].Data[Monst->_mdir];
4632 }
4633 
DeleteMonsterList()4634 void DeleteMonsterList()
4635 {
4636 	int i;
4637 	for (i = 0; i < MAX_PLRS; i++) {
4638 		if (monster[i]._mDelFlag) {
4639 			monster[i]._mx = 1;
4640 			monster[i]._my = 0;
4641 			monster[i]._mfutx = 0;
4642 			monster[i]._mfuty = 0;
4643 			monster[i]._moldx = 0;
4644 			monster[i]._moldy = 0;
4645 			monster[i]._mDelFlag = FALSE;
4646 		}
4647 	}
4648 
4649 	i = MAX_PLRS;
4650 	while (i < nummonsters) {
4651 		if (monster[monstactive[i]]._mDelFlag) {
4652 			DeleteMonster(i);
4653 			i = 0; // TODO: check if this should be MAX_PLRS.
4654 		} else {
4655 			i++;
4656 		}
4657 	}
4658 }
4659 
ProcessMonsters()4660 void ProcessMonsters()
4661 {
4662 	int i, mi, mx, my, _menemy;
4663 	BOOL raflag;
4664 	MonsterStruct *Monst;
4665 
4666 	DeleteMonsterList();
4667 
4668 	assert((DWORD)nummonsters <= MAXMONSTERS);
4669 	for (i = 0; i < nummonsters; i++) {
4670 		mi = monstactive[i];
4671 		Monst = &monster[mi];
4672 		raflag = FALSE;
4673 		if (gbIsMultiplayer) {
4674 			SetRndSeed(Monst->_mAISeed);
4675 			Monst->_mAISeed = AdvanceRndSeed();
4676 		}
4677 		if (!(monster[mi]._mFlags & MFLAG_NOHEAL) && Monst->_mhitpoints < Monst->_mmaxhp && Monst->_mhitpoints >> 6 > 0) {
4678 			if (Monst->mLevel > 1) {
4679 				Monst->_mhitpoints += Monst->mLevel >> 1;
4680 			} else {
4681 				Monst->_mhitpoints += Monst->mLevel;
4682 			}
4683 		}
4684 		mx = Monst->_mx;
4685 		my = Monst->_my;
4686 
4687 		if (dFlags[mx][my] & BFLAG_VISIBLE && Monst->_msquelch == 0) {
4688 			if (Monst->MType->mtype == MT_CLEAVER) {
4689 				PlaySFX(USFX_CLEAVER);
4690 			}
4691 			if (Monst->MType->mtype == MT_NAKRUL) {
4692 				if (gbCowQuest) {
4693 					PlaySFX(USFX_NAKRUL6);
4694 				} else {
4695 					if (IsUberRoomOpened)
4696 						PlaySFX(USFX_NAKRUL4);
4697 					else
4698 						PlaySFX(USFX_NAKRUL5);
4699 				}
4700 			}
4701 			if (Monst->MType->mtype == MT_DEFILER)
4702 				PlaySFX(USFX_DEFILER8);
4703 			M_Enemy(mi);
4704 		}
4705 
4706 		if (Monst->_mFlags & MFLAG_TARGETS_MONSTER) {
4707 			_menemy = Monst->_menemy;
4708 			assurance((DWORD)_menemy < MAXMONSTERS, _menemy);
4709 			Monst->_lastx = monster[Monst->_menemy]._mfutx;
4710 			Monst->_menemyx = Monst->_lastx;
4711 			Monst->_lasty = monster[Monst->_menemy]._mfuty;
4712 			Monst->_menemyy = Monst->_lasty;
4713 		} else {
4714 			_menemy = Monst->_menemy;
4715 			assurance((DWORD)_menemy < MAX_PLRS, _menemy);
4716 			Monst->_menemyx = plr[Monst->_menemy]._pfutx;
4717 			Monst->_menemyy = plr[Monst->_menemy]._pfuty;
4718 			if (dFlags[mx][my] & BFLAG_VISIBLE) {
4719 				Monst->_msquelch = UCHAR_MAX;
4720 				Monst->_lastx = plr[Monst->_menemy]._pfutx;
4721 				Monst->_lasty = plr[Monst->_menemy]._pfuty;
4722 			} else if (Monst->_msquelch != 0 && Monst->MType->mtype != MT_DIABLO) { /// BUGFIX: change '_mAi' to 'MType->mtype'
4723 				Monst->_msquelch--;
4724 			}
4725 		}
4726 		do {
4727 			if (!(Monst->_mFlags & MFLAG_SEARCH)) {
4728 				AiProc[Monst->_mAi](mi);
4729 			} else if (!MAI_Path(mi)) {
4730 				AiProc[Monst->_mAi](mi);
4731 			}
4732 			switch (Monst->_mmode) {
4733 			case MM_STAND:
4734 				raflag = M_DoStand(mi);
4735 				break;
4736 			case MM_WALK:
4737 			case MM_WALK2:
4738 			case MM_WALK3:
4739 				raflag = M_DoWalk(mi, Monst->_mmode);
4740 				break;
4741 			case MM_ATTACK:
4742 				raflag = M_DoAttack(mi);
4743 				break;
4744 			case MM_GOTHIT:
4745 				raflag = M_DoGotHit(mi);
4746 				break;
4747 			case MM_DEATH:
4748 				raflag = M_DoDeath(mi);
4749 				break;
4750 			case MM_SATTACK:
4751 				raflag = M_DoSAttack(mi);
4752 				break;
4753 			case MM_FADEIN:
4754 				raflag = M_DoFadein(mi);
4755 				break;
4756 			case MM_FADEOUT:
4757 				raflag = M_DoFadeout(mi);
4758 				break;
4759 			case MM_RATTACK:
4760 				raflag = M_DoRAttack(mi);
4761 				break;
4762 			case MM_SPSTAND:
4763 				raflag = M_DoSpStand(mi);
4764 				break;
4765 			case MM_RSPATTACK:
4766 				raflag = M_DoRSpAttack(mi);
4767 				break;
4768 			case MM_DELAY:
4769 				raflag = M_DoDelay(mi);
4770 				break;
4771 			case MM_CHARGE:
4772 				raflag = FALSE;
4773 				break;
4774 			case MM_STONE:
4775 				raflag = M_DoStone(mi);
4776 				break;
4777 			case MM_HEAL:
4778 				raflag = M_DoHeal(mi);
4779 				break;
4780 			case MM_TALK:
4781 				raflag = M_DoTalk(mi);
4782 				break;
4783 			}
4784 			if (raflag) {
4785 				GroupUnity(mi);
4786 			}
4787 		} while (raflag);
4788 		if (Monst->_mmode != MM_STONE) {
4789 			Monst->_mAnimCnt++;
4790 			if (!(Monst->_mFlags & MFLAG_ALLOW_SPECIAL) && Monst->_mAnimCnt >= Monst->_mAnimDelay) {
4791 				Monst->_mAnimCnt = 0;
4792 				if (Monst->_mFlags & MFLAG_LOCK_ANIMATION) {
4793 					Monst->_mAnimFrame--;
4794 					if (Monst->_mAnimFrame == 0) {
4795 						Monst->_mAnimFrame = Monst->_mAnimLen;
4796 					}
4797 				} else {
4798 					Monst->_mAnimFrame++;
4799 					if (Monst->_mAnimFrame > Monst->_mAnimLen) {
4800 						Monst->_mAnimFrame = 1;
4801 					}
4802 				}
4803 			}
4804 		}
4805 	}
4806 
4807 	DeleteMonsterList();
4808 }
4809 
FreeMonsters()4810 void FreeMonsters()
4811 {
4812 	int mtype;
4813 	int i, j;
4814 
4815 	for (i = 0; i < nummtypes; i++) {
4816 		mtype = Monsters[i].mtype;
4817 		for (j = 0; j < 6; j++) {
4818 			if (animletter[j] != 's' || monsterdata[mtype].has_special) {
4819 				MemFreeDbg(Monsters[i].Anims[j].CMem);
4820 			}
4821 		}
4822 	}
4823 
4824 	FreeMissiles2();
4825 }
4826 
DirOK(int i,int mdir)4827 BOOL DirOK(int i, int mdir)
4828 {
4829 	int fx, fy;
4830 	int x, y;
4831 	int mcount, mi;
4832 
4833 	commitment((DWORD)i < MAXMONSTERS, i);
4834 	fx = monster[i]._mx + offset_x[mdir];
4835 	fy = monster[i]._my + offset_y[mdir];
4836 	if (fy < 0 || fy >= MAXDUNY || fx < 0 || fx >= MAXDUNX || !PosOkMonst(i, fx, fy))
4837 		return FALSE;
4838 	if (mdir == DIR_E) {
4839 		if (SolidLoc(fx, fy + 1) || dFlags[fx][fy + 1] & BFLAG_MONSTLR)
4840 			return FALSE;
4841 	} else if (mdir == DIR_W) {
4842 		if (SolidLoc(fx + 1, fy) || dFlags[fx + 1][fy] & BFLAG_MONSTLR)
4843 			return FALSE;
4844 	} else if (mdir == DIR_N) {
4845 		if (SolidLoc(fx + 1, fy) || SolidLoc(fx, fy + 1))
4846 			return FALSE;
4847 	} else if (mdir == DIR_S)
4848 		if (SolidLoc(fx - 1, fy) || SolidLoc(fx, fy - 1))
4849 			return FALSE;
4850 	if (monster[i].leaderflag == 1) {
4851 		if (abs(fx - monster[monster[i].leader]._mfutx) >= 4
4852 		    || abs(fy - monster[monster[i].leader]._mfuty) >= 4) {
4853 			return FALSE;
4854 		}
4855 		return TRUE;
4856 	}
4857 	if (monster[i]._uniqtype == 0 || !(UniqMonst[monster[i]._uniqtype - 1].mUnqAttr & 2))
4858 		return TRUE;
4859 	mcount = 0;
4860 	for (x = fx - 3; x <= fx + 3; x++) {
4861 		for (y = fy - 3; y <= fy + 3; y++) {
4862 			if (y < 0 || y >= MAXDUNY || x < 0 || x >= MAXDUNX)
4863 				continue;
4864 			mi = dMonster[x][y];
4865 			if (mi < 0)
4866 				mi = -mi;
4867 			if (mi != 0)
4868 				mi--;
4869 			// BUGFIX: should only run pack member check if mi was non-zero prior to executing the body of the above if-statement.
4870 			if (monster[mi].leaderflag == 1
4871 			    && monster[mi].leader == i
4872 			    && monster[mi]._mfutx == x
4873 			    && monster[mi]._mfuty == y) {
4874 				mcount++;
4875 			}
4876 		}
4877 	}
4878 	return mcount == monster[i].packsize;
4879 }
4880 
PosOkMissile(int x,int y)4881 BOOL PosOkMissile(int x, int y)
4882 {
4883 	return !nMissileTable[dPiece[x][y]] && !(dFlags[x][y] & BFLAG_MONSTLR);
4884 }
4885 
CheckNoSolid(int x,int y)4886 BOOL CheckNoSolid(int x, int y)
4887 {
4888 	return nSolidTable[dPiece[x][y]] == FALSE;
4889 }
4890 
LineClearF(BOOL (* Clear)(int,int),int x1,int y1,int x2,int y2)4891 BOOL LineClearF(BOOL (*Clear)(int, int), int x1, int y1, int x2, int y2)
4892 {
4893 	int xorg, yorg;
4894 	int dx, dy;
4895 	int d;
4896 	int xincD, yincD, dincD, dincH;
4897 	int tmp;
4898 	BOOL done = FALSE;
4899 
4900 	xorg = x1;
4901 	yorg = y1;
4902 	dx = x2 - x1;
4903 	dy = y2 - y1;
4904 	if (abs(dx) > abs(dy)) {
4905 		if (dx < 0) {
4906 			tmp = x1;
4907 			x1 = x2;
4908 			x2 = tmp;
4909 			tmp = y1;
4910 			y1 = y2;
4911 			y2 = tmp;
4912 			dx = -dx;
4913 			dy = -dy;
4914 		}
4915 		if (dy > 0) {
4916 			d = 2 * dy - dx;
4917 			dincD = 2 * dy;
4918 			dincH = 2 * (dy - dx);
4919 			yincD = 1;
4920 		} else {
4921 			d = 2 * dy + dx;
4922 			dincD = 2 * dy;
4923 			dincH = 2 * (dx + dy);
4924 			yincD = -1;
4925 		}
4926 		while (!done && (x1 != x2 || y1 != y2)) {
4927 			if ((d <= 0) ^ (yincD < 0)) {
4928 				d += dincD;
4929 			} else {
4930 				d += dincH;
4931 				y1 += yincD;
4932 			}
4933 			x1++;
4934 			done = ((x1 != xorg || y1 != yorg) && !Clear(x1, y1));
4935 		}
4936 	} else {
4937 		if (dy < 0) {
4938 			tmp = y1;
4939 			y1 = y2;
4940 			y2 = tmp;
4941 			tmp = x1;
4942 			x1 = x2;
4943 			x2 = tmp;
4944 			dy = -dy;
4945 			dx = -dx;
4946 		}
4947 		if (dx > 0) {
4948 			d = 2 * dx - dy;
4949 			dincD = 2 * dx;
4950 			dincH = 2 * (dx - dy);
4951 			xincD = 1;
4952 		} else {
4953 			d = 2 * dx + dy;
4954 			dincD = 2 * dx;
4955 			dincH = 2 * (dy + dx);
4956 			xincD = -1;
4957 		}
4958 		while (!done && (y1 != y2 || x1 != x2)) {
4959 			if ((d <= 0) ^ (xincD < 0)) {
4960 				d += dincD;
4961 			} else {
4962 				d += dincH;
4963 				x1 += xincD;
4964 			}
4965 			y1++;
4966 			done = ((y1 != yorg || x1 != xorg) && !Clear(x1, y1));
4967 		}
4968 	}
4969 	return x1 == x2 && y1 == y2;
4970 }
4971 
LineClear(int x1,int y1,int x2,int y2)4972 BOOL LineClear(int x1, int y1, int x2, int y2)
4973 {
4974 	return LineClearF(PosOkMissile, x1, y1, x2, y2);
4975 }
4976 
LineClearF1(BOOL (* Clear)(int,int,int),int monst,int x1,int y1,int x2,int y2)4977 BOOL LineClearF1(BOOL (*Clear)(int, int, int), int monst, int x1, int y1, int x2, int y2)
4978 {
4979 	int dx, dy;
4980 	int d;
4981 	int xorg, yorg;
4982 	int xincD, yincD, dincD, dincH;
4983 	int tmp;
4984 	BOOL done = FALSE;
4985 
4986 	xorg = x1;
4987 	yorg = y1;
4988 	dx = x2 - x1;
4989 	dy = y2 - y1;
4990 	if (abs(dx) > abs(dy)) {
4991 		if (dx < 0) {
4992 			tmp = x1;
4993 			x1 = x2;
4994 			x2 = tmp;
4995 			tmp = y1;
4996 			y1 = y2;
4997 			y2 = tmp;
4998 			dx = -dx;
4999 			dy = -dy;
5000 		}
5001 		if (dy > 0) {
5002 			d = 2 * dy - dx;
5003 			dincD = 2 * dy;
5004 			dincH = 2 * (dy - dx);
5005 			yincD = 1;
5006 		} else {
5007 			d = 2 * dy + dx;
5008 			dincD = 2 * dy;
5009 			dincH = 2 * (dx + dy);
5010 			yincD = -1;
5011 		}
5012 		while (!done && (x1 != x2 || y1 != y2)) {
5013 			if ((d <= 0) ^ (yincD < 0)) {
5014 				d += dincD;
5015 			} else {
5016 				d += dincH;
5017 				y1 += yincD;
5018 			}
5019 			x1++;
5020 			done = ((x1 != xorg || y1 != yorg) && !Clear(monst, x1, y1));
5021 		}
5022 	} else {
5023 		if (dy < 0) {
5024 			tmp = y1;
5025 			y1 = y2;
5026 			y2 = tmp;
5027 			tmp = x1;
5028 			x1 = x2;
5029 			x2 = tmp;
5030 			dy = -dy;
5031 			dx = -dx;
5032 		}
5033 		if (dx > 0) {
5034 			d = 2 * dx - dy;
5035 			dincD = 2 * dx;
5036 			dincH = 2 * (dx - dy);
5037 			xincD = 1;
5038 		} else {
5039 			d = 2 * dx + dy;
5040 			dincD = 2 * dx;
5041 			dincH = 2 * (dy + dx);
5042 			xincD = -1;
5043 		}
5044 		while (!done && (y1 != y2 || x1 != x2)) {
5045 			if ((d <= 0) ^ (xincD < 0)) {
5046 				d += dincD;
5047 			} else {
5048 				d += dincH;
5049 				x1 += xincD;
5050 			}
5051 			y1++;
5052 			done = ((y1 != yorg || x1 != xorg) && !Clear(monst, x1, y1));
5053 		}
5054 	}
5055 	return x1 == x2 && y1 == y2;
5056 }
5057 
SyncMonsterAnim(int i)5058 void SyncMonsterAnim(int i)
5059 {
5060 	int _mdir;
5061 
5062 	assurance((DWORD)i < MAXMONSTERS || i < 0, i);
5063 	monster[i].MType = &Monsters[monster[i]._mMTidx];
5064 	monster[i].MData = Monsters[monster[i]._mMTidx].MData;
5065 	if (monster[i]._uniqtype != 0)
5066 		monster[i].mName = UniqMonst[monster[i]._uniqtype - 1].mName;
5067 	else
5068 		monster[i].mName = monster[i].MData->mName;
5069 	_mdir = monster[i]._mdir;
5070 
5071 	switch (monster[i]._mmode) {
5072 	case MM_STAND:
5073 		monster[i]._mAnimData = monster[i].MType->Anims[MA_STAND].Data[_mdir];
5074 		break;
5075 	case MM_WALK:
5076 	case MM_WALK2:
5077 	case MM_WALK3:
5078 		monster[i]._mAnimData = monster[i].MType->Anims[MA_WALK].Data[_mdir];
5079 		break;
5080 	case MM_ATTACK:
5081 	case MM_RATTACK:
5082 		monster[i]._mAnimData = monster[i].MType->Anims[MA_ATTACK].Data[_mdir];
5083 		break;
5084 	case MM_GOTHIT:
5085 		monster[i]._mAnimData = monster[i].MType->Anims[MA_GOTHIT].Data[_mdir];
5086 		break;
5087 	case MM_DEATH:
5088 		monster[i]._mAnimData = monster[i].MType->Anims[MA_DEATH].Data[_mdir];
5089 		break;
5090 	case MM_SATTACK:
5091 	case MM_FADEIN:
5092 	case MM_FADEOUT:
5093 		monster[i]._mAnimData = monster[i].MType->Anims[MA_SPECIAL].Data[_mdir];
5094 		break;
5095 	case MM_SPSTAND:
5096 	case MM_RSPATTACK:
5097 		monster[i]._mAnimData = monster[i].MType->Anims[MA_SPECIAL].Data[_mdir];
5098 		break;
5099 	case MM_HEAL:
5100 		monster[i]._mAnimData = monster[i].MType->Anims[MA_SPECIAL].Data[_mdir];
5101 		break;
5102 	case MM_DELAY:
5103 		monster[i]._mAnimData = monster[i].MType->Anims[MA_STAND].Data[_mdir];
5104 		break;
5105 	case MM_TALK:
5106 		monster[i]._mAnimData = monster[i].MType->Anims[MA_STAND].Data[_mdir];
5107 		break;
5108 	case MM_CHARGE:
5109 		monster[i]._mAnimData = monster[i].MType->Anims[MA_ATTACK].Data[_mdir];
5110 		monster[i]._mAnimFrame = 1;
5111 		monster[i]._mAnimLen = monster[i].MType->Anims[MA_ATTACK].Frames;
5112 		break;
5113 	default:
5114 		monster[i]._mAnimData = monster[i].MType->Anims[MA_STAND].Data[_mdir];
5115 		monster[i]._mAnimFrame = 1;
5116 		monster[i]._mAnimLen = monster[i].MType->Anims[MA_STAND].Frames;
5117 		break;
5118 	}
5119 }
5120 
M_FallenFear(int x,int y)5121 void M_FallenFear(int x, int y)
5122 {
5123 	MonsterStruct *m;
5124 	int i, rundist;
5125 
5126 	for (i = 0; i < nummonsters; i++) {
5127 		m = &monster[monstactive[i]];
5128 
5129 		switch (m->MType->mtype) {
5130 		case MT_RFALLSP:
5131 		case MT_RFALLSD:
5132 			rundist = 7;
5133 			break;
5134 		case MT_DFALLSP:
5135 		case MT_DFALLSD:
5136 			rundist = 5;
5137 			break;
5138 		case MT_YFALLSP:
5139 		case MT_YFALLSD:
5140 			rundist = 3;
5141 			break;
5142 		case MT_BFALLSP:
5143 		case MT_BFALLSD:
5144 			rundist = 2;
5145 			break;
5146 		default:
5147 			continue;
5148 		}
5149 		if (m->_mAi == AI_FALLEN
5150 		    && abs(x - m->_mx) < 5
5151 		    && abs(y - m->_my) < 5
5152 		    && m->_mhitpoints >> 6 > 0) {
5153 			m->_mgoal = MGOAL_RETREAT;
5154 			m->_mgoalvar1 = rundist;
5155 			m->_mdir = GetDirection(x, y, m->_mx, m->_my);
5156 		}
5157 	}
5158 }
5159 
GetMonsterTypeText(const MonsterData & monsterData)5160 const char *GetMonsterTypeText(const MonsterData &monsterData)
5161 {
5162 	switch (monsterData.mMonstClass) {
5163 	case MC_ANIMAL:
5164 		return "Animal";
5165 	case MC_DEMON:
5166 		return "Demon";
5167 	case MC_UNDEAD:
5168 		return "Undead";
5169 	}
5170 
5171 	app_fatal("Unknown mMonstClass %d", monsterData.mMonstClass);
5172 }
5173 
PrintMonstHistory(int mt)5174 void PrintMonstHistory(int mt)
5175 {
5176 	int minHP, maxHP, res;
5177 
5178 	if (sgOptions.Gameplay.bShowMonsterType) {
5179 		sprintf(tempstr, "Type: %s  Kills: %i", GetMonsterTypeText(monsterdata[mt]), monstkills[mt]);
5180 	} else {
5181 		sprintf(tempstr, "Total kills: %i", monstkills[mt]);
5182 	}
5183 
5184 	AddPanelString(tempstr, TRUE);
5185 	if (monstkills[mt] >= 30) {
5186 		minHP = monsterdata[mt].mMinHP;
5187 		maxHP = monsterdata[mt].mMaxHP;
5188 		if (!gbIsHellfire && mt == MT_DIABLO) {
5189 			minHP -= 2000;
5190 			maxHP -= 2000;
5191 		}
5192 		if (!gbIsMultiplayer) {
5193 			minHP >>= 1;
5194 			maxHP >>= 1;
5195 		}
5196 		if (minHP < 1)
5197 			minHP = 1;
5198 		if (maxHP < 1)
5199 			maxHP = 1;
5200 
5201 		int hpBonusNightmare = 1;
5202 		int hpBonusHell = 3;
5203 		if (gbIsHellfire) {
5204 			hpBonusNightmare = (!gbIsMultiplayer ? 50 : 100);
5205 			hpBonusHell = (!gbIsMultiplayer ? 100 : 200);
5206 		}
5207 		if (gnDifficulty == DIFF_NIGHTMARE) {
5208 			minHP = 3 * minHP + hpBonusNightmare;
5209 			maxHP = 3 * maxHP + hpBonusNightmare;
5210 		} else if (gnDifficulty == DIFF_HELL) {
5211 			minHP = 4 * minHP + hpBonusHell;
5212 			maxHP = 4 * maxHP + hpBonusHell;
5213 		}
5214 		sprintf(tempstr, "Hit Points: %i-%i", minHP, maxHP);
5215 		AddPanelString(tempstr, TRUE);
5216 	}
5217 	if (monstkills[mt] >= 15) {
5218 		if (gnDifficulty != DIFF_HELL)
5219 			res = monsterdata[mt].mMagicRes;
5220 		else
5221 			res = monsterdata[mt].mMagicRes2;
5222 		res = res & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING | IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING);
5223 		if (!res) {
5224 			strcpy(tempstr, "No magic resistance");
5225 			AddPanelString(tempstr, TRUE);
5226 		} else {
5227 			if (res & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING)) {
5228 				strcpy(tempstr, "Resists: ");
5229 				if (res & RESIST_MAGIC)
5230 					strcat(tempstr, "Magic ");
5231 				if (res & RESIST_FIRE)
5232 					strcat(tempstr, "Fire ");
5233 				if (res & RESIST_LIGHTNING)
5234 					strcat(tempstr, "Lightning ");
5235 				tempstr[strlen(tempstr) - 1] = '\0';
5236 				AddPanelString(tempstr, TRUE);
5237 			}
5238 			if (res & (IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING)) {
5239 				strcpy(tempstr, "Immune: ");
5240 				if (res & IMMUNE_MAGIC)
5241 					strcat(tempstr, "Magic ");
5242 				if (res & IMMUNE_FIRE)
5243 					strcat(tempstr, "Fire ");
5244 				if (res & IMMUNE_LIGHTNING)
5245 					strcat(tempstr, "Lightning ");
5246 				tempstr[strlen(tempstr) - 1] = '\0';
5247 				AddPanelString(tempstr, TRUE);
5248 			}
5249 		}
5250 	}
5251 	pinfoflag = TRUE;
5252 }
5253 
PrintUniqueHistory()5254 void PrintUniqueHistory()
5255 {
5256 	int res;
5257 
5258 	if (sgOptions.Gameplay.bShowMonsterType) {
5259 		sprintf(tempstr, "Type: %s", GetMonsterTypeText(*monster[pcursmonst].MData));
5260 		AddPanelString(tempstr, TRUE);
5261 	}
5262 
5263 	res = monster[pcursmonst].mMagicRes & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING | IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING);
5264 	if (!res) {
5265 		strcpy(tempstr, "No resistances");
5266 		AddPanelString(tempstr, TRUE);
5267 		strcpy(tempstr, "No Immunities");
5268 	} else {
5269 		if (res & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING))
5270 			strcpy(tempstr, "Some Magic Resistances");
5271 		else
5272 			strcpy(tempstr, "No resistances");
5273 		AddPanelString(tempstr, TRUE);
5274 		if (res & (IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING)) {
5275 			strcpy(tempstr, "Some Magic Immunities");
5276 		} else {
5277 			strcpy(tempstr, "No Immunities");
5278 		}
5279 	}
5280 	AddPanelString(tempstr, TRUE);
5281 	pinfoflag = TRUE;
5282 }
5283 
MissToMonst(int i,int x,int y)5284 void MissToMonst(int i, int x, int y)
5285 {
5286 	int oldx, oldy;
5287 	int newx, newy;
5288 	int m, pnum;
5289 	MissileStruct *Miss;
5290 	MonsterStruct *Monst;
5291 
5292 	assurance((DWORD)i < MAXMISSILES, i);
5293 
5294 	Miss = &missile[i];
5295 	m = Miss->_misource;
5296 
5297 	assurance((DWORD)m < MAXMONSTERS, m);
5298 
5299 	Monst = &monster[m];
5300 	oldx = Miss->_mix;
5301 	oldy = Miss->_miy;
5302 	dMonster[x][y] = m + 1;
5303 	Monst->_mdir = Miss->_mimfnum;
5304 	Monst->_mx = x;
5305 	Monst->_my = y;
5306 	M_StartStand(m, Monst->_mdir);
5307 	if (Monst->MType->mtype < MT_INCIN || Monst->MType->mtype > MT_HELLBURN) {
5308 		if (!(Monst->_mFlags & MFLAG_TARGETS_MONSTER))
5309 			M_StartHit(m, -1, 0);
5310 		else
5311 			M2MStartHit(m, -1, 0);
5312 	} else {
5313 		M_StartFadein(m, Monst->_mdir, FALSE);
5314 	}
5315 
5316 	if (!(Monst->_mFlags & MFLAG_TARGETS_MONSTER)) {
5317 		pnum = dPlayer[oldx][oldy] - 1;
5318 		if (dPlayer[oldx][oldy] > 0) {
5319 			if (Monst->MType->mtype != MT_GLOOM && (Monst->MType->mtype < MT_INCIN || Monst->MType->mtype > MT_HELLBURN)) {
5320 				M_TryH2HHit(m, dPlayer[oldx][oldy] - 1, 500, Monst->mMinDamage2, Monst->mMaxDamage2);
5321 				if (pnum == dPlayer[oldx][oldy] - 1 && (Monst->MType->mtype < MT_NSNAKE || Monst->MType->mtype > MT_GSNAKE)) {
5322 					if (plr[pnum]._pmode != PM_GOTHIT && plr[pnum]._pmode != PM_DEATH)
5323 						StartPlrHit(pnum, 0, TRUE);
5324 					newx = oldx + offset_x[Monst->_mdir];
5325 					newy = oldy + offset_y[Monst->_mdir];
5326 					if (PosOkPlayer(pnum, newx, newy)) {
5327 						plr[pnum]._px = newx;
5328 						plr[pnum]._py = newy;
5329 						FixPlayerLocation(pnum, plr[pnum]._pdir);
5330 						FixPlrWalkTags(pnum);
5331 						dPlayer[newx][newy] = pnum + 1;
5332 						SetPlayerOld(pnum);
5333 					}
5334 				}
5335 			}
5336 		}
5337 	} else {
5338 		if (dMonster[oldx][oldy] > 0) {
5339 			if (Monst->MType->mtype != MT_GLOOM && (Monst->MType->mtype < MT_INCIN || Monst->MType->mtype > MT_HELLBURN)) {
5340 				M_TryM2MHit(m, dMonster[oldx][oldy] - 1, 500, Monst->mMinDamage2, Monst->mMaxDamage2);
5341 				if (Monst->MType->mtype < MT_NSNAKE || Monst->MType->mtype > MT_GSNAKE) {
5342 					newx = oldx + offset_x[Monst->_mdir];
5343 					newy = oldy + offset_y[Monst->_mdir];
5344 					if (PosOkMonst(dMonster[oldx][oldy] - 1, newx, newy)) {
5345 						m = dMonster[oldx][oldy];
5346 						dMonster[newx][newy] = m;
5347 						dMonster[oldx][oldy] = 0;
5348 						m--;
5349 						monster[m]._mx = newx;
5350 						monster[m]._mfutx = newx;
5351 						monster[m]._my = newy;
5352 						monster[m]._mfuty = newy;
5353 					}
5354 				}
5355 			}
5356 		}
5357 	}
5358 }
5359 
PosOkMonst(int i,int x,int y)5360 BOOL PosOkMonst(int i, int x, int y)
5361 {
5362 	int oi, mi, j;
5363 	BOOL ret, fire;
5364 
5365 	ret = !SolidLoc(x, y) && dPlayer[x][y] == 0 && dMonster[x][y] == 0;
5366 	if (ret && dObject[x][y] != 0) {
5367 		oi = dObject[x][y] > 0 ? dObject[x][y] - 1 : -(dObject[x][y] + 1);
5368 		if (object[oi]._oSolidFlag)
5369 			ret = FALSE;
5370 	}
5371 	if (ret)
5372 		ret = monster_posok(i, x, y);
5373 
5374 	return ret;
5375 }
5376 
monster_posok(int i,int x,int y)5377 BOOLEAN monster_posok(int i, int x, int y)
5378 {
5379 	int mi, j;
5380 	BOOLEAN ret, fire, lightning;
5381 
5382 	ret = TRUE;
5383 	mi = dMissile[x][y];
5384 	if (mi != 0 && i >= 0) {
5385 		fire = FALSE;
5386 		lightning = FALSE;
5387 		if (mi > 0) {
5388 			if (missile[mi - 1]._mitype == MIS_FIREWALL) { // BUGFIX: Change 'mi' to 'mi - 1' (fixed)
5389 				fire = TRUE;
5390 			} else if (missile[mi - 1]._mitype == MIS_LIGHTWALL) { // BUGFIX: Change 'mi' to 'mi - 1' (fixed)
5391 				lightning = TRUE;
5392 			}
5393 		} else {
5394 			for (j = 0; j < nummissiles; j++) {
5395 				mi = missileactive[j];
5396 				if (missile[mi]._mix == x && missile[mi]._miy == y) {
5397 					if (missile[mi]._mitype == MIS_FIREWALL) {
5398 						fire = TRUE;
5399 						break;
5400 					}
5401 					if (missile[mi]._mitype == MIS_LIGHTWALL) {
5402 						lightning = TRUE;
5403 						break;
5404 					}
5405 				}
5406 			}
5407 		}
5408 		if (fire && (!(monster[i].mMagicRes & IMMUNE_FIRE) || monster[i].MType->mtype == MT_DIABLO))
5409 			ret = FALSE;
5410 		if (lightning && (!(monster[i].mMagicRes & IMMUNE_LIGHTNING) || monster[i].MType->mtype == MT_DIABLO))
5411 			ret = FALSE;
5412 	}
5413 
5414 	return ret;
5415 }
5416 
PosOkMonst2(int i,int x,int y)5417 BOOL PosOkMonst2(int i, int x, int y)
5418 {
5419 	int oi, mi, j;
5420 	BOOL ret, fire;
5421 
5422 	ret = !SolidLoc(x, y);
5423 	if (ret && dObject[x][y] != 0) {
5424 		oi = dObject[x][y] > 0 ? dObject[x][y] - 1 : -(dObject[x][y] + 1);
5425 		if (object[oi]._oSolidFlag)
5426 			ret = FALSE;
5427 	}
5428 	if (ret)
5429 		ret = monster_posok(i, x, y);
5430 
5431 	return ret;
5432 }
5433 
PosOkMonst3(int i,int x,int y)5434 BOOL PosOkMonst3(int i, int x, int y)
5435 {
5436 	int j, oi, objtype, mi;
5437 	bool ret, fire, isdoor;
5438 
5439 	ret = TRUE;
5440 	isdoor = FALSE;
5441 
5442 	if (ret && dObject[x][y] != 0) {
5443 		oi = dObject[x][y] > 0 ? dObject[x][y] - 1 : -(dObject[x][y] + 1);
5444 		objtype = object[oi]._otype;
5445 		isdoor = objtype == OBJ_L1LDOOR || objtype == OBJ_L1RDOOR
5446 		    || objtype == OBJ_L2LDOOR || objtype == OBJ_L2RDOOR
5447 		    || objtype == OBJ_L3LDOOR || objtype == OBJ_L3RDOOR;
5448 		if (object[oi]._oSolidFlag && !isdoor) {
5449 			ret = FALSE;
5450 		}
5451 	}
5452 	if (ret) {
5453 		ret = (!SolidLoc(x, y) || isdoor) && dPlayer[x][y] == 0 && dMonster[x][y] == 0;
5454 	}
5455 	if (ret)
5456 		ret = monster_posok(i, x, y);
5457 
5458 	return ret;
5459 }
5460 
IsSkel(int mt)5461 BOOL IsSkel(int mt)
5462 {
5463 	return mt >= MT_WSKELAX && mt <= MT_XSKELAX
5464 	    || mt >= MT_WSKELBW && mt <= MT_XSKELBW
5465 	    || mt >= MT_WSKELSD && mt <= MT_XSKELSD;
5466 }
5467 
IsGoat(int mt)5468 BOOL IsGoat(int mt)
5469 {
5470 	return mt >= MT_NGOATMC && mt <= MT_GGOATMC
5471 	    || mt >= MT_NGOATBW && mt <= MT_GGOATBW;
5472 }
5473 
M_SpawnSkel(int x,int y,int dir)5474 int M_SpawnSkel(int x, int y, int dir)
5475 {
5476 	int i, j, skeltypes, skel;
5477 
5478 	j = 0;
5479 	for (i = 0; i < nummtypes; i++) {
5480 		if (IsSkel(Monsters[i].mtype))
5481 			j++;
5482 	}
5483 
5484 	if (j) {
5485 		skeltypes = random_(136, j);
5486 		j = 0;
5487 		for (i = 0; i < nummtypes && j <= skeltypes; i++) {
5488 			if (IsSkel(Monsters[i].mtype))
5489 				j++;
5490 		}
5491 		skel = AddMonster(x, y, dir, i - 1, TRUE);
5492 		if (skel != -1)
5493 			M_StartSpStand(skel, dir);
5494 
5495 		return skel;
5496 	}
5497 
5498 	return -1;
5499 }
5500 
ActivateSpawn(int i,int x,int y,int dir)5501 void ActivateSpawn(int i, int x, int y, int dir)
5502 {
5503 	dMonster[x][y] = i + 1;
5504 	monster[i]._mx = x;
5505 	monster[i]._my = y;
5506 	monster[i]._mfutx = x;
5507 	monster[i]._mfuty = y;
5508 	monster[i]._moldx = x;
5509 	monster[i]._moldy = y;
5510 	M_StartSpStand(i, dir);
5511 }
5512 
SpawnSkeleton(int ii,int x,int y)5513 BOOL SpawnSkeleton(int ii, int x, int y)
5514 {
5515 	int dx, dy, xx, yy, dir, j, k, rs;
5516 	BOOL savail;
5517 	int monstok[3][3];
5518 
5519 	if (ii == -1)
5520 		return FALSE;
5521 
5522 	if (PosOkMonst(-1, x, y)) {
5523 		dir = GetDirection(x, y, x, y);
5524 		ActivateSpawn(ii, x, y, dir);
5525 		return TRUE;
5526 	}
5527 
5528 	savail = FALSE;
5529 	yy = 0;
5530 	for (j = y - 1; j <= y + 1; j++) {
5531 		xx = 0;
5532 		for (k = x - 1; k <= x + 1; k++) {
5533 			monstok[xx][yy] = PosOkMonst(-1, k, j);
5534 			savail |= monstok[xx][yy];
5535 			xx++;
5536 		}
5537 		yy++;
5538 	}
5539 	if (!savail) {
5540 		return FALSE;
5541 	}
5542 
5543 	rs = random_(137, 15) + 1;
5544 	xx = 0;
5545 	yy = 0;
5546 	while (rs > 0) {
5547 		if (monstok[xx][yy])
5548 			rs--;
5549 		if (rs > 0) {
5550 			xx++;
5551 			if (xx == 3) {
5552 				xx = 0;
5553 				yy++;
5554 				if (yy == 3)
5555 					yy = 0;
5556 			}
5557 		}
5558 	}
5559 
5560 	dx = x - 1 + xx;
5561 	dy = y - 1 + yy;
5562 	dir = GetDirection(dx, dy, x, y);
5563 	ActivateSpawn(ii, dx, dy, dir);
5564 
5565 	return TRUE;
5566 }
5567 
PreSpawnSkeleton()5568 int PreSpawnSkeleton()
5569 {
5570 	int i, j, skeltypes, skel;
5571 
5572 	j = 0;
5573 
5574 	for (i = 0; i < nummtypes; i++) {
5575 		if (IsSkel(Monsters[i].mtype))
5576 			j++;
5577 	}
5578 
5579 	if (j) {
5580 		skeltypes = random_(136, j);
5581 		j = 0;
5582 		for (i = 0; i < nummtypes && j <= skeltypes; i++) {
5583 			if (IsSkel(Monsters[i].mtype))
5584 				j++;
5585 		}
5586 		skel = AddMonster(0, 0, 0, i - 1, FALSE);
5587 		if (skel != -1)
5588 			M_StartStand(skel, 0);
5589 
5590 		return skel;
5591 	}
5592 
5593 	return -1;
5594 }
5595 
TalktoMonster(int i)5596 void TalktoMonster(int i)
5597 {
5598 	MonsterStruct *Monst;
5599 	int pnum, itm;
5600 
5601 	assurance((DWORD)i < MAXMONSTERS, i);
5602 
5603 	Monst = &monster[i];
5604 	pnum = Monst->_menemy;
5605 	Monst->_mmode = MM_TALK;
5606 	if (Monst->_mAi == AI_SNOTSPIL || Monst->_mAi == AI_LACHDAN) {
5607 		if (QuestStatus(Q_LTBANNER) && quests[Q_LTBANNER]._qvar1 == 2 && PlrHasItem(pnum, IDI_BANNER, &itm)) {
5608 			RemoveInvItem(pnum, itm);
5609 			quests[Q_LTBANNER]._qactive = QUEST_DONE;
5610 			Monst->mtalkmsg = TEXT_BANNER12;
5611 			Monst->_mgoal = MGOAL_INQUIRING;
5612 		}
5613 		if (QuestStatus(Q_VEIL) && Monst->mtalkmsg >= TEXT_VEIL9) {
5614 			if (PlrHasItem(pnum, IDI_GLDNELIX, &itm)) {
5615 				RemoveInvItem(pnum, itm);
5616 				Monst->mtalkmsg = TEXT_VEIL11;
5617 				Monst->_mgoal = MGOAL_INQUIRING;
5618 			}
5619 		}
5620 	}
5621 }
5622 
SpawnGolum(int i,int x,int y,int mi)5623 void SpawnGolum(int i, int x, int y, int mi)
5624 {
5625 	assurance((DWORD)i < MAXMONSTERS, i);
5626 
5627 	dMonster[x][y] = i + 1;
5628 	monster[i]._mx = x;
5629 	monster[i]._my = y;
5630 	monster[i]._mfutx = x;
5631 	monster[i]._mfuty = y;
5632 	monster[i]._moldx = x;
5633 	monster[i]._moldy = y;
5634 	monster[i]._pathcount = 0;
5635 	monster[i]._mmaxhp = 2 * (320 * missile[mi]._mispllvl + plr[i]._pMaxMana / 3);
5636 	monster[i]._mhitpoints = monster[i]._mmaxhp;
5637 	monster[i].mArmorClass = 25;
5638 	monster[i].mHit = 5 * (missile[mi]._mispllvl + 8) + 2 * plr[i]._pLevel;
5639 	monster[i].mMinDamage = 2 * (missile[mi]._mispllvl + 4);
5640 	monster[i].mMaxDamage = 2 * (missile[mi]._mispllvl + 8);
5641 	monster[i]._mFlags |= MFLAG_GOLEM;
5642 	M_StartSpStand(i, 0);
5643 	M_Enemy(i);
5644 	if (i == myplr) {
5645 		NetSendCmdGolem(
5646 		    monster[i]._mx,
5647 		    monster[i]._my,
5648 		    monster[i]._mdir,
5649 		    monster[i]._menemy,
5650 		    monster[i]._mhitpoints,
5651 		    currlevel);
5652 	}
5653 }
5654 
CanTalkToMonst(int m)5655 BOOL CanTalkToMonst(int m)
5656 {
5657 	commitment((DWORD)m < MAXMONSTERS, m);
5658 
5659 	if (monster[m]._mgoal == MGOAL_INQUIRING) {
5660 		return TRUE;
5661 	}
5662 
5663 	return monster[m]._mgoal == MGOAL_TALKING;
5664 }
5665 
CheckMonsterHit(int m,BOOL * ret)5666 BOOL CheckMonsterHit(int m, BOOL *ret)
5667 {
5668 	commitment((DWORD)m < MAXMONSTERS, m);
5669 
5670 	if (monster[m]._mAi == AI_GARG && monster[m]._mFlags & MFLAG_ALLOW_SPECIAL) {
5671 		monster[m]._mFlags &= ~MFLAG_ALLOW_SPECIAL;
5672 		monster[m]._mmode = MM_SATTACK;
5673 		*ret = TRUE;
5674 		return TRUE;
5675 	}
5676 
5677 	if (monster[m].MType->mtype >= MT_COUNSLR && monster[m].MType->mtype <= MT_ADVOCATE) {
5678 		if (monster[m]._mgoal != MGOAL_NORMAL) {
5679 			*ret = FALSE;
5680 			return TRUE;
5681 		}
5682 	}
5683 
5684 	return FALSE;
5685 }
5686 
encode_enemy(int m)5687 int encode_enemy(int m)
5688 {
5689 	if (monster[m]._mFlags & MFLAG_TARGETS_MONSTER)
5690 		return monster[m]._menemy + MAX_PLRS;
5691 	else
5692 		return monster[m]._menemy;
5693 }
5694 
decode_enemy(int m,int enemy)5695 void decode_enemy(int m, int enemy)
5696 {
5697 	if (enemy < MAX_PLRS) {
5698 		monster[m]._mFlags &= ~MFLAG_TARGETS_MONSTER;
5699 		monster[m]._menemy = enemy;
5700 		monster[m]._menemyx = plr[enemy]._pfutx;
5701 		monster[m]._menemyy = plr[enemy]._pfuty;
5702 	} else {
5703 		monster[m]._mFlags |= MFLAG_TARGETS_MONSTER;
5704 		enemy -= MAX_PLRS;
5705 		monster[m]._menemy = enemy;
5706 		monster[m]._menemyx = monster[enemy]._mfutx;
5707 		monster[m]._menemyy = monster[enemy]._mfuty;
5708 	}
5709 }
5710 
5711 DEVILUTION_END_NAMESPACE
5712