1 /*
2 ===========================================================================
3 Copyright (C) 2000 - 2013, Raven Software, Inc.
4 Copyright (C) 2001 - 2013, Activision, Inc.
5 Copyright (C) 2013 - 2015, OpenJK contributors
6
7 This file is part of the OpenJK source code.
8
9 OpenJK is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License version 2 as
11 published by the Free Software Foundation.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, see <http://www.gnu.org/licenses/>.
20 ===========================================================================
21 */
22
23 #include "b_local.h"
24 #include "g_nav.h"
25 #include "anims.h"
26 #include "wp_saber.h"
27 #include "../qcommon/tri_coll_test.h"
28 #include "g_navigator.h"
29 #include "../cgame/cg_local.h"
30 #include "g_functions.h"
31
32 //Externs
33 extern qboolean G_ValidEnemy( gentity_t *self, gentity_t *enemy );
34 extern void CG_DrawAlert( vec3_t origin, float rating );
35 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
36 extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f );
37 extern void G_StartMatrixEffect( gentity_t *ent, int meFlags = 0, int length = 1000, float timeScale = 0.0f, int spinTime = 0 );
38 extern void ForceJump( gentity_t *self, usercmd_t *ucmd );
39 extern void NPC_ClearLookTarget( gentity_t *self );
40 extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime );
41 extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
42 extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask );
43 extern qboolean NPC_CheckEnemyStealth( void );
44 extern gitem_t *FindItemForAmmo( ammo_t ammo );
45 extern void ForceLightning( gentity_t *self );
46 extern void ForceHeal( gentity_t *self );
47 extern void ForceRage( gentity_t *self );
48 extern void ForceProtect( gentity_t *self );
49 extern void ForceAbsorb( gentity_t *self );
50 extern qboolean ForceDrain2( gentity_t *self );
51 extern int WP_MissileBlockForBlock( int saberBlock );
52 extern qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
53 extern qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
54 extern void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower );
55 extern void WP_KnockdownTurret( gentity_t *self, gentity_t *pas );
56 extern void WP_DeactivateSaber( gentity_t *self, qboolean clearLength = qfalse );
57 extern int PM_AnimLength( int index, animNumber_t anim );
58 extern qboolean PM_SaberInStart( int move );
59 extern qboolean PM_SaberInSpecialAttack( int anim );
60 extern qboolean PM_SaberInAttack( int move );
61 extern qboolean PM_SaberInBounce( int move );
62 extern qboolean PM_SaberInParry( int move );
63 extern qboolean PM_SaberInKnockaway( int move );
64 extern qboolean PM_SaberInBrokenParry( int move );
65 extern qboolean PM_SaberInDeflect( int move );
66 extern qboolean PM_SpinningSaberAnim( int anim );
67 extern qboolean PM_FlippingAnim( int anim );
68 extern qboolean PM_RollingAnim( int anim );
69 extern qboolean PM_InKnockDown( playerState_t *ps );
70 extern qboolean PM_InRoll( playerState_t *ps );
71 extern qboolean PM_InGetUp( playerState_t *ps );
72 extern qboolean PM_InSpecialJump( int anim );
73 extern qboolean PM_SuperBreakWinAnim( int anim );
74 extern qboolean PM_InOnGroundAnim ( playerState_t *ps );
75 extern qboolean PM_DodgeAnim( int anim );
76 extern qboolean PM_DodgeHoldAnim( int anim );
77 extern qboolean PM_InAirKickingAnim( int anim );
78 extern qboolean PM_KickingAnim( int anim );
79 extern qboolean PM_StabDownAnim( int anim );
80 extern qboolean PM_SuperBreakLoseAnim( int anim );
81 extern qboolean PM_SaberInKata( saberMoveName_t saberMove );
82 extern qboolean PM_InRollIgnoreTimer( playerState_t *ps );
83 extern qboolean PM_PainAnim( int anim );
84 extern qboolean G_CanKickEntity( gentity_t *self, gentity_t *target );
85 extern saberMoveName_t G_PickAutoKick( gentity_t *self, gentity_t *enemy, qboolean storeMove );
86 extern saberMoveName_t G_PickAutoMultiKick( gentity_t *self, qboolean allowSingles, qboolean storeMove );
87 extern qboolean NAV_DirSafe( gentity_t *self, vec3_t dir, float dist );
88 extern qboolean NAV_MoveDirSafe( gentity_t *self, usercmd_t *cmd, float distScale = 1.0f );
89 extern float NPC_EnemyRangeFromBolt( int boltIndex );
90 extern qboolean WP_SabersCheckLock2( gentity_t *attacker, gentity_t *defender, sabersLockMode_t lockMode );
91 extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock );
92 extern qboolean G_EntIsBreakable( int entityNum, gentity_t *breaker );
93 extern qboolean PM_LockedAnim( int anim );
94 extern qboolean G_ClearLineOfSight(const vec3_t point1, const vec3_t point2, int ignore, int clipmask);
95
96 extern cvar_t *g_saberRealisticCombat;
97 extern cvar_t *d_slowmodeath;
98 extern cvar_t *g_saberNewControlScheme;
99 extern int parryDebounce[];
100
101 //Locals
102 static void Jedi_Aggression( gentity_t *self, int change );
103 qboolean Jedi_WaitingAmbush( gentity_t *self );
104 void Tavion_SithSwordRecharge( void );
105 qboolean Rosh_BeingHealed( gentity_t *self );
106
107 static qboolean enemy_in_striking_range = qfalse;
108 static int jediSpeechDebounceTime[TEAM_NUM_TEAMS];//used to stop several jedi from speaking all at once
109
NPC_CultistDestroyer_Precache(void)110 void NPC_CultistDestroyer_Precache( void )
111 {
112 G_SoundIndex( "sound/movers/objects/green_beam_lp2.wav" );
113 G_EffectIndex( "force/destruction_exp" );
114 }
115
NPC_ShadowTrooper_Precache(void)116 void NPC_ShadowTrooper_Precache( void )
117 {
118 RegisterItem( FindItemForAmmo( AMMO_FORCE ) );
119 G_SoundIndex( "sound/chars/shadowtrooper/cloak.wav" );
120 G_SoundIndex( "sound/chars/shadowtrooper/decloak.wav" );
121 }
122
NPC_Rosh_Dark_Precache(void)123 void NPC_Rosh_Dark_Precache( void )
124 {
125 G_EffectIndex( "force/kothos_recharge.efx" );
126 G_EffectIndex( "force/kothos_beam.efx" );
127 }
128
Jedi_ClearTimers(gentity_t * ent)129 void Jedi_ClearTimers( gentity_t *ent )
130 {
131 TIMER_Set( ent, "roamTime", 0 );
132 TIMER_Set( ent, "chatter", 0 );
133 TIMER_Set( ent, "strafeLeft", 0 );
134 TIMER_Set( ent, "strafeRight", 0 );
135 TIMER_Set( ent, "noStrafe", 0 );
136 TIMER_Set( ent, "walking", 0 );
137 TIMER_Set( ent, "taunting", 0 );
138 TIMER_Set( ent, "parryTime", 0 );
139 TIMER_Set( ent, "parryReCalcTime", 0 );
140 TIMER_Set( ent, "forceJumpChasing", 0 );
141 TIMER_Set( ent, "jumpChaseDebounce", 0 );
142 TIMER_Set( ent, "moveforward", 0 );
143 TIMER_Set( ent, "moveback", 0 );
144 TIMER_Set( ent, "movenone", 0 );
145 TIMER_Set( ent, "moveright", 0 );
146 TIMER_Set( ent, "moveleft", 0 );
147 TIMER_Set( ent, "movecenter", 0 );
148 TIMER_Set( ent, "saberLevelDebounce", 0 );
149 TIMER_Set( ent, "noRetreat", 0 );
150 TIMER_Set( ent, "holdLightning", 0 );
151 TIMER_Set( ent, "gripping", 0 );
152 TIMER_Set( ent, "draining", 0 );
153 TIMER_Set( ent, "noturn", 0 );
154 TIMER_Set( ent, "specialEvasion", 0 );
155 }
156
Jedi_CultistDestroyer(gentity_t * self)157 qboolean Jedi_CultistDestroyer( gentity_t *self )
158 {
159 if ( !self || !self->client )
160 {
161 return qfalse;
162 }
163 //FIXME: just make a flag, dude!
164 if ( self->client->NPC_class == CLASS_REBORN
165 && self->s.weapon == WP_MELEE
166 && Q_stricmp( "cultist_destroyer", self->NPC_type ) == 0 )
167 {
168 return qtrue;
169 }
170 return qfalse;
171 }
172
Jedi_PlayBlockedPushSound(gentity_t * self)173 void Jedi_PlayBlockedPushSound( gentity_t *self )
174 {
175 if ( !self->s.number )
176 {
177 G_AddVoiceEvent( self, EV_PUSHFAIL, 3000 );
178 }
179 else if ( self->health > 0 && self->NPC && self->NPC->blockedSpeechDebounceTime < level.time )
180 {
181 G_AddVoiceEvent( self, EV_PUSHFAIL, 3000 );
182 self->NPC->blockedSpeechDebounceTime = level.time + 3000;
183 }
184 }
185
Jedi_PlayDeflectSound(gentity_t * self)186 void Jedi_PlayDeflectSound( gentity_t *self )
187 {
188 if ( !self->s.number )
189 {
190 G_AddVoiceEvent( self, Q_irand( EV_DEFLECT1, EV_DEFLECT3 ), 3000 );
191 }
192 else if ( self->health > 0 && self->NPC && self->NPC->blockedSpeechDebounceTime < level.time )
193 {
194 G_AddVoiceEvent( self, Q_irand( EV_DEFLECT1, EV_DEFLECT3 ), 3000 );
195 self->NPC->blockedSpeechDebounceTime = level.time + 3000;
196 }
197 }
198
NPC_Jedi_PlayConfusionSound(gentity_t * self)199 void NPC_Jedi_PlayConfusionSound( gentity_t *self )
200 {
201 if ( self->health > 0 )
202 {
203 if ( self->client
204 && ( self->client->NPC_class == CLASS_ALORA
205 || self->client->NPC_class == CLASS_TAVION
206 || self->client->NPC_class == CLASS_DESANN ) )
207 {
208 G_AddVoiceEvent( self, Q_irand( EV_CONFUSE1, EV_CONFUSE3 ), 2000 );
209 }
210 else if ( Q_irand( 0, 1 ) )
211 {
212 G_AddVoiceEvent( self, Q_irand( EV_TAUNT1, EV_TAUNT3 ), 2000 );
213 }
214 else
215 {
216 G_AddVoiceEvent( self, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 2000 );
217 }
218 }
219 }
220
Jedi_StopKnockdown(gentity_t * self,gentity_t * pusher,const vec3_t pushDir)221 qboolean Jedi_StopKnockdown( gentity_t *self, gentity_t *pusher, const vec3_t pushDir )
222 {
223 if ( self->s.number < MAX_CLIENTS || !self->NPC )
224 {//only NPCs
225 return qfalse;
226 }
227
228 if ( self->client->ps.forcePowerLevel[FP_LEVITATION] < FORCE_LEVEL_1 )
229 {//only force-users
230 return qfalse;
231 }
232
233 if ( self->client->moveType == MT_FLYSWIM )
234 {//can't knock me down when I'm flying
235 return qtrue;
236 }
237
238 if ( self->NPC && (self->NPC->aiFlags&NPCAI_BOSS_CHARACTER) )
239 {//bosses always get out of a knockdown
240 }
241 else if ( Q_irand( 0, RANK_CAPTAIN+5 ) > self->NPC->rank )
242 {//lower their rank, the more likely they are fall down
243 return qfalse;
244 }
245
246 vec3_t pDir, fwd, right, ang = {0, self->currentAngles[YAW], 0};
247 float fDot, rDot;
248 int strafeTime = Q_irand( 1000, 2000 );
249
250 AngleVectors( ang, fwd, right, NULL );
251 VectorNormalize2( pushDir, pDir );
252 fDot = DotProduct( pDir, fwd );
253 rDot = DotProduct( pDir, right );
254
255 //flip or roll with it
256 usercmd_t tempCmd;
257 if ( fDot >= 0.4f )
258 {
259 tempCmd.forwardmove = 127;
260 TIMER_Set( self, "moveforward", strafeTime );
261 }
262 else if ( fDot <= -0.4f )
263 {
264 tempCmd.forwardmove = -127;
265 TIMER_Set( self, "moveback", strafeTime );
266 }
267 else if ( rDot > 0 )
268 {
269 tempCmd.rightmove = 127;
270 TIMER_Set( self, "strafeRight", strafeTime );
271 TIMER_Set( self, "strafeLeft", -1 );
272 }
273 else
274 {
275 tempCmd.rightmove = -127;
276 TIMER_Set( self, "strafeLeft", strafeTime );
277 TIMER_Set( self, "strafeRight", -1 );
278 }
279 G_AddEvent( self, EV_JUMP, 0 );
280 if ( !Q_irand( 0, 1 ) )
281 {//flip
282 self->client->ps.forceJumpCharge = 280;//FIXME: calc this intelligently?
283 ForceJump( self, &tempCmd );
284 }
285 else
286 {//roll
287 TIMER_Set( self, "duck", strafeTime );
288 }
289 self->painDebounceTime = 0;//so we do something
290
291 return qtrue;
292 }
293 extern void Boba_FireDecide( void );
294 extern void RT_FireDecide( void );
295 extern void Boba_FlyStart( gentity_t *self );
296
297
298
299
300
301 //===============================================================================================
302 //TAVION BOSS
303 //===============================================================================================
NPC_TavionScepter_Precache(void)304 void NPC_TavionScepter_Precache( void )
305 {
306 G_EffectIndex( "scepter/beam_warmup.efx" );
307 G_EffectIndex( "scepter/beam.efx" );
308 G_EffectIndex( "scepter/slam_warmup.efx" );
309 G_EffectIndex( "scepter/slam.efx" );
310 G_EffectIndex( "scepter/impact.efx" );
311 G_SoundIndex( "sound/weapons/scepter/loop.wav" );
312 G_SoundIndex( "sound/weapons/scepter/slam_warmup.wav" );
313 G_SoundIndex( "sound/weapons/scepter/beam_warmup.wav" );
314 }
315
NPC_TavionSithSword_Precache(void)316 void NPC_TavionSithSword_Precache( void )
317 {
318 G_EffectIndex( "scepter/recharge.efx" );
319 G_EffectIndex( "scepter/invincibility.efx" );
320 G_EffectIndex( "scepter/sword.efx" );
321 G_SoundIndex( "sound/weapons/scepter/recharge.wav" );
322 }
323
Tavion_ScepterDamage(void)324 void Tavion_ScepterDamage( void )
325 {
326 if ( !NPC->ghoul2.size()
327 || NPC->weaponModel[1] <= 0 )
328 {
329 return;
330 }
331
332 if ( NPC->genericBolt1 != -1 )
333 {
334 int curTime = (cg.time?cg.time:level.time);
335 qboolean hit = qfalse;
336 int lastHit = ENTITYNUM_NONE;
337 for ( int time = curTime-25; time <= curTime+25&&!hit; time += 25 )
338 {
339 mdxaBone_t boltMatrix;
340 vec3_t tip, dir, base, angles={0,NPC->currentAngles[YAW],0};
341 trace_t trace;
342
343 gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->weaponModel[1],
344 NPC->genericBolt1,
345 &boltMatrix, angles, NPC->currentOrigin, time,
346 NULL, NPC->s.modelScale );
347 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, base );
348 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_X, dir );
349 VectorMA( base, 512, dir, tip );
350 #ifndef FINAL_BUILD
351 if ( d_saberCombat->integer > 1 )
352 {
353 G_DebugLine(base, tip, 1000, 0x000000ff, qtrue);
354 }
355 #endif
356 gi.trace( &trace, base, vec3_origin, vec3_origin, tip, NPC->s.number, MASK_SHOT, G2_RETURNONHIT, 10 );
357 if ( trace.fraction < 1.0f )
358 {//hit something
359 gentity_t *traceEnt = &g_entities[trace.entityNum];
360
361 //FIXME: too expensive!
362 //if ( time == curTime )
363 {//UGH
364 G_PlayEffect( G_EffectIndex( "scepter/impact.efx" ), trace.endpos, trace.plane.normal );
365 }
366
367 if ( traceEnt->takedamage
368 && trace.entityNum != lastHit
369 && (!traceEnt->client || traceEnt == NPC->enemy || traceEnt->client->NPC_class != NPC->client->NPC_class) )
370 {//smack
371 int dmg = Q_irand( 10, 20 )*(g_spskill->integer+1);//NOTE: was 6-12
372 //FIXME: debounce?
373 //FIXME: do dismemberment
374 G_Damage( traceEnt, NPC, NPC, vec3_origin, trace.endpos, dmg, DAMAGE_NO_KNOCKBACK, MOD_SABER );//MOD_MELEE );
375 if ( traceEnt->client )
376 {
377 if ( !Q_irand( 0, 2 ) )
378 {
379 G_AddVoiceEvent( NPC, Q_irand( EV_CONFUSE1, EV_CONFUSE2 ), 10000 );
380 }
381 else
382 {
383 G_AddVoiceEvent( NPC, EV_JDETECTED3, 10000 );
384 }
385 G_Throw( traceEnt, dir, Q_flrand( 50, 80 ) );
386 if ( traceEnt->health > 0 && !Q_irand( 0, 2 ) )//FIXME: base on skill!
387 {//do pain on enemy
388 G_Knockdown( traceEnt, NPC, dir, 300, qtrue );
389 }
390 }
391 hit = qtrue;
392 lastHit = trace.entityNum;
393 }
394 }
395 }
396 }
397 }
398
Tavion_ScepterSlam(void)399 void Tavion_ScepterSlam( void )
400 {
401 if ( !NPC->ghoul2.size()
402 || NPC->weaponModel[1] <= 0 )
403 {
404 return;
405 }
406
407 int boltIndex = gi.G2API_AddBolt(&NPC->ghoul2[NPC->weaponModel[1]], "*weapon");
408 if ( boltIndex != -1 )
409 {
410 mdxaBone_t boltMatrix;
411 vec3_t handle, bottom, angles={0,NPC->currentAngles[YAW],0};
412 trace_t trace;
413 gentity_t *radiusEnts[ 128 ];
414 int numEnts;
415 const float radius = 300.0f;
416 const float halfRad = (radius/2);
417 float dist;
418 int i;
419 vec3_t mins, maxs, entDir;
420
421 gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->weaponModel[1],
422 boltIndex,
423 &boltMatrix, angles, NPC->currentOrigin, (cg.time?cg.time:level.time),
424 NULL, NPC->s.modelScale );
425 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, handle );
426 VectorCopy( handle, bottom );
427 bottom[2] -= 128.0f;
428
429 gi.trace( &trace, handle, vec3_origin, vec3_origin, bottom, NPC->s.number, MASK_SHOT, G2_RETURNONHIT, 10 );
430 G_PlayEffect( G_EffectIndex( "scepter/slam.efx" ), trace.endpos, trace.plane.normal );
431
432 //Setup the bbox to search in
433 for ( i = 0; i < 3; i++ )
434 {
435 mins[i] = trace.endpos[i] - radius;
436 maxs[i] = trace.endpos[i] + radius;
437 }
438
439 //Get the number of entities in a given space
440 numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, 128 );
441
442 for ( i = 0; i < numEnts; i++ )
443 {
444 if ( !radiusEnts[i]->inuse )
445 {
446 continue;
447 }
448
449 if ( (radiusEnts[i]->flags&FL_NO_KNOCKBACK) )
450 {//don't throw them back
451 continue;
452 }
453
454 if ( radiusEnts[i] == NPC )
455 {//Skip myself
456 continue;
457 }
458
459 if ( radiusEnts[i]->client == NULL )
460 {//must be a client
461 if ( G_EntIsBreakable( radiusEnts[i]->s.number, NPC ) )
462 {//damage breakables within range, but not as much
463 G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, 100, 0, MOD_EXPLOSIVE_SPLASH );
464 }
465 continue;
466 }
467
468 if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR)
469 || (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_WAMPA) )
470 {//can't be one being held
471 continue;
472 }
473
474 VectorSubtract( radiusEnts[i]->currentOrigin, trace.endpos, entDir );
475 dist = VectorNormalize( entDir );
476 if ( dist <= radius )
477 {
478 if ( dist < halfRad )
479 {//close enough to do damage, too
480 G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, Q_irand( 20, 30 ), DAMAGE_NO_KNOCKBACK, MOD_EXPLOSIVE_SPLASH );
481 }
482 if ( radiusEnts[i]->client
483 && radiusEnts[i]->client->NPC_class != CLASS_RANCOR
484 && radiusEnts[i]->client->NPC_class != CLASS_ATST )
485 {
486 float throwStr = 0.0f;
487 if ( g_spskill->integer > 1 )
488 {
489 throwStr = 10.0f+((radius-dist)/2.0f);
490 if ( throwStr > 150.0f )
491 {
492 throwStr = 150.0f;
493 }
494 }
495 else
496 {
497 throwStr = 10.0f+((radius-dist)/4.0f);
498 if ( throwStr > 85.0f )
499 {
500 throwStr = 85.0f;
501 }
502 }
503 entDir[2] += 0.1f;
504 VectorNormalize( entDir );
505 G_Throw( radiusEnts[i], entDir, throwStr );
506 if ( radiusEnts[i]->health > 0 )
507 {
508 if ( dist < halfRad
509 || radiusEnts[i]->client->ps.groundEntityNum != ENTITYNUM_NONE )
510 {//within range of my fist or within ground-shaking range and not in the air
511 G_Knockdown( radiusEnts[i], NPC, vec3_origin, 500, qtrue );
512 }
513 }
514 }
515 }
516 }
517 }
518 }
519
Tavion_StartScepterBeam(void)520 void Tavion_StartScepterBeam( void )
521 {
522 G_PlayEffect( G_EffectIndex( "scepter/beam_warmup.efx" ), NPC->weaponModel[1], NPC->genericBolt1, NPC->s.number, NPC->currentOrigin, 0, qtrue );
523 G_SoundOnEnt( NPC, CHAN_ITEM, "sound/weapons/scepter/beam_warmup.wav" );
524 NPC->client->ps.legsAnimTimer = NPC->client->ps.torsoAnimTimer = 0;
525 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_SCEPTER_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
526 NPC->client->ps.torsoAnimTimer += 200;
527 NPC->painDebounceTime = level.time + NPC->client->ps.torsoAnimTimer;
528 NPC->client->ps.pm_time = NPC->client->ps.torsoAnimTimer;
529 NPC->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
530 VectorClear( NPC->client->ps.velocity );
531 VectorClear( NPC->client->ps.moveDir );
532 }
533
Tavion_StartScepterSlam(void)534 void Tavion_StartScepterSlam( void )
535 {
536 G_PlayEffect( G_EffectIndex( "scepter/slam_warmup.efx" ), NPC->weaponModel[1], NPC->genericBolt1, NPC->s.number, NPC->currentOrigin, 0, qtrue );
537 G_SoundOnEnt( NPC, CHAN_ITEM, "sound/weapons/scepter/slam_warmup.wav" );
538 NPC->client->ps.legsAnimTimer = NPC->client->ps.torsoAnimTimer = 0;
539 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TAVION_SCEPTERGROUND, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
540 NPC->painDebounceTime = level.time + NPC->client->ps.torsoAnimTimer;
541 NPC->client->ps.pm_time = NPC->client->ps.torsoAnimTimer;
542 NPC->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
543 VectorClear( NPC->client->ps.velocity );
544 VectorClear( NPC->client->ps.moveDir );
545 NPC->count = 0;
546 }
547
Tavion_SithSwordRecharge(void)548 void Tavion_SithSwordRecharge( void )
549 {
550 if ( NPC->client->ps.torsoAnim != BOTH_TAVION_SWORDPOWER
551 && NPC->count
552 && TIMER_Done( NPC, "rechargeDebounce" )
553 && NPC->weaponModel[0] != -1 )
554 {
555 NPC->s.loopSound = G_SoundIndex( "sound/weapons/scepter/recharge.wav" );
556 int boltIndex = gi.G2API_AddBolt(&NPC->ghoul2[NPC->weaponModel[0]], "*weapon");
557 NPC->client->ps.legsAnimTimer = NPC->client->ps.torsoAnimTimer = 0;
558 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TAVION_SWORDPOWER, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
559 G_PlayEffect( G_EffectIndex( "scepter/recharge.efx" ), NPC->weaponModel[0], boltIndex, NPC->s.number, NPC->currentOrigin, NPC->client->ps.torsoAnimTimer, qtrue );
560 NPC->painDebounceTime = level.time + NPC->client->ps.torsoAnimTimer;
561 NPC->client->ps.pm_time = NPC->client->ps.torsoAnimTimer;
562 NPC->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
563 VectorClear( NPC->client->ps.velocity );
564 VectorClear( NPC->client->ps.moveDir );
565 NPC->client->ps.powerups[PW_INVINCIBLE] = level.time + NPC->client->ps.torsoAnimTimer + 10000;
566 G_PlayEffect( G_EffectIndex( "scepter/invincibility.efx" ), NPC->playerModel, 0, NPC->s.number, NPC->currentOrigin, NPC->client->ps.torsoAnimTimer + 10000, qfalse );
567 TIMER_Set( NPC, "rechargeDebounce", NPC->client->ps.torsoAnimTimer + 10000 + Q_irand(10000,20000) );
568 NPC->count--;
569 //now you have a chance of killing her
570 NPC->flags &= ~FL_UNDYING;
571 }
572 }
573
574 //======================================================================================
575 //END TAVION BOSS
576 //======================================================================================
577
Jedi_Cloak(gentity_t * self)578 void Jedi_Cloak( gentity_t *self )
579 {
580 if ( self && self->client )
581 {
582 if ( !self->client->ps.powerups[PW_CLOAKED] )
583 {//cloak
584 self->client->ps.powerups[PW_CLOAKED] = Q3_INFINITE;
585 self->client->ps.powerups[PW_UNCLOAKING] = level.time + 2000;
586 //FIXME: debounce attacks?
587 //FIXME: temp sound
588 G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/shadowtrooper/cloak.wav" );
589 }
590 }
591 }
592
Jedi_Decloak(gentity_t * self)593 void Jedi_Decloak( gentity_t *self )
594 {
595 if ( self && self->client )
596 {
597 if ( self->client->ps.powerups[PW_CLOAKED] )
598 {//Uncloak
599 self->client->ps.powerups[PW_CLOAKED] = 0;
600 self->client->ps.powerups[PW_UNCLOAKING] = level.time + 2000;
601 //FIXME: temp sound
602 G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/shadowtrooper/decloak.wav" );
603 }
604 }
605 }
606
Jedi_CheckCloak(void)607 void Jedi_CheckCloak( void )
608 {
609 if ( NPC
610 && NPC->client
611 && NPC->client->NPC_class == CLASS_SHADOWTROOPER
612 && Q_stricmpn("shadowtrooper", NPC->NPC_type, 13 ) == 0 )
613 {
614 if ( NPC->client->ps.SaberActive() ||
615 NPC->health <= 0 ||
616 NPC->client->ps.saberInFlight ||
617 (NPC->client->ps.eFlags&EF_FORCE_GRIPPED) ||
618 (NPC->client->ps.eFlags&EF_FORCE_DRAINED) ||
619 NPC->painDebounceTime > level.time )
620 {//can't be cloaked if saber is on, or dead or saber in flight or taking pain or being gripped
621 Jedi_Decloak( NPC );
622 }
623 else if ( NPC->health > 0
624 && !NPC->client->ps.saberInFlight
625 && !(NPC->client->ps.eFlags&EF_FORCE_GRIPPED)
626 && !(NPC->client->ps.eFlags&EF_FORCE_DRAINED)
627 && NPC->painDebounceTime < level.time )
628 {//still alive, have saber in hand, not taking pain and not being gripped
629 Jedi_Cloak( NPC );
630 }
631 }
632 }
633 /*
634 ==========================================================================================
635 AGGRESSION
636 ==========================================================================================
637 */
Jedi_Aggression(gentity_t * self,int change)638 static void Jedi_Aggression( gentity_t *self, int change )
639 {
640 int upper_threshold, lower_threshold;
641
642 self->NPC->stats.aggression += change;
643
644 //FIXME: base this on initial NPC stats
645 if ( self->client->playerTeam == TEAM_PLAYER )
646 {//good guys are less aggressive
647 upper_threshold = 7;
648 lower_threshold = 1;
649 }
650 else
651 {//bad guys are more aggressive
652 if ( self->client->NPC_class == CLASS_DESANN )
653 {
654 upper_threshold = 20;
655 lower_threshold = 5;
656 }
657 else
658 {
659 upper_threshold = 10;
660 lower_threshold = 3;
661 }
662 }
663
664 if ( self->NPC->stats.aggression > upper_threshold )
665 {
666 self->NPC->stats.aggression = upper_threshold;
667 }
668 else if ( self->NPC->stats.aggression < lower_threshold )
669 {
670 self->NPC->stats.aggression = lower_threshold;
671 }
672 //Com_Printf( "(%d) %s agg %d change: %d\n", level.time, self->NPC_type, self->NPC->stats.aggression, change );
673 }
674
Jedi_AggressionErosion(int amt)675 static void Jedi_AggressionErosion( int amt )
676 {
677 if ( TIMER_Done( NPC, "roamTime" ) )
678 {//the longer we're not alerted and have no enemy, the more our aggression goes down
679 TIMER_Set( NPC, "roamTime", Q_irand( 2000, 5000 ) );
680 Jedi_Aggression( NPC, amt );
681 }
682
683 if ( NPCInfo->stats.aggression < 4 || (NPCInfo->stats.aggression < 6&&NPC->client->NPC_class == CLASS_DESANN))
684 {//turn off the saber
685 WP_DeactivateSaber( NPC );
686 }
687 }
688
NPC_Jedi_RateNewEnemy(gentity_t * self,gentity_t * enemy)689 void NPC_Jedi_RateNewEnemy( gentity_t *self, gentity_t *enemy )
690 {
691 float healthAggression;
692 float weaponAggression;
693
694 switch( enemy->s.weapon )
695 {
696 case WP_SABER:
697 healthAggression = (float)self->health/200.0f*6.0f;
698 weaponAggression = 7;//go after him
699 break;
700 case WP_BLASTER:
701 if ( DistanceSquared( self->currentOrigin, enemy->currentOrigin ) < 65536 )//256 squared
702 {
703 healthAggression = (float)self->health/200.0f*8.0f;
704 weaponAggression = 8;//go after him
705 }
706 else
707 {
708 healthAggression = 8.0f - ((float)self->health/200.0f*8.0f);
709 weaponAggression = 2;//hang back for a second
710 }
711 break;
712 default:
713 healthAggression = (float)self->health/200.0f*8.0f;
714 weaponAggression = 6;//approach
715 break;
716 }
717 //Average these with current aggression
718 int newAggression = ceil( (healthAggression + weaponAggression + (float)self->NPC->stats.aggression )/3.0f);
719 //Com_Printf( "(%d) new agg %d - new enemy\n", level.time, newAggression );
720 Jedi_Aggression( self, newAggression - self->NPC->stats.aggression );
721
722 //don't taunt right away
723 TIMER_Set( self, "chatter", Q_irand( 4000, 7000 ) );
724 }
725
Jedi_Rage(void)726 static void Jedi_Rage( void )
727 {
728 Jedi_Aggression( NPC, 10 - NPCInfo->stats.aggression + Q_irand( -2, 2 ) );
729 TIMER_Set( NPC, "roamTime", 0 );
730 TIMER_Set( NPC, "chatter", 0 );
731 TIMER_Set( NPC, "walking", 0 );
732 TIMER_Set( NPC, "taunting", 0 );
733 TIMER_Set( NPC, "jumpChaseDebounce", 0 );
734 TIMER_Set( NPC, "movenone", 0 );
735 TIMER_Set( NPC, "movecenter", 0 );
736 TIMER_Set( NPC, "noturn", 0 );
737 ForceRage( NPC );
738 }
739
Jedi_RageStop(gentity_t * self)740 void Jedi_RageStop( gentity_t *self )
741 {
742 if ( self->NPC )
743 {//calm down and back off
744 TIMER_Set( self, "roamTime", 0 );
745 Jedi_Aggression( self, Q_irand( -5, 0 ) );
746 }
747 }
748 /*
749 ==========================================================================================
750 SPEAKING
751 ==========================================================================================
752 */
753
Jedi_BattleTaunt(void)754 static qboolean Jedi_BattleTaunt( void )
755 {
756 if ( TIMER_Done( NPC, "chatter" )
757 && !Q_irand( 0, 3 )
758 && NPCInfo->blockedSpeechDebounceTime < level.time
759 && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time )
760 {
761 int event = -1;
762 if ( NPC->enemy
763 && NPC->enemy->client
764 && (NPC->enemy->client->NPC_class == CLASS_RANCOR
765 || NPC->enemy->client->NPC_class == CLASS_WAMPA
766 || NPC->enemy->client->NPC_class == CLASS_SAND_CREATURE) )
767 {//never taunt these mindless creatures
768 //NOTE: howlers? tusken? etc? Only reborn?
769 }
770 else
771 {
772 if ( NPC->client->playerTeam == TEAM_PLAYER
773 && NPC->enemy && NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_JEDI )
774 {//a jedi fighting a jedi - training
775 if ( NPC->client->NPC_class == CLASS_JEDI && NPCInfo->rank == RANK_COMMANDER )
776 {//only trainer taunts
777 event = EV_TAUNT1;
778 }
779 }
780 else
781 {//reborn or a jedi fighting an enemy
782 event = Q_irand( EV_TAUNT1, EV_TAUNT3 );
783 }
784 if ( event != -1 )
785 {
786 G_AddVoiceEvent( NPC, event, 3000 );
787 jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 6000;
788 if ( (NPCInfo->aiFlags&NPCAI_ROSH) )
789 {
790 TIMER_Set( NPC, "chatter", Q_irand( 8000, 20000 ) );
791 }
792 else
793 {
794 TIMER_Set( NPC, "chatter", Q_irand( 5000, 10000 ) );
795 }
796
797 if ( NPC->enemy && NPC->enemy->NPC && NPC->enemy->s.weapon == WP_SABER && NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_JEDI )
798 {//Have the enemy jedi say something in response when I'm done?
799 }
800 return qtrue;
801 }
802 }
803 }
804 return qfalse;
805 }
806
807 /*
808 ==========================================================================================
809 MOVEMENT
810 ==========================================================================================
811 */
Jedi_ClearPathToSpot(vec3_t dest,int impactEntNum)812 static qboolean Jedi_ClearPathToSpot( vec3_t dest, int impactEntNum )
813 {
814 trace_t trace;
815 vec3_t mins, start, end, dir;
816 float dist, drop;
817
818 //Offset the step height
819 VectorSet( mins, NPC->mins[0], NPC->mins[1], NPC->mins[2] + STEPSIZE );
820
821 gi.trace( &trace, NPC->currentOrigin, mins, NPC->maxs, dest, NPC->s.number, NPC->clipmask, (EG2_Collision)0, 0 );
822
823 //Do a simple check
824 if ( trace.allsolid || trace.startsolid )
825 {//inside solid
826 return qfalse;
827 }
828
829 if ( trace.fraction < 1.0f )
830 {//hit something
831 if ( impactEntNum != ENTITYNUM_NONE && trace.entityNum == impactEntNum )
832 {//hit what we're going after
833 return qtrue;
834 }
835 else
836 {
837 return qfalse;
838 }
839 }
840
841 //otherwise, clear path in a straight line.
842 //Now at intervals of my size, go along the trace and trace down STEPSIZE to make sure there is a solid floor.
843 VectorSubtract( dest, NPC->currentOrigin, dir );
844 dist = VectorNormalize( dir );
845 if ( dest[2] > NPC->currentOrigin[2] )
846 {//going up, check for steps
847 drop = STEPSIZE;
848 }
849 else
850 {//going down or level, check for moderate drops
851 drop = 64;
852 }
853 for ( float i = NPC->maxs[0]*2; i < dist; i += NPC->maxs[0]*2 )
854 {//FIXME: does this check the last spot, too? We're assuming that should be okay since the enemy is there?
855 VectorMA( NPC->currentOrigin, i, dir, start );
856 VectorCopy( start, end );
857 end[2] -= drop;
858 gi.trace( &trace, start, mins, NPC->maxs, end, NPC->s.number, NPC->clipmask, (EG2_Collision)0, 0 );//NPC->mins?
859 if ( trace.fraction < 1.0f || trace.allsolid || trace.startsolid )
860 {//good to go
861 continue;
862 }
863 //no floor here! (or a long drop?)
864 return qfalse;
865 }
866 //we made it!
867 return qtrue;
868 }
869
NPC_MoveDirClear(int forwardmove,int rightmove,qboolean reset)870 qboolean NPC_MoveDirClear( int forwardmove, int rightmove, qboolean reset )
871 {
872 vec3_t forward, right, testPos, angles, mins;
873 trace_t trace;
874 float fwdDist, rtDist;
875 float bottom_max = -STEPSIZE*4 - 1;
876
877 if ( !forwardmove && !rightmove )
878 {//not even moving
879 //gi.Printf( "%d skipping walk-cliff check (not moving)\n", level.time );
880 return qtrue;
881 }
882
883 if ( ucmd.upmove > 0 || NPC->client->ps.forceJumpCharge )
884 {//Going to jump
885 //gi.Printf( "%d skipping walk-cliff check (going to jump)\n", level.time );
886 return qtrue;
887 }
888
889 if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE )
890 {//in the air
891 //gi.Printf( "%d skipping walk-cliff check (in air)\n", level.time );
892 return qtrue;
893 }
894 /*
895 if ( fabs( AngleDelta( NPC->currentAngles[YAW], NPCInfo->desiredYaw ) ) < 5.0 )//!ucmd.angles[YAW] )
896 {//Not turning much, don't do this
897 //NOTE: Should this not happen only if you're not turning AT ALL?
898 // You could be turning slowly but moving fast, so that would
899 // still let you walk right off a cliff...
900 //NOTE: Or maybe it is a good idea to ALWAYS do this, regardless
901 // of whether ot not we're turning? But why would we be walking
902 // straight into a wall or off a cliff unless we really wanted to?
903 return;
904 }
905 */
906
907 //FIXME: to really do this right, we'd have to actually do a pmove to predict where we're
908 //going to be... maybe this should be a flag and pmove handles it and sets a flag so AI knows
909 //NEXT frame? Or just incorporate current velocity, runspeed and possibly friction?
910 VectorCopy( NPC->mins, mins );
911 mins[2] += STEPSIZE;
912 angles[PITCH] = angles[ROLL] = 0;
913 angles[YAW] = NPC->client->ps.viewangles[YAW];//Add ucmd.angles[YAW]?
914 AngleVectors( angles, forward, right, NULL );
915 fwdDist = ((float)forwardmove)/2.0f;
916 rtDist = ((float)rightmove)/2.0f;
917 VectorMA( NPC->currentOrigin, fwdDist, forward, testPos );
918 VectorMA( testPos, rtDist, right, testPos );
919 gi.trace( &trace, NPC->currentOrigin, mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP, (EG2_Collision)0, 0 );
920 if ( trace.allsolid || trace.startsolid )
921 {//hmm, trace started inside this brush... how do we decide if we should continue?
922 //FIXME: what do we do if we start INSIDE a CONTENTS_BOTCLIP? Try the trace again without that in the clipmask?
923 if ( reset )
924 {
925 trace.fraction = 1.0f;
926 }
927 VectorCopy( testPos, trace.endpos );
928 //return qtrue;
929 }
930 if ( trace.fraction < 0.6 )
931 {//Going to bump into something very close, don't move, just turn
932 if ( (NPC->enemy && trace.entityNum == NPC->enemy->s.number) || (NPCInfo->goalEntity && trace.entityNum == NPCInfo->goalEntity->s.number) )
933 {//okay to bump into enemy or goal
934 //gi.Printf( "%d bump into enemy/goal okay\n", level.time );
935 return qtrue;
936 }
937 else if ( reset )
938 {//actually want to screw with the ucmd
939 //gi.Printf( "%d avoiding walk into wall (entnum %d)\n", level.time, trace.entityNum );
940 ucmd.forwardmove = 0;
941 ucmd.rightmove = 0;
942 VectorClear( NPC->client->ps.moveDir );
943 }
944 return qfalse;
945 }
946
947 if ( NPCInfo->goalEntity )
948 {
949 if ( NPCInfo->goalEntity->currentOrigin[2] < NPC->currentOrigin[2] )
950 {//goal is below me, okay to step off at least that far plus stepheight
951 bottom_max += NPCInfo->goalEntity->currentOrigin[2] - NPC->currentOrigin[2];
952 }
953 }
954 VectorCopy( trace.endpos, testPos );
955 testPos[2] += bottom_max;
956
957 gi.trace( &trace, trace.endpos, mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask, (EG2_Collision)0, 0 );
958
959 //FIXME:Should we try to see if we can still get to our goal using the waypoint network from this trace.endpos?
960 //OR: just put NPC clip brushes on these edges (still fall through when die)
961
962 if ( trace.allsolid || trace.startsolid )
963 {//Not going off a cliff
964 //gi.Printf( "%d walk off cliff okay (droptrace in solid)\n", level.time );
965 return qtrue;
966 }
967
968 if ( trace.fraction < 1.0 )
969 {//Not going off a cliff
970 //FIXME: what if plane.normal is sloped? We'll slide off, not land... plus this doesn't account for slide-movement...
971 //gi.Printf( "%d walk off cliff okay will hit entnum %d at dropdist of %4.2f\n", level.time, trace.entityNum, (trace.fraction*bottom_max) );
972 return qtrue;
973 }
974
975 //going to fall at least bottom_max, don't move, just turn... is this bad, though? What if we want them to drop off?
976 if ( reset )
977 {//actually want to screw with the ucmd
978 //gi.Printf( "%d avoiding walk off cliff\n", level.time );
979 ucmd.forwardmove *= -1.0;//= 0;
980 ucmd.rightmove *= -1.0;//= 0;
981 VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir );
982 }
983 return qfalse;
984 }
985 /*
986 -------------------------
987 Jedi_HoldPosition
988 -------------------------
989 */
990
Jedi_HoldPosition(void)991 static void Jedi_HoldPosition( void )
992 {
993 //NPCInfo->squadState = SQUAD_STAND_AND_SHOOT;
994 NPCInfo->goalEntity = NULL;
995
996 /*
997 if ( TIMER_Done( NPC, "stand" ) )
998 {
999 TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) );
1000 }
1001 */
1002 }
1003
1004 /*
1005 -------------------------
1006 Jedi_Move
1007 -------------------------
1008 */
1009
Jedi_Move(gentity_t * goal,qboolean retreat)1010 static qboolean Jedi_Move( gentity_t *goal, qboolean retreat )
1011 {
1012 NPCInfo->combatMove = qtrue;
1013 NPCInfo->goalEntity = goal;
1014
1015 qboolean moved = NPC_MoveToGoal( qtrue );
1016 if (!moved)
1017 {
1018 Jedi_HoldPosition();
1019 }
1020
1021 // NAV_TODO: Put Retreate Behavior Here
1022 //FIXME: temp retreat behavior- should really make this toward a safe spot or maybe to outflank enemy
1023 if ( retreat )
1024 {//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?
1025 ucmd.forwardmove *= -1;
1026 ucmd.rightmove *= -1;
1027 //we clear moveDir here so the Jedi's ucmd-driven movement does do not enter checks
1028 VectorClear( NPC->client->ps.moveDir );
1029 //VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir );
1030 }
1031 return moved;
1032 }
1033
Jedi_Hunt(void)1034 static qboolean Jedi_Hunt( void )
1035 {
1036 //gi.Printf( "Hunting\n" );
1037 //if we're at all interested in fighting, go after him
1038 if ( NPCInfo->stats.aggression > 1 )
1039 {//approach enemy
1040 NPCInfo->combatMove = qtrue;
1041 if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
1042 {
1043 NPC_UpdateAngles( qtrue, qtrue );
1044 return qtrue;
1045 }
1046 else
1047 {
1048 /* if ( NPCInfo->goalEntity == NULL )
1049 {//hunt
1050 NPCInfo->goalEntity = NPC->enemy;
1051 }
1052 if (NPC->client && NPC->client->NPC_class==CLASS_BOBAFETT)
1053 {
1054 NPCInfo->goalEntity = NPC->enemy;
1055 }*/
1056 // NPC_SetMoveGoal(NPC, NPC->enemy->currentOrigin, 40.0f, false, 0, NPC->enemy);
1057 NPCInfo->goalEntity = NPC->enemy;
1058 NPCInfo->goalRadius = 40.0f;
1059
1060 //Jedi_Move( NPC->enemy, qfalse );
1061 if ( NPC_MoveToGoal( qfalse ) )
1062 {
1063 NPC_UpdateAngles( qtrue, qtrue );
1064 return qtrue;
1065 }
1066 }
1067 }
1068 return qfalse;
1069 }
1070
1071 /*
1072 static qboolean Jedi_Track( void )
1073 {
1074 //if we're at all interested in fighting, go after him
1075 if ( NPCInfo->stats.aggression > 1 )
1076 {//approach enemy
1077 NPCInfo->combatMove = qtrue;
1078 NPC_SetMoveGoal( NPC, NPCInfo->enemyLastSeenLocation, 16, qtrue );
1079 if ( NPC_MoveToGoal( qfalse ) )
1080 {
1081 NPC_UpdateAngles( qtrue, qtrue );
1082 return qtrue;
1083 }
1084 }
1085 return qfalse;
1086 }
1087 */
1088
Jedi_StartBackOff(void)1089 static void Jedi_StartBackOff( void )
1090 {
1091 TIMER_Set( NPC, "roamTime", -level.time );
1092 TIMER_Set( NPC, "strafeLeft", -level.time );
1093 TIMER_Set( NPC, "strafeRight", -level.time );
1094 TIMER_Set( NPC, "walking", -level.time );
1095 TIMER_Set( NPC, "moveforward", -level.time );
1096 TIMER_Set( NPC, "movenone", -level.time );
1097 TIMER_Set( NPC, "moveright", -level.time );
1098 TIMER_Set( NPC, "moveleft", -level.time );
1099 TIMER_Set( NPC, "movecenter", -level.time );
1100 TIMER_Set( NPC, "moveback", 1000 );
1101 ucmd.forwardmove = -127;
1102 ucmd.rightmove = 0;
1103 ucmd.upmove = 0;
1104 if ( d_JediAI->integer )
1105 {
1106 Com_Printf( "%s backing off from spin attack!\n", NPC->NPC_type );
1107 }
1108 TIMER_Set( NPC, "specialEvasion", 1000 );
1109 TIMER_Set( NPC, "noRetreat", -level.time );
1110 if ( PM_PainAnim(NPC->client->ps.legsAnim) )
1111 {
1112 NPC->client->ps.legsAnimTimer = 0;
1113 }
1114 VectorClear( NPC->client->ps.moveDir );
1115 }
1116
Jedi_Retreat(void)1117 static qboolean Jedi_Retreat( void )
1118 {
1119 if ( !TIMER_Done( NPC, "noRetreat" ) )
1120 {//don't actually move
1121 return qfalse;
1122 }
1123 //FIXME: when retreating, we should probably see if we can retreat
1124 //in the direction we want. If not...? Evade?
1125 //gi.Printf( "Retreating\n" );
1126 return Jedi_Move( NPC->enemy, qtrue );
1127 }
1128
Jedi_Advance(void)1129 static qboolean Jedi_Advance( void )
1130 {
1131 if ( (NPCInfo->aiFlags&NPCAI_HEAL_ROSH) )
1132 {
1133 return qfalse;
1134 }
1135 if ( !NPC->client->ps.saberInFlight )
1136 {
1137 NPC->client->ps.SaberActivate();
1138 }
1139 //gi.Printf( "Advancing\n" );
1140 return Jedi_Move( NPC->enemy, qfalse );
1141
1142 //TIMER_Set( NPC, "roamTime", Q_irand( 2000, 4000 ) );
1143 //TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) );
1144 //TIMER_Set( NPC, "duck", 0 );
1145 }
1146
Jedi_AdjustSaberAnimLevel(gentity_t * self,int newLevel)1147 static void Jedi_AdjustSaberAnimLevel( gentity_t *self, int newLevel )
1148 {
1149 if ( !self || !self->client )
1150 {
1151 return;
1152 }
1153 //FIXME: each NPC shold have a unique pattern of behavior for the order in which they
1154 if ( self->client->playerTeam == TEAM_ENEMY )
1155 {
1156 //FIXME: CLASS_CULTIST + self->NPC->rank instead of these Q_stricmps?
1157 if ( !Q_stricmp( "cultist_saber_all", self->NPC_type )
1158 || !Q_stricmp( "cultist_saber_all_throw", self->NPC_type ) )
1159 {//use any, regardless of rank, etc.
1160 }
1161 else if ( !Q_stricmp( "cultist_saber", self->NPC_type )
1162 || !Q_stricmp( "cultist_saber_throw", self->NPC_type ) )
1163 {//fast only
1164 self->client->ps.saberAnimLevel = SS_FAST;
1165 }
1166 else if ( !Q_stricmp( "cultist_saber_med", self->NPC_type )
1167 || !Q_stricmp( "cultist_saber_med_throw", self->NPC_type ) )
1168 {//med only
1169 self->client->ps.saberAnimLevel = SS_MEDIUM;
1170 }
1171 else if ( !Q_stricmp( "cultist_saber_strong", self->NPC_type )
1172 || !Q_stricmp( "cultist_saber_strong_throw", self->NPC_type ) )
1173 {//strong only
1174 self->client->ps.saberAnimLevel = SS_STRONG;
1175 }
1176 else
1177 {//regular reborn
1178 if ( self->NPC->rank == RANK_CIVILIAN || self->NPC->rank == RANK_LT_JG )
1179 {//grunt and fencer always uses quick attacks
1180 self->client->ps.saberAnimLevel = SS_FAST;
1181 return;
1182 }
1183 if ( self->NPC->rank == RANK_CREWMAN
1184 || self->NPC->rank == RANK_ENSIGN )
1185 {//acrobat & force-users always use medium attacks
1186 self->client->ps.saberAnimLevel = SS_MEDIUM;
1187 return;
1188 }
1189 /*
1190 if ( self->NPC->rank == RANK_LT )
1191 {//boss always uses strong attacks
1192 self->client->ps.saberAnimLevel = SS_STRONG;
1193 return;
1194 }
1195 */
1196 }
1197 }
1198 if ( newLevel < SS_FAST )
1199 {
1200 newLevel = SS_FAST;
1201 }
1202 else if ( newLevel > SS_STAFF )
1203 {
1204 newLevel = SS_STAFF;
1205 }
1206 //use the different attacks, how often they switch and under what circumstances
1207 if ( !(self->client->ps.saberStylesKnown&(1<<newLevel)) )
1208 {//don't know that style, sorry
1209 return;
1210 }
1211 else
1212 {//go ahead and set it
1213 self->client->ps.saberAnimLevel = newLevel;
1214 }
1215
1216 if ( d_JediAI->integer )
1217 {
1218 switch ( self->client->ps.saberAnimLevel )
1219 {
1220 case SS_FAST:
1221 gi.Printf( S_COLOR_GREEN"%s Saber Attack Set: fast\n", self->NPC_type );
1222 break;
1223 case SS_MEDIUM:
1224 gi.Printf( S_COLOR_YELLOW"%s Saber Attack Set: medium\n", self->NPC_type );
1225 break;
1226 case SS_STRONG:
1227 gi.Printf( S_COLOR_RED"%s Saber Attack Set: strong\n", self->NPC_type );
1228 break;
1229 }
1230 }
1231 }
1232
Jedi_CheckDecreaseSaberAnimLevel(void)1233 static void Jedi_CheckDecreaseSaberAnimLevel( void )
1234 {
1235 if ( !NPC->client->ps.weaponTime && !(ucmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS)) )
1236 {//not attacking
1237 if ( TIMER_Done( NPC, "saberLevelDebounce" ) && !Q_irand( 0, 10 ) )
1238 {
1239 //Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.saberAnimLevel-1) );//drop
1240 Jedi_AdjustSaberAnimLevel( NPC, Q_irand( SS_FAST, SS_STRONG ));//random
1241 TIMER_Set( NPC, "saberLevelDebounce", Q_irand( 3000, 10000 ) );
1242 }
1243 }
1244 else
1245 {
1246 TIMER_Set( NPC, "saberLevelDebounce", Q_irand( 1000, 5000 ) );
1247 }
1248 }
1249
Jedi_DecideKick(void)1250 static qboolean Jedi_DecideKick( void )
1251 {
1252 if ( PM_InKnockDown( &NPC->client->ps ) )
1253 {
1254 return qfalse;
1255 }
1256 if ( PM_InRoll( &NPC->client->ps ) )
1257 {
1258 return qfalse;
1259 }
1260 if ( PM_InGetUp( &NPC->client->ps ) )
1261 {
1262 return qfalse;
1263 }
1264 if ( !NPC->enemy || (NPC->enemy->s.number < MAX_CLIENTS&&NPC->enemy->health<=0) )
1265 {//have no enemy or enemy is a dead player
1266 return qfalse;
1267 }
1268 //FIXME: check FP_SABER_OFFENSE?
1269 //FIXME: check for saber staff style only?
1270 //FIXME: g_spskill?
1271 if ( Q_irand( 0, RANK_CAPTAIN+5 ) > NPCInfo->rank )
1272 {//low chance, based on rank
1273 return qfalse;
1274 }
1275 if ( Q_irand( 0, 10 ) > NPCInfo->stats.aggression )
1276 {//the madder the better
1277 return qfalse;
1278 }
1279 if ( !TIMER_Done( NPC, "kickDebounce" ) )
1280 {//just did one
1281 return qfalse;
1282 }
1283 if ( NPC->client->ps.weapon == WP_SABER )
1284 {
1285 if ( (NPC->client->ps.saber[0].saberFlags&SFL_NO_KICKS) )
1286 {
1287 return qfalse;
1288 }
1289 else if ( NPC->client->ps.dualSabers
1290 && (NPC->client->ps.saber[1].saberFlags&SFL_NO_KICKS) )
1291 {
1292 return qfalse;
1293 }
1294 }
1295 //go for it!
1296 return qtrue;
1297 }
1298
Kyle_GrabEnemy(void)1299 void Kyle_GrabEnemy( void )
1300 {
1301 WP_SabersCheckLock2( NPC, NPC->enemy, (sabersLockMode_t)Q_irand(LOCK_KYLE_GRAB1,LOCK_KYLE_GRAB2) );//LOCK_KYLE_GRAB3
1302 TIMER_Set( NPC, "grabEnemyDebounce", NPC->client->ps.torsoAnimTimer + Q_irand( 4000, 20000 ) );
1303 }
1304
Kyle_TryGrab(void)1305 void Kyle_TryGrab( void )
1306 {
1307 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_KYLE_GRAB, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
1308 NPC->client->ps.torsoAnimTimer += 200;
1309 NPC->client->ps.weaponTime = NPC->client->ps.torsoAnimTimer;
1310 NPC->client->ps.saberMove = NPC->client->ps.saberMoveNext = LS_READY;
1311 VectorClear( NPC->client->ps.velocity );
1312 VectorClear( NPC->client->ps.moveDir );
1313 ucmd.rightmove = ucmd.forwardmove = ucmd.upmove = 0;
1314 NPC->painDebounceTime = level.time + NPC->client->ps.torsoAnimTimer;
1315 //WTF?
1316 NPC->client->ps.SaberDeactivate();
1317 }
1318
Kyle_CanDoGrab(void)1319 qboolean Kyle_CanDoGrab( void )
1320 {
1321 if ( NPC->client->NPC_class == CLASS_KYLE && (NPC->spawnflags&1) )
1322 {//Boss Kyle
1323 if ( NPC->enemy && NPC->enemy->client )
1324 {//have a valid enemy
1325 if ( TIMER_Done( NPC, "grabEnemyDebounce" ) )
1326 {//okay to grab again
1327 if ( NPC->client->ps.groundEntityNum != ENTITYNUM_NONE
1328 && NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
1329 {//me and enemy are on ground
1330 if ( !PM_InOnGroundAnim( &NPC->enemy->client->ps ) )
1331 {
1332 if ( (NPC->client->ps.weaponTime <= 200||NPC->client->ps.torsoAnim==BOTH_KYLE_GRAB)
1333 && !NPC->client->ps.saberInFlight )
1334 {
1335 if ( fabs(NPC->enemy->currentOrigin[2]-NPC->currentOrigin[2])<=8.0f )
1336 {//close to same level of ground
1337 if ( DistanceSquared( NPC->enemy->currentOrigin, NPC->currentOrigin ) <= 10000.0f )
1338 {
1339 return qtrue;
1340 }
1341 }
1342 }
1343 }
1344 }
1345 }
1346 }
1347 }
1348 return qfalse;
1349 }
1350
Jedi_CombatDistance(int enemy_dist)1351 static void Jedi_CombatDistance( int enemy_dist )
1352 {//FIXME: for many of these checks, what we really want is horizontal distance to enemy
1353 if ( Jedi_CultistDestroyer( NPC ) )
1354 {//destroyer
1355 Jedi_Advance();
1356 //always run, regardless of what navigation tells us to do!
1357 NPC->client->ps.speed = NPCInfo->stats.runSpeed;
1358 ucmd.buttons &= ~BUTTON_WALKING;
1359 return;
1360 }
1361 if ( enemy_dist < 128
1362 && NPC->enemy
1363 && NPC->enemy->client
1364 && (NPC->enemy->client->ps.torsoAnim == BOTH_SPINATTACK6
1365 || NPC->enemy->client->ps.torsoAnim == BOTH_SPINATTACK7 ) )
1366 {//whoa, back off!!!
1367 if ( Q_irand( -3, NPCInfo->rank ) > RANK_CREWMAN )
1368 {
1369 Jedi_StartBackOff();
1370 return;
1371 }
1372 }
1373 if ( NPC->client->ps.forcePowersActive&(1<<FP_GRIP) &&
1374 NPC->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
1375 {//when gripping, don't move
1376 return;
1377 }
1378 else if ( !TIMER_Done( NPC, "gripping" ) )
1379 {//stopped gripping, clear timers just in case
1380 TIMER_Set( NPC, "gripping", -level.time );
1381 TIMER_Set( NPC, "attackDelay", Q_irand( 0, 1000 ) );
1382 }
1383
1384 if ( NPC->client->ps.forcePowersActive&(1<<FP_DRAIN) &&
1385 NPC->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_1 )
1386 {//when draining, don't move
1387 return;
1388 }
1389 else if ( !TIMER_Done( NPC, "draining" ) )
1390 {//stopped draining, clear timers just in case
1391 TIMER_Set( NPC, "draining", -level.time );
1392 TIMER_Set( NPC, "attackDelay", Q_irand( 0, 1000 ) );
1393 }
1394
1395 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
1396 {
1397 if ( !TIMER_Done( NPC, "flameTime" ) )
1398 {
1399 if ( enemy_dist > 50 )
1400 {
1401 Jedi_Advance();
1402 }
1403 else if ( enemy_dist <= 0 )
1404 {
1405 Jedi_Retreat();
1406 }
1407 }
1408 else if ( enemy_dist < 200 )
1409 {
1410 Jedi_Retreat();
1411 }
1412 else if ( enemy_dist > 1024 )
1413 {
1414 Jedi_Advance();
1415 }
1416 }
1417 else if ( NPC->client->ps.legsAnim == BOTH_ALORA_SPIN_THROW )
1418 {//don't move at all
1419 //FIXME: sabers need trails
1420 }
1421 else if ( NPC->client->ps.torsoAnim == BOTH_KYLE_GRAB )
1422 {//see if we grabbed enemy
1423 if ( NPC->client->ps.torsoAnimTimer <= 200 )
1424 {
1425 if ( Kyle_CanDoGrab()
1426 && NPC_EnemyRangeFromBolt( NPC->handRBolt ) <= 72.0f )
1427 {//grab him!
1428 Kyle_GrabEnemy();
1429 return;
1430 }
1431 else
1432 {
1433 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
1434 NPC->client->ps.weaponTime = NPC->client->ps.torsoAnimTimer;
1435 return;
1436 }
1437 }
1438 //else just sit here?
1439 return;
1440 }
1441 else if ( NPC->client->ps.saberInFlight &&
1442 !PM_SaberInBrokenParry( NPC->client->ps.saberMove )
1443 && NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
1444 {//maintain distance
1445 if ( enemy_dist < NPC->client->ps.saberEntityDist )
1446 {
1447 Jedi_Retreat();
1448 }
1449 else if ( enemy_dist > NPC->client->ps.saberEntityDist && enemy_dist > 100 )
1450 {
1451 Jedi_Advance();
1452 }
1453 if ( NPC->client->ps.weapon == WP_SABER //using saber
1454 && NPC->client->ps.saberEntityState == SES_LEAVING //not returning yet
1455 && NPC->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_1 //2nd or 3rd level lightsaber
1456 && !(NPC->client->ps.forcePowersActive&(1 << FP_SPEED))
1457 && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water
1458 {//hold it out there
1459 ucmd.buttons |= BUTTON_ALT_ATTACK;
1460 //FIXME: time limit?
1461 }
1462 }
1463 else if ( !TIMER_Done( NPC, "taunting" ) )
1464 {
1465 if ( enemy_dist <= 64 )
1466 {//he's getting too close
1467 ucmd.buttons |= BUTTON_ATTACK;
1468 if ( !NPC->client->ps.saberInFlight )
1469 {
1470 NPC->client->ps.SaberActivate();
1471 }
1472 TIMER_Set( NPC, "taunting", -level.time );
1473 }
1474 else if ( NPC->client->ps.torsoAnim == BOTH_GESTURE1 && NPC->client->ps.torsoAnimTimer < 2000 )
1475 {//we're almost done with our special taunt
1476 //FIXME: this doesn't always work, for some reason
1477 if ( !NPC->client->ps.saberInFlight )
1478 {
1479 NPC->client->ps.SaberActivate();
1480 }
1481 }
1482 }
1483 else if ( NPC->client->ps.saberEventFlags&SEF_LOCK_WON )
1484 {//we won a saber lock, press the advantage
1485 if ( enemy_dist > 0 )
1486 {//get closer so we can hit!
1487 Jedi_Advance();
1488 }
1489 if ( enemy_dist > 128 )
1490 {//lost 'em
1491 NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON;
1492 }
1493 if ( NPC->enemy->painDebounceTime + 2000 < level.time )
1494 {//the window of opportunity is gone
1495 NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON;
1496 }
1497 //don't strafe?
1498 TIMER_Set( NPC, "strafeLeft", -1 );
1499 TIMER_Set( NPC, "strafeRight", -1 );
1500 }
1501 else if ( NPC->enemy->client
1502 && NPC->enemy->s.weapon == WP_SABER
1503 && NPC->enemy->client->ps.saberLockTime > level.time
1504 && NPC->client->ps.saberLockTime < level.time )
1505 {//enemy is in a saberLock and we are not
1506 if ( enemy_dist < 64 )
1507 {//FIXME: maybe just pick another enemy?
1508 Jedi_Retreat();
1509 }
1510 }
1511 else if ( NPC->enemy->s.weapon == WP_TURRET
1512 && !Q_stricmp( "PAS", NPC->enemy->classname )
1513 && NPC->enemy->s.apos.trType == TR_STATIONARY )
1514 {
1515 if ( enemy_dist > forcePushPullRadius[FORCE_LEVEL_1] - 16 )
1516 {
1517 Jedi_Advance();
1518 }
1519 int testlevel;
1520 if ( NPC->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_1 )
1521 {//
1522 testlevel = FORCE_LEVEL_1;
1523 }
1524 else
1525 {
1526 testlevel = NPC->client->ps.forcePowerLevel[FP_PUSH];
1527 }
1528 if ( enemy_dist < forcePushPullRadius[testlevel] - 16 )
1529 {//close enough to push
1530 if ( InFront( NPC->enemy->currentOrigin, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, 0.6f ) )
1531 {//knock it down
1532 WP_KnockdownTurret( NPC, NPC->enemy );
1533 //do the forcethrow call just for effect
1534 ForceThrow( NPC, qfalse );
1535 }
1536 }
1537 }
1538 else if ( enemy_dist <= 64
1539 && ((NPCInfo->scriptFlags&SCF_DONT_FIRE)||(!Q_stricmp("Yoda",NPC->NPC_type)&&!Q_irand(0,10))) )
1540 {//can't use saber and they're in striking range
1541 if ( !Q_irand( 0, 5 ) && InFront( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.2f ) )
1542 {
1543 if ( ((NPCInfo->scriptFlags&SCF_DONT_FIRE)||NPC->max_health - NPC->health > NPC->max_health*0.25f)//lost over 1/4 of our health or not firing
1544 && WP_ForcePowerUsable( NPC, FP_DRAIN, 20 )//know how to drain and have enough power
1545 && !Q_irand( 0, 2 ) )
1546 {//drain
1547 TIMER_Set( NPC, "draining", 3000 );
1548 TIMER_Set( NPC, "attackDelay", 3000 );
1549 Jedi_Advance();
1550 return;
1551 }
1552 else
1553 {
1554 if ( Jedi_DecideKick() )
1555 {//let's try a kick
1556 if ( G_PickAutoMultiKick( NPC, qfalse, qtrue ) != LS_NONE
1557 || (G_CanKickEntity(NPC, NPC->enemy ) && G_PickAutoKick( NPC, NPC->enemy, qtrue ) != LS_NONE ) )
1558 {//kicked!
1559 TIMER_Set( NPC, "kickDebounce", Q_irand( 3000, 10000 ) );
1560 return;
1561 }
1562 }
1563 ForceThrow( NPC, qfalse );
1564 }
1565 }
1566 Jedi_Retreat();
1567 }
1568 else if ( enemy_dist <= 64
1569 && NPC->max_health - NPC->health > NPC->max_health*0.25f//lost over 1/4 of our health
1570 && NPC->client->ps.forcePowersKnown&(1<<FP_DRAIN) //know how to drain
1571 && WP_ForcePowerAvailable( NPC, FP_DRAIN, 20 )//have enough power
1572 && !Q_irand( 0, 10 )
1573 && InFront( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.2f ) )
1574 {
1575 TIMER_Set( NPC, "draining", 3000 );
1576 TIMER_Set( NPC, "attackDelay", 3000 );
1577 Jedi_Advance();
1578 return;
1579 }
1580 else if ( enemy_dist <= -16 )
1581 {//we're too damn close!
1582 if ( !Q_irand( 0, 30 )
1583 && Kyle_CanDoGrab() )
1584 {
1585 Kyle_TryGrab();
1586 return;
1587 }
1588 else if ( NPC->client->ps.stats[STAT_WEAPONS]&(1<<WP_SCEPTER)
1589 && !Q_irand( 0, 20 ) )
1590 {
1591 Tavion_StartScepterSlam();
1592 return;
1593 }
1594 if ( Jedi_DecideKick() )
1595 {//let's try a kick
1596 if ( G_PickAutoMultiKick( NPC, qfalse, qtrue ) != LS_NONE
1597 || (G_CanKickEntity(NPC, NPC->enemy ) && G_PickAutoKick( NPC, NPC->enemy, qtrue ) != LS_NONE ) )
1598 {//kicked!
1599 TIMER_Set( NPC, "kickDebounce", Q_irand( 3000, 10000 ) );
1600 return;
1601 }
1602 }
1603 Jedi_Retreat();
1604 }
1605 else if ( enemy_dist <= 0 )
1606 {//we're within striking range
1607 //if we are attacking, see if we should stop
1608 if ( NPCInfo->stats.aggression < 4 )
1609 {//back off and defend
1610 if ( !Q_irand( 0, 30 )
1611 && Kyle_CanDoGrab() )
1612 {
1613 Kyle_TryGrab();
1614 return;
1615 }
1616 else if ( NPC->client->ps.stats[STAT_WEAPONS]&(1<<WP_SCEPTER)
1617 && !Q_irand( 0, 20 ) )
1618 {
1619 Tavion_StartScepterSlam();
1620 return;
1621 }
1622 if ( Jedi_DecideKick() )
1623 {//let's try a kick
1624 if ( G_PickAutoMultiKick( NPC, qfalse, qtrue ) != LS_NONE
1625 || (G_CanKickEntity(NPC, NPC->enemy ) && G_PickAutoKick( NPC, NPC->enemy, qtrue ) != LS_NONE ) )
1626 {//kicked!
1627 TIMER_Set( NPC, "kickDebounce", Q_irand( 3000, 10000 ) );
1628 return;
1629 }
1630 }
1631 Jedi_Retreat();
1632 }
1633 }
1634 else if ( enemy_dist > 256 )
1635 {//we're way out of range
1636 qboolean usedForce = qfalse;
1637 if ( NPCInfo->stats.aggression < Q_irand( 0, 20 )
1638 && NPC->health < NPC->max_health*0.75f
1639 && !Q_irand( 0, 2 ) )
1640 {
1641 if ( NPC->enemy
1642 && NPC->enemy->s.number < MAX_CLIENTS
1643 && NPC->client->NPC_class!=CLASS_KYLE
1644 && ((NPCInfo->aiFlags&NPCAI_BOSS_CHARACTER)
1645 || NPC->client->NPC_class==CLASS_SHADOWTROOPER)
1646 && Q_irand(0, 3-g_spskill->integer) )
1647 {//hmm, bosses should do this less against the player
1648 }
1649 else if ( NPC->client->ps.saber[0].type == SABER_SITH_SWORD
1650 && NPC->weaponModel[0] != -1 )
1651 {
1652 Tavion_SithSwordRecharge();
1653 usedForce = qtrue;
1654 }
1655 else if ( (NPC->client->ps.forcePowersKnown&(1<<FP_HEAL)) != 0
1656 && (NPC->client->ps.forcePowersActive&(1<<FP_HEAL)) == 0
1657 && Q_irand( 0, 1 ) )
1658 {
1659 ForceHeal( NPC );
1660 usedForce = qtrue;
1661 //FIXME: check level of heal and know not to move or attack when healing
1662 }
1663 else if ( (NPC->client->ps.forcePowersKnown&(1<<FP_PROTECT)) != 0
1664 && (NPC->client->ps.forcePowersActive&(1<<FP_PROTECT)) == 0
1665 && Q_irand( 0, 1 ) )
1666 {
1667 ForceProtect( NPC );
1668 usedForce = qtrue;
1669 }
1670 else if ( (NPC->client->ps.forcePowersKnown&(1<<FP_ABSORB)) != 0
1671 && (NPC->client->ps.forcePowersActive&(1<<FP_ABSORB)) == 0
1672 && Q_irand( 0, 1 ) )
1673 {
1674 ForceAbsorb( NPC );
1675 usedForce = qtrue;
1676 }
1677 else if ( (NPC->client->ps.forcePowersKnown&(1<<FP_RAGE)) != 0
1678 && (NPC->client->ps.forcePowersActive&(1<<FP_RAGE)) == 0
1679 && Q_irand( 0, 1 ) )
1680 {
1681 Jedi_Rage();
1682 usedForce = qtrue;
1683 }
1684 //FIXME: what about things like mind tricks and force sight?
1685 }
1686 if ( enemy_dist > 384 )
1687 {//FIXME: check for enemy facing away and/or moving away
1688 if ( !Q_irand( 0, 10 ) && NPCInfo->blockedSpeechDebounceTime < level.time && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time )
1689 {
1690 if ( NPC_ClearLOS( NPC->enemy ) )
1691 {
1692 G_AddVoiceEvent( NPC, Q_irand( EV_JCHASE1, EV_JCHASE3 ), 3000 );
1693 }
1694 jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000;
1695 }
1696 }
1697 //Unless we're totally hiding, go after him
1698 if ( NPCInfo->stats.aggression > 0 )
1699 {//approach enemy
1700 if ( !usedForce )
1701 {
1702 if ( NPC->enemy
1703 && NPC->enemy->client
1704 && (NPC->enemy->client->ps.torsoAnim == BOTH_SPINATTACK6
1705 || NPC->enemy->client->ps.torsoAnim == BOTH_SPINATTACK7 ) )
1706 {//stay put!
1707 }
1708 else
1709 {
1710 Jedi_Advance();
1711 }
1712 }
1713 }
1714 }
1715 /*
1716 else if ( enemy_dist < 96 && NPC->enemy && NPC->enemy->client && NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE )
1717 {//too close and in air, so retreat
1718 Jedi_Retreat();
1719 }
1720 */
1721 //FIXME: enemy_dist calc needs to include all blade lengths, and include distance from hand to start of blade....
1722 else if ( enemy_dist > 50 )//FIXME: not hardcoded- base on our reach (modelScale?) and saberLengthMax
1723 {//we're out of striking range and we are allowed to attack
1724 //first, check some tactical force power decisions
1725 if ( NPC->enemy && NPC->enemy->client && (NPC->enemy->client->ps.eFlags&EF_FORCE_GRIPPED) )
1726 {//They're being gripped, rush them!
1727 if ( NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
1728 {//they're on the ground, so advance
1729 if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT )
1730 {//not parrying
1731 if ( enemy_dist > 200 || !(NPCInfo->scriptFlags&SCF_DONT_FIRE) )
1732 {//far away or allowed to use saber
1733 Jedi_Advance();
1734 }
1735 }
1736 }
1737 if ( (NPCInfo->rank >= RANK_LT_JG||WP_ForcePowerUsable( NPC, FP_SABERTHROW, 0 ))
1738 && !Q_irand( 0, 5 )
1739 && !(NPC->client->ps.forcePowersActive&(1 << FP_SPEED))
1740 && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water
1741 {//throw saber
1742 ucmd.buttons |= BUTTON_ALT_ATTACK;
1743 }
1744 }
1745 else if ( NPC->enemy && NPC->enemy->client && //valid enemy
1746 NPC->enemy->client->ps.saberInFlight && NPC->enemy->client->ps.saber[0].Active() && //enemy throwing saber
1747 !NPC->client->ps.weaponTime && //I'm not busy
1748 WP_ForcePowerAvailable( NPC, FP_GRIP, 0 ) && //I can use the power
1749 !Q_irand( 0, 10 ) && //don't do it all the time, averages to 1 check a second
1750 Q_irand( 0, 6 ) < g_spskill->integer && //more likely on harder diff
1751 Q_irand( RANK_CIVILIAN, RANK_CAPTAIN ) < NPCInfo->rank //more likely against harder enemies
1752 && InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 20, 30 ) )
1753 {//They're throwing their saber, grip them!
1754 //taunt
1755 if ( TIMER_Done( NPC, "chatter" ) && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time && NPCInfo->blockedSpeechDebounceTime < level.time )
1756 {
1757 G_AddVoiceEvent( NPC, Q_irand( EV_TAUNT1, EV_TAUNT3 ), 3000 );
1758 jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000;
1759 if ( (NPCInfo->aiFlags&NPCAI_ROSH) )
1760 {
1761 TIMER_Set( NPC, "chatter", 6000 );
1762 }
1763 else
1764 {
1765 TIMER_Set( NPC, "chatter", 3000 );
1766 }
1767 }
1768
1769 //grip
1770 TIMER_Set( NPC, "gripping", 3000 );
1771 TIMER_Set( NPC, "attackDelay", 3000 );
1772 }
1773 else
1774 {
1775 if ( NPC->enemy && NPC->enemy->client && (NPC->enemy->client->ps.forcePowersActive&(1<<FP_GRIP)) )
1776 {//They're choking someone, probably an ally, run at them and do some sort of attack
1777 if ( NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
1778 {//they're on the ground, so advance
1779 if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT )
1780 {//not parrying
1781 if ( enemy_dist > 200 || !(NPCInfo->scriptFlags&SCF_DONT_FIRE) )
1782 {//far away or allowed to use saber
1783 Jedi_Advance();
1784 }
1785 }
1786 }
1787 }
1788 if ( NPC->client->NPC_class == CLASS_KYLE
1789 && (NPC->spawnflags&1)
1790 && (NPC->enemy&&NPC->enemy->client&&!NPC->enemy->client->ps.saberInFlight)
1791 && TIMER_Done( NPC, "kyleTakesSaber" )
1792 && !Q_irand( 0, 20 ) )
1793 {
1794 ForceThrow( NPC, qtrue );
1795 }
1796 else if ( NPC->client->ps.stats[STAT_WEAPONS]&(1<<WP_SCEPTER)
1797 && !Q_irand( 0, 20 ) )
1798 {
1799 Tavion_StartScepterBeam();
1800 return;
1801 }
1802 else
1803 {
1804 int chanceScale = 0;
1805 if ( NPC->client->NPC_class == CLASS_KYLE && (NPC->spawnflags&1) )
1806 {
1807 chanceScale = 4;
1808 }
1809 else if ( NPC->enemy
1810 && NPC->enemy->s.number < MAX_CLIENTS
1811 && ((NPCInfo->aiFlags&NPCAI_BOSS_CHARACTER)
1812 || NPC->client->NPC_class==CLASS_SHADOWTROOPER) )
1813 {//hmm, bosses do this less against player
1814 chanceScale = 8 - g_spskill->integer*2;
1815 }
1816 else if ( NPC->client->NPC_class == CLASS_DESANN
1817 || !Q_stricmp("Yoda",NPC->NPC_type) )
1818 //|| (NPC->client->NPC_class == CLASS_CULTIST && NPC->client->ps.weapon == WP_NONE) )//force-only cultists use force a lot
1819 {
1820 chanceScale = 1;
1821 }
1822 else if ( NPCInfo->rank == RANK_ENSIGN )
1823 {
1824 chanceScale = 2;
1825 }
1826 else if ( NPCInfo->rank >= RANK_LT_JG )
1827 {
1828 chanceScale = 5;
1829 }
1830 if ( chanceScale
1831 && (enemy_dist > Q_irand( 100, 200 ) || (NPCInfo->scriptFlags&SCF_DONT_FIRE) || (!Q_stricmp("Yoda",NPC->NPC_type)&&!Q_irand(0,3)) )
1832 && enemy_dist < 500
1833 && (Q_irand( 0, chanceScale*10 )<5 || (NPC->enemy->client && NPC->enemy->client->ps.weapon != WP_SABER && !Q_irand( 0, chanceScale ) ) ) )
1834 {//else, randomly try some kind of attack every now and then
1835 //FIXME: Cultist fencers don't have any of these fancy powers
1836 // the only thing they might be able to do is throw their saber
1837 if ( (NPCInfo->rank == RANK_ENSIGN //old reborn crap
1838 || NPCInfo->rank > RANK_LT_JG //old reborn crap
1839 /*
1840 || WP_ForcePowerUsable( NPC, FP_PULL, 0 )
1841 || WP_ForcePowerUsable( NPC, FP_LIGHTNING, 0 )
1842 || WP_ForcePowerUsable( NPC, FP_DRAIN, 0 )
1843 || WP_ForcePowerUsable( NPC, FP_GRIP, 0 )
1844 || WP_ForcePowerUsable( NPC, FP_SABERTHROW, 0 )
1845 */
1846 )
1847 && (!Q_irand( 0, 1 ) || NPC->s.weapon != WP_SABER) )
1848 {
1849 if ( WP_ForcePowerUsable( NPC, FP_PULL, 0 ) && !Q_irand( 0, 2 ) )
1850 {
1851 //force pull the guy to me!
1852 //FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
1853 ForceThrow( NPC, qtrue );
1854 //maybe strafe too?
1855 TIMER_Set( NPC, "duck", enemy_dist*3 );
1856 if ( Q_irand( 0, 1 ) )
1857 {
1858 ucmd.buttons |= BUTTON_ATTACK;
1859 }
1860 }
1861 else if ( WP_ForcePowerUsable( NPC, FP_LIGHTNING, 0 )
1862 && (((NPCInfo->scriptFlags&SCF_DONT_FIRE)&&Q_stricmp("cultist_lightning",NPC->NPC_type)) || Q_irand( 0, 1 )) )
1863 {
1864 ForceLightning( NPC );
1865 if ( NPC->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 )
1866 {
1867 NPC->client->ps.weaponTime = Q_irand( 1000, 3000+(g_spskill->integer*500) );
1868 TIMER_Set( NPC, "holdLightning", NPC->client->ps.weaponTime );
1869 }
1870 TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime );
1871 }
1872 else if ( NPC->health < NPC->max_health * 0.75f
1873 && Q_irand( FORCE_LEVEL_0, NPC->client->ps.forcePowerLevel[FP_DRAIN] ) > FORCE_LEVEL_1
1874 && WP_ForcePowerUsable( NPC, FP_DRAIN, 0 )
1875 && (((NPCInfo->scriptFlags&SCF_DONT_FIRE)&&Q_stricmp("cultist_drain",NPC->NPC_type)) || Q_irand( 0, 1 )) )
1876 {
1877 ForceDrain2( NPC );
1878 NPC->client->ps.weaponTime = Q_irand( 1000, 3000+(g_spskill->integer*500) );
1879 TIMER_Set( NPC, "draining", NPC->client->ps.weaponTime );
1880 TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime );
1881 }
1882 else if ( WP_ForcePowerUsable( NPC, FP_GRIP, 0 )
1883 && NPC->enemy && InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 20, 30 ) )
1884 {
1885 //taunt
1886 if ( TIMER_Done( NPC, "chatter" ) && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time && NPCInfo->blockedSpeechDebounceTime < level.time )
1887 {
1888 G_AddVoiceEvent( NPC, Q_irand( EV_TAUNT1, EV_TAUNT3 ), 3000 );
1889 jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000;
1890 if ( (NPCInfo->aiFlags&NPCAI_ROSH) )
1891 {
1892 TIMER_Set( NPC, "chatter", 6000 );
1893 }
1894 else
1895 {
1896 TIMER_Set( NPC, "chatter", 3000 );
1897 }
1898 }
1899
1900 //grip
1901 TIMER_Set( NPC, "gripping", 3000 );
1902 TIMER_Set( NPC, "attackDelay", 3000 );
1903 }
1904 else
1905 {
1906 if ( WP_ForcePowerUsable( NPC, FP_SABERTHROW, 0 )
1907 && !(NPC->client->ps.forcePowersActive&(1 << FP_SPEED))
1908 && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water
1909 {//throw saber
1910 ucmd.buttons |= BUTTON_ALT_ATTACK;
1911 }
1912 }
1913 }
1914 else
1915 {
1916 if ( (NPCInfo->rank >= RANK_LT_JG||WP_ForcePowerUsable( NPC, FP_SABERTHROW, 0 ))
1917 && !(NPC->client->ps.forcePowersActive&(1 << FP_SPEED))
1918 && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water
1919 {//throw saber
1920 ucmd.buttons |= BUTTON_ALT_ATTACK;
1921 }
1922 }
1923 }
1924 //see if we should advance now
1925 else if ( NPCInfo->stats.aggression > 5 )
1926 {//approach enemy
1927 if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT )
1928 {//not parrying
1929 if ( !NPC->enemy->client || NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
1930 {//they're on the ground, so advance
1931 if ( enemy_dist > 200 || !(NPCInfo->scriptFlags&SCF_DONT_FIRE) )
1932 {//far away or allowed to use saber
1933 Jedi_Advance();
1934 }
1935 }
1936 }
1937 }
1938 else
1939 {//maintain this distance?
1940 //walk?
1941 }
1942 }
1943 }
1944 }
1945 else
1946 {//we're not close enough to attack, but not far enough away to be safe
1947 if ( !Q_irand( 0, 30 )
1948 && Kyle_CanDoGrab() )
1949 {
1950 Kyle_TryGrab();
1951 return;
1952 }
1953 if ( NPCInfo->stats.aggression < 4 )
1954 {//back off and defend
1955 if ( Jedi_DecideKick() )
1956 {//let's try a kick
1957 if ( G_PickAutoMultiKick( NPC, qfalse, qtrue ) != LS_NONE
1958 || (G_CanKickEntity(NPC, NPC->enemy ) && G_PickAutoKick( NPC, NPC->enemy, qtrue ) != LS_NONE ) )
1959 {//kicked!
1960 TIMER_Set( NPC, "kickDebounce", Q_irand( 3000, 10000 ) );
1961 return;
1962 }
1963 }
1964 Jedi_Retreat();
1965 }
1966 else if ( NPCInfo->stats.aggression > 5 )
1967 {//try to get closer
1968 if ( enemy_dist > 0 && !(NPCInfo->scriptFlags&SCF_DONT_FIRE))
1969 {//we're allowed to use our lightsaber, get closer
1970 if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT )
1971 {//not parrying
1972 if ( !NPC->enemy->client || NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
1973 {//they're on the ground, so advance
1974 Jedi_Advance();
1975 }
1976 }
1977 }
1978 }
1979 else
1980 {//agression is 4 or 5... somewhere in the middle
1981 //what do we do here? Nothing?
1982 //Move forward and back?
1983 }
1984 }
1985 //if really really mad, rage!
1986 if ( NPCInfo->stats.aggression > Q_irand( 5, 15 )
1987 && NPC->health < NPC->max_health*0.75f
1988 && !Q_irand( 0, 2 ) )
1989 {
1990 if ( (NPC->client->ps.forcePowersKnown&(1<<FP_RAGE)) != 0
1991 && (NPC->client->ps.forcePowersActive&(1<<FP_RAGE)) == 0 )
1992 {
1993 Jedi_Rage();
1994 }
1995 }
1996 }
1997
Jedi_Strafe(int strafeTimeMin,int strafeTimeMax,int nextStrafeTimeMin,int nextStrafeTimeMax,qboolean walking)1998 static qboolean Jedi_Strafe( int strafeTimeMin, int strafeTimeMax, int nextStrafeTimeMin, int nextStrafeTimeMax, qboolean walking )
1999 {
2000 if ( Jedi_CultistDestroyer( NPC ) )
2001 {//never strafe
2002 return qfalse;
2003 }
2004 if ( NPC->client->ps.saberEventFlags&SEF_LOCK_WON && NPC->enemy && NPC->enemy->painDebounceTime > level.time )
2005 {//don't strafe if pressing the advantage of winning a saberLock
2006 return qfalse;
2007 }
2008 if ( TIMER_Done( NPC, "strafeLeft" ) && TIMER_Done( NPC, "strafeRight" ) )
2009 {
2010 qboolean strafed = qfalse;
2011 //TODO: make left/right choice a tactical decision rather than random:
2012 // try to keep own back away from walls and ledges,
2013 // try to keep enemy's back to a ledge or wall
2014 // Maybe try to strafe toward designer-placed "safe spots" or "goals"?
2015 int strafeTime = Q_irand( strafeTimeMin, strafeTimeMax );
2016
2017 if ( Q_irand( 0, 1 ) )
2018 {
2019 if ( NPC_MoveDirClear( ucmd.forwardmove, -127, qfalse ) )
2020 {
2021 TIMER_Set( NPC, "strafeLeft", strafeTime );
2022 strafed = qtrue;
2023 }
2024 else if ( NPC_MoveDirClear( ucmd.forwardmove, 127, qfalse ) )
2025 {
2026 TIMER_Set( NPC, "strafeRight", strafeTime );
2027 strafed = qtrue;
2028 }
2029 }
2030 else
2031 {
2032 if ( NPC_MoveDirClear( ucmd.forwardmove, 127, qfalse ) )
2033 {
2034 TIMER_Set( NPC, "strafeRight", strafeTime );
2035 strafed = qtrue;
2036 }
2037 else if ( NPC_MoveDirClear( ucmd.forwardmove, -127, qfalse ) )
2038 {
2039 TIMER_Set( NPC, "strafeLeft", strafeTime );
2040 strafed = qtrue;
2041 }
2042 }
2043
2044 if ( strafed )
2045 {
2046 TIMER_Set( NPC, "noStrafe", strafeTime + Q_irand( nextStrafeTimeMin, nextStrafeTimeMax ) );
2047 if ( walking )
2048 {//should be a slow strafe
2049 TIMER_Set( NPC, "walking", strafeTime );
2050 }
2051 return qtrue;
2052 }
2053 }
2054 return qfalse;
2055 }
2056
2057 /*
2058 static void Jedi_FaceEntity( gentity_t *self, gentity_t *other, qboolean doPitch )
2059 {
2060 vec3_t entPos;
2061 vec3_t muzzle;
2062
2063 //Get the positions
2064 CalcEntitySpot( other, SPOT_ORIGIN, entPos );
2065
2066 //Get the positions
2067 CalcEntitySpot( self, SPOT_HEAD_LEAN, muzzle );//SPOT_HEAD
2068
2069 //Find the desired angles
2070 vec3_t angles;
2071
2072 GetAnglesForDirection( muzzle, entPos, angles );
2073
2074 self->NPC->desiredYaw = AngleNormalize360( angles[YAW] );
2075 if ( doPitch )
2076 {
2077 self->NPC->desiredPitch = AngleNormalize360( angles[PITCH] );
2078 }
2079 }
2080 */
2081
2082 /*
2083 qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc )
2084
2085 Jedi will play a dodge anim, blur, and make the force speed noise.
2086
2087 Right now used to dodge instant-hit weapons.
2088
2089 FIXME: possibly call this for saber melee evasion and/or missile evasion?
2090 FIXME: possibly let player do this too?
2091 */
Jedi_DodgeEvasion(gentity_t * self,gentity_t * shooter,trace_t * tr,int hitLoc)2092 qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc )
2093 {
2094 int dodgeAnim = -1;
2095
2096 if ( !self || !self->client || self->health <= 0 )
2097 {
2098 return qfalse;
2099 }
2100
2101 if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE )
2102 {//can't dodge in mid-air
2103 return qfalse;
2104 }
2105
2106 if ( self->client->ps.pm_time && (self->client->ps.pm_flags&PMF_TIME_KNOCKBACK) )
2107 {//in some effect that stops me from moving on my own
2108 return qfalse;
2109 }
2110
2111 if ( self->enemy == shooter )
2112 {//FIXME: make it so that we are better able to dodge shots from my current enemy
2113 }
2114 if ( self->s.number )
2115 {//if an NPC, check game skill setting
2116 /*
2117 if ( self->NPC && (self->NPC->aiFlags&NPCAI_BOSS_CHARACTER) )
2118 {//those NPCs are "bosses" and always succeed
2119 if ( Q_irand( 0, 2 ) > g_spskill->integer )
2120 {//more of a chance of failing the dodge on lower difficulty
2121 return qfalse;
2122 }
2123 //FIXME: check my overall skill (rank) to determine if I should be able to dodge it?
2124 //check force speed power level to determine if I should be able to dodge it
2125 if ( Q_irand( 0, 3 ) > self->client->ps.forcePowerLevel[FP_SPEED] )
2126 {//more likely to fail on lower force speed level, but NPCs are generally better at it than the player
2127 return qfalse;
2128 }
2129 }
2130 */
2131 }
2132 else
2133 {//the player
2134 if ( !(self->client->ps.forcePowersActive&(1<<FP_SPEED)) )
2135 {//not already in speed
2136 if ( !WP_ForcePowerUsable( self, FP_SPEED, 0 ) )
2137 {//make sure we have it and have enough force power
2138 return qfalse;
2139 }
2140 }
2141 //check force speed power level to determine if I should be able to dodge it
2142 if ( Q_irand( 1, 10 ) > self->client->ps.forcePowerLevel[FP_SPEED] )
2143 {//more likely to fail on lower force speed level
2144 return qfalse;
2145 }
2146 }
2147
2148 if ( hitLoc == HL_NONE )
2149 {
2150 if ( tr )
2151 {
2152 for ( int z = 0; z < MAX_G2_COLLISIONS; z++ )
2153 {
2154 if ( tr->G2CollisionMap[z].mEntityNum == -1 )
2155 {//actually, completely break out of this for loop since nothing after this in the aray should ever be valid either
2156 continue;//break;//
2157 }
2158
2159 CCollisionRecord &coll = tr->G2CollisionMap[z];
2160 G_GetHitLocFromSurfName( &g_entities[coll.mEntityNum], gi.G2API_GetSurfaceName( &g_entities[coll.mEntityNum].ghoul2[coll.mModelIndex], coll.mSurfaceIndex ), &hitLoc, coll.mCollisionPosition, NULL, NULL, MOD_UNKNOWN );
2161 //only want the first
2162 break;
2163 }
2164 }
2165 }
2166
2167 switch( hitLoc )
2168 {
2169 case HL_NONE:
2170 return qfalse;
2171 break;
2172
2173 case HL_FOOT_RT:
2174 case HL_FOOT_LT:
2175 case HL_LEG_RT:
2176 case HL_LEG_LT:
2177 case HL_WAIST:
2178 if ( !self->s.number )
2179 {//don't force the player to jump
2180 return qfalse;
2181 }
2182 else
2183 {
2184 if ( !self->enemy && G_ValidEnemy(self,shooter))
2185 {
2186 G_SetEnemy( self, shooter );
2187 }
2188 if ( self->NPC
2189 && ((self->NPC->scriptFlags&SCF_NO_ACROBATICS) || PM_InKnockDown( &self->client->ps ) ) )
2190 {
2191 return qfalse;
2192 }
2193 if ( self->client
2194 && (self->client->ps.forceRageRecoveryTime > level.time || (self->client->ps.forcePowersActive&(1<<FP_RAGE))) )
2195 {//no fancy dodges when raging or recovering
2196 return qfalse;
2197 }
2198 if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand(0,1))
2199 {
2200 return qfalse; // half the time he dodges
2201 }
2202
2203
2204 if ( self->client->NPC_class == CLASS_BOBAFETT
2205 || (self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER)
2206 || self->client->NPC_class == CLASS_ROCKETTROOPER )
2207 {
2208 self->client->ps.forceJumpCharge = 280;//FIXME: calc this intelligently?
2209 }
2210 else
2211 {
2212 self->client->ps.forceJumpCharge = 320;//FIXME: calc this intelligently?
2213 WP_ForcePowerStop( self, FP_GRIP );
2214 }
2215 return qtrue;
2216 }
2217 break;
2218
2219 case HL_BACK_RT:
2220 dodgeAnim = BOTH_DODGE_FL;
2221 break;
2222 case HL_CHEST_RT:
2223 dodgeAnim = BOTH_DODGE_BL;
2224 break;
2225 case HL_BACK_LT:
2226 dodgeAnim = BOTH_DODGE_FR;
2227 break;
2228 case HL_CHEST_LT:
2229 dodgeAnim = BOTH_DODGE_BR;
2230 break;
2231 case HL_BACK:
2232 case HL_CHEST:
2233 dodgeAnim = Q_irand( BOTH_DODGE_FL, BOTH_DODGE_R );
2234 break;
2235 case HL_ARM_RT:
2236 case HL_HAND_RT:
2237 dodgeAnim = BOTH_DODGE_L;
2238 break;
2239 case HL_ARM_LT:
2240 case HL_HAND_LT:
2241 dodgeAnim = BOTH_DODGE_R;
2242 break;
2243 case HL_HEAD:
2244 dodgeAnim = Q_irand( BOTH_DODGE_FL, BOTH_DODGE_BR );
2245 break;
2246 }
2247
2248 if ( dodgeAnim != -1 )
2249 {
2250 int extraHoldTime = 0;//Q_irand( 5, 40 ) * 50;
2251 /*
2252 int type = SETANIM_TORSO;
2253 if ( VectorCompare( self->client->ps.velocity, vec3_origin ) )
2254 {//not moving
2255 type = SETANIM_BOTH;
2256 }
2257 */
2258 if ( self->s.number < MAX_CLIENTS )
2259 {//player
2260 if ( (self->client->ps.forcePowersActive&(1<<FP_SPEED)) )
2261 {//in speed
2262 if ( PM_DodgeAnim( self->client->ps.torsoAnim )
2263 && !PM_DodgeHoldAnim( self->client->ps.torsoAnim ) )
2264 {//already in a dodge
2265 //use the hold pose, don't start it all over again
2266 dodgeAnim = BOTH_DODGE_HOLD_FL+(dodgeAnim-BOTH_DODGE_FL);
2267 extraHoldTime = 200;
2268 }
2269 }
2270 }
2271
2272 //set the dodge anim we chose
2273 NPC_SetAnim( self, SETANIM_BOTH, dodgeAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );//type
2274 if ( extraHoldTime && self->client->ps.torsoAnimTimer < extraHoldTime )
2275 {
2276 self->client->ps.torsoAnimTimer += extraHoldTime;
2277 }
2278 //if ( type == SETANIM_BOTH )
2279 {
2280 self->client->ps.legsAnimTimer = self->client->ps.torsoAnimTimer;
2281 }
2282
2283 if ( self->s.number )
2284 {//NPC
2285 //maybe force them to stop moving in this case?
2286 self->client->ps.pm_time = self->client->ps.torsoAnimTimer + Q_irand( 100, 1000 );
2287 self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
2288 //do force speed effect
2289 self->client->ps.forcePowersActive |= (1 << FP_SPEED);
2290 self->client->ps.forcePowerDuration[FP_SPEED] = level.time + self->client->ps.torsoAnimTimer;
2291 //sound
2292 G_Sound( self, G_SoundIndex( "sound/weapons/force/speed.wav" ) );
2293 }
2294 else
2295 {//player
2296 ForceSpeed( self, 500 );
2297 }
2298
2299 WP_ForcePowerStop( self, FP_GRIP );
2300 if ( !self->enemy && G_ValidEnemy( self, shooter) )
2301 {
2302 G_SetEnemy( self, shooter );
2303 if ( self->s.number )
2304 {
2305 Jedi_Aggression( self, 10 );
2306 }
2307 }
2308 return qtrue;
2309 }
2310 return qfalse;
2311 }
2312
Jedi_CheckFlipEvasions(gentity_t * self,float rightdot,float zdiff)2313 evasionType_t Jedi_CheckFlipEvasions( gentity_t *self, float rightdot, float zdiff )
2314 {
2315 if ( self->NPC && (self->NPC->scriptFlags&SCF_NO_ACROBATICS) )
2316 {
2317 return EVASION_NONE;
2318 }
2319 if ( self->client )
2320 {
2321 if ( self->client->NPC_class == CLASS_BOBAFETT )
2322 {//boba can't flip
2323 return EVASION_NONE;
2324 }
2325 if ( self->client->ps.forceRageRecoveryTime > level.time
2326 || (self->client->ps.forcePowersActive&(1<<FP_RAGE)) )
2327 {//no fancy dodges when raging
2328 return EVASION_NONE;
2329 }
2330 }
2331
2332 //Check for:
2333 //ARIALS/CARTWHEELS
2334 //WALL-RUNS
2335 //WALL-FLIPS
2336 //FIXME: if facing a wall, do forward wall-walk-backflip
2337 //FIXME: add new JKA ones
2338
2339 //FIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXME
2340 //
2341 //Make these check for do not enter and ledges!!!
2342 //
2343 //FIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXME
2344
2345 if ( self->client->ps.legsAnim == BOTH_WALL_RUN_LEFT || self->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT )
2346 {//already running on a wall
2347 vec3_t right, fwdAngles = {0, self->client->ps.viewangles[YAW], 0};
2348 int anim = -1;
2349
2350 AngleVectors( fwdAngles, NULL, right, NULL );
2351
2352 float animLength = PM_AnimLength( self->client->clientInfo.animFileIndex, (animNumber_t)self->client->ps.legsAnim );
2353 if ( self->client->ps.legsAnim == BOTH_WALL_RUN_LEFT && rightdot < 0 )
2354 {//I'm running on a wall to my left and the attack is on the left
2355 if ( animLength - self->client->ps.legsAnimTimer > 400
2356 && self->client->ps.legsAnimTimer > 400 )
2357 {//not at the beginning or end of the anim
2358 anim = BOTH_WALL_RUN_LEFT_FLIP;
2359 }
2360 }
2361 else if ( self->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT && rightdot > 0 )
2362 {//I'm running on a wall to my right and the attack is on the right
2363 if ( animLength - self->client->ps.legsAnimTimer > 400
2364 && self->client->ps.legsAnimTimer > 400 )
2365 {//not at the beginning or end of the anim
2366 anim = BOTH_WALL_RUN_RIGHT_FLIP;
2367 }
2368 }
2369 if ( anim != -1 )
2370 {//flip off the wall!
2371 //FIXME: check the direction we will flip towards for do-not-enter/walls/drops?
2372 //NOTE: we presume there is still a wall there!
2373 if ( anim == BOTH_WALL_RUN_LEFT_FLIP )
2374 {
2375 self->client->ps.velocity[0] *= 0.5f;
2376 self->client->ps.velocity[1] *= 0.5f;
2377 VectorMA( self->client->ps.velocity, 150, right, self->client->ps.velocity );
2378 }
2379 else if ( anim == BOTH_WALL_RUN_RIGHT_FLIP )
2380 {
2381 self->client->ps.velocity[0] *= 0.5f;
2382 self->client->ps.velocity[1] *= 0.5f;
2383 VectorMA( self->client->ps.velocity, -150, right, self->client->ps.velocity );
2384 }
2385 int parts = SETANIM_LEGS;
2386 if ( !self->client->ps.weaponTime )
2387 {
2388 parts = SETANIM_BOTH;
2389 }
2390 NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
2391 self->client->ps.pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL);
2392 G_AddEvent( self, EV_JUMP, 0 );
2393 return EVASION_OTHER;
2394 }
2395 }
2396 else if ( self->client->NPC_class != CLASS_DESANN //desann doesn't do these kind of frilly acrobatics
2397 && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank >= RANK_LT)
2398 && Q_irand( 0, 1 )
2399 && !PM_InRoll( &self->client->ps )
2400 && !PM_InKnockDown( &self->client->ps )
2401 && !PM_SaberInSpecialAttack( self->client->ps.torsoAnim ) )
2402 {
2403 vec3_t fwd, right, traceto, mins = {self->mins[0],self->mins[1],self->mins[2]+STEPSIZE}, maxs = {self->maxs[0],self->maxs[1],24}, fwdAngles = {0, self->client->ps.viewangles[YAW], 0};
2404 trace_t trace;
2405
2406 AngleVectors( fwdAngles, fwd, right, NULL );
2407
2408 int parts = SETANIM_BOTH, anim;
2409 float speed, checkDist;
2410 qboolean allowCartWheels = qtrue;
2411
2412 if ( self->client->ps.weapon == WP_SABER )
2413 {
2414 if ( (self->client->ps.saber[0].saberFlags&SFL_NO_CARTWHEELS) )
2415 {
2416 allowCartWheels = qfalse;
2417 }
2418 else if ( self->client->ps.dualSabers
2419 && (self->client->ps.saber[1].saberFlags&SFL_NO_CARTWHEELS) )
2420 {
2421 allowCartWheels = qfalse;
2422 }
2423 }
2424
2425 if ( PM_SaberInAttack( self->client->ps.saberMove )
2426 || PM_SaberInStart( self->client->ps.saberMove ) )
2427 {
2428 parts = SETANIM_LEGS;
2429 }
2430 if ( rightdot >= 0 )
2431 {
2432 if ( Q_irand( 0, 1 ) )
2433 {
2434 anim = BOTH_ARIAL_LEFT;
2435 }
2436 else
2437 {
2438 anim = BOTH_CARTWHEEL_LEFT;
2439 }
2440 checkDist = -128;
2441 speed = -200;
2442 }
2443 else
2444 {
2445 if ( Q_irand( 0, 1 ) )
2446 {
2447 anim = BOTH_ARIAL_RIGHT;
2448 }
2449 else
2450 {
2451 anim = BOTH_CARTWHEEL_RIGHT;
2452 }
2453 checkDist = 128;
2454 speed = 200;
2455 }
2456 //trace in the dir that we want to go
2457 VectorMA( self->currentOrigin, checkDist, right, traceto );
2458 gi.trace( &trace, self->currentOrigin, mins, maxs, traceto, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP, (EG2_Collision)0, 0 );
2459 if ( trace.fraction >= 1.0f && allowCartWheels )
2460 {//it's clear, let's do it
2461 //FIXME: check for drops?
2462 NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
2463 self->client->ps.weaponTime = self->client->ps.legsAnimTimer;//don't attack again until this anim is done
2464 vec3_t fwdAngles, jumpRt;
2465 VectorCopy( self->client->ps.viewangles, fwdAngles );
2466 fwdAngles[PITCH] = fwdAngles[ROLL] = 0;
2467 //do the flip
2468 AngleVectors( fwdAngles, NULL, jumpRt, NULL );
2469 VectorScale( jumpRt, speed, self->client->ps.velocity );
2470 self->client->ps.forceJumpCharge = 0;//so we don't play the force flip anim
2471 self->client->ps.velocity[2] = 200;
2472 self->client->ps.forceJumpZStart = self->currentOrigin[2];//so we don't take damage if we land at same height
2473 self->client->ps.pm_flags |= PMF_JUMPING;
2474 if ( self->client->NPC_class == CLASS_BOBAFETT
2475 || (self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER) )
2476 {
2477 G_AddEvent( self, EV_JUMP, 0 );
2478 }
2479 else
2480 {
2481 G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" );
2482 }
2483 //ucmd.upmove = 0;
2484 return EVASION_CARTWHEEL;
2485 }
2486 else if ( !(trace.contents&CONTENTS_BOTCLIP) )
2487 {//hit a wall, not a do-not-enter brush
2488 //FIXME: before we check any of these jump-type evasions, we should check for headroom, right?
2489 //Okay, see if we can flip *off* the wall and go the other way
2490 vec3_t idealNormal;
2491 VectorSubtract( self->currentOrigin, traceto, idealNormal );
2492 VectorNormalize( idealNormal );
2493 gentity_t *traceEnt = &g_entities[trace.entityNum];
2494 if ( (trace.entityNum<ENTITYNUM_WORLD&&traceEnt&&traceEnt->s.solid!=SOLID_BMODEL) || DotProduct( trace.plane.normal, idealNormal ) > 0.7f )
2495 {//it's a ent of some sort or it's a wall roughly facing us
2496 float bestCheckDist = 0;
2497 //hmm, see if we're moving forward
2498 if ( DotProduct( self->client->ps.velocity, fwd ) < 200 )
2499 {//not running forward very fast
2500 //check to see if it's okay to move the other way
2501 if ( (trace.fraction*checkDist) <= 32 )
2502 {//wall on that side is close enough to wall-flip off of or wall-run on
2503 bestCheckDist = checkDist;
2504 checkDist *= -1.0f;
2505 VectorMA( self->currentOrigin, checkDist, right, traceto );
2506 //trace in the dir that we want to go
2507 gi.trace( &trace, self->currentOrigin, mins, maxs, traceto, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP, (EG2_Collision)0, 0 );
2508 if ( trace.fraction >= 1.0f )
2509 {//it's clear, let's do it
2510 qboolean allowWallFlips = qtrue;
2511 if ( self->client->ps.weapon == WP_SABER )
2512 {
2513 if ( (self->client->ps.saber[0].saberFlags&SFL_NO_WALL_FLIPS) )
2514 {
2515 allowWallFlips = qfalse;
2516 }
2517 else if ( self->client->ps.dualSabers
2518 && (self->client->ps.saber[1].saberFlags&SFL_NO_WALL_FLIPS) )
2519 {
2520 allowWallFlips = qfalse;
2521 }
2522 }
2523 if ( allowWallFlips )
2524 {//okay to do wall-flips with this saber
2525 //FIXME: check for drops?
2526 //turn the cartwheel into a wallflip in the other dir
2527 if ( rightdot > 0 )
2528 {
2529 anim = BOTH_WALL_FLIP_LEFT;
2530 self->client->ps.velocity[0] = self->client->ps.velocity[1] = 0;
2531 VectorMA( self->client->ps.velocity, 150, right, self->client->ps.velocity );
2532 }
2533 else
2534 {
2535 anim = BOTH_WALL_FLIP_RIGHT;
2536 self->client->ps.velocity[0] = self->client->ps.velocity[1] = 0;
2537 VectorMA( self->client->ps.velocity, -150, right, self->client->ps.velocity );
2538 }
2539 self->client->ps.velocity[2] = forceJumpStrength[FORCE_LEVEL_2]/2.25f;
2540 //animate me
2541 int parts = SETANIM_LEGS;
2542 if ( !self->client->ps.weaponTime )
2543 {
2544 parts = SETANIM_BOTH;
2545 }
2546 NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
2547 self->client->ps.forceJumpZStart = self->currentOrigin[2];//so we don't take damage if we land at same height
2548 self->client->ps.pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL);
2549 if ( self->client->NPC_class == CLASS_BOBAFETT
2550 || (self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER))
2551 {
2552 G_AddEvent( self, EV_JUMP, 0 );
2553 }
2554 else
2555 {
2556 G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" );
2557 }
2558 return EVASION_OTHER;
2559 }
2560 }
2561 else
2562 {//boxed in on both sides
2563 if ( DotProduct( self->client->ps.velocity, fwd ) < 0 )
2564 {//moving backwards
2565 return EVASION_NONE;
2566 }
2567 if ( (trace.fraction*checkDist) <= 32 && (trace.fraction*checkDist) < bestCheckDist )
2568 {
2569 bestCheckDist = checkDist;
2570 }
2571 }
2572 }
2573 else
2574 {//too far from that wall to flip or run off it, check other side
2575 checkDist *= -1.0f;
2576 VectorMA( self->currentOrigin, checkDist, right, traceto );
2577 //trace in the dir that we want to go
2578 gi.trace( &trace, self->currentOrigin, mins, maxs, traceto, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP, (EG2_Collision)0, 0 );
2579 if ( (trace.fraction*checkDist) <= 32 )
2580 {//wall on this side is close enough
2581 bestCheckDist = checkDist;
2582 }
2583 else
2584 {//neither side has a wall within 32
2585 return EVASION_NONE;
2586 }
2587 }
2588 }
2589 //Try wall run?
2590 if ( bestCheckDist )
2591 {//one of the walls was close enough to wall-run on
2592 qboolean allowWallRuns = qtrue;
2593 if ( self->client->ps.weapon == WP_SABER )
2594 {
2595 if ( (self->client->ps.saber[0].saberFlags&SFL_NO_WALL_RUNS) )
2596 {
2597 allowWallRuns = qfalse;
2598 }
2599 else if ( self->client->ps.dualSabers
2600 && (self->client->ps.saber[1].saberFlags&SFL_NO_WALL_RUNS) )
2601 {
2602 allowWallRuns = qfalse;
2603 }
2604 }
2605 if ( allowWallRuns )
2606 {//okay to do wallruns with this saber
2607 //FIXME: check for long enough wall and a drop at the end?
2608 if ( bestCheckDist > 0 )
2609 {//it was to the right
2610 anim = BOTH_WALL_RUN_RIGHT;
2611 }
2612 else
2613 {//it was to the left
2614 anim = BOTH_WALL_RUN_LEFT;
2615 }
2616 self->client->ps.velocity[2] = forceJumpStrength[FORCE_LEVEL_2]/2.25f;
2617 //animate me
2618 int parts = SETANIM_LEGS;
2619 if ( !self->client->ps.weaponTime )
2620 {
2621 parts = SETANIM_BOTH;
2622 }
2623 NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
2624 self->client->ps.forceJumpZStart = self->currentOrigin[2];//so we don't take damage if we land at same height
2625 self->client->ps.pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL);
2626 if ( self->client->NPC_class == CLASS_BOBAFETT
2627 || (self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER))
2628 {
2629 G_AddEvent( self, EV_JUMP, 0 );
2630 }
2631 else
2632 {
2633 G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" );
2634 }
2635 return EVASION_OTHER;
2636 }
2637 }
2638 //else check for wall in front, do backflip off wall
2639 }
2640 }
2641 }
2642 return EVASION_NONE;
2643 }
2644
Jedi_ReCalcParryTime(gentity_t * self,evasionType_t evasionType)2645 int Jedi_ReCalcParryTime( gentity_t *self, evasionType_t evasionType )
2646 {
2647 if ( !self->client )
2648 {
2649 return 0;
2650 }
2651 if ( !self->s.number )
2652 {//player
2653 return parryDebounce[self->client->ps.forcePowerLevel[FP_SABER_DEFENSE]];
2654 }
2655 else if ( self->NPC )
2656 {
2657 /*
2658 if ( !g_saberRealisticCombat->integer
2659 && ( g_spskill->integer == 2 || (g_spskill->integer == 1 && (self->client->NPC_class == CLASS_TAVION||self->client->NPC_class == CLASS_ALORA) ) ) )
2660 {
2661 if ( (self->client->NPC_class == CLASS_TAVION||self->client->NPC_class == CLASS_ALORA) )
2662 {
2663 return 0;
2664 }
2665 else
2666 {
2667 return Q_irand( 0, 150 );
2668 }
2669 }
2670 else
2671 */
2672 {
2673 int baseTime;
2674 if ( evasionType == EVASION_DODGE )
2675 {
2676 baseTime = self->client->ps.torsoAnimTimer;
2677 }
2678 else if ( evasionType == EVASION_CARTWHEEL )
2679 {
2680 baseTime = self->client->ps.torsoAnimTimer;
2681 }
2682 else if ( self->client->ps.saberInFlight )
2683 {
2684 baseTime = Q_irand( 1, 3 ) * 50;
2685 }
2686 else
2687 {
2688 /*
2689 baseTime = 1000;
2690
2691 switch ( g_spskill->integer )
2692 {
2693 case 0:
2694 baseTime = 1500;
2695 break;
2696 case 1:
2697 baseTime = 1000;
2698 break;
2699 case 2:
2700 default:
2701 baseTime = 500;
2702 break;
2703 }
2704 */
2705 if ( 1 )//g_saberRealisticCombat->integer )
2706 {
2707 baseTime = 500;
2708
2709 switch ( g_spskill->integer )
2710 {
2711 case 0:
2712 baseTime = 400;//was 500
2713 break;
2714 case 1:
2715 baseTime = 200;//was 300
2716 break;
2717 case 2:
2718 default:
2719 baseTime = 100;
2720 break;
2721 }
2722 }
2723 else
2724 {
2725 baseTime = 150;//500;
2726
2727 switch ( g_spskill->integer )
2728 {
2729 case 0:
2730 baseTime = 200;//500;
2731 break;
2732 case 1:
2733 baseTime = 100;//300;
2734 break;
2735 case 2:
2736 default:
2737 baseTime = 50;//100;
2738 break;
2739 }
2740 }
2741
2742 if ( self->client->NPC_class == CLASS_ALORA
2743 || self->client->NPC_class == CLASS_SHADOWTROOPER
2744 || self->client->NPC_class == CLASS_TAVION )
2745 {//Tavion & Alora are faster
2746 baseTime = ceil(baseTime/2.0f);
2747 }
2748 else if ( self->NPC->rank >= RANK_LT_JG )
2749 {//fencers, bosses, shadowtroopers, luke, desann, et al use the norm
2750 if ( !Q_irand( 0, 2 ) )
2751 {//with the occasional fast parry
2752 baseTime = ceil(baseTime/2.0f);
2753 }
2754 }
2755 else if ( self->NPC->rank == RANK_CIVILIAN )
2756 {//grunts are slowest
2757 baseTime = baseTime*Q_irand(1,3);
2758 }
2759 else if ( self->NPC->rank == RANK_CREWMAN )
2760 {//acrobats aren't so bad
2761 if ( evasionType == EVASION_PARRY
2762 || evasionType == EVASION_DUCK_PARRY
2763 || evasionType == EVASION_JUMP_PARRY )
2764 {//slower with parries
2765 baseTime = baseTime*Q_irand(1,2);
2766 }
2767 else
2768 {//faster with acrobatics
2769 //baseTime = baseTime;
2770 }
2771 }
2772 else
2773 {//force users are kinda slow
2774 baseTime = baseTime*Q_irand(1,2);
2775 }
2776 if ( evasionType == EVASION_DUCK || evasionType == EVASION_DUCK_PARRY )
2777 {
2778 baseTime += 250;//300;//100;
2779 }
2780 else if ( evasionType == EVASION_JUMP || evasionType == EVASION_JUMP_PARRY )
2781 {
2782 baseTime += 400;//500;//50;
2783 }
2784 else if ( evasionType == EVASION_OTHER )
2785 {
2786 baseTime += 50;//100;
2787 }
2788 else if ( evasionType == EVASION_FJUMP )
2789 {
2790 baseTime += 300;//400;//100;
2791 }
2792 }
2793
2794 return baseTime;
2795 }
2796 }
2797 return 0;
2798 }
2799
Jedi_QuickReactions(gentity_t * self)2800 qboolean Jedi_QuickReactions( gentity_t *self )
2801 {
2802 if ( ( self->client->NPC_class == CLASS_JEDI && NPCInfo->rank == RANK_COMMANDER ) ||
2803 self->client->NPC_class == CLASS_SHADOWTROOPER || self->client->NPC_class == CLASS_ALORA || self->client->NPC_class == CLASS_TAVION ||
2804 (self->client->ps.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_1&&g_spskill->integer>1) ||
2805 (self->client->ps.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_2&&g_spskill->integer>0) )
2806 {
2807 return qtrue;
2808 }
2809 return qfalse;
2810 }
2811
Jedi_SaberBusy(gentity_t * self)2812 qboolean Jedi_SaberBusy( gentity_t *self )
2813 {
2814 if ( self->client->ps.torsoAnimTimer > 300
2815 && ( (PM_SaberInAttack( self->client->ps.saberMove )&&self->client->ps.saberAnimLevel==SS_STRONG)
2816 || PM_SpinningSaberAnim( self->client->ps.torsoAnim )
2817 || PM_SaberInSpecialAttack( self->client->ps.torsoAnim )
2818 //|| PM_SaberInBounce( self->client->ps.saberMove )
2819 || PM_SaberInBrokenParry( self->client->ps.saberMove )
2820 //|| PM_SaberInDeflect( self->client->ps.saberMove )
2821 || PM_FlippingAnim( self->client->ps.torsoAnim )
2822 || PM_RollingAnim( self->client->ps.torsoAnim ) ) )
2823 {//my saber is not in a parrying position
2824 return qtrue;
2825 }
2826 return qfalse;
2827 }
2828
Jedi_InNoAIAnim(gentity_t * self)2829 qboolean Jedi_InNoAIAnim( gentity_t *self )
2830 {
2831 if ( !self || !self->client )
2832 {//wtf???
2833 return qtrue;
2834 }
2835
2836 if ( NPCInfo->rank >= RANK_COMMANDER )
2837 {//boss-level guys can multitask, the rest need to chill out during special moves
2838 return qfalse;
2839 }
2840
2841 if ( PM_KickingAnim( NPC->client->ps.legsAnim )
2842 ||PM_StabDownAnim( NPC->client->ps.legsAnim )
2843 ||PM_InAirKickingAnim( NPC->client->ps.legsAnim )
2844 ||PM_InRollIgnoreTimer( &NPC->client->ps )
2845 ||PM_SaberInKata((saberMoveName_t)NPC->client->ps.saberMove)
2846 ||PM_SuperBreakWinAnim( NPC->client->ps.torsoAnim )
2847 ||PM_SuperBreakLoseAnim( NPC->client->ps.torsoAnim ) )
2848 {
2849 return qtrue;
2850 }
2851
2852 switch ( self->client->ps.legsAnim )
2853 {
2854 case BOTH_BUTTERFLY_LEFT:
2855 case BOTH_BUTTERFLY_RIGHT:
2856 case BOTH_BUTTERFLY_FL1:
2857 case BOTH_BUTTERFLY_FR1:
2858 case BOTH_FLIP_F:
2859 case BOTH_FLIP_B:
2860 case BOTH_FLIP_L:
2861 case BOTH_FLIP_R:
2862 case BOTH_DODGE_FL:
2863 case BOTH_DODGE_FR:
2864 case BOTH_DODGE_BL:
2865 case BOTH_DODGE_BR:
2866 case BOTH_DODGE_L:
2867 case BOTH_DODGE_R:
2868 case BOTH_DODGE_HOLD_FL:
2869 case BOTH_DODGE_HOLD_FR:
2870 case BOTH_DODGE_HOLD_BL:
2871 case BOTH_DODGE_HOLD_BR:
2872 case BOTH_DODGE_HOLD_L:
2873 case BOTH_DODGE_HOLD_R:
2874 case BOTH_FORCEWALLRUNFLIP_START:
2875 case BOTH_JUMPATTACK6:
2876 case BOTH_JUMPATTACK7:
2877 case BOTH_JUMPFLIPSLASHDOWN1:
2878 case BOTH_JUMPFLIPSTABDOWN:
2879 case BOTH_FORCELEAP2_T__B_:
2880 case BOTH_ROLL_STAB:
2881 case BOTH_SPINATTACK6:
2882 case BOTH_SPINATTACK7:
2883 case BOTH_PULL_IMPALE_STAB:
2884 case BOTH_PULL_IMPALE_SWING:
2885 case BOTH_A6_FB:
2886 case BOTH_A6_LR:
2887 case BOTH_A7_HILT:
2888 return qtrue;
2889 break;
2890 }
2891 return qfalse;
2892 }
2893
Jedi_CheckJumpEvasionSafety(gentity_t * self,usercmd_t * cmd,evasionType_t evasionType)2894 void Jedi_CheckJumpEvasionSafety( gentity_t *self, usercmd_t *cmd, evasionType_t evasionType )
2895 {
2896 if ( evasionType != EVASION_OTHER//not a FlipEvasion, which does it's own safety checks
2897 && NPC->client->ps.groundEntityNum != ENTITYNUM_NONE )
2898 {//on terra firma right now
2899 if ( NPC->client->ps.velocity[2] > 0
2900 || NPC->client->ps.forceJumpCharge
2901 || cmd->upmove > 0 )
2902 {//going to jump
2903 if ( !NAV_MoveDirSafe( NPC, cmd, NPC->client->ps.speed*10.0f ) )
2904 {//we can't jump in the dir we're pushing in
2905 //cancel the evasion
2906 NPC->client->ps.velocity[2] = NPC->client->ps.forceJumpCharge = 0;
2907 cmd->upmove = 0;
2908 if ( d_JediAI->integer )
2909 {
2910 Com_Printf( S_COLOR_RED"jump not safe, cancelling!" );
2911 }
2912 }
2913 else if ( NPC->client->ps.velocity[0] || NPC->client->ps.velocity[1] )
2914 {//sliding
2915 vec3_t jumpDir;
2916 float jumpDist = VectorNormalize2( NPC->client->ps.velocity, jumpDir );
2917 if ( !NAV_DirSafe( NPC, jumpDir, jumpDist ) )
2918 {//this jump combined with our momentum would send us into a do not enter brush, so cancel it
2919 //cancel the evasion
2920 NPC->client->ps.velocity[2] = NPC->client->ps.forceJumpCharge = 0;
2921 cmd->upmove = 0;
2922 if ( d_JediAI->integer )
2923 {
2924 Com_Printf( S_COLOR_RED"jump not safe, cancelling!\n" );
2925 }
2926 }
2927 }
2928 if ( d_JediAI->integer )
2929 {
2930 Com_Printf( S_COLOR_GREEN"jump checked, is safe\n" );
2931 }
2932 }
2933 }
2934 }
2935 /*
2936 -------------------------
2937 Jedi_SaberBlock
2938
2939 Pick proper block anim
2940
2941 FIXME: Based on difficulty level/enemy saber combat skill, make this decision-making more/less effective
2942
2943 NOTE: always blocking projectiles in this func!
2944
2945 -------------------------
2946 */
2947 extern qboolean G_FindClosestPointOnLineSegment( const vec3_t start, const vec3_t end, const vec3_t from, vec3_t result );
Jedi_SaberBlockGo(gentity_t * self,usercmd_t * cmd,vec3_t pHitloc,vec3_t phitDir,gentity_t * incoming,float dist=0.0f)2948 evasionType_t Jedi_SaberBlockGo( gentity_t *self, usercmd_t *cmd, vec3_t pHitloc, vec3_t phitDir, gentity_t *incoming, float dist = 0.0f )
2949 {
2950 vec3_t hitloc, hitdir, diff, fwdangles={0,0,0}, right;
2951 float rightdot;
2952 float zdiff;
2953 int duckChance = 0;
2954 int dodgeAnim = -1;
2955 qboolean saberBusy = qfalse;
2956 evasionType_t evasionType = EVASION_NONE;
2957
2958 if ( !self || !self->client )
2959 {
2960 return EVASION_NONE;
2961 }
2962
2963 if ( PM_LockedAnim( self->client->ps.torsoAnim )
2964 && self->client->ps.torsoAnimTimer )
2965 {//Never interrupt these...
2966 return EVASION_NONE;
2967 }
2968 if ( PM_InSpecialJump( self->client->ps.legsAnim )
2969 && PM_SaberInSpecialAttack( self->client->ps.torsoAnim ) )
2970 {
2971 return EVASION_NONE;
2972 }
2973
2974 if ( Jedi_InNoAIAnim( self ) )
2975 {
2976 return EVASION_NONE;
2977 }
2978
2979
2980 //FIXME: if we don't have our saber in hand, pick the force throw option or a jump or strafe!
2981 //FIXME: reborn don't block enough anymore
2982 if ( !incoming )
2983 {
2984 VectorCopy( pHitloc, hitloc );
2985 VectorCopy( phitDir, hitdir );
2986 //FIXME: maybe base this on rank some? And/or g_spskill?
2987 if ( self->client->ps.saberInFlight )
2988 {//DOH! do non-saber evasion!
2989 saberBusy = qtrue;
2990 }
2991 /*
2992 else if ( Jedi_QuickReactions( self ) )
2993 {//jedi trainer and tavion are must faster at parrying and can do it whenever they like
2994 //Also, on medium, all level 3 people can parry any time and on hard, all level 2 or 3 people can parry any time
2995 }
2996 */
2997 else
2998 {
2999 saberBusy = Jedi_SaberBusy( self );
3000 }
3001 }
3002 else
3003 {
3004 if ( incoming->s.weapon == WP_SABER )
3005 {//flying lightsaber, face it!
3006 //FIXME: for this to actually work, we'd need to call update angles too?
3007 //Jedi_FaceEntity( self, incoming, qtrue );
3008 }
3009 VectorCopy( incoming->currentOrigin, hitloc );
3010 VectorNormalize2( incoming->s.pos.trDelta, hitdir );
3011 }
3012
3013 VectorSubtract( hitloc, self->client->renderInfo.eyePoint, diff );
3014 diff[2] = 0;
3015 //VectorNormalize( diff );
3016 fwdangles[1] = self->client->ps.viewangles[1];
3017 // Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
3018 AngleVectors( fwdangles, NULL, right, NULL );
3019
3020 rightdot = DotProduct(right, diff);// + Q_flrand(-0.10f,0.10f);
3021 //totalHeight = self->client->renderInfo.eyePoint[2] - self->absmin[2];
3022 zdiff = hitloc[2] - self->client->renderInfo.eyePoint[2];// + Q_irand(-6,6);
3023
3024 qboolean doDodge = qfalse;
3025 qboolean alwaysDodgeOrRoll = qfalse;
3026 if ( self->client->NPC_class == CLASS_BOBAFETT )
3027 {
3028 saberBusy = qtrue;
3029 doDodge = qtrue;
3030 alwaysDodgeOrRoll = qtrue;
3031 }
3032 else
3033 {
3034 if ( self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER )
3035 {
3036 saberBusy = qtrue;
3037 alwaysDodgeOrRoll = qtrue;
3038 }
3039 //see if we can dodge if need-be
3040 if ( (dist>16&&(Q_irand( 0, 2 )||saberBusy))
3041 || self->client->ps.saberInFlight
3042 || !self->client->ps.SaberActive()
3043 || (self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER) )
3044 {//either it will miss by a bit (and 25% chance) OR our saber is not in-hand OR saber is off
3045 if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank >= RANK_LT_JG) )
3046 {//acrobat or fencer or above
3047 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE &&//on the ground
3048 !(self->client->ps.pm_flags&PMF_DUCKED)&&cmd->upmove>=0&&TIMER_Done( self, "duck" )//not ducking
3049 && !PM_InRoll( &self->client->ps )//not rolling
3050 && !PM_InKnockDown( &self->client->ps )//not knocked down
3051 && ( self->client->ps.saberInFlight ||
3052 (self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER) ||
3053 (!PM_SaberInAttack( self->client->ps.saberMove )//not attacking
3054 && !PM_SaberInStart( self->client->ps.saberMove )//not starting an attack
3055 && !PM_SpinningSaberAnim( self->client->ps.torsoAnim )//not in a saber spin
3056 && !PM_SaberInSpecialAttack( self->client->ps.torsoAnim ))//not in a special attack
3057 )
3058 )
3059 {//need to check all these because it overrides both torso and legs with the dodge
3060 doDodge = qtrue;
3061 }
3062 }
3063 }
3064 }
3065
3066 qboolean doRoll = qfalse;
3067 if ( ( self->client->NPC_class == CLASS_BOBAFETT //boba fett
3068 || (self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER) //non-saber reborn (cultist)
3069 )
3070 && !Q_irand( 0, 2 )
3071 )
3072 {
3073 doRoll = qtrue;
3074 }
3075
3076 // Figure out what quadrant the block was in.
3077 if ( d_JediAI->integer )
3078 {
3079 gi.Printf( "(%d) evading attack from height %4.2f, zdiff: %4.2f, rightdot: %4.2f\n", level.time, hitloc[2]-self->absmin[2],zdiff,rightdot);
3080 }
3081
3082 //UL = > -1//-6
3083 //UR = > -6//-9
3084 //TOP = > +6//+4
3085 //FIXME: take FP_SABER_DEFENSE into account here somehow?
3086 if ( zdiff >= -5 )//was 0
3087 {
3088 if ( incoming || !saberBusy || alwaysDodgeOrRoll )
3089 {
3090 if ( rightdot > 12
3091 || (rightdot > 3 && zdiff < 5)
3092 || (!incoming&&fabs(hitdir[2])<0.25f) )//was normalized, 0.3
3093 {//coming from right
3094 if ( doDodge )
3095 {
3096 if ( doRoll )
3097 {//roll!
3098 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
3099 TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) );
3100 TIMER_Set( self, "strafeRight", 0 );
3101 evasionType = EVASION_DUCK;
3102 }
3103 else if ( Q_irand( 0, 1 ) )
3104 {
3105 dodgeAnim = BOTH_DODGE_FL;
3106 }
3107 else
3108 {
3109 dodgeAnim = BOTH_DODGE_BL;
3110 }
3111 }
3112 else
3113 {
3114 self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
3115 evasionType = EVASION_PARRY;
3116 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
3117 {
3118 if ( zdiff > 5 )
3119 {
3120 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
3121 evasionType = EVASION_DUCK_PARRY;
3122 if ( d_JediAI->integer )
3123 {
3124 gi.Printf( "duck " );
3125 }
3126 }
3127 else
3128 {
3129 duckChance = 6;
3130 }
3131 }
3132 }
3133 if ( d_JediAI->integer )
3134 {
3135 gi.Printf( "UR block\n" );
3136 }
3137 }
3138 else if ( rightdot < -12
3139 || (rightdot < -3 && zdiff < 5)
3140 || (!incoming&&fabs(hitdir[2])<0.25f) )//was normalized, -0.3
3141 {//coming from left
3142 if ( doDodge )
3143 {
3144 if ( doRoll )
3145 {
3146 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
3147 TIMER_Start( self, "strafeRight", Q_irand( 500, 1500 ) );
3148 TIMER_Set( self, "strafeLeft", 0 );
3149 evasionType = EVASION_DUCK;
3150 }
3151 else if ( Q_irand( 0, 1 ) )
3152 {
3153 dodgeAnim = BOTH_DODGE_FR;
3154 }
3155 else
3156 {
3157 dodgeAnim = BOTH_DODGE_BR;
3158 }
3159 }
3160 else
3161 {
3162 self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
3163 evasionType = EVASION_PARRY;
3164 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
3165 {
3166 if ( zdiff > 5 )
3167 {
3168 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
3169 evasionType = EVASION_DUCK_PARRY;
3170 if ( d_JediAI->integer )
3171 {
3172 gi.Printf( "duck " );
3173 }
3174 }
3175 else
3176 {
3177 duckChance = 6;
3178 }
3179 }
3180 }
3181 if ( d_JediAI->integer )
3182 {
3183 gi.Printf( "UL block\n" );
3184 }
3185 }
3186 else
3187 {
3188 self->client->ps.saberBlocked = BLOCKED_TOP;
3189 evasionType = EVASION_PARRY;
3190 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
3191 {
3192 duckChance = 4;
3193 }
3194 if ( d_JediAI->integer )
3195 {
3196 gi.Printf( "TOP block\n" );
3197 }
3198 }
3199 }
3200 else
3201 {
3202 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
3203 {
3204 //duckChance = 2;
3205 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
3206 evasionType = EVASION_DUCK;
3207 if ( d_JediAI->integer )
3208 {
3209 gi.Printf( "duck " );
3210 }
3211 }
3212 }
3213 }
3214 //LL = -22//= -18 to -39
3215 //LR = -23//= -20 to -41
3216 else if ( zdiff > -22 )//was-15 )
3217 {
3218 if ( 1 )//zdiff < -10 )
3219 {//hmm, pretty low, but not low enough to use the low block, so we need to duck
3220 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
3221 {
3222 //duckChance = 2;
3223 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
3224 evasionType = EVASION_DUCK;
3225 if ( d_JediAI->integer )
3226 {
3227 gi.Printf( "duck " );
3228 }
3229 }
3230 else
3231 {//in air! Ducking does no good
3232 }
3233 }
3234 if ( incoming || !saberBusy || alwaysDodgeOrRoll )
3235 {
3236 if ( rightdot > 8 || (rightdot > 3 && zdiff < -11) )//was normalized, 0.2
3237 {
3238 if ( doDodge )
3239 {
3240 if ( doRoll )
3241 {//roll!
3242 TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) );
3243 TIMER_Set( self, "strafeRight", 0 );
3244 }
3245 else
3246 {
3247 dodgeAnim = BOTH_DODGE_L;
3248 }
3249 }
3250 else
3251 {
3252 self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
3253 if ( evasionType == EVASION_DUCK )
3254 {
3255 evasionType = EVASION_DUCK_PARRY;
3256 }
3257 else
3258 {
3259 evasionType = EVASION_PARRY;
3260 }
3261 }
3262 if ( d_JediAI->integer )
3263 {
3264 gi.Printf( "mid-UR block\n" );
3265 }
3266 }
3267 else if ( rightdot < -8 || (rightdot < -3 && zdiff < -11) )//was normalized, -0.2
3268 {
3269 if ( doDodge )
3270 {
3271 if ( doRoll )
3272 {//roll!
3273 TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) );
3274 TIMER_Set( self, "strafeRight", 0 );
3275 }
3276 else
3277 {
3278 dodgeAnim = BOTH_DODGE_R;
3279 }
3280 }
3281 else
3282 {
3283 self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
3284 if ( evasionType == EVASION_DUCK )
3285 {
3286 evasionType = EVASION_DUCK_PARRY;
3287 }
3288 else
3289 {
3290 evasionType = EVASION_PARRY;
3291 }
3292 }
3293 if ( d_JediAI->integer )
3294 {
3295 gi.Printf( "mid-UL block\n" );
3296 }
3297 }
3298 else
3299 {
3300 self->client->ps.saberBlocked = BLOCKED_TOP;
3301 if ( evasionType == EVASION_DUCK )
3302 {
3303 evasionType = EVASION_DUCK_PARRY;
3304 }
3305 else
3306 {
3307 evasionType = EVASION_PARRY;
3308 }
3309 if ( d_JediAI->integer )
3310 {
3311 gi.Printf( "mid-TOP block\n" );
3312 }
3313 }
3314 }
3315 }
3316 else
3317 {
3318 if ( saberBusy || (zdiff < -36 && ( zdiff < -44 || !Q_irand( 0, 2 ) ) ) )//was -30 and -40//2nd one was -46
3319 {//jump!
3320 if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE )
3321 {//already in air, duck to pull up legs
3322 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
3323 evasionType = EVASION_DUCK;
3324 if ( d_JediAI->integer )
3325 {
3326 gi.Printf( "legs up\n" );
3327 }
3328 if ( incoming || !saberBusy )
3329 {
3330 //since the jump may be cleared if not safe, set a lower block too
3331 if ( rightdot >= 0 )
3332 {
3333 self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
3334 evasionType = EVASION_DUCK_PARRY;
3335 if ( d_JediAI->integer )
3336 {
3337 gi.Printf( "LR block\n" );
3338 }
3339 }
3340 else
3341 {
3342 self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
3343 evasionType = EVASION_DUCK_PARRY;
3344 if ( d_JediAI->integer )
3345 {
3346 gi.Printf( "LL block\n" );
3347 }
3348 }
3349 }
3350 }
3351 else
3352 {//gotta jump!
3353 if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank > RANK_LT_JG ) &&
3354 (!Q_irand( 0, 10 ) || (!Q_irand( 0, 2 ) && (cmd->forwardmove || cmd->rightmove))) )
3355 {//superjump
3356 //FIXME: check the jump, if can't, then block
3357 if ( self->NPC
3358 && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS)
3359 && self->client->ps.forceRageRecoveryTime < level.time
3360 && !(self->client->ps.forcePowersActive&(1<<FP_RAGE))
3361 && !PM_InKnockDown( &self->client->ps ) )
3362 {
3363 self->client->ps.forceJumpCharge = 320;//FIXME: calc this intelligently
3364 evasionType = EVASION_FJUMP;
3365 if ( d_JediAI->integer )
3366 {
3367 gi.Printf( "force jump + " );
3368 }
3369 }
3370 }
3371 else
3372 {//normal jump
3373 //FIXME: check the jump, if can't, then block
3374 if ( self->NPC
3375 && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS)
3376 && self->client->ps.forceRageRecoveryTime < level.time
3377 && !(self->client->ps.forcePowersActive&(1<<FP_RAGE)) )
3378 {
3379 if ( (self->client->NPC_class == CLASS_BOBAFETT
3380 || (self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER)
3381 )
3382 && !Q_irand( 0, 1 ) )
3383 {//flip!
3384 if ( rightdot > 0 )
3385 {
3386 TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) );
3387 TIMER_Set( self, "strafeRight", 0 );
3388 TIMER_Set( self, "walking", 0 );
3389 }
3390 else
3391 {
3392 TIMER_Start( self, "strafeRight", Q_irand( 500, 1500 ) );
3393 TIMER_Set( self, "strafeLeft", 0 );
3394 TIMER_Set( self, "walking", 0 );
3395 }
3396 }
3397 else
3398 {
3399 if ( self == NPC )
3400 {
3401 cmd->upmove = 127;
3402 }
3403 else
3404 {
3405 self->client->ps.velocity[2] = JUMP_VELOCITY;
3406 }
3407 }
3408 evasionType = EVASION_JUMP;
3409 if ( d_JediAI->integer )
3410 {
3411 gi.Printf( "jump + " );
3412 }
3413 }
3414 if ( self->client->NPC_class == CLASS_ALORA
3415 || self->client->NPC_class == CLASS_SHADOWTROOPER
3416 || self->client->NPC_class == CLASS_TAVION )
3417 {
3418 if ( !incoming
3419 && self->client->ps.groundEntityNum < ENTITYNUM_NONE
3420 && !Q_irand( 0, 2 ) )
3421 {
3422 if ( !PM_SaberInAttack( self->client->ps.saberMove )
3423 && !PM_SaberInStart( self->client->ps.saberMove )
3424 && !PM_InRoll( &self->client->ps )
3425 && !PM_InKnockDown( &self->client->ps )
3426 && !PM_SaberInSpecialAttack( self->client->ps.torsoAnim ) )
3427 {//do the butterfly!
3428 int butterflyAnim;
3429 if ( self->client->NPC_class == CLASS_ALORA
3430 && !Q_irand( 0, 2 ) )
3431 {
3432 butterflyAnim = BOTH_ALORA_SPIN;
3433 }
3434 else if ( Q_irand( 0, 1 ) )
3435 {
3436 butterflyAnim = BOTH_BUTTERFLY_LEFT;
3437 }
3438 else
3439 {
3440 butterflyAnim = BOTH_BUTTERFLY_RIGHT;
3441 }
3442 evasionType = EVASION_CARTWHEEL;
3443 NPC_SetAnim( self, SETANIM_BOTH, butterflyAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
3444 self->client->ps.velocity[2] = 225;
3445 self->client->ps.forceJumpZStart = self->currentOrigin[2];//so we don't take damage if we land at same height
3446 self->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL;
3447 self->client->ps.SaberActivateTrail( 300 );//FIXME: reset this when done!
3448 /*
3449 if ( self->client->NPC_class == CLASS_BOBAFETT
3450 || (self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER) )
3451 {
3452 G_AddEvent( self, EV_JUMP, 0 );
3453 }
3454 else
3455 */
3456 {
3457 G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" );
3458 }
3459 cmd->upmove = 0;
3460 saberBusy = qtrue;
3461 }
3462 }
3463 }
3464 }
3465 if ( ((evasionType = Jedi_CheckFlipEvasions( self, rightdot, zdiff ))!=EVASION_NONE) )
3466 {
3467 if ( d_slowmodeath->integer > 5 && self->enemy && !self->enemy->s.number )
3468 {
3469 G_StartMatrixEffect( self );
3470 }
3471 saberBusy = qtrue;
3472 }
3473 else if ( incoming || !saberBusy )
3474 {
3475 //since the jump may be cleared if not safe, set a lower block too
3476 if ( rightdot >= 0 )
3477 {
3478 self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
3479 if ( evasionType == EVASION_JUMP )
3480 {
3481 evasionType = EVASION_JUMP_PARRY;
3482 }
3483 else if ( evasionType == EVASION_NONE )
3484 {
3485 evasionType = EVASION_PARRY;
3486 }
3487 if ( d_JediAI->integer )
3488 {
3489 gi.Printf( "LR block\n" );
3490 }
3491 }
3492 else
3493 {
3494 self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
3495 if ( evasionType == EVASION_JUMP )
3496 {
3497 evasionType = EVASION_JUMP_PARRY;
3498 }
3499 else if ( evasionType == EVASION_NONE )
3500 {
3501 evasionType = EVASION_PARRY;
3502 }
3503 if ( d_JediAI->integer )
3504 {
3505 gi.Printf( "LL block\n" );
3506 }
3507 }
3508 }
3509 }
3510 }
3511 else
3512 {
3513 if ( incoming || !saberBusy )
3514 {
3515 if ( rightdot >= 0 )
3516 {
3517 self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
3518 evasionType = EVASION_PARRY;
3519 if ( d_JediAI->integer )
3520 {
3521 gi.Printf( "LR block\n" );
3522 }
3523 }
3524 else
3525 {
3526 self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
3527 evasionType = EVASION_PARRY;
3528 if ( d_JediAI->integer )
3529 {
3530 gi.Printf( "LL block\n" );
3531 }
3532 }
3533 if ( incoming && incoming->s.weapon == WP_SABER )
3534 {//thrown saber!
3535 if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank > RANK_LT_JG ) &&
3536 (!Q_irand( 0, 10 ) || (!Q_irand( 0, 2 ) && (cmd->forwardmove || cmd->rightmove))) )
3537 {//superjump
3538 //FIXME: check the jump, if can't, then block
3539 if ( self->NPC
3540 && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS)
3541 && self->client->ps.forceRageRecoveryTime < level.time
3542 && !(self->client->ps.forcePowersActive&(1<<FP_RAGE))
3543 && !PM_InKnockDown( &self->client->ps ) )
3544 {
3545 self->client->ps.forceJumpCharge = 320;//FIXME: calc this intelligently
3546 evasionType = EVASION_FJUMP;
3547 if ( d_JediAI->integer )
3548 {
3549 gi.Printf( "force jump + " );
3550 }
3551 }
3552 }
3553 else
3554 {//normal jump
3555 //FIXME: check the jump, if can't, then block
3556 if ( self->NPC
3557 && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS)
3558 && self->client->ps.forceRageRecoveryTime < level.time
3559 && !(self->client->ps.forcePowersActive&(1<<FP_RAGE)))
3560 {
3561 if ( self == NPC )
3562 {
3563 cmd->upmove = 127;
3564 }
3565 else
3566 {
3567 self->client->ps.velocity[2] = JUMP_VELOCITY;
3568 }
3569 evasionType = EVASION_JUMP_PARRY;
3570 if ( d_JediAI->integer )
3571 {
3572 gi.Printf( "jump + " );
3573 }
3574 }
3575 }
3576 }
3577 }
3578 }
3579 }
3580 if ( evasionType == EVASION_NONE )
3581 {
3582 return EVASION_NONE;
3583 }
3584 //=======================================================================================
3585 //see if it's okay to jump
3586 Jedi_CheckJumpEvasionSafety( self, cmd, evasionType );
3587 //=======================================================================================
3588 //stop taunting
3589 TIMER_Set( self, "taunting", 0 );
3590 //stop gripping
3591 TIMER_Set( self, "gripping", -level.time );
3592 WP_ForcePowerStop( self, FP_GRIP );
3593 //stop draining
3594 TIMER_Set( self, "draining", -level.time );
3595 WP_ForcePowerStop( self, FP_DRAIN );
3596
3597 if ( dodgeAnim != -1 )
3598 {//dodged
3599 evasionType = EVASION_DODGE;
3600 NPC_SetAnim( self, SETANIM_BOTH, dodgeAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
3601 self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
3602 //force them to stop moving in this case
3603 self->client->ps.pm_time = self->client->ps.torsoAnimTimer;
3604 //FIXME: maybe make a sound? Like a grunt? EV_JUMP?
3605 self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
3606 //dodged, not block
3607 if ( d_slowmodeath->integer > 5 && self->enemy && !self->enemy->s.number )
3608 {
3609 G_StartMatrixEffect( self );
3610 }
3611 }
3612 else
3613 {
3614 if ( duckChance )
3615 {
3616 if ( !Q_irand( 0, duckChance ) )
3617 {
3618 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
3619 if ( evasionType == EVASION_PARRY )
3620 {
3621 evasionType = EVASION_DUCK_PARRY;
3622 }
3623 else
3624 {
3625 evasionType = EVASION_DUCK;
3626 }
3627 /*
3628 if ( d_JediAI->integer )
3629 {
3630 gi.Printf( "duck " );
3631 }
3632 */
3633 }
3634 }
3635
3636 if ( incoming )
3637 {
3638 self->client->ps.saberBlocked = WP_MissileBlockForBlock( self->client->ps.saberBlocked );
3639 }
3640
3641 }
3642 //if ( self->client->ps.saberBlocked != BLOCKED_NONE )
3643 {
3644 int parryReCalcTime = Jedi_ReCalcParryTime( self, evasionType );
3645 if ( self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] < level.time + parryReCalcTime )
3646 {
3647 self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + parryReCalcTime;
3648 }
3649 }
3650 return evasionType;
3651 }
3652
Jedi_CheckEvadeSpecialAttacks(void)3653 static evasionType_t Jedi_CheckEvadeSpecialAttacks( void )
3654 {
3655 if ( !NPC
3656 || !NPC->client )
3657 {
3658 return EVASION_NONE;
3659 }
3660
3661 if ( !NPC->enemy
3662 || NPC->enemy->health <= 0
3663 || !NPC->enemy->client )
3664 {//don't keep blocking him once he's dead (or if not a client)
3665 return EVASION_NONE;
3666 }
3667
3668 if ( NPC->enemy->s.number >= MAX_CLIENTS )
3669 {//only do these against player
3670 return EVASION_NONE;
3671 }
3672
3673 if ( !TIMER_Done( NPC, "specialEvasion" ) )
3674 {//still evading from last time
3675 return EVASION_NONE;
3676 }
3677
3678 if ( NPC->enemy->client->ps.torsoAnim == BOTH_SPINATTACK6
3679 || NPC->enemy->client->ps.torsoAnim == BOTH_SPINATTACK7 )
3680 {//back away from these
3681 if ( (NPCInfo->aiFlags&NPCAI_BOSS_CHARACTER)
3682 || NPC->client->NPC_class == CLASS_SHADOWTROOPER
3683 || NPC->client->NPC_class == CLASS_ALORA
3684 || Q_irand( 0, NPCInfo->rank ) > RANK_LT_JG )
3685 {//see if we should back off
3686 if ( InFront( NPC->currentOrigin, NPC->enemy->currentOrigin, NPC->enemy->currentAngles ) )
3687 {//facing me
3688 float minSafeDistSq = (NPC->maxs[0]*1.5f+NPC->enemy->maxs[0]*1.5f+NPC->enemy->client->ps.SaberLength()+24.0f);
3689 minSafeDistSq *= minSafeDistSq;
3690 if ( DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ) < minSafeDistSq )
3691 {//back off!
3692 Jedi_StartBackOff();
3693 return EVASION_OTHER;
3694 }
3695 }
3696 }
3697 }
3698 else
3699 {//check some other attacks?
3700 //check roll-stab
3701 if ( NPC->enemy->client->ps.torsoAnim == BOTH_ROLL_STAB
3702 || (NPC->enemy->client->ps.torsoAnim == BOTH_ROLL_F && ((NPC->enemy->client->pers.lastCommand.buttons&BUTTON_ATTACK)||(NPC->enemy->client->ps.pm_flags&PMF_ATTACK_HELD)) ) )
3703 {//either already in a roll-stab or may go into one
3704 if ( (NPCInfo->aiFlags&NPCAI_BOSS_CHARACTER)
3705 || NPC->client->NPC_class == CLASS_SHADOWTROOPER
3706 || NPC->client->NPC_class == CLASS_ALORA
3707 || Q_irand( -3, NPCInfo->rank ) > RANK_LT_JG )
3708 {//see if we should evade
3709 vec3_t yawOnlyAngles = {0, NPC->enemy->currentAngles[YAW], 0 };
3710 if ( InFront( NPC->currentOrigin, NPC->enemy->currentOrigin, yawOnlyAngles, 0.25f ) )
3711 {//facing me
3712 float minSafeDistSq = (NPC->maxs[0]*1.5f+NPC->enemy->maxs[0]*1.5f+NPC->enemy->client->ps.SaberLength()+24.0f);
3713 minSafeDistSq *= minSafeDistSq;
3714 float distSq = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin );
3715 if ( distSq < minSafeDistSq )
3716 {//evade!
3717 qboolean doJump = (qboolean)( NPC->enemy->client->ps.torsoAnim == BOTH_ROLL_STAB || distSq < 3000.0f );//not much time left, just jump!
3718 if ( (NPCInfo->scriptFlags&SCF_NO_ACROBATICS)
3719 || !doJump )
3720 {//roll?
3721 vec3_t enemyRight, dir2Me;
3722
3723 AngleVectors( yawOnlyAngles, NULL, enemyRight, NULL );
3724 VectorSubtract( NPC->currentOrigin, NPC->enemy->currentOrigin, dir2Me );
3725 VectorNormalize( dir2Me );
3726 float dot = DotProduct( enemyRight, dir2Me );
3727
3728 ucmd.forwardmove = 0;
3729 TIMER_Start( NPC, "duck", Q_irand( 500, 1500 ) );
3730 ucmd.upmove = -127;
3731 //NOTE: this *assumes* I'm facing him!
3732 if ( dot > 0 )
3733 {//I'm to his right
3734 if ( !NPC_MoveDirClear( 0, -127, qfalse ) )
3735 {//fuck, jump instead
3736 doJump = qtrue;
3737 }
3738 else
3739 {
3740 TIMER_Start( NPC, "strafeLeft", Q_irand( 500, 1500 ) );
3741 TIMER_Set( NPC, "strafeRight", 0 );
3742 ucmd.rightmove = -127;
3743 if ( d_JediAI->integer )
3744 {
3745 Com_Printf( "%s rolling left from roll-stab!\n", NPC->NPC_type );
3746 }
3747 if ( NPC->client->ps.groundEntityNum != ENTITYNUM_NONE )
3748 {//fuck it, just force it
3749 NPC_SetAnim(NPC,SETANIM_BOTH,BOTH_ROLL_L,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
3750 G_AddEvent( NPC, EV_ROLL, 0 );
3751 NPC->client->ps.saberMove = LS_NONE;
3752 }
3753 }
3754 }
3755 else
3756 {//I'm to his left
3757 if ( !NPC_MoveDirClear( 0, 127, qfalse ) )
3758 {//fuck, jump instead
3759 doJump = qtrue;
3760 }
3761 else
3762 {
3763 TIMER_Start( NPC, "strafeRight", Q_irand( 500, 1500 ) );
3764 TIMER_Set( NPC, "strafeLeft", 0 );
3765 ucmd.rightmove = 127;
3766 if ( d_JediAI->integer )
3767 {
3768 Com_Printf( "%s rolling right from roll-stab!\n", NPC->NPC_type );
3769 }
3770 if ( NPC->client->ps.groundEntityNum != ENTITYNUM_NONE )
3771 {//fuck it, just force it
3772 NPC_SetAnim(NPC,SETANIM_BOTH,BOTH_ROLL_R,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
3773 G_AddEvent( NPC, EV_ROLL, 0 );
3774 NPC->client->ps.saberMove = LS_NONE;
3775 }
3776 }
3777 }
3778 if ( !doJump )
3779 {
3780 TIMER_Set( NPC, "specialEvasion", 3000 );
3781 return EVASION_DUCK;
3782 }
3783 }
3784 //didn't roll, do jump
3785 if ( NPC->s.weapon != WP_SABER
3786 || (NPCInfo->aiFlags&NPCAI_BOSS_CHARACTER)
3787 || NPC->client->NPC_class == CLASS_SHADOWTROOPER
3788 || NPC->client->NPC_class == CLASS_ALORA
3789 || Q_irand( -3, NPCInfo->rank ) > RANK_CREWMAN )
3790 {//superjump
3791 NPC->client->ps.forceJumpCharge = 320;//FIXME: calc this intelligently
3792 if ( Q_irand( 0, 2 ) )
3793 {//make it a backflip
3794 ucmd.forwardmove = -127;
3795 TIMER_Set( NPC, "roamTime", -level.time );
3796 TIMER_Set( NPC, "strafeLeft", -level.time );
3797 TIMER_Set( NPC, "strafeRight", -level.time );
3798 TIMER_Set( NPC, "walking", -level.time );
3799 TIMER_Set( NPC, "moveforward", -level.time );
3800 TIMER_Set( NPC, "movenone", -level.time );
3801 TIMER_Set( NPC, "moveright", -level.time );
3802 TIMER_Set( NPC, "moveleft", -level.time );
3803 TIMER_Set( NPC, "movecenter", -level.time );
3804 TIMER_Set( NPC, "moveback", Q_irand( 500, 1000 ) );
3805 if ( d_JediAI->integer )
3806 {
3807 Com_Printf( "%s backflipping from roll-stab!\n", NPC->NPC_type );
3808 }
3809 }
3810 else
3811 {
3812 if ( d_JediAI->integer )
3813 {
3814 Com_Printf( "%s force-jumping over roll-stab!\n", NPC->NPC_type );
3815 }
3816 }
3817 TIMER_Set( NPC, "specialEvasion", 3000 );
3818 return EVASION_FJUMP;
3819 }
3820 else
3821 {//normal jump
3822 ucmd.upmove = 127;
3823 if ( d_JediAI->integer )
3824 {
3825 Com_Printf( "%s jumping over roll-stab!\n", NPC->NPC_type );
3826 }
3827 TIMER_Set( NPC, "specialEvasion", 2000 );
3828 return EVASION_JUMP;
3829 }
3830 }
3831 }
3832 }
3833 }
3834 }
3835 return EVASION_NONE;
3836 }
3837
3838 extern int WPDEBUG_SaberColor( saber_colors_t saberColor );
Jedi_SaberBlock(void)3839 static qboolean Jedi_SaberBlock( void )
3840 {
3841 vec3_t hitloc, saberTipOld, saberTip, top, bottom, axisPoint, saberPoint, dir;//saberBase,
3842 vec3_t pointDir, baseDir, tipDir, saberHitPoint, saberMins={-4,-4,-4}, saberMaxs={4,4,4};
3843 float pointDist, baseDirPerc;
3844 float dist, bestDist = Q3_INFINITE;
3845 int saberNum = 0, bladeNum = 0;
3846 int closestSaberNum = 0, closestBladeNum = 0;
3847
3848 //FIXME: reborn don't block enough anymore
3849 /*
3850 //maybe do this on easy only... or only on grunt-level reborn
3851 if ( NPC->client->ps.weaponTime )
3852 {//i'm attacking right now
3853 return qfalse;
3854 }
3855 */
3856
3857 if ( !TIMER_Done( NPC, "parryReCalcTime" ) )
3858 {//can't do our own re-think of which parry to use yet
3859 return qfalse;
3860 }
3861
3862 if ( NPC->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] > level.time )
3863 {//can't move the saber to another position yet
3864 return qfalse;
3865 }
3866
3867 /*
3868 if ( NPCInfo->rank < RANK_LT_JG && Q_irand( 0, (2 - g_spskill->integer) ) )
3869 {//lower rank reborn have a random chance of not doing it at all
3870 NPC->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 300;
3871 return qfalse;
3872 }
3873 */
3874
3875 if ( NPC->enemy->health <= 0 || !NPC->enemy->client )
3876 {//don't keep blocking him once he's dead (or if not a client)
3877 return qfalse;
3878 }
3879 /*
3880 //VectorMA( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDir, saberTip );
3881 //VectorMA( NPC->enemy->client->renderInfo.muzzlePointNext, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDirNext, saberTipNext );
3882 VectorMA( NPC->enemy->client->renderInfo.muzzlePointOld, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDirOld, saberTipOld );
3883 VectorMA( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDir, saberTip );
3884
3885 VectorSubtract( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->renderInfo.muzzlePointOld, dir );//get the dir
3886 VectorAdd( dir, NPC->enemy->client->renderInfo.muzzlePoint, saberBase );//extrapolate
3887
3888 VectorSubtract( saberTip, saberTipOld, dir );//get the dir
3889 VectorAdd( dir, saberTip, saberTipOld );//extrapolate
3890
3891 VectorCopy( NPC->currentOrigin, top );
3892 top[2] = NPC->absmax[2];
3893 VectorCopy( NPC->currentOrigin, bottom );
3894 bottom[2] = NPC->absmin[2];
3895
3896 float dist = ShortestLineSegBewteen2LineSegs( saberBase, saberTipOld, bottom, top, saberPoint, axisPoint );
3897 if ( 0 )//dist > NPC->maxs[0]*4 )//was *3
3898 {//FIXME: sometimes he reacts when you're too far away to actually hit him
3899 if ( d_JediAI->integer )
3900 {
3901 gi.Printf( "enemy saber dist: %4.2f\n", dist );
3902 }
3903 TIMER_Set( NPC, "parryTime", -1 );
3904 return qfalse;
3905 }
3906
3907 //get the actual point of impact
3908 trace_t tr;
3909 gi.trace( &tr, saberPoint, vec3_origin, vec3_origin, axisPoint, NPC->enemy->s.number, MASK_SHOT, G2_RETURNONHIT, 10 );
3910 if ( tr.allsolid || tr.startsolid )
3911 {//estimate
3912 VectorSubtract( saberPoint, axisPoint, dir );
3913 VectorNormalize( dir );
3914 VectorMA( axisPoint, NPC->maxs[0]*1.22, dir, hitloc );
3915 }
3916 else
3917 {
3918 VectorCopy( tr.endpos, hitloc );
3919 }
3920 */
3921
3922 //FIXME: need to check against both sabers/blades now!...?
3923
3924 for ( saberNum = 0; saberNum < MAX_SABERS; saberNum++ )
3925 {
3926 for ( bladeNum = 0; bladeNum < NPC->enemy->client->ps.saber[saberNum].numBlades; bladeNum++ )
3927 {
3928 if ( NPC->enemy->client->ps.saber[saberNum].type != SABER_NONE
3929 && NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].length > 0 )
3930 {//valid saber and this blade is on
3931 VectorMA( NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld, NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].length, NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld, saberTipOld );
3932 VectorMA( NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].length, NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, saberTip );
3933
3934 VectorCopy( NPC->currentOrigin, top );
3935 top[2] = NPC->absmax[2];
3936 VectorCopy( NPC->currentOrigin, bottom );
3937 bottom[2] = NPC->absmin[2];
3938
3939 dist = ShortestLineSegBewteen2LineSegs( NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, saberTip, bottom, top, saberPoint, axisPoint );
3940 if ( dist < bestDist )
3941 {
3942 bestDist = dist;
3943 closestSaberNum = saberNum;
3944 closestBladeNum = bladeNum;
3945 }
3946 }
3947 }
3948 }
3949
3950 if ( bestDist > NPC->maxs[0]*5 )//was *3
3951 {//FIXME: sometimes he reacts when you're too far away to actually hit him
3952 if ( d_JediAI->integer )
3953 {
3954 Com_Printf( S_COLOR_RED"enemy saber dist: %4.2f\n", bestDist );
3955 }
3956 /*
3957 if ( bestDist < 300 //close
3958 && !Jedi_QuickReactions( NPC )//quick reaction people can interrupt themselves
3959 && (PM_SaberInStart( NPC->enemy->client->ps.saberMove ) || BG_SaberInAttack( NPC->enemy->client->ps.saberMove )) )//enemy is swinging at me
3960 {//he's swinging at me and close enough to be a threat, don't start an attack right now
3961 TIMER_Set( NPC, "parryTime", 100 );
3962 }
3963 else
3964 */
3965 {
3966 TIMER_Set( NPC, "parryTime", -1 );
3967 }
3968 return qfalse;
3969 }
3970
3971 dist = bestDist;
3972
3973 if ( d_JediAI->integer )
3974 {
3975 Com_Printf( S_COLOR_GREEN"enemy saber dist: %4.2f\n", dist );
3976 }
3977
3978 //now use the closest blade for my evasion check
3979 VectorMA( NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].muzzlePointOld, NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].length, NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].muzzleDirOld, saberTipOld );
3980 VectorMA( NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].muzzlePoint, NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].length, NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].muzzleDir, saberTip );
3981
3982 VectorCopy( NPC->currentOrigin, top );
3983 top[2] = NPC->absmax[2];
3984 VectorCopy( NPC->currentOrigin, bottom );
3985 bottom[2] = NPC->absmin[2];
3986
3987 dist = ShortestLineSegBewteen2LineSegs( NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].muzzlePoint, saberTip, bottom, top, saberPoint, axisPoint );
3988 VectorSubtract( saberPoint, NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].muzzlePoint, pointDir );
3989 pointDist = VectorLength( pointDir );
3990
3991 if ( NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].length <= 0 )
3992 {
3993 baseDirPerc = 0.5f;
3994 }
3995 else
3996 {
3997 baseDirPerc = pointDist/NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].length;
3998 }
3999 VectorSubtract( NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].muzzlePoint, NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].muzzlePointOld, baseDir );
4000 VectorSubtract( saberTip, saberTipOld, tipDir );
4001 VectorScale( baseDir, baseDirPerc, baseDir );
4002 VectorMA( baseDir, 1.0f-baseDirPerc, tipDir, dir );
4003 VectorMA( saberPoint, 200, dir, hitloc );
4004
4005 //get the actual point of impact
4006 trace_t tr;
4007 gi.trace( &tr, saberPoint, saberMins, saberMaxs, hitloc, NPC->enemy->s.number, CONTENTS_BODY, (EG2_Collision)0, 0 );//, G2_RETURNONHIT, 10 );
4008 if ( tr.allsolid || tr.startsolid || tr.fraction >= 1.0f )
4009 {//estimate
4010 vec3_t dir2Me;
4011 VectorSubtract( axisPoint, saberPoint, dir2Me );
4012 dist = VectorNormalize( dir2Me );
4013 if ( DotProduct( dir, dir2Me ) < 0.2f )
4014 {//saber is not swinging in my direction
4015 /*
4016 if ( dist < 300 //close
4017 && !Jedi_QuickReactions( NPC )//quick reaction people can interrupt themselves
4018 && (PM_SaberInStart( NPC->enemy->client->ps.saberMove ) || PM_SaberInAttack( NPC->enemy->client->ps.saberMove )) )//enemy is swinging at me
4019 {//he's swinging at me and close enough to be a threat, don't start an attack right now
4020 TIMER_Set( NPC, "parryTime", 100 );
4021 }
4022 else
4023 */
4024 {
4025 TIMER_Set( NPC, "parryTime", -1 );
4026 }
4027 return qfalse;
4028 }
4029 ShortestLineSegBewteen2LineSegs( saberPoint, hitloc, bottom, top, saberHitPoint, hitloc );
4030 /*
4031 VectorSubtract( saberPoint, axisPoint, dir );
4032 VectorNormalize( dir );
4033 VectorMA( axisPoint, NPC->maxs[0]*1.22, dir, hitloc );
4034 */
4035 }
4036 else
4037 {
4038 VectorCopy( tr.endpos, hitloc );
4039 }
4040
4041 if ( d_JediAI->integer )
4042 {
4043 G_DebugLine( saberPoint, hitloc, FRAMETIME, WPDEBUG_SaberColor( NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].color ), qtrue );
4044 }
4045
4046 //FIXME: if saber is off and/or we have force speed and want to be really cocky,
4047 // and the swing misses by some amount, we can use the dodges here... :)
4048 evasionType_t evasionType;
4049 if ( (evasionType=Jedi_SaberBlockGo( NPC, &ucmd, hitloc, dir, NULL, dist )) != EVASION_NONE )
4050 {//did some sort of evasion
4051 if ( evasionType != EVASION_DODGE )
4052 {//(not dodge)
4053 if ( !NPC->client->ps.saberInFlight )
4054 {//make sure saber is on
4055 NPC->client->ps.SaberActivate();
4056 }
4057
4058 //debounce our parry recalc time
4059 int parryReCalcTime = Jedi_ReCalcParryTime( NPC, evasionType );
4060 TIMER_Set( NPC, "parryReCalcTime", Q_irand( 0, parryReCalcTime ) );
4061 if ( d_JediAI->integer )
4062 {
4063 gi.Printf( "Keep parry choice until: %d\n", level.time + parryReCalcTime );
4064 }
4065
4066 //determine how long to hold this anim
4067 if ( TIMER_Done( NPC, "parryTime" ) )
4068 {
4069 if ( NPC->client->NPC_class == CLASS_TAVION
4070 || NPC->client->NPC_class == CLASS_SHADOWTROOPER
4071 || NPC->client->NPC_class == CLASS_ALORA )
4072 {
4073 TIMER_Set( NPC, "parryTime", Q_irand( parryReCalcTime/2, parryReCalcTime*1.5 ) );
4074 }
4075 else if ( NPCInfo->rank >= RANK_LT_JG )
4076 {//fencers and higher hold a parry less
4077 TIMER_Set( NPC, "parryTime", parryReCalcTime );
4078 }
4079 else
4080 {//others hold it longer
4081 TIMER_Set( NPC, "parryTime", Q_irand( 1, 2 )*parryReCalcTime );
4082 }
4083 }
4084 }
4085 else
4086 {//dodged
4087 int dodgeTime = NPC->client->ps.torsoAnimTimer;
4088 if ( NPCInfo->rank > RANK_LT_COMM && NPC->client->NPC_class != CLASS_DESANN )
4089 {//higher-level guys can dodge faster
4090 dodgeTime -= 200;
4091 }
4092 TIMER_Set( NPC, "parryReCalcTime", dodgeTime );
4093 TIMER_Set( NPC, "parryTime", dodgeTime );
4094 }
4095 }
4096 if ( evasionType != EVASION_DUCK_PARRY
4097 && evasionType != EVASION_JUMP_PARRY
4098 && evasionType != EVASION_JUMP
4099 && evasionType != EVASION_DUCK
4100 && evasionType != EVASION_FJUMP )
4101 {
4102 if ( Jedi_CheckEvadeSpecialAttacks() != EVASION_NONE )
4103 {//got a new evasion!
4104 //see if it's okay to jump
4105 Jedi_CheckJumpEvasionSafety( NPC, &ucmd, evasionType );
4106 }
4107 }
4108 return qtrue;
4109 }
4110 /*
4111 -------------------------
4112 Jedi_EvasionSaber
4113
4114 defend if other is using saber and attacking me!
4115 -------------------------
4116 */
Jedi_EvasionSaber(vec3_t enemy_movedir,float enemy_dist,vec3_t enemy_dir)4117 static void Jedi_EvasionSaber( vec3_t enemy_movedir, float enemy_dist, vec3_t enemy_dir )
4118 {
4119 vec3_t dirEnemy2Me;
4120 int evasionChance = 30;//only step aside 30% if he's moving at me but not attacking
4121 qboolean enemy_attacking = qfalse;
4122 qboolean throwing_saber = qfalse;
4123 qboolean shooting_lightning = qfalse;
4124
4125 if ( !NPC->enemy->client )
4126 {
4127 return;
4128 }
4129 else if ( NPC->enemy->client
4130 && NPC->enemy->s.weapon == WP_SABER
4131 && NPC->enemy->client->ps.saberLockTime > level.time )
4132 {//don't try to block/evade an enemy who is in a saberLock
4133 return;
4134 }
4135 else if ( (NPC->client->ps.saberEventFlags&SEF_LOCK_WON)
4136 && NPC->enemy->painDebounceTime > level.time )
4137 {//pressing the advantage of winning a saber lock
4138 return;
4139 }
4140
4141 if ( NPC->enemy->client->ps.saberInFlight && !TIMER_Done( NPC, "taunting" ) )
4142 {//if he's throwing his saber, stop taunting
4143 TIMER_Set( NPC, "taunting", -level.time );
4144 if ( !NPC->client->ps.saberInFlight )
4145 {
4146 NPC->client->ps.SaberActivate();
4147 }
4148 }
4149
4150 if ( TIMER_Done( NPC, "parryTime" ) )
4151 {
4152 if ( NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE &&
4153 NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
4154 {//wasn't blocked myself
4155 NPC->client->ps.saberBlocked = BLOCKED_NONE;
4156 }
4157 }
4158
4159 if ( NPC->enemy->client->ps.weaponTime && NPC->enemy->client->ps.weaponstate == WEAPON_FIRING )
4160 {
4161 if ( (!NPC->client->ps.saberInFlight || (NPC->client->ps.dualSabers&&NPC->client->ps.saber[1].Active()) )
4162 && Jedi_SaberBlock() )
4163 {
4164 return;
4165 }
4166 }
4167 else if ( Jedi_CheckEvadeSpecialAttacks() != EVASION_NONE )
4168 {
4169 return;
4170 }
4171
4172
4173 VectorSubtract( NPC->currentOrigin, NPC->enemy->currentOrigin, dirEnemy2Me );
4174 VectorNormalize( dirEnemy2Me );
4175
4176 if ( NPC->enemy->client->ps.weaponTime && NPC->enemy->client->ps.weaponstate == WEAPON_FIRING )
4177 {//enemy is attacking
4178 enemy_attacking = qtrue;
4179 evasionChance = 90;
4180 }
4181
4182 if ( (NPC->enemy->client->ps.forcePowersActive&(1<<FP_LIGHTNING) ) )
4183 {//enemy is shooting lightning
4184 enemy_attacking = qtrue;
4185 shooting_lightning = qtrue;
4186 evasionChance = 50;
4187 }
4188
4189 if ( NPC->enemy->client->ps.saberInFlight
4190 && NPC->enemy->client->ps.saberEntityNum != ENTITYNUM_NONE
4191 && NPC->enemy->client->ps.saberEntityState != SES_RETURNING )
4192 {//enemy is shooting lightning
4193 enemy_attacking = qtrue;
4194 throwing_saber = qtrue;
4195 }
4196
4197 //FIXME: this needs to take skill and rank(reborn type) into account much more
4198 if ( Q_irand( 0, 100 ) < evasionChance )
4199 {//check to see if he's coming at me
4200 float facingAmt;
4201 if ( VectorCompare( enemy_movedir, vec3_origin ) || shooting_lightning || throwing_saber )
4202 {//he's not moving (or he's using a ranged attack), see if he's facing me
4203 vec3_t enemy_fwd;
4204 AngleVectors( NPC->enemy->client->ps.viewangles, enemy_fwd, NULL, NULL );
4205 facingAmt = DotProduct( enemy_fwd, dirEnemy2Me );
4206 }
4207 else
4208 {//he's moving
4209 facingAmt = DotProduct( enemy_movedir, dirEnemy2Me );
4210 }
4211
4212 if ( Q_flrand( 0.25, 1 ) < facingAmt )
4213 {//coming at/facing me!
4214 int whichDefense = 0;
4215 /*if ( NPC->client->NPC_class == CLASS_SABOTEUR )
4216 {
4217 int sabDef = Q_irand( 0, 3 );
4218 if ( sabDef )
4219 {//25% chance of trying normal jedi defense logic
4220 whichDefense = 100;
4221 }
4222 else
4223 {
4224 if ( sabDef == 1 )
4225 {//25% chance of strafing
4226 Jedi_Strafe( 300, 1000, 0, 1000, qfalse );
4227 }
4228 else
4229 {//50% chance of trying to dodge/roll/jump using jedi missile evasion logic
4230 Jedi_SaberBlock();
4231 }
4232 return;
4233 }
4234 }
4235 else */if ( NPC->client->ps.weaponTime
4236 || NPC->client->ps.saberInFlight
4237 || NPC->client->NPC_class == CLASS_BOBAFETT
4238 || (NPC->client->NPC_class == CLASS_REBORN && NPC->s.weapon != WP_SABER)
4239 || NPC->client->NPC_class == CLASS_ROCKETTROOPER )
4240 {//I'm attacking or recovering from a parry, can only try to strafe/jump right now
4241 if ( Q_irand( 0, 10 ) < NPCInfo->stats.aggression )
4242 {
4243 return;
4244 }
4245 whichDefense = 100;
4246 }
4247 else
4248 {
4249 if ( shooting_lightning )
4250 {//check for lightning attack
4251 //only valid defense is strafe and/or jump
4252 whichDefense = 100;
4253 }
4254 else if ( throwing_saber )
4255 {//he's thrown his saber! See if it's coming at me
4256 float saberDist;
4257 vec3_t saberDir2Me;
4258 vec3_t saberMoveDir;
4259 gentity_t *saber = &g_entities[NPC->enemy->client->ps.saberEntityNum];
4260 VectorSubtract( NPC->currentOrigin, saber->currentOrigin, saberDir2Me );
4261 saberDist = VectorNormalize( saberDir2Me );
4262 VectorCopy( saber->s.pos.trDelta, saberMoveDir );
4263 VectorNormalize( saberMoveDir );
4264 if ( !Q_irand( 0, 3 ) )
4265 {
4266 //Com_Printf( "(%d) raise agg - enemy threw saber\n", level.time );
4267 Jedi_Aggression( NPC, 1 );
4268 }
4269 if ( DotProduct( saberMoveDir, saberDir2Me ) > 0.5 )
4270 {//it's heading towards me
4271 if ( saberDist < 100 )
4272 {//it's close
4273 whichDefense = Q_irand( 3, 6 );
4274 }
4275 else if ( saberDist < 200 )
4276 {//got some time, yet, try pushing
4277 whichDefense = Q_irand( 0, 8 );
4278 }
4279 }
4280 }
4281 if ( whichDefense )
4282 {//already chose one
4283 }
4284 else if ( enemy_dist > 80 || !enemy_attacking )
4285 {//he's pretty far, or not swinging, just strafe
4286 if ( VectorCompare( enemy_movedir, vec3_origin ) )
4287 {//if he's not moving, not swinging and far enough away, no evasion necc.
4288 return;
4289 }
4290 if ( Q_irand( 0, 10 ) < NPCInfo->stats.aggression )
4291 {
4292 return;
4293 }
4294 whichDefense = 100;
4295 }
4296 else
4297 {//he's getting close and swinging at me
4298 vec3_t fwd;
4299 //see if I'm facing him
4300 AngleVectors( NPC->client->ps.viewangles, fwd, NULL, NULL );
4301 if ( DotProduct( enemy_dir, fwd ) < 0.5 )
4302 {//I'm not really facing him, best option is to strafe
4303 whichDefense = Q_irand( 5, 16 );
4304 }
4305 else if ( enemy_dist < 56 )
4306 {//he's very close, maybe we should be more inclined to block or throw
4307 whichDefense = Q_irand( NPCInfo->stats.aggression, 12 );
4308 }
4309 else
4310 {
4311 whichDefense = Q_irand( 2, 16 );
4312 }
4313 }
4314 }
4315
4316 if ( whichDefense >= 4 && whichDefense <= 12 )
4317 {//would try to block
4318 if ( NPC->client->ps.saberInFlight )
4319 {//can't, saber in not in hand, so fall back to strafe/jump
4320 whichDefense = 100;
4321 }
4322 }
4323
4324 switch( whichDefense )
4325 {
4326 case 0:
4327 case 1:
4328 case 2:
4329 case 3:
4330 //use jedi force push? or kick?
4331 //FIXME: try to do this if health low or enemy back to a cliff?
4332 if ( Jedi_DecideKick()//let's try a kick
4333 && ( G_PickAutoMultiKick( NPC, qfalse, qtrue ) != LS_NONE
4334 || (G_CanKickEntity(NPC, NPC->enemy )&&G_PickAutoKick( NPC, NPC->enemy, qtrue )!=LS_NONE)
4335 )
4336 )
4337 {//kicked
4338 TIMER_Set( NPC, "kickDebounce", Q_irand( 3000, 10000 ) );
4339 }
4340 else if ( (NPCInfo->rank == RANK_ENSIGN || NPCInfo->rank > RANK_LT_JG) && TIMER_Done( NPC, "parryTime" ) )
4341 {//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
4342 ForceThrow( NPC, qfalse );
4343 }
4344 break;
4345 case 4:
4346 case 5:
4347 case 6:
4348 case 7:
4349 case 8:
4350 case 9:
4351 case 10:
4352 case 11:
4353 case 12:
4354 //try to parry the blow
4355 //gi.Printf( "blocking\n" );
4356 Jedi_SaberBlock();
4357 break;
4358 default:
4359 //Evade!
4360 //start a strafe left/right if not already
4361 if ( !Q_irand( 0, 5 ) || !Jedi_Strafe( 300, 1000, 0, 1000, qfalse ) )
4362 {//certain chance they will pick an alternative evasion
4363 //if couldn't strafe, try a different kind of evasion...
4364 if ( Jedi_DecideKick() && G_CanKickEntity(NPC, NPC->enemy ) && G_PickAutoKick( NPC, NPC->enemy, qtrue ) != LS_NONE )
4365 {//kicked!
4366 TIMER_Set( NPC, "kickDebounce", Q_irand( 3000, 10000 ) );
4367 }
4368 else if ( shooting_lightning || throwing_saber || enemy_dist < 80 )
4369 {
4370 //FIXME: force-jump+forward - jump over the guy!
4371 if ( shooting_lightning || (!Q_irand( 0, 2 ) && NPCInfo->stats.aggression < 4 && TIMER_Done( NPC, "parryTime" ) ) )
4372 {
4373 if ( (NPCInfo->rank == RANK_ENSIGN || NPCInfo->rank > RANK_LT_JG) && !shooting_lightning && Q_irand( 0, 2 ) )
4374 {//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
4375 ForceThrow( NPC, qfalse );
4376 }
4377 else if ( (NPCInfo->rank==RANK_CREWMAN||NPCInfo->rank>RANK_LT_JG)
4378 && !(NPCInfo->scriptFlags&SCF_NO_ACROBATICS)
4379 && NPC->client->ps.forceRageRecoveryTime < level.time
4380 && !(NPC->client->ps.forcePowersActive&(1<<FP_RAGE))
4381 && !PM_InKnockDown( &NPC->client->ps ) )
4382 {//FIXME: make this a function call?
4383 //FIXME: check for clearance, safety of landing spot?
4384 NPC->client->ps.forceJumpCharge = 480;
4385 //Don't jump again for another 2 to 5 seconds
4386 TIMER_Set( NPC, "jumpChaseDebounce", Q_irand( 2000, 5000 ) );
4387 if ( Q_irand( 0, 2 ) )
4388 {
4389 ucmd.forwardmove = 127;
4390 VectorClear( NPC->client->ps.moveDir );
4391 }
4392 else
4393 {
4394 ucmd.forwardmove = -127;
4395 VectorClear( NPC->client->ps.moveDir );
4396 }
4397 //FIXME: if this jump is cleared, we can't block... so pick a random lower block?
4398 if ( Q_irand( 0, 1 ) )//FIXME: make intelligent
4399 {
4400 NPC->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
4401 }
4402 else
4403 {
4404 NPC->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
4405 }
4406 }
4407 }
4408 else if ( enemy_attacking )
4409 {
4410 Jedi_SaberBlock();
4411 }
4412 }
4413 }
4414 else
4415 {//strafed
4416 if ( d_JediAI->integer )
4417 {
4418 gi.Printf( "def strafe\n" );
4419 }
4420 if ( !(NPCInfo->scriptFlags&SCF_NO_ACROBATICS)
4421 && NPC->client->ps.forceRageRecoveryTime < level.time
4422 && !(NPC->client->ps.forcePowersActive&(1<<FP_RAGE))
4423 && (NPCInfo->rank == RANK_CREWMAN || NPCInfo->rank > RANK_LT_JG )
4424 && !PM_InKnockDown( &NPC->client->ps )
4425 && !Q_irand( 0, 5 ) )
4426 {//FIXME: make this a function call?
4427 //FIXME: check for clearance, safety of landing spot?
4428 if ( NPC->client->NPC_class == CLASS_BOBAFETT
4429 || (NPC->client->NPC_class == CLASS_REBORN && NPC->s.weapon != WP_SABER)
4430 || NPC->client->NPC_class == CLASS_ROCKETTROOPER )
4431 {
4432 NPC->client->ps.forceJumpCharge = 280;//FIXME: calc this intelligently?
4433 }
4434 else
4435 {
4436 NPC->client->ps.forceJumpCharge = 320;
4437 }
4438 //Don't jump again for another 2 to 5 seconds
4439 TIMER_Set( NPC, "jumpChaseDebounce", Q_irand( 2000, 5000 ) );
4440 }
4441 }
4442 break;
4443 }
4444
4445 //turn off slow walking no matter what
4446 TIMER_Set( NPC, "walking", -level.time );
4447 TIMER_Set( NPC, "taunting", -level.time );
4448 }
4449 }
4450 }
4451 /*
4452 -------------------------
4453 Jedi_Flee
4454 -------------------------
4455 */
4456 /*
4457
4458 static qboolean Jedi_Flee( void )
4459 {
4460 return qfalse;
4461 }
4462 */
4463
4464
4465 /*
4466 ==========================================================================================
4467 INTERNAL AI ROUTINES
4468 ==========================================================================================
4469 */
Jedi_FindEnemyInCone(gentity_t * self,gentity_t * fallback,float minDot)4470 gentity_t *Jedi_FindEnemyInCone( gentity_t *self, gentity_t *fallback, float minDot )
4471 {
4472 vec3_t forward, mins, maxs, dir;
4473 float dist, bestDist = Q3_INFINITE;
4474 gentity_t *enemy = fallback;
4475 gentity_t *check = NULL;
4476 gentity_t *entityList[MAX_GENTITIES];
4477 int e, numListedEntities;
4478 trace_t tr;
4479
4480 if ( !self->client )
4481 {
4482 return enemy;
4483 }
4484
4485 AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
4486
4487 for ( e = 0 ; e < 3 ; e++ )
4488 {
4489 mins[e] = self->currentOrigin[e] - 1024;
4490 maxs[e] = self->currentOrigin[e] + 1024;
4491 }
4492 numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
4493
4494 for ( e = 0 ; e < numListedEntities ; e++ )
4495 {
4496 check = entityList[e];
4497 if ( check == self )
4498 {//me
4499 continue;
4500 }
4501 if ( !(check->inuse) )
4502 {//freed
4503 continue;
4504 }
4505 if ( !check->client )
4506 {//not a client - FIXME: what about turrets?
4507 continue;
4508 }
4509 if ( check->client->playerTeam != self->client->enemyTeam )
4510 {//not an enemy - FIXME: what about turrets?
4511 continue;
4512 }
4513 if ( check->health <= 0 )
4514 {//dead
4515 continue;
4516 }
4517
4518 if ( !gi.inPVS( check->currentOrigin, self->currentOrigin ) )
4519 {//can't potentially see them
4520 continue;
4521 }
4522
4523 VectorSubtract( check->currentOrigin, self->currentOrigin, dir );
4524 dist = VectorNormalize( dir );
4525
4526 if ( DotProduct( dir, forward ) < minDot )
4527 {//not in front
4528 continue;
4529 }
4530
4531 //really should have a clear LOS to this thing...
4532 gi.trace( &tr, self->currentOrigin, vec3_origin, vec3_origin, check->currentOrigin, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
4533 if ( tr.fraction < 1.0f && tr.entityNum != check->s.number )
4534 {//must have clear shot
4535 continue;
4536 }
4537
4538 if ( dist < bestDist )
4539 {//closer than our last best one
4540 dist = bestDist;
4541 enemy = check;
4542 }
4543 }
4544 return enemy;
4545 }
4546
Jedi_SetEnemyInfo(vec3_t enemy_dest,vec3_t enemy_dir,float * enemy_dist,vec3_t enemy_movedir,float * enemy_movespeed,int prediction)4547 static void Jedi_SetEnemyInfo( vec3_t enemy_dest, vec3_t enemy_dir, float *enemy_dist, vec3_t enemy_movedir, float *enemy_movespeed, int prediction )
4548 {
4549 if ( !NPC || !NPC->enemy )
4550 {//no valid enemy
4551 return;
4552 }
4553 if ( !NPC->enemy->client )
4554 {
4555 VectorClear( enemy_movedir );
4556 *enemy_movespeed = 0;
4557 VectorCopy( NPC->enemy->currentOrigin, enemy_dest );
4558 enemy_dest[2] += NPC->enemy->mins[2] + 24;//get it's origin to a height I can work with
4559 VectorSubtract( enemy_dest, NPC->currentOrigin, enemy_dir );
4560 //FIXME: enemy_dist calc needs to include all blade lengths, and include distance from hand to start of blade....
4561 *enemy_dist = VectorNormalize( enemy_dir );// - (NPC->client->ps.saberLengthMax + NPC->maxs[0]*1.5 + 16);
4562 }
4563 else
4564 {//see where enemy is headed
4565 VectorCopy( NPC->enemy->client->ps.velocity, enemy_movedir );
4566 *enemy_movespeed = VectorNormalize( enemy_movedir );
4567 //figure out where he'll be, say, 3 frames from now
4568 VectorMA( NPC->enemy->currentOrigin, *enemy_movespeed * 0.001 * prediction, enemy_movedir, enemy_dest );
4569 //figure out what dir the enemy's estimated position is from me and how far from the tip of my saber he is
4570 VectorSubtract( enemy_dest, NPC->currentOrigin, enemy_dir );//NPC->client->renderInfo.muzzlePoint
4571 //FIXME: enemy_dist calc needs to include all blade lengths, and include distance from hand to start of blade....
4572 *enemy_dist = VectorNormalize( enemy_dir ) - (NPC->client->ps.SaberLengthMax() + NPC->maxs[0]*1.5 + 16);
4573 //FIXME: keep a group of enemies around me and use that info to make decisions...
4574 // For instance, if there are multiple enemies, evade more, push them away
4575 // and use medium attacks. If enemies are using blasters, switch to fast.
4576 // If one jedi enemy, use strong attacks. Use grip when fighting one or
4577 // two enemies, use lightning spread when fighting multiple enemies, etc.
4578 // Also, when kill one, check rest of group instead of walking up to victim.
4579 }
4580 //init this to false
4581 enemy_in_striking_range = qfalse;
4582 if ( *enemy_dist <= 0.0f )
4583 {
4584 enemy_in_striking_range = qtrue;
4585 }
4586 else
4587 {//if he's too far away, see if he's at least facing us or coming towards us
4588 if ( *enemy_dist <= 32.0f )
4589 {//has to be facing us
4590 vec3_t eAngles = {0,NPC->currentAngles[YAW],0};
4591 if ( InFOV( NPC->currentOrigin, NPC->enemy->currentOrigin, eAngles, 30, 90 ) )
4592 {//in striking range
4593 enemy_in_striking_range = qtrue;
4594 }
4595 }
4596 if ( *enemy_dist >= 64.0f )
4597 {//we have to be approaching each other
4598 float vDot = 1.0f;
4599 if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) )
4600 {//I am moving, see if I'm moving toward the enemy
4601 vec3_t eDir;
4602 VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, eDir );
4603 VectorNormalize( eDir );
4604 vDot = DotProduct( eDir, NPC->client->ps.velocity );
4605 }
4606 else if ( NPC->enemy->client && !VectorCompare( NPC->enemy->client->ps.velocity, vec3_origin ) )
4607 {//I'm not moving, but the enemy is, see if he's moving towards me
4608 vec3_t meDir;
4609 VectorSubtract( NPC->currentOrigin, NPC->enemy->currentOrigin, meDir );
4610 VectorNormalize( meDir );
4611 vDot = DotProduct( meDir, NPC->enemy->client->ps.velocity );
4612 }
4613 else
4614 {//neither of us is moving, below check will fail, so just return
4615 return;
4616 }
4617 if ( vDot >= *enemy_dist )
4618 {//moving towards each other
4619 enemy_in_striking_range = qtrue;
4620 }
4621 }
4622 }
4623 }
4624
NPC_EvasionSaber(void)4625 void NPC_EvasionSaber( void )
4626 {
4627 if ( ucmd.upmove <= 0//not jumping
4628 && (!ucmd.upmove || !ucmd.rightmove) )//either just ducking or just strafing (i.e.: not rolling
4629 {//see if we need to avoid their saber
4630 vec3_t enemy_dir, enemy_movedir, enemy_dest;
4631 float enemy_dist, enemy_movespeed;
4632 //set enemy
4633 Jedi_SetEnemyInfo( enemy_dest, enemy_dir, &enemy_dist, enemy_movedir, &enemy_movespeed, 300 );
4634 Jedi_EvasionSaber( enemy_movedir, enemy_dist, enemy_dir );
4635 }
4636 }
4637
4638 extern float WP_SpeedOfMissileForWeapon( int wp, qboolean alt_fire );
Jedi_FaceEnemy(qboolean doPitch)4639 static void Jedi_FaceEnemy( qboolean doPitch )
4640 {
4641 vec3_t enemy_eyes, eyes, angles;
4642
4643 if ( NPC == NULL )
4644 return;
4645
4646 if ( NPC->enemy == NULL )
4647 return;
4648
4649 if ( NPC->client->ps.forcePowersActive & (1<<FP_GRIP) &&
4650 NPC->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
4651 {//don't update?
4652 NPCInfo->desiredPitch = NPC->client->ps.viewangles[PITCH];
4653 NPCInfo->desiredYaw = NPC->client->ps.viewangles[YAW];
4654 return;
4655 }
4656 CalcEntitySpot( NPC, SPOT_HEAD, eyes );
4657
4658 CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_eyes );
4659
4660 if ( NPC->client->NPC_class == CLASS_BOBAFETT
4661 && TIMER_Done( NPC, "flameTime" )
4662 && NPC->s.weapon != WP_NONE
4663 && NPC->s.weapon != WP_DISRUPTOR
4664 && (NPC->s.weapon != WP_ROCKET_LAUNCHER||!(NPCInfo->scriptFlags&SCF_ALT_FIRE))
4665 && NPC->s.weapon != WP_THERMAL
4666 && NPC->s.weapon != WP_TRIP_MINE
4667 && NPC->s.weapon != WP_DET_PACK
4668 && NPC->s.weapon != WP_STUN_BATON
4669 && NPC->s.weapon != WP_MELEE )
4670 {//boba leads his enemy
4671 if ( NPC->health < NPC->max_health*0.5f )
4672 {//lead
4673 float missileSpeed = WP_SpeedOfMissileForWeapon( NPC->s.weapon, ((qboolean)(NPCInfo->scriptFlags&SCF_ALT_FIRE)) );
4674 if ( missileSpeed )
4675 {
4676 float eDist = Distance( eyes, enemy_eyes );
4677 eDist /= missileSpeed;//How many seconds it will take to get to the enemy
4678 VectorMA( enemy_eyes, eDist*Q_flrand(0.95f,1.25f), NPC->enemy->client->ps.velocity, enemy_eyes );
4679 }
4680 }
4681 }
4682
4683 //Find the desired angles
4684 if ( !NPC->client->ps.saberInFlight
4685 && (NPC->client->ps.legsAnim == BOTH_A2_STABBACK1
4686 || NPC->client->ps.legsAnim == BOTH_CROUCHATTACKBACK1
4687 || NPC->client->ps.legsAnim == BOTH_ATTACK_BACK
4688 || NPC->client->ps.legsAnim == BOTH_A7_KICK_B )
4689 )
4690 {//point *away*
4691 GetAnglesForDirection( enemy_eyes, eyes, angles );
4692 }
4693 else if ( NPC->client->ps.legsAnim == BOTH_A7_KICK_R )
4694 {//keep enemy to right
4695 }
4696 else if ( NPC->client->ps.legsAnim == BOTH_A7_KICK_L )
4697 {//keep enemy to left
4698 }
4699 else if ( NPC->client->ps.legsAnim == BOTH_A7_KICK_RL
4700 || NPC->client->ps.legsAnim == BOTH_A7_KICK_BF
4701 || NPC->client->ps.legsAnim == BOTH_A7_KICK_S )
4702 {//???
4703 }
4704 else
4705 {//point towards him
4706 GetAnglesForDirection( eyes, enemy_eyes, angles );
4707 }
4708
4709 NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] );
4710 /*
4711 if ( NPC->client->ps.saberBlocked == BLOCKED_UPPER_LEFT )
4712 {//temp hack- to make up for poor coverage on left side
4713 NPCInfo->desiredYaw += 30;
4714 }
4715 */
4716
4717 if ( doPitch )
4718 {
4719 NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] );
4720 if ( NPC->client->ps.saberInFlight )
4721 {//tilt down a little
4722 NPCInfo->desiredPitch += 10;
4723 }
4724 }
4725 //FIXME: else desiredPitch = 0? Or keep previous?
4726 }
4727
Jedi_DebounceDirectionChanges(void)4728 static void Jedi_DebounceDirectionChanges( void )
4729 {
4730 //FIXME: check these before making fwd/back & right/left decisions?
4731 //Time-debounce changes in forward/back dir
4732 if ( ucmd.forwardmove > 0 )
4733 {
4734 if ( !TIMER_Done( NPC, "moveback" ) || !TIMER_Done( NPC, "movenone" ) )
4735 {
4736 ucmd.forwardmove = 0;
4737 //now we have to normalize the total movement again
4738 if ( ucmd.rightmove > 0 )
4739 {
4740 ucmd.rightmove = 127;
4741 }
4742 else if ( ucmd.rightmove < 0 )
4743 {
4744 ucmd.rightmove = -127;
4745 }
4746 VectorClear( NPC->client->ps.moveDir );
4747 TIMER_Set( NPC, "moveback", -level.time );
4748 if ( TIMER_Done( NPC, "movenone" ) )
4749 {
4750 TIMER_Set( NPC, "movenone", Q_irand( 1000, 2000 ) );
4751 }
4752 }
4753 else if ( TIMER_Done( NPC, "moveforward" ) )
4754 {//FIXME: should be if it's zero?
4755 if ( TIMER_Done( NPC, "lastmoveforward" ) )
4756 {
4757 int holdDirTime = Q_irand( 500, 2000 );
4758 TIMER_Set( NPC, "moveforward", holdDirTime );
4759 //so we don't keep doing this over and over again - new nav stuff makes them coast to a stop, so they could be just slowing down from the last "moveback" timer's ending...
4760 TIMER_Set( NPC, "lastmoveforward", holdDirTime + Q_irand(1000,2000) );
4761 }
4762 }
4763 else
4764 {//NOTE: edge checking should stop me if this is bad... but what if it sends us colliding into the enemy?
4765 //if being forced to move forward, do a full-speed moveforward
4766 ucmd.forwardmove = 127;
4767 VectorClear( NPC->client->ps.moveDir );
4768 }
4769 }
4770 else if ( ucmd.forwardmove < 0 )
4771 {
4772 if ( !TIMER_Done( NPC, "moveforward" ) || !TIMER_Done( NPC, "movenone" ) )
4773 {
4774 ucmd.forwardmove = 0;
4775 //now we have to normalize the total movement again
4776 if ( ucmd.rightmove > 0 )
4777 {
4778 ucmd.rightmove = 127;
4779 }
4780 else if ( ucmd.rightmove < 0 )
4781 {
4782 ucmd.rightmove = -127;
4783 }
4784 VectorClear( NPC->client->ps.moveDir );
4785 TIMER_Set( NPC, "moveforward", -level.time );
4786 if ( TIMER_Done( NPC, "movenone" ) )
4787 {
4788 TIMER_Set( NPC, "movenone", Q_irand( 1000, 2000 ) );
4789 }
4790 }
4791 else if ( TIMER_Done( NPC, "moveback" ) )
4792 {//FIXME: should be if it's zero?
4793 if ( TIMER_Done( NPC, "lastmoveback" ) )
4794 {
4795 int holdDirTime = Q_irand( 500, 2000 );
4796 TIMER_Set( NPC, "moveback", holdDirTime );
4797 //so we don't keep doing this over and over again - new nav stuff makes them coast to a stop, so they could be just slowing down from the last "moveback" timer's ending...
4798 TIMER_Set( NPC, "lastmoveback", holdDirTime + Q_irand(1000,2000) );
4799 }
4800 }
4801 else
4802 {//NOTE: edge checking should stop me if this is bad...
4803 //if being forced to move back, do a full-speed moveback
4804 ucmd.forwardmove = -127;
4805 VectorClear( NPC->client->ps.moveDir );
4806 }
4807 }
4808 else if ( !TIMER_Done( NPC, "moveforward" ) )
4809 {//NOTE: edge checking should stop me if this is bad... but what if it sends us colliding into the enemy?
4810 ucmd.forwardmove = 127;
4811 VectorClear( NPC->client->ps.moveDir );
4812 }
4813 else if ( !TIMER_Done( NPC, "moveback" ) )
4814 {//NOTE: edge checking should stop me if this is bad...
4815 ucmd.forwardmove = -127;
4816 VectorClear( NPC->client->ps.moveDir );
4817 }
4818 //Time-debounce changes in right/left dir
4819 if ( ucmd.rightmove > 0 )
4820 {
4821 if ( !TIMER_Done( NPC, "moveleft" ) || !TIMER_Done( NPC, "movecenter" ) )
4822 {
4823 ucmd.rightmove = 0;
4824 //now we have to normalize the total movement again
4825 if ( ucmd.forwardmove > 0 )
4826 {
4827 ucmd.forwardmove = 127;
4828 }
4829 else if ( ucmd.forwardmove < 0 )
4830 {
4831 ucmd.forwardmove = -127;
4832 }
4833 VectorClear( NPC->client->ps.moveDir );
4834 TIMER_Set( NPC, "moveleft", -level.time );
4835 if ( TIMER_Done( NPC, "movecenter" ) )
4836 {
4837 TIMER_Set( NPC, "movecenter", Q_irand( 1000, 2000 ) );
4838 }
4839 }
4840 else if ( TIMER_Done( NPC, "moveright" ) )
4841 {//FIXME: should be if it's zero?
4842 if ( TIMER_Done( NPC, "lastmoveright" ) )
4843 {
4844 int holdDirTime = Q_irand( 250, 1500 );
4845 TIMER_Set( NPC, "moveright", holdDirTime );
4846 //so we don't keep doing this over and over again - new nav stuff makes them coast to a stop, so they could be just slowing down from the last "moveback" timer's ending...
4847 TIMER_Set( NPC, "lastmoveright", holdDirTime + Q_irand(1000,2000) );
4848 }
4849 }
4850 else
4851 {//NOTE: edge checking should stop me if this is bad...
4852 //if being forced to move back, do a full-speed moveright
4853 ucmd.rightmove = 127;
4854 VectorClear( NPC->client->ps.moveDir );
4855 }
4856 }
4857 else if ( ucmd.rightmove < 0 )
4858 {
4859 if ( !TIMER_Done( NPC, "moveright" ) || !TIMER_Done( NPC, "movecenter" ) )
4860 {
4861 ucmd.rightmove = 0;
4862 //now we have to normalize the total movement again
4863 if ( ucmd.forwardmove > 0 )
4864 {
4865 ucmd.forwardmove = 127;
4866 }
4867 else if ( ucmd.forwardmove < 0 )
4868 {
4869 ucmd.forwardmove = -127;
4870 }
4871 VectorClear( NPC->client->ps.moveDir );
4872 TIMER_Set( NPC, "moveright", -level.time );
4873 if ( TIMER_Done( NPC, "movecenter" ) )
4874 {
4875 TIMER_Set( NPC, "movecenter", Q_irand( 1000, 2000 ) );
4876 }
4877 }
4878 else if ( TIMER_Done( NPC, "moveleft" ) )
4879 {//FIXME: should be if it's zero?
4880 if ( TIMER_Done( NPC, "lastmoveleft" ) )
4881 {
4882 int holdDirTime = Q_irand( 250, 1500 );
4883 TIMER_Set( NPC, "moveleft", holdDirTime );
4884 //so we don't keep doing this over and over again - new nav stuff makes them coast to a stop, so they could be just slowing down from the last "moveback" timer's ending...
4885 TIMER_Set( NPC, "lastmoveleft", holdDirTime + Q_irand(1000,2000) );
4886 }
4887 }
4888 else
4889 {//NOTE: edge checking should stop me if this is bad...
4890 //if being forced to move back, do a full-speed moveleft
4891 ucmd.rightmove = -127;
4892 VectorClear( NPC->client->ps.moveDir );
4893 }
4894 }
4895 else if ( !TIMER_Done( NPC, "moveright" ) )
4896 {//NOTE: edge checking should stop me if this is bad...
4897 ucmd.rightmove = 127;
4898 VectorClear( NPC->client->ps.moveDir );
4899 }
4900 else if ( !TIMER_Done( NPC, "moveleft" ) )
4901 {//NOTE: edge checking should stop me if this is bad...
4902 ucmd.rightmove = -127;
4903 VectorClear( NPC->client->ps.moveDir );
4904 }
4905 }
4906
Jedi_TimersApply(void)4907 static void Jedi_TimersApply( void )
4908 {
4909 //use careful anim/slower movement if not already moving
4910 if ( !ucmd.forwardmove && !TIMER_Done( NPC, "walking" ) )
4911 {
4912 ucmd.buttons |= (BUTTON_WALKING);
4913 }
4914
4915 if ( !TIMER_Done( NPC, "taunting" ) )
4916 {
4917 ucmd.buttons |= (BUTTON_WALKING);
4918 }
4919
4920 if ( !ucmd.rightmove )
4921 {//only if not already strafing
4922 //FIXME: if enemy behind me and turning to face enemy, don't strafe in that direction, too
4923 if ( !TIMER_Done( NPC, "strafeLeft" ) )
4924 {
4925 if ( NPCInfo->desiredYaw > NPC->client->ps.viewangles[YAW] + 60 )
4926 {//we want to turn left, don't apply the strafing
4927 }
4928 else
4929 {//go ahead and strafe left
4930 ucmd.rightmove = -127;
4931 VectorClear( NPC->client->ps.moveDir );
4932 }
4933 }
4934 else if ( !TIMER_Done( NPC, "strafeRight" ) )
4935 {
4936 if ( NPCInfo->desiredYaw < NPC->client->ps.viewangles[YAW] - 60 )
4937 {//we want to turn right, don't apply the strafing
4938 }
4939 else
4940 {//go ahead and strafe left
4941 ucmd.rightmove = 127;
4942 VectorClear( NPC->client->ps.moveDir );
4943 }
4944 }
4945 }
4946
4947 Jedi_DebounceDirectionChanges();
4948
4949 if ( !TIMER_Done( NPC, "gripping" ) )
4950 {//FIXME: what do we do if we ran out of power? NPC's can't?
4951 //FIXME: don't keep turning to face enemy or we'll end up spinning around
4952 ucmd.buttons |= BUTTON_FORCEGRIP;
4953 }
4954
4955 if ( !TIMER_Done( NPC, "draining" ) )
4956 {//FIXME: what do we do if we ran out of power? NPC's can't?
4957 //FIXME: don't keep turning to face enemy or we'll end up spinning around
4958 ucmd.buttons |= BUTTON_FORCE_DRAIN;
4959 }
4960
4961 if ( !TIMER_Done( NPC, "holdLightning" ) )
4962 {//hold down the lightning key
4963 ucmd.buttons |= BUTTON_FORCE_LIGHTNING;
4964 }
4965 }
4966
Jedi_CombatTimersUpdate(int enemy_dist)4967 static void Jedi_CombatTimersUpdate( int enemy_dist )
4968 {
4969 if ( Jedi_CultistDestroyer( NPC ) )
4970 {
4971 Jedi_Aggression( NPC, 5 );
4972 return;
4973 }
4974 //===START MISSING CODE=================================================================
4975 if ( TIMER_Done( NPC, "roamTime" ) )
4976 {
4977 TIMER_Set( NPC, "roamTime", Q_irand( 2000, 5000 ) );
4978 //okay, now mess with agression
4979 if ( NPC->enemy && NPC->enemy->client )
4980 {
4981 switch( NPC->enemy->client->ps.weapon )
4982 {
4983 //FIXME: add new weapons
4984 case WP_SABER:
4985 //If enemy has a lightsaber, always close in
4986 if ( !NPC->enemy->client->ps.SaberActive() )
4987 {//fool! Standing around unarmed, charge!
4988 //Com_Printf( "(%d) raise agg - enemy saber off\n", level.time );
4989 Jedi_Aggression( NPC, 2 );
4990 }
4991 else
4992 {
4993 //Com_Printf( "(%d) raise agg - enemy saber\n", level.time );
4994 Jedi_Aggression( NPC, 1 );
4995 }
4996 break;
4997 case WP_BLASTER:
4998 case WP_BRYAR_PISTOL:
4999 case WP_BLASTER_PISTOL:
5000 case WP_DISRUPTOR:
5001 case WP_BOWCASTER:
5002 case WP_REPEATER:
5003 case WP_DEMP2:
5004 case WP_FLECHETTE:
5005 case WP_ROCKET_LAUNCHER:
5006 case WP_CONCUSSION:
5007 //if he has a blaster, move in when:
5008 //They're not shooting at me
5009 if ( NPC->enemy->attackDebounceTime < level.time )
5010 {//does this apply to players?
5011 //Com_Printf( "(%d) raise agg - enemy not shooting ranged weap\n", level.time );
5012 Jedi_Aggression( NPC, 1 );
5013 }
5014 //He's closer than a dist that gives us time to deflect
5015 if ( enemy_dist < 256 )
5016 {
5017 //Com_Printf( "(%d) raise agg - enemy ranged weap- too close\n", level.time );
5018 Jedi_Aggression( NPC, 1 );
5019 }
5020 break;
5021 default:
5022 break;
5023 }
5024 }
5025 }
5026
5027 if ( TIMER_Done( NPC, "noStrafe" ) && TIMER_Done( NPC, "strafeLeft" ) && TIMER_Done( NPC, "strafeRight" ) )
5028 {
5029 //FIXME: Maybe more likely to do this if aggression higher? Or some other stat?
5030 if ( !Q_irand( 0, 4 ) )
5031 {//start a strafe
5032 if ( Jedi_Strafe( 1000, 3000, 0, 4000, qtrue ) )
5033 {
5034 if ( d_JediAI->integer )
5035 {
5036 gi.Printf( "off strafe\n" );
5037 }
5038 }
5039 }
5040 else
5041 {//postpone any strafing for a while
5042 TIMER_Set( NPC, "noStrafe", Q_irand( 1000, 3000 ) );
5043 }
5044 }
5045 //===END MISSING CODE=================================================================
5046 if ( NPC->client->ps.saberEventFlags )
5047 {//some kind of saber combat event is still pending
5048 int newFlags = NPC->client->ps.saberEventFlags;
5049 if ( NPC->client->ps.saberEventFlags&SEF_PARRIED )
5050 {//parried
5051 TIMER_Set( NPC, "parryTime", -1 );
5052 /*
5053 if ( NPCInfo->rank >= RANK_LT_JG )
5054 {
5055 NPC->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 100;
5056 }
5057 else
5058 {
5059 NPC->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500;
5060 }
5061 */
5062 if ( NPC->enemy && (!NPC->enemy->client||PM_SaberInKnockaway( NPC->enemy->client->ps.saberMove )) )
5063 {//advance!
5064 Jedi_Aggression( NPC, 1 );//get closer
5065 Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.saberAnimLevel-1) );//use a faster attack
5066 }
5067 else
5068 {
5069 if ( !Q_irand( 0, 1 ) )//FIXME: dependant on rank/diff?
5070 {
5071 //Com_Printf( "(%d) drop agg - we parried\n", level.time );
5072 Jedi_Aggression( NPC, -1 );
5073 }
5074 if ( !Q_irand( 0, 1 ) )
5075 {
5076 Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.saberAnimLevel-1) );
5077 }
5078 }
5079 if ( d_JediAI->integer )
5080 {
5081 gi.Printf( "(%d) PARRY: agg %d, no parry until %d\n", level.time, NPCInfo->stats.aggression, level.time + 100 );
5082 }
5083 newFlags &= ~SEF_PARRIED;
5084 }
5085 if ( !NPC->client->ps.weaponTime && (NPC->client->ps.saberEventFlags&SEF_HITENEMY) )//hit enemy
5086 {//we hit our enemy last time we swung, drop our aggression
5087 if ( !Q_irand( 0, 1 ) )//FIXME: dependant on rank/diff?
5088 {
5089 //Com_Printf( "(%d) drop agg - we hit enemy\n", level.time );
5090 Jedi_Aggression( NPC, -1 );
5091 if ( d_JediAI->integer )
5092 {
5093 gi.Printf( "(%d) HIT: agg %d\n", level.time, NPCInfo->stats.aggression );
5094 }
5095 if ( !Q_irand( 0, 3 )
5096 && NPCInfo->blockedSpeechDebounceTime < level.time
5097 && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time
5098 && NPC->painDebounceTime < level.time - 1000 )
5099 {
5100 G_AddVoiceEvent( NPC, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 3000 );
5101 jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000;
5102 }
5103 }
5104 if ( !Q_irand( 0, 2 ) )
5105 {
5106 Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.saberAnimLevel+1) );
5107 }
5108 newFlags &= ~SEF_HITENEMY;
5109 }
5110 if ( (NPC->client->ps.saberEventFlags&SEF_BLOCKED) )
5111 {//was blocked whilst attacking
5112 if ( PM_SaberInBrokenParry( NPC->client->ps.saberMove )
5113 || NPC->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
5114 {
5115 //Com_Printf( "(%d) drop agg - we were knock-blocked\n", level.time );
5116 if ( NPC->client->ps.saberInFlight )
5117 {//lost our saber, too!!!
5118 Jedi_Aggression( NPC, -5 );//really really really should back off!!!
5119 }
5120 else
5121 {
5122 Jedi_Aggression( NPC, -2 );//really should back off!
5123 }
5124 Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.saberAnimLevel+1) );//use a stronger attack
5125 if ( d_JediAI->integer )
5126 {
5127 gi.Printf( "(%d) KNOCK-BLOCKED: agg %d\n", level.time, NPCInfo->stats.aggression );
5128 }
5129 }
5130 else
5131 {
5132 if ( !Q_irand( 0, 2 ) )//FIXME: dependant on rank/diff?
5133 {
5134 //Com_Printf( "(%d) drop agg - we were blocked\n", level.time );
5135 Jedi_Aggression( NPC, -1 );
5136 if ( d_JediAI->integer )
5137 {
5138 gi.Printf( "(%d) BLOCKED: agg %d\n", level.time, NPCInfo->stats.aggression );
5139 }
5140 }
5141 if ( !Q_irand( 0, 1 ) )
5142 {
5143 Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.saberAnimLevel+1) );
5144 }
5145 }
5146 newFlags &= ~SEF_BLOCKED;
5147 //FIXME: based on the type of parry the enemy is doing and my skill,
5148 // choose an attack that is likely to get around the parry?
5149 // right now that's generic in the saber animation code, auto-picks
5150 // a next anim for me, but really should be AI-controlled.
5151 }
5152 if ( NPC->client->ps.saberEventFlags&SEF_DEFLECTED )
5153 {//deflected a shot
5154 newFlags &= ~SEF_DEFLECTED;
5155 if ( !Q_irand( 0, 3 ) )
5156 {
5157 Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.saberAnimLevel-1) );
5158 }
5159 }
5160 if ( NPC->client->ps.saberEventFlags&SEF_HITWALL )
5161 {//hit a wall
5162 newFlags &= ~SEF_HITWALL;
5163 }
5164 if ( NPC->client->ps.saberEventFlags&SEF_HITOBJECT )
5165 {//hit some other damagable object
5166 if ( !Q_irand( 0, 3 ) )
5167 {
5168 Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.saberAnimLevel-1) );
5169 }
5170 newFlags &= ~SEF_HITOBJECT;
5171 }
5172 NPC->client->ps.saberEventFlags = newFlags;
5173 }
5174 }
5175
Jedi_CombatIdle(int enemy_dist)5176 static void Jedi_CombatIdle( int enemy_dist )
5177 {
5178 if ( !TIMER_Done( NPC, "parryTime" ) )
5179 {
5180 return;
5181 }
5182 if ( NPC->client->ps.saberInFlight )
5183 {//don't do this idle stuff if throwing saber
5184 return;
5185 }
5186 if ( NPC->client->ps.forcePowersActive&(1<<FP_RAGE)
5187 || NPC->client->ps.forceRageRecoveryTime > level.time )
5188 {//never taunt while raging or recovering from rage
5189 return;
5190 }
5191 if ( NPC->client->ps.stats[STAT_WEAPONS]&(1<<WP_SCEPTER) )
5192 {//never taunt when holding scepter
5193 return;
5194 }
5195 if ( NPC->client->ps.saber[0].type == SABER_SITH_SWORD )
5196 {//never taunt when holding sith sword
5197 return;
5198 }
5199 //FIXME: make these distance numbers defines?
5200 if ( enemy_dist >= 64 )
5201 {//FIXME: only do this if standing still?
5202 //based on aggression, flaunt/taunt
5203 int chance = 20;
5204 if ( NPC->client->NPC_class == CLASS_SHADOWTROOPER )
5205 {
5206 chance = 10;
5207 }
5208 //FIXME: possibly throw local objects at enemy?
5209 if ( Q_irand( 2, chance ) < NPCInfo->stats.aggression )
5210 {
5211 if ( TIMER_Done( NPC, "chatter" ) )
5212 {//FIXME: add more taunt behaviors
5213 //FIXME: sometimes he turns it off, then turns it right back on again???
5214 if ( enemy_dist > 200
5215 && NPC->client->NPC_class != CLASS_BOBAFETT
5216 && (NPC->client->NPC_class != CLASS_REBORN || NPC->s.weapon == WP_SABER)
5217 && NPC->client->NPC_class != CLASS_ROCKETTROOPER
5218 && NPC->client->ps.SaberActive()
5219 && !Q_irand( 0, 5 ) )
5220 {//taunt even more, turn off the saber
5221 //FIXME: don't do this if health low?
5222 if ( NPC->client->ps.saberAnimLevel != SS_STAFF
5223 && NPC->client->ps.saberAnimLevel != SS_DUAL )
5224 {//those taunts leave saber on
5225 WP_DeactivateSaber( NPC );
5226 }
5227 //Don't attack for a bit
5228 NPCInfo->stats.aggression = 3;
5229 //FIXME: maybe start strafing?
5230 //debounce this
5231 if ( NPC->client->playerTeam != TEAM_PLAYER && !Q_irand( 0, 1 ))
5232 {
5233 NPC->client->ps.taunting = level.time + 100;
5234 TIMER_Set( NPC, "chatter", Q_irand( 5000, 10000 ) );
5235 TIMER_Set( NPC, "taunting", 5500 );
5236 }
5237 else
5238 {
5239 Jedi_BattleTaunt();
5240 TIMER_Set( NPC, "taunting", Q_irand( 5000, 10000 ) );
5241 }
5242 }
5243 else if ( Jedi_BattleTaunt() )
5244 {//FIXME: pick some anims
5245 }
5246 }
5247 }
5248 }
5249 }
5250
5251 extern qboolean PM_SaberInParry( int move );
Jedi_AttackDecide(int enemy_dist)5252 static qboolean Jedi_AttackDecide( int enemy_dist )
5253 {
5254 if ( !TIMER_Done( NPC, "allyJediDelay" ) )
5255 {
5256 return qfalse;
5257 }
5258
5259 if ( Jedi_CultistDestroyer( NPC ) )
5260 {//destroyer
5261 if ( enemy_dist <= 32 )
5262 {//go boom!
5263 //float?
5264 //VectorClear( NPC->client->ps.velocity );
5265 //NPC->client->ps.gravity = 0;
5266 //NPC->svFlags |= SVF_CUSTOM_GRAVITY;
5267 //NPC->client->moveType = MT_FLYSWIM;
5268 //NPC->flags |= FL_NO_KNOCKBACK;
5269 NPC->flags |= FL_GODMODE;
5270 NPC->takedamage = qfalse;
5271
5272 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_FORCE_RAGE, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE );
5273 NPC->client->ps.forcePowersActive |= ( 1 << FP_RAGE );
5274 NPC->painDebounceTime = NPC->useDebounceTime = level.time + NPC->client->ps.torsoAnimTimer;
5275 return qtrue;
5276 }
5277 return qfalse;
5278 }
5279 if ( NPC->enemy->client
5280 && NPC->enemy->s.weapon == WP_SABER
5281 && NPC->enemy->client->ps.saberLockTime > level.time
5282 && NPC->client->ps.saberLockTime < level.time )
5283 {//enemy is in a saberLock and we are not
5284 return qfalse;
5285 }
5286
5287 if ( NPC->client->ps.saberEventFlags&SEF_LOCK_WON )
5288 {//we won a saber lock, press the advantage with an attack!
5289 int chance = 0;
5290 if ( (NPCInfo->aiFlags&NPCAI_BOSS_CHARACTER) )
5291 {//desann and luke
5292 chance = 20;
5293 }
5294 else if ( NPC->client->NPC_class == CLASS_TAVION
5295 || NPC->client->NPC_class == CLASS_ALORA )
5296 {//tavion
5297 chance = 10;
5298 }
5299 else if ( NPC->client->NPC_class == CLASS_SHADOWTROOPER )
5300 {//shadowtrooper
5301 chance = 5;
5302 }
5303 else if ( NPC->client->NPC_class == CLASS_REBORN && NPCInfo->rank == RANK_LT_JG )
5304 {//fencer
5305 chance = 5;
5306 }
5307 else
5308 {
5309 chance = NPCInfo->rank;
5310 }
5311 if ( Q_irand( 0, 30 ) < chance )
5312 {//based on skill with some randomness
5313 NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON;//clear this now that we are using the opportunity
5314 TIMER_Set( NPC, "noRetreat", Q_irand( 500, 2000 ) );
5315 //FIXME: check enemy_dist?
5316 NPC->client->ps.weaponTime = NPCInfo->shotTime = NPC->attackDebounceTime = 0;
5317 //NPC->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500;
5318 NPC->client->ps.saberBlocked = BLOCKED_NONE;
5319 WeaponThink( qtrue );
5320 return qtrue;
5321 }
5322 }
5323
5324 if ( NPC->client->NPC_class == CLASS_TAVION ||
5325 NPC->client->NPC_class == CLASS_ALORA ||
5326 NPC->client->NPC_class == CLASS_SHADOWTROOPER ||
5327 ( NPC->client->NPC_class == CLASS_REBORN && NPCInfo->rank == RANK_LT_JG ) ||
5328 ( NPC->client->NPC_class == CLASS_JEDI && NPCInfo->rank == RANK_COMMANDER ) )
5329 {//tavion, fencers, jedi trainer are all good at following up a parry with an attack
5330 if ( ( PM_SaberInParry( NPC->client->ps.saberMove ) || PM_SaberInKnockaway( NPC->client->ps.saberMove ) )
5331 && NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
5332 {//try to attack straight from a parry
5333 NPC->client->ps.weaponTime = NPCInfo->shotTime = NPC->attackDebounceTime = 0;
5334 //NPC->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500;
5335 NPC->client->ps.saberBlocked = BLOCKED_NONE;
5336 Jedi_AdjustSaberAnimLevel( NPC, SS_FAST );//try to follow-up with a quick attack
5337 WeaponThink( qtrue );
5338 return qtrue;
5339 }
5340 }
5341
5342 //try to hit them if we can
5343 if ( !enemy_in_striking_range )
5344 {
5345 return qfalse;
5346 }
5347
5348 if ( !TIMER_Done( NPC, "parryTime" ) )
5349 {
5350 return qfalse;
5351 }
5352
5353 if ( (NPCInfo->scriptFlags&SCF_DONT_FIRE) )
5354 {//not allowed to attack
5355 return qfalse;
5356 }
5357
5358 if ( !(ucmd.buttons&BUTTON_ATTACK)
5359 && !(ucmd.buttons&BUTTON_ALT_ATTACK)
5360 && !(ucmd.buttons&BUTTON_FORCE_FOCUS) )
5361 {//not already attacking
5362 //Try to attack
5363 WeaponThink( qtrue );
5364 }
5365
5366 //FIXME: Maybe try to push enemy off a ledge?
5367
5368 //close enough to step forward
5369
5370 //FIXME: an attack debounce timer other than the phaser debounce time?
5371 // or base it on aggression?
5372
5373 if ( ucmd.buttons&BUTTON_ATTACK && !NPC_Jumping())
5374 {//attacking
5375 /*
5376 if ( enemy_dist > 32 && NPCInfo->stats.aggression >= 4 )
5377 {//move forward if we're too far away and we're chasing him
5378 ucmd.forwardmove = 127;
5379 }
5380 else if ( enemy_dist < 0 )
5381 {//move back if we're too close
5382 ucmd.forwardmove = -127;
5383 }
5384 */
5385 //FIXME: based on the type of parry/attack the enemy is doing and my skill,
5386 // choose an attack that is likely to get around the parry?
5387 // right now that's generic in the saber animation code, auto-picks
5388 // a next anim for me, but really should be AI-controlled.
5389 //FIXME: have this interact with/override above strafing code?
5390 if ( !ucmd.rightmove )
5391 {//not already strafing
5392 if ( !Q_irand( 0, 3 ) )
5393 {//25% chance of doing this
5394 vec3_t right, dir2enemy;
5395
5396 AngleVectors( NPC->currentAngles, NULL, right, NULL );
5397 VectorSubtract( NPC->enemy->currentOrigin, NPC->currentAngles, dir2enemy );
5398 if ( DotProduct( right, dir2enemy ) > 0 )
5399 {//he's to my right, strafe left
5400 ucmd.rightmove = -127;
5401 VectorClear( NPC->client->ps.moveDir );
5402 }
5403 else
5404 {//he's to my left, strafe right
5405 ucmd.rightmove = 127;
5406 VectorClear( NPC->client->ps.moveDir );
5407 }
5408 }
5409 }
5410 return qtrue;
5411 }
5412
5413 return qfalse;
5414 }
5415
5416
5417 extern void G_UcmdMoveForDir( gentity_t *self, usercmd_t *cmd, vec3_t dir );
5418 extern qboolean PM_KickingAnim( int anim );
Jedi_CheckEnemyMovement(float enemy_dist)5419 static void Jedi_CheckEnemyMovement( float enemy_dist )
5420 {
5421 if ( !NPC->enemy || !NPC->enemy->client )
5422 {
5423 return;
5424 }
5425
5426 if ( !(NPCInfo->aiFlags&NPCAI_BOSS_CHARACTER) )
5427 {
5428 if ( PM_KickingAnim( NPC->enemy->client->ps.legsAnim )
5429 && NPC->client->ps.groundEntityNum != ENTITYNUM_NONE
5430 //FIXME: I'm relatively close to him
5431 && (NPC->enemy->client->ps.legsAnim == BOTH_A7_KICK_RL
5432 || NPC->enemy->client->ps.legsAnim == BOTH_A7_KICK_BF
5433 || NPC->enemy->client->ps.legsAnim == BOTH_A7_KICK_S
5434 || (NPC->enemy->enemy && NPC->enemy->enemy == NPC))
5435 )
5436 {//run into the kick!
5437 ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0;
5438 VectorClear( NPC->client->ps.moveDir );
5439 Jedi_Advance();
5440 }
5441 else if ( NPC->enemy->client->ps.torsoAnim == BOTH_A7_HILT
5442 && NPC->client->ps.groundEntityNum != ENTITYNUM_NONE )
5443 {//run into the hilt bash
5444 //FIXME : only if in front!
5445 ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0;
5446 VectorClear( NPC->client->ps.moveDir );
5447 Jedi_Advance();
5448 }
5449 else if ( (NPC->enemy->client->ps.torsoAnim == BOTH_A6_FB
5450 || NPC->enemy->client->ps.torsoAnim == BOTH_A6_LR)
5451 && NPC->client->ps.groundEntityNum != ENTITYNUM_NONE )
5452 {//run into the attack
5453 //FIXME : only if on R/L or F/B?
5454 ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0;
5455 VectorClear( NPC->client->ps.moveDir );
5456 Jedi_Advance();
5457 }
5458 else if ( NPC->enemy->enemy && NPC->enemy->enemy == NPC )
5459 {//enemy is mad at *me*
5460 if ( NPC->enemy->client->ps.legsAnim == BOTH_JUMPFLIPSLASHDOWN1
5461 || NPC->enemy->client->ps.legsAnim == BOTH_JUMPFLIPSTABDOWN
5462 || NPC->enemy->client->ps.legsAnim == BOTH_FLIP_ATTACK7 )
5463 {//enemy is flipping over me
5464 if ( Q_irand( 0, NPCInfo->rank ) < RANK_LT )
5465 {//be nice and stand still for him...
5466 ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0;
5467 VectorClear( NPC->client->ps.moveDir );
5468 NPC->client->ps.forceJumpCharge = 0;
5469 TIMER_Set( NPC, "strafeLeft", -1 );
5470 TIMER_Set( NPC, "strafeRight", -1 );
5471 TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) );
5472 TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) );
5473 TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) );
5474 }
5475 }
5476 else if ( NPC->enemy->client->ps.legsAnim == BOTH_WALL_FLIP_BACK1
5477 || NPC->enemy->client->ps.legsAnim == BOTH_WALL_FLIP_RIGHT
5478 || NPC->enemy->client->ps.legsAnim == BOTH_WALL_FLIP_LEFT
5479 || NPC->enemy->client->ps.legsAnim == BOTH_WALL_RUN_LEFT_FLIP
5480 || NPC->enemy->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT_FLIP )
5481 {//he's flipping off a wall
5482 if ( NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE )
5483 {//still in air
5484 if ( enemy_dist < 256 )
5485 {//close
5486 if ( Q_irand( 0, NPCInfo->rank ) < RANK_LT )
5487 {//be nice and stand still for him...
5488 /*
5489 ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0;
5490 VectorClear( NPC->client->ps.moveDir );
5491 NPC->client->ps.forceJumpCharge = 0;
5492 TIMER_Set( NPC, "strafeLeft", -1 );
5493 TIMER_Set( NPC, "strafeRight", -1 );
5494 TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) );
5495 TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) );
5496 TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) );
5497 TIMER_Set( NPC, "noturn", Q_irand( 200, 500 ) );
5498 */
5499 //stop current movement
5500 ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0;
5501 VectorClear( NPC->client->ps.moveDir );
5502 NPC->client->ps.forceJumpCharge = 0;
5503 TIMER_Set( NPC, "strafeLeft", -1 );
5504 TIMER_Set( NPC, "strafeRight", -1 );
5505 TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) );
5506 TIMER_Set( NPC, "noturn", Q_irand( 250, 500 )*(3-g_spskill->integer) );
5507
5508 vec3_t enemyFwd, dest, dir;
5509
5510 VectorCopy( NPC->enemy->client->ps.velocity, enemyFwd );
5511 VectorNormalize( enemyFwd );
5512 VectorMA( NPC->enemy->currentOrigin, -64, enemyFwd, dest );
5513 VectorSubtract( dest, NPC->currentOrigin, dir );
5514 if ( VectorNormalize( dir ) > 32 )
5515 {
5516 G_UcmdMoveForDir( NPC, &ucmd, dir );
5517 }
5518 else
5519 {
5520 TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) );
5521 TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) );
5522 }
5523 }
5524 }
5525 }
5526 }
5527 else if ( NPC->enemy->client->ps.legsAnim == BOTH_A2_STABBACK1 )
5528 {//he's stabbing backwards
5529 if ( enemy_dist < 256 && enemy_dist > 64 )
5530 {//close
5531 if ( !InFront( NPC->currentOrigin, NPC->enemy->currentOrigin, NPC->enemy->currentAngles, 0.0f ) )
5532 {//behind him
5533 if ( !Q_irand( 0, NPCInfo->rank ) )
5534 {//be nice and stand still for him...
5535 //stop current movement
5536 ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0;
5537 VectorClear( NPC->client->ps.moveDir );
5538 NPC->client->ps.forceJumpCharge = 0;
5539 TIMER_Set( NPC, "strafeLeft", -1 );
5540 TIMER_Set( NPC, "strafeRight", -1 );
5541 TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) );
5542
5543 vec3_t enemyFwd, dest, dir;
5544
5545 AngleVectors( NPC->enemy->currentAngles, enemyFwd, NULL, NULL );
5546 VectorMA( NPC->enemy->currentOrigin, -32, enemyFwd, dest );
5547 VectorSubtract( dest, NPC->currentOrigin, dir );
5548 if ( VectorNormalize( dir ) > 64 )
5549 {
5550 G_UcmdMoveForDir( NPC, &ucmd, dir );
5551 }
5552 else
5553 {
5554 TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) );
5555 TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) );
5556 }
5557 }
5558 }
5559 }
5560 }
5561 }
5562 }
5563 //FIXME: also:
5564 // If enemy doing wall flip, keep running forward
5565 // If enemy doing back-attack and we're behind him keep running forward toward his back, don't strafe
5566 }
5567
Jedi_CheckJumps(void)5568 static void Jedi_CheckJumps( void )
5569 {
5570 if ( (NPCInfo->scriptFlags&SCF_NO_ACROBATICS) )
5571 {
5572 NPC->client->ps.forceJumpCharge = 0;
5573 ucmd.upmove = 0;
5574 return;
5575 }
5576 //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
5577 //FIXME: all this work and he still jumps off ledges... *sigh*... need CONTENTS_BOTCLIP do-not-enter brushes...?
5578 vec3_t jumpVel = {0,0,0};
5579
5580 if ( NPC->client->ps.forceJumpCharge )
5581 {
5582 //gi.Printf( "(%d) force jump\n", level.time );
5583 WP_GetVelocityForForceJump( NPC, jumpVel, &ucmd );
5584 }
5585 else if ( ucmd.upmove > 0 )
5586 {
5587 //gi.Printf( "(%d) regular jump\n", level.time );
5588 VectorCopy( NPC->client->ps.velocity, jumpVel );
5589 jumpVel[2] = JUMP_VELOCITY;
5590 }
5591 else
5592 {
5593 return;
5594 }
5595
5596 //NOTE: for now, we clear ucmd.forwardmove & ucmd.rightmove while in air to avoid jumps going awry...
5597 if ( !jumpVel[0] && !jumpVel[1] )//FIXME: && !ucmd.forwardmove && !ucmd.rightmove?
5598 {//we assume a jump straight up is safe
5599 //gi.Printf( "(%d) jump straight up is safe\n", level.time );
5600 return;
5601 }
5602 //Now predict where this is going
5603 //in steps, keep evaluating the trajectory until the new z pos is <= than current z pos, trace down from there
5604 trace_t trace;
5605 trajectory_t tr;
5606 vec3_t lastPos, testPos, bottom;
5607 int elapsedTime;
5608
5609 VectorCopy( NPC->currentOrigin, tr.trBase );
5610 VectorCopy( jumpVel, tr.trDelta );
5611 tr.trType = TR_GRAVITY;
5612 tr.trTime = level.time;
5613 VectorCopy( NPC->currentOrigin, lastPos );
5614
5615 //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?
5616 for ( elapsedTime = 500; elapsedTime <= 4000; elapsedTime += 500 )
5617 {
5618 EvaluateTrajectory( &tr, level.time + elapsedTime, testPos );
5619 //FIXME: account for PM_AirMove if ucmd.forwardmove and/or ucmd.rightmove is non-zero...
5620 if ( testPos[2] < lastPos[2] )
5621 {//going down, don't check for BOTCLIP
5622 gi.trace( &trace, lastPos, NPC->mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask, (EG2_Collision)0, 0 );//FIXME: include CONTENTS_BOTCLIP?
5623 }
5624 else
5625 {//going up, check for BOTCLIP
5626 gi.trace( &trace, lastPos, NPC->mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP, (EG2_Collision)0, 0 );
5627 }
5628 if ( trace.allsolid || trace.startsolid )
5629 {//WTF?
5630 //FIXME: what do we do when we start INSIDE the CONTENTS_BOTCLIP? Do the trace again without that clipmask?
5631 goto jump_unsafe;
5632 return;
5633 }
5634 if ( trace.fraction < 1.0f )
5635 {//hit something
5636 if ( trace.contents & CONTENTS_BOTCLIP )
5637 {//hit a do-not-enter brush
5638 goto jump_unsafe;
5639 return;
5640 }
5641 //FIXME: trace through func_glass?
5642 break;
5643 }
5644 VectorCopy( testPos, lastPos );
5645 }
5646 //okay, reached end of jump, now trace down from here for a floor
5647 VectorCopy( trace.endpos, bottom );
5648 if ( bottom[2] > NPC->currentOrigin[2] )
5649 {//only care about dist down from current height or lower
5650 bottom[2] = NPC->currentOrigin[2];
5651 }
5652 else if ( NPC->currentOrigin[2] - bottom[2] > 400 )
5653 {//whoa, long drop, don't do it!
5654 //probably no floor at end of jump, so don't jump
5655 goto jump_unsafe;
5656 return;
5657 }
5658 bottom[2] -= 128;
5659 gi.trace( &trace, trace.endpos, NPC->mins, NPC->maxs, bottom, NPC->s.number, NPC->clipmask, (EG2_Collision)0, 0 );
5660 if ( trace.allsolid || trace.startsolid || trace.fraction < 1.0f )
5661 {//hit ground!
5662 if ( trace.entityNum < ENTITYNUM_WORLD )
5663 {//landed on an ent
5664 gentity_t *groundEnt = &g_entities[trace.entityNum];
5665 if ( groundEnt->svFlags&SVF_GLASS_BRUSH )
5666 {//don't land on breakable glass!
5667 goto jump_unsafe;
5668 return;
5669 }
5670 }
5671 //gi.Printf( "(%d) jump is safe\n", level.time );
5672 return;
5673 }
5674 jump_unsafe:
5675 //probably no floor at end of jump, so don't jump
5676 //gi.Printf( "(%d) unsafe jump cleared\n", level.time );
5677 NPC->client->ps.forceJumpCharge = 0;
5678 ucmd.upmove = 0;
5679 }
5680
5681 extern void RT_JetPackEffect( int duration );
RT_CheckJump(void)5682 void RT_CheckJump( void )
5683 {
5684 int jumpEntNum = ENTITYNUM_NONE;
5685 vec3_t jumpPos = {0,0,0};
5686
5687 if ( !NPCInfo->goalEntity )
5688 {
5689 if ( NPC->enemy )
5690 {
5691 //FIXME: debounce this?
5692 if ( TIMER_Done( NPC, "roamTime" )
5693 && Q_irand( 0, 9 ) )
5694 {//okay to try to find another spot to be
5695 int cpFlags = (CP_CLEAR|CP_HAS_ROUTE);//must have a clear shot at enemy
5696 float enemyDistSq = DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin );
5697 //FIXME: base these ranges on weapon
5698 if ( enemyDistSq > (2048*2048) )
5699 {//hmm, close in?
5700 cpFlags |= CP_APPROACH_ENEMY;
5701 }
5702 else if ( enemyDistSq < (256*256) )
5703 {//back off!
5704 cpFlags |= CP_RETREAT;
5705 }
5706 int sendFlags = cpFlags;
5707 int cp = NPC_FindCombatPointRetry( NPC->currentOrigin,
5708 NPC->currentOrigin,
5709 NPC->currentOrigin,
5710 &sendFlags,
5711 256,
5712 NPCInfo->lastFailedCombatPoint );
5713 if ( cp == -1 )
5714 {//try again, no route needed since we can rocket-jump to it!
5715 cpFlags &= ~CP_HAS_ROUTE;
5716 cp = NPC_FindCombatPointRetry( NPC->currentOrigin,
5717 NPC->currentOrigin,
5718 NPC->currentOrigin,
5719 &cpFlags,
5720 256,
5721 NPCInfo->lastFailedCombatPoint );
5722 }
5723 if ( cp != -1 )
5724 {
5725 NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp );
5726 }
5727 else
5728 {//FIXME: okay to do this if have good close-range weapon...
5729 //FIXME: should we really try to go right for him?!
5730 //NPCInfo->goalEntity = NPC->enemy;
5731 jumpEntNum = NPC->enemy->s.number;
5732 VectorCopy( NPC->enemy->currentOrigin, jumpPos );
5733 //return;
5734 }
5735 TIMER_Set( NPC, "roamTime", Q_irand( 3000, 12000 ) );
5736 }
5737 else
5738 {//FIXME: okay to do this if have good close-range weapon...
5739 //FIXME: should we really try to go right for him?!
5740 //NPCInfo->goalEntity = NPC->enemy;
5741 jumpEntNum = NPC->enemy->s.number;
5742 VectorCopy( NPC->enemy->currentOrigin, jumpPos );
5743 //return;
5744 }
5745 }
5746 else
5747 {
5748 return;
5749 }
5750 }
5751 else
5752 {
5753 jumpEntNum = NPCInfo->goalEntity->s.number;
5754 VectorCopy( NPCInfo->goalEntity->currentOrigin, jumpPos );
5755 }
5756 vec3_t vec2Goal;
5757 VectorSubtract( jumpPos, NPC->currentOrigin, vec2Goal );
5758 if ( fabs( vec2Goal[2] ) < 32 )
5759 {//not a big height diff, see how far it is
5760 vec2Goal[2] = 0;
5761 if ( VectorLengthSquared( vec2Goal ) < (256*256) )
5762 {//too close! Don't rocket-jump to it...
5763 return;
5764 }
5765 }
5766 //If we can't get straight at him
5767 if ( !Jedi_ClearPathToSpot( jumpPos, jumpEntNum ) )
5768 {//hunt him down
5769 if ( (NPC_ClearLOS( NPC->enemy )||NPCInfo->enemyLastSeenTime>level.time-500)
5770 && InFOV( jumpPos, NPC->currentOrigin, NPC->client->ps.viewangles, 20, 60 ) )
5771 {
5772 if ( NPC_TryJump( jumpPos ) ) // Rocket Trooper
5773 {//just do the jetpack effect for a litte bit
5774 RT_JetPackEffect( Q_irand( 800, 1500) );
5775 return;
5776 }
5777 }
5778
5779 if ( Jedi_Hunt() && !(NPCInfo->aiFlags&NPCAI_BLOCKED) )//FIXME: have to do this because they can ping-pong forever
5780 {//can macro-navigate to him
5781 return;
5782 }
5783 else
5784 {//FIXME: try to find a waypoint that can see enemy, jump from there
5785 if ( STEER::HasBeenBlockedFor(NPC, 2000) )
5786 {//try to jump to the blockedTargetPosition
5787 if ( NPC_TryJump(NPCInfo->blockedTargetPosition) ) // Rocket Trooper
5788 {//just do the jetpack effect for a litte bit
5789 RT_JetPackEffect( Q_irand( 800, 1500) );
5790 }
5791 }
5792 }
5793 }
5794 }
5795 ////////////////////////////////////////////////////////////////////////////////////////
5796 //
5797 ////////////////////////////////////////////////////////////////////////////////////////
Jedi_Combat(void)5798 static void Jedi_Combat( void )
5799 {
5800 vec3_t enemy_dir, enemy_movedir, enemy_dest;
5801 float enemy_dist, enemy_movespeed;
5802 trace_t trace;
5803
5804 //See where enemy will be 300 ms from now
5805 Jedi_SetEnemyInfo( enemy_dest, enemy_dir, &enemy_dist, enemy_movedir, &enemy_movespeed, 300 );
5806
5807 if ( NPC_Jumping( ) )
5808 {//I'm in the middle of a jump, so just see if I should attack
5809 Jedi_AttackDecide( enemy_dist );
5810 return;
5811 }
5812
5813 if ( TIMER_Done( NPC, "allyJediDelay" ) )
5814 {
5815 if ( !(NPC->client->ps.forcePowersActive&(1<<FP_GRIP)) || NPC->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
5816 {//not gripping
5817 //If we can't get straight at him
5818 if ( !Jedi_ClearPathToSpot( enemy_dest, NPC->enemy->s.number ) )
5819 {//hunt him down
5820 //gi.Printf( "No Clear Path\n" );
5821 if ( (NPC_ClearLOS( NPC->enemy )||NPCInfo->enemyLastSeenTime>level.time-500) && NPC_FaceEnemy( qtrue ) )//( NPCInfo->rank == RANK_CREWMAN || NPCInfo->rank > RANK_LT_JG ) &&
5822 {
5823 //try to jump to him?
5824 /*
5825 vec3_t end;
5826 VectorCopy( NPC->currentOrigin, end );
5827 end[2] += 36;
5828 gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP );
5829 if ( !trace.allsolid && !trace.startsolid && trace.fraction >= 1.0 )
5830 {
5831 vec3_t angles, forward;
5832 VectorCopy( NPC->client->ps.viewangles, angles );
5833 angles[0] = 0;
5834 AngleVectors( angles, forward, NULL, NULL );
5835 VectorMA( end, 64, forward, end );
5836 gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP );
5837 if ( !trace.allsolid && !trace.startsolid )
5838 {
5839 if ( trace.fraction >= 1.0 || trace.plane.normal[2] > 0 )
5840 {
5841 ucmd.upmove = 127;
5842 ucmd.forwardmove = 127;
5843 return;
5844 }
5845 }
5846 }
5847 */
5848 //FIXME: about every 1 second calc a velocity,
5849 //run a loop of traces with evaluate trajectory
5850 //for gravity with my size, see if it makes it...
5851 //this will also catch misacalculations that send you off ledges!
5852 //gi.Printf( "Considering Jump\n" );
5853 if (NPC->client && NPC->client->NPC_class==CLASS_BOBAFETT)
5854 {
5855 Boba_FireDecide();
5856 }
5857 /* else if ( NPC_TryJump( NPC->enemy ) ) // Jedi, can see enemy, but can't get to him
5858 {//FIXME: what about jumping to his enemyLastSeenLocation?
5859 return;
5860 }*/
5861 }
5862
5863 //Check for evasion
5864 if ( TIMER_Done( NPC, "parryTime" ) )
5865 {//finished parrying
5866 if ( NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE &&
5867 NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
5868 {//wasn't blocked myself
5869 NPC->client->ps.saberBlocked = BLOCKED_NONE;
5870 }
5871 }
5872 if ( Jedi_Hunt() && !(NPCInfo->aiFlags&NPCAI_BLOCKED) )//FIXME: have to do this because they can ping-pong forever
5873 {//can macro-navigate to him
5874 if ( enemy_dist < 384 && !Q_irand( 0, 10 ) && NPCInfo->blockedSpeechDebounceTime < level.time && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time && !NPC_ClearLOS( NPC->enemy ) )
5875 {
5876 G_AddVoiceEvent( NPC, Q_irand( EV_JLOST1, EV_JLOST3 ), 3000 );
5877 jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000;
5878 }
5879 if (NPC->client && NPC->client->NPC_class==CLASS_BOBAFETT)
5880 {
5881 Boba_FireDecide();
5882 }
5883
5884 return;
5885 }
5886 //well, try to head for his last seen location
5887 /*
5888 else if ( Jedi_Track() )
5889 {
5890 return;
5891 }
5892 */ else
5893 {//FIXME: try to find a waypoint that can see enemy, jump from there
5894 /* if ( STEER::HasBeenBlockedFor(NPC, 3000) )
5895 {//try to jump to the blockedDest
5896 if (NPCInfo->blockedTargetEntity)
5897 {
5898 NPC_TryJump(NPCInfo->blockedTargetEntity); // commented Out
5899 }
5900 else
5901 {
5902 NPC_TryJump(NPCInfo->blockedTargetPosition);// commented Out
5903 }
5904 }*/
5905
5906 }
5907 }
5908 }
5909 //else, we can see him or we can't track him at all
5910
5911 //every few seconds, decide if we should we advance or retreat?
5912 Jedi_CombatTimersUpdate( enemy_dist );
5913
5914 //We call this even if lost enemy to keep him moving and to update the taunting behavior
5915 //maintain a distance from enemy appropriate for our aggression level
5916 Jedi_CombatDistance( enemy_dist );
5917 }
5918
5919 //if ( !enemy_lost )
5920 if (NPC->client->NPC_class != CLASS_BOBAFETT)
5921 {
5922 //Update our seen enemy position
5923 if ( !NPC->enemy->client || ( NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE && NPC->client->ps.groundEntityNum != ENTITYNUM_NONE ) )
5924 {
5925 VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation );
5926 }
5927 NPCInfo->enemyLastSeenTime = level.time;
5928 }
5929
5930 //Turn to face the enemy
5931 if ( TIMER_Done( NPC, "noturn" ) && !NPC_Jumping() )
5932 {
5933 Jedi_FaceEnemy( qtrue );
5934 }
5935 NPC_UpdateAngles( qtrue, qtrue );
5936
5937 //Check for evasion
5938 if ( TIMER_Done( NPC, "parryTime" ) )
5939 {//finished parrying
5940 if ( NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE &&
5941 NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
5942 {//wasn't blocked myself
5943 NPC->client->ps.saberBlocked = BLOCKED_NONE;
5944 }
5945 }
5946 if ( NPC->enemy->s.weapon == WP_SABER )
5947 {
5948 Jedi_EvasionSaber( enemy_movedir, enemy_dist, enemy_dir );
5949 }
5950 else
5951 {//do we need to do any evasion for other kinds of enemies?
5952 }
5953
5954 //apply strafing/walking timers, etc.
5955 Jedi_TimersApply();
5956
5957 if ( TIMER_Done( NPC, "allyJediDelay" ) )
5958 {
5959 if ( ( !NPC->client->ps.saberInFlight || (NPC->client->ps.saberAnimLevel == SS_DUAL && NPC->client->ps.saber[1].Active()) )
5960 && (!(NPC->client->ps.forcePowersActive&(1<<FP_GRIP))||NPC->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2) )
5961 {//not throwing saber or using force grip
5962 //see if we can attack
5963 if ( !Jedi_AttackDecide( enemy_dist ) )
5964 {//we're not attacking, decide what else to do
5965 Jedi_CombatIdle( enemy_dist );
5966 //FIXME: lower aggression when actually strike offensively? Or just when do damage?
5967 }
5968 else
5969 {//we are attacking
5970 //stop taunting
5971 TIMER_Set( NPC, "taunting", -level.time );
5972 }
5973 }
5974 else
5975 {
5976 }
5977 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
5978 {
5979 Boba_FireDecide();
5980 }
5981 else if ( NPC->client->NPC_class == CLASS_ROCKETTROOPER )
5982 {
5983 RT_FireDecide();
5984 }
5985 }
5986
5987 //Check for certain enemy special moves
5988 Jedi_CheckEnemyMovement( enemy_dist );
5989 //Make sure that we don't jump off ledges over long drops
5990 Jedi_CheckJumps();
5991 //Just make sure we don't strafe into walls or off cliffs
5992
5993 if ( VectorCompare( NPC->client->ps.moveDir, vec3_origin )//stomped the NAV system's moveDir
5994 && !NPC_MoveDirClear( ucmd.forwardmove, ucmd.rightmove, qtrue ) )//check ucmd-driven movement
5995 {//uh-oh, we are going to fall or hit something
5996 /*
5997 navInfo_t info;
5998 //Get the move info
5999 NAV_GetLastMove( info );
6000 if ( !(info.flags & NIF_MACRO_NAV) )
6001 {//micro-navigation told us to step off a ledge, try using macronav for now
6002 NPC_MoveToGoal( qfalse );
6003 }
6004 */
6005 //reset the timers.
6006 TIMER_Set( NPC, "strafeLeft", 0 );
6007 TIMER_Set( NPC, "strafeRight", 0 );
6008 }
6009 }
6010
6011
6012 /*
6013 ==========================================================================================
6014 EXTERNALLY CALLED BEHAVIOR STATES
6015 ==========================================================================================
6016 */
6017
6018 /*
6019 -------------------------
6020 NPC_Jedi_Pain
6021 -------------------------
6022 */
6023
NPC_Jedi_Pain(gentity_t * self,gentity_t * inflictor,gentity_t * other,const vec3_t point,int damage,int mod,int hitLoc)6024 void NPC_Jedi_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
6025 {
6026 //FIXME: base the actual aggression add/subtract on health?
6027 //FIXME: don't do this more than once per frame?
6028 //FIXME: when take pain, stop force gripping....?
6029 if ( other->s.weapon == WP_SABER )
6030 {//back off
6031 TIMER_Set( self, "parryTime", -1 );
6032 if ( self->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",self->NPC_type) )
6033 {//less for Desann
6034 self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_spskill->integer)*50;
6035 }
6036 else if ( self->NPC->rank >= RANK_LT_JG )
6037 {
6038 self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_spskill->integer)*100;//300
6039 }
6040 else
6041 {
6042 self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_spskill->integer)*200;//500
6043 }
6044 if ( !Q_irand( 0, 3 ) )
6045 {//ouch... maybe switch up which saber power level we're using
6046 Jedi_AdjustSaberAnimLevel( self, Q_irand( SS_FAST, SS_STRONG ) );
6047 }
6048 if ( !Q_irand( 0, 1 ) )//damage > 20 || self->health < 40 ||
6049 {
6050 //Com_Printf( "(%d) drop agg - hit by saber\n", level.time );
6051 Jedi_Aggression( self, -1 );
6052 }
6053 if ( d_JediAI->integer )
6054 {
6055 gi.Printf( "(%d) PAIN: agg %d, no parry until %d\n", level.time, self->NPC->stats.aggression, level.time+500 );
6056 }
6057 //for testing only
6058 // Figure out what quadrant the hit was in.
6059 if ( d_JediAI->integer )
6060 {
6061 vec3_t diff, fwdangles, right;
6062
6063 VectorSubtract( point, self->client->renderInfo.eyePoint, diff );
6064 diff[2] = 0;
6065 fwdangles[1] = self->client->ps.viewangles[1];
6066 AngleVectors( fwdangles, NULL, right, NULL );
6067 float rightdot = DotProduct(right, diff);
6068 float zdiff = point[2] - self->client->renderInfo.eyePoint[2];
6069
6070 gi.Printf( "(%d) saber hit at height %4.2f, zdiff: %4.2f, rightdot: %4.2f\n", level.time, point[2]-self->absmin[2],zdiff,rightdot);
6071 }
6072 }
6073 else
6074 {//attack
6075 //Com_Printf( "(%d) raise agg - hit by ranged\n", level.time );
6076 Jedi_Aggression( self, 1 );
6077 }
6078
6079 self->NPC->enemyCheckDebounceTime = 0;
6080
6081 WP_ForcePowerStop( self, FP_GRIP );
6082
6083 NPC_Pain( self, inflictor, other, point, damage, mod );
6084
6085 if ( !damage && self->health > 0 )
6086 {//FIXME: better way to know I was pushed
6087 G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
6088 }
6089
6090 //drop me from the ceiling if I'm on it
6091 if ( Jedi_WaitingAmbush( self ) )
6092 {
6093 self->client->noclip = false;
6094 }
6095 if ( self->client->ps.legsAnim == BOTH_CEILING_CLING )
6096 {
6097 NPC_SetAnim( self, SETANIM_LEGS, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
6098 }
6099 if ( self->client->ps.torsoAnim == BOTH_CEILING_CLING )
6100 {
6101 NPC_SetAnim( self, SETANIM_TORSO, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
6102 }
6103
6104 //check special defenses
6105 if ( other
6106 && other->client
6107 && !OnSameTeam( self, other ))
6108 {//hit by a client
6109 //FIXME: delay this until *after* the pain anim?
6110 if ( mod == MOD_FORCE_GRIP
6111 || mod == MOD_FORCE_LIGHTNING
6112 || mod == MOD_FORCE_DRAIN )
6113 {//see if we should turn on absorb
6114 if ( (self->client->ps.forcePowersKnown&(1<<FP_ABSORB)) != 0
6115 && (self->client->ps.forcePowersActive&(1<<FP_ABSORB)) == 0 )
6116 {//know absorb and not already using it
6117 if ( other->s.number >= MAX_CLIENTS //enemy is an NPC
6118 || Q_irand( 0, g_spskill->integer+1 ) )//enemy is player
6119 {
6120 if ( Q_irand( 0, self->NPC->rank ) > RANK_ENSIGN )
6121 {
6122 if ( !Q_irand( 0, 5 ) )
6123 {
6124 ForceAbsorb( self );
6125 }
6126 }
6127 }
6128 }
6129 }
6130 else if ( damage > Q_irand( 5, 20 ) )
6131 {//respectable amount of normal damage
6132 if ( (self->client->ps.forcePowersKnown&(1<<FP_PROTECT)) != 0
6133 && (self->client->ps.forcePowersActive&(1<<FP_PROTECT)) == 0 )
6134 {//know protect and not already using it
6135 if ( other->s.number >= MAX_CLIENTS //enemy is an NPC
6136 || Q_irand( 0, g_spskill->integer+1 ) )//enemy is player
6137 {
6138 if ( Q_irand( 0, self->NPC->rank ) > RANK_ENSIGN )
6139 {
6140 if ( !Q_irand( 0, 1 ) )
6141 {
6142 if ( other->s.number < MAX_CLIENTS
6143 && ((self->NPC->aiFlags&NPCAI_BOSS_CHARACTER)
6144 || self->client->NPC_class==CLASS_SHADOWTROOPER)
6145 && Q_irand(0, 6-g_spskill->integer) )
6146 {
6147 }
6148 else
6149 {
6150 ForceProtect( self );
6151 }
6152 }
6153 }
6154 }
6155 }
6156 }
6157 }
6158 }
6159
Jedi_CheckDanger(void)6160 qboolean Jedi_CheckDanger( void )
6161 {
6162 int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue );
6163 if ( level.alertEvents[alertEvent].level >= AEL_DANGER )
6164 {//run away!
6165 if ( !level.alertEvents[alertEvent].owner
6166 || !level.alertEvents[alertEvent].owner->client
6167 || (level.alertEvents[alertEvent].owner!=NPC&&level.alertEvents[alertEvent].owner->client->playerTeam!=NPC->client->playerTeam) )
6168 {//no owner
6169 return qfalse;
6170 }
6171 G_SetEnemy( NPC, level.alertEvents[alertEvent].owner );
6172 NPCInfo->enemyLastSeenTime = level.time;
6173 TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
6174 return qtrue;
6175 }
6176 return qfalse;
6177 }
6178
6179 extern int g_crosshairEntNum;
Jedi_CheckAmbushPlayer(void)6180 qboolean Jedi_CheckAmbushPlayer( void )
6181 {
6182 if ( !player || !player->client )
6183 {
6184 return qfalse;
6185 }
6186
6187 if ( !NPC_ValidEnemy( player ) )
6188 {
6189 return qfalse;
6190 }
6191
6192 if ( NPC->client->ps.powerups[PW_CLOAKED] || g_crosshairEntNum != NPC->s.number )
6193 {//if I'm not cloaked and the player's crosshair is on me, I will wake up, otherwise do this stuff down here...
6194 if ( !gi.inPVS( player->currentOrigin, NPC->currentOrigin ) )
6195 {//must be in same room
6196 return qfalse;
6197 }
6198 else
6199 {
6200 if ( !NPC->client->ps.powerups[PW_CLOAKED] )
6201 {
6202 NPC_SetLookTarget( NPC, 0, 0 );
6203 }
6204 }
6205 float target_dist, zDiff = NPC->currentOrigin[2]-player->currentOrigin[2];
6206 if ( zDiff <= 0 || zDiff > 512 )
6207 {//never ambush if they're above me or way way below me
6208 return qfalse;
6209 }
6210
6211 //If the target is this close, then wake up regardless
6212 if ( (target_dist = DistanceHorizontalSquared( player->currentOrigin, NPC->currentOrigin )) > 4096 )
6213 {//closer than 64 - always ambush
6214 if ( target_dist > 147456 )
6215 {//> 384, not close enough to ambush
6216 return qfalse;
6217 }
6218 //Check FOV first
6219 if ( NPC->client->ps.powerups[PW_CLOAKED] )
6220 {
6221 if ( InFOV( player, NPC, 30, 90 ) == qfalse )
6222 {
6223 return qfalse;
6224 }
6225 }
6226 else
6227 {
6228 if ( InFOV( player, NPC, 45, 90 ) == qfalse )
6229 {
6230 return qfalse;
6231 }
6232 }
6233 }
6234
6235 if ( !NPC_ClearLOS( player ) )
6236 {
6237 return qfalse;
6238 }
6239 }
6240
6241 G_SetEnemy( NPC, player );
6242 NPCInfo->enemyLastSeenTime = level.time;
6243 TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
6244 return qtrue;
6245 }
6246
Jedi_Ambush(gentity_t * self)6247 void Jedi_Ambush( gentity_t *self )
6248 {
6249 self->client->noclip = false;
6250 self->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL;
6251 NPC_SetAnim( self, SETANIM_BOTH, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
6252 self->client->ps.weaponTime = NPC->client->ps.torsoAnimTimer;
6253 if ( self->client->NPC_class != CLASS_BOBAFETT
6254 && self->client->NPC_class != CLASS_ROCKETTROOPER )
6255 {
6256 self->client->ps.SaberActivate();
6257 }
6258 Jedi_Decloak( self );
6259 G_AddVoiceEvent( self, Q_irand( EV_ANGER1, EV_ANGER3 ), 1000 );
6260 }
6261
Jedi_WaitingAmbush(gentity_t * self)6262 qboolean Jedi_WaitingAmbush( gentity_t *self )
6263 {
6264 if ( (self->spawnflags&JSF_AMBUSH) && self->client->noclip )
6265 {
6266 return qtrue;
6267 }
6268 return qfalse;
6269 }
6270 /*
6271 -------------------------
6272 Jedi_Patrol
6273 -------------------------
6274 */
6275
Jedi_Patrol(void)6276 static void Jedi_Patrol( void )
6277 {
6278 NPC->client->ps.saberBlocked = BLOCKED_NONE;
6279
6280 if ( Jedi_WaitingAmbush( NPC ) )
6281 {//hiding on the ceiling
6282 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_CEILING_CLING, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
6283 if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
6284 {//look for enemies
6285 if ( Jedi_CheckAmbushPlayer() || Jedi_CheckDanger() )
6286 {//found him!
6287 Jedi_Ambush( NPC );
6288 NPC_UpdateAngles( qtrue, qtrue );
6289 return;
6290 }
6291 }
6292 }
6293 else if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )//NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
6294 {//look for enemies
6295 gentity_t *best_enemy = NULL;
6296 float best_enemy_dist = Q3_INFINITE;
6297 for ( int i = 0; i < ENTITYNUM_WORLD; i++ )
6298 {
6299 gentity_t *enemy = &g_entities[i];
6300 float enemy_dist;
6301 if ( enemy && enemy->client && NPC_ValidEnemy( enemy ))
6302 {
6303 if ( gi.inPVS( NPC->currentOrigin, enemy->currentOrigin ) )
6304 {//we could potentially see him
6305 enemy_dist = DistanceSquared( NPC->currentOrigin, enemy->currentOrigin );
6306 if ( enemy->s.number == 0 || enemy_dist < best_enemy_dist )
6307 {
6308 //if the enemy is close enough, or threw his saber, take him as the enemy
6309 //FIXME: what if he throws a thermal detonator?
6310 //FIXME: use jediSpeechDebounceTime[NPC->client->playerTeam] < level.time ) check for anger sound
6311 if ( enemy_dist < (220*220) || ( NPCInfo->investigateCount>= 3 && NPC->client->ps.SaberActive() ) )
6312 {
6313 G_SetEnemy( NPC, enemy );
6314 //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now
6315 NPCInfo->stats.aggression = 3;
6316 break;
6317 }
6318 else if ( enemy->client->ps.saberInFlight && enemy->client->ps.SaberActive() )
6319 {//threw his saber, see if it's heading toward me and close enough to consider a threat
6320 float saberDist;
6321 vec3_t saberDir2Me;
6322 vec3_t saberMoveDir;
6323 gentity_t *saber = &g_entities[enemy->client->ps.saberEntityNum];
6324 VectorSubtract( NPC->currentOrigin, saber->currentOrigin, saberDir2Me );
6325 saberDist = VectorNormalize( saberDir2Me );
6326 VectorCopy( saber->s.pos.trDelta, saberMoveDir );
6327 VectorNormalize( saberMoveDir );
6328 if ( DotProduct( saberMoveDir, saberDir2Me ) > 0.5 )
6329 {//it's heading towards me
6330 if ( saberDist < 200 )
6331 {//incoming!
6332 G_SetEnemy( NPC, enemy );
6333 //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now
6334 NPCInfo->stats.aggression = 3;
6335 break;
6336 }
6337 }
6338 }
6339 best_enemy_dist = enemy_dist;
6340 best_enemy = enemy;
6341 }
6342 }
6343 }
6344 }
6345 if ( !NPC->enemy )
6346 {//still not mad
6347 if ( !best_enemy )
6348 {
6349 //Com_Printf( "(%d) drop agg - no enemy (patrol)\n", level.time );
6350 Jedi_AggressionErosion(-1);
6351 //FIXME: what about alerts? But not if ignore alerts
6352 }
6353 else
6354 {//have one to consider
6355 if ( NPC_ClearLOS( best_enemy ) )
6356 {//we have a clear (of architecture) LOS to him
6357 if ( (NPCInfo->aiFlags&NPCAI_NO_JEDI_DELAY) )
6358 {//just get mad right away
6359 if ( DistanceHorizontalSquared( NPC->currentOrigin, best_enemy->currentOrigin ) < (1024*1024) )
6360 {
6361 G_SetEnemy( NPC, best_enemy );
6362 NPCInfo->stats.aggression = 20;
6363 }
6364 }
6365 else if ( best_enemy->s.number )
6366 {//just attack
6367 G_SetEnemy( NPC, best_enemy );
6368 NPCInfo->stats.aggression = 3;
6369 }
6370 else if ( NPC->client->NPC_class != CLASS_BOBAFETT )
6371 {//the player, toy with him
6372 //get progressively more interested over time
6373 if ( TIMER_Done( NPC, "watchTime" ) )
6374 {//we want to pick him up in stages
6375 if ( TIMER_Get( NPC, "watchTime" ) == -1 )
6376 {//this is the first time, we'll ignore him for a couple seconds
6377 TIMER_Set( NPC, "watchTime", Q_irand( 3000, 5000 ) );
6378 goto finish;
6379 }
6380 else
6381 {//okay, we've ignored him, now start to notice him
6382 if ( !NPCInfo->investigateCount )
6383 {
6384 G_AddVoiceEvent( NPC, Q_irand( EV_JDETECTED1, EV_JDETECTED3 ), 3000 );
6385 }
6386 NPCInfo->investigateCount++;
6387 TIMER_Set( NPC, "watchTime", Q_irand( 4000, 10000 ) );
6388 }
6389 }
6390 //while we're waiting, do what we need to do
6391 if ( best_enemy_dist < (440*440) || NPCInfo->investigateCount >= 2 )
6392 {//stage three: keep facing him
6393 NPC_FaceEntity( best_enemy, qtrue );
6394 if ( best_enemy_dist < (330*330) )
6395 {//stage four: turn on the saber
6396 if ( !NPC->client->ps.saberInFlight )
6397 {
6398 NPC->client->ps.SaberActivate();
6399 }
6400 }
6401 }
6402 else if ( best_enemy_dist < (550*550) || NPCInfo->investigateCount == 1 )
6403 {//stage two: stop and face him every now and then
6404 if ( TIMER_Done( NPC, "watchTime" ) )
6405 {
6406 NPC_FaceEntity( best_enemy, qtrue );
6407 }
6408 }
6409 else
6410 {//stage one: look at him.
6411 NPC_SetLookTarget( NPC, best_enemy->s.number, 0 );
6412 }
6413 }
6414 }
6415 else if ( TIMER_Done( NPC, "watchTime" ) )
6416 {//haven't seen him in a bit, clear the lookTarget
6417 NPC_ClearLookTarget( NPC );
6418 }
6419 }
6420 }
6421 }
6422 finish:
6423 //If we have somewhere to go, then do that
6424 if ( UpdateGoal() )
6425 {
6426 ucmd.buttons |= BUTTON_WALKING;
6427 //Jedi_Move( NPCInfo->goalEntity );
6428 NPC_MoveToGoal( qtrue );
6429 }
6430
6431 NPC_UpdateAngles( qtrue, qtrue );
6432
6433 if ( NPC->enemy )
6434 {//just picked one up
6435 NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 );
6436 }
6437 }
6438
Jedi_CanPullBackSaber(gentity_t * self)6439 qboolean Jedi_CanPullBackSaber( gentity_t *self )
6440 {
6441 if ( self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN && !TIMER_Done( self, "parryTime" ) )
6442 {
6443 return qfalse;
6444 }
6445
6446 if ( self->client->NPC_class == CLASS_SHADOWTROOPER
6447 || self->client->NPC_class == CLASS_ALORA
6448 || ( self->NPC && (self->NPC->aiFlags&NPCAI_BOSS_CHARACTER) ) )
6449 {
6450 return qtrue;
6451 }
6452
6453 if ( self->painDebounceTime > level.time )//|| self->client->ps.weaponTime > 0 )
6454 {
6455 return qfalse;
6456 }
6457
6458 return qtrue;
6459 }
6460 /*
6461 -------------------------
6462 NPC_BSJedi_FollowLeader
6463 -------------------------
6464 */
6465 extern qboolean NAV_CheckAhead( gentity_t *self, vec3_t end, trace_t &trace, int clipmask );
6466
NPC_BSJedi_FollowLeader(void)6467 void NPC_BSJedi_FollowLeader( void )
6468 {
6469 NPC->client->ps.saberBlocked = BLOCKED_NONE;
6470 if ( !NPC->enemy )
6471 {
6472 //Com_Printf( "(%d) drop agg - no enemy (follow)\n", level.time );
6473 Jedi_AggressionErosion(-1);
6474 }
6475
6476 //did we drop our saber? If so, go after it!
6477 if ( NPC->client->ps.saberInFlight )
6478 {//saber is not in hand
6479 if ( NPC->client->ps.saberEntityNum < ENTITYNUM_NONE && NPC->client->ps.saberEntityNum > 0 )//player is 0
6480 {//
6481 if ( g_entities[NPC->client->ps.saberEntityNum].s.pos.trType == TR_STATIONARY )
6482 {//fell to the ground, try to pick it up...
6483 if ( Jedi_CanPullBackSaber( NPC ) )
6484 {
6485 //FIXME: if it's on the ground and we just pulled it back to us, should we
6486 // stand still for a bit to make sure it gets to us...?
6487 // otherwise we could end up running away from it while it's on its
6488 // way back to us and we could lose it again.
6489 NPC->client->ps.saberBlocked = BLOCKED_NONE;
6490 NPCInfo->goalEntity = &g_entities[NPC->client->ps.saberEntityNum];
6491 ucmd.buttons |= BUTTON_ATTACK;
6492 if ( NPC->enemy && NPC->enemy->health > 0 )
6493 {//get our saber back NOW!
6494 if ( !NPC_MoveToGoal( qtrue ) )//Jedi_Move( NPCInfo->goalEntity, qfalse );
6495 {//can't nav to it, try jumping to it
6496 NPC_FaceEntity( NPCInfo->goalEntity, qtrue );
6497 NPC_TryJump( NPCInfo->goalEntity );
6498 }
6499 NPC_UpdateAngles( qtrue, qtrue );
6500 return;
6501 }
6502 }
6503 }
6504 }
6505 }
6506
6507 //try normal movement
6508 NPC_BSFollowLeader();
6509
6510
6511 if (!NPC->enemy &&
6512 NPC->health < NPC->max_health &&
6513 (NPC->client->ps.forcePowersKnown&(1<<FP_HEAL)) != 0 &&
6514 (NPC->client->ps.forcePowersActive&(1<<FP_HEAL)) == 0 &&
6515 TIMER_Done(NPC, "FollowHealDebouncer"))
6516 {
6517 if (Q_irand(0,3)==0)
6518 {
6519 TIMER_Set(NPC, "FollowHealDebouncer", Q_irand(12000, 18000));
6520 ForceHeal( NPC );
6521 }
6522 else
6523 {
6524 TIMER_Set(NPC, "FollowHealDebouncer", Q_irand(1000, 2000));
6525 }
6526 }
6527
6528 }
6529
Jedi_CheckKataAttack(void)6530 qboolean Jedi_CheckKataAttack( void )
6531 {
6532 if ( NPCInfo->rank >= RANK_LT_COMM )
6533 {//only top-level guys and bosses do this
6534 if ( (ucmd.buttons&BUTTON_ATTACK) )
6535 {//attacking
6536 if ( (g_saberNewControlScheme->integer
6537 && !(ucmd.buttons&BUTTON_FORCE_FOCUS) )
6538 ||(!g_saberNewControlScheme->integer
6539 && !(ucmd.buttons&BUTTON_ALT_ATTACK) ) )
6540 {//not already going to do a kata move somehow
6541 if ( NPC->client->ps.groundEntityNum != ENTITYNUM_NONE )
6542 {//on the ground
6543 if ( ucmd.upmove <= 0 && NPC->client->ps.forceJumpCharge <= 0 )
6544 {//not going to try to jump
6545 /*
6546 if ( (NPCInfo->scriptFlags&SCF_NO_ACROBATICS) )
6547 {//uh-oh, no jumping moves!
6548 if ( NPC->client->ps.saberAnimLevel == SS_STAFF )
6549 {//this kata move has a jump in it...
6550 return qfalse;
6551 }
6552 }
6553 */
6554
6555 if ( Q_irand( 0, g_spskill->integer+1 ) //50% chance on easy, 66% on medium, 75% on hard
6556 && !Q_irand( 0, 9 ) )//10% chance overall
6557 {//base on skill level
6558 ucmd.upmove = 0;
6559 VectorClear( NPC->client->ps.moveDir );
6560 if ( g_saberNewControlScheme->integer )
6561 {
6562 ucmd.buttons |= BUTTON_FORCE_FOCUS;
6563 }
6564 else
6565 {
6566 ucmd.buttons |= BUTTON_ALT_ATTACK;
6567 }
6568 return qtrue;
6569 }
6570 }
6571 }
6572 }
6573 }
6574 }
6575 return qfalse;
6576 }
6577
6578
6579 /*
6580 -------------------------
6581 Jedi_Attack
6582 -------------------------
6583 */
6584
Jedi_Attack(void)6585 static void Jedi_Attack( void )
6586 {
6587 //Don't do anything if we're in a pain anim
6588 if ( NPC->painDebounceTime > level.time )
6589 {
6590 if ( Q_irand( 0, 1 ) )
6591 {
6592 Jedi_FaceEnemy( qtrue );
6593 }
6594 NPC_UpdateAngles( qtrue, qtrue );
6595 if ( NPC->client->ps.torsoAnim == BOTH_KYLE_GRAB )
6596 {//see if we grabbed enemy
6597 if ( NPC->client->ps.torsoAnimTimer <= 200 )
6598 {
6599 if ( Kyle_CanDoGrab()
6600 && NPC_EnemyRangeFromBolt( NPC->handRBolt ) < 88.0f )
6601 {//grab him!
6602 Kyle_GrabEnemy();
6603 return;
6604 }
6605 else
6606 {
6607 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
6608 NPC->client->ps.weaponTime = NPC->client->ps.torsoAnimTimer;
6609 return;
6610 }
6611 }
6612 //else just sit here?
6613 }
6614 return;
6615 }
6616
6617 if ( NPC->client->ps.saberLockTime > level.time )
6618 {
6619 //FIXME: maybe kick out of saberlock?
6620 //maybe if I'm losing I should try to force-push out of it? Very rarely, though...
6621 if ( NPC->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2
6622 && NPC->client->ps.saberLockTime < level.time + 5000
6623 && !Q_irand( 0, 10 ))
6624 {
6625 ForceThrow( NPC, qfalse );
6626 }
6627 //based on my skill, hit attack button every other to every several frames in order to push enemy back
6628 else
6629 {
6630 float chance;
6631
6632 if ( NPC->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",NPC->NPC_type) )
6633 {
6634 if ( g_spskill->integer )
6635 {
6636 chance = 4.0f;//he pushes *hard*
6637 }
6638 else
6639 {
6640 chance = 3.0f;//he pushes *hard*
6641 }
6642 }
6643 else if ( NPC->client->NPC_class == CLASS_TAVION
6644 || NPC->client->NPC_class == CLASS_SHADOWTROOPER
6645 || NPC->client->NPC_class == CLASS_ALORA
6646 || (NPC->client->NPC_class == CLASS_KYLE&&(NPC->spawnflags&1)) )
6647 {
6648 chance = 2.0f+g_spskill->value;//from 2 to 4
6649 }
6650 else
6651 {//the escalation in difficulty is nice, here, but cap it so it doesn't get *impossible* on hard
6652 float maxChance = (float)(RANK_LT)/2.0f+3.0f;//5?
6653 if ( !g_spskill->value )
6654 {
6655 chance = (float)(NPCInfo->rank)/2.0f;
6656 }
6657 else
6658 {
6659 chance = (float)(NPCInfo->rank)/2.0f+1.0f;
6660 }
6661 if ( chance > maxChance )
6662 {
6663 chance = maxChance;
6664 }
6665 }
6666 if ( (NPCInfo->aiFlags&NPCAI_BOSS_CHARACTER) )
6667 {
6668 chance += Q_irand(0,2);
6669 }
6670 else if ( (NPCInfo->aiFlags&NPCAI_SUBBOSS_CHARACTER) )
6671 {
6672 chance += Q_irand(-1,1);
6673 }
6674 if ( Q_flrand( -4.0f, chance ) >= 0.0f && !(NPC->client->ps.pm_flags&PMF_ATTACK_HELD) )
6675 {
6676 ucmd.buttons |= BUTTON_ATTACK;
6677 }
6678 }
6679 NPC_UpdateAngles( qtrue, qtrue );
6680 return;
6681 }
6682 //did we drop our saber? If so, go after it!
6683 if ( NPC->client->ps.saberInFlight )
6684 {//saber is not in hand
6685 if ( NPC->client->ps.saberEntityNum < ENTITYNUM_NONE && NPC->client->ps.saberEntityNum > 0 )//player is 0
6686 {//
6687 if ( g_entities[NPC->client->ps.saberEntityNum].s.pos.trType == TR_STATIONARY )
6688 {//fell to the ground, try to pick it up
6689 if ( Jedi_CanPullBackSaber( NPC ) )
6690 {
6691 NPC->client->ps.saberBlocked = BLOCKED_NONE;
6692 NPCInfo->goalEntity = &g_entities[NPC->client->ps.saberEntityNum];
6693 ucmd.buttons |= BUTTON_ATTACK;
6694 if ( NPC->enemy && NPC->enemy->health > 0 )
6695 {//get our saber back NOW!
6696 Jedi_Move( NPCInfo->goalEntity, qfalse );
6697 NPC_UpdateAngles( qtrue, qtrue );
6698 if ( NPC->enemy->s.weapon == WP_SABER )
6699 {//be sure to continue evasion
6700 vec3_t enemy_dir, enemy_movedir, enemy_dest;
6701 float enemy_dist, enemy_movespeed;
6702 Jedi_SetEnemyInfo( enemy_dest, enemy_dir, &enemy_dist, enemy_movedir, &enemy_movespeed, 300 );
6703 Jedi_EvasionSaber( enemy_movedir, enemy_dist, enemy_dir );
6704 }
6705 return;
6706 }
6707 }
6708 }
6709 }
6710 }
6711 //see if our enemy was killed by us, gloat and turn off saber after cool down.
6712 //FIXME: don't do this if we have other enemies to fight...?
6713 if ( NPC->enemy )
6714 {
6715 if ( NPC->enemy->health <= 0
6716 && NPC->enemy->enemy == NPC
6717 && (NPC->client->playerTeam != TEAM_PLAYER||(NPC->client->NPC_class==CLASS_KYLE&&(NPC->spawnflags&1)&&NPC->enemy==player)) )//good guys don't gloat (unless it's Kyle having just killed his student
6718 {//my enemy is dead and I killed him
6719 NPCInfo->enemyCheckDebounceTime = 0;//keep looking for others
6720
6721 if ( NPC->client->NPC_class == CLASS_BOBAFETT
6722 || (NPC->client->NPC_class == CLASS_REBORN && NPC->s.weapon != WP_SABER)
6723 || NPC->client->NPC_class == CLASS_ROCKETTROOPER )
6724 {
6725 if ( NPCInfo->walkDebounceTime < level.time && NPCInfo->walkDebounceTime >= 0 )
6726 {
6727 TIMER_Set( NPC, "gloatTime", 10000 );
6728 NPCInfo->walkDebounceTime = -1;
6729 }
6730 if ( !TIMER_Done( NPC, "gloatTime" ) )
6731 {
6732 if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared
6733 {
6734 NPCInfo->goalEntity = NPC->enemy;
6735 Jedi_Move( NPC->enemy, qfalse );
6736 ucmd.buttons |= BUTTON_WALKING;
6737 }
6738 else
6739 {
6740 TIMER_Set( NPC, "gloatTime", 0 );
6741 }
6742 }
6743 else if ( NPCInfo->walkDebounceTime == -1 )
6744 {
6745 NPCInfo->walkDebounceTime = -2;
6746 G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 );
6747 jediSpeechDebounceTime[NPC->client->playerTeam] = level.time + 3000;
6748 NPCInfo->desiredPitch = 0;
6749 NPCInfo->goalEntity = NULL;
6750 }
6751 Jedi_FaceEnemy( qtrue );
6752 NPC_UpdateAngles( qtrue, qtrue );
6753 return;
6754 }
6755 else
6756 {
6757 if ( !TIMER_Done( NPC, "parryTime" ) )
6758 {
6759 TIMER_Set( NPC, "parryTime", -1 );
6760 NPC->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500;
6761 }
6762 NPC->client->ps.saberBlocked = BLOCKED_NONE;
6763 if ( NPC->client->ps.SaberActive() || NPC->client->ps.saberInFlight )
6764 {//saber is still on (or we're trying to pull it back), count down erosion and keep facing the enemy
6765 //FIXME: need to stop this from happening over and over again when they're blocking their victim's saber
6766 //FIXME: turn off saber sooner so we get cool walk anim?
6767 //Com_Printf( "(%d) drop agg - enemy dead\n", level.time );
6768 Jedi_AggressionErosion(-3);
6769 if ( !NPC->client->ps.SaberActive() && !NPC->client->ps.saberInFlight )
6770 {//turned off saber (in hand), gloat
6771 G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 );
6772 jediSpeechDebounceTime[NPC->client->playerTeam] = level.time + 3000;
6773 NPCInfo->desiredPitch = 0;
6774 NPCInfo->goalEntity = NULL;
6775 }
6776 TIMER_Set( NPC, "gloatTime", 10000 );
6777 }
6778 if ( NPC->client->ps.SaberActive() || NPC->client->ps.saberInFlight || !TIMER_Done( NPC, "gloatTime" ) )
6779 {//keep walking
6780 if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared
6781 {
6782 NPCInfo->goalEntity = NPC->enemy;
6783 Jedi_Move( NPC->enemy, qfalse );
6784 ucmd.buttons |= BUTTON_WALKING;
6785 }
6786 else
6787 {//got there
6788 if ( NPC->health < NPC->max_health )
6789 {
6790 if ( NPC->client->ps.saber[0].type == SABER_SITH_SWORD
6791 && NPC->weaponModel[0] != -1 )
6792 {
6793 Tavion_SithSwordRecharge();
6794 }
6795 else if ( (NPC->client->ps.forcePowersKnown&(1<<FP_HEAL)) != 0
6796 && (NPC->client->ps.forcePowersActive&(1<<FP_HEAL)) == 0 )
6797 {
6798 ForceHeal( NPC );
6799 }
6800 }
6801 }
6802 Jedi_FaceEnemy( qtrue );
6803 NPC_UpdateAngles( qtrue, qtrue );
6804 return;
6805 }
6806 }
6807 }
6808 }
6809
6810 //If we don't have an enemy, just idle
6811 if ( NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ) )
6812 {
6813 if ( NPC->enemy->count <= 0 )
6814 {//it's out of ammo
6815 if ( NPC->enemy->activator && NPC_ValidEnemy( NPC->enemy->activator ) )
6816 {
6817 gentity_t *turretOwner = NPC->enemy->activator;
6818 G_ClearEnemy( NPC );
6819 G_SetEnemy( NPC, turretOwner );
6820 }
6821 else
6822 {
6823 G_ClearEnemy( NPC );
6824 }
6825 }
6826 }
6827 else if ( NPC->enemy &&
6828 NPC->enemy->NPC
6829 && NPC->enemy->NPC->charmedTime > level.time )
6830 {//my enemy was charmed
6831 if ( OnSameTeam( NPC, NPC->enemy ) )
6832 {//has been charmed to be on my team
6833 G_ClearEnemy( NPC );
6834 }
6835 }
6836 if ( NPC->client->playerTeam == TEAM_ENEMY
6837 && NPC->client->enemyTeam == TEAM_PLAYER
6838 && NPC->enemy
6839 && NPC->enemy->client
6840 && NPC->enemy->client->playerTeam != NPC->client->enemyTeam
6841 && OnSameTeam( NPC, NPC->enemy )
6842 && !(NPC->svFlags&SVF_LOCKEDENEMY) )
6843 {//an evil jedi somehow got another evil NPC as an enemy, they were probably charmed and it's run out now
6844 if ( !NPC_ValidEnemy( NPC->enemy ) )
6845 {
6846 G_ClearEnemy( NPC );
6847 }
6848 }
6849 NPC_CheckEnemy( qtrue, qtrue );
6850
6851 if ( !NPC->enemy )
6852 {
6853 NPC->client->ps.saberBlocked = BLOCKED_NONE;
6854 if ( NPCInfo->tempBehavior == BS_HUNT_AND_KILL )
6855 {//lost him, go back to what we were doing before
6856 NPCInfo->tempBehavior = BS_DEFAULT;
6857 NPC_UpdateAngles( qtrue, qtrue );
6858 return;
6859 }
6860 Jedi_Patrol();//was calling Idle... why?
6861 return;
6862 }
6863
6864 //always face enemy if have one
6865 NPCInfo->combatMove = qtrue;
6866
6867 //Track the player and kill them if possible
6868 Jedi_Combat();
6869
6870 if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES)
6871 || ((NPC->client->ps.forcePowersActive&(1<<FP_HEAL))&&NPC->client->ps.forcePowerLevel[FP_HEAL]<FORCE_LEVEL_2))
6872 {//this is really stupid, but okay...
6873 ucmd.forwardmove = 0;
6874 ucmd.rightmove = 0;
6875 if ( ucmd.upmove > 0 )
6876 {
6877 ucmd.upmove = 0;
6878 }
6879 NPC->client->ps.forceJumpCharge = 0;
6880 VectorClear( NPC->client->ps.moveDir );
6881 }
6882
6883 //NOTE: for now, we clear ucmd.forwardmove & ucmd.rightmove while in air to avoid jumps going awry...
6884 if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE )
6885 {//don't push while in air, throws off jumps!
6886 //FIXME: if we are in the air over a drop near a ledge, should we try to push back towards the ledge?
6887 ucmd.forwardmove = 0;
6888 ucmd.rightmove = 0;
6889 VectorClear( NPC->client->ps.moveDir );
6890 }
6891
6892 if ( !TIMER_Done( NPC, "duck" ) )
6893 {
6894 ucmd.upmove = -127;
6895 }
6896
6897 if ( NPC->client->NPC_class != CLASS_BOBAFETT
6898 && (NPC->client->NPC_class != CLASS_REBORN || NPC->s.weapon == WP_SABER)
6899 && NPC->client->NPC_class != CLASS_ROCKETTROOPER )
6900 {
6901 if ( PM_SaberInBrokenParry( NPC->client->ps.saberMove ) || NPC->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
6902 {//just make sure they don't pull their saber to them if they're being blocked
6903 ucmd.buttons &= ~BUTTON_ATTACK;
6904 }
6905 }
6906
6907 if( (NPCInfo->scriptFlags&SCF_DONT_FIRE) //not allowed to attack
6908 || ((NPC->client->ps.forcePowersActive&(1<<FP_HEAL))&&NPC->client->ps.forcePowerLevel[FP_HEAL]<FORCE_LEVEL_3)
6909 || ((NPC->client->ps.saberEventFlags&SEF_INWATER)&&!NPC->client->ps.saberInFlight) )//saber in water
6910 {
6911 ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS);
6912 }
6913
6914 if ( (NPCInfo->scriptFlags&SCF_NO_ACROBATICS) )
6915 {
6916 ucmd.upmove = 0;
6917 NPC->client->ps.forceJumpCharge = 0;
6918 }
6919
6920 if ( NPC->client->NPC_class != CLASS_BOBAFETT
6921 && (NPC->client->NPC_class != CLASS_REBORN || NPC->s.weapon == WP_SABER)
6922 && NPC->client->NPC_class != CLASS_ROCKETTROOPER )
6923 {
6924 Jedi_CheckDecreaseSaberAnimLevel();
6925 }
6926
6927 if ( ucmd.buttons & BUTTON_ATTACK && NPC->client->playerTeam == TEAM_ENEMY )
6928 {
6929 if ( Q_irand( 0, NPC->client->ps.saberAnimLevel ) > 0
6930 && Q_irand( 0, NPC->max_health+10 ) > NPC->health
6931 && !Q_irand( 0, 3 ))
6932 {//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
6933 G_AddVoiceEvent( NPC, Q_irand( EV_COMBAT1, EV_COMBAT3 ), 1000 );
6934 }
6935 }
6936
6937 //Check for trying a kata move
6938 //FIXME: what about force-pull attacks?
6939 if ( Jedi_CheckKataAttack() )
6940 {//doing a kata attack
6941 }
6942 else
6943 {//check other special combat behavior
6944 if ( NPC->client->NPC_class != CLASS_BOBAFETT
6945 && (NPC->client->NPC_class != CLASS_REBORN || NPC->s.weapon == WP_SABER)
6946 && NPC->client->NPC_class != CLASS_ROCKETTROOPER )
6947 {
6948 if ( NPC->client->NPC_class == CLASS_TAVION
6949 || NPC->client->NPC_class == CLASS_SHADOWTROOPER
6950 || NPC->client->NPC_class == CLASS_ALORA
6951 || (g_spskill->integer && ( NPC->client->NPC_class == CLASS_DESANN || NPCInfo->rank >= Q_irand( RANK_CREWMAN, RANK_CAPTAIN ))))
6952 {//Tavion will kick in force speed if the player does...
6953 if ( NPC->enemy
6954 && !NPC->enemy->s.number
6955 && NPC->enemy->client
6956 && (NPC->enemy->client->ps.forcePowersActive & (1<<FP_SPEED))
6957 && !(NPC->client->ps.forcePowersActive & (1<<FP_SPEED)) )
6958 {
6959 int chance = 0;
6960 switch ( g_spskill->integer )
6961 {
6962 case 0:
6963 chance = 9;
6964 break;
6965 case 1:
6966 chance = 3;
6967 break;
6968 case 2:
6969 chance = 1;
6970 break;
6971 }
6972 if ( !Q_irand( 0, chance ) )
6973 {
6974 ForceSpeed( NPC );
6975 }
6976 }
6977 }
6978 }
6979 //Sometimes Alora flips towards you instead of runs
6980 if ( NPC->client->NPC_class == CLASS_ALORA )
6981 {
6982 if ( (ucmd.buttons&BUTTON_ALT_ATTACK) )
6983 {//chance of doing a special dual saber throw
6984 if ( NPC->client->ps.saberAnimLevel == SS_DUAL
6985 && !NPC->client->ps.saberInFlight )
6986 {
6987 if ( Distance( NPC->enemy->currentOrigin, NPC->currentOrigin ) >= 120 )
6988 {
6989 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ALORA_SPIN_THROW, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 );
6990 NPC->client->ps.weaponTime = NPC->client->ps.torsoAnimTimer;
6991 //FIXME: don't move
6992 //FIXME: sabers need trails and sounds
6993 }
6994 }
6995 }
6996 else if ( NPC->enemy
6997 && ucmd.forwardmove > 0
6998 && fabs((float)ucmd.rightmove) < 32
6999 && !(ucmd.buttons&BUTTON_WALKING)
7000 && !(ucmd.buttons&BUTTON_ATTACK)
7001 && NPC->client->ps.saberMove == LS_READY
7002 && NPC->client->ps.legsAnim == BOTH_RUN_DUAL )
7003 {//running at us, not attacking
7004 if ( Distance( NPC->enemy->currentOrigin, NPC->currentOrigin ) > 80 )
7005 {
7006 if ( NPC->client->ps.legsAnim == BOTH_FLIP_F
7007 || NPC->client->ps.legsAnim == BOTH_ALORA_FLIP_1
7008 || NPC->client->ps.legsAnim == BOTH_ALORA_FLIP_2
7009 || NPC->client->ps.legsAnim == BOTH_ALORA_FLIP_3 )
7010 {
7011 if ( NPC->client->ps.legsAnimTimer <= 200 && Q_irand( 0, 2 ) )
7012 {//go ahead and start anotther
7013 NPC_SetAnim( NPC, SETANIM_BOTH, Q_irand(BOTH_ALORA_FLIP_1,BOTH_ALORA_FLIP_3), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 );
7014 }
7015 }
7016 else if ( !Q_irand( 0, 6 ) )
7017 {
7018 NPC_SetAnim( NPC, SETANIM_BOTH, Q_irand(BOTH_ALORA_FLIP_1,BOTH_ALORA_FLIP_3), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 );
7019 }
7020 }
7021 }
7022 }
7023 }
7024
7025 if ( VectorCompare( NPC->client->ps.moveDir, vec3_origin )
7026 && (ucmd.forwardmove||ucmd.rightmove) )
7027 {//using ucmds to move this turn, not NAV
7028 if ( (ucmd.buttons&BUTTON_WALKING) )
7029 {//FIXME: NAV system screws with speed directly, so now I have to re-set it myself!
7030 NPC->client->ps.speed = NPCInfo->stats.walkSpeed;
7031 }
7032 else
7033 {
7034 NPC->client->ps.speed = NPCInfo->stats.runSpeed;
7035 }
7036 }
7037 }
7038
Rosh_BeingHealed(gentity_t * self)7039 qboolean Rosh_BeingHealed( gentity_t *self )
7040 {
7041 if ( self
7042 && self->NPC
7043 && self->client
7044 && (self->NPC->aiFlags&NPCAI_ROSH)
7045 && (self->flags&FL_UNDYING)
7046 && ( self->health == 1 //need healing
7047 || self->client->ps.powerups[PW_INVINCIBLE] > level.time ) )//being healed
7048 {
7049 return qtrue;
7050 }
7051 return qfalse;
7052 }
7053
Rosh_TwinPresent(gentity_t * self)7054 qboolean Rosh_TwinPresent( gentity_t *self )
7055 {
7056 gentity_t *foundTwin = G_Find( NULL, FOFS(NPC_type), "DKothos" );
7057 if ( !foundTwin
7058 || foundTwin->health < 0 )
7059 {
7060 foundTwin = G_Find( NULL, FOFS(NPC_type), "VKothos" );
7061 }
7062 if ( !foundTwin
7063 || foundTwin->health < 0 )
7064 {//oh well, both twins are dead...
7065 return qfalse;
7066 }
7067 return qtrue;
7068 }
7069
Rosh_TwinNearBy(gentity_t * self)7070 qboolean Rosh_TwinNearBy( gentity_t *self )
7071 {
7072 gentity_t *foundTwin = G_Find( NULL, FOFS(NPC_type), "DKothos" );
7073 if ( !foundTwin
7074 || foundTwin->health < 0 )
7075 {
7076 foundTwin = G_Find( NULL, FOFS(NPC_type), "VKothos" );
7077 }
7078 if ( !foundTwin
7079 || foundTwin->health < 0 )
7080 {//oh well, both twins are dead...
7081 return qfalse;
7082 }
7083 if ( self->client
7084 && foundTwin->client )
7085 {
7086 if ( Distance( self->currentOrigin, foundTwin->currentOrigin ) <= 512.0f
7087 && G_ClearLineOfSight( self->client->renderInfo.eyePoint, foundTwin->client->renderInfo.eyePoint, foundTwin->s.number, MASK_OPAQUE ) )
7088 {
7089 //make them look charge me for a bit while I do this
7090 TIMER_Set( self, "chargeMeUp", Q_irand( 2000, 4000 ) );
7091 return qtrue;
7092 }
7093 }
7094 return qfalse;
7095 }
7096
Kothos_HealRosh(void)7097 qboolean Kothos_HealRosh( void )
7098 {
7099 if ( NPC->client
7100 && NPC->client->leader
7101 && NPC->client->leader->client )
7102 {
7103 if ( DistanceSquared( NPC->client->leader->currentOrigin, NPC->currentOrigin ) <= (256*256)
7104 && G_ClearLineOfSight( NPC->client->leader->client->renderInfo.eyePoint, NPC->client->renderInfo.eyePoint, NPC->s.number, MASK_OPAQUE ) )
7105 {
7106 //NPC_FaceEntity( NPC->client->leader, qtrue );
7107 NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_FORCE_2HANDEDLIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7108 NPC->client->ps.torsoAnimTimer = 1000;
7109
7110 //FIXME: unique effect and sound
7111 //NPC->client->ps.eFlags |= EF_POWERING_ROSH;
7112 if ( NPC->ghoul2.size() )
7113 {
7114 mdxaBone_t boltMatrix;
7115 vec3_t fxOrg, fxDir, angles={0,NPC->currentAngles[YAW],0};
7116
7117 gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel,
7118 (Q_irand(0,1)?NPC->handLBolt:NPC->handRBolt),
7119 &boltMatrix, angles, NPC->currentOrigin, (cg.time?cg.time:level.time),
7120 NULL, NPC->s.modelScale );
7121 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
7122 VectorSubtract( NPC->client->leader->currentOrigin, fxOrg, fxDir );
7123 VectorNormalize( fxDir );
7124 G_PlayEffect( G_EffectIndex( "force/kothos_beam.efx" ), fxOrg, fxDir );
7125 }
7126 //BEG HACK LINE
7127 gentity_t *tent = G_TempEntity( NPC->currentOrigin, EV_KOTHOS_BEAM );
7128 tent->svFlags |= SVF_BROADCAST;
7129 tent->s.otherEntityNum = NPC->s.number;
7130 tent->s.otherEntityNum2 = NPC->client->leader->s.number;
7131 //END HACK LINE
7132
7133 NPC->client->leader->health += Q_irand( 1+g_spskill->integer*2, 4+g_spskill->integer*3 );//from 1-5 to 4-10
7134 if ( NPC->client->leader->client )
7135 {
7136 if ( NPC->client->leader->client->ps.legsAnim == BOTH_FORCEHEAL_START
7137 && NPC->client->leader->health >= NPC->client->leader->max_health )
7138 {//let him get up now
7139 NPC_SetAnim( NPC->client->leader, SETANIM_BOTH, BOTH_FORCEHEAL_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7140 //FIXME: temp effect
7141 G_PlayEffect( G_EffectIndex( "force/kothos_recharge.efx" ), NPC->client->leader->playerModel, 0, NPC->client->leader->s.number, NPC->client->leader->currentOrigin, NPC->client->leader->client->ps.torsoAnimTimer, qfalse );
7142 //make him invincible while we recharge him
7143 NPC->client->leader->client->ps.powerups[PW_INVINCIBLE] = level.time + NPC->client->leader->client->ps.torsoAnimTimer;
7144 NPC->client->leader->NPC->ignorePain = qfalse;
7145 NPC->client->leader->health = NPC->client->leader->max_health;
7146 }
7147 else
7148 {
7149 G_PlayEffect( G_EffectIndex( "force/kothos_recharge.efx" ), NPC->client->leader->playerModel, 0, NPC->client->leader->s.number, NPC->client->leader->currentOrigin, 500, qfalse );
7150 NPC->client->leader->client->ps.powerups[PW_INVINCIBLE] = level.time + 500;
7151 }
7152 }
7153 //decrement
7154 NPC->count--;
7155 if ( !NPC->count )
7156 {
7157 TIMER_Set( NPC, "healRoshDebounce", Q_irand( 5000, 10000 ) );
7158 NPC->count = 100;
7159 }
7160 //now protect me, too
7161 if ( g_spskill->integer )
7162 {//not on easy
7163 G_PlayEffect( G_EffectIndex( "force/kothos_recharge.efx" ), NPC->playerModel, 0, NPC->s.number, NPC->currentOrigin, 500, qfalse );
7164 NPC->client->ps.powerups[PW_INVINCIBLE] = level.time + 500;
7165 }
7166 return qtrue;
7167 }
7168 }
7169 return qfalse;
7170 }
7171
Kothos_PowerRosh(void)7172 void Kothos_PowerRosh( void )
7173 {
7174 if ( NPC->client
7175 && NPC->client->leader )
7176 {
7177 if ( Distance( NPC->client->leader->currentOrigin, NPC->currentOrigin ) <= 512.0f
7178 && G_ClearLineOfSight( NPC->client->leader->client->renderInfo.eyePoint, NPC->client->renderInfo.eyePoint, NPC->s.number, MASK_OPAQUE ) )
7179 {
7180 NPC_FaceEntity( NPC->client->leader, qtrue );
7181 NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_FORCELIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7182 NPC->client->ps.torsoAnimTimer = 500;
7183 //FIXME: unique effect and sound
7184 //NPC->client->ps.eFlags |= EF_POWERING_ROSH;
7185 G_PlayEffect( G_EffectIndex( "force/kothos_beam.efx" ), NPC->playerModel, NPC->handLBolt, NPC->s.number, NPC->currentOrigin, 500, qfalse );
7186 if ( NPC->client->leader->client )
7187 {//hmm, give him some force?
7188 NPC->client->leader->client->ps.forcePower++;
7189 }
7190 }
7191 }
7192 }
7193
Kothos_Retreat(void)7194 qboolean Kothos_Retreat( void )
7195 {
7196 STEER::Activate( NPC );
7197 STEER::Evade( NPC, NPC->enemy );
7198 STEER::AvoidCollisions( NPC, NPC->client->leader );
7199 STEER::DeActivate( NPC, &ucmd );
7200 if ( (NPCInfo->aiFlags&NPCAI_BLOCKED) )
7201 {
7202 if ( level.time - NPCInfo->blockedDebounceTime > 1000 )
7203 {
7204 return qfalse;
7205 }
7206 }
7207 return qtrue;
7208 }
7209
7210 #define TWINS_DANGER_DIST_EASY (128.0f*128.0f)
7211 #define TWINS_DANGER_DIST_MEDIUM (192.0f*192.0f)
7212 #define TWINS_DANGER_DIST_HARD (256.0f*256.0f)
Twins_DangerDist(void)7213 float Twins_DangerDist( void )
7214 {
7215 switch ( g_spskill->integer )
7216 {
7217 case 0:
7218 return TWINS_DANGER_DIST_EASY;
7219 break;
7220 case 1:
7221 return TWINS_DANGER_DIST_MEDIUM;
7222 break;
7223 case 2:
7224 default:
7225 return TWINS_DANGER_DIST_HARD;
7226 break;
7227 }
7228 }
7229
Jedi_InSpecialMove(void)7230 qboolean Jedi_InSpecialMove( void )
7231 {
7232 if ( NPC->client->ps.torsoAnim == BOTH_KYLE_PA_1
7233 || NPC->client->ps.torsoAnim == BOTH_KYLE_PA_2
7234 || NPC->client->ps.torsoAnim == BOTH_KYLE_PA_3
7235 || NPC->client->ps.torsoAnim == BOTH_PLAYER_PA_1
7236 || NPC->client->ps.torsoAnim == BOTH_PLAYER_PA_2
7237 || NPC->client->ps.torsoAnim == BOTH_PLAYER_PA_3
7238 || NPC->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_END
7239 || NPC->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRABBED )
7240 {
7241 NPC_UpdateAngles( qtrue, qtrue );
7242 return qtrue;
7243 }
7244
7245 if ( Jedi_InNoAIAnim( NPC ) )
7246 {//in special anims, don't do force powers or attacks, just face the enemy
7247 if ( NPC->enemy )
7248 {
7249 NPC_FaceEnemy( qtrue );
7250 }
7251 else
7252 {
7253 NPC_UpdateAngles( qtrue, qtrue );
7254 }
7255 return qtrue;
7256 }
7257
7258 /*
7259 if ( NPC->client->ps.forceGripEntityNum < ENTITYNUM_WORLD
7260 && (NPC->client->ps.forcePowersActive&(1<<FP_GRIP))
7261 && NPC->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 )
7262 {//stop facing the enemy, just use your current angles
7263 NPC_UpdateAngles( qtrue, qtrue );
7264 }
7265 */
7266
7267 if ( NPC->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_START
7268 || NPC->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_HOLD )
7269 {
7270 if ( !TIMER_Done( NPC, "draining" ) )
7271 {//FIXME: what do we do if we ran out of power? NPC's can't?
7272 //FIXME: don't keep turning to face enemy or we'll end up spinning around
7273 ucmd.buttons |= BUTTON_FORCE_DRAIN;
7274 }
7275 NPC_UpdateAngles( qtrue, qtrue );
7276 return qtrue;
7277 }
7278
7279 if ( NPC->client->ps.torsoAnim == BOTH_TAVION_SWORDPOWER )
7280 {
7281 NPC->health += Q_irand( 1, 2 );
7282 if ( NPC->health > NPC->max_health )
7283 {
7284 NPC->health = NPC->max_health;
7285 }
7286 NPC_UpdateAngles( qtrue, qtrue );
7287 return qtrue;
7288 }
7289
7290 if ( NPC->client->ps.torsoAnim == BOTH_SCEPTER_START )
7291 {
7292 if ( NPC->client->ps.torsoAnimTimer <= 100 )
7293 {//go into the hold
7294 NPC->s.loopSound = G_SoundIndex( "sound/weapons/scepter/loop.wav" );
7295 G_PlayEffect( G_EffectIndex( "scepter/beam.efx" ), NPC->weaponModel[1], NPC->genericBolt1, NPC->s.number, NPC->currentOrigin, 10000, qtrue );
7296 NPC->client->ps.legsAnimTimer = NPC->client->ps.torsoAnimTimer = 0;
7297 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_SCEPTER_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7298 NPC->client->ps.torsoAnimTimer += 200;
7299 NPC->painDebounceTime = level.time + NPC->client->ps.torsoAnimTimer;
7300 NPC->client->ps.pm_time = NPC->client->ps.torsoAnimTimer;
7301 NPC->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
7302 VectorClear( NPC->client->ps.velocity );
7303 VectorClear( NPC->client->ps.moveDir );
7304 }
7305 if ( NPC->enemy )
7306 {
7307 NPC_FaceEnemy( qtrue );
7308 }
7309 else
7310 {
7311 NPC_UpdateAngles( qtrue, qtrue );
7312 }
7313 return qtrue;
7314 }
7315 else if ( NPC->client->ps.torsoAnim == BOTH_SCEPTER_HOLD )
7316 {
7317 if ( NPC->client->ps.torsoAnimTimer <= 100 )
7318 {
7319 NPC->s.loopSound = 0;
7320 G_StopEffect( G_EffectIndex( "scepter/beam.efx" ), NPC->weaponModel[1], NPC->genericBolt1, NPC->s.number );
7321 NPC->client->ps.legsAnimTimer = NPC->client->ps.torsoAnimTimer = 0;
7322 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_SCEPTER_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7323 NPC->painDebounceTime = level.time + NPC->client->ps.torsoAnimTimer;
7324 NPC->client->ps.pm_time = NPC->client->ps.torsoAnimTimer;
7325 NPC->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
7326 VectorClear( NPC->client->ps.velocity );
7327 VectorClear( NPC->client->ps.moveDir );
7328 }
7329 else
7330 {
7331 Tavion_ScepterDamage();
7332 }
7333 if ( NPC->enemy )
7334 {
7335 NPC_FaceEnemy( qtrue );
7336 }
7337 else
7338 {
7339 NPC_UpdateAngles( qtrue, qtrue );
7340 }
7341 return qtrue;
7342 }
7343 else if ( NPC->client->ps.torsoAnim == BOTH_SCEPTER_STOP )
7344 {
7345 if ( NPC->enemy )
7346 {
7347 NPC_FaceEnemy( qtrue );
7348 }
7349 else
7350 {
7351 NPC_UpdateAngles( qtrue, qtrue );
7352 }
7353 return qtrue;
7354 }
7355 else if ( NPC->client->ps.torsoAnim == BOTH_TAVION_SCEPTERGROUND )
7356 {
7357 if ( NPC->client->ps.torsoAnimTimer <= 1200
7358 && !NPC->count )
7359 {
7360 Tavion_ScepterSlam();
7361 NPC->count = 1;
7362 }
7363 NPC_UpdateAngles( qtrue, qtrue );
7364 return qtrue;
7365 }
7366
7367 if ( Jedi_CultistDestroyer( NPC ) )
7368 {
7369 if ( !NPC->takedamage )
7370 {//ready to explode
7371 if ( NPC->useDebounceTime <= level.time )
7372 {
7373 //this should damage everyone - FIXME: except other destroyers?
7374 NPC->client->playerTeam = TEAM_FREE;//FIXME: will this destroy wampas, tusken & rancors?
7375 WP_Explode( NPC );
7376 return qtrue;
7377 }
7378 if ( NPC->enemy )
7379 {
7380 NPC_FaceEnemy( qfalse );
7381 }
7382 return qtrue;
7383 }
7384 }
7385
7386 if ( NPC->client->NPC_class == CLASS_REBORN )
7387 {
7388 if ( (NPCInfo->aiFlags&NPCAI_HEAL_ROSH) )
7389 {
7390 if ( !NPC->client->leader )
7391 {//find Rosh
7392 NPC->client->leader = G_Find( NULL, FOFS(NPC_type), "rosh_dark" );
7393 }
7394 //NPC->client->ps.eFlags &= ~EF_POWERING_ROSH;
7395 if ( NPC->client->leader )
7396 {
7397 qboolean helpingRosh = qfalse;
7398 NPC->flags |= FL_LOCK_PLAYER_WEAPONS;
7399 NPC->client->leader->flags |= FL_UNDYING;
7400 if ( NPC->client->leader->client )
7401 {
7402 NPC->client->leader->client->ps.forcePowersKnown |= FORCE_POWERS_ROSH_FROM_TWINS;
7403 }
7404 if ( NPC->client->leader->client->ps.legsAnim == BOTH_FORCEHEAL_START
7405 && TIMER_Done( NPC, "healRoshDebounce" ) )
7406 {
7407 if ( Kothos_HealRosh() )
7408 {
7409 helpingRosh = qtrue;
7410 }
7411 else
7412 {//can't get to him!
7413 NPC_BSJedi_FollowLeader();
7414 NPC_UpdateAngles( qtrue, qtrue );
7415 return qtrue;
7416 }
7417 }
7418
7419 /*
7420 if ( !helpingRosh
7421 && !TIMER_Done( NPC->client->leader, "chargeMeUp" )
7422 && NPC->client->leader->health > 0)
7423 {
7424 Kothos_PowerRosh();
7425 helpingRosh = qtrue;
7426 }
7427 */
7428
7429 if ( helpingRosh )
7430 {
7431 WP_ForcePowerStop( NPC, FP_LIGHTNING );
7432 WP_ForcePowerStop( NPC, FP_DRAIN );
7433 WP_ForcePowerStop( NPC, FP_GRIP );
7434 NPC_FaceEntity( NPC->client->leader, qtrue );
7435 return qtrue;
7436 }
7437 else if ( NPC->enemy && DistanceSquared( NPC->enemy->currentOrigin, NPC->currentOrigin ) < Twins_DangerDist() )
7438 {
7439 if ( NPC->enemy && Kothos_Retreat() )
7440 {
7441 NPC_FaceEnemy( qtrue );
7442 //NPC_UpdateAngles( qtrue, qtrue );
7443 if ( TIMER_Done( NPC, "attackDelay" ) )
7444 {
7445 if ( NPC->painDebounceTime > level.time
7446 || (NPC->health < 100 && Q_irand(-20, (g_spskill->integer+1)*10) > 0 )
7447 || !Q_irand( 0, 80-(g_spskill->integer*20) ) )
7448 {
7449 NPC->flags &= ~FL_LOCK_PLAYER_WEAPONS;
7450 switch ( Q_irand( 0, 7+g_spskill->integer ) )//on easy: no lightning
7451 {
7452 case 0:
7453 case 1:
7454 case 2:
7455 case 3:
7456 ForceThrow( NPC, qfalse, qfalse );
7457 NPC->client->ps.weaponTime = Q_irand( 1000, 3000 )+(2-g_spskill->integer)*1000;
7458 if ( NPC->painDebounceTime <= level.time
7459 && NPC->health >= 100 )
7460 {
7461 TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime );
7462 }
7463 break;
7464 case 4:
7465 case 5:
7466 ForceDrain2( NPC );
7467 NPC->client->ps.weaponTime = Q_irand( 3000, 6000 )+(2-g_spskill->integer)*2000;
7468 TIMER_Set( NPC, "draining", NPC->client->ps.weaponTime );
7469 if ( NPC->painDebounceTime <= level.time
7470 && NPC->health >= 100 )
7471 {
7472 TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime );
7473 }
7474 break;
7475 case 6:
7476 case 7:
7477 if ( NPC->enemy && InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 20, 30 ) )
7478 {
7479 NPC->client->ps.weaponTime = Q_irand( 3000, 6000 )+(2-g_spskill->integer)*2000;
7480 TIMER_Set( NPC, "gripping", 3000 );
7481 if ( NPC->painDebounceTime <= level.time
7482 && NPC->health >= 100 )
7483 {
7484 TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime );
7485 }
7486 }
7487 break;
7488 case 8:
7489 case 9:
7490 default:
7491 ForceLightning( NPC );
7492 if ( NPC->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 )
7493 {
7494 NPC->client->ps.weaponTime = Q_irand( 3000, 6000 )+(2-g_spskill->integer)*2000;
7495 TIMER_Set( NPC, "holdLightning", NPC->client->ps.weaponTime );
7496 }
7497 if ( NPC->painDebounceTime <= level.time
7498 && NPC->health >= 100 )
7499 {
7500 TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime );
7501 }
7502 break;
7503 }
7504 }
7505 }
7506 else
7507 {
7508 NPC->flags &= ~FL_LOCK_PLAYER_WEAPONS;
7509 }
7510 Jedi_TimersApply();
7511 return qtrue;
7512 }
7513 else
7514 {
7515 NPC->flags &= ~FL_LOCK_PLAYER_WEAPONS;
7516 }
7517 }
7518 else if ( !G_ClearLOS( NPC, NPC->client->leader )
7519 || DistanceSquared( NPC->currentOrigin, NPC->client->leader->currentOrigin ) > (512*512) )
7520 {//can't see Rosh or too far away, catch up with him
7521 if ( !TIMER_Done( NPC, "attackDelay" ) )
7522 {
7523 NPC->flags &= ~FL_LOCK_PLAYER_WEAPONS;
7524 }
7525 NPC_BSJedi_FollowLeader();
7526 NPC_UpdateAngles( qtrue, qtrue );
7527 return qtrue;
7528 }
7529 else
7530 {
7531 if ( !TIMER_Done( NPC, "attackDelay" ) )
7532 {
7533 NPC->flags &= ~FL_LOCK_PLAYER_WEAPONS;
7534 }
7535 STEER::Activate( NPC );
7536 STEER::Stop( NPC );
7537 STEER::DeActivate( NPC, &ucmd );
7538 NPC_FaceEnemy( qtrue );
7539 //NPC_UpdateAngles( qtrue, qtrue );
7540 return qtrue;
7541 //NPC_BSJedi_FollowLeader();
7542 }
7543 }
7544 NPC_UpdateAngles( qtrue, qtrue );
7545 //NPC->client->ps.eFlags &= ~EF_POWERING_ROSH;
7546 //G_StopEffect( G_EffectIndex( "force/kothos_beam.efx" ), NPC->playerModel, NPC->handLBolt, NPC->s.number );
7547 }
7548 else if ( (NPCInfo->aiFlags&NPCAI_ROSH) )
7549 {
7550 if ( (NPC->flags&FL_UNDYING) )
7551 {//Vil and/or Dasariah still around to heal me
7552 if ( NPC->health == 1 //need healing
7553 || NPC->client->ps.powerups[PW_INVINCIBLE] > level.time )//being healed
7554 {//FIXME: custom anims
7555 if ( Rosh_TwinPresent( NPC ) )
7556 {
7557 if ( !NPC->client->ps.weaponTime )
7558 {//not attacking
7559 if ( NPC->client->ps.legsAnim != BOTH_FORCEHEAL_START
7560 && NPC->client->ps.legsAnim != BOTH_FORCEHEAL_STOP )
7561 {//get down and wait for Vil or Dasariah to help us
7562 //FIXME: sound?
7563 NPC->client->ps.legsAnimTimer = NPC->client->ps.torsoAnimTimer = 0;
7564 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_FORCEHEAL_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7565 NPC->client->ps.torsoAnimTimer = NPC->client->ps.legsAnimTimer = -1;
7566 NPC->client->ps.SaberDeactivate();
7567 NPCInfo->ignorePain = qtrue;
7568 }
7569 }
7570 NPC->client->ps.saberBlocked = BLOCKED_NONE;
7571 NPC->client->ps.saberMove = NPC->client->ps.saberMoveNext = LS_NONE;
7572 NPC->painDebounceTime = level.time + 500;
7573 NPC->client->ps.pm_time = 500;
7574 NPC->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
7575 VectorClear( NPC->client->ps.velocity );
7576 VectorClear( NPC->client->ps.moveDir );
7577 return qtrue;
7578 }
7579 }
7580 }
7581 }
7582 }
7583
7584 if ( PM_SuperBreakWinAnim( NPC->client->ps.torsoAnim ) )
7585 {
7586 NPC_FaceEnemy( qtrue );
7587 if ( NPC->client->ps.groundEntityNum != ENTITYNUM_NONE )
7588 {
7589 VectorClear( NPC->client->ps.velocity );
7590 }
7591 VectorClear( NPC->client->ps.moveDir );
7592 ucmd.rightmove = ucmd.forwardmove = ucmd.upmove = 0;
7593 return qtrue;
7594 }
7595
7596 return qfalse;
7597 }
7598
7599 extern void NPC_BSST_Patrol( void );
7600 extern void NPC_BSSniper_Default( void );
7601 extern void G_UcmdMoveForDir( gentity_t *self, usercmd_t *cmd, vec3_t dir );
NPC_BSJedi_Default(void)7602 void NPC_BSJedi_Default( void )
7603 {
7604 if ( Jedi_InSpecialMove() )
7605 {
7606 return;
7607 }
7608
7609 Jedi_CheckCloak();
7610
7611 if( !NPC->enemy )
7612 {//don't have an enemy, look for one
7613 if ( NPC->client->NPC_class == CLASS_BOBAFETT
7614 || (NPC->client->NPC_class == CLASS_REBORN && NPC->s.weapon != WP_SABER)
7615 || NPC->client->NPC_class == CLASS_ROCKETTROOPER )
7616 {
7617 NPC_BSST_Patrol();
7618 }
7619 else
7620 {
7621 Jedi_Patrol();
7622 }
7623 }
7624 else//if ( NPC->enemy )
7625 {//have an enemy
7626 if ( Jedi_WaitingAmbush( NPC ) )
7627 {//we were still waiting to drop down - must have had enemy set on me outside my AI
7628 Jedi_Ambush( NPC );
7629 }
7630
7631 if ( Jedi_CultistDestroyer( NPC )
7632 && !NPCInfo->charmedTime )
7633 {//destroyer
7634 //permanent effect
7635 NPCInfo->charmedTime = Q3_INFINITE;
7636 NPC->client->ps.forcePowersActive |= ( 1 << FP_RAGE );
7637 NPC->client->ps.forcePowerDuration[FP_RAGE] = Q3_INFINITE;
7638 //NPC->client->ps.eFlags |= EF_FORCE_DRAINED;
7639 //FIXME: precache me!
7640 NPC->s.loopSound = G_SoundIndex( "sound/movers/objects/green_beam_lp2.wav" );//test/charm.wav" );
7641 }
7642
7643 Jedi_Attack();
7644 //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...
7645 if ( ((!ucmd.buttons&&!NPC->client->ps.forcePowersActive)||(NPC->enemy&&NPC->enemy->health<=0)) && NPCInfo->enemyCheckDebounceTime < level.time )
7646 {//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
7647 //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?
7648 gentity_t *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy?
7649 NPC->enemy = NULL;
7650 gentity_t *newEnemy = NPC_CheckEnemy( (qboolean)(NPCInfo->confusionTime < level.time), qfalse, qfalse );
7651 NPC->enemy = sav_enemy;
7652 if ( newEnemy && newEnemy != sav_enemy )
7653 {//picked up a new enemy!
7654 NPC->lastEnemy = NPC->enemy;
7655 G_SetEnemy( NPC, newEnemy );
7656 }
7657 NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 1000, 3000 );
7658 }
7659 }
7660 if ( NPC->client->ps.saber[0].type == SABER_SITH_SWORD
7661 && NPC->weaponModel[0] != -1 )
7662 {
7663 if ( NPC->health < 100
7664 && !Q_irand( 0, 20 ) )
7665 {
7666 Tavion_SithSwordRecharge();
7667 }
7668 }
7669 }
7670