00001 #include "b_local.h"
00002 #include "g_nav.h"
00003 #include "anims.h"
00004
00005 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
00006 extern void AI_GroupUpdateSquadstates( AIGroupInfo_t *group, gentity_t *member, int newSquadState );
00007 extern qboolean AI_GroupContainsEntNum( AIGroupInfo_t *group, int entNum );
00008 extern void AI_GroupUpdateEnemyLastSeen( AIGroupInfo_t *group, vec3_t spot );
00009 extern void AI_GroupUpdateClearShotTime( AIGroupInfo_t *group );
00010 extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
00011 extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask );
00012 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
00013 extern void NPC_CheckGetNewWeapon( void );
00014 extern int GetTime ( int lastTime );
00015 extern void NPC_AimAdjust( int change );
00016 extern qboolean FlyingCreature( gentity_t *ent );
00017
00018 extern vmCvar_t d_asynchronousGroupAI;
00019
00020 #define MAX_VIEW_DIST 1024
00021 #define MAX_VIEW_SPEED 250
00022 #define MAX_LIGHT_INTENSITY 255
00023 #define MIN_LIGHT_THRESHOLD 0.1
00024 #define ST_MIN_LIGHT_THRESHOLD 30
00025 #define ST_MAX_LIGHT_THRESHOLD 180
00026 #define DISTANCE_THRESHOLD 0.075f
00027
00028 #define DISTANCE_SCALE 0.35f //These first three get your base detection rating, ideally add up to 1
00029 #define FOV_SCALE 0.40f //
00030 #define LIGHT_SCALE 0.25f //
00031
00032 #define SPEED_SCALE 0.25f //These next two are bonuses
00033 #define TURNING_SCALE 0.25f //
00034
00035 #define REALIZE_THRESHOLD 0.6f
00036 #define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.75 )
00037
00038 qboolean NPC_CheckPlayerTeamStealth( void );
00039
00040 static qboolean enemyLOS;
00041 static qboolean enemyCS;
00042 static qboolean enemyInFOV;
00043 static qboolean hitAlly;
00044 static qboolean faceEnemy;
00045 static qboolean move;
00046 static qboolean shoot;
00047 static float enemyDist;
00048 static vec3_t impactPos;
00049
00050 int groupSpeechDebounceTime[TEAM_NUM_TEAMS];
00051
00052
00053 enum
00054 {
00055 LSTATE_NONE = 0,
00056 LSTATE_UNDERFIRE,
00057 LSTATE_INVESTIGATE,
00058 };
00059
00060 void ST_AggressionAdjust( gentity_t *self, int change )
00061 {
00062 int upper_threshold, lower_threshold;
00063
00064 self->NPC->stats.aggression += change;
00065
00066
00067 if ( self->client->playerTeam == NPCTEAM_PLAYER )
00068 {
00069 upper_threshold = 7;
00070 lower_threshold = 1;
00071 }
00072 else
00073 {
00074 upper_threshold = 10;
00075 lower_threshold = 3;
00076 }
00077
00078 if ( self->NPC->stats.aggression > upper_threshold )
00079 {
00080 self->NPC->stats.aggression = upper_threshold;
00081 }
00082 else if ( self->NPC->stats.aggression < lower_threshold )
00083 {
00084 self->NPC->stats.aggression = lower_threshold;
00085 }
00086 }
00087
00088 void ST_ClearTimers( gentity_t *ent )
00089 {
00090 TIMER_Set( ent, "chatter", 0 );
00091 TIMER_Set( ent, "duck", 0 );
00092 TIMER_Set( ent, "stand", 0 );
00093 TIMER_Set( ent, "shuffleTime", 0 );
00094 TIMER_Set( ent, "sleepTime", 0 );
00095 TIMER_Set( ent, "enemyLastVisible", 0 );
00096 TIMER_Set( ent, "roamTime", 0 );
00097 TIMER_Set( ent, "hideTime", 0 );
00098 TIMER_Set( ent, "attackDelay", 0 );
00099 TIMER_Set( ent, "stick", 0 );
00100 TIMER_Set( ent, "scoutTime", 0 );
00101 TIMER_Set( ent, "flee", 0 );
00102 TIMER_Set( ent, "interrogating", 0 );
00103 TIMER_Set( ent, "verifyCP", 0 );
00104 }
00105
00106 enum
00107 {
00108 SPEECH_CHASE,
00109 SPEECH_CONFUSED,
00110 SPEECH_COVER,
00111 SPEECH_DETECTED,
00112 SPEECH_GIVEUP,
00113 SPEECH_LOOK,
00114 SPEECH_LOST,
00115 SPEECH_OUTFLANK,
00116 SPEECH_ESCAPING,
00117 SPEECH_SIGHT,
00118 SPEECH_SOUND,
00119 SPEECH_SUSPICIOUS,
00120 SPEECH_YELL,
00121 SPEECH_PUSHED
00122 };
00123
00124 static void ST_Speech( gentity_t *self, int speechType, float failChance )
00125 {
00126 if ( random() < failChance )
00127 {
00128 return;
00129 }
00130
00131 if ( failChance >= 0 )
00132 {
00133 if ( self->NPC->group )
00134 {
00135 if ( self->NPC->group->speechDebounceTime > level.time )
00136 {
00137 return;
00138 }
00139
00140
00141
00142
00143
00144
00145
00146
00147
00148 }
00149 else if ( !TIMER_Done( self, "chatter" ) )
00150 {
00151 return;
00152 }
00153 else if ( groupSpeechDebounceTime[self->client->playerTeam] > level.time )
00154 {
00155
00156 return;
00157 }
00158 }
00159
00160 if ( self->NPC->group )
00161 {
00162
00163 self->NPC->group->speechDebounceTime = level.time + Q_irand( 2000, 4000 );
00164 }
00165 else
00166 {
00167 TIMER_Set( self, "chatter", Q_irand( 2000, 4000 ) );
00168 }
00169 groupSpeechDebounceTime[self->client->playerTeam] = level.time + Q_irand( 2000, 4000 );
00170
00171 if ( self->NPC->blockedSpeechDebounceTime > level.time )
00172 {
00173 return;
00174 }
00175
00176 switch( speechType )
00177 {
00178 case SPEECH_CHASE:
00179 G_AddVoiceEvent( self, Q_irand(EV_CHASE1, EV_CHASE3), 2000 );
00180 break;
00181 case SPEECH_CONFUSED:
00182 G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 );
00183 break;
00184 case SPEECH_COVER:
00185 G_AddVoiceEvent( self, Q_irand(EV_COVER1, EV_COVER5), 2000 );
00186 break;
00187 case SPEECH_DETECTED:
00188 G_AddVoiceEvent( self, Q_irand(EV_DETECTED1, EV_DETECTED5), 2000 );
00189 break;
00190 case SPEECH_GIVEUP:
00191 G_AddVoiceEvent( self, Q_irand(EV_GIVEUP1, EV_GIVEUP4), 2000 );
00192 break;
00193 case SPEECH_LOOK:
00194 G_AddVoiceEvent( self, Q_irand(EV_LOOK1, EV_LOOK2), 2000 );
00195 break;
00196 case SPEECH_LOST:
00197 G_AddVoiceEvent( self, EV_LOST1, 2000 );
00198 break;
00199 case SPEECH_OUTFLANK:
00200 G_AddVoiceEvent( self, Q_irand(EV_OUTFLANK1, EV_OUTFLANK2), 2000 );
00201 break;
00202 case SPEECH_ESCAPING:
00203 G_AddVoiceEvent( self, Q_irand(EV_ESCAPING1, EV_ESCAPING3), 2000 );
00204 break;
00205 case SPEECH_SIGHT:
00206 G_AddVoiceEvent( self, Q_irand(EV_SIGHT1, EV_SIGHT3), 2000 );
00207 break;
00208 case SPEECH_SOUND:
00209 G_AddVoiceEvent( self, Q_irand(EV_SOUND1, EV_SOUND3), 2000 );
00210 break;
00211 case SPEECH_SUSPICIOUS:
00212 G_AddVoiceEvent( self, Q_irand(EV_SUSPICIOUS1, EV_SUSPICIOUS5), 2000 );
00213 break;
00214 case SPEECH_YELL:
00215 G_AddVoiceEvent( self, Q_irand( EV_ANGER1, EV_ANGER3 ), 2000 );
00216 break;
00217 case SPEECH_PUSHED:
00218 G_AddVoiceEvent( self, Q_irand( EV_PUSHED1, EV_PUSHED3 ), 2000 );
00219 break;
00220 default:
00221 break;
00222 }
00223
00224 self->NPC->blockedSpeechDebounceTime = level.time + 2000;
00225 }
00226
00227 void ST_MarkToCover( gentity_t *self )
00228 {
00229 if ( !self || !self->NPC )
00230 {
00231 return;
00232 }
00233 self->NPC->localState = LSTATE_UNDERFIRE;
00234 TIMER_Set( self, "attackDelay", Q_irand( 500, 2500 ) );
00235 ST_AggressionAdjust( self, -3 );
00236 if ( self->NPC->group && self->NPC->group->numGroup > 1 )
00237 {
00238 ST_Speech( self, SPEECH_COVER, 0 );
00239 }
00240 }
00241
00242 void ST_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int minTime, int maxTime )
00243 {
00244 if ( !self || !self->NPC )
00245 {
00246 return;
00247 }
00248 G_StartFlee( self, enemy, dangerPoint, dangerLevel, minTime, maxTime );
00249 if ( self->NPC->group && self->NPC->group->numGroup > 1 )
00250 {
00251 ST_Speech( self, SPEECH_COVER, 0 );
00252 }
00253 }
00254
00255
00256
00257
00258
00259
00260 void NPC_ST_Pain(gentity_t *self, gentity_t *attacker, int damage)
00261 {
00262 self->NPC->localState = LSTATE_UNDERFIRE;
00263
00264 TIMER_Set( self, "duck", -1 );
00265 TIMER_Set( self, "hideTime", -1 );
00266 TIMER_Set( self, "stand", 2000 );
00267
00268 NPC_Pain( self, attacker, damage );
00269
00270 if ( !damage && self->health > 0 )
00271 {
00272 G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
00273 }
00274 }
00275
00276
00277
00278
00279
00280
00281
00282 static void ST_HoldPosition( void )
00283 {
00284 if ( NPCInfo->squadState == SQUAD_RETREAT )
00285 {
00286 TIMER_Set( NPC, "flee", -level.time );
00287 }
00288 TIMER_Set( NPC, "verifyCP", Q_irand( 1000, 3000 ) );
00289 NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue );
00290
00291 if ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) )
00292 {
00293 AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT );
00294 NPCInfo->goalEntity = NULL;
00295 }
00296
00297
00298
00299
00300
00301
00302 }
00303
00304 void NPC_ST_SayMovementSpeech( void )
00305 {
00306 if ( !NPCInfo->movementSpeech )
00307 {
00308 return;
00309 }
00310 if ( NPCInfo->group &&
00311 NPCInfo->group->commander &&
00312 NPCInfo->group->commander->client &&
00313 NPCInfo->group->commander->client->NPC_class == CLASS_IMPERIAL &&
00314 !Q_irand( 0, 3 ) )
00315 {
00316 ST_Speech( NPCInfo->group->commander, NPCInfo->movementSpeech, NPCInfo->movementSpeechChance );
00317 }
00318 else
00319 {
00320 ST_Speech( NPC, NPCInfo->movementSpeech, NPCInfo->movementSpeechChance );
00321 }
00322
00323 NPCInfo->movementSpeech = 0;
00324 NPCInfo->movementSpeechChance = 0.0f;
00325 }
00326
00327 void NPC_ST_StoreMovementSpeech( int speech, float chance )
00328 {
00329 NPCInfo->movementSpeech = speech;
00330 NPCInfo->movementSpeechChance = chance;
00331 }
00332
00333
00334
00335
00336
00337 void ST_TransferMoveGoal( gentity_t *self, gentity_t *other );
00338 static qboolean ST_Move( void )
00339 {
00340 qboolean moved;
00341 navInfo_t info;
00342
00343 NPCInfo->combatMove = qtrue;
00344
00345 moved = NPC_MoveToGoal( qtrue );
00346
00347
00348 NAV_GetLastMove( &info );
00349
00350
00351
00352 if ( info.flags & NIF_COLLISION )
00353 {
00354 if ( info.blocker == NPC->enemy )
00355 {
00356 ST_HoldPosition();
00357 }
00358 }
00359
00360
00361 if ( moved == qfalse )
00362 {
00363 if ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) )
00364 {
00365 if ( info.blocker && info.blocker->NPC && NPCInfo->group != NULL && info.blocker->NPC->group == NPCInfo->group )
00366 {
00367
00368 int j;
00369
00370 for ( j = 0; j < NPCInfo->group->numGroup; j++ )
00371 {
00372 if ( NPCInfo->group->member[j].number == NPCInfo->blockingEntNum )
00373 {
00374 ST_TransferMoveGoal( NPC, &g_entities[NPCInfo->group->member[j].number] );
00375 break;
00376 }
00377 }
00378 }
00379
00380 ST_HoldPosition();
00381 }
00382 }
00383 else
00384 {
00385
00386 NPC_ST_SayMovementSpeech();
00387 }
00388
00389 return moved;
00390 }
00391
00392
00393
00394
00395
00396
00397
00398
00399 static void NPC_ST_SleepShuffle( void )
00400 {
00401
00402 if ( G_ActivateBehavior( NPC, BSET_AWAKE) )
00403 {
00404 return;
00405 }
00406
00407
00408 if ( TIMER_Done( NPC, "shuffleTime" ) )
00409 {
00410
00411
00412
00413
00414
00415
00416
00417
00418
00419
00420
00421
00422
00423
00424
00425
00426
00427
00428 TIMER_Set( NPC, "shuffleTime", 4000 );
00429 TIMER_Set( NPC, "sleepTime", 2000 );
00430 return;
00431 }
00432
00433
00434 if ( TIMER_Done( NPC, "sleepTime" ) )
00435 {
00436 NPC_CheckPlayerTeamStealth();
00437 TIMER_Set( NPC, "sleepTime", 2000 );
00438 }
00439 }
00440
00441
00442
00443
00444
00445
00446
00447 void NPC_BSST_Sleep( void )
00448 {
00449 int alertEvent = NPC_CheckAlertEvents( qfalse, qtrue, -1, qfalse, AEL_MINOR );
00450
00451
00452 if ( alertEvent >= 0 )
00453 {
00454
00455 if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) )
00456 {
00457 if ( &g_entities[0] && g_entities[0].health > 0 )
00458 {
00459 G_SetEnemy( NPC, &g_entities[0] );
00460 return;
00461 }
00462 }
00463
00464
00465 NPC_ST_SleepShuffle();
00466 return;
00467 }
00468 }
00469
00470
00471
00472
00473
00474
00475
00476 qboolean NPC_CheckEnemyStealth( gentity_t *target )
00477 {
00478 float target_dist, minDist = 40;
00479 float maxViewDist;
00480 qboolean clearLOS;
00481
00482
00483 if ( NPC->enemy != NULL )
00484 return qtrue;
00485
00486
00487 if ( target->flags & FL_NOTARGET )
00488 return qfalse;
00489
00490 if ( target->health <= 0 )
00491 {
00492 return qfalse;
00493 }
00494
00495 if ( target->client->ps.weapon == WP_SABER && !target->client->ps.saberHolstered && !target->client->ps.saberInFlight )
00496 {
00497 minDist = 100;
00498 }
00499
00500 target_dist = DistanceSquared( target->r.currentOrigin, NPC->r.currentOrigin );
00501
00502
00503 if ( !(target->client->ps.pm_flags&PMF_DUCKED)
00504 && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES)
00505 && (target_dist) < (minDist*minDist) )
00506 {
00507 G_SetEnemy( NPC, target );
00508 NPCInfo->enemyLastSeenTime = level.time;
00509 TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
00510 return qtrue;
00511 }
00512
00513 maxViewDist = MAX_VIEW_DIST;
00514
00515 if ( NPCInfo->stats.visrange > maxViewDist )
00516 {
00517 maxViewDist = NPCInfo->stats.visrange;
00518 }
00519
00520 if ( target_dist > (maxViewDist*maxViewDist) )
00521 {
00522 return qfalse;
00523 }
00524
00525
00526 if ( InFOV( target, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse )
00527 return qfalse;
00528
00529
00530 clearLOS = NPC_ClearLOS4( target );
00531
00532
00533 if ( clearLOS )
00534 {
00535 vec3_t targ_org;
00536 float hAngle_perc;
00537 float vAngle_perc;
00538 float target_speed;
00539 int target_crouching;
00540 float dist_rating;
00541 float speed_rating;
00542 float turning_rating;
00543 float light_level;
00544 float FOV_perc;
00545 float vis_rating;
00546 float dist_influence;
00547 float fov_influence;
00548 float light_influence;
00549 float target_rating;
00550 int contents;
00551 float realize, cautious;
00552
00553 if ( target->client->NPC_class == CLASS_ATST )
00554 {
00555 G_SetEnemy( NPC, target );
00556 TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
00557 return qtrue;
00558 }
00559 VectorSet(targ_org, target->r.currentOrigin[0],target->r.currentOrigin[1],target->r.currentOrigin[2]+target->r.maxs[2]-4);
00560 hAngle_perc = NPC_GetHFOVPercentage( targ_org, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, NPCInfo->stats.hfov );
00561 vAngle_perc = NPC_GetVFOVPercentage( targ_org, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, NPCInfo->stats.vfov );
00562
00563
00564 vAngle_perc *= vAngle_perc;
00565 hAngle_perc *= ( hAngle_perc * hAngle_perc );
00566
00567
00568
00569
00570
00571
00572 target_dist = Distance( target->r.currentOrigin, NPC->r.currentOrigin );
00573
00574 target_speed = VectorLength( target->client->ps.velocity );
00575 target_crouching = ( target->client->pers.cmd.upmove < 0 );
00576 dist_rating = ( target_dist / maxViewDist );
00577 speed_rating = ( target_speed / MAX_VIEW_SPEED );
00578 turning_rating = 5.0f;
00579 light_level = (255/MAX_LIGHT_INTENSITY);
00580 FOV_perc = 1.0f - ( hAngle_perc + vAngle_perc ) * 0.5f;
00581 vis_rating = 0.0f;
00582
00583
00584 if ( light_level < MIN_LIGHT_THRESHOLD )
00585 return qfalse;
00586
00587
00588 if ( dist_rating < DISTANCE_THRESHOLD )
00589 {
00590 G_SetEnemy( NPC, target );
00591 TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
00592 return qtrue;
00593 }
00594
00595
00596 if ( dist_rating > 1.0f )
00597 return qfalse;
00598
00599
00600 if ( speed_rating > 1.0f )
00601 speed_rating = 1.0f;
00602
00603
00604
00605
00606 dist_influence = DISTANCE_SCALE * ( ( 1.0f - dist_rating ) );
00607
00608 fov_influence = FOV_SCALE * ( 1.0f - FOV_perc );
00609
00610 light_influence = ( light_level - 0.5f ) * LIGHT_SCALE;
00611
00612
00613 target_rating = dist_influence + fov_influence + light_influence;
00614
00615
00616 contents = trap_PointContents( targ_org, target->s.number );
00617 if ( contents&CONTENTS_WATER )
00618 {
00619 int myContents = trap_PointContents( NPC->client->renderInfo.eyePoint, NPC->s.number );
00620 if ( !(myContents&CONTENTS_WATER) )
00621 {
00622 if ( NPC->client->NPC_class == CLASS_SWAMPTROOPER )
00623 {
00624 vis_rating = 0.10f;
00625 }
00626 else
00627 {
00628 vis_rating = 0.35f;
00629 }
00630 }
00631 else
00632 {
00633 if ( NPC->client->NPC_class == CLASS_SWAMPTROOPER )
00634 {
00635 }
00636 else
00637 {
00638 vis_rating = 0.15f;
00639 }
00640 }
00641 }
00642 else
00643 {
00644 if ( contents&CONTENTS_FOG )
00645 {
00646 vis_rating = 0.15f;
00647 }
00648 }
00649
00650 target_rating *= (1.0f - vis_rating);
00651
00652
00653 target_rating += speed_rating * SPEED_SCALE;
00654 target_rating += turning_rating * TURNING_SCALE;
00655
00656
00657
00658 if ( target_crouching )
00659 {
00660 target_rating *= 0.9f;
00661 }
00662
00663
00664
00665 if ( NPC->client->NPC_class == CLASS_SWAMPTROOPER )
00666 {
00667 realize = (float)CAUTIOUS_THRESHOLD;
00668 cautious = (float)CAUTIOUS_THRESHOLD * 0.75f;
00669 }
00670 else
00671 {
00672 realize = (float)REALIZE_THRESHOLD;
00673 cautious = (float)CAUTIOUS_THRESHOLD * 0.75f;
00674 }
00675
00676 if ( target_rating > realize && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) )
00677 {
00678 G_SetEnemy( NPC, target );
00679 NPCInfo->enemyLastSeenTime = level.time;
00680 TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
00681 return qtrue;
00682 }
00683
00684
00685 if ( target_rating > cautious && !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
00686 {
00687 if ( TIMER_Done( NPC, "enemyLastVisible" ) )
00688 {
00689 int lookTime = Q_irand( 4500, 8500 );
00690
00691 TIMER_Set( NPC, "enemyLastVisible", lookTime );
00692
00693 ST_Speech( NPC, SPEECH_SIGHT, 0 );
00694 NPC_TempLookTarget( NPC, target->s.number, lookTime, lookTime );
00695
00696 }
00697 else if ( TIMER_Get( NPC, "enemyLastVisible" ) <= level.time + 500 && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) )
00698 {
00699 if ( NPCInfo->rank < RANK_LT && !Q_irand( 0, 2 ) )
00700 {
00701 int interrogateTime = Q_irand( 2000, 4000 );
00702 ST_Speech( NPC, SPEECH_SUSPICIOUS, 0 );
00703 TIMER_Set( NPC, "interrogating", interrogateTime );
00704 G_SetEnemy( NPC, target );
00705 NPCInfo->enemyLastSeenTime = level.time;
00706 TIMER_Set( NPC, "attackDelay", interrogateTime );
00707 TIMER_Set( NPC, "stand", interrogateTime );
00708 }
00709 else
00710 {
00711 G_SetEnemy( NPC, target );
00712 NPCInfo->enemyLastSeenTime = level.time;
00713
00714 TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
00715 TIMER_Set( NPC, "stand", Q_irand( 500, 2500 ) );
00716 }
00717 return qtrue;
00718 }
00719
00720 return qfalse;
00721 }
00722 }
00723
00724 return qfalse;
00725 }
00726
00727 qboolean NPC_CheckPlayerTeamStealth( void )
00728 {
00729
00730
00731
00732
00733
00734
00735
00736 gentity_t *enemy;
00737 int i;
00738
00739 for ( i = 0; i < ENTITYNUM_WORLD; i++ )
00740 {
00741 enemy = &g_entities[i];
00742
00743 if (!enemy->inuse)
00744 {
00745 continue;
00746 }
00747
00748 if ( enemy && enemy->client && NPC_ValidEnemy( enemy ) && enemy->client->playerTeam == NPC->client->enemyTeam )
00749 {
00750 if ( NPC_CheckEnemyStealth( enemy ) )
00751 {
00752 return qtrue;
00753 }
00754 }
00755 }
00756 return qfalse;
00757 }
00758
00759
00760
00761
00762
00763
00764 #define MAX_CHECK_THRESHOLD 1
00765
00766 static qboolean NPC_ST_InvestigateEvent( int eventID, qboolean extraSuspicious )
00767 {
00768
00769 if ( NPCInfo->confusionTime < level.time )
00770 {
00771 if ( level.alertEvents[eventID].level == AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) )
00772 {
00773 NPCInfo->lastAlertID = level.alertEvents[eventID].ID;
00774 if ( !level.alertEvents[eventID].owner ||
00775 !level.alertEvents[eventID].owner->client ||
00776 level.alertEvents[eventID].owner->health <= 0 ||
00777 level.alertEvents[eventID].owner->client->playerTeam != NPC->client->enemyTeam )
00778 {
00779 return qfalse;
00780 }
00781
00782
00783 G_SetEnemy( NPC, level.alertEvents[eventID].owner );
00784 NPCInfo->enemyLastSeenTime = level.time;
00785 TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
00786 if ( level.alertEvents[eventID].type == AET_SOUND )
00787 {
00788 TIMER_Set( NPC, "roamTime", Q_irand( 500, 2500 ) );
00789 }
00790 return qtrue;
00791 }
00792 }
00793
00794
00795 if ( level.alertEvents[eventID].ID == NPCInfo->lastAlertID )
00796 {
00797 return qfalse;
00798 }
00799 NPCInfo->lastAlertID = level.alertEvents[eventID].ID;
00800
00801
00802
00803
00804
00805
00806
00807
00808
00809 if ( level.alertEvents[eventID].type == AET_SIGHT )
00810 {
00811 if ( level.alertEvents[eventID].light < Q_irand( ST_MIN_LIGHT_THRESHOLD, ST_MAX_LIGHT_THRESHOLD ) )
00812 {
00813 return qfalse;
00814 }
00815 }
00816
00817
00818 VectorCopy( level.alertEvents[eventID].position, NPCInfo->investigateGoal );
00819
00820
00821 NPCInfo->investigateCount += ( extraSuspicious ) ? 2 : 1;
00822
00823
00824 if ( NPCInfo->investigateCount > 4 )
00825 NPCInfo->investigateCount = 4;
00826
00827
00828 if ( level.alertEvents[eventID].level > AEL_MINOR && NPCInfo->investigateCount > 1 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
00829 {
00830
00831 if ( G_ExpandPointToBBox( NPCInfo->investigateGoal, NPC->r.mins, NPC->r.maxs, NPC->s.number, ((NPC->clipmask&~CONTENTS_BODY)|CONTENTS_BOTCLIP) ) )
00832 {
00833
00834 vec3_t end;
00835 trace_t trace;
00836 VectorCopy( NPCInfo->investigateGoal, end );
00837 end[2] -= 512;
00838 trap_Trace( &trace, NPCInfo->investigateGoal, NPC->r.mins, NPC->r.maxs, end, ENTITYNUM_NONE, ((NPC->clipmask&~CONTENTS_BODY)|CONTENTS_BOTCLIP) );
00839 if ( trace.fraction >= 1.0f )
00840 {
00841
00842 }
00843 else
00844 {
00845 VectorCopy( trace.endpos, NPCInfo->investigateGoal );
00846 NPC_SetMoveGoal( NPC, NPCInfo->investigateGoal, 16, qtrue, -1, NULL );
00847 NPCInfo->localState = LSTATE_INVESTIGATE;
00848 }
00849 }
00850 else
00851 {
00852 int id = NPC_FindCombatPoint( NPCInfo->investigateGoal, NPCInfo->investigateGoal, NPCInfo->investigateGoal, CP_INVESTIGATE|CP_HAS_ROUTE, 0, -1 );
00853
00854 if ( id != -1 )
00855 {
00856 NPC_SetMoveGoal( NPC, level.combatPoints[id].origin, 16, qtrue, id, NULL );
00857 NPCInfo->localState = LSTATE_INVESTIGATE;
00858 }
00859 }
00860
00861
00862 if ( NPCInfo->investigateDebounceTime+NPCInfo->pauseTime > level.time )
00863 {
00864 if ( NPCInfo->group &&
00865 NPCInfo->group->commander &&
00866 NPCInfo->group->commander->client &&
00867 NPCInfo->group->commander->client->NPC_class == CLASS_IMPERIAL &&
00868 !Q_irand( 0, 3 ) )
00869 {
00870 ST_Speech( NPCInfo->group->commander, SPEECH_LOOK, 0 );
00871 }
00872 else
00873 {
00874 ST_Speech( NPC, SPEECH_LOOK, 0 );
00875 }
00876 }
00877 else
00878 {
00879 if ( level.alertEvents[eventID].type == AET_SIGHT )
00880 {
00881 ST_Speech( NPC, SPEECH_SIGHT, 0 );
00882 }
00883 else if ( level.alertEvents[eventID].type == AET_SOUND )
00884 {
00885 ST_Speech( NPC, SPEECH_SOUND, 0 );
00886 }
00887 }
00888
00889 NPCInfo->investigateDebounceTime = NPCInfo->investigateCount * 5000;
00890 NPCInfo->investigateSoundDebounceTime = level.time + 2000;
00891 NPCInfo->pauseTime = level.time;
00892 }
00893 else
00894 {
00895
00896 if ( level.alertEvents[eventID].type == AET_SIGHT )
00897 {
00898 ST_Speech( NPC, SPEECH_SIGHT, 0 );
00899 }
00900 else if ( level.alertEvents[eventID].type == AET_SOUND )
00901 {
00902 ST_Speech( NPC, SPEECH_SOUND, 0 );
00903 }
00904
00905 NPCInfo->investigateDebounceTime = NPCInfo->investigateCount * 1000;
00906 NPCInfo->investigateSoundDebounceTime = level.time + 1000;
00907 NPCInfo->pauseTime = level.time;
00908 VectorCopy( level.alertEvents[eventID].position, NPCInfo->investigateGoal );
00909 }
00910
00911 if ( level.alertEvents[eventID].level >= AEL_DANGER )
00912 {
00913 NPCInfo->investigateDebounceTime = Q_irand( 500, 2500 );
00914 }
00915
00916
00917 NPCInfo->tempBehavior = BS_INVESTIGATE;
00918 return qtrue;
00919 }
00920
00921
00922
00923
00924
00925
00926
00927 static void ST_OffsetLook( float offset, vec3_t out )
00928 {
00929 vec3_t angles, forward, temp;
00930
00931 GetAnglesForDirection( NPC->r.currentOrigin, NPCInfo->investigateGoal, angles );
00932 angles[YAW] += offset;
00933 AngleVectors( angles, forward, NULL, NULL );
00934 VectorMA( NPC->r.currentOrigin, 64, forward, out );
00935
00936 CalcEntitySpot( NPC, SPOT_HEAD, temp );
00937 out[2] = temp[2];
00938 }
00939
00940
00941
00942
00943
00944
00945
00946 static void ST_LookAround( void )
00947 {
00948 vec3_t lookPos;
00949 float perc = (float) ( level.time - NPCInfo->pauseTime ) / (float) NPCInfo->investigateDebounceTime;
00950
00951
00952 if ( perc < 0.25 )
00953 {
00954 VectorCopy( NPCInfo->investigateGoal, lookPos );
00955 }
00956 else if ( perc < 0.5f )
00957 {
00958 ST_OffsetLook( 0.0f, lookPos );
00959 }
00960 else if ( perc < 0.75f )
00961 {
00962 ST_OffsetLook( 45.0f, lookPos );
00963 }
00964 else
00965 {
00966 ST_OffsetLook( -45.0f, lookPos );
00967 }
00968
00969 NPC_FacePosition( lookPos, qtrue );
00970 }
00971
00972
00973
00974
00975
00976
00977
00978 void NPC_BSST_Investigate( void )
00979 {
00980
00981 AI_GetGroup( NPC );
00982
00983 if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON )
00984 {
00985 WeaponThink( qtrue );
00986 }
00987
00988 if ( NPCInfo->confusionTime < level.time )
00989 {
00990 if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
00991 {
00992
00993 if ( NPC_CheckPlayerTeamStealth() )
00994 {
00995
00996 ST_Speech( NPC, SPEECH_DETECTED, 0 );
00997 NPCInfo->tempBehavior = BS_DEFAULT;
00998 NPC_UpdateAngles( qtrue, qtrue );
00999 return;
01000 }
01001 }
01002 }
01003
01004 if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
01005 {
01006 int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, NPCInfo->lastAlertID, qfalse, AEL_MINOR );
01007
01008
01009 if ( alertEvent >= 0 )
01010 {
01011 if ( NPCInfo->confusionTime < level.time )
01012 {
01013 if ( NPC_CheckForDanger( alertEvent ) )
01014 {
01015 ST_Speech( NPC, SPEECH_COVER, 0 );
01016 return;
01017 }
01018 }
01019
01020 if ( level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID )
01021 {
01022 NPC_ST_InvestigateEvent( alertEvent, qtrue );
01023 }
01024 }
01025 }
01026
01027
01028 if ( ( NPCInfo->investigateDebounceTime + NPCInfo->pauseTime ) < level.time )
01029 {
01030 NPCInfo->tempBehavior = BS_DEFAULT;
01031 NPCInfo->goalEntity = UpdateGoal();
01032
01033 NPC_UpdateAngles( qtrue, qtrue );
01034
01035 ST_Speech( NPC, SPEECH_GIVEUP, 0 );
01036 return;
01037 }
01038
01039
01040
01041
01042 if ( NPCInfo->localState == LSTATE_INVESTIGATE && (NPCInfo->goalEntity!=NULL) )
01043 {
01044
01045 if ( NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPCInfo->goalEntity->r.currentOrigin, 32, FlyingCreature( NPC ) ) == qfalse )
01046 {
01047 ucmd.buttons |= BUTTON_WALKING;
01048
01049
01050 if ( NPC_MoveToGoal( qtrue ) )
01051 {
01052
01053 NPCInfo->investigateDebounceTime = NPCInfo->investigateCount * 5000;
01054 NPCInfo->pauseTime = level.time;
01055
01056 NPC_UpdateAngles( qtrue, qtrue );
01057 return;
01058 }
01059 }
01060
01061
01062
01063
01064 NPCInfo->localState = LSTATE_NONE;
01065 }
01066
01067
01068 ST_LookAround();
01069 }
01070
01071
01072
01073
01074
01075
01076
01077 void NPC_BSST_Patrol( void )
01078 {
01079
01080
01081 AI_GetGroup( NPC );
01082
01083 if ( NPCInfo->confusionTime < level.time )
01084 {
01085
01086 if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
01087 {
01088 if ( NPC_CheckPlayerTeamStealth() )
01089 {
01090
01091
01092 NPC_UpdateAngles( qtrue, qtrue );
01093 return;
01094 }
01095 }
01096 }
01097
01098 if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
01099 {
01100 int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_MINOR );
01101
01102
01103 if ( alertEvent >= 0 )
01104 {
01105 if ( NPC_ST_InvestigateEvent( alertEvent, qfalse ) )
01106 {
01107 NPC_UpdateAngles( qtrue, qtrue );
01108 return;
01109 }
01110 }
01111 }
01112
01113
01114 if ( UpdateGoal() )
01115 {
01116 ucmd.buttons |= BUTTON_WALKING;
01117
01118 NPC_MoveToGoal( qtrue );
01119 }
01120 else
01121 {
01122 if ( NPC->client->NPC_class != CLASS_IMPERIAL && NPC->client->NPC_class != CLASS_IMPWORKER )
01123 {
01124 if ( TIMER_Done( NPC, "enemyLastVisible" ) )
01125 {
01126 if ( !Q_irand( 0, 30 ) )
01127 {
01128 NPCInfo->desiredYaw = NPC->s.angles[1] + Q_irand( -90, 90 );
01129 }
01130 if ( !Q_irand( 0, 30 ) )
01131 {
01132 NPCInfo->desiredPitch = Q_irand( -20, 20 );
01133 }
01134