#include #include #include "aicult.h" #include "actor.h" #include "db.h" #include "debug4g.h" #include "dude.h" #include "engine.h" #include "eventq.h" #include "gameutil.h" #include "globals.h" #include "misc.h" #include "multi.h" #include "player.h" #include "sfx.h" #include "trig.h" /**************************************************************************** ** LOCAL CONSTANTS ****************************************************************************/ #define kCultistTFireDist M2X(100) #define kCultistSFireDist M2X(10) #define kCultistThrowDist1 M2X(16) #define kCultistThrowDist2 M2X(10) #define kSlopeThrow -8192 // tan(26) #define kVectorsPerBarrel 8 /**************************************************************************** ** LOCAL callback prototypes ****************************************************************************/ static void TommyCallback( int /* type */, int nXIndex ); static void ShotCallback( int /* type */, int nXIndex ); static void ThrowCallback( int /* type */, int nXIndex ); /**************************************************************************** ** LOCAL think/move function prototypes ****************************************************************************/ static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite ); static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite ); static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite ); /**************************************************************************** ** GLOBAL CONSTANTS ****************************************************************************/ AISTATE cultistIdle = { kSeqDudeIdle, NULL, 0, NULL, NULL, aiThinkTarget, NULL }; AISTATE cultistChase = { kSeqCultistWalk, NULL, 0, NULL, aiMoveForward, thinkChase, NULL }; AISTATE cultistDodge = { kSeqCultistWalk, NULL, 90, NULL, aiMoveDodge, NULL, &cultistChase }; AISTATE cultistGoto = { kSeqCultistWalk, NULL, 3600, NULL, aiMoveForward, thinkGoto, &cultistIdle }; AISTATE cultistTThrow = { kSeqCultistAttack2, ThrowCallback, 120, NULL, NULL, NULL, &cultistTFire }; AISTATE cultistSThrow = { kSeqCultistAttack2, ThrowCallback, 120, NULL, NULL, NULL, &cultistSFire }; AISTATE cultistSearch = { kSeqCultistWalk, NULL, 3600, NULL, aiMoveForward, thinkSearch, &cultistIdle }; AISTATE cultistSFire = { kSeqCultistAttack1, ShotCallback, 60, NULL, NULL, NULL, &cultistChase }; AISTATE cultistTFire = { kSeqCultistAttack1, TommyCallback, 0, NULL, aiMoveTurn, thinkChase, &cultistTFire }; AISTATE cultistRecoil = { kSeqDudeRecoil, NULL, 0, NULL, NULL, NULL, &cultistDodge }; /**************************************************************************** ** LOCAL FUNCTIONS ****************************************************************************/ /**************************************************************************** ** TommyCallback() ** ** ****************************************************************************/ static void TommyCallback( int /* type */, int nXIndex ) { XSPRITE *pXSprite = &xsprite[nXIndex]; int nSprite = pXSprite->reference; SPRITE *pSprite = &sprite[nSprite]; int dx = Cos(pSprite->ang) >> 16; int dy = Sin(pSprite->ang) >> 16; int dz = gDudeSlope[nXIndex]; // dispersal modifiers here dx += BiRandom(1000); dy += BiRandom(1000); dz += BiRandom(1000); actFireVector(nSprite, pSprite->z, dx, dy, dz, kVectorBullet); sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxTomFire); } /**************************************************************************** ** ShotCallback() ** ** ****************************************************************************/ static void ShotCallback( int /* type */, int nXIndex ) { XSPRITE *pXSprite = &xsprite[nXIndex]; int nSprite = pXSprite->reference; SPRITE *pSprite = &sprite[nSprite]; int dx = Cos(pSprite->ang) >> 16; int dy = Sin(pSprite->ang) >> 16; int dz = gDudeSlope[nXIndex]; // aim modifiers dx += BiRandom(4000); dy += BiRandom(4000); dz += BiRandom(4000); for ( int i = 0; i < kVectorsPerBarrel; i++ ) { actFireVector(nSprite, pSprite->z, dx + BiRandom(3000), dy + BiRandom(3000), dz + BiRandom(3000), kVectorShell); } sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxShotFire); } /**************************************************************************** ** ThrowCallback() ** ** ****************************************************************************/ static void ThrowCallback( int /* type */, int nXIndex ) { XSPRITE *pXSprite = &xsprite[nXIndex]; int nSprite = pXSprite->reference; SPRITE *pSprite = &sprite[nSprite]; sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxTNTToss); int nThing = actFireThing( nSprite, pSprite->z, gDudeSlope[nXIndex] + kSlopeThrow, kThingTNTStick, (M2X(10.0) << 4) / kTimerRate); evPost(nThing, SS_SPRITE, 2 * kTimerRate); // 2 second burn off } /**************************************************************************** ** TargetNearExplosion() ** ** ****************************************************************************/ static BOOL TargetNearExplosion( SPRITE *pTarget ) { for (short nSprite = headspritesect[pTarget->sectnum]; nSprite >= 0; nSprite = nextspritesect[nSprite]) { // check for TNT sticks or explosions in the same sector as the target if (sprite[nSprite].type == kThingTNTStick || sprite[nSprite].statnum == kStatExplosion) return TRUE; // indicate danger } return FALSE; } /**************************************************************************** ** thinkSearch() ** ** ****************************************************************************/ static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite ) { aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng); aiThinkTarget(pSprite, pXSprite); } /**************************************************************************** ** thinkGoto() ** ** ****************************************************************************/ static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite ) { int dx, dy, dist; dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax); DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase]; SPRITE *pTarget = &sprite[pXSprite->target]; XSPRITE *pXTarget = &xsprite[pTarget->extra]; dx = pXSprite->targetX - pSprite->x; dy = pXSprite->targetY - pSprite->y; int nAngle = getangle(dx, dy); dist = qdist(dx, dy); aiChooseDirection(pSprite, pXSprite, nAngle); // if reached target, change to search mode if ( dist < M2X(1.0) && qabs(pSprite->ang - nAngle) < pDudeInfo->periphery ) aiNewState(pSprite, pXSprite, &cultistSearch); aiThinkTarget(pSprite, pXSprite); } /**************************************************************************** ** thinkChase() ** ** ****************************************************************************/ static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite ) { if ( pXSprite->target == -1) { aiNewState(pSprite, pXSprite, &cultistGoto); return; } int dx, dy, dist; dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax); DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase]; dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites); SPRITE *pTarget = &sprite[pXSprite->target]; XSPRITE *pXTarget = &xsprite[pTarget->extra]; // check target dx = pTarget->x - pSprite->x; dy = pTarget->y - pSprite->y; aiChooseDirection(pSprite, pXSprite, getangle(dx, dy)); if ( pXTarget->health == 0 ) { // target is dead aiNewState(pSprite, pXSprite, &cultistSearch); return; } if ( IsPlayerSprite( pTarget ) ) { PLAYER *pPlayer = &gPlayer[ pTarget->type - kDudePlayer1 ]; if ( powerupCheck( pPlayer, kItemLtdInvisibility - kItemBase ) > 0 ) { aiNewState(pSprite, pXSprite, &cultistSearch); return; } } dist = qdist(dx, dy); if ( dist <= pDudeInfo->seeDist ) { int nAngle = getangle(dx, dy); int losAngle = ((kAngle180 + nAngle - pSprite->ang) & kAngleMask) - kAngle180; int eyeAboveZ = pDudeInfo->eyeHeight * pSprite->yrepeat << 2; // is there a line of sight to the target? if ( cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum) ) { // is the target visible? if ( dist < pDudeInfo->seeDist && qabs(losAngle) <= pDudeInfo->periphery ) { aiSetTarget(pXSprite, pXSprite->target); int nXSprite = sprite[pXSprite->reference].extra; gDudeSlope[nXSprite] = divscale(pTarget->z - pSprite->z, dist, 10); // check to see if we can attack switch ( pSprite->type ) { case kDudeTommyCultist: if ( dist < kCultistThrowDist1 && dist > kCultistThrowDist2 && qabs(losAngle) < kAngle15 && !TargetNearExplosion(pTarget) && (pTarget->flags & kAttrGravity) && !( IsPlayerSprite(pXSprite->target) && gPlayer[pTarget->type - kDudePlayer1].run ) && Chance(0x4000) ) aiNewState(pSprite, pXSprite, &cultistTThrow); else if ( dist < kCultistTFireDist && qabs(losAngle) < kAngle15 ) aiNewState(pSprite, pXSprite, &cultistTFire); break; case kDudeShotgunCultist: if ( dist < kCultistThrowDist1 && dist > kCultistThrowDist2 && qabs(losAngle) < kAngle15 && !TargetNearExplosion(pTarget) && (pTarget->flags & kAttrGravity) && !(IsPlayerSprite(pXSprite->target) && gPlayer[pTarget->type - kDudePlayer1].run) && Chance(0x4000) ) aiNewState(pSprite, pXSprite, &cultistSThrow); else if ( dist < kCultistSFireDist && qabs(losAngle) < kAngle15 ) aiNewState(pSprite, pXSprite, &cultistSFire); break; } return; } } } dprintf("Cultist %d lost sight of target %d\n", pXSprite->reference, pXSprite->target); aiNewState(pSprite, pXSprite, &cultistGoto); pXSprite->target = -1; }