codemp/game/NPC_AI_Jedi.c

Go to the documentation of this file.
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 { //perhaps write this at some point?
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 //#define       DISTANCE_THRESHOLD      0.075f
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 ); //clearLength = qfalse
00061 extern void WP_ActivateSaber( gentity_t *self );
00062 //extern void WP_SaberBlock(gentity_t *saber, vec3_t hitloc);
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];//used to stop several jedi from speaking all at once
00095 //Local state enums
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         {//if on a surface and not in a spin or flip, play full body resist
00225                 parts = SETANIM_BOTH;
00226         }
00227         else
00228         {//play resist just in torso
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                         //still stop them from attacking or moving for a bit, though
00245                         //FIXME: maybe push just a little (like, slide)?
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                         //play the full body push effect on me
00254                         //self->forcePushTime = level.time + 600; // let the push effect last for 600 ms
00255                         //rwwFIXMEFIXME: Do this?
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         //play my force push effect on my hand
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 ) //forceKnockdown = qfalse
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) )//moveType == MT_FLYSWIM )
00284         {//can't knock me down when I'm flying
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         {//flip or roll with it
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                 {//flip
00324                         self->client->ps.fd.forceJumpCharge = 280;//FIXME: calc this intelligently?
00325                         ForceJump( self, &tempCmd );
00326                 }
00327                 else
00328                 {//roll
00329                         TIMER_Set( self, "duck", strafeTime );
00330                 }
00331                 self->painDebounceTime = 0;//so we do something
00332         }
00333         else if ( !Q_irand( 0, 1 ) && forceKnockdown )
00334         {//resist
00335                 WP_ResistForcePush( self, pusher, qtrue );
00336         }
00337         else
00338         {//fall down
00339                 return qfalse;
00340         }
00341 
00342         return qtrue;
00343 }
00344 
00345 void Boba_FlyStart( gentity_t *self )
00346 {//switch to seeker AI for a while
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;//moveType = MT_FLYSWIM;
00355                 self->client->jetPackTime = level.time + Q_irand( 3000, 10000 );
00356                 //take-off sound
00357                 G_SoundOnEnt( self, CHAN_ITEM, "sound/boba/jeton.wav" );
00358                 //jet loop sound
00359                 self->s.loopSound = G_SoundIndex( "sound/boba/jethover.wav" );
00360                 if ( self->NPC )
00361                 {
00362                         self->count = Q3_INFINITE; // SEEKER shot ammo count
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         //stop jet loop sound
00377         self->s.loopSound = 0;
00378         if ( self->NPC )
00379         {
00380                 self->count = 0; // SEEKER shot ammo count
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));//moveType==MT_FLYSWIM));
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         //G_PlayEffect( "boba/fthrw", start, dir );
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_NO_HIT_LOC|*/DAMAGE_IGNORE_TEAM, MOD_LAVA );
00414                 //rwwFIXMEFIXME: add DAMAGE_NO_HIT_LOC?
00415         }
00416 }
00417 
00418 //extern void SP_fx_explosion_trail( gentity_t *ent );
00419 void Boba_StartFlameThrower( gentity_t *self )
00420 {
00421         int     flameTime = 4000;//Q_irand( 1000, 3000 );
00422         mdxaBone_t      boltMatrix;
00423         vec3_t          org, dir;
00424 
00425         self->client->ps.torsoTimer = flameTime;//+1000;
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         gentity_t *fire = G_Spawn();
00434         if ( fire != NULL )
00435         {
00436                 mdxaBone_t      boltMatrix;
00437                 vec3_t          org, dir, ang;
00438                 gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel, NPC->handRBolt,
00439                                 &boltMatrix, NPC->r.currentAngles, NPC->r.currentOrigin, (cg.time?cg.time:level.time),
00440                                 NULL, NPC->s.modelScale );
00441 
00442                 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
00443                 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, dir );
00444                 vectoangles( dir, ang );
00445 
00446                 VectorCopy( org, fire->s.origin );
00447                 VectorCopy( ang, fire->s.angles );
00448 
00449                 fire->targetname = "bobafire";
00450                 SP_fx_explosion_trail( fire );
00451                 fire->damage = 1;
00452                 fire->radius = 10;
00453                 fire->speed = 200;
00454                 fire->fxID = G_EffectIndex( "boba/fthrw" );//"env/exp_fire_trail" );//"env/small_fire"
00455                 fx_explosion_trail_link( fire );
00456                 fx_explosion_trail_use( fire, NPC, NPC );
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         //qboolean move = qtrue;
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         {//take off
00500                 Boba_FlyStart( NPC );
00501         }
00502 
00503         if ( !NPC->enemy )
00504         {
00505                 return;
00506         }
00507 
00508         /*
00509         if ( NPC->enemy->enemy != NPC && NPC->health == NPC->client->pers.maxHealth )
00510         {
00511                 NPCInfo->scriptFlags |= SCF_ALT_FIRE;
00512                 Boba_ChangeWeapon( WP_DISRUPTOR );
00513         }
00514         else */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 );//attack debounce
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         {//enemy is in front of me or they're very close and not behind me
00546                 enemyInFOV = qtrue;
00547         }
00548 
00549         if ( (enemyDist < (128*128)&&enemyInFOV) || !TIMER_Done( NPC, "flameTime" ) )
00550         {//flamethrower
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 )//128
00559         {//enemy within 128
00560                 if ( (NPC->client->ps.weapon == WP_FLECHETTE || NPC->client->ps.weapon == WP_REPEATER) && 
00561                         (NPCInfo->scriptFlags & SCF_ALT_FIRE) )
00562                 {//shooting an explosive, but enemy too close, switch to primary fire
00563                         NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
00564                         //FIXME: we can never go back to alt-fire this way since, after this, we don't know if we were initially supposed to use alt-fire or not...
00565                 }
00566         }
00567         else if ( enemyDist > 65536 )//256 squared
00568         {
00569                 if ( NPC->client->ps.weapon == WP_DISRUPTOR )
00570                 {//sniping... should be assumed
00571                         if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) )
00572                         {//use primary fire
00573                                 NPCInfo->scriptFlags |= SCF_ALT_FIRE;
00574                                 //reset fire-timing variables
00575                                 NPC_ChangeWeapon( WP_DISRUPTOR );
00576                                 NPC_UpdateAngles( qtrue, qtrue );
00577                                 return;
00578                         }
00579                 }
00580         }
00581 
00582         //can we see our target?
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;//not true, but should stop us from firing
00593                         }
00594                         else
00595                         {//can we shoot our target?
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 )//128*128
00597                                 {
00598                                         enemyCS = qfalse;//not true, but should stop us from firing
00599                                         hitAlly = qtrue;//us!
00600                                         //FIXME: if too close, run away!
00601                                 }
00602                                 else if ( enemyInFOV )
00603                                 {//if enemy is FOV, go ahead and check for shooting
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                                         {//can hit enemy or enemy ally or will hit glass or other minor breakable (or in emplaced gun), so shoot anyway
00611                                                 enemyCS = qtrue;
00612                                                 //NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy
00613                                                 VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation );
00614                                         }
00615                                         else
00616                                         {//Hmm, have to get around this bastard
00617                                                 //NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy
00618                                                 if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam )
00619                                                 {//would hit an ally, don't fire!!!
00620                                                         hitAlly = qtrue;
00621                                                 }
00622                                                 else
00623                                                 {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire
00624                                                 }
00625                                         }
00626                                 }
00627                                 else
00628                                 {
00629                                         enemyCS = qfalse;//not true, but should stop us from firing
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                         //NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy
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                         {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot?
00649                                 faceEnemy = qtrue;
00650                         }
00651                         if ( enemyCS )
00652                         {
00653                                 shoot = qtrue;
00654                         }
00655                 }
00656 
00657                 if ( !enemyCS )
00658                 {//if have a clear shot, always try
00659                         //See if we should continue to fire on their last position
00661                         if ( !hitAlly //we're not going to hit an ally
00662                                 && enemyInFOV //enemy is in our FOV //FIXME: or we don't have a clear LOS?
00663                                 && NPCInfo->enemyLastSeenTime > 0 )//we've seen the enemy
00664                         {
00665                                 if ( level.time - NPCInfo->enemyLastSeenTime < 10000 )//we have seem the enemy in the last 10 seconds
00666                                 {
00667                                         if ( !Q_irand( 0, 10 ) )
00668                                         {
00669                                                 //Fire on the last known position
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                                                 {//never checked ShotEntity this frame, so must do a trace...
00679                                                         trace_t tr;
00680                                                         //vec3_t        mins = {-2,-2,-2}, maxs = {2,2,2};
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                                                 //see if impact would be too close to me
00689                                                 distThreshold = 16384/*128*128*/;//default
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/*256*256*/;
00698                                                         break;
00699                                                 case WP_REPEATER:
00700                                                         if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
00701                                                         {
00702                                                                 distThreshold = 65536/*256*256*/;
00703                                                         }
00704                                                         break;
00705                                                 default:
00706                                                         break;
00707                                                 }
00708 
00709                                                 dist = DistanceSquared( impactPos, muzzle );
00710 
00711                                                 if ( dist < distThreshold )
00712                                                 {//impact would be too close to me
00713                                                         tooClose = qtrue;
00714                                                 }
00715                                                 else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 ||
00716                                                         (NPCInfo->group && level.time - NPCInfo->group->lastSeenEnemyTime > 5000 ))
00717                                                 {//we've haven't seen them in the last 5 seconds
00718                                                         //see if it's too far from where he is
00719                                                         distThreshold = 65536/*256*256*/;//default
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/*512*512*/;
00728                                                                 break;
00729                                                         case WP_REPEATER:
00730                                                                 if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
00731                                                                 {
00732                                                                         distThreshold = 262144/*512*512*/;
00733                                                                 }
00734                                                                 break;
00735                                                         default:
00736                                                                 break;
00737                                                         }
00738                                                         dist = DistanceSquared( impactPos, NPCInfo->enemyLastSeenLocation );
00739                                                         if ( dist > distThreshold )
00740                                                         {//impact would be too far from enemy
00741                                                                 tooFar = qtrue;
00742                                                         }
00743                                                 }
00744 
00745                                                 if ( !tooClose && !tooFar )
00746                                                 {//okay too shoot at last pos
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                 //FIXME: don't shoot right away!
00763                 if ( NPC->client->ps.weaponTime > 0 )
00764                 {
00765                         if ( NPC->s.weapon == WP_ROCKET_LAUNCHER )
00766                         {
00767                                 if ( !enemyLOS || !enemyCS )
00768                                 {//cancel it
00769                                         NPC->client->ps.weaponTime = 0;
00770                                 }
00771                                 else
00772                                 {//delay our next attempt
00773                                         TIMER_Set( NPC, "nextAttackDelay", Q_irand( 500, 1000 ) );
00774                                 }
00775                         }
00776                 }
00777                 else if ( shoot )
00778                 {//try to shoot if it's time
00779                         if ( TIMER_Done( NPC, "nextAttackDelay" ) )
00780                         {
00781                                 if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here
00782                                 {
00783                                         WeaponThink( qtrue );
00784                                 }
00785                                 //NASTY
00786                                 if ( NPC->s.weapon == WP_ROCKET_LAUNCHER 
00787                                         && (ucmd.buttons&BUTTON_ATTACK) 
00788                                         && !Q_irand( 0, 3 ) )
00789                                 {//every now and then, shoot a homing rocket
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                         {//cloak
00808                                 self->client->ps.powerups[PW_CLOAKED] = Q3_INFINITE;
00809 
00810                                 //FIXME: debounce attacks?
00811                                 //FIXME: temp sound
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                         {//Uncloak
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                 //      (NPC->client->ps.eFlags&EF_FORCE_GRIPPED) ||
00843                 //      (NPC->client->ps.eFlags&EF_FORCE_DRAINED) ||
00844                         NPC->painDebounceTime > level.time )
00845                 {//can't be cloaked if saber is on, or dead or saber in flight or taking pain or being gripped
00846                         Jedi_Decloak( NPC );
00847                 }
00848                 else if ( NPC->health > 0 
00849                         && !NPC->client->ps.saberInFlight 
00850                 //      && !(NPC->client->ps.eFlags&EF_FORCE_GRIPPED) 
00851                 //      && !(NPC->client->ps.eFlags&EF_FORCE_DRAINED) 
00852                         && NPC->painDebounceTime < level.time )
00853                 {//still alive, have saber in hand, not taking pain and not being gripped
00854                         Jedi_Cloak( NPC );
00855                 }
00856         }
00857 }
00858 /*
00859 ==========================================================================================
00860 AGGRESSION
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         //FIXME: base this on initial NPC stats
00870         if ( self->client->playerTeam == NPCTEAM_PLAYER )
00871         {//good guys are less aggressive
00872                 upper_threshold = 7;
00873                 lower_threshold = 1;
00874         }
00875         else
00876         {//bad guys are more aggressive
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         //Com_Printf( "(%d) %s agg %d change: %d\n", level.time, self->NPC_type, self->NPC->stats.aggression, change );
00898 }
00899 
00900 static void Jedi_AggressionErosion( int amt )
00901 {
00902         if ( TIMER_Done( NPC, "roamTime" ) )
00903         {//the longer we're not alerted and have no enemy, the more our aggression goes down
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         {//turn off the saber
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;//go after him
00925                 break;
00926         case WP_BLASTER:
00927                 if ( DistanceSquared( self->r.currentOrigin, enemy->r.currentOrigin ) < 65536 )//256 squared
00928                 {
00929                         healthAggression = (float)self->health/200.0f*8.0f;
00930                         weaponAggression = 8;//go after him
00931                 }
00932                 else
00933                 {
00934                         healthAggression = 8.0f - ((float)self->health/200.0f*8.0f);
00935                         weaponAggression = 2;//hang back for a second
00936                 }
00937                 break;
00938         default:
00939                 healthAggression = (float)self->health/200.0f*8.0f;
00940                 weaponAggression = 6;//approach
00941                 break;
00942         }
00943         //Average these with current aggression
00944         newAggression = ceil( (healthAggression + weaponAggression + (float)self->NPC->stats.aggression )/3.0f);
00945         //Com_Printf( "(%d) new agg %d - new enemy\n", level.time, newAggression );
00946         Jedi_Aggression( self, newAggression - self->NPC->stats.aggression );
00947 
00948         //don't taunt right away
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         {//calm down and back off
00970                 TIMER_Set( self, "roamTime", 0 );
00971                 Jedi_Aggression( self, Q_irand( -5, 0 ) );
00972         }
00973 }
00974 /*
00975 ==========================================================================================
00976 SPEAKING
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                 {//a jedi fighting a jedi - training
00991                         if ( NPC->client->NPC_class == CLASS_JEDI && NPCInfo->rank == RANK_COMMANDER )
00992                         {//only trainer taunts
00993                                 event = EV_TAUNT1;
00994                         }
00995                 }
00996                 else
00997                 {//reborn or a jedi fighting an enemy
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                         {//Have the enemy jedi say something in response when I'm done?
01008                         }
01009                         return qtrue;
01010                 }
01011         }
01012         return qfalse;
01013 }
01014 
01015 /*
01016 ==========================================================================================
01017 MOVEMENT
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         //Offset the step height
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         //Do a simple check
01033         if ( trace.allsolid || trace.startsolid )
01034         {//inside solid
01035                 return qfalse;
01036         }
01037 
01038         if ( trace.fraction < 1.0f )
01039         {//hit something
01040                 if ( impactEntNum != ENTITYNUM_NONE && trace.entityNum == impactEntNum )
01041                 {//hit what we're going after
01042                         return qtrue;
01043                 }
01044                 else
01045                 {
01046                         return qfalse;
01047                 }
01048         }
01049 
01050         //otherwise, clear path in a straight line.  
01051         //Now at intervals of my size, go along the trace and trace down STEPSIZE to make sure there is a solid floor.
01052         VectorSubtract( dest, NPC->r.currentOrigin, dir );
01053         dist = VectorNormalize( dir );
01054         if ( dest[2] > NPC->r.currentOrigin[2] )
01055         {//going up, check for steps
01056                 drop = STEPSIZE;
01057         }
01058         else
01059         {//going down or level, check for moderate drops
01060                 drop = 64;
01061         }
01062         for ( i = NPC->r.maxs[0]*2; i < dist; i += NPC->r.maxs[0]*2 )
01063         {//FIXME: does this check the last spot, too?  We're assuming that should be okay since the enemy is there?
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 );//NPC->r.mins?
01068                 if ( trace.fraction < 1.0f || trace.allsolid || trace.startsolid )
01069                 {//good to go
01070                         continue;
01071                 }
01072                 //no floor here! (or a long drop?)
01073                 return qfalse;
01074         }
01075         //we made it!
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         {//not even moving
01088                 //Com_Printf( "%d skipping walk-cliff check (not moving)\n", level.time );
01089                 return qtrue;
01090         }
01091 
01092         if ( ucmd.upmove > 0 || NPC->client->ps.fd.forceJumpCharge )
01093         {//Going to jump
01094                 //Com_Printf( "%d skipping walk-cliff check (going to jump)\n", level.time );
01095                 return qtrue;
01096         }
01097 
01098         if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE )
01099         {//in the air
01100                 //Com_Printf( "%d skipping walk-cliff check (in air)\n", level.time );
01101                 return qtrue;
01102         }
01103         /*
01104         if ( fabs( AngleDelta( NPC->r.currentAngles[YAW], NPCInfo->desiredYaw ) ) < 5.0 )//!ucmd.angles[YAW] )
01105         {//Not turning much, don't do this
01106                 //NOTE: Should this not happen only if you're not turning AT ALL?
01107                 //      You could be turning slowly but moving fast, so that would
01108                 //      still let you walk right off a cliff...
01109                 //NOTE: Or maybe it is a good idea to ALWAYS do this, regardless
01110                 //      of whether ot not we're turning?  But why would we be walking
01111                 //  straight into a wall or off a cliff unless we really wanted to?
01112                 return;
01113         }
01114         */
01115 
01116         //FIXME: to really do this right, we'd have to actually do a pmove to predict where we're
01117         //going to be... maybe this should be a flag and pmove handles it and sets a flag so AI knows
01118         //NEXT frame?  Or just incorporate current velocity, runspeed and possibly friction?
01119         VectorCopy( NPC->r.mins, mins );
01120         mins[2] += STEPSIZE;
01121         angles[PITCH] = angles[ROLL] = 0;
01122         angles[YAW] = NPC->client->ps.viewangles[YAW];//Add ucmd.angles[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         {//hmm, trace started inside this brush... how do we decide if we should continue?
01131                 //FIXME: what do we do if we start INSIDE a CONTENTS_BOTCLIP? Try the trace again without that in the clipmask?
01132                 if ( reset )
01133                 {
01134                         trace.fraction = 1.0f;
01135                 }
01136                 VectorCopy( testPos, trace.endpos );
01137                 //return qtrue;
01138         }
01139         if ( trace.fraction < 0.6 )
01140         {//Going to bump into something very close, don't move, just turn
01141                 if ( (NPC->enemy && trace.entityNum == NPC->enemy->s.number) || (NPCInfo->goalEntity && trace.entityNum == NPCInfo->goalEntity->s.number) )
01142                 {//okay to bump into enemy or goal
01143                         //Com_Printf( "%d bump into enemy/goal okay\n", level.time );
01144                         return qtrue;
01145                 }
01146                 else if ( reset )
01147                 {//actually want to screw with the ucmd
01148                         //Com_Printf( "%d avoiding walk into wall (entnum %d)\n", level.time, trace.entityNum );
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                 {//goal is below me, okay to step off at least that far plus stepheight
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         //FIXME:Should we try to see if we can still get to our goal using the waypoint network from this trace.endpos?
01169         //OR: just put NPC clip brushes on these edges (still fall through when die)
01170 
01171         if ( trace.allsolid || trace.startsolid )
01172         {//Not going off a cliff
01173                 //Com_Printf( "%d walk off cliff okay (droptrace in solid)\n", level.time );
01174                 return qtrue;
01175         }
01176 
01177         if ( trace.fraction < 1.0 )
01178         {//Not going off a cliff
01179                 //FIXME: what if plane.normal is sloped?  We'll slide off, not land... plus this doesn't account for slide-movement... 
01180                 //Com_Printf( "%d walk off cliff okay will hit entnum %d at dropdist of %4.2f\n", level.time, trace.entityNum, (trace.fraction*bottom_max) );
01181                 return qtrue;
01182         }
01183 
01184         //going to fall at least bottom_max, don't move, just turn... is this bad, though?  What if we want them to drop off?
01185         if ( reset )
01186         {//actually want to screw with the ucmd
01187                 //Com_Printf( "%d avoiding walk off cliff\n", level.time );
01188                 ucmd.forwardmove *= -1.0;//= 0;
01189                 ucmd.rightmove *= -1.0;//= 0;
01190                 VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir );
01191         }
01192         return qfalse;
01193 }
01194 /*
01195 -------------------------
01196 Jedi_HoldPosition
01197 -------------------------
01198 */
01199 
01200 static void Jedi_HoldPosition( void )
01201 {
01202         //NPCInfo->squadState = SQUAD_STAND_AND_SHOOT;
01203         NPCInfo->goalEntity = NULL;
01204         
01205         /*
01206         if ( TIMER_Done( NPC, "stand" ) )
01207         {
01208                 TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) );
01209         }
01210         */
01211 }
01212 
01213 /*
01214 -------------------------
01215 Jedi_Move
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         //FIXME: temp retreat behavior- should really make this toward a safe spot or maybe to outflank enemy
01230         if ( retreat )
01231         {//FIXME: should we trace and make sure we can go this way?  Or somehow let NPC_MoveToGoal know we want to retreat and have it handle it?
01232                 ucmd.forwardmove *= -1;
01233                 ucmd.rightmove *= -1;
01234                 VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir );
01235         }
01236         
01237         //Get the move info
01238         NAV_GetLastMove( &info );
01239 
01240         //If we hit our target, then stop and fire!
01241         if ( ( info.flags & NIF_COLLISION ) && ( info.blocker == NPC->enemy ) )
01242         {
01243                 Jedi_HoldPosition();
01244         }
01245 
01246         //If our move failed, then reset
01247         if ( moved == qfalse )
01248         {
01249                 Jedi_HoldPosition();
01250         }
01251 }
01252 
01253 static qboolean Jedi_Hunt( void )
01254 {
01255         //Com_Printf( "Hunting\n" );
01256         //if we're at all interested in fighting, go after him
01257         if ( NPCInfo->stats.aggression > 1 )
01258         {//approach enemy
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                         {//hunt
01269                                 NPCInfo->goalEntity = NPC->enemy;
01270                         }
01271                         //Jedi_Move( NPC->enemy, qfalse );
01272                         if ( NPC_MoveToGoal( qfalse ) )
01273                         {
01274                                 NPC_UpdateAngles( qtrue, qtrue );
01275                                 return qtrue;
01276                         }
01277                 }
01278         }
01279         return qfalse;
01280 }
01281 
01282 /*
01283 static qboolean Jedi_Track( void )
01284 {
01285         //if we're at all interested in fighting, go after him
01286         if ( NPCInfo->stats.aggression > 1 )
01287         {//approach enemy
01288                 NPCInfo->combatMove = qtrue;
01289                 NPC_SetMoveGoal( NPC, NPCInfo->enemyLastSeenLocation, 16, qtrue );
01290                 if ( NPC_MoveToGoal( qfalse ) )
01291                 {
01292                         NPC_UpdateAngles( qtrue, qtrue );
01293                         return qtrue;
01294                 }
01295         }
01296         return qfalse;
01297 }
01298 */
01299 
01300 static void Jedi_Retreat( void )
01301 {
01302         if ( !TIMER_Done( NPC, "noRetreat" ) )
01303         {//don't actually move
01304                 return;
01305         }
01306         //FIXME: when retreating, we should probably see if we can retreat 
01307         //in the direction we want.  If not...?  Evade?
01308         //Com_Printf( "Retreating\n" );
01309         Jedi_Move( NPC->enemy, qtrue );
01310 }
01311 
01312 static void Jedi_Advance( void )
01313 {
01314         if ( !NPC->client->ps.saberInFlight )
01315         {
01316                 //NPC->client->ps.SaberActivate();
01317                 WP_ActivateSaber(NPC);
01318         }
01319         //Com_Printf( "Advancing\n" );
01320         Jedi_Move( NPC->enemy, qfalse );
01321                 
01322         //TIMER_Set( NPC, "roamTime", Q_irand( 2000, 4000 ) );
01323         //TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) );
01324         //TIMER_Set( NPC, "duck", 0 );
01325 }
01326 
01327 static void Jedi_AdjustSaberAnimLevel( gentity_t *self, int newLevel )
01328 {       
01329         if ( !self || !self->client )
01330         {
01331                 return;
01332         }
01333         //FIXME: each NPC shold have a unique pattern of behavior for the order in which they
01334         if ( self->client->NPC_class == CLASS_TAVION )
01335         {//special attacks
01336                 self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_5;
01337                 return;
01338         }
01339         else if ( self->client->NPC_class == CLASS_DESANN )
01340         {//special attacks
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                 {//grunt and fencer always uses quick attacks
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                 {//acrobat & force-users always use medium attacks
01354                         self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_2;
01355                         return;
01356                 }
01357                 /*
01358                 if ( self->NPC->rank == RANK_LT ) 
01359                 {//boss always uses strong attacks
01360                         self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_3;
01361                         return;
01362                 }
01363                 */
01364         }
01365         //use the different attacks, how often they switch and under what circumstances
01366         if ( newLevel > self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] )
01367         {//cap it
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         {//go ahead and set it
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         {//not attacking
01400                 if ( TIMER_Done( NPC, "saberLevelDebounce" ) && !Q_irand( 0, 10 ) )
01401                 {
01402                         //Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel-1) );//drop
01403                         Jedi_AdjustSaberAnimLevel( NPC, Q_irand( FORCE_LEVEL_1, FORCE_LEVEL_3 ));//random
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 {//FIXME: for many of these checks, what we really want is horizontal distance to enemy
01415         if ( NPC->client->ps.fd.forcePowersActive&(1<<FP_GRIP) &&
01416                 NPC->client->ps.fd.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
01417         {//when gripping, don't move
01418                 return;
01419         }
01420         else if ( !TIMER_Done( NPC, "gripping" ) )
01421         {//stopped gripping, clear timers just in case
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         {//when draining, don't move
01429                 return;
01430         }
01431         else if ( !TIMER_Done( NPC, "draining" ) )
01432         {//stopped draining, clear timers just in case
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         {//maintain distance
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 //using saber
01472                         && NPC->client->ps.saberEntityState == SES_LEAVING  //not returning yet
01473                         && NPC->client->ps.fd.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_1 //2nd or 3rd level lightsaber
01474                         && !(NPC->client->ps.fd.forcePowersActive&(1 << FP_SPEED))
01475                         && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water
01476                 {//hold it out there
01477                         ucmd.buttons |= BUTTON_ALT_ATTACK;
01478                         //FIXME: time limit?
01479                 }
01480         }
01481         else if ( !TIMER_Done( NPC, "taunting" ) )
01482         {
01483                 if ( enemy_dist <= 64 )
01484                 {//he's getting too close
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                 //else if ( NPC->client->ps.torsoAnim == BOTH_GESTURE1 && NPC->client->ps.torsoTimer < 2000 )
01493                 else if (NPC->client->ps.forceHandExtend == HANDEXTEND_JEDITAUNT && (NPC->client->ps.forceHandExtendTime - level.time) < 200)
01494                 {//we're almost done with our special taunt
01495                         //FIXME: this doesn't always work, for some reason
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         {//we won a saber lock, press the advantage
01504                 if ( enemy_dist > 0 )
01505                 {//get closer so we can hit!
01506                         Jedi_Advance();
01507                 }
01508                 if ( enemy_dist > 128 )
01509                 {//lost 'em
01510                         NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON;
01511                 }
01512                 if ( NPC->enemy->painDebounceTime + 2000 < level.time )
01513                 {//the window of opportunity is gone
01514                         NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON;
01515                 }
01516                 //don't strafe?
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         {//enemy is in a saberLock and we are not
01525                 if ( enemy_dist < 64 )
01526                 {//FIXME: maybe just pick another enemy?
01527                         Jedi_Retreat();
01528                 }
01529         }
01530         /*
01531         else if ( NPC->enemy->s.weapon == WP_TURRET 
01532                 && !Q_stricmp( "PAS", NPC->enemy->classname ) 
01533                 && NPC->enemy->s.apos.trType == TR_STATIONARY )
01534         {
01535                 int     testlevel;
01536                 if ( enemy_dist > forcePushPullRadius[FORCE_LEVEL_1] - 16 )
01537                 {
01538                         Jedi_Advance();
01539                 }
01540                 if ( NPC->client->ps.fd.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_1 )
01541                 {//
01542                         testlevel = FORCE_LEVEL_1;
01543                 }
01544                 else
01545                 {
01546                         testlevel = NPC->client->ps.fd.forcePowerLevel[FP_PUSH];
01547                 }
01548                 if ( enemy_dist < forcePushPullRadius[testlevel] - 16 )
01549                 {//close enough to push
01550                         if ( InFront( NPC->enemy->r.currentOrigin, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, 0.6f ) )
01551                         {//knock it down
01552                                 WP_KnockdownTurret( NPC, NPC->enemy );
01553                                 //do the forcethrow call just for effect
01554                                 ForceThrow( NPC, qfalse );
01555                         }
01556                 }
01557         }
01558         */
01559         //rwwFIXMEFIXME: Give them the ability to do this again (turret needs to be fixed up to allow it)
01560         else if ( enemy_dist <= 64 
01561                 && ((NPCInfo->scriptFlags&SCF_DONT_FIRE)||(!Q_stricmp("Yoda",NPC->NPC_type)&&!Q_irand(0,10))) )
01562         {//can't use saber and they're in striking range
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)//lost over 1/4 of our health or not firing
01566                                 && NPC->client->ps.fd.forcePowersKnown&(1<<FP_DRAIN) //know how to drain
01567                                 && WP_ForcePowerAvailable( NPC, FP_DRAIN, 20 )//have enough power
01568                                 && !Q_irand( 0, 2 ) )
01569                         {//drain
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//lost over 1/4 of our health
01584                 && NPC->client->ps.fd.forcePowersKnown&(1<<FP_DRAIN) //know how to drain
01585                 && WP_ForcePowerAvailable( NPC, FP_DRAIN, 20 )//have enough power
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         {//we're too damn close!
01596                 Jedi_Retreat();
01597         }
01598         else if ( enemy_dist <= 0 )
01599         {//we're within striking range
01600                 //if we are attacking, see if we should stop
01601                 if ( NPCInfo->stats.aggression < 4 )
01602                 {//back off and defend
01603                         Jedi_Retreat();
01604                 }
01605         }
01606         else if ( enemy_dist > 256 )
01607         {//we're way out of range
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                                 //FIXME: check level of heal and know not to move or attack when healing
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                         //FIXME: what about things like mind tricks and force sight?
01643                 }
01644                 if ( enemy_dist > 384 )
01645                 {//FIXME: check for enemy facing away and/or moving away
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                 //Unless we're totally hiding, go after him
01656                 if ( NPCInfo->stats.aggression > 0 )
01657                 {//approach enemy
01658                         if ( !usedForce )
01659                         {
01660                                 Jedi_Advance();
01661                         }
01662                 }
01663         }
01664         /*
01665         else if ( enemy_dist < 96 && NPC->enemy && NPC->enemy->client && NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE )
01666         {//too close and in air, so retreat
01667                 Jedi_Retreat();
01668         }
01669         */
01670         //FIXME: enemy_dist calc needs to include all blade lengths, and include distance from hand to start of blade....
01671         else if ( enemy_dist > 50 )//FIXME: not hardcoded- base on our reach (modelScale?) and saberLengthMax
01672         {//we're out of striking range and we are allowed to attack
01673                 //first, check some tactical force power decisions
01674                 if ( NPC->enemy && NPC->enemy->client && (NPC->enemy->client->ps.fd.forceGripBeingGripped > level.time) )
01675                 {//They're being gripped, rush them!
01676                         if ( NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
01677                         {//they're on the ground, so advance
01678                                 if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT )
01679                                 {//not parrying
01680                                         if ( enemy_dist > 200 || !(NPCInfo->scriptFlags&SCF_DONT_FIRE) )
01681                                         {//far away or allowed to use saber
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) )//saber not in water
01690                         {//throw saber
01691                                 ucmd.buttons |= BUTTON_ALT_ATTACK;
01692                         }
01693                 }
01694                 else if ( NPC->enemy && NPC->enemy->client && //valid enemy
01695                         NPC->enemy->client->ps.saberInFlight && /*NPC->enemy->client->ps.saber[0].Active()*/ NPC->enemy->client->ps.saberEntityNum && //enemy throwing saber
01696                         NPC->client->ps.weaponTime <= 0 && //I'm not busy
01697                         WP_ForcePowerAvailable( NPC, FP_GRIP, 0 ) && //I can use the power
01698                         !Q_irand( 0, 10 ) && //don't do it all the time, averages to 1 check a second
01699                         Q_irand( 0, 6 ) < g_spskill.integer && //more likely on harder diff
01700                         Q_irand( RANK_CIVILIAN, RANK_CAPTAIN ) < NPCInfo->rank )//more likely against harder enemies
01701                 {//They're throwing their saber, grip them!
01702                         //taunt
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                         //grip
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                         {//They're choking someone, probably an ally, run at them and do some sort of attack
01720                                 if ( NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
01721                                 {//they're on the ground, so advance
01722                                         if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT )
01723                                         {//not parrying
01724                                                 if ( enemy_dist > 200 || !(NPCInfo->scriptFlags&SCF_DONT_FIRE) )
01725                                                 {//far away or allowed to use saber
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                         {//else, randomly try some kind of attack every now and then
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                                                 //force pull the guy to me!
01754                                                 //FIXME: check forcePushRadius[NPC->client->ps.fd.forcePowerLevel[FP_PUSH]]
01755                                                 ForceThrow( NPC, qtrue );
01756                                                 //maybe strafe too?
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                                         /*else if ( NPC->health < NPC->client->pers.maxHealth * 0.75f
01774                                                 && Q_irand( FORCE_LEVEL_0, NPC->client->ps.fd.forcePowerLevel[FP_DRAIN] ) > FORCE_LEVEL_1
01775                                                 && WP_ForcePowerAvailable( NPC, FP_DRAIN, 0 ) 
01776                                                 && Q_irand( 0, 1 ) )
01777                                         {
01778                                                 ForceDrain2( NPC );
01779                                                 NPC->client->ps.weaponTime = Q_irand( 1000, 3000+(g_spskill.integer*500) );
01780                                                 TIMER_Set( NPC, "draining", NPC->client->ps.weaponTime );
01781                                                 TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime );
01782                                         }*/
01783                                         //rwwFIXMEFIXME: After new drain stuff from SP is in re-enable this.
01784                                         else if ( WP_ForcePowerAvailable( NPC, FP_GRIP, 0 ) )
01785                                         {
01786                                                 //taunt
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                                                 //grip
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) )//saber not in water
01803                                                 {//throw saber
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) )//saber not in water
01813                                         {//throw saber
01814                                                 ucmd.buttons |= BUTTON_ALT_ATTACK;
01815                                         }
01816                                 }
01817                         }
01818                         //see if we should advance now
01819                         else if ( NPCInfo->stats.aggression > 5 )
01820                         {//approach enemy
01821                                 if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT )
01822                                 {//not parrying
01823                                         if ( !NPC->enemy->client || NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
01824                                         {//they're on the ground, so advance
01825                                                 if ( enemy_dist > 200 || !(NPCInfo->scriptFlags&SCF_DONT_FIRE) )
01826                                                 {//far away or allowed to use saber
01827                                                         Jedi_Advance();
01828                                                 }
01829                                         }
01830                                 }
01831                         }
01832                         else
01833                         {//maintain this distance?
01834                                 //walk?
01835                         }
01836                 }
01837         }
01838         else
01839         {//we're not close enough to attack, but not far enough away to be safe
01840                 if ( NPCInfo->stats.aggression < 4 )
01841                 {//back off and defend
01842                         Jedi_Retreat();
01843                 }
01844                 else if ( NPCInfo->stats.aggression > 5 )
01845                 {//try to get closer
01846                         if ( enemy_dist > 0 && !(NPCInfo->scriptFlags&SCF_DONT_FIRE))
01847                         {//we're allowed to use our lightsaber, get closer
01848                                 if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT )
01849                                 {//not parrying
01850                                         if ( !NPC->enemy->client || NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
01851                                         {//they're on the ground, so advance
01852                                                 Jedi_Advance();
01853                                         }
01854                                 }
01855                         }
01856                 }
01857                 else
01858                 {//agression is 4 or 5... somewhere in the middle
01859                         //what do we do here?  Nothing?
01860                         //Move forward and back?
01861                 }
01862         }
01863         //if really really mad, rage!
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         {//don't strafe if pressing the advantage of winning a saberLock
01880                 return qfalse;
01881         }
01882         if ( TIMER_Done( NPC, "strafeLeft" ) && TIMER_Done( NPC, "strafeRight" ) )
01883         {
01884                 qboolean strafed = qfalse;
01885                 //TODO: make left/right choice a tactical decision rather than random:
01886                 //              try to keep own back away from walls and ledges, 
01887                 //              try to keep enemy's back to a ledge or wall
01888                 //              Maybe try to strafe toward designer-placed "safe spots" or "goals"?
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                         {//should be a slow strafe
01923                                 TIMER_Set( NPC, "walking", strafeTime );
01924                         }
01925                         return qtrue;
01926                 }
01927         }
01928         return qfalse;
01929 }
01930 
01931 /*
01932 static void Jedi_FaceEntity( gentity_t *self, gentity_t *other, qboolean doPitch )
01933 {
01934         vec3_t          entPos;
01935         vec3_t          muzzle;
01936 
01937         //Get the positions
01938         CalcEntitySpot( other, SPOT_ORIGIN, entPos );
01939 
01940         //Get the positions
01941         CalcEntitySpot( self, SPOT_HEAD_LEAN, muzzle );//SPOT_HEAD
01942 
01943         //Find the desired angles
01944         vec3_t  angles;
01945 
01946         GetAnglesForDirection( muzzle, entPos, angles );
01947 
01948         self->NPC->desiredYaw           = AngleNormalize360( angles[YAW] );
01949         if ( doPitch )
01950         {
01951                 self->NPC->desiredPitch = AngleNormalize360( angles[PITCH] );
01952         }
01953 }
01954 */
01955 
01956 /*
01957 qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc )
01958 
01959 Jedi will play a dodge anim, blur, and make the force speed noise.
01960 
01961 Right now used to dodge instant-hit weapons.
01962 
01963 FIXME: possibly call this for saber melee evasion and/or missile evasion?
01964 FIXME: possibly let player do this too?
01965 */
01966 //rwwFIXMEFIXME: Going to use qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc ) from
01967 //w_saber.c.. maybe use seperate one for NPCs or add cases to that one?
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         {//no fancy dodges when raging
01978                 return EVASION_NONE;
01979         }
01980         //Check for:
01981         //ARIALS/CARTWHEELS
01982         //WALL-RUNS
01983         //WALL-FLIPS
01984         //FIXME: if facing a wall, do forward wall-walk-backflip
01985         if ( self->client->ps.legsAnim == BOTH_WALL_RUN_LEFT || self->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT )
01986         {//already running on a wall
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                 {//I'm running on a wall to my left and the attack is on the left
01998                         if ( animLength - self->client->ps.legsTimer > 400
01999                                 && self->client->ps.legsTimer > 400 )
02000                         {//not at the beginning or end of the anim
02001                                 anim = BOTH_WALL_RUN_LEFT_FLIP;
02002                         }
02003                 }
02004                 else if ( self->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT && rightdot > 0 )
02005                 {//I'm running on a wall to my right and the attack is on the right
02006                         if ( animLength - self->client->ps.legsTimer > 400
02007                                 && self->client->ps.legsTimer > 400 )
02008                         {//not at the beginning or end of the anim
02009                                 anim = BOTH_WALL_RUN_RIGHT_FLIP;
02010                         }
02011                 }
02012                 if ( anim != -1 )
02013                 {//flip off the wall!
02014                         int parts;
02015                         //FIXME: check the direction we will flip towards for do-not-enter/walls/drops?
02016                         //NOTE: we presume there is still a wall there!
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                         //self->client->ps.pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL);
02036                         //rwwFIXMEFIXME: Add these pm flags?
02037                         G_AddEvent( self, EV_JUMP, 0 );
02038                         return EVASION_OTHER;
02039                 }
02040         }
02041         else if ( self->client->NPC_class != CLASS_DESANN //desann doesn't do these kind of frilly acrobatics
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                 //trace in the dir that we want to go
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                 {//it's clear, let's do it
02127                         //FIXME: check for drops?
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;//don't attack again until this anim is done
02132                         VectorCopy( self->client->ps.viewangles, fwdAngles );
02133                         fwdAngles[PITCH] = fwdAngles[ROLL] = 0;
02134                         //do the flip
02135                         AngleVectors( fwdAngles, NULL, jumpRt, NULL );
02136                         VectorScale( jumpRt, speed, self->client->ps.velocity );
02137                         self->client->ps.fd.forceJumpCharge = 0;//so we don't play the force flip anim
02138                         self->client->ps.velocity[2] = 200;
02139                         self->client->ps.fd.forceJumpZStart = self->r.currentOrigin[2];//so we don't take damage if we land at same height
02140                         //self->client->ps.pm_flags |= PMF_JUMPING;
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                         //ucmd.upmove = 0;
02150                         return EVASION_CARTWHEEL;
02151                 }
02152                 else if ( !(trace.contents&CONTENTS_BOTCLIP) )
02153                 {//hit a wall, not a do-not-enter brush
02154                         //FIXME: before we check any of these jump-type evasions, we should check for headroom, right?
02155                         //Okay, see if we can flip *off* the wall and go the other way
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                         {//it's a ent of some sort or it's a wall roughly facing us
02164                                 float bestCheckDist = 0;
02165                                 //hmm, see if we're moving forward
02166                                 if ( DotProduct( self->client->ps.velocity, fwd ) < 200 )
02167                                 {//not running forward very fast
02168                                         //check to see if it's okay to move the other way
02169                                         if ( (trace.fraction*checkDist) <= 32 )
02170                                         {//wall on that side is close enough to wall-flip off of or wall-run on
02171                                                 bestCheckDist = checkDist;
02172                                                 checkDist *= -1.0f;
02173                                                 VectorMA( self->r.currentOrigin, checkDist, right, traceto );
02174                                                 //trace in the dir that we want to go
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                                                 {//it's clear, let's do it
02178                                                         if ( allowWallFlips )
02179                                                         {//okay to do wall-flips with this saber
02180                                                                 int parts;
02181 
02182                                                                 //FIXME: check for drops?
02183                                                                 //turn the cartwheel into a wallflip in the other dir
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                                                                 //animate me
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];//so we don't take damage if we land at same height
02205                                                                 //self->client->ps.pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL);
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                                                 {//boxed in on both sides
02219                                                         if ( DotProduct( self->client->ps.velocity, fwd ) < 0 )
02220                                                         {//moving backwards
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                                         {//too far from that wall to flip or run off it, check other side
02231                                                 checkDist *= -1.0f;
02232                                                 VectorMA( self->r.currentOrigin, checkDist, right, traceto );
02233                                                 //trace in the dir that we want to go
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                                                 {//wall on this side is close enough
02237                                                         bestCheckDist = checkDist;
02238                                                 }
02239                                                 else
02240                                                 {//neither side has a wall within 32
02241                                                         return EVASION_NONE;
02242                                                 }
02243                                         }
02244                                 }
02245                                 //Try wall run?
02246                                 if ( bestCheckDist )
02247                                 {//one of the walls was close enough to wall-run on
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                                         {//okay to do wallruns with this saber
02266                                                 int parts;
02267 
02268                                                 //FIXME: check for long enough wall and a drop at the end?
02269                                                 if ( bestCheckDist > 0 )
02270                                                 {//it was to the right
02271                                                         anim = BOTH_WALL_RUN_RIGHT;
02272                                                 }
02273                                                 else
02274                                                 {//it was to the left
02275                                                         anim = BOTH_WALL_RUN_LEFT;
02276                                                 }
02277                                                 self->client->ps.velocity[2] = forceJumpStrength[FORCE_LEVEL_2]/2.25f;
02278                                                 //animate me
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];//so we don't take damage if we land at same height
02286                                                 //self->client->ps.pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL);
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                                 //else check for wall in front, do backflip off wall
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         {//player 
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;//500;
02367 
02368                                         switch ( g_spskill.integer )
02369                                         {
02370                                         case 0:
02371                                                 baseTime = 200;//500;
02372                                                 break;
02373                                         case 1:
02374                                                 baseTime = 100;//300;
02375                                                 break;
02376                                         case 2:
02377                                         default:
02378                                                 baseTime = 50;//100;
02379                                                 break;
02380                                         }
02381                                 }
02382 
02383                                 if ( self->client->NPC_class == CLASS_TAVION )
02384                                 {//Tavion is faster
02385                                         baseTime = ceil(baseTime/2.0f);
02386                                 }
02387                                 else if ( self->NPC->rank >= RANK_LT_JG )
02388                                 {//fencers, bosses, shadowtroopers, luke, desann, et al use the norm
02389                                         if ( Q_irand( 0, 2 ) )
02390                                         {//medium speed parry
02391                                                 baseTime = baseTime;
02392                                         }
02393                                         else
02394                                         {//with the occasional fast parry
02395                                                 baseTime = ceil(baseTime/2.0f);
02396                                         }
02397                                 }
02398                                 else if ( self->NPC->rank == RANK_CIVILIAN )
02399                                 {//grunts are slowest
02400                                         baseTime = baseTime*Q_irand(1,3);
02401                                 }
02402                                 else if ( self->NPC->rank == RANK_CREWMAN )
02403                                 {//acrobats aren't so bad
02404                                         if ( evasionType == EVASION_PARRY
02405                                                 || evasionType == EVASION_DUCK_PARRY
02406                                                 || evasionType == EVASION_JUMP_PARRY )
02407                                         {//slower with parries
02408                                                 baseTime = baseTime*Q_irand(1,2);
02409                                         }
02410                                         else
02411                                         {//faster with acrobatics
02412                                                 //baseTime = baseTime;
02413                                         }
02414                                 }
02415                                 else
02416                                 {//force users are kinda slow
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                 //|| PM_SaberInBounce( self->client->ps.saberMove ) 
02462                 || PM_SaberInBrokenParry( self->client->ps.saberMove ) 
02463                 //|| PM_SaberInDeflect( self->client->ps.saberMove ) 
02464                 || BG_FlippingAnim( self->client->ps.torsoAnim ) 
02465                 || PM_RollingAnim( self->client->ps.torsoAnim ) ) )
02466         {//my saber is not in a parrying position
02467                 return qtrue;
02468         }
02469         return qfalse;
02470 }
02471 
02472 /*
02473 -------------------------
02474 Jedi_SaberBlock
02475 
02476 Pick proper block anim
02477 
02478 FIXME: Based on difficulty level/enemy saber combat skill, make this decision-making more/less effective
02479 
02480 NOTE: always blocking projectiles in this func!
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 ) //dist = 0.0f
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         //FIXME: if we don't have our saber in hand, pick the force throw option or a jump or strafe!
02496         //FIXME: reborn don't block enough anymore
02497         if ( !incoming )
02498         {
02499                 VectorCopy( pHitloc, hitloc );
02500                 VectorCopy( phitDir, hitdir );
02501                 //FIXME: maybe base this on rank some?  And/or g_spskill?
02502                 if ( self->client->ps.saberInFlight )
02503                 {//DOH!  do non-saber evasion!
02504                         saberBusy = qtrue;
02505                 }
02506                 else if ( Jedi_QuickReactions( self ) )
02507                 {//jedi trainer and tavion are must faster at parrying and can do it whenever they like
02508                         //Also, on medium, all level 3 people can parry any time and on hard, all level 2 or 3 people can parry any time
02509                 }
02510                 else
02511                 {
02512                         saberBusy = Jedi_SaberBusy( self );
02513                 }
02514         }
02515         else
02516         {       
02517                 if ( incoming->s.weapon == WP_SABER )
02518                 {//flying lightsaber, face it!
02519                         //FIXME: for this to actually work, we'd need to call update angles too?
02520                         //Jedi_FaceEntity( self, incoming, qtrue );
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         //VectorNormalize( diff );
02533         fwdangles[1] = self->client->ps.viewangles[1];
02534         // Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
02535         AngleVectors( fwdangles, NULL, right, NULL );
02536 
02537         rightdot = DotProduct(right, diff);// + flrand(-0.10f,0.10f);
02538         //totalHeight = self->client->renderInfo.eyePoint[2] - self->r.absmin[2];
02539         zdiff = hitloc[2] - self->client->renderInfo.eyePoint[2];// + Q_irand(-6,6);
02540         
02541         //see if we can dodge if need-be
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         {//either it will miss by a bit (and 25% chance) OR our saber is not in-hand OR saber is off
02547                 if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank >= RANK_LT_JG) )
02548                 {//acrobat or fencer or above
02549                         if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE &&//on the ground
02550                                 !(self->client->ps.pm_flags&PMF_DUCKED)&&cmd->upmove>=0&&TIMER_Done( self, "duck" )//not ducking
02551                                 && !BG_InRoll( &self->client->ps, self->client->ps.legsAnim )//not rolling
02552                                 && !PM_InKnockDown( &self->client->ps )//not knocked down
02553                                 && ( self->client->ps.saberInFlight ||
02554                                         self->client->NPC_class == CLASS_BOBAFETT ||
02555                                         (!BG_SaberInAttack( self->client->ps.saberMove )//not attacking
02556                                         && !PM_SaberInStart( self->client->ps.saberMove )//not starting an attack
02557                                         && !BG_SpinningSaberAnim( self->client->ps.torsoAnim )//not in a saber spin
02558                                         && !BG_SaberInSpecialAttack( self->client->ps.torsoAnim ))//not in a special attack
02559                                         )
02560                                 )
02561                         {//need to check all these because it overrides both torso and legs with the dodge
02562                                 doDodge = qtrue;
02563                         }
02564                 }
02565         }
02566         // Figure out what quadrant the block was in.
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         //UL = > -1//-6
02573         //UR = > -6//-9
02574         //TOP = > +6//+4
02575         //FIXME: take FP_SABER_DEFENSE into account here somehow?
02576         if ( zdiff >= -5 )//was 0
02577         {
02578                 if ( incoming || !saberBusy )
02579                 {
02580                         if ( rightdot > 12 
02581                                 || (rightdot > 3 && zdiff < 5) 
02582                                 || (!incoming&&fabs(hitdir[2])<0.25f) )//was normalized, 0.3
02583                         {//coming from right
02584                                 if ( doDodge )
02585                                 {
02586                                         if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) )
02587                                         {//roll!
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) )//was normalized, -0.3
02633                         {//coming from left
02634                                 if ( doDodge )
02635                                 {
02636                                         if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) )
02637                                         {//roll!
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                                 //duckChance = 2;
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         //LL = -22//= -18 to -39
02711         //LR = -23//= -20 to -41
02712         else if ( zdiff > -22 )//was-15 )
02713         {
02714                 if ( 1 )//zdiff < -10 )
02715                 {//hmm, pretty low, but not low enough to use the low block, so we need to duck
02716                         if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
02717                         {
02718                                 //duckChance = 2;
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                         {//in air!  Ducking does no good
02729                         }
02730                 }
02731                 if ( incoming || !saberBusy )
02732                 {
02733                         if ( rightdot > 8 || (rightdot > 3 && zdiff < -11) )//was normalized, 0.2
02734                         {
02735                                 if ( doDodge )
02736                                 {
02737                                         if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) )
02738                                         {//roll!
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) )//was normalized, -0.2
02765                         {
02766                                 if ( doDodge )
02767                                 {
02768                                         if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) )
02769                                         {//roll!
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 ) ) ) )//was -30 and -40//2nd one was -46
02815         {//jump!
02816                 if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE )
02817                 {//already in air, duck to pull up legs
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                                 //since the jump may be cleared if not safe, set a lower block too
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                 {//gotta jump!
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                         {//superjump
02854                                 //FIXME: check the jump, if can't, then block
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;//FIXME: calc this intelligently
02862                                         evasionType = EVASION_FJUMP;
02863                                         evaded = qtrue;
02864                                         if ( d_JediAI.integer )
02865                                         {
02866                                                 Com_Printf( "force jump + " );
02867                                         }
02868                                 }
02869                         }
02870                         else
02871                         {//normal jump
02872                                 //FIXME: check the jump, if can't, then block
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                                         {//roll!
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                                                 {//do the butterfly!
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];//so we don't take damage if we land at same height
02936                                                 //      self->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL;
02937                                                 //      self->client->ps.SaberActivateTrail( 300 );//FIXME: reset this when done!
02938                                                         //Ah well. No hacking from the server for now.
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                                 //since the jump may be cleared if not safe, set a lower block too
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                         {//thrown saber!
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                                 {//superjump
03029                                         //FIXME: check the jump, if can't, then block
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;//FIXME: calc this intelligently
03037                                                 evasionType = EVASION_FJUMP;
03038                                                 if ( d_JediAI.integer )
03039                                                 {
03040                                                         Com_Printf( "force jump + " );
03041                                                 }
03042                                         }
03043                                 }
03044                                 else
03045                                 {//normal jump
03046                                         //FIXME: check the jump, if can't, then block
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         //stop taunting
03077         TIMER_Set( self, "taunting", 0 );
03078         //stop gripping
03079         TIMER_Set( self, "gripping", -level.time );
03080         WP_ForcePowerStop( self, FP_GRIP );
03081         //stop draining
03082         TIMER_Set( self, "draining", -level.time );
03083         WP_ForcePowerStop( self, FP_DRAIN );
03084 
03085         if ( dodgeAnim != -1 )
03086         {//dodged
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                 //force them to stop moving in this case
03091                 self->client->ps.pm_time = self->client->ps.torsoTimer;
03092                 //FIXME: maybe make a sound?  Like a grunt?  EV_JUMP?
03093                 self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
03094                 //dodged, not block
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                                 if ( d_JediAI.integer )
03117                                 {
03118                                         Com_Printf( "duck " );
03119                                 }
03120                                 */
03121                         }
03122                 }
03123 
03124                 if ( incoming )
03125                 {
03126                         self->client->ps.saberBlocked = WP_MissileBlockForBlock( self->client->ps.saberBlocked );
03127                 }
03128 
03129         }
03130         //if ( self->client->ps.saberBlocked != BLOCKED_NONE )
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 ) //saberNum = 0, bladeNum = 0
03144 {
03145         vec3_t hitloc, saberTipOld, saberTip, top, bottom, axisPoint, saberPoint, dir;//saberBase, 
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         //FIXME: reborn don't block enough anymore
03153         /*
03154         //maybe do this on easy only... or only on grunt-level reborn
03155         if ( NPC->client->ps.weaponTime )
03156         {//i'm attacking right now
03157                 return qfalse;
03158         }
03159         */
03160 
03161         if ( !TIMER_Done( NPC, "parryReCalcTime" ) )
03162         {//can't do our own re-think of which parry to use yet
03163                 return qfalse;
03164         }
03165 
03166         if ( NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] > level.time )
03167         {//can't move the saber to another position yet
03168                 return qfalse;
03169         }
03170         
03171         /*
03172         if ( NPCInfo->rank < RANK_LT_JG && Q_irand( 0, (2 - g_spskill.integer) ) )
03173         {//lower rank reborn have a random chance of not doing it at all
03174                 NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 300;
03175                 return qfalse;
03176         }
03177         */
03178 
03179         if ( NPC->enemy->health <= 0 || !NPC->enemy->client )
03180         {//don't keep blocking him once he's dead (or if not a client)
03181                 return qfalse;
03182         }
03183         /*
03184         //VectorMA( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDir, saberTip );
03185         //VectorMA( NPC->enemy->client->renderInfo.muzzlePointNext, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDirNext, saberTipNext );
03186         VectorMA( NPC->enemy->client->renderInfo.muzzlePointOld, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDirOld, saberTipOld );
03187         VectorMA( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDir, saberTip );
03188 
03189         VectorSubtract( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->renderInfo.muzzlePointOld, dir );//get the dir
03190         VectorAdd( dir, NPC->enemy->client->renderInfo.muzzlePoint, saberBase );//extrapolate
03191 
03192         VectorSubtract( saberTip, saberTipOld, dir );//get the dir
03193         VectorAdd( dir, saberTip, saberTipOld );//extrapolate
03194 
03195         VectorCopy( NPC->r.currentOrigin, top );
03196         top[2] = NPC->r.absmax[2];
03197         VectorCopy( NPC->r.currentOrigin, bottom );
03198         bottom[2] = NPC->r.absmin[2];
03199 
03200         float dist = ShortestLineSegBewteen2LineSegs( saberBase, saberTipOld, bottom, top, saberPoint, axisPoint );
03201         if ( 0 )//dist > NPC->r.maxs[0]*4 )//was *3
03202         {//FIXME: sometimes he reacts when you're too far away to actually hit him
03203                 if ( d_JediAI.integer )
03204                 {
03205                         Com_Printf( "enemy saber dist: %4.2f\n", dist );
03206                 }
03207                 TIMER_Set( NPC, "parryTime", -1 );
03208                 return qfalse;
03209         }
03210 
03211         //get the actual point of impact
03212         trace_t tr;
03213         trap_Trace( &tr, saberPoint, vec3_origin, vec3_origin, axisPoint, NPC->enemy->s.number, MASK_SHOT, G2_RETURNONHIT, 10 );
03214         if ( tr.allsolid || tr.startsolid )
03215         {//estimate
03216                 VectorSubtract( saberPoint, axisPoint, dir );
03217                 VectorNormalize( dir );
03218                 VectorMA( axisPoint, NPC->r.maxs[0]*1.22, dir, hitloc );
03219         }
03220         else
03221         {
03222                 VectorCopy( tr.endpos, hitloc );
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 //      VectorCopy(NPC->enemy->client->lastSaberBase_Always, muzzlePoint);
03231 //      VectorMA(muzzlePoint, GAME_SABER_LENGTH, NPC->enemy->client->lastSaberDir_Always, saberTip);
03232 //      VectorCopy(saberTip, saberTipOld);
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 )//was *3
03241         {//FIXME: sometimes he reacts when you're too far away to actually hit him
03242                 if ( d_JediAI.integer )
03243                 {
03244                         Com_Printf( S_COLOR_RED"enemy saber dist: %4.2f\n", dist );
03245                 }
03246                 /*
03247                 if ( dist < 300 //close
03248                         && !Jedi_QuickReactions( NPC )//quick reaction people can interrupt themselves
03249                         && (PM_SaberInStart( NPC->enemy->client->ps.saberMove ) || BG_SaberInAttack( NPC->enemy->client->ps.saberMove )) )//enemy is swinging at me
03250                 {//he's swinging at me and close enough to be a threat, don't start an attack right now
03251                         TIMER_Set( NPC, "parryTime", 100 );
03252                 }
03253                 else
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         //get the actual point of impact
03285         trap_Trace( &tr, saberPoint, saberMins, saberMaxs, hitloc, NPC->enemy->s.number, CONTENTS_BODY );//, G2_RETURNONHIT, 10 );
03286         if ( tr.allsolid || tr.startsolid || tr.fraction >= 1.0f )
03287         {//estimate
03288                 vec3_t  dir2Me;
03289                 VectorSubtract( axisPoint, saberPoint, dir2Me );
03290                 dist = VectorNormalize( dir2Me );
03291                 if ( DotProduct( dir, dir2Me ) < 0.2f )
03292                 {//saber is not swinging in my direction
03293                         /*
03294                         if ( dist < 300 //close
03295                                 && !Jedi_QuickReactions( NPC )//quick reaction people can interrupt themselves
03296                                 && (PM_SaberInStart( NPC->enemy->client->ps.saberMove ) || BG_SaberInAttack( NPC->enemy->client->ps.saberMove )) )//enemy is swinging at me
03297                         {//he's swinging at me and close enough to be a threat, don't start an attack right now
03298                                 TIMER_Set( NPC, "parryTime", 100 );
03299                         }
03300                         else
03301                         */
03302                         {
03303                                 TIMER_Set( NPC, "parryTime", -1 );
03304                         }
03305                         return qfalse;
03306                 }
03307                 ShortestLineSegBewteen2LineSegs( saberPoint, hitloc, bottom, top, saberHitPoint, hitloc );
03308                 /*
03309                 VectorSubtract( saberPoint, axisPoint, dir );
03310                 VectorNormalize( dir );
03311                 VectorMA( axisPoint, NPC->r.maxs[0]*1.22, dir, hitloc );
03312                 */
03313         }
03314         else
03315         {
03316                 VectorCopy( tr.endpos, hitloc );
03317         }
03318         
03319         if ( d_JediAI.integer )
03320         {
03321                 //G_DebugLine( saberPoint, hitloc, FRAMETIME, WPDEBUG_SaberColor( NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].color ), qtrue );
03322                 G_TestLine(saberPoint, hitloc, 0x0000ff, FRAMETIME);
03323         }
03324 
03325         //FIXME: if saber is off and/or we have force speed and want to be really cocky, 
03326         //              and the swing misses by some amount, we can use the dodges here... :)
03327         if ( (evasionType=Jedi_SaberBlockGo( NPC, &ucmd, hitloc, dir, NULL, dist )) != EVASION_DODGE )
03328         {//we did block (not dodge)
03329                 int parryReCalcTime;
03330 
03331                 if ( !NPC->client->ps.saberInFlight )
03332                 {//make sure saber is on
03333                         WP_ActivateSaber(NPC);
03334                 }
03335 
03336                 //debounce our parry recalc time
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                 //determine how long to hold this anim
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                         {//fencers and higher hold a parry less
03353                                 TIMER_Set( NPC, "parryTime", parryReCalcTime );
03354                         }
03355                         else
03356                         {//others hold it longer
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                 {//higher-level guys can dodge faster
03366                         dodgeTime -= 200;
03367                 }
03368                 TIMER_Set( NPC, "parryReCalcTime", dodgeTime );
03369                 TIMER_Set( NPC, "parryTime", dodgeTime );
03370         }
03371         return qtrue;
03372 }
03373 /*
03374 -------------------------
03375 Jedi_EvasionSaber
03376 
03377 defend if other is using saber and attacking me!
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;//only step aside 30% if he's moving at me but not attacking
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         {//don't try to block/evade an enemy who is in a saberLock
03396                 return;
03397         }
03398         else if ( NPC->client->ps.saberEventFlags&SEF_LOCK_WON && NPC->enemy->painDebounceTime > level.time )
03399         {//pressing the advantage of winning a saber lock
03400                 return;
03401         }
03402 
03403         if ( NPC->enemy->client->ps.saberInFlight && !TIMER_Done( NPC, "taunting" ) )
03404         {//if he's throwing his saber, stop taunting
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                 {//wasn't blocked myself
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         {//enemy is attacking
03434                 enemy_attacking = qtrue;
03435                 evasionChance = 90;
03436         }
03437 
03438         if ( (NPC->enemy->client->ps.fd.forcePowersActive&(1<<FP_LIGHTNING) ) )
03439         {//enemy is shooting lightning
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         {//enemy is shooting lightning
03447                 enemy_attacking = qtrue;
03448                 throwing_saber = qtrue;
03449         }
03450 
03451         //FIXME: this needs to take skill and rank(reborn type) into account much more
03452         if ( Q_irand( 0, 100 ) < evasionChance )
03453         {//check to see if he's coming at me
03454                 float facingAmt;
03455                 if ( VectorCompare( enemy_movedir, vec3_origin ) || shooting_lightning || throwing_saber )
03456                 {//he's not moving (or he's using a ranged attack), see if he's facing me
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                 {//he's moving
03463                         facingAmt = DotProduct( enemy_movedir, dirEnemy2Me );
03464                 }
03465                         
03466                 if ( flrand( 0.25, 1 ) < facingAmt )
03467                 {//coming at/facing me!  
03468                         int whichDefense = 0;
03469                         if ( NPC->client->ps.weaponTime || NPC->client->ps.saberInFlight || NPC->client->NPC_class == CLASS_BOBAFETT )
03470                         {//I'm attacking or recovering from a parry, can only try to strafe/jump right now
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                                 {//check for lightning attack
03481                                         //only valid defense is strafe and/or jump
03482                                         whichDefense = 100;
03483                                 }
03484                                 else if ( throwing_saber )
03485                                 {//he's thrown his saber!  See if it's coming at me
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                                                 //Com_Printf( "(%d) raise agg - enemy threw saber\n", level.time );
03497                                                 Jedi_Aggression( NPC, 1 );
03498                                         }
03499                                         if ( DotProduct( saberMoveDir, saberDir2Me ) > 0.5 )
03500                                         {//it's heading towards me
03501                                                 if ( saberDist < 100 )
03502                                                 {//it's close
03503                                                         whichDefense = Q_irand( 3, 6 );
03504                                                 }
03505                                                 else if ( saberDist < 200 )
03506                                                 {//got some time, yet, try pushing
03507                                                         whichDefense = Q_irand( 0, 8 );
03508                                                 }
03509                                         }
03510                                 }
03511                                 if ( whichDefense )
03512                                 {//already chose one
03513                                 }
03514                                 else if ( enemy_dist > 80 || !enemy_attacking )
03515                                 {//he's pretty far, or not swinging, just strafe
03516                                         if ( VectorCompare( enemy_movedir, vec3_origin ) )
03517                                         {//if he's not moving, not swinging and far enough away, no evasion necc.
03518                                                 return;
03519                                         }
03520                                         if ( Q_irand( 0, 10 ) < NPCInfo->stats.aggression )
03521                                         {
03522                                                 return;
03523                                         }
03524                                         whichDefense = 100;
03525                                 }
03526                                 else
03527                                 {//he's getting close and swinging at me
03528                                         vec3_t  fwd;
03529                                         //see if I'm facing him
03530                                         AngleVectors( NPC->client->ps.viewangles, fwd, NULL, NULL );
03531                                         if ( DotProduct( enemy_dir, fwd ) < 0.5 )
03532                                         {//I'm not really facing him, best option is to strafe
03533                                                 whichDefense = Q_irand( 5, 16 );
03534                                         }
03535                                         else if ( enemy_dist < 56 )
03536                                         {//he's very close, maybe we should be more inclined to block or throw
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                         {//would try to block
03548                                 if ( NPC->client->ps.saberInFlight )
03549                                 {//can't, saber in not in hand, so fall back to strafe/jump
03550                                         whichDefense = 100;
03551                                 }
03552                         }
03553 
03554                         switch( whichDefense )
03555                         {
03556                         case 0:
03557                         case 1:
03558                         case 2:
03559                         case 3:
03560                                 //use jedi force push?
03561                                 //FIXME: try to do this if health low or enemy back to a cliff?
03562                                 if ( (NPCInfo->rank == RANK_ENSIGN || NPCInfo->rank > RANK_LT_JG) && TIMER_Done( NPC, "parryTime" ) )
03563                                 {//FIXME: check forcePushRadius[NPC->client->ps.fd.forcePowerLevel[FP_PUSH]]
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                                 //try to parry the blow
03577                                 //Com_Printf( "blocking\n" );
03578                                 Jedi_SaberBlock(0, 0);
03579                                 break;
03580                         default:
03581                                 //Evade!
03582                                 //start a strafe left/right if not already
03583                                 if ( !Q_irand( 0, 5 ) || !Jedi_Strafe( 300, 1000, 0, 1000, qfalse ) )
03584                                 {//certain chance they will pick an alternative evasion
03585                                         //if couldn't strafe, try a different kind of evasion...
03586                                         if ( shooting_lightning || throwing_saber || enemy_dist < 80 )
03587                                         {
03588                                                 //FIXME: force-jump+forward - jump over the guy!
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                                                         {//FIXME: check forcePushRadius[NPC->client->ps.fd.forcePowerLevel[FP_PUSH]]
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                                                         {//FIXME: make this a function call?
03601                                                                 //FIXME: check for clearance, safety of landing spot?
03602                                                                 NPC->client->ps.fd.forceJumpCharge = 480;
03603                                                                 //Don't jump again for another 2 to 5 seconds
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                                                                 //FIXME: if this jump is cleared, we can't block... so pick a random lower block?
03616                                                                 if ( Q_irand( 0, 1 ) )//FIXME: make intelligent
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                                 {//strafed
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                                         {//FIXME: make this a function call?
03645                                                 //FIXME: check for clearance, safety of landing spot?
03646                                                 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
03647                                                 {
03648                                                         NPC->client->ps.fd.forceJumpCharge = 280;//FIXME: calc this intelligently?
03649                                                 }
03650                                                 else
03651                                                 {
03652                                                         NPC->client->ps.fd.forceJumpCharge = 320;
03653                                                 }
03654                                                 //Don't jump again for another 2 to 5 seconds
03655                                                 TIMER_Set( NPC, "jumpChaseDebounce", Q_irand( 2000, 5000 ) );
03656                                         }
03657                                 }
03658                                 break;
03659                         }
03660                 
03661                         //turn off slow walking no matter what
03662                         TIMER_Set( NPC, "walking", -level.time );
03663                         TIMER_Set( NPC, "taunting", -level.time );
03664                 }
03665         }
03666 }
03667 /*
03668 -------------------------
03669 Jedi_Flee
03670 -------------------------
03671 */
03672 /*
03673 
03674 static qboolean Jedi_Flee( void )
03675 {
03676         return qfalse;
03677 }
03678 */
03679 
03680 
03681 /*
03682 ==========================================================================================
03683 INTERNAL AI ROUTINES
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                 {//me
03715                         continue;
03716                 }
03717                 if ( !(check->inuse) )
03718                 {//freed
03719                         continue;
03720                 }
03721                 if ( !check->client )
03722                 {//not a client - FIXME: what about turrets?
03723                         continue;
03724                 }
03725                 if ( check->client->playerTeam != self->client->enemyTeam )
03726                 {//not an enemy - FIXME: what about turrets?
03727                         continue;
03728                 }
03729                 if ( check->health <= 0 )
03730                 {//dead
03731                         continue;
03732                 }
03733 
03734                 if ( !trap_InPVS( check->r.currentOrigin, self->r.currentOrigin ) )
03735                 {//can't potentially see them
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                 {//not in front
03744                         continue;
03745                 }
03746 
03747                 //really should have a clear LOS to this thing...
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                 {//must have clear shot
03751                         continue;
03752                 }
03753 
03754                 if ( dist < bestDist )
03755                 {//closer than our last best one
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         {//no valid enemy
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;//get it's origin to a height I can work with
03775                 VectorSubtract( enemy_dest, NPC->r.currentOrigin, enemy_dir );
03776                 //FIXME: enemy_dist calc needs to include all blade lengths, and include distance from hand to start of blade....
03777                 *enemy_dist = VectorNormalize( enemy_dir );// - (NPC->client->ps.saberLengthMax + NPC->r.maxs[0]*1.5 + 16);
03778         }
03779         else
03780         {//see where enemy is headed
03781                 VectorCopy( NPC->enemy->client->ps.velocity, enemy_movedir );
03782                 *enemy_movespeed = VectorNormalize( enemy_movedir );
03783                 //figure out where he'll be, say, 3 frames from now
03784                 VectorMA( NPC->enemy->r.currentOrigin, *enemy_movespeed * 0.001 * prediction, enemy_movedir, enemy_dest );
03785                 //figure out what dir the enemy's estimated position is from me and how far from the tip of my saber he is
03786                 VectorSubtract( enemy_dest, NPC->r.currentOrigin, enemy_dir );//NPC->client->renderInfo.muzzlePoint
03787                 //FIXME: enemy_dist calc needs to include all blade lengths, and include distance from hand to start of blade....
03788                 *enemy_dist = VectorNormalize( enemy_dir ) - (NPC->client->saber[0].blade[0].lengthMax + NPC->r.maxs[0]*1.5 + 16); //just use the blade 0 len I guess
03789                 //FIXME: keep a group of enemies around me and use that info to make decisions...
03790                 //              For instance, if there are multiple enemies, evade more, push them away
03791                 //              and use medium attacks.  If enemies are using blasters, switch to fast.
03792                 //              If one jedi enemy, use strong attacks.  Use grip when fighting one or
03793                 //              two enemies, use lightning spread when fighting multiple enemies, etc.
03794                 //              Also, when kill one, check rest of group instead of walking up to victim.
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         {//don't update?
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                 /*&& NPC->s.weapon != WP_MELEE*/ )
03830         {//boba leads his enemy
03831                 if ( NPC->health < NPC->client->pers.maxHealth*0.5f )
03832                 {//lead
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;//How many seconds it will take to get to the enemy
03838                                 VectorMA( enemy_eyes, eDist*flrand(0.95f,1.25f), NPC->enemy->client->ps.velocity, enemy_eyes );
03839                         }
03840                 }
03841         }
03842 
03843         //Find the desired angles
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         {//point *away*
03850                 GetAnglesForDirection( enemy_eyes, eyes, angles );
03851         }
03852         else
03853         {//point towards him
03854                 GetAnglesForDirection( eyes, enemy_eyes, angles );
03855         }
03856 
03857         NPCInfo->desiredYaw     = AngleNormalize360( angles[YAW] );
03858         /*
03859         if ( NPC->client->ps.saberBlocked == BLOCKED_UPPER_LEFT )
03860         {//temp hack- to make up for poor coverage on left side
03861                 NPCInfo->desiredYaw += 30;
03862         }
03863         */
03864 
03865         if ( doPitch )
03866         {
03867                 NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] );
03868                 if ( NPC->client->ps.saberInFlight )
03869                 {//tilt down a little
03870                         NPCInfo->desiredPitch += 10;
03871                 }
03872         }
03873         //FIXME: else desiredPitch = 0?  Or keep previous?
03874 }
03875 
03876 static void Jedi_DebounceDirectionChanges( void )
03877 {
03878         //FIXME: check these before making fwd/back & right/left decisions?
03879         //Time-debounce changes in forward/back dir
03880         if ( ucmd.forwardmove > 0 )
03881         {
03882                 if ( !TIMER_Done( NPC, "moveback" ) || !TIMER_Done( NPC, "movenone" ) )
03883                 {
03884                         ucmd.forwardmove = 0;
03885                         //now we have to normalize the total movement again
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                 {//FIXME: should be if it's zero?
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                         //now we have to normalize the total movement again
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                 {//FIXME: should be if it's zero?
03929                         TIMER_Set( NPC, "moveback", Q_irand( 250, 1000 ) );
03930                 }
03931         }
03932         else if ( !TIMER_Done( NPC, "moveforward" ) )
03933         {//NOTE: edge checking should stop me if this is bad... but what if it sends us colliding into the enemy?
03934                 ucmd.forwardmove = 127;
03935                 VectorClear( NPC->client->ps.moveDir );
03936         }
03937         else if ( !TIMER_Done( NPC, "moveback" ) )
03938         {//NOTE: edge checking should stop me if this is bad...
03939                 ucmd.forwardmove = -127;
03940                 VectorClear( NPC->client->ps.moveDir );
03941         }
03942         //Time-debounce changes in right/left dir
03943         if ( ucmd.rightmove > 0 )
03944         {
03945                 if ( !TIMER_Done( NPC, "moveleft" ) || !TIMER_Done( NPC, "movecenter" ) )
03946                 {
03947                         ucmd.rightmove = 0;
03948                         //now we have to normalize the total movement again
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                 {//FIXME: should be if it's zero?
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                         //now we have to normalize the total movement again
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                 {//FIXME: should be if it's zero?
03992                         TIMER_Set( NPC, "moveleft", Q_irand( 250, 1500 ) );
03993                 }
03994         }
03995         else if ( !TIMER_Done( NPC, "moveright" ) )
03996         {//NOTE: edge checking should stop me if this is bad... 
03997                 ucmd.rightmove = 127;
03998                 VectorClear( NPC->client->ps.moveDir );
03999         }
04000         else if ( !TIMER_Done( NPC, "moveleft" ) )
04001         {//NOTE: edge checking should stop me if this is bad...
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         {//only if not already strafing
04011                 //FIXME: if enemy behind me and turning to face enemy, don't strafe in that direction, too
04012                 if ( !TIMER_Done( NPC, "strafeLeft" ) )
04013                 {
04014                         if ( NPCInfo->desiredYaw > NPC->client->ps.viewangles[YAW] + 60 )
04015                         {//we want to turn left, don't apply the strafing
04016                         }
04017                         else
04018                         {//go ahead and strafe left
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                         {//we want to turn right, don't apply the strafing
04027                         }
04028                         else
04029                         {//go ahead and strafe left
04030                                 ucmd.rightmove = 127;
04031                                 VectorClear( NPC->client->ps.moveDir );
04032                         }
04033                 }
04034         }
04035 
04036         Jedi_DebounceDirectionChanges();
04037 
04038         //use careful anim/slower movement if not already moving
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         {//FIXME: what do we do if we ran out of power?  NPC's can't?
04051                 //FIXME: don't keep turning to face enemy or we'll end up spinning around
04052                 ucmd.buttons |= BUTTON_FORCEGRIP;
04053         }
04054 
04055         if ( !TIMER_Done( NPC, "draining" ) )
04056         {//FIXME: what do we do if we ran out of power?  NPC's can't?
04057                 //FIXME: don't keep turning to face enemy or we'll end up spinning around
04058                 ucmd.buttons |= BUTTON_FORCE_DRAIN;
04059         }
04060 
04061         if ( !TIMER_Done( NPC, "holdLightning" ) )
04062         {//hold down the lightning key
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                 //okay, now mess with agression
04073                 if ( NPC->client->ps.fd.forcePowersActive&(1<<FP_RAGE) )
04074                 {//raging
04075                         Jedi_Aggression( NPC, Q_irand( 0, 3 ) );
04076                 }
04077                 else if ( NPC->client->ps.fd.forceRageRecoveryTime > level.time )
04078                 {//recovering
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                                 //If enemy has a lightsaber, always close in
04087                                 if ( BG_SabersOff( &NPC->enemy->client->ps ) )
04088                                 {//fool!  Standing around unarmed, charge!
04089                                         //Com_Printf( "(%d) raise agg - enemy saber off\n", level.time );
04090                                         Jedi_Aggression( NPC, 2 );
04091                                 }
04092                                 else
04093                                 {
04094                                         //Com_Printf( "(%d) raise agg - enemy saber\n", level.time );
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                                 //if he has a blaster, move in when:
04107                                 //They're not shooting at me
04108                                 if ( NPC->enemy->attackDebounceTime < level.time )
04109                                 {//does this apply to players?
04110                                         //Com_Printf( "(%d) raise agg - enemy not shooting ranged weap\n", level.time );
04111                                         Jedi_Aggression( NPC, 1 );
04112                                 }
04113                                 //He's closer than a dist that gives us time to deflect
04114                                 if ( enemy_dist < 256 )
04115                                 {
04116                                         //Com_Printf( "(%d) raise agg - enemy ranged weap- too close\n", level.time );
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                 //FIXME: Maybe more likely to do this if aggression higher?  Or some other stat?
04129                 if ( !Q_irand( 0, 4 ) )
04130                 {//start a strafe
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                 {//postpone any strafing for a while
04141                         TIMER_Set( NPC, "noStrafe", Q_irand( 1000, 3000 ) );
04142                 }
04143         }
04144 
04145         if ( NPC->client->ps.saberEventFlags )
04146         {//some kind of saber combat event is still pending
04147                 int newFlags = NPC->client->ps.saberEventFlags;
04148                 if ( NPC->client->ps.saberEventFlags&SEF_PARRIED )
04149                 {//parried
04150                         TIMER_Set( NPC, "parryTime", -1 );
04151                         /*
04152                         if ( NPCInfo->rank >= RANK_LT_JG )
04153                         {
04154                                 NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 100;
04155                         }
04156                         else
04157                         {
04158                                 NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500;
04159                         }
04160                         */
04161                         if ( NPC->enemy && PM_SaberInKnockaway( NPC->enemy->client->ps.saberMove ) )
04162                         {//advance!
04163                                 Jedi_Aggression( NPC, 1 );//get closer
04164                                 Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel-1) );//use a faster attack
04165                         }
04166                         else
04167                         {
04168                                 if ( !Q_irand( 0, 1 ) )//FIXME: dependant on rank/diff?
04169                                 {
04170                                         //Com_Printf( "(%d) drop agg - we parried\n", level.time );
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) )//hit enemy
04185                 {//we hit our enemy last time we swung, drop our aggression
04186                         if ( !Q_irand( 0, 1 ) )//FIXME: dependant on rank/diff?
04187                         {
04188                                 //Com_Printf( "(%d) drop agg - we hit enemy\n", level.time );
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                 {//was blocked whilst attacking
04211                         if ( PM_SaberInBrokenParry( NPC->client->ps.saberMove )
04212                                 || NPC->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
04213                         {
04214                                 //Com_Printf( "(%d) drop agg - we were knock-blocked\n", level.time );
04215                                 if ( NPC->client->ps.saberInFlight )
04216                                 {//lost our saber, too!!!
04217                                         Jedi_Aggression( NPC, -5 );//really really really should back off!!!
04218                                 }
04219                                 else
04220                                 {
04221                                         Jedi_Aggression( NPC, -2 );//really should back off!
04222                                 }
04223                                 Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel+1) );//use a stronger attack
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 ) )//FIXME: dependant on rank/diff?
04232                                 {
04233                                         //Com_Printf( "(%d) drop agg - we were blocked\n", level.time );
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                         //FIXME: based on the type of parry the enemy is doing and my skill,
04247                         //              choose an attack that is likely to get around the parry?
04248                         //              right now that's generic in the saber animation code, auto-picks
04249                         //              a next anim for me, but really should be AI-controlled.
04250                 }
04251                 if ( NPC->client->ps.saberEventFlags&SEF_DEFLECTED )
04252                 {//deflected a shot
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                 {//hit a wall
04261                         newFlags &= ~SEF_HITWALL;
04262                 }
04263                 if ( NPC->client->ps.saberEventFlags&SEF_HITOBJECT )
04264                 {//hit some other damagable object
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         {//don't do this idle stuff if throwing saber
04283                 return;
04284         }
04285         if ( NPC->client->ps.fd.forcePowersActive&(1<<FP_RAGE) 
04286                 || NPC->client->ps.fd.forceRageRecoveryTime > level.time )
04287         {//never taunt while raging or recovering from rage
04288                 return;
04289         }
04290         //FIXME: make these distance numbers defines?
04291         if ( enemy_dist >= 64 )
04292         {//FIXME: only do this if standing still?
04293                 //based on aggression, flaunt/taunt
04294                 int chance = 20;
04295                 if ( NPC->client->NPC_class == CLASS_SHADOWTROOPER )
04296                 {
04297                         chance = 10;
04298                 }
04299                 //FIXME: possibly throw local objects at enemy?
04300                 if ( Q_irand( 2, chance ) < NPCInfo->stats.aggression )
04301                 {
04302                         if ( TIMER_Done( NPC, "chatter" ) && NPC->client->ps.forceHandExtend == HANDEXTEND_NONE )
04303                         {//FIXME: add more taunt behaviors
04304                                 //FIXME: sometimes he turns it off, then turns it right back on again???
04305                                 if ( enemy_dist > 200 
04306                                         && NPC->client->NPC_class != CLASS_BOBAFETT
04307                                         && !NPC->client->ps.saberHolstered
04308                                         && !Q_irand( 0, 5 ) )
04309                                 {//taunt even more, turn off the saber
04310                                         //FIXME: don't do this if health low?
04311                                         WP_DeactivateSaber( NPC, qfalse );
04312                                         //Don't attack for a bit
04313                                         NPCInfo->stats.aggression = 3;
04314                                         //FIXME: maybe start strafing?
04315                                         //debounce this
04316                                         if ( NPC->client->playerTeam != NPCTEAM_PLAYER && !Q_irand( 0, 1 ))
04317                                         {
04318                                                 //NPC->client->ps.taunting = level.time + 100;
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                                 {//FIXME: pick some anims
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         {//enemy is in a saberLock and we are not
04346                 return qfalse;
04347         }
04348 
04349         if ( NPC->client->ps.saberEventFlags&SEF_LOCK_WON )
04350         {//we won a saber lock, press the advantage with an attack!
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                 {//desann and luke
04354                         chance = 20;
04355                 }
04356                 else if ( NPC->client->NPC_class == CLASS_TAVION )
04357                 {//tavion
04358                         chance = 10;
04359                 }
04360                 else if ( NPC->client->NPC_class == CLASS_REBORN && NPCInfo->rank == RANK_LT_JG ) 
04361                 {//fencer
04362                         chance = 5;
04363                 }
04364                 else
04365                 {
04366                         chance = NPCInfo->rank;
04367                 }
04368                 if ( Q_irand( 0, 30 ) < chance )
04369                 {//based on skill with some randomness
04370                         NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON;//clear this now that we are using the opportunity
04371                         TIMER_Set( NPC, "noRetreat", Q_irand( 500, 2000 ) );
04372                         //FIXME: check enemy_dist?
04373                         NPC->client->ps.weaponTime = NPCInfo->shotTime = NPC->attackDebounceTime = 0;
04374                         //NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500;
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         {//tavion, fencers, jedi trainer are all good at following up a parry with an attack
04385                 if ( ( PM_SaberInParry( NPC->client->ps.saberMove ) || PM_SaberInKnockaway( NPC->client->ps.saberMove ) )
04386                         && NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
04387                 {//try to attack straight from a parry
04388                         NPC->client->ps.weaponTime = NPCInfo->shotTime = NPC->attackDebounceTime = 0;
04389                         //NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500;
04390                         NPC->client->ps.saberBlocked = BLOCKED_NONE;
04391                         Jedi_AdjustSaberAnimLevel( NPC, FORCE_LEVEL_1 );//try to follow-up with a quick attack
04392                         WeaponThink( qtrue );
04393                         return qtrue;
04394                 }
04395         }
04396 
04397         //try to hit them if we can
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         {//not allowed to attack
04410                 return qfalse;
04411         }
04412 
04413         if ( !(ucmd.buttons&BUTTON_ATTACK) && !(ucmd.buttons&BUTTON_ALT_ATTACK) )
04414         {//not already attacking
04415                 //Try to attack
04416                 WeaponThink( qtrue );
04417         }
04418         
04419         //FIXME:  Maybe try to push enemy off a ledge?
04420 
04421         //close enough to step forward
04422 
04423         //FIXME: an attack debounce timer other than the phaser debounce time?
04424         //              or base it on aggression?
04425 
04426         if ( ucmd.buttons&BUTTON_ATTACK )
04427         {//attacking
04428                 /*
04429                 if ( enemy_dist > 32 && NPCInfo->stats.aggression >= 4 )
04430                 {//move forward if we're too far away and we're chasing him
04431                         ucmd.forwardmove = 127;
04432                 }
04433                 else if ( enemy_dist < 0 )
04434                 {//move back if we're too close
04435                         ucmd.forwardmove = -127;
04436                 }
04437                 */
04438                 //FIXME: based on the type of parry/attack the enemy is doing and my skill,
04439                 //              choose an attack that is likely to get around the parry?
04440                 //              right now that's generic in the saber animation code, auto-picks
04441                 //              a next anim for me, but really should be AI-controlled.
04442                 //FIXME: have this interact with/override above strafing code?
04443                 if ( !ucmd.rightmove )
04444                 {//not already strafing
04445                         if ( !Q_irand( 0, 3 ) )
04446                         {//25% chance of doing this
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                                 {//he's to my right, strafe left
04453                                         ucmd.rightmove = -127;
04454                                         VectorClear( NPC->client->ps.moveDir );
04455                                 }
04456                                 else
04457                                 {//he's to my left, strafe right
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 {//FIXME: if land on enemy, knock him down & jump off again
04475         /*
04476         if ( dest[2] - NPC->r.currentOrigin[2] < 64 && DistanceHorizontal( NPC->r.currentOrigin, dest ) > 256 )
04477         {//a pretty horizontal jump, easy to fake:
04478                 vec3_t enemy_diff;
04479 
04480                 VectorSubtract( dest, NPC->r.currentOrigin, enemy_diff );
04481                 float enemy_z_diff = enemy_diff[2];
04482                 enemy_diff[2] = 0;
04483                 float enemy_xy_diff = VectorNormalize( enemy_diff );
04484 
04485                 VectorScale( enemy_diff, enemy_xy_diff*0.8, NPC->client->ps.velocity );
04486                 if ( enemy_z_diff < 64 )
04487                 {
04488                         NPC->client->ps.velocity[2] = enemy_xy_diff;
04489                 }
04490                 else
04491                 {
04492                         NPC->client->ps.velocity[2] = enemy_z_diff*2+enemy_xy_diff/2;
04493                 }
04494         }
04495         else
04496         */
04497         if ( 1 )
04498         {
04499                 float   targetDist, shotSpeed = 300, travelTime, impactDist, bestImpactDist = Q3_INFINITE;//fireSpeed, 
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                         {//save the first one as the worst case scenario
04518                                 VectorCopy( shotVel, failCase );
04519                         }
04520 
04521                         if ( 1 )//tracePath )
04522                         {//do a rough trace of the path
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                                 //This may be kind of wasteful, especially on long throws... use larger steps?  Divide the travelTime into a certain hard number of slices?  Trace just to apex and down?
04533                                 for ( elapsedTime = timeStep; elapsedTime < floor(travelTime)+timeStep; elapsedTime += timeStep )
04534                                 {
04535                                         if ( (float)elapsedTime > travelTime )
04536                                         {//cap it
04537                                                 elapsedTime = floor( travelTime );
04538                                         }
04539                                         BG_EvaluateTrajectory( &tr, level.time + elapsedTime, testPos );
04540                                         if ( testPos[2] < lastPos[2] )
04541                                         {//going down, ignore botclip
04542                                                 trap_Trace( &trace, lastPos, NPC->r.mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask );
04543                                         }
04544                                         else
04545                                         {//going up, check for botclip
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                                         {//hit something
04556                                                 if ( trace.entityNum == goalEntNum )
04557                                                 {//hit the enemy, that's perfect!
04558                                                         //Hmm, don't want to land on him, though...
04559                                                         break;
04560                                                 }
04561                                                 else 
04562                                                 {
04563                                                         if ( trace.contents & CONTENTS_BOTCLIP )
04564                                                         {//hit a do-not-enter brush
04565                                                                 blocked = qtrue;
04566                                                                 break;
04567                                                         }
04568                                                         if ( trace.plane.normal[2] > 0.7 && DistanceSquared( trace.endpos, dest ) < 4096 )//hit within 64 of desired location, should be okay
04569                                                         {//close enough!
04570                                                                 break;
04571                                                         }
04572                                                         else
04573                                                         {//FIXME: maybe find the extents of this brush and go above or below it on next try somehow?
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                                         {//reached end, all clear
04587                                                 if ( trace.fraction >= 1.0f )
04588                                                 {//hmm, make sure we'll land on the ground...
04589                                                         //FIXME: do we care how far below ourselves or our dest we'll land?
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                                                         {//would fall too far
04595                                                                 blocked = qtrue;
04596                                                         }
04597                                                 }
04598                                                 break;
04599                                         }
04600                                         else
04601                                         {
04602                                                 //all clear, try next slice
04603                                                 VectorCopy( testPos, lastPos );
04604                                         }
04605                                 }
04606                                 if ( blocked )
04607                                 {//hit something, adjust speed (which will change arc)
04608                                         hitCount++;
04609                                         shotSpeed = 300 + ((hitCount-2) * 100);//from 100 to 900 (skipping 300)
04610                                         if ( hitCount >= 2 )
04611                                         {//skip 300 since that was the first value we tested
04612                                                 shotSpeed += 100;
04613                                         }
04614                                 }
04615                                 else
04616                                 {//made it!
04617                                         break;
04618                                 }
04619                         }
04620                         else
04621                         {//no need to check the path, go with first calc
04622                                 break;
04623                         }
04624                 }
04625 
04626                 if ( hitCount >= maxHits )
04627                 {//NOTE: worst case scenario, use the one that impacted closest to the target (or just use the first try...?)
04628                         //NOTE: or try failcase?
04629                         VectorCopy( failCase, NPC->client->ps.velocity );
04630                 }
04631                 VectorCopy( shotVel, NPC->client->ps.velocity );
04632         }
04633         else
04634         {//a more complicated jump
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] )//NPCInfo->goalEntity->r.currentOrigin
04639                 {
04640                         VectorCopy( NPC->r.currentOrigin, p1 );
04641                         VectorCopy( dest, p2 );//NPCInfo->goalEntity->r.currentOrigin
04642                 }
04643                 else if ( NPC->r.currentOrigin[2] < dest[2] )//NPCInfo->goalEntity->r.currentOrigin
04644                 {
04645                         VectorCopy( dest, p1 );//NPCInfo->goalEntity->r.currentOrigin
04646                         VectorCopy( NPC->r.currentOrigin, p2 );
04647                 }
04648                 else
04649                 {
04650                         VectorCopy( NPC->r.currentOrigin, p1 );
04651                         VectorCopy( dest, p2 );//NPCInfo->goalEntity->r.currentOrigin
04652                 }
04653 
04654                 //z = xy*xy
04655                 VectorSubtract( p2, p1, dir );
04656                 dir[2] = 0;
04657 
04658                 //Get xy and z diffs
04659                 xy = VectorNormalize( dir );
04660                 z = p1[2] - p2[2];
04661 
04662                 apexHeight = APEX_HEIGHT/2;
04663 
04664                 //Determine most desirable apex height
04665                 //FIXME: length of xy will change curve of parabola, need to account for this
04666                 //somewhere... PARA_WIDTH
04667                 /*
04668                 apexHeight = (APEX_HEIGHT * PARA_WIDTH/xy) + (APEX_HEIGHT * z/128);
04669                 if ( apexHeight < APEX_HEIGHT * 0.5 )
04670                 {
04671                         apexHeight = APEX_HEIGHT*0.5;
04672                 }
04673                 else if ( apexHeight > APEX_HEIGHT * 2 )
04674                 {
04675                         apexHeight = APEX_HEIGHT*2;
04676                 }
04677                 */
04678 
04679                 z = (sqrt(apexHeight + z) - sqrt(apexHeight));
04680 
04681                 assert(z >= 0);
04682 
04683 //              Com_Printf("apex is %4.2f percent from p1: ", (xy-z)*0.5/xy*100.0f);
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                 //Now we have the apex, aim for it
04696                 height = apex[2] - NPC->r.currentOrigin[2];
04697                 time = sqrt( height / ( .5 * NPC->client->ps.gravity ) );//was 0.5, but didn't work well for very long jumps
04698                 if ( !time ) 
04699                 {
04700                         //Com_Printf( S_COLOR_RED"ERROR: no time in jump\n" );
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;//er... probably bad, but...
04709                 VectorScale( NPC->client->ps.velocity, forward, NPC->client->ps.velocity );
04710 
04711                 //FIXME:  Uh.... should we trace/EvaluateTrajectory this to make sure we have clearance and we land where we want?
04712                 NPC->client->ps.velocity[2] = time * NPC->client->ps.gravity;
04713 
04714                 //Com_Printf("Jump Velocity: %4.2f, %4.2f, %4.2f\n", NPC->client->ps.velocity[0], NPC->client->ps.velocity[1], NPC->client->ps.velocity[2] );
04715         }
04716         return qtrue;
04717 }
04718 
04719 static qboolean Jedi_TryJump( gentity_t *goal )
04720 {//FIXME: never does a simple short, regular jump...
04721         //FIXME: I need to be on ground too!
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                         {//enemy is on terra firma
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/*was -256*/ )//for now, jedi don't take falling damage && (NPC->health > 20 || goal_z_diff > 0 ) && (NPC->health >= 100 || goal_z_diff > -128 ))//closer than @512
04740                                 {
04741                                         qboolean debounce = qfalse;
04742                                         if ( NPC->health < 150 && ((NPC->health < 30 && goal_z_diff < 0) || goal_z_diff < -128 ) )
04743                                         {//don't jump, just walk off... doesn't help with ledges, though
04744                                                 debounce = qtrue;
04745                                         }
04746                                         else if ( goal_z_diff < 32 && goal_xy_dist < 200 )
04747                                         {//what is their ideal jump height?
04748                                                 ucmd.upmove = 127;
04749                                                 debounce = qtrue;
04750                                         }
04751                                         else
04752                                         {
04753                                                 /*
04754                                                 //NO!  All Jedi can jump-navigate now...
04755                                                 if ( NPCInfo->rank != RANK_CREWMAN && NPCInfo->rank <= RANK_LT_JG )
04756                                                 {//can't do acrobatics
04757                                                         return qfalse;
04758                                                 }
04759                                                 */
04760                                                 if ( goal_z_diff > 0 || goal_xy_dist > 128 )
04761                                                 {//Fake a force-jump
04762                                                         //Screw it, just do my own calc & throw
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                                                                 {//FIXME: make it so it doesn't try the same spot again?
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                                                                         {//hit floor, okay to land here
04794                                                                                 break;
04795                                                                         }
04796                                                                         sideTry++;
04797                                                                 }
04798                                                                 if ( sideTry >= 10 )
04799                                                                 {//screw it, just jump right at him?
04800                                                                         VectorCopy( goal->r.currentOrigin, dest );
04801                                                                 }
04802                                                         }
04803                                                         if ( Jedi_Jump( dest, goal->s.number ) )
04804                                                         {
04805                                                                 //Com_Printf( "(%d) pre-checked force jump\n", level.time );
04806 
04807                                                                 //FIXME: store the dir we;re going in in case something gets in the way of the jump?
04808                                                                 //? = vectoyaw( NPC->client->ps.velocity );
04809                                                                 /*
04810                                                                 if ( NPC->client->ps.velocity[2] < 320 )
04811                                                                 {
04812                                                                         NPC->client->ps.velocity[2] = 320;
04813                                                                 }
04814                                                                 else
04815                                                                 */
04816                                                                 {//FIXME: make this a function call
04817                                                                         int jumpAnim;
04818                                                                         //FIXME: this should be more intelligent, like the normal force jump anim logic
04819                                                                         if ( NPC->client->NPC_class == CLASS_BOBAFETT 
04820                                                                                 ||( NPCInfo->rank != RANK_CREWMAN && NPCInfo->rank <= RANK_LT_JG ) )
04821                                                                         {//can't do acrobatics
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                                                                 //NPC->client->ps.pm_flags |= PMF_JUMPING;
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                                                 //Don't jump again for another 2 to 5 seconds
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         {//force-jumping at the enemy
04871 //              if ( !(NPC->client->ps.pm_flags & PMF_JUMPING )//forceJumpZStart )
04872 //                      && !(NPC->client->ps.pm_flags&PMF_TRIGGER_PUSHED))
04873                 if (NPC->client->ps.groundEntityNum != ENTITYNUM_NONE) //rwwFIXMEFIXME: Not sure if this is gonna work, use the PM flags ideally.
04874                 {//landed
04875                         TIMER_Set( NPC, "forceJumpChasing", 0 );
04876                 }
04877                 else
04878                 {
04879                         NPC_FaceEntity( goal, qtrue );
04880                         //FIXME: push me torward where I was heading
04881                         //FIXME: if hit a ledge as soon as we jumped, we need to push toward our goal... must remember original jump dir and/or original jump dest
04882                         /*
04883                         vec3_t  viewangles_xy={0,0,0}, goal_dir, goal_xy_dir, forward, right;
04884                         float   goal_dist;
04885                         
04886                         //gert horz dir to goal
04887                         VectorSubtract( goal->r.currentOrigin, NPC->r.currentOrigin, goal_dir );
04888                         VectorCopy( goal_dir, goal_xy_dir );
04889                         goal_dist = VectorNormalize( goal_dir );
04890                         goal_xy_dir[2] = 0;
04891                         VectorNormalize( goal_xy_dir );
04892 
04893                         //get horz facing
04894                         viewangles_xy[1] = NPC->client->ps.viewangles[1];
04895                         AngleVectors( viewangles_xy, forward, right, NULL );
04896 
04897                         //get movement commands to push me toward enemy
04898                         float fDot = DotProduct( forward, goal_dir ) * 127;
04899                         float rDot = DotProduct( right, goal_dir ) * 127;
04900                 
04901                         ucmd.forwardmove = floor(fDot);
04902                         ucmd.rightmove = floor(rDot);
04903                         ucmd.upmove = 0;//don't duck
04904                         //Cheat:
04905                         if ( goal_dist < 128 && goal->r.currentOrigin[2] > NPC->r.currentOrigin[2] && NPC->client->ps.velocity[2] <= 0 )
04906                         {
04907                                 NPC->client->ps.velocity[2] += 320;
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                 {//enemy is mad at *me*
04931                         if ( NPC->enemy->client->ps.legsAnim == BOTH_JUMPFLIPSLASHDOWN1 ||
04932                                 NPC->enemy->client->ps.legsAnim == BOTH_JUMPFLIPSTABDOWN )
04933                         {//enemy is flipping over me
04934                                 if ( Q_irand( 0, NPCInfo->rank ) < RANK_LT )
04935                                 {//be nice and stand still for him...
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                         {//he's flipping off a wall
04952                                 if ( NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE )
04953                                 {//still in air
04954                                         if ( enemy_dist < 256 )
04955                                         {//close
04956                                                 if ( Q_irand( 0, NPCInfo->rank ) < RANK_LT )
04957                                                 {//be nice and stand still for him...
04958                                                         vec3_t enemyFwd, dest, dir;
04959 
04960                                                         /*
04961                                                         ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0;
04962                                                         VectorClear( NPC->client->ps.moveDir );
04963                                                         NPC->client->ps.fd.forceJumpCharge = 0;
04964                                                         TIMER_Set( NPC, "strafeLeft", -1 );
04965                                                         TIMER_Set( NPC, "strafeRight", -1 );
04966                                                         TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) );
04967                                                         TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) );
04968                                                         TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) );
04969                                                         TIMER_Set( NPC, "noturn", Q_irand( 200, 500 ) );
04970                                                         */
04971                                                         //stop current movement
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                         {//he's stabbing backwards
04999                                 if ( enemy_dist < 256 && enemy_dist > 64 )
05000                                 {//close
05001                                         if ( !InFront( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin, NPC->enemy->r.currentAngles, 0.0f ) )
05002                                         {//behind him
05003                                                 if ( !Q_irand( 0, NPCInfo->rank ) )
05004                                                 {//be nice and stand still for him...
05005                                                         vec3_t enemyFwd, dest, dir;
05006 
05007                                                         //stop current movement
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         //FIXME: also:
05034         //              If enemy doing wall flip, keep running forward
05035         //              If enemy doing back-attack and we're behind him keep running forward toward his back, don't strafe
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         //FIXME: should probably check this before AI decides that best move is to jump?  Otherwise, they may end up just standing there and looking dumb
05053         //FIXME: all this work and he still jumps off ledges... *sigh*... need CONTENTS_BOTCLIP do-not-enter brushes...?
05054         VectorClear(jumpVel);
05055 
05056         if ( NPC->client->ps.fd.forceJumpCharge )
05057         {
05058                 //Com_Printf( "(%d) force jump\n", level.time );
05059                 WP_GetVelocityForForceJump( NPC, jumpVel, &ucmd );
05060         }
05061         else if ( ucmd.upmove > 0 )
05062         {
05063                 //Com_Printf( "(%d) regular jump\n", level.time );
05064                 VectorCopy( NPC->client->ps.velocity, jumpVel );
05065                 jumpVel[2] = JUMP_VELOCITY;
05066         }
05067         else
05068         {
05069                 return;
05070         }
05071         
05072         //NOTE: for now, we clear ucmd.forwardmove & ucmd.rightmove while in air to avoid jumps going awry...
05073         if ( !jumpVel[0] && !jumpVel[1] )//FIXME: && !ucmd.forwardmove && !ucmd.rightmove?
05074         {//we assume a jump straight up is safe
05075                 //Com_Printf( "(%d) jump straight up is safe\n", level.time );
05076                 return;
05077         }
05078         //Now predict where this is going
05079         //in steps, keep evaluating the trajectory until the new z pos is <= than current z pos, trace down from there
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); //shut the compiler up
05088 
05089         //This may be kind of wasteful, especially on long throws... use larger steps?  Divide the travelTime into a certain hard number of slices?  Trace just to apex and down?
05090         for ( elapsedTime = 500; elapsedTime <= 4000; elapsedTime += 500 )
05091         {
05092                 BG_EvaluateTrajectory( &tr, level.time + elapsedTime, testPos );
05093                 //FIXME: account for PM_AirMove if ucmd.forwardmove and/or ucmd.rightmove is non-zero...
05094                 if ( testPos[2] < lastPos[2] )
05095                 {//going down, don't check for BOTCLIP
05096                         trap_Trace( &trace, lastPos, NPC->r.mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask );//FIXME: include CONTENTS_BOTCLIP?
05097                 }
05098                 else
05099                 {//going up, check for BOTCLIP
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                 {//WTF?
05104                         //FIXME: what do we do when we start INSIDE the CONTENTS_BOTCLIP?  Do the trace again without that clipmask?
05105                         goto jump_unsafe;
05106                         return;
05107                 }
05108                 if ( trace.fraction < 1.0f )
05109                 {//hit something
05110                         if ( trace.contents & CONTENTS_BOTCLIP )
05111                         {//hit a do-not-enter brush
05112                                 goto jump_unsafe;
05113                                 return;
05114                         }
05115                         //FIXME: trace through func_glass?
05116                         break;
05117                 }
05118                 VectorCopy( testPos, lastPos );
05119         }
05120         //okay, reached end of jump, now trace down from here for a floor
05121         VectorCopy( trace.endpos, bottom );
05122         if ( bottom[2] > NPC->r.currentOrigin[2] )
05123         {//only care about dist down from current height or lower
05124                 bottom[2] = NPC->r.currentOrigin[2];
05125         }
05126         else if ( NPC->r.currentOrigin[2] - bottom[2] > 400 )
05127         {//whoa, long drop, don't do it!
05128                 //probably no floor at end of jump, so don't jump
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         {//hit ground!
05136                 if ( trace.entityNum < ENTITYNUM_WORLD )
05137                 {//landed on an ent
05138                         gentity_t *groundEnt = &g_entities[trace.entityNum];
05139                         if ( groundEnt->r.svFlags&SVF_GLASS_BRUSH )
05140                         {//don't land on breakable glass!
05141                                 goto jump_unsafe;
05142                                 return;
05143                         }
05144                 }
05145                 //Com_Printf( "(%d) jump is safe\n", level.time );
05146                 return;
05147         }
05148 jump_unsafe:
05149         //probably no floor at end of jump, so don't jump
05150         //Com_Printf( "(%d) unsafe jump cleared\n", level.time );
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         //See where enemy will be 300 ms from now
05162         Jedi_SetEnemyInfo( enemy_dest, enemy_dir, &enemy_dist, enemy_movedir, &enemy_movespeed, 300 );
05163 
05164         if ( Jedi_Jumping( NPC->enemy ) )
05165         {//I'm in the middle of a jump, so just see if I should attack
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         {//not gripping
05172                 //If we can't get straight at him
05173                 if ( !Jedi_ClearPathToSpot( enemy_dest, NPC->enemy->s.number ) )
05174                 {//hunt him down
05175                         //Com_Printf( "No Clear Path\n" );
05176                         if ( (NPC_ClearLOS4( NPC->enemy )||NPCInfo->enemyLastSeenTime>level.time-500) && NPC_FaceEnemy( qtrue ) )//( NPCInfo->rank == RANK_CREWMAN || NPCInfo->rank > RANK_LT_JG ) && 
05177                         {
05178                                 //try to jump to him?
05179                                 /*
05180                                 vec3_t end;
05181                                 VectorCopy( NPC->r.currentOrigin, end );
05182                                 end[2] += 36;
05183                                 trap_Trace( &trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, end, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP );
05184                                 if ( !trace.allsolid && !trace.startsolid && trace.fraction >= 1.0 )
05185                                 {
05186                                         vec3_t angles, forward;
05187                                         VectorCopy( NPC->client->ps.viewangles, angles );
05188                                         angles[0] = 0;
05189                                         AngleVectors( angles, forward, NULL, NULL );
05190                                         VectorMA( end, 64, forward, end );
05191                                         trap_Trace( &trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, end, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP );
05192                                         if ( !trace.allsolid && !trace.startsolid )
05193                                         {
05194                                                 if ( trace.fraction >= 1.0 || trace.plane.normal[2] > 0 )
05195                                                 {
05196                                                         ucmd.upmove = 127;
05197                                                         ucmd.forwardmove = 127;
05198                                                         return;
05199                                                 }
05200                                         }
05201                                 }
05202                                 */
05203                                 //FIXME: about every 1 second calc a velocity, 
05204                                 //run a loop of traces with evaluate trajectory 
05205                                 //for gravity with my size, see if it makes it...
05206                                 //this will also catch misacalculations that send you off ledges!
05207                                 //Com_Printf( "Considering Jump\n" );
05208                                 if ( Jedi_TryJump( NPC->enemy ) )
05209                                 {//FIXME: what about jumping to his enemyLastSeenLocation?
05210                                         return;
05211                                 }
05212                         }
05213 
05214                         //Check for evasion
05215                         if ( TIMER_Done( NPC, "parryTime" ) )
05216                         {//finished parrying
05217                                 if ( NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE &&
05218                                         NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
05219                                 {//wasn't blocked myself
05220                                         NPC->client->ps.saberBlocked = BLOCKED_NONE;
05221                                 }
05222                         }
05223                         if ( Jedi_Hunt() && !(NPCInfo->aiFlags&NPCAI_BLOCKED) )//FIXME: have to do this because they can ping-pong forever
05224                         {//can macro-navigate to him
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                         //well, try to head for his last seen location
05234         /*
05235                         else if ( Jedi_Track() )
05236                         {
05237                                 return;
05238                         }
05239         */              else
05240                         {//FIXME: try to find a waypoint that can see enemy, jump from there
05241                                 if ( NPCInfo->aiFlags & NPCAI_BLOCKED )
05242                                 {//try to jump to the blockedDest
05243                                         gentity_t *tempGoal = G_Spawn();//ugh, this is NOT good...?
05244                                         G_SetOrigin( tempGoal, NPCInfo->blockedDest );
05245                                         trap_LinkEntity( tempGoal );
05246                                         if ( Jedi_TryJump( tempGoal ) )
05247                                         {//going to jump to the dest
05248                                                 G_FreeEntity( tempGoal );
05249                                                 return;
05250                                         }
05251                                         G_FreeEntity( tempGoal );
05252                                 }
05253 
05254                                 enemy_lost = qtrue;
05255                         }
05256                 }
05257         }
05258         //else, we can see him or we can't track him at all
05259 
05260         //every few seconds, decide if we should we advance or retreat?
05261         Jedi_CombatTimersUpdate( enemy_dist );
05262 
05263         //We call this even if lost enemy to keep him moving and to update the taunting behavior
05264         //maintain a distance from enemy appropriate for our aggression level
05265         Jedi_CombatDistance( enemy_dist );
05266 
05267         //if ( !enemy_lost )
05268         {
05269                 //Update our seen enemy position
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         //Turn to face the enemy
05278         if ( TIMER_Done( NPC, "noturn" ) )
05279         {
05280                 Jedi_FaceEnemy( qtrue );
05281         }
05282         NPC_UpdateAngles( qtrue, qtrue );
05283         
05284         //Check for evasion
05285         if ( TIMER_Done( NPC, "parryTime" ) )
05286         {//finished parrying
05287                 if ( NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE &&
05288                         NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
05289                 {//wasn't blocked myself
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         {//do we need to do any evasion for other kinds of enemies?
05299         }
05300 
05301         //apply strafing/walking timers, etc.
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         {//not throwing saber or using force grip
05306                 //see if we can attack
05307                 if ( !Jedi_AttackDecide( enemy_dist ) )
05308                 {//we're not attacking, decide what else to do
05309                         Jedi_CombatIdle( enemy_dist );
05310                         //FIXME: lower aggression when actually strike offensively?  Or just when do damage?
05311                 }
05312                 else
05313                 {//we are attacking
05314                         //stop taunting
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         //Check for certain enemy special moves
05327         Jedi_CheckEnemyMovement( enemy_dist );
05328         //Make sure that we don't jump off ledges over long drops
05329         Jedi_CheckJumps();
05330         //Just make sure we don't strafe into walls or off cliffs
05331         if ( !NPC_MoveDirClear( ucmd.forwardmove, ucmd.rightmove, qtrue ) )
05332         {//uh-oh, we are going to fall or hit something
05333                 navInfo_t       info;
05334                 //Get the move info
05335                 NAV_GetLastMove( &info );
05336                 if ( !(info.flags & NIF_MACRO_NAV) )
05337                 {//micro-navigation told us to step off a ledge, try using macronav for now
05338                         NPC_MoveToGoal( qfalse );
05339                 }
05340                 //reset the timers.
05341                 TIMER_Set( NPC, "strafeLeft", 0 );
05342                 TIMER_Set( NPC, "strafeRight", 0 );
05343         }
05344 }
05345 
05346 /*
05347 ==========================================================================================
05348 EXTERNALLY CALLED BEHAVIOR STATES
05349 ==========================================================================================
05350 */
05351 
05352 /*
05353 -------------------------
05354 NPC_Jedi_Pain
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         //FIXME: base the actual aggression add/subtract on health?
05366         //FIXME: don't do this more than once per frame?
05367         //FIXME: when take pain, stop force gripping....?
05368         if ( other->s.weapon == WP_SABER )
05369         {//back off
05370                 TIMER_Set( self, "parryTime", -1 );
05371                 if ( self->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",self->NPC_type) )
05372                 {//less for Desann
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;//300
05378                 }
05379                 else
05380                 {
05381                         self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_spskill.integer)*200;//500
05382                 }
05383                 if ( !Q_irand( 0, 3 ) )
05384                 {//ouch... maybe switch up which saber power level we're using
05385                         Jedi_AdjustSaberAnimLevel( self, Q_irand( FORCE_LEVEL_1, FORCE_LEVEL_3 ) );
05386                 }
05387                 if ( !Q_irand( 0, 1 ) )//damage > 20 || self->health < 40 || 
05388                 {
05389                         //Com_Printf( "(%d) drop agg - hit by saber\n", level.time );
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                 //for testing only
05397                 // Figure out what quadrant the hit was in.
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         {//attack
05415                 //Com_Printf( "(%d) raise agg - hit by ranged\n", level.time );
05416                 Jedi_Aggression( self, 1 );
05417         }
05418 
05419         self->NPC->enemyCheckDebounceTime = 0;
05420 
05421         WP_ForcePowerStop( self, FP_GRIP );
05422 
05423         //NPC_Pain( self, inflictor, other, point, damage, mod );
05424         NPC_Pain(self, attacker, damage);
05425 
05426         if ( !damage && self->health > 0 )
05427         {//FIXME: better way to know I was pushed
05428                 G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
05429         }
05430 
05431         //drop me from the ceiling if I'm on it
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         {//run away!
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                 {//no owner
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 //              if ( NPC->client->ps.powerups[PW_CLOAKED] || g_crosshairEntNum != NPC->s.number )
05487                 if (NPC->client->ps.powerups[PW_CLOAKED] || !NPC_SomeoneLookingAtMe(NPC)) //rwwFIXMEFIXME: Need to pay attention to who is under crosshair for each player or something.
05488                 {//if I'm not cloaked and the player's crosshair is on me, I will wake up, otherwise do this stuff down here...
05489                         if ( !trap_InPVS( player->r.currentOrigin, NPC->r.currentOrigin ) )
05490                         {//must be in same room
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                         {//never ambush if they're above me or way way below me
05503                                 continue;
05504                         }
05505 
05506                         //If the target is this close, then wake up regardless
05507                         if ( (target_dist = DistanceHorizontalSquared( player->r.currentOrigin, NPC->r.currentOrigin )) > 4096 )
05508                         {//closer than 64 - always ambush
05509                                 if ( target_dist > 147456 )
05510                                 {//> 384, not close enough to ambush
05511                                         continue;
05512                                 }
05513                                 //Check FOV first
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                 //Got him, return true;
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         //Didn't get anyone.
05544         return qfalse;
05545 }
05546 
05547 void Jedi_Ambush( gentity_t *self )
05548 {
05549         self->client->noclip = qfalse;
05550 //      self->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL;
05551         NPC_SetAnim( self, SETANIM_BOTH, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
05552         self->client->ps.weaponTime = self->client->ps.torsoTimer; //NPC->client->ps.torsoTimer; //what the?
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 Jedi_Patrol
05572 -------------------------
05573 */
05574 
05575 static void Jedi_Patrol( void )
05576 {
05577         NPC->client->ps.saberBlocked = BLOCKED_NONE;
05578 
05579         if ( Jedi_WaitingAmbush( NPC ) )
05580         {//hiding on the ceiling
05581                 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_CEILING_CLING, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
05582                 if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
05583                 {//look for enemies
05584                         if ( Jedi_CheckAmbushPlayer() || Jedi_CheckDanger() )
05585                         {//found him!
05586                                 Jedi_Ambush( NPC );
05587                                 NPC_UpdateAngles( qtrue, qtrue );
05588                                 return;
05589                         }
05590                 }
05591         }
05592         else if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )//NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
05593         {//look for enemies
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                                 {//we could potentially see him
05605                                         enemy_dist = DistanceSquared( NPC->r.currentOrigin, enemy->r.currentOrigin );
05606                                         if ( enemy->s.eType == ET_PLAYER || enemy_dist < best_enemy_dist )
05607                                         {
05608                                                 //if the enemy is close enough, or threw his saber, take him as the enemy
05609                                                 //FIXME: what if he throws a thermal detonator?
05610                                                 if ( enemy_dist < (220*220) || ( NPCInfo->investigateCount>= 3 && !NPC->client->ps.saberHolstered ) )
05611                                                 {
05612                                                         G_SetEnemy( NPC, enemy );
05613                                                         //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now
05614                                                         NPCInfo->stats.aggression = 3;
05615                                                         break;
05616                                                 }
05617                                                 else if ( enemy->client->ps.saberInFlight && !enemy->client->ps.saberHolstered )
05618                                                 {//threw his saber, see if it's heading toward me and close enough to consider a threat
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                                                         {//it's heading towards me
05629                                                                 if ( saberDist < 200 )
05630                                                                 {//incoming!
05631                                                                         G_SetEnemy( NPC, enemy );
05632                                                                         //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now
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                 {//still not mad
05646                         if ( !best_enemy )
05647                         {
05648                                 //Com_Printf( "(%d) drop agg - no enemy (patrol)\n", level.time );
05649                                 Jedi_AggressionErosion(-1);
05650                                 //FIXME: what about alerts?  But not if ignore alerts
05651                         }
05652                         else
05653                         {//have one to consider
05654                                 if ( NPC_ClearLOS4( best_enemy ) )
05655                                 {//we have a clear (of architecture) LOS to him
05656                                         if ( best_enemy->s.number )
05657                                         {//just attack
05658                                                 G_SetEnemy( NPC, best_enemy );
05659                                                 NPCInfo->stats.aggression = 3;
05660                                         }
05661                                         else if ( NPC->client->NPC_class != CLASS_BOBAFETT )
05662                                         {//the player, toy with him
05663                                                 //get progressively more interested over time
05664                                                 if ( TIMER_Done( NPC, "watchTime" ) )
05665                                                 {//we want to pick him up in stages
05666                                                         if ( TIMER_Get( NPC, "watchTime" ) == -1 )
05667                                                         {//this is the first time, we'll ignore him for a couple seconds
05668                                                                 TIMER_Set( NPC, "watchTime", Q_irand( 3000, 5000 ) );
05669                                                                 goto finish;
05670                                                         }
05671                                                         else
05672                                                         {//okay, we've ignored him, now start to notice him
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                                                 //while we're waiting, do what we need to do
05682                                                 if ( best_enemy_dist < (440*440) || NPCInfo->investigateCount >= 2 )
05683                                                 {//stage three: keep facing him
05684                                                         NPC_FaceEntity( best_enemy, qtrue );
05685                                                         if ( best_enemy_dist < (330*330) )
05686                                                         {//stage four: turn on the saber
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                                                 {//stage two: stop and face him every now and then
05695                                                         if ( TIMER_Done( NPC, "watchTime" ) )
05696                                                         {
05697                                                                 NPC_FaceEntity( best_enemy, qtrue );
05698                                                         }
05699                                                 }
05700                                                 else
05701                                                 {//stage one: look at him.
05702                                                         NPC_SetLookTarget( NPC, best_enemy->s.number, 0 );
05703                                                 }
05704                                         }
05705                                 }
05706                                 else if ( TIMER_Done( NPC, "watchTime" ) )
05707                                 {//haven't seen him in a bit, clear the lookTarget
05708                                         NPC_ClearLookTarget( NPC );
05709                                 }
05710                         }
05711                 }
05712         }
05713 finish:
05714         //If we have somewhere to go, then do that
05715         if ( UpdateGoal() )
05716         {
05717                 ucmd.buttons |= BUTTON_WALKING;
05718                 //Jedi_Move( NPCInfo->goalEntity );
05719                 NPC_MoveToGoal( qtrue );
05720         }
05721 
05722         NPC_UpdateAngles( qtrue, qtrue );
05723 
05724         if ( NPC->enemy )
05725         {//just picked one up
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 )//|| self->client->ps.weaponTime > 0 )
05747         {
05748                 return qfalse;
05749         }
05750 
05751         return qtrue;
05752 }
05753 /*
05754 -------------------------
05755 NPC_BSJedi_FollowLeader
05756 -------------------------
05757 */
05758 void NPC_BSJedi_FollowLeader( void )
05759 {
05760         NPC->client->ps.saberBlocked = BLOCKED_NONE;
05761         if ( !NPC->enemy )
05762         {
05763                 //Com_Printf( "(%d) drop agg - no enemy (follow)\n", level.time );
05764                 Jedi_AggressionErosion(-1);
05765         }
05766         
05767         //did we drop our saber?  If so, go after it!
05768         if ( NPC->client->ps.saberInFlight )
05769         {//saber is not in hand
05770                 if ( NPC->client->ps.saberEntityNum < ENTITYNUM_NONE && NPC->client->ps.saberEntityNum > 0 )//player is 0
05771                 {//
05772                         if ( g_entities[NPC->client->ps.saberEntityNum].s.pos.trType == TR_STATIONARY )
05773                         {//fell to the ground, try to pick it up... 
05774                                 if ( Jedi_CanPullBackSaber( NPC ) )
05775                                 {
05776                                         //FIXME: if it's on the ground and we just pulled it back to us, should we
05777                                         //              stand still for a bit to make sure it gets to us...?
05778                                         //              otherwise we could end up running away from it while it's on its
05779                                         //              way back to us and we could lose it again.
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                                         {//get our saber back NOW!
05785                                                 if ( !NPC_MoveToGoal( qtrue ) )//Jedi_Move( NPCInfo->goalEntity, qfalse );
05786                                                 {//can't nav to it, try jumping to it
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                 {//in mid-jump
05804                         return;
05805                 }
05806 
05807                 if ( !NAV_CheckAhead( NPC, NPCInfo->goalEntity->r.currentOrigin, &trace, ( NPC->clipmask & ~CONTENTS_BODY )|CONTENTS_BOTCLIP ) )
05808                 {//can't get straight to him
05809                         if ( NPC_ClearLOS4( NPCInfo->goalEntity ) && NPC_FaceEntity( NPCInfo->goalEntity, qtrue ) )
05810                         {//no line of sight
05811                                 if ( Jedi_TryJump( NPCInfo->goalEntity ) )
05812                                 {//started a jump
05813                                         return;
05814                                 }
05815                         }
05816                 }
05817                 if ( NPCInfo->aiFlags & NPCAI_BLOCKED )
05818                 {//try to jump to the blockedDest
05819                         if ( fabs(NPCInfo->blockedDest[2]-NPC->r.currentOrigin[2]) > 64 )
05820                         {
05821                                 gentity_t *tempGoal = G_Spawn();//ugh, this is NOT good...?
05822                                 G_SetOrigin( tempGoal, NPCInfo->blockedDest );
05823                                 trap_LinkEntity( tempGoal );
05824                                 TIMER_Set( NPC, "jumpChaseDebounce", -1 );
05825                                 if ( Jedi_TryJump( tempGoal ) )
05826                                 {//going to jump to the dest
05827                                         G_FreeEntity( tempGoal );
05828                                         return;
05829                                 }
05830                                 G_FreeEntity( tempGoal );
05831                         }
05832                 }
05833         }
05834         //try normal movement
05835         NPC_BSFollowLeader();
05836 }
05837 
05838 
05839 /*
05840 -------------------------
05841 Jedi_Attack
05842 -------------------------
05843 */
05844 
05845 static void Jedi_Attack( void )
05846 {
05847         //Don't do anything if we're in a pain anim
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                 //FIXME: maybe if I'm losing I should try to force-push out of it?  Very rarely, though...
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                 //based on my skill, hit attack button every other to every several frames in order to push enemy back
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;//he pushes *hard*
05877                                 }
05878                                 else
05879                                 {
05880                                         chance = 3.0f;//he pushes *hard*
05881                                 }
05882                         }
05883                         else if ( NPC->client->NPC_class == CLASS_TAVION )
05884                         {
05885                                 chance = 2.0f+g_spskill.value;//from 2 to 4
05886                         }
05887                         else
05888                         {//the escalation in difficulty is nice, here, but cap it so it doesn't get *impossible* on hard
05889                                 float maxChance = (float)(RANK_LT)/2.0f+3.0f;//5?
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                 //      if ( flrand( -4.0f, chance ) >= 0.0f && !(NPC->client->ps.pm_flags&PMF_ATTACK_HELD) )
05904                 //      {
05905                 //              ucmd.buttons |= BUTTON_ATTACK;
05906                 //      }
05907                         if ( flrand( -4.0f, chance ) >= 0.0f )
05908                         {
05909                                 ucmd.buttons |= BUTTON_ATTACK;
05910                         }
05911                         //rwwFIXMEFIXME: support for PMF_ATTACK_HELD
05912                 }
05913                 NPC_UpdateAngles( qtrue, qtrue );
05914                 return;
05915         }
05916         //did we drop our saber?  If so, go after it!
05917         if ( NPC->client->ps.saberInFlight )
05918         {//saber is not in hand
05919         //      if ( NPC->client->ps.saberEntityNum < ENTITYNUM_NONE && NPC->client->ps.saberEntityNum > 0 )//player is 0
05920                 if (!NPC->client->ps.saberEntityNum && NPC->client->saberStoredIndex) //this is valid, it's 0 when our saber is gone -rww (mp-specific)
05921                 {//
05922                         //if ( g_entities[NPC->client->ps.saberEntityNum].s.pos.trType == TR_STATIONARY )
05923                         if (1) //no matter
05924                         {//fell to the ground, try to pick it up
05925                         //      if ( Jedi_CanPullBackSaber( NPC ) )
05926                                 if (1) //no matter
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                                         {//get our saber back NOW!
05933                                                 Jedi_Move( NPCInfo->goalEntity, qfalse );
05934                                                 NPC_UpdateAngles( qtrue, qtrue );
05935                                                 if ( NPC->enemy->s.weapon == WP_SABER )
05936                                                 {//be sure to continue evasion
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         //see if our enemy was killed by us, gloat and turn off saber after cool down.
05949         //FIXME: don't do this if we have other enemies to fight...?
05950         if ( NPC->enemy )
05951         {
05952                 if ( NPC->enemy->health <= 0 && NPC->enemy->enemy == NPC && NPC->client->playerTeam != NPCTEAM_PLAYER )//good guys don't gloat
05953                 {//my enemy is dead and I killed him
05954                         NPCInfo->enemyCheckDebounceTime = 0;//keep looking for others
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) )//64 squared
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                                 {//saber is still on (or we're trying to pull it back), count down erosion and keep facing the enemy
05998                                         //FIXME: need to stop this from happening over and over again when they're blocking their victim's saber
05999                                         //FIXME: turn off saber sooner so we get cool walk anim?
06000                                         //Com_Printf( "(%d) drop agg - enemy dead\n", level.time );
06001                                         Jedi_AggressionErosion(-3);
06002                                         if ( BG_SabersOff( &NPC->client->ps ) && !NPC->client->ps.saberInFlight )
06003                                         {//turned off saber (in hand), gloat
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                                 {//keep walking
06013                                         if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->r.currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared
06014                                         {
06015                                                 NPCInfo->goalEntity = NPC->enemy;
06016                                                 Jedi_Move( NPC->enemy, qfalse );
06017                                                 ucmd.buttons |= BUTTON_WALKING;
06018                                         }
06019                                         else
06020                                         {//got there
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         //If we don't have an enemy, just idle
06037         if ( NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ) )
06038         {
06039                 if ( NPC->enemy->count <= 0 )
06040                 {//it's out of ammo
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                 {//lost him, go back to what we were doing before
06060                         NPCInfo->tempBehavior = BS_DEFAULT;
06061                         NPC_UpdateAngles( qtrue, qtrue );
06062                         return;
06063                 }
06064                 Jedi_Patrol();//was calling Idle... why?
06065                 return;
06066         }
06067 
06068         //always face enemy if have one
06069         NPCInfo->combatMove = qtrue;
06070 
06071         //Track the player and kill them if possible
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         {//this is really stupid, but okay...
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         //NOTE: for now, we clear ucmd.forwardmove & ucmd.rightmove while in air to avoid jumps going awry...
06088         if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE )
06089         {//don't push while in air, throws off jumps!
06090                 //FIXME: if we are in the air over a drop near a ledge, should we try to push back towards the ledge?
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                 {//just make sure they don't pull their saber to them if they're being blocked
06105                         ucmd.buttons &= ~BUTTON_ATTACK;
06106                 }
06107         }
06108 
06109         if( (NPCInfo->scriptFlags&SCF_DONT_FIRE) //not allowed to attack
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) )//saber in water
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                 {//the more we're hurt and the stronger the attack we're using, the more likely we are to make a anger noise when we swing
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                 {//Tavion will kick in force speed if the player does...
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         {//don't have an enemy, look for one
06176                 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
06177                 {
06178                         NPC_BSST_Patrol();
06179                 }
06180                 else
06181                 {
06182                         Jedi_Patrol();
06183                 }
06184         }
06185         else//if ( NPC->enemy )
06186         {//have an enemy
06187                 if ( Jedi_WaitingAmbush( NPC ) )
06188                 {//we were still waiting to drop down - must have had enemy set on me outside my AI
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                 //if we have multiple-jedi combat, probably need to keep checking (at certain debounce intervals) for a better (closer, more active) enemy and switch if needbe...
06203                 if ( ((!ucmd.buttons&&!NPC->client->ps.fd.forcePowersActive)||(NPC->enemy&&NPC->enemy->health<=0)) && NPCInfo->enemyCheckDebounceTime < level.time )
06204                 {//not doing anything (or walking toward a vanquished enemy - fixme: always taunt the player?), not using force powers and it's time to look again
06205                         //FIXME: build a list of all local enemies (since we have to find best anyway) for other AI factors- like when to use group attacks, determine when to change tactics, when surrounded, when blocked by another in the enemy group, etc.  Should we build this group list or let the enemies maintain their own list and we just access it?
06206                         gentity_t *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy?
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                         {//picked up a new enemy!
06214                                 NPC->lastEnemy = NPC->enemy;
06215                                 G_SetEnemy( NPC, newEnemy );
06216                         }
06217                         NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 1000, 3000 );
06218                 }
06219         }
06220 }