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