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 "aicult.h"
32 #include "blood.h"
33 #include "db.h"
34 #include "dude.h"
35 #include "eventq.h"
36 #include "levels.h"
37 #include "player.h"
38 #include "seq.h"
39 #include "sfx.h"
40 #include "trig.h"
41 
42 static void TommySeqCallback(int, int);
43 static void TeslaSeqCallback(int, int);
44 static void ShotSeqCallback(int, int);
45 static void ThrowSeqCallback(int, int);
46 static void sub_68170(int, int);
47 static void sub_68230(int, int);
48 static void thinkSearch(spritetype *, XSPRITE *);
49 static void thinkGoto(spritetype *, XSPRITE *);
50 static void thinkChase(spritetype *, XSPRITE *);
51 
52 static int nTommyClient = seqRegisterClient(TommySeqCallback);
53 static int nTeslaClient = seqRegisterClient(TeslaSeqCallback);
54 static int nShotClient = seqRegisterClient(ShotSeqCallback);
55 static int nThrowClient = seqRegisterClient(ThrowSeqCallback);
56 static int n68170Client = seqRegisterClient(sub_68170);
57 static int n68230Client = seqRegisterClient(sub_68230);
58 
59 AISTATE cultistIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
60 AISTATE cultistProneIdle = { kAiStateIdle, 17, -1, 0, NULL, NULL, aiThinkTarget, NULL };
61 AISTATE fanaticProneIdle = { kAiStateIdle, 17, -1, 0, NULL, NULL, aiThinkTarget, NULL };
62 AISTATE cultistProneIdle3 = { kAiStateIdle, 17, -1, 0, NULL, NULL, aiThinkTarget, NULL };
63 AISTATE cultistChase = { kAiStateChase, 9, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
64 AISTATE fanaticChase = { kAiStateChase, 0, -1, 0, NULL, aiMoveTurn, thinkChase, NULL };
65 AISTATE cultistDodge = { kAiStateMove, 9, -1, 90, NULL, aiMoveDodge, NULL, &cultistChase };
66 AISTATE cultistGoto = { kAiStateMove, 9, -1, 600, NULL, aiMoveForward, thinkGoto, &cultistIdle };
67 AISTATE cultistProneChase = { kAiStateChase, 14, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
68 AISTATE cultistProneDodge = { kAiStateMove, 14, -1, 90, NULL, aiMoveDodge, NULL, &cultistProneChase };
69 AISTATE cultistTThrow = { kAiStateChase, 7, nThrowClient, 120, NULL, NULL, NULL, &cultistTFire };
70 AISTATE cultistSThrow = { kAiStateChase, 7, nThrowClient, 120, NULL, NULL, NULL, &cultistSFire };
71 AISTATE cultistTsThrow = { kAiStateChase, 7, nThrowClient, 120, NULL, NULL, NULL, &cultistTsFire };
72 AISTATE cultistDThrow = { kAiStateChase, 7, nThrowClient, 120, NULL, NULL, NULL, &cultistChase };
73 AISTATE cultist139A78 = { kAiStateChase, 7, n68170Client, 120, NULL, NULL, NULL, &cultistChase };
74 AISTATE cultist139A94 = { kAiStateChase, 7, n68230Client, 120, NULL, NULL, NULL, &cultistIdle };
75 AISTATE cultist139AB0 = { kAiStateChase, 7, n68230Client, 120, NULL, NULL, thinkSearch, &cultist139A94 };
76 AISTATE cultist139ACC = { kAiStateChase, 7, n68230Client, 120, NULL, NULL, thinkSearch, &cultist139AB0 };
77 AISTATE cultist139AE8 = { kAiStateChase, 7, n68230Client, 120, NULL, NULL, thinkSearch, &cultist139AE8 };
78 AISTATE cultistSearch = { kAiStateSearch, 9, -1, 1800, NULL, aiMoveForward, thinkSearch, &cultistIdle };
79 AISTATE cultistSFire = { kAiStateChase, 6, nShotClient, 60, NULL, NULL, NULL, &cultistChase };
80 AISTATE cultistTFire = { kAiStateChase, 6, nTommyClient, 0, NULL, aiMoveTurn, thinkChase, &cultistTFire };
81 AISTATE cultistTsFire = { kAiStateChase, 6, nTeslaClient, 0, NULL, aiMoveTurn, thinkChase, &cultistChase };
82 AISTATE cultistSProneFire = { kAiStateChase, 8, nShotClient, 60, NULL, NULL, NULL, &cultistProneChase };
83 AISTATE cultistTProneFire = { kAiStateChase, 8, nTommyClient, 0, NULL, aiMoveTurn, thinkChase, &cultistTProneFire };
84 AISTATE cultistTsProneFire = { kAiStateChase, 8, nTeslaClient, 0, NULL, aiMoveTurn, NULL, &cultistTsProneFire };
85 AISTATE cultistRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &cultistDodge };
86 AISTATE cultistProneRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &cultistProneDodge };
87 AISTATE cultistTeslaRecoil = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &cultistDodge };
88 AISTATE cultistSwimIdle = { kAiStateIdle, 13, -1, 0, NULL, NULL, aiThinkTarget, NULL };
89 AISTATE cultistSwimChase = { kAiStateChase, 13, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
90 AISTATE cultistSwimDodge = { kAiStateMove, 13, -1, 90, NULL, aiMoveDodge, NULL, &cultistSwimChase };
91 AISTATE cultistSwimGoto = { kAiStateMove, 13, -1, 600, NULL, aiMoveForward, thinkGoto, &cultistSwimIdle };
92 AISTATE cultistSwimSearch = { kAiStateSearch, 13, -1, 1800, NULL, aiMoveForward, thinkSearch, &cultistSwimIdle };
93 AISTATE cultistSSwimFire = { kAiStateChase, 8, nShotClient, 60, NULL, NULL, NULL, &cultistSwimChase };
94 AISTATE cultistTSwimFire = { kAiStateChase, 8, nTommyClient, 0, NULL, aiMoveTurn, thinkChase, &cultistTSwimFire };
95 AISTATE cultistTsSwimFire = { kAiStateChase, 8, nTeslaClient, 0, NULL, aiMoveTurn, thinkChase, &cultistTsSwimFire };
96 AISTATE cultistSwimRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &cultistSwimDodge };
97 
TommySeqCallback(int,int nXSprite)98 static void TommySeqCallback(int, int nXSprite)
99 {
100     XSPRITE *pXSprite = &xsprite[nXSprite];
101     int nSprite = pXSprite->reference;
102     spritetype *pSprite = &sprite[nSprite];
103     int dx = Cos(pSprite->ang) >> 16;
104     int dy = Sin(pSprite->ang) >> 16;
105     int dz = gDudeSlope[nXSprite];
106     dx += Random3((5-gGameOptions.nDifficulty)*1000);
107     dy += Random3((5-gGameOptions.nDifficulty)*1000);
108     dz += Random3((5-gGameOptions.nDifficulty)*500);
109     actFireVector(pSprite, 0, 0, dx, dy, dz, kVectorBullet);
110     sfxPlay3DSound(pSprite, 4001, -1, 0);
111 }
112 
TeslaSeqCallback(int,int nXSprite)113 static void TeslaSeqCallback(int, int nXSprite)
114 {
115     XSPRITE *pXSprite = &xsprite[nXSprite];
116     int nSprite = pXSprite->reference;
117     spritetype *pSprite = &sprite[nSprite];
118     if (Chance(dword_138BB0[gGameOptions.nDifficulty]))
119     {
120         int dx = Cos(pSprite->ang) >> 16;
121         int dy = Sin(pSprite->ang) >> 16;
122         int dz = gDudeSlope[nXSprite];
123         dx += Random3((5-gGameOptions.nDifficulty)*1000);
124         dy += Random3((5-gGameOptions.nDifficulty)*1000);
125         dz += Random3((5-gGameOptions.nDifficulty)*500);
126         actFireMissile(pSprite, 0, 0, dx, dy, dz, kMissileTeslaRegular);
127         sfxPlay3DSound(pSprite, 470, -1, 0);
128     }
129 }
130 
ShotSeqCallback(int,int nXSprite)131 static void ShotSeqCallback(int, int nXSprite)
132 {
133     XSPRITE *pXSprite = &xsprite[nXSprite];
134     int nSprite = pXSprite->reference;
135     spritetype *pSprite = &sprite[nSprite];
136     int dx = Cos(pSprite->ang) >> 16;
137     int dy = Sin(pSprite->ang) >> 16;
138     int dz = gDudeSlope[nXSprite];
139     dx += Random2((5-gGameOptions.nDifficulty)*1000-500);
140     dy += Random2((5-gGameOptions.nDifficulty)*1000-500);
141     dz += Random2((5-gGameOptions.nDifficulty)*500);
142     for (int i = 0; i < 8; i++)
143     {
144         int r1 = Random3(500);
145         int r2 = Random3(1000);
146         int r3 = Random3(1000);
147         actFireVector(pSprite, 0, 0, dx+r3, dy+r2, dz+r1, kVectorShell);
148     }
149     if (Chance(0x8000))
150         sfxPlay3DSound(pSprite, 1001, -1, 0);
151     else
152         sfxPlay3DSound(pSprite, 1002, -1, 0);
153 }
154 
ThrowSeqCallback(int,int nXSprite)155 static void ThrowSeqCallback(int, int nXSprite)
156 {
157     XSPRITE *pXSprite = &xsprite[nXSprite];
158     int nSprite = pXSprite->reference;
159     spritetype *pSprite = &sprite[nSprite];
160     int nMissile = kThingArmedTNTStick;
161     if (gGameOptions.nDifficulty > 2)
162         nMissile = kThingArmedTNTBundle;
163     char v4 = Chance(0x6000);
164     sfxPlay3DSound(pSprite, 455, -1, 0);
165     dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
166     spritetype *pTarget = &sprite[pXSprite->target];
167     dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
168     int dx = pTarget->x - pSprite->x;
169     int dy = pTarget->y - pSprite->y;
170     int dz = pTarget->z - pSprite->z;
171     int nDist = approxDist(dx, dy);
172     int nDist2 = nDist / 540;
173     if (nDist > 0x1e00)
174         v4 = 0;
175     spritetype *pMissile = actFireThing(pSprite, 0, 0, dz/128-14500, nMissile, (nDist2<<23)/120);
176     if (v4)
177         xsprite[pMissile->extra].Impact = 1;
178     else
179         evPost(pMissile->index, 3, 120*(1+Random(2)), kCmdOn);
180 }
181 
sub_68170(int,int nXSprite)182 static void sub_68170(int, int nXSprite)
183 {
184     XSPRITE *pXSprite = &xsprite[nXSprite];
185     int nSprite = pXSprite->reference;
186     spritetype *pSprite = &sprite[nSprite];
187     int nMissile = kThingArmedTNTStick;
188     if (gGameOptions.nDifficulty > 2)
189         nMissile = kThingArmedTNTBundle;
190     sfxPlay3DSound(pSprite, 455, -1, 0);
191     spritetype *pMissile = actFireThing(pSprite, 0, 0, gDudeSlope[nXSprite]-9460, nMissile, 0x133333);
192     evPost(pMissile->index, 3, 120*(2+Random(2)), kCmdOn);
193 }
194 
sub_68230(int,int nXSprite)195 static void sub_68230(int, int nXSprite)
196 {
197     XSPRITE *pXSprite = &xsprite[nXSprite];
198     int nSprite = pXSprite->reference;
199     spritetype *pSprite = &sprite[nSprite];
200     int nMissile = kThingArmedTNTStick;
201     if (gGameOptions.nDifficulty > 2)
202         nMissile = kThingArmedTNTBundle;
203     sfxPlay3DSound(pSprite, 455, -1, 0);
204     dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
205     spritetype *pTarget = &sprite[pXSprite->target];
206     dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
207     int dx = pTarget->x - pSprite->x;
208     int dy = pTarget->y - pSprite->y;
209     int dz = pTarget->z - pSprite->z;
210     int nDist = approxDist(dx, dy);
211     int nDist2 = nDist / 540;
212     spritetype *pMissile = actFireThing(pSprite, 0, 0, dz/128-14500, nMissile, (nDist2<<17)/120);
213     xsprite[pMissile->extra].Impact = 1;
214 }
215 
TargetNearExplosion(spritetype * pSprite)216 static char TargetNearExplosion(spritetype *pSprite)
217 {
218     for (short nSprite = headspritesect[pSprite->sectnum]; nSprite >= 0; nSprite = nextspritesect[nSprite])
219     {
220         if (sprite[nSprite].type == kThingArmedTNTStick || sprite[nSprite].statnum == kStatExplosion)
221             return 1;
222     }
223     return 0;
224 }
225 
thinkSearch(spritetype * pSprite,XSPRITE * pXSprite)226 static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
227 {
228     aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
229     sub_5F15C(pSprite, pXSprite);
230 }
231 
thinkGoto(spritetype * pSprite,XSPRITE * pXSprite)232 static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
233 {
234     dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
235     DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
236     int dx = pXSprite->targetX-pSprite->x;
237     int dy = pXSprite->targetY-pSprite->y;
238     int nAngle = getangle(dx, dy);
239     int nDist = approxDist(dx, dy);
240     aiChooseDirection(pSprite, pXSprite, nAngle);
241     if (nDist < 5120 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
242     {
243         switch (pXSprite->medium)
244         {
245         case kMediumNormal:
246             aiNewState(pSprite, pXSprite, &cultistSearch);
247             break;
248         case kMediumWater:
249         case kMediumGoo:
250             aiNewState(pSprite, pXSprite, &cultistSwimSearch);
251             break;
252         }
253     }
254     aiThinkTarget(pSprite, pXSprite);
255 }
256 
thinkChase(spritetype * pSprite,XSPRITE * pXSprite)257 static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
258 {
259     if (pXSprite->target == -1)
260     {
261         switch (pXSprite->medium)
262         {
263         case kMediumNormal:
264             aiNewState(pSprite, pXSprite, &cultistGoto);
265             break;
266         case kMediumWater:
267         case kMediumGoo:
268             aiNewState(pSprite, pXSprite, &cultistSwimGoto);
269             break;
270         }
271         return;
272     }
273     dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
274     DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
275     dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
276     spritetype *pTarget = &sprite[pXSprite->target];
277     XSPRITE *pXTarget = &xsprite[pTarget->extra];
278     int dx = pTarget->x-pSprite->x;
279     int dy = pTarget->y-pSprite->y;
280     aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
281     if (pXTarget->health == 0)
282     {
283         switch (pXSprite->medium)
284         {
285         case kMediumNormal:
286             aiNewState(pSprite, pXSprite, &cultistSearch);
287             if (pSprite->type == kDudeCultistTommy)
288                 aiPlay3DSound(pSprite, 4021+Random(4), AI_SFX_PRIORITY_1, -1);
289             else
290                 aiPlay3DSound(pSprite, 1021+Random(4), AI_SFX_PRIORITY_1, -1);
291             break;
292         case kMediumWater:
293         case kMediumGoo:
294             aiNewState(pSprite, pXSprite, &cultistSwimSearch);
295             break;
296         }
297         return;
298     }
299     if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], kPwUpShadowCloak) > 0)
300     {
301         switch (pXSprite->medium)
302         {
303         case kMediumNormal:
304             aiNewState(pSprite, pXSprite, &cultistSearch);
305             break;
306         case kMediumWater:
307         case kMediumGoo:
308             aiNewState(pSprite, pXSprite, &cultistSwimSearch);
309             break;
310         }
311         return;
312     }
313     int nDist = approxDist(dx, dy);
314     if (nDist <= pDudeInfo->seeDist)
315     {
316         int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
317         int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
318         if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
319         {
320             if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
321             {
322                 aiSetTarget(pXSprite, pXSprite->target);
323                 int nXSprite = sprite[pXSprite->reference].extra;
324                 gDudeSlope[nXSprite] = divscale(pTarget->z-pSprite->z, nDist, 10);
325                 switch (pSprite->type) {
326                 case kDudeCultistTommy:
327                     if (nDist < 0x1e00 && nDist > 0xe00 && klabs(nDeltaAngle) < 85 && !TargetNearExplosion(pTarget)
328                         && (pTarget->flags&2) && gGameOptions.nDifficulty > 2 && IsPlayerSprite(pTarget) && gPlayer[pTarget->type-kDudePlayer1].isRunning
329                         && Chance(0x8000))
330                     {
331                         int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
332                         switch (hit)
333                         {
334                         case -1:
335                             if (pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
336                                 aiNewState(pSprite, pXSprite, &cultistTThrow);
337                             break;
338                         case 0:
339                         case 4:
340                             break;
341                         case 3:
342                             if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistShotgun && pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
343                                 aiNewState(pSprite, pXSprite, &cultistTThrow);
344                             break;
345                         default:
346                             aiNewState(pSprite, pXSprite, &cultistTThrow);
347                             break;
348                         }
349                     }
350                     else if (nDist < 0x4600 && klabs(nDeltaAngle) < 28)
351                     {
352                         int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
353                         switch (hit)
354                         {
355                         case -1:
356                             if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
357                                 aiNewState(pSprite, pXSprite, &cultistTFire);
358                             else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
359                                 aiNewState(pSprite, pXSprite, &cultistTProneFire);
360                             else if (sub_5BDA8(pSprite, 13) && (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo))
361                                 aiNewState(pSprite, pXSprite, &cultistTSwimFire);
362                             break;
363                         case 3:
364                             if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistShotgun)
365                             {
366                                 if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
367                                     aiNewState(pSprite, pXSprite, &cultistTFire);
368                                 else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
369                                     aiNewState(pSprite, pXSprite, &cultistTProneFire);
370                                 else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
371                                     aiNewState(pSprite, pXSprite, &cultistTSwimFire);
372                             }
373                             else
374                             {
375                                 if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
376                                     aiNewState(pSprite, pXSprite, &cultistDodge);
377                                 else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
378                                     aiNewState(pSprite, pXSprite, &cultistProneDodge);
379                                 else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
380                                     aiNewState(pSprite, pXSprite, &cultistSwimDodge);
381                             }
382                             break;
383                         default:
384                             if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
385                                 aiNewState(pSprite, pXSprite, &cultistTFire);
386                             else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
387                                 aiNewState(pSprite, pXSprite, &cultistTProneFire);
388                             else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
389                                 aiNewState(pSprite, pXSprite, &cultistTSwimFire);
390                             break;
391                         }
392                     }
393                     break;
394                 case kDudeCultistShotgun:
395                     if (nDist < 0x2c00 && nDist > 0x1400 && !TargetNearExplosion(pTarget)
396                         && (pTarget->flags&2) && gGameOptions.nDifficulty >= 2 && IsPlayerSprite(pTarget) && !gPlayer[pTarget->type-kDudePlayer1].isRunning
397                         && Chance(0x8000))
398                     {
399                         int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
400                         switch (hit)
401                         {
402                         case -1:
403                             if (pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
404                                 aiNewState(pSprite, pXSprite, &cultistSThrow);
405                             break;
406                         case 0:
407                         case 4:
408                             break;
409                         case 3:
410                             if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistShotgun && pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
411                                 aiNewState(pSprite, pXSprite, &cultistSThrow);
412                             break;
413                         default:
414                             aiNewState(pSprite, pXSprite, &cultistSThrow);
415                             break;
416                         }
417                     }
418                     else if (nDist < 0x3200 && klabs(nDeltaAngle) < 28)
419                     {
420                         int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
421                         switch (hit)
422                         {
423                         case -1:
424                             if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
425                                 aiNewState(pSprite, pXSprite, &cultistSFire);
426                             else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
427                                 aiNewState(pSprite, pXSprite, &cultistSProneFire);
428                             else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
429                                 aiNewState(pSprite, pXSprite, &cultistSSwimFire);
430                             break;
431                         case 3:
432                             if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistTommy)
433                             {
434                                 if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
435                                     aiNewState(pSprite, pXSprite, &cultistSFire);
436                                 else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
437                                     aiNewState(pSprite, pXSprite, &cultistSProneFire);
438                                 else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
439                                     aiNewState(pSprite, pXSprite, &cultistSSwimFire);
440                             }
441                             else
442                             {
443                                 if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
444                                     aiNewState(pSprite, pXSprite, &cultistDodge);
445                                 else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
446                                     aiNewState(pSprite, pXSprite, &cultistProneDodge);
447                                 else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
448                                     aiNewState(pSprite, pXSprite, &cultistSwimDodge);
449                             }
450                             break;
451                         default:
452                             if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
453                                 aiNewState(pSprite, pXSprite, &cultistSFire);
454                             else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
455                                 aiNewState(pSprite, pXSprite, &cultistSProneFire);
456                             else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
457                                 aiNewState(pSprite, pXSprite, &cultistSSwimFire);
458                             break;
459                         }
460                     }
461                     break;
462                 case kDudeCultistTesla:
463                     if (nDist < 0x1e00 && nDist > 0xe00 && !TargetNearExplosion(pTarget)
464                         && (pTarget->flags&2) && gGameOptions.nDifficulty > 2 && IsPlayerSprite(pTarget) && gPlayer[pTarget->type-kDudePlayer1].isRunning
465                         && Chance(0x8000))
466                     {
467                         int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
468                         switch (hit)
469                         {
470                         case -1:
471                             if (pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
472                                 aiNewState(pSprite, pXSprite, &cultistTsThrow);
473                             break;
474                         case 0:
475                         case 4:
476                             break;
477                         case 3:
478                             if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistShotgun && pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
479                                 aiNewState(pSprite, pXSprite, &cultistTsThrow);
480                             break;
481                         default:
482                             aiNewState(pSprite, pXSprite, &cultistTsThrow);
483                             break;
484                         }
485                     }
486                     else if (nDist < 0x3200 && klabs(nDeltaAngle) < 28)
487                     {
488                         int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
489                         switch (hit)
490                         {
491                         case -1:
492                             if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
493                                 aiNewState(pSprite, pXSprite, &cultistTsFire);
494                             else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
495                                 aiNewState(pSprite, pXSprite, &cultistTsProneFire);
496                             else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
497                                 aiNewState(pSprite, pXSprite, &cultistTsSwimFire);
498                             break;
499                         case 3:
500                             if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistTommy)
501                             {
502                                 if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
503                                     aiNewState(pSprite, pXSprite, &cultistTsFire);
504                                 else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
505                                     aiNewState(pSprite, pXSprite, &cultistTsProneFire);
506                                 else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
507                                     aiNewState(pSprite, pXSprite, &cultistTsSwimFire);
508                             }
509                             else
510                             {
511                                 if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
512                                     aiNewState(pSprite, pXSprite, &cultistDodge);
513                                 else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
514                                     aiNewState(pSprite, pXSprite, &cultistProneDodge);
515                                 else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
516                                     aiNewState(pSprite, pXSprite, &cultistSwimDodge);
517                             }
518                             break;
519                         default:
520                             if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
521                                 aiNewState(pSprite, pXSprite, &cultistTsFire);
522                             else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
523                                 aiNewState(pSprite, pXSprite, &cultistTsProneFire);
524                             else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
525                                 aiNewState(pSprite, pXSprite, &cultistTsSwimFire);
526                             break;
527                         }
528                     }
529                     break;
530                 case kDudeCultistTNT:
531                     if (nDist < 0x2c00 && nDist > 0x1400 && klabs(nDeltaAngle) < 85
532                         && (pTarget->flags&2) && IsPlayerSprite(pTarget))
533                     {
534                         int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
535                         switch (hit)
536                         {
537                         case -1:
538                             if (pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
539                                 aiNewState(pSprite, pXSprite, &cultistDThrow);
540                             break;
541                         case 4:
542                             break;
543                         case 3:
544                             if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistShotgun && pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
545                                 aiNewState(pSprite, pXSprite, &cultistDThrow);
546                             break;
547                         default:
548                             aiNewState(pSprite, pXSprite, &cultistDThrow);
549                             break;
550                         }
551                     }
552                     else if (nDist < 0x1400 && klabs(nDeltaAngle) < 85
553                         && (pTarget->flags&2) && IsPlayerSprite(pTarget))
554                     {
555                         int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
556                         switch (hit)
557                         {
558                         case -1:
559                             if (pXSprite->medium != 1 && pXSprite->medium != kMediumGoo)
560                                 aiNewState(pSprite, pXSprite, &cultist139A78);
561                             break;
562                         case 4:
563                             break;
564                         case 3:
565                             if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistShotgun && pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
566                                 aiNewState(pSprite, pXSprite, &cultist139A78);
567                             break;
568                         default:
569                             aiNewState(pSprite, pXSprite, &cultist139A78);
570                             break;
571                         }
572                     }
573                     break;
574                 case kDudeCultistBeast:
575                     if (nDist < 0x1e00 && nDist > 0xe00 && !TargetNearExplosion(pTarget)
576                         && (pTarget->flags&2) && gGameOptions.nDifficulty > 2 && IsPlayerSprite(pTarget) && gPlayer[pTarget->type-kDudePlayer1].isRunning
577                         && Chance(0x8000))
578                     {
579                         int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
580                         switch (hit)
581                         {
582                         case -1:
583                             if (pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
584                                 aiNewState(pSprite, pXSprite, &cultistSThrow);
585                             break;
586                         case 0:
587                         case 4:
588                             break;
589                         case 3:
590                             if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistShotgun && pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
591                                 aiNewState(pSprite, pXSprite, &cultistSThrow);
592                             break;
593                         default:
594                             aiNewState(pSprite, pXSprite, &cultistSThrow);
595                             break;
596                         }
597                     }
598                     else if (nDist < 0x3200 && klabs(nDeltaAngle) < 28)
599                     {
600                         int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
601                         switch (hit)
602                         {
603                         case -1:
604                             if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
605                                 aiNewState(pSprite, pXSprite, &cultistSFire);
606                             else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
607                                 aiNewState(pSprite, pXSprite, &cultistSProneFire);
608                             else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
609                                 aiNewState(pSprite, pXSprite, &cultistSSwimFire);
610                             break;
611                         case 3:
612                             if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistTommy)
613                             {
614                                 if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
615                                     aiNewState(pSprite, pXSprite, &cultistSFire);
616                                 else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
617                                     aiNewState(pSprite, pXSprite, &cultistSProneFire);
618                                 else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
619                                     aiNewState(pSprite, pXSprite, &cultistSSwimFire);
620                             }
621                             else
622                             {
623                                 if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
624                                     aiNewState(pSprite, pXSprite, &cultistDodge);
625                                 else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
626                                     aiNewState(pSprite, pXSprite, &cultistProneDodge);
627                                 else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
628                                     aiNewState(pSprite, pXSprite, &cultistSwimDodge);
629                             }
630                             break;
631                         default:
632                             if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
633                                 aiNewState(pSprite, pXSprite, &cultistSFire);
634                             else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == kMediumNormal)
635                                 aiNewState(pSprite, pXSprite, &cultistSProneFire);
636                             else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
637                                 aiNewState(pSprite, pXSprite, &cultistSSwimFire);
638                             break;
639                         }
640                     }
641                     break;
642                 }
643                 return;
644             }
645         }
646     }
647     switch (pXSprite->medium)
648     {
649     case kMediumNormal:
650         aiNewState(pSprite, pXSprite, &cultistGoto);
651         break;
652     case kMediumWater:
653     case kMediumGoo:
654         aiNewState(pSprite, pXSprite, &cultistSwimGoto);
655         break;
656     }
657     pXSprite->target = -1;
658 }
659