123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853 |
- // leave this line at the top of all AI_xxxx.cpp files for PCH reasons...
- #include "g_headers.h"
-
- #include "b_local.h"
- // These define the working combat range for these suckers
- #define MIN_DISTANCE 54
- #define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE )
- #define MAX_DISTANCE 128
- #define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE )
- #define LSTATE_CLEAR 0
- #define LSTATE_WAITING 1
- #define LSTATE_FLEE 2
- #define LSTATE_BERZERK 3
- #define HOWLER_RETREAT_DIST 300.0f
- #define HOWLER_PANIC_HEALTH 10
- extern void G_UcmdMoveForDir( gentity_t *self, usercmd_t *cmd, vec3_t dir );
- extern void G_GetBoltPosition( gentity_t *self, int boltIndex, vec3_t pos, int modelIndex = 0 );
- extern int PM_AnimLength( int index, animNumber_t anim );
- extern qboolean NAV_DirSafe( gentity_t *self, vec3_t dir, float dist );
- extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock );
- extern float NPC_EntRangeFromBolt( gentity_t *targEnt, int boltIndex );
- extern int NPC_GetEntsNearBolt( gentity_t **radiusEnts, float radius, int boltIndex, vec3_t boltOrg );
- extern qboolean PM_InKnockDown( playerState_t *ps );
- extern qboolean PM_HasAnimation( gentity_t *ent, int animation );
- static void Howler_Attack( float enemyDist, qboolean howl = qfalse );
- /*
- -------------------------
- NPC_Howler_Precache
- -------------------------
- */
- void NPC_Howler_Precache( void )
- {
- int i;
- //G_SoundIndex( "sound/chars/howler/howl.mp3" );
- G_EffectIndex( "howler/sonic" );
- G_SoundIndex( "sound/chars/howler/howl.mp3" );
- for ( i = 1; i < 3; i++ )
- {
- G_SoundIndex( va( "sound/chars/howler/idle_hiss%d.mp3", i ) );
- }
- for ( i = 1; i < 6; i++ )
- {
- G_SoundIndex( va( "sound/chars/howler/howl_talk%d.mp3", i ) );
- G_SoundIndex( va( "sound/chars/howler/howl_yell%d.mp3", i ) );
- }
- }
- void Howler_ClearTimers( gentity_t *self )
- {
- //clear all my timers
- TIMER_Set( self, "flee", -level.time );
- TIMER_Set( self, "retreating", -level.time );
- TIMER_Set( self, "standing", -level.time );
- TIMER_Set( self, "walking", -level.time );
- TIMER_Set( self, "running", -level.time );
- TIMER_Set( self, "aggressionDecay", -level.time );
- TIMER_Set( self, "speaking", -level.time );
- }
- static qboolean NPC_Howler_Move( int randomJumpChance = 0 )
- {
- if ( !TIMER_Done( NPC, "standing" ) )
- {//standing around
- return qfalse;
- }
- if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE )
- {//in air, don't do anything
- return qfalse;
- }
- if ( (!NPC->enemy&&TIMER_Done( NPC, "running" )) || !TIMER_Done( NPC, "walking" ) )
- {
- ucmd.buttons |= BUTTON_WALKING;
- }
- if ( (!randomJumpChance||Q_irand( 0, randomJumpChance ))
- && NPC_MoveToGoal( qtrue ) )
- {
- if ( VectorCompare( NPC->client->ps.moveDir, vec3_origin )
- || !NPC->client->ps.speed )
- {//uh.... wtf? Got there?
- if ( NPCInfo->goalEntity )
- {
- NPC_FaceEntity( NPCInfo->goalEntity, qfalse );
- }
- else
- {
- NPC_UpdateAngles( qfalse, qtrue );
- }
- return qtrue;
- }
- //TEMP: don't want to strafe
- VectorClear( NPC->client->ps.moveDir );
- ucmd.rightmove = 0.0f;
- // Com_Printf( "Howler moving %d\n",ucmd.forwardmove );
- //if backing up, go slow...
- if ( ucmd.forwardmove < 0.0f )
- {
- ucmd.buttons |= BUTTON_WALKING;
- //if ( NPC->client->ps.speed > NPCInfo->stats.walkSpeed )
- {//don't walk faster than I'm allowed to
- NPC->client->ps.speed = NPCInfo->stats.walkSpeed;
- }
- }
- else
- {
- if ( (ucmd.buttons&BUTTON_WALKING) )
- {
- NPC->client->ps.speed = NPCInfo->stats.walkSpeed;
- }
- else
- {
- NPC->client->ps.speed = NPCInfo->stats.runSpeed;
- }
- }
- NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW];
- NPC_UpdateAngles( qfalse, qtrue );
- }
- else if ( NPCInfo->goalEntity )
- {//couldn't get where we wanted to go, try to jump there
- NPC_FaceEntity( NPCInfo->goalEntity, qfalse );
- NPC_TryJump( NPCInfo->goalEntity, 400.0f, -256.0f );
- }
- return qtrue;
- }
- /*
- -------------------------
- Howler_Idle
- -------------------------
- */
- static void Howler_Idle( void )
- {
- }
- /*
- -------------------------
- Howler_Patrol
- -------------------------
- */
- static void Howler_Patrol( void )
- {
- NPCInfo->localState = LSTATE_CLEAR;
- //If we have somewhere to go, then do that
- if ( UpdateGoal() )
- {
- NPC_Howler_Move( 100 );
- }
- vec3_t dif;
- VectorSubtract( g_entities[0].currentOrigin, NPC->currentOrigin, dif );
- if ( VectorLengthSquared( dif ) < 256 * 256 )
- {
- G_SetEnemy( NPC, &g_entities[0] );
- }
- if ( NPC_CheckEnemyExt( qtrue ) == qfalse )
- {
- Howler_Idle();
- return;
- }
- Howler_Attack( 0.0f, qtrue );
- }
-
- /*
- -------------------------
- Howler_Move
- -------------------------
- */
- static qboolean Howler_Move( qboolean visible )
- {
- if ( NPCInfo->localState != LSTATE_WAITING )
- {
- NPCInfo->goalEntity = NPC->enemy;
- NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range
- return NPC_Howler_Move( 30 );
- }
- return qfalse;
- }
- //---------------------------------------------------------
- static void Howler_TryDamage( int damage, qboolean tongue, qboolean knockdown )
- {
- vec3_t start, end, dir;
- trace_t tr;
- if ( tongue )
- {
- G_GetBoltPosition( NPC, NPC->genericBolt1, start );
- G_GetBoltPosition( NPC, NPC->genericBolt2, end );
- VectorSubtract( end, start, dir );
- float dist = VectorNormalize( dir );
- VectorMA( start, dist+16, dir, end );
- }
- else
- {
- VectorCopy( NPC->currentOrigin, start );
- AngleVectors( NPC->currentAngles, dir, NULL, NULL );
- VectorMA( start, MIN_DISTANCE*2, dir, end );
- }
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer > 1 )
- {
- G_DebugLine(start, end, 1000, 0x000000ff, qtrue);
- }
- #endif
- // Should probably trace from the mouth, but, ah well.
- gi.trace( &tr, start, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT );
- if ( tr.entityNum < ENTITYNUM_WORLD )
- {//hit *something*
- gentity_t *victim = &g_entities[tr.entityNum];
- if ( !victim->client
- || victim->client->NPC_class != CLASS_HOWLER )
- {//not another howler
- if ( knockdown && victim->client )
- {//only do damage if victim isn't knocked down. If he isn't, knock him down
- if ( PM_InKnockDown( &victim->client->ps ) )
- {
- return;
- }
- }
- //FIXME: some sort of damage effect (claws and tongue are cutting you... blood?)
- G_Damage( victim, NPC, NPC, dir, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_MELEE );
- if ( knockdown && victim->health > 0 )
- {//victim still alive
- G_Knockdown( victim, NPC, NPC->client->ps.velocity, 500, qfalse );
- }
- }
- }
- }
- static void Howler_Howl( void )
- {
- gentity_t *radiusEnts[ 128 ];
- int numEnts;
- const float radius = (NPC->spawnflags&1)?256:128;
- const float halfRadSquared = ((radius/2)*(radius/2));
- const float radiusSquared = (radius*radius);
- float distSq;
- int i;
- vec3_t boltOrg;
- AddSoundEvent( NPC, NPC->currentOrigin, 512, AEL_DANGER, qfalse, qtrue );
- numEnts = NPC_GetEntsNearBolt( radiusEnts, radius, NPC->handLBolt, boltOrg );
- for ( i = 0; i < numEnts; i++ )
- {
- if ( !radiusEnts[i]->inuse )
- {
- continue;
- }
-
- if ( radiusEnts[i] == NPC )
- {//Skip the rancor ent
- continue;
- }
-
- if ( radiusEnts[i]->client == NULL )
- {//must be a client
- continue;
- }
- if ( radiusEnts[i]->client->NPC_class == CLASS_HOWLER )
- {//other howlers immune
- continue;
- }
- distSq = DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg );
- if ( distSq <= radiusSquared )
- {
- if ( distSq < halfRadSquared )
- {//close enough to do damage, too
- if ( Q_irand( 0, g_spskill->integer ) )
- {//does no damage on easy, does 1 point every other frame on medium, more often on hard
- G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, NPC->currentOrigin, 1, DAMAGE_NO_KNOCKBACK, MOD_IMPACT );
- }
- }
- if ( radiusEnts[i]->health > 0
- && radiusEnts[i]->client
- && radiusEnts[i]->client->NPC_class != CLASS_RANCOR
- && radiusEnts[i]->client->NPC_class != CLASS_ATST
- && !PM_InKnockDown( &radiusEnts[i]->client->ps ) )
- {
- if ( PM_HasAnimation( radiusEnts[i], BOTH_SONICPAIN_START ) )
- {
- if ( radiusEnts[i]->client->ps.torsoAnim != BOTH_SONICPAIN_START
- && radiusEnts[i]->client->ps.torsoAnim != BOTH_SONICPAIN_HOLD )
- {
- NPC_SetAnim( radiusEnts[i], SETANIM_LEGS, BOTH_SONICPAIN_START, SETANIM_FLAG_NORMAL );
- NPC_SetAnim( radiusEnts[i], SETANIM_TORSO, BOTH_SONICPAIN_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- radiusEnts[i]->client->ps.torsoAnimTimer += 100;
- radiusEnts[i]->client->ps.weaponTime = radiusEnts[i]->client->ps.torsoAnimTimer;
- }
- else if ( radiusEnts[i]->client->ps.torsoAnimTimer <= 100 )
- {//at the end of the sonic pain start or hold anim
- NPC_SetAnim( radiusEnts[i], SETANIM_LEGS, BOTH_SONICPAIN_HOLD, SETANIM_FLAG_NORMAL );
- NPC_SetAnim( radiusEnts[i], SETANIM_TORSO, BOTH_SONICPAIN_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- radiusEnts[i]->client->ps.torsoAnimTimer += 100;
- radiusEnts[i]->client->ps.weaponTime = radiusEnts[i]->client->ps.torsoAnimTimer;
- }
- }
- /*
- else if ( distSq < halfRadSquared
- && radiusEnts[i]->client->ps.groundEntityNum != ENTITYNUM_NONE
- && !Q_irand( 0, 10 ) )//FIXME: base on skill
- {//within range
- G_Knockdown( radiusEnts[i], NPC, vec3_origin, 500, qfalse );
- }
- */
- }
- }
- }
- float playerDist = NPC_EntRangeFromBolt( player, NPC->genericBolt1 );
- if ( playerDist < 256.0f )
- {
- CGCam_Shake( 1.0f*playerDist/128.0f, 200 );
- }
- }
- //------------------------------
- static void Howler_Attack( float enemyDist, qboolean howl )
- {
- int dmg = (NPCInfo->localState==LSTATE_BERZERK)?5:2;
- if ( !TIMER_Exists( NPC, "attacking" ))
- {
- int attackAnim = BOTH_GESTURE1;
- // Going to do an attack
- if ( NPC->enemy && NPC->enemy->client && PM_InKnockDown( &NPC->enemy->client->ps )
- && enemyDist <= MIN_DISTANCE )
- {
- attackAnim = BOTH_ATTACK2;
- }
- else if ( !Q_irand( 0, 4 ) || howl )
- {//howl attack
- //G_SoundOnEnt( NPC, CHAN_VOICE, "sound/chars/howler/howl.mp3" );
- }
- else if ( enemyDist > MIN_DISTANCE && Q_irand( 0, 1 ) )
- {//lunge attack
- //jump foward
- vec3_t fwd, yawAng = {0, NPC->client->ps.viewangles[YAW], 0};
- AngleVectors( yawAng, fwd, NULL, NULL );
- VectorScale( fwd, (enemyDist*3.0f), NPC->client->ps.velocity );
- NPC->client->ps.velocity[2] = 200;
- NPC->client->ps.groundEntityNum = ENTITYNUM_NONE;
-
- attackAnim = BOTH_ATTACK1;
- }
- else
- {//tongue attack
- attackAnim = BOTH_ATTACK2;
- }
- NPC_SetAnim( NPC, SETANIM_BOTH, attackAnim, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD | SETANIM_FLAG_RESTART );
- if ( NPCInfo->localState == LSTATE_BERZERK )
- {//attack again right away
- TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer );
- }
- else
- {
- TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer + Q_irand( 0, 1500 ) );//FIXME: base on skill
- TIMER_Set( NPC, "standing", -level.time );
- TIMER_Set( NPC, "walking", -level.time );
- TIMER_Set( NPC, "running", NPC->client->ps.legsAnimTimer + 5000 );
- }
- TIMER_Set( NPC, "attack_dmg", 200 ); // level two damage
- }
- // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks
- switch ( NPC->client->ps.legsAnim )
- {
- case BOTH_ATTACK1:
- case BOTH_MELEE1:
- if ( NPC->client->ps.legsAnimTimer > 650//more than 13 frames left
- && PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t)NPC->client->ps.legsAnim ) - NPC->client->ps.legsAnimTimer >= 800 )//at least 16 frames into anim
- {
- Howler_TryDamage( dmg, qfalse, qfalse );
- }
- break;
- case BOTH_ATTACK2:
- case BOTH_MELEE2:
- if ( NPC->client->ps.legsAnimTimer > 350//more than 7 frames left
- && PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t)NPC->client->ps.legsAnim ) - NPC->client->ps.legsAnimTimer >= 550 )//at least 11 frames into anim
- {
- Howler_TryDamage( dmg, qtrue, qfalse );
- }
- break;
- case BOTH_GESTURE1:
- {
- if ( NPC->client->ps.legsAnimTimer > 1800//more than 36 frames left
- && PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t)NPC->client->ps.legsAnim ) - NPC->client->ps.legsAnimTimer >= 950 )//at least 19 frames into anim
- {
- Howler_Howl();
- if ( !NPC->count )
- {
- G_PlayEffect( G_EffectIndex( "howler/sonic" ), NPC->playerModel, NPC->genericBolt1, NPC->s.number, NPC->currentOrigin, 4750, qtrue );
- G_SoundOnEnt( NPC, CHAN_VOICE, "sound/chars/howler/howl.mp3" );
- NPC->count = 1;
- }
- }
- }
- break;
- default:
- //anims seem to get reset after a load, so just stop attacking and it will restart as needed.
- TIMER_Remove( NPC, "attacking" );
- break;
- }
- // Just using this to remove the attacking flag at the right time
- TIMER_Done2( NPC, "attacking", qtrue );
- }
- //----------------------------------
- static void Howler_Combat( void )
- {
- qboolean faced = qfalse;
- float distance;
- qboolean advance = qfalse;
- if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE )
- {//not on the ground
- if ( NPC->client->ps.legsAnim == BOTH_JUMP1
- || NPC->client->ps.legsAnim == BOTH_INAIR1 )
- {//flying through the air with the greatest of ease, etc
- Howler_TryDamage( 10, qfalse, qfalse );
- }
- }
- else
- {//not in air, see if we should attack or advance
- // If we cannot see our target or we have somewhere to go, then do that
- if ( !NPC_ClearLOS( NPC->enemy ) )//|| UpdateGoal( ))
- {
- NPCInfo->goalEntity = NPC->enemy;
- NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range
- if ( NPCInfo->localState == LSTATE_BERZERK )
- {
- NPC_Howler_Move( 3 );
- }
- else
- {
- NPC_Howler_Move( 10 );
- }
- NPC_UpdateAngles( qfalse, qtrue );
- return;
- }
- distance = DistanceHorizontal( NPC->currentOrigin, NPC->enemy->currentOrigin );
- if ( NPC->enemy && NPC->enemy->client && PM_InKnockDown( &NPC->enemy->client->ps ) )
- {//get really close to knocked down enemies
- advance = (qboolean)( distance > MIN_DISTANCE ? qtrue : qfalse );
- }
- else
- {
- advance = (qboolean)( distance > MAX_DISTANCE ? qtrue : qfalse );//MIN_DISTANCE
- }
- if (( advance || NPCInfo->localState == LSTATE_WAITING ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack
- {
- if ( TIMER_Done2( NPC, "takingPain", qtrue ))
- {
- NPCInfo->localState = LSTATE_CLEAR;
- }
- else if ( TIMER_Done( NPC, "standing" ) )
- {
- faced = Howler_Move( 1 );
- }
- }
- else
- {
- Howler_Attack( distance );
- }
- }
- if ( !faced )
- {
- if ( //TIMER_Done( NPC, "standing" ) //not just standing there
- //!advance //not moving
- TIMER_Done( NPC, "attacking" ) )// not attacking
- {//not standing around
- // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb
- NPC_FaceEnemy( qtrue );
- }
- else
- {
- NPC_UpdateAngles( qfalse, qtrue );
- }
- }
- }
- /*
- -------------------------
- NPC_Howler_Pain
- -------------------------
- */
- void NPC_Howler_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
- {
- if ( !self || !self->NPC )
- {
- return;
- }
- if ( self->NPC->localState != LSTATE_BERZERK )//damage >= 10 )
- {
- self->NPC->stats.aggression += damage;
- self->NPC->localState = LSTATE_WAITING;
- TIMER_Remove( self, "attacking" );
- VectorCopy( self->NPC->lastPathAngles, self->s.angles );
- //if ( self->client->ps.legsAnim == BOTH_GESTURE1 )
- {
- G_StopEffect( G_EffectIndex( "howler/sonic" ), self->playerModel, self->genericBolt1, self->s.number );
- }
- NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
- TIMER_Set( self, "takingPain", self->client->ps.legsAnimTimer );//2900 );
- if ( self->health > HOWLER_PANIC_HEALTH )
- {//still have some health left
- if ( Q_irand( 0, self->max_health ) > self->health )//FIXME: or check damage?
- {//back off!
- TIMER_Set( self, "standing", -level.time );
- TIMER_Set( self, "running", -level.time );
- TIMER_Set( self, "walking", -level.time );
- TIMER_Set( self, "retreating", Q_irand( 1000, 5000 ) );
- }
- else
- {//go after him!
- TIMER_Set( self, "standing", -level.time );
- TIMER_Set( self, "running", self->client->ps.legsAnimTimer+Q_irand(3000,6000) );
- TIMER_Set( self, "walking", -level.time );
- TIMER_Set( self, "retreating", -level.time );
- }
- }
- else if ( self->NPC )
- {//panic!
- if ( Q_irand( 0, 1 ) )
- {//berzerk
- self->NPC->localState = LSTATE_BERZERK;
- }
- else
- {//flee
- self->NPC->localState = LSTATE_FLEE;
- TIMER_Set( self, "flee", Q_irand( 10000, 30000 ) );
- }
- }
- }
- }
- /*
- -------------------------
- NPC_BSHowler_Default
- -------------------------
- */
- void NPC_BSHowler_Default( void )
- {
- if ( NPC->client->ps.legsAnim != BOTH_GESTURE1 )
- {
- NPC->count = 0;
- }
- //FIXME: if in jump, do damage in front and maybe knock them down?
- if ( !TIMER_Done( NPC, "attacking" ) )
- {
- if ( NPC->enemy )
- {
- //NPC_FaceEnemy( qfalse );
- Howler_Attack( Distance( NPC->enemy->currentOrigin, NPC->currentOrigin ) );
- }
- else
- {
- //NPC_UpdateAngles( qfalse, qtrue );
- Howler_Attack( 0.0f );
- }
- NPC_UpdateAngles( qfalse, qtrue );
- return;
- }
- if ( NPC->enemy )
- {
- if ( NPCInfo->stats.aggression > 0 )
- {
- if ( TIMER_Done( NPC, "aggressionDecay" ) )
- {
- NPCInfo->stats.aggression--;
- TIMER_Set( NPC, "aggressionDecay", 500 );
- }
- }
- if ( !TIMER_Done( NPC, "flee" )
- && NPC_BSFlee() ) //this can clear ENEMY
- {//successfully trying to run away
- return;
- }
- if ( NPC->enemy == NULL)
- {
- NPC_UpdateAngles( qfalse, qtrue );
- return;
- }
- if ( NPCInfo->localState == LSTATE_FLEE )
- {//we were fleeing, now done (either timer ran out or we cannot flee anymore
- if ( NPC_ClearLOS( NPC->enemy ) )
- {//if enemy is still around, go berzerk
- NPCInfo->localState = LSTATE_BERZERK;
- }
- else
- {//otherwise, lick our wounds?
- NPCInfo->localState = LSTATE_CLEAR;
- TIMER_Set( NPC, "standing", Q_irand( 3000, 10000 ) );
- }
- }
- else if ( NPCInfo->localState == LSTATE_BERZERK )
- {//go nuts!
- }
- else if ( NPCInfo->stats.aggression >= Q_irand( 75, 125 ) )
- {//that's it, go nuts!
- NPCInfo->localState = LSTATE_BERZERK;
- }
- else if ( !TIMER_Done( NPC, "retreating" ) )
- {//trying to back off
- NPC_FaceEnemy( qtrue );
- if ( NPC->client->ps.speed > NPCInfo->stats.walkSpeed )
- {
- NPC->client->ps.speed = NPCInfo->stats.walkSpeed;
- }
- ucmd.buttons |= BUTTON_WALKING;
- if ( Distance( NPC->enemy->currentOrigin, NPC->currentOrigin ) < HOWLER_RETREAT_DIST )
- {//enemy is close
- vec3_t moveDir;
- AngleVectors( NPC->currentAngles, moveDir, NULL, NULL );
- VectorScale( moveDir, -1, moveDir );
- if ( !NAV_DirSafe( NPC, moveDir, 8 ) )
- {//enemy is backing me up against a wall or ledge! Start to get really mad!
- NPCInfo->stats.aggression += 2;
- }
- else
- {//back off
- ucmd.forwardmove = -127;
- }
- //enemy won't leave me alone, get mad...
- NPCInfo->stats.aggression++;
- }
- return;
- }
- else if ( TIMER_Done( NPC, "standing" ) )
- {//not standing around
- if ( !(NPCInfo->last_ucmd.forwardmove)
- && !(NPCInfo->last_ucmd.rightmove) )
- {//stood last frame
- if ( TIMER_Done( NPC, "walking" )
- && TIMER_Done( NPC, "running" ) )
- {//not walking or running
- if ( Q_irand( 0, 2 ) )
- {//run for a while
- TIMER_Set( NPC, "walking", Q_irand( 4000, 8000 ) );
- }
- else
- {//walk for a bit
- TIMER_Set( NPC, "running", Q_irand( 2500, 5000 ) );
- }
- }
- }
- else if ( (NPCInfo->last_ucmd.buttons&BUTTON_WALKING) )
- {//walked last frame
- if ( TIMER_Done( NPC, "walking" ) )
- {//just finished walking
- if ( Q_irand( 0, 5 ) || DistanceSquared( NPC->enemy->currentOrigin, NPC->currentOrigin ) < MAX_DISTANCE_SQR )
- {//run for a while
- TIMER_Set( NPC, "running", Q_irand( 4000, 20000 ) );
- }
- else
- {//stand for a bit
- TIMER_Set( NPC, "standing", Q_irand( 2000, 6000 ) );
- }
- }
- }
- else
- {//ran last frame
- if ( TIMER_Done( NPC, "running" ) )
- {//just finished running
- if ( Q_irand( 0, 8 ) || DistanceSquared( NPC->enemy->currentOrigin, NPC->currentOrigin ) < MAX_DISTANCE_SQR )
- {//walk for a while
- TIMER_Set( NPC, "walking", Q_irand( 3000, 10000 ) );
- }
- else
- {//stand for a bit
- TIMER_Set( NPC, "standing", Q_irand( 2000, 6000 ) );
- }
- }
- }
- }
- if ( NPC_ValidEnemy( NPC->enemy ) == qfalse )
- {
- TIMER_Remove( NPC, "lookForNewEnemy" );//make them look again right now
- if ( !NPC->enemy->inuse || level.time - NPC->enemy->s.time > Q_irand( 10000, 15000 ) )
- {//it's been a while since the enemy died, or enemy is completely gone, get bored with him
- NPC->enemy = NULL;
- Howler_Patrol();
- NPC_UpdateAngles( qtrue, qtrue );
- return;
- }
- }
- if ( TIMER_Done( NPC, "lookForNewEnemy" ) )
- {
- gentity_t *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy?
- NPC->enemy = NULL;
- gentity_t *newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse );
- NPC->enemy = sav_enemy;
- if ( newEnemy && newEnemy != sav_enemy )
- {//picked up a new enemy!
- NPC->lastEnemy = NPC->enemy;
- G_SetEnemy( NPC, newEnemy );
- if ( NPC->enemy != NPC->lastEnemy )
- {//clear this so that we only sniff the player the first time we pick them up
- NPC->useDebounceTime = 0;
- }
- //hold this one for at least 5-15 seconds
- TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) );
- }
- else
- {//look again in 2-5 secs
- TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) );
- }
- }
- Howler_Combat();
- if ( TIMER_Done( NPC, "speaking" ) )
- {
- if ( !TIMER_Done( NPC, "standing" )
- || !TIMER_Done( NPC, "retreating" ))
- {
- G_SoundOnEnt( NPC, CHAN_VOICE, va( "sound/chars/howler/idle_hiss%d.mp3", Q_irand( 1, 2 ) ) );
- }
- else if ( !TIMER_Done( NPC, "walking" )
- || NPCInfo->localState == LSTATE_FLEE )
- {
- G_SoundOnEnt( NPC, CHAN_VOICE, va( "sound/chars/howler/howl_talk%d.mp3", Q_irand( 1, 5 ) ) );
- }
- else
- {
- G_SoundOnEnt( NPC, CHAN_VOICE, va( "sound/chars/howler/howl_yell%d.mp3", Q_irand( 1, 5 ) ) );
- }
- if ( NPCInfo->localState == LSTATE_BERZERK
- || NPCInfo->localState == LSTATE_FLEE )
- {
- TIMER_Set( NPC, "speaking", Q_irand( 1000, 4000 ) );
- }
- else
- {
- TIMER_Set( NPC, "speaking", Q_irand( 3000, 8000 ) );
- }
- }
- return;
- }
- else
- {
- if ( TIMER_Done( NPC, "speaking" ) )
- {
- if ( !Q_irand( 0, 3 ) )
- {
- G_SoundOnEnt( NPC, CHAN_VOICE, va( "sound/chars/howler/idle_hiss%d.mp3", Q_irand( 1, 2 ) ) );
- }
- else
- {
- G_SoundOnEnt( NPC, CHAN_VOICE, va( "sound/chars/howler/howl_talk%d.mp3", Q_irand( 1, 5 ) ) );
- }
- TIMER_Set( NPC, "speaking", Q_irand( 4000, 12000 ) );
- }
- if ( NPCInfo->stats.aggression > 0 )
- {
- if ( TIMER_Done( NPC, "aggressionDecay" ) )
- {
- NPCInfo->stats.aggression--;
- TIMER_Set( NPC, "aggressionDecay", 200 );
- }
- }
- if ( TIMER_Done( NPC, "standing" ) )
- {//not standing around
- if ( !(NPCInfo->last_ucmd.forwardmove)
- && !(NPCInfo->last_ucmd.rightmove) )
- {//stood last frame
- if ( TIMER_Done( NPC, "walking" )
- && TIMER_Done( NPC, "running" ) )
- {//not walking or running
- if ( NPCInfo->goalEntity )
- {//have somewhere to go
- if ( Q_irand( 0, 2 ) )
- {//walk for a while
- TIMER_Set( NPC, "walking", Q_irand( 3000, 10000 ) );
- }
- else
- {//run for a bit
- TIMER_Set( NPC, "running", Q_irand( 2500, 5000 ) );
- }
- }
- }
- }
- else if ( (NPCInfo->last_ucmd.buttons&BUTTON_WALKING) )
- {//walked last frame
- if ( TIMER_Done( NPC, "walking" ) )
- {//just finished walking
- if ( Q_irand( 0, 3 ) )
- {//run for a while
- TIMER_Set( NPC, "running", Q_irand( 3000, 6000 ) );
- }
- else
- {//stand for a bit
- TIMER_Set( NPC, "standing", Q_irand( 2500, 5000 ) );
- }
- }
- }
- else
- {//ran last frame
- if ( TIMER_Done( NPC, "running" ) )
- {//just finished running
- if ( Q_irand( 0, 2 ) )
- {//walk for a while
- TIMER_Set( NPC, "walking", Q_irand( 6000, 15000 ) );
- }
- else
- {//stand for a bit
- TIMER_Set( NPC, "standing", Q_irand( 4000, 6000 ) );
- }
- }
- }
- }
- if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES )
- {
- Howler_Patrol();
- }
- else
- {
- Howler_Idle();
- }
- }
- NPC_UpdateAngles( qfalse, qtrue );
- }
|