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