/ source / blood / src / aicult.cpp
aicult.cpp
  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 "endgame.h"
 36  #include "eventq.h"
 37  #include "levels.h"
 38  #include "player.h"
 39  #include "seq.h"
 40  #include "sfx.h"
 41  #include "trig.h"
 42  
 43  static void TommySeqCallback(int, int);
 44  static void TeslaSeqCallback(int, int);
 45  static void ShotSeqCallback(int, int);
 46  static void ThrowSeqCallback(int, int);
 47  static void sub_68170(int, int);
 48  static void sub_68230(int, int);
 49  static void thinkSearch(spritetype *, XSPRITE *);
 50  static void thinkGoto(spritetype *, XSPRITE *);
 51  static void thinkChase(spritetype *, XSPRITE *);
 52  
 53  static int nTommyClient = seqRegisterClient(TommySeqCallback);
 54  static int nTeslaClient = seqRegisterClient(TeslaSeqCallback);
 55  static int nShotClient = seqRegisterClient(ShotSeqCallback);
 56  static int nThrowClient = seqRegisterClient(ThrowSeqCallback);
 57  static int n68170Client = seqRegisterClient(sub_68170);
 58  static int n68230Client = seqRegisterClient(sub_68230);
 59  
 60  AISTATE cultistIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
 61  AISTATE cultistProneIdle = { kAiStateIdle, 17, -1, 0, NULL, NULL, aiThinkTarget, NULL };
 62  AISTATE fanaticProneIdle = { kAiStateIdle, 17, -1, 0, NULL, NULL, aiThinkTarget, NULL };
 63  AISTATE cultistProneIdle3 = { kAiStateIdle, 17, -1, 0, NULL, NULL, aiThinkTarget, NULL };
 64  AISTATE cultistChase = { kAiStateChase, 9, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
 65  AISTATE fanaticChase = { kAiStateChase, 0, -1, 0, NULL, aiMoveTurn, thinkChase, NULL };
 66  AISTATE cultistDodge = { kAiStateMove, 9, -1, 90, NULL, aiMoveDodge, NULL, &cultistChase };
 67  AISTATE cultistGoto = { kAiStateMove, 9, -1, 600, NULL, aiMoveForward, thinkGoto, &cultistIdle };
 68  AISTATE cultistProneChase = { kAiStateChase, 14, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
 69  AISTATE cultistProneDodge = { kAiStateMove, 14, -1, 90, NULL, aiMoveDodge, NULL, &cultistProneChase };
 70  AISTATE cultistTThrow = { kAiStateChase, 7, nThrowClient, 120, NULL, NULL, NULL, &cultistTFire };
 71  AISTATE cultistSThrow = { kAiStateChase, 7, nThrowClient, 120, NULL, NULL, NULL, &cultistSFire };
 72  AISTATE cultistTsThrow = { kAiStateChase, 7, nThrowClient, 120, NULL, NULL, NULL, &cultistTsFire };
 73  AISTATE cultistDThrow = { kAiStateChase, 7, nThrowClient, 120, NULL, NULL, NULL, &cultistChase };
 74  AISTATE cultist139A78 = { kAiStateChase, 7, n68170Client, 120, NULL, NULL, NULL, &cultistChase };
 75  AISTATE cultist139A94 = { kAiStateChase, 7, n68230Client, 120, NULL, NULL, NULL, &cultistIdle };
 76  AISTATE cultist139AB0 = { kAiStateChase, 7, n68230Client, 120, NULL, NULL, thinkSearch, &cultist139A94 };
 77  AISTATE cultist139ACC = { kAiStateChase, 7, n68230Client, 120, NULL, NULL, thinkSearch, &cultist139AB0 };
 78  AISTATE cultist139AE8 = { kAiStateChase, 7, n68230Client, 120, NULL, NULL, thinkSearch, &cultist139AE8 };
 79  AISTATE cultistSearch = { kAiStateSearch, 9, -1, 1800, NULL, aiMoveForward, thinkSearch, &cultistIdle };
 80  AISTATE cultistSFire = { kAiStateChase, 6, nShotClient, 60, NULL, NULL, NULL, &cultistChase };
 81  AISTATE cultistTFire = { kAiStateChase, 6, nTommyClient, 0, NULL, aiMoveTurn, thinkChase, &cultistTFire };
 82  AISTATE cultistTsFire = { kAiStateChase, 6, nTeslaClient, 0, NULL, aiMoveTurn, thinkChase, &cultistChase };
 83  AISTATE cultistSProneFire = { kAiStateChase, 8, nShotClient, 60, NULL, NULL, NULL, &cultistProneChase };
 84  AISTATE cultistTProneFire = { kAiStateChase, 8, nTommyClient, 0, NULL, aiMoveTurn, thinkChase, &cultistTProneFire };
 85  AISTATE cultistTsProneFire = { kAiStateChase, 8, nTeslaClient, 0, NULL, aiMoveTurn, NULL, &cultistTsProneFire }; // vanilla, broken
 86  AISTATE cultistTsProneFireFixed = { kAiStateChase, 8, nTeslaClient, 0, NULL, aiMoveTurn, thinkChase, &cultistTsProneFireFixed };
 87  AISTATE cultistRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &cultistDodge };
 88  AISTATE cultistProneRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &cultistProneDodge };
 89  AISTATE cultistTeslaRecoil = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &cultistDodge };
 90  AISTATE cultistSwimIdle = { kAiStateIdle, 13, -1, 0, NULL, NULL, aiThinkTarget, NULL };
 91  AISTATE cultistSwimChase = { kAiStateChase, 13, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
 92  AISTATE cultistSwimDodge = { kAiStateMove, 13, -1, 90, NULL, aiMoveDodge, NULL, &cultistSwimChase };
 93  AISTATE cultistSwimGoto = { kAiStateMove, 13, -1, 600, NULL, aiMoveForward, thinkGoto, &cultistSwimIdle };
 94  AISTATE cultistSwimSearch = { kAiStateSearch, 13, -1, 1800, NULL, aiMoveForward, thinkSearch, &cultistSwimIdle };
 95  AISTATE cultistSSwimFire = { kAiStateChase, 8, nShotClient, 60, NULL, NULL, NULL, &cultistSwimChase };
 96  AISTATE cultistTSwimFire = { kAiStateChase, 8, nTommyClient, 0, NULL, aiMoveTurn, thinkChase, &cultistTSwimFire };
 97  AISTATE cultistTsSwimFire = { kAiStateChase, 8, nTeslaClient, 0, NULL, aiMoveTurn, thinkChase, &cultistTsSwimFire };
 98  AISTATE cultistSwimRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &cultistSwimDodge };
 99  
100  static void TommySeqCallback(int, int nXSprite)
101  {
102      XSPRITE *pXSprite = &xsprite[nXSprite];
103      int nSprite = pXSprite->reference;
104      spritetype *pSprite = &sprite[nSprite];
105      int dx = Cos(pSprite->ang) >> 16;
106      int dy = Sin(pSprite->ang) >> 16;
107      int dz = gDudeSlope[nXSprite];
108      dx += Random3((5-gGameOptions.nDifficulty)*1000);
109      dy += Random3((5-gGameOptions.nDifficulty)*1000);
110      dz += Random3((5-gGameOptions.nDifficulty)*500);
111      bool useProjectile = gGameOptions.nHitscanProjectiles && !VanillaMode(); // if enemy hitscan projectiles are enabled, spawn bullet projectile
112      if (useProjectile && spriRangeIsFine(pXSprite->target)) // if target is valid
113      {
114          spritetype *pTarget = &sprite[pXSprite->target];
115          if (klabs(pSprite->z-pTarget->z) < 30000) // if height difference is under 30000, calculate prediction for projectile
116          {
117              const int nDiff = approxDist(dx, dy);
118              int nDist = ClipRange(approxDist(pSprite->x-pTarget->x, pSprite->y-pTarget->y), 0, 20000);
119              if (nDist < 1250) // target is very close, just use hitscan
120                  useProjectile = false;
121              else if (gGameOptions.nDifficulty > 2) // if difficulty is above lightly broiled
122              {
123                  dx += ((xvel[pTarget->index]+nDiff)>>8)+((xvel[pTarget->index]+nDiff)>>9)+((xvel[pTarget->index]+nDiff)>>10);
124                  dy += ((yvel[pTarget->index]+nDiff)>>8)+((yvel[pTarget->index]+nDiff)>>9)+((yvel[pTarget->index]+nDiff)>>10);
125              }
126          }
127      }
128      if (!useProjectile) // normal hitscan
129          actFireVector(pSprite, 0, 0, dx, dy, dz, kVectorBullet);
130      else // projectile
131          actFireMissile(pSprite, 0, 0, dx, dy, dz, kMissileBullet);
132  #ifdef NOONE_EXTENSIONS
133      if (!gModernMap && EnemiesNotBlood() && !VanillaMode())
134  #else
135      if (EnemiesNotBlood() && !VanillaMode())
136  #endif
137          fxSpawnEjectingBrass(pSprite, pSprite->z, 25, 35);
138      sfxPlay3DSound(pSprite, 4001, -1, 0);
139  }
140  
141  static void TeslaSeqCallback(int, int nXSprite)
142  {
143      XSPRITE *pXSprite = &xsprite[nXSprite];
144      int nSprite = pXSprite->reference;
145      spritetype *pSprite = &sprite[nSprite];
146      if (Chance(gCultTeslaFireChance[gGameOptions.nDifficulty]))
147      {
148          int dx = Cos(pSprite->ang) >> 16;
149          int dy = Sin(pSprite->ang) >> 16;
150          int dz = gDudeSlope[nXSprite];
151          dx += Random3((5-gGameOptions.nDifficulty)*1000);
152          dy += Random3((5-gGameOptions.nDifficulty)*1000);
153          dz += Random3((5-gGameOptions.nDifficulty)*500);
154          actFireMissile(pSprite, 0, 0, dx, dy, dz, kMissileTeslaRegular);
155          sfxPlay3DSound(pSprite, 470, -1, 0);
156      }
157  }
158  
159  static void ShotSeqCallback(int, int nXSprite)
160  {
161      XSPRITE *pXSprite = &xsprite[nXSprite];
162      int nSprite = pXSprite->reference;
163      spritetype *pSprite = &sprite[nSprite];
164      int dx = Cos(pSprite->ang) >> 16;
165      int dy = Sin(pSprite->ang) >> 16;
166      int dz = gDudeSlope[nXSprite];
167      dx += Random2((5-gGameOptions.nDifficulty)*1000-500);
168      dy += Random2((5-gGameOptions.nDifficulty)*1000-500);
169      dz += Random2((5-gGameOptions.nDifficulty)*500);
170      bool useProjectile = gGameOptions.nHitscanProjectiles && !VanillaMode(); // if enemy hitscan projectiles are enabled, spawn bullet projectile
171      if (useProjectile && spriRangeIsFine(pXSprite->target)) // if target is valid
172      {
173          const spritetype *pTarget = &sprite[pXSprite->target];
174          if ((klabs(pSprite->z-pTarget->z) < 30000)) // if height difference is under 30000, calculate prediction for projectile
175          {
176              const int nDiff = approxDist(dx, dy);
177              int nDist = ClipRange(approxDist(pSprite->x-pTarget->x, pSprite->y-pTarget->y), 0, 20000);
178              if (nDist < 1250) // target is very close, just use hitscan
179                  useProjectile = false;
180              else if ((gGameOptions.nDifficulty > 2) && Random2(gGameOptions.nDifficulty+2)) // if difficulty is above lightly broiled
181              {
182                  dx += (xvel[pTarget->index]+nDiff)>>7;
183                  dy += (yvel[pTarget->index]+nDiff)>>7;
184              }
185          }
186      }
187      for (int i = 0; i < 8; i++)
188      {
189          int r1 = Random3(500);
190          int r2 = Random3(1000);
191          int r3 = Random3(1000);
192          if (!useProjectile) // normal hitscan
193              actFireVector(pSprite, 0, 0, dx+r3, dy+r2, dz+r1, kVectorShell);
194          else // projectile
195              actFireMissile(pSprite, 0, 0, dx+r3, dy+r2, dz+r1, kMissileShell);
196      }
197  #ifdef NOONE_EXTENSIONS
198      if (!gModernMap && EnemiesNotBlood() && !VanillaMode())
199  #else
200      if (EnemiesNotBlood() && !VanillaMode())
201  #endif
202          fxSpawnEjectingShell(pSprite, pSprite->z, 25, 35);
203      if (Chance(0x8000))
204          sfxPlay3DSound(pSprite, 1001, -1, 0);
205      else
206          sfxPlay3DSound(pSprite, 1002, -1, 0);
207  }
208  
209  static void ThrowSeqCallback(int, int nXSprite)
210  {
211      XSPRITE *pXSprite = &xsprite[nXSprite];
212      int nSprite = pXSprite->reference;
213      spritetype *pSprite = &sprite[nSprite];
214      if (!sectRangeIsFine(pSprite->sectnum)) // invalid sector, abort
215          return;
216      if (!VanillaMode() && !spriRangeIsFine(pXSprite->target)) // not a valid target, abort
217          return thinkChase(pSprite, pXSprite);
218      int nMissile = kThingArmedTNTStick;
219      if (gGameOptions.nDifficulty > 2)
220          nMissile = kThingArmedTNTBundle;
221      if (gGameOptions.bEnemyRandomTNT && !VanillaMode()) // if enemy random tnt is on, and not in vanilla mode, randomly change thrown tnt type
222      {
223          if (!Random(20))
224              nMissile = kThingNapalmBall;
225          else if (!Random(15))
226              nMissile = kThingArmedSpray;
227          else if (!Random(5))
228              nMissile = kThingArmedProxBomb;
229          else if (!Random(25))
230              nMissile = kThingZombieHead;
231          else if (!Random(10))
232              nMissile = kThingPodFireBall;
233          else if (!Random(15))
234              nMissile = kThingPodGreenBall;
235          else if (!Random(35))
236          {
237              spritetype *pSpawn = actSpawnDude(pSprite, pSprite->type, pSprite->clipdist<<1, 0);
238              if (pSpawn)
239              {
240                  gKillMgr.AddCount(pSpawn);
241                  pSpawn->ang = pSprite->ang;
242                  nSprite = pSprite->index;
243                  int x = Cos(pSprite->ang)>>16;
244                  int y = Sin(pSprite->ang)>>16;
245                  xvel[pSpawn->index] = xvel[nSprite] + mulscale14(0x155555<<1, x);
246                  yvel[pSpawn->index] = yvel[nSprite] + mulscale14(0x155555<<1, y);
247                  zvel[pSpawn->index] = -1500000;
248                  return;
249              }
250          }
251          else if (!Random(35)) // throw self forward
252          {
253              int x = Cos(pSprite->ang)>>16;
254              int y = Sin(pSprite->ang)>>16;
255              xvel[nSprite] = xvel[nSprite] + mulscale14(0x155555<<1, x);
256              yvel[nSprite] = yvel[nSprite] + mulscale14(0x155555<<1, y);
257              zvel[nSprite] = -1250000;
258              return;
259          }
260      }
261      char v4 = Chance(0x6000);
262      sfxPlay3DSound(pSprite, 455, -1, 0);
263      dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
264      spritetype *pTarget = &sprite[pXSprite->target];
265      dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
266      int dx = pTarget->x - pSprite->x;
267      int dy = pTarget->y - pSprite->y;
268      int dz = pTarget->z - pSprite->z;
269      int nDist = approxDist(dx, dy);
270      int nDist2 = nDist / 540;
271      if (nDist > 0x1e00)
272          v4 = 0;
273      spritetype *pMissile = actFireThing(pSprite, 0, 0, dz/128-14500, nMissile, (nDist2<<23)/120);
274      if (v4)
275          xsprite[pMissile->extra].Impact = 1;
276      else
277          evPost(pMissile->index, 3, 120*(1+Random(2)), kCmdOn, nSprite);
278  }
279  
280  static void sub_68170(int, int nXSprite)
281  {
282      XSPRITE *pXSprite = &xsprite[nXSprite];
283      int nSprite = pXSprite->reference;
284      spritetype *pSprite = &sprite[nSprite];
285      if (!sectRangeIsFine(pSprite->sectnum)) // invalid sector, abort
286          return;
287      int nMissile = kThingArmedTNTStick;
288      if (gGameOptions.nDifficulty > 2)
289          nMissile = kThingArmedTNTBundle;
290      if (gGameOptions.bEnemyRandomTNT && !VanillaMode()) // if enemy random tnt is on, and not in vanilla mode, randomly change thrown tnt type
291      {
292          if (!Random(20))
293              nMissile = kThingNapalmBall;
294          else if(!Random(15))
295              nMissile = kThingArmedSpray;
296          else if(!Random(5))
297              nMissile = kThingArmedProxBomb;
298          else if(!Random(25))
299              nMissile = kThingZombieHead;
300          else if(!Random(10))
301              nMissile = kThingPodFireBall;
302          else if(!Random(15))
303              nMissile = kThingPodGreenBall;
304      }
305  
306      sfxPlay3DSound(pSprite, 455, -1, 0);
307      spritetype *pMissile = actFireThing(pSprite, 0, 0, gDudeSlope[nXSprite]-9460, nMissile, 0x133333);
308      evPost(pMissile->index, 3, 120*(2+Random(2)), kCmdOn, nSprite);
309  }
310  
311  static void sub_68230(int, int nXSprite)
312  {
313      XSPRITE *pXSprite = &xsprite[nXSprite];
314      int nSprite = pXSprite->reference;
315      spritetype *pSprite = &sprite[nSprite];
316      if (!sectRangeIsFine(pSprite->sectnum)) // invalid sector, abort
317          return;
318      if (!VanillaMode() && !spriRangeIsFine(pXSprite->target)) // not a valid target, abort
319          return thinkChase(pSprite, pXSprite);
320      int nMissile = kThingArmedTNTStick;
321      if (gGameOptions.nDifficulty > 2)
322          nMissile = kThingArmedTNTBundle;
323      if (gGameOptions.bEnemyRandomTNT && !VanillaMode()) // if enemy random tnt is on, and not in vanilla mode, randomly change thrown tnt type
324      {
325          if (!Random(20))
326              nMissile = kThingNapalmBall;
327          else if(!Random(15))
328              nMissile = kThingArmedSpray;
329          else if(!Random(5))
330              nMissile = kThingArmedProxBomb;
331          else if(!Random(25))
332              nMissile = kThingZombieHead;
333          else if(!Random(10))
334              nMissile = kThingPodFireBall;
335          else if(!Random(15))
336              nMissile = kThingPodGreenBall;
337      }
338  
339      sfxPlay3DSound(pSprite, 455, -1, 0);
340      dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
341      spritetype *pTarget = &sprite[pXSprite->target];
342      dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
343      int dx = pTarget->x - pSprite->x;
344      int dy = pTarget->y - pSprite->y;
345      int dz = pTarget->z - pSprite->z;
346      int nDist = approxDist(dx, dy);
347      int nDist2 = nDist / 540;
348      spritetype *pMissile = actFireThing(pSprite, 0, 0, dz/128-14500, nMissile, (nDist2<<23)/120);
349      xsprite[pMissile->extra].Impact = 1;
350  }
351  
352  static char TargetNearExplosion(spritetype *pSprite)
353  {
354      for (short nSprite = headspritesect[pSprite->sectnum]; nSprite >= 0; nSprite = nextspritesect[nSprite])
355      {
356          if (sprite[nSprite].type == kThingArmedTNTStick || sprite[nSprite].statnum == kStatExplosion)
357              return 1;
358      }
359      return 0;
360  }
361  
362  static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
363  {
364      aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
365      aiLookForTarget(pSprite, pXSprite);
366  }
367  
368  static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
369  {
370      dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
371      DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
372      int dx = pXSprite->targetX-pSprite->x;
373      int dy = pXSprite->targetY-pSprite->y;
374      int nAngle = getangle(dx, dy);
375      int nDist = approxDist(dx, dy);
376      aiChooseDirection(pSprite, pXSprite, nAngle);
377      if (nDist < 5120 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
378      {
379          switch (pXSprite->medium)
380          {
381          case kMediumNormal:
382              aiNewState(pSprite, pXSprite, &cultistSearch);
383              break;
384          case kMediumWater:
385          case kMediumGoo:
386              aiNewState(pSprite, pXSprite, &cultistSwimSearch);
387              break;
388          }
389      }
390      aiThinkTarget(pSprite, pXSprite);
391  }
392  
393  static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
394  {
395      if (pXSprite->target == -1)
396      {
397          switch (pXSprite->medium)
398          {
399          case kMediumNormal:
400              aiNewState(pSprite, pXSprite, &cultistGoto);
401              break;
402          case kMediumWater:
403          case kMediumGoo:
404              aiNewState(pSprite, pXSprite, &cultistSwimGoto);
405              break;
406          }
407          return;
408      }
409      dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
410      DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
411      dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
412      spritetype *pTarget = &sprite[pXSprite->target];
413      XSPRITE *pXTarget = &xsprite[pTarget->extra];
414      int dx = pTarget->x-pSprite->x;
415      int dy = pTarget->y-pSprite->y;
416      aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
417      if (pXTarget->health == 0)
418      {
419          switch (pXSprite->medium)
420          {
421          case kMediumNormal:
422              aiNewState(pSprite, pXSprite, &cultistSearch);
423              if (pSprite->type == kDudeCultistTommy)
424                  aiPlay3DSound(pSprite, 4021+Random(4), AI_SFX_PRIORITY_1, -1);
425              else
426                  aiPlay3DSound(pSprite, 1021+Random(4), AI_SFX_PRIORITY_1, -1);
427              break;
428          case kMediumWater:
429          case kMediumGoo:
430              aiNewState(pSprite, pXSprite, &cultistSwimSearch);
431              break;
432          }
433          return;
434      }
435      if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], kPwUpShadowCloak) > 0)
436      {
437          switch (pXSprite->medium)
438          {
439          case kMediumNormal:
440              aiNewState(pSprite, pXSprite, &cultistSearch);
441              break;
442          case kMediumWater:
443          case kMediumGoo:
444              aiNewState(pSprite, pXSprite, &cultistSwimSearch);
445              break;
446          }
447          return;
448      }
449      int nDist = approxDist(dx, dy);
450      if (nDist <= pDudeInfo->seeDist)
451      {
452          int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
453          int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
454          if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
455          {
456              if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
457              {
458                  aiSetTarget(pXSprite, pXSprite->target);
459                  int nXSprite = sprite[pXSprite->reference].extra;
460                  gDudeSlope[nXSprite] = divscale10(pTarget->z-pSprite->z, nDist);
461                  switch (pSprite->type) {
462                  case kDudeCultistTommy:
463                      if (nDist < 0x1e00 && nDist > 0xe00 && klabs(nDeltaAngle) < 85 && !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, &cultistTThrow);
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, &cultistTThrow);
480                              break;
481                          default:
482                              aiNewState(pSprite, pXSprite, &cultistTThrow);
483                              break;
484                          }
485                      }
486                      else if (nDist < 0x4600 && 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 (!dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
493                                  aiNewState(pSprite, pXSprite, &cultistTFire);
494                              else if (dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
495                                  aiNewState(pSprite, pXSprite, &cultistTProneFire);
496                              else if (dudeIsPlayingSeq(pSprite, 13) && (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo))
497                                  aiNewState(pSprite, pXSprite, &cultistTSwimFire);
498                              break;
499                          case 3:
500                              if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistShotgun)
501                              {
502                                  if (!dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
503                                      aiNewState(pSprite, pXSprite, &cultistTFire);
504                                  else if (dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
505                                      aiNewState(pSprite, pXSprite, &cultistTProneFire);
506                                  else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
507                                      aiNewState(pSprite, pXSprite, &cultistTSwimFire);
508                              }
509                              else
510                              {
511                                  if (!dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
512                                      aiNewState(pSprite, pXSprite, &cultistDodge);
513                                  else if (dudeIsPlayingSeq(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 (!dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
521                                  aiNewState(pSprite, pXSprite, &cultistTFire);
522                              else if (dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
523                                  aiNewState(pSprite, pXSprite, &cultistTProneFire);
524                              else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
525                                  aiNewState(pSprite, pXSprite, &cultistTSwimFire);
526                              break;
527                          }
528                      }
529                      break;
530                  case kDudeCultistShotgun:
531                      if (nDist < 0x2c00 && nDist > 0x1400 && !TargetNearExplosion(pTarget)
532                          && (pTarget->flags&2) && gGameOptions.nDifficulty >= 2 && IsPlayerSprite(pTarget) && !gPlayer[pTarget->type-kDudePlayer1].isRunning
533                          && Chance(0x8000))
534                      {
535                          int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
536                          switch (hit)
537                          {
538                          case -1:
539                              if (pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
540                                  aiNewState(pSprite, pXSprite, &cultistSThrow);
541                              break;
542                          case 0:
543                          case 4:
544                              break;
545                          case 3:
546                              if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistShotgun && pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
547                                  aiNewState(pSprite, pXSprite, &cultistSThrow);
548                              break;
549                          default:
550                              aiNewState(pSprite, pXSprite, &cultistSThrow);
551                              break;
552                          }
553                      }
554                      else if (nDist < 0x3200 && klabs(nDeltaAngle) < 28)
555                      {
556                          int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
557                          switch (hit)
558                          {
559                          case -1:
560                              if (!dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
561                                  aiNewState(pSprite, pXSprite, &cultistSFire);
562                              else if (dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
563                                  aiNewState(pSprite, pXSprite, &cultistSProneFire);
564                              else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
565                                  aiNewState(pSprite, pXSprite, &cultistSSwimFire);
566                              break;
567                          case 3:
568                              if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistTommy)
569                              {
570                                  if (!dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
571                                      aiNewState(pSprite, pXSprite, &cultistSFire);
572                                  else if (dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
573                                      aiNewState(pSprite, pXSprite, &cultistSProneFire);
574                                  else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
575                                      aiNewState(pSprite, pXSprite, &cultistSSwimFire);
576                              }
577                              else
578                              {
579                                  if (!dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
580                                      aiNewState(pSprite, pXSprite, &cultistDodge);
581                                  else if (dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
582                                      aiNewState(pSprite, pXSprite, &cultistProneDodge);
583                                  else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
584                                      aiNewState(pSprite, pXSprite, &cultistSwimDodge);
585                              }
586                              break;
587                          default:
588                              if (!dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
589                                  aiNewState(pSprite, pXSprite, &cultistSFire);
590                              else if (dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
591                                  aiNewState(pSprite, pXSprite, &cultistSProneFire);
592                              else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
593                                  aiNewState(pSprite, pXSprite, &cultistSSwimFire);
594                              break;
595                          }
596                      }
597                      break;
598                  case kDudeCultistTesla:
599                      if (nDist < 0x1e00 && nDist > 0xe00 && !TargetNearExplosion(pTarget)
600                          && (pTarget->flags&2) && gGameOptions.nDifficulty > 2 && IsPlayerSprite(pTarget) && gPlayer[pTarget->type-kDudePlayer1].isRunning
601                          && Chance(0x8000))
602                      {
603                          int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
604                          switch (hit)
605                          {
606                          case -1:
607                              if (pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
608                                  aiNewState(pSprite, pXSprite, &cultistTsThrow);
609                              break;
610                          case 0:
611                          case 4:
612                              break;
613                          case 3:
614                              if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistShotgun && pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
615                                  aiNewState(pSprite, pXSprite, &cultistTsThrow);
616                              break;
617                          default:
618                              aiNewState(pSprite, pXSprite, &cultistTsThrow);
619                              break;
620                          }
621                      }
622                      else if (nDist < 0x3200 && klabs(nDeltaAngle) < 28)
623                      {
624                          AISTATE *pCultistTsProneFire = EnemiesNBlood() && !VanillaMode() ? &cultistTsProneFireFixed : &cultistTsProneFire; // use non-glitched prone fire state
625                          int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
626                          switch (hit)
627                          {
628                          case -1:
629                              if (!dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
630                                  aiNewState(pSprite, pXSprite, &cultistTsFire);
631                              else if (dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
632                                  aiNewState(pSprite, pXSprite, pCultistTsProneFire);
633                              else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
634                                  aiNewState(pSprite, pXSprite, &cultistTsSwimFire);
635                              break;
636                          case 3:
637                              if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistTommy)
638                              {
639                                  if (!dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
640                                      aiNewState(pSprite, pXSprite, &cultistTsFire);
641                                  else if (dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
642                                      aiNewState(pSprite, pXSprite, pCultistTsProneFire);
643                                  else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
644                                      aiNewState(pSprite, pXSprite, &cultistTsSwimFire);
645                              }
646                              else
647                              {
648                                  if (!dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
649                                      aiNewState(pSprite, pXSprite, &cultistDodge);
650                                  else if (dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
651                                      aiNewState(pSprite, pXSprite, &cultistProneDodge);
652                                  else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
653                                      aiNewState(pSprite, pXSprite, &cultistSwimDodge);
654                              }
655                              break;
656                          default:
657                              if (!dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
658                                  aiNewState(pSprite, pXSprite, &cultistTsFire);
659                              else if (dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
660                                  aiNewState(pSprite, pXSprite, pCultistTsProneFire);
661                              else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
662                                  aiNewState(pSprite, pXSprite, &cultistTsSwimFire);
663                              break;
664                          }
665                      }
666                      break;
667                  case kDudeCultistTNT:
668                      if (nDist < 0x2c00 && nDist > 0x1400 && klabs(nDeltaAngle) < 85
669                          && (pTarget->flags&2) && IsPlayerSprite(pTarget))
670                      {
671                          int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
672                          switch (hit)
673                          {
674                          case -1:
675                              if (pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
676                                  aiNewState(pSprite, pXSprite, &cultistDThrow);
677                              break;
678                          case 4:
679                              break;
680                          case 3:
681                              if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistShotgun && pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
682                                  aiNewState(pSprite, pXSprite, &cultistDThrow);
683                              break;
684                          default:
685                              aiNewState(pSprite, pXSprite, &cultistDThrow);
686                              break;
687                          }
688                      }
689                      else if (nDist < 0x1400 && klabs(nDeltaAngle) < 85
690                          && (pTarget->flags&2) && IsPlayerSprite(pTarget))
691                      {
692                          int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
693                          switch (hit)
694                          {
695                          case -1:
696                              if (pXSprite->medium != 1 && pXSprite->medium != kMediumGoo)
697                                  aiNewState(pSprite, pXSprite, &cultist139A78);
698                              break;
699                          case 4:
700                              break;
701                          case 3:
702                              if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistShotgun && pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
703                                  aiNewState(pSprite, pXSprite, &cultist139A78);
704                              break;
705                          default:
706                              aiNewState(pSprite, pXSprite, &cultist139A78);
707                              break;
708                          }
709                      }
710                      break;
711                  case kDudeCultistBeast:
712                      if (nDist < 0x1e00 && nDist > 0xe00 && !TargetNearExplosion(pTarget)
713                          && (pTarget->flags&2) && gGameOptions.nDifficulty > 2 && IsPlayerSprite(pTarget) && gPlayer[pTarget->type-kDudePlayer1].isRunning
714                          && Chance(0x8000))
715                      {
716                          int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
717                          switch (hit)
718                          {
719                          case -1:
720                              if (pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
721                                  aiNewState(pSprite, pXSprite, &cultistSThrow);
722                              break;
723                          case 0:
724                          case 4:
725                              break;
726                          case 3:
727                              if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistShotgun && pXSprite->medium != kMediumWater && pXSprite->medium != kMediumGoo)
728                                  aiNewState(pSprite, pXSprite, &cultistSThrow);
729                              break;
730                          default:
731                              aiNewState(pSprite, pXSprite, &cultistSThrow);
732                              break;
733                          }
734                      }
735                      else if (nDist < 0x3200 && klabs(nDeltaAngle) < 28)
736                      {
737                          int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
738                          switch (hit)
739                          {
740                          case -1:
741                              if (!dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
742                                  aiNewState(pSprite, pXSprite, &cultistSFire);
743                              else if (dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
744                                  aiNewState(pSprite, pXSprite, &cultistSProneFire);
745                              else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
746                                  aiNewState(pSprite, pXSprite, &cultistSSwimFire);
747                              break;
748                          case 3:
749                              if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeCultistTommy)
750                              {
751                                  if (!dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
752                                      aiNewState(pSprite, pXSprite, &cultistSFire);
753                                  else if (dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
754                                      aiNewState(pSprite, pXSprite, &cultistSProneFire);
755                                  else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
756                                      aiNewState(pSprite, pXSprite, &cultistSSwimFire);
757                              }
758                              else
759                              {
760                                  if (!dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
761                                      aiNewState(pSprite, pXSprite, &cultistDodge);
762                                  else if (dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
763                                      aiNewState(pSprite, pXSprite, &cultistProneDodge);
764                                  else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
765                                      aiNewState(pSprite, pXSprite, &cultistSwimDodge);
766                              }
767                              break;
768                          default:
769                              if (!dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
770                                  aiNewState(pSprite, pXSprite, &cultistSFire);
771                              else if (dudeIsPlayingSeq(pSprite, 14) && pXSprite->medium == kMediumNormal)
772                                  aiNewState(pSprite, pXSprite, &cultistSProneFire);
773                              else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
774                                  aiNewState(pSprite, pXSprite, &cultistSSwimFire);
775                              break;
776                          }
777                      }
778                      break;
779                  }
780                  return;
781              }
782          }
783      }
784      switch (pXSprite->medium)
785      {
786      case kMediumNormal:
787          aiNewState(pSprite, pXSprite, &cultistGoto);
788          break;
789      case kMediumWater:
790      case kMediumGoo:
791          aiNewState(pSprite, pXSprite, &cultistSwimGoto);
792          break;
793      }
794      pXSprite->target = -1;
795  }