00001 #include "b_local.h"
00002 #include "g_nav.h"
00003
00004 #include "../namespace_begin.h"
00005 extern gitem_t *BG_FindItemForAmmo( ammo_t ammo );
00006 #include "../namespace_end.h"
00007 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
00008
00009 #define MIN_DISTANCE 256
00010 #define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE )
00011
00012 #define SENTRY_FORWARD_BASE_SPEED 10
00013 #define SENTRY_FORWARD_MULTIPLIER 5
00014
00015 #define SENTRY_VELOCITY_DECAY 0.85f
00016 #define SENTRY_STRAFE_VEL 256
00017 #define SENTRY_STRAFE_DIS 200
00018 #define SENTRY_UPWARD_PUSH 32
00019 #define SENTRY_HOVER_HEIGHT 24
00020
00021
00022 enum
00023 {
00024 LSTATE_NONE = 0,
00025 LSTATE_ASLEEP,
00026 LSTATE_WAKEUP,
00027 LSTATE_ACTIVE,
00028 LSTATE_POWERING_UP,
00029 LSTATE_ATTACKING,
00030 };
00031
00032
00033
00034
00035
00036
00037 void NPC_Sentry_Precache(void)
00038 {
00039 int i;
00040
00041 G_SoundIndex( "sound/chars/sentry/misc/sentry_explo" );
00042 G_SoundIndex( "sound/chars/sentry/misc/sentry_pain" );
00043 G_SoundIndex( "sound/chars/sentry/misc/sentry_shield_open" );
00044 G_SoundIndex( "sound/chars/sentry/misc/sentry_shield_close" );
00045 G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_1_lp" );
00046 G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_2_lp" );
00047
00048 for ( i = 1; i < 4; i++)
00049 {
00050 G_SoundIndex( va( "sound/chars/sentry/misc/talk%d", i ) );
00051 }
00052
00053 G_EffectIndex( "bryar/muzzle_flash");
00054 G_EffectIndex( "env/med_explode");
00055
00056 RegisterItem( BG_FindItemForAmmo( AMMO_BLASTER ));
00057 }
00058
00059
00060
00061
00062
00063
00064 void sentry_use( gentity_t *self, gentity_t *other, gentity_t *activator)
00065 {
00066 G_ActivateBehavior(self,BSET_USE);
00067
00068 self->flags &= ~FL_SHIELDED;
00069 NPC_SetAnim( self, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00070
00071 self->NPC->localState = LSTATE_ACTIVE;
00072 }
00073
00074
00075
00076
00077
00078
00079 void NPC_Sentry_Pain(gentity_t *self, gentity_t *attacker, int damage)
00080 {
00081 int mod = gPainMOD;
00082
00083 NPC_Pain( self, attacker, damage );
00084
00085 if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT )
00086 {
00087 self->NPC->burstCount = 0;
00088 TIMER_Set( self, "attackDelay", Q_irand( 9000, 12000) );
00089 self->flags |= FL_SHIELDED;
00090 NPC_SetAnim( self, SETANIM_BOTH, BOTH_FLY_SHIELDED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00091 G_Sound( self, CHAN_AUTO, G_SoundIndex("sound/chars/sentry/misc/sentry_pain") );
00092
00093 self->NPC->localState = LSTATE_ACTIVE;
00094 }
00095
00096
00097
00098
00099
00100
00101
00102
00103
00104
00105 }
00106
00107
00108
00109
00110
00111
00112 void Sentry_Fire (void)
00113 {
00114 vec3_t muzzle;
00115 static vec3_t forward, vright, up;
00116 gentity_t *missile;
00117 mdxaBone_t boltMatrix;
00118 int bolt;
00119 int which;
00120
00121 NPC->flags &= ~FL_SHIELDED;
00122
00123 if ( NPCInfo->localState == LSTATE_POWERING_UP )
00124 {
00125 if ( TIMER_Done( NPC, "powerup" ))
00126 {
00127 NPCInfo->localState = LSTATE_ATTACKING;
00128 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00129 }
00130 else
00131 {
00132
00133 return;
00134 }
00135 }
00136 else if ( NPCInfo->localState == LSTATE_ACTIVE )
00137 {
00138 NPCInfo->localState = LSTATE_POWERING_UP;
00139
00140 G_Sound( NPC, CHAN_AUTO, G_SoundIndex("sound/chars/sentry/misc/sentry_shield_open") );
00141 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00142 TIMER_Set( NPC, "powerup", 250 );
00143 return;
00144 }
00145 else if ( NPCInfo->localState != LSTATE_ATTACKING )
00146 {
00147
00148 NPCInfo->localState = LSTATE_ACTIVE;
00149 return;
00150 }
00151
00152
00153 which = NPCInfo->burstCount % 3;
00154 switch( which )
00155 {
00156 case 0:
00157 bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash1");
00158 break;
00159 case 1:
00160 bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash2");
00161 break;
00162 case 2:
00163 default:
00164 bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash03");
00165 }
00166
00167 trap_G2API_GetBoltMatrix( NPC->ghoul2, 0,
00168 bolt,
00169 &boltMatrix, NPC->r.currentAngles, NPC->r.currentOrigin, level.time,
00170 NULL, NPC->modelScale );
00171
00172 BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, muzzle );
00173
00174 AngleVectors( NPC->r.currentAngles, forward, vright, up );
00175
00176
00177 G_PlayEffectID( G_EffectIndex("bryar/muzzle_flash"), muzzle, forward );
00178
00179 missile = CreateMissile( muzzle, forward, 1600, 10000, NPC, qfalse );
00180
00181 missile->classname = "bryar_proj";
00182 missile->s.weapon = WP_BRYAR_PISTOL;
00183
00184 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
00185 missile->methodOfDeath = MOD_BRYAR_PISTOL;
00186 missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
00187
00188 NPCInfo->burstCount++;
00189 NPC->attackDebounceTime = level.time + 50;
00190 missile->damage = 5;
00191
00192
00193 if ( g_spskill.integer == 0 )
00194 {
00195 NPC->attackDebounceTime += 200;
00196 missile->damage = 1;
00197 }
00198 else if ( g_spskill.integer == 1 )
00199 {
00200 NPC->attackDebounceTime += 100;
00201 missile->damage = 3;
00202 }
00203 }
00204
00205
00206
00207
00208
00209
00210 void Sentry_MaintainHeight( void )
00211 {
00212 float dif;
00213
00214 NPC->s.loopSound = G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_1_lp" );
00215
00216
00217 NPC_UpdateAngles( qtrue, qtrue );
00218
00219
00220 if ( NPC->enemy )
00221 {
00222
00223 dif = (NPC->enemy->r.currentOrigin[2]+NPC->enemy->r.maxs[2]) - NPC->r.currentOrigin[2];
00224
00225
00226 if ( fabs( dif ) > 8 )
00227 {
00228 if ( fabs( dif ) > SENTRY_HOVER_HEIGHT )
00229 {
00230 dif = ( dif < 0 ? -24 : 24 );
00231 }
00232
00233 NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2;
00234 }
00235 }
00236 else
00237 {
00238 gentity_t *goal = NULL;
00239
00240 if ( NPCInfo->goalEntity )
00241 {
00242 goal = NPCInfo->goalEntity;
00243 }
00244 else
00245 {
00246 goal = NPCInfo->lastGoalEntity;
00247 }
00248
00249 if (goal)
00250 {
00251 dif = goal->r.currentOrigin[2] - NPC->r.currentOrigin[2];
00252
00253 if ( fabs( dif ) > SENTRY_HOVER_HEIGHT )
00254 {
00255 ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 );
00256 }
00257 else
00258 {
00259 if ( NPC->client->ps.velocity[2] )
00260 {
00261 NPC->client->ps.velocity[2] *= SENTRY_VELOCITY_DECAY;
00262
00263 if ( fabs( NPC->client->ps.velocity[2] ) < 2 )
00264 {
00265 NPC->client->ps.velocity[2] = 0;
00266 }
00267 }
00268 }
00269 }
00270
00271 else if ( NPC->client->ps.velocity[2] )
00272 {
00273 NPC->client->ps.velocity[2] *= SENTRY_VELOCITY_DECAY;
00274
00275 if ( fabs( NPC->client->ps.velocity[2] ) < 1 )
00276 {
00277 NPC->client->ps.velocity[2] = 0;
00278 }
00279 }
00280 }
00281
00282
00283 if ( NPC->client->ps.velocity[0] )
00284 {
00285 NPC->client->ps.velocity[0] *= SENTRY_VELOCITY_DECAY;
00286
00287 if ( fabs( NPC->client->ps.velocity[0] ) < 1 )
00288 {
00289 NPC->client->ps.velocity[0] = 0;
00290 }
00291 }
00292
00293 if ( NPC->client->ps.velocity[1] )
00294 {
00295 NPC->client->ps.velocity[1] *= SENTRY_VELOCITY_DECAY;
00296
00297 if ( fabs( NPC->client->ps.velocity[1] ) < 1 )
00298 {
00299 NPC->client->ps.velocity[1] = 0;
00300 }
00301 }
00302
00303 NPC_FaceEnemy( qtrue );
00304 }
00305
00306
00307
00308
00309
00310
00311 void Sentry_Idle( void )
00312 {
00313 Sentry_MaintainHeight();
00314
00315
00316 if (NPCInfo->localState == LSTATE_WAKEUP)
00317 {
00318 if (NPC->client->ps.torsoTimer<=0)
00319 {
00320 NPCInfo->scriptFlags |= SCF_LOOK_FOR_ENEMIES;
00321 NPCInfo->burstCount = 0;
00322 }
00323 }
00324 else
00325 {
00326 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_SLEEP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00327 NPC->flags |= FL_SHIELDED;
00328
00329 NPC_BSIdle();
00330 }
00331 }
00332
00333
00334
00335
00336
00337
00338 void Sentry_Strafe( void )
00339 {
00340 int dir;
00341 vec3_t end, right;
00342 trace_t tr;
00343
00344 AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL );
00345
00346
00347
00348 dir = ( rand() & 1 ) ? -1 : 1;
00349 VectorMA( NPC->r.currentOrigin, SENTRY_STRAFE_DIS * dir, right, end );
00350
00351 trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID );
00352
00353
00354 if ( tr.fraction > 0.9f )
00355 {
00356 VectorMA( NPC->client->ps.velocity, SENTRY_STRAFE_VEL * dir, right, NPC->client->ps.velocity );
00357
00358
00359 NPC->client->ps.velocity[2] += SENTRY_UPWARD_PUSH;
00360
00361
00362
00363 NPCInfo->standTime = level.time + 3000 + random() * 500;
00364 }
00365 }
00366
00367
00368
00369
00370
00371
00372 void Sentry_Hunt( qboolean visible, qboolean advance )
00373 {
00374 float distance, speed;
00375 vec3_t forward;
00376
00377
00378 if ( NPCInfo->standTime < level.time )
00379 {
00380
00381 if ( visible )
00382 {
00383 Sentry_Strafe();
00384 return;
00385 }
00386 }
00387
00388
00389 if ( !advance && visible )
00390 return;
00391
00392
00393 if ( visible == qfalse )
00394 {
00395
00396 NPCInfo->goalEntity = NPC->enemy;
00397 NPCInfo->goalRadius = 12;
00398
00399
00400 if ( NPC_GetMoveDirection( forward, &distance ) == qfalse )
00401 return;
00402 }
00403 else
00404 {
00405 VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, forward );
00406 distance = VectorNormalize( forward );
00407 }
00408
00409 speed = SENTRY_FORWARD_BASE_SPEED + SENTRY_FORWARD_MULTIPLIER * g_spskill.integer;
00410 VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity );
00411 }
00412
00413
00414
00415
00416
00417
00418 void Sentry_RangedAttack( qboolean visible, qboolean advance )
00419 {
00420 if ( TIMER_Done( NPC, "attackDelay" ) && NPC->attackDebounceTime < level.time && visible )
00421 {
00422 if ( NPCInfo->burstCount > 6 )
00423 {
00424 if ( !NPC->fly_sound_debounce_time )
00425 {
00426 NPC->fly_sound_debounce_time = level.time + Q_irand( 500, 2000 );
00427 }
00428 else if ( NPC->fly_sound_debounce_time < level.time )
00429 {
00430 NPCInfo->localState = LSTATE_ACTIVE;
00431 NPC->fly_sound_debounce_time = NPCInfo->burstCount = 0;
00432 TIMER_Set( NPC, "attackDelay", Q_irand( 2000, 3500) );
00433 NPC->flags |= FL_SHIELDED;
00434 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_FLY_SHIELDED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00435 G_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/sentry/misc/sentry_shield_close" );
00436 }
00437 }
00438 else
00439 {
00440 Sentry_Fire();
00441 }
00442 }
00443
00444 if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
00445 {
00446 Sentry_Hunt( visible, advance );
00447 }
00448 }
00449
00450
00451
00452
00453
00454
00455 void Sentry_AttackDecision( void )
00456 {
00457 float distance;
00458 qboolean visible;
00459 qboolean advance;
00460
00461
00462 Sentry_MaintainHeight();
00463
00464 NPC->s.loopSound = G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_2_lp" );
00465
00466
00467 if ( TIMER_Done(NPC,"patrolNoise") )
00468 {
00469 if (TIMER_Done(NPC,"angerNoise"))
00470 {
00471 G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/sentry/misc/talk%d", Q_irand(1, 3)) );
00472
00473 TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) );
00474 }
00475 }
00476
00477
00478 if (NPC->enemy->health<1)
00479 {
00480 NPC->enemy = NULL;
00481 Sentry_Idle();
00482 return;
00483 }
00484
00485
00486 if ( NPC_CheckEnemyExt(qfalse) == qfalse )
00487 {
00488 Sentry_Idle();
00489 return;
00490 }
00491
00492
00493 distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin );
00494 visible = NPC_ClearLOS4( NPC->enemy );
00495 advance = (qboolean)(distance > MIN_DISTANCE_SQR);
00496
00497
00498 if ( visible == qfalse )
00499 {
00500 if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
00501 {
00502 Sentry_Hunt( visible, advance );
00503 return;
00504 }
00505 }
00506
00507 NPC_FaceEnemy( qtrue );
00508
00509 Sentry_RangedAttack( visible, advance );
00510 }
00511
00512 qboolean NPC_CheckPlayerTeamStealth( void );
00513
00514
00515
00516
00517
00518
00519 void NPC_Sentry_Patrol( void )
00520 {
00521 Sentry_MaintainHeight();
00522
00523
00524 if (!NPC->enemy)
00525 {
00526 if ( NPC_CheckPlayerTeamStealth() )
00527 {
00528
00529 NPC_UpdateAngles( qtrue, qtrue );
00530 return;
00531 }
00532
00533 if ( UpdateGoal() )
00534 {
00535
00536 ucmd.buttons |= BUTTON_WALKING;
00537 NPC_MoveToGoal( qtrue );
00538 }
00539
00540
00541 if (TIMER_Done(NPC,"patrolNoise"))
00542 {
00543 G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/sentry/misc/talk%d", Q_irand(1, 3)) );
00544
00545 TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) );
00546 }
00547 }
00548
00549 NPC_UpdateAngles( qtrue, qtrue );
00550 }
00551
00552
00553
00554
00555
00556
00557 void NPC_BSSentry_Default( void )
00558 {
00559 if ( NPC->targetname )
00560 {
00561 NPC->use = sentry_use;
00562 }
00563
00564 if (( NPC->enemy ) && (NPCInfo->localState != LSTATE_WAKEUP))
00565 {
00566
00567 Sentry_AttackDecision();
00568 }
00569 else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES )
00570 {
00571 NPC_Sentry_Patrol();
00572 }
00573 else
00574 {
00575 Sentry_Idle();
00576 }
00577 }