#include #include #include "aizomba.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 kAxeZombieMeleeDist M2X(2.0) //M2X(1.6) /**************************************************************************** ** LOCAL callback prototypes ****************************************************************************/ static void HackCallback( int /* type */, int nXIndex ); /**************************************************************************** ** LOCAL think/move/entry function prototypes ****************************************************************************/ static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite ); static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite ); static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite ); static void thinkPonder( SPRITE *pSprite, XSPRITE *pXSprite ); static void entryEZombie( SPRITE *pSprite, XSPRITE *pXSprite ); static void entryAIdle( SPRITE */*pSprite*/, XSPRITE *pXSprite ); static void myThinkTarget( SPRITE *pSprite, XSPRITE *pXSprite ); static void myThinkSearch( SPRITE *pSprite, XSPRITE *pXSprite ); /**************************************************************************** ** GLOBAL CONSTANTS ****************************************************************************/ AISTATE zombieAIdle = { kSeqDudeIdle, NULL, 0, entryAIdle, NULL, aiThinkTarget, NULL }; AISTATE zombieAChase = { kSeqAxeZombieWalk, NULL, 0, NULL, aiMoveForward, thinkChase, NULL }; AISTATE zombieAPonder = { kSeqDudeIdle, NULL, 0, NULL, aiMoveTurn, thinkPonder, NULL }; AISTATE zombieAGoto = { kSeqAxeZombieWalk, NULL, 3600, NULL, aiMoveForward, thinkGoto, &zombieAIdle }; AISTATE zombieAHack = { kSeqAxeZombieAttack, HackCallback, 120, NULL, NULL, NULL, &zombieAPonder }; AISTATE zombieASearch = { kSeqAxeZombieWalk, NULL, 3600, NULL, aiMoveForward, thinkSearch, &zombieAIdle }; AISTATE zombieARecoil = { kSeqDudeRecoil, NULL, 0, NULL, NULL, NULL, &zombieAPonder }; AISTATE zombieEIdle = { kSeqEarthZombieIdle, NULL, 0, NULL, NULL, aiThinkTarget, NULL }; AISTATE zombieEUp2 = { kSeqDudeIdle, NULL, 1, entryEZombie, NULL, NULL, &zombieASearch }; AISTATE zombieEUp = { kSeqEarthZombieUp, NULL, 180, NULL, NULL, NULL, &zombieEUp2 }; AISTATE zombie2Idle = { kSeqDudeIdle, NULL, 0, entryAIdle, NULL, myThinkTarget, NULL }; AISTATE zombie2Search = { kSeqAxeZombieWalk, NULL, 3600, NULL, aiMoveForward, myThinkSearch, &zombie2Idle }; /**************************************************************************** ** LOCAL FUNCTIONS ****************************************************************************/ /**************************************************************************** ** TommyCallback() ** ** ****************************************************************************/ static void HackCallback( 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 = 0; actFireVector(nSprite, pSprite->z, dx, dy, dz, kVectorAxe); } /**************************************************************************** ** 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 ) { dprintf("Axe zombie %d switching to search mode\n", pXSprite->reference); aiNewState(pSprite, pXSprite, &zombieASearch); } aiThinkTarget(pSprite, pXSprite); } /**************************************************************************** ** thinkChase() ** ** ****************************************************************************/ static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite ) { if ( pXSprite->target == -1) { aiNewState(pSprite, pXSprite, &zombieASearch); 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, &zombieASearch); return; } if ( IsPlayerSprite( pTarget ) ) { PLAYER *pPlayer = &gPlayer[ pTarget->type - kDudePlayer1 ]; if ( powerupCheck( pPlayer, kItemLtdInvisibility - kItemBase ) > 0 || powerupCheck( pPlayer, kItemDeathMask - kItemBase ) > 0 ) { aiNewState(pSprite, pXSprite, &zombieAGoto); 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 ( qabs(losAngle) <= pDudeInfo->periphery ) { aiSetTarget(pXSprite, pXSprite->target); // check to see if we can attack if ( dist < kAxeZombieMeleeDist && qabs(losAngle) < kAngle15 ) { sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxAxeAir); aiNewState(pSprite, pXSprite, &zombieAHack); } return; } } } dprintf("Axe zombie %d lost sight of target %d\n", pXSprite->reference, pXSprite->target); aiNewState(pSprite, pXSprite, &zombieAGoto); pXSprite->target = -1; } /**************************************************************************** ** thinkPonder() ** ** ****************************************************************************/ static void thinkPonder( SPRITE *pSprite, XSPRITE *pXSprite ) { if ( pXSprite->target == -1) { aiNewState(pSprite, pXSprite, &zombieASearch); 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, &zombieASearch); return; } if ( IsPlayerSprite( pTarget ) ) { PLAYER *pPlayer = &gPlayer[ pTarget->type - kDudePlayer1 ]; if ( powerupCheck( pPlayer, kItemLtdInvisibility - kItemBase ) > 0 || powerupCheck( pPlayer, kItemDeathMask - kItemBase ) > 0 ) { aiNewState(pSprite, pXSprite, &zombieAGoto); 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 ( qabs(losAngle) <= pDudeInfo->periphery ) { aiSetTarget(pXSprite, pXSprite->target); // check to see if we can attack if ( dist < kAxeZombieMeleeDist ) { if ( qabs(losAngle) < kAngle15 ) { sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxAxeAir); aiNewState(pSprite, pXSprite, &zombieAHack); } return; } } } } aiNewState(pSprite, pXSprite, &zombieAChase); } /**************************************************************************** ** myThinkTarget() ** ** ****************************************************************************/ static void myThinkTarget( SPRITE *pSprite, XSPRITE *pXSprite ) { dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax); DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase]; for (int i = 0; i < numplayers; i++) { PLAYER *pPlayer = &gPlayer[i]; // skip this player if the he owns the dude or is invisible if ( pSprite->owner == pPlayer->nSprite || pPlayer->xsprite->health == 0 || powerupCheck( pPlayer, kItemLtdInvisibility - kItemBase ) > 0 ) continue; int x = pPlayer->sprite->x; int y = pPlayer->sprite->y; int z = pPlayer->sprite->z; short nSector = pPlayer->sprite->sectnum; int dx = x - pSprite->x; int dy = y - pSprite->y; int dist = qdist(dx, dy); if ( dist <= pDudeInfo->seeDist || dist <= pDudeInfo->hearDist ) { int eyeAboveZ = pDudeInfo->eyeHeight * pSprite->yrepeat << 2; // is there a line of sight to the player? if ( cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum) ) { int nAngle = getangle(dx, dy); int losAngle = ((kAngle180 + nAngle - pSprite->ang) & kAngleMask) - kAngle180; // is the player visible? if ( dist < pDudeInfo->seeDist && qabs(losAngle) <= pDudeInfo->periphery ) { aiSetTarget( pXSprite, pPlayer->nSprite ); aiActivateDude( pSprite, pXSprite ); return; } // we may want to make hearing a function of sensitivity, rather than distance if ( dist < pDudeInfo->hearDist ) { aiSetTarget(pXSprite, x, y, z); aiActivateDude( pSprite, pXSprite ); return; } } } } } /**************************************************************************** ** myThinkSearch() ** ** ****************************************************************************/ static void myThinkSearch( SPRITE *pSprite, XSPRITE *pXSprite ) { aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng); myThinkTarget(pSprite, pXSprite); } /**************************************************************************** ** entryEZombie() ** ** ****************************************************************************/ static void entryEZombie( SPRITE *pSprite, XSPRITE */*pXSprite*/ ) { pSprite->flags |= kAttrMove; pSprite->type = kDudeAxeZombie; } /**************************************************************************** ** entryAZombieIdle() ** ** ****************************************************************************/ static void entryAIdle( SPRITE */*pSprite*/, XSPRITE *pXSprite ) { pXSprite->target = -1; } /**************************************************************************** ** aiAlertLackey() ** ** ****************************************************************************/ void aiAlertLackey( PLAYER *pPlayer, int nLackey, int nSource, int nDamage ) { // tell lackeys all about your troubles... SPRITE *pLackey = &sprite[nLackey]; XSPRITE *pXLackey = &xsprite[pLackey->extra]; if ( pXLackey->target == -1 || pXLackey->target == pPlayer->nSprite ) { // give a dude a target //dprintf("giving dude a new target\n"); aiSetTarget(pXLackey, nSource); aiActivateDude(pLackey, pXLackey); } else if (nSource != pXLackey->target) { // retarget SPRITE *pSource = &sprite[nSource]; int nThresh = nDamage * dudeInfo[pSource->type - kDudeBase].changeTarget; if ( Chance(nThresh) ) { //dprintf("aiSetTarget lackey #%i to %i\n",i,nSource); aiSetTarget(pXLackey, nSource); aiActivateDude(pLackey, pXLackey); } } }