00001 #include "b_local.h"
00002 #include "g_nav.h"
00003 #include "anims.h"
00004 #include "w_saber.h"
00005
00006 #include "../namespace_begin.h"
00007 extern qboolean BG_SabersOff( playerState_t *ps );
00008 #include "../namespace_end.h"
00009
00010 extern void CG_DrawAlert( vec3_t origin, float rating );
00011 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
00012 extern void ForceJump( gentity_t *self, usercmd_t *ucmd );
00013 extern vmCvar_t g_saberRealisticCombat;
00014 extern vmCvar_t d_slowmodeath;
00015
00016 void G_StartMatrixEffect( gentity_t *ent )
00017 {
00018
00019 }
00020
00021 #define MAX_VIEW_DIST 2048
00022 #define MAX_VIEW_SPEED 100
00023 #define JEDI_MAX_LIGHT_INTENSITY 64
00024 #define JEDI_MIN_LIGHT_THRESHOLD 10
00025 #define JEDI_MAX_LIGHT_THRESHOLD 50
00026
00027 #define DISTANCE_SCALE 0.25f
00028
00029 #define SPEED_SCALE 0.25f
00030 #define FOV_SCALE 0.5f
00031 #define LIGHT_SCALE 0.25f
00032
00033 #define REALIZE_THRESHOLD 0.6f
00034 #define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.3 )
00035
00036 #define MAX_CHECK_THRESHOLD 1
00037
00038 extern void NPC_ClearLookTarget( gentity_t *self );
00039 extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime );
00040 extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
00041 extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask );
00042 extern qboolean NPC_CheckEnemyStealth( void );
00043 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
00044
00045 #include "../namespace_begin.h"
00046 extern gitem_t *BG_FindItemForAmmo( ammo_t ammo );
00047 #include "../namespace_end.h"
00048
00049 extern void ForceThrow( gentity_t *self, qboolean pull );
00050 extern void ForceLightning( gentity_t *self );
00051 extern void ForceHeal( gentity_t *self );
00052 extern void ForceRage( gentity_t *self );
00053 extern void ForceProtect( gentity_t *self );
00054 extern void ForceAbsorb( gentity_t *self );
00055 extern int WP_MissileBlockForBlock( int saberBlock );
00056 extern qboolean G_GetHitLocFromSurfName( gentity_t *ent, const char *surfName, int *hitLoc, vec3_t point, vec3_t dir, vec3_t bladeDir, int mod );
00057 extern qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
00058 extern qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
00059 extern void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower );
00060 extern void WP_DeactivateSaber( gentity_t *self, qboolean clearLength );
00061 extern void WP_ActivateSaber( gentity_t *self );
00062
00063
00064 #include "../namespace_begin.h"
00065 extern qboolean PM_SaberInStart( int move );
00066 extern qboolean BG_SaberInSpecialAttack( int anim );
00067 extern qboolean BG_SaberInAttack( int move );
00068 extern qboolean PM_SaberInBounce( int move );
00069 extern qboolean PM_SaberInParry( int move );
00070 extern qboolean PM_SaberInKnockaway( int move );
00071 extern qboolean PM_SaberInBrokenParry( int move );
00072 extern qboolean PM_SaberInDeflect( int move );
00073 extern qboolean BG_SpinningSaberAnim( int anim );
00074 extern qboolean BG_FlippingAnim( int anim );
00075 extern qboolean PM_RollingAnim( int anim );
00076 extern qboolean PM_InKnockDown( playerState_t *ps );
00077 extern qboolean BG_InRoll( playerState_t *ps, int anim );
00078 extern qboolean BG_CrouchAnim( int anim );
00079 #include "../namespace_end.h"
00080
00081 extern qboolean NPC_SomeoneLookingAtMe(gentity_t *ent);
00082
00083 extern int WP_GetVelocityForForceJump( gentity_t *self, vec3_t jumpVel, usercmd_t *ucmd );
00084
00085 extern void G_TestLine(vec3_t start, vec3_t end, int color, int time);
00086
00087 static void Jedi_Aggression( gentity_t *self, int change );
00088 qboolean Jedi_WaitingAmbush( gentity_t *self );
00089
00090 #include "../namespace_begin.h"
00091 extern int bg_parryDebounce[];
00092 #include "../namespace_end.h"
00093
00094 static int jediSpeechDebounceTime[TEAM_NUM_TEAMS];
00095
00096 enum
00097 {
00098 LSTATE_NONE = 0,
00099 LSTATE_UNDERFIRE,
00100 LSTATE_INVESTIGATE,
00101 };
00102
00103 void NPC_ShadowTrooper_Precache( void )
00104 {
00105 RegisterItem( BG_FindItemForAmmo( AMMO_FORCE ) );
00106 G_SoundIndex( "sound/chars/shadowtrooper/cloak.wav" );
00107 G_SoundIndex( "sound/chars/shadowtrooper/decloak.wav" );
00108 }
00109
00110 void Jedi_ClearTimers( gentity_t *ent )
00111 {
00112 TIMER_Set( ent, "roamTime", 0 );
00113 TIMER_Set( ent, "chatter", 0 );
00114 TIMER_Set( ent, "strafeLeft", 0 );
00115 TIMER_Set( ent, "strafeRight", 0 );
00116 TIMER_Set( ent, "noStrafe", 0 );
00117 TIMER_Set( ent, "walking", 0 );
00118 TIMER_Set( ent, "taunting", 0 );
00119 TIMER_Set( ent, "parryTime", 0 );
00120 TIMER_Set( ent, "parryReCalcTime", 0 );
00121 TIMER_Set( ent, "forceJumpChasing", 0 );
00122 TIMER_Set( ent, "jumpChaseDebounce", 0 );
00123 TIMER_Set( ent, "moveforward", 0 );
00124 TIMER_Set( ent, "moveback", 0 );
00125 TIMER_Set( ent, "movenone", 0 );
00126 TIMER_Set( ent, "moveright", 0 );
00127 TIMER_Set( ent, "moveleft", 0 );
00128 TIMER_Set( ent, "movecenter", 0 );
00129 TIMER_Set( ent, "saberLevelDebounce", 0 );
00130 TIMER_Set( ent, "noRetreat", 0 );
00131 TIMER_Set( ent, "holdLightning", 0 );
00132 TIMER_Set( ent, "gripping", 0 );
00133 TIMER_Set( ent, "draining", 0 );
00134 TIMER_Set( ent, "noturn", 0 );
00135 }
00136
00137 void Jedi_PlayBlockedPushSound( gentity_t *self )
00138 {
00139 if ( !self->s.number )
00140 {
00141 G_AddVoiceEvent( self, EV_PUSHFAIL, 3000 );
00142 }
00143 else if ( self->health > 0 && self->NPC && self->NPC->blockedSpeechDebounceTime < level.time )
00144 {
00145 G_AddVoiceEvent( self, EV_PUSHFAIL, 3000 );
00146 self->NPC->blockedSpeechDebounceTime = level.time + 3000;
00147 }
00148 }
00149
00150 void Jedi_PlayDeflectSound( gentity_t *self )
00151 {
00152 if ( !self->s.number )
00153 {
00154 G_AddVoiceEvent( self, Q_irand( EV_DEFLECT1, EV_DEFLECT3 ), 3000 );
00155 }
00156 else if ( self->health > 0 && self->NPC && self->NPC->blockedSpeechDebounceTime < level.time )
00157 {
00158 G_AddVoiceEvent( self, Q_irand( EV_DEFLECT1, EV_DEFLECT3 ), 3000 );
00159 self->NPC->blockedSpeechDebounceTime = level.time + 3000;
00160 }
00161 }
00162
00163 void NPC_Jedi_PlayConfusionSound( gentity_t *self )
00164 {
00165 if ( self->health > 0 )
00166 {
00167 if ( self->client && ( self->client->NPC_class == CLASS_TAVION || self->client->NPC_class == CLASS_DESANN ) )
00168 {
00169 G_AddVoiceEvent( self, Q_irand( EV_CONFUSE1, EV_CONFUSE3 ), 2000 );
00170 }
00171 else if ( Q_irand( 0, 1 ) )
00172 {
00173 G_AddVoiceEvent( self, Q_irand( EV_TAUNT1, EV_TAUNT3 ), 2000 );
00174 }
00175 else
00176 {
00177 G_AddVoiceEvent( self, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 2000 );
00178 }
00179 }
00180 }
00181
00182 void Boba_Precache( void )
00183 {
00184 G_SoundIndex( "sound/boba/jeton.wav" );
00185 G_SoundIndex( "sound/boba/jethover.wav" );
00186 G_SoundIndex( "sound/effects/combustfire.mp3" );
00187 G_EffectIndex( "boba/jet" );
00188 G_EffectIndex( "boba/fthrw" );
00189 }
00190
00191 extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel, int boltNum, int weaponNum );
00192 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
00193 void Boba_ChangeWeapon( int wp )
00194 {
00195 if ( NPC->s.weapon == wp )
00196 {
00197 return;
00198 }
00199 NPC_ChangeWeapon( wp );
00200 G_AddEvent( NPC, EV_GENERAL_SOUND, G_SoundIndex( "sound/weapons/change.wav" ));
00201 }
00202
00203 void WP_ResistForcePush( gentity_t *self, gentity_t *pusher, qboolean noPenalty )
00204 {
00205 int parts;
00206 qboolean runningResist = qfalse;
00207
00208 if ( !self || self->health <= 0 || !self->client || !pusher || !pusher->client )
00209 {
00210 return;
00211 }
00212 if ( (!self->s.number || self->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",self->NPC_type) || self->client->NPC_class == CLASS_LUKE)
00213 && (VectorLengthSquared( self->client->ps.velocity ) > 10000 || self->client->ps.fd.forcePowerLevel[FP_PUSH] >= FORCE_LEVEL_3 || self->client->ps.fd.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 ) )
00214 {
00215 runningResist = qtrue;
00216 }
00217 if ( !runningResist
00218 && self->client->ps.groundEntityNum != ENTITYNUM_NONE
00219 && !BG_SpinningSaberAnim( self->client->ps.legsAnim )
00220 && !BG_FlippingAnim( self->client->ps.legsAnim )
00221 && !PM_RollingAnim( self->client->ps.legsAnim )
00222 && !PM_InKnockDown( &self->client->ps )
00223 && !BG_CrouchAnim( self->client->ps.legsAnim ))
00224 {
00225 parts = SETANIM_BOTH;
00226 }
00227 else
00228 {
00229 parts = SETANIM_TORSO;
00230 }
00231 NPC_SetAnim( self, parts, BOTH_RESISTPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00232 if ( !noPenalty )
00233 {
00234 char buf[128];
00235 float tFVal = 0;
00236
00237 trap_Cvar_VariableStringBuffer("timescale", buf, sizeof(buf));
00238
00239 tFVal = atof(buf);
00240
00241 if ( !runningResist )
00242 {
00243 VectorClear( self->client->ps.velocity );
00244
00245
00246 self->client->ps.weaponTime = 1000;
00247 if ( self->client->ps.fd.forcePowersActive&(1<<FP_SPEED) )
00248 {
00249 self->client->ps.weaponTime = floor( self->client->ps.weaponTime * tFVal );
00250 }
00251 self->client->ps.pm_time = self->client->ps.weaponTime;
00252 self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
00253
00254
00255
00256 }
00257 else
00258 {
00259 self->client->ps.weaponTime = 600;
00260 if ( self->client->ps.fd.forcePowersActive&(1<<FP_SPEED) )
00261 {
00262 self->client->ps.weaponTime = floor( self->client->ps.weaponTime * tFVal );
00263 }
00264 }
00265 }
00266
00267 self->client->ps.powerups[PW_DISINT_4] = level.time + self->client->ps.torsoTimer + 500;
00268 self->client->ps.powerups[PW_PULL] = 0;
00269 Jedi_PlayBlockedPushSound( self );
00270 }
00271
00272 qboolean Boba_StopKnockdown( gentity_t *self, gentity_t *pusher, vec3_t pushDir, qboolean forceKnockdown )
00273 {
00274 vec3_t pDir, fwd, right, ang;
00275 float fDot, rDot;
00276 int strafeTime;
00277
00278 if ( self->client->NPC_class != CLASS_BOBAFETT )
00279 {
00280 return qfalse;
00281 }
00282
00283 if ( (self->client->ps.eFlags2&EF2_FLYING) )
00284 {
00285 return qtrue;
00286 }
00287
00288 VectorSet(ang, 0, self->r.currentAngles[YAW], 0);
00289 strafeTime = Q_irand( 1000, 2000 );
00290
00291 AngleVectors( ang, fwd, right, NULL );
00292 VectorNormalize2( pushDir, pDir );
00293 fDot = DotProduct( pDir, fwd );
00294 rDot = DotProduct( pDir, right );
00295
00296 if ( Q_irand( 0, 2 ) )
00297 {
00298 usercmd_t tempCmd;
00299 if ( fDot >= 0.4f )
00300 {
00301 tempCmd.forwardmove = 127;
00302 TIMER_Set( self, "moveforward", strafeTime );
00303 }
00304 else if ( fDot <= -0.4f )
00305 {
00306 tempCmd.forwardmove = -127;
00307 TIMER_Set( self, "moveback", strafeTime );
00308 }
00309 else if ( rDot > 0 )
00310 {
00311 tempCmd.rightmove = 127;
00312 TIMER_Set( self, "strafeRight", strafeTime );
00313 TIMER_Set( self, "strafeLeft", -1 );
00314 }
00315 else
00316 {
00317 tempCmd.rightmove = -127;
00318 TIMER_Set( self, "strafeLeft", strafeTime );
00319 TIMER_Set( self, "strafeRight", -1 );
00320 }
00321 G_AddEvent( self, EV_JUMP, 0 );
00322 if ( !Q_irand( 0, 1 ) )
00323 {
00324 self->client->ps.fd.forceJumpCharge = 280;
00325 ForceJump( self, &tempCmd );
00326 }
00327 else
00328 {
00329 TIMER_Set( self, "duck", strafeTime );
00330 }
00331 self->painDebounceTime = 0;
00332 }
00333 else if ( !Q_irand( 0, 1 ) && forceKnockdown )
00334 {
00335 WP_ResistForcePush( self, pusher, qtrue );
00336 }
00337 else
00338 {
00339 return qfalse;
00340 }
00341
00342 return qtrue;
00343 }
00344
00345 void Boba_FlyStart( gentity_t *self )
00346 {
00347 if ( TIMER_Done( self, "jetRecharge" ) )
00348 {
00349 self->client->ps.gravity = 0;
00350 if ( self->NPC )
00351 {
00352 self->NPC->aiFlags |= NPCAI_CUSTOM_GRAVITY;
00353 }
00354 self->client->ps.eFlags2 |= EF2_FLYING;
00355 self->client->jetPackTime = level.time + Q_irand( 3000, 10000 );
00356
00357 G_SoundOnEnt( self, CHAN_ITEM, "sound/boba/jeton.wav" );
00358
00359 self->s.loopSound = G_SoundIndex( "sound/boba/jethover.wav" );
00360 if ( self->NPC )
00361 {
00362 self->count = Q3_INFINITE;
00363 }
00364 }
00365 }
00366
00367 void Boba_FlyStop( gentity_t *self )
00368 {
00369 self->client->ps.gravity = g_gravity.value;
00370 if ( self->NPC )
00371 {
00372 self->NPC->aiFlags &= ~NPCAI_CUSTOM_GRAVITY;
00373 }
00374 self->client->ps.eFlags2 &= ~EF2_FLYING;
00375 self->client->jetPackTime = 0;
00376
00377 self->s.loopSound = 0;
00378 if ( self->NPC )
00379 {
00380 self->count = 0;
00381 TIMER_Set( self, "jetRecharge", Q_irand( 1000, 5000 ) );
00382 TIMER_Set( self, "jumpChaseDebounce", Q_irand( 500, 2000 ) );
00383 }
00384 }
00385
00386 qboolean Boba_Flying( gentity_t *self )
00387 {
00388 return ((qboolean)(self->client->ps.eFlags2&EF2_FLYING));
00389 }
00390
00391 void Boba_FireFlameThrower( gentity_t *self )
00392 {
00393 int damage = Q_irand( 20, 30 );
00394 trace_t tr;
00395 gentity_t *traceEnt = NULL;
00396 mdxaBone_t boltMatrix;
00397 vec3_t start, end, dir, traceMins = {-4, -4, -4}, traceMaxs = {4, 4, 4};
00398
00399 trap_G2API_GetBoltMatrix( self->ghoul2, 0, self->client->renderInfo.handLBolt,
00400 &boltMatrix, self->r.currentAngles, self->r.currentOrigin, level.time,
00401 NULL, self->modelScale );
00402
00403 BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, start );
00404 BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, dir );
00405
00406 VectorMA( start, 128, dir, end );
00407
00408 trap_Trace( &tr, start, traceMins, traceMaxs, end, self->s.number, MASK_SHOT );
00409
00410 traceEnt = &g_entities[tr.entityNum];
00411 if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage )
00412 {
00413 G_Damage( traceEnt, self, self, dir, tr.endpos, damage, DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_IGNORE_TEAM, MOD_LAVA );
00414
00415 }
00416 }
00417
00418
00419 void Boba_StartFlameThrower( gentity_t *self )
00420 {
00421 int flameTime = 4000;
00422 mdxaBone_t boltMatrix;
00423 vec3_t org, dir;
00424
00425 self->client->ps.torsoTimer = flameTime;
00426 if ( self->NPC )
00427 {
00428 TIMER_Set( self, "nextAttackDelay", flameTime );
00429 TIMER_Set( self, "walking", 0 );
00430 }
00431 TIMER_Set( self, "flameTime", flameTime );
00432
00433
00434
00435
00436
00437
00438
00439
00440
00441
00442
00443
00444
00445
00446
00447
00448
00449
00450
00451
00452
00453
00454
00455
00456
00457
00458
00459
00460 G_SoundOnEnt( self, CHAN_WEAPON, "sound/effects/combustfire.mp3" );
00461
00462 trap_G2API_GetBoltMatrix(NPC->ghoul2, 0, NPC->client->renderInfo.handRBolt, &boltMatrix, NPC->r.currentAngles,
00463 NPC->r.currentOrigin, level.time, NULL, NPC->modelScale);
00464
00465 BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, org );
00466 BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, dir );
00467
00468 G_PlayEffectID( G_EffectIndex("boba/fthrw"), org, dir);
00469 }
00470
00471 void Boba_DoFlameThrower( gentity_t *self )
00472 {
00473 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00474 if ( TIMER_Done( self, "nextAttackDelay" ) && TIMER_Done( self, "flameTime" ) )
00475 {
00476 Boba_StartFlameThrower( self );
00477 }
00478 Boba_FireFlameThrower( self );
00479 }
00480
00481 void Boba_FireDecide( void )
00482 {
00483 qboolean enemyLOS = qfalse;
00484 qboolean enemyCS = qfalse;
00485 qboolean enemyInFOV = qfalse;
00486
00487 qboolean faceEnemy = qfalse;
00488 qboolean shoot = qfalse;
00489 qboolean hitAlly = qfalse;
00490 vec3_t impactPos;
00491 float enemyDist;
00492 float dot;
00493 vec3_t enemyDir, shootDir;
00494
00495 if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE
00496 && NPC->client->ps.fd.forceJumpZStart
00497 && !BG_FlippingAnim( NPC->client->ps.legsAnim )
00498 && !Q_irand( 0, 10 ) )
00499 {
00500 Boba_FlyStart( NPC );
00501 }
00502
00503 if ( !NPC->enemy )
00504 {
00505 return;
00506 }
00507
00508
00509
00510
00511
00512
00513
00514 if ( NPC->enemy->s.weapon == WP_SABER )
00515 {
00516 NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
00517 Boba_ChangeWeapon( WP_ROCKET_LAUNCHER );
00518 }
00519 else
00520 {
00521 if ( NPC->health < NPC->client->pers.maxHealth*0.5f )
00522 {
00523 NPCInfo->scriptFlags |= SCF_ALT_FIRE;
00524 Boba_ChangeWeapon( WP_BLASTER );
00525 NPCInfo->burstMin = 3;
00526 NPCInfo->burstMean = 12;
00527 NPCInfo->burstMax = 20;
00528 NPCInfo->burstSpacing = Q_irand( 300, 750 );
00529 }
00530 else
00531 {
00532 NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
00533 Boba_ChangeWeapon( WP_BLASTER );
00534 }
00535 }
00536
00537 VectorClear( impactPos );
00538 enemyDist = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin );
00539
00540 VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, enemyDir );
00541 VectorNormalize( enemyDir );
00542 AngleVectors( NPC->client->ps.viewangles, shootDir, NULL, NULL );
00543 dot = DotProduct( enemyDir, shootDir );
00544 if ( dot > 0.5f ||( enemyDist * (1.0f-dot)) < 10000 )
00545 {
00546 enemyInFOV = qtrue;
00547 }
00548
00549 if ( (enemyDist < (128*128)&&enemyInFOV) || !TIMER_Done( NPC, "flameTime" ) )
00550 {
00551 Boba_DoFlameThrower( NPC );
00552 enemyCS = qfalse;
00553 shoot = qfalse;
00554 NPCInfo->enemyLastSeenTime = level.time;
00555 faceEnemy = qtrue;
00556 ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK);
00557 }
00558 else if ( enemyDist < MIN_ROCKET_DIST_SQUARED )
00559 {
00560 if ( (NPC->client->ps.weapon == WP_FLECHETTE || NPC->client->ps.weapon == WP_REPEATER) &&
00561 (NPCInfo->scriptFlags & SCF_ALT_FIRE) )
00562 {
00563 NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
00564
00565 }
00566 }
00567 else if ( enemyDist > 65536 )
00568 {
00569 if ( NPC->client->ps.weapon == WP_DISRUPTOR )
00570 {
00571 if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) )
00572 {
00573 NPCInfo->scriptFlags |= SCF_ALT_FIRE;
00574
00575 NPC_ChangeWeapon( WP_DISRUPTOR );
00576 NPC_UpdateAngles( qtrue, qtrue );
00577 return;
00578 }
00579 }
00580 }
00581
00582
00583 if ( TIMER_Done( NPC, "nextAttackDelay" ) && TIMER_Done( NPC, "flameTime" ) )
00584 {
00585 if ( NPC_ClearLOS4( NPC->enemy ) )
00586 {
00587 NPCInfo->enemyLastSeenTime = level.time;
00588 enemyLOS = qtrue;
00589
00590 if ( NPC->client->ps.weapon == WP_NONE )
00591 {
00592 enemyCS = qfalse;
00593 }
00594 else
00595 {
00596 if ( (NPC->client->ps.weapon == WP_ROCKET_LAUNCHER || (NPC->client->ps.weapon == WP_FLECHETTE && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_ROCKET_DIST_SQUARED )
00597 {
00598 enemyCS = qfalse;
00599 hitAlly = qtrue;
00600
00601 }
00602 else if ( enemyInFOV )
00603 {
00604 int hit = NPC_ShotEntity( NPC->enemy, impactPos );
00605 gentity_t *hitEnt = &g_entities[hit];
00606
00607 if ( hit == NPC->enemy->s.number
00608 || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam )
00609 || ( hitEnt && hitEnt->takedamage && ((hitEnt->r.svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) ) )
00610 {
00611 enemyCS = qtrue;
00612
00613 VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation );
00614 }
00615 else
00616 {
00617
00618 if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam )
00619 {
00620 hitAlly = qtrue;
00621 }
00622 else
00623 {
00624 }
00625 }
00626 }
00627 else
00628 {
00629 enemyCS = qfalse;
00630 }
00631 }
00632 }
00633 else if ( trap_InPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ) )
00634 {
00635 NPCInfo->enemyLastSeenTime = level.time;
00636 faceEnemy = qtrue;
00637
00638 }
00639
00640 if ( NPC->client->ps.weapon == WP_NONE )
00641 {
00642 faceEnemy = qfalse;
00643 shoot = qfalse;
00644 }
00645 else
00646 {
00647 if ( enemyLOS )
00648 {
00649 faceEnemy = qtrue;
00650 }
00651 if ( enemyCS )
00652 {
00653 shoot = qtrue;
00654 }
00655 }
00656
00657 if ( !enemyCS )
00658 {
00659
00661 if ( !hitAlly
00662 && enemyInFOV
00663 && NPCInfo->enemyLastSeenTime > 0 )
00664 {
00665 if ( level.time - NPCInfo->enemyLastSeenTime < 10000 )
00666 {
00667 if ( !Q_irand( 0, 10 ) )
00668 {
00669
00670 vec3_t muzzle, dir, angles;
00671 qboolean tooClose = qfalse;
00672 qboolean tooFar = qfalse;
00673 float distThreshold;
00674 float dist;
00675
00676 CalcEntitySpot( NPC, SPOT_HEAD, muzzle );
00677 if ( VectorCompare( impactPos, vec3_origin ) )
00678 {
00679 trace_t tr;
00680
00681 vec3_t forward, end;
00682 AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL );
00683 VectorMA( muzzle, 8192, forward, end );
00684 trap_Trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT );
00685 VectorCopy( tr.endpos, impactPos );
00686 }
00687
00688
00689 distThreshold = 16384;
00690 switch ( NPC->s.weapon )
00691 {
00692 case WP_ROCKET_LAUNCHER:
00693 case WP_FLECHETTE:
00694 case WP_THERMAL:
00695 case WP_TRIP_MINE:
00696 case WP_DET_PACK:
00697 distThreshold = 65536;
00698 break;
00699 case WP_REPEATER:
00700 if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
00701 {
00702 distThreshold = 65536;
00703 }
00704 break;
00705 default:
00706 break;
00707 }
00708
00709 dist = DistanceSquared( impactPos, muzzle );
00710
00711 if ( dist < distThreshold )
00712 {
00713 tooClose = qtrue;
00714 }
00715 else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 ||
00716 (NPCInfo->group && level.time - NPCInfo->group->lastSeenEnemyTime > 5000 ))
00717 {
00718
00719 distThreshold = 65536;
00720 switch ( NPC->s.weapon )
00721 {
00722 case WP_ROCKET_LAUNCHER:
00723 case WP_FLECHETTE:
00724 case WP_THERMAL:
00725 case WP_TRIP_MINE:
00726 case WP_DET_PACK:
00727 distThreshold = 262144;
00728 break;
00729 case WP_REPEATER:
00730 if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
00731 {
00732 distThreshold = 262144;
00733 }
00734 break;
00735 default:
00736 break;
00737 }
00738 dist = DistanceSquared( impactPos, NPCInfo->enemyLastSeenLocation );
00739 if ( dist > distThreshold )
00740 {
00741 tooFar = qtrue;
00742 }
00743 }
00744
00745 if ( !tooClose && !tooFar )
00746 {
00747 VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir );
00748 VectorNormalize( dir );
00749 vectoangles( dir, angles );
00750
00751 NPCInfo->desiredYaw = angles[YAW];
00752 NPCInfo->desiredPitch = angles[PITCH];
00753
00754 shoot = qtrue;
00755 faceEnemy = qfalse;
00756 }
00757 }
00758 }
00759 }
00760 }
00761
00762
00763 if ( NPC->client->ps.weaponTime > 0 )
00764 {
00765 if ( NPC->s.weapon == WP_ROCKET_LAUNCHER )
00766 {
00767 if ( !enemyLOS || !enemyCS )
00768 {
00769 NPC->client->ps.weaponTime = 0;
00770 }
00771 else
00772 {
00773 TIMER_Set( NPC, "nextAttackDelay", Q_irand( 500, 1000 ) );
00774 }
00775 }
00776 }
00777 else if ( shoot )
00778 {
00779 if ( TIMER_Done( NPC, "nextAttackDelay" ) )
00780 {
00781 if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) )
00782 {
00783 WeaponThink( qtrue );
00784 }
00785
00786 if ( NPC->s.weapon == WP_ROCKET_LAUNCHER
00787 && (ucmd.buttons&BUTTON_ATTACK)
00788 && !Q_irand( 0, 3 ) )
00789 {
00790 ucmd.buttons &= ~BUTTON_ATTACK;
00791 ucmd.buttons |= BUTTON_ALT_ATTACK;
00792 NPC->client->ps.weaponTime = Q_irand( 500, 1500 );
00793 }
00794 }
00795 }
00796 }
00797 }
00798
00799 void Jedi_Cloak( gentity_t *self )
00800 {
00801 if ( self )
00802 {
00803 self->flags |= FL_NOTARGET;
00804 if ( self->client )
00805 {
00806 if ( !self->client->ps.powerups[PW_CLOAKED] )
00807 {
00808 self->client->ps.powerups[PW_CLOAKED] = Q3_INFINITE;
00809
00810
00811
00812 G_Sound( self, CHAN_ITEM, G_SoundIndex("sound/chars/shadowtrooper/cloak.wav") );
00813 }
00814 }
00815 }
00816 }
00817
00818 void Jedi_Decloak( gentity_t *self )
00819 {
00820 if ( self )
00821 {
00822 self->flags &= ~FL_NOTARGET;
00823 if ( self->client )
00824 {
00825 if ( self->client->ps.powerups[PW_CLOAKED] )
00826 {
00827 self->client->ps.powerups[PW_CLOAKED] = 0;
00828
00829 G_Sound( self, CHAN_ITEM, G_SoundIndex("sound/chars/shadowtrooper/decloak.wav") );
00830 }
00831 }
00832 }
00833 }
00834
00835 void Jedi_CheckCloak( void )
00836 {
00837 if ( NPC && NPC->client && NPC->client->NPC_class == CLASS_SHADOWTROOPER )
00838 {
00839 if ( !NPC->client->ps.saberHolstered ||
00840 NPC->health <= 0 ||
00841 NPC->client->ps.saberInFlight ||
00842
00843
00844 NPC->painDebounceTime > level.time )
00845 {
00846 Jedi_Decloak( NPC );
00847 }
00848 else if ( NPC->health > 0
00849 && !NPC->client->ps.saberInFlight
00850
00851
00852 && NPC->painDebounceTime < level.time )
00853 {
00854 Jedi_Cloak( NPC );
00855 }
00856 }
00857 }
00858
00859
00860
00861
00862
00863 static void Jedi_Aggression( gentity_t *self, int change )
00864 {
00865 int upper_threshold, lower_threshold;
00866
00867 self->NPC->stats.aggression += change;
00868
00869
00870 if ( self->client->playerTeam == NPCTEAM_PLAYER )
00871 {
00872 upper_threshold = 7;
00873 lower_threshold = 1;
00874 }
00875 else
00876 {
00877 if ( self->client->NPC_class == CLASS_DESANN )
00878 {
00879 upper_threshold = 20;
00880 lower_threshold = 5;
00881 }
00882 else
00883 {
00884 upper_threshold = 10;
00885 lower_threshold = 3;
00886 }
00887 }
00888
00889 if ( self->NPC->stats.aggression > upper_threshold )
00890 {
00891 self->NPC->stats.aggression = upper_threshold;
00892 }
00893 else if ( self->NPC->stats.aggression < lower_threshold )
00894 {
00895 self->NPC->stats.aggression = lower_threshold;
00896 }
00897
00898 }
00899
00900 static void Jedi_AggressionErosion( int amt )
00901 {
00902 if ( TIMER_Done( NPC, "roamTime" ) )
00903 {
00904 TIMER_Set( NPC, "roamTime", Q_irand( 2000, 5000 ) );
00905 Jedi_Aggression( NPC, amt );
00906 }
00907
00908 if ( NPCInfo->stats.aggression < 4 || (NPCInfo->stats.aggression < 6&&NPC->client->NPC_class == CLASS_DESANN))
00909 {
00910 WP_DeactivateSaber( NPC, qfalse );
00911 }
00912 }
00913
00914 void NPC_Jedi_RateNewEnemy( gentity_t *self, gentity_t *enemy )
00915 {
00916 float healthAggression;
00917 float weaponAggression;
00918 int newAggression;
00919
00920 switch( enemy->s.weapon )
00921 {
00922 case WP_SABER:
00923 healthAggression = (float)self->health/200.0f*6.0f;
00924 weaponAggression = 7;
00925 break;
00926 case WP_BLASTER:
00927 if ( DistanceSquared( self->r.currentOrigin, enemy->r.currentOrigin ) < 65536 )
00928 {
00929 healthAggression = (float)self->health/200.0f*8.0f;
00930 weaponAggression = 8;
00931 }
00932 else
00933 {
00934 healthAggression = 8.0f - ((float)self->health/200.0f*8.0f);
00935 weaponAggression = 2;
00936 }
00937 break;
00938 default:
00939 healthAggression = (float)self->health/200.0f*8.0f;
00940 weaponAggression = 6;
00941 break;
00942 }
00943
00944 newAggression = ceil( (healthAggression + weaponAggression + (float)self->NPC->stats.aggression )/3.0f);
00945
00946 Jedi_Aggression( self, newAggression - self->NPC->stats.aggression );
00947
00948
00949 TIMER_Set( self, "chatter", Q_irand( 4000, 7000 ) );
00950 }
00951
00952 static void Jedi_Rage( void )
00953 {
00954 Jedi_Aggression( NPC, 10 - NPCInfo->stats.aggression + Q_irand( -2, 2 ) );
00955 TIMER_Set( NPC, "roamTime", 0 );
00956 TIMER_Set( NPC, "chatter", 0 );
00957 TIMER_Set( NPC, "walking", 0 );
00958 TIMER_Set( NPC, "taunting", 0 );
00959 TIMER_Set( NPC, "jumpChaseDebounce", 0 );
00960 TIMER_Set( NPC, "movenone", 0 );
00961 TIMER_Set( NPC, "movecenter", 0 );
00962 TIMER_Set( NPC, "noturn", 0 );
00963 ForceRage( NPC );
00964 }
00965
00966 void Jedi_RageStop( gentity_t *self )
00967 {
00968 if ( self->NPC )
00969 {
00970 TIMER_Set( self, "roamTime", 0 );
00971 Jedi_Aggression( self, Q_irand( -5, 0 ) );
00972 }
00973 }
00974
00975
00976
00977
00978
00979
00980 static qboolean Jedi_BattleTaunt( void )
00981 {
00982 if ( TIMER_Done( NPC, "chatter" )
00983 && !Q_irand( 0, 3 )
00984 && NPCInfo->blockedSpeechDebounceTime < level.time
00985 && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time )
00986 {
00987 int event = -1;
00988 if ( NPC->client->playerTeam == NPCTEAM_PLAYER
00989 && NPC->enemy && NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_JEDI )
00990 {
00991 if ( NPC->client->NPC_class ==