1 //-------------------------------------------------------------------------
2 /*
3 Copyright (C) 2010-2019 EDuke32 developers and contributors
4 Copyright (C) 2019 sirlemonhead, Nuke.YKT
5
6 This file is part of PCExhumed.
7
8 PCExhumed is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License version 2
10 as published by the Free Software Foundation.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15
16 See the GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 */
22 //-------------------------------------------------------------------------
23
24 #include "mummy.h"
25 #include "sequence.h"
26 #include "move.h"
27 #include "map.h"
28 #include "sound.h"
29 #include "exhumed.h"
30 #include "random.h"
31 #include "trigdat.h"
32 #include "bullet.h"
33 #include "items.h"
34 #include "save.h"
35 #include <assert.h>
36 #include "engine.h"
37
38 struct Mummy
39 {
40 short nHealth;
41 short B;
42 short nAction;
43 short nSprite;
44 short nTarget;
45 short F;
46 short G;
47 short H;
48 };
49
50 static actionSeq ActionSeq[] = {
51 {8, 0},
52 {0, 0},
53 {16, 0},
54 {24, 0},
55 {32, 1},
56 {40, 1},
57 {48, 1},
58 {50, 0}
59 };
60
61 short nMummies = -1;
62 Mummy MummyList[kMaxMummies];
63
64
InitMummy()65 void InitMummy()
66 {
67 nMummies = 0;
68 }
69
BuildMummy(int nSprite,int x,int y,int z,int nSector,int nAngle)70 int BuildMummy(int nSprite, int x, int y, int z, int nSector, int nAngle)
71 {
72 if (nMummies >= kMaxMummies) {
73 return -1;
74 }
75
76 short nMummy = nMummies++;
77
78 if (nSprite == -1)
79 {
80 nSprite = insertsprite(nSector, 102);
81 }
82 else
83 {
84 x = sprite[nSprite].x;
85 y = sprite[nSprite].y;
86 z = sprite[nSprite].z;
87 nAngle = sprite[nSprite].ang;
88
89 changespritestat(nSprite, 102);
90 }
91
92 assert(nSprite >= 0 && nSprite < kMaxSprites);
93
94 sprite[nSprite].x = x;
95 sprite[nSprite].y = y;
96 sprite[nSprite].z = z;
97 sprite[nSprite].cstat = 0x101;
98 sprite[nSprite].shade = -12;
99 sprite[nSprite].clipdist = 32;
100 sprite[nSprite].xvel = 0;
101 sprite[nSprite].yvel = 0;
102 sprite[nSprite].zvel = 0;
103 sprite[nSprite].xrepeat = 42;
104 sprite[nSprite].yrepeat = 42;
105 sprite[nSprite].pal = sector[sprite[nSprite].sectnum].ceilingpal;
106 sprite[nSprite].xoffset = 0;
107 sprite[nSprite].yoffset = 0;
108 sprite[nSprite].ang = nAngle;
109 sprite[nSprite].picnum = 1;
110 sprite[nSprite].hitag = 0;
111 sprite[nSprite].lotag = runlist_HeadRun() + 1;
112 sprite[nSprite].extra = -1;
113
114 // GrabTimeSlot(3);
115
116 MummyList[nMummy].nAction = 0;
117 MummyList[nMummy].nHealth = 640;
118 MummyList[nMummy].B = 0;
119 MummyList[nMummy].nSprite = nSprite;
120 MummyList[nMummy].nTarget = -1;
121 MummyList[nMummy].F = nMummy;
122 MummyList[nMummy].G = 0;
123
124 sprite[nSprite].owner = runlist_AddRunRec(sprite[nSprite].lotag - 1, nMummy | 0xE0000);
125
126 MummyList[nMummy].H = runlist_AddRunRec(NewRun, nMummy | 0xE0000);
127
128 nCreaturesLeft++;
129
130 return (nMummy | 0xE0000);
131 }
132
CheckMummyRevive(short nMummy)133 void CheckMummyRevive(short nMummy)
134 {
135 short nSprite = MummyList[nMummy].nSprite;
136
137 for (int i = 0; i < nMummies; i++)
138 {
139 if (i != nMummy)
140 {
141 short nSprite2 = MummyList[i].nSprite;
142 if (sprite[nSprite2].statnum != 102) {
143 continue;
144 }
145
146 if (MummyList[i].nAction != 5) {
147 continue;
148 }
149
150 int x = klabs(sprite[nSprite2].x - sprite[nSprite].x) >> 8;
151 int y = klabs(sprite[nSprite2].y - sprite[nSprite].y) >> 8;
152
153 if (x <= 20 && y <= 20)
154 {
155 if (cansee(sprite[nSprite].x, sprite[nSprite].y, sprite[nSprite].z - 8192, sprite[nSprite].sectnum,
156 sprite[nSprite2].x, sprite[nSprite2].y, sprite[nSprite2].z - 8192, sprite[nSprite2].sectnum))
157 {
158 sprite[nSprite2].cstat = 0;
159 MummyList[i].nAction = 6;
160 MummyList[i].B = 0;
161 }
162 }
163 }
164 }
165 }
166
FuncMummy(int a,int nDamage,int nRun)167 void FuncMummy(int a, int nDamage, int nRun)
168 {
169 short nMummy = RunData[nRun].nVal;
170 assert(nMummy >= 0 && nMummy < kMaxMummies);
171
172 short nTarget = UpdateEnemy(&MummyList[nMummy].nTarget);
173
174 short nSprite = MummyList[nMummy].nSprite;
175 short nAction = MummyList[nMummy].nAction;
176
177 int nMessage = a & kMessageMask;
178
179 switch (nMessage)
180 {
181 default:
182 {
183 DebugOut("unknown msg %d for Mummy\n", nMessage);
184 break;
185 }
186
187 case 0x20000:
188 {
189 Gravity(nSprite);
190
191 int nSeq = SeqOffsets[kSeqMummy] + ActionSeq[nAction].a;
192
193 sprite[nSprite].picnum = seq_GetSeqPicnum2(nSeq, MummyList[nMummy].B);
194
195 short nFrame = SeqBase[nSeq] + MummyList[nMummy].B;
196 short nFrameFlag = FrameFlag[nFrame];
197
198 seq_MoveSequence(nSprite, nSeq, MummyList[nMummy].B);
199
200 bool bVal = false;
201
202 MummyList[nMummy].B++;
203 if (MummyList[nMummy].B >= SeqSize[nSeq])
204 {
205 MummyList[nMummy].B = 0;
206
207 bVal = true;
208 }
209
210 if (nTarget != -1 && nAction < 4)
211 {
212 if ((!sprite[nTarget].cstat) && nAction)
213 {
214 MummyList[nMummy].nAction = 0;
215 MummyList[nMummy].B = 0;
216 sprite[nSprite].xvel = 0;
217 sprite[nSprite].yvel = 0;
218 }
219 }
220
221 int nMov = MoveCreatureWithCaution(nSprite);
222
223 if (nAction > 7)
224 return;
225
226 switch (nAction)
227 {
228 case 0:
229 {
230 if ((MummyList[nMummy].F & 0x1F) == (totalmoves & 0x1F))
231 {
232 sprite[nSprite].cstat = 0x101;
233
234 if (nTarget < 0)
235 {
236 int nTarget = FindPlayer(nSprite, 100);
237 if (nTarget >= 0)
238 {
239 D3PlayFX(StaticSound[kSoundMummyChant0], nSprite);
240 MummyList[nMummy].B = 0;
241 MummyList[nMummy].nTarget = nTarget;
242 MummyList[nMummy].nAction = 1;
243 MummyList[nMummy].G = 90;
244
245 sprite[nSprite].xvel = Cos(sprite[nSprite].ang) >> 2;
246 sprite[nSprite].yvel = sintable[sprite[nSprite].ang] >> 2; // NOTE no angle masking in original code
247 }
248 }
249 }
250 return;
251 }
252
253 case 1:
254 {
255 if (MummyList[nMummy].G > 0)
256 {
257 MummyList[nMummy].G--;
258 }
259
260 if ((MummyList[nMummy].F & 0x1F) == (totalmoves & 0x1F))
261 {
262 sprite[nSprite].cstat = 0x101;
263
264 PlotCourseToSprite(nSprite, nTarget);
265
266 if (MummyList[nMummy].nAction == 1)
267 {
268 if (RandomBit())
269 {
270 if (cansee(sprite[nSprite].x, sprite[nSprite].y, sprite[nSprite].z - GetSpriteHeight(nSprite), sprite[nSprite].sectnum,
271 sprite[nTarget].x, sprite[nTarget].y, sprite[nTarget].z - GetSpriteHeight(nTarget), sprite[nTarget].sectnum))
272 {
273 MummyList[nMummy].nAction = 3;
274 MummyList[nMummy].B = 0;
275
276 sprite[nSprite].xvel = 0;
277 sprite[nSprite].yvel = 0;
278 return;
279 }
280 }
281 }
282 }
283
284 // loc_2B5A8
285 if (!MummyList[nMummy].B)
286 {
287 sprite[nSprite].xvel = Cos(sprite[nSprite].ang) >> 1;
288 sprite[nSprite].yvel = Sin(sprite[nSprite].ang) >> 1;
289 }
290
291 if (sprite[nSprite].xvel || sprite[nSprite].yvel)
292 {
293 if (sprite[nSprite].xvel > 0)
294 {
295 sprite[nSprite].xvel -= 1024;
296 if (sprite[nSprite].xvel < 0) {
297 sprite[nSprite].xvel = 0;
298 }
299 }
300 else if (sprite[nSprite].xvel < 0)
301 {
302 sprite[nSprite].xvel += 1024;
303 if (sprite[nSprite].xvel > 0) {
304 sprite[nSprite].xvel = 0;
305 }
306 }
307
308 if (sprite[nSprite].yvel > 0)
309 {
310 sprite[nSprite].yvel -= 1024;
311 if (sprite[nSprite].yvel < 0) {
312 sprite[nSprite].yvel = 0;
313 }
314 }
315 else if (sprite[nSprite].yvel < 0)
316 {
317 sprite[nSprite].yvel += 1024;
318 if (sprite[nSprite].yvel > 0) {
319 sprite[nSprite].yvel = 0;
320 }
321 }
322 }
323
324 if (nMov)
325 {
326 switch (nMov & 0xC000)
327 {
328 case 0x8000:
329 {
330 sprite[nSprite].ang = (sprite[nSprite].ang + ((RandomWord() & 0x3FF) + 1024)) & kAngleMask;
331 sprite[nSprite].xvel = Cos(sprite[nSprite].ang) >> 2;
332 sprite[nSprite].yvel = Sin(sprite[nSprite].ang) >> 2;
333 return;
334 }
335
336 case 0xC000:
337 {
338 if ((nMov & 0x3FFF) == nTarget)
339 {
340 int nAngle = getangle(sprite[nTarget].x - sprite[nSprite].x, sprite[nTarget].y - sprite[nSprite].y);
341 if (AngleDiff(sprite[nSprite].ang, nAngle) < 64)
342 {
343 MummyList[nMummy].nAction = 2;
344 MummyList[nMummy].B = 0;
345
346 sprite[nSprite].xvel = 0;
347 sprite[nSprite].yvel = 0;
348 }
349 }
350 return;
351 }
352 }
353 }
354
355 break;
356 }
357
358 case 2:
359 {
360 if (nTarget == -1)
361 {
362 MummyList[nMummy].nAction = 0;
363 MummyList[nMummy].B = 0;
364 }
365 else
366 {
367 if (PlotCourseToSprite(nSprite, nTarget) >= 1024)
368 {
369 MummyList[nMummy].nAction = 1;
370 MummyList[nMummy].B = 0;
371 }
372 else if (nFrameFlag & 0x80)
373 {
374 runlist_DamageEnemy(nTarget, nSprite, 5);
375 }
376 }
377 return;
378 }
379
380 case 3:
381 {
382 if (bVal)
383 {
384 MummyList[nMummy].B = 0;
385 MummyList[nMummy].nAction = 0;
386 MummyList[nMummy].G = 100;
387 MummyList[nMummy].nTarget = -1;
388 return;
389 }
390 else if (nFrameFlag & 0x80)
391 {
392 SetQuake(nSprite, 100);
393
394 // low 16 bits of returned var contains the sprite index, the high 16 the bullet number
395 int nBullet = BuildBullet(nSprite, 9, 0, 0, -15360, sprite[nSprite].ang, nTarget + 10000, 1);
396 CheckMummyRevive(nMummy);
397
398 if (nBullet > -1)
399 {
400 if (!RandomSize(3))
401 {
402 // FIXME CHECKME - nBullet & 0xFFFF can be -1. Original code doesn't handle this??
403
404 SetBulletEnemy(nBullet >> 16, nTarget); // isolate the bullet number (shift off the sprite index)
405 sprite[nBullet & 0xFFFF].pal = 5;
406 }
407 }
408 }
409 return;
410 }
411
412 case 4:
413 {
414 if (bVal)
415 {
416 MummyList[nMummy].B = 0;
417 MummyList[nMummy].nAction = 5;
418 }
419 return;
420 }
421
422 case 5:
423 {
424 MummyList[nMummy].B = 0;
425 return;
426 }
427
428 case 6:
429 {
430 if (bVal)
431 {
432 sprite[nSprite].cstat = 0x101;
433
434 MummyList[nMummy].nAction = 0;
435 MummyList[nMummy].nHealth = 300;
436 MummyList[nMummy].nTarget = -1;
437
438 nCreaturesLeft++;
439 }
440 return;
441 }
442
443 case 7:
444 {
445 if (nMov & 0x20000)
446 {
447 sprite[nSprite].xvel >>= 1;
448 sprite[nSprite].yvel >>= 1;
449 }
450
451 if (bVal)
452 {
453 sprite[nSprite].xvel = 0;
454 sprite[nSprite].yvel = 0;
455 sprite[nSprite].cstat = 0x101;
456
457 MummyList[nMummy].nAction = 0;
458 MummyList[nMummy].B = 0;
459 MummyList[nMummy].nTarget = -1;
460 }
461
462 return;
463 }
464 }
465
466 return;
467 }
468
469 case 0x90000:
470 {
471 seq_PlotSequence(a & 0xFFFF, SeqOffsets[kSeqMummy] + ActionSeq[nAction].a, MummyList[nMummy].B, ActionSeq[nAction].b);
472 return;
473 }
474
475 case 0xA0000:
476 {
477 if (MummyList[nMummy].nHealth <= 0)
478 return;
479
480 nDamage = runlist_CheckRadialDamage(nSprite);
481 // fall through to 0x80000
482 fallthrough__;
483 }
484 case 0x80000:
485 {
486 if (nDamage <= 0)
487 return;
488
489 if (MummyList[nMummy].nHealth <= 0) {
490 return;
491 }
492
493 MummyList[nMummy].nHealth -= nDamage;
494
495 if (MummyList[nMummy].nHealth <= 0)
496 {
497 MummyList[nMummy].nHealth = 0;
498 sprite[nSprite].cstat &= 0xFEFE;
499 nCreaturesLeft--;
500
501 DropMagic(nSprite);
502
503 MummyList[nMummy].B = 0;
504 MummyList[nMummy].nAction = 4;
505
506 sprite[nSprite].xvel = 0;
507 sprite[nSprite].yvel = 0;
508 sprite[nSprite].zvel = 0;
509 sprite[nSprite].z = sector[sprite[nSprite].sectnum].floorz;
510 }
511 else
512 {
513 if (!RandomSize(2))
514 {
515 MummyList[nMummy].nAction = 7;
516 MummyList[nMummy].B = 0;
517
518 sprite[nSprite].xvel = 0;
519 sprite[nSprite].yvel = 0;
520 }
521 }
522
523 return;
524 }
525 }
526 }
527
528 class MummyLoadSave : public LoadSave
529 {
530 public:
531 virtual void Load();
532 virtual void Save();
533 };
534
Load()535 void MummyLoadSave::Load()
536 {
537 Read(&nMummies, sizeof(nMummies));
538 Read(&MummyList, sizeof(MummyList[0]) * nMummies);
539 }
540
Save()541 void MummyLoadSave::Save()
542 {
543 Write(&nMummies, sizeof(nMummies));
544 Write(&MummyList, sizeof(MummyList[0]) * nMummies);
545 }
546
547 static MummyLoadSave* myLoadSave;
548
MummyLoadSaveConstruct()549 void MummyLoadSaveConstruct()
550 {
551 myLoadSave = new MummyLoadSave();
552 }