codemp/game/NPC_AI_MineMonster.c

Go to the documentation of this file.
00001 #include "b_local.h"
00002 
00003 // These define the working combat range for these suckers
00004 #define MIN_DISTANCE            54
00005 #define MIN_DISTANCE_SQR        ( MIN_DISTANCE * MIN_DISTANCE )
00006 
00007 #define MAX_DISTANCE            128
00008 #define MAX_DISTANCE_SQR        ( MAX_DISTANCE * MAX_DISTANCE )
00009 
00010 #define LSTATE_CLEAR            0
00011 #define LSTATE_WAITING          1
00012 
00013 /*
00014 -------------------------
00015 NPC_MineMonster_Precache
00016 -------------------------
00017 */
00018 void NPC_MineMonster_Precache( void )
00019 {
00020         int i;
00021 
00022         for ( i = 0; i < 4; i++ )
00023         {
00024                 G_SoundIndex( va("sound/chars/mine/misc/bite%i.wav", i+1 ));
00025                 G_SoundIndex( va("sound/chars/mine/misc/miss%i.wav", i+1 ));
00026         }
00027 }
00028 
00029 
00030 /*
00031 -------------------------
00032 MineMonster_Idle
00033 -------------------------
00034 */
00035 void MineMonster_Idle( void )
00036 {
00037         if ( UpdateGoal() )
00038         {
00039                 ucmd.buttons &= ~BUTTON_WALKING;
00040                 NPC_MoveToGoal( qtrue );
00041         }
00042 }
00043 
00044 
00045 /*
00046 -------------------------
00047 MineMonster_Patrol
00048 -------------------------
00049 */
00050 void MineMonster_Patrol( void )
00051 {
00052         vec3_t dif;
00053 
00054         NPCInfo->localState = LSTATE_CLEAR;
00055 
00056         //If we have somewhere to go, then do that
00057         if ( UpdateGoal() )
00058         {
00059                 ucmd.buttons &= ~BUTTON_WALKING;
00060                 NPC_MoveToGoal( qtrue );
00061         }
00062         else
00063         {
00064                 if ( TIMER_Done( NPC, "patrolTime" ))
00065                 {
00066                         TIMER_Set( NPC, "patrolTime", crandom() * 5000 + 5000 );
00067                 }
00068         }
00069 
00070         //rwwFIXMEFIXME: Care about all clients, not just client 0
00071         VectorSubtract( g_entities[0].r.currentOrigin, NPC->r.currentOrigin, dif );
00072 
00073         if ( VectorLengthSquared( dif ) < 256 * 256 )
00074         {
00075                 G_SetEnemy( NPC, &g_entities[0] );
00076         }
00077 
00078         if ( NPC_CheckEnemyExt( qtrue ) == qfalse )
00079         {
00080                 MineMonster_Idle();
00081                 return;
00082         }
00083 }
00084  
00085 /*
00086 -------------------------
00087 MineMonster_Move
00088 -------------------------
00089 */
00090 void MineMonster_Move( qboolean visible )
00091 {
00092         if ( NPCInfo->localState != LSTATE_WAITING )
00093         {
00094                 NPCInfo->goalEntity = NPC->enemy;
00095                 NPC_MoveToGoal( qtrue );
00096                 NPCInfo->goalRadius = MAX_DISTANCE;     // just get us within combat range
00097         }
00098 }
00099 
00100 //---------------------------------------------------------
00101 void MineMonster_TryDamage( gentity_t *enemy, int damage )
00102 {
00103         vec3_t  end, dir;
00104         trace_t tr;
00105 
00106         if ( !enemy )
00107         {
00108                 return;
00109         }
00110 
00111         AngleVectors( NPC->client->ps.viewangles, dir, NULL, NULL );
00112         VectorMA( NPC->r.currentOrigin, MIN_DISTANCE, dir, end );
00113 
00114         // Should probably trace from the mouth, but, ah well.
00115         trap_Trace( &tr, NPC->r.currentOrigin, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT );
00116 
00117         if ( tr.entityNum >= 0 && tr.entityNum < ENTITYNUM_NONE )
00118         {
00119                 G_Damage( &g_entities[tr.entityNum], NPC, NPC, dir, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_MELEE );
00120                 G_Sound( NPC, CHAN_AUTO, G_EffectIndex(va("sound/chars/mine/misc/bite%i.wav", Q_irand(1,4))));
00121         }
00122         else
00123         {
00124                 G_Sound( NPC, CHAN_AUTO, G_EffectIndex(va("sound/chars/mine/misc/miss%i.wav", Q_irand(1,4))));
00125         }
00126 }
00127 
00128 //------------------------------
00129 void MineMonster_Attack( void )
00130 {
00131         if ( !TIMER_Exists( NPC, "attacking" ))
00132         {
00133                 // usually try and play a jump attack if the player somehow got above them....or just really rarely
00134                 if ( NPC->enemy && ((NPC->enemy->r.currentOrigin[2] - NPC->r.currentOrigin[2] > 10 && random() > 0.1f ) 
00135                                                 || random() > 0.8f ))
00136                 {
00137                         // Going to do ATTACK4
00138                         TIMER_Set( NPC, "attacking", 1750 + random() * 200 );
00139                         NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK4, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00140 
00141                         TIMER_Set( NPC, "attack2_dmg", 950 ); // level two damage
00142                 }
00143                 else if ( random() > 0.5f )
00144                 {
00145                         if ( random() > 0.8f )
00146                         {
00147                                 // Going to do ATTACK3, (rare)
00148                                 TIMER_Set( NPC, "attacking", 850 );
00149                                 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00150 
00151                                 TIMER_Set( NPC, "attack2_dmg", 400 ); // level two damage
00152                         }
00153                         else
00154                         {
00155                                 // Going to do ATTACK1
00156                                 TIMER_Set( NPC, "attacking", 850 );
00157                                 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00158 
00159                                 TIMER_Set( NPC, "attack1_dmg", 450 ); // level one damage
00160                         }
00161                 }
00162                 else
00163                 {
00164                         // Going to do ATTACK2
00165                         TIMER_Set( NPC, "attacking", 1250 );
00166                         NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00167 
00168                         TIMER_Set( NPC, "attack1_dmg", 700 ); // level one damage
00169                 }
00170         }
00171         else
00172         {
00173                 // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks
00174                 if ( TIMER_Done2( NPC, "attack1_dmg", qtrue ))
00175                 {
00176                         MineMonster_TryDamage( NPC->enemy, 5 );
00177                 }
00178                 else if ( TIMER_Done2( NPC, "attack2_dmg", qtrue ))
00179                 {
00180                         MineMonster_TryDamage( NPC->enemy, 10 );
00181                 }
00182         }
00183 
00184         // Just using this to remove the attacking flag at the right time
00185         TIMER_Done2( NPC, "attacking", qtrue );
00186 }
00187 
00188 //----------------------------------
00189 void MineMonster_Combat( void )
00190 {
00191         float distance;
00192         qboolean advance;
00193 
00194         // If we cannot see our target or we have somewhere to go, then do that
00195         if ( !NPC_ClearLOS4( NPC->enemy ) || UpdateGoal( ))
00196         {
00197                 NPCInfo->combatMove = qtrue;
00198                 NPCInfo->goalEntity = NPC->enemy;
00199                 NPCInfo->goalRadius = MAX_DISTANCE;     // just get us within combat range
00200 
00201                 NPC_MoveToGoal( qtrue );
00202                 return;
00203         }
00204 
00205         // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb
00206         NPC_FaceEnemy( qtrue );
00207 
00208         distance        = DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin );       
00209 
00210         advance = (qboolean)( distance > MIN_DISTANCE_SQR ? qtrue : qfalse  );
00211 
00212         if (( advance || NPCInfo->localState == LSTATE_WAITING ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack
00213         {
00214                 if ( TIMER_Done2( NPC, "takingPain", qtrue ))
00215                 {
00216                         NPCInfo->localState = LSTATE_CLEAR;
00217                 }
00218                 else
00219                 {
00220                         MineMonster_Move( 1 );
00221                 }
00222         }
00223         else
00224         {
00225                 MineMonster_Attack();
00226         }
00227 }
00228 
00229 /*
00230 -------------------------
00231 NPC_MineMonster_Pain
00232 -------------------------
00233 */
00234 void NPC_MineMonster_Pain(gentity_t *self, gentity_t *attacker, int damage)
00235 {
00236         G_AddEvent( self, EV_PAIN, floor((float)self->health/self->client->pers.maxHealth*100.0f) );
00237 
00238         if ( damage >= 10 )
00239         {
00240                 TIMER_Remove( self, "attacking" );
00241                 TIMER_Remove( self, "attacking1_dmg" );
00242                 TIMER_Remove( self, "attacking2_dmg" );
00243                 TIMER_Set( self, "takingPain", 1350 );
00244 
00245                 VectorCopy( self->NPC->lastPathAngles, self->s.angles );
00246 
00247                 NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00248 
00249                 if ( self->NPC )
00250                 {
00251                         self->NPC->localState = LSTATE_WAITING;
00252                 }
00253         }
00254 }
00255 
00256 
00257 /*
00258 -------------------------
00259 NPC_BSMineMonster_Default
00260 -------------------------
00261 */
00262 void NPC_BSMineMonster_Default( void )
00263 {
00264         if ( NPC->enemy )
00265         {
00266                 MineMonster_Combat();
00267         }
00268         else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES )
00269         {
00270                 MineMonster_Patrol();
00271         }
00272         else
00273         {
00274                 MineMonster_Idle();
00275         }
00276 
00277         NPC_UpdateAngles( qtrue, qtrue );
00278 }