00001 #include "b_local.h"
00002 #include "g_nav.h"
00003
00004 extern void Boba_FireDecide( void );
00005
00006 void Seeker_Strafe( void );
00007
00008 #define VELOCITY_DECAY 0.7f
00009
00010 #define MIN_MELEE_RANGE 320
00011 #define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE )
00012
00013 #define MIN_DISTANCE 80
00014 #define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE )
00015
00016 #define SEEKER_STRAFE_VEL 100
00017 #define SEEKER_STRAFE_DIS 200
00018 #define SEEKER_UPWARD_PUSH 32
00019
00020 #define SEEKER_FORWARD_BASE_SPEED 10
00021 #define SEEKER_FORWARD_MULTIPLIER 2
00022
00023 #define SEEKER_SEEK_RADIUS 1024
00024
00025
00026 void NPC_Seeker_Precache(void)
00027 {
00028 G_SoundIndex("sound/chars/seeker/misc/fire.wav");
00029 G_SoundIndex( "sound/chars/seeker/misc/hiss.wav");
00030 G_EffectIndex( "env/small_explode");
00031 }
00032
00033
00034 void NPC_Seeker_Pain(gentity_t *self, gentity_t *attacker, int damage)
00035 {
00036 if ( !(self->NPC->aiFlags&NPCAI_CUSTOM_GRAVITY ))
00037 {
00038 G_Damage( self, NULL, NULL, (float*)vec3_origin, (float*)vec3_origin, 999, 0, MOD_FALLING );
00039 }
00040
00041 SaveNPCGlobals();
00042 SetNPCGlobals( self );
00043 Seeker_Strafe();
00044 RestoreNPCGlobals();
00045 NPC_Pain( self, attacker, damage );
00046 }
00047
00048
00049 void Seeker_MaintainHeight( void )
00050 {
00051 float dif;
00052
00053
00054 NPC_UpdateAngles( qtrue, qtrue );
00055
00056
00057 if ( NPC->enemy )
00058 {
00059 if (TIMER_Done( NPC, "heightChange" ))
00060 {
00061 float difFactor;
00062
00063 TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 ));
00064
00065
00066 dif = (NPC->enemy->r.currentOrigin[2] + flrand( NPC->enemy->r.maxs[2]/2, NPC->enemy->r.maxs[2]+8 )) - NPC->r.currentOrigin[2];
00067
00068 difFactor = 1.0f;
00069 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00070 {
00071 if ( TIMER_Done( NPC, "flameTime" ) )
00072 {
00073 difFactor = 10.0f;
00074 }
00075 }
00076
00077
00078 if ( fabs( dif ) > 2*difFactor )
00079 {
00080 if ( fabs( dif ) > 24*difFactor )
00081 {
00082 dif = ( dif < 0 ? -24*difFactor : 24*difFactor );
00083 }
00084
00085 NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2;
00086 }
00087 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00088 {
00089 NPC->client->ps.velocity[2] *= flrand( 0.85f, 3.0f );
00090 }
00091 }
00092 }
00093 else
00094 {
00095 gentity_t *goal = NULL;
00096
00097 if ( NPCInfo->goalEntity )
00098 {
00099 goal = NPCInfo->goalEntity;
00100 }
00101 else
00102 {
00103 goal = NPCInfo->lastGoalEntity;
00104 }
00105 if ( goal )
00106 {
00107 dif = goal->r.currentOrigin[2] - NPC->r.currentOrigin[2];
00108
00109 if ( fabs( dif ) > 24 )
00110 {
00111 ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 );
00112 }
00113 else
00114 {
00115 if ( NPC->client->ps.velocity[2] )
00116 {
00117 NPC->client->ps.velocity[2] *= VELOCITY_DECAY;
00118
00119 if ( fabs( NPC->client->ps.velocity[2] ) < 2 )
00120 {
00121 NPC->client->ps.velocity[2] = 0;
00122 }
00123 }
00124 }
00125 }
00126 }
00127
00128
00129 if ( NPC->client->ps.velocity[0] )
00130 {
00131 NPC->client->ps.velocity[0] *= VELOCITY_DECAY;
00132
00133 if ( fabs( NPC->client->ps.velocity[0] ) < 1 )
00134 {
00135 NPC->client->ps.velocity[0] = 0;
00136 }
00137 }
00138
00139 if ( NPC->client->ps.velocity[1] )
00140 {
00141 NPC->client->ps.velocity[1] *= VELOCITY_DECAY;
00142
00143 if ( fabs( NPC->client->ps.velocity[1] ) < 1 )
00144 {
00145 NPC->client->ps.velocity[1] = 0;
00146 }
00147 }
00148 }
00149
00150
00151 void Seeker_Strafe( void )
00152 {
00153 int side;
00154 vec3_t end, right, dir;
00155 trace_t tr;
00156
00157 if ( random() > 0.7f || !NPC->enemy || !NPC->enemy->client )
00158 {
00159
00160 AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL );
00161
00162
00163
00164 side = ( rand() & 1 ) ? -1 : 1;
00165 VectorMA( NPC->r.currentOrigin, SEEKER_STRAFE_DIS * side, right, end );
00166
00167 trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID );
00168
00169
00170 if ( tr.fraction > 0.9f )
00171 {
00172 float vel = SEEKER_STRAFE_VEL;
00173 float upPush = SEEKER_UPWARD_PUSH;
00174 if ( NPC->client->NPC_class != CLASS_BOBAFETT )
00175 {
00176 G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" ));
00177 }
00178 else
00179 {
00180 vel *= 3.0f;
00181 upPush *= 4.0f;
00182 }
00183 VectorMA( NPC->client->ps.velocity, vel*side, right, NPC->client->ps.velocity );
00184
00185 NPC->client->ps.velocity[2] += upPush;
00186
00187 NPCInfo->standTime = level.time + 1000 + random() * 500;
00188 }
00189 }
00190 else
00191 {
00192 float stDis;
00193
00194
00195 AngleVectors( NPC->enemy->client->renderInfo.eyeAngles, dir, right, NULL );
00196
00197
00198 side = ( rand() & 1 ) ? -1 : 1;
00199 stDis = SEEKER_STRAFE_DIS;
00200 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00201 {
00202 stDis *= 2.0f;
00203 }
00204 VectorMA( NPC->enemy->r.currentOrigin, stDis * side, right, end );
00205
00206
00207 VectorMA( end, crandom() * 25, dir, end );
00208
00209 trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID );
00210
00211
00212 if ( tr.fraction > 0.9f )
00213 {
00214 float dis, upPush;
00215
00216 VectorSubtract( tr.endpos, NPC->r.currentOrigin, dir );
00217 dir[2] *= 0.25;
00218 dis = VectorNormalize( dir );
00219
00220
00221 VectorMA( NPC->client->ps.velocity, dis, dir, NPC->client->ps.velocity );
00222
00223 upPush = SEEKER_UPWARD_PUSH;
00224 if ( NPC->client->NPC_class != CLASS_BOBAFETT )
00225 {
00226 G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" ));
00227 }
00228 else
00229 {
00230 upPush *= 4.0f;
00231 }
00232
00233
00234 NPC->client->ps.velocity[2] += upPush;
00235
00236 NPCInfo->standTime = level.time + 2500 + random() * 500;
00237 }
00238 }
00239 }
00240
00241
00242 void Seeker_Hunt( qboolean visible, qboolean advance )
00243 {
00244 float distance, speed;
00245 vec3_t forward;
00246
00247 NPC_FaceEnemy( qtrue );
00248
00249
00250 if ( NPCInfo->standTime < level.time )
00251 {
00252
00253 if ( visible )
00254 {
00255 Seeker_Strafe();
00256 return;
00257 }
00258 }
00259
00260
00261 if ( advance == qfalse )
00262 {
00263 return;
00264 }
00265
00266
00267 if ( visible == qfalse )
00268 {
00269
00270 NPCInfo->goalEntity = NPC->enemy;
00271 NPCInfo->goalRadius = 24;
00272
00273
00274 if ( NPC_GetMoveDirection( forward, &distance ) == qfalse )
00275 {
00276 return;
00277 }
00278 }
00279 else
00280 {
00281 VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, forward );
00282 distance = VectorNormalize( forward );
00283 }
00284
00285 speed = SEEKER_FORWARD_BASE_SPEED + SEEKER_FORWARD_MULTIPLIER * g_spskill.integer;
00286 VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity );
00287 }
00288
00289
00290 void Seeker_Fire( void )
00291 {
00292 vec3_t dir, enemy_org, muzzle;
00293 gentity_t *missile;
00294
00295 CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org );
00296 VectorSubtract( enemy_org, NPC->r.currentOrigin, dir );
00297 VectorNormalize( dir );
00298
00299
00300 VectorMA( NPC->r.currentOrigin, 15, dir, muzzle );
00301
00302 missile = CreateMissile( muzzle, dir, 1000, 10000, NPC, qfalse );
00303
00304 G_PlayEffectID( G_EffectIndex("blaster/muzzle_flash"), NPC->r.currentOrigin, dir );
00305
00306 missile->classname = "blaster";
00307 missile->s.weapon = WP_BLASTER;
00308
00309 missile->damage = 5;
00310 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
00311 missile->methodOfDeath = MOD_BLASTER;
00312 missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
00313 if ( NPC->r.ownerNum < ENTITYNUM_NONE )
00314 {
00315 missile->r.ownerNum = NPC->r.ownerNum;
00316 }
00317 }
00318
00319
00320 void Seeker_Ranged( qboolean visible, qboolean advance )
00321 {
00322 if ( NPC->client->NPC_class != CLASS_BOBAFETT )
00323 {
00324 if ( NPC->count > 0 )
00325 {
00326 if ( TIMER_Done( NPC, "attackDelay" ))
00327 {
00328 TIMER_Set( NPC, "attackDelay", Q_irand( 250, 2500 ));
00329 Seeker_Fire();
00330 NPC->count--;
00331 }
00332 }
00333 else
00334 {
00335
00336
00337
00338
00339 G_Damage( NPC, NPC, NPC, NULL, NULL, 999, 0, MOD_UNKNOWN );
00340 }
00341 }
00342
00343 if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
00344 {
00345 Seeker_Hunt( visible, advance );
00346 }
00347 }
00348
00349
00350 void Seeker_Attack( void )
00351 {
00352 float distance;
00353 qboolean visible;
00354 qboolean advance;
00355
00356
00357 Seeker_MaintainHeight();
00358
00359
00360 distance = DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin );
00361 visible = NPC_ClearLOS4( NPC->enemy );
00362 advance = (qboolean)(distance > MIN_DISTANCE_SQR);
00363
00364 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00365 {
00366 advance = (qboolean)(distance>(200.0f*200.0f));
00367 }
00368
00369
00370 if ( visible == qfalse )
00371 {
00372 if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
00373 {
00374 Seeker_Hunt( visible, advance );
00375 return;
00376 }
00377 }
00378
00379 Seeker_Ranged( visible, advance );
00380 }
00381
00382
00383 void Seeker_FindEnemy( void )
00384 {
00385 int numFound;
00386 float dis, bestDis = SEEKER_SEEK_RADIUS * SEEKER_SEEK_RADIUS + 1;
00387 vec3_t mins, maxs;
00388 int entityList[MAX_GENTITIES];
00389 gentity_t *ent, *best = NULL;
00390 int i;
00391
00392 VectorSet( maxs, SEEKER_SEEK_RADIUS, SEEKER_SEEK_RADIUS, SEEKER_SEEK_RADIUS );
00393 VectorScale( maxs, -1, mins );
00394
00395 numFound = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
00396
00397 for ( i = 0 ; i < numFound ; i++ )
00398 {
00399 ent = &g_entities[entityList[i]];
00400
00401 if ( ent->s.number == NPC->s.number
00402 || !ent->client
00403 || ent->health <= 0
00404 || !ent->inuse )
00405 {
00406 continue;
00407 }
00408
00409 if ( ent->client->playerTeam == NPC->client->playerTeam || ent->client->playerTeam == NPCTEAM_NEUTRAL )
00410 {
00411 continue;
00412 }
00413
00414
00415 if ( !NPC_ClearLOS4( ent ))
00416 {
00417 continue;
00418 }
00419
00420 dis = DistanceHorizontalSquared( NPC->r.currentOrigin, ent->r.currentOrigin );
00421
00422 if ( dis <= bestDis )
00423 {
00424 bestDis = dis;
00425 best = ent;
00426 }
00427 }
00428
00429 if ( best )
00430 {
00431
00432 NPC->random = random() * 6.3f;
00433
00434 NPC->enemy = best;
00435 }
00436 }
00437
00438
00439 void Seeker_FollowOwner( void )
00440 {
00441 float dis, minDistSqr;
00442 vec3_t pt, dir;
00443 gentity_t *owner = &g_entities[NPC->s.owner];
00444
00445 Seeker_MaintainHeight();
00446
00447 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00448 {
00449 owner = NPC->enemy;
00450 }
00451 if ( !owner || owner == NPC || !owner->client )
00452 {
00453 return;
00454 }
00455
00456 dis = DistanceHorizontalSquared( NPC->r.currentOrigin, owner->r.currentOrigin );
00457
00458 minDistSqr = MIN_DISTANCE_SQR;
00459
00460 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00461 {
00462 if ( TIMER_Done( NPC, "flameTime" ) )
00463 {
00464 minDistSqr = 200*200;
00465 }
00466 }
00467
00468 if ( dis < minDistSqr )
00469 {
00470
00471 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00472 {
00473 pt[0] = owner->r.currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 250;
00474 pt[1] = owner->r.currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 250;
00475 if ( NPC->client->jetPackTime < level.time )
00476 {
00477 pt[2] = NPC->r.currentOrigin[2] - 64;
00478 }
00479 else
00480 {
00481 pt[2] = owner->r.currentOrigin[2] + 200;
00482 }
00483 }
00484 else
00485 {
00486 pt[0] = owner->r.currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 56;
00487 pt[1] = owner->r.currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 56;
00488 pt[2] = owner->r.currentOrigin[2] + 40;
00489 }
00490
00491 VectorSubtract( pt, NPC->r.currentOrigin, dir );
00492 VectorMA( NPC->client->ps.velocity, 0.8f, dir, NPC->client->ps.velocity );
00493 }
00494 else
00495 {
00496 if ( NPC->client->NPC_class != CLASS_BOBAFETT )
00497 {
00498 if ( TIMER_Done( NPC, "seekerhiss" ))
00499 {
00500 TIMER_Set( NPC, "seekerhiss", 1000 + random() * 1000 );
00501 G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" ));
00502 }
00503 }
00504
00505
00506 NPCInfo->goalEntity = owner;
00507 NPCInfo->goalRadius = 32;
00508 NPC_MoveToGoal( qtrue );
00509 NPC->parent = owner;
00510 }
00511
00512 if ( NPCInfo->enemyCheckDebounceTime < level.time )
00513 {
00514
00515 Seeker_FindEnemy();
00516 NPCInfo->enemyCheckDebounceTime = level.time + 500;
00517 }
00518
00519 NPC_UpdateAngles( qtrue, qtrue );
00520 }
00521
00522
00523 void NPC_BSSeeker_Default( void )
00524 {
00525
00526
00527
00528
00529
00530
00531
00532
00533
00534
00535
00536 if ( NPC->r.ownerNum < ENTITYNUM_NONE )
00537 {
00538 gentity_t *owner = &g_entities[0];
00539 if ( owner->health <= 0
00540 || (owner->client && owner->client->pers.connected == CON_DISCONNECTED) )
00541 {
00542
00543 G_Damage( NPC, NULL, NULL, NULL, NULL, 10000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG );
00544 return;
00545 }
00546 }
00547
00548 if ( NPC->random == 0.0f )
00549 {
00550
00551 NPC->random = random() * 6.3f;
00552 }
00553
00554 if ( NPC->enemy && NPC->enemy->health && NPC->enemy->inuse )
00555 {
00556 if ( NPC->client->NPC_class != CLASS_BOBAFETT && ( NPC->enemy->s.number == 0 || ( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_SEEKER )) )
00557 {
00558
00559 NPC->enemy = NULL;
00560 }
00561 else
00562 {
00563 Seeker_Attack();
00564 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00565 {
00566 Boba_FireDecide();
00567 }
00568 return;
00569 }
00570 }
00571
00572
00573 Seeker_FollowOwner();
00574 }