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 == CLASS_JEDI && NPCInfo->rank == RANK_COMMANDER )
00992 {
00993 event = EV_TAUNT1;
00994 }
00995 }
00996 else
00997 {
00998 event = Q_irand( EV_TAUNT1, EV_TAUNT3 );
00999 }
01000 if ( event != -1 )
01001 {
01002 G_AddVoiceEvent( NPC, event, 3000 );
01003 jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 6000;
01004 TIMER_Set( NPC, "chatter", Q_irand( 5000, 10000 ) );
01005
01006 if ( NPC->enemy && NPC->enemy->NPC && NPC->enemy->s.weapon == WP_SABER && NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_JEDI )
01007 {
01008 }
01009 return qtrue;
01010 }
01011 }
01012 return qfalse;
01013 }
01014
01015
01016
01017
01018
01019
01020 static qboolean Jedi_ClearPathToSpot( vec3_t dest, int impactEntNum )
01021 {
01022 trace_t trace;
01023 vec3_t mins, start, end, dir;
01024 float dist, drop;
01025 float i;
01026
01027
01028 VectorSet( mins, NPC->r.mins[0], NPC->r.mins[1], NPC->r.mins[2] + STEPSIZE );
01029
01030 trap_Trace( &trace, NPC->r.currentOrigin, mins, NPC->r.maxs, dest, NPC->s.number, NPC->clipmask );
01031
01032
01033 if ( trace.allsolid || trace.startsolid )
01034 {
01035 return qfalse;
01036 }
01037
01038 if ( trace.fraction < 1.0f )
01039 {
01040 if ( impactEntNum != ENTITYNUM_NONE && trace.entityNum == impactEntNum )
01041 {
01042 return qtrue;
01043 }
01044 else
01045 {
01046 return qfalse;
01047 }
01048 }
01049
01050
01051
01052 VectorSubtract( dest, NPC->r.currentOrigin, dir );
01053 dist = VectorNormalize( dir );
01054 if ( dest[2] > NPC->r.currentOrigin[2] )
01055 {
01056 drop = STEPSIZE;
01057 }
01058 else
01059 {
01060 drop = 64;
01061 }
01062 for ( i = NPC->r.maxs[0]*2; i < dist; i += NPC->r.maxs[0]*2 )
01063 {
01064 VectorMA( NPC->r.currentOrigin, i, dir, start );
01065 VectorCopy( start, end );
01066 end[2] -= drop;
01067 trap_Trace( &trace, start, mins, NPC->r.maxs, end, NPC->s.number, NPC->clipmask );
01068 if ( trace.fraction < 1.0f || trace.allsolid || trace.startsolid )
01069 {
01070 continue;
01071 }
01072
01073 return qfalse;
01074 }
01075
01076 return qtrue;
01077 }
01078
01079 qboolean NPC_MoveDirClear( int forwardmove, int rightmove, qboolean reset )
01080 {
01081 vec3_t forward, right, testPos, angles, mins;
01082 trace_t trace;
01083 float fwdDist, rtDist;
01084 float bottom_max = -STEPSIZE*4 - 1;
01085
01086 if ( !forwardmove && !rightmove )
01087 {
01088
01089 return qtrue;
01090 }
01091
01092 if ( ucmd.upmove > 0 || NPC->client->ps.fd.forceJumpCharge )
01093 {
01094
01095 return qtrue;
01096 }
01097
01098 if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE )
01099 {
01100
01101 return qtrue;
01102 }
01103
01104
01105
01106
01107
01108
01109
01110
01111
01112
01113
01114
01115
01116
01117
01118
01119 VectorCopy( NPC->r.mins, mins );
01120 mins[2] += STEPSIZE;
01121 angles[PITCH] = angles[ROLL] = 0;
01122 angles[YAW] = NPC->client->ps.viewangles[YAW];
01123 AngleVectors( angles, forward, right, NULL );
01124 fwdDist = ((float)forwardmove)/2.0f;
01125 rtDist = ((float)rightmove)/2.0f;
01126 VectorMA( NPC->r.currentOrigin, fwdDist, forward, testPos );
01127 VectorMA( testPos, rtDist, right, testPos );
01128 trap_Trace( &trace, NPC->r.currentOrigin, mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP );
01129 if ( trace.allsolid || trace.startsolid )
01130 {
01131
01132 if ( reset )
01133 {
01134 trace.fraction = 1.0f;
01135 }
01136 VectorCopy( testPos, trace.endpos );
01137
01138 }
01139 if ( trace.fraction < 0.6 )
01140 {
01141 if ( (NPC->enemy && trace.entityNum == NPC->enemy->s.number) || (NPCInfo->goalEntity && trace.entityNum == NPCInfo->goalEntity->s.number) )
01142 {
01143
01144 return qtrue;
01145 }
01146 else if ( reset )
01147 {
01148
01149 ucmd.forwardmove = 0;
01150 ucmd.rightmove = 0;
01151 VectorClear( NPC->client->ps.moveDir );
01152 }
01153 return qfalse;
01154 }
01155
01156 if ( NPCInfo->goalEntity )
01157 {
01158 if ( NPCInfo->goalEntity->r.currentOrigin[2] < NPC->r.currentOrigin[2] )
01159 {
01160 bottom_max += NPCInfo->goalEntity->r.currentOrigin[2] - NPC->r.currentOrigin[2];
01161 }
01162 }
01163 VectorCopy( trace.endpos, testPos );
01164 testPos[2] += bottom_max;
01165
01166 trap_Trace( &trace, trace.endpos, mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask );
01167
01168
01169
01170
01171 if ( trace.allsolid || trace.startsolid )
01172 {
01173
01174 return qtrue;
01175 }
01176
01177 if ( trace.fraction < 1.0 )
01178 {
01179
01180
01181 return qtrue;
01182 }
01183
01184
01185 if ( reset )
01186 {
01187
01188 ucmd.forwardmove *= -1.0;
01189 ucmd.rightmove *= -1.0;
01190 VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir );
01191 }
01192 return qfalse;
01193 }
01194
01195
01196
01197
01198
01199
01200 static void Jedi_HoldPosition( void )
01201 {
01202
01203 NPCInfo->goalEntity = NULL;
01204
01205
01206
01207
01208
01209
01210
01211 }
01212
01213
01214
01215
01216
01217
01218
01219 static void Jedi_Move( gentity_t *goal, qboolean retreat )
01220 {
01221 qboolean moved;
01222 navInfo_t info;
01223
01224 NPCInfo->combatMove = qtrue;
01225 NPCInfo->goalEntity = goal;
01226
01227 moved = NPC_MoveToGoal( qtrue );
01228
01229
01230 if ( retreat )
01231 {
01232 ucmd.forwardmove *= -1;
01233 ucmd.rightmove *= -1;
01234 VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir );
01235 }
01236
01237
01238 NAV_GetLastMove( &info );
01239
01240
01241 if ( ( info.flags & NIF_COLLISION ) && ( info.blocker == NPC->enemy ) )
01242 {
01243 Jedi_HoldPosition();
01244 }
01245
01246
01247 if ( moved == qfalse )
01248 {
01249 Jedi_HoldPosition();
01250 }
01251 }
01252
01253 static qboolean Jedi_Hunt( void )
01254 {
01255
01256
01257 if ( NPCInfo->stats.aggression > 1 )
01258 {
01259 NPCInfo->combatMove = qtrue;
01260 if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
01261 {
01262 NPC_UpdateAngles( qtrue, qtrue );
01263 return qtrue;
01264 }
01265 else
01266 {
01267 if ( NPCInfo->goalEntity == NULL )
01268 {
01269 NPCInfo->goalEntity = NPC->enemy;
01270 }
01271
01272 if ( NPC_MoveToGoal( qfalse ) )
01273 {
01274 NPC_UpdateAngles( qtrue, qtrue );
01275 return qtrue;
01276 }
01277 }
01278 }
01279 return qfalse;
01280 }
01281
01282
01283
01284
01285
01286
01287
01288
01289
01290
01291
01292
01293
01294
01295
01296
01297
01298
01299
01300 static void Jedi_Retreat( void )
01301 {
01302 if ( !TIMER_Done( NPC, "noRetreat" ) )
01303 {
01304 return;
01305 }
01306
01307
01308
01309 Jedi_Move( NPC->enemy, qtrue );
01310 }
01311
01312 static void Jedi_Advance( void )
01313 {
01314 if ( !NPC->client->ps.saberInFlight )
01315 {
01316
01317 WP_ActivateSaber(NPC);
01318 }
01319
01320 Jedi_Move( NPC->enemy, qfalse );
01321
01322
01323
01324
01325 }
01326
01327 static void Jedi_AdjustSaberAnimLevel( gentity_t *self, int newLevel )
01328 {
01329 if ( !self || !self->client )
01330 {
01331 return;
01332 }
01333
01334 if ( self->client->NPC_class == CLASS_TAVION )
01335 {
01336 self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_5;
01337 return;
01338 }
01339 else if ( self->client->NPC_class == CLASS_DESANN )
01340 {
01341 self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_4;
01342 return;
01343 }
01344 if ( self->client->playerTeam == NPCTEAM_ENEMY )
01345 {
01346 if ( self->NPC->rank == RANK_CIVILIAN || self->NPC->rank == RANK_LT_JG )
01347 {
01348 self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1;
01349 return;
01350 }
01351 if ( self->NPC->rank == RANK_CREWMAN
01352 || self->NPC->rank == RANK_ENSIGN )
01353 {
01354 self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_2;
01355 return;
01356 }
01357
01358
01359
01360
01361
01362
01363
01364 }
01365
01366 if ( newLevel > self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] )
01367 {
01368 self->client->ps.fd.saberAnimLevel = self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE];
01369 }
01370 else if ( newLevel < FORCE_LEVEL_1 )
01371 {
01372 self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1;
01373 }
01374 else
01375 {
01376 self->client->ps.fd.saberAnimLevel = newLevel;
01377 }
01378
01379 if ( d_JediAI.integer )
01380 {
01381 switch ( self->client->ps.fd.saberAnimLevel )
01382 {
01383 case FORCE_LEVEL_1:
01384 Com_Printf( S_COLOR_GREEN"%s Saber Attack Set: fast\n", self->NPC_type );
01385 break;
01386 case FORCE_LEVEL_2:
01387 Com_Printf( S_COLOR_YELLOW"%s Saber Attack Set: medium\n", self->NPC_type );
01388 break;
01389 case FORCE_LEVEL_3:
01390 Com_Printf( S_COLOR_RED"%s Saber Attack Set: strong\n", self->NPC_type );
01391 break;
01392 }
01393 }
01394 }
01395
01396 static void Jedi_CheckDecreaseSaberAnimLevel( void )
01397 {
01398 if ( !NPC->client->ps.weaponTime && !(ucmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)) )
01399 {
01400 if ( TIMER_Done( NPC, "saberLevelDebounce" ) && !Q_irand( 0, 10 ) )
01401 {
01402
01403 Jedi_AdjustSaberAnimLevel( NPC, Q_irand( FORCE_LEVEL_1, FORCE_LEVEL_3 ));
01404 TIMER_Set( NPC, "saberLevelDebounce", Q_irand( 3000, 10000 ) );
01405 }
01406 }
01407 else
01408 {
01409 TIMER_Set( NPC, "saberLevelDebounce", Q_irand( 1000, 5000 ) );
01410 }
01411 }
01412
01413 static void Jedi_CombatDistance( int enemy_dist )
01414 {
01415 if ( NPC->client->ps.fd.forcePowersActive&(1<<FP_GRIP) &&
01416 NPC->client->ps.fd.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
01417 {
01418 return;
01419 }
01420 else if ( !TIMER_Done( NPC, "gripping" ) )
01421 {
01422 TIMER_Set( NPC, "gripping", -level.time );
01423 TIMER_Set( NPC, "attackDelay", Q_irand( 0, 1000 ) );
01424 }
01425
01426 if ( NPC->client->ps.fd.forcePowersActive&(1<<FP_DRAIN) &&
01427 NPC->client->ps.fd.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_1 )
01428 {
01429 return;
01430 }
01431 else if ( !TIMER_Done( NPC, "draining" ) )
01432 {
01433 TIMER_Set( NPC, "draining", -level.time );
01434 TIMER_Set( NPC, "attackDelay", Q_irand( 0, 1000 ) );
01435 }
01436
01437 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
01438 {
01439 if ( !TIMER_Done( NPC, "flameTime" ) )
01440 {
01441 if ( enemy_dist > 50 )
01442 {
01443 Jedi_Advance();
01444 }
01445 else if ( enemy_dist <= 0 )
01446 {
01447 Jedi_Retreat();
01448 }
01449 }
01450 else if ( enemy_dist < 200 )
01451 {
01452 Jedi_Retreat();
01453 }
01454 else if ( enemy_dist > 1024 )
01455 {
01456 Jedi_Advance();
01457 }
01458 }
01459 else if ( NPC->client->ps.saberInFlight &&
01460 !PM_SaberInBrokenParry( NPC->client->ps.saberMove )
01461 && NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
01462 {
01463 if ( enemy_dist < NPC->client->ps.saberEntityDist )
01464 {
01465 Jedi_Retreat();
01466 }
01467 else if ( enemy_dist > NPC->client->ps.saberEntityDist && enemy_dist > 100 )
01468 {
01469 Jedi_Advance();
01470 }
01471 if ( NPC->client->ps.weapon == WP_SABER
01472 && NPC->client->ps.saberEntityState == SES_LEAVING
01473 && NPC->client->ps.fd.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_1
01474 && !(NPC->client->ps.fd.forcePowersActive&(1 << FP_SPEED))
01475 && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )
01476 {
01477 ucmd.buttons |= BUTTON_ALT_ATTACK;
01478
01479 }
01480 }
01481 else if ( !TIMER_Done( NPC, "taunting" ) )
01482 {
01483 if ( enemy_dist <= 64 )
01484 {
01485 ucmd.buttons |= BUTTON_ATTACK;
01486 if ( !NPC->client->ps.saberInFlight )
01487 {
01488 WP_ActivateSaber(NPC);
01489 }
01490 TIMER_Set( NPC, "taunting", -level.time );
01491 }
01492
01493 else if (NPC->client->ps.forceHandExtend == HANDEXTEND_JEDITAUNT && (NPC->client->ps.forceHandExtendTime - level.time) < 200)
01494 {
01495
01496 if ( !NPC->client->ps.saberInFlight )
01497 {
01498 WP_ActivateSaber(NPC);
01499 }
01500 }
01501 }
01502 else if ( NPC->client->ps.saberEventFlags&SEF_LOCK_WON )
01503 {
01504 if ( enemy_dist > 0 )
01505 {
01506 Jedi_Advance();
01507 }
01508 if ( enemy_dist > 128 )
01509 {
01510 NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON;
01511 }
01512 if ( NPC->enemy->painDebounceTime + 2000 < level.time )
01513 {
01514 NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON;
01515 }
01516
01517 TIMER_Set( NPC, "strafeLeft", -1 );
01518 TIMER_Set( NPC, "strafeRight", -1 );
01519 }
01520 else if ( NPC->enemy->client
01521 && NPC->enemy->s.weapon == WP_SABER
01522 && NPC->enemy->client->ps.saberLockTime > level.time
01523 && NPC->client->ps.saberLockTime < level.time )
01524 {
01525 if ( enemy_dist < 64 )
01526 {
01527 Jedi_Retreat();
01528 }
01529 }
01530
01531
01532
01533
01534
01535
01536
01537
01538
01539
01540
01541
01542
01543
01544
01545
01546
01547
01548
01549
01550
01551
01552
01553
01554
01555
01556
01557
01558
01559
01560 else if ( enemy_dist <= 64
01561 && ((NPCInfo->scriptFlags&SCF_DONT_FIRE)||(!Q_stricmp("Yoda",NPC->NPC_type)&&!Q_irand(0,10))) )
01562 {
01563 if ( !Q_irand( 0, 5 ) && InFront( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 0.2f ) )
01564 {
01565 if ( ((NPCInfo->scriptFlags&SCF_DONT_FIRE)||NPC->client->pers.maxHealth - NPC->health > NPC->client->pers.maxHealth*0.25f)
01566 && NPC->client->ps.fd.forcePowersKnown&(1<<FP_DRAIN)
01567 && WP_ForcePowerAvailable( NPC, FP_DRAIN, 20 )
01568 && !Q_irand( 0, 2 ) )
01569 {
01570 TIMER_Set( NPC, "draining", 3000 );
01571 TIMER_Set( NPC, "attackDelay", 3000 );
01572 Jedi_Advance();
01573 return;
01574 }
01575 else
01576 {
01577 ForceThrow( NPC, qfalse );
01578 }
01579 }
01580 Jedi_Retreat();
01581 }
01582 else if ( enemy_dist <= 64
01583 && NPC->client->pers.maxHealth - NPC->health > NPC->client->pers.maxHealth*0.25f
01584 && NPC->client->ps.fd.forcePowersKnown&(1<<FP_DRAIN)
01585 && WP_ForcePowerAvailable( NPC, FP_DRAIN, 20 )
01586 && !Q_irand( 0, 10 )
01587 && InFront( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 0.2f ) )
01588 {
01589 TIMER_Set( NPC, "draining", 3000 );
01590 TIMER_Set( NPC, "attackDelay", 3000 );
01591 Jedi_Advance();
01592 return;
01593 }
01594 else if ( enemy_dist <= -16 )
01595 {
01596 Jedi_Retreat();
01597 }
01598 else if ( enemy_dist <= 0 )
01599 {
01600
01601 if ( NPCInfo->stats.aggression < 4 )
01602 {
01603 Jedi_Retreat();
01604 }
01605 }
01606 else if ( enemy_dist > 256 )
01607 {
01608 qboolean usedForce = qfalse;
01609 if ( NPCInfo->stats.aggression < Q_irand( 0, 20 )
01610 && NPC->health < NPC->client->pers.maxHealth*0.75f
01611 && !Q_irand( 0, 2 ) )
01612 {
01613 if ( (NPC->client->ps.fd.forcePowersKnown&(1<<FP_HEAL)) != 0
01614 && (NPC->client->ps.fd.forcePowersActive&(1<<FP_HEAL)) == 0
01615 && Q_irand( 0, 1 ) )
01616 {
01617 ForceHeal( NPC );
01618 usedForce = qtrue;
01619
01620 }
01621 else if ( (NPC->client->ps.fd.forcePowersKnown&(1<<FP_PROTECT)) != 0
01622 && (NPC->client->ps.fd.forcePowersActive&(1<<FP_PROTECT)) == 0
01623 && Q_irand( 0, 1 ) )
01624 {
01625 ForceProtect( NPC );
01626 usedForce = qtrue;
01627 }
01628 else if ( (NPC->client->ps.fd.forcePowersKnown&(1<<FP_ABSORB)) != 0
01629 && (NPC->client->ps.fd.forcePowersActive&(1<<FP_ABSORB)) == 0
01630 && Q_irand( 0, 1 ) )
01631 {
01632 ForceAbsorb( NPC );
01633 usedForce = qtrue;
01634 }
01635 else if ( (NPC->client->ps.fd.forcePowersKnown&(1<<FP_RAGE)) != 0
01636 && (NPC->client->ps.fd.forcePowersActive&(1<<FP_RAGE)) == 0
01637 && Q_irand( 0, 1 ) )
01638 {
01639 Jedi_Rage();
01640 usedForce = qtrue;
01641 }
01642
01643 }
01644 if ( enemy_dist > 384 )
01645 {
01646 if ( !Q_irand( 0, 10 ) && NPCInfo->blockedSpeechDebounceTime < level.time && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time )
01647 {
01648 if ( NPC_ClearLOS4( NPC->enemy ) )
01649 {
01650 G_AddVoiceEvent( NPC, Q_irand( EV_JCHASE1, EV_JCHASE3 ), 3000 );
01651 }
01652 jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000;
01653 }
01654 }
01655
01656 if ( NPCInfo->stats.aggression > 0 )
01657 {
01658 if ( !usedForce )
01659 {
01660 Jedi_Advance();
01661 }
01662 }
01663 }
01664
01665
01666
01667
01668
01669
01670
01671 else if ( enemy_dist > 50 )
01672 {
01673
01674 if ( NPC->enemy && NPC->enemy->client && (NPC->enemy->client->ps.fd.forceGripBeingGripped > level.time) )
01675 {
01676 if ( NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
01677 {
01678 if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT )
01679 {
01680 if ( enemy_dist > 200 || !(NPCInfo->scriptFlags&SCF_DONT_FIRE) )
01681 {
01682 Jedi_Advance();
01683 }
01684 }
01685 }
01686 if ( NPCInfo->rank >= RANK_LT_JG
01687 && !Q_irand( 0, 5 )
01688 && !(NPC->client->ps.fd.forcePowersActive&(1 << FP_SPEED))
01689 && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )
01690 {
01691 ucmd.buttons |= BUTTON_ALT_ATTACK;
01692 }
01693 }
01694 else if ( NPC->enemy && NPC->enemy->client &&
01695 NPC->enemy->client->ps.saberInFlight && NPC->enemy->client->ps.saberEntityNum &&
01696 NPC->client->ps.weaponTime <= 0 &&
01697 WP_ForcePowerAvailable( NPC, FP_GRIP, 0 ) &&
01698 !Q_irand( 0, 10 ) &&
01699 Q_irand( 0, 6 ) < g_spskill.integer &&
01700 Q_irand( RANK_CIVILIAN, RANK_CAPTAIN ) < NPCInfo->rank )
01701 {
01702
01703 if ( TIMER_Done( NPC, "chatter" ) && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time && NPCInfo->blockedSpeechDebounceTime < level.time )
01704 {
01705 G_AddVoiceEvent( NPC, Q_irand( EV_TAUNT1, EV_TAUNT3 ), 3000 );
01706 jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000;
01707 TIMER_Set( NPC, "chatter", 3000 );
01708 }
01709
01710
01711 TIMER_Set( NPC, "gripping", 3000 );
01712 TIMER_Set( NPC, "attackDelay", 3000 );
01713 }
01714 else
01715 {
01716 int chanceScale;
01717
01718 if ( NPC->enemy && NPC->enemy->client && (NPC->enemy->client->ps.fd.forcePowersActive&(1<<FP_GRIP)) )
01719 {
01720 if ( NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
01721 {
01722 if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT )
01723 {
01724 if ( enemy_dist > 200 || !(NPCInfo->scriptFlags&SCF_DONT_FIRE) )
01725 {
01726 Jedi_Advance();
01727 }
01728 }
01729 }
01730 }
01731 chanceScale = 0;
01732 if ( NPC->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",NPC->NPC_type) )
01733 {
01734 chanceScale = 1;
01735 }
01736 else if ( NPCInfo->rank == RANK_ENSIGN )
01737 {
01738 chanceScale = 2;
01739 }
01740 else if ( NPCInfo->rank >= RANK_LT_JG )
01741 {
01742 chanceScale = 5;
01743 }
01744 if ( chanceScale
01745 && (enemy_dist > Q_irand( 100, 200 ) || (NPCInfo->scriptFlags&SCF_DONT_FIRE) || (!Q_stricmp("Yoda",NPC->NPC_type)&&!Q_irand(0,3)) )
01746 && enemy_dist < 500
01747 && (Q_irand( 0, chanceScale*10 )<5 || (NPC->enemy->client && NPC->enemy->client->ps.weapon != WP_SABER && !Q_irand( 0, chanceScale ) ) ) )
01748 {
01749 if ( (NPCInfo->rank == RANK_ENSIGN || NPCInfo->rank > RANK_LT_JG) && !Q_irand( 0, 1 ) )
01750 {
01751 if ( WP_ForcePowerAvailable( NPC, FP_PULL, 0 ) && !Q_irand( 0, 2 ) )
01752 {
01753
01754
01755 ForceThrow( NPC, qtrue );
01756
01757 TIMER_Set( NPC, "duck", enemy_dist*3 );
01758 if ( Q_irand( 0, 1 ) )
01759 {
01760 ucmd.buttons |= BUTTON_ATTACK;
01761 }
01762 }
01763 else if ( WP_ForcePowerAvailable( NPC, FP_LIGHTNING, 0 ) && Q_irand( 0, 1 ) )
01764 {
01765 ForceLightning( NPC );
01766 if ( NPC->client->ps.fd.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 )
01767 {
01768 NPC->client->ps.weaponTime = Q_irand( 1000, 3000+(g_spskill.integer*500) );
01769 TIMER_Set( NPC, "holdLightning", NPC->client->ps.weaponTime );
01770 }
01771 TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime );
01772 }
01773
01774
01775
01776
01777
01778
01779
01780
01781
01782
01783
01784 else if ( WP_ForcePowerAvailable( NPC, FP_GRIP, 0 ) )
01785 {
01786
01787 if ( TIMER_Done( NPC, "chatter" ) && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time && NPCInfo->blockedSpeechDebounceTime < level.time )
01788 {
01789 G_AddVoiceEvent( NPC, Q_irand( EV_TAUNT1, EV_TAUNT3 ), 3000 );
01790 jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000;
01791 TIMER_Set( NPC, "chatter", 3000 );
01792 }
01793
01794
01795 TIMER_Set( NPC, "gripping", 3000 );
01796 TIMER_Set( NPC, "attackDelay", 3000 );
01797 }
01798 else
01799 {
01800 if ( WP_ForcePowerAvailable( NPC, FP_SABERTHROW, 0 )
01801 && !(NPC->client->ps.fd.forcePowersActive&(1 << FP_SPEED))
01802 && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )
01803 {
01804 ucmd.buttons |= BUTTON_ALT_ATTACK;
01805 }
01806 }
01807 }
01808 else
01809 {
01810 if ( NPCInfo->rank >= RANK_LT_JG
01811 && !(NPC->client->ps.fd.forcePowersActive&(1 << FP_SPEED))
01812 && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )
01813 {
01814 ucmd.buttons |= BUTTON_ALT_ATTACK;
01815 }
01816 }
01817 }
01818
01819 else if ( NPCInfo->stats.aggression > 5 )
01820 {
01821 if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT )
01822 {
01823 if ( !NPC->enemy->client || NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
01824 {
01825 if ( enemy_dist > 200 || !(NPCInfo->scriptFlags&SCF_DONT_FIRE) )
01826 {
01827 Jedi_Advance();
01828 }
01829 }
01830 }
01831 }
01832 else
01833 {
01834
01835 }
01836 }
01837 }
01838 else
01839 {
01840 if ( NPCInfo->stats.aggression < 4 )
01841 {
01842 Jedi_Retreat();
01843 }
01844 else if ( NPCInfo->stats.aggression > 5 )
01845 {
01846 if ( enemy_dist > 0 && !(NPCInfo->scriptFlags&SCF_DONT_FIRE))
01847 {
01848 if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT )
01849 {
01850 if ( !NPC->enemy->client || NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
01851 {
01852 Jedi_Advance();
01853 }
01854 }
01855 }
01856 }
01857 else
01858 {
01859
01860
01861 }
01862 }
01863
01864 if ( NPCInfo->stats.aggression > Q_irand( 5, 15 )
01865 && NPC->health < NPC->client->pers.maxHealth*0.75f
01866 && !Q_irand( 0, 2 ) )
01867 {
01868 if ( (NPC->client->ps.fd.forcePowersKnown&(1<<FP_RAGE)) != 0
01869 && (NPC->client->ps.fd.forcePowersActive&(1<<FP_RAGE)) == 0 )
01870 {
01871 Jedi_Rage();
01872 }
01873 }
01874 }
01875
01876 static qboolean Jedi_Strafe( int strafeTimeMin, int strafeTimeMax, int nextStrafeTimeMin, int nextStrafeTimeMax, qboolean walking )
01877 {
01878 if ( NPC->client->ps.saberEventFlags&SEF_LOCK_WON && NPC->enemy && NPC->enemy->painDebounceTime > level.time )
01879 {
01880 return qfalse;
01881 }
01882 if ( TIMER_Done( NPC, "strafeLeft" ) && TIMER_Done( NPC, "strafeRight" ) )
01883 {
01884 qboolean strafed = qfalse;
01885
01886
01887
01888
01889 int strafeTime = Q_irand( strafeTimeMin, strafeTimeMax );
01890
01891 if ( Q_irand( 0, 1 ) )
01892 {
01893 if ( NPC_MoveDirClear( ucmd.forwardmove, -127, qfalse ) )
01894 {
01895 TIMER_Set( NPC, "strafeLeft", strafeTime );
01896 strafed = qtrue;
01897 }
01898 else if ( NPC_MoveDirClear( ucmd.forwardmove, 127, qfalse ) )
01899 {
01900 TIMER_Set( NPC, "strafeRight", strafeTime );
01901 strafed = qtrue;
01902 }
01903 }
01904 else
01905 {
01906 if ( NPC_MoveDirClear( ucmd.forwardmove, 127, qfalse ) )
01907 {
01908 TIMER_Set( NPC, "strafeRight", strafeTime );
01909 strafed = qtrue;
01910 }
01911 else if ( NPC_MoveDirClear( ucmd.forwardmove, -127, qfalse ) )
01912 {
01913 TIMER_Set( NPC, "strafeLeft", strafeTime );
01914 strafed = qtrue;
01915 }
01916 }
01917
01918 if ( strafed )
01919 {
01920 TIMER_Set( NPC, "noStrafe", strafeTime + Q_irand( nextStrafeTimeMin, nextStrafeTimeMax ) );
01921 if ( walking )
01922 {
01923 TIMER_Set( NPC, "walking", strafeTime );
01924 }
01925 return qtrue;
01926 }
01927 }
01928 return qfalse;
01929 }
01930
01931
01932
01933
01934
01935
01936
01937
01938
01939
01940
01941
01942
01943
01944
01945
01946
01947
01948
01949
01950
01951
01952
01953
01954
01955
01956
01957
01958
01959
01960
01961
01962
01963
01964
01965
01966
01967
01968
01969 evasionType_t Jedi_CheckFlipEvasions( gentity_t *self, float rightdot, float zdiff )
01970 {
01971 if ( self->NPC && (self->NPC->scriptFlags&SCF_NO_ACROBATICS) )
01972 {
01973 return EVASION_NONE;
01974 }
01975 if ( self->client
01976 && (self->client->ps.fd.forceRageRecoveryTime > level.time || (self->client->ps.fd.forcePowersActive&(1<<FP_RAGE))) )
01977 {
01978 return EVASION_NONE;
01979 }
01980
01981
01982
01983
01984
01985 if ( self->client->ps.legsAnim == BOTH_WALL_RUN_LEFT || self->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT )
01986 {
01987 vec3_t right, fwdAngles;
01988 int anim = -1;
01989 float animLength;
01990
01991 VectorSet(fwdAngles, 0, self->client->ps.viewangles[YAW], 0);
01992
01993 AngleVectors( fwdAngles, NULL, right, NULL );
01994
01995 animLength = BG_AnimLength( self->localAnimIndex, (animNumber_t)self->client->ps.legsAnim );
01996 if ( self->client->ps.legsAnim == BOTH_WALL_RUN_LEFT && rightdot < 0 )
01997 {
01998 if ( animLength - self->client->ps.legsTimer > 400
01999 && self->client->ps.legsTimer > 400 )
02000 {
02001 anim = BOTH_WALL_RUN_LEFT_FLIP;
02002 }
02003 }
02004 else if ( self->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT && rightdot > 0 )
02005 {
02006 if ( animLength - self->client->ps.legsTimer > 400
02007 && self->client->ps.legsTimer > 400 )
02008 {
02009 anim = BOTH_WALL_RUN_RIGHT_FLIP;
02010 }
02011 }
02012 if ( anim != -1 )
02013 {
02014 int parts;
02015
02016
02017 if ( anim == BOTH_WALL_RUN_LEFT_FLIP )
02018 {
02019 self->client->ps.velocity[0] *= 0.5f;
02020 self->client->ps.velocity[1] *= 0.5f;
02021 VectorMA( self->client->ps.velocity, 150, right, self->client->ps.velocity );
02022 }
02023 else if ( anim == BOTH_WALL_RUN_RIGHT_FLIP )
02024 {
02025 self->client->ps.velocity[0] *= 0.5f;
02026 self->client->ps.velocity[1] *= 0.5f;
02027 VectorMA( self->client->ps.velocity, -150, right, self->client->ps.velocity );
02028 }
02029 parts = SETANIM_LEGS;
02030 if ( !self->client->ps.weaponTime )
02031 {
02032 parts = SETANIM_BOTH;
02033 }
02034 NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
02035
02036
02037 G_AddEvent( self, EV_JUMP, 0 );
02038 return EVASION_OTHER;
02039 }
02040 }
02041 else if ( self->client->NPC_class != CLASS_DESANN
02042 && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank >= RANK_LT)
02043 && Q_irand( 0, 1 )
02044 && !BG_InRoll( &self->client->ps, self->client->ps.legsAnim )
02045 && !PM_InKnockDown( &self->client->ps )
02046 && !BG_SaberInSpecialAttack( self->client->ps.torsoAnim ) )
02047 {
02048 vec3_t fwd, right, traceto, mins, maxs, fwdAngles;
02049 trace_t trace;
02050 int parts, anim;
02051 float speed, checkDist;
02052 qboolean allowCartWheels = qtrue;
02053 qboolean allowWallFlips = qtrue;
02054
02055 if ( self->client->ps.weapon == WP_SABER )
02056 {
02057 if ( self->client->saber[0].model
02058 && self->client->saber[0].model[0]
02059 && (self->client->saber[0].saberFlags&SFL_NO_CARTWHEELS) )
02060 {
02061 allowCartWheels = qfalse;
02062 }
02063 else if ( self->client->saber[1].model
02064 && self->client->saber[1].model[0]
02065 && (self->client->saber[1].saberFlags&SFL_NO_CARTWHEELS) )
02066 {
02067 allowCartWheels = qfalse;
02068 }
02069 if ( self->client->saber[0].model
02070 && self->client->saber[0].model[0]
02071 && (self->client->saber[0].saberFlags&SFL_NO_WALL_FLIPS) )
02072 {
02073 allowWallFlips = qfalse;
02074 }
02075 else if ( self->client->saber[1].model
02076 && self->client->saber[1].model[0]
02077 && (self->client->saber[1].saberFlags&SFL_NO_WALL_FLIPS) )
02078 {
02079 allowWallFlips = qfalse;
02080 }
02081 }
02082
02083 VectorSet(mins, self->r.mins[0],self->r.mins[1],0);
02084 VectorSet(maxs, self->r.maxs[0],self->r.maxs[1],24);
02085 VectorSet(fwdAngles, 0, self->client->ps.viewangles[YAW], 0);
02086
02087 AngleVectors( fwdAngles, fwd, right, NULL );
02088
02089 parts = SETANIM_BOTH;
02090
02091 if ( BG_SaberInAttack( self->client->ps.saberMove )
02092 || PM_SaberInStart( self->client->ps.saberMove ) )
02093 {
02094 parts = SETANIM_LEGS;
02095 }
02096 if ( rightdot >= 0 )
02097 {
02098 if ( Q_irand( 0, 1 ) )
02099 {
02100 anim = BOTH_ARIAL_LEFT;
02101 }
02102 else
02103 {
02104 anim = BOTH_CARTWHEEL_LEFT;
02105 }
02106 checkDist = -128;
02107 speed = -200;
02108 }
02109 else
02110 {
02111 if ( Q_irand( 0, 1 ) )
02112 {
02113 anim = BOTH_ARIAL_RIGHT;
02114 }
02115 else
02116 {
02117 anim = BOTH_CARTWHEEL_RIGHT;
02118 }
02119 checkDist = 128;
02120 speed = 200;
02121 }
02122
02123 VectorMA( self->r.currentOrigin, checkDist, right, traceto );
02124 trap_Trace( &trace, self->r.currentOrigin, mins, maxs, traceto, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP );
02125 if ( trace.fraction >= 1.0f && allowCartWheels )
02126 {
02127
02128 vec3_t fwdAngles, jumpRt;
02129
02130 NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
02131 self->client->ps.weaponTime = self->client->ps.legsTimer;
02132 VectorCopy( self->client->ps.viewangles, fwdAngles );
02133 fwdAngles[PITCH] = fwdAngles[ROLL] = 0;
02134
02135 AngleVectors( fwdAngles, NULL, jumpRt, NULL );
02136 VectorScale( jumpRt, speed, self->client->ps.velocity );
02137 self->client->ps.fd.forceJumpCharge = 0;
02138 self->client->ps.velocity[2] = 200;
02139 self->client->ps.fd.forceJumpZStart = self->r.currentOrigin[2];
02140
02141 if ( self->client->NPC_class == CLASS_BOBAFETT )
02142 {
02143 G_AddEvent( self, EV_JUMP, 0 );
02144 }
02145 else
02146 {
02147 G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" );
02148 }
02149
02150 return EVASION_CARTWHEEL;
02151 }
02152 else if ( !(trace.contents&CONTENTS_BOTCLIP) )
02153 {
02154
02155
02156 vec3_t idealNormal;
02157 gentity_t *traceEnt;
02158
02159 VectorSubtract( self->r.currentOrigin, traceto, idealNormal );
02160 VectorNormalize( idealNormal );
02161 traceEnt = &g_entities[trace.entityNum];
02162 if ( (trace.entityNum<ENTITYNUM_WORLD&&traceEnt&&traceEnt->s.solid!=SOLID_BMODEL) || DotProduct( trace.plane.normal, idealNormal ) > 0.7f )
02163 {
02164 float bestCheckDist = 0;
02165
02166 if ( DotProduct( self->client->ps.velocity, fwd ) < 200 )
02167 {
02168
02169 if ( (trace.fraction*checkDist) <= 32 )
02170 {
02171 bestCheckDist = checkDist;
02172 checkDist *= -1.0f;
02173 VectorMA( self->r.currentOrigin, checkDist, right, traceto );
02174
02175 trap_Trace( &trace, self->r.currentOrigin, mins, maxs, traceto, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP );
02176 if ( trace.fraction >= 1.0f )
02177 {
02178 if ( allowWallFlips )
02179 {
02180 int parts;
02181
02182
02183
02184 if ( rightdot > 0 )
02185 {
02186 anim = BOTH_WALL_FLIP_LEFT;
02187 self->client->ps.velocity[0] = self->client->ps.velocity[1] = 0;
02188 VectorMA( self->client->ps.velocity, 150, right, self->client->ps.velocity );
02189 }
02190 else
02191 {
02192 anim = BOTH_WALL_FLIP_RIGHT;
02193 self->client->ps.velocity[0] = self->client->ps.velocity[1] = 0;
02194 VectorMA( self->client->ps.velocity, -150, right, self->client->ps.velocity );
02195 }
02196 self->client->ps.velocity[2] = forceJumpStrength[FORCE_LEVEL_2]/2.25f;
02197
02198 parts = SETANIM_LEGS;
02199 if ( !self->client->ps.weaponTime )
02200 {
02201 parts = SETANIM_BOTH;
02202 }
02203 NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
02204 self->client->ps.fd.forceJumpZStart = self->r.currentOrigin[2];
02205
02206 if ( self->client->NPC_class == CLASS_BOBAFETT )
02207 {
02208 G_AddEvent( self, EV_JUMP, 0 );
02209 }
02210 else
02211 {
02212 G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" );
02213 }
02214 return EVASION_OTHER;
02215 }
02216 }
02217 else
02218 {
02219 if ( DotProduct( self->client->ps.velocity, fwd ) < 0 )
02220 {
02221 return EVASION_NONE;
02222 }
02223 if ( (trace.fraction*checkDist) <= 32 && (trace.fraction*checkDist) < bestCheckDist )
02224 {
02225 bestCheckDist = checkDist;
02226 }
02227 }
02228 }
02229 else
02230 {
02231 checkDist *= -1.0f;
02232 VectorMA( self->r.currentOrigin, checkDist, right, traceto );
02233
02234 trap_Trace( &trace, self->r.currentOrigin, mins, maxs, traceto, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP );
02235 if ( (trace.fraction*checkDist) <= 32 )
02236 {
02237 bestCheckDist = checkDist;
02238 }
02239 else
02240 {
02241 return EVASION_NONE;
02242 }
02243 }
02244 }
02245
02246 if ( bestCheckDist )
02247 {
02248 qboolean allowWallRuns = qtrue;
02249 if ( self->client->ps.weapon == WP_SABER )
02250 {
02251 if ( self->client->saber[0].model
02252 && self->client->saber[0].model[0]
02253 && (self->client->saber[0].saberFlags&SFL_NO_WALL_RUNS) )
02254 {
02255 allowWallRuns = qfalse;
02256 }
02257 else if ( self->client->saber[1].model
02258 && self->client->saber[1].model[0]
02259 && (self->client->saber[1].saberFlags&SFL_NO_WALL_RUNS) )
02260 {
02261 allowWallRuns = qfalse;
02262 }
02263 }
02264 if ( allowWallRuns )
02265 {
02266 int parts;
02267
02268
02269 if ( bestCheckDist > 0 )
02270 {
02271 anim = BOTH_WALL_RUN_RIGHT;
02272 }
02273 else
02274 {
02275 anim = BOTH_WALL_RUN_LEFT;
02276 }
02277 self->client->ps.velocity[2] = forceJumpStrength[FORCE_LEVEL_2]/2.25f;
02278
02279 parts = SETANIM_LEGS;
02280 if ( !self->client->ps.weaponTime )
02281 {
02282 parts = SETANIM_BOTH;
02283 }
02284 NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
02285 self->client->ps.fd.forceJumpZStart = self->r.currentOrigin[2];
02286
02287 if ( self->client->NPC_class == CLASS_BOBAFETT )
02288 {
02289 G_AddEvent( self, EV_JUMP, 0 );
02290 }
02291 else
02292 {
02293 G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" );
02294 }
02295 return EVASION_OTHER;
02296 }
02297 }
02298
02299 }
02300 }
02301 }
02302 return EVASION_NONE;
02303 }
02304
02305 int Jedi_ReCalcParryTime( gentity_t *self, evasionType_t evasionType )
02306 {
02307 if ( !self->client )
02308 {
02309 return 0;
02310 }
02311 if ( !self->s.number )
02312 {
02313 return bg_parryDebounce[self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE]];
02314 }
02315 else if ( self->NPC )
02316 {
02317 if ( !g_saberRealisticCombat.integer
02318 && ( g_spskill.integer == 2 || (g_spskill.integer == 1 && self->client->NPC_class == CLASS_TAVION) ) )
02319 {
02320 if ( self->client->NPC_class == CLASS_TAVION )
02321 {
02322 return 0;
02323 }
02324 else
02325 {
02326 return Q_irand( 0, 150 );
02327 }
02328 }
02329 else
02330 {
02331 int baseTime;
02332 if ( evasionType == EVASION_DODGE )
02333 {
02334 baseTime = self->client->ps.torsoTimer;
02335 }
02336 else if ( evasionType == EVASION_CARTWHEEL )
02337 {
02338 baseTime = self->client->ps.torsoTimer;
02339 }
02340 else if ( self->client->ps.saberInFlight )
02341 {
02342 baseTime = Q_irand( 1, 3 ) * 50;
02343 }
02344 else
02345 {
02346 if ( g_saberRealisticCombat.integer )
02347 {
02348 baseTime = 500;
02349
02350 switch ( g_spskill.integer )
02351 {
02352 case 0:
02353 baseTime = 500;
02354 break;
02355 case 1:
02356 baseTime = 300;
02357 break;
02358 case 2:
02359 default:
02360 baseTime = 100;
02361 break;
02362 }
02363 }
02364 else
02365 {
02366 baseTime = 150;
02367
02368 switch ( g_spskill.integer )
02369 {
02370 case 0:
02371 baseTime = 200;
02372 break;
02373 case 1:
02374 baseTime = 100;
02375 break;
02376 case 2:
02377 default:
02378 baseTime = 50;
02379 break;
02380 }
02381 }
02382
02383 if ( self->client->NPC_class == CLASS_TAVION )
02384 {
02385 baseTime = ceil(baseTime/2.0f);
02386 }
02387 else if ( self->NPC->rank >= RANK_LT_JG )
02388 {
02389 if ( Q_irand( 0, 2 ) )
02390 {
02391 baseTime = baseTime;
02392 }
02393 else
02394 {
02395 baseTime = ceil(baseTime/2.0f);
02396 }
02397 }
02398 else if ( self->NPC->rank == RANK_CIVILIAN )
02399 {
02400 baseTime = baseTime*Q_irand(1,3);
02401 }
02402 else if ( self->NPC->rank == RANK_CREWMAN )
02403 {
02404 if ( evasionType == EVASION_PARRY
02405 || evasionType == EVASION_DUCK_PARRY
02406 || evasionType == EVASION_JUMP_PARRY )
02407 {
02408 baseTime = baseTime*Q_irand(1,2);
02409 }
02410 else
02411 {
02412
02413 }
02414 }
02415 else
02416 {
02417 baseTime = baseTime*Q_irand(1,2);
02418 }
02419 if ( evasionType == EVASION_DUCK || evasionType == EVASION_DUCK_PARRY )
02420 {
02421 baseTime += 100;
02422 }
02423 else if ( evasionType == EVASION_JUMP || evasionType == EVASION_JUMP_PARRY )
02424 {
02425 baseTime += 50;
02426 }
02427 else if ( evasionType == EVASION_OTHER )
02428 {
02429 baseTime += 100;
02430 }
02431 else if ( evasionType == EVASION_FJUMP )
02432 {
02433 baseTime += 100;
02434 }
02435 }
02436
02437 return baseTime;
02438 }
02439 }
02440 return 0;
02441 }
02442
02443 qboolean Jedi_QuickReactions( gentity_t *self )
02444 {
02445 if ( ( self->client->NPC_class == CLASS_JEDI && NPCInfo->rank == RANK_COMMANDER ) ||
02446 self->client->NPC_class == CLASS_TAVION ||
02447 (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_1&&g_spskill.integer>1) ||
02448 (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_2&&g_spskill.integer>0) )
02449 {
02450 return qtrue;
02451 }
02452 return qfalse;
02453 }
02454
02455 qboolean Jedi_SaberBusy( gentity_t *self )
02456 {
02457 if ( self->client->ps.torsoTimer > 300
02458 && ( (BG_SaberInAttack( self->client->ps.saberMove )&&self->client->ps.fd.saberAnimLevel==FORCE_LEVEL_3)
02459 || BG_SpinningSaberAnim( self->client->ps.torsoAnim )
02460 || BG_SaberInSpecialAttack( self->client->ps.torsoAnim )
02461
02462 || PM_SaberInBrokenParry( self->client->ps.saberMove )
02463
02464 || BG_FlippingAnim( self->client->ps.torsoAnim )
02465 || PM_RollingAnim( self->client->ps.torsoAnim ) ) )
02466 {
02467 return qtrue;
02468 }
02469 return qfalse;
02470 }
02471
02472
02473
02474
02475
02476
02477
02478
02479
02480
02481
02482
02483
02484 extern qboolean G_FindClosestPointOnLineSegment( const vec3_t start, const vec3_t end, const vec3_t from, vec3_t result );
02485 evasionType_t Jedi_SaberBlockGo( gentity_t *self, usercmd_t *cmd, vec3_t pHitloc, vec3_t phitDir, gentity_t *incoming, float dist )
02486 {
02487 vec3_t hitloc, hitdir, diff, fwdangles={0,0,0}, right;
02488 float rightdot;
02489 float zdiff;
02490 int duckChance = 0;
02491 int dodgeAnim = -1;
02492 qboolean saberBusy = qfalse, evaded = qfalse, doDodge = qfalse;
02493 evasionType_t evasionType = EVASION_NONE;
02494
02495
02496
02497 if ( !incoming )
02498 {
02499 VectorCopy( pHitloc, hitloc );
02500 VectorCopy( phitDir, hitdir );
02501
02502 if ( self->client->ps.saberInFlight )
02503 {
02504 saberBusy = qtrue;
02505 }
02506 else if ( Jedi_QuickReactions( self ) )
02507 {
02508
02509 }
02510 else
02511 {
02512 saberBusy = Jedi_SaberBusy( self );
02513 }
02514 }
02515 else
02516 {
02517 if ( incoming->s.weapon == WP_SABER )
02518 {
02519
02520
02521 }
02522 VectorCopy( incoming->r.currentOrigin, hitloc );
02523 VectorNormalize2( incoming->s.pos.trDelta, hitdir );
02524 }
02525 if ( self->client && self->client->NPC_class == CLASS_BOBAFETT )
02526 {
02527 saberBusy = qtrue;
02528 }
02529
02530 VectorSubtract( hitloc, self->client->renderInfo.eyePoint, diff );
02531 diff[2] = 0;
02532
02533 fwdangles[1] = self->client->ps.viewangles[1];
02534
02535 AngleVectors( fwdangles, NULL, right, NULL );
02536
02537 rightdot = DotProduct(right, diff);
02538
02539 zdiff = hitloc[2] - self->client->renderInfo.eyePoint[2];
02540
02541
02542 if ( (dist>16&&(Q_irand( 0, 2 )||saberBusy))
02543 || self->client->ps.saberInFlight
02544 || BG_SabersOff( &self->client->ps )
02545 || self->client->NPC_class == CLASS_BOBAFETT )
02546 {
02547 if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank >= RANK_LT_JG) )
02548 {
02549 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE &&
02550 !(self->client->ps.pm_flags&PMF_DUCKED)&&cmd->upmove>=0&&TIMER_Done( self, "duck" )
02551 && !BG_InRoll( &self->client->ps, self->client->ps.legsAnim )
02552 && !PM_InKnockDown( &self->client->ps )
02553 && ( self->client->ps.saberInFlight ||
02554 self->client->NPC_class == CLASS_BOBAFETT ||
02555 (!BG_SaberInAttack( self->client->ps.saberMove )
02556 && !PM_SaberInStart( self->client->ps.saberMove )
02557 && !BG_SpinningSaberAnim( self->client->ps.torsoAnim )
02558 && !BG_SaberInSpecialAttack( self->client->ps.torsoAnim ))
02559 )
02560 )
02561 {
02562 doDodge = qtrue;
02563 }
02564 }
02565 }
02566
02567 if ( d_JediAI.integer )
02568 {
02569 Com_Printf( "(%d) evading attack from height %4.2f, zdiff: %4.2f, rightdot: %4.2f\n", level.time, hitloc[2]-self->r.absmin[2],zdiff,rightdot);
02570 }
02571
02572
02573
02574
02575
02576 if ( zdiff >= -5 )
02577 {
02578 if ( incoming || !saberBusy )
02579 {
02580 if ( rightdot > 12
02581 || (rightdot > 3 && zdiff < 5)
02582 || (!incoming&&fabs(hitdir[2])<0.25f) )
02583 {
02584 if ( doDodge )
02585 {
02586 if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) )
02587 {
02588 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
02589 TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) );
02590 TIMER_Set( self, "strafeRight", 0 );
02591 evasionType = EVASION_DUCK;
02592 evaded = qtrue;
02593 }
02594 else if ( Q_irand( 0, 1 ) )
02595 {
02596 dodgeAnim = BOTH_DODGE_FL;
02597 }
02598 else
02599 {
02600 dodgeAnim = BOTH_DODGE_BL;
02601 }
02602 }
02603 else
02604 {
02605 self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
02606 evasionType = EVASION_PARRY;
02607 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
02608 {
02609 if ( zdiff > 5 )
02610 {
02611 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
02612 evasionType = EVASION_DUCK_PARRY;
02613 evaded = qtrue;
02614 if ( d_JediAI.integer )
02615 {
02616 Com_Printf( "duck " );
02617 }
02618 }
02619 else
02620 {
02621 duckChance = 6;
02622 }
02623 }
02624 }
02625 if ( d_JediAI.integer )
02626 {
02627 Com_Printf( "UR block\n" );
02628 }
02629 }
02630 else if ( rightdot < -12
02631 || (rightdot < -3 && zdiff < 5)
02632 || (!incoming&&fabs(hitdir[2])<0.25f) )
02633 {
02634 if ( doDodge )
02635 {
02636 if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) )
02637 {
02638 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
02639 TIMER_Start( self, "strafeRight", Q_irand( 500, 1500 ) );
02640 TIMER_Set( self, "strafeLeft", 0 );
02641 evasionType = EVASION_DUCK;
02642 evaded = qtrue;
02643 }
02644 else if ( Q_irand( 0, 1 ) )
02645 {
02646 dodgeAnim = BOTH_DODGE_FR;
02647 }
02648 else
02649 {
02650 dodgeAnim = BOTH_DODGE_BR;
02651 }
02652 }
02653 else
02654 {
02655 self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
02656 evasionType = EVASION_PARRY;
02657 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
02658 {
02659 if ( zdiff > 5 )
02660 {
02661 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
02662 evasionType = EVASION_DUCK_PARRY;
02663 evaded = qtrue;
02664 if ( d_JediAI.integer )
02665 {
02666 Com_Printf( "duck " );
02667 }
02668 }
02669 else
02670 {
02671 duckChance = 6;
02672 }
02673 }
02674 }
02675 if ( d_JediAI.integer )
02676 {
02677 Com_Printf( "UL block\n" );
02678 }
02679 }
02680 else
02681 {
02682 self->client->ps.saberBlocked = BLOCKED_TOP;
02683 evasionType = EVASION_PARRY;
02684 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
02685 {
02686 duckChance = 4;
02687 }
02688 if ( d_JediAI.integer )
02689 {
02690 Com_Printf( "TOP block\n" );
02691 }
02692 }
02693 evaded = qtrue;
02694 }
02695 else
02696 {
02697 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
02698 {
02699
02700 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
02701 evasionType = EVASION_DUCK;
02702 evaded = qtrue;
02703 if ( d_JediAI.integer )
02704 {
02705 Com_Printf( "duck " );
02706 }
02707 }
02708 }
02709 }
02710
02711
02712 else if ( zdiff > -22 )
02713 {
02714 if ( 1 )
02715 {
02716 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
02717 {
02718
02719 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
02720 evasionType = EVASION_DUCK;
02721 evaded = qtrue;
02722 if ( d_JediAI.integer )
02723 {
02724 Com_Printf( "duck " );
02725 }
02726 }
02727 else
02728 {
02729 }
02730 }
02731 if ( incoming || !saberBusy )
02732 {
02733 if ( rightdot > 8 || (rightdot > 3 && zdiff < -11) )
02734 {
02735 if ( doDodge )
02736 {
02737 if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) )
02738 {
02739 TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) );
02740 TIMER_Set( self, "strafeRight", 0 );
02741 }
02742 else
02743 {
02744 dodgeAnim = BOTH_DODGE_L;
02745 }
02746 }
02747 else
02748 {
02749 self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
02750 if ( evasionType == EVASION_DUCK )
02751 {
02752 evasionType = EVASION_DUCK_PARRY;
02753 }
02754 else
02755 {
02756 evasionType = EVASION_PARRY;
02757 }
02758 }
02759 if ( d_JediAI.integer )
02760 {
02761 Com_Printf( "mid-UR block\n" );
02762 }
02763 }
02764 else if ( rightdot < -8 || (rightdot < -3 && zdiff < -11) )
02765 {
02766 if ( doDodge )
02767 {
02768 if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) )
02769 {
02770 TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) );
02771 TIMER_Set( self, "strafeRight", 0 );
02772 }
02773 else
02774 {
02775 dodgeAnim = BOTH_DODGE_R;
02776 }
02777 }
02778 else
02779 {
02780 self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
02781 if ( evasionType == EVASION_DUCK )
02782 {
02783 evasionType = EVASION_DUCK_PARRY;
02784 }
02785 else
02786 {
02787 evasionType = EVASION_PARRY;
02788 }
02789 }
02790 if ( d_JediAI.integer )
02791 {
02792 Com_Printf( "mid-UL block\n" );
02793 }
02794 }
02795 else
02796 {
02797 self->client->ps.saberBlocked = BLOCKED_TOP;
02798 if ( evasionType == EVASION_DUCK )
02799 {
02800 evasionType = EVASION_DUCK_PARRY;
02801 }
02802 else
02803 {
02804 evasionType = EVASION_PARRY;
02805 }
02806 if ( d_JediAI.integer )
02807 {
02808 Com_Printf( "mid-TOP block\n" );
02809 }
02810 }
02811 evaded = qtrue;
02812 }
02813 }
02814 else if ( saberBusy || (zdiff < -36 && ( zdiff < -44 || !Q_irand( 0, 2 ) ) ) )
02815 {
02816 if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE )
02817 {
02818 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
02819 evasionType = EVASION_DUCK;
02820 evaded = qtrue;
02821 if ( d_JediAI.integer )
02822 {
02823 Com_Printf( "legs up\n" );
02824 }
02825 if ( incoming || !saberBusy )
02826 {
02827
02828 if ( rightdot >= 0 )
02829 {
02830 self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
02831 evasionType = EVASION_DUCK_PARRY;
02832 if ( d_JediAI.integer )
02833 {
02834 Com_Printf( "LR block\n" );
02835 }
02836 }
02837 else
02838 {
02839 self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
02840 evasionType = EVASION_DUCK_PARRY;
02841 if ( d_JediAI.integer )
02842 {
02843 Com_Printf( "LL block\n" );
02844 }
02845 }
02846 evaded = qtrue;
02847 }
02848 }
02849 else
02850 {
02851 if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank > RANK_LT_JG ) &&
02852 (!Q_irand( 0, 10 ) || (!Q_irand( 0, 2 ) && (cmd->forwardmove || cmd->rightmove))) )
02853 {
02854
02855 if ( self->NPC
02856 && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS)
02857 && self->client->ps.fd.forceRageRecoveryTime < level.time
02858 && !(self->client->ps.fd.forcePowersActive&(1<<FP_RAGE))
02859 && !PM_InKnockDown( &self->client->ps ) )
02860 {
02861 self->client->ps.fd.forceJumpCharge = 320;
02862 evasionType = EVASION_FJUMP;
02863 evaded = qtrue;
02864 if ( d_JediAI.integer )
02865 {
02866 Com_Printf( "force jump + " );
02867 }
02868 }
02869 }
02870 else
02871 {
02872
02873 if ( self->NPC
02874 && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS)
02875 && self->client->ps.fd.forceRageRecoveryTime < level.time
02876 && !(self->client->ps.fd.forcePowersActive&(1<<FP_RAGE)) )
02877 {
02878 if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 1 ) )
02879 {
02880 if ( rightdot > 0 )
02881 {
02882 TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) );
02883 TIMER_Set( self, "strafeRight", 0 );
02884 TIMER_Set( self, "walking", 0 );
02885 }
02886 else
02887 {
02888 TIMER_Start( self, "strafeRight", Q_irand( 500, 1500 ) );
02889 TIMER_Set( self, "strafeLeft", 0 );
02890 TIMER_Set( self, "walking", 0 );
02891 }
02892 }
02893 else
02894 {
02895 if ( self == NPC )
02896 {
02897 cmd->upmove = 127;
02898 }
02899 else
02900 {
02901 self->client->ps.velocity[2] = JUMP_VELOCITY;
02902 }
02903 }
02904 evasionType = EVASION_JUMP;
02905 evaded = qtrue;
02906 if ( d_JediAI.integer )
02907 {
02908 Com_Printf( "jump + " );
02909 }
02910 }
02911 if ( self->client->NPC_class == CLASS_TAVION )
02912 {
02913 if ( !incoming
02914 && self->client->ps.groundEntityNum < ENTITYNUM_NONE
02915 && !Q_irand( 0, 2 ) )
02916 {
02917 if ( !BG_SaberInAttack( self->client->ps.saberMove )
02918 && !PM_SaberInStart( self->client->ps.saberMove )
02919 && !BG_InRoll( &self->client->ps, self->client->ps.legsAnim )
02920 && !PM_InKnockDown( &self->client->ps )
02921 && !BG_SaberInSpecialAttack( self->client->ps.torsoAnim ) )
02922 {
02923 int butterflyAnim;
02924 if ( Q_irand( 0, 1 ) )
02925 {
02926 butterflyAnim = BOTH_BUTTERFLY_LEFT;
02927 }
02928 else
02929 {
02930 butterflyAnim = BOTH_BUTTERFLY_RIGHT;
02931 }
02932 evasionType = EVASION_CARTWHEEL;
02933 NPC_SetAnim( self, SETANIM_BOTH, butterflyAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
02934 self->client->ps.velocity[2] = 225;
02935 self->client->ps.fd.forceJumpZStart = self->r.currentOrigin[2];
02936
02937
02938
02939 if ( self->client->NPC_class == CLASS_BOBAFETT )
02940 {
02941 G_AddEvent( self, EV_JUMP, 0 );
02942 }
02943 else
02944 {
02945 G_Sound( self, CHAN_BODY, G_SoundIndex("sound/weapons/force/jump.wav") );
02946 }
02947 cmd->upmove = 0;
02948 saberBusy = qtrue;
02949 evaded = qtrue;
02950 }
02951 }
02952 }
02953 }
02954 if ( ((evasionType = Jedi_CheckFlipEvasions( self, rightdot, zdiff ))!=EVASION_NONE) )
02955 {
02956 if ( d_slowmodeath.integer > 5 && self->enemy && !self->enemy->s.number )
02957 {
02958 G_StartMatrixEffect( self );
02959 }
02960 saberBusy = qtrue;
02961 evaded = qtrue;
02962 }
02963 else if ( incoming || !saberBusy )
02964 {
02965
02966 if ( rightdot >= 0 )
02967 {
02968 self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
02969 if ( evasionType == EVASION_JUMP )
02970 {
02971 evasionType = EVASION_JUMP_PARRY;
02972 }
02973 else if ( evasionType == EVASION_NONE )
02974 {
02975 evasionType = EVASION_PARRY;
02976 }
02977 if ( d_JediAI.integer )
02978 {
02979 Com_Printf( "LR block\n" );
02980 }
02981 }
02982 else
02983 {
02984 self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
02985 if ( evasionType == EVASION_JUMP )
02986 {
02987 evasionType = EVASION_JUMP_PARRY;
02988 }
02989 else if ( evasionType == EVASION_NONE )
02990 {
02991 evasionType = EVASION_PARRY;
02992 }
02993 if ( d_JediAI.integer )
02994 {
02995 Com_Printf( "LL block\n" );
02996 }
02997 }
02998 evaded = qtrue;
02999 }
03000 }
03001 }
03002 else
03003 {
03004 if ( incoming || !saberBusy )
03005 {
03006 if ( rightdot >= 0 )
03007 {
03008 self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
03009 evasionType = EVASION_PARRY;
03010 if ( d_JediAI.integer )
03011 {
03012 Com_Printf( "LR block\n" );
03013 }
03014 }
03015 else
03016 {
03017 self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
03018 evasionType = EVASION_PARRY;
03019 if ( d_JediAI.integer )
03020 {
03021 Com_Printf( "LL block\n" );
03022 }
03023 }
03024 if ( incoming && incoming->s.weapon == WP_SABER )
03025 {
03026 if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank > RANK_LT_JG ) &&
03027 (!Q_irand( 0, 10 ) || (!Q_irand( 0, 2 ) && (cmd->forwardmove || cmd->rightmove))) )
03028 {
03029
03030 if ( self->NPC
03031 && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS)
03032 && self->client->ps.fd.forceRageRecoveryTime < level.time
03033 && !(self->client->ps.fd.forcePowersActive&(1<<FP_RAGE))
03034 && !PM_InKnockDown( &self->client->ps ) )
03035 {
03036 self->client->ps.fd.forceJumpCharge = 320;
03037 evasionType = EVASION_FJUMP;
03038 if ( d_JediAI.integer )
03039 {
03040 Com_Printf( "force jump + " );
03041 }
03042 }
03043 }
03044 else
03045 {
03046
03047 if ( self->NPC
03048 && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS)
03049 && self->client->ps.fd.forceRageRecoveryTime < level.time
03050 && !(self->client->ps.fd.forcePowersActive&(1<<FP_RAGE)))
03051 {
03052 if ( self == NPC )
03053 {
03054 cmd->upmove = 127;
03055 }
03056 else
03057 {
03058 self->client->ps.velocity[2] = JUMP_VELOCITY;
03059 }
03060 evasionType = EVASION_JUMP_PARRY;
03061 if ( d_JediAI.integer )
03062 {
03063 Com_Printf( "jump + " );
03064 }
03065 }
03066 }
03067 }
03068 evaded = qtrue;
03069 }
03070 }
03071
03072 if ( evasionType == EVASION_NONE )
03073 {
03074 return EVASION_NONE;
03075 }
03076
03077 TIMER_Set( self, "taunting", 0 );
03078
03079 TIMER_Set( self, "gripping", -level.time );
03080 WP_ForcePowerStop( self, FP_GRIP );
03081
03082 TIMER_Set( self, "draining", -level.time );
03083 WP_ForcePowerStop( self, FP_DRAIN );
03084
03085 if ( dodgeAnim != -1 )
03086 {
03087 evasionType = EVASION_DODGE;
03088 NPC_SetAnim( self, SETANIM_BOTH, dodgeAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
03089 self->client->ps.weaponTime = self->client->ps.torsoTimer;
03090
03091 self->client->ps.pm_time = self->client->ps.torsoTimer;
03092
03093 self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
03094
03095 if ( d_slowmodeath.integer > 5 && self->enemy && !self->enemy->s.number )
03096 {
03097 G_StartMatrixEffect( self );
03098 }
03099 }
03100 else
03101 {
03102 if ( duckChance )
03103 {
03104 if ( !Q_irand( 0, duckChance ) )
03105 {
03106 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
03107 if ( evasionType == EVASION_PARRY )
03108 {
03109 evasionType = EVASION_DUCK_PARRY;
03110 }
03111 else
03112 {
03113 evasionType = EVASION_DUCK;
03114 }
03115
03116
03117
03118
03119
03120
03121 }
03122 }
03123
03124 if ( incoming )
03125 {
03126 self->client->ps.saberBlocked = WP_MissileBlockForBlock( self->client->ps.saberBlocked );
03127 }
03128
03129 }
03130
03131 {
03132 int parryReCalcTime = Jedi_ReCalcParryTime( self, evasionType );
03133 if ( self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] < level.time + parryReCalcTime )
03134 {
03135 self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + parryReCalcTime;
03136 }
03137 }
03138 return evasionType;
03139 }
03140
03141 extern float ShortestLineSegBewteen2LineSegs( vec3_t start1, vec3_t end1, vec3_t start2, vec3_t end2, vec3_t close_pnt1, vec3_t close_pnt2 );
03142 extern int WPDEBUG_SaberColor( saber_colors_t saberColor );
03143 static qboolean Jedi_SaberBlock( int saberNum, int bladeNum )
03144 {
03145 vec3_t hitloc, saberTipOld, saberTip, top, bottom, axisPoint, saberPoint, dir;
03146 vec3_t pointDir, baseDir, tipDir, saberHitPoint, saberMins, saberMaxs;
03147 float pointDist, baseDirPerc, dist;
03148 float bladeLen = 0;
03149 trace_t tr;
03150 evasionType_t evasionType;
03151
03152
03153
03154
03155
03156
03157
03158
03159
03160
03161 if ( !TIMER_Done( NPC, "parryReCalcTime" ) )
03162 {
03163 return qfalse;
03164 }
03165
03166 if ( NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] > level.time )
03167 {
03168 return qfalse;
03169 }
03170
03171
03172
03173
03174
03175
03176
03177
03178
03179 if ( NPC->enemy->health <= 0 || !NPC->enemy->client )
03180 {
03181 return qfalse;
03182 }
03183
03184
03185
03186
03187
03188
03189
03190
03191
03192
03193
03194
03195
03196
03197
03198
03199
03200
03201
03202
03203
03204
03205
03206
03207
03208
03209
03210
03211
03212
03213
03214
03215
03216
03217
03218
03219
03220
03221
03222
03223
03224
03225 VectorSet(saberMins,-4,-4,-4);
03226 VectorSet(saberMaxs,4,4,4);
03227
03228 VectorMA( NPC->enemy->client->saber[saberNum].blade[bladeNum].muzzlePointOld, NPC->enemy->client->saber[saberNum].blade[bladeNum].length, NPC->enemy->client->saber[saberNum].blade[bladeNum].muzzleDirOld, saberTipOld );
03229 VectorMA( NPC->enemy->client->saber[saberNum].blade[bladeNum].muzzlePoint, NPC->enemy->client->saber[saberNum].blade[bladeNum].length, NPC->enemy->client->saber[saberNum].blade[bladeNum].muzzleDir, saberTip );
03230
03231
03232
03233
03234 VectorCopy( NPC->r.currentOrigin, top );
03235 top[2] = NPC->r.absmax[2];
03236 VectorCopy( NPC->r.currentOrigin, bottom );
03237 bottom[2] = NPC->r.absmin[2];
03238
03239 dist = ShortestLineSegBewteen2LineSegs( NPC->enemy->client->renderInfo.muzzlePoint, saberTip, bottom, top, saberPoint, axisPoint );
03240 if ( dist > NPC->r.maxs[0]*5 )
03241 {
03242 if ( d_JediAI.integer )
03243 {
03244 Com_Printf( S_COLOR_RED"enemy saber dist: %4.2f\n", dist );
03245 }
03246
03247
03248
03249
03250
03251
03252
03253
03254
03255 {
03256 TIMER_Set( NPC, "parryTime", -1 );
03257 }
03258 return qfalse;
03259 }
03260 if ( d_JediAI.integer )
03261 {
03262 Com_Printf( S_COLOR_GREEN"enemy saber dist: %4.2f\n", dist );
03263 }
03264
03265 VectorSubtract( saberPoint, NPC->enemy->client->renderInfo.muzzlePoint, pointDir );
03266 pointDist = VectorLength( pointDir );
03267
03268 bladeLen = NPC->enemy->client->saber[saberNum].blade[bladeNum].length;
03269
03270 if ( bladeLen <= 0 )
03271 {
03272 baseDirPerc = 0.5f;
03273 }
03274 else
03275 {
03276 baseDirPerc = pointDist/bladeLen;
03277 }
03278 VectorSubtract( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->renderInfo.muzzlePointOld, baseDir );
03279 VectorSubtract( saberTip, saberTipOld, tipDir );
03280 VectorScale( baseDir, baseDirPerc, baseDir );
03281 VectorMA( baseDir, 1.0f-baseDirPerc, tipDir, dir );
03282 VectorMA( saberPoint, 200, dir, hitloc );
03283
03284
03285 trap_Trace( &tr, saberPoint, saberMins, saberMaxs, hitloc, NPC->enemy->s.number, CONTENTS_BODY );
03286 if ( tr.allsolid || tr.startsolid || tr.fraction >= 1.0f )
03287 {
03288 vec3_t dir2Me;
03289 VectorSubtract( axisPoint, saberPoint, dir2Me );
03290 dist = VectorNormalize( dir2Me );
03291 if ( DotProduct( dir, dir2Me ) < 0.2f )
03292 {
03293
03294
03295
03296
03297
03298
03299
03300
03301
03302 {
03303 TIMER_Set( NPC, "parryTime", -1 );
03304 }
03305 return qfalse;
03306 }
03307 ShortestLineSegBewteen2LineSegs( saberPoint, hitloc, bottom, top, saberHitPoint, hitloc );
03308
03309
03310
03311
03312
03313 }
03314 else
03315 {
03316 VectorCopy( tr.endpos, hitloc );
03317 }
03318
03319 if ( d_JediAI.integer )
03320 {
03321
03322 G_TestLine(saberPoint, hitloc, 0x0000ff, FRAMETIME);
03323 }
03324
03325
03326
03327 if ( (evasionType=Jedi_SaberBlockGo( NPC, &ucmd, hitloc, dir, NULL, dist )) != EVASION_DODGE )
03328 {
03329 int parryReCalcTime;
03330
03331 if ( !NPC->client->ps.saberInFlight )
03332 {
03333 WP_ActivateSaber(NPC);
03334 }
03335
03336
03337 parryReCalcTime = Jedi_ReCalcParryTime( NPC, evasionType );
03338 TIMER_Set( NPC, "parryReCalcTime", Q_irand( 0, parryReCalcTime ) );
03339 if ( d_JediAI.integer )
03340 {
03341 Com_Printf( "Keep parry choice until: %d\n", level.time + parryReCalcTime );
03342 }
03343
03344
03345 if ( TIMER_Done( NPC, "parryTime" ) )
03346 {
03347 if ( NPC->client->NPC_class == CLASS_TAVION )
03348 {
03349 TIMER_Set( NPC, "parryTime", Q_irand( parryReCalcTime/2, parryReCalcTime*1.5 ) );
03350 }
03351 else if ( NPCInfo->rank >= RANK_LT_JG )
03352 {
03353 TIMER_Set( NPC, "parryTime", parryReCalcTime );
03354 }
03355 else
03356 {
03357 TIMER_Set( NPC, "parryTime", Q_irand( 1, 2 )*parryReCalcTime );
03358 }
03359 }
03360 }
03361 else
03362 {
03363 int dodgeTime = NPC->client->ps.torsoTimer;
03364 if ( NPCInfo->rank > RANK_LT_COMM && NPC->client->NPC_class != CLASS_DESANN )
03365 {
03366 dodgeTime -= 200;
03367 }
03368 TIMER_Set( NPC, "parryReCalcTime", dodgeTime );
03369 TIMER_Set( NPC, "parryTime", dodgeTime );
03370 }
03371 return qtrue;
03372 }
03373
03374
03375
03376
03377
03378
03379
03380 static void Jedi_EvasionSaber( vec3_t enemy_movedir, float enemy_dist, vec3_t enemy_dir )
03381 {
03382 vec3_t dirEnemy2Me;
03383 int evasionChance = 30;
03384 qboolean enemy_attacking = qfalse;
03385 qboolean throwing_saber = qfalse;
03386 qboolean shooting_lightning = qfalse;
03387
03388 if ( !NPC->enemy->client )
03389 {
03390 return;
03391 }
03392 else if ( NPC->enemy->client
03393 && NPC->enemy->s.weapon == WP_SABER
03394 && NPC->enemy->client->ps.saberLockTime > level.time )
03395 {
03396 return;
03397 }
03398 else if ( NPC->client->ps.saberEventFlags&SEF_LOCK_WON && NPC->enemy->painDebounceTime > level.time )
03399 {
03400 return;
03401 }
03402
03403 if ( NPC->enemy->client->ps.saberInFlight && !TIMER_Done( NPC, "taunting" ) )
03404 {
03405 TIMER_Set( NPC, "taunting", -level.time );
03406 if ( !NPC->client->ps.saberInFlight )
03407 {
03408 WP_ActivateSaber(NPC);
03409 }
03410 }
03411
03412 if ( TIMER_Done( NPC, "parryTime" ) )
03413 {
03414 if ( NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE &&
03415 NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
03416 {
03417 NPC->client->ps.saberBlocked = BLOCKED_NONE;
03418 }
03419 }
03420
03421 if ( NPC->enemy->client->ps.weaponTime && NPC->enemy->client->ps.weaponstate == WEAPON_FIRING )
03422 {
03423 if ( !NPC->client->ps.saberInFlight && Jedi_SaberBlock(0, 0) )
03424 {
03425 return;
03426 }
03427 }
03428
03429 VectorSubtract( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin, dirEnemy2Me );
03430 VectorNormalize( dirEnemy2Me );
03431
03432 if ( NPC->enemy->client->ps.weaponTime && NPC->enemy->client->ps.weaponstate == WEAPON_FIRING )
03433 {
03434 enemy_attacking = qtrue;
03435 evasionChance = 90;
03436 }
03437
03438 if ( (NPC->enemy->client->ps.fd.forcePowersActive&(1<<FP_LIGHTNING) ) )
03439 {
03440 enemy_attacking = qtrue;
03441 shooting_lightning = qtrue;
03442 evasionChance = 50;
03443 }
03444
03445 if ( NPC->enemy->client->ps.saberInFlight && NPC->enemy->client->ps.saberEntityNum != ENTITYNUM_NONE && NPC->enemy->client->ps.saberEntityState != SES_RETURNING )
03446 {
03447 enemy_attacking = qtrue;
03448 throwing_saber = qtrue;
03449 }
03450
03451
03452 if ( Q_irand( 0, 100 ) < evasionChance )
03453 {
03454 float facingAmt;
03455 if ( VectorCompare( enemy_movedir, vec3_origin ) || shooting_lightning || throwing_saber )
03456 {
03457 vec3_t enemy_fwd;
03458 AngleVectors( NPC->enemy->client->ps.viewangles, enemy_fwd, NULL, NULL );
03459 facingAmt = DotProduct( enemy_fwd, dirEnemy2Me );
03460 }
03461 else
03462 {
03463 facingAmt = DotProduct( enemy_movedir, dirEnemy2Me );
03464 }
03465
03466 if ( flrand( 0.25, 1 ) < facingAmt )
03467 {
03468 int whichDefense = 0;
03469 if ( NPC->client->ps.weaponTime || NPC->client->ps.saberInFlight || NPC->client->NPC_class == CLASS_BOBAFETT )
03470 {
03471 if ( Q_irand( 0, 10 ) < NPCInfo->stats.aggression )
03472 {
03473 return;
03474 }
03475 whichDefense = 100;
03476 }
03477 else
03478 {
03479 if ( shooting_lightning )
03480 {
03481
03482 whichDefense = 100;
03483 }
03484 else if ( throwing_saber )
03485 {
03486 float saberDist;
03487 vec3_t saberDir2Me;
03488 vec3_t saberMoveDir;
03489 gentity_t *saber = &g_entities[NPC->enemy->client->ps.saberEntityNum];
03490 VectorSubtract( NPC->r.currentOrigin, saber->r.currentOrigin, saberDir2Me );
03491 saberDist = VectorNormalize( saberDir2Me );
03492 VectorCopy( saber->s.pos.trDelta, saberMoveDir );
03493 VectorNormalize( saberMoveDir );
03494 if ( !Q_irand( 0, 3 ) )
03495 {
03496
03497 Jedi_Aggression( NPC, 1 );
03498 }
03499 if ( DotProduct( saberMoveDir, saberDir2Me ) > 0.5 )
03500 {
03501 if ( saberDist < 100 )
03502 {
03503 whichDefense = Q_irand( 3, 6 );
03504 }
03505 else if ( saberDist < 200 )
03506 {
03507 whichDefense = Q_irand( 0, 8 );
03508 }
03509 }
03510 }
03511 if ( whichDefense )
03512 {
03513 }
03514 else if ( enemy_dist > 80 || !enemy_attacking )
03515 {
03516 if ( VectorCompare( enemy_movedir, vec3_origin ) )
03517 {
03518 return;
03519 }
03520 if ( Q_irand( 0, 10 ) < NPCInfo->stats.aggression )
03521 {
03522 return;
03523 }
03524 whichDefense = 100;
03525 }
03526 else
03527 {
03528 vec3_t fwd;
03529
03530 AngleVectors( NPC->client->ps.viewangles, fwd, NULL, NULL );
03531 if ( DotProduct( enemy_dir, fwd ) < 0.5 )
03532 {
03533 whichDefense = Q_irand( 5, 16 );
03534 }
03535 else if ( enemy_dist < 56 )
03536 {
03537 whichDefense = Q_irand( NPCInfo->stats.aggression, 12 );
03538 }
03539 else
03540 {
03541 whichDefense = Q_irand( 2, 16 );
03542 }
03543 }
03544 }
03545
03546 if ( whichDefense >= 4 && whichDefense <= 12 )
03547 {
03548 if ( NPC->client->ps.saberInFlight )
03549 {
03550 whichDefense = 100;
03551 }
03552 }
03553
03554 switch( whichDefense )
03555 {
03556 case 0:
03557 case 1:
03558 case 2:
03559 case 3:
03560
03561
03562 if ( (NPCInfo->rank == RANK_ENSIGN || NPCInfo->rank > RANK_LT_JG) && TIMER_Done( NPC, "parryTime" ) )
03563 {
03564 ForceThrow( NPC, qfalse );
03565 }
03566 break;
03567 case 4:
03568 case 5:
03569 case 6:
03570 case 7:
03571 case 8:
03572 case 9:
03573 case 10:
03574 case 11:
03575 case 12:
03576
03577
03578 Jedi_SaberBlock(0, 0);
03579 break;
03580 default:
03581
03582
03583 if ( !Q_irand( 0, 5 ) || !Jedi_Strafe( 300, 1000, 0, 1000, qfalse ) )
03584 {
03585
03586 if ( shooting_lightning || throwing_saber || enemy_dist < 80 )
03587 {
03588
03589 if ( shooting_lightning || (!Q_irand( 0, 2 ) && NPCInfo->stats.aggression < 4 && TIMER_Done( NPC, "parryTime" ) ) )
03590 {
03591 if ( (NPCInfo->rank == RANK_ENSIGN || NPCInfo->rank > RANK_LT_JG) && !shooting_lightning && Q_irand( 0, 2 ) )
03592 {
03593 ForceThrow( NPC, qfalse );
03594 }
03595 else if ( (NPCInfo->rank==RANK_CREWMAN||NPCInfo->rank>RANK_LT_JG)
03596 && !(NPCInfo->scriptFlags&SCF_NO_ACROBATICS)
03597 && NPC->client->ps.fd.forceRageRecoveryTime < level.time
03598 && !(NPC->client->ps.fd.forcePowersActive&(1<<FP_RAGE))
03599 && !PM_InKnockDown( &NPC->client->ps ) )
03600 {
03601
03602 NPC->client->ps.fd.forceJumpCharge = 480;
03603
03604 TIMER_Set( NPC, "jumpChaseDebounce", Q_irand( 2000, 5000 ) );
03605 if ( Q_irand( 0, 2 ) )
03606 {
03607 ucmd.forwardmove = 127;
03608 VectorClear( NPC->client->ps.moveDir );
03609 }
03610 else
03611 {
03612 ucmd.forwardmove = -127;
03613 VectorClear( NPC->client->ps.moveDir );
03614 }
03615
03616 if ( Q_irand( 0, 1 ) )
03617 {
03618 NPC->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
03619 }
03620 else
03621 {
03622 NPC->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
03623 }
03624 }
03625 }
03626 else if ( enemy_attacking )
03627 {
03628 Jedi_SaberBlock(0, 0);
03629 }
03630 }
03631 }
03632 else
03633 {
03634 if ( d_JediAI.integer )
03635 {
03636 Com_Printf( "def strafe\n" );
03637 }
03638 if ( !(NPCInfo->scriptFlags&SCF_NO_ACROBATICS)
03639 && NPC->client->ps.fd.forceRageRecoveryTime < level.time
03640 && !(NPC->client->ps.fd.forcePowersActive&(1<<FP_RAGE))
03641 && (NPCInfo->rank == RANK_CREWMAN || NPCInfo->rank > RANK_LT_JG )
03642 && !PM_InKnockDown( &NPC->client->ps )
03643 && !Q_irand( 0, 5 ) )
03644 {
03645
03646 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
03647 {
03648 NPC->client->ps.fd.forceJumpCharge = 280;
03649 }
03650 else
03651 {
03652 NPC->client->ps.fd.forceJumpCharge = 320;
03653 }
03654
03655 TIMER_Set( NPC, "jumpChaseDebounce", Q_irand( 2000, 5000 ) );
03656 }
03657 }
03658 break;
03659 }
03660
03661
03662 TIMER_Set( NPC, "walking", -level.time );
03663 TIMER_Set( NPC, "taunting", -level.time );
03664 }
03665 }
03666 }
03667
03668
03669
03670
03671
03672
03673
03674
03675
03676
03677
03678
03679
03680
03681
03682
03683
03684
03685
03686 gentity_t *Jedi_FindEnemyInCone( gentity_t *self, gentity_t *fallback, float minDot )
03687 {
03688 vec3_t forward, mins, maxs, dir;
03689 float dist, bestDist = Q3_INFINITE;
03690 gentity_t *enemy = fallback;
03691 gentity_t *check = NULL;
03692 int entityList[MAX_GENTITIES];
03693 int e, numListedEntities;
03694 trace_t tr;
03695
03696 if ( !self->client )
03697 {
03698 return enemy;
03699 }
03700
03701 AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
03702
03703 for ( e = 0 ; e < 3 ; e++ )
03704 {
03705 mins[e] = self->r.currentOrigin[e] - 1024;
03706 maxs[e] = self->r.currentOrigin[e] + 1024;
03707 }
03708 numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
03709
03710 for ( e = 0 ; e < numListedEntities ; e++ )
03711 {
03712 check = &g_entities[entityList[e]];
03713 if ( check == self )
03714 {
03715 continue;
03716 }
03717 if ( !(check->inuse) )
03718 {
03719 continue;
03720 }
03721 if ( !check->client )
03722 {
03723 continue;
03724 }
03725 if ( check->client->playerTeam != self->client->enemyTeam )
03726 {
03727 continue;
03728 }
03729 if ( check->health <= 0 )
03730 {
03731 continue;
03732 }
03733
03734 if ( !trap_InPVS( check->r.currentOrigin, self->r.currentOrigin ) )
03735 {
03736 continue;
03737 }
03738
03739 VectorSubtract( check->r.currentOrigin, self->r.currentOrigin, dir );
03740 dist = VectorNormalize( dir );
03741
03742 if ( DotProduct( dir, forward ) < minDot )
03743 {
03744 continue;
03745 }
03746
03747
03748 trap_Trace( &tr, self->r.currentOrigin, vec3_origin, vec3_origin, check->r.currentOrigin, self->s.number, MASK_SHOT );
03749 if ( tr.fraction < 1.0f && tr.entityNum != check->s.number )
03750 {
03751 continue;
03752 }
03753
03754 if ( dist < bestDist )
03755 {
03756 dist = bestDist;
03757 enemy = check;
03758 }
03759 }
03760 return enemy;
03761 }
03762
03763 static void Jedi_SetEnemyInfo( vec3_t enemy_dest, vec3_t enemy_dir, float *enemy_dist, vec3_t enemy_movedir, float *enemy_movespeed, int prediction )
03764 {
03765 if ( !NPC || !NPC->enemy )
03766 {
03767 return;
03768 }
03769 if ( !NPC->enemy->client )
03770 {
03771 VectorClear( enemy_movedir );
03772 *enemy_movespeed = 0;
03773 VectorCopy( NPC->enemy->r.currentOrigin, enemy_dest );
03774 enemy_dest[2] += NPC->enemy->r.mins[2] + 24;
03775 VectorSubtract( enemy_dest, NPC->r.currentOrigin, enemy_dir );
03776
03777 *enemy_dist = VectorNormalize( enemy_dir );
03778 }
03779 else
03780 {
03781 VectorCopy( NPC->enemy->client->ps.velocity, enemy_movedir );
03782 *enemy_movespeed = VectorNormalize( enemy_movedir );
03783
03784 VectorMA( NPC->enemy->r.currentOrigin, *enemy_movespeed * 0.001 * prediction, enemy_movedir, enemy_dest );
03785
03786 VectorSubtract( enemy_dest, NPC->r.currentOrigin, enemy_dir );
03787
03788 *enemy_dist = VectorNormalize( enemy_dir ) - (NPC->client->saber[0].blade[0].lengthMax + NPC->r.maxs[0]*1.5 + 16);
03789
03790
03791
03792
03793
03794
03795 }
03796 }
03797
03798 extern float WP_SpeedOfMissileForWeapon( int wp, qboolean alt_fire );
03799 static void Jedi_FaceEnemy( qboolean doPitch )
03800 {
03801 vec3_t enemy_eyes, eyes, angles;
03802
03803 if ( NPC == NULL )
03804 return;
03805
03806 if ( NPC->enemy == NULL )
03807 return;
03808
03809 if ( NPC->client->ps.fd.forcePowersActive & (1<<FP_GRIP) &&
03810 NPC->client->ps.fd.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
03811 {
03812 NPCInfo->desiredPitch = NPC->client->ps.viewangles[PITCH];
03813 NPCInfo->desiredYaw = NPC->client->ps.viewangles[YAW];
03814 return;
03815 }
03816 CalcEntitySpot( NPC, SPOT_HEAD, eyes );
03817
03818 CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_eyes );
03819
03820 if ( NPC->client->NPC_class == CLASS_BOBAFETT
03821 && TIMER_Done( NPC, "flameTime" )
03822 && NPC->s.weapon != WP_NONE
03823 && NPC->s.weapon != WP_DISRUPTOR
03824 && (NPC->s.weapon != WP_ROCKET_LAUNCHER||!(NPCInfo->scriptFlags&SCF_ALT_FIRE))
03825 && NPC->s.weapon != WP_THERMAL
03826 && NPC->s.weapon != WP_TRIP_MINE
03827 && NPC->s.weapon != WP_DET_PACK
03828 && NPC->s.weapon != WP_STUN_BATON
03829 )
03830 {
03831 if ( NPC->health < NPC->client->pers.maxHealth*0.5f )
03832 {
03833 float missileSpeed = WP_SpeedOfMissileForWeapon( NPC->s.weapon, ((qboolean)(NPCInfo->scriptFlags&SCF_ALT_FIRE)) );
03834 if ( missileSpeed )
03835 {
03836 float eDist = Distance( eyes, enemy_eyes );
03837 eDist /= missileSpeed;
03838 VectorMA( enemy_eyes, eDist*flrand(0.95f,1.25f), NPC->enemy->client->ps.velocity, enemy_eyes );
03839 }
03840 }
03841 }
03842
03843
03844 if ( !NPC->client->ps.saberInFlight
03845 && (NPC->client->ps.legsAnim == BOTH_A2_STABBACK1
03846 || NPC->client->ps.legsAnim == BOTH_CROUCHATTACKBACK1
03847 || NPC->client->ps.legsAnim == BOTH_ATTACK_BACK)
03848 )
03849 {
03850 GetAnglesForDirection( enemy_eyes, eyes, angles );
03851 }
03852 else
03853 {
03854 GetAnglesForDirection( eyes, enemy_eyes, angles );
03855 }
03856
03857 NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] );
03858
03859
03860
03861
03862
03863
03864
03865 if ( doPitch )
03866 {
03867 NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] );
03868 if ( NPC->client->ps.saberInFlight )
03869 {
03870 NPCInfo->desiredPitch += 10;
03871 }
03872 }
03873
03874 }
03875
03876 static void Jedi_DebounceDirectionChanges( void )
03877 {
03878
03879
03880 if ( ucmd.forwardmove > 0 )
03881 {
03882 if ( !TIMER_Done( NPC, "moveback" ) || !TIMER_Done( NPC, "movenone" ) )
03883 {
03884 ucmd.forwardmove = 0;
03885
03886 if ( ucmd.rightmove > 0 )
03887 {
03888 ucmd.rightmove = 127;
03889 }
03890 else if ( ucmd.rightmove < 0 )
03891 {
03892 ucmd.rightmove = -127;
03893 }
03894 VectorClear( NPC->client->ps.moveDir );
03895 TIMER_Set( NPC, "moveback", -level.time );
03896 if ( TIMER_Done( NPC, "movenone" ) )
03897 {
03898 TIMER_Set( NPC, "movenone", Q_irand( 1000, 2000 ) );
03899 }
03900 }
03901 else if ( TIMER_Done( NPC, "moveforward" ) )
03902 {
03903 TIMER_Set( NPC, "moveforward", Q_irand( 500, 2000 ) );
03904 }
03905 }
03906 else if ( ucmd.forwardmove < 0 )
03907 {
03908 if ( !TIMER_Done( NPC, "moveforward" ) || !TIMER_Done( NPC, "movenone" ) )
03909 {
03910 ucmd.forwardmove = 0;
03911
03912 if ( ucmd.rightmove > 0 )
03913 {
03914 ucmd.rightmove = 127;
03915 }
03916 else if ( ucmd.rightmove < 0 )
03917 {
03918 ucmd.rightmove = -127;
03919 }
03920 VectorClear( NPC->client->ps.moveDir );
03921 TIMER_Set( NPC, "moveforward", -level.time );
03922 if ( TIMER_Done( NPC, "movenone" ) )
03923 {
03924 TIMER_Set( NPC, "movenone", Q_irand( 1000, 2000 ) );
03925 }
03926 }
03927 else if ( TIMER_Done( NPC, "moveback" ) )
03928 {
03929 TIMER_Set( NPC, "moveback", Q_irand( 250, 1000 ) );
03930 }
03931 }
03932 else if ( !TIMER_Done( NPC, "moveforward" ) )
03933 {
03934 ucmd.forwardmove = 127;
03935 VectorClear( NPC->client->ps.moveDir );
03936 }
03937 else if ( !TIMER_Done( NPC, "moveback" ) )
03938 {
03939 ucmd.forwardmove = -127;
03940 VectorClear( NPC->client->ps.moveDir );
03941 }
03942
03943 if ( ucmd.rightmove > 0 )
03944 {
03945 if ( !TIMER_Done( NPC, "moveleft" ) || !TIMER_Done( NPC, "movecenter" ) )
03946 {
03947 ucmd.rightmove = 0;
03948
03949 if ( ucmd.forwardmove > 0 )
03950 {
03951 ucmd.forwardmove = 127;
03952 }
03953 else if ( ucmd.forwardmove < 0 )
03954 {
03955 ucmd.forwardmove = -127;
03956 }
03957 VectorClear( NPC->client->ps.moveDir );
03958 TIMER_Set( NPC, "moveleft", -level.time );
03959 if ( TIMER_Done( NPC, "movecenter" ) )
03960 {
03961 TIMER_Set( NPC, "movecenter", Q_irand( 1000, 2000 ) );
03962 }
03963 }
03964 else if ( TIMER_Done( NPC, "moveright" ) )
03965 {
03966 TIMER_Set( NPC, "moveright", Q_irand( 250, 1500 ) );
03967 }
03968 }
03969 else if ( ucmd.rightmove < 0 )
03970 {
03971 if ( !TIMER_Done( NPC, "moveright" ) || !TIMER_Done( NPC, "movecenter" ) )
03972 {
03973 ucmd.rightmove = 0;
03974
03975 if ( ucmd.forwardmove > 0 )
03976 {
03977 ucmd.forwardmove = 127;
03978 }
03979 else if ( ucmd.forwardmove < 0 )
03980 {
03981 ucmd.forwardmove = -127;
03982 }
03983 VectorClear( NPC->client->ps.moveDir );
03984 TIMER_Set( NPC, "moveright", -level.time );
03985 if ( TIMER_Done( NPC, "movecenter" ) )
03986 {
03987 TIMER_Set( NPC, "movecenter", Q_irand( 1000, 2000 ) );
03988 }
03989 }
03990 else if ( TIMER_Done( NPC, "moveleft" ) )
03991 {
03992 TIMER_Set( NPC, "moveleft", Q_irand( 250, 1500 ) );
03993 }
03994 }
03995 else if ( !TIMER_Done( NPC, "moveright" ) )
03996 {
03997 ucmd.rightmove = 127;
03998 VectorClear( NPC->client->ps.moveDir );
03999 }
04000 else if ( !TIMER_Done( NPC, "moveleft" ) )
04001 {
04002 ucmd.rightmove = -127;
04003 VectorClear( NPC->client->ps.moveDir );
04004 }
04005 }
04006
04007 static void Jedi_TimersApply( void )
04008 {
04009 if ( !ucmd.rightmove )
04010 {
04011
04012 if ( !TIMER_Done( NPC, "strafeLeft" ) )
04013 {
04014 if ( NPCInfo->desiredYaw > NPC->client->ps.viewangles[YAW] + 60 )
04015 {
04016 }
04017 else
04018 {
04019 ucmd.rightmove = -127;
04020 VectorClear( NPC->client->ps.moveDir );
04021 }
04022 }
04023 else if ( !TIMER_Done( NPC, "strafeRight" ) )
04024 {
04025 if ( NPCInfo->desiredYaw < NPC->client->ps.viewangles[YAW] - 60 )
04026 {
04027 }
04028 else
04029 {
04030 ucmd.rightmove = 127;
04031 VectorClear( NPC->client->ps.moveDir );
04032 }
04033 }
04034 }
04035
04036 Jedi_DebounceDirectionChanges();
04037
04038
04039 if ( !ucmd.forwardmove && !TIMER_Done( NPC, "walking" ) )
04040 {
04041 ucmd.buttons |= (BUTTON_WALKING);
04042 }
04043
04044 if ( !TIMER_Done( NPC, "taunting" ) )
04045 {
04046 ucmd.buttons |= (BUTTON_WALKING);
04047 }
04048
04049 if ( !TIMER_Done( NPC, "gripping" ) )
04050 {
04051
04052 ucmd.buttons |= BUTTON_FORCEGRIP;
04053 }
04054
04055 if ( !TIMER_Done( NPC, "draining" ) )
04056 {
04057
04058 ucmd.buttons |= BUTTON_FORCE_DRAIN;
04059 }
04060
04061 if ( !TIMER_Done( NPC, "holdLightning" ) )
04062 {
04063 ucmd.buttons |= BUTTON_FORCE_LIGHTNING;
04064 }
04065 }
04066
04067 static void Jedi_CombatTimersUpdate( int enemy_dist )
04068 {
04069 if ( TIMER_Done( NPC, "roamTime" ) )
04070 {
04071 TIMER_Set( NPC, "roamTime", Q_irand( 2000, 5000 ) );
04072
04073 if ( NPC->client->ps.fd.forcePowersActive&(1<<FP_RAGE) )
04074 {
04075 Jedi_Aggression( NPC, Q_irand( 0, 3 ) );
04076 }
04077 else if ( NPC->client->ps.fd.forceRageRecoveryTime > level.time )
04078 {
04079 Jedi_Aggression( NPC, Q_irand( 0, -2 ) );
04080 }
04081 if ( NPC->enemy && NPC->enemy->client )
04082 {
04083 switch( NPC->enemy->client->ps.weapon )
04084 {
04085 case WP_SABER:
04086
04087 if ( BG_SabersOff( &NPC->enemy->client->ps ) )
04088 {
04089
04090 Jedi_Aggression( NPC, 2 );
04091 }
04092 else
04093 {
04094
04095 Jedi_Aggression( NPC, 1 );
04096 }
04097 break;
04098 case WP_BLASTER:
04099 case WP_BRYAR_PISTOL:
04100 case WP_DISRUPTOR:
04101 case WP_BOWCASTER:
04102 case WP_REPEATER:
04103 case WP_DEMP2:
04104 case WP_FLECHETTE:
04105 case WP_ROCKET_LAUNCHER:
04106
04107
04108 if ( NPC->enemy->attackDebounceTime < level.time )
04109 {
04110
04111 Jedi_Aggression( NPC, 1 );
04112 }
04113
04114 if ( enemy_dist < 256 )
04115 {
04116
04117 Jedi_Aggression( NPC, 1 );
04118 }
04119 break;
04120 default:
04121 break;
04122 }
04123 }
04124 }
04125
04126 if ( TIMER_Done( NPC, "noStrafe" ) && TIMER_Done( NPC, "strafeLeft" ) && TIMER_Done( NPC, "strafeRight" ) )
04127 {
04128
04129 if ( !Q_irand( 0, 4 ) )
04130 {
04131 if ( Jedi_Strafe( 1000, 3000, 0, 4000, qtrue ) )
04132 {
04133 if ( d_JediAI.integer )
04134 {
04135 Com_Printf( "off strafe\n" );
04136 }
04137 }
04138 }
04139 else
04140 {
04141 TIMER_Set( NPC, "noStrafe", Q_irand( 1000, 3000 ) );
04142 }
04143 }
04144
04145 if ( NPC->client->ps.saberEventFlags )
04146 {
04147 int newFlags = NPC->client->ps.saberEventFlags;
04148 if ( NPC->client->ps.saberEventFlags&SEF_PARRIED )
04149 {
04150 TIMER_Set( NPC, "parryTime", -1 );
04151
04152
04153
04154
04155
04156
04157
04158
04159
04160
04161 if ( NPC->enemy && PM_SaberInKnockaway( NPC->enemy->client->ps.saberMove ) )
04162 {
04163 Jedi_Aggression( NPC, 1 );
04164 Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel-1) );
04165 }
04166 else
04167 {
04168 if ( !Q_irand( 0, 1 ) )
04169 {
04170
04171 Jedi_Aggression( NPC, -1 );
04172 }
04173 if ( !Q_irand( 0, 1 ) )
04174 {
04175 Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel-1) );
04176 }
04177 }
04178 if ( d_JediAI.integer )
04179 {
04180 Com_Printf( "(%d) PARRY: agg %d, no parry until %d\n", level.time, NPCInfo->stats.aggression, level.time + 100 );
04181 }
04182 newFlags &= ~SEF_PARRIED;
04183 }
04184 if ( !NPC->client->ps.weaponTime && (NPC->client->ps.saberEventFlags&SEF_HITENEMY) )
04185 {
04186 if ( !Q_irand( 0, 1 ) )
04187 {
04188
04189 Jedi_Aggression( NPC, -1 );
04190 if ( d_JediAI.integer )
04191 {
04192 Com_Printf( "(%d) HIT: agg %d\n", level.time, NPCInfo->stats.aggression );
04193 }
04194 if ( !Q_irand( 0, 3 )
04195 && NPCInfo->blockedSpeechDebounceTime < level.time
04196 && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time
04197 && NPC->painDebounceTime < level.time - 1000 )
04198 {
04199 G_AddVoiceEvent( NPC, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 3000 );
04200 jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000;
04201 }
04202 }
04203 if ( !Q_irand( 0, 2 ) )
04204 {
04205 Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel+1) );
04206 }
04207 newFlags &= ~SEF_HITENEMY;
04208 }
04209 if ( (NPC->client->ps.saberEventFlags&SEF_BLOCKED) )
04210 {
04211 if ( PM_SaberInBrokenParry( NPC->client->ps.saberMove )
04212 || NPC->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
04213 {
04214
04215 if ( NPC->client->ps.saberInFlight )
04216 {
04217 Jedi_Aggression( NPC, -5 );
04218 }
04219 else
04220 {
04221 Jedi_Aggression( NPC, -2 );
04222 }
04223 Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel+1) );
04224 if ( d_JediAI.integer )
04225 {
04226 Com_Printf( "(%d) KNOCK-BLOCKED: agg %d\n", level.time, NPCInfo->stats.aggression );
04227 }
04228 }
04229 else
04230 {
04231 if ( !Q_irand( 0, 2 ) )
04232 {
04233
04234 Jedi_Aggression( NPC, -1 );
04235 if ( d_JediAI.integer )
04236 {
04237 Com_Printf( "(%d) BLOCKED: agg %d\n", level.time, NPCInfo->stats.aggression );
04238 }
04239 }
04240 if ( !Q_irand( 0, 1 ) )
04241 {
04242 Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel+1) );
04243 }
04244 }
04245 newFlags &= ~SEF_BLOCKED;
04246
04247
04248
04249
04250 }
04251 if ( NPC->client->ps.saberEventFlags&SEF_DEFLECTED )
04252 {
04253 newFlags &= ~SEF_DEFLECTED;
04254 if ( !Q_irand( 0, 3 ) )
04255 {
04256 Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel-1) );
04257 }
04258 }
04259 if ( NPC->client->ps.saberEventFlags&SEF_HITWALL )
04260 {
04261 newFlags &= ~SEF_HITWALL;
04262 }
04263 if ( NPC->client->ps.saberEventFlags&SEF_HITOBJECT )
04264 {
04265 if ( !Q_irand( 0, 3 ) )
04266 {
04267 Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel-1) );
04268 }
04269 newFlags &= ~SEF_HITOBJECT;
04270 }
04271 NPC->client->ps.saberEventFlags = newFlags;
04272 }
04273 }
04274
04275 static void Jedi_CombatIdle( int enemy_dist )
04276 {
04277 if ( !TIMER_Done( NPC, "parryTime" ) )
04278 {
04279 return;
04280 }
04281 if ( NPC->client->ps.saberInFlight )
04282 {
04283 return;
04284 }
04285 if ( NPC->client->ps.fd.forcePowersActive&(1<<FP_RAGE)
04286 || NPC->client->ps.fd.forceRageRecoveryTime > level.time )
04287 {
04288 return;
04289 }
04290
04291 if ( enemy_dist >= 64 )
04292 {
04293
04294 int chance = 20;
04295 if ( NPC->client->NPC_class == CLASS_SHADOWTROOPER )
04296 {
04297 chance = 10;
04298 }
04299
04300 if ( Q_irand( 2, chance ) < NPCInfo->stats.aggression )
04301 {
04302 if ( TIMER_Done( NPC, "chatter" ) && NPC->client->ps.forceHandExtend == HANDEXTEND_NONE )
04303 {
04304
04305 if ( enemy_dist > 200
04306 && NPC->client->NPC_class != CLASS_BOBAFETT
04307 && !NPC->client->ps.saberHolstered
04308 && !Q_irand( 0, 5 ) )
04309 {
04310
04311 WP_DeactivateSaber( NPC, qfalse );
04312
04313 NPCInfo->stats.aggression = 3;
04314
04315
04316 if ( NPC->client->playerTeam != NPCTEAM_PLAYER && !Q_irand( 0, 1 ))
04317 {
04318
04319 NPC->client->ps.forceHandExtend = HANDEXTEND_JEDITAUNT;
04320 NPC->client->ps.forceHandExtendTime = level.time + 5000;
04321
04322 TIMER_Set( NPC, "chatter", Q_irand( 5000, 10000 ) );
04323 TIMER_Set( NPC, "taunting", 5500 );
04324 }
04325 else
04326 {
04327 Jedi_BattleTaunt();
04328 TIMER_Set( NPC, "taunting", Q_irand( 5000, 10000 ) );
04329 }
04330 }
04331 else if ( Jedi_BattleTaunt() )
04332 {
04333 }
04334 }
04335 }
04336 }
04337 }
04338
04339 static qboolean Jedi_AttackDecide( int enemy_dist )
04340 {
04341 if ( NPC->enemy->client
04342 && NPC->enemy->s.weapon == WP_SABER
04343 && NPC->enemy->client->ps.saberLockTime > level.time
04344 && NPC->client->ps.saberLockTime < level.time )
04345 {
04346 return qfalse;
04347 }
04348
04349 if ( NPC->client->ps.saberEventFlags&SEF_LOCK_WON )
04350 {
04351 int chance = 0;
04352 if ( NPC->client->NPC_class == CLASS_DESANN || NPC->client->NPC_class == CLASS_LUKE || !Q_stricmp("Yoda",NPC->NPC_type) )
04353 {
04354 chance = 20;
04355 }
04356 else if ( NPC->client->NPC_class == CLASS_TAVION )
04357 {
04358 chance = 10;
04359 }
04360 else if ( NPC->client->NPC_class == CLASS_REBORN && NPCInfo->rank == RANK_LT_JG )
04361 {
04362 chance = 5;
04363 }
04364 else
04365 {
04366 chance = NPCInfo->rank;
04367 }
04368 if ( Q_irand( 0, 30 ) < chance )
04369 {
04370 NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON;
04371 TIMER_Set( NPC, "noRetreat", Q_irand( 500, 2000 ) );
04372
04373 NPC->client->ps.weaponTime = NPCInfo->shotTime = NPC->attackDebounceTime = 0;
04374
04375 NPC->client->ps.saberBlocked = BLOCKED_NONE;
04376 WeaponThink( qtrue );
04377 return qtrue;
04378 }
04379 }
04380
04381 if ( NPC->client->NPC_class == CLASS_TAVION ||
04382 ( NPC->client->NPC_class == CLASS_REBORN && NPCInfo->rank == RANK_LT_JG ) ||
04383 ( NPC->client->NPC_class == CLASS_JEDI && NPCInfo->rank == RANK_COMMANDER ) )
04384 {
04385 if ( ( PM_SaberInParry( NPC->client->ps.saberMove ) || PM_SaberInKnockaway( NPC->client->ps.saberMove ) )
04386 && NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
04387 {
04388 NPC->client->ps.weaponTime = NPCInfo->shotTime = NPC->attackDebounceTime = 0;
04389
04390 NPC->client->ps.saberBlocked = BLOCKED_NONE;
04391 Jedi_AdjustSaberAnimLevel( NPC, FORCE_LEVEL_1 );
04392 WeaponThink( qtrue );
04393 return qtrue;
04394 }
04395 }
04396
04397
04398 if ( enemy_dist >= 64 )
04399 {
04400 return qfalse;
04401 }
04402
04403 if ( !TIMER_Done( NPC, "parryTime" ) )
04404 {
04405 return qfalse;
04406 }
04407
04408 if ( (NPCInfo->scriptFlags&SCF_DONT_FIRE) )
04409 {
04410 return qfalse;
04411 }
04412
04413 if ( !(ucmd.buttons&BUTTON_ATTACK) && !(ucmd.buttons&BUTTON_ALT_ATTACK) )
04414 {
04415
04416 WeaponThink( qtrue );
04417 }
04418
04419
04420
04421
04422
04423
04424
04425
04426 if ( ucmd.buttons&BUTTON_ATTACK )
04427 {
04428
04429
04430
04431
04432
04433
04434
04435
04436
04437
04438
04439
04440
04441
04442
04443 if ( !ucmd.rightmove )
04444 {
04445 if ( !Q_irand( 0, 3 ) )
04446 {
04447 vec3_t right, dir2enemy;
04448
04449 AngleVectors( NPC->r.currentAngles, NULL, right, NULL );
04450 VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentAngles, dir2enemy );
04451 if ( DotProduct( right, dir2enemy ) > 0 )
04452 {
04453 ucmd.rightmove = -127;
04454 VectorClear( NPC->client->ps.moveDir );
04455 }
04456 else
04457 {
04458 ucmd.rightmove = 127;
04459 VectorClear( NPC->client->ps.moveDir );
04460 }
04461 }
04462 }
04463 return qtrue;
04464 }
04465
04466 return qfalse;
04467 }
04468
04469 #define APEX_HEIGHT 200.0f
04470 #define PARA_WIDTH (sqrt(APEX_HEIGHT)+sqrt(APEX_HEIGHT))
04471 #define JUMP_SPEED 200.0f
04472
04473 static qboolean Jedi_Jump( vec3_t dest, int goalEntNum )
04474 {
04475
04476
04477
04478
04479
04480
04481
04482
04483
04484
04485
04486
04487
04488
04489
04490
04491
04492
04493
04494
04495
04496
04497 if ( 1 )
04498 {
04499 float targetDist, shotSpeed = 300, travelTime, impactDist, bestImpactDist = Q3_INFINITE;
04500 vec3_t targetDir, shotVel, failCase;
04501 trace_t trace;
04502 trajectory_t tr;
04503 qboolean blocked;
04504 int elapsedTime, timeStep = 500, hitCount = 0, maxHits = 7;
04505 vec3_t lastPos, testPos, bottom;
04506
04507 while ( hitCount < maxHits )
04508 {
04509 VectorSubtract( dest, NPC->r.currentOrigin, targetDir );
04510 targetDist = VectorNormalize( targetDir );
04511
04512 VectorScale( targetDir, shotSpeed, shotVel );
04513 travelTime = targetDist/shotSpeed;
04514 shotVel[2] += travelTime * 0.5 * NPC->client->ps.gravity;
04515
04516 if ( !hitCount )
04517 {
04518 VectorCopy( shotVel, failCase );
04519 }
04520
04521 if ( 1 )
04522 {
04523 blocked = qfalse;
04524
04525 VectorCopy( NPC->r.currentOrigin, tr.trBase );
04526 VectorCopy( shotVel, tr.trDelta );
04527 tr.trType = TR_GRAVITY;
04528 tr.trTime = level.time;
04529 travelTime *= 1000.0f;
04530 VectorCopy( NPC->r.currentOrigin, lastPos );
04531
04532
04533 for ( elapsedTime = timeStep; elapsedTime < floor(travelTime)+timeStep; elapsedTime += timeStep )
04534 {
04535 if ( (float)elapsedTime > travelTime )
04536 {
04537 elapsedTime = floor( travelTime );
04538 }
04539 BG_EvaluateTrajectory( &tr, level.time + elapsedTime, testPos );
04540 if ( testPos[2] < lastPos[2] )
04541 {
04542 trap_Trace( &trace, lastPos, NPC->r.mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask );
04543 }
04544 else
04545 {
04546 trap_Trace( &trace, lastPos, NPC->r.mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP );
04547 }
04548
04549 if ( trace.allsolid || trace.startsolid )
04550 {
04551 blocked = qtrue;
04552 break;
04553 }
04554 if ( trace.fraction < 1.0f )
04555 {
04556 if ( trace.entityNum == goalEntNum )
04557 {
04558
04559 break;
04560 }
04561 else
04562 {
04563 if ( trace.contents & CONTENTS_BOTCLIP )
04564 {
04565 blocked = qtrue;
04566 break;
04567 }
04568 if ( trace.plane.normal[2] > 0.7 && DistanceSquared( trace.endpos, dest ) < 4096 )
04569 {
04570 break;
04571 }
04572 else
04573 {
04574 impactDist = DistanceSquared( trace.endpos, dest );
04575 if ( impactDist < bestImpactDist )
04576 {
04577 bestImpactDist = impactDist;
04578 VectorCopy( shotVel, failCase );
04579 }
04580 blocked = qtrue;
04581 break;
04582 }
04583 }
04584 }
04585 if ( elapsedTime == floor( travelTime ) )
04586 {
04587 if ( trace.fraction >= 1.0f )
04588 {
04589
04590 VectorCopy( trace.endpos, bottom );
04591 bottom[2] -= 128;
04592 trap_Trace( &trace, trace.endpos, NPC->r.mins, NPC->r.maxs, bottom, NPC->s.number, NPC->clipmask );
04593 if ( trace.fraction >= 1.0f )
04594 {
04595 blocked = qtrue;
04596 }
04597 }
04598 break;
04599 }
04600 else
04601 {
04602
04603 VectorCopy( testPos, lastPos );
04604 }
04605 }
04606 if ( blocked )
04607 {
04608 hitCount++;
04609 shotSpeed = 300 + ((hitCount-2) * 100);
04610 if ( hitCount >= 2 )
04611 {
04612 shotSpeed += 100;
04613 }
04614 }
04615 else
04616 {
04617 break;
04618 }
04619 }
04620 else
04621 {
04622 break;
04623 }
04624 }
04625
04626 if ( hitCount >= maxHits )
04627 {
04628
04629 VectorCopy( failCase, NPC->client->ps.velocity );
04630 }
04631 VectorCopy( shotVel, NPC->client->ps.velocity );
04632 }
04633 else
04634 {
04635 vec3_t dir, p1, p2, apex;
04636 float time, height, forward, z, xy, dist, apexHeight;
04637
04638 if ( NPC->r.currentOrigin[2] > dest[2] )
04639 {
04640 VectorCopy( NPC->r.currentOrigin, p1 );
04641 VectorCopy( dest, p2 );
04642 }
04643 else if ( NPC->r.currentOrigin[2] < dest[2] )
04644 {
04645 VectorCopy( dest, p1 );
04646 VectorCopy( NPC->r.currentOrigin, p2 );
04647 }
04648 else
04649 {
04650 VectorCopy( NPC->r.currentOrigin, p1 );
04651 VectorCopy( dest, p2 );
04652 }
04653
04654
04655 VectorSubtract( p2, p1, dir );
04656 dir[2] = 0;
04657
04658
04659 xy = VectorNormalize( dir );
04660 z = p1[2] - p2[2];
04661
04662 apexHeight = APEX_HEIGHT/2;
04663
04664
04665
04666
04667
04668
04669
04670
04671
04672
04673
04674
04675
04676
04677
04678
04679 z = (sqrt(apexHeight + z) - sqrt(apexHeight));
04680
04681 assert(z >= 0);
04682
04683
04684
04685 xy -= z;
04686 xy *= 0.5;
04687
04688 assert(xy > 0);
04689
04690 VectorMA( p1, xy, dir, apex );
04691 apex[2] += apexHeight;
04692
04693 VectorCopy(apex, NPC->pos1);
04694
04695
04696 height = apex[2] - NPC->r.currentOrigin[2];
04697 time = sqrt( height / ( .5 * NPC->client->ps.gravity ) );
04698 if ( !time )
04699 {
04700
04701 return qfalse;
04702 }
04703
04704 VectorSubtract ( apex, NPC->r.currentOrigin, NPC->client->ps.velocity );
04705 NPC->client->ps.velocity[2] = 0;
04706 dist = VectorNormalize( NPC->client->ps.velocity );
04707
04708 forward = dist / time * 1.25;
04709 VectorScale( NPC->client->ps.velocity, forward, NPC->client->ps.velocity );
04710
04711
04712 NPC->client->ps.velocity[2] = time * NPC->client->ps.gravity;
04713
04714
04715 }
04716 return qtrue;
04717 }
04718
04719 static qboolean Jedi_TryJump( gentity_t *goal )
04720 {
04721
04722 if ( (NPCInfo->scriptFlags&SCF_NO_ACROBATICS) )
04723 {
04724 return qfalse;
04725 }
04726 if ( TIMER_Done( NPC, "jumpChaseDebounce" ) )
04727 {
04728 if ( (!goal->client || goal->client->ps.groundEntityNum != ENTITYNUM_NONE) )
04729 {
04730 if ( !PM_InKnockDown( &NPC->client->ps ) && !BG_InRoll( &NPC->client->ps, NPC->client->ps.legsAnim ) )
04731 {
04732 vec3_t goal_diff;
04733 float goal_z_diff;
04734 float goal_xy_dist;
04735 VectorSubtract( goal->r.currentOrigin, NPC->r.currentOrigin, goal_diff );
04736 goal_z_diff = goal_diff[2];
04737 goal_diff[2] = 0;
04738 goal_xy_dist = VectorNormalize( goal_diff );
04739 if ( goal_xy_dist < 550 && goal_z_diff > -400 )
04740 {
04741 qboolean debounce = qfalse;
04742 if ( NPC->health < 150 && ((NPC->health < 30 && goal_z_diff < 0) || goal_z_diff < -128 ) )
04743 {
04744 debounce = qtrue;
04745 }
04746 else if ( goal_z_diff < 32 && goal_xy_dist < 200 )
04747 {
04748 ucmd.upmove = 127;
04749 debounce = qtrue;
04750 }
04751 else
04752 {
04753
04754
04755
04756
04757
04758
04759
04760 if ( goal_z_diff > 0 || goal_xy_dist > 128 )
04761 {
04762
04763 vec3_t dest;
04764 VectorCopy( goal->r.currentOrigin, dest );
04765 if ( goal == NPC->enemy )
04766 {
04767 int sideTry = 0;
04768 while( sideTry < 10 )
04769 {
04770 trace_t trace;
04771 vec3_t bottom;
04772
04773 if ( Q_irand( 0, 1 ) )
04774 {
04775 dest[0] += NPC->enemy->r.maxs[0]*1.25;
04776 }
04777 else
04778 {
04779 dest[0] += NPC->enemy->r.mins[0]*1.25;
04780 }
04781 if ( Q_irand( 0, 1 ) )
04782 {
04783 dest[1] += NPC->enemy->r.maxs[1]*1.25;
04784 }
04785 else
04786 {
04787 dest[1] += NPC->enemy->r.mins[1]*1.25;
04788 }
04789 VectorCopy( dest, bottom );
04790 bottom[2] -= 128;
04791 trap_Trace( &trace, dest, NPC->r.mins, NPC->r.maxs, bottom, goal->s.number, NPC->clipmask );
04792 if ( trace.fraction < 1.0f )
04793 {
04794 break;
04795 }
04796 sideTry++;
04797 }
04798 if ( sideTry >= 10 )
04799 {
04800 VectorCopy( goal->r.currentOrigin, dest );
04801 }
04802 }
04803 if ( Jedi_Jump( dest, goal->s.number ) )
04804 {
04805
04806
04807
04808
04809
04810
04811
04812
04813
04814
04815
04816 {
04817 int jumpAnim;
04818
04819 if ( NPC->client->NPC_class == CLASS_BOBAFETT
04820 ||( NPCInfo->rank != RANK_CREWMAN && NPCInfo->rank <= RANK_LT_JG ) )
04821 {
04822 jumpAnim = BOTH_FORCEJUMP1;
04823 }
04824 else
04825 {
04826 jumpAnim = BOTH_FLIP_F;
04827 }
04828 NPC_SetAnim( NPC, SETANIM_BOTH, jumpAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
04829 }
04830
04831 NPC->client->ps.fd.forceJumpZStart = NPC->r.currentOrigin[2];
04832
04833
04834 NPC->client->ps.weaponTime = NPC->client->ps.torsoTimer;
04835 NPC->client->ps.fd.forcePowersActive |= ( 1 << FP_LEVITATION );
04836 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
04837 {
04838 G_SoundOnEnt( NPC, CHAN_ITEM, "sound/boba/jeton.wav" );
04839 NPC->client->jetPackTime = level.time + Q_irand( 1000, 3000 );
04840 }
04841 else
04842 {
04843 G_SoundOnEnt( NPC, CHAN_BODY, "sound/weapons/force/jump.wav" );
04844 }
04845
04846 TIMER_Set( NPC, "forceJumpChasing", Q_irand( 2000, 3000 ) );
04847 debounce = qtrue;
04848 }
04849 }
04850 }
04851 if ( debounce )
04852 {
04853
04854 TIMER_Set( NPC, "jumpChaseDebounce", Q_irand( 2000, 5000 ) );
04855 ucmd.forwardmove = 127;
04856 VectorClear( NPC->client->ps.moveDir );
04857 TIMER_Set( NPC, "duck", -level.time );
04858 return qtrue;
04859 }
04860 }
04861 }
04862 }
04863 }
04864 return qfalse;
04865 }
04866
04867 static qboolean Jedi_Jumping( gentity_t *goal )
04868 {
04869 if ( !TIMER_Done( NPC, "forceJumpChasing" ) && goal )
04870 {
04871
04872
04873 if (NPC->client->ps.groundEntityNum != ENTITYNUM_NONE)
04874 {
04875 TIMER_Set( NPC, "forceJumpChasing", 0 );
04876 }
04877 else
04878 {
04879 NPC_FaceEntity( goal, qtrue );
04880
04881
04882
04883
04884
04885
04886
04887
04888
04889
04890
04891
04892
04893
04894
04895
04896
04897
04898
04899
04900
04901
04902
04903
04904
04905
04906
04907
04908
04909
04910 return qtrue;
04911 }
04912 }
04913 return qfalse;
04914 }
04915
04916 extern void G_UcmdMoveForDir( gentity_t *self, usercmd_t *cmd, vec3_t dir );
04917 static void Jedi_CheckEnemyMovement( float enemy_dist )
04918 {
04919 if ( !NPC->enemy || !NPC->enemy->client )
04920 {
04921 return;
04922 }
04923
04924 if ( NPC->client->NPC_class != CLASS_TAVION
04925 && NPC->client->NPC_class != CLASS_DESANN
04926 && NPC->client->NPC_class != CLASS_LUKE
04927 && Q_stricmp("Yoda",NPC->NPC_type) )
04928 {
04929 if ( NPC->enemy->enemy && NPC->enemy->enemy == NPC )
04930 {
04931 if ( NPC->enemy->client->ps.legsAnim == BOTH_JUMPFLIPSLASHDOWN1 ||
04932 NPC->enemy->client->ps.legsAnim == BOTH_JUMPFLIPSTABDOWN )
04933 {
04934 if ( Q_irand( 0, NPCInfo->rank ) < RANK_LT )
04935 {
04936 ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0;
04937 VectorClear( NPC->client->ps.moveDir );
04938 NPC->client->ps.fd.forceJumpCharge = 0;
04939 TIMER_Set( NPC, "strafeLeft", -1 );
04940 TIMER_Set( NPC, "strafeRight", -1 );
04941 TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) );
04942 TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) );
04943 TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) );
04944 }
04945 }
04946 else if ( NPC->enemy->client->ps.legsAnim == BOTH_WALL_FLIP_BACK1
04947 || NPC->enemy->client->ps.legsAnim == BOTH_WALL_FLIP_RIGHT
04948 || NPC->enemy->client->ps.legsAnim == BOTH_WALL_FLIP_LEFT
04949 || NPC->enemy->client->ps.legsAnim == BOTH_WALL_RUN_LEFT_FLIP
04950 || NPC->enemy->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT_FLIP )
04951 {
04952 if ( NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE )
04953 {
04954 if ( enemy_dist < 256 )
04955 {
04956 if ( Q_irand( 0, NPCInfo->rank ) < RANK_LT )
04957 {
04958 vec3_t enemyFwd, dest, dir;
04959
04960
04961
04962
04963
04964
04965
04966
04967
04968
04969
04970
04971
04972 ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0;
04973 VectorClear( NPC->client->ps.moveDir );
04974 NPC->client->ps.fd.forceJumpCharge = 0;
04975 TIMER_Set( NPC, "strafeLeft", -1 );
04976 TIMER_Set( NPC, "strafeRight", -1 );
04977 TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) );
04978 TIMER_Set( NPC, "noturn", Q_irand( 250, 500 )*(3-g_spskill.integer) );
04979
04980 VectorCopy( NPC->enemy->client->ps.velocity, enemyFwd );
04981 VectorNormalize( enemyFwd );
04982 VectorMA( NPC->enemy->r.currentOrigin, -64, enemyFwd, dest );
04983 VectorSubtract( dest, NPC->r.currentOrigin, dir );
04984 if ( VectorNormalize( dir ) > 32 )
04985 {
04986 G_UcmdMoveForDir( NPC, &ucmd, dir );
04987 }
04988 else
04989 {
04990 TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) );
04991 TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) );
04992 }
04993 }
04994 }
04995 }
04996 }
04997 else if ( NPC->enemy->client->ps.legsAnim == BOTH_A2_STABBACK1 )
04998 {
04999 if ( enemy_dist < 256 && enemy_dist > 64 )
05000 {
05001 if ( !InFront( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin, NPC->enemy->r.currentAngles, 0.0f ) )
05002 {
05003 if ( !Q_irand( 0, NPCInfo->rank ) )
05004 {
05005 vec3_t enemyFwd, dest, dir;
05006
05007
05008 ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0;
05009 VectorClear( NPC->client->ps.moveDir );
05010 NPC->client->ps.fd.forceJumpCharge = 0;
05011 TIMER_Set( NPC, "strafeLeft", -1 );
05012 TIMER_Set( NPC, "strafeRight", -1 );
05013 TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) );
05014
05015 AngleVectors( NPC->enemy->r.currentAngles, enemyFwd, NULL, NULL );
05016 VectorMA( NPC->enemy->r.currentOrigin, -32, enemyFwd, dest );
05017 VectorSubtract( dest, NPC->r.currentOrigin, dir );
05018 if ( VectorNormalize( dir ) > 64 )
05019 {
05020 G_UcmdMoveForDir( NPC, &ucmd, dir );
05021 }
05022 else
05023 {
05024 TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) );
05025 TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) );
05026 }
05027 }
05028 }
05029 }
05030 }
05031 }
05032 }
05033
05034
05035
05036 }
05037
05038 static void Jedi_CheckJumps( void )
05039 {
05040 vec3_t jumpVel;
05041 trace_t trace;
05042 trajectory_t tr;
05043 vec3_t lastPos, testPos, bottom;
05044 int elapsedTime;
05045
05046 if ( (NPCInfo->scriptFlags&SCF_NO_ACROBATICS) )
05047 {
05048 NPC->client->ps.fd.forceJumpCharge = 0;
05049 ucmd.upmove = 0;
05050 return;
05051 }
05052
05053
05054 VectorClear(jumpVel);
05055
05056 if ( NPC->client->ps.fd.forceJumpCharge )
05057 {
05058
05059 WP_GetVelocityForForceJump( NPC, jumpVel, &ucmd );
05060 }
05061 else if ( ucmd.upmove > 0 )
05062 {
05063
05064 VectorCopy( NPC->client->ps.velocity, jumpVel );
05065 jumpVel[2] = JUMP_VELOCITY;
05066 }
05067 else
05068 {
05069 return;
05070 }
05071
05072
05073 if ( !jumpVel[0] && !jumpVel[1] )
05074 {
05075
05076 return;
05077 }
05078
05079
05080
05081 VectorCopy( NPC->r.currentOrigin, tr.trBase );
05082 VectorCopy( jumpVel, tr.trDelta );
05083 tr.trType = TR_GRAVITY;
05084 tr.trTime = level.time;
05085 VectorCopy( NPC->r.currentOrigin, lastPos );
05086
05087 VectorClear(trace.endpos);
05088
05089
05090 for ( elapsedTime = 500; elapsedTime <= 4000; elapsedTime += 500 )
05091 {
05092 BG_EvaluateTrajectory( &tr, level.time + elapsedTime, testPos );
05093
05094 if ( testPos[2] < lastPos[2] )
05095 {
05096 trap_Trace( &trace, lastPos, NPC->r.mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask );
05097 }
05098 else
05099 {
05100 trap_Trace( &trace, lastPos, NPC->r.mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP );
05101 }
05102 if ( trace.allsolid || trace.startsolid )
05103 {
05104
05105 goto jump_unsafe;
05106 return;
05107 }
05108 if ( trace.fraction < 1.0f )
05109 {
05110 if ( trace.contents & CONTENTS_BOTCLIP )
05111 {
05112 goto jump_unsafe;
05113 return;
05114 }
05115
05116 break;
05117 }
05118 VectorCopy( testPos, lastPos );
05119 }
05120
05121 VectorCopy( trace.endpos, bottom );
05122 if ( bottom[2] > NPC->r.currentOrigin[2] )
05123 {
05124 bottom[2] = NPC->r.currentOrigin[2];
05125 }
05126 else if ( NPC->r.currentOrigin[2] - bottom[2] > 400 )
05127 {
05128
05129 goto jump_unsafe;
05130 return;
05131 }
05132 bottom[2] -= 128;
05133 trap_Trace( &trace, trace.endpos, NPC->r.mins, NPC->r.maxs, bottom, NPC->s.number, NPC->clipmask );
05134 if ( trace.allsolid || trace.startsolid || trace.fraction < 1.0f )
05135 {
05136 if ( trace.entityNum < ENTITYNUM_WORLD )
05137 {
05138 gentity_t *groundEnt = &g_entities[trace.entityNum];
05139 if ( groundEnt->r.svFlags&SVF_GLASS_BRUSH )
05140 {
05141 goto jump_unsafe;
05142 return;
05143 }
05144 }
05145
05146 return;
05147 }
05148 jump_unsafe:
05149
05150
05151 NPC->client->ps.fd.forceJumpCharge = 0;
05152 ucmd.upmove = 0;
05153 }
05154
05155 static void Jedi_Combat( void )
05156 {
05157 vec3_t enemy_dir, enemy_movedir, enemy_dest;
05158 float enemy_dist, enemy_movespeed;
05159 qboolean enemy_lost = qfalse;
05160
05161
05162 Jedi_SetEnemyInfo( enemy_dest, enemy_dir, &enemy_dist, enemy_movedir, &enemy_movespeed, 300 );
05163
05164 if ( Jedi_Jumping( NPC->enemy ) )
05165 {
05166 Jedi_AttackDecide( enemy_dist );
05167 return;
05168 }
05169
05170 if ( !(NPC->client->ps.fd.forcePowersActive&(1<<FP_GRIP)) || NPC->client->ps.fd.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
05171 {
05172
05173 if ( !Jedi_ClearPathToSpot( enemy_dest, NPC->enemy->s.number ) )
05174 {
05175
05176 if ( (NPC_ClearLOS4( NPC->enemy )||NPCInfo->enemyLastSeenTime>level.time-500) && NPC_FaceEnemy( qtrue ) )
05177 {
05178
05179
05180
05181
05182
05183
05184
05185
05186
05187
05188
05189
05190
05191
05192
05193
05194
05195
05196
05197
05198
05199
05200
05201
05202
05203
05204
05205
05206
05207
05208 if ( Jedi_TryJump( NPC->enemy ) )
05209 {
05210 return;
05211 }
05212 }
05213
05214
05215 if ( TIMER_Done( NPC, "parryTime" ) )
05216 {
05217 if ( NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE &&
05218 NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
05219 {
05220 NPC->client->ps.saberBlocked = BLOCKED_NONE;
05221 }
05222 }
05223 if ( Jedi_Hunt() && !(NPCInfo->aiFlags&NPCAI_BLOCKED) )
05224 {
05225 if ( enemy_dist < 384 && !Q_irand( 0, 10 ) && NPCInfo->blockedSpeechDebounceTime < level.time && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time && !NPC_ClearLOS4( NPC->enemy ) )
05226 {
05227 G_AddVoiceEvent( NPC, Q_irand( EV_JLOST1, EV_JLOST3 ), 3000 );
05228 jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000;
05229 }
05230
05231 return;
05232 }
05233
05234
05235
05236
05237
05238
05239 else
05240 {
05241 if ( NPCInfo->aiFlags & NPCAI_BLOCKED )
05242 {
05243 gentity_t *tempGoal = G_Spawn();
05244 G_SetOrigin( tempGoal, NPCInfo->blockedDest );
05245 trap_LinkEntity( tempGoal );
05246 if ( Jedi_TryJump( tempGoal ) )
05247 {
05248 G_FreeEntity( tempGoal );
05249 return;
05250 }
05251 G_FreeEntity( tempGoal );
05252 }
05253
05254 enemy_lost = qtrue;
05255 }
05256 }
05257 }
05258
05259
05260
05261 Jedi_CombatTimersUpdate( enemy_dist );
05262
05263
05264
05265 Jedi_CombatDistance( enemy_dist );
05266
05267
05268 {
05269
05270 if ( !NPC->enemy->client || ( NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE && NPC->client->ps.groundEntityNum != ENTITYNUM_NONE ) )
05271 {
05272 VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation );
05273 }
05274 NPCInfo->enemyLastSeenTime = level.time;
05275 }
05276
05277
05278 if ( TIMER_Done( NPC, "noturn" ) )
05279 {
05280 Jedi_FaceEnemy( qtrue );
05281 }
05282 NPC_UpdateAngles( qtrue, qtrue );
05283
05284
05285 if ( TIMER_Done( NPC, "parryTime" ) )
05286 {
05287 if ( NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE &&
05288 NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
05289 {
05290 NPC->client->ps.saberBlocked = BLOCKED_NONE;
05291 }
05292 }
05293 if ( NPC->enemy->s.weapon == WP_SABER )
05294 {
05295 Jedi_EvasionSaber( enemy_movedir, enemy_dist, enemy_dir );
05296 }
05297 else
05298 {
05299 }
05300
05301
05302 Jedi_TimersApply();
05303
05304 if ( !NPC->client->ps.saberInFlight && (!(NPC->client->ps.fd.forcePowersActive&(1<<FP_GRIP))||NPC->client->ps.fd.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2) )
05305 {
05306
05307 if ( !Jedi_AttackDecide( enemy_dist ) )
05308 {
05309 Jedi_CombatIdle( enemy_dist );
05310
05311 }
05312 else
05313 {
05314
05315 TIMER_Set( NPC, "taunting", -level.time );
05316 }
05317 }
05318 else
05319 {
05320 }
05321 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
05322 {
05323 Boba_FireDecide();
05324 }
05325
05326
05327 Jedi_CheckEnemyMovement( enemy_dist );
05328
05329 Jedi_CheckJumps();
05330
05331 if ( !NPC_MoveDirClear( ucmd.forwardmove, ucmd.rightmove, qtrue ) )
05332 {
05333 navInfo_t info;
05334
05335 NAV_GetLastMove( &info );
05336 if ( !(info.flags & NIF_MACRO_NAV) )
05337 {
05338 NPC_MoveToGoal( qfalse );
05339 }
05340
05341 TIMER_Set( NPC, "strafeLeft", 0 );
05342 TIMER_Set( NPC, "strafeRight", 0 );
05343 }
05344 }
05345
05346
05347
05348
05349
05350
05351
05352
05353
05354
05355
05356
05357
05358 void NPC_Jedi_Pain(gentity_t *self, gentity_t *attacker, int damage)
05359 {
05360 gentity_t *other = attacker;
05361 vec3_t point;
05362
05363 VectorCopy(gPainPoint, point);
05364
05365
05366
05367
05368 if ( other->s.weapon == WP_SABER )
05369 {
05370 TIMER_Set( self, "parryTime", -1 );
05371 if ( self->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",self->NPC_type) )
05372 {
05373 self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_spskill.integer)*50;
05374 }
05375 else if ( self->NPC->rank >= RANK_LT_JG )
05376 {
05377 self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_spskill.integer)*100;
05378 }
05379 else
05380 {
05381 self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_spskill.integer)*200;
05382 }
05383 if ( !Q_irand( 0, 3 ) )
05384 {
05385 Jedi_AdjustSaberAnimLevel( self, Q_irand( FORCE_LEVEL_1, FORCE_LEVEL_3 ) );
05386 }
05387 if ( !Q_irand( 0, 1 ) )
05388 {
05389
05390 Jedi_Aggression( self, -1 );
05391 }
05392 if ( d_JediAI.integer )
05393 {
05394 Com_Printf( "(%d) PAIN: agg %d, no parry until %d\n", level.time, self->NPC->stats.aggression, level.time+500 );
05395 }
05396
05397
05398 if ( d_JediAI.integer )
05399 {
05400 vec3_t diff, fwdangles, right;
05401 float rightdot, zdiff;
05402
05403 VectorSubtract( point, self->client->renderInfo.eyePoint, diff );
05404 diff[2] = 0;
05405 fwdangles[1] = self->client->ps.viewangles[1];
05406 AngleVectors( fwdangles, NULL, right, NULL );
05407 rightdot = DotProduct(right, diff);
05408 zdiff = point[2] - self->client->renderInfo.eyePoint[2];
05409
05410 Com_Printf( "(%d) saber hit at height %4.2f, zdiff: %4.2f, rightdot: %4.2f\n", level.time, point[2]-self->r.absmin[2],zdiff,rightdot);
05411 }
05412 }
05413 else
05414 {
05415
05416 Jedi_Aggression( self, 1 );
05417 }
05418
05419 self->NPC->enemyCheckDebounceTime = 0;
05420
05421 WP_ForcePowerStop( self, FP_GRIP );
05422
05423
05424 NPC_Pain(self, attacker, damage);
05425
05426 if ( !damage && self->health > 0 )
05427 {
05428 G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
05429 }
05430
05431
05432 if ( Jedi_WaitingAmbush( self ) )
05433 {
05434 self->client->noclip = qfalse;
05435 }
05436 if ( self->client->ps.legsAnim == BOTH_CEILING_CLING )
05437 {
05438 NPC_SetAnim( self, SETANIM_LEGS, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
05439 }
05440 if ( self->client->ps.torsoAnim == BOTH_CEILING_CLING )
05441 {
05442 NPC_SetAnim( self, SETANIM_TORSO, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
05443 }
05444 }
05445
05446 qboolean Jedi_CheckDanger( void )
05447 {
05448 int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_MINOR );
05449 if ( level.alertEvents[alertEvent].level >= AEL_DANGER )
05450 {
05451 if ( !level.alertEvents[alertEvent].owner
05452 || !level.alertEvents[alertEvent].owner->client
05453 || (level.alertEvents[alertEvent].owner!=NPC&&level.alertEvents[alertEvent].owner->client->playerTeam!=NPC->client->playerTeam) )
05454 {
05455 return qfalse;
05456 }
05457 G_SetEnemy( NPC, level.alertEvents[alertEvent].owner );
05458 NPCInfo->enemyLastSeenTime = level.time;
05459 TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
05460 return qtrue;
05461 }
05462 return qfalse;
05463 }
05464
05465 qboolean Jedi_CheckAmbushPlayer( void )
05466 {
05467 int i = 0;
05468 gentity_t *player;
05469 float target_dist;
05470 float zDiff;
05471
05472 for ( i = 0; i < MAX_CLIENTS; i++ )
05473 {
05474 player = &g_entities[i];
05475
05476 if ( !player || !player->client )
05477 {
05478 continue;
05479 }
05480
05481 if ( !NPC_ValidEnemy( player ) )
05482 {
05483 continue;
05484 }
05485
05486
05487 if (NPC->client->ps.powerups[PW_CLOAKED] || !NPC_SomeoneLookingAtMe(NPC))
05488 {
05489 if ( !trap_InPVS( player->r.currentOrigin, NPC->r.currentOrigin ) )
05490 {
05491 continue;
05492 }
05493 else
05494 {
05495 if ( !NPC->client->ps.powerups[PW_CLOAKED] )
05496 {
05497 NPC_SetLookTarget( NPC, 0, 0 );
05498 }
05499 }
05500 zDiff = NPC->r.currentOrigin[2]-player->r.currentOrigin[2];
05501 if ( zDiff <= 0 || zDiff > 512 )
05502 {
05503 continue;
05504 }
05505
05506
05507 if ( (target_dist = DistanceHorizontalSquared( player->r.currentOrigin, NPC->r.currentOrigin )) > 4096 )
05508 {
05509 if ( target_dist > 147456 )
05510 {
05511 continue;
05512 }
05513
05514 if ( NPC->client->ps.powerups[PW_CLOAKED] )
05515 {
05516 if ( InFOV( player, NPC, 30, 90 ) == qfalse )
05517 {
05518 continue;
05519 }
05520 }
05521 else
05522 {
05523 if ( InFOV( player, NPC, 45, 90 ) == qfalse )
05524 {
05525 continue;
05526 }
05527 }
05528 }
05529
05530 if ( !NPC_ClearLOS4( player ) )
05531 {
05532 continue;
05533 }
05534 }
05535
05536
05537 G_SetEnemy( NPC, player );
05538 NPCInfo->enemyLastSeenTime = level.time;
05539 TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
05540 return qtrue;
05541 }
05542
05543
05544 return qfalse;
05545 }
05546
05547 void Jedi_Ambush( gentity_t *self )
05548 {
05549 self->client->noclip = qfalse;
05550
05551 NPC_SetAnim( self, SETANIM_BOTH, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
05552 self->client->ps.weaponTime = self->client->ps.torsoTimer;
05553 if ( self->client->NPC_class != CLASS_BOBAFETT )
05554 {
05555 WP_ActivateSaber(self);
05556 }
05557 Jedi_Decloak( self );
05558 G_AddVoiceEvent( self, Q_irand( EV_ANGER1, EV_ANGER3 ), 1000 );
05559 }
05560
05561 qboolean Jedi_WaitingAmbush( gentity_t *self )
05562 {
05563 if ( (self->spawnflags&JSF_AMBUSH) && self->client->noclip )
05564 {
05565 return qtrue;
05566 }
05567 return qfalse;
05568 }
05569
05570
05571
05572
05573
05574
05575 static void Jedi_Patrol( void )
05576 {
05577 NPC->client->ps.saberBlocked = BLOCKED_NONE;
05578
05579 if ( Jedi_WaitingAmbush( NPC ) )
05580 {
05581 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_CEILING_CLING, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
05582 if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
05583 {
05584 if ( Jedi_CheckAmbushPlayer() || Jedi_CheckDanger() )
05585 {
05586 Jedi_Ambush( NPC );
05587 NPC_UpdateAngles( qtrue, qtrue );
05588 return;
05589 }
05590 }
05591 }
05592 else if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
05593 {
05594 gentity_t *best_enemy = NULL;
05595 float best_enemy_dist = Q3_INFINITE;
05596 int i;
05597 for ( i = 0; i < ENTITYNUM_WORLD; i++ )
05598 {
05599 gentity_t *enemy = &g_entities[i];
05600 float enemy_dist;
05601 if ( enemy && enemy->client && NPC_ValidEnemy( enemy ) && enemy->client->playerTeam == NPC->client->enemyTeam )
05602 {
05603 if ( trap_InPVS( NPC->r.currentOrigin, enemy->r.currentOrigin ) )
05604 {
05605 enemy_dist = DistanceSquared( NPC->r.currentOrigin, enemy->r.currentOrigin );
05606 if ( enemy->s.eType == ET_PLAYER || enemy_dist < best_enemy_dist )
05607 {
05608
05609
05610 if ( enemy_dist < (220*220) || ( NPCInfo->investigateCount>= 3 && !NPC->client->ps.saberHolstered ) )
05611 {
05612 G_SetEnemy( NPC, enemy );
05613
05614 NPCInfo->stats.aggression = 3;
05615 break;
05616 }
05617 else if ( enemy->client->ps.saberInFlight && !enemy->client->ps.saberHolstered )
05618 {
05619 float saberDist;
05620 vec3_t saberDir2Me;
05621 vec3_t saberMoveDir;
05622 gentity_t *saber = &g_entities[enemy->client->ps.saberEntityNum];
05623 VectorSubtract( NPC->r.currentOrigin, saber->r.currentOrigin, saberDir2Me );
05624 saberDist = VectorNormalize( saberDir2Me );
05625 VectorCopy( saber->s.pos.trDelta, saberMoveDir );
05626 VectorNormalize( saberMoveDir );
05627 if ( DotProduct( saberMoveDir, saberDir2Me ) > 0.5 )
05628 {
05629 if ( saberDist < 200 )
05630 {
05631 G_SetEnemy( NPC, enemy );
05632
05633 NPCInfo->stats.aggression = 3;
05634 break;
05635 }
05636 }
05637 }
05638 best_enemy_dist = enemy_dist;
05639 best_enemy = enemy;
05640 }
05641 }
05642 }
05643 }
05644 if ( !NPC->enemy )
05645 {
05646 if ( !best_enemy )
05647 {
05648
05649 Jedi_AggressionErosion(-1);
05650
05651 }
05652 else
05653 {
05654 if ( NPC_ClearLOS4( best_enemy ) )
05655 {
05656 if ( best_enemy->s.number )
05657 {
05658 G_SetEnemy( NPC, best_enemy );
05659 NPCInfo->stats.aggression = 3;
05660 }
05661 else if ( NPC->client->NPC_class != CLASS_BOBAFETT )
05662 {
05663
05664 if ( TIMER_Done( NPC, "watchTime" ) )
05665 {
05666 if ( TIMER_Get( NPC, "watchTime" ) == -1 )
05667 {
05668 TIMER_Set( NPC, "watchTime", Q_irand( 3000, 5000 ) );
05669 goto finish;
05670 }
05671 else
05672 {
05673 if ( !NPCInfo->investigateCount )
05674 {
05675 G_AddVoiceEvent( NPC, Q_irand( EV_JDETECTED1, EV_JDETECTED3 ), 3000 );
05676 }
05677 NPCInfo->investigateCount++;
05678 TIMER_Set( NPC, "watchTime", Q_irand( 4000, 10000 ) );
05679 }
05680 }
05681
05682 if ( best_enemy_dist < (440*440) || NPCInfo->investigateCount >= 2 )
05683 {
05684 NPC_FaceEntity( best_enemy, qtrue );
05685 if ( best_enemy_dist < (330*330) )
05686 {
05687 if ( !NPC->client->ps.saberInFlight )
05688 {
05689 WP_ActivateSaber(NPC);
05690 }
05691 }
05692 }
05693 else if ( best_enemy_dist < (550*550) || NPCInfo->investigateCount == 1 )
05694 {
05695 if ( TIMER_Done( NPC, "watchTime" ) )
05696 {
05697 NPC_FaceEntity( best_enemy, qtrue );
05698 }
05699 }
05700 else
05701 {
05702 NPC_SetLookTarget( NPC, best_enemy->s.number, 0 );
05703 }
05704 }
05705 }
05706 else if ( TIMER_Done( NPC, "watchTime" ) )
05707 {
05708 NPC_ClearLookTarget( NPC );
05709 }
05710 }
05711 }
05712 }
05713 finish:
05714
05715 if ( UpdateGoal() )
05716 {
05717 ucmd.buttons |= BUTTON_WALKING;
05718
05719 NPC_MoveToGoal( qtrue );
05720 }
05721
05722 NPC_UpdateAngles( qtrue, qtrue );
05723
05724 if ( NPC->enemy )
05725 {
05726 NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 );
05727 }
05728 }
05729
05730 qboolean Jedi_CanPullBackSaber( gentity_t *self )
05731 {
05732 if ( self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN && !TIMER_Done( self, "parryTime" ) )
05733 {
05734 return qfalse;
05735 }
05736
05737 if ( self->client->NPC_class == CLASS_SHADOWTROOPER
05738 || self->client->NPC_class == CLASS_TAVION
05739 || self->client->NPC_class == CLASS_LUKE
05740 || self->client->NPC_class == CLASS_DESANN
05741 || !Q_stricmp( "Yoda", self->NPC_type ) )
05742 {
05743 return qtrue;
05744 }
05745
05746 if ( self->painDebounceTime > level.time )
05747 {
05748 return qfalse;
05749 }
05750
05751 return qtrue;
05752 }
05753
05754
05755
05756
05757
05758 void NPC_BSJedi_FollowLeader( void )
05759 {
05760 NPC->client->ps.saberBlocked = BLOCKED_NONE;
05761 if ( !NPC->enemy )
05762 {
05763
05764 Jedi_AggressionErosion(-1);
05765 }
05766
05767
05768 if ( NPC->client->ps.saberInFlight )
05769 {
05770 if ( NPC->client->ps.saberEntityNum < ENTITYNUM_NONE && NPC->client->ps.saberEntityNum > 0 )
05771 {
05772 if ( g_entities[NPC->client->ps.saberEntityNum].s.pos.trType == TR_STATIONARY )
05773 {
05774 if ( Jedi_CanPullBackSaber( NPC ) )
05775 {
05776
05777
05778
05779
05780 NPC->client->ps.saberBlocked = BLOCKED_NONE;
05781 NPCInfo->goalEntity = &g_entities[NPC->client->ps.saberEntityNum];
05782 ucmd.buttons |= BUTTON_ATTACK;
05783 if ( NPC->enemy && NPC->enemy->health > 0 )
05784 {
05785 if ( !NPC_MoveToGoal( qtrue ) )
05786 {
05787 NPC_FaceEntity( NPCInfo->goalEntity, qtrue );
05788 Jedi_TryJump( NPCInfo->goalEntity );
05789 }
05790 NPC_UpdateAngles( qtrue, qtrue );
05791 return;
05792 }
05793 }
05794 }
05795 }
05796 }
05797
05798 if ( NPCInfo->goalEntity )
05799 {
05800 trace_t trace;
05801
05802 if ( Jedi_Jumping( NPCInfo->goalEntity ) )
05803 {
05804 return;
05805 }
05806
05807 if ( !NAV_CheckAhead( NPC, NPCInfo->goalEntity->r.currentOrigin, &trace, ( NPC->clipmask & ~CONTENTS_BODY )|CONTENTS_BOTCLIP ) )
05808 {
05809 if ( NPC_ClearLOS4( NPCInfo->goalEntity ) && NPC_FaceEntity( NPCInfo->goalEntity, qtrue ) )
05810 {
05811 if ( Jedi_TryJump( NPCInfo->goalEntity ) )
05812 {
05813 return;
05814 }
05815 }
05816 }
05817 if ( NPCInfo->aiFlags & NPCAI_BLOCKED )
05818 {
05819 if ( fabs(NPCInfo->blockedDest[2]-NPC->r.currentOrigin[2]) > 64 )
05820 {
05821 gentity_t *tempGoal = G_Spawn();
05822 G_SetOrigin( tempGoal, NPCInfo->blockedDest );
05823 trap_LinkEntity( tempGoal );
05824 TIMER_Set( NPC, "jumpChaseDebounce", -1 );
05825 if ( Jedi_TryJump( tempGoal ) )
05826 {
05827 G_FreeEntity( tempGoal );
05828 return;
05829 }
05830 G_FreeEntity( tempGoal );
05831 }
05832 }
05833 }
05834
05835 NPC_BSFollowLeader();
05836 }
05837
05838
05839
05840
05841
05842
05843
05844
05845 static void Jedi_Attack( void )
05846 {
05847
05848 if ( NPC->painDebounceTime > level.time )
05849 {
05850 if ( Q_irand( 0, 1 ) )
05851 {
05852 Jedi_FaceEnemy( qtrue );
05853 }
05854 NPC_UpdateAngles( qtrue, qtrue );
05855 return;
05856 }
05857
05858 if ( NPC->client->ps.saberLockTime > level.time )
05859 {
05860
05861 if ( NPC->client->ps.fd.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2
05862 && NPC->client->ps.saberLockTime < level.time + 5000
05863 && !Q_irand( 0, 10 ))
05864 {
05865 ForceThrow( NPC, qfalse );
05866 }
05867
05868 else
05869 {
05870 float chance;
05871
05872 if ( NPC->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",NPC->NPC_type) )
05873 {
05874 if ( g_spskill.integer )
05875 {
05876 chance = 4.0f;
05877 }
05878 else
05879 {
05880 chance = 3.0f;
05881 }
05882 }
05883 else if ( NPC->client->NPC_class == CLASS_TAVION )
05884 {
05885 chance = 2.0f+g_spskill.value;
05886 }
05887 else
05888 {
05889 float maxChance = (float)(RANK_LT)/2.0f+3.0f;
05890 if ( !g_spskill.value )
05891 {
05892 chance = (float)(NPCInfo->rank)/2.0f;
05893 }
05894 else
05895 {
05896 chance = (float)(NPCInfo->rank)/2.0f+1.0f;
05897 }
05898 if ( chance > maxChance )
05899 {
05900 chance = maxChance;
05901 }
05902 }
05903
05904
05905
05906
05907 if ( flrand( -4.0f, chance ) >= 0.0f )
05908 {
05909 ucmd.buttons |= BUTTON_ATTACK;
05910 }
05911
05912 }
05913 NPC_UpdateAngles( qtrue, qtrue );
05914 return;
05915 }
05916
05917 if ( NPC->client->ps.saberInFlight )
05918 {
05919
05920 if (!NPC->client->ps.saberEntityNum && NPC->client->saberStoredIndex)
05921 {
05922
05923 if (1)
05924 {
05925
05926 if (1)
05927 {
05928 NPC->client->ps.saberBlocked = BLOCKED_NONE;
05929 NPCInfo->goalEntity = &g_entities[NPC->client->saberStoredIndex];
05930 ucmd.buttons |= BUTTON_ATTACK;
05931 if ( NPC->enemy && NPC->enemy->health > 0 )
05932 {
05933 Jedi_Move( NPCInfo->goalEntity, qfalse );
05934 NPC_UpdateAngles( qtrue, qtrue );
05935 if ( NPC->enemy->s.weapon == WP_SABER )
05936 {
05937 vec3_t enemy_dir, enemy_movedir, enemy_dest;
05938 float enemy_dist, enemy_movespeed;
05939 Jedi_SetEnemyInfo( enemy_dest, enemy_dir, &enemy_dist, enemy_movedir, &enemy_movespeed, 300 );
05940 Jedi_EvasionSaber( enemy_movedir, enemy_dist, enemy_dir );
05941 }
05942 return;
05943 }
05944 }
05945 }
05946 }
05947 }
05948
05949
05950 if ( NPC->enemy )
05951 {
05952 if ( NPC->enemy->health <= 0 && NPC->enemy->enemy == NPC && NPC->client->playerTeam != NPCTEAM_PLAYER )
05953 {
05954 NPCInfo->enemyCheckDebounceTime = 0;
05955
05956 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
05957 {
05958 if ( NPCInfo->walkDebounceTime < level.time && NPCInfo->walkDebounceTime >= 0 )
05959 {
05960 TIMER_Set( NPC, "gloatTime", 10000 );
05961 NPCInfo->walkDebounceTime = -1;
05962 }
05963 if ( !TIMER_Done( NPC, "gloatTime" ) )
05964 {
05965 if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->r.currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
05966 {
05967 NPCInfo->goalEntity = NPC->enemy;
05968 Jedi_Move( NPC->enemy, qfalse );
05969 ucmd.buttons |= BUTTON_WALKING;
05970 }
05971 else
05972 {
05973 TIMER_Set( NPC, "gloatTime", 0 );
05974 }
05975 }
05976 else if ( NPCInfo->walkDebounceTime == -1 )
05977 {
05978 NPCInfo->walkDebounceTime = -2;
05979 G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 );
05980 jediSpeechDebounceTime[NPC->client->playerTeam] = level.time + 3000;
05981 NPCInfo->desiredPitch = 0;
05982 NPCInfo->goalEntity = NULL;
05983 }
05984 Jedi_FaceEnemy( qtrue );
05985 NPC_UpdateAngles( qtrue, qtrue );
05986 return;
05987 }
05988 else
05989 {
05990 if ( !TIMER_Done( NPC, "parryTime" ) )
05991 {
05992 TIMER_Set( NPC, "parryTime", -1 );
05993 NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500;
05994 }
05995 NPC->client->ps.saberBlocked = BLOCKED_NONE;
05996 if ( !NPC->client->ps.saberHolstered && NPC->client->ps.saberInFlight )
05997 {
05998
05999
06000
06001 Jedi_AggressionErosion(-3);
06002 if ( BG_SabersOff( &NPC->client->ps ) && !NPC->client->ps.saberInFlight )
06003 {
06004 G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 );
06005 jediSpeechDebounceTime[NPC->client->playerTeam] = level.time + 3000;
06006 NPCInfo->desiredPitch = 0;
06007 NPCInfo->goalEntity = NULL;
06008 }
06009 TIMER_Set( NPC, "gloatTime", 10000 );
06010 }
06011 if ( !NPC->client->ps.saberHolstered || NPC->client->ps.saberInFlight || !TIMER_Done( NPC, "gloatTime" ) )
06012 {
06013 if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->r.currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
06014 {
06015 NPCInfo->goalEntity = NPC->enemy;
06016 Jedi_Move( NPC->enemy, qfalse );
06017 ucmd.buttons |= BUTTON_WALKING;
06018 }
06019 else
06020 {
06021 if ( NPC->health < NPC->client->pers.maxHealth
06022 && (NPC->client->ps.fd.forcePowersKnown&(1<<FP_HEAL)) != 0
06023 && (NPC->client->ps.fd.forcePowersActive&(1<<FP_HEAL)) == 0 )
06024 {
06025 ForceHeal( NPC );
06026 }
06027 }
06028 Jedi_FaceEnemy( qtrue );
06029 NPC_UpdateAngles( qtrue, qtrue );
06030 return;
06031 }
06032 }
06033 }
06034 }
06035
06036
06037 if ( NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ) )
06038 {
06039 if ( NPC->enemy->count <= 0 )
06040 {
06041 if ( NPC->enemy->activator && NPC_ValidEnemy( NPC->enemy->activator ) )
06042 {
06043 gentity_t *turretOwner = NPC->enemy->activator;
06044 G_ClearEnemy( NPC );
06045 G_SetEnemy( NPC, turretOwner );
06046 }
06047 else
06048 {
06049 G_ClearEnemy( NPC );
06050 }
06051 }
06052 }
06053 NPC_CheckEnemy( qtrue, qtrue, qtrue );
06054
06055 if ( !NPC->enemy )
06056 {
06057 NPC->client->ps.saberBlocked = BLOCKED_NONE;
06058 if ( NPCInfo->tempBehavior == BS_HUNT_AND_KILL )
06059 {
06060 NPCInfo->tempBehavior = BS_DEFAULT;
06061 NPC_UpdateAngles( qtrue, qtrue );
06062 return;
06063 }
06064 Jedi_Patrol();
06065 return;
06066 }
06067
06068
06069 NPCInfo->combatMove = qtrue;
06070
06071
06072 Jedi_Combat();
06073
06074 if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES)
06075 || ((NPC->client->ps.fd.forcePowersActive&(1<<FP_HEAL))&&NPC->client->ps.fd.forcePowerLevel[FP_HEAL]<FORCE_LEVEL_2))
06076 {
06077 ucmd.forwardmove = 0;
06078 ucmd.rightmove = 0;
06079 if ( ucmd.upmove > 0 )
06080 {
06081 ucmd.upmove = 0;
06082 }
06083 NPC->client->ps.fd.forceJumpCharge = 0;
06084 VectorClear( NPC->client->ps.moveDir );
06085 }
06086
06087
06088 if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE )
06089 {
06090
06091 ucmd.forwardmove = 0;
06092 ucmd.rightmove = 0;
06093 VectorClear( NPC->client->ps.moveDir );
06094 }
06095
06096 if ( !TIMER_Done( NPC, "duck" ) )
06097 {
06098 ucmd.upmove = -127;
06099 }
06100
06101 if ( NPC->client->NPC_class != CLASS_BOBAFETT )
06102 {
06103 if ( PM_SaberInBrokenParry( NPC->client->ps.saberMove ) || NPC->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
06104 {
06105 ucmd.buttons &= ~BUTTON_ATTACK;
06106 }
06107 }
06108
06109 if( (NPCInfo->scriptFlags&SCF_DONT_FIRE)
06110 || ((NPC->client->ps.fd.forcePowersActive&(1<<FP_HEAL))&&NPC->client->ps.fd.forcePowerLevel[FP_HEAL]<FORCE_LEVEL_3)
06111 || ((NPC->client->ps.saberEventFlags&SEF_INWATER)&&!NPC->client->ps.saberInFlight) )
06112 {
06113 ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK);
06114 }
06115
06116 if ( NPCInfo->scriptFlags&SCF_NO_ACROBATICS )
06117 {
06118 ucmd.upmove = 0;
06119 NPC->client->ps.fd.forceJumpCharge = 0;
06120 }
06121
06122 if ( NPC->client->NPC_class != CLASS_BOBAFETT )
06123 {
06124 Jedi_CheckDecreaseSaberAnimLevel();
06125 }
06126
06127 if ( ucmd.buttons & BUTTON_ATTACK && NPC->client->playerTeam == NPCTEAM_ENEMY )
06128 {
06129 if ( Q_irand( 0, NPC->client->ps.fd.saberAnimLevel ) > 0
06130 && Q_irand( 0, NPC->client->pers.maxHealth+10 ) > NPC->health
06131 && !Q_irand( 0, 3 ))
06132 {
06133 G_AddVoiceEvent( NPC, Q_irand( EV_COMBAT1, EV_COMBAT3 ), 1000 );
06134 }
06135 }
06136
06137 if ( NPC->client->NPC_class != CLASS_BOBAFETT )
06138 {
06139 if ( NPC->client->NPC_class == CLASS_TAVION
06140 || (g_spskill.integer && ( NPC->client->NPC_class == CLASS_DESANN || NPCInfo->rank >= Q_irand( RANK_CREWMAN, RANK_CAPTAIN ))))
06141 {
06142 if ( NPC->enemy
06143 && !NPC->enemy->s.number
06144 && NPC->enemy->client
06145 && (NPC->enemy->client->ps.fd.forcePowersActive & (1<<FP_SPEED))
06146 && !(NPC->client->ps.fd.forcePowersActive & (1<<FP_SPEED)) )
06147 {
06148 int chance = 0;
06149 switch ( g_spskill.integer )
06150 {
06151 case 0:
06152 chance = 9;
06153 case 1:
06154 chance = 3;
06155 case 2:
06156 chance = 1;
06157 break;
06158 }
06159 if ( !Q_irand( 0, chance ) )
06160 {
06161 ForceSpeed( NPC, 0 );
06162 }
06163 }
06164 }
06165 }
06166 }
06167
06168 extern void NPC_BSST_Patrol( void );
06169 extern void NPC_BSSniper_Default( void );
06170 void NPC_BSJedi_Default( void )
06171 {
06172
06173 Jedi_CheckCloak();
06174 if( !NPC->enemy )
06175 {
06176 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
06177 {
06178 NPC_BSST_Patrol();
06179 }
06180 else
06181 {
06182 Jedi_Patrol();
06183 }
06184 }
06185 else
06186 {
06187 if ( Jedi_WaitingAmbush( NPC ) )
06188 {
06189 Jedi_Ambush( NPC );
06190 }
06191 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
06192 {
06193 if ( NPC->enemy->enemy != NPC && NPC->health == NPC->client->pers.maxHealth && DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin )>(800*800) )
06194 {
06195 NPCInfo->scriptFlags |= SCF_ALT_FIRE;
06196 Boba_ChangeWeapon( WP_DISRUPTOR );
06197 NPC_BSSniper_Default();
06198 return;
06199 }
06200 }
06201 Jedi_Attack();
06202
06203 if ( ((!ucmd.buttons&&!NPC->client->ps.fd.forcePowersActive)||(NPC->enemy&&NPC->enemy->health<=0)) && NPCInfo->enemyCheckDebounceTime < level.time )
06204 {
06205
06206 gentity_t *sav_enemy = NPC->enemy;
06207 gentity_t *newEnemy;
06208
06209 NPC->enemy = NULL;
06210 newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse );
06211 NPC->enemy = sav_enemy;
06212 if ( newEnemy && newEnemy != sav_enemy )
06213 {
06214 NPC->lastEnemy = NPC->enemy;
06215 G_SetEnemy( NPC, newEnemy );
06216 }
06217 NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 1000, 3000 );
06218 }
06219 }
06220 }