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 }
01135 }
01136 }
01137
01138 NPC_UpdateAngles( qtrue, qtrue );
01139
01140 if ( NPC->client->NPC_class == CLASS_IMPERIAL || NPC->client->NPC_class == CLASS_IMPWORKER )
01141 {
01142 if ( ucmd.forwardmove || ucmd.rightmove || ucmd.upmove )
01143 {
01144
01145 if( (NPC->client->ps.torsoTimer <= 0) || (NPC->client->ps.torsoAnim == BOTH_STAND4) )
01146 {
01147 if ( (ucmd.buttons&BUTTON_WALKING) && !(NPCInfo->scriptFlags&SCF_RUNNING) )
01148 {
01149
01150 NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_STAND4, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
01151 NPC->client->ps.torsoTimer = 200;
01152 }
01153 }
01154 }
01155 else
01156 {
01157
01158 if( ( NPC->client->ps.torsoTimer <= 0 || (NPC->client->ps.torsoAnim == BOTH_STAND4) ) &&
01159 ( NPC->client->ps.legsTimer <= 0 || (NPC->client->ps.legsAnim == BOTH_STAND4) ) )
01160 {
01161 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND4, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
01162 NPC->client->ps.torsoTimer = NPC->client->ps.legsTimer = 200;
01163 }
01164 }
01165
01166 if ( NPC->client->ps.weapon != WP_NONE )
01167 {
01168 ChangeWeapon( NPC, WP_NONE );
01169 NPC->client->ps.weapon = WP_NONE;
01170 NPC->client->ps.weaponstate = WEAPON_READY;
01171
01172
01173
01174
01175
01176
01177
01178
01179 }
01180 }
01181 }
01182
01183
01184
01185
01186
01187
01188
01189
01190
01191
01192
01193
01194
01195
01196
01197
01198
01199
01200
01201
01202
01203
01204
01205
01206
01207
01208
01209
01210
01211
01212 static void ST_CheckMoveState( void )
01213 {
01214 if ( trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) )
01215 {
01216 move = qtrue;
01217 }
01218
01219 else if ( NPCInfo->squadState == SQUAD_SCOUT )
01220 {
01221
01222 if ( TIMER_Done( NPC, "stick" ) == qfalse )
01223 {
01224 move = qfalse;
01225 return;
01226 }
01227
01228
01229 if ( enemyLOS )
01230 {
01231 if ( enemyCS )
01232 {
01233
01234 if ( NPCInfo->goalEntity == NPC->enemy )
01235 {
01236 AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT );
01237 move = qfalse;
01238 return;
01239 }
01240 }
01241 }
01242 else
01243 {
01244
01245 faceEnemy = qfalse;
01246 }
01247
01248
01249
01250
01251
01252
01253
01254
01255
01256
01257
01258
01259 }
01260
01261 else if ( NPCInfo->squadState == SQUAD_RETREAT )
01262 {
01263 if ( NPCInfo->goalEntity )
01264 {
01265 faceEnemy = qfalse;
01266 }
01267 else
01268 {
01269 NPCInfo->squadState = SQUAD_STAND_AND_SHOOT;
01270 }
01271 }
01272
01273 else if ( NPCInfo->squadState == SQUAD_TRANSITION )
01274 {
01275
01276 if ( !NPCInfo->goalEntity )
01277 {
01278 NPCInfo->squadState = SQUAD_STAND_AND_SHOOT;
01279 }
01280 }
01281
01282 else if ( NPCInfo->squadState == SQUAD_POINT )
01283 {
01284 if ( TIMER_Done( NPC, "stick" ) )
01285 {
01286 AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT );
01287 return;
01288 }
01289
01290 move = qfalse;
01291 return;
01292 }
01293
01294 else if ( NPCInfo->squadState == SQUAD_STAND_AND_SHOOT )
01295 {
01296 move = qfalse;
01297 return;
01298 }
01299
01300 else if ( NPCInfo->squadState == SQUAD_COVER )
01301 {
01302
01303 move = qfalse;
01304 return;
01305 }
01306
01307 else if ( NPCInfo->squadState == SQUAD_IDLE )
01308 {
01309 if ( !NPCInfo->goalEntity )
01310 {
01311 move = qfalse;
01312 return;
01313 }
01314 }
01315
01316 else
01317 {
01318 }
01319
01320
01321 if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) )
01322 {
01323
01324 if ( NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPCInfo->goalEntity->r.currentOrigin, 16, FlyingCreature( NPC ) ) ||
01325 ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) && NPCInfo->squadState == SQUAD_SCOUT && enemyLOS && enemyDist <= 10000 ) )
01326 {
01327 int newSquadState = SQUAD_STAND_AND_SHOOT;
01328
01329 switch ( NPCInfo->squadState )
01330 {
01331 case SQUAD_RETREAT:
01332
01333 TIMER_Set( NPC, "duck", (NPC->client->pers.maxHealth - NPC->health) * 100 );
01334 TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) );
01335 TIMER_Set( NPC, "flee", -level.time );
01336 newSquadState = SQUAD_COVER;
01337 break;
01338 case SQUAD_TRANSITION:
01339 TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) );
01340 break;
01341 case SQUAD_SCOUT:
01342 break;
01343 default:
01344 break;
01345 }
01346 AI_GroupUpdateSquadstates( NPCInfo->group, NPC, newSquadState );
01347 NPC_ReachedGoal();
01348
01349 TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) );
01350
01351 TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) );
01352 return;
01353 }
01354
01355
01356 TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) );
01357 }
01358 }
01359
01360 void ST_ResolveBlockedShot( int hit )
01361 {
01362 int stuckTime;
01363
01364 if ( TIMER_Get( NPC, "roamTime" ) > TIMER_Get( NPC, "stick" ) )
01365 {
01366 stuckTime = TIMER_Get( NPC, "roamTime" )-level.time;
01367 }
01368 else
01369 {
01370 stuckTime = TIMER_Get( NPC, "stick" )-level.time;
01371 }
01372
01373 if ( TIMER_Done( NPC, "duck" ) )
01374 {
01375 if ( AI_GroupContainsEntNum( NPCInfo->group, hit ) )
01376 {
01377 gentity_t *member = &g_entities[hit];
01378 if ( TIMER_Done( member, "duck" ) )
01379 {
01380 if ( TIMER_Done( member, "stand" ) )
01381 {
01382
01383 TIMER_Set( member, "duck", stuckTime );
01384 return;
01385 }
01386 }
01387 }
01388 }
01389 else
01390 {
01391 if ( TIMER_Done( NPC, "stand" ) )
01392 {
01393 TIMER_Set( NPC, "stand", stuckTime );
01394 return;
01395 }
01396 }
01397
01398
01399 TIMER_Set( NPC, "roamTime", -1 );
01400 TIMER_Set( NPC, "stick", -1 );
01401 TIMER_Set( NPC, "duck", -1 );
01402 TIMER_Set( NPC, "attakDelay", Q_irand( 1000, 3000 ) );
01403 }
01404
01405
01406
01407
01408
01409
01410
01411 static void ST_CheckFireState( void )
01412 {
01413 if ( enemyCS )
01414 {
01415 return;
01416 }
01417
01418 if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT )
01419 {
01420 return;
01421 }
01422
01423 if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) )
01424 {
01425 return;
01426 }
01427
01428
01430 if ( !hitAlly
01431 && enemyInFOV
01432 && NPCInfo->enemyLastSeenTime > 0
01433 && NPCInfo->group
01434 && (NPCInfo->group->numState[SQUAD_RETREAT]>0||NPCInfo->group->numState[SQUAD_TRANSITION]>0||NPCInfo->group->numState[SQUAD_SCOUT]>0) )
01435 {
01436 if ( level.time - NPCInfo->enemyLastSeenTime < 10000 &&
01437 (!NPCInfo->group || level.time - NPCInfo->group->lastSeenEnemyTime < 10000 ))
01438 {
01439 if ( !Q_irand( 0, 10 ) )
01440 {
01441
01442 vec3_t muzzle, dir, angles;
01443 qboolean tooClose = qfalse;
01444 qboolean tooFar = qfalse;
01445 float distThreshold;
01446 float dist;
01447
01448 CalcEntitySpot( NPC, SPOT_HEAD, muzzle );
01449 if ( VectorCompare( impactPos, vec3_origin ) )
01450 {
01451 trace_t tr;
01452
01453 vec3_t forward, end;
01454 AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL );
01455 VectorMA( muzzle, 8192, forward, end );
01456 trap_Trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT );
01457 VectorCopy( tr.endpos, impactPos );
01458 }
01459
01460
01461 distThreshold = 16384;
01462 switch ( NPC->s.weapon )
01463 {
01464 case WP_ROCKET_LAUNCHER:
01465 case WP_FLECHETTE:
01466 case WP_THERMAL:
01467 case WP_TRIP_MINE:
01468 case WP_DET_PACK:
01469 distThreshold = 65536;
01470 break;
01471 case WP_REPEATER:
01472 if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
01473 {
01474 distThreshold = 65536;
01475 }
01476 break;
01477 default:
01478 break;
01479 }
01480
01481 dist = DistanceSquared( impactPos, muzzle );
01482
01483 if ( dist < distThreshold )
01484 {
01485 tooClose = qtrue;
01486 }
01487 else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 ||
01488 (NPCInfo->group && level.time - NPCInfo->group->lastSeenEnemyTime > 5000 ))
01489 {
01490
01491 distThreshold = 65536;
01492 switch ( NPC->s.weapon )
01493 {
01494 case WP_ROCKET_LAUNCHER:
01495 case WP_FLECHETTE:
01496 case WP_THERMAL:
01497 case WP_TRIP_MINE:
01498 case WP_DET_PACK:
01499 distThreshold = 262144;
01500 break;
01501 case WP_REPEATER:
01502 if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
01503 {
01504 distThreshold = 262144;
01505 }
01506 break;
01507 default:
01508 break;
01509 }
01510 dist = DistanceSquared( impactPos, NPCInfo->enemyLastSeenLocation );
01511 if ( dist > distThreshold )
01512 {
01513 tooFar = qtrue;
01514 }
01515 }
01516
01517 if ( !tooClose && !tooFar )
01518 {
01519 VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir );
01520 VectorNormalize( dir );
01521 vectoangles( dir, angles );
01522
01523 NPCInfo->desiredYaw = angles[YAW];
01524 NPCInfo->desiredPitch = angles[PITCH];
01525
01526 shoot = qtrue;
01527 faceEnemy = qfalse;
01528
01529 return;
01530 }
01531 }
01532 }
01533 }
01534 }
01535
01536 void ST_TrackEnemy( gentity_t *self, vec3_t enemyPos )
01537 {
01538
01539 TIMER_Set( self, "attackDelay", Q_irand( 1000, 2000 ) );
01540
01541 TIMER_Set( self, "stick", Q_irand( 500, 1500 ) );
01542 TIMER_Set( self, "stand", -1 );
01543 TIMER_Set( self, "scoutTime", TIMER_Get( self, "stick" )-level.time+Q_irand(5000, 10000) );
01544
01545 NPC_FreeCombatPoint( self->NPC->combatPoint, qfalse );
01546
01547 NPC_SetMoveGoal( self, enemyPos, 16, qfalse, -1, NULL );
01548 }
01549
01550 int ST_ApproachEnemy( gentity_t *self )
01551 {
01552 TIMER_Set( self, "attackDelay", Q_irand( 250, 500 ) );
01553
01554 TIMER_Set( self, "stick", Q_irand( 1000, 2000 ) );
01555 TIMER_Set( self, "stand", -1 );
01556 TIMER_Set( self, "scoutTime", TIMER_Get( self, "stick" )-level.time+Q_irand(5000, 10000) );
01557
01558 NPC_FreeCombatPoint( self->NPC->combatPoint, qfalse );
01559
01560 return (CP_CLEAR|CP_CLOSEST);
01561 }
01562
01563 void ST_HuntEnemy( gentity_t *self )
01564 {
01565
01566
01567 TIMER_Set( NPC, "stick", Q_irand( 250, 1000 ) );
01568 TIMER_Set( NPC, "stand", -1 );
01569 TIMER_Set( NPC, "scoutTime", TIMER_Get( NPC, "stick" )-level.time+Q_irand(5000, 10000) );
01570
01571 NPC_FreeCombatPoint( NPCInfo->combatPoint, qfalse );
01572
01573 if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
01574 {
01575 self->NPC->goalEntity = NPC->enemy;
01576 }
01577 }
01578
01579 void ST_TransferTimers( gentity_t *self, gentity_t *other )
01580 {
01581 TIMER_Set( other, "attackDelay", TIMER_Get( self, "attackDelay" )-level.time );
01582 TIMER_Set( other, "duck", TIMER_Get( self, "duck" )-level.time );
01583 TIMER_Set( other, "stick", TIMER_Get( self, "stick" )-level.time );
01584 TIMER_Set( other, "scoutTime", TIMER_Get( self, "scout" )-level.time );
01585 TIMER_Set( other, "roamTime", TIMER_Get( self, "roamTime" )-level.time );
01586 TIMER_Set( other, "stand", TIMER_Get( self, "stand" )-level.time );
01587 TIMER_Set( self, "attackDelay", -1 );
01588 TIMER_Set( self, "duck", -1 );
01589 TIMER_Set( self, "stick", -1 );
01590 TIMER_Set( self, "scoutTime", -1 );
01591 TIMER_Set( self, "roamTime", -1 );
01592 TIMER_Set( self, "stand", -1 );
01593 }
01594
01595 void ST_TransferMoveGoal( gentity_t *self, gentity_t *other )
01596 {
01597 if ( trap_ICARUS_TaskIDPending( self, TID_MOVE_NAV ) )
01598 {
01599 return;
01600 }
01601 if ( self->NPC->combatPoint != -1 )
01602 {
01603 self->NPC->lastFailedCombatPoint = other->NPC->combatPoint = self->NPC->combatPoint;
01604 self->NPC->combatPoint = -1;
01605 }
01606 else
01607 {
01608 if ( self->NPC->goalEntity == self->NPC->tempGoal )
01609 {
01610 NPC_SetMoveGoal( other, self->NPC->tempGoal->r.currentOrigin, self->NPC->goalRadius, ((self->NPC->tempGoal->flags&FL_NAVGOAL)?qtrue:qfalse), -1, NULL );
01611 }
01612 else
01613 {
01614 other->NPC->goalEntity = self->NPC->goalEntity;
01615 }
01616 }
01617
01618 AI_GroupUpdateSquadstates( self->NPC->group, other, NPCInfo->squadState );
01619
01620
01621 ST_TransferTimers( self, other );
01622
01623
01624 AI_GroupUpdateSquadstates( self->NPC->group, self, SQUAD_STAND_AND_SHOOT );
01625 TIMER_Set( self, "stand", Q_irand( 1000, 3000 ) );
01626 }
01627
01628 int ST_GetCPFlags( void )
01629 {
01630 int cpFlags = 0;
01631 if ( NPC && NPCInfo->group )
01632 {
01633 if ( NPC == NPCInfo->group->commander && NPC->client->NPC_class == CLASS_IMPERIAL )
01634 {
01635 if ( NPCInfo->group->numGroup > 1 && Q_irand( -3, NPCInfo->group->numGroup ) > 1 )
01636 {
01637 if ( Q_irand( 0, 1 ) )
01638 {
01639 ST_Speech( NPC, SPEECH_CHASE, 0.5 );
01640 }
01641 else
01642 {
01643 ST_Speech( NPC, SPEECH_YELL, 0.5 );
01644 }
01645 }
01646 cpFlags = (CP_CLEAR|CP_COVER|CP_AVOID|CP_SAFE|CP_RETREAT);
01647 }
01648 else if ( NPCInfo->group->morale < 0 )
01649 {
01650 cpFlags = (CP_COVER|CP_AVOID|CP_SAFE|CP_RETREAT);
01651 }
01652 else if ( NPCInfo->group->morale < NPCInfo->group->numGroup )
01653 {
01654 int moraleDrop = NPCInfo->group->numGroup - NPCInfo->group->morale;
01655 if ( moraleDrop < -6 )
01656 {
01657 cpFlags = (CP_FLEE|CP_RETREAT|CP_COVER|CP_AVOID|CP_SAFE);
01658 }
01659 else if ( moraleDrop < -3 )
01660 {
01661 cpFlags = (CP_RETREAT|CP_COVER|CP_AVOID|CP_SAFE);
01662 }
01663 else if ( moraleDrop < 0 )
01664 {
01665 cpFlags = (CP_COVER|CP_AVOID|CP_SAFE);
01666 }
01667 }
01668 else
01669 {
01670 int moraleBoost = NPCInfo->group->morale - NPCInfo->group->numGroup;
01671 if ( moraleBoost > 20 )
01672 {
01673 cpFlags = (CP_CLEAR|CP_FLANK|CP_APPROACH_ENEMY);
01674 }
01675 else if ( moraleBoost > 15 )
01676 {
01677 cpFlags = (CP_CLEAR|CP_CLOSEST|CP_APPROACH_ENEMY);
01678 }
01679 else if ( moraleBoost > 10 )
01680 {
01681 cpFlags = (CP_CLEAR|CP_APPROACH_ENEMY);
01682 }
01683 }
01684 }
01685 if ( !cpFlags )
01686 {
01687
01688 switch( Q_irand( 0, 3 ) )
01689 {
01690 case 0:
01691 cpFlags = (CP_CLEAR|CP_COVER|CP_NEAREST);
01692 break;
01693 case 1:
01694 cpFlags = (CP_CLEAR|CP_COVER|CP_APPROACH_ENEMY);
01695 break;
01696 case 2:
01697 cpFlags = (CP_CLEAR|CP_COVER|CP_CLOSEST|CP_APPROACH_ENEMY);
01698 break;
01699 case 3:
01700 cpFlags = (CP_CLEAR|CP_COVER|CP_FLANK|CP_APPROACH_ENEMY);
01701 break;
01702 }
01703 }
01704 if ( NPC && (NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) )
01705 {
01706 cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST);
01707 cpFlags |= CP_NEAREST;
01708 }
01709 return cpFlags;
01710 }
01711
01712
01713
01714
01715
01716
01717
01718
01719
01720
01721
01722
01723
01724 void ST_Commander( void )
01725 {
01726 int i, j;
01727 int cp, cpFlags_org, cpFlags;
01728 AIGroupInfo_t *group = NPCInfo->group;
01729 gentity_t *member;
01730 qboolean runner = qfalse;
01731 qboolean enemyLost = qfalse;
01732 qboolean enemyProtected = qfalse;
01733 qboolean scouting = qfalse;
01734 int squadState;
01735 int curMemberNum, lastMemberNum;
01736 float avoidDist;
01737
01738 group->processed = qtrue;
01739
01740 if ( group->enemy == NULL || group->enemy->client == NULL )
01741 {
01742 return;
01743 }
01744
01745
01746
01747
01748
01749
01750
01751 SaveNPCGlobals();
01752
01753 if ( group->lastSeenEnemyTime < level.time - 180000 )
01754 {
01755 ST_Speech( NPC, SPEECH_LOST, 0.0f );
01756 group->enemy->waypoint = NAV_FindClosestWaypointForEnt( group->enemy, WAYPOINT_NONE );
01757 for ( i = 0; i < group->numGroup; i++ )
01758 {
01759 member = &g_entities[group->member[i].number];
01760 SetNPCGlobals( member );
01761 if ( trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) )
01762 {
01763 continue;
01764 }
01765 if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
01766 {
01767 continue;
01768 }
01769
01770 G_ClearEnemy( NPC );
01771 NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, group->enemy->waypoint );
01772 if ( NPC->waypoint == WAYPOINT_NONE )
01773 {
01774 NPCInfo->behaviorState = BS_DEFAULT;
01775 }
01776 else if ( group->enemy->waypoint == WAYPOINT_NONE || (trap_Nav_GetPathCost( NPC->waypoint, group->enemy->waypoint ) >= Q3_INFINITE) )
01777 {
01778 NPC_BSSearchStart( NPC->waypoint, BS_SEARCH );
01779 }
01780 else
01781 {
01782 NPC_BSSearchStart( group->enemy->waypoint, BS_SEARCH );
01783 }
01784 }
01785 group->enemy = NULL;
01786 RestoreNPCGlobals();
01787 return;
01788 }
01789
01790
01791
01792
01793
01794
01795
01796
01797
01798
01799
01800
01801
01802
01803
01804
01805
01806
01807
01808
01809
01810
01811
01812
01813
01814
01815
01816 if ( group->numState[SQUAD_SCOUT] > 0 ||
01817 group->numState[SQUAD_TRANSITION] > 0 ||
01818 group->numState[SQUAD_RETREAT] > 0 )
01819 {
01820 runner = qtrue;
01821 }
01822
01823 if ( group->lastSeenEnemyTime > level.time - 32000 && group->lastSeenEnemyTime < level.time - 30000 )
01824 {
01825 if ( group->commander && !Q_irand( 0, 1 ) )
01826 {
01827 ST_Speech( group->commander, SPEECH_ESCAPING, 0.0f );
01828 }
01829 else
01830 {
01831 ST_Speech( NPC, SPEECH_ESCAPING, 0.0f );
01832 }
01833
01834 NPCInfo->blockedSpeechDebounceTime = level.time + 3000;
01835 }
01836
01837 if ( group->lastSeenEnemyTime < level.time - 10000 )
01838 {
01839 enemyLost = qtrue;
01840 }
01841
01842 if ( group->lastClearShotTime < level.time - 5000 )
01843 {
01844 enemyProtected = qtrue;
01845 }
01846
01847
01848
01849
01850 if ( d_asynchronousGroupAI.integer )
01851 {
01852 group->activeMemberNum++;
01853 if ( group->activeMemberNum >= group->numGroup )
01854 {
01855 group->activeMemberNum = 0;
01856 }
01857 curMemberNum = group->activeMemberNum;
01858 lastMemberNum = curMemberNum + 1;
01859 }
01860 else
01861 {
01862 curMemberNum = 0;
01863 lastMemberNum = group->numGroup;
01864 }
01865 for ( i = curMemberNum; i < lastMemberNum; i++ )
01866 {
01867
01868 cp = -1;
01869 cpFlags = 0;
01870 squadState = SQUAD_IDLE;
01871 avoidDist = 0;
01872 scouting = qfalse;
01873
01874
01875 member = &g_entities[group->member[i].number];
01876 if ( !member->enemy )
01877 {
01878 continue;
01879 }
01880 SetNPCGlobals( member );
01881
01882 if ( !TIMER_Done( NPC, "flee" ) )
01883 {
01884 continue;
01885 }
01886
01887 if ( trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) )
01888 {
01889 continue;
01890 }
01891
01892 if ( NPC->s.weapon == WP_NONE
01893 && NPCInfo->goalEntity
01894 && NPCInfo->goalEntity == NPCInfo->tempGoal
01895 && NPCInfo->goalEntity->enemy
01896 && NPCInfo->goalEntity->enemy->s.eType == ET_ITEM )
01897 {
01898 continue;
01899 }
01900
01901
01902 if ( !group->commander || group->commander->NPC->rank < RANK_ENSIGN )
01903 {
01904 if ( NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) )
01905 {
01906 ST_Speech( NPC, SPEECH_COVER, 0 );
01907 continue;
01908 }
01909 }
01910
01911 if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
01912 {
01913 continue;
01914 }
01915
01916
01917 if ( NPCInfo->squadState != SQUAD_RETREAT )
01918 {
01919 if ( NPC->client->ps.weapon == WP_NONE )
01920 {
01921 if ( NPCInfo->goalEntity == NULL || NPCInfo->goalEntity->enemy == NULL || NPCInfo->goalEntity->enemy->s.eType != ET_ITEM )
01922 {
01923 if ( TIMER_Done( NPC, "hideTime" ) || (DistanceSquared( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) < 65536 && NPC_ClearLOS4( NPC->enemy )) )
01924 {
01925
01926 NPC_StartFlee( NPC->enemy, NPC->enemy->r.currentOrigin, AEL_DANGER_GREAT, 5000, 10000 );
01927 }
01928 }
01929 continue;
01930 }
01931 if ( TIMER_Done( NPC, "roamTime" ) && TIMER_Done( NPC, "hideTime" ) && NPC->health > 10 && !trap_InPVS( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) )
01932 {
01933
01934 cpFlags |= (CP_CLEAR|CP_COVER);
01935 }
01936 else if ( NPCInfo->localState == LSTATE_UNDERFIRE )
01937 {
01938 switch( group->enemy->client->ps.weapon )
01939 {
01940 case WP_SABER:
01941 if ( DistanceSquared( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) < 65536 )
01942 {
01943 cpFlags |= (CP_AVOID_ENEMY|CP_COVER|CP_AVOID|CP_RETREAT);
01944 if ( !group->commander || group->commander->NPC->rank < RANK_ENSIGN )
01945 {
01946 squadState = SQUAD_RETREAT;
01947 }
01948 avoidDist = 256;
01949 }
01950 break;
01951 default:
01952 case WP_BLASTER:
01953 cpFlags |= (CP_COVER);
01954 break;
01955 }
01956 if ( NPC->health <= 10 )
01957 {
01958 if ( !group->commander || group->commander->NPC->rank < RANK_ENSIGN )
01959 {
01960 cpFlags |= (CP_FLEE|CP_AVOID|CP_RETREAT);
01961 squadState = SQUAD_RETREAT;
01962 }
01963 }
01964 }
01965 else
01966 {
01967 if ( trap_InPVS( NPC->r.currentOrigin, group->enemy->r.currentOrigin ) )
01968 {
01969 if ( NPC->client->ps.weapon == WP_ROCKET_LAUNCHER &&
01970 DistanceSquared( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) < MIN_ROCKET_DIST_SQUARED &&
01971 NPCInfo->squadState != SQUAD_TRANSITION )
01972 {
01973 cpFlags |= (CP_AVOID_ENEMY|CP_CLEAR|CP_AVOID);
01974 avoidDist = 256;
01975 }
01976 else
01977 {
01978 switch( group->enemy->client->ps.weapon )
01979 {
01980 case WP_SABER:
01981
01982 if (!group->enemy->client->ps.saberHolstered)
01983 {
01984 if ( DistanceSquared( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) < 65536 )
01985 {
01986 if ( TIMER_Done( NPC, "hideTime" ) )
01987 {
01988 if ( NPCInfo->squadState != SQUAD_TRANSITION )
01989 {
01990 cpFlags |= (CP_AVOID_ENEMY|CP_CLEAR|CP_AVOID);
01991 avoidDist = 256;
01992 }
01993 }
01994 }
01995 }
01996 default:
01997 break;
01998 }
01999 }
02000 }
02001 }
02002 }
02003
02004 if ( !cpFlags )
02005 {
02006 if ( runner && NPCInfo->combatPoint != -1 )
02007 {
02008 if ( NPCInfo->squadState != SQUAD_SCOUT &&
02009 NPCInfo->squadState != SQUAD_TRANSITION &&
02010 NPCInfo->squadState != SQUAD_RETREAT )
02011 {
02012 if ( TIMER_Done( NPC, "verifyCP" ) && DistanceSquared( NPC->r.currentOrigin, level.combatPoints[NPCInfo->combatPoint].origin ) > 64*64 )
02013 {
02014
02015
02016 cp = NPCInfo->combatPoint;
02017 cpFlags |= ST_GetCPFlags();
02018 }
02019 else
02020 {
02021
02022 TIMER_Set( NPC, "duck", -1 );
02023
02024 TIMER_Set( NPC, "attackDelay", -1 );
02025
02026 }
02027 }
02028 else
02029 {
02030
02031 if ( NPCInfo->aiFlags & NPCAI_BLOCKED )
02032 {
02033
02034 for ( j = 0; j < group->numGroup; j++ )
02035 {
02036 if ( group->member[j].number == NPCInfo->blockingEntNum )
02037 {
02038 ST_TransferMoveGoal( NPC, &g_entities[group->member[j].number] );
02039 break;
02040 }
02041 }
02042 }
02043
02044 continue;
02045 }
02046 }
02047 else
02048 {
02049 if ( NPCInfo->combatPoint != -1 )
02050 {
02051 if ( NPCInfo->squadState != SQUAD_SCOUT &&
02052 NPCInfo->squadState != SQUAD_TRANSITION &&
02053 NPCInfo->squadState != SQUAD_RETREAT )
02054 {
02055 if ( TIMER_Done( NPC, "verifyCP" ) )
02056 {
02057 if ( DistanceSquared( NPC->r.currentOrigin, level.combatPoints[NPCInfo->combatPoint].origin ) > 64*64 )
02058 {
02059
02060 cp = NPCInfo->combatPoint;
02061 cpFlags |= ST_GetCPFlags();
02062 }
02063 }
02064 }
02065 }
02066 if ( enemyLost )
02067 {
02068
02069 if ( group->numState[SQUAD_SCOUT] <= 0 )
02070 {
02071 scouting = qtrue;
02072 NPC_ST_StoreMovementSpeech( SPEECH_CHASE, 0.0f );
02073 }
02074
02075 ST_TrackEnemy( NPC, group->enemyLastSeenPos );
02076
02077 AI_GroupUpdateSquadstates( group, NPC, SQUAD_SCOUT );
02078
02079 runner = qtrue;
02080 }
02081 else if ( enemyProtected )
02082 {
02083
02084
02085
02086 if ( TIMER_Done( NPC, "roamTime" ) && !Q_irand( 0, group->numGroup) )
02087 {
02088 cpFlags |= ST_ApproachEnemy( NPC );
02089
02090 AI_GroupUpdateSquadstates( group, NPC, SQUAD_SCOUT );
02091 }
02092 }
02093 else
02094 {
02095
02096
02097 {
02098 if ( NPCInfo->combatPoint == -1 )
02099 {
02100 if ( 1 )
02101 {
02102 cpFlags |= ST_GetCPFlags();
02103 }
02104 else
02105 {
02106 TIMER_Set( NPC, "stick", Q_irand( 2000, 4000 ) );
02107 TIMER_Set( NPC, "roamTime", Q_irand( 1000, 3000 ) );
02108 }
02109 }
02110 else if ( TIMER_Done( NPC, "roamTime" ) )
02111 {
02112 if ( i == 0 )
02113 {
02114 if ( (group->morale-group->numGroup>0) && !Q_irand( 0, 4 ) )
02115 {
02116 cpFlags |= (CP_CLEAR|CP_COVER|CP_FLANK|CP_APPROACH_ENEMY);
02117 }
02118 else if ( (group->morale-group->numGroup<0) )
02119 {
02120 cpFlags |= ST_GetCPFlags();
02121 }
02122 else
02123 {
02124 TIMER_Set( NPC, "roamTime", Q_irand( 2000, 5000 ) );
02125 TIMER_Set( NPC, "stick", Q_irand( 2000, 5000 ) );
02126
02127 TIMER_Set( NPC, "duck", Q_irand( 3000, 4000 ) );
02128 AI_GroupUpdateSquadstates( group, NPC, SQUAD_POINT );
02129 }
02130 }
02131 else if ( i == group->numGroup - 1 )
02132 {
02133 if ( (group->morale-group->numGroup<0) )
02134 {
02135 TIMER_Set( NPC, "roamTime", Q_irand( 2000, 5000 ) );
02136 TIMER_Set( NPC, "stick", Q_irand( 2000, 5000 ) );
02137 }
02138 else if ( (group->morale-group->numGroup>0) )
02139 {
02140 cpFlags |= ST_ApproachEnemy( NPC );
02141
02142 AI_GroupUpdateSquadstates( group, NPC, SQUAD_SCOUT );
02143 }
02144 else
02145 {
02146 cpFlags |= ST_GetCPFlags();
02147 }
02148 }
02149 else
02150 {
02151 if ( (group->morale-group->numGroup<0) || !Q_irand( 0, 4 ) )
02152 {
02153 cpFlags |= ST_GetCPFlags();
02154 }
02155 else
02156 {
02157 TIMER_Set( NPC, "stick", Q_irand( 2000, 4000 ) );
02158 TIMER_Set( NPC, "roamTime", Q_irand( 2000, 4000 ) );
02159 }
02160 }
02161 }
02162 }
02163 if ( !cpFlags )
02164 {
02165
02166
02167
02168
02169
02170
02171
02172
02173
02174
02175
02176 if ( TIMER_Done( NPC, "duck" ) )
02177 {
02178 if ( TIMER_Done( NPC, "stand" ) )
02179 {
02180 if ( NPCInfo->combatPoint == -1 || (level.combatPoints[NPCInfo->combatPoint].flags&CPF_DUCK) )
02181 {
02182 if ( !Q_irand( 0, 3 ) )
02183 {
02184 TIMER_Set( NPC, "duck", Q_irand( 1000, 3000 ) );
02185 }
02186 }
02187 }
02188 }
02189
02190 }
02191 }
02192 }
02193 }
02194
02195
02196 NPCInfo->localState = LSTATE_NONE;
02197
02198 if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST )
02199 {
02200 cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST);
02201 cpFlags |= CP_NEAREST;
02202 }
02203
02204 if ( cpFlags )
02205 {
02206
02207
02208
02209
02210
02211
02212
02213 if ( group->enemy->client->ps.weapon == WP_SABER && !group->enemy->client->ps.saberHolstered )
02214 {
02215 cpFlags |= CP_AVOID_ENEMY;
02216 avoidDist = 256;
02217 }
02218
02219
02220 cpFlags_org = cpFlags;
02221
02222
02223 if ( cp == -1 )
02224 {
02225 cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, group->enemy->r.currentOrigin, cpFlags|CP_HAS_ROUTE, avoidDist, NPCInfo->lastFailedCombatPoint );
02226 }
02227 while ( cp == -1 && cpFlags != CP_ANY )
02228 {
02229 if ( cpFlags & CP_INVESTIGATE )
02230 {
02231 cpFlags &= ~CP_INVESTIGATE;
02232 }
02233 else if ( cpFlags & CP_SQUAD )
02234 {
02235 cpFlags &= ~CP_SQUAD;
02236 }
02237 else if ( cpFlags & CP_DUCK )
02238 {
02239 cpFlags &= ~CP_DUCK;
02240 }
02241 else if ( cpFlags & CP_NEAREST )
02242 {
02243 cpFlags &= ~CP_NEAREST;
02244 }
02245 else if ( cpFlags & CP_FLANK )
02246 {
02247 cpFlags &= ~CP_FLANK;
02248 }
02249 else if ( cpFlags & CP_SAFE )
02250 {
02251 cpFlags &= ~CP_SAFE;
02252 }
02253 else if ( cpFlags & CP_CLOSEST )
02254 {
02255 cpFlags &= ~CP_CLOSEST;
02256
02257 cpFlags |= CP_APPROACH_ENEMY;
02258 }
02259 else if ( cpFlags & CP_APPROACH_ENEMY )
02260 {
02261 cpFlags &= ~CP_APPROACH_ENEMY;
02262 }
02263 else if ( cpFlags & CP_COVER )
02264 {
02265 cpFlags &= ~CP_COVER;
02266
02267 cpFlags |= CP_DUCK;
02268 }
02269 else if ( cpFlags & CP_CLEAR )
02270 {
02271 cpFlags &= ~CP_CLEAR;
02272 }
02273 else if ( cpFlags & CP_AVOID_ENEMY )
02274 {
02275 cpFlags &= ~CP_AVOID_ENEMY;
02276 }
02277 else if ( cpFlags & CP_RETREAT )
02278 {
02279 cpFlags &= ~CP_RETREAT;
02280 }
02281 else if ( cpFlags &CP_FLEE )
02282 {
02283 cpFlags &= ~CP_FLEE;
02284
02285 cpFlags |= (CP_COVER|CP_AVOID_ENEMY);
02286 }
02287 else if ( cpFlags & CP_AVOID )
02288 {
02289 cpFlags &= ~CP_AVOID;
02290 }
02291 else
02292 {
02293 cpFlags = CP_ANY;
02294 }
02295
02296 cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, group->enemy->r.currentOrigin, cpFlags|CP_HAS_ROUTE, avoidDist, -1 );
02297 }
02298
02299 if ( cp != -1 )
02300 {
02301
02302 runner = qtrue;
02303
02304 TIMER_Set( NPC, "roamTime", Q3_INFINITE );
02305 TIMER_Set( NPC, "verifyCP", Q_irand( 1000, 3000 ) );
02306 NPC_SetCombatPoint( cp );
02307 NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp, NULL );
02308
02309
02310
02311 {
02312
02313
02314
02315
02316 if ( squadState != SQUAD_IDLE )
02317 {
02318 AI_GroupUpdateSquadstates( group, NPC, squadState );
02319 }
02320 else if ( cpFlags&CP_FLEE )
02321 {
02322 AI_GroupUpdateSquadstates( group, NPC, SQUAD_RETREAT );
02323 }
02324 else
02325 {
02326 AI_GroupUpdateSquadstates( group, NPC, SQUAD_TRANSITION );
02327 }
02328
02329
02330 if ( !(cpFlags_org&CP_FLEE) )
02331 {
02332
02333 }
02334
02335
02336
02337
02338
02339
02340
02341
02342
02343 if ( cpFlags & CP_FLANK )
02344 {
02345 if ( group->numGroup > 1 )
02346 {
02347 NPC_ST_StoreMovementSpeech( SPEECH_OUTFLANK, -1 );
02348 }
02349 }
02350 else
02351 {
02352 if ( group->numGroup > 1 )
02353 {
02354 float dot = 1.0f;
02355 if ( !Q_irand( 0, 3 ) )
02356 {
02357 vec3_t eDir2Me, eDir2CP;
02358
02359 VectorSubtract( NPC->r.currentOrigin, group->enemy->r.currentOrigin, eDir2Me );
02360 VectorNormalize( eDir2Me );
02361
02362 VectorSubtract( level.combatPoints[NPCInfo->combatPoint].origin, group->enemy->r.currentOrigin, eDir2CP );
02363 VectorNormalize( eDir2CP );
02364
02365 dot = DotProduct( eDir2Me, eDir2CP );
02366 }
02367
02368 if ( dot < 0.4 )
02369 {
02370 NPC_ST_StoreMovementSpeech( SPEECH_OUTFLANK, -1 );
02371 }
02372 else if ( !Q_irand( 0, 10 ) )
02373 {
02374 NPC_ST_StoreMovementSpeech( SPEECH_YELL, 0.2f );
02375 }
02376 }
02377 }
02378
02379
02380
02381
02382
02383
02384
02385
02386
02387 }
02388 }
02389 else if ( NPCInfo->squadState == SQUAD_SCOUT )
02390 {
02391 ST_HuntEnemy( NPC );
02392
02393 AI_GroupUpdateSquadstates( group, NPC, SQUAD_SCOUT );
02394
02395 }
02396 }
02397 }
02398
02399 RestoreNPCGlobals();
02400 return;
02401 }
02402
02403
02404
02405
02406
02407
02408
02409 void NPC_BSST_Attack( void )
02410 {
02411 vec3_t enemyDir, shootDir;
02412 float dot;
02413
02414
02415 if ( NPC->painDebounceTime > level.time )
02416 {
02417 NPC_UpdateAngles( qtrue, qtrue );
02418 return;
02419 }
02420
02421
02422
02423 if ( NPC_CheckEnemyExt(qfalse) == qfalse )
02424 {
02425 NPC->enemy = NULL;
02426 if( NPC->client->playerTeam == NPCTEAM_PLAYER )
02427 {
02428 NPC_BSPatrol();
02429 }
02430 else
02431 {
02432 NPC_BSST_Patrol();
02433 }
02434 return;
02435 }
02436
02437
02438
02439
02440 if ( TIMER_Done( NPC, "interrogating" ) )
02441 {
02442 AI_GetGroup( NPC );
02443 }
02444 else
02445 {
02446
02447 }
02448
02449 if ( NPCInfo->group )
02450 {
02451 if ( !NPCInfo->group->processed )
02452 {
02453 #if AI_TIMERS
02454 int startTime = GetTime(0);
02455 #endif// AI_TIMERS
02456 ST_Commander();
02457 #if AI_TIMERS
02458 int commTime = GetTime ( startTime );
02459 if ( commTime > 20 )
02460 {
02461 gi.Printf( S_COLOR_RED"ERROR: Commander time: %d\n", commTime );
02462 }
02463 else if ( commTime > 10 )
02464 {
02465 gi.Printf( S_COLOR_YELLOW"WARNING: Commander time: %d\n", commTime );
02466 }
02467 else if ( commTime > 2 )
02468 {
02469 gi.Printf( S_COLOR_GREEN"Commander time: %d\n", commTime );
02470 }
02471 #endif// AI_TIMERS
02472 }
02473 }
02474 else if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) )
02475 {
02476 ST_Speech( NPC, SPEECH_COVER, 0 );
02477 NPC_UpdateAngles( qtrue, qtrue );
02478 return;
02479 }
02480
02481 if ( !NPC->enemy )
02482 {
02483 NPC_BSST_Patrol();
02484 return;
02485 }
02486
02487 enemyLOS = enemyCS = enemyInFOV = qfalse;
02488 move = qtrue;
02489 faceEnemy = qfalse;
02490 shoot = qfalse;
02491 hitAlly = qfalse;
02492 VectorClear( impactPos );
02493 enemyDist = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin );
02494
02495 VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, enemyDir );
02496 VectorNormalize( enemyDir );
02497 AngleVectors( NPC->client->ps.viewangles, shootDir, NULL, NULL );
02498 dot = DotProduct( enemyDir, shootDir );
02499 if ( dot > 0.5f ||( enemyDist * (1.0f-dot)) < 10000 )
02500 {
02501 enemyInFOV = qtrue;
02502 }
02503
02504 if ( enemyDist < MIN_ROCKET_DIST_SQUARED )
02505 {
02506 if ( (NPC->client->ps.weapon == WP_FLECHETTE || NPC->client->ps.weapon == WP_REPEATER) &&
02507 (NPCInfo->scriptFlags & SCF_ALT_FIRE) )
02508 {
02509 NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
02510
02511 }
02512 }
02513 else if ( enemyDist > 65536 )
02514 {
02515 if ( NPC->client->ps.weapon == WP_DISRUPTOR )
02516 {
02517 if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) )
02518 {
02519 NPCInfo->scriptFlags |= SCF_ALT_FIRE;
02520
02521 NPC_ChangeWeapon( WP_DISRUPTOR );
02522 NPC_UpdateAngles( qtrue, qtrue );
02523 return;
02524 }
02525 }
02526 }
02527
02528
02529 if ( NPC_ClearLOS4( NPC->enemy ) )
02530 {
02531 AI_GroupUpdateEnemyLastSeen( NPCInfo->group, NPC->enemy->r.currentOrigin );
02532 NPCInfo->enemyLastSeenTime = level.time;
02533 enemyLOS = qtrue;
02534
02535 if ( NPC->client->ps.weapon == WP_NONE )
02536 {
02537 enemyCS = qfalse;
02538 NPC_AimAdjust( -1 );
02539 }
02540 else
02541 {
02542 if ( (NPC->client->ps.weapon == WP_ROCKET_LAUNCHER || (NPC->client->ps.weapon == WP_FLECHETTE && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_ROCKET_DIST_SQUARED )
02543 {
02544 enemyCS = qfalse;
02545 hitAlly = qtrue;
02546
02547 }
02548 else if ( enemyInFOV )
02549 {
02550 int hit = NPC_ShotEntity( NPC->enemy, impactPos );
02551 gentity_t *hitEnt = &g_entities[hit];
02552
02553 if ( hit == NPC->enemy->s.number
02554 || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam )
02555 || ( hitEnt && hitEnt->takedamage && ((hitEnt->r.svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) ) )
02556 {
02557 AI_GroupUpdateClearShotTime( NPCInfo->group );
02558 enemyCS = qtrue;
02559 NPC_AimAdjust( 2 );
02560 VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation );
02561 }
02562 else
02563 {
02564 NPC_AimAdjust( 1 );
02565 ST_ResolveBlockedShot( hit );
02566 if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam )
02567 {
02568 hitAlly = qtrue;
02569 }
02570 else
02571 {
02572 }
02573 }
02574 }
02575 else
02576 {
02577 enemyCS = qfalse;
02578 }
02579 }
02580 }
02581 else if ( trap_InPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ) )
02582 {
02583 NPCInfo->enemyLastSeenTime = level.time;
02584 faceEnemy = qtrue;
02585 NPC_AimAdjust( -1 );
02586 }
02587
02588 if ( NPC->client->ps.weapon == WP_NONE )
02589 {
02590 faceEnemy = qfalse;
02591 shoot = qfalse;
02592 }
02593 else
02594 {
02595 if ( enemyLOS )
02596 {
02597 faceEnemy = qtrue;
02598 }
02599 if ( enemyCS )
02600 {
02601 shoot = qtrue;
02602 }
02603 }
02604
02605
02606 ST_CheckMoveState();
02607
02608
02609 ST_CheckFireState();
02610
02611 if ( faceEnemy )
02612 {
02613 NPC_FaceEnemy( qtrue );
02614 }
02615
02616 if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
02617 {
02618 if ( NPCInfo->goalEntity == NPC->enemy )
02619 {
02620 move = qfalse;
02621 }
02622 }
02623
02624 if ( NPC->client->ps.weaponTime > 0 && NPC->s.weapon == WP_ROCKET_LAUNCHER )
02625 {
02626 move = qfalse;
02627 }
02628
02629 if ( move )
02630 {
02631 if ( NPCInfo->goalEntity )
02632 {
02633 move = ST_Move();
02634 }
02635 else
02636 {
02637 move = qfalse;
02638 }
02639 }
02640
02641 if ( !move )
02642 {
02643 if ( !TIMER_Done( NPC, "duck" ) )
02644 {
02645 ucmd.upmove = -127;
02646 }
02647
02648 }
02649 else
02650 {
02651 TIMER_Set( NPC, "duck", -1 );
02652 }
02653
02654 if ( !TIMER_Done( NPC, "flee" ) )
02655 {
02656 faceEnemy = qfalse;
02657 }
02658
02659
02660
02661 if ( !faceEnemy )
02662 {
02663 if ( !move )
02664 {
02665 VectorCopy( NPC->client->ps.viewangles, NPCInfo->lastPathAngles );
02666 }
02667 NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW];
02668 NPCInfo->desiredPitch = 0;
02669 NPC_UpdateAngles( qtrue, qtrue );
02670 if ( move )
02671 {
02672 shoot = qfalse;
02673 }
02674 }
02675
02676 if ( NPCInfo->scriptFlags & SCF_DONT_FIRE )
02677 {
02678 shoot = qfalse;
02679 }
02680
02681 if ( NPC->enemy && NPC->enemy->enemy )
02682 {
02683 if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER )
02684 {
02685 shoot = qfalse;
02686 }
02687 }
02688
02689 if ( NPC->client->ps.weaponTime > 0 )
02690 {
02691 if ( NPC->s.weapon == WP_ROCKET_LAUNCHER )
02692 {
02693 if ( !enemyLOS || !enemyCS )
02694 {
02695 NPC->client->ps.weaponTime = 0;
02696 }
02697 else
02698 {
02699 TIMER_Set( NPC, "attackDelay", Q_irand( 3000, 5000 ) );
02700 }
02701 }
02702 }
02703 else if ( shoot )
02704 {
02705 if ( TIMER_Done( NPC, "attackDelay" ) )
02706 {
02707 if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) )
02708 {
02709 WeaponThink( qtrue );
02710 }
02711
02712 if ( NPC->s.weapon == WP_ROCKET_LAUNCHER
02713 && (ucmd.buttons&BUTTON_ATTACK)
02714 && !move
02715 && g_spskill.integer > 1
02716 && !Q_irand( 0, 3 ) )
02717 {
02718 ucmd.buttons &= ~BUTTON_ATTACK;
02719 ucmd.buttons |= BUTTON_ALT_ATTACK;
02720 NPC->client->ps.weaponTime = Q_irand( 1000, 2500 );
02721 }
02722 }
02723 }
02724 }
02725
02726 void NPC_BSST_Default( void )
02727 {
02728 if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON )
02729 {
02730 WeaponThink( qtrue );
02731 }
02732
02733 if( !NPC->enemy )
02734 {
02735 NPC_BSST_Patrol();
02736 }
02737 else
02738 {
02739 NPC_CheckGetNewWeapon();
02740 NPC_BSST_Attack();
02741 }
02742 }