00001 #include "b_local.h"
00002 #include "g_nav.h"
00003 #include "anims.h"
00004
00005
00006 #include "../namespace_begin.h"
00007 extern qboolean BG_SabersOff( playerState_t *ps );
00008 #include "../namespace_end.h"
00009
00010 extern void CG_DrawAlert( vec3_t origin, float rating );
00011 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
00012 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
00013 extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
00014 extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask );
00015 extern void NPC_AimAdjust( int change );
00016 extern qboolean FlyingCreature( gentity_t *ent );
00017
00018 #define MAX_VIEW_DIST 1024
00019 #define MAX_VIEW_SPEED 250
00020 #define MAX_LIGHT_INTENSITY 255
00021 #define MIN_LIGHT_THRESHOLD 0.1
00022
00023 #define DISTANCE_SCALE 0.25f
00024 #define DISTANCE_THRESHOLD 0.075f
00025 #define SPEED_SCALE 0.25f
00026 #define FOV_SCALE 0.5f
00027 #define LIGHT_SCALE 0.25f
00028
00029 #define REALIZE_THRESHOLD 0.6f
00030 #define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.75 )
00031
00032 qboolean NPC_CheckPlayerTeamStealth( void );
00033
00034 static qboolean enemyLOS3;
00035 static qboolean enemyCS3;
00036 static qboolean faceEnemy3;
00037 static qboolean move3;
00038 static qboolean shoot3;
00039 static float enemyDist3;
00040
00041
00042 enum
00043 {
00044 LSTATE_NONE = 0,
00045 LSTATE_UNDERFIRE,
00046 LSTATE_INVESTIGATE,
00047 };
00048
00049 void Grenadier_ClearTimers( gentity_t *ent )
00050 {
00051 TIMER_Set( ent, "chatter", 0 );
00052 TIMER_Set( ent, "duck", 0 );
00053 TIMER_Set( ent, "stand", 0 );
00054 TIMER_Set( ent, "shuffleTime", 0 );
00055 TIMER_Set( ent, "sleepTime", 0 );
00056 TIMER_Set( ent, "enemyLastVisible", 0 );
00057 TIMER_Set( ent, "roamTime", 0 );
00058 TIMER_Set( ent, "hideTime", 0 );
00059 TIMER_Set( ent, "attackDelay", 0 );
00060 TIMER_Set( ent, "stick", 0 );
00061 TIMER_Set( ent, "scoutTime", 0 );
00062 TIMER_Set( ent, "flee", 0 );
00063 }
00064
00065 void NPC_Grenadier_PlayConfusionSound( gentity_t *self )
00066 {
00067 if ( self->health > 0 )
00068 {
00069 G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 );
00070 }
00071
00072 TIMER_Set( self, "enemyLastVisible", 0 );
00073 TIMER_Set( self, "flee", 0 );
00074 self->NPC->squadState = SQUAD_IDLE;
00075 self->NPC->tempBehavior = BS_DEFAULT;
00076
00077
00078 G_ClearEnemy( self );
00079
00080 self->NPC->investigateCount = 0;
00081 }
00082
00083
00084
00085
00086
00087
00088
00089
00090 void NPC_Grenadier_Pain(gentity_t *self, gentity_t *attacker, int damage)
00091 {
00092 self->NPC->localState = LSTATE_UNDERFIRE;
00093
00094 TIMER_Set( self, "duck", -1 );
00095 TIMER_Set( self, "stand", 2000 );
00096
00097 NPC_Pain( self, attacker, damage );
00098
00099 if ( !damage && self->health > 0 )
00100 {
00101 G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
00102 }
00103 }
00104
00105
00106
00107
00108
00109
00110
00111 static void Grenadier_HoldPosition( void )
00112 {
00113 NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue );
00114 NPCInfo->goalEntity = NULL;
00115
00116
00117
00118
00119
00120
00121 }
00122
00123
00124
00125
00126
00127
00128
00129 static qboolean Grenadier_Move( void )
00130 {
00131 qboolean moved;
00132 navInfo_t info;
00133
00134 NPCInfo->combatMove = qtrue;
00135 moved = NPC_MoveToGoal( qtrue );
00136
00137
00138 NAV_GetLastMove( &info );
00139
00140
00141
00142 if ( info.flags & NIF_COLLISION )
00143 {
00144 if ( info.blocker == NPC->enemy )
00145 {
00146 Grenadier_HoldPosition();
00147 }
00148 }
00149
00150
00151 if ( moved == qfalse )
00152 {
00153 if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && NPC->client->ps.weapon == WP_THERMAL && NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy )
00154 {
00155
00156 int cpFlags = (CP_CLEAR|CP_HAS_ROUTE);
00157 int cp;
00158
00159 if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST )
00160 {
00161 cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST);
00162 cpFlags |= CP_NEAREST;
00163 }
00164 cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->r.currentOrigin, cpFlags, 32, -1 );
00165 if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) )
00166 {
00167 cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->enemy->r.currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32, -1 );
00168 }
00169
00170 if ( cp != -1 )
00171 {
00172 NPC_SetCombatPoint( cp );
00173 NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp, NULL );
00174 return moved;
00175 }
00176 }
00177
00178 Grenadier_HoldPosition();
00179 }
00180
00181 return moved;
00182 }
00183
00184
00185
00186
00187
00188
00189
00190 void NPC_BSGrenadier_Patrol( void )
00191 {
00192 if ( NPCInfo->confusionTime < level.time )
00193 {
00194
00195 if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
00196 {
00197 if ( NPC_CheckPlayerTeamStealth() )
00198 {
00199
00200
00201 NPC_UpdateAngles( qtrue, qtrue );
00202 return;
00203 }
00204 }
00205
00206 if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
00207 {
00208
00209 int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS );
00210 if ( NPC_CheckForDanger( alertEvent ) )
00211 {
00212 NPC_UpdateAngles( qtrue, qtrue );
00213 return;
00214 }
00215 else
00216 {
00217
00218 if ( alertEvent >= 0 && level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID )
00219 {
00220 NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID;
00221 if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED )
00222 {
00223 if ( level.alertEvents[alertEvent].owner &&
00224 level.alertEvents[alertEvent].owner->client &&
00225 level.alertEvents[alertEvent].owner->health >= 0 &&
00226 level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam )
00227 {
00228 G_SetEnemy( NPC, level.alertEvents[alertEvent].owner );
00229
00230 TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
00231 }
00232 }
00233 else
00234 {
00235
00236 VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal );
00237 NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 );
00238 if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS )
00239 {
00240 NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 );
00241 }
00242 }
00243 }
00244 }
00245
00246 if ( NPCInfo->investigateDebounceTime > level.time )
00247 {
00248
00249 vec3_t dir, angles;
00250 float o_yaw, o_pitch;
00251
00252 VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir );
00253 vectoangles( dir, angles );
00254
00255 o_yaw = NPCInfo->desiredYaw;
00256 o_pitch = NPCInfo->desiredPitch;
00257 NPCInfo->desiredYaw = angles[YAW];
00258 NPCInfo->desiredPitch = angles[PITCH];
00259
00260 NPC_UpdateAngles( qtrue, qtrue );
00261
00262 NPCInfo->desiredYaw = o_yaw;
00263 NPCInfo->desiredPitch = o_pitch;
00264 return;
00265 }
00266 }
00267 }
00268
00269
00270 if ( UpdateGoal() )
00271 {
00272 ucmd.buttons |= BUTTON_WALKING;
00273 NPC_MoveToGoal( qtrue );
00274 }
00275
00276 NPC_UpdateAngles( qtrue, qtrue );
00277 }
00278
00279
00280
00281
00282
00283
00284
00285
00286
00287
00288
00289
00290
00291
00292
00293
00294
00295
00296
00297
00298
00299
00300
00301
00302
00303
00304
00305
00306
00307 static void Grenadier_CheckMoveState( void )
00308 {
00309
00310 if ( !(NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) )
00311 {
00312 if ( NPCInfo->goalEntity == NPC->enemy )
00313 {
00314 move3 = qfalse;
00315 return;
00316 }
00317 }
00318
00319 else if ( NPCInfo->squadState == SQUAD_RETREAT )
00320 {
00321 if ( TIMER_Done( NPC, "flee" ) )
00322 {
00323 NPCInfo->squadState = SQUAD_IDLE;
00324 }
00325 else
00326 {
00327 faceEnemy3 = qfalse;
00328 }
00329 }
00330
00331
00332
00333
00334
00335
00336
00337
00338
00339
00340
00341
00342
00343 if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) )
00344 {
00345
00346 if ( NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPCInfo->goalEntity->r.currentOrigin, 16, FlyingCreature( NPC ) ) ||
00347 ( NPCInfo->squadState == SQUAD_SCOUT && enemyLOS3 && enemyDist3 <= 10000 ) )
00348 {
00349 int newSquadState = SQUAD_STAND_AND_SHOOT;
00350
00351 switch ( NPCInfo->squadState )
00352 {
00353 case SQUAD_RETREAT:
00354 TIMER_Set( NPC, "duck", (NPC->client->pers.maxHealth - NPC->health) * 100 );
00355 TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) );
00356 newSquadState = SQUAD_COVER;
00357 break;
00358 case SQUAD_TRANSITION:
00359 TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) );
00360 break;
00361 case SQUAD_SCOUT:
00362 break;
00363 default:
00364 break;
00365 }
00366 NPC_ReachedGoal();
00367
00368 TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) );
00369
00370 TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) );
00371
00372 if ( NPCInfo->squadState == SQUAD_RETREAT )
00373 {
00374 TIMER_Set( NPC, "flee", -level.time );
00375 NPCInfo->squadState = SQUAD_IDLE;
00376 }
00377 return;
00378 }
00379
00380
00381 TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) );
00382 }
00383
00384 if ( !NPCInfo->goalEntity )
00385 {
00386 if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
00387 {
00388 NPCInfo->goalEntity = NPC->enemy;
00389 }
00390 }
00391 }
00392
00393
00394
00395
00396
00397
00398
00399 static void Grenadier_CheckFireState( void )
00400 {
00401 if ( enemyCS3 )
00402 {
00403 return;
00404 }
00405
00406 if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT )
00407 {
00408 return;
00409 }
00410
00411 if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) )
00412 {
00413 return;
00414 }
00415
00416
00417
00418
00419
00420
00421
00422
00423
00424
00425
00426
00427
00428
00429
00430
00431
00432
00433
00434
00435
00436
00437
00438
00439 }
00440
00441 qboolean Grenadier_EvaluateShot( int hit )
00442 {
00443 if ( !NPC->enemy )
00444 {
00445 return qfalse;
00446 }
00447
00448 if ( hit == NPC->enemy->s.number || (&g_entities[hit] != NULL && (g_entities[hit].r.svFlags&SVF_GLASS_BRUSH)) )
00449 {
00450 return qtrue;
00451 }
00452 return qfalse;
00453 }
00454
00455
00456
00457
00458
00459
00460
00461 void NPC_BSGrenadier_Attack( void )
00462 {
00463
00464 if ( NPC->painDebounceTime > level.time )
00465 {
00466 NPC_UpdateAngles( qtrue, qtrue );
00467 return;
00468 }
00469
00470
00471
00472 if ( NPC_CheckEnemyExt(qfalse) == qfalse )
00473 {
00474 NPC->enemy = NULL;
00475 NPC_BSGrenadier_Patrol();
00476 return;
00477 }
00478
00479 if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) )
00480 {
00481 NPC_UpdateAngles( qtrue, qtrue );
00482 return;
00483 }
00484
00485 if ( !NPC->enemy )
00486 {
00487 NPC_BSGrenadier_Patrol();
00488 return;
00489 }
00490
00491 enemyLOS3 = enemyCS3 = qfalse;
00492 move3 = qtrue;
00493 faceEnemy3 = qfalse;
00494 shoot3 = qfalse;
00495 enemyDist3 = DistanceSquared( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin );
00496
00497
00498 if ( enemyDist3 < 16384
00499 && (!NPC->enemy->client
00500 || NPC->enemy->client->ps.weapon != WP_SABER
00501 || BG_SabersOff( &NPC->enemy->client->ps )
00502 )
00503 )
00504 {
00505 if ( NPC->client->ps.weapon == WP_THERMAL )
00506 {
00507 trace_t trace;
00508 trap_Trace ( &trace, NPC->r.currentOrigin, NPC->enemy->r.mins, NPC->enemy->r.maxs, NPC->enemy->r.currentOrigin, NPC->s.number, NPC->enemy->clipmask );
00509 if ( !trace.allsolid && !trace.startsolid && (trace.fraction == 1.0 || trace.entityNum == NPC->enemy->s.number ) )
00510 {
00511
00512 NPC_ChangeWeapon( WP_STUN_BATON );
00513 if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
00514 {
00515 NPCInfo->scriptFlags |= SCF_CHASE_ENEMIES;
00516 }
00517 }
00518 }
00519 }
00520 else if ( enemyDist3 > 65536 || (NPC->enemy->client && NPC->enemy->client->ps.weapon == WP_SABER && !NPC->enemy->client->ps.saberHolstered) )
00521 {
00522 if ( NPC->client->ps.weapon == WP_STUN_BATON && (NPC->client->ps.stats[STAT_WEAPONS]&(1<<WP_THERMAL)) )
00523 {
00524
00525 NPC_ChangeWeapon( WP_THERMAL );
00526 }
00527 }
00528
00529
00530 if ( NPC_ClearLOS4( NPC->enemy ) )
00531 {
00532 NPCInfo->enemyLastSeenTime = level.time;
00533 enemyLOS3 = qtrue;
00534
00535 if ( NPC->client->ps.weapon == WP_STUN_BATON )
00536 {
00537 if ( enemyDist3 <= 4096 && InFOV3( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 90, 45 ) )
00538 {
00539 VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation );
00540 enemyCS3 = qtrue;
00541 }
00542 }
00543 else if ( InFOV3( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 45, 90 ) )
00544 {
00545
00546
00547 int hit = NPC_ShotEntity( NPC->enemy, NULL );
00548 gentity_t *hitEnt = &g_entities[hit];
00549 if ( hit == NPC->enemy->s.number
00550 || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) )
00551 {
00552 float enemyHorzDist;
00553
00554 VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation );
00555 enemyHorzDist = DistanceHorizontalSquared( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin );
00556 if ( enemyHorzDist < 1048576 )
00557 {
00558 enemyCS3 = qtrue;
00559 NPC_AimAdjust( 2 );
00560 }
00561 else
00562 {
00563 NPC_AimAdjust( 1 );
00564 }
00565 }
00566 }
00567 }
00568 else
00569 {
00570 NPC_AimAdjust( -1 );
00571 }
00572
00573
00574
00575
00576
00577
00578
00579
00580 if ( enemyLOS3 )
00581 {
00582 faceEnemy3 = qtrue;
00583 }
00584
00585 if ( enemyCS3 )
00586 {
00587 shoot3 = qtrue;
00588 if ( NPC->client->ps.weapon == WP_THERMAL )
00589 {
00590 move3 = qfalse;
00591 }
00592 else if ( NPC->client->ps.weapon == WP_STUN_BATON && enemyDist3 < (NPC->r.maxs[0]+NPC->enemy->r.maxs[0]+16)*(NPC->r.maxs[0]+NPC->enemy->r.maxs[0]+16) )
00593 {
00594 move3 = qfalse;
00595 }
00596 }
00597
00598
00599 Grenadier_CheckMoveState();
00600
00601
00602 Grenadier_CheckFireState();
00603
00604 if ( move3 )
00605 {
00606 if ( NPCInfo->goalEntity )
00607 {
00608 move3 = Grenadier_Move();
00609 }
00610 else
00611 {
00612 move3 = qfalse;
00613 }
00614 }
00615
00616 if ( !move3 )
00617 {
00618 if ( !TIMER_Done( NPC, "duck" ) )
00619 {
00620 ucmd.upmove = -127;
00621 }
00622
00623 }
00624 else
00625 {
00626 TIMER_Set( NPC, "duck", -1 );
00627 }
00628
00629 if ( !faceEnemy3 )
00630 {
00631 if ( move3 )
00632 {
00633 NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW];
00634 NPCInfo->desiredPitch = 0;
00635 shoot3 = qfalse;
00636 }
00637 NPC_UpdateAngles( qtrue, qtrue );
00638 }
00639 else
00640 {
00641 NPC_FaceEnemy(qtrue);
00642 }
00643
00644 if ( NPCInfo->scriptFlags&SCF_DONT_FIRE )
00645 {
00646 shoot3 = qfalse;
00647 }
00648
00649
00650 if ( shoot3 )
00651 {
00652 if ( TIMER_Done( NPC, "attackDelay" ) )
00653 {
00654 if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) )
00655 {
00656 WeaponThink( qtrue );
00657 TIMER_Set( NPC, "attackDelay", NPCInfo->shotTime-level.time );
00658 }
00659
00660 }
00661 }
00662 }
00663
00664 void NPC_BSGrenadier_Default( void )
00665 {
00666 if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON )
00667 {
00668 WeaponThink( qtrue );
00669 }
00670
00671 if( !NPC->enemy )
00672 {
00673 NPC_BSGrenadier_Patrol();
00674 }
00675 else
00676 {
00677 NPC_BSGrenadier_Attack();
00678 }
00679 }