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 }