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