1 /**
2  * @file player.cpp
3  *
4  * Implementation of player functionality, leveling, actions, creation, 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 int plr_lframe_size;
15 int plr_wframe_size;
16 BYTE plr_gfx_flag = 0;
17 int plr_aframe_size;
18 int myplr;
19 PlayerStruct plr[MAX_PLRS];
20 int plr_fframe_size;
21 int plr_qframe_size;
22 BOOL deathflag;
23 int plr_hframe_size;
24 int plr_bframe_size;
25 BYTE plr_gfx_bflag = 0;
26 int plr_sframe_size;
27 int deathdelay;
28 int plr_dframe_size;
29 
30 /** Maps from armor animation to letter used in graphic files. */
31 const char ArmourChar[4] = { 'L', 'M', 'H', 0 };
32 /** Maps from weapon animation to letter used in graphic files. */
33 const char WepChar[10] = { 'N', 'U', 'S', 'D', 'B', 'A', 'M', 'H', 'T', 0 };
34 /** Maps from player class to letter used in graphic files. */
35 const char CharChar[] = {
36 	'W',
37 	'R',
38 	'S',
39 	'M',
40 	'B',
41 	'C',
42 	0
43 };
44 
45 /* data */
46 
47 /** Specifies the X-coordinate delta from the player start location in Tristram. */
48 int plrxoff[9] = { 0, 2, 0, 2, 1, 0, 1, 2, 1 };
49 /** Specifies the Y-coordinate delta from the player start location in Tristram. */
50 int plryoff[9] = { 0, 2, 2, 0, 1, 1, 0, 1, 2 };
51 /** Specifies the X-coordinate delta from a player, used for instanced when casting resurrect. */
52 int plrxoff2[9] = { 0, 1, 0, 1, 2, 0, 1, 2, 2 };
53 /** Specifies the Y-coordinate delta from a player, used for instanced when casting resurrect. */
54 int plryoff2[9] = { 0, 0, 1, 1, 0, 2, 2, 1, 2 };
55 /** Specifies the frame of each animation for which an action is triggered, for each player class. */
56 char PlrGFXAnimLens[NUM_CLASSES][11] = {
57 	{ 10, 16, 8, 2, 20, 20, 6, 20, 8, 9, 14 },
58 	{ 8, 18, 8, 4, 20, 16, 7, 20, 8, 10, 12 },
59 	{ 8, 16, 8, 6, 20, 12, 8, 20, 8, 12, 8 },
60 	{ 8, 16, 8, 3, 20, 18, 6, 20, 8, 12, 13 },
61 	{ 8, 18, 8, 4, 20, 16, 7, 20, 8, 10, 12 },
62 	{ 10, 16, 8, 2, 20, 20, 6, 20, 8, 9, 14 },
63 };
64 /** Maps from player class to player velocity. */
65 int PWVel[NUM_CLASSES][3] = {
66 	{ 2048, 1024, 512 },
67 	{ 2048, 1024, 512 },
68 	{ 2048, 1024, 512 },
69 	{ 2048, 1024, 512 },
70 	{ 2048, 1024, 512 },
71 	{ 2048, 1024, 512 },
72 };
73 /** Total number of frames in walk animation. */
74 int AnimLenFromClass[NUM_CLASSES] = {
75 	8,
76 	8,
77 	8,
78 	8,
79 	8,
80 	8,
81 };
82 /** Maps from player_class to starting stat in strength. */
83 int StrengthTbl[NUM_CLASSES] = {
84 	30,
85 	20,
86 	15,
87 	25,
88 	20,
89 	40,
90 };
91 /** Maps from player_class to starting stat in magic. */
92 int MagicTbl[NUM_CLASSES] = {
93 	// clang-format off
94 	10,
95 	15,
96 	35,
97 	15,
98 	20,
99 	 0,
100 	// clang-format on
101 };
102 /** Maps from player_class to starting stat in dexterity. */
103 int DexterityTbl[NUM_CLASSES] = {
104 	20,
105 	30,
106 	15,
107 	25,
108 	25,
109 	20,
110 };
111 /** Maps from player_class to starting stat in vitality. */
112 int VitalityTbl[NUM_CLASSES] = {
113 	25,
114 	20,
115 	20,
116 	20,
117 	20,
118 	25,
119 };
120 /** Specifies the chance to block bonus of each player class.*/
121 int ToBlkTbl[NUM_CLASSES] = {
122 	30,
123 	20,
124 	10,
125 	25,
126 	25,
127 	30,
128 };
129 /** Maps from player_class to maximum stats. */
130 int MaxStats[NUM_CLASSES][4] = {
131 	// clang-format off
132 	{ 250,  50,  60, 100 },
133 	{  55,  70, 250,  80 },
134 	{  45, 250,  85,  80 },
135 	{ 150,  80, 150,  80 },
136 	{ 120, 120, 120, 100 },
137 	{ 255,   0,  55, 150 },
138 	// clang-format on
139 };
140 /** Specifies the experience point limit of each level. */
141 int ExpLvlsTbl[MAXCHARLEVEL] = {
142 	0,
143 	2000,
144 	4620,
145 	8040,
146 	12489,
147 	18258,
148 	25712,
149 	35309,
150 	47622,
151 	63364,
152 	83419,
153 	108879,
154 	141086,
155 	181683,
156 	231075,
157 	313656,
158 	424067,
159 	571190,
160 	766569,
161 	1025154,
162 	1366227,
163 	1814568,
164 	2401895,
165 	3168651,
166 	4166200,
167 	5459523,
168 	7130496,
169 	9281874,
170 	12042092,
171 	15571031,
172 	20066900,
173 	25774405,
174 	32994399,
175 	42095202,
176 	53525811,
177 	67831218,
178 	85670061,
179 	107834823,
180 	135274799,
181 	169122009,
182 	210720231,
183 	261657253,
184 	323800420,
185 	399335440,
186 	490808349,
187 	601170414,
188 	733825617,
189 	892680222,
190 	1082908612,
191 	1310707109,
192 	1583495809
193 };
194 const char *const ClassPathTbl[] = {
195 	"Warrior",
196 	"Rogue",
197 	"Sorceror",
198 	"Monk",
199 	"Rogue",
200 	"Warrior",
201 };
202 
GetBaseAttributeValue(attribute_id attribute) const203 Sint32 PlayerStruct::GetBaseAttributeValue(attribute_id attribute) const
204 {
205 	switch (attribute) {
206 	case attribute_id::ATTRIB_DEX:
207 		return this->_pBaseDex;
208 	case attribute_id::ATTRIB_MAG:
209 		return this->_pBaseMag;
210 	case attribute_id::ATTRIB_STR:
211 		return this->_pBaseStr;
212 	case attribute_id::ATTRIB_VIT:
213 		return this->_pBaseVit;
214 	default:
215 		app_fatal("Unsupported attribute");
216 	}
217 }
218 
GetMaximumAttributeValue(attribute_id attribute) const219 Sint32 PlayerStruct::GetMaximumAttributeValue(attribute_id attribute) const
220 {
221 	return MaxStats[_pClass][attribute];
222 }
223 
SetPlayerGPtrs(BYTE * pData,BYTE ** pAnim)224 void SetPlayerGPtrs(BYTE *pData, BYTE **pAnim)
225 {
226 	int i;
227 
228 	for (i = 0; i < 8; i++) {
229 		pAnim[i] = CelGetFrameStart(pData, i);
230 	}
231 }
232 
LoadPlrGFX(int pnum,player_graphic gfxflag)233 void LoadPlrGFX(int pnum, player_graphic gfxflag)
234 {
235 	char prefix[16];
236 	char pszName[256];
237 	const char *szCel;
238 	PlayerStruct *p;
239 	BYTE *pData, *pAnim;
240 	DWORD i;
241 
242 	if ((DWORD)pnum >= MAX_PLRS) {
243 		app_fatal("LoadPlrGFX: illegal player %d", pnum);
244 	}
245 
246 	p = &plr[pnum];
247 
248 	plr_class c = p->_pClass;
249 	if (c == PC_BARD && hfbard_mpq == NULL) {
250 		c = PC_ROGUE;
251 	} else if (c == PC_BARBARIAN && hfbarb_mpq == NULL) {
252 		c = PC_WARRIOR;
253 	}
254 
255 	sprintf(prefix, "%c%c%c", CharChar[c], ArmourChar[p->_pgfxnum >> 4], WepChar[p->_pgfxnum & 0xF]);
256 	const char *cs = ClassPathTbl[c];
257 
258 	for (i = 1; i <= PFILE_NONDEATH; i <<= 1) {
259 		if (!(i & gfxflag)) {
260 			continue;
261 		}
262 
263 		switch (i) {
264 		case PFILE_STAND:
265 			szCel = "AS";
266 			if (leveltype == DTYPE_TOWN) {
267 				szCel = "ST";
268 			}
269 			pData = p->_pNData;
270 			pAnim = (BYTE *)p->_pNAnim;
271 			break;
272 		case PFILE_WALK:
273 			szCel = "AW";
274 			if (leveltype == DTYPE_TOWN) {
275 				szCel = "WL";
276 			}
277 			pData = p->_pWData;
278 			pAnim = (BYTE *)p->_pWAnim;
279 			break;
280 		case PFILE_ATTACK:
281 			if (leveltype == DTYPE_TOWN) {
282 				continue;
283 			}
284 			szCel = "AT";
285 			pData = p->_pAData;
286 			pAnim = (BYTE *)p->_pAAnim;
287 			break;
288 		case PFILE_HIT:
289 			if (leveltype == DTYPE_TOWN) {
290 				continue;
291 			}
292 			szCel = "HT";
293 			pData = p->_pHData;
294 			pAnim = (BYTE *)p->_pHAnim;
295 			break;
296 		case PFILE_LIGHTNING:
297 			if (leveltype == DTYPE_TOWN) {
298 				continue;
299 			}
300 			szCel = "LM";
301 			pData = p->_pLData;
302 			pAnim = (BYTE *)p->_pLAnim;
303 			break;
304 		case PFILE_FIRE:
305 			if (leveltype == DTYPE_TOWN) {
306 				continue;
307 			}
308 			szCel = "FM";
309 			pData = p->_pFData;
310 			pAnim = (BYTE *)p->_pFAnim;
311 			break;
312 		case PFILE_MAGIC:
313 			if (leveltype == DTYPE_TOWN) {
314 				continue;
315 			}
316 			szCel = "QM";
317 			pData = p->_pTData;
318 			pAnim = (BYTE *)p->_pTAnim;
319 			break;
320 		case PFILE_DEATH:
321 			if (p->_pgfxnum & 0xF) {
322 				continue;
323 			}
324 			szCel = "DT";
325 			pData = p->_pDData;
326 			pAnim = (BYTE *)p->_pDAnim;
327 			break;
328 		case PFILE_BLOCK:
329 			if (leveltype == DTYPE_TOWN) {
330 				continue;
331 			}
332 			if (!p->_pBlockFlag) {
333 				continue;
334 			}
335 
336 			szCel = "BL";
337 			pData = p->_pBData;
338 			pAnim = (BYTE *)p->_pBAnim;
339 			break;
340 		default:
341 			app_fatal("PLR:2");
342 		}
343 
344 		sprintf(pszName, "PlrGFX\\%s\\%s\\%s%s.CL2", cs, prefix, prefix, szCel);
345 		LoadFileWithMem(pszName, pData);
346 		SetPlayerGPtrs((BYTE *)pData, (BYTE **)pAnim);
347 		p->_pGFXLoad |= i;
348 	}
349 }
350 
InitPlayerGFX(int pnum)351 void InitPlayerGFX(int pnum)
352 {
353 	if ((DWORD)pnum >= MAX_PLRS) {
354 		app_fatal("InitPlayerGFX: illegal player %d", pnum);
355 	}
356 
357 	if (plr[pnum]._pHitPoints >> 6 == 0) {
358 		plr[pnum]._pgfxnum = 0;
359 		LoadPlrGFX(pnum, PFILE_DEATH);
360 	} else {
361 		LoadPlrGFX(pnum, PFILE_NONDEATH);
362 	}
363 }
364 
GetPlrGFXSize(const char * szCel)365 static DWORD GetPlrGFXSize(const char *szCel)
366 {
367 	DWORD c;
368 	const char *a, *w;
369 	DWORD dwSize, dwMaxSize;
370 	HANDLE hsFile;
371 	char pszName[256];
372 	char Type[16];
373 
374 	dwMaxSize = 0;
375 
376 	for (c = 0; c < NUM_CLASSES; c++) {
377 		if (gbIsSpawn && (c == PC_ROGUE || c == PC_SORCERER))
378 			continue;
379 		if (!gbIsHellfire && c == PC_MONK)
380 			continue;
381 		if ((c == PC_BARD && hfbard_mpq == NULL) || (c == PC_BARBARIAN && hfbarb_mpq == NULL))
382 			continue;
383 
384 		for (a = &ArmourChar[0]; *a; a++) {
385 			if (gbIsSpawn && a != &ArmourChar[0])
386 				break;
387 			for (w = &WepChar[0]; *w; w++) { // BUGFIX loads non-existing animagions; DT is only for N, BT is only for U, D & H (fixed)
388 				if (szCel[0] == 'D' && szCel[1] == 'T' && *w != 'N') {
389 					continue; //Death has no weapon
390 				}
391 				if (szCel[0] == 'B' && szCel[1] == 'L' && (*w != 'U' && *w != 'D' && *w != 'H')) {
392 					continue; //No block without weapon
393 				}
394 				sprintf(Type, "%c%c%c", CharChar[c], *a, *w);
395 				sprintf(pszName, "PlrGFX\\%s\\%s\\%s%s.CL2", ClassPathTbl[c], Type, Type, szCel);
396 				if (SFileOpenFile(pszName, &hsFile)) {
397 					/// ASSERT: assert(hsFile);
398 					dwSize = SFileGetFileSize(hsFile, NULL);
399 					SFileCloseFile(hsFile);
400 					if (dwMaxSize <= dwSize) {
401 						dwMaxSize = dwSize;
402 					}
403 				}
404 			}
405 		}
406 	}
407 
408 	return dwMaxSize;
409 }
410 
InitPlrGFXMem(int pnum)411 void InitPlrGFXMem(int pnum)
412 {
413 	if ((DWORD)pnum >= MAX_PLRS) {
414 		app_fatal("InitPlrGFXMem: illegal player %d", pnum);
415 	}
416 
417 	if (!(plr_gfx_flag & 0x1)) { //STAND
418 		plr_gfx_flag |= 0x1;
419 		// ST: TOWN, AS: DUNGEON
420 		plr_sframe_size = std::max(GetPlrGFXSize("ST"), GetPlrGFXSize("AS"));
421 	}
422 	plr[pnum]._pNData = DiabloAllocPtr(plr_sframe_size);
423 
424 	if (!(plr_gfx_flag & 0x2)) { //WALK
425 		plr_gfx_flag |= 0x2;
426 		// WL: TOWN, AW: DUNGEON
427 		plr_wframe_size = std::max(GetPlrGFXSize("WL"), GetPlrGFXSize("AW"));
428 	}
429 	plr[pnum]._pWData = DiabloAllocPtr(plr_wframe_size);
430 
431 	if (!(plr_gfx_flag & 0x4)) { //ATTACK
432 		plr_gfx_flag |= 0x4;
433 		plr_aframe_size = GetPlrGFXSize("AT");
434 	}
435 	plr[pnum]._pAData = DiabloAllocPtr(plr_aframe_size);
436 
437 	if (!(plr_gfx_flag & 0x8)) { //HIT
438 		plr_gfx_flag |= 0x8;
439 		plr_hframe_size = GetPlrGFXSize("HT");
440 	}
441 	plr[pnum]._pHData = DiabloAllocPtr(plr_hframe_size);
442 
443 	if (!(plr_gfx_flag & 0x10)) { //LIGHTNING
444 		plr_gfx_flag |= 0x10;
445 		plr_lframe_size = GetPlrGFXSize("LM");
446 	}
447 	plr[pnum]._pLData = DiabloAllocPtr(plr_lframe_size);
448 
449 	if (!(plr_gfx_flag & 0x20)) { //FIRE
450 		plr_gfx_flag |= 0x20;
451 		plr_fframe_size = GetPlrGFXSize("FM");
452 	}
453 	plr[pnum]._pFData = DiabloAllocPtr(plr_fframe_size);
454 
455 	if (!(plr_gfx_flag & 0x40)) { //MAGIC
456 		plr_gfx_flag |= 0x40;
457 		plr_qframe_size = GetPlrGFXSize("QM");
458 	}
459 	plr[pnum]._pTData = DiabloAllocPtr(plr_qframe_size);
460 
461 	if (!(plr_gfx_flag & 0x80)) { //DEATH
462 		plr_gfx_flag |= 0x80;
463 		plr_dframe_size = GetPlrGFXSize("DT");
464 	}
465 	plr[pnum]._pDData = DiabloAllocPtr(plr_dframe_size);
466 
467 	if (!(plr_gfx_bflag & 0x1)) { //BLOCK
468 		plr_gfx_bflag |= 0x1;
469 		plr_bframe_size = GetPlrGFXSize("BL");
470 	}
471 	plr[pnum]._pBData = DiabloAllocPtr(plr_bframe_size);
472 
473 	plr[pnum]._pGFXLoad = 0;
474 }
475 
FreePlayerGFX(int pnum)476 void FreePlayerGFX(int pnum)
477 {
478 	if ((DWORD)pnum >= MAX_PLRS) {
479 		app_fatal("FreePlayerGFX: illegal player %d", pnum);
480 	}
481 
482 	MemFreeDbg(plr[pnum]._pNData);
483 	MemFreeDbg(plr[pnum]._pWData);
484 	MemFreeDbg(plr[pnum]._pAData);
485 	MemFreeDbg(plr[pnum]._pHData);
486 	MemFreeDbg(plr[pnum]._pLData);
487 	MemFreeDbg(plr[pnum]._pFData);
488 	MemFreeDbg(plr[pnum]._pTData);
489 	MemFreeDbg(plr[pnum]._pDData);
490 	MemFreeDbg(plr[pnum]._pBData);
491 	plr[pnum]._pGFXLoad = 0;
492 }
493 
NewPlrAnim(int pnum,BYTE * Peq,int numFrames,int Delay,int width)494 void NewPlrAnim(int pnum, BYTE *Peq, int numFrames, int Delay, int width)
495 {
496 	if ((DWORD)pnum >= MAX_PLRS) {
497 		app_fatal("NewPlrAnim: illegal player %d", pnum);
498 	}
499 
500 	plr[pnum]._pAnimData = Peq;
501 	plr[pnum]._pAnimLen = numFrames;
502 	plr[pnum]._pAnimFrame = 1;
503 	plr[pnum]._pAnimCnt = 0;
504 	plr[pnum]._pAnimDelay = Delay;
505 	plr[pnum]._pAnimWidth = width;
506 	plr[pnum]._pAnimWidth2 = (width - 64) >> 1;
507 }
508 
ClearPlrPVars(int pnum)509 void ClearPlrPVars(int pnum)
510 {
511 	if ((DWORD)pnum >= MAX_PLRS) {
512 		app_fatal("ClearPlrPVars: illegal player %d", pnum);
513 	}
514 
515 	plr[pnum]._pVar1 = 0;
516 	plr[pnum]._pVar2 = 0;
517 	plr[pnum]._pVar3 = DIR_S;
518 	plr[pnum]._pVar4 = 0;
519 	plr[pnum]._pVar5 = 0;
520 	plr[pnum]._pVar6 = 0;
521 	plr[pnum]._pVar7 = 0;
522 	plr[pnum]._pVar8 = 0;
523 }
524 
SetPlrAnims(int pnum)525 void SetPlrAnims(int pnum)
526 {
527 	int gn;
528 
529 	if ((DWORD)pnum >= MAX_PLRS) {
530 		app_fatal("SetPlrAnims: illegal player %d", pnum);
531 	}
532 
533 	plr[pnum]._pNWidth = 96;
534 	plr[pnum]._pWWidth = 96;
535 	plr[pnum]._pAWidth = 128;
536 	plr[pnum]._pHWidth = 96;
537 	plr[pnum]._pSWidth = 96;
538 	plr[pnum]._pDWidth = 128;
539 	plr[pnum]._pBWidth = 96;
540 
541 	plr_class pc = plr[pnum]._pClass;
542 
543 	if (leveltype == DTYPE_TOWN) {
544 		plr[pnum]._pNFrames = PlrGFXAnimLens[pc][7];
545 		plr[pnum]._pWFrames = PlrGFXAnimLens[pc][8];
546 		plr[pnum]._pDFrames = PlrGFXAnimLens[pc][4];
547 		plr[pnum]._pSFrames = PlrGFXAnimLens[pc][5];
548 	} else {
549 		plr[pnum]._pNFrames = PlrGFXAnimLens[pc][0];
550 		plr[pnum]._pWFrames = PlrGFXAnimLens[pc][2];
551 		plr[pnum]._pAFrames = PlrGFXAnimLens[pc][1];
552 		plr[pnum]._pHFrames = PlrGFXAnimLens[pc][6];
553 		plr[pnum]._pSFrames = PlrGFXAnimLens[pc][5];
554 		plr[pnum]._pDFrames = PlrGFXAnimLens[pc][4];
555 		plr[pnum]._pBFrames = PlrGFXAnimLens[pc][3];
556 		plr[pnum]._pAFNum = PlrGFXAnimLens[pc][9];
557 	}
558 	plr[pnum]._pSFNum = PlrGFXAnimLens[pc][10];
559 
560 	gn = plr[pnum]._pgfxnum & 0xF;
561 	if (pc == PC_WARRIOR) {
562 		if (gn == ANIM_ID_BOW) {
563 			if (leveltype != DTYPE_TOWN) {
564 				plr[pnum]._pNFrames = 8;
565 			}
566 			plr[pnum]._pAWidth = 96;
567 			plr[pnum]._pAFNum = 11;
568 		} else if (gn == ANIM_ID_AXE) {
569 			plr[pnum]._pAFrames = 20;
570 			plr[pnum]._pAFNum = 10;
571 		} else if (gn == ANIM_ID_STAFF) {
572 			plr[pnum]._pAFrames = 16;
573 			plr[pnum]._pAFNum = 11;
574 		}
575 	} else if (pc == PC_ROGUE) {
576 		if (gn == ANIM_ID_AXE) {
577 			plr[pnum]._pAFrames = 22;
578 			plr[pnum]._pAFNum = 13;
579 		} else if (gn == ANIM_ID_BOW) {
580 			plr[pnum]._pAFrames = 12;
581 			plr[pnum]._pAFNum = 7;
582 		} else if (gn == ANIM_ID_STAFF) {
583 			plr[pnum]._pAFrames = 16;
584 			plr[pnum]._pAFNum = 11;
585 		}
586 	} else if (pc == PC_SORCERER) {
587 		plr[pnum]._pSWidth = 128;
588 		if (gn == ANIM_ID_UNARMED) {
589 			plr[pnum]._pAFrames = 20;
590 		} else if (gn == ANIM_ID_UNARMED_SHIELD) {
591 			plr[pnum]._pAFNum = 9;
592 		} else if (gn == ANIM_ID_BOW) {
593 			plr[pnum]._pAFrames = 20;
594 			plr[pnum]._pAFNum = 16;
595 		} else if (gn == ANIM_ID_AXE) {
596 			plr[pnum]._pAFrames = 24;
597 			plr[pnum]._pAFNum = 16;
598 		}
599 	} else if (pc == PC_MONK) {
600 		plr[pnum]._pNWidth = 112;
601 		plr[pnum]._pWWidth = 112;
602 		plr[pnum]._pAWidth = 130;
603 		plr[pnum]._pHWidth = 98;
604 		plr[pnum]._pSWidth = 114;
605 		plr[pnum]._pDWidth = 160;
606 		plr[pnum]._pBWidth = 98;
607 
608 		switch (gn) {
609 		case ANIM_ID_UNARMED:
610 		case ANIM_ID_UNARMED_SHIELD:
611 			plr[pnum]._pAFrames = 12;
612 			plr[pnum]._pAFNum = 7;
613 			break;
614 		case ANIM_ID_BOW:
615 			plr[pnum]._pAFrames = 20;
616 			plr[pnum]._pAFNum = 14;
617 			break;
618 		case ANIM_ID_AXE:
619 			plr[pnum]._pAFrames = 23;
620 			plr[pnum]._pAFNum = 14;
621 			break;
622 		case ANIM_ID_STAFF:
623 			plr[pnum]._pAFrames = 13;
624 			plr[pnum]._pAFNum = 8;
625 			break;
626 		}
627 	} else if (pc == PC_BARD) {
628 		if (gn == ANIM_ID_AXE) {
629 			plr[pnum]._pAFrames = 22;
630 			plr[pnum]._pAFNum = 13;
631 		} else if (gn == ANIM_ID_BOW) {
632 			plr[pnum]._pAFrames = 12;
633 			plr[pnum]._pAFNum = 11;
634 		} else if (gn == ANIM_ID_STAFF) {
635 			plr[pnum]._pAFrames = 16;
636 			plr[pnum]._pAFNum = 11;
637 		} else if (gn == ANIM_ID_SWORD_SHIELD || gn == ANIM_ID_SWORD) {
638 			plr[pnum]._pAFrames = 10;
639 		}
640 	} else if (pc == PC_BARBARIAN) {
641 		if (gn == ANIM_ID_AXE) {
642 			plr[pnum]._pAFrames = 20;
643 			plr[pnum]._pAFNum = 8;
644 		} else if (gn == ANIM_ID_BOW) {
645 			if (leveltype != DTYPE_TOWN) {
646 				plr[pnum]._pNFrames = 8;
647 			}
648 			plr[pnum]._pAWidth = 96;
649 			plr[pnum]._pAFNum = 11;
650 		} else if (gn == ANIM_ID_STAFF) {
651 			plr[pnum]._pAFrames = 16;
652 			plr[pnum]._pAFNum = 11;
653 		} else if (gn == ANIM_ID_MACE || gn == ANIM_ID_MACE_SHIELD) {
654 			plr[pnum]._pAFNum = 8;
655 		}
656 	}
657 }
658 
659 /**
660  * @param c plr_classes value
661  */
CreatePlayer(int pnum,plr_class c)662 void CreatePlayer(int pnum, plr_class c)
663 {
664 	char val;
665 	int hp, mana;
666 	int i;
667 
668 	memset(&plr[pnum], 0, sizeof(PlayerStruct));
669 	SetRndSeed(SDL_GetTicks());
670 
671 	if ((DWORD)pnum >= MAX_PLRS) {
672 		app_fatal("CreatePlayer: illegal player %d", pnum);
673 	}
674 	plr[pnum]._pClass = c;
675 
676 	val = StrengthTbl[c];
677 	plr[pnum]._pStrength = val;
678 	plr[pnum]._pBaseStr = val;
679 
680 	val = MagicTbl[c];
681 	plr[pnum]._pMagic = val;
682 	plr[pnum]._pBaseMag = val;
683 
684 	val = DexterityTbl[c];
685 	plr[pnum]._pDexterity = val;
686 	plr[pnum]._pBaseDex = val;
687 
688 	val = VitalityTbl[c];
689 	plr[pnum]._pVitality = val;
690 	plr[pnum]._pBaseVit = val;
691 
692 	plr[pnum]._pStatPts = 0;
693 	plr[pnum].pTownWarps = 0;
694 	plr[pnum].pDungMsgs = 0;
695 	plr[pnum].pDungMsgs2 = 0;
696 	plr[pnum].pLvlLoad = 0;
697 	plr[pnum].pDiabloKillLevel = 0;
698 	plr[pnum].pDifficulty = DIFF_NORMAL;
699 
700 	plr[pnum]._pLevel = 1;
701 
702 	if (plr[pnum]._pClass == PC_MONK) {
703 		plr[pnum]._pDamageMod = (plr[pnum]._pStrength + plr[pnum]._pDexterity) * plr[pnum]._pLevel / 150;
704 	} else if (plr[pnum]._pClass == PC_ROGUE || plr[pnum]._pClass == PC_BARD) {
705 		plr[pnum]._pDamageMod = plr[pnum]._pLevel * (plr[pnum]._pStrength + plr[pnum]._pDexterity) / 200;
706 	} else {
707 		plr[pnum]._pDamageMod = plr[pnum]._pStrength * plr[pnum]._pLevel / 100;
708 	}
709 
710 	plr[pnum]._pBaseToBlk = ToBlkTbl[c];
711 
712 	plr[pnum]._pHitPoints = (plr[pnum]._pVitality + 10) << 6;
713 	if (plr[pnum]._pClass == PC_WARRIOR || plr[pnum]._pClass == PC_BARBARIAN) {
714 		plr[pnum]._pHitPoints <<= 1;
715 	} else if (plr[pnum]._pClass == PC_ROGUE || plr[pnum]._pClass == PC_MONK || plr[pnum]._pClass == PC_BARD) {
716 		plr[pnum]._pHitPoints += plr[pnum]._pHitPoints >> 1;
717 	}
718 
719 	plr[pnum]._pMaxHP = plr[pnum]._pHitPoints;
720 	plr[pnum]._pHPBase = plr[pnum]._pHitPoints;
721 	plr[pnum]._pMaxHPBase = plr[pnum]._pHitPoints;
722 
723 	plr[pnum]._pMana = plr[pnum]._pMagic << 6;
724 	if (plr[pnum]._pClass == PC_SORCERER) {
725 		plr[pnum]._pMana <<= 1;
726 	} else if (plr[pnum]._pClass == PC_BARD) {
727 		plr[pnum]._pMana += plr[pnum]._pMana * 3 / 4;
728 	} else if (plr[pnum]._pClass == PC_ROGUE || plr[pnum]._pClass == PC_MONK) {
729 		plr[pnum]._pMana += plr[pnum]._pMana >> 1;
730 	}
731 
732 	plr[pnum]._pMaxMana = plr[pnum]._pMana;
733 	plr[pnum]._pManaBase = plr[pnum]._pMana;
734 	plr[pnum]._pMaxManaBase = plr[pnum]._pMana;
735 
736 	plr[pnum]._pMaxLvl = plr[pnum]._pLevel;
737 	plr[pnum]._pExperience = 0;
738 	plr[pnum]._pMaxExp = plr[pnum]._pExperience;
739 	plr[pnum]._pNextExper = ExpLvlsTbl[1];
740 	plr[pnum]._pArmorClass = 0;
741 	if (plr[pnum]._pClass == PC_BARBARIAN) {
742 		plr[pnum]._pMagResist = 1;
743 		plr[pnum]._pFireResist = 1;
744 		plr[pnum]._pLghtResist = 1;
745 	} else {
746 		plr[pnum]._pMagResist = 0;
747 		plr[pnum]._pFireResist = 0;
748 		plr[pnum]._pLghtResist = 0;
749 	}
750 	plr[pnum]._pLightRad = 10;
751 	plr[pnum]._pInfraFlag = FALSE;
752 
753 	plr[pnum]._pRSplType = RSPLTYPE_SKILL;
754 	if (c == PC_WARRIOR) {
755 		plr[pnum]._pAblSpells = GetSpellBitmask(SPL_REPAIR);
756 		plr[pnum]._pRSpell = SPL_REPAIR;
757 	} else if (c == PC_ROGUE) {
758 		plr[pnum]._pAblSpells = GetSpellBitmask(SPL_DISARM);
759 		plr[pnum]._pRSpell = SPL_DISARM;
760 	} else if (c == PC_SORCERER) {
761 		plr[pnum]._pAblSpells = GetSpellBitmask(SPL_RECHARGE);
762 		plr[pnum]._pRSpell = SPL_RECHARGE;
763 	} else if (c == PC_MONK) {
764 		plr[pnum]._pAblSpells = GetSpellBitmask(SPL_SEARCH);
765 		plr[pnum]._pRSpell = SPL_SEARCH;
766 	} else if (c == PC_BARD) {
767 		plr[pnum]._pAblSpells = GetSpellBitmask(SPL_IDENTIFY);
768 		plr[pnum]._pRSpell = SPL_IDENTIFY;
769 	} else if (c == PC_BARBARIAN) {
770 		plr[pnum]._pAblSpells = GetSpellBitmask(SPL_BLODBOIL);
771 		plr[pnum]._pRSpell = SPL_BLODBOIL;
772 	}
773 
774 	if (c == PC_SORCERER) {
775 		plr[pnum]._pMemSpells = GetSpellBitmask(SPL_FIREBOLT);
776 		plr[pnum]._pRSplType = RSPLTYPE_SPELL;
777 		plr[pnum]._pRSpell = SPL_FIREBOLT;
778 	} else {
779 		plr[pnum]._pMemSpells = 0;
780 	}
781 
782 	for (i = 0; i < sizeof(plr[pnum]._pSplLvl) / sizeof(plr[pnum]._pSplLvl[0]); i++) {
783 		plr[pnum]._pSplLvl[i] = 0;
784 	}
785 
786 	plr[pnum]._pSpellFlags = 0;
787 
788 	if (plr[pnum]._pClass == PC_SORCERER) {
789 		plr[pnum]._pSplLvl[SPL_FIREBOLT] = 2;
790 	}
791 
792 	// interestingly, only the first three hotkeys are reset
793 	// TODO: BUGFIX: clear all 4 hotkeys instead of 3 (demo leftover)
794 	for (i = 0; i < 3; i++) {
795 		plr[pnum]._pSplHotKey[i] = SPL_INVALID;
796 	}
797 
798 	if (c == PC_WARRIOR) {
799 		plr[pnum]._pgfxnum = ANIM_ID_SWORD_SHIELD;
800 	} else if (c == PC_ROGUE) {
801 		plr[pnum]._pgfxnum = ANIM_ID_BOW;
802 	} else if (c == PC_SORCERER) {
803 		plr[pnum]._pgfxnum = ANIM_ID_STAFF;
804 	} else if (c == PC_MONK) {
805 		plr[pnum]._pgfxnum = ANIM_ID_STAFF;
806 	} else if (c == PC_BARD) {
807 		plr[pnum]._pgfxnum = ANIM_ID_SWORD_SHIELD;
808 	} else if (c == PC_BARBARIAN) {
809 		plr[pnum]._pgfxnum = ANIM_ID_SWORD_SHIELD;
810 	}
811 
812 	for (i = 0; i < NUMLEVELS; i++) {
813 		plr[pnum]._pLvlVisited[i] = FALSE;
814 	}
815 
816 	for (i = 0; i < 10; i++) {
817 		plr[pnum]._pSLvlVisited[i] = FALSE;
818 	}
819 
820 	plr[pnum]._pLvlChanging = FALSE;
821 	plr[pnum].pTownWarps = 0;
822 	plr[pnum].pLvlLoad = 0;
823 	plr[pnum].pBattleNet = FALSE;
824 	plr[pnum].pManaShield = FALSE;
825 	plr[pnum].pDamAcFlags = 0;
826 	plr[pnum].wReflections = 0;
827 
828 	InitDungMsgs(pnum);
829 	CreatePlrItems(pnum);
830 	SetRndSeed(0);
831 }
832 
CalcStatDiff(int pnum)833 int CalcStatDiff(int pnum)
834 {
835 	plr_class c = plr[pnum]._pClass;
836 	return MaxStats[c][ATTRIB_STR]
837 	    - plr[pnum]._pBaseStr
838 	    + MaxStats[c][ATTRIB_MAG]
839 	    - plr[pnum]._pBaseMag
840 	    + MaxStats[c][ATTRIB_DEX]
841 	    - plr[pnum]._pBaseDex
842 	    + MaxStats[c][ATTRIB_VIT]
843 	    - plr[pnum]._pBaseVit;
844 }
845 
NextPlrLevel(int pnum)846 void NextPlrLevel(int pnum)
847 {
848 	int hp, mana;
849 
850 	if ((DWORD)pnum >= MAX_PLRS) {
851 		app_fatal("NextPlrLevel: illegal player %d", pnum);
852 	}
853 
854 	plr[pnum]._pLevel++;
855 	plr[pnum]._pMaxLvl++;
856 
857 	CalcPlrInv(pnum, TRUE);
858 
859 	if (CalcStatDiff(pnum) < 5) {
860 		plr[pnum]._pStatPts = CalcStatDiff(pnum);
861 	} else {
862 		plr[pnum]._pStatPts += 5;
863 	}
864 
865 	plr[pnum]._pNextExper = ExpLvlsTbl[plr[pnum]._pLevel];
866 
867 	hp = plr[pnum]._pClass == PC_SORCERER ? 64 : 128;
868 	if (!gbIsMultiplayer) {
869 		hp++;
870 	}
871 	plr[pnum]._pMaxHP += hp;
872 	plr[pnum]._pHitPoints = plr[pnum]._pMaxHP;
873 	plr[pnum]._pMaxHPBase += hp;
874 	plr[pnum]._pHPBase = plr[pnum]._pMaxHPBase;
875 
876 	if (pnum == myplr) {
877 		drawhpflag = TRUE;
878 	}
879 
880 	if (plr[pnum]._pClass == PC_WARRIOR)
881 		mana = 64;
882 	else if (plr[pnum]._pClass == PC_BARBARIAN)
883 		mana = 0;
884 	else
885 		mana = 128;
886 
887 	if (!gbIsMultiplayer) {
888 		mana++;
889 	}
890 	plr[pnum]._pMaxMana += mana;
891 	plr[pnum]._pMaxManaBase += mana;
892 
893 	if (!(plr[pnum]._pIFlags & ISPL_NOMANA)) {
894 		plr[pnum]._pMana = plr[pnum]._pMaxMana;
895 		plr[pnum]._pManaBase = plr[pnum]._pMaxManaBase;
896 	}
897 
898 	if (pnum == myplr) {
899 		drawmanaflag = TRUE;
900 	}
901 
902 	if (sgbControllerActive)
903 		FocusOnCharInfo();
904 
905 	CalcPlrInv(pnum, TRUE);
906 }
907 
AddPlrExperience(int pnum,int lvl,int exp)908 void AddPlrExperience(int pnum, int lvl, int exp)
909 {
910 	int powerLvlCap, expCap, newLvl, i;
911 
912 	if (pnum != myplr) {
913 		return;
914 	}
915 
916 	if ((DWORD)myplr >= MAX_PLRS) {
917 		app_fatal("AddPlrExperience: illegal player %d", myplr);
918 	}
919 
920 	if (plr[myplr]._pHitPoints <= 0) {
921 		return;
922 	}
923 
924 	// Adjust xp based on difference in level between player and monster
925 	exp *= 1 + ((double)lvl - plr[pnum]._pLevel) / 10;
926 	if (exp < 0) {
927 		exp = 0;
928 	}
929 
930 	// Prevent power leveling
931 	if (gbIsMultiplayer) {
932 		powerLvlCap = plr[pnum]._pLevel < 0 ? 0 : plr[pnum]._pLevel;
933 		if (powerLvlCap >= 50) {
934 			powerLvlCap = 50;
935 		}
936 		// cap to 1/20 of current levels xp
937 		if (exp >= ExpLvlsTbl[powerLvlCap] / 20) {
938 			exp = ExpLvlsTbl[powerLvlCap] / 20;
939 		}
940 		// cap to 200 * current level
941 		expCap = 200 * powerLvlCap;
942 		if (exp >= expCap) {
943 			exp = expCap;
944 		}
945 	}
946 
947 	plr[pnum]._pExperience += exp;
948 	if ((DWORD)plr[pnum]._pExperience > MAXEXP) {
949 		plr[pnum]._pExperience = MAXEXP;
950 	}
951 
952 	if (sgOptions.Gameplay.bExperienceBar) {
953 		force_redraw = 255;
954 	}
955 
956 	if (plr[pnum]._pExperience >= ExpLvlsTbl[49]) {
957 		plr[pnum]._pLevel = 50;
958 		return;
959 	}
960 
961 	// Increase player level if applicable
962 	newLvl = 0;
963 	while (plr[pnum]._pExperience >= ExpLvlsTbl[newLvl]) {
964 		newLvl++;
965 	}
966 	if (newLvl != plr[pnum]._pLevel) {
967 		for (i = newLvl - plr[pnum]._pLevel; i > 0; i--) {
968 			NextPlrLevel(pnum);
969 		}
970 	}
971 
972 	NetSendCmdParam1(FALSE, CMD_PLRLEVEL, plr[myplr]._pLevel);
973 }
974 
AddPlrMonstExper(int lvl,int exp,char pmask)975 void AddPlrMonstExper(int lvl, int exp, char pmask)
976 {
977 	int totplrs, i, e;
978 
979 	totplrs = 0;
980 	for (i = 0; i < MAX_PLRS; i++) {
981 		if ((1 << i) & pmask) {
982 			totplrs++;
983 		}
984 	}
985 
986 	if (totplrs) {
987 		e = exp / totplrs;
988 		if (pmask & (1 << myplr))
989 			AddPlrExperience(myplr, lvl, e);
990 	}
991 }
992 
InitPlayer(int pnum,BOOL FirstTime)993 void InitPlayer(int pnum, BOOL FirstTime)
994 {
995 	DWORD i;
996 
997 	if ((DWORD)pnum >= MAX_PLRS) {
998 		app_fatal("InitPlayer: illegal player %d", pnum);
999 	}
1000 
1001 	if (FirstTime) {
1002 		plr[pnum]._pRSplType = RSPLTYPE_INVALID;
1003 		plr[pnum]._pRSpell = SPL_INVALID;
1004 		if (pnum == myplr)
1005 			LoadHotkeys();
1006 		plr[pnum]._pSBkSpell = SPL_INVALID;
1007 		plr[pnum]._pSpell = plr[pnum]._pRSpell;
1008 		plr[pnum]._pSplType = plr[pnum]._pRSplType;
1009 		if ((plr[pnum]._pgfxnum & 0xF) == ANIM_ID_BOW) {
1010 			plr[pnum]._pwtype = WT_RANGED;
1011 		} else {
1012 			plr[pnum]._pwtype = WT_MELEE;
1013 		}
1014 		plr[pnum].pManaShield = FALSE;
1015 	}
1016 
1017 	if (plr[pnum].plrlevel == currlevel || leveldebug) {
1018 
1019 		SetPlrAnims(pnum);
1020 
1021 		plr[pnum]._pxoff = 0;
1022 		plr[pnum]._pyoff = 0;
1023 		plr[pnum]._pxvel = 0;
1024 		plr[pnum]._pyvel = 0;
1025 
1026 		ClearPlrPVars(pnum);
1027 
1028 		if (plr[pnum]._pHitPoints >> 6 > 0) {
1029 			plr[pnum]._pmode = PM_STAND;
1030 			NewPlrAnim(pnum, plr[pnum]._pNAnim[DIR_S], plr[pnum]._pNFrames, 3, plr[pnum]._pNWidth);
1031 			plr[pnum]._pAnimFrame = random_(2, plr[pnum]._pNFrames - 1) + 1;
1032 			plr[pnum]._pAnimCnt = random_(2, 3);
1033 		} else {
1034 			plr[pnum]._pmode = PM_DEATH;
1035 			NewPlrAnim(pnum, plr[pnum]._pDAnim[DIR_S], plr[pnum]._pDFrames, 1, plr[pnum]._pDWidth);
1036 			plr[pnum]._pAnimFrame = plr[pnum]._pAnimLen - 1;
1037 			plr[pnum]._pVar8 = 2 * plr[pnum]._pAnimLen;
1038 		}
1039 
1040 		plr[pnum]._pdir = DIR_S;
1041 
1042 		if (pnum == myplr) {
1043 			if (!FirstTime || currlevel != 0) {
1044 				plr[pnum]._px = ViewX;
1045 				plr[pnum]._py = ViewY;
1046 			}
1047 			plr[pnum]._ptargx = plr[pnum]._px;
1048 			plr[pnum]._ptargy = plr[pnum]._py;
1049 		} else {
1050 			plr[pnum]._ptargx = plr[pnum]._px;
1051 			plr[pnum]._ptargy = plr[pnum]._py;
1052 			for (i = 0; i < 8 && !PosOkPlayer(pnum, plrxoff2[i] + plr[pnum]._px, plryoff2[i] + plr[pnum]._py); i++)
1053 				;
1054 			plr[pnum]._px += plrxoff2[i];
1055 			plr[pnum]._py += plryoff2[i];
1056 		}
1057 
1058 		plr[pnum]._pfutx = plr[pnum]._px;
1059 		plr[pnum]._pfuty = plr[pnum]._py;
1060 		plr[pnum].walkpath[0] = WALK_NONE;
1061 		plr[pnum].destAction = ACTION_NONE;
1062 
1063 		if (pnum == myplr) {
1064 			plr[pnum]._plid = AddLight(plr[pnum]._px, plr[pnum]._py, plr[pnum]._pLightRad);
1065 			ChangeLightXY(plr[myplr]._plid, plr[myplr]._px, plr[myplr]._py); // fix for a bug where old light is still visible at the entrance after reentering level
1066 		} else {
1067 			plr[pnum]._plid = NO_LIGHT;
1068 		}
1069 		plr[pnum]._pvid = AddVision(plr[pnum]._px, plr[pnum]._py, plr[pnum]._pLightRad, pnum == myplr);
1070 	}
1071 
1072 	if (plr[pnum]._pClass == PC_WARRIOR) {
1073 		plr[pnum]._pAblSpells = GetSpellBitmask(SPL_REPAIR);
1074 	} else if (plr[pnum]._pClass == PC_ROGUE) {
1075 		plr[pnum]._pAblSpells = GetSpellBitmask(SPL_DISARM);
1076 	} else if (plr[pnum]._pClass == PC_SORCERER) {
1077 		plr[pnum]._pAblSpells = GetSpellBitmask(SPL_RECHARGE);
1078 	} else if (plr[pnum]._pClass == PC_MONK) {
1079 		plr[pnum]._pAblSpells = GetSpellBitmask(SPL_SEARCH);
1080 	} else if (plr[pnum]._pClass == PC_BARD) {
1081 		plr[pnum]._pAblSpells = GetSpellBitmask(SPL_IDENTIFY);
1082 	} else if (plr[pnum]._pClass == PC_BARBARIAN) {
1083 		plr[pnum]._pAblSpells = GetSpellBitmask(SPL_BLODBOIL);
1084 	}
1085 
1086 #ifdef _DEBUG
1087 	if (debug_mode_dollar_sign && FirstTime) {
1088 		plr[pnum]._pMemSpells |= 1 << (SPL_TELEPORT - 1);
1089 		if (!plr[myplr]._pSplLvl[SPL_TELEPORT]) {
1090 			plr[myplr]._pSplLvl[SPL_TELEPORT] = 1;
1091 		}
1092 	}
1093 	if (debug_mode_key_inverted_v && FirstTime) {
1094 		plr[pnum]._pMemSpells = SPL_INVALID;
1095 	}
1096 #endif
1097 
1098 	plr[pnum]._pNextExper = ExpLvlsTbl[plr[pnum]._pLevel];
1099 	plr[pnum]._pInvincible = FALSE;
1100 
1101 	if (pnum == myplr) {
1102 		deathdelay = 0;
1103 		deathflag = FALSE;
1104 		ScrollInfo._sxoff = 0;
1105 		ScrollInfo._syoff = 0;
1106 		ScrollInfo._sdir = SDIR_NONE;
1107 	}
1108 }
1109 
InitMultiView()1110 void InitMultiView()
1111 {
1112 	if ((DWORD)myplr >= MAX_PLRS) {
1113 		app_fatal("InitPlayer: illegal player %d", myplr);
1114 	}
1115 
1116 	ViewX = plr[myplr]._px;
1117 	ViewY = plr[myplr]._py;
1118 }
1119 
SolidLoc(int x,int y)1120 BOOL SolidLoc(int x, int y)
1121 {
1122 	if (x < 0 || y < 0 || x >= MAXDUNX || y >= MAXDUNY) {
1123 		return FALSE;
1124 	}
1125 
1126 	return nSolidTable[dPiece[x][y]];
1127 }
1128 
PlrDirOK(int pnum,int dir)1129 BOOL PlrDirOK(int pnum, int dir)
1130 {
1131 	int px, py;
1132 	BOOL isOk;
1133 
1134 	if ((DWORD)pnum >= MAX_PLRS) {
1135 		app_fatal("PlrDirOK: illegal player %d", pnum);
1136 	}
1137 
1138 	px = plr[pnum]._px + offset_x[dir];
1139 	py = plr[pnum]._py + offset_y[dir];
1140 
1141 	if (px < 0 || !dPiece[px][py] || !PosOkPlayer(pnum, px, py)) {
1142 		return FALSE;
1143 	}
1144 
1145 	isOk = TRUE;
1146 	if (dir == DIR_E) {
1147 		isOk = !SolidLoc(px, py + 1) && !(dFlags[px][py + 1] & BFLAG_PLAYERLR);
1148 	}
1149 
1150 	if (isOk && dir == DIR_W) {
1151 		isOk = !SolidLoc(px + 1, py) && !(dFlags[px + 1][py] & BFLAG_PLAYERLR);
1152 	}
1153 
1154 	return isOk;
1155 }
1156 
PlrClrTrans(int x,int y)1157 void PlrClrTrans(int x, int y)
1158 {
1159 	int i, j;
1160 
1161 	for (i = y - 1; i <= y + 1; i++) {
1162 		for (j = x - 1; j <= x + 1; j++) {
1163 			TransList[dTransVal[j][i]] = FALSE;
1164 		}
1165 	}
1166 }
1167 
PlrDoTrans(int x,int y)1168 void PlrDoTrans(int x, int y)
1169 {
1170 	int i, j;
1171 
1172 	if (leveltype != DTYPE_CATHEDRAL && leveltype != DTYPE_CATACOMBS) {
1173 		TransList[1] = TRUE;
1174 	} else {
1175 		for (i = y - 1; i <= y + 1; i++) {
1176 			for (j = x - 1; j <= x + 1; j++) {
1177 				if (!nSolidTable[dPiece[j][i]] && dTransVal[j][i]) {
1178 					TransList[dTransVal[j][i]] = TRUE;
1179 				}
1180 			}
1181 		}
1182 	}
1183 }
1184 
SetPlayerOld(int pnum)1185 void SetPlayerOld(int pnum)
1186 {
1187 	if ((DWORD)pnum >= MAX_PLRS) {
1188 		app_fatal("SetPlayerOld: illegal player %d", pnum);
1189 	}
1190 
1191 	plr[pnum]._poldx = plr[pnum]._px;
1192 	plr[pnum]._poldy = plr[pnum]._py;
1193 }
1194 
FixPlayerLocation(int pnum,direction bDir)1195 void FixPlayerLocation(int pnum, direction bDir)
1196 {
1197 	if ((DWORD)pnum >= MAX_PLRS) {
1198 		app_fatal("FixPlayerLocation: illegal player %d", pnum);
1199 	}
1200 
1201 	plr[pnum]._pfutx = plr[pnum]._px;
1202 	plr[pnum]._pfuty = plr[pnum]._py;
1203 	plr[pnum]._ptargx = plr[pnum]._px;
1204 	plr[pnum]._ptargy = plr[pnum]._py;
1205 	plr[pnum]._pxoff = 0;
1206 	plr[pnum]._pyoff = 0;
1207 	plr[pnum]._pdir = bDir;
1208 	if (pnum == myplr) {
1209 		ScrollInfo._sxoff = 0;
1210 		ScrollInfo._syoff = 0;
1211 		ScrollInfo._sdir = SDIR_NONE;
1212 		ViewX = plr[pnum]._px;
1213 		ViewY = plr[pnum]._py;
1214 	}
1215 	ChangeLightXY(plr[pnum]._plid, plr[pnum]._px, plr[pnum]._py);
1216 	ChangeVisionXY(plr[pnum]._pvid, plr[pnum]._px, plr[pnum]._py);
1217 }
1218 
StartStand(int pnum,direction dir)1219 void StartStand(int pnum, direction dir)
1220 {
1221 	if ((DWORD)pnum >= MAX_PLRS) {
1222 		app_fatal("StartStand: illegal player %d", pnum);
1223 	}
1224 
1225 	if (!plr[pnum]._pInvincible || plr[pnum]._pHitPoints != 0 || pnum != myplr) {
1226 		if (!(plr[pnum]._pGFXLoad & PFILE_STAND)) {
1227 			LoadPlrGFX(pnum, PFILE_STAND);
1228 		}
1229 
1230 		NewPlrAnim(pnum, plr[pnum]._pNAnim[dir], plr[pnum]._pNFrames, 3, plr[pnum]._pNWidth);
1231 		plr[pnum]._pmode = PM_STAND;
1232 		FixPlayerLocation(pnum, dir);
1233 		FixPlrWalkTags(pnum);
1234 		dPlayer[plr[pnum]._px][plr[pnum]._py] = pnum + 1;
1235 		SetPlayerOld(pnum);
1236 	} else {
1237 		SyncPlrKill(pnum, -1);
1238 	}
1239 }
1240 
StartWalkStand(int pnum)1241 void StartWalkStand(int pnum)
1242 {
1243 	if ((DWORD)pnum >= MAX_PLRS) {
1244 		app_fatal("StartWalkStand: illegal player %d", pnum);
1245 	}
1246 
1247 	plr[pnum]._pmode = PM_STAND;
1248 	plr[pnum]._pfutx = plr[pnum]._px;
1249 	plr[pnum]._pfuty = plr[pnum]._py;
1250 	plr[pnum]._pxoff = 0;
1251 	plr[pnum]._pyoff = 0;
1252 
1253 	if (pnum == myplr) {
1254 		ScrollInfo._sxoff = 0;
1255 		ScrollInfo._syoff = 0;
1256 		ScrollInfo._sdir = SDIR_NONE;
1257 		ViewX = plr[pnum]._px;
1258 		ViewY = plr[pnum]._py;
1259 	}
1260 }
1261 
PM_ChangeLightOff(int pnum)1262 void PM_ChangeLightOff(int pnum)
1263 {
1264 	int x, y;
1265 	int xmul, ymul;
1266 	int lx, ly;
1267 	int offx, offy;
1268 	const LightListStruct *l;
1269 
1270 	if ((DWORD)pnum >= MAX_PLRS) {
1271 		app_fatal("PM_ChangeLightOff: illegal player %d", pnum);
1272 	}
1273 
1274 	if (plr[pnum]._plid == NO_LIGHT)
1275 		return;
1276 
1277 	l = &LightList[plr[pnum]._plid];
1278 	x = 2 * plr[pnum]._pyoff + plr[pnum]._pxoff;
1279 	y = 2 * plr[pnum]._pyoff - plr[pnum]._pxoff;
1280 	if (x < 0) {
1281 		xmul = -1;
1282 		x = -x;
1283 	} else {
1284 		xmul = 1;
1285 	}
1286 	if (y < 0) {
1287 		ymul = -1;
1288 		y = -y;
1289 	} else {
1290 		ymul = 1;
1291 	}
1292 
1293 	x = (x >> 3) * xmul;
1294 	y = (y >> 3) * ymul;
1295 	lx = x + (l->_lx << 3);
1296 	ly = y + (l->_ly << 3);
1297 	offx = l->_xoff + (l->_lx << 3);
1298 	offy = l->_yoff + (l->_ly << 3);
1299 
1300 	if (abs(lx - offx) < 3 && abs(ly - offy) < 3)
1301 		return;
1302 
1303 	ChangeLightOff(plr[pnum]._plid, x, y);
1304 }
1305 
PM_ChangeOffset(int pnum)1306 void PM_ChangeOffset(int pnum)
1307 {
1308 	int px, py;
1309 
1310 	if ((DWORD)pnum >= MAX_PLRS) {
1311 		app_fatal("PM_ChangeOffset: illegal player %d", pnum);
1312 	}
1313 
1314 	plr[pnum]._pVar8++;
1315 	px = plr[pnum]._pVar6 / 256;
1316 	py = plr[pnum]._pVar7 / 256;
1317 
1318 	plr[pnum]._pVar6 += plr[pnum]._pxvel;
1319 	plr[pnum]._pVar7 += plr[pnum]._pyvel;
1320 
1321 	if (currlevel == 0 && gbRunInTown) {
1322 		plr[pnum]._pVar6 += plr[pnum]._pxvel;
1323 		plr[pnum]._pVar7 += plr[pnum]._pyvel;
1324 	}
1325 
1326 	plr[pnum]._pxoff = plr[pnum]._pVar6 >> 8;
1327 	plr[pnum]._pyoff = plr[pnum]._pVar7 >> 8;
1328 
1329 	px -= plr[pnum]._pVar6 >> 8;
1330 	py -= plr[pnum]._pVar7 >> 8;
1331 
1332 	if (pnum == myplr && ScrollInfo._sdir) {
1333 		ScrollInfo._sxoff += px;
1334 		ScrollInfo._syoff += py;
1335 	}
1336 
1337 	PM_ChangeLightOff(pnum);
1338 }
1339 
1340 /**
1341  * @brief Start moving a player to a new tile
1342  */
StartWalk(int pnum,int xvel,int yvel,int xoff,int yoff,int xadd,int yadd,int mapx,int mapy,direction EndDir,int sdir,int variant)1343 void StartWalk(int pnum, int xvel, int yvel, int xoff, int yoff, int xadd, int yadd, int mapx, int mapy, direction EndDir, int sdir, int variant)
1344 {
1345 	if ((DWORD)pnum >= MAX_PLRS) {
1346 		app_fatal("StartWalk: illegal player %d", pnum);
1347 	}
1348 
1349 	if (plr[pnum]._pInvincible && plr[pnum]._pHitPoints == 0 && pnum == myplr) {
1350 		SyncPlrKill(pnum, -1);
1351 		return;
1352 	}
1353 
1354 	SetPlayerOld(pnum);
1355 
1356 	if (!PlrDirOK(pnum, EndDir)) {
1357 		return;
1358 	}
1359 
1360 	//The player's tile position after finishing this movement action
1361 	int px = xadd + plr[pnum]._px;
1362 	int py = yadd + plr[pnum]._py;
1363 	plr[pnum]._pfutx = px;
1364 	plr[pnum]._pfuty = py;
1365 
1366 	//If this is the local player then update the camera offset position
1367 	if (pnum == myplr) {
1368 		ScrollInfo._sdx = plr[pnum]._px - ViewX;
1369 		ScrollInfo._sdy = plr[pnum]._py - ViewY;
1370 	}
1371 
1372 	switch (variant) {
1373 	case PM_WALK:
1374 		dPlayer[px][py] = -(pnum + 1);
1375 		plr[pnum]._pmode = PM_WALK;
1376 		plr[pnum]._pxvel = xvel;
1377 		plr[pnum]._pyvel = yvel;
1378 		plr[pnum]._pxoff = 0;
1379 		plr[pnum]._pyoff = 0;
1380 		plr[pnum]._pVar1 = xadd;
1381 		plr[pnum]._pVar2 = yadd;
1382 		plr[pnum]._pVar3 = EndDir;
1383 
1384 		plr[pnum]._pVar6 = 0;
1385 		plr[pnum]._pVar7 = 0;
1386 		break;
1387 	case PM_WALK2:
1388 		dPlayer[plr[pnum]._px][plr[pnum]._py] = -(pnum + 1);
1389 		plr[pnum]._pVar1 = plr[pnum]._px;
1390 		plr[pnum]._pVar2 = plr[pnum]._py;
1391 		plr[pnum]._px = px; // Move player to the next tile to maintain correct render order
1392 		plr[pnum]._py = py;
1393 		dPlayer[plr[pnum]._px][plr[pnum]._py] = pnum + 1;
1394 		plr[pnum]._pxoff = xoff; // Offset player sprite to align with their previous tile position
1395 		plr[pnum]._pyoff = yoff;
1396 
1397 		ChangeLightXY(plr[pnum]._plid, plr[pnum]._px, plr[pnum]._py);
1398 		PM_ChangeLightOff(pnum);
1399 
1400 		plr[pnum]._pmode = PM_WALK2;
1401 		plr[pnum]._pxvel = xvel;
1402 		plr[pnum]._pyvel = yvel;
1403 		plr[pnum]._pVar6 = xoff * 256;
1404 		plr[pnum]._pVar7 = yoff * 256;
1405 		plr[pnum]._pVar3 = EndDir;
1406 		break;
1407 	case PM_WALK3:
1408 		int x = mapx + plr[pnum]._px;
1409 		int y = mapy + plr[pnum]._py;
1410 
1411 		dPlayer[plr[pnum]._px][plr[pnum]._py] = -(pnum + 1);
1412 		dPlayer[px][py] = -(pnum + 1);
1413 		plr[pnum]._pVar4 = x;
1414 		plr[pnum]._pVar5 = y;
1415 		dFlags[x][y] |= BFLAG_PLAYERLR;
1416 		plr[pnum]._pxoff = xoff; // Offset player sprite to align with their previous tile position
1417 		plr[pnum]._pyoff = yoff;
1418 
1419 		if (leveltype != DTYPE_TOWN) {
1420 			ChangeLightXY(plr[pnum]._plid, x, y);
1421 			PM_ChangeLightOff(pnum);
1422 		}
1423 
1424 		plr[pnum]._pmode = PM_WALK3;
1425 		plr[pnum]._pxvel = xvel;
1426 		plr[pnum]._pyvel = yvel;
1427 		plr[pnum]._pVar1 = px;
1428 		plr[pnum]._pVar2 = py;
1429 		plr[pnum]._pVar6 = xoff * 256;
1430 		plr[pnum]._pVar7 = yoff * 256;
1431 		plr[pnum]._pVar3 = EndDir;
1432 		break;
1433 	}
1434 
1435 	//Load walk animation in case it's not loaded yet
1436 	if (!(plr[pnum]._pGFXLoad & PFILE_WALK)) {
1437 		LoadPlrGFX(pnum, PFILE_WALK);
1438 	}
1439 
1440 	//Start walk animation
1441 	NewPlrAnim(pnum, plr[pnum]._pWAnim[EndDir], plr[pnum]._pWFrames, 0, plr[pnum]._pWWidth);
1442 
1443 	plr[pnum]._pdir = EndDir;
1444 	plr[pnum]._pVar8 = 0;
1445 
1446 	if (pnum != myplr) {
1447 		return;
1448 	}
1449 
1450 	if (zoomflag) {
1451 		if (abs(ScrollInfo._sdx) >= 3 || abs(ScrollInfo._sdy) >= 3) {
1452 			ScrollInfo._sdir = SDIR_NONE;
1453 		} else {
1454 			ScrollInfo._sdir = sdir;
1455 		}
1456 	} else if (abs(ScrollInfo._sdx) >= 2 || abs(ScrollInfo._sdy) >= 2) {
1457 		ScrollInfo._sdir = SDIR_NONE;
1458 	} else {
1459 		ScrollInfo._sdir = sdir;
1460 	}
1461 }
1462 
StartAttack(int pnum,direction d)1463 void StartAttack(int pnum, direction d)
1464 {
1465 	if ((DWORD)pnum >= MAX_PLRS) {
1466 		app_fatal("StartAttack: illegal player %d", pnum);
1467 	}
1468 
1469 	if (plr[pnum]._pInvincible && plr[pnum]._pHitPoints == 0 && pnum == myplr) {
1470 		SyncPlrKill(pnum, -1);
1471 		return;
1472 	}
1473 
1474 	if (!(plr[pnum]._pGFXLoad & PFILE_ATTACK)) {
1475 		LoadPlrGFX(pnum, PFILE_ATTACK);
1476 	}
1477 
1478 	NewPlrAnim(pnum, plr[pnum]._pAAnim[d], plr[pnum]._pAFrames, 0, plr[pnum]._pAWidth);
1479 	plr[pnum]._pmode = PM_ATTACK;
1480 	FixPlayerLocation(pnum, d);
1481 	SetPlayerOld(pnum);
1482 }
1483 
StartRangeAttack(int pnum,direction d,int cx,int cy)1484 void StartRangeAttack(int pnum, direction d, int cx, int cy)
1485 {
1486 	if ((DWORD)pnum >= MAX_PLRS) {
1487 		app_fatal("StartRangeAttack: illegal player %d", pnum);
1488 	}
1489 
1490 	if (plr[pnum]._pInvincible && plr[pnum]._pHitPoints == 0 && pnum == myplr) {
1491 		SyncPlrKill(pnum, -1);
1492 		return;
1493 	}
1494 
1495 	if (!(plr[pnum]._pGFXLoad & PFILE_ATTACK)) {
1496 		LoadPlrGFX(pnum, PFILE_ATTACK);
1497 	}
1498 	NewPlrAnim(pnum, plr[pnum]._pAAnim[d], plr[pnum]._pAFrames, 0, plr[pnum]._pAWidth);
1499 
1500 	plr[pnum]._pmode = PM_RATTACK;
1501 	FixPlayerLocation(pnum, d);
1502 	SetPlayerOld(pnum);
1503 	plr[pnum]._pVar1 = cx;
1504 	plr[pnum]._pVar2 = cy;
1505 }
1506 
StartPlrBlock(int pnum,direction dir)1507 void StartPlrBlock(int pnum, direction dir)
1508 {
1509 	if ((DWORD)pnum >= MAX_PLRS) {
1510 		app_fatal("StartPlrBlock: illegal player %d", pnum);
1511 	}
1512 
1513 	if (plr[pnum]._pInvincible && plr[pnum]._pHitPoints == 0 && pnum == myplr) {
1514 		SyncPlrKill(pnum, -1);
1515 		return;
1516 	}
1517 
1518 	PlaySfxLoc(IS_ISWORD, plr[pnum]._px, plr[pnum]._py);
1519 
1520 	if (!(plr[pnum]._pGFXLoad & PFILE_BLOCK)) {
1521 		LoadPlrGFX(pnum, PFILE_BLOCK);
1522 	}
1523 	NewPlrAnim(pnum, plr[pnum]._pBAnim[dir], plr[pnum]._pBFrames, 2, plr[pnum]._pBWidth);
1524 
1525 	plr[pnum]._pmode = PM_BLOCK;
1526 	FixPlayerLocation(pnum, dir);
1527 	SetPlayerOld(pnum);
1528 }
1529 
StartSpell(int pnum,direction d,int cx,int cy)1530 void StartSpell(int pnum, direction d, int cx, int cy)
1531 {
1532 	if ((DWORD)pnum >= MAX_PLRS)
1533 		app_fatal("StartSpell: illegal player %d", pnum);
1534 
1535 	if (plr[pnum]._pInvincible && plr[pnum]._pHitPoints == 0 && pnum == myplr) {
1536 		SyncPlrKill(pnum, -1);
1537 		return;
1538 	}
1539 
1540 	if (leveltype != DTYPE_TOWN) {
1541 		switch (spelldata[plr[pnum]._pSpell].sType) {
1542 		case STYPE_FIRE:
1543 			if (!(plr[pnum]._pGFXLoad & PFILE_FIRE)) {
1544 				LoadPlrGFX(pnum, PFILE_FIRE);
1545 			}
1546 			NewPlrAnim(pnum, plr[pnum]._pFAnim[d], plr[pnum]._pSFrames, 0, plr[pnum]._pSWidth);
1547 			break;
1548 		case STYPE_LIGHTNING:
1549 			if (!(plr[pnum]._pGFXLoad & PFILE_LIGHTNING)) {
1550 				LoadPlrGFX(pnum, PFILE_LIGHTNING);
1551 			}
1552 			NewPlrAnim(pnum, plr[pnum]._pLAnim[d], plr[pnum]._pSFrames, 0, plr[pnum]._pSWidth);
1553 			break;
1554 		case STYPE_MAGIC:
1555 			if (!(plr[pnum]._pGFXLoad & PFILE_MAGIC)) {
1556 				LoadPlrGFX(pnum, PFILE_MAGIC);
1557 			}
1558 			NewPlrAnim(pnum, plr[pnum]._pTAnim[d], plr[pnum]._pSFrames, 0, plr[pnum]._pSWidth);
1559 			break;
1560 		}
1561 	}
1562 
1563 	PlaySfxLoc(spelldata[plr[pnum]._pSpell].sSFX, plr[pnum]._px, plr[pnum]._py);
1564 
1565 	plr[pnum]._pmode = PM_SPELL;
1566 
1567 	FixPlayerLocation(pnum, d);
1568 	SetPlayerOld(pnum);
1569 
1570 	plr[pnum]._pVar1 = cx;
1571 	plr[pnum]._pVar2 = cy;
1572 	plr[pnum]._pVar4 = GetSpellLevel(pnum, plr[pnum]._pSpell);
1573 	plr[pnum]._pVar8 = 1;
1574 }
1575 
FixPlrWalkTags(int pnum)1576 void FixPlrWalkTags(int pnum)
1577 {
1578 	int pp, pn;
1579 	int dx, dy, y, x;
1580 
1581 	if ((DWORD)pnum >= MAX_PLRS) {
1582 		app_fatal("FixPlrWalkTags: illegal player %d", pnum);
1583 	}
1584 
1585 	pp = pnum + 1;
1586 	pn = -(pnum + 1);
1587 	dx = plr[pnum]._poldx;
1588 	dy = plr[pnum]._poldy;
1589 	for (y = dy - 1; y <= dy + 1; y++) {
1590 		for (x = dx - 1; x <= dx + 1; x++) {
1591 			if (x >= 0 && x < MAXDUNX && y >= 0 && y < MAXDUNY && (dPlayer[x][y] == pp || dPlayer[x][y] == pn)) {
1592 				dPlayer[x][y] = 0;
1593 			}
1594 		}
1595 	}
1596 
1597 	if (dx >= 0 && dx < MAXDUNX - 1 && dy >= 0 && dy < MAXDUNY - 1) {
1598 		dFlags[dx + 1][dy] &= ~BFLAG_PLAYERLR;
1599 		dFlags[dx][dy + 1] &= ~BFLAG_PLAYERLR;
1600 	}
1601 }
1602 
RemovePlrFromMap(int pnum)1603 void RemovePlrFromMap(int pnum)
1604 {
1605 	int x, y;
1606 	int pp, pn;
1607 
1608 	pp = pnum + 1;
1609 	pn = -(pnum + 1);
1610 
1611 	for (y = 1; y < MAXDUNY; y++)
1612 		for (x = 1; x < MAXDUNX; x++)
1613 			if (dPlayer[x][y - 1] == pn || dPlayer[x - 1][y] == pn)
1614 				if (dFlags[x][y] & BFLAG_PLAYERLR)
1615 					dFlags[x][y] &= ~BFLAG_PLAYERLR;
1616 
1617 	for (y = 0; y < MAXDUNY; y++)
1618 		for (x = 0; x < MAXDUNX; x++)
1619 			if (dPlayer[x][y] == pp || dPlayer[x][y] == pn)
1620 				dPlayer[x][y] = 0;
1621 }
1622 
StartPlrHit(int pnum,int dam,BOOL forcehit)1623 void StartPlrHit(int pnum, int dam, BOOL forcehit)
1624 {
1625 	if ((DWORD)pnum >= MAX_PLRS) {
1626 		app_fatal("StartPlrHit: illegal player %d", pnum);
1627 	}
1628 
1629 	if (plr[pnum]._pInvincible && plr[pnum]._pHitPoints == 0 && pnum == myplr) {
1630 		SyncPlrKill(pnum, -1);
1631 		return;
1632 	}
1633 
1634 	if (plr[pnum]._pClass == PC_WARRIOR) {
1635 		PlaySfxLoc(PS_WARR69, plr[pnum]._px, plr[pnum]._py);
1636 	} else if (plr[pnum]._pClass == PC_ROGUE) {
1637 		PlaySfxLoc(PS_ROGUE69, plr[pnum]._px, plr[pnum]._py);
1638 	} else if (plr[pnum]._pClass == PC_SORCERER) {
1639 		PlaySfxLoc(PS_MAGE69, plr[pnum]._px, plr[pnum]._py);
1640 	} else if (plr[pnum]._pClass == PC_MONK) {
1641 		PlaySfxLoc(PS_MONK69, plr[pnum]._px, plr[pnum]._py);
1642 	} else if (plr[pnum]._pClass == PC_BARD) {
1643 		PlaySfxLoc(PS_ROGUE69, plr[pnum]._px, plr[pnum]._py);
1644 	} else if (plr[pnum]._pClass == PC_BARBARIAN) {
1645 		PlaySfxLoc(PS_WARR69, plr[pnum]._px, plr[pnum]._py);
1646 	}
1647 
1648 	drawhpflag = TRUE;
1649 	if (plr[pnum]._pClass == PC_BARBARIAN) {
1650 		if (dam >> 6 < plr[pnum]._pLevel + plr[pnum]._pLevel / 4 && !forcehit) {
1651 			return;
1652 		}
1653 	} else if (dam >> 6 < plr[pnum]._pLevel && !forcehit) {
1654 		return;
1655 	}
1656 
1657 	direction pd = plr[pnum]._pdir;
1658 
1659 	if (!(plr[pnum]._pGFXLoad & PFILE_HIT)) {
1660 		LoadPlrGFX(pnum, PFILE_HIT);
1661 	}
1662 	NewPlrAnim(pnum, plr[pnum]._pHAnim[pd], plr[pnum]._pHFrames, 0, plr[pnum]._pHWidth);
1663 
1664 	plr[pnum]._pmode = PM_GOTHIT;
1665 	FixPlayerLocation(pnum, pd);
1666 	FixPlrWalkTags(pnum);
1667 	dPlayer[plr[pnum]._px][plr[pnum]._py] = pnum + 1;
1668 	SetPlayerOld(pnum);
1669 }
1670 
RespawnDeadItem(ItemStruct * itm,int x,int y)1671 void RespawnDeadItem(ItemStruct *itm, int x, int y)
1672 {
1673 	if (numitems >= MAXITEMS)
1674 		return;
1675 
1676 	int ii = AllocateItem();
1677 
1678 	dItem[x][y] = ii + 1;
1679 
1680 	item[ii] = *itm;
1681 	item[ii]._ix = x;
1682 	item[ii]._iy = y;
1683 	RespawnItem(ii, TRUE);
1684 
1685 	itm->_itype = ITYPE_NONE;
1686 }
1687 
PlrDeadItem(int pnum,ItemStruct * itm,int xx,int yy)1688 static void PlrDeadItem(int pnum, ItemStruct *itm, int xx, int yy)
1689 {
1690 	int x, y;
1691 	int i, j, k;
1692 
1693 	if (itm->isEmpty())
1694 		return;
1695 
1696 	if ((DWORD)pnum >= MAX_PLRS) {
1697 		app_fatal("PlrDeadItem: illegal player %d", pnum);
1698 	}
1699 
1700 	x = xx + plr[pnum]._px;
1701 	y = yy + plr[pnum]._py;
1702 	if ((xx || yy) && ItemSpaceOk(x, y)) {
1703 		RespawnDeadItem(itm, x, y);
1704 		plr[pnum].HoldItem = *itm;
1705 		NetSendCmdPItem(FALSE, CMD_RESPAWNITEM, x, y);
1706 		return;
1707 	}
1708 
1709 	for (k = 1; k < 50; k++) {
1710 		for (j = -k; j <= k; j++) {
1711 			y = j + plr[pnum]._py;
1712 			for (i = -k; i <= k; i++) {
1713 				x = i + plr[pnum]._px;
1714 				if (ItemSpaceOk(x, y)) {
1715 					RespawnDeadItem(itm, x, y);
1716 					plr[pnum].HoldItem = *itm;
1717 					NetSendCmdPItem(FALSE, CMD_RESPAWNITEM, x, y);
1718 					return;
1719 				}
1720 			}
1721 		}
1722 	}
1723 }
1724 
1725 #if defined(__clang__) || defined(__GNUC__)
1726 __attribute__((no_sanitize("shift-base")))
1727 #endif
1728 void
StartPlayerKill(int pnum,int earflag)1729 StartPlayerKill(int pnum, int earflag)
1730 {
1731 	BOOL diablolevel;
1732 	int i, pdd;
1733 	PlayerStruct *p;
1734 	ItemStruct ear;
1735 	ItemStruct *pi;
1736 
1737 	p = &plr[pnum];
1738 	if (p->_pHitPoints <= 0 && p->_pmode == PM_DEATH) {
1739 		return;
1740 	}
1741 
1742 	if (myplr == pnum) {
1743 		NetSendCmdParam1(TRUE, CMD_PLRDEAD, earflag);
1744 	}
1745 
1746 	diablolevel = gbIsMultiplayer && plr[pnum].plrlevel == 16;
1747 
1748 	if ((DWORD)pnum >= MAX_PLRS) {
1749 		app_fatal("StartPlayerKill: illegal player %d", pnum);
1750 	}
1751 
1752 	if (plr[pnum]._pClass == PC_WARRIOR) {
1753 		PlaySfxLoc(PS_DEAD, p->_px, p->_py); // BUGFIX: should use `PS_WARR71` like other classes
1754 	} else if (plr[pnum]._pClass == PC_ROGUE) {
1755 		PlaySfxLoc(PS_ROGUE71, p->_px, p->_py);
1756 	} else if (plr[pnum]._pClass == PC_SORCERER) {
1757 		PlaySfxLoc(PS_MAGE71, p->_px, p->_py);
1758 	} else if (plr[pnum]._pClass == PC_MONK) {
1759 		PlaySfxLoc(PS_MONK71, p->_px, p->_py);
1760 	} else if (plr[pnum]._pClass == PC_BARD) {
1761 		PlaySfxLoc(PS_ROGUE71, p->_px, p->_py);
1762 	} else if (plr[pnum]._pClass == PC_BARBARIAN) {
1763 		PlaySfxLoc(PS_WARR71, p->_px, p->_py);
1764 	}
1765 
1766 	if (p->_pgfxnum) {
1767 		p->_pgfxnum = 0;
1768 		p->_pGFXLoad = 0;
1769 		SetPlrAnims(pnum);
1770 	}
1771 
1772 	if (!(p->_pGFXLoad & PFILE_DEATH)) {
1773 		LoadPlrGFX(pnum, PFILE_DEATH);
1774 	}
1775 
1776 	NewPlrAnim(pnum, p->_pDAnim[p->_pdir], p->_pDFrames, 1, p->_pDWidth);
1777 
1778 	p->_pBlockFlag = FALSE;
1779 	p->_pmode = PM_DEATH;
1780 	p->_pInvincible = TRUE;
1781 	SetPlayerHitPoints(pnum, 0);
1782 	p->_pVar8 = 1;
1783 
1784 	if (pnum != myplr && !earflag && !diablolevel) {
1785 		for (i = 0; i < NUM_INVLOC; i++) {
1786 			p->InvBody[i]._itype = ITYPE_NONE;
1787 		}
1788 		CalcPlrInv(pnum, FALSE);
1789 	}
1790 
1791 	if (plr[pnum].plrlevel == currlevel) {
1792 		FixPlayerLocation(pnum, p->_pdir);
1793 		RemovePlrFromMap(pnum);
1794 		dFlags[p->_px][p->_py] |= BFLAG_DEAD_PLAYER;
1795 		SetPlayerOld(pnum);
1796 
1797 		if (pnum == myplr) {
1798 			drawhpflag = TRUE;
1799 			deathdelay = 30;
1800 
1801 			if (pcurs >= CURSOR_FIRSTITEM) {
1802 				PlrDeadItem(pnum, &p->HoldItem, 0, 0);
1803 				SetCursor_(CURSOR_HAND);
1804 			}
1805 
1806 			if (!diablolevel) {
1807 				DropHalfPlayersGold(pnum);
1808 				if (earflag != -1) {
1809 					if (earflag != 0) {
1810 						SetPlrHandItem(&ear, IDI_EAR);
1811 						sprintf(ear._iName, "Ear of %s", plr[pnum]._pName);
1812 						if (plr[pnum]._pClass == PC_SORCERER) {
1813 							ear._iCurs = ICURS_EAR_SORCERER;
1814 						} else if (plr[pnum]._pClass == PC_WARRIOR) {
1815 							ear._iCurs = ICURS_EAR_WARRIOR;
1816 						} else if (plr[pnum]._pClass == PC_ROGUE) {
1817 							ear._iCurs = ICURS_EAR_ROGUE;
1818 						} else if (plr[pnum]._pClass == PC_MONK || plr[pnum]._pClass == PC_BARD || plr[pnum]._pClass == PC_BARBARIAN) {
1819 							ear._iCurs = ICURS_EAR_ROGUE;
1820 						}
1821 
1822 						ear._iCreateInfo = plr[pnum]._pName[0] << 8 | plr[pnum]._pName[1];
1823 						ear._iSeed = plr[pnum]._pName[2] << 24 | plr[pnum]._pName[3] << 16 | plr[pnum]._pName[4] << 8 | plr[pnum]._pName[5];
1824 						ear._ivalue = plr[pnum]._pLevel;
1825 
1826 						if (FindGetItem(IDI_EAR, ear._iCreateInfo, ear._iSeed) == -1) {
1827 							PlrDeadItem(pnum, &ear, 0, 0);
1828 						}
1829 					} else {
1830 						pi = &p->InvBody[0];
1831 						i = NUM_INVLOC;
1832 						while (i--) {
1833 							pdd = (i + p->_pdir) & 7;
1834 							PlrDeadItem(pnum, pi, offset_x[pdd], offset_y[pdd]);
1835 							pi++;
1836 						}
1837 
1838 						CalcPlrInv(pnum, FALSE);
1839 					}
1840 				}
1841 			}
1842 		}
1843 	}
1844 	SetPlayerHitPoints(pnum, 0);
1845 }
1846 
DropHalfPlayersGold(int pnum)1847 void DropHalfPlayersGold(int pnum)
1848 {
1849 	int i, hGold;
1850 
1851 	if ((DWORD)pnum >= MAX_PLRS) {
1852 		app_fatal("DropHalfPlayersGold: illegal player %d", pnum);
1853 	}
1854 
1855 	hGold = plr[pnum]._pGold >> 1;
1856 	for (i = 0; i < MAXBELTITEMS && hGold > 0; i++) {
1857 		if (plr[pnum].SpdList[i]._itype == ITYPE_GOLD && plr[pnum].SpdList[i]._ivalue != MaxGold) {
1858 			if (hGold < plr[pnum].SpdList[i]._ivalue) {
1859 				plr[pnum].SpdList[i]._ivalue -= hGold;
1860 				SetSpdbarGoldCurs(pnum, i);
1861 				SetPlrHandItem(&plr[pnum].HoldItem, IDI_GOLD);
1862 				GetGoldSeed(pnum, &plr[pnum].HoldItem);
1863 				SetPlrHandGoldCurs(&plr[pnum].HoldItem);
1864 				plr[pnum].HoldItem._ivalue = hGold;
1865 				PlrDeadItem(pnum, &plr[pnum].HoldItem, 0, 0);
1866 				hGold = 0;
1867 			} else {
1868 				hGold -= plr[pnum].SpdList[i]._ivalue;
1869 				RemoveSpdBarItem(pnum, i);
1870 				SetPlrHandItem(&plr[pnum].HoldItem, IDI_GOLD);
1871 				GetGoldSeed(pnum, &plr[pnum].HoldItem);
1872 				SetPlrHandGoldCurs(&plr[pnum].HoldItem);
1873 				plr[pnum].HoldItem._ivalue = plr[pnum].SpdList[i]._ivalue;
1874 				PlrDeadItem(pnum, &plr[pnum].HoldItem, 0, 0);
1875 				i = -1;
1876 			}
1877 		}
1878 	}
1879 	if (hGold > 0) {
1880 		for (i = 0; i < MAXBELTITEMS && hGold > 0; i++) {
1881 			if (plr[pnum].SpdList[i]._itype == ITYPE_GOLD) {
1882 				if (hGold < plr[pnum].SpdList[i]._ivalue) {
1883 					plr[pnum].SpdList[i]._ivalue -= hGold;
1884 					SetSpdbarGoldCurs(pnum, i);
1885 					SetPlrHandItem(&plr[pnum].HoldItem, IDI_GOLD);
1886 					GetGoldSeed(pnum, &plr[pnum].HoldItem);
1887 					SetPlrHandGoldCurs(&plr[pnum].HoldItem);
1888 					plr[pnum].HoldItem._ivalue = hGold;
1889 					PlrDeadItem(pnum, &plr[pnum].HoldItem, 0, 0);
1890 					hGold = 0;
1891 				} else {
1892 					hGold -= plr[pnum].SpdList[i]._ivalue;
1893 					RemoveSpdBarItem(pnum, i);
1894 					SetPlrHandItem(&plr[pnum].HoldItem, IDI_GOLD);
1895 					GetGoldSeed(pnum, &plr[pnum].HoldItem);
1896 					SetPlrHandGoldCurs(&plr[pnum].HoldItem);
1897 					plr[pnum].HoldItem._ivalue = plr[pnum].SpdList[i]._ivalue;
1898 					PlrDeadItem(pnum, &plr[pnum].HoldItem, 0, 0);
1899 					i = -1;
1900 				}
1901 			}
1902 		}
1903 	}
1904 	force_redraw = 255;
1905 	if (hGold > 0) {
1906 		for (i = 0; i < plr[pnum]._pNumInv && hGold > 0; i++) {
1907 			if (plr[pnum].InvList[i]._itype == ITYPE_GOLD && plr[pnum].InvList[i]._ivalue != MaxGold) {
1908 				if (hGold < plr[pnum].InvList[i]._ivalue) {
1909 					plr[pnum].InvList[i]._ivalue -= hGold;
1910 					SetGoldCurs(pnum, i);
1911 					SetPlrHandItem(&plr[pnum].HoldItem, IDI_GOLD);
1912 					GetGoldSeed(pnum, &plr[pnum].HoldItem);
1913 					SetPlrHandGoldCurs(&plr[pnum].HoldItem);
1914 					plr[pnum].HoldItem._ivalue = hGold;
1915 					PlrDeadItem(pnum, &plr[pnum].HoldItem, 0, 0);
1916 					hGold = 0;
1917 				} else {
1918 					hGold -= plr[pnum].InvList[i]._ivalue;
1919 					RemoveInvItem(pnum, i);
1920 					SetPlrHandItem(&plr[pnum].HoldItem, IDI_GOLD);
1921 					GetGoldSeed(pnum, &plr[pnum].HoldItem);
1922 					SetPlrHandGoldCurs(&plr[pnum].HoldItem);
1923 					plr[pnum].HoldItem._ivalue = plr[pnum].InvList[i]._ivalue;
1924 					PlrDeadItem(pnum, &plr[pnum].HoldItem, 0, 0);
1925 					i = -1;
1926 				}
1927 			}
1928 		}
1929 	}
1930 	if (hGold > 0) {
1931 		for (i = 0; i < plr[pnum]._pNumInv && hGold > 0; i++) {
1932 			if (plr[pnum].InvList[i]._itype == ITYPE_GOLD) {
1933 				if (hGold < plr[pnum].InvList[i]._ivalue) {
1934 					plr[pnum].InvList[i]._ivalue -= hGold;
1935 					SetGoldCurs(pnum, i);
1936 					SetPlrHandItem(&plr[pnum].HoldItem, IDI_GOLD);
1937 					GetGoldSeed(pnum, &plr[pnum].HoldItem);
1938 					SetPlrHandGoldCurs(&plr[pnum].HoldItem);
1939 					plr[pnum].HoldItem._ivalue = hGold;
1940 					PlrDeadItem(pnum, &plr[pnum].HoldItem, 0, 0);
1941 					hGold = 0;
1942 				} else {
1943 					hGold -= plr[pnum].InvList[i]._ivalue;
1944 					RemoveInvItem(pnum, i);
1945 					SetPlrHandItem(&plr[pnum].HoldItem, IDI_GOLD);
1946 					GetGoldSeed(pnum, &plr[pnum].HoldItem);
1947 					SetPlrHandGoldCurs(&plr[pnum].HoldItem);
1948 					plr[pnum].HoldItem._ivalue = plr[pnum].InvList[i]._ivalue;
1949 					PlrDeadItem(pnum, &plr[pnum].HoldItem, 0, 0);
1950 					i = -1;
1951 				}
1952 			}
1953 		}
1954 	}
1955 	plr[pnum]._pGold = CalculateGold(pnum);
1956 }
1957 
StripTopGold(int pnum)1958 void StripTopGold(int pnum)
1959 {
1960 	ItemStruct tmpItem;
1961 	int i, val;
1962 
1963 	if ((DWORD)pnum >= MAX_PLRS) {
1964 		app_fatal("StripTopGold: illegal player %d", pnum);
1965 	}
1966 	tmpItem = plr[pnum].HoldItem;
1967 
1968 	for (i = 0; i < plr[pnum]._pNumInv; i++) {
1969 		if (plr[pnum].InvList[i]._itype == ITYPE_GOLD) {
1970 			if (plr[pnum].InvList[i]._ivalue > MaxGold) {
1971 				val = plr[pnum].InvList[i]._ivalue - MaxGold;
1972 				plr[pnum].InvList[i]._ivalue = MaxGold;
1973 				SetGoldCurs(pnum, i);
1974 				SetPlrHandItem(&plr[pnum].HoldItem, 0);
1975 				GetGoldSeed(pnum, &plr[pnum].HoldItem);
1976 				plr[pnum].HoldItem._ivalue = val;
1977 				SetPlrHandGoldCurs(&plr[pnum].HoldItem);
1978 				if (!GoldAutoPlace(pnum))
1979 					PlrDeadItem(pnum, &plr[pnum].HoldItem, 0, 0);
1980 			}
1981 		}
1982 	}
1983 	plr[pnum]._pGold = CalculateGold(pnum);
1984 	plr[pnum].HoldItem = tmpItem;
1985 }
1986 
SyncPlrKill(int pnum,int earflag)1987 void SyncPlrKill(int pnum, int earflag)
1988 {
1989 	int ma, i;
1990 
1991 	if (plr[pnum]._pHitPoints <= 0 && currlevel == 0) {
1992 		SetPlayerHitPoints(pnum, 64);
1993 		return;
1994 	}
1995 
1996 	for (i = 0; i < nummissiles; i++) {
1997 		ma = missileactive[i];
1998 		if (missile[ma]._mitype == MIS_MANASHIELD && missile[ma]._misource == pnum && missile[ma]._miDelFlag == FALSE) {
1999 			if (earflag != -1) {
2000 				missile[ma]._miVar8 = earflag;
2001 			}
2002 
2003 			return;
2004 		}
2005 	}
2006 
2007 	SetPlayerHitPoints(pnum, 0);
2008 	StartPlayerKill(pnum, earflag);
2009 }
2010 
RemovePlrMissiles(int pnum)2011 void RemovePlrMissiles(int pnum)
2012 {
2013 	int i, am;
2014 	int mx, my;
2015 
2016 	if (currlevel != 0 && pnum == myplr && (monster[myplr]._mx != 1 || monster[myplr]._my != 0)) {
2017 		M_StartKill(myplr, myplr);
2018 		AddDead(monster[myplr]._mx, monster[myplr]._my, (monster[myplr].MType)->mdeadval, monster[myplr]._mdir);
2019 		mx = monster[myplr]._mx;
2020 		my = monster[myplr]._my;
2021 		dMonster[mx][my] = 0;
2022 		monster[myplr]._mDelFlag = TRUE;
2023 		DeleteMonsterList();
2024 	}
2025 
2026 	for (i = 0; i < nummissiles; i++) {
2027 		am = missileactive[i];
2028 		if (missile[am]._mitype == MIS_STONE && missile[am]._misource == pnum) {
2029 			monster[missile[am]._miVar2]._mmode = (MON_MODE)missile[am]._miVar1;
2030 		}
2031 		if (missile[am]._mitype == MIS_MANASHIELD && missile[am]._misource == pnum) {
2032 			ClearMissileSpot(am);
2033 			DeleteMissile(am, i);
2034 		}
2035 		if (missile[am]._mitype == MIS_ETHEREALIZE && missile[am]._misource == pnum) {
2036 			ClearMissileSpot(am);
2037 			DeleteMissile(am, i);
2038 		}
2039 	}
2040 }
2041 
InitLevelChange(int pnum)2042 void InitLevelChange(int pnum)
2043 {
2044 	RemovePlrMissiles(pnum);
2045 	if (pnum == myplr && qtextflag) {
2046 		qtextflag = FALSE;
2047 		stream_stop();
2048 	}
2049 
2050 	RemovePlrFromMap(pnum);
2051 	SetPlayerOld(pnum);
2052 	if (pnum == myplr) {
2053 		dPlayer[plr[myplr]._px][plr[myplr]._py] = myplr + 1;
2054 	} else {
2055 		plr[pnum]._pLvlVisited[plr[pnum].plrlevel] = TRUE;
2056 	}
2057 
2058 	ClrPlrPath(pnum);
2059 	plr[pnum].destAction = ACTION_NONE;
2060 	plr[pnum]._pLvlChanging = TRUE;
2061 
2062 	if (pnum == myplr) {
2063 		plr[pnum].pLvlLoad = 10;
2064 	}
2065 }
2066 
2067 #if defined(__clang__) || defined(__GNUC__)
2068 __attribute__((no_sanitize("shift-base")))
2069 #endif
2070 void
StartNewLvl(int pnum,int fom,int lvl)2071 StartNewLvl(int pnum, int fom, int lvl)
2072 {
2073 	InitLevelChange(pnum);
2074 
2075 	if ((DWORD)pnum >= MAX_PLRS) {
2076 		app_fatal("StartNewLvl: illegal player %d", pnum);
2077 	}
2078 
2079 	switch (fom) {
2080 	case WM_DIABNEXTLVL:
2081 	case WM_DIABPREVLVL:
2082 		plr[pnum].plrlevel = lvl;
2083 		break;
2084 	case WM_DIABRTNLVL:
2085 	case WM_DIABTOWNWARP:
2086 		plr[pnum].plrlevel = lvl;
2087 		break;
2088 	case WM_DIABSETLVL:
2089 		setlvlnum = lvl;
2090 		break;
2091 	case WM_DIABTWARPUP:
2092 		plr[myplr].pTownWarps |= 1 << (leveltype - 2);
2093 		plr[pnum].plrlevel = lvl;
2094 		break;
2095 	case WM_DIABRETOWN:
2096 		break;
2097 	default:
2098 		app_fatal("StartNewLvl");
2099 	}
2100 
2101 	if (pnum == myplr) {
2102 		plr[pnum]._pmode = PM_NEWLVL;
2103 		plr[pnum]._pInvincible = TRUE;
2104 		PostMessage(fom, 0, 0);
2105 		if (gbIsMultiplayer) {
2106 			NetSendCmdParam2(TRUE, CMD_NEWLVL, fom, lvl);
2107 		}
2108 	}
2109 }
RestartTownLvl(int pnum)2110 void RestartTownLvl(int pnum)
2111 {
2112 	InitLevelChange(pnum);
2113 	if ((DWORD)pnum >= MAX_PLRS) {
2114 		app_fatal("RestartTownLvl: illegal player %d", pnum);
2115 	}
2116 
2117 	plr[pnum].plrlevel = 0;
2118 	plr[pnum]._pInvincible = FALSE;
2119 
2120 	SetPlayerHitPoints(pnum, 64);
2121 
2122 	plr[pnum]._pMana = 0;
2123 	plr[pnum]._pManaBase = plr[pnum]._pMana - (plr[pnum]._pMaxMana - plr[pnum]._pMaxManaBase);
2124 
2125 	CalcPlrInv(pnum, FALSE);
2126 
2127 	if (pnum == myplr) {
2128 		plr[pnum]._pmode = PM_NEWLVL;
2129 		plr[pnum]._pInvincible = TRUE;
2130 		PostMessage(WM_DIABRETOWN, 0, 0);
2131 	}
2132 }
2133 
StartWarpLvl(int pnum,int pidx)2134 void StartWarpLvl(int pnum, int pidx)
2135 {
2136 	InitLevelChange(pnum);
2137 
2138 	if (gbIsMultiplayer) {
2139 		if (plr[pnum].plrlevel != 0) {
2140 			plr[pnum].plrlevel = 0;
2141 		} else {
2142 			plr[pnum].plrlevel = portal[pidx].level;
2143 		}
2144 	}
2145 
2146 	if (pnum == myplr) {
2147 		SetCurrentPortal(pidx);
2148 		plr[pnum]._pmode = PM_NEWLVL;
2149 		plr[pnum]._pInvincible = TRUE;
2150 		PostMessage(WM_DIABWARPLVL, 0, 0);
2151 	}
2152 }
2153 
PM_DoStand(int pnum)2154 BOOL PM_DoStand(int pnum)
2155 {
2156 	return FALSE;
2157 }
2158 
2159 /**
2160  * @brief Continue movement towards new tile
2161  */
PM_DoWalk(int pnum,int variant)2162 bool PM_DoWalk(int pnum, int variant)
2163 {
2164 	if ((DWORD)pnum >= MAX_PLRS) {
2165 		app_fatal("PM_DoWalk: illegal player %d", pnum);
2166 	}
2167 
2168 	//Play walking sound effect on certain animation frames
2169 	if (sgOptions.Audio.bWalkingSound) {
2170 		if (plr[pnum]._pAnimFrame == 3
2171 		    || (plr[pnum]._pWFrames == 8 && plr[pnum]._pAnimFrame == 7)
2172 		    || (plr[pnum]._pWFrames != 8 && plr[pnum]._pAnimFrame == 4)) {
2173 			PlaySfxLoc(PS_WALK1, plr[pnum]._px, plr[pnum]._py);
2174 		}
2175 	}
2176 
2177 	//"Jog" in town which works by doubling movement speed and skipping every other animation frame
2178 	if (currlevel == 0 && gbRunInTown) {
2179 		if (plr[pnum]._pAnimFrame % 2 == 0) {
2180 			plr[pnum]._pAnimFrame++;
2181 			plr[pnum]._pVar8++;
2182 		}
2183 		if (plr[pnum]._pAnimFrame >= plr[pnum]._pWFrames) {
2184 			plr[pnum]._pAnimFrame = 0;
2185 		}
2186 	}
2187 
2188 	//Acquire length of walk animation length (this is 8 for every class, so the AnimLenFromClass array is redundant right now)
2189 	int anim_len = 8;
2190 	if (currlevel != 0) {
2191 		anim_len = AnimLenFromClass[plr[pnum]._pClass];
2192 	}
2193 
2194 	//Check if we reached new tile
2195 	if (plr[pnum]._pVar8 >= anim_len) {
2196 
2197 		//Update the player's tile position
2198 		switch (variant) {
2199 		case PM_WALK:
2200 			dPlayer[plr[pnum]._px][plr[pnum]._py] = 0;
2201 			plr[pnum]._px += plr[pnum]._pVar1;
2202 			plr[pnum]._py += plr[pnum]._pVar2;
2203 			dPlayer[plr[pnum]._px][plr[pnum]._py] = pnum + 1;
2204 			break;
2205 		case PM_WALK2:
2206 			dPlayer[plr[pnum]._pVar1][plr[pnum]._pVar2] = 0;
2207 			break;
2208 		case PM_WALK3:
2209 			dPlayer[plr[pnum]._px][plr[pnum]._py] = 0;
2210 			dFlags[plr[pnum]._pVar4][plr[pnum]._pVar5] &= ~BFLAG_PLAYERLR;
2211 			plr[pnum]._px = plr[pnum]._pVar1;
2212 			plr[pnum]._py = plr[pnum]._pVar2;
2213 			dPlayer[plr[pnum]._px][plr[pnum]._py] = pnum + 1;
2214 			break;
2215 		}
2216 
2217 		//Update the coordinates for lighting and vision entries for the player
2218 		if (leveltype != DTYPE_TOWN) {
2219 			ChangeLightXY(plr[pnum]._plid, plr[pnum]._px, plr[pnum]._py);
2220 			ChangeVisionXY(plr[pnum]._pvid, plr[pnum]._px, plr[pnum]._py);
2221 		}
2222 
2223 		//Update the "camera" tile position
2224 		if (pnum == myplr && ScrollInfo._sdir) {
2225 			ViewX = plr[pnum]._px - ScrollInfo._sdx;
2226 			ViewY = plr[pnum]._py - ScrollInfo._sdy;
2227 		}
2228 
2229 		if (plr[pnum].walkpath[0] != WALK_NONE) {
2230 			StartWalkStand(pnum);
2231 		} else {
2232 			StartStand(pnum, (direction)plr[pnum]._pVar3);
2233 		}
2234 
2235 		ClearPlrPVars(pnum);
2236 
2237 		//Reset the "sub-tile" position of the player's light entry to 0
2238 		if (leveltype != DTYPE_TOWN) {
2239 			ChangeLightOff(plr[pnum]._plid, 0, 0);
2240 		}
2241 
2242 		AutoGoldPickup(pnum);
2243 		return true;
2244 	} else { //We didn't reach new tile so update player's "sub-tile" position
2245 		PM_ChangeOffset(pnum);
2246 		return false;
2247 	}
2248 }
2249 
WeaponDurDecay(int pnum,int ii)2250 static bool WeaponDurDecay(int pnum, int ii)
2251 {
2252 	if (!plr[pnum].InvBody[ii].isEmpty() && plr[pnum].InvBody[ii]._iClass == ICLASS_WEAPON && plr[pnum].InvBody[ii]._iDamAcFlags & 2) {
2253 		plr[pnum].InvBody[ii]._iPLDam -= 5;
2254 		if (plr[pnum].InvBody[ii]._iPLDam <= -100) {
2255 			NetSendCmdDelItem(TRUE, ii);
2256 			plr[pnum].InvBody[ii]._itype = ITYPE_NONE;
2257 			CalcPlrInv(pnum, TRUE);
2258 			return true;
2259 		}
2260 		CalcPlrInv(pnum, TRUE);
2261 	}
2262 	return false;
2263 }
2264 
WeaponDur(int pnum,int durrnd)2265 BOOL WeaponDur(int pnum, int durrnd)
2266 {
2267 	if (pnum != myplr) {
2268 		return FALSE;
2269 	}
2270 
2271 	if (WeaponDurDecay(pnum, INVLOC_HAND_LEFT))
2272 		return TRUE;
2273 	if (WeaponDurDecay(pnum, INVLOC_HAND_RIGHT))
2274 		return TRUE;
2275 
2276 	if (random_(3, durrnd) != 0) {
2277 		return FALSE;
2278 	}
2279 
2280 	if ((DWORD)pnum >= MAX_PLRS) {
2281 		app_fatal("WeaponDur: illegal player %d", pnum);
2282 	}
2283 
2284 	if (!plr[pnum].InvBody[INVLOC_HAND_LEFT].isEmpty() && plr[pnum].InvBody[INVLOC_HAND_LEFT]._iClass == ICLASS_WEAPON) {
2285 		if (plr[pnum].InvBody[INVLOC_HAND_LEFT]._iDurability == DUR_INDESTRUCTIBLE) {
2286 			return FALSE;
2287 		}
2288 
2289 		plr[pnum].InvBody[INVLOC_HAND_LEFT]._iDurability--;
2290 		if (plr[pnum].InvBody[INVLOC_HAND_LEFT]._iDurability <= 0) {
2291 			NetSendCmdDelItem(TRUE, INVLOC_HAND_LEFT);
2292 			plr[pnum].InvBody[INVLOC_HAND_LEFT]._itype = ITYPE_NONE;
2293 			CalcPlrInv(pnum, TRUE);
2294 			return TRUE;
2295 		}
2296 	}
2297 
2298 	if (!plr[pnum].InvBody[INVLOC_HAND_RIGHT].isEmpty() && plr[pnum].InvBody[INVLOC_HAND_RIGHT]._iClass == ICLASS_WEAPON) {
2299 		if (plr[pnum].InvBody[INVLOC_HAND_RIGHT]._iDurability == DUR_INDESTRUCTIBLE) {
2300 			return FALSE;
2301 		}
2302 
2303 		plr[pnum].InvBody[INVLOC_HAND_RIGHT]._iDurability--;
2304 		if (plr[pnum].InvBody[INVLOC_HAND_RIGHT]._iDurability == 0) {
2305 			NetSendCmdDelItem(TRUE, INVLOC_HAND_RIGHT);
2306 			plr[pnum].InvBody[INVLOC_HAND_RIGHT]._itype = ITYPE_NONE;
2307 			CalcPlrInv(pnum, TRUE);
2308 			return TRUE;
2309 		}
2310 	}
2311 
2312 	if (plr[pnum].InvBody[INVLOC_HAND_LEFT].isEmpty() && plr[pnum].InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_SHIELD) {
2313 		if (plr[pnum].InvBody[INVLOC_HAND_RIGHT]._iDurability == DUR_INDESTRUCTIBLE) {
2314 			return FALSE;
2315 		}
2316 
2317 		plr[pnum].InvBody[INVLOC_HAND_RIGHT]._iDurability--;
2318 		if (plr[pnum].InvBody[INVLOC_HAND_RIGHT]._iDurability == 0) {
2319 			NetSendCmdDelItem(TRUE, INVLOC_HAND_RIGHT);
2320 			plr[pnum].InvBody[INVLOC_HAND_RIGHT]._itype = ITYPE_NONE;
2321 			CalcPlrInv(pnum, TRUE);
2322 			return TRUE;
2323 		}
2324 	}
2325 
2326 	if (plr[pnum].InvBody[INVLOC_HAND_RIGHT].isEmpty() && plr[pnum].InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_SHIELD) {
2327 		if (plr[pnum].InvBody[INVLOC_HAND_LEFT]._iDurability == DUR_INDESTRUCTIBLE) {
2328 			return FALSE;
2329 		}
2330 
2331 		plr[pnum].InvBody[INVLOC_HAND_LEFT]._iDurability--;
2332 		if (plr[pnum].InvBody[INVLOC_HAND_LEFT]._iDurability == 0) {
2333 			NetSendCmdDelItem(TRUE, INVLOC_HAND_LEFT);
2334 			plr[pnum].InvBody[INVLOC_HAND_LEFT]._itype = ITYPE_NONE;
2335 			CalcPlrInv(pnum, TRUE);
2336 			return TRUE;
2337 		}
2338 	}
2339 
2340 	return FALSE;
2341 }
2342 
PlrHitMonst(int pnum,int m)2343 BOOL PlrHitMonst(int pnum, int m)
2344 {
2345 	BOOL rv, ret;
2346 	int hit, hper, mind, maxd, ddp, dam, skdam, phanditype, tmac;
2347 	hper = 0;
2348 	ret = FALSE;
2349 	BOOL adjacentDamage = FALSE;
2350 
2351 	if ((DWORD)m >= MAXMONSTERS) {
2352 		app_fatal("PlrHitMonst: illegal monster %d", m);
2353 	}
2354 
2355 	if ((monster[m]._mhitpoints >> 6) <= 0) {
2356 		return FALSE;
2357 	}
2358 
2359 	if (monster[m].MType->mtype == MT_ILLWEAV && monster[m]._mgoal == MGOAL_RETREAT) {
2360 		return FALSE;
2361 	}
2362 
2363 	if (monster[m]._mmode == MM_CHARGE) {
2364 		return FALSE;
2365 	}
2366 
2367 	if (pnum < 0) {
2368 		adjacentDamage = TRUE;
2369 		pnum = -pnum;
2370 		if (plr[pnum]._pLevel > 20)
2371 			hper -= 30;
2372 		else
2373 			hper -= (35 - plr[pnum]._pLevel) * 2;
2374 	}
2375 
2376 	if ((DWORD)pnum >= MAX_PLRS) {
2377 		app_fatal("PlrHitMonst: illegal player %d", pnum);
2378 	}
2379 
2380 	rv = FALSE;
2381 
2382 	hit = random_(4, 100);
2383 	if (monster[m]._mmode == MM_STONE) {
2384 		hit = 0;
2385 	}
2386 
2387 	tmac = monster[m].mArmorClass;
2388 	if (gbIsHellfire && plr[pnum]._pIEnAc > 0) {
2389 		int _pIEnAc = plr[pnum]._pIEnAc - 1;
2390 		if (_pIEnAc > 0)
2391 			tmac >>= _pIEnAc;
2392 		else
2393 			tmac -= tmac >> 2;
2394 
2395 		if (plr[pnum]._pClass == PC_BARBARIAN) {
2396 			tmac -= monster[m].mArmorClass / 8;
2397 		}
2398 
2399 		if (tmac < 0)
2400 			tmac = 0;
2401 	} else {
2402 		tmac -= plr[pnum]._pIEnAc;
2403 	}
2404 
2405 	hper += (plr[pnum]._pDexterity >> 1) + plr[pnum]._pLevel + 50 - tmac;
2406 	if (plr[pnum]._pClass == PC_WARRIOR) {
2407 		hper += 20;
2408 	}
2409 	hper += plr[pnum]._pIBonusToHit;
2410 	if (hper < 5) {
2411 		hper = 5;
2412 	}
2413 	if (hper > 95) {
2414 		hper = 95;
2415 	}
2416 
2417 	if (CheckMonsterHit(m, &ret)) {
2418 		return ret;
2419 	}
2420 #ifdef _DEBUG
2421 	if (hit < hper || debug_mode_key_inverted_v || debug_mode_dollar_sign) {
2422 #else
2423 	if (hit < hper) {
2424 #endif
2425 		if (plr[pnum]._pIFlags & ISPL_FIREDAM && plr[pnum]._pIFlags & ISPL_LIGHTDAM) {
2426 			int midam = plr[pnum]._pIFMinDam + random_(3, plr[pnum]._pIFMaxDam - plr[pnum]._pIFMinDam);
2427 			AddMissile(plr[pnum]._px, plr[pnum]._py, plr[pnum]._pVar1, plr[pnum]._pVar2, plr[pnum]._pdir, MIS_SPECARROW, TARGET_MONSTERS, pnum, midam, 0);
2428 		}
2429 		mind = plr[pnum]._pIMinDam;
2430 		maxd = plr[pnum]._pIMaxDam;
2431 		dam = random_(5, maxd - mind + 1) + mind;
2432 		dam += dam * plr[pnum]._pIBonusDam / 100;
2433 		dam += plr[pnum]._pIBonusDamMod;
2434 		int dam2 = dam << 6;
2435 		dam += plr[pnum]._pDamageMod;
2436 		if (plr[pnum]._pClass == PC_WARRIOR || plr[pnum]._pClass == PC_BARBARIAN) {
2437 			ddp = plr[pnum]._pLevel;
2438 			if (random_(6, 100) < ddp) {
2439 				dam <<= 1;
2440 			}
2441 		}
2442 
2443 		phanditype = ITYPE_NONE;
2444 		if (plr[pnum].InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_SWORD || plr[pnum].InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_SWORD) {
2445 			phanditype = ITYPE_SWORD;
2446 		}
2447 		if (plr[pnum].InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_MACE || plr[pnum].InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_MACE) {
2448 			phanditype = ITYPE_MACE;
2449 		}
2450 
2451 		switch (monster[m].MData->mMonstClass) {
2452 		case MC_UNDEAD:
2453 			if (phanditype == ITYPE_SWORD) {
2454 				dam -= dam >> 1;
2455 			} else if (phanditype == ITYPE_MACE) {
2456 				dam += dam >> 1;
2457 			}
2458 			break;
2459 		case MC_ANIMAL:
2460 			if (phanditype == ITYPE_MACE) {
2461 				dam -= dam >> 1;
2462 			} else if (phanditype == ITYPE_SWORD) {
2463 				dam += dam >> 1;
2464 			}
2465 			break;
2466 		case MC_DEMON:
2467 			if (plr[pnum]._pIFlags & ISPL_3XDAMVDEM) {
2468 				dam *= 3;
2469 			}
2470 			break;
2471 		}
2472 
2473 		if (plr[pnum].pDamAcFlags & 0x01 && random_(6, 100) < 5) {
2474 			dam *= 3;
2475 		}
2476 
2477 		if (plr[pnum].pDamAcFlags & 0x10 && monster[m].MType->mtype != MT_DIABLO && monster[m]._uniqtype == 0 && random_(6, 100) < 10) {
2478 			monster_43C785(m);
2479 		}
2480 
2481 		dam <<= 6;
2482 		if (plr[pnum].pDamAcFlags & 0x08) {
2483 			int r = random_(6, 201);
2484 			if (r >= 100)
2485 				r = 100 + (r - 100) * 5;
2486 			dam = dam * r / 100;
2487 		}
2488 
2489 		if (adjacentDamage)
2490 			dam >>= 2;
2491 
2492 		if (pnum == myplr) {
2493 			if (plr[pnum].pDamAcFlags & 0x04) {
2494 				dam2 += plr[pnum]._pIGetHit << 6;
2495 				if (dam2 >= 0) {
2496 					if (plr[pnum]._pHitPoints > dam2) {
2497 						plr[pnum]._pHitPoints -= dam2;
2498 						plr[pnum]._pHPBase -= dam2;
2499 					} else {
2500 						dam2 = (1 << 6);
2501 						plr[pnum]._pHPBase -= plr[pnum]._pHitPoints - dam2;
2502 						plr[pnum]._pHitPoints = dam2;
2503 					}
2504 				}
2505 				dam <<= 1;
2506 			}
2507 			monster[m]._mhitpoints -= dam;
2508 		}
2509 
2510 		if (plr[pnum]._pIFlags & ISPL_RNDSTEALLIFE) {
2511 			skdam = random_(7, dam >> 3);
2512 			plr[pnum]._pHitPoints += skdam;
2513 			if (plr[pnum]._pHitPoints > plr[pnum]._pMaxHP) {
2514 				plr[pnum]._pHitPoints = plr[pnum]._pMaxHP;
2515 			}
2516 			plr[pnum]._pHPBase += skdam;
2517 			if (plr[pnum]._pHPBase > plr[pnum]._pMaxHPBase) {
2518 				plr[pnum]._pHPBase = plr[pnum]._pMaxHPBase;
2519 			}
2520 			drawhpflag = TRUE;
2521 		}
2522 		if (plr[pnum]._pIFlags & (ISPL_STEALMANA_3 | ISPL_STEALMANA_5) && !(plr[pnum]._pIFlags & ISPL_NOMANA)) {
2523 			if (plr[pnum]._pIFlags & ISPL_STEALMANA_3) {
2524 				skdam = 3 * dam / 100;
2525 			}
2526 			if (plr[pnum]._pIFlags & ISPL_STEALMANA_5) {
2527 				skdam = 5 * dam / 100;
2528 			}
2529 			plr[pnum]._pMana += skdam;
2530 			if (plr[pnum]._pMana > plr[pnum]._pMaxMana) {
2531 				plr[pnum]._pMana = plr[pnum]._pMaxMana;
2532 			}
2533 			plr[pnum]._pManaBase += skdam;
2534 			if (plr[pnum]._pManaBase > plr[pnum]._pMaxManaBase) {
2535 				plr[pnum]._pManaBase = plr[pnum]._pMaxManaBase;
2536 			}
2537 			drawmanaflag = TRUE;
2538 		}
2539 		if (plr[pnum]._pIFlags & (ISPL_STEALLIFE_3 | ISPL_STEALLIFE_5)) {
2540 			if (plr[pnum]._pIFlags & ISPL_STEALLIFE_3) {
2541 				skdam = 3 * dam / 100;
2542 			}
2543 			if (plr[pnum]._pIFlags & ISPL_STEALLIFE_5) {
2544 				skdam = 5 * dam / 100;
2545 			}
2546 			plr[pnum]._pHitPoints += skdam;
2547 			if (plr[pnum]._pHitPoints > plr[pnum]._pMaxHP) {
2548 				plr[pnum]._pHitPoints = plr[pnum]._pMaxHP;
2549 			}
2550 			plr[pnum]._pHPBase += skdam;
2551 			if (plr[pnum]._pHPBase > plr[pnum]._pMaxHPBase) {
2552 				plr[pnum]._pHPBase = plr[pnum]._pMaxHPBase;
2553 			}
2554 			drawhpflag = TRUE;
2555 		}
2556 		if (plr[pnum]._pIFlags & ISPL_NOHEALPLR) {
2557 			monster[m]._mFlags |= MFLAG_NOHEAL;
2558 		}
2559 #ifdef _DEBUG
2560 		if (debug_mode_dollar_sign || debug_mode_key_inverted_v) {
2561 			monster[m]._mhitpoints = 0; /* double check */
2562 		}
2563 #endif
2564 		if ((monster[m]._mhitpoints >> 6) <= 0) {
2565 			if (monster[m]._mmode == MM_STONE) {
2566 				M_StartKill(m, pnum);
2567 				monster[m]._mmode = MM_STONE;
2568 			} else {
2569 				M_StartKill(m, pnum);
2570 			}
2571 		} else {
2572 			if (monster[m]._mmode == MM_STONE) {
2573 				M_StartHit(m, pnum, dam);
2574 				monster[m]._mmode = MM_STONE;
2575 			} else {
2576 				if (plr[pnum]._pIFlags & ISPL_KNOCKBACK) {
2577 					M_GetKnockback(m);
2578 				}
2579 				M_StartHit(m, pnum, dam);
2580 			}
2581 		}
2582 		rv = TRUE;
2583 	}
2584 
2585 	return rv;
2586 }
2587 
2588 BOOL PlrHitPlr(int pnum, char p)
2589 {
2590 	BOOL rv;
2591 	int hit, hper, blk, blkper, mind, maxd, dam, lvl, skdam, tac;
2592 
2593 	if ((DWORD)p >= MAX_PLRS) {
2594 		app_fatal("PlrHitPlr: illegal target player %d", p);
2595 	}
2596 
2597 	rv = FALSE;
2598 
2599 	if (plr[p]._pInvincible) {
2600 		return rv;
2601 	}
2602 
2603 	if (plr[p]._pSpellFlags & 1) {
2604 		return rv;
2605 	}
2606 
2607 	if ((DWORD)pnum >= MAX_PLRS) {
2608 		app_fatal("PlrHitPlr: illegal attacking player %d", pnum);
2609 	}
2610 
2611 	hit = random_(4, 100);
2612 
2613 	hper = (plr[pnum]._pDexterity >> 1) + plr[pnum]._pLevel + 50 - (plr[p]._pIBonusAC + plr[p]._pIAC + plr[p]._pDexterity / 5);
2614 
2615 	if (plr[pnum]._pClass == PC_WARRIOR) {
2616 		hper += 20;
2617 	}
2618 	hper += plr[pnum]._pIBonusToHit;
2619 	if (hper < 5) {
2620 		hper = 5;
2621 	}
2622 	if (hper > 95) {
2623 		hper = 95;
2624 	}
2625 
2626 	if ((plr[p]._pmode == PM_STAND || plr[p]._pmode == PM_ATTACK) && plr[p]._pBlockFlag) {
2627 		blk = random_(5, 100);
2628 	} else {
2629 		blk = 100;
2630 	}
2631 
2632 	blkper = plr[p]._pDexterity + plr[p]._pBaseToBlk + (plr[p]._pLevel << 1) - (plr[pnum]._pLevel << 1);
2633 	if (blkper < 0) {
2634 		blkper = 0;
2635 	}
2636 	if (blkper > 100) {
2637 		blkper = 100;
2638 	}
2639 
2640 	if (hit < hper) {
2641 		if (blk < blkper) {
2642 			direction dir = GetDirection(plr[p]._px, plr[p]._py, plr[pnum]._px, plr[pnum]._py);
2643 			StartPlrBlock(p, dir);
2644 		} else {
2645 			mind = plr[pnum]._pIMinDam;
2646 			maxd = plr[pnum]._pIMaxDam;
2647 			dam = random_(5, maxd - mind + 1) + mind;
2648 			dam += (dam * plr[pnum]._pIBonusDam) / 100;
2649 			dam += plr[pnum]._pIBonusDamMod + plr[pnum]._pDamageMod;
2650 
2651 			if (plr[pnum]._pClass == PC_WARRIOR || plr[pnum]._pClass == PC_BARBARIAN) {
2652 				lvl = plr[pnum]._pLevel;
2653 				if (random_(6, 100) < lvl) {
2654 					dam <<= 1;
2655 				}
2656 			}
2657 			skdam = dam << 6;
2658 			if (plr[pnum]._pIFlags & ISPL_RNDSTEALLIFE) {
2659 				tac = random_(7, skdam >> 3);
2660 				plr[pnum]._pHitPoints += tac;
2661 				if (plr[pnum]._pHitPoints > plr[pnum]._pMaxHP) {
2662 					plr[pnum]._pHitPoints = plr[pnum]._pMaxHP;
2663 				}
2664 				plr[pnum]._pHPBase += tac;
2665 				if (plr[pnum]._pHPBase > plr[pnum]._pMaxHPBase) {
2666 					plr[pnum]._pHPBase = plr[pnum]._pMaxHPBase;
2667 				}
2668 				drawhpflag = TRUE;
2669 			}
2670 			if (pnum == myplr) {
2671 				NetSendCmdDamage(TRUE, p, skdam);
2672 			}
2673 			StartPlrHit(p, skdam, FALSE);
2674 		}
2675 
2676 		rv = TRUE;
2677 	}
2678 
2679 	return rv;
2680 }
2681 
2682 BOOL PlrHitObj(int pnum, int mx, int my)
2683 {
2684 	int oi;
2685 
2686 	if (dObject[mx][my] > 0) {
2687 		oi = dObject[mx][my] - 1;
2688 	} else {
2689 		oi = -dObject[mx][my] - 1;
2690 	}
2691 
2692 	if (object[oi]._oBreak == 1) {
2693 		BreakObject(pnum, oi);
2694 		return TRUE;
2695 	}
2696 
2697 	return FALSE;
2698 }
2699 
2700 BOOL PM_DoAttack(int pnum)
2701 {
2702 	int frame, dir, dx, dy, m;
2703 	BOOL didhit = FALSE;
2704 
2705 	if ((DWORD)pnum >= MAX_PLRS) {
2706 		app_fatal("PM_DoAttack: illegal player %d", pnum);
2707 	}
2708 
2709 	frame = plr[pnum]._pAnimFrame;
2710 	if (plr[pnum]._pIFlags & ISPL_QUICKATTACK && frame == 1) {
2711 		plr[pnum]._pAnimFrame++;
2712 	}
2713 	if (plr[pnum]._pIFlags & ISPL_FASTATTACK && (frame == 1 || frame == 3)) {
2714 		plr[pnum]._pAnimFrame++;
2715 	}
2716 	if (plr[pnum]._pIFlags & ISPL_FASTERATTACK && (frame == 1 || frame == 3 || frame == 5)) {
2717 		plr[pnum]._pAnimFrame++;
2718 	}
2719 	if (plr[pnum]._pIFlags & ISPL_FASTESTATTACK && (frame == 1 || frame == 4)) {
2720 		plr[pnum]._pAnimFrame += 2;
2721 	}
2722 	if (plr[pnum]._pAnimFrame == plr[pnum]._pAFNum - 1) {
2723 		PlaySfxLoc(PS_SWING, plr[pnum]._px, plr[pnum]._py);
2724 	}
2725 
2726 	if (plr[pnum]._pAnimFrame == plr[pnum]._pAFNum) {
2727 		dx = plr[pnum]._px + offset_x[plr[pnum]._pdir];
2728 		dy = plr[pnum]._py + offset_y[plr[pnum]._pdir];
2729 
2730 		if (dMonster[dx][dy] != 0) {
2731 			if (dMonster[dx][dy] > 0) {
2732 				m = dMonster[dx][dy] - 1;
2733 			} else {
2734 				m = -(dMonster[dx][dy] + 1);
2735 			}
2736 			if (CanTalkToMonst(m)) {
2737 				plr[pnum]._pVar1 = 0;
2738 				return FALSE;
2739 			}
2740 		}
2741 
2742 		if (!(plr[pnum]._pIFlags & ISPL_FIREDAM) || !(plr[pnum]._pIFlags & ISPL_LIGHTDAM)) {
2743 			if (plr[pnum]._pIFlags & ISPL_FIREDAM) {
2744 				AddMissile(dx, dy, 1, 0, 0, MIS_WEAPEXP, TARGET_MONSTERS, pnum, 0, 0);
2745 			} else if (plr[pnum]._pIFlags & ISPL_LIGHTDAM) {
2746 				AddMissile(dx, dy, 2, 0, 0, MIS_WEAPEXP, TARGET_MONSTERS, pnum, 0, 0);
2747 			}
2748 		}
2749 
2750 		if (dMonster[dx][dy]) {
2751 			m = dMonster[dx][dy];
2752 			if (dMonster[dx][dy] > 0) {
2753 				m = dMonster[dx][dy] - 1;
2754 			} else {
2755 				m = -(dMonster[dx][dy] + 1);
2756 			}
2757 			didhit = PlrHitMonst(pnum, m);
2758 		} else if (dPlayer[dx][dy] != 0 && (!gbFriendlyMode || gbFriendlyFire)) {
2759 			BYTE p = dPlayer[dx][dy];
2760 			if (dPlayer[dx][dy] > 0) {
2761 				p = dPlayer[dx][dy] - 1;
2762 			} else {
2763 				p = -(dPlayer[dx][dy] + 1);
2764 			}
2765 			didhit = PlrHitPlr(pnum, p);
2766 		} else if (dObject[dx][dy] > 0) {
2767 			didhit = PlrHitObj(pnum, dx, dy);
2768 		}
2769 		if ((plr[pnum]._pClass == PC_MONK
2770 		        && (plr[pnum].InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_STAFF || plr[pnum].InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_STAFF))
2771 		    || (plr[pnum]._pClass == PC_BARD
2772 		        && plr[pnum].InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_SWORD && plr[pnum].InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_SWORD)
2773 		    || (plr[pnum]._pClass == PC_BARBARIAN
2774 		        && (plr[pnum].InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_AXE || plr[pnum].InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_AXE
2775 		            || (((plr[pnum].InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_MACE && plr[pnum].InvBody[INVLOC_HAND_LEFT]._iLoc == ILOC_TWOHAND)
2776 		                    || (plr[pnum].InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_MACE && plr[pnum].InvBody[INVLOC_HAND_RIGHT]._iLoc == ILOC_TWOHAND)
2777 		                    || (plr[pnum].InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_SWORD && plr[pnum].InvBody[INVLOC_HAND_LEFT]._iLoc == ILOC_TWOHAND)
2778 		                    || (plr[pnum].InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_SWORD && plr[pnum].InvBody[INVLOC_HAND_RIGHT]._iLoc == ILOC_TWOHAND))
2779 		                && !(plr[pnum].InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_SHIELD || plr[pnum].InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_SHIELD))))) {
2780 			dx = plr[pnum]._px + offset_x[(plr[pnum]._pdir + 1) % 8];
2781 			dy = plr[pnum]._py + offset_y[(plr[pnum]._pdir + 1) % 8];
2782 			m = ((dMonster[dx][dy] > 0) ? dMonster[dx][dy] : -dMonster[dx][dy]) - 1;
2783 			if (dMonster[dx][dy] != 0 && !CanTalkToMonst(m) && monster[m]._moldx == dx && monster[m]._moldy == dy) {
2784 				if (PlrHitMonst(-pnum, m))
2785 					didhit = TRUE;
2786 			}
2787 			dx = plr[pnum]._px + offset_x[(plr[pnum]._pdir + 7) % 8];
2788 			dy = plr[pnum]._py + offset_y[(plr[pnum]._pdir + 7) % 8];
2789 			m = ((dMonster[dx][dy] > 0) ? dMonster[dx][dy] : -dMonster[dx][dy]) - 1;
2790 			if (dMonster[dx][dy] != 0 && !CanTalkToMonst(m) && monster[m]._moldx == dx && monster[m]._moldy == dy) {
2791 				if (PlrHitMonst(-pnum, m))
2792 					didhit = TRUE;
2793 			}
2794 		}
2795 
2796 		if (didhit && WeaponDur(pnum, 30)) {
2797 			StartStand(pnum, plr[pnum]._pdir);
2798 			ClearPlrPVars(pnum);
2799 			return TRUE;
2800 		}
2801 	}
2802 
2803 	if (plr[pnum]._pAnimFrame == plr[pnum]._pAFrames) {
2804 		StartStand(pnum, plr[pnum]._pdir);
2805 		ClearPlrPVars(pnum);
2806 		return TRUE;
2807 	} else {
2808 		return FALSE;
2809 	}
2810 }
2811 
2812 BOOL PM_DoRangeAttack(int pnum)
2813 {
2814 	int origFrame, mistype;
2815 
2816 	if ((DWORD)pnum >= MAX_PLRS) {
2817 		app_fatal("PM_DoRangeAttack: illegal player %d", pnum);
2818 	}
2819 
2820 	if (!gbIsHellfire) {
2821 		origFrame = plr[pnum]._pAnimFrame;
2822 		if (plr[pnum]._pIFlags & ISPL_QUICKATTACK && origFrame == 1) {
2823 			plr[pnum]._pAnimFrame++;
2824 		}
2825 		if (plr[pnum]._pIFlags & ISPL_FASTATTACK && (origFrame == 1 || origFrame == 3)) {
2826 			plr[pnum]._pAnimFrame++;
2827 		}
2828 	}
2829 
2830 	int arrows = 0;
2831 	if (plr[pnum]._pAnimFrame == plr[pnum]._pAFNum) {
2832 		arrows = 1;
2833 	}
2834 	if ((plr[pnum]._pIFlags & ISPL_MULT_ARROWS) != 0 && plr[pnum]._pAnimFrame == plr[pnum]._pAFNum + 2) {
2835 		arrows = 2;
2836 	}
2837 
2838 	for (int arrow = 0; arrow < arrows; arrow++) {
2839 		int xoff = 0;
2840 		int yoff = 0;
2841 		if (arrows != 1) {
2842 			int angle = arrow == 0 ? -1 : 1;
2843 			int x = plr[pnum]._pVar1 - plr[pnum]._px;
2844 			if (x)
2845 				yoff = x < 0 ? angle : -angle;
2846 			int y = plr[pnum]._pVar2 - plr[pnum]._py;
2847 			if (y)
2848 				xoff = y < 0 ? -angle : angle;
2849 		}
2850 
2851 		int dmg = 4;
2852 		mistype = MIS_ARROW;
2853 		if (plr[pnum]._pIFlags & ISPL_FIRE_ARROWS) {
2854 			mistype = MIS_FARROW;
2855 		}
2856 		if (plr[pnum]._pIFlags & ISPL_LIGHT_ARROWS) {
2857 			mistype = MIS_LARROW;
2858 		}
2859 		if ((plr[pnum]._pIFlags & ISPL_FIRE_ARROWS) != 0 && (plr[pnum]._pIFlags & ISPL_LIGHT_ARROWS) != 0) {
2860 			dmg = plr[pnum]._pIFMinDam + random_(3, plr[pnum]._pIFMaxDam - plr[pnum]._pIFMinDam);
2861 			mistype = MIS_SPECARROW;
2862 		}
2863 
2864 		AddMissile(
2865 		    plr[pnum]._px,
2866 		    plr[pnum]._py,
2867 		    plr[pnum]._pVar1 + xoff,
2868 		    plr[pnum]._pVar2 + yoff,
2869 		    plr[pnum]._pdir,
2870 		    mistype,
2871 		    TARGET_MONSTERS,
2872 		    pnum,
2873 		    dmg,
2874 		    0);
2875 
2876 		if (arrow == 0 && mistype != MIS_SPECARROW) {
2877 			PlaySfxLoc(arrows != 1 ? IS_STING1 : PS_BFIRE, plr[pnum]._px, plr[pnum]._py);
2878 		}
2879 
2880 		if (WeaponDur(pnum, 40)) {
2881 			StartStand(pnum, plr[pnum]._pdir);
2882 			ClearPlrPVars(pnum);
2883 			return TRUE;
2884 		}
2885 	}
2886 
2887 	if (plr[pnum]._pAnimFrame >= plr[pnum]._pAFrames) {
2888 		StartStand(pnum, plr[pnum]._pdir);
2889 		ClearPlrPVars(pnum);
2890 		return TRUE;
2891 	} else {
2892 		return FALSE;
2893 	}
2894 }
2895 
2896 void ShieldDur(int pnum)
2897 {
2898 	if (pnum != myplr) {
2899 		return;
2900 	}
2901 
2902 	if ((DWORD)pnum >= MAX_PLRS) {
2903 		app_fatal("ShieldDur: illegal player %d", pnum);
2904 	}
2905 
2906 	if (plr[pnum].InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_SHIELD) {
2907 		if (plr[pnum].InvBody[INVLOC_HAND_LEFT]._iDurability == DUR_INDESTRUCTIBLE) {
2908 			return;
2909 		}
2910 
2911 		plr[pnum].InvBody[INVLOC_HAND_LEFT]._iDurability--;
2912 		if (plr[pnum].InvBody[INVLOC_HAND_LEFT]._iDurability == 0) {
2913 			NetSendCmdDelItem(TRUE, INVLOC_HAND_LEFT);
2914 			plr[pnum].InvBody[INVLOC_HAND_LEFT]._itype = ITYPE_NONE;
2915 			CalcPlrInv(pnum, TRUE);
2916 		}
2917 	}
2918 
2919 	if (plr[pnum].InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_SHIELD) {
2920 		if (plr[pnum].InvBody[INVLOC_HAND_RIGHT]._iDurability != DUR_INDESTRUCTIBLE) {
2921 			plr[pnum].InvBody[INVLOC_HAND_RIGHT]._iDurability--;
2922 			if (plr[pnum].InvBody[INVLOC_HAND_RIGHT]._iDurability == 0) {
2923 				NetSendCmdDelItem(TRUE, INVLOC_HAND_RIGHT);
2924 				plr[pnum].InvBody[INVLOC_HAND_RIGHT]._itype = ITYPE_NONE;
2925 				CalcPlrInv(pnum, TRUE);
2926 			}
2927 		}
2928 	}
2929 }
2930 
2931 BOOL PM_DoBlock(int pnum)
2932 {
2933 	if ((DWORD)pnum >= MAX_PLRS) {
2934 		app_fatal("PM_DoBlock: illegal player %d", pnum);
2935 	}
2936 
2937 	if (plr[pnum]._pIFlags & ISPL_FASTBLOCK && plr[pnum]._pAnimFrame != 1) {
2938 		plr[pnum]._pAnimFrame = plr[pnum]._pBFrames;
2939 	}
2940 
2941 	if (plr[pnum]._pAnimFrame >= plr[pnum]._pBFrames) {
2942 		StartStand(pnum, plr[pnum]._pdir);
2943 		ClearPlrPVars(pnum);
2944 
2945 		if (random_(3, 10) == 0) {
2946 			ShieldDur(pnum);
2947 		}
2948 		return TRUE;
2949 	}
2950 
2951 	return FALSE;
2952 }
2953 
2954 static void ArmorDur(int pnum)
2955 {
2956 	int a;
2957 	ItemStruct *pi;
2958 	PlayerStruct *p;
2959 
2960 	if (pnum != myplr) {
2961 		return;
2962 	}
2963 
2964 	if ((DWORD)pnum >= MAX_PLRS) {
2965 		app_fatal("ArmorDur: illegal player %d", pnum);
2966 	}
2967 
2968 	p = &plr[pnum];
2969 	if (p->InvBody[INVLOC_CHEST].isEmpty() && p->InvBody[INVLOC_HEAD].isEmpty()) {
2970 		return;
2971 	}
2972 
2973 	a = random_(8, 3);
2974 	if (!p->InvBody[INVLOC_CHEST].isEmpty() && p->InvBody[INVLOC_HEAD].isEmpty()) {
2975 		a = 1;
2976 	}
2977 	if (p->InvBody[INVLOC_CHEST].isEmpty() && !p->InvBody[INVLOC_HEAD].isEmpty()) {
2978 		a = 0;
2979 	}
2980 
2981 	if (a != 0) {
2982 		pi = &p->InvBody[INVLOC_CHEST];
2983 	} else {
2984 		pi = &p->InvBody[INVLOC_HEAD];
2985 	}
2986 	if (pi->_iDurability == DUR_INDESTRUCTIBLE) {
2987 		return;
2988 	}
2989 
2990 	pi->_iDurability--;
2991 	if (pi->_iDurability != 0) {
2992 		return;
2993 	}
2994 
2995 	if (a != 0) {
2996 		NetSendCmdDelItem(TRUE, INVLOC_CHEST);
2997 	} else {
2998 		NetSendCmdDelItem(TRUE, INVLOC_HEAD);
2999 	}
3000 	pi->_itype = ITYPE_NONE;
3001 	CalcPlrInv(pnum, TRUE);
3002 }
3003 
3004 BOOL PM_DoSpell(int pnum)
3005 {
3006 	if ((DWORD)pnum >= MAX_PLRS) {
3007 		app_fatal("PM_DoSpell: illegal player %d", pnum);
3008 	}
3009 
3010 	if (plr[pnum]._pVar8 == plr[pnum]._pSFNum) {
3011 		CastSpell(
3012 		    pnum,
3013 		    plr[pnum]._pSpell,
3014 		    plr[pnum]._px,
3015 		    plr[pnum]._py,
3016 		    plr[pnum]._pVar1,
3017 		    plr[pnum]._pVar2,
3018 		    plr[pnum]._pVar4);
3019 
3020 		if (plr[pnum]._pSplFrom == 0) {
3021 			EnsureValidReadiedSpell(plr[pnum]);
3022 		}
3023 	}
3024 
3025 	plr[pnum]._pVar8++;
3026 
3027 	if (leveltype == DTYPE_TOWN) {
3028 		if (plr[pnum]._pVar8 > plr[pnum]._pSFrames) {
3029 			StartWalkStand(pnum);
3030 			ClearPlrPVars(pnum);
3031 			return TRUE;
3032 		}
3033 	} else if (plr[pnum]._pAnimFrame == plr[pnum]._pSFrames) {
3034 		StartStand(pnum, plr[pnum]._pdir);
3035 		ClearPlrPVars(pnum);
3036 		return TRUE;
3037 	}
3038 
3039 	return FALSE;
3040 }
3041 
3042 BOOL PM_DoGotHit(int pnum)
3043 {
3044 	int frame;
3045 
3046 	if ((DWORD)pnum >= MAX_PLRS) {
3047 		app_fatal("PM_DoGotHit: illegal player %d", pnum);
3048 	}
3049 
3050 	frame = plr[pnum]._pAnimFrame;
3051 	if (plr[pnum]._pIFlags & ISPL_FASTRECOVER && frame == 3) {
3052 		plr[pnum]._pAnimFrame++;
3053 	}
3054 	if (plr[pnum]._pIFlags & ISPL_FASTERRECOVER && (frame == 3 || frame == 5)) {
3055 		plr[pnum]._pAnimFrame++;
3056 	}
3057 	if (plr[pnum]._pIFlags & ISPL_FASTESTRECOVER && (frame == 1 || frame == 3 || frame == 5)) {
3058 		plr[pnum]._pAnimFrame++;
3059 	}
3060 
3061 	if (plr[pnum]._pAnimFrame >= plr[pnum]._pHFrames) {
3062 		StartStand(pnum, plr[pnum]._pdir);
3063 		ClearPlrPVars(pnum);
3064 		if (random_(3, 4) != 0) {
3065 			ArmorDur(pnum);
3066 		}
3067 
3068 		return TRUE;
3069 	}
3070 
3071 	return FALSE;
3072 }
3073 
3074 BOOL PM_DoDeath(int pnum)
3075 {
3076 	if ((DWORD)pnum >= MAX_PLRS) {
3077 		app_fatal("PM_DoDeath: illegal player %d", pnum);
3078 	}
3079 
3080 	if (plr[pnum]._pVar8 >= 2 * plr[pnum]._pDFrames) {
3081 		if (deathdelay > 1 && pnum == myplr) {
3082 			deathdelay--;
3083 			if (deathdelay == 1) {
3084 				deathflag = TRUE;
3085 				if (!gbIsMultiplayer) {
3086 					gamemenu_on();
3087 				}
3088 			}
3089 		}
3090 
3091 		plr[pnum]._pAnimDelay = 10000;
3092 		plr[pnum]._pAnimFrame = plr[pnum]._pAnimLen;
3093 		dFlags[plr[pnum]._px][plr[pnum]._py] |= BFLAG_DEAD_PLAYER;
3094 	}
3095 
3096 	if (plr[pnum]._pVar8 < 100) {
3097 		plr[pnum]._pVar8++;
3098 	}
3099 
3100 	return FALSE;
3101 }
3102 
3103 BOOL PM_DoNewLvl(int pnum)
3104 {
3105 	return FALSE;
3106 }
3107 
3108 void CheckNewPath(int pnum)
3109 {
3110 	int i, x, y;
3111 	int xvel3, xvel, yvel;
3112 
3113 	if ((DWORD)pnum >= MAX_PLRS) {
3114 		app_fatal("CheckNewPath: illegal player %d", pnum);
3115 	}
3116 
3117 	if (plr[pnum].destAction == ACTION_ATTACKMON) {
3118 		i = plr[pnum].destParam1;
3119 		MakePlrPath(pnum, monster[i]._mfutx, monster[i]._mfuty, FALSE);
3120 	}
3121 
3122 	if (plr[pnum].destAction == ACTION_ATTACKPLR) {
3123 		i = plr[pnum].destParam1;
3124 		MakePlrPath(pnum, plr[i]._pfutx, plr[i]._pfuty, FALSE);
3125 	}
3126 
3127 	direction d;
3128 	if (plr[pnum].walkpath[0] != WALK_NONE) {
3129 		if (plr[pnum]._pmode == PM_STAND) {
3130 			if (pnum == myplr) {
3131 				if (plr[pnum].destAction == ACTION_ATTACKMON || plr[pnum].destAction == ACTION_ATTACKPLR) {
3132 					i = plr[pnum].destParam1;
3133 
3134 					if (plr[pnum].destAction == ACTION_ATTACKMON) {
3135 						x = abs(plr[pnum]._pfutx - monster[i]._mfutx);
3136 						y = abs(plr[pnum]._pfuty - monster[i]._mfuty);
3137 						d = GetDirection(plr[pnum]._pfutx, plr[pnum]._pfuty, monster[i]._mfutx, monster[i]._mfuty);
3138 					} else {
3139 						x = abs(plr[pnum]._pfutx - plr[i]._pfutx);
3140 						y = abs(plr[pnum]._pfuty - plr[i]._pfuty);
3141 						d = GetDirection(plr[pnum]._pfutx, plr[pnum]._pfuty, plr[i]._pfutx, plr[i]._pfuty);
3142 					}
3143 
3144 					if (x < 2 && y < 2) {
3145 						ClrPlrPath(pnum);
3146 						if (monster[i].mtalkmsg && monster[i].mtalkmsg != TEXT_VILE14) {
3147 							TalktoMonster(i);
3148 						} else {
3149 							StartAttack(pnum, d);
3150 						}
3151 						plr[pnum].destAction = ACTION_NONE;
3152 					}
3153 				}
3154 			}
3155 
3156 			if (currlevel != 0) {
3157 				xvel3 = PWVel[plr[pnum]._pClass][0];
3158 				xvel = PWVel[plr[pnum]._pClass][1];
3159 				yvel = PWVel[plr[pnum]._pClass][2];
3160 			} else {
3161 				xvel3 = 2048;
3162 				xvel = 1024;
3163 				yvel = 512;
3164 			}
3165 
3166 			switch (plr[pnum].walkpath[0]) {
3167 			case WALK_N:
3168 				StartWalk(pnum, 0, -xvel, 0, 0, -1, -1, 0, 0, DIR_N, SDIR_N, PM_WALK);
3169 				break;
3170 			case WALK_NE:
3171 				StartWalk(pnum, xvel, -yvel, 0, 0, 0, -1, 0, 0, DIR_NE, SDIR_NE, PM_WALK);
3172 				break;
3173 			case WALK_E:
3174 				StartWalk(pnum, xvel3, 0, -32, -16, 1, -1, 1, 0, DIR_E, SDIR_E, PM_WALK3);
3175 				break;
3176 			case WALK_SE:
3177 				StartWalk(pnum, xvel, yvel, -32, -16, 1, 0, 0, 0, DIR_SE, SDIR_SE, PM_WALK2);
3178 				break;
3179 			case WALK_S:
3180 				StartWalk(pnum, 0, xvel, 0, -32, 1, 1, 0, 0, DIR_S, SDIR_S, PM_WALK2);
3181 				break;
3182 			case WALK_SW:
3183 				StartWalk(pnum, -xvel, yvel, 32, -16, 0, 1, 0, 0, DIR_SW, SDIR_SW, PM_WALK2);
3184 				break;
3185 			case WALK_W:
3186 				StartWalk(pnum, -xvel3, 0, 32, -16, -1, 1, 0, 1, DIR_W, SDIR_W, PM_WALK3);
3187 				break;
3188 			case WALK_NW:
3189 				StartWalk(pnum, -xvel, -yvel, 0, 0, -1, 0, 0, 0, DIR_NW, SDIR_NW, PM_WALK);
3190 				break;
3191 			}
3192 
3193 			for (i = 1; i < MAX_PATH_LENGTH; i++) {
3194 				plr[pnum].walkpath[i - 1] = plr[pnum].walkpath[i];
3195 			}
3196 
3197 			plr[pnum].walkpath[MAX_PATH_LENGTH - 1] = WALK_NONE;
3198 
3199 			if (plr[pnum]._pmode == PM_STAND) {
3200 				StartStand(pnum, plr[pnum]._pdir);
3201 				plr[pnum].destAction = ACTION_NONE;
3202 			}
3203 		}
3204 
3205 		return;
3206 	}
3207 	if (plr[pnum].destAction == ACTION_NONE) {
3208 		return;
3209 	}
3210 
3211 	if (plr[pnum]._pmode == PM_STAND) {
3212 		switch (plr[pnum].destAction) {
3213 		case ACTION_ATTACK:
3214 			d = GetDirection(plr[pnum]._px, plr[pnum]._py, plr[pnum].destParam1, plr[pnum].destParam2);
3215 			StartAttack(pnum, d);
3216 			break;
3217 		case ACTION_ATTACKMON:
3218 			i = plr[pnum].destParam1;
3219 			x = abs(plr[pnum]._px - monster[i]._mfutx);
3220 			y = abs(plr[pnum]._py - monster[i]._mfuty);
3221 			if (x <= 1 && y <= 1) {
3222 				d = GetDirection(plr[pnum]._pfutx, plr[pnum]._pfuty, monster[i]._mfutx, monster[i]._mfuty);
3223 				if (monster[i].mtalkmsg && monster[i].mtalkmsg != TEXT_VILE14) {
3224 					TalktoMonster(i);
3225 				} else {
3226 					StartAttack(pnum, d);
3227 				}
3228 			}
3229 			break;
3230 		case ACTION_ATTACKPLR:
3231 			i = plr[pnum].destParam1;
3232 			x = abs(plr[pnum]._px - plr[i]._pfutx);
3233 			y = abs(plr[pnum]._py - plr[i]._pfuty);
3234 			if (x <= 1 && y <= 1) {
3235 				d = GetDirection(plr[pnum]._pfutx, plr[pnum]._pfuty, plr[i]._pfutx, plr[i]._pfuty);
3236 				StartAttack(pnum, d);
3237 			}
3238 			break;
3239 		case ACTION_RATTACK:
3240 			d = GetDirection(plr[pnum]._px, plr[pnum]._py, plr[pnum].destParam1, plr[pnum].destParam2);
3241 			StartRangeAttack(pnum, d, plr[pnum].destParam1, plr[pnum].destParam2);
3242 			break;
3243 		case ACTION_RATTACKMON:
3244 			i = plr[pnum].destParam1;
3245 			d = GetDirection(plr[pnum]._pfutx, plr[pnum]._pfuty, monster[i]._mfutx, monster[i]._mfuty);
3246 			if (monster[i].mtalkmsg && monster[i].mtalkmsg != TEXT_VILE14) {
3247 				TalktoMonster(i);
3248 			} else {
3249 				StartRangeAttack(pnum, d, monster[i]._mfutx, monster[i]._mfuty);
3250 			}
3251 			break;
3252 		case ACTION_RATTACKPLR:
3253 			i = plr[pnum].destParam1;
3254 			d = GetDirection(plr[pnum]._pfutx, plr[pnum]._pfuty, plr[i]._pfutx, plr[i]._pfuty);
3255 			StartRangeAttack(pnum, d, plr[i]._pfutx, plr[i]._pfuty);
3256 			break;
3257 		case ACTION_SPELL:
3258 			d = GetDirection(plr[pnum]._px, plr[pnum]._py, plr[pnum].destParam1, plr[pnum].destParam2);
3259 			StartSpell(pnum, d, plr[pnum].destParam1, plr[pnum].destParam2);
3260 			plr[pnum]._pVar4 = plr[pnum].destParam3;
3261 			break;
3262 		case ACTION_SPELLWALL:
3263 			StartSpell(pnum, plr[pnum].destParam3, plr[pnum].destParam1, plr[pnum].destParam2);
3264 			plr[pnum]._pVar3 = plr[pnum].destParam3;
3265 			plr[pnum]._pVar4 = plr[pnum].destParam4;
3266 			break;
3267 		case ACTION_SPELLMON:
3268 			i = plr[pnum].destParam1;
3269 			d = GetDirection(plr[pnum]._px, plr[pnum]._py, monster[i]._mfutx, monster[i]._mfuty);
3270 			StartSpell(pnum, d, monster[i]._mfutx, monster[i]._mfuty);
3271 			plr[pnum]._pVar4 = plr[pnum].destParam2;
3272 			break;
3273 		case ACTION_SPELLPLR:
3274 			i = plr[pnum].destParam1;
3275 			d = GetDirection(plr[pnum]._px, plr[pnum]._py, plr[i]._pfutx, plr[i]._pfuty);
3276 			StartSpell(pnum, d, plr[i]._pfutx, plr[i]._pfuty);
3277 			plr[pnum]._pVar4 = plr[pnum].destParam2;
3278 			break;
3279 		case ACTION_OPERATE:
3280 			i = plr[pnum].destParam1;
3281 			x = abs(plr[pnum]._px - object[i]._ox);
3282 			y = abs(plr[pnum]._py - object[i]._oy);
3283 			if (y > 1 && dObject[object[i]._ox][object[i]._oy - 1] == -(i + 1)) {
3284 				y = abs(plr[pnum]._py - object[i]._oy + 1);
3285 			}
3286 			if (x <= 1 && y <= 1) {
3287 				if (object[i]._oBreak == 1) {
3288 					d = GetDirection(plr[pnum]._px, plr[pnum]._py, object[i]._ox, object[i]._oy);
3289 					StartAttack(pnum, d);
3290 				} else {
3291 					OperateObject(pnum, i, FALSE);
3292 				}
3293 			}
3294 			break;
3295 		case ACTION_DISARM:
3296 			i = plr[pnum].destParam1;
3297 			x = abs(plr[pnum]._px - object[i]._ox);
3298 			y = abs(plr[pnum]._py - object[i]._oy);
3299 			if (y > 1 && dObject[object[i]._ox][object[i]._oy - 1] == -(i + 1)) {
3300 				y = abs(plr[pnum]._py - object[i]._oy + 1);
3301 			}
3302 			if (x <= 1 && y <= 1) {
3303 				if (object[i]._oBreak == 1) {
3304 					d = GetDirection(plr[pnum]._px, plr[pnum]._py, object[i]._ox, object[i]._oy);
3305 					StartAttack(pnum, d);
3306 				} else {
3307 					TryDisarm(pnum, i);
3308 					OperateObject(pnum, i, FALSE);
3309 				}
3310 			}
3311 			break;
3312 		case ACTION_OPERATETK:
3313 			i = plr[pnum].destParam1;
3314 			if (object[i]._oBreak != 1) {
3315 				OperateObject(pnum, i, TRUE);
3316 			}
3317 			break;
3318 		case ACTION_PICKUPITEM:
3319 			if (pnum == myplr) {
3320 				i = plr[pnum].destParam1;
3321 				x = abs(plr[pnum]._px - item[i]._ix);
3322 				y = abs(plr[pnum]._py - item[i]._iy);
3323 				if (x <= 1 && y <= 1 && pcurs == CURSOR_HAND && !item[i]._iRequest) {
3324 					NetSendCmdGItem(TRUE, CMD_REQUESTGITEM, myplr, myplr, i);
3325 					item[i]._iRequest = TRUE;
3326 				}
3327 			}
3328 			break;
3329 		case ACTION_PICKUPAITEM:
3330 			if (pnum == myplr) {
3331 				i = plr[pnum].destParam1;
3332 				x = abs(plr[pnum]._px - item[i]._ix);
3333 				y = abs(plr[pnum]._py - item[i]._iy);
3334 				if (x <= 1 && y <= 1 && pcurs == CURSOR_HAND) {
3335 					NetSendCmdGItem(TRUE, CMD_REQUESTAGITEM, myplr, myplr, i);
3336 				}
3337 			}
3338 			break;
3339 		case ACTION_TALK:
3340 			if (pnum == myplr) {
3341 				TalkToTowner(pnum, plr[pnum].destParam1);
3342 			}
3343 			break;
3344 		default:
3345 			break;
3346 		}
3347 
3348 		FixPlayerLocation(pnum, plr[pnum]._pdir);
3349 		plr[pnum].destAction = ACTION_NONE;
3350 
3351 		return;
3352 	}
3353 
3354 	if (plr[pnum]._pmode == PM_ATTACK && plr[pnum]._pAnimFrame > plr[myplr]._pAFNum) {
3355 		if (plr[pnum].destAction == ACTION_ATTACK) {
3356 			d = GetDirection(plr[pnum]._pfutx, plr[pnum]._pfuty, plr[pnum].destParam1, plr[pnum].destParam2);
3357 			StartAttack(pnum, d);
3358 			plr[pnum].destAction = ACTION_NONE;
3359 		} else if (plr[pnum].destAction == ACTION_ATTACKMON) {
3360 			i = plr[pnum].destParam1;
3361 			x = abs(plr[pnum]._px - monster[i]._mfutx);
3362 			y = abs(plr[pnum]._py - monster[i]._mfuty);
3363 			if (x <= 1 && y <= 1) {
3364 				d = GetDirection(plr[pnum]._pfutx, plr[pnum]._pfuty, monster[i]._mfutx, monster[i]._mfuty);
3365 				StartAttack(pnum, d);
3366 			}
3367 			plr[pnum].destAction = ACTION_NONE;
3368 		} else if (plr[pnum].destAction == ACTION_ATTACKPLR) {
3369 			i = plr[pnum].destParam1;
3370 			x = abs(plr[pnum]._px - plr[i]._pfutx);
3371 			y = abs(plr[pnum]._py - plr[i]._pfuty);
3372 			if (x <= 1 && y <= 1) {
3373 				d = GetDirection(plr[pnum]._pfutx, plr[pnum]._pfuty, plr[i]._pfutx, plr[i]._pfuty);
3374 				StartAttack(pnum, d);
3375 			}
3376 			plr[pnum].destAction = ACTION_NONE;
3377 		} else if (plr[pnum].destAction == ACTION_OPERATE) {
3378 			i = plr[pnum].destParam1;
3379 			x = abs(plr[pnum]._px - object[i]._ox);
3380 			y = abs(plr[pnum]._py - object[i]._oy);
3381 			if (y > 1 && dObject[object[i]._ox][object[i]._oy - 1] == -(i + 1)) {
3382 				y = abs(plr[pnum]._py - object[i]._oy + 1);
3383 			}
3384 			if (x <= 1 && y <= 1) {
3385 				if (object[i]._oBreak == 1) {
3386 					d = GetDirection(plr[pnum]._px, plr[pnum]._py, object[i]._ox, object[i]._oy);
3387 					StartAttack(pnum, d);
3388 				} else {
3389 					OperateObject(pnum, i, FALSE);
3390 				}
3391 			}
3392 		}
3393 	}
3394 
3395 	if (plr[pnum]._pmode == PM_RATTACK && plr[pnum]._pAnimFrame > plr[myplr]._pAFNum) {
3396 		if (plr[pnum].destAction == ACTION_RATTACK) {
3397 			d = GetDirection(plr[pnum]._px, plr[pnum]._py, plr[pnum].destParam1, plr[pnum].destParam2);
3398 			StartRangeAttack(pnum, d, plr[pnum].destParam1, plr[pnum].destParam2);
3399 			plr[pnum].destAction = ACTION_NONE;
3400 		} else if (plr[pnum].destAction == ACTION_RATTACKMON) {
3401 			i = plr[pnum].destParam1;
3402 			d = GetDirection(plr[pnum]._px, plr[pnum]._py, monster[i]._mfutx, monster[i]._mfuty);
3403 			StartRangeAttack(pnum, d, monster[i]._mfutx, monster[i]._mfuty);
3404 			plr[pnum].destAction = ACTION_NONE;
3405 		} else if (plr[pnum].destAction == ACTION_RATTACKPLR) {
3406 			i = plr[pnum].destParam1;
3407 			d = GetDirection(plr[pnum]._px, plr[pnum]._py, plr[i]._pfutx, plr[i]._pfuty);
3408 			StartRangeAttack(pnum, d, plr[i]._pfutx, plr[i]._pfuty);
3409 			plr[pnum].destAction = ACTION_NONE;
3410 		}
3411 	}
3412 
3413 	if (plr[pnum]._pmode == PM_SPELL && plr[pnum]._pAnimFrame > plr[pnum]._pSFNum) {
3414 		if (plr[pnum].destAction == ACTION_SPELL) {
3415 			d = GetDirection(plr[pnum]._px, plr[pnum]._py, plr[pnum].destParam1, plr[pnum].destParam2);
3416 			StartSpell(pnum, d, plr[pnum].destParam1, plr[pnum].destParam2);
3417 			plr[pnum].destAction = ACTION_NONE;
3418 		} else if (plr[pnum].destAction == ACTION_SPELLMON) {
3419 			i = plr[pnum].destParam1;
3420 			d = GetDirection(plr[pnum]._px, plr[pnum]._py, monster[i]._mfutx, monster[i]._mfuty);
3421 			StartSpell(pnum, d, monster[i]._mfutx, monster[i]._mfuty);
3422 			plr[pnum].destAction = ACTION_NONE;
3423 		} else if (plr[pnum].destAction == ACTION_SPELLPLR) {
3424 			i = plr[pnum].destParam1;
3425 			d = GetDirection(plr[pnum]._px, plr[pnum]._py, plr[i]._pfutx, plr[i]._pfuty);
3426 			StartSpell(pnum, d, plr[i]._pfutx, plr[i]._pfuty);
3427 			plr[pnum].destAction = ACTION_NONE;
3428 		}
3429 	}
3430 }
3431 
3432 BOOL PlrDeathModeOK(int p)
3433 {
3434 	if (p != myplr) {
3435 		return TRUE;
3436 	}
3437 
3438 	if ((DWORD)p >= MAX_PLRS) {
3439 		app_fatal("PlrDeathModeOK: illegal player %d", p);
3440 	}
3441 
3442 	if (plr[p]._pmode == PM_DEATH) {
3443 		return TRUE;
3444 	} else if (plr[p]._pmode == PM_QUIT) {
3445 		return TRUE;
3446 	} else if (plr[p]._pmode == PM_NEWLVL) {
3447 		return TRUE;
3448 	}
3449 
3450 	return FALSE;
3451 }
3452 
3453 void ValidatePlayer()
3454 {
3455 	int gt, i, b;
3456 
3457 	if ((DWORD)myplr >= MAX_PLRS) {
3458 		app_fatal("ValidatePlayer: illegal player %d", myplr);
3459 	}
3460 	if (plr[myplr]._pLevel > MAXCHARLEVEL - 1)
3461 		plr[myplr]._pLevel = MAXCHARLEVEL - 1;
3462 	if (plr[myplr]._pExperience > plr[myplr]._pNextExper) {
3463 		plr[myplr]._pExperience = plr[myplr]._pNextExper;
3464 		if (sgOptions.Gameplay.bExperienceBar) {
3465 			force_redraw = 255;
3466 		}
3467 	}
3468 
3469 	gt = 0;
3470 	for (i = 0; i < plr[myplr]._pNumInv; i++) {
3471 		if (plr[myplr].InvList[i]._itype == ITYPE_GOLD) {
3472 			int maxGold = GOLD_MAX_LIMIT;
3473 			if (gbIsHellfire) {
3474 				maxGold *= 2;
3475 			}
3476 			if (plr[myplr].InvList[i]._ivalue > maxGold) {
3477 				plr[myplr].InvList[i]._ivalue = maxGold;
3478 			}
3479 			gt += plr[myplr].InvList[i]._ivalue;
3480 		}
3481 	}
3482 	if (gt != plr[myplr]._pGold)
3483 		plr[myplr]._pGold = gt;
3484 
3485 	plr_class pc = plr[myplr]._pClass;
3486 	if (plr[myplr]._pBaseStr > MaxStats[pc][ATTRIB_STR]) {
3487 		plr[myplr]._pBaseStr = MaxStats[pc][ATTRIB_STR];
3488 	}
3489 	if (plr[myplr]._pBaseMag > MaxStats[pc][ATTRIB_MAG]) {
3490 		plr[myplr]._pBaseMag = MaxStats[pc][ATTRIB_MAG];
3491 	}
3492 	if (plr[myplr]._pBaseDex > MaxStats[pc][ATTRIB_DEX]) {
3493 		plr[myplr]._pBaseDex = MaxStats[pc][ATTRIB_DEX];
3494 	}
3495 	if (plr[myplr]._pBaseVit > MaxStats[pc][ATTRIB_VIT]) {
3496 		plr[myplr]._pBaseVit = MaxStats[pc][ATTRIB_VIT];
3497 	}
3498 
3499 	Uint64 msk = 0;
3500 	for (b = SPL_FIREBOLT; b < MAX_SPELLS; b++) {
3501 		if (GetSpellBookLevel((spell_id)b) != -1) {
3502 			msk |= GetSpellBitmask(b);
3503 			if (plr[myplr]._pSplLvl[b] > MAX_SPELL_LEVEL)
3504 				plr[myplr]._pSplLvl[b] = MAX_SPELL_LEVEL;
3505 		}
3506 	}
3507 
3508 	plr[myplr]._pMemSpells &= msk;
3509 }
3510 
3511 static void CheckCheatStats(int pnum)
3512 {
3513 	if (plr[pnum]._pStrength > 750) {
3514 		plr[pnum]._pStrength = 750;
3515 	}
3516 
3517 	if (plr[pnum]._pDexterity > 750) {
3518 		plr[pnum]._pDexterity = 750;
3519 	}
3520 
3521 	if (plr[pnum]._pMagic > 750) {
3522 		plr[pnum]._pMagic = 750;
3523 	}
3524 
3525 	if (plr[pnum]._pVitality > 750) {
3526 		plr[pnum]._pVitality = 750;
3527 	}
3528 
3529 	if (plr[pnum]._pHitPoints > 128000) {
3530 		plr[pnum]._pHitPoints = 128000;
3531 	}
3532 
3533 	if (plr[pnum]._pMana > 128000) {
3534 		plr[pnum]._pMana = 128000;
3535 	}
3536 }
3537 
3538 void ProcessPlayers()
3539 {
3540 	if ((DWORD)myplr >= MAX_PLRS) {
3541 		app_fatal("ProcessPlayers: illegal player %d", myplr);
3542 	}
3543 
3544 	if (plr[myplr].pLvlLoad > 0) {
3545 		plr[myplr].pLvlLoad--;
3546 	}
3547 
3548 	if (sfxdelay > 0) {
3549 		sfxdelay--;
3550 		if (sfxdelay == 0) {
3551 			switch (sfxdnum) {
3552 			case USFX_DEFILER1:
3553 				InitQTextMsg(TEXT_DEFILER1);
3554 				break;
3555 			case USFX_DEFILER2:
3556 				InitQTextMsg(TEXT_DEFILER2);
3557 				break;
3558 			case USFX_DEFILER3:
3559 				InitQTextMsg(TEXT_DEFILER3);
3560 				break;
3561 			case USFX_DEFILER4:
3562 				InitQTextMsg(TEXT_DEFILER4);
3563 				break;
3564 			default:
3565 				PlaySFX(sfxdnum);
3566 			}
3567 		}
3568 	}
3569 
3570 	ValidatePlayer();
3571 
3572 	for (int pnum = 0; pnum < MAX_PLRS; pnum++) {
3573 		if (plr[pnum].plractive && currlevel == plr[pnum].plrlevel && (pnum == myplr || !plr[pnum]._pLvlChanging)) {
3574 			CheckCheatStats(pnum);
3575 
3576 			if (!PlrDeathModeOK(pnum) && (plr[pnum]._pHitPoints >> 6) <= 0) {
3577 				SyncPlrKill(pnum, -1);
3578 			}
3579 
3580 			if (pnum == myplr) {
3581 				if ((plr[pnum]._pIFlags & ISPL_DRAINLIFE) && currlevel != 0) {
3582 					plr[pnum]._pHitPoints -= 4;
3583 					plr[pnum]._pHPBase -= 4;
3584 					if ((plr[pnum]._pHitPoints >> 6) <= 0) {
3585 						SyncPlrKill(pnum, 0);
3586 					}
3587 					drawhpflag = TRUE;
3588 				}
3589 				if (plr[pnum]._pIFlags & ISPL_NOMANA && plr[pnum]._pManaBase > 0) {
3590 					plr[pnum]._pManaBase -= plr[pnum]._pMana;
3591 					plr[pnum]._pMana = 0;
3592 					drawmanaflag = TRUE;
3593 				}
3594 			}
3595 
3596 			bool tplayer = false;
3597 			do {
3598 				switch (plr[pnum]._pmode) {
3599 				case PM_STAND:
3600 					tplayer = PM_DoStand(pnum);
3601 					break;
3602 				case PM_WALK:
3603 				case PM_WALK2:
3604 				case PM_WALK3:
3605 					tplayer = PM_DoWalk(pnum, plr[pnum]._pmode);
3606 					break;
3607 				case PM_ATTACK:
3608 					tplayer = PM_DoAttack(pnum);
3609 					break;
3610 				case PM_RATTACK:
3611 					tplayer = PM_DoRangeAttack(pnum);
3612 					break;
3613 				case PM_BLOCK:
3614 					tplayer = PM_DoBlock(pnum);
3615 					break;
3616 				case PM_SPELL:
3617 					tplayer = PM_DoSpell(pnum);
3618 					break;
3619 				case PM_GOTHIT:
3620 					tplayer = PM_DoGotHit(pnum);
3621 					break;
3622 				case PM_DEATH:
3623 					tplayer = PM_DoDeath(pnum);
3624 					break;
3625 				case PM_NEWLVL:
3626 					tplayer = PM_DoNewLvl(pnum);
3627 					break;
3628 				case PM_QUIT:
3629 					tplayer = false;
3630 					break;
3631 				}
3632 				CheckNewPath(pnum);
3633 			} while (tplayer);
3634 
3635 			plr[pnum]._pAnimCnt++;
3636 			if (plr[pnum]._pAnimCnt > plr[pnum]._pAnimDelay) {
3637 				plr[pnum]._pAnimCnt = 0;
3638 				plr[pnum]._pAnimFrame++;
3639 				if (plr[pnum]._pAnimFrame > plr[pnum]._pAnimLen) {
3640 					plr[pnum]._pAnimFrame = 1;
3641 				}
3642 			}
3643 		}
3644 	}
3645 }
3646 
3647 void ClrPlrPath(int pnum)
3648 {
3649 	if ((DWORD)pnum >= MAX_PLRS) {
3650 		app_fatal("ClrPlrPath: illegal player %d", pnum);
3651 	}
3652 
3653 	memset(plr[pnum].walkpath, WALK_NONE, sizeof(plr[pnum].walkpath));
3654 }
3655 
3656 BOOL PosOkPlayer(int pnum, int x, int y)
3657 {
3658 	DWORD p;
3659 	char bv;
3660 
3661 	if (x < 0 || x >= MAXDUNX || y < 0 || y >= MAXDUNY)
3662 		return FALSE;
3663 	if (dPiece[x][y] == 0)
3664 		return FALSE;
3665 	if (SolidLoc(x, y))
3666 		return FALSE;
3667 	if (dPlayer[x][y] != 0) {
3668 		if (dPlayer[x][y] > 0) {
3669 			p = dPlayer[x][y] - 1;
3670 		} else {
3671 			p = -(dPlayer[x][y] + 1);
3672 		}
3673 		if (p != pnum
3674 		    && p < MAX_PLRS
3675 		    && plr[p]._pHitPoints != 0) {
3676 			return FALSE;
3677 		}
3678 	}
3679 
3680 	if (dMonster[x][y] != 0) {
3681 		if (currlevel == 0) {
3682 			return FALSE;
3683 		}
3684 		if (dMonster[x][y] <= 0) {
3685 			return FALSE;
3686 		}
3687 		if ((monster[dMonster[x][y] - 1]._mhitpoints >> 6) > 0) {
3688 			return FALSE;
3689 		}
3690 	}
3691 
3692 	if (dObject[x][y] != 0) {
3693 		if (dObject[x][y] > 0) {
3694 			bv = dObject[x][y] - 1;
3695 		} else {
3696 			bv = -(dObject[x][y] + 1);
3697 		}
3698 		if (object[bv]._oSolidFlag) {
3699 			return FALSE;
3700 		}
3701 	}
3702 
3703 	return TRUE;
3704 }
3705 
3706 void MakePlrPath(int pnum, int xx, int yy, BOOL endspace)
3707 {
3708 	int path;
3709 
3710 	if ((DWORD)pnum >= MAX_PLRS) {
3711 		app_fatal("MakePlrPath: illegal player %d", pnum);
3712 	}
3713 
3714 	plr[pnum]._ptargx = xx;
3715 	plr[pnum]._ptargy = yy;
3716 	if (plr[pnum]._pfutx == xx && plr[pnum]._pfuty == yy) {
3717 		return;
3718 	}
3719 
3720 	path = FindPath(PosOkPlayer, pnum, plr[pnum]._pfutx, plr[pnum]._pfuty, xx, yy, plr[pnum].walkpath);
3721 	if (!path) {
3722 		return;
3723 	}
3724 
3725 	if (!endspace) {
3726 		path--;
3727 
3728 		switch (plr[pnum].walkpath[path]) {
3729 		case WALK_NE:
3730 			yy++;
3731 			break;
3732 		case WALK_NW:
3733 			xx++;
3734 			break;
3735 		case WALK_SE:
3736 			xx--;
3737 			break;
3738 		case WALK_SW:
3739 			yy--;
3740 			break;
3741 		case WALK_N:
3742 			xx++;
3743 			yy++;
3744 			break;
3745 		case WALK_E:
3746 			xx--;
3747 			yy++;
3748 			break;
3749 		case WALK_S:
3750 			xx--;
3751 			yy--;
3752 			break;
3753 		case WALK_W:
3754 			xx++;
3755 			yy--;
3756 			break;
3757 		}
3758 
3759 		plr[pnum]._ptargx = xx;
3760 		plr[pnum]._ptargy = yy;
3761 	}
3762 
3763 	plr[pnum].walkpath[path] = WALK_NONE;
3764 }
3765 
3766 void CheckPlrSpell()
3767 {
3768 	BOOL addflag = FALSE;
3769 	int rspell, sd, sl;
3770 
3771 	if ((DWORD)myplr >= MAX_PLRS) {
3772 		app_fatal("CheckPlrSpell: illegal player %d", myplr);
3773 	}
3774 
3775 	rspell = plr[myplr]._pRSpell;
3776 	if (rspell == SPL_INVALID) {
3777 		if (plr[myplr]._pClass == PC_WARRIOR) {
3778 			PlaySFX(PS_WARR34);
3779 		} else if (plr[myplr]._pClass == PC_ROGUE) {
3780 			PlaySFX(PS_ROGUE34);
3781 		} else if (plr[myplr]._pClass == PC_SORCERER) {
3782 			PlaySFX(PS_MAGE34);
3783 		} else if (plr[myplr]._pClass == PC_MONK) {
3784 			PlaySFX(PS_MONK34);
3785 		} else if (plr[myplr]._pClass == PC_BARD) {
3786 			PlaySFX(PS_ROGUE34);
3787 		} else if (plr[myplr]._pClass == PC_BARBARIAN) {
3788 			PlaySFX(PS_WARR34);
3789 		}
3790 		return;
3791 	}
3792 
3793 	if (leveltype == DTYPE_TOWN && !spelldata[rspell].sTownSpell) {
3794 		if (plr[myplr]._pClass == PC_WARRIOR) {
3795 			PlaySFX(PS_WARR27);
3796 		} else if (plr[myplr]._pClass == PC_ROGUE) {
3797 			PlaySFX(PS_ROGUE27);
3798 		} else if (plr[myplr]._pClass == PC_SORCERER) {
3799 			PlaySFX(PS_MAGE27);
3800 		} else if (plr[myplr]._pClass == PC_MONK) {
3801 			PlaySFX(PS_MONK27);
3802 		} else if (plr[myplr]._pClass == PC_BARD) {
3803 			PlaySFX(PS_ROGUE27);
3804 		} else if (plr[myplr]._pClass == PC_BARBARIAN) {
3805 			PlaySFX(PS_WARR27);
3806 		}
3807 		return;
3808 	}
3809 
3810 	if (!sgbControllerActive) {
3811 		if (pcurs != CURSOR_HAND)
3812 			return;
3813 
3814 		if (MouseY >= PANEL_TOP && MouseX >= PANEL_LEFT && MouseX <= RIGHT_PANEL) // inside main panel
3815 			return;
3816 
3817 		if (
3818 		    ((chrflag || questlog) && MouseX < SPANEL_WIDTH && MouseY < SPANEL_HEIGHT)    // inside left panel
3819 		    || ((invflag || sbookflag) && MouseX > RIGHT_PANEL && MouseY < SPANEL_HEIGHT) // inside right panel
3820 		) {
3821 			if (rspell != SPL_HEAL
3822 			    && rspell != SPL_IDENTIFY
3823 			    && rspell != SPL_REPAIR
3824 			    && rspell != SPL_INFRA
3825 			    && rspell != SPL_RECHARGE)
3826 				return;
3827 		}
3828 	}
3829 
3830 	switch (plr[myplr]._pRSplType) {
3831 	case RSPLTYPE_SKILL:
3832 	case RSPLTYPE_SPELL:
3833 		addflag = CheckSpell(myplr, rspell, plr[myplr]._pRSplType, FALSE);
3834 		break;
3835 	case RSPLTYPE_SCROLL:
3836 		addflag = UseScroll();
3837 		break;
3838 	case RSPLTYPE_CHARGES:
3839 		addflag = UseStaff();
3840 		break;
3841 	case RSPLTYPE_INVALID:
3842 		return;
3843 	}
3844 
3845 	if (addflag) {
3846 		if (plr[myplr]._pRSpell == SPL_FIREWALL || plr[myplr]._pRSpell == SPL_LIGHTWALL) {
3847 			sd = GetDirection(plr[myplr]._px, plr[myplr]._py, cursmx, cursmy);
3848 			sl = GetSpellLevel(myplr, plr[myplr]._pRSpell);
3849 			NetSendCmdLocParam3(TRUE, CMD_SPELLXYD, cursmx, cursmy, plr[myplr]._pRSpell, sd, sl);
3850 		} else if (pcursmonst != -1) {
3851 			sl = GetSpellLevel(myplr, plr[myplr]._pRSpell);
3852 			NetSendCmdParam3(TRUE, CMD_SPELLID, pcursmonst, plr[myplr]._pRSpell, sl);
3853 		} else if (pcursplr != -1) {
3854 			sl = GetSpellLevel(myplr, plr[myplr]._pRSpell);
3855 			NetSendCmdParam3(TRUE, CMD_SPELLPID, pcursplr, plr[myplr]._pRSpell, sl);
3856 		} else { //145
3857 			sl = GetSpellLevel(myplr, plr[myplr]._pRSpell);
3858 			NetSendCmdLocParam2(TRUE, CMD_SPELLXY, cursmx, cursmy, plr[myplr]._pRSpell, sl);
3859 		}
3860 		return;
3861 	}
3862 
3863 	if (plr[myplr]._pRSplType == RSPLTYPE_SPELL) {
3864 		if (plr[myplr]._pClass == PC_WARRIOR) {
3865 			PlaySFX(PS_WARR35);
3866 		} else if (plr[myplr]._pClass == PC_ROGUE) {
3867 			PlaySFX(PS_ROGUE35);
3868 		} else if (plr[myplr]._pClass == PC_SORCERER) {
3869 			PlaySFX(PS_MAGE35);
3870 		} else if (plr[myplr]._pClass == PC_MONK) {
3871 			PlaySFX(PS_MONK35);
3872 		} else if (plr[myplr]._pClass == PC_BARD) {
3873 			PlaySFX(PS_ROGUE35);
3874 		} else if (plr[myplr]._pClass == PC_BARBARIAN) {
3875 			PlaySFX(PS_WARR35);
3876 		}
3877 	}
3878 }
3879 
3880 void SyncPlrAnim(int pnum)
3881 {
3882 	int dir, sType;
3883 
3884 	if ((DWORD)pnum >= MAX_PLRS) {
3885 		app_fatal("SyncPlrAnim: illegal player %d", pnum);
3886 	}
3887 
3888 	dir = plr[pnum]._pdir;
3889 	switch (plr[pnum]._pmode) {
3890 	case PM_STAND:
3891 		plr[pnum]._pAnimData = plr[pnum]._pNAnim[dir];
3892 		break;
3893 	case PM_WALK:
3894 	case PM_WALK2:
3895 	case PM_WALK3:
3896 		plr[pnum]._pAnimData = plr[pnum]._pWAnim[dir];
3897 		break;
3898 	case PM_ATTACK:
3899 		plr[pnum]._pAnimData = plr[pnum]._pAAnim[dir];
3900 		break;
3901 	case PM_RATTACK:
3902 		plr[pnum]._pAnimData = plr[pnum]._pAAnim[dir];
3903 		break;
3904 	case PM_BLOCK:
3905 		plr[pnum]._pAnimData = plr[pnum]._pBAnim[dir];
3906 		break;
3907 	case PM_SPELL:
3908 		if (pnum == myplr)
3909 			sType = spelldata[plr[pnum]._pSpell].sType;
3910 		else
3911 			sType = STYPE_FIRE;
3912 		if (sType == STYPE_FIRE)
3913 			plr[pnum]._pAnimData = plr[pnum]._pFAnim[dir];
3914 		if (sType == STYPE_LIGHTNING)
3915 			plr[pnum]._pAnimData = plr[pnum]._pLAnim[dir];
3916 		if (sType == STYPE_MAGIC)
3917 			plr[pnum]._pAnimData = plr[pnum]._pTAnim[dir];
3918 		break;
3919 	case PM_GOTHIT:
3920 		plr[pnum]._pAnimData = plr[pnum]._pHAnim[dir];
3921 		break;
3922 	case PM_NEWLVL:
3923 		plr[pnum]._pAnimData = plr[pnum]._pNAnim[dir];
3924 		break;
3925 	case PM_DEATH:
3926 		plr[pnum]._pAnimData = plr[pnum]._pDAnim[dir];
3927 		break;
3928 	case PM_QUIT:
3929 		plr[pnum]._pAnimData = plr[pnum]._pNAnim[dir];
3930 		break;
3931 	default:
3932 		app_fatal("SyncPlrAnim");
3933 	}
3934 }
3935 
3936 void SyncInitPlrPos(int pnum)
3937 {
3938 	int x, y, xx, yy, range;
3939 	DWORD i;
3940 	BOOL posOk;
3941 
3942 	plr[pnum]._ptargx = plr[pnum]._px;
3943 	plr[pnum]._ptargy = plr[pnum]._py;
3944 
3945 	if (!gbIsMultiplayer || plr[pnum].plrlevel != currlevel) {
3946 		return;
3947 	}
3948 
3949 	for (i = 0; i < 8; i++) {
3950 		x = plr[pnum]._px + plrxoff2[i];
3951 		y = plr[pnum]._py + plryoff2[i];
3952 		if (PosOkPlayer(pnum, x, y)) {
3953 			break;
3954 		}
3955 	}
3956 
3957 	if (!PosOkPlayer(pnum, x, y)) {
3958 		posOk = FALSE;
3959 		for (range = 1; range < 50 && !posOk; range++) {
3960 			for (yy = -range; yy <= range && !posOk; yy++) {
3961 				y = yy + plr[pnum]._py;
3962 				for (xx = -range; xx <= range && !posOk; xx++) {
3963 					x = xx + plr[pnum]._px;
3964 					if (PosOkPlayer(pnum, x, y) && !PosOkPortal(currlevel, x, y)) {
3965 						posOk = TRUE;
3966 					}
3967 				}
3968 			}
3969 		}
3970 	}
3971 
3972 	plr[pnum]._px = x;
3973 	plr[pnum]._py = y;
3974 	dPlayer[x][y] = pnum + 1;
3975 
3976 	if (pnum == myplr) {
3977 		plr[pnum]._pfutx = x;
3978 		plr[pnum]._pfuty = y;
3979 		plr[pnum]._ptargx = x;
3980 		plr[pnum]._ptargy = y;
3981 		ViewX = x;
3982 		ViewY = y;
3983 	}
3984 }
3985 
3986 void SyncInitPlr(int pnum)
3987 {
3988 	if ((DWORD)pnum >= MAX_PLRS) {
3989 		app_fatal("SyncInitPlr: illegal player %d", pnum);
3990 	}
3991 
3992 	SetPlrAnims(pnum);
3993 	SyncInitPlrPos(pnum);
3994 }
3995 
3996 void CheckStats(int p)
3997 {
3998 	int c, i;
3999 
4000 	if ((DWORD)p >= MAX_PLRS) {
4001 		app_fatal("CheckStats: illegal player %d", p);
4002 	}
4003 
4004 	if (plr[p]._pClass == PC_WARRIOR) {
4005 		c = PC_WARRIOR;
4006 	} else if (plr[p]._pClass == PC_ROGUE) {
4007 		c = PC_ROGUE;
4008 	} else if (plr[p]._pClass == PC_SORCERER) {
4009 		c = PC_SORCERER;
4010 	} else if (plr[p]._pClass == PC_MONK) {
4011 		c = PC_MONK;
4012 	} else if (plr[p]._pClass == PC_BARD) {
4013 		c = PC_BARD;
4014 	} else if (plr[p]._pClass == PC_BARBARIAN) {
4015 		c = PC_BARBARIAN;
4016 	}
4017 
4018 	for (i = 0; i < 4; i++) {
4019 		switch (i) {
4020 		case ATTRIB_STR:
4021 			if (plr[p]._pBaseStr > MaxStats[c][ATTRIB_STR]) {
4022 				plr[p]._pBaseStr = MaxStats[c][ATTRIB_STR];
4023 			} else if (plr[p]._pBaseStr < 0) {
4024 				plr[p]._pBaseStr = 0;
4025 			}
4026 			break;
4027 		case ATTRIB_MAG:
4028 			if (plr[p]._pBaseMag > MaxStats[c][ATTRIB_MAG]) {
4029 				plr[p]._pBaseMag = MaxStats[c][ATTRIB_MAG];
4030 			} else if (plr[p]._pBaseMag < 0) {
4031 				plr[p]._pBaseMag = 0;
4032 			}
4033 			break;
4034 		case ATTRIB_DEX:
4035 			if (plr[p]._pBaseDex > MaxStats[c][ATTRIB_DEX]) {
4036 				plr[p]._pBaseDex = MaxStats[c][ATTRIB_DEX];
4037 			} else if (plr[p]._pBaseDex < 0) {
4038 				plr[p]._pBaseDex = 0;
4039 			}
4040 			break;
4041 		case ATTRIB_VIT:
4042 			if (plr[p]._pBaseVit > MaxStats[c][ATTRIB_VIT]) {
4043 				plr[p]._pBaseVit = MaxStats[c][ATTRIB_VIT];
4044 			} else if (plr[p]._pBaseVit < 0) {
4045 				plr[p]._pBaseVit = 0;
4046 			}
4047 			break;
4048 		}
4049 	}
4050 }
4051 
4052 void ModifyPlrStr(int p, int l)
4053 {
4054 	int max;
4055 
4056 	if ((DWORD)p >= MAX_PLRS) {
4057 		app_fatal("ModifyPlrStr: illegal player %d", p);
4058 	}
4059 
4060 	max = MaxStats[plr[p]._pClass][ATTRIB_STR];
4061 	if (plr[p]._pBaseStr + l > max) {
4062 		l = max - plr[p]._pBaseStr;
4063 	}
4064 
4065 	plr[p]._pStrength += l;
4066 	plr[p]._pBaseStr += l;
4067 
4068 	if (plr[p]._pClass == PC_ROGUE) {
4069 		plr[p]._pDamageMod = plr[p]._pLevel * (plr[p]._pStrength + plr[p]._pDexterity) / 200;
4070 	} else {
4071 		plr[p]._pDamageMod = plr[p]._pLevel * plr[p]._pStrength / 100;
4072 	}
4073 
4074 	CalcPlrInv(p, TRUE);
4075 
4076 	if (p == myplr) {
4077 		NetSendCmdParam1(FALSE, CMD_SETSTR, plr[p]._pBaseStr);
4078 	}
4079 }
4080 
4081 void ModifyPlrMag(int p, int l)
4082 {
4083 	int max, ms;
4084 
4085 	if ((DWORD)p >= MAX_PLRS) {
4086 		app_fatal("ModifyPlrMag: illegal player %d", p);
4087 	}
4088 
4089 	max = MaxStats[plr[p]._pClass][ATTRIB_MAG];
4090 	if (plr[p]._pBaseMag + l > max) {
4091 		l = max - plr[p]._pBaseMag;
4092 	}
4093 
4094 	plr[p]._pMagic += l;
4095 	plr[p]._pBaseMag += l;
4096 
4097 	ms = l << 6;
4098 	if (plr[p]._pClass == PC_SORCERER) {
4099 		ms <<= 1;
4100 	} else if (plr[p]._pClass == PC_BARD) {
4101 		ms += ms >> 1;
4102 	}
4103 
4104 	plr[p]._pMaxManaBase += ms;
4105 	plr[p]._pMaxMana += ms;
4106 	if (!(plr[p]._pIFlags & ISPL_NOMANA)) {
4107 		plr[p]._pManaBase += ms;
4108 		plr[p]._pMana += ms;
4109 	}
4110 
4111 	CalcPlrInv(p, TRUE);
4112 
4113 	if (p == myplr) {
4114 		NetSendCmdParam1(FALSE, CMD_SETMAG, plr[p]._pBaseMag);
4115 	}
4116 }
4117 
4118 void ModifyPlrDex(int p, int l)
4119 {
4120 	int max;
4121 
4122 	if ((DWORD)p >= MAX_PLRS) {
4123 		app_fatal("ModifyPlrDex: illegal player %d", p);
4124 	}
4125 
4126 	max = MaxStats[plr[p]._pClass][ATTRIB_DEX];
4127 	if (plr[p]._pBaseDex + l > max) {
4128 		l = max - plr[p]._pBaseDex;
4129 	}
4130 
4131 	plr[p]._pDexterity += l;
4132 	plr[p]._pBaseDex += l;
4133 	CalcPlrInv(p, TRUE);
4134 
4135 	if (plr[p]._pClass == PC_ROGUE) {
4136 		plr[p]._pDamageMod = plr[p]._pLevel * (plr[p]._pDexterity + plr[p]._pStrength) / 200;
4137 	}
4138 
4139 	if (p == myplr) {
4140 		NetSendCmdParam1(FALSE, CMD_SETDEX, plr[p]._pBaseDex);
4141 	}
4142 }
4143 
4144 void ModifyPlrVit(int p, int l)
4145 {
4146 	int max, ms;
4147 
4148 	if ((DWORD)p >= MAX_PLRS) {
4149 		app_fatal("ModifyPlrVit: illegal player %d", p);
4150 	}
4151 
4152 	max = MaxStats[plr[p]._pClass][ATTRIB_VIT];
4153 	if (plr[p]._pBaseVit + l > max) {
4154 		l = max - plr[p]._pBaseVit;
4155 	}
4156 
4157 	plr[p]._pVitality += l;
4158 	plr[p]._pBaseVit += l;
4159 
4160 	ms = l << 6;
4161 	if (plr[p]._pClass == PC_WARRIOR) {
4162 		ms <<= 1;
4163 	} else if (plr[p]._pClass == PC_BARBARIAN) {
4164 		ms <<= 1;
4165 	}
4166 
4167 	plr[p]._pHPBase += ms;
4168 	plr[p]._pMaxHPBase += ms;
4169 	plr[p]._pHitPoints += ms;
4170 	plr[p]._pMaxHP += ms;
4171 
4172 	CalcPlrInv(p, TRUE);
4173 
4174 	if (p == myplr) {
4175 		NetSendCmdParam1(FALSE, CMD_SETVIT, plr[p]._pBaseVit);
4176 	}
4177 }
4178 
4179 void SetPlayerHitPoints(int pnum, int val)
4180 {
4181 	if ((DWORD)pnum >= MAX_PLRS) {
4182 		app_fatal("SetPlayerHitPoints: illegal player %d", pnum);
4183 	}
4184 
4185 	plr[pnum]._pHitPoints = val;
4186 	plr[pnum]._pHPBase = val + plr[pnum]._pMaxHPBase - plr[pnum]._pMaxHP;
4187 
4188 	if (pnum == myplr) {
4189 		drawhpflag = TRUE;
4190 	}
4191 }
4192 
4193 void SetPlrStr(int p, int v)
4194 {
4195 	int dm;
4196 
4197 	if ((DWORD)p >= MAX_PLRS) {
4198 		app_fatal("SetPlrStr: illegal player %d", p);
4199 	}
4200 
4201 	plr[p]._pBaseStr = v;
4202 	CalcPlrInv(p, TRUE);
4203 
4204 	if (plr[p]._pClass == PC_ROGUE) {
4205 		dm = plr[p]._pLevel * (plr[p]._pStrength + plr[p]._pDexterity) / 200;
4206 	} else {
4207 		dm = plr[p]._pLevel * plr[p]._pStrength / 100;
4208 	}
4209 
4210 	plr[p]._pDamageMod = dm;
4211 }
4212 
4213 void SetPlrMag(int p, int v)
4214 {
4215 	int m;
4216 
4217 	if ((DWORD)p >= MAX_PLRS) {
4218 		app_fatal("SetPlrMag: illegal player %d", p);
4219 	}
4220 
4221 	plr[p]._pBaseMag = v;
4222 
4223 	m = v << 6;
4224 	if (plr[p]._pClass == PC_SORCERER) {
4225 		m <<= 1;
4226 	} else if (plr[p]._pClass == PC_BARD) {
4227 		m += m >> 1;
4228 	}
4229 
4230 	plr[p]._pMaxManaBase = m;
4231 	plr[p]._pMaxMana = m;
4232 	CalcPlrInv(p, TRUE);
4233 }
4234 
4235 void SetPlrDex(int p, int v)
4236 {
4237 	int dm;
4238 
4239 	if ((DWORD)p >= MAX_PLRS) {
4240 		app_fatal("SetPlrDex: illegal player %d", p);
4241 	}
4242 
4243 	plr[p]._pBaseDex = v;
4244 	CalcPlrInv(p, TRUE);
4245 
4246 	if (plr[p]._pClass == PC_ROGUE) {
4247 		dm = plr[p]._pLevel * (plr[p]._pStrength + plr[p]._pDexterity) / 200;
4248 	} else {
4249 		dm = plr[p]._pStrength * plr[p]._pLevel / 100;
4250 	}
4251 
4252 	plr[p]._pDamageMod = dm;
4253 }
4254 
4255 void SetPlrVit(int p, int v)
4256 {
4257 	int hp;
4258 
4259 	if ((DWORD)p >= MAX_PLRS) {
4260 		app_fatal("SetPlrVit: illegal player %d", p);
4261 	}
4262 
4263 	plr[p]._pBaseVit = v;
4264 
4265 	hp = v << 6;
4266 	if (plr[p]._pClass == PC_WARRIOR) {
4267 		hp <<= 1;
4268 	} else if (plr[p]._pClass == PC_BARBARIAN) {
4269 		hp <<= 1;
4270 	}
4271 
4272 	plr[p]._pHPBase = hp;
4273 	plr[p]._pMaxHPBase = hp;
4274 	CalcPlrInv(p, TRUE);
4275 }
4276 
4277 void InitDungMsgs(int pnum)
4278 {
4279 	if ((DWORD)pnum >= MAX_PLRS) {
4280 		app_fatal("InitDungMsgs: illegal player %d", pnum);
4281 	}
4282 
4283 	plr[pnum].pDungMsgs = 0;
4284 	plr[pnum].pDungMsgs2 = 0;
4285 }
4286 
4287 void PlayDungMsgs()
4288 {
4289 	if ((DWORD)myplr >= MAX_PLRS) {
4290 		app_fatal("PlayDungMsgs: illegal player %d", myplr);
4291 	}
4292 
4293 	if (currlevel == 1 && !plr[myplr]._pLvlVisited[1] && !gbIsMultiplayer && !(plr[myplr].pDungMsgs & DMSG_CATHEDRAL)) {
4294 		sfxdelay = 40;
4295 		if (plr[myplr]._pClass == PC_WARRIOR) {
4296 			sfxdnum = PS_WARR97;
4297 		} else if (plr[myplr]._pClass == PC_ROGUE) {
4298 			sfxdnum = PS_ROGUE97;
4299 		} else if (plr[myplr]._pClass == PC_SORCERER) {
4300 			sfxdnum = PS_MAGE97;
4301 		} else if (plr[myplr]._pClass == PC_MONK) {
4302 			sfxdnum = PS_MONK97;
4303 		} else if (plr[myplr]._pClass == PC_BARD) {
4304 			sfxdnum = PS_ROGUE97;
4305 		} else if (plr[myplr]._pClass == PC_BARBARIAN) {
4306 			sfxdnum = PS_WARR97;
4307 		}
4308 		plr[myplr].pDungMsgs = plr[myplr].pDungMsgs | DMSG_CATHEDRAL;
4309 	} else if (currlevel == 5 && !plr[myplr]._pLvlVisited[5] && !gbIsMultiplayer && !(plr[myplr].pDungMsgs & DMSG_CATACOMBS)) {
4310 		sfxdelay = 40;
4311 		if (plr[myplr]._pClass == PC_WARRIOR) {
4312 			sfxdnum = PS_WARR96B;
4313 		} else if (plr[myplr]._pClass == PC_ROGUE) {
4314 			sfxdnum = PS_ROGUE96;
4315 		} else if (plr[myplr]._pClass == PC_SORCERER) {
4316 			sfxdnum = PS_MAGE96;
4317 		} else if (plr[myplr]._pClass == PC_MONK) {
4318 			sfxdnum = PS_MONK96;
4319 		} else if (plr[myplr]._pClass == PC_BARD) {
4320 			sfxdnum = PS_ROGUE96;
4321 		} else if (plr[myplr]._pClass == PC_BARBARIAN) {
4322 			sfxdnum = PS_WARR96B;
4323 		}
4324 		plr[myplr].pDungMsgs |= DMSG_CATACOMBS;
4325 	} else if (currlevel == 9 && !plr[myplr]._pLvlVisited[9] && !gbIsMultiplayer && !(plr[myplr].pDungMsgs & DMSG_CAVES)) {
4326 		sfxdelay = 40;
4327 		if (plr[myplr]._pClass == PC_WARRIOR) {
4328 			sfxdnum = PS_WARR98;
4329 		} else if (plr[myplr]._pClass == PC_ROGUE) {
4330 			sfxdnum = PS_ROGUE98;
4331 		} else if (plr[myplr]._pClass == PC_SORCERER) {
4332 			sfxdnum = PS_MAGE98;
4333 		} else if (plr[myplr]._pClass == PC_MONK) {
4334 			sfxdnum = PS_MONK98;
4335 		} else if (plr[myplr]._pClass == PC_BARD) {
4336 			sfxdnum = PS_ROGUE98;
4337 		} else if (plr[myplr]._pClass == PC_BARBARIAN) {
4338 			sfxdnum = PS_WARR98;
4339 		}
4340 		plr[myplr].pDungMsgs |= DMSG_CAVES;
4341 	} else if (currlevel == 13 && !plr[myplr]._pLvlVisited[13] && !gbIsMultiplayer && !(plr[myplr].pDungMsgs & DMSG_HELL)) {
4342 		sfxdelay = 40;
4343 		if (plr[myplr]._pClass == PC_WARRIOR) {
4344 			sfxdnum = PS_WARR99;
4345 		} else if (plr[myplr]._pClass == PC_ROGUE) {
4346 			sfxdnum = PS_ROGUE99;
4347 		} else if (plr[myplr]._pClass == PC_SORCERER) {
4348 			sfxdnum = PS_MAGE99;
4349 		} else if (plr[myplr]._pClass == PC_MONK) {
4350 			sfxdnum = PS_MONK99;
4351 		} else if (plr[myplr]._pClass == PC_BARD) {
4352 			sfxdnum = PS_ROGUE99;
4353 		} else if (plr[myplr]._pClass == PC_BARBARIAN) {
4354 			sfxdnum = PS_WARR99;
4355 		}
4356 		plr[myplr].pDungMsgs |= DMSG_HELL;
4357 	} else if (currlevel == 16 && !plr[myplr]._pLvlVisited[15] && !gbIsMultiplayer && !(plr[myplr].pDungMsgs & DMSG_DIABLO)) { // BUGFIX: _pLvlVisited should check 16 or this message will never play
4358 		sfxdelay = 40;
4359 		if (plr[myplr]._pClass == PC_WARRIOR || plr[myplr]._pClass == PC_ROGUE || plr[myplr]._pClass == PC_SORCERER || plr[myplr]._pClass == PC_MONK || plr[myplr]._pClass == PC_BARD || plr[myplr]._pClass == PC_BARBARIAN) {
4360 			sfxdnum = PS_DIABLVLINT;
4361 		}
4362 		plr[myplr].pDungMsgs |= DMSG_DIABLO;
4363 	} else if (currlevel == 17 && !plr[myplr]._pLvlVisited[17] && !gbIsMultiplayer && !(plr[myplr].pDungMsgs2 & 1)) {
4364 		sfxdelay = 10;
4365 		sfxdnum = USFX_DEFILER1;
4366 		quests[Q_DEFILER]._qactive = 2;
4367 		quests[Q_DEFILER]._qlog = 1;
4368 		quests[Q_DEFILER]._qmsg = TEXT_DEFILER1;
4369 		plr[myplr].pDungMsgs2 |= 1;
4370 	} else if (currlevel == 19 && !plr[myplr]._pLvlVisited[19] && !gbIsMultiplayer && !(plr[myplr].pDungMsgs2 & 4)) {
4371 		sfxdelay = 10;
4372 		sfxdnum = USFX_DEFILER3;
4373 		plr[myplr].pDungMsgs2 |= 4;
4374 	} else if (currlevel == 21 && !plr[myplr]._pLvlVisited[21] && !gbIsMultiplayer && !(plr[myplr].pDungMsgs & 32)) {
4375 		sfxdelay = 30;
4376 		if (plr[myplr]._pClass == PC_WARRIOR) {
4377 			sfxdnum = PS_WARR92;
4378 		} else if (plr[myplr]._pClass == PC_ROGUE) {
4379 			sfxdnum = PS_ROGUE92;
4380 		} else if (plr[myplr]._pClass == PC_SORCERER) {
4381 			sfxdnum = PS_MAGE92;
4382 		} else if (plr[myplr]._pClass == PC_MONK) {
4383 			sfxdnum = PS_MONK92;
4384 		} else if (plr[myplr]._pClass == PC_BARD) {
4385 			sfxdnum = PS_ROGUE92;
4386 		} else if (plr[myplr]._pClass == PC_BARBARIAN) {
4387 			sfxdnum = PS_WARR92;
4388 		}
4389 		plr[myplr].pDungMsgs |= 32;
4390 	} else {
4391 		sfxdelay = 0;
4392 	}
4393 }
4394 
4395 int get_max_strength(int i)
4396 {
4397 	return MaxStats[i][ATTRIB_STR];
4398 }
4399 
4400 int get_max_magic(int i)
4401 {
4402 	return MaxStats[i][ATTRIB_MAG];
4403 }
4404 
4405 int get_max_dexterity(int i)
4406 {
4407 	return MaxStats[i][ATTRIB_DEX];
4408 }
4409 
4410 DEVILUTION_END_NAMESPACE
4411