1 //-------------------------------------------------------------------------
2 /*
3 Copyright (C) 2010-2019 EDuke32 developers and contributors
4 Copyright (C) 2019 Nuke.YKT
5
6 This file is part of NBlood.
7
8 NBlood 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 #include "compat.h"
24 #include "build.h"
25 #include "pragmas.h"
26 #include "mmulti.h"
27 #include "common_game.h"
28
29 #include "actor.h"
30 #include "ai.h"
31 #include "aicerber.h"
32 #include "blood.h"
33 #include "db.h"
34 #include "dude.h"
35 #include "levels.h"
36 #include "player.h"
37 #include "seq.h"
38 #include "sfx.h"
39 #include "trig.h"
40
41 static void BiteSeqCallback(int, int);
42 static void BurnSeqCallback(int, int);
43 static void BurnSeqCallback2(int, int);
44 static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite);
45 static void thinkTarget(spritetype *pSprite, XSPRITE *pXSprite);
46 static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite);
47 static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite);
48
49 static int nBiteClient = seqRegisterClient(BiteSeqCallback);
50 static int nBurnClient = seqRegisterClient(BurnSeqCallback);
51 static int nBurnClient2 = seqRegisterClient(BurnSeqCallback2);
52
53 AISTATE cerberusIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, thinkTarget, NULL };
54 AISTATE cerberusSearch = { kAiStateSearch, 7, -1, 1800, NULL, aiMoveForward, thinkSearch, &cerberusIdle };
55 AISTATE cerberusChase = { kAiStateChase, 7, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
56 AISTATE cerberusRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &cerberusSearch };
57 AISTATE cerberusTeslaRecoil = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &cerberusSearch };
58 AISTATE cerberusGoto = { kAiStateMove, 7, -1, 600, NULL, aiMoveForward, thinkGoto, &cerberusIdle };
59 AISTATE cerberusBite = { kAiStateChase, 6, nBiteClient, 60, NULL, NULL, NULL, &cerberusChase };
60 AISTATE cerberusBurn = { kAiStateChase, 6, nBurnClient, 60, NULL, NULL, NULL, &cerberusChase };
61 AISTATE cerberus3Burn = { kAiStateChase, 6, nBurnClient2, 60, NULL, NULL, NULL, &cerberusChase };
62 AISTATE cerberus2Idle = { kAiStateIdle, 0, -1, 0, NULL, NULL, thinkTarget, NULL };
63 AISTATE cerberus2Search = { kAiStateSearch, 7, -1, 1800, NULL, aiMoveForward, thinkSearch, &cerberus2Idle };
64 AISTATE cerberus2Chase = { kAiStateChase, 7, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
65 AISTATE cerberus2Recoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &cerberus2Search };
66 AISTATE cerberus2Goto = { kAiStateMove, 7, -1, 600, NULL, aiMoveForward, thinkGoto, &cerberus2Idle };
67 AISTATE cerberus2Bite = { kAiStateChase, 6, nBiteClient, 60, NULL, NULL, NULL, &cerberus2Chase };
68 AISTATE cerberus2Burn = { kAiStateChase, 6, nBurnClient, 60, NULL, NULL, NULL, &cerberus2Chase };
69 AISTATE cerberus4Burn = { kAiStateChase, 6, nBurnClient2, 60, NULL, NULL, NULL, &cerberus2Chase };
70 AISTATE cerberus139890 = { kAiStateOther, 7, -1, 120, NULL, aiMoveTurn, NULL, &cerberusChase };
71 AISTATE cerberus1398AC = { kAiStateOther, 7, -1, 120, NULL, aiMoveTurn, NULL, &cerberusChase };
72
BiteSeqCallback(int,int nXSprite)73 static void BiteSeqCallback(int, int nXSprite)
74 {
75 XSPRITE *pXSprite = &xsprite[nXSprite];
76 int nSprite = pXSprite->reference;
77 spritetype *pSprite = &sprite[nSprite];
78 int dx = Cos(pSprite->ang)>>16;
79 int dy = Sin(pSprite->ang)>>16;
80 ///dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
81 if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax)) {
82 consoleSysMsg("pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
83 return;
84 }
85 ///dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
86 if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites)) {
87 consoleSysMsg("pXSprite->target >= 0 && pXSprite->target < kMaxSprites");
88 return;
89 }
90 spritetype *pTarget = &sprite[pXSprite->target];
91 int dz = pTarget->z-pSprite->z;
92 actFireVector(pSprite, 350, -100, dx, dy, dz, kVectorCerberusHack);
93 actFireVector(pSprite, -350, 0, dx, dy, dz, kVectorCerberusHack);
94 actFireVector(pSprite, 0, 0, dx, dy, dz, kVectorCerberusHack);
95 }
96
BurnSeqCallback(int,int nXSprite)97 static void BurnSeqCallback(int, int nXSprite)
98 {
99 XSPRITE *pXSprite = &xsprite[nXSprite];
100 int nSprite = pXSprite->reference;
101 spritetype *pSprite = &sprite[nSprite];
102 DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
103 int height = pDudeInfo->eyeHeight*pSprite->yrepeat;
104 ///dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
105 if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites)) {
106 consoleSysMsg("pXSprite->target >= 0 && pXSprite->target < kMaxSprites");
107 return;
108 }
109 int x = pSprite->x;
110 int y = pSprite->y;
111 int z = height; // ???
112 TARGETTRACK tt1 = { 0x10000, 0x10000, 0x100, 0x55, 0x1aaaaa };
113 Aim aim;
114 aim.dx = Cos(pSprite->ang)>>16;
115 aim.dy = Sin(pSprite->ang)>>16;
116 aim.dz = gDudeSlope[nXSprite];
117 int nClosest = 0x7fffffff;
118 for (short nSprite2 = headspritestat[kStatDude]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
119 {
120 spritetype *pSprite2 = &sprite[nSprite2];
121 if (pSprite == pSprite2 || !(pSprite2->flags&8))
122 continue;
123 int x2 = pSprite2->x;
124 int y2 = pSprite2->y;
125 int z2 = pSprite2->z;
126 int nDist = approxDist(x2-x, y2-y);
127 if (nDist == 0 || nDist > 0x2800)
128 continue;
129 if (tt1.at10)
130 {
131 int t = divscale(nDist, tt1.at10, 12);
132 x2 += (xvel[nSprite2]*t)>>12;
133 y2 += (yvel[nSprite2]*t)>>12;
134 z2 += (zvel[nSprite2]*t)>>8;
135 }
136 int tx = x+mulscale30(Cos(pSprite->ang), nDist);
137 int ty = y+mulscale30(Sin(pSprite->ang), nDist);
138 int tz = z+mulscale(gDudeSlope[nXSprite], nDist, 10);
139 int tsr = mulscale(9460, nDist, 10);
140 int top, bottom;
141 GetSpriteExtents(pSprite2, &top, &bottom);
142 if (tz-tsr > bottom || tz+tsr < top)
143 continue;
144 int dx = (tx-x2)>>4;
145 int dy = (ty-y2)>>4;
146 int dz = (tz-z2)>>8;
147 int nDist2 = ksqrt(dx*dx+dy*dy+dz*dz);
148 if (nDist2 < nClosest)
149 {
150 int nAngle = getangle(x2-x, y2-y);
151 int nDeltaAngle = ((nAngle-pSprite->ang+1024)&2047)-1024;
152 if (klabs(nDeltaAngle) <= tt1.at8)
153 {
154 int tz = pSprite2->z-pSprite->z;
155 if (cansee(x, y, z, pSprite->sectnum, x2, y2, z2, pSprite2->sectnum))
156 {
157 nClosest = nDist2;
158 aim.dx = Cos(nAngle)>>16;
159 aim.dy = Sin(nAngle)>>16;
160 aim.dz = divscale(tz, nDist, 10);
161 }
162 else
163 aim.dz = tz;
164 }
165 }
166 }
167 switch (pSprite->type) {
168 case kDudeCerberusTwoHead:
169 actFireMissile(pSprite, -350, 0, aim.dx, aim.dy, aim.dz, kMissileFireballCerberus);
170 actFireMissile(pSprite, 350, -100, aim.dx, aim.dy, aim.dz, kMissileFireballCerberus);
171 break;
172 case kDudeCerberusOneHead:
173 actFireMissile(pSprite, 350, -100, aim.dx, aim.dy, aim.dz, kMissileFireballCerberus);
174 break;
175 }
176 }
177
BurnSeqCallback2(int,int nXSprite)178 static void BurnSeqCallback2(int, int nXSprite)
179 {
180 XSPRITE *pXSprite = &xsprite[nXSprite];
181 int nSprite = pXSprite->reference;
182 spritetype *pSprite = &sprite[nSprite];
183 ///dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
184 if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites)) {
185 consoleSysMsg("pXSprite->target >= 0 && pXSprite->target < kMaxSprites");
186 return;
187 }
188 DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
189 int height = pDudeInfo->eyeHeight*pSprite->yrepeat;
190
191 int x = pSprite->x;
192 int y = pSprite->y;
193 int z = height; // ???
194 TARGETTRACK tt1 = { 0x10000, 0x10000, 0x100, 0x55, 0x1aaaaa };
195 Aim aim;
196 int ax, ay, az;
197 aim.dx = ax = Cos(pSprite->ang)>>16;
198 aim.dy = ay = Sin(pSprite->ang)>>16;
199 aim.dz = gDudeSlope[nXSprite];
200 az = 0;
201 int nClosest = 0x7fffffff;
202 for (short nSprite2 = headspritestat[kStatDude]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
203 {
204 spritetype *pSprite2 = &sprite[nSprite2];
205 if (pSprite == pSprite2 || !(pSprite2->flags&8))
206 continue;
207 int x2 = pSprite2->x;
208 int y2 = pSprite2->y;
209 int z2 = pSprite2->z;
210 int nDist = approxDist(x2-x, y2-y);
211 if (nDist == 0 || nDist > 0x2800)
212 continue;
213 if (tt1.at10)
214 {
215 int t = divscale(nDist, tt1.at10, 12);
216 x2 += (xvel[nSprite2]*t)>>12;
217 y2 += (yvel[nSprite2]*t)>>12;
218 z2 += (zvel[nSprite2]*t)>>8;
219 }
220 int tx = x+mulscale30(Cos(pSprite->ang), nDist);
221 int ty = y+mulscale30(Sin(pSprite->ang), nDist);
222 int tz = z+mulscale(gDudeSlope[nXSprite], nDist, 10);
223 int tsr = mulscale(9460, nDist, 10);
224 int top, bottom;
225 GetSpriteExtents(pSprite2, &top, &bottom);
226 if (tz-tsr > bottom || tz+tsr < top)
227 continue;
228 int dx = (tx-x2)>>4;
229 int dy = (ty-y2)>>4;
230 int dz = (tz-z2)>>8;
231 int nDist2 = ksqrt(dx*dx+dy*dy+dz*dz);
232 if (nDist2 < nClosest)
233 {
234 int nAngle = getangle(x2-x, y2-y);
235 int nDeltaAngle = ((nAngle-pSprite->ang+1024)&2047)-1024;
236 if (klabs(nDeltaAngle) <= tt1.at8)
237 {
238 DUDEINFO *pDudeInfo2 = getDudeInfo(pSprite2->type);
239 int height = (pDudeInfo2->aimHeight*pSprite2->yrepeat)<<2;
240 int tz = (z2-height)-z;
241 if (cansee(x, y, z, pSprite->sectnum, x2, y2, z2, pSprite2->sectnum))
242 {
243 nClosest = nDist2;
244 aim.dx = Cos(nAngle)>>16;
245 aim.dy = Sin(nAngle)>>16;
246 aim.dz = divscale(tz, nDist, 10);
247 }
248 else
249 aim.dz = tz;
250 }
251 }
252 }
253 switch (pSprite->type) {
254 case kDudeCerberusTwoHead:
255 actFireMissile(pSprite, 350, -100, aim.dx, aim.dy, -aim.dz, kMissileFlameHound);
256 actFireMissile(pSprite, -350, 0, ax, ay, az, kMissileFlameHound);
257 break;
258 case kDudeCerberusOneHead:
259 actFireMissile(pSprite, 350, -100, aim.dx, aim.dy, -aim.dz, kMissileFlameHound);
260 break;
261 }
262 }
263
thinkSearch(spritetype * pSprite,XSPRITE * pXSprite)264 static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
265 {
266 aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
267 aiThinkTarget(pSprite, pXSprite);
268 }
269
thinkTarget(spritetype * pSprite,XSPRITE * pXSprite)270 static void thinkTarget(spritetype *pSprite, XSPRITE *pXSprite)
271 {
272 ///dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
273 if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax)) {
274 consoleSysMsg("pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
275 return;
276 }
277 DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
278 DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
279 if (pDudeExtraE->at8 && pDudeExtraE->at4 < 10)
280 pDudeExtraE->at4++;
281 else if (pDudeExtraE->at4 >= 10 && pDudeExtraE->at8)
282 {
283 pXSprite->goalAng += 256;
284 POINT3D *pTarget = &baseSprite[pSprite->index];
285 aiSetTarget(pXSprite, pTarget->x, pTarget->y, pTarget->z);
286 if (pSprite->type == kDudeCerberusTwoHead)
287 aiNewState(pSprite, pXSprite, &cerberus139890);
288 else
289 aiNewState(pSprite, pXSprite, &cerberus1398AC);
290 return;
291 }
292 if (Chance(pDudeInfo->alertChance))
293 {
294 for (int p = connecthead; p >= 0; p = connectpoint2[p])
295 {
296 PLAYER *pPlayer = &gPlayer[p];
297 if (pPlayer->pXSprite->health == 0 || powerupCheck(pPlayer, kPwUpShadowCloak) > 0)
298 continue;
299 int x = pPlayer->pSprite->x;
300 int y = pPlayer->pSprite->y;
301 int z = pPlayer->pSprite->z;
302 int nSector = pPlayer->pSprite->sectnum;
303 int dx = x-pSprite->x;
304 int dy = y-pSprite->y;
305 int nDist = approxDist(dx, dy);
306 if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
307 continue;
308 if (!cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z-((pDudeInfo->eyeHeight*pSprite->yrepeat)<<2), pSprite->sectnum))
309 continue;
310 int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
311 if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
312 {
313 pDudeExtraE->at0 = 0;
314 aiSetTarget(pXSprite, pPlayer->nSprite);
315 aiActivateDude(pSprite, pXSprite);
316 }
317 else if (nDist < pDudeInfo->hearDist)
318 {
319 pDudeExtraE->at0 = 0;
320 aiSetTarget(pXSprite, x, y, z);
321 aiActivateDude(pSprite, pXSprite);
322 }
323 else
324 continue;
325 break;
326 }
327 }
328 }
329
thinkGoto(spritetype * pSprite,XSPRITE * pXSprite)330 static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
331 {
332 ///dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
333 if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax)) {
334 consoleSysMsg("pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
335 return;
336 }
337 DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
338 int dx = pXSprite->targetX-pSprite->x;
339 int dy = pXSprite->targetY-pSprite->y;
340 int nAngle = getangle(dx, dy);
341 int nDist = approxDist(dx, dy);
342 aiChooseDirection(pSprite, pXSprite, nAngle);
343 if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
344 {
345 switch (pSprite->type) {
346 case kDudeCerberusTwoHead:
347 aiNewState(pSprite, pXSprite, &cerberusSearch);
348 break;
349 case kDudeCerberusOneHead:
350 aiNewState(pSprite, pXSprite, &cerberus2Search);
351 break;
352 }
353 }
354 aiThinkTarget(pSprite, pXSprite);
355 }
356
thinkChase(spritetype * pSprite,XSPRITE * pXSprite)357 static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
358 {
359 if (pXSprite->target == -1) {
360 switch (pSprite->type) {
361 case kDudeCerberusTwoHead:
362 aiNewState(pSprite, pXSprite, &cerberusGoto);
363 break;
364 case kDudeCerberusOneHead:
365 aiNewState(pSprite, pXSprite, &cerberus2Goto);
366 break;
367 }
368 return;
369 }
370
371 ///dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
372 if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax)) {
373 consoleSysMsg("pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
374 return;
375 }
376
377 DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
378
379 ///dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
380 if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites)) {
381 consoleSysMsg("pXSprite->target >= 0 && pXSprite->target < kMaxSprites");
382 return;
383 }
384 spritetype *pTarget = &sprite[pXSprite->target];
385 XSPRITE *pXTarget = &xsprite[pTarget->extra];
386 int dx = pTarget->x-pSprite->x;
387 int dy = pTarget->y-pSprite->y;
388 aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
389
390 if (pXTarget->health == 0) {
391 switch (pSprite->type) {
392 case kDudeCerberusTwoHead:
393 aiNewState(pSprite, pXSprite, &cerberusSearch);
394 break;
395 case kDudeCerberusOneHead:
396 aiNewState(pSprite, pXSprite, &cerberus2Search);
397 break;
398 }
399 return;
400 }
401
402 if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], kPwUpShadowCloak) > 0) {
403 switch (pSprite->type) {
404 case kDudeCerberusTwoHead:
405 aiNewState(pSprite, pXSprite, &cerberusSearch);
406 break;
407 case kDudeCerberusOneHead:
408 aiNewState(pSprite, pXSprite, &cerberus2Search);
409 break;
410 }
411 return;
412 }
413
414 int nDist = approxDist(dx, dy);
415 if (nDist <= pDudeInfo->seeDist)
416 {
417 int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
418 int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
419 if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
420 {
421 if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery) {
422 aiSetTarget(pXSprite, pXSprite->target);
423
424 if (nDist < 0x1b00 && nDist > 0xd00 && klabs(nDeltaAngle) < 85) {
425 switch (pSprite->type) {
426 case kDudeCerberusTwoHead:
427 aiNewState(pSprite, pXSprite, &cerberusBurn);
428 break;
429 case kDudeCerberusOneHead:
430 aiNewState(pSprite, pXSprite, &cerberus2Burn);
431 break;
432 }
433 }
434
435 else if (nDist < 0xb00 && nDist > 0x500 && klabs(nDeltaAngle) < 85) {
436 switch (pSprite->type) {
437 case kDudeCerberusTwoHead:
438 aiNewState(pSprite, pXSprite, &cerberus3Burn);
439 break;
440 case kDudeCerberusOneHead:
441 aiNewState(pSprite, pXSprite, &cerberus4Burn);
442 break;
443 }
444 }
445 else if (nDist < 0x200 && klabs(nDeltaAngle) < 85)
446 {
447 int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
448 switch (pSprite->type) {
449 case kDudeCerberusTwoHead:
450 switch (hit) {
451 case -1:
452 aiNewState(pSprite, pXSprite, &cerberusBite);
453 break;
454 case 3:
455 if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeHellHound)
456 aiNewState(pSprite, pXSprite, &cerberusBite);
457 break;
458 case 0:
459 case 4:
460 break;
461 default:
462 aiNewState(pSprite, pXSprite, &cerberusBite);
463 break;
464 }
465 break;
466 case kDudeCerberusOneHead:
467 switch (hit) {
468 case -1:
469 aiNewState(pSprite, pXSprite, &cerberus2Bite);
470 break;
471 case 3:
472 if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeHellHound)
473 aiNewState(pSprite, pXSprite, &cerberus2Bite);
474 break;
475 case 0:
476 case 4:
477 break;
478 default:
479 aiNewState(pSprite, pXSprite, &cerberus2Bite);
480 break;
481 }
482 break;
483 }
484 }
485 return;
486 }
487 }
488 }
489
490 switch (pSprite->type) {
491 case kDudeCerberusTwoHead:
492 aiNewState(pSprite, pXSprite, &cerberusGoto);
493 break;
494 case kDudeCerberusOneHead:
495 aiNewState(pSprite, pXSprite, &cerberus2Goto);
496 break;
497 }
498 pXSprite->target = -1;
499 }
500