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 "w_saber.h"
27
28 extern qboolean BG_SabersOff( playerState_t *ps );
29
30 extern void CG_DrawAlert( vec3_t origin, float rating );
31 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
32 extern void ForceJump( gentity_t *self, usercmd_t *ucmd );
33
34 #define MAX_VIEW_DIST 2048
35 #define MAX_VIEW_SPEED 100
36 #define JEDI_MAX_LIGHT_INTENSITY 64
37 #define JEDI_MIN_LIGHT_THRESHOLD 10
38 #define JEDI_MAX_LIGHT_THRESHOLD 50
39
40 #define DISTANCE_SCALE 0.25f
41 #define SPEED_SCALE 0.25f
42 #define FOV_SCALE 0.5f
43 #define LIGHT_SCALE 0.25f
44
45 #define REALIZE_THRESHOLD 0.6f
46 #define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.3 )
47
48 #define MAX_CHECK_THRESHOLD 1
49
50 extern void NPC_ClearLookTarget( gentity_t *self );
51 extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime );
52 extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
53 extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask );
54 extern qboolean NPC_CheckEnemyStealth( void );
55 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
56
57 extern gitem_t *BG_FindItemForAmmo( ammo_t ammo );
58
59 extern void ForceThrow( gentity_t *self, qboolean pull );
60 extern void ForceLightning( gentity_t *self );
61 extern void ForceHeal( gentity_t *self );
62 extern void ForceRage( gentity_t *self );
63 extern void ForceProtect( gentity_t *self );
64 extern void ForceAbsorb( gentity_t *self );
65 extern int WP_MissileBlockForBlock( int saberBlock );
66 extern qboolean G_GetHitLocFromSurfName( gentity_t *ent, const char *surfName, int *hitLoc, vec3_t point, vec3_t dir, vec3_t bladeDir, int mod );
67 extern qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower );
68 extern qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
69 extern void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower );
70 extern void WP_DeactivateSaber( gentity_t *self, qboolean clearLength ); //clearLength = qfalse
71 extern void WP_ActivateSaber( gentity_t *self );
72
73 extern qboolean PM_SaberInStart( int move );
74 extern qboolean BG_SaberInSpecialAttack( int anim );
75 extern qboolean BG_SaberInAttack( int move );
76 extern qboolean PM_SaberInBounce( int move );
77 extern qboolean PM_SaberInParry( int move );
78 extern qboolean PM_SaberInKnockaway( int move );
79 extern qboolean PM_SaberInBrokenParry( int move );
80 extern qboolean PM_SaberInDeflect( int move );
81 extern qboolean BG_SpinningSaberAnim( int anim );
82 extern qboolean BG_FlippingAnim( int anim );
83 extern qboolean PM_RollingAnim( int anim );
84 extern qboolean PM_InKnockDown( playerState_t *ps );
85 extern qboolean BG_InRoll( playerState_t *ps, int anim );
86 extern qboolean BG_CrouchAnim( int anim );
87
88 extern qboolean NPC_SomeoneLookingAtMe(gentity_t *ent);
89
90 extern int WP_GetVelocityForForceJump( gentity_t *self, vec3_t jumpVel, usercmd_t *ucmd );
91
92 extern void G_TestLine(vec3_t start, vec3_t end, int color, int time);
93
94 static void Jedi_Aggression( gentity_t *self, int change );
95 qboolean Jedi_WaitingAmbush( gentity_t *self );
96
97 extern int bg_parryDebounce[];
98
99 static int jediSpeechDebounceTime[TEAM_NUM_TEAMS];//used to stop several jedi from speaking all at once
100 //Local state enums
101 enum
102 {
103 LSTATE_NONE = 0,
104 LSTATE_UNDERFIRE,
105 LSTATE_INVESTIGATE,
106 };
107
NPC_ShadowTrooper_Precache(void)108 void NPC_ShadowTrooper_Precache( void )
109 {
110 RegisterItem( BG_FindItemForAmmo( AMMO_FORCE ) );
111 G_SoundIndex( "sound/chars/shadowtrooper/cloak.wav" );
112 G_SoundIndex( "sound/chars/shadowtrooper/decloak.wav" );
113 }
114
Jedi_ClearTimers(gentity_t * ent)115 void Jedi_ClearTimers( gentity_t *ent )
116 {
117 TIMER_Set( ent, "roamTime", 0 );
118 TIMER_Set( ent, "chatter", 0 );
119 TIMER_Set( ent, "strafeLeft", 0 );
120 TIMER_Set( ent, "strafeRight", 0 );
121 TIMER_Set( ent, "noStrafe", 0 );
122 TIMER_Set( ent, "walking", 0 );
123 TIMER_Set( ent, "taunting", 0 );
124 TIMER_Set( ent, "parryTime", 0 );
125 TIMER_Set( ent, "parryReCalcTime", 0 );
126 TIMER_Set( ent, "forceJumpChasing", 0 );
127 TIMER_Set( ent, "jumpChaseDebounce", 0 );
128 TIMER_Set( ent, "moveforward", 0 );
129 TIMER_Set( ent, "moveback", 0 );
130 TIMER_Set( ent, "movenone", 0 );
131 TIMER_Set( ent, "moveright", 0 );
132 TIMER_Set( ent, "moveleft", 0 );
133 TIMER_Set( ent, "movecenter", 0 );
134 TIMER_Set( ent, "saberLevelDebounce", 0 );
135 TIMER_Set( ent, "noRetreat", 0 );
136 TIMER_Set( ent, "holdLightning", 0 );
137 TIMER_Set( ent, "gripping", 0 );
138 TIMER_Set( ent, "draining", 0 );
139 TIMER_Set( ent, "noturn", 0 );
140 }
141
Jedi_PlayBlockedPushSound(gentity_t * self)142 void Jedi_PlayBlockedPushSound( gentity_t *self )
143 {
144 if ( self->s.number >= 0 && self->s.number < MAX_CLIENTS )
145 {
146 G_AddVoiceEvent( self, EV_PUSHFAIL, 3000 );
147 }
148 else if ( self->health > 0 && self->NPC && self->NPC->blockedSpeechDebounceTime < level.time )
149 {
150 G_AddVoiceEvent( self, EV_PUSHFAIL, 3000 );
151 self->NPC->blockedSpeechDebounceTime = level.time + 3000;
152 }
153 }
154
Jedi_PlayDeflectSound(gentity_t * self)155 void Jedi_PlayDeflectSound( gentity_t *self )
156 {
157 if ( self->s.number >= 0 && self->s.number < MAX_CLIENTS )
158 {
159 G_AddVoiceEvent( self, Q_irand( EV_DEFLECT1, EV_DEFLECT3 ), 3000 );
160 }
161 else if ( self->health > 0 && self->NPC && self->NPC->blockedSpeechDebounceTime < level.time )
162 {
163 G_AddVoiceEvent( self, Q_irand( EV_DEFLECT1, EV_DEFLECT3 ), 3000 );
164 self->NPC->blockedSpeechDebounceTime = level.time + 3000;
165 }
166 }
167
NPC_Jedi_PlayConfusionSound(gentity_t * self)168 void NPC_Jedi_PlayConfusionSound( gentity_t *self )
169 {
170 if ( self->health > 0 )
171 {
172 if ( self->client && ( self->client->NPC_class == CLASS_TAVION || self->client->NPC_class == CLASS_DESANN ) )
173 {
174 G_AddVoiceEvent( self, Q_irand( EV_CONFUSE1, EV_CONFUSE3 ), 2000 );
175 }
176 else if ( Q_irand( 0, 1 ) )
177 {
178 G_AddVoiceEvent( self, Q_irand( EV_TAUNT1, EV_TAUNT3 ), 2000 );
179 }
180 else
181 {
182 G_AddVoiceEvent( self, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 2000 );
183 }
184 }
185 }
186
Jedi_CultistDestroyer(gentity_t * self)187 qboolean Jedi_CultistDestroyer( gentity_t *self )
188 {
189 if ( !self || !self->client )
190 {
191 return qfalse;
192 }
193 if( self->client->NPC_class == CLASS_REBORN &&
194 self->s.weapon == WP_MELEE &&
195 !Q_stricmp( "cultist_destroyer", self->NPC_type ) )
196 {
197 return qtrue;
198 }
199 return qfalse;
200 }
201
Boba_Precache(void)202 void Boba_Precache( void )
203 {
204 G_SoundIndex( "sound/boba/jeton.wav" );
205 G_SoundIndex( "sound/boba/jethover.wav" );
206 G_SoundIndex( "sound/effects/combustfire.mp3" );
207 G_EffectIndex( "boba/jet" );
208 G_EffectIndex( "boba/fthrw" );
209 }
210
211 extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel, int boltNum, int weaponNum );
212 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
Boba_ChangeWeapon(int wp)213 void Boba_ChangeWeapon( int wp )
214 {
215 if ( NPCS.NPC->s.weapon == wp )
216 {
217 return;
218 }
219 NPC_ChangeWeapon( wp );
220 G_AddEvent( NPCS.NPC, EV_GENERAL_SOUND, G_SoundIndex( "sound/weapons/change.wav" ));
221 }
222
WP_ResistForcePush(gentity_t * self,gentity_t * pusher,qboolean noPenalty)223 void WP_ResistForcePush( gentity_t *self, gentity_t *pusher, qboolean noPenalty )
224 {
225 int parts;
226 qboolean runningResist = qfalse;
227
228 if ( !self || self->health <= 0 || !self->client || !pusher || !pusher->client )
229 {
230 return;
231 }
232 if ( ((self->s.number >= 0 && self->s.number < MAX_CLIENTS) || self->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",self->NPC_type) || self->client->NPC_class == CLASS_LUKE)
233 && (VectorLengthSquared( self->client->ps.velocity ) > 10000 || self->client->ps.fd.forcePowerLevel[FP_PUSH] >= FORCE_LEVEL_3 || self->client->ps.fd.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 ) )
234 {
235 runningResist = qtrue;
236 }
237 if ( !runningResist
238 && self->client->ps.groundEntityNum != ENTITYNUM_NONE
239 && !BG_SpinningSaberAnim( self->client->ps.legsAnim )
240 && !BG_FlippingAnim( self->client->ps.legsAnim )
241 && !PM_RollingAnim( self->client->ps.legsAnim )
242 && !PM_InKnockDown( &self->client->ps )
243 && !BG_CrouchAnim( self->client->ps.legsAnim ))
244 {//if on a surface and not in a spin or flip, play full body resist
245 parts = SETANIM_BOTH;
246 }
247 else
248 {//play resist just in torso
249 parts = SETANIM_TORSO;
250 }
251 NPC_SetAnim( self, parts, BOTH_RESISTPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
252 if ( !noPenalty )
253 {
254 char buf[128];
255 float tFVal = 0;
256
257 trap->Cvar_VariableStringBuffer("timescale", buf, sizeof(buf));
258
259 tFVal = atof(buf);
260
261 if ( !runningResist )
262 {
263 VectorClear( self->client->ps.velocity );
264 //still stop them from attacking or moving for a bit, though
265 //FIXME: maybe push just a little (like, slide)?
266 self->client->ps.weaponTime = 1000;
267 if ( self->client->ps.fd.forcePowersActive&(1<<FP_SPEED) )
268 {
269 self->client->ps.weaponTime = floor( self->client->ps.weaponTime * tFVal );
270 }
271 self->client->ps.pm_time = self->client->ps.weaponTime;
272 self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
273 //play the full body push effect on me
274 //self->forcePushTime = level.time + 600; // let the push effect last for 600 ms
275 //rwwFIXMEFIXME: Do this?
276 }
277 else
278 {
279 self->client->ps.weaponTime = 600;
280 if ( self->client->ps.fd.forcePowersActive&(1<<FP_SPEED) )
281 {
282 self->client->ps.weaponTime = floor( self->client->ps.weaponTime * tFVal );
283 }
284 }
285 }
286 //play my force push effect on my hand
287 self->client->ps.powerups[PW_DISINT_4] = level.time + self->client->ps.torsoTimer + 500;
288 self->client->ps.powerups[PW_PULL] = 0;
289 Jedi_PlayBlockedPushSound( self );
290 }
291
Boba_StopKnockdown(gentity_t * self,gentity_t * pusher,vec3_t pushDir,qboolean forceKnockdown)292 qboolean Boba_StopKnockdown( gentity_t *self, gentity_t *pusher, vec3_t pushDir, qboolean forceKnockdown ) //forceKnockdown = qfalse
293 {
294 vec3_t pDir, fwd, right, ang;
295 float fDot, rDot;
296 int strafeTime;
297
298 if ( self->client->NPC_class != CLASS_BOBAFETT )
299 {
300 return qfalse;
301 }
302
303 if ( (self->client->ps.eFlags2&EF2_FLYING) )//moveType == MT_FLYSWIM )
304 {//can't knock me down when I'm flying
305 return qtrue;
306 }
307
308 VectorSet(ang, 0, self->r.currentAngles[YAW], 0);
309 strafeTime = Q_irand( 1000, 2000 );
310
311 AngleVectors( ang, fwd, right, NULL );
312 VectorNormalize2( pushDir, pDir );
313 fDot = DotProduct( pDir, fwd );
314 rDot = DotProduct( pDir, right );
315
316 if ( Q_irand( 0, 2 ) )
317 {//flip or roll with it
318 usercmd_t tempCmd;
319 if ( fDot >= 0.4f )
320 {
321 tempCmd.forwardmove = 127;
322 TIMER_Set( self, "moveforward", strafeTime );
323 }
324 else if ( fDot <= -0.4f )
325 {
326 tempCmd.forwardmove = -127;
327 TIMER_Set( self, "moveback", strafeTime );
328 }
329 else if ( rDot > 0 )
330 {
331 tempCmd.rightmove = 127;
332 TIMER_Set( self, "strafeRight", strafeTime );
333 TIMER_Set( self, "strafeLeft", -1 );
334 }
335 else
336 {
337 tempCmd.rightmove = -127;
338 TIMER_Set( self, "strafeLeft", strafeTime );
339 TIMER_Set( self, "strafeRight", -1 );
340 }
341 G_AddEvent( self, EV_JUMP, 0 );
342 if ( !Q_irand( 0, 1 ) )
343 {//flip
344 self->client->ps.fd.forceJumpCharge = 280;//FIXME: calc this intelligently?
345 ForceJump( self, &tempCmd );
346 }
347 else
348 {//roll
349 TIMER_Set( self, "duck", strafeTime );
350 }
351 self->painDebounceTime = 0;//so we do something
352 }
353 else if ( !Q_irand( 0, 1 ) && forceKnockdown )
354 {//resist
355 WP_ResistForcePush( self, pusher, qtrue );
356 }
357 else
358 {//fall down
359 return qfalse;
360 }
361
362 return qtrue;
363 }
364
Boba_FlyStart(gentity_t * self)365 void Boba_FlyStart( gentity_t *self )
366 {//switch to seeker AI for a while
367 if ( TIMER_Done( self, "jetRecharge" ) )
368 {
369 self->client->ps.gravity = 0;
370 if ( self->NPC )
371 {
372 self->NPC->aiFlags |= NPCAI_CUSTOM_GRAVITY;
373 }
374 self->client->ps.eFlags2 |= EF2_FLYING;//moveType = MT_FLYSWIM;
375 self->client->jetPackTime = level.time + Q_irand( 3000, 10000 );
376 //take-off sound
377 G_SoundOnEnt( self, CHAN_ITEM, "sound/boba/jeton.wav" );
378 //jet loop sound
379 self->s.loopSound = G_SoundIndex( "sound/boba/jethover.wav" );
380 if ( self->NPC )
381 {
382 self->count = Q3_INFINITE; // SEEKER shot ammo count
383 }
384 }
385 }
386
Boba_FlyStop(gentity_t * self)387 void Boba_FlyStop( gentity_t *self )
388 {
389 self->client->ps.gravity = g_gravity.value;
390 if ( self->NPC )
391 {
392 self->NPC->aiFlags &= ~NPCAI_CUSTOM_GRAVITY;
393 }
394 self->client->ps.eFlags2 &= ~EF2_FLYING;
395 self->client->jetPackTime = 0;
396 //stop jet loop sound
397 self->s.loopSound = 0;
398 if ( self->NPC )
399 {
400 self->count = 0; // SEEKER shot ammo count
401 TIMER_Set( self, "jetRecharge", Q_irand( 1000, 5000 ) );
402 TIMER_Set( self, "jumpChaseDebounce", Q_irand( 500, 2000 ) );
403 }
404 }
405
Boba_Flying(gentity_t * self)406 qboolean Boba_Flying( gentity_t *self )
407 {
408 return ((qboolean)(self->client->ps.eFlags2&EF2_FLYING));//moveType==MT_FLYSWIM));
409 }
410
Boba_FireFlameThrower(gentity_t * self)411 void Boba_FireFlameThrower( gentity_t *self )
412 {
413 int damage = Q_irand( 20, 30 );
414 trace_t tr;
415 gentity_t *traceEnt = NULL;
416 mdxaBone_t boltMatrix;
417 vec3_t start, end, dir, traceMins = {-4, -4, -4}, traceMaxs = {4, 4, 4};
418
419 trap->G2API_GetBoltMatrix( self->ghoul2, 0, self->client->renderInfo.handLBolt,
420 &boltMatrix, self->r.currentAngles, self->r.currentOrigin, level.time,
421 NULL, self->modelScale );
422
423 BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, start );
424 BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, dir );
425 //G_PlayEffect( "boba/fthrw", start, dir );
426 VectorMA( start, 128, dir, end );
427
428 trap->Trace( &tr, start, traceMins, traceMaxs, end, self->s.number, MASK_SHOT, qfalse, 0, 0 );
429
430 traceEnt = &g_entities[tr.entityNum];
431 if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage )
432 {
433 G_Damage( traceEnt, self, self, dir, tr.endpos, damage, DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|/*DAMAGE_NO_HIT_LOC|*/DAMAGE_IGNORE_TEAM, MOD_LAVA );
434 //rwwFIXMEFIXME: add DAMAGE_NO_HIT_LOC?
435 }
436 }
437
Boba_StartFlameThrower(gentity_t * self)438 void Boba_StartFlameThrower( gentity_t *self )
439 {
440 int flameTime = 4000;//Q_irand( 1000, 3000 );
441 mdxaBone_t boltMatrix;
442 vec3_t org, dir;
443
444 self->client->ps.torsoTimer = flameTime;//+1000;
445 if ( self->NPC )
446 {
447 TIMER_Set( self, "nextAttackDelay", flameTime );
448 TIMER_Set( self, "walking", 0 );
449 }
450 TIMER_Set( self, "flameTime", flameTime );
451 /*
452 gentity_t *fire = G_Spawn();
453 if ( fire != NULL )
454 {
455 mdxaBone_t boltMatrix;
456 vec3_t org, dir, ang;
457 trap->G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel, NPC->handRBolt,
458 &boltMatrix, NPC->r.currentAngles, NPC->r.currentOrigin, (cg.time?cg.time:level.time),
459 NULL, NPC->s.modelScale );
460
461 trap->G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
462 trap->G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, dir );
463 vectoangles( dir, ang );
464
465 VectorCopy( org, fire->s.origin );
466 VectorCopy( ang, fire->s.angles );
467
468 fire->targetname = "bobafire";
469 SP_fx_explosion_trail( fire );
470 fire->damage = 1;
471 fire->radius = 10;
472 fire->speed = 200;
473 fire->fxID = G_EffectIndex( "boba/fthrw" );//"env/exp_fire_trail" );//"env/small_fire"
474 fx_explosion_trail_link( fire );
475 fx_explosion_trail_use( fire, NPC, NPC );
476
477 }
478 */
479 G_SoundOnEnt( self, CHAN_WEAPON, "sound/effects/combustfire.mp3" );
480
481 trap->G2API_GetBoltMatrix(NPCS.NPC->ghoul2, 0, NPCS.NPC->client->renderInfo.handRBolt, &boltMatrix, NPCS.NPC->r.currentAngles,
482 NPCS.NPC->r.currentOrigin, level.time, NULL, NPCS.NPC->modelScale);
483
484 BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, org );
485 BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, dir );
486
487 G_PlayEffectID( G_EffectIndex("boba/fthrw"), org, dir);
488 }
489
Boba_DoFlameThrower(gentity_t * self)490 void Boba_DoFlameThrower( gentity_t *self )
491 {
492 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
493 if ( TIMER_Done( self, "nextAttackDelay" ) && TIMER_Done( self, "flameTime" ) )
494 {
495 Boba_StartFlameThrower( self );
496 }
497 Boba_FireFlameThrower( self );
498 }
499
Boba_FireDecide(void)500 void Boba_FireDecide( void )
501 {
502 qboolean enemyLOS = qfalse, enemyCS = qfalse, enemyInFOV = qfalse;
503 //qboolean move = qtrue;
504 qboolean shoot = qfalse, hitAlly = qfalse;
505 vec3_t impactPos, enemyDir, shootDir;
506 float enemyDist, dot;
507
508 if ( NPCS.NPC->client->ps.groundEntityNum == ENTITYNUM_NONE
509 && NPCS.NPC->client->ps.fd.forceJumpZStart
510 && !BG_FlippingAnim( NPCS.NPC->client->ps.legsAnim )
511 && !Q_irand( 0, 10 ) )
512 {//take off
513 Boba_FlyStart( NPCS.NPC );
514 }
515
516 if ( !NPCS.NPC->enemy )
517 {
518 return;
519 }
520
521 /*
522 if ( NPC->enemy->enemy != NPC && NPC->health == NPC->client->pers.maxHealth )
523 {
524 NPCInfo->scriptFlags |= SCF_ALT_FIRE;
525 Boba_ChangeWeapon( WP_DISRUPTOR );
526 }
527 else */if ( NPCS.NPC->enemy->s.weapon == WP_SABER )
528 {
529 NPCS.NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
530 Boba_ChangeWeapon( WP_ROCKET_LAUNCHER );
531 }
532 else
533 {
534 if ( NPCS.NPC->health < NPCS.NPC->client->pers.maxHealth*0.5f )
535 {
536 NPCS.NPCInfo->scriptFlags |= SCF_ALT_FIRE;
537 Boba_ChangeWeapon( WP_BLASTER );
538 NPCS.NPCInfo->burstMin = 3;
539 NPCS.NPCInfo->burstMean = 12;
540 NPCS.NPCInfo->burstMax = 20;
541 NPCS.NPCInfo->burstSpacing = Q_irand( 300, 750 );//attack debounce
542 }
543 else
544 {
545 NPCS.NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
546 Boba_ChangeWeapon( WP_BLASTER );
547 }
548 }
549
550 VectorClear( impactPos );
551 enemyDist = DistanceSquared( NPCS.NPC->r.currentOrigin, NPCS.NPC->enemy->r.currentOrigin );
552
553 VectorSubtract( NPCS.NPC->enemy->r.currentOrigin, NPCS.NPC->r.currentOrigin, enemyDir );
554 VectorNormalize( enemyDir );
555 AngleVectors( NPCS.NPC->client->ps.viewangles, shootDir, NULL, NULL );
556 dot = DotProduct( enemyDir, shootDir );
557 if ( dot > 0.5f ||( enemyDist * (1.0f-dot)) < 10000 )
558 {//enemy is in front of me or they're very close and not behind me
559 enemyInFOV = qtrue;
560 }
561
562 if ( (enemyDist < (128*128)&&enemyInFOV) || !TIMER_Done( NPCS.NPC, "flameTime" ) )
563 {//flamethrower
564 Boba_DoFlameThrower( NPCS.NPC );
565 enemyCS = qfalse;
566 shoot = qfalse;
567 NPCS.NPCInfo->enemyLastSeenTime = level.time;
568 NPCS.ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK);
569 }
570 else if ( enemyDist < MIN_ROCKET_DIST_SQUARED )//128
571 {//enemy within 128
572 if ( (NPCS.NPC->client->ps.weapon == WP_FLECHETTE || NPCS.NPC->client->ps.weapon == WP_REPEATER) &&
573 (NPCS.NPCInfo->scriptFlags & SCF_ALT_FIRE) )
574 {//shooting an explosive, but enemy too close, switch to primary fire
575 NPCS.NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
576 //FIXME: we can never go back to alt-fire this way since, after this, we don't know if we were initially supposed to use alt-fire or not...
577 }
578 }
579 else if ( enemyDist > 65536 )//256 squared
580 {
581 if ( NPCS.NPC->client->ps.weapon == WP_DISRUPTOR )
582 {//sniping... should be assumed
583 if ( !(NPCS.NPCInfo->scriptFlags&SCF_ALT_FIRE) )
584 {//use primary fire
585 NPCS.NPCInfo->scriptFlags |= SCF_ALT_FIRE;
586 //reset fire-timing variables
587 NPC_ChangeWeapon( WP_DISRUPTOR );
588 NPC_UpdateAngles( qtrue, qtrue );
589 return;
590 }
591 }
592 }
593
594 //can we see our target?
595 if ( TIMER_Done( NPCS.NPC, "nextAttackDelay" ) && TIMER_Done( NPCS.NPC, "flameTime" ) )
596 {
597 if ( NPC_ClearLOS4( NPCS.NPC->enemy ) )
598 {
599 NPCS.NPCInfo->enemyLastSeenTime = level.time;
600 enemyLOS = qtrue;
601
602 if ( NPCS.NPC->client->ps.weapon == WP_NONE )
603 {
604 enemyCS = qfalse;//not true, but should stop us from firing
605 }
606 else
607 {//can we shoot our target?
608 if ( (NPCS.NPC->client->ps.weapon == WP_ROCKET_LAUNCHER || (NPCS.NPC->client->ps.weapon == WP_FLECHETTE && (NPCS.NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_ROCKET_DIST_SQUARED )//128*128
609 {
610 enemyCS = qfalse;//not true, but should stop us from firing
611 hitAlly = qtrue;//us!
612 //FIXME: if too close, run away!
613 }
614 else if ( enemyInFOV )
615 {//if enemy is FOV, go ahead and check for shooting
616 int hit = NPC_ShotEntity( NPCS.NPC->enemy, impactPos );
617 gentity_t *hitEnt = &g_entities[hit];
618
619 if ( hit == NPCS.NPC->enemy->s.number
620 || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPCS.NPC->client->enemyTeam )
621 || ( hitEnt && hitEnt->takedamage && ((hitEnt->r.svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPCS.NPC->s.weapon == WP_EMPLACED_GUN) ) )
622 {//can hit enemy or enemy ally or will hit glass or other minor breakable (or in emplaced gun), so shoot anyway
623 enemyCS = qtrue;
624 //NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy
625 VectorCopy( NPCS.NPC->enemy->r.currentOrigin, NPCS.NPCInfo->enemyLastSeenLocation );
626 }
627 else
628 {//Hmm, have to get around this bastard
629 //NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy
630 if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPCS.NPC->client->playerTeam )
631 {//would hit an ally, don't fire!!!
632 hitAlly = qtrue;
633 }
634 else
635 {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire
636 }
637 }
638 }
639 else
640 {
641 enemyCS = qfalse;//not true, but should stop us from firing
642 }
643 }
644 }
645 else if ( trap->InPVS( NPCS.NPC->enemy->r.currentOrigin, NPCS.NPC->r.currentOrigin ) )
646 {
647 NPCS.NPCInfo->enemyLastSeenTime = level.time;
648 //NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy
649 }
650
651 if ( NPCS.NPC->client->ps.weapon == WP_NONE )
652 {
653 shoot = qfalse;
654 }
655 else
656 {
657 if ( enemyCS )
658 {
659 shoot = qtrue;
660 }
661 }
662
663 if ( !enemyCS )
664 {//if have a clear shot, always try
665 //See if we should continue to fire on their last position
666 //!TIMER_Done( NPC, "stick" ) ||
667 if ( !hitAlly //we're not going to hit an ally
668 && enemyInFOV //enemy is in our FOV //FIXME: or we don't have a clear LOS?
669 && NPCS.NPCInfo->enemyLastSeenTime > 0 )//we've seen the enemy
670 {
671 if ( level.time - NPCS.NPCInfo->enemyLastSeenTime < 10000 )//we have seem the enemy in the last 10 seconds
672 {
673 if ( !Q_irand( 0, 10 ) )
674 {
675 //Fire on the last known position
676 vec3_t muzzle, dir, angles;
677 qboolean tooClose = qfalse;
678 qboolean tooFar = qfalse;
679 float distThreshold;
680 float dist;
681
682 CalcEntitySpot( NPCS.NPC, SPOT_HEAD, muzzle );
683 if ( VectorCompare( impactPos, vec3_origin ) )
684 {//never checked ShotEntity this frame, so must do a trace...
685 trace_t tr;
686 //vec3_t mins = {-2,-2,-2}, maxs = {2,2,2};
687 vec3_t forward, end;
688 AngleVectors( NPCS.NPC->client->ps.viewangles, forward, NULL, NULL );
689 VectorMA( muzzle, 8192, forward, end );
690 trap->Trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPCS.NPC->s.number, MASK_SHOT, qfalse, 0, 0 );
691 VectorCopy( tr.endpos, impactPos );
692 }
693
694 //see if impact would be too close to me
695 distThreshold = 16384/*128*128*/;//default
696 switch ( NPCS.NPC->s.weapon )
697 {
698 case WP_ROCKET_LAUNCHER:
699 case WP_FLECHETTE:
700 case WP_THERMAL:
701 case WP_TRIP_MINE:
702 case WP_DET_PACK:
703 distThreshold = 65536/*256*256*/;
704 break;
705 case WP_REPEATER:
706 if ( NPCS.NPCInfo->scriptFlags&SCF_ALT_FIRE )
707 {
708 distThreshold = 65536/*256*256*/;
709 }
710 break;
711 default:
712 break;
713 }
714
715 dist = DistanceSquared( impactPos, muzzle );
716
717 if ( dist < distThreshold )
718 {//impact would be too close to me
719 tooClose = qtrue;
720 }
721 else if ( level.time - NPCS.NPCInfo->enemyLastSeenTime > 5000 ||
722 (NPCS.NPCInfo->group && level.time - NPCS.NPCInfo->group->lastSeenEnemyTime > 5000 ))
723 {//we've haven't seen them in the last 5 seconds
724 //see if it's too far from where he is
725 distThreshold = 65536/*256*256*/;//default
726 switch ( NPCS.NPC->s.weapon )
727 {
728 case WP_ROCKET_LAUNCHER:
729 case WP_FLECHETTE:
730 case WP_THERMAL:
731 case WP_TRIP_MINE:
732 case WP_DET_PACK:
733 distThreshold = 262144/*512*512*/;
734 break;
735 case WP_REPEATER:
736 if ( NPCS.NPCInfo->scriptFlags&SCF_ALT_FIRE )
737 {
738 distThreshold = 262144/*512*512*/;
739 }
740 break;
741 default:
742 break;
743 }
744 dist = DistanceSquared( impactPos, NPCS.NPCInfo->enemyLastSeenLocation );
745 if ( dist > distThreshold )
746 {//impact would be too far from enemy
747 tooFar = qtrue;
748 }
749 }
750
751 if ( !tooClose && !tooFar )
752 {//okay too shoot at last pos
753 VectorSubtract( NPCS.NPCInfo->enemyLastSeenLocation, muzzle, dir );
754 VectorNormalize( dir );
755 vectoangles( dir, angles );
756
757 NPCS.NPCInfo->desiredYaw = angles[YAW];
758 NPCS.NPCInfo->desiredPitch = angles[PITCH];
759
760 shoot = qtrue;
761 }
762 }
763 }
764 }
765 }
766
767 //FIXME: don't shoot right away!
768 if ( NPCS.NPC->client->ps.weaponTime > 0 )
769 {
770 if ( NPCS.NPC->s.weapon == WP_ROCKET_LAUNCHER )
771 {
772 if ( !enemyLOS || !enemyCS )
773 {//cancel it
774 NPCS.NPC->client->ps.weaponTime = 0;
775 }
776 else
777 {//delay our next attempt
778 TIMER_Set( NPCS.NPC, "nextAttackDelay", Q_irand( 500, 1000 ) );
779 }
780 }
781 }
782 else if ( shoot )
783 {//try to shoot if it's time
784 if ( TIMER_Done( NPCS.NPC, "nextAttackDelay" ) )
785 {
786 if( !(NPCS.NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here
787 {
788 WeaponThink( qtrue );
789 }
790 //NASTY
791 if ( NPCS.NPC->s.weapon == WP_ROCKET_LAUNCHER
792 && (NPCS.ucmd.buttons&BUTTON_ATTACK)
793 && !Q_irand( 0, 3 ) )
794 {//every now and then, shoot a homing rocket
795 NPCS.ucmd.buttons &= ~BUTTON_ATTACK;
796 NPCS.ucmd.buttons |= BUTTON_ALT_ATTACK;
797 NPCS.NPC->client->ps.weaponTime = Q_irand( 500, 1500 );
798 }
799 }
800 }
801 }
802 }
803
Jedi_Cloak(gentity_t * self)804 void Jedi_Cloak( gentity_t *self )
805 {
806 if ( self )
807 {
808 self->flags |= FL_NOTARGET;
809 if ( self->client )
810 {
811 if ( !self->client->ps.powerups[PW_CLOAKED] )
812 {//cloak
813 self->client->ps.powerups[PW_CLOAKED] = Q3_INFINITE;
814
815 //FIXME: debounce attacks?
816 //FIXME: temp sound
817 G_Sound( self, CHAN_ITEM, G_SoundIndex("sound/chars/shadowtrooper/cloak.wav") );
818 }
819 }
820 }
821 }
822
Jedi_Decloak(gentity_t * self)823 void Jedi_Decloak( gentity_t *self )
824 {
825 if ( self )
826 {
827 self->flags &= ~FL_NOTARGET;
828 if ( self->client )
829 {
830 if ( self->client->ps.powerups[PW_CLOAKED] )
831 {//Uncloak
832 self->client->ps.powerups[PW_CLOAKED] = 0;
833
834 G_Sound( self, CHAN_ITEM, G_SoundIndex("sound/chars/shadowtrooper/decloak.wav") );
835 }
836 }
837 }
838 }
839
Jedi_CheckCloak(void)840 void Jedi_CheckCloak( void )
841 {
842 if ( NPCS.NPC && NPCS.NPC->client && NPCS.NPC->client->NPC_class == CLASS_SHADOWTROOPER )
843 {
844 if ( !NPCS.NPC->client->ps.saberHolstered ||
845 NPCS.NPC->health <= 0 ||
846 NPCS.NPC->client->ps.saberInFlight ||
847 // (NPC->client->ps.eFlags&EF_FORCE_GRIPPED) ||
848 // (NPC->client->ps.eFlags&EF_FORCE_DRAINED) ||
849 NPCS.NPC->painDebounceTime > level.time )
850 {//can't be cloaked if saber is on, or dead or saber in flight or taking pain or being gripped
851 Jedi_Decloak( NPCS.NPC );
852 }
853 else if ( NPCS.NPC->health > 0
854 && !NPCS.NPC->client->ps.saberInFlight
855 // && !(NPC->client->ps.eFlags&EF_FORCE_GRIPPED)
856 // && !(NPC->client->ps.eFlags&EF_FORCE_DRAINED)
857 && NPCS.NPC->painDebounceTime < level.time )
858 {//still alive, have saber in hand, not taking pain and not being gripped
859 Jedi_Cloak( NPCS.NPC );
860 }
861 }
862 }
863 /*
864 ==========================================================================================
865 AGGRESSION
866 ==========================================================================================
867 */
Jedi_Aggression(gentity_t * self,int change)868 static void Jedi_Aggression( gentity_t *self, int change )
869 {
870 int upper_threshold, lower_threshold;
871
872 self->NPC->stats.aggression += change;
873
874 //FIXME: base this on initial NPC stats
875 if ( self->client->playerTeam == NPCTEAM_PLAYER )
876 {//good guys are less aggressive
877 upper_threshold = 7;
878 lower_threshold = 1;
879 }
880 else
881 {//bad guys are more aggressive
882 if ( self->client->NPC_class == CLASS_DESANN )
883 {
884 upper_threshold = 20;
885 lower_threshold = 5;
886 }
887 else
888 {
889 upper_threshold = 10;
890 lower_threshold = 3;
891 }
892 }
893
894 if ( self->NPC->stats.aggression > upper_threshold )
895 {
896 self->NPC->stats.aggression = upper_threshold;
897 }
898 else if ( self->NPC->stats.aggression < lower_threshold )
899 {
900 self->NPC->stats.aggression = lower_threshold;
901 }
902 //Com_Printf( "(%d) %s agg %d change: %d\n", level.time, self->NPC_type, self->NPC->stats.aggression, change );
903 }
904
Jedi_AggressionErosion(int amt)905 static void Jedi_AggressionErosion( int amt )
906 {
907 if ( TIMER_Done( NPCS.NPC, "roamTime" ) )
908 {//the longer we're not alerted and have no enemy, the more our aggression goes down
909 TIMER_Set( NPCS.NPC, "roamTime", Q_irand( 2000, 5000 ) );
910 Jedi_Aggression( NPCS.NPC, amt );
911 }
912
913 if ( NPCS.NPCInfo->stats.aggression < 4 || (NPCS.NPCInfo->stats.aggression < 6&&NPCS.NPC->client->NPC_class == CLASS_DESANN))
914 {//turn off the saber
915 WP_DeactivateSaber( NPCS.NPC, qfalse );
916 }
917 }
918
NPC_Jedi_RateNewEnemy(gentity_t * self,gentity_t * enemy)919 void NPC_Jedi_RateNewEnemy( gentity_t *self, gentity_t *enemy )
920 {
921 float healthAggression;
922 float weaponAggression;
923 int newAggression;
924
925 switch( enemy->s.weapon )
926 {
927 case WP_SABER:
928 healthAggression = (float)self->health/200.0f*6.0f;
929 weaponAggression = 7;//go after him
930 break;
931 case WP_BLASTER:
932 if ( DistanceSquared( self->r.currentOrigin, enemy->r.currentOrigin ) < 65536 )//256 squared
933 {
934 healthAggression = (float)self->health/200.0f*8.0f;
935 weaponAggression = 8;//go after him
936 }
937 else
938 {
939 healthAggression = 8.0f - ((float)self->health/200.0f*8.0f);
940 weaponAggression = 2;//hang back for a second
941 }
942 break;
943 default:
944 healthAggression = (float)self->health/200.0f*8.0f;
945 weaponAggression = 6;//approach
946 break;
947 }
948 //Average these with current aggression
949 newAggression = ceil( (healthAggression + weaponAggression + (float)self->NPC->stats.aggression )/3.0f);
950 //Com_Printf( "(%d) new agg %d - new enemy\n", level.time, newAggression );
951 Jedi_Aggression( self, newAggression - self->NPC->stats.aggression );
952
953 //don't taunt right away
954 TIMER_Set( self, "chatter", Q_irand( 4000, 7000 ) );
955 }
956
Jedi_Rage(void)957 static void Jedi_Rage( void )
958 {
959 Jedi_Aggression( NPCS.NPC, 10 - NPCS.NPCInfo->stats.aggression + Q_irand( -2, 2 ) );
960 TIMER_Set( NPCS.NPC, "roamTime", 0 );
961 TIMER_Set( NPCS.NPC, "chatter", 0 );
962 TIMER_Set( NPCS.NPC, "walking", 0 );
963 TIMER_Set( NPCS.NPC, "taunting", 0 );
964 TIMER_Set( NPCS.NPC, "jumpChaseDebounce", 0 );
965 TIMER_Set( NPCS.NPC, "movenone", 0 );
966 TIMER_Set( NPCS.NPC, "movecenter", 0 );
967 TIMER_Set( NPCS.NPC, "noturn", 0 );
968 ForceRage( NPCS.NPC );
969 }
970
Jedi_RageStop(gentity_t * self)971 void Jedi_RageStop( gentity_t *self )
972 {
973 if ( self->NPC )
974 {//calm down and back off
975 TIMER_Set( self, "roamTime", 0 );
976 Jedi_Aggression( self, Q_irand( -5, 0 ) );
977 }
978 }
979 /*
980 ==========================================================================================
981 SPEAKING
982 ==========================================================================================
983 */
984
Jedi_BattleTaunt(void)985 static qboolean Jedi_BattleTaunt( void )
986 {
987 if ( TIMER_Done( NPCS.NPC, "chatter" )
988 && !Q_irand( 0, 3 )
989 && NPCS.NPCInfo->blockedSpeechDebounceTime < level.time
990 && jediSpeechDebounceTime[NPCS.NPC->client->playerTeam] < level.time )
991 {
992 int event = -1;
993 if ( NPCS.NPC->client->playerTeam == NPCTEAM_PLAYER
994 && NPCS.NPC->enemy && NPCS.NPC->enemy->client && NPCS.NPC->enemy->client->NPC_class == CLASS_JEDI )
995 {//a jedi fighting a jedi - training
996 if ( NPCS.NPC->client->NPC_class == CLASS_JEDI && NPCS.NPCInfo->rank == RANK_COMMANDER )
997 {//only trainer taunts
998 event = EV_TAUNT1;
999 }
1000 }
1001 else
1002 {//reborn or a jedi fighting an enemy
1003 event = Q_irand( EV_TAUNT1, EV_TAUNT3 );
1004 }
1005 if ( event != -1 )
1006 {
1007 G_AddVoiceEvent( NPCS.NPC, event, 3000 );
1008 jediSpeechDebounceTime[NPCS.NPC->client->playerTeam] = NPCS.NPCInfo->blockedSpeechDebounceTime = level.time + 6000;
1009 TIMER_Set( NPCS.NPC, "chatter", Q_irand( 5000, 10000 ) );
1010
1011 if ( NPCS.NPC->enemy && NPCS.NPC->enemy->NPC && NPCS.NPC->enemy->s.weapon == WP_SABER && NPCS.NPC->enemy->client && NPCS.NPC->enemy->client->NPC_class == CLASS_JEDI )
1012 {//Have the enemy jedi say something in response when I'm done?
1013 }
1014 return qtrue;
1015 }
1016 }
1017 return qfalse;
1018 }
1019
1020 /*
1021 ==========================================================================================
1022 MOVEMENT
1023 ==========================================================================================
1024 */
Jedi_ClearPathToSpot(vec3_t dest,int impactEntNum)1025 static qboolean Jedi_ClearPathToSpot( vec3_t dest, int impactEntNum )
1026 {
1027 trace_t trace;
1028 vec3_t mins, start, end, dir;
1029 float dist, drop, i;
1030
1031 //Offset the step height
1032 VectorSet( mins, NPCS.NPC->r.mins[0], NPCS.NPC->r.mins[1], NPCS.NPC->r.mins[2] + STEPSIZE );
1033
1034 trap->Trace( &trace, NPCS.NPC->r.currentOrigin, mins, NPCS.NPC->r.maxs, dest, NPCS.NPC->s.number, NPCS.NPC->clipmask, qfalse, 0, 0 );
1035
1036 //Do a simple check
1037 if ( trace.allsolid || trace.startsolid )
1038 {//inside solid
1039 return qfalse;
1040 }
1041
1042 if ( trace.fraction < 1.0f )
1043 {//hit something
1044 if ( impactEntNum != ENTITYNUM_NONE && trace.entityNum == impactEntNum )
1045 {//hit what we're going after
1046 return qtrue;
1047 }
1048 else
1049 {
1050 return qfalse;
1051 }
1052 }
1053
1054 //otherwise, clear path in a straight line.
1055 //Now at intervals of my size, go along the trace and trace down STEPSIZE to make sure there is a solid floor.
1056 VectorSubtract( dest, NPCS.NPC->r.currentOrigin, dir );
1057 dist = VectorNormalize( dir );
1058 if ( dest[2] > NPCS.NPC->r.currentOrigin[2] )
1059 {//going up, check for steps
1060 drop = STEPSIZE;
1061 }
1062 else
1063 {//going down or level, check for moderate drops
1064 drop = 64;
1065 }
1066 for ( i = NPCS.NPC->r.maxs[0]*2; i < dist; i += NPCS.NPC->r.maxs[0]*2 )
1067 {//FIXME: does this check the last spot, too? We're assuming that should be okay since the enemy is there?
1068 VectorMA( NPCS.NPC->r.currentOrigin, i, dir, start );
1069 VectorCopy( start, end );
1070 end[2] -= drop;
1071 trap->Trace( &trace, start, mins, NPCS.NPC->r.maxs, end, NPCS.NPC->s.number, NPCS.NPC->clipmask, qfalse, 0, 0 );//NPC->r.mins?
1072 if ( trace.fraction < 1.0f || trace.allsolid || trace.startsolid )
1073 {//good to go
1074 continue;
1075 }
1076 //no floor here! (or a long drop?)
1077 return qfalse;
1078 }
1079 //we made it!
1080 return qtrue;
1081 }
1082
NPC_MoveDirClear(int forwardmove,int rightmove,qboolean reset)1083 qboolean NPC_MoveDirClear( int forwardmove, int rightmove, qboolean reset )
1084 {
1085 vec3_t forward, right, testPos, angles, mins;
1086 trace_t trace;
1087 float fwdDist, rtDist;
1088 float bottom_max = -STEPSIZE*4 - 1;
1089
1090 if ( !forwardmove && !rightmove )
1091 {//not even moving
1092 //Com_Printf( "%d skipping walk-cliff check (not moving)\n", level.time );
1093 return qtrue;
1094 }
1095
1096 if ( NPCS.ucmd.upmove > 0 || NPCS.NPC->client->ps.fd.forceJumpCharge )
1097 {//Going to jump
1098 //Com_Printf( "%d skipping walk-cliff check (going to jump)\n", level.time );
1099 return qtrue;
1100 }
1101
1102 if ( NPCS.NPC->client->ps.groundEntityNum == ENTITYNUM_NONE )
1103 {//in the air
1104 //Com_Printf( "%d skipping walk-cliff check (in air)\n", level.time );
1105 return qtrue;
1106 }
1107 /*
1108 if ( fabs( AngleDelta( NPC->r.currentAngles[YAW], NPCInfo->desiredYaw ) ) < 5.0 )//!ucmd.angles[YAW] )
1109 {//Not turning much, don't do this
1110 //NOTE: Should this not happen only if you're not turning AT ALL?
1111 // You could be turning slowly but moving fast, so that would
1112 // still let you walk right off a cliff...
1113 //NOTE: Or maybe it is a good idea to ALWAYS do this, regardless
1114 // of whether ot not we're turning? But why would we be walking
1115 // straight into a wall or off a cliff unless we really wanted to?
1116 return;
1117 }
1118 */
1119
1120 //FIXME: to really do this right, we'd have to actually do a pmove to predict where we're
1121 //going to be... maybe this should be a flag and pmove handles it and sets a flag so AI knows
1122 //NEXT frame? Or just incorporate current velocity, runspeed and possibly friction?
1123 VectorCopy( NPCS.NPC->r.mins, mins );
1124 mins[2] += STEPSIZE;
1125 angles[PITCH] = angles[ROLL] = 0;
1126 angles[YAW] = NPCS.NPC->client->ps.viewangles[YAW];//Add ucmd.angles[YAW]?
1127 AngleVectors( angles, forward, right, NULL );
1128 fwdDist = ((float)forwardmove)/2.0f;
1129 rtDist = ((float)rightmove)/2.0f;
1130 VectorMA( NPCS.NPC->r.currentOrigin, fwdDist, forward, testPos );
1131 VectorMA( testPos, rtDist, right, testPos );
1132 trap->Trace( &trace, NPCS.NPC->r.currentOrigin, mins, NPCS.NPC->r.maxs, testPos, NPCS.NPC->s.number, NPCS.NPC->clipmask|CONTENTS_BOTCLIP, qfalse, 0, 0 );
1133 if ( trace.allsolid || trace.startsolid )
1134 {//hmm, trace started inside this brush... how do we decide if we should continue?
1135 //FIXME: what do we do if we start INSIDE a CONTENTS_BOTCLIP? Try the trace again without that in the clipmask?
1136 if ( reset )
1137 {
1138 trace.fraction = 1.0f;
1139 }
1140 VectorCopy( testPos, trace.endpos );
1141 //return qtrue;
1142 }
1143 if ( trace.fraction < 0.6 )
1144 {//Going to bump into something very close, don't move, just turn
1145 if ( (NPCS.NPC->enemy && trace.entityNum == NPCS.NPC->enemy->s.number) || (NPCS.NPCInfo->goalEntity && trace.entityNum == NPCS.NPCInfo->goalEntity->s.number) )
1146 {//okay to bump into enemy or goal
1147 //Com_Printf( "%d bump into enemy/goal okay\n", level.time );
1148 return qtrue;
1149 }
1150 else if ( reset )
1151 {//actually want to screw with the ucmd
1152 //Com_Printf( "%d avoiding walk into wall (entnum %d)\n", level.time, trace.entityNum );
1153 NPCS.ucmd.forwardmove = 0;
1154 NPCS.ucmd.rightmove = 0;
1155 VectorClear( NPCS.NPC->client->ps.moveDir );
1156 }
1157 return qfalse;
1158 }
1159
1160 if ( NPCS.NPCInfo->goalEntity )
1161 {
1162 if ( NPCS.NPCInfo->goalEntity->r.currentOrigin[2] < NPCS.NPC->r.currentOrigin[2] )
1163 {//goal is below me, okay to step off at least that far plus stepheight
1164 bottom_max += NPCS.NPCInfo->goalEntity->r.currentOrigin[2] - NPCS.NPC->r.currentOrigin[2];
1165 }
1166 }
1167 VectorCopy( trace.endpos, testPos );
1168 testPos[2] += bottom_max;
1169
1170 trap->Trace( &trace, trace.endpos, mins, NPCS.NPC->r.maxs, testPos, NPCS.NPC->s.number, NPCS.NPC->clipmask, qfalse, 0, 0 );
1171
1172 //FIXME:Should we try to see if we can still get to our goal using the waypoint network from this trace.endpos?
1173 //OR: just put NPC clip brushes on these edges (still fall through when die)
1174
1175 if ( trace.allsolid || trace.startsolid )
1176 {//Not going off a cliff
1177 //Com_Printf( "%d walk off cliff okay (droptrace in solid)\n", level.time );
1178 return qtrue;
1179 }
1180
1181 if ( trace.fraction < 1.0 )
1182 {//Not going off a cliff
1183 //FIXME: what if plane.normal is sloped? We'll slide off, not land... plus this doesn't account for slide-movement...
1184 //Com_Printf( "%d walk off cliff okay will hit entnum %d at dropdist of %4.2f\n", level.time, trace.entityNum, (trace.fraction*bottom_max) );
1185 return qtrue;
1186 }
1187
1188 //going to fall at least bottom_max, don't move, just turn... is this bad, though? What if we want them to drop off?
1189 if ( reset )
1190 {//actually want to screw with the ucmd
1191 //Com_Printf( "%d avoiding walk off cliff\n", level.time );
1192 NPCS.ucmd.forwardmove *= -1.0;//= 0;
1193 NPCS.ucmd.rightmove *= -1.0;//= 0;
1194 VectorScale( NPCS.NPC->client->ps.moveDir, -1, NPCS.NPC->client->ps.moveDir );
1195 }
1196 return qfalse;
1197 }
1198 /*
1199 -------------------------
1200 Jedi_HoldPosition
1201 -------------------------
1202 */
1203
Jedi_HoldPosition(void)1204 static void Jedi_HoldPosition( void )
1205 {
1206 //NPCInfo->squadState = SQUAD_STAND_AND_SHOOT;
1207 NPCS.NPCInfo->goalEntity = NULL;
1208
1209 /*
1210 if ( TIMER_Done( NPC, "stand" ) )
1211 {
1212 TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) );
1213 }
1214 */
1215 }
1216
1217 /*
1218 -------------------------
1219 Jedi_Move
1220 -------------------------
1221 */
1222
Jedi_Move(gentity_t * goal,qboolean retreat)1223 static void Jedi_Move( gentity_t *goal, qboolean retreat )
1224 {
1225 qboolean moved;
1226 navInfo_t info;
1227
1228 NPCS.NPCInfo->combatMove = qtrue;
1229 NPCS.NPCInfo->goalEntity = goal;
1230
1231 moved = NPC_MoveToGoal( qtrue );
1232
1233 //FIXME: temp retreat behavior- should really make this toward a safe spot or maybe to outflank enemy
1234 if ( retreat )
1235 {//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?
1236 NPCS.ucmd.forwardmove *= -1;
1237 NPCS.ucmd.rightmove *= -1;
1238 VectorScale( NPCS.NPC->client->ps.moveDir, -1, NPCS.NPC->client->ps.moveDir );
1239 }
1240
1241 //Get the move info
1242 NAV_GetLastMove( &info );
1243
1244 //If we hit our target, then stop and fire!
1245 if ( ( info.flags & NIF_COLLISION ) && ( info.blocker == NPCS.NPC->enemy ) )
1246 {
1247 Jedi_HoldPosition();
1248 }
1249
1250 //If our move failed, then reset
1251 if ( moved == qfalse )
1252 {
1253 Jedi_HoldPosition();
1254 }
1255 }
1256
Jedi_Hunt(void)1257 static qboolean Jedi_Hunt( void )
1258 {
1259 //Com_Printf( "Hunting\n" );
1260 //if we're at all interested in fighting, go after him
1261 if ( NPCS.NPCInfo->stats.aggression > 1 )
1262 {//approach enemy
1263 NPCS.NPCInfo->combatMove = qtrue;
1264 if ( !(NPCS.NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
1265 {
1266 NPC_UpdateAngles( qtrue, qtrue );
1267 return qtrue;
1268 }
1269 else
1270 {
1271 if ( NPCS.NPCInfo->goalEntity == NULL )
1272 {//hunt
1273 NPCS.NPCInfo->goalEntity = NPCS.NPC->enemy;
1274 }
1275 //Jedi_Move( NPC->enemy, qfalse );
1276 if ( NPC_MoveToGoal( qfalse ) )
1277 {
1278 NPC_UpdateAngles( qtrue, qtrue );
1279 return qtrue;
1280 }
1281 }
1282 }
1283 return qfalse;
1284 }
1285
1286 /*
1287 static qboolean Jedi_Track( void )
1288 {
1289 //if we're at all interested in fighting, go after him
1290 if ( NPCInfo->stats.aggression > 1 )
1291 {//approach enemy
1292 NPCInfo->combatMove = qtrue;
1293 NPC_SetMoveGoal( NPC, NPCInfo->enemyLastSeenLocation, 16, qtrue );
1294 if ( NPC_MoveToGoal( qfalse ) )
1295 {
1296 NPC_UpdateAngles( qtrue, qtrue );
1297 return qtrue;
1298 }
1299 }
1300 return qfalse;
1301 }
1302 */
1303
Jedi_Retreat(void)1304 static void Jedi_Retreat( void )
1305 {
1306 if ( !TIMER_Done( NPCS.NPC, "noRetreat" ) )
1307 {//don't actually move
1308 return;
1309 }
1310 //FIXME: when retreating, we should probably see if we can retreat
1311 //in the direction we want. If not...? Evade?
1312 //Com_Printf( "Retreating\n" );
1313 Jedi_Move( NPCS.NPC->enemy, qtrue );
1314 }
1315
Jedi_Advance(void)1316 static void Jedi_Advance( void )
1317 {
1318 if ( !NPCS.NPC->client->ps.saberInFlight )
1319 {
1320 //NPC->client->ps.SaberActivate();
1321 WP_ActivateSaber(NPCS.NPC);
1322 }
1323 //Com_Printf( "Advancing\n" );
1324 Jedi_Move( NPCS.NPC->enemy, qfalse );
1325
1326 //TIMER_Set( NPC, "roamTime", Q_irand( 2000, 4000 ) );
1327 //TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) );
1328 //TIMER_Set( NPC, "duck", 0 );
1329 }
1330
Jedi_AdjustSaberAnimLevel(gentity_t * self,int newLevel)1331 static void Jedi_AdjustSaberAnimLevel( gentity_t *self, int newLevel )
1332 {
1333 if ( !self || !self->client )
1334 {
1335 return;
1336 }
1337 //FIXME: each NPC shold have a unique pattern of behavior for the order in which they
1338 if ( self->client->NPC_class == CLASS_TAVION )
1339 {//special attacks
1340 self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_5;
1341 return;
1342 }
1343 else if ( self->client->NPC_class == CLASS_DESANN )
1344 {//special attacks
1345 self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_4;
1346 return;
1347 }
1348 if ( self->client->playerTeam == NPCTEAM_ENEMY )
1349 {
1350 if ( self->NPC->rank == RANK_CIVILIAN || self->NPC->rank == RANK_LT_JG )
1351 {//grunt and fencer always uses quick attacks
1352 self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1;
1353 return;
1354 }
1355 if ( self->NPC->rank == RANK_CREWMAN
1356 || self->NPC->rank == RANK_ENSIGN )
1357 {//acrobat & force-users always use medium attacks
1358 self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_2;
1359 return;
1360 }
1361 /*
1362 if ( self->NPC->rank == RANK_LT )
1363 {//boss always uses strong attacks
1364 self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_3;
1365 return;
1366 }
1367 */
1368 }
1369 //use the different attacks, how often they switch and under what circumstances
1370 if ( newLevel > self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] )
1371 {//cap it
1372 self->client->ps.fd.saberAnimLevel = self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE];
1373 }
1374 else if ( newLevel < FORCE_LEVEL_1 )
1375 {
1376 self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1;
1377 }
1378 else
1379 {//go ahead and set it
1380 self->client->ps.fd.saberAnimLevel = newLevel;
1381 }
1382
1383 if ( d_JediAI.integer )
1384 {
1385 switch ( self->client->ps.fd.saberAnimLevel )
1386 {
1387 case FORCE_LEVEL_1:
1388 Com_Printf( S_COLOR_GREEN"%s Saber Attack Set: fast\n", self->NPC_type );
1389 break;
1390 case FORCE_LEVEL_2:
1391 Com_Printf( S_COLOR_YELLOW"%s Saber Attack Set: medium\n", self->NPC_type );
1392 break;
1393 case FORCE_LEVEL_3:
1394 Com_Printf( S_COLOR_RED"%s Saber Attack Set: strong\n", self->NPC_type );
1395 break;
1396 }
1397 }
1398 }
1399
Jedi_CheckDecreaseSaberAnimLevel(void)1400 static void Jedi_CheckDecreaseSaberAnimLevel( void )
1401 {
1402 if ( !NPCS.NPC->client->ps.weaponTime && !(NPCS.ucmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)) )
1403 {//not attacking
1404 if ( TIMER_Done( NPCS.NPC, "saberLevelDebounce" ) && !Q_irand( 0, 10 ) )
1405 {
1406 //Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel-1) );//drop
1407 Jedi_AdjustSaberAnimLevel( NPCS.NPC, Q_irand( FORCE_LEVEL_1, FORCE_LEVEL_3 ));//random
1408 TIMER_Set( NPCS.NPC, "saberLevelDebounce", Q_irand( 3000, 10000 ) );
1409 }
1410 }
1411 else
1412 {
1413 TIMER_Set( NPCS.NPC, "saberLevelDebounce", Q_irand( 1000, 5000 ) );
1414 }
1415 }
1416
1417 extern void ForceDrain( gentity_t *self );
Jedi_CombatDistance(int enemy_dist)1418 static void Jedi_CombatDistance( int enemy_dist )
1419 {//FIXME: for many of these checks, what we really want is horizontal distance to enemy
1420 if ( NPCS.NPC->client->ps.fd.forcePowersActive&(1<<FP_GRIP) &&
1421 NPCS.NPC->client->ps.fd.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
1422 {//when gripping, don't move
1423 return;
1424 }
1425 else if ( !TIMER_Done( NPCS.NPC, "gripping" ) )
1426 {//stopped gripping, clear timers just in case
1427 TIMER_Set( NPCS.NPC, "gripping", -level.time );
1428 TIMER_Set( NPCS.NPC, "attackDelay", Q_irand( 0, 1000 ) );
1429 }
1430
1431 if ( Jedi_CultistDestroyer( NPCS.NPC ) )
1432 {
1433 Jedi_Advance();
1434 NPCS.NPC->client->ps.speed = NPCS.NPCInfo->stats.runSpeed;
1435 NPCS.ucmd.buttons &= ~BUTTON_WALKING;
1436 }
1437
1438 if ( NPCS.NPC->client->ps.fd.forcePowersActive&(1<<FP_DRAIN) &&
1439 NPCS.NPC->client->ps.fd.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_1 )
1440 {//when draining, don't move
1441 return;
1442 }
1443 else if ( !TIMER_Done( NPCS.NPC, "draining" ) )
1444 {//stopped draining, clear timers just in case
1445 TIMER_Set( NPCS.NPC, "draining", -level.time );
1446 TIMER_Set( NPCS.NPC, "attackDelay", Q_irand( 0, 1000 ) );
1447 }
1448
1449 if ( NPCS.NPC->client->NPC_class == CLASS_BOBAFETT )
1450 {
1451 if ( !TIMER_Done( NPCS.NPC, "flameTime" ) )
1452 {
1453 if ( enemy_dist > 50 )
1454 {
1455 Jedi_Advance();
1456 }
1457 else if ( enemy_dist <= 0 )
1458 {
1459 Jedi_Retreat();
1460 }
1461 }
1462 else if ( enemy_dist < 200 )
1463 {
1464 Jedi_Retreat();
1465 }
1466 else if ( enemy_dist > 1024 )
1467 {
1468 Jedi_Advance();
1469 }
1470 }
1471 else if ( NPCS.NPC->client->ps.saberInFlight &&
1472 !PM_SaberInBrokenParry( NPCS.NPC->client->ps.saberMove )
1473 && NPCS.NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
1474 {//maintain distance
1475 if ( enemy_dist < NPCS.NPC->client->ps.saberEntityDist )
1476 {
1477 Jedi_Retreat();
1478 }
1479 else if ( enemy_dist > NPCS.NPC->client->ps.saberEntityDist && enemy_dist > 100 )
1480 {
1481 Jedi_Advance();
1482 }
1483 if ( NPCS.NPC->client->ps.weapon == WP_SABER //using saber
1484 && NPCS.NPC->client->ps.saberEntityState == SES_LEAVING //not returning yet
1485 && NPCS.NPC->client->ps.fd.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_1 //2nd or 3rd level lightsaber
1486 && !(NPCS.NPC->client->ps.fd.forcePowersActive&(1 << FP_SPEED))
1487 && !(NPCS.NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water
1488 {//hold it out there
1489 NPCS.ucmd.buttons |= BUTTON_ALT_ATTACK;
1490 //FIXME: time limit?
1491 }
1492 }
1493 else if ( !TIMER_Done( NPCS.NPC, "taunting" ) )
1494 {
1495 if ( enemy_dist <= 64 )
1496 {//he's getting too close
1497 NPCS.ucmd.buttons |= BUTTON_ATTACK;
1498 if ( !NPCS.NPC->client->ps.saberInFlight )
1499 {
1500 WP_ActivateSaber(NPCS.NPC);
1501 }
1502 TIMER_Set( NPCS.NPC, "taunting", -level.time );
1503 }
1504 //else if ( NPC->client->ps.torsoAnim == BOTH_GESTURE1 && NPC->client->ps.torsoTimer < 2000 )
1505 else if (NPCS.NPC->client->ps.forceHandExtend == HANDEXTEND_JEDITAUNT && (NPCS.NPC->client->ps.forceHandExtendTime - level.time) < 200)
1506 {//we're almost done with our special taunt
1507 //FIXME: this doesn't always work, for some reason
1508 if ( !NPCS.NPC->client->ps.saberInFlight )
1509 {
1510 WP_ActivateSaber(NPCS.NPC);
1511 }
1512 }
1513 }
1514 else if ( NPCS.NPC->client->ps.saberEventFlags&SEF_LOCK_WON )
1515 {//we won a saber lock, press the advantage
1516 if ( enemy_dist > 0 )
1517 {//get closer so we can hit!
1518 Jedi_Advance();
1519 }
1520 if ( enemy_dist > 128 )
1521 {//lost 'em
1522 NPCS.NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON;
1523 }
1524 if ( NPCS.NPC->enemy->painDebounceTime + 2000 < level.time )
1525 {//the window of opportunity is gone
1526 NPCS.NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON;
1527 }
1528 //don't strafe?
1529 TIMER_Set( NPCS.NPC, "strafeLeft", -1 );
1530 TIMER_Set( NPCS.NPC, "strafeRight", -1 );
1531 }
1532 else if ( NPCS.NPC->enemy->client
1533 && NPCS.NPC->enemy->s.weapon == WP_SABER
1534 && NPCS.NPC->enemy->client->ps.saberLockTime > level.time
1535 && NPCS.NPC->client->ps.saberLockTime < level.time )
1536 {//enemy is in a saberLock and we are not
1537 if ( enemy_dist < 64 )
1538 {//FIXME: maybe just pick another enemy?
1539 Jedi_Retreat();
1540 }
1541 }
1542 //rwwFIXMEFIXME: Give them the ability to do this again (turret needs to be fixed up to allow it)
1543 else if ( enemy_dist <= 64
1544 && ((NPCS.NPCInfo->scriptFlags&SCF_DONT_FIRE)||(!Q_stricmp("Yoda",NPCS.NPC->NPC_type)&&!Q_irand(0,10))) )
1545 {//can't use saber and they're in striking range
1546 if ( !Q_irand( 0, 5 ) && InFront( NPCS.NPC->enemy->r.currentOrigin, NPCS.NPC->r.currentOrigin, NPCS.NPC->client->ps.viewangles, 0.2f ) )
1547 {
1548 if ( ((NPCS.NPCInfo->scriptFlags&SCF_DONT_FIRE)||NPCS.NPC->client->pers.maxHealth - NPCS.NPC->health > NPCS.NPC->client->pers.maxHealth*0.25f)//lost over 1/4 of our health or not firing
1549 && NPCS.NPC->client->ps.fd.forcePowersKnown&(1<<FP_DRAIN) //know how to drain
1550 && WP_ForcePowerAvailable( NPCS.NPC, FP_DRAIN, 20 )//have enough power
1551 && !Q_irand( 0, 2 ) )
1552 {//drain
1553 TIMER_Set( NPCS.NPC, "draining", 3000 );
1554 TIMER_Set( NPCS.NPC, "attackDelay", 3000 );
1555 Jedi_Advance();
1556 return;
1557 }
1558 else
1559 {
1560 ForceThrow( NPCS.NPC, qfalse );
1561 }
1562 }
1563 Jedi_Retreat();
1564 }
1565 else if ( enemy_dist <= 64
1566 && NPCS.NPC->client->pers.maxHealth - NPCS.NPC->health > NPCS.NPC->client->pers.maxHealth*0.25f//lost over 1/4 of our health
1567 && NPCS.NPC->client->ps.fd.forcePowersKnown&(1<<FP_DRAIN) //know how to drain
1568 && WP_ForcePowerAvailable( NPCS.NPC, FP_DRAIN, 20 )//have enough power
1569 && !Q_irand( 0, 10 )
1570 && InFront( NPCS.NPC->enemy->r.currentOrigin, NPCS.NPC->r.currentOrigin, NPCS.NPC->client->ps.viewangles, 0.2f ) )
1571 {
1572 TIMER_Set( NPCS.NPC, "draining", 3000 );
1573 TIMER_Set( NPCS.NPC, "attackDelay", 3000 );
1574 Jedi_Advance();
1575 return;
1576 }
1577 else if ( enemy_dist <= -16 )
1578 {//we're too damn close!
1579 Jedi_Retreat();
1580 }
1581 else if ( enemy_dist <= 0 )
1582 {//we're within striking range
1583 //if we are attacking, see if we should stop
1584 if ( NPCS.NPCInfo->stats.aggression < 4 )
1585 {//back off and defend
1586 Jedi_Retreat();
1587 }
1588 }
1589 else if ( enemy_dist > 256 )
1590 {//we're way out of range
1591 qboolean usedForce = qfalse;
1592 if ( NPCS.NPCInfo->stats.aggression < Q_irand( 0, 20 )
1593 && NPCS.NPC->health < NPCS.NPC->client->pers.maxHealth*0.75f
1594 && !Q_irand( 0, 2 ) )
1595 {
1596 if ( (NPCS.NPC->client->ps.fd.forcePowersKnown&(1<<FP_HEAL)) != 0
1597 && (NPCS.NPC->client->ps.fd.forcePowersActive&(1<<FP_HEAL)) == 0
1598 && Q_irand( 0, 1 ) )
1599 {
1600 ForceHeal( NPCS.NPC );
1601 usedForce = qtrue;
1602 //FIXME: check level of heal and know not to move or attack when healing
1603 }
1604 else if ( (NPCS.NPC->client->ps.fd.forcePowersKnown&(1<<FP_PROTECT)) != 0
1605 && (NPCS.NPC->client->ps.fd.forcePowersActive&(1<<FP_PROTECT)) == 0
1606 && Q_irand( 0, 1 ) )
1607 {
1608 ForceProtect( NPCS.NPC );
1609 usedForce = qtrue;
1610 }
1611 else if ( (NPCS.NPC->client->ps.fd.forcePowersKnown&(1<<FP_ABSORB)) != 0
1612 && (NPCS.NPC->client->ps.fd.forcePowersActive&(1<<FP_ABSORB)) == 0
1613 && Q_irand( 0, 1 ) )
1614 {
1615 ForceAbsorb( NPCS.NPC );
1616 usedForce = qtrue;
1617 }
1618 else if ( (NPCS.NPC->client->ps.fd.forcePowersKnown&(1<<FP_RAGE)) != 0
1619 && (NPCS.NPC->client->ps.fd.forcePowersActive&(1<<FP_RAGE)) == 0
1620 && Q_irand( 0, 1 ) )
1621 {
1622 Jedi_Rage();
1623 usedForce = qtrue;
1624 }
1625 //FIXME: what about things like mind tricks and force sight?
1626 }
1627 if ( enemy_dist > 384 )
1628 {//FIXME: check for enemy facing away and/or moving away
1629 if ( !Q_irand( 0, 10 ) && NPCS.NPCInfo->blockedSpeechDebounceTime < level.time && jediSpeechDebounceTime[NPCS.NPC->client->playerTeam] < level.time )
1630 {
1631 if ( NPC_ClearLOS4( NPCS.NPC->enemy ) )
1632 {
1633 G_AddVoiceEvent( NPCS.NPC, Q_irand( EV_JCHASE1, EV_JCHASE3 ), 3000 );
1634 }
1635 jediSpeechDebounceTime[NPCS.NPC->client->playerTeam] = NPCS.NPCInfo->blockedSpeechDebounceTime = level.time + 3000;
1636 }
1637 }
1638 //Unless we're totally hiding, go after him
1639 if ( NPCS.NPCInfo->stats.aggression > 0 )
1640 {//approach enemy
1641 if ( !usedForce )
1642 {
1643 Jedi_Advance();
1644 }
1645 }
1646 }
1647 //FIXME: enemy_dist calc needs to include all blade lengths, and include distance from hand to start of blade....
1648 else if ( enemy_dist > 50 )//FIXME: not hardcoded- base on our reach (modelScale?) and saberLengthMax
1649 {//we're out of striking range and we are allowed to attack
1650 //first, check some tactical force power decisions
1651 if ( NPCS.NPC->enemy && NPCS.NPC->enemy->client && (NPCS.NPC->enemy->client->ps.fd.forceGripBeingGripped > level.time) )
1652 {//They're being gripped, rush them!
1653 if ( NPCS.NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
1654 {//they're on the ground, so advance
1655 if ( TIMER_Done( NPCS.NPC, "parryTime" ) || NPCS.NPCInfo->rank > RANK_LT )
1656 {//not parrying
1657 if ( enemy_dist > 200 || !(NPCS.NPCInfo->scriptFlags&SCF_DONT_FIRE) )
1658 {//far away or allowed to use saber
1659 Jedi_Advance();
1660 }
1661 }
1662 }
1663 if ( NPCS.NPCInfo->rank >= RANK_LT_JG
1664 && !Q_irand( 0, 5 )
1665 && !(NPCS.NPC->client->ps.fd.forcePowersActive&(1 << FP_SPEED))
1666 && !(NPCS.NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water
1667 {//throw saber
1668 NPCS.ucmd.buttons |= BUTTON_ALT_ATTACK;
1669 }
1670 }
1671 else if ( NPCS.NPC->enemy && NPCS.NPC->enemy->client && //valid enemy
1672 NPCS.NPC->enemy->client->ps.saberInFlight && /*NPC->enemy->client->ps.saber[0].Active()*/ NPCS.NPC->enemy->client->ps.saberEntityNum && //enemy throwing saber
1673 NPCS.NPC->client->ps.weaponTime <= 0 && //I'm not busy
1674 WP_ForcePowerAvailable( NPCS.NPC, FP_GRIP, 0 ) && //I can use the power
1675 !Q_irand( 0, 10 ) && //don't do it all the time, averages to 1 check a second
1676 Q_irand( 0, 6 ) < g_npcspskill.integer && //more likely on harder diff
1677 Q_irand( RANK_CIVILIAN, RANK_CAPTAIN ) < NPCS.NPCInfo->rank )//more likely against harder enemies
1678 {//They're throwing their saber, grip them!
1679 //taunt
1680 if ( TIMER_Done( NPCS.NPC, "chatter" ) && jediSpeechDebounceTime[NPCS.NPC->client->playerTeam] < level.time && NPCS.NPCInfo->blockedSpeechDebounceTime < level.time )
1681 {
1682 G_AddVoiceEvent( NPCS.NPC, Q_irand( EV_TAUNT1, EV_TAUNT3 ), 3000 );
1683 jediSpeechDebounceTime[NPCS.NPC->client->playerTeam] = NPCS.NPCInfo->blockedSpeechDebounceTime = level.time + 3000;
1684 TIMER_Set( NPCS.NPC, "chatter", 3000 );
1685 }
1686
1687 //grip
1688 TIMER_Set( NPCS.NPC, "gripping", 3000 );
1689 TIMER_Set( NPCS.NPC, "attackDelay", 3000 );
1690 }
1691 else
1692 {
1693 int chanceScale;
1694
1695 if ( NPCS.NPC->enemy && NPCS.NPC->enemy->client && (NPCS.NPC->enemy->client->ps.fd.forcePowersActive&(1<<FP_GRIP)) )
1696 {//They're choking someone, probably an ally, run at them and do some sort of attack
1697 if ( NPCS.NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
1698 {//they're on the ground, so advance
1699 if ( TIMER_Done( NPCS.NPC, "parryTime" ) || NPCS.NPCInfo->rank > RANK_LT )
1700 {//not parrying
1701 if ( enemy_dist > 200 || !(NPCS.NPCInfo->scriptFlags&SCF_DONT_FIRE) )
1702 {//far away or allowed to use saber
1703 Jedi_Advance();
1704 }
1705 }
1706 }
1707 }
1708 chanceScale = 0;
1709 if ( NPCS.NPC->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",NPCS.NPC->NPC_type) )
1710 {
1711 chanceScale = 1;
1712 }
1713 else if ( NPCS.NPCInfo->rank == RANK_ENSIGN )
1714 {
1715 chanceScale = 2;
1716 }
1717 else if ( NPCS.NPCInfo->rank >= RANK_LT_JG )
1718 {
1719 chanceScale = 5;
1720 }
1721 if ( chanceScale
1722 && (enemy_dist > Q_irand( 100, 200 ) || (NPCS.NPCInfo->scriptFlags&SCF_DONT_FIRE) || (!Q_stricmp("Yoda",NPCS.NPC->NPC_type)&&!Q_irand(0,3)) )
1723 && enemy_dist < 500
1724 && (Q_irand( 0, chanceScale*10 )<5 || (NPCS.NPC->enemy->client && NPCS.NPC->enemy->client->ps.weapon != WP_SABER && !Q_irand( 0, chanceScale ) ) ) )
1725 {//else, randomly try some kind of attack every now and then
1726 if ( ((NPCS.NPCInfo->rank == RANK_ENSIGN || NPCS.NPCInfo->rank > RANK_LT_JG) && !Q_irand( 0, 1 )) || NPCS.NPC->s.weapon != WP_SABER )
1727 {
1728 if ( WP_ForcePowerUsable( NPCS.NPC, FP_PULL ) && !Q_irand( 0, 2 ) )
1729 {
1730 //force pull the guy to me!
1731 //FIXME: check forcePushRadius[NPC->client->ps.fd.forcePowerLevel[FP_PUSH]]
1732 ForceThrow( NPCS.NPC, qtrue );
1733 //maybe strafe too?
1734 TIMER_Set( NPCS.NPC, "duck", enemy_dist*3 );
1735 if ( Q_irand( 0, 1 ) )
1736 {
1737 NPCS.ucmd.buttons |= BUTTON_ATTACK;
1738 }
1739 }
1740 else if ( (WP_ForcePowerUsable( NPCS.NPC, FP_LIGHTNING )
1741 && ((NPCS.NPCInfo->scriptFlags & SCF_DONT_FIRE) && Q_stricmp("cultist_lightning",NPCS.NPC->NPC_type))) || Q_irand( 0, 1 ))
1742 {
1743 ForceLightning( NPCS.NPC );
1744 if ( NPCS.NPC->client->ps.fd.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 )
1745 {
1746 NPCS.NPC->client->ps.weaponTime = Q_irand( 1000, 3000+(g_npcspskill.integer*500) );
1747 TIMER_Set( NPCS.NPC, "holdLightning", NPCS.NPC->client->ps.weaponTime );
1748 }
1749 TIMER_Set( NPCS.NPC, "attackDelay", NPCS.NPC->client->ps.weaponTime );
1750 }
1751 //rwwFIXMEFIXME: After new drain stuff from SP is in re-enable this.
1752 else if ( (NPCS.NPC->health < NPCS.NPC->client->ps.stats[STAT_MAX_HEALTH] * 0.75f
1753 && Q_irand( FORCE_LEVEL_0, NPCS.NPC->client->ps.fd.forcePowerLevel[FP_DRAIN] ) > FORCE_LEVEL_1
1754 && WP_ForcePowerUsable( NPCS.NPC, FP_DRAIN )
1755 && ((NPCS.NPCInfo->scriptFlags&SCF_DONT_FIRE)&&Q_stricmp("cultist_drain",NPCS.NPC->NPC_type))) || Q_irand( 0, 1 ) )
1756 {
1757 ForceDrain( NPCS.NPC );
1758 NPCS.NPC->client->ps.weaponTime = Q_irand( 1000, 3000+(g_npcspskill.integer*500) );
1759 TIMER_Set( NPCS.NPC, "draining", NPCS.NPC->client->ps.weaponTime );
1760 TIMER_Set( NPCS.NPC, "attackDelay", NPCS.NPC->client->ps.weaponTime );
1761 }
1762 else if ( WP_ForcePowerUsable( NPCS.NPC, FP_GRIP )
1763 && NPCS.NPC->enemy && InFOV( NPCS.NPC->enemy, NPCS.NPC, 20, 30 ) )
1764 {
1765 //taunt
1766 if ( TIMER_Done( NPCS.NPC, "chatter" ) && jediSpeechDebounceTime[NPCS.NPC->client->playerTeam] < level.time && NPCS.NPCInfo->blockedSpeechDebounceTime < level.time )
1767 {
1768 G_AddVoiceEvent( NPCS.NPC, Q_irand( EV_TAUNT1, EV_TAUNT3 ), 3000 );
1769 jediSpeechDebounceTime[NPCS.NPC->client->playerTeam] = NPCS.NPCInfo->blockedSpeechDebounceTime = level.time + 3000;
1770 TIMER_Set( NPCS.NPC, "chatter", 3000 );
1771 }
1772
1773 //grip
1774 TIMER_Set( NPCS.NPC, "gripping", 3000 );
1775 TIMER_Set( NPCS.NPC, "attackDelay", 3000 );
1776 }
1777 else
1778 {
1779 if ( WP_ForcePowerUsable( NPCS.NPC, FP_SABERTHROW )
1780 && !(NPCS.NPC->client->ps.fd.forcePowersActive&(1 << FP_SPEED))
1781 && !(NPCS.NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water
1782 {//throw saber
1783 NPCS.ucmd.buttons |= BUTTON_ALT_ATTACK;
1784 }
1785 }
1786 }
1787 else
1788 {
1789 if ( NPCS.NPCInfo->rank >= RANK_LT_JG
1790 && !(NPCS.NPC->client->ps.fd.forcePowersActive&(1 << FP_SPEED))
1791 && !(NPCS.NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water
1792 {//throw saber
1793 NPCS.ucmd.buttons |= BUTTON_ALT_ATTACK;
1794 }
1795 }
1796 }
1797 //see if we should advance now
1798 else if ( NPCS.NPCInfo->stats.aggression > 5 )
1799 {//approach enemy
1800 if ( TIMER_Done( NPCS.NPC, "parryTime" ) || NPCS.NPCInfo->rank > RANK_LT )
1801 {//not parrying
1802 if ( !NPCS.NPC->enemy->client || NPCS.NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
1803 {//they're on the ground, so advance
1804 if ( enemy_dist > 200 || !(NPCS.NPCInfo->scriptFlags&SCF_DONT_FIRE) )
1805 {//far away or allowed to use saber
1806 Jedi_Advance();
1807 }
1808 }
1809 }
1810 }
1811 else
1812 {//maintain this distance?
1813 //walk?
1814 }
1815 }
1816 }
1817 else
1818 {//we're not close enough to attack, but not far enough away to be safe
1819 if ( NPCS.NPCInfo->stats.aggression < 4 )
1820 {//back off and defend
1821 Jedi_Retreat();
1822 }
1823 else if ( NPCS.NPCInfo->stats.aggression > 5 )
1824 {//try to get closer
1825 if ( enemy_dist > 0 && !(NPCS.NPCInfo->scriptFlags&SCF_DONT_FIRE))
1826 {//we're allowed to use our lightsaber, get closer
1827 if ( TIMER_Done( NPCS.NPC, "parryTime" ) || NPCS.NPCInfo->rank > RANK_LT )
1828 {//not parrying
1829 if ( !NPCS.NPC->enemy->client || NPCS.NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
1830 {//they're on the ground, so advance
1831 Jedi_Advance();
1832 }
1833 }
1834 }
1835 }
1836 else
1837 {//agression is 4 or 5... somewhere in the middle
1838 //what do we do here? Nothing?
1839 //Move forward and back?
1840 }
1841 }
1842 //if really really mad, rage!
1843 if ( NPCS.NPCInfo->stats.aggression > Q_irand( 5, 15 )
1844 && NPCS.NPC->health < NPCS.NPC->client->pers.maxHealth*0.75f
1845 && !Q_irand( 0, 2 ) )
1846 {
1847 if ( (NPCS.NPC->client->ps.fd.forcePowersKnown&(1<<FP_RAGE)) != 0
1848 && (NPCS.NPC->client->ps.fd.forcePowersActive&(1<<FP_RAGE)) == 0 )
1849 {
1850 Jedi_Rage();
1851 }
1852 }
1853 }
1854
Jedi_Strafe(int strafeTimeMin,int strafeTimeMax,int nextStrafeTimeMin,int nextStrafeTimeMax,qboolean walking)1855 static qboolean Jedi_Strafe( int strafeTimeMin, int strafeTimeMax, int nextStrafeTimeMin, int nextStrafeTimeMax, qboolean walking )
1856 {
1857 if( Jedi_CultistDestroyer( NPCS.NPC ) )
1858 {
1859 return qfalse;
1860 }
1861 if ( NPCS.NPC->client->ps.saberEventFlags&SEF_LOCK_WON && NPCS.NPC->enemy && NPCS.NPC->enemy->painDebounceTime > level.time )
1862 {//don't strafe if pressing the advantage of winning a saberLock
1863 return qfalse;
1864 }
1865 if ( TIMER_Done( NPCS.NPC, "strafeLeft" ) && TIMER_Done( NPCS.NPC, "strafeRight" ) )
1866 {
1867 qboolean strafed = qfalse;
1868 //TODO: make left/right choice a tactical decision rather than random:
1869 // try to keep own back away from walls and ledges,
1870 // try to keep enemy's back to a ledge or wall
1871 // Maybe try to strafe toward designer-placed "safe spots" or "goals"?
1872 int strafeTime = Q_irand( strafeTimeMin, strafeTimeMax );
1873
1874 if ( Q_irand( 0, 1 ) )
1875 {
1876 if ( NPC_MoveDirClear( NPCS.ucmd.forwardmove, -127, qfalse ) )
1877 {
1878 TIMER_Set( NPCS.NPC, "strafeLeft", strafeTime );
1879 strafed = qtrue;
1880 }
1881 else if ( NPC_MoveDirClear( NPCS.ucmd.forwardmove, 127, qfalse ) )
1882 {
1883 TIMER_Set( NPCS.NPC, "strafeRight", strafeTime );
1884 strafed = qtrue;
1885 }
1886 }
1887 else
1888 {
1889 if ( NPC_MoveDirClear( NPCS.ucmd.forwardmove, 127, qfalse ) )
1890 {
1891 TIMER_Set( NPCS.NPC, "strafeRight", strafeTime );
1892 strafed = qtrue;
1893 }
1894 else if ( NPC_MoveDirClear( NPCS.ucmd.forwardmove, -127, qfalse ) )
1895 {
1896 TIMER_Set( NPCS.NPC, "strafeLeft", strafeTime );
1897 strafed = qtrue;
1898 }
1899 }
1900
1901 if ( strafed )
1902 {
1903 TIMER_Set( NPCS.NPC, "noStrafe", strafeTime + Q_irand( nextStrafeTimeMin, nextStrafeTimeMax ) );
1904 if ( walking )
1905 {//should be a slow strafe
1906 TIMER_Set( NPCS.NPC, "walking", strafeTime );
1907 }
1908 return qtrue;
1909 }
1910 }
1911 return qfalse;
1912 }
1913
1914 /*
1915 static void Jedi_FaceEntity( gentity_t *self, gentity_t *other, qboolean doPitch )
1916 {
1917 vec3_t entPos;
1918 vec3_t muzzle;
1919
1920 //Get the positions
1921 CalcEntitySpot( other, SPOT_ORIGIN, entPos );
1922
1923 //Get the positions
1924 CalcEntitySpot( self, SPOT_HEAD_LEAN, muzzle );//SPOT_HEAD
1925
1926 //Find the desired angles
1927 vec3_t angles;
1928
1929 GetAnglesForDirection( muzzle, entPos, angles );
1930
1931 self->NPC->desiredYaw = AngleNormalize360( angles[YAW] );
1932 if ( doPitch )
1933 {
1934 self->NPC->desiredPitch = AngleNormalize360( angles[PITCH] );
1935 }
1936 }
1937 */
1938
1939 /*
1940 qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc )
1941
1942 Jedi will play a dodge anim, blur, and make the force speed noise.
1943
1944 Right now used to dodge instant-hit weapons.
1945
1946 FIXME: possibly call this for saber melee evasion and/or missile evasion?
1947 FIXME: possibly let player do this too?
1948 */
1949 //rwwFIXMEFIXME: Going to use qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc ) from
1950 //w_saber.c.. maybe use seperate one for NPCs or add cases to that one?
1951
Jedi_CheckFlipEvasions(gentity_t * self,float rightdot,float zdiff)1952 evasionType_t Jedi_CheckFlipEvasions( gentity_t *self, float rightdot, float zdiff )
1953 {
1954 if ( self->NPC && (self->NPC->scriptFlags&SCF_NO_ACROBATICS) )
1955 {
1956 return EVASION_NONE;
1957 }
1958 if ( self->client
1959 && (self->client->ps.fd.forceRageRecoveryTime > level.time || (self->client->ps.fd.forcePowersActive&(1<<FP_RAGE))) )
1960 {//no fancy dodges when raging
1961 return EVASION_NONE;
1962 }
1963 //Check for:
1964 //ARIALS/CARTWHEELS
1965 //WALL-RUNS
1966 //WALL-FLIPS
1967 //FIXME: if facing a wall, do forward wall-walk-backflip
1968 if ( self->client->ps.legsAnim == BOTH_WALL_RUN_LEFT || self->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT )
1969 {//already running on a wall
1970 vec3_t right, fwdAngles;
1971 int anim = -1;
1972 float animLength;
1973
1974 VectorSet(fwdAngles, 0, self->client->ps.viewangles[YAW], 0);
1975
1976 AngleVectors( fwdAngles, NULL, right, NULL );
1977
1978 animLength = BG_AnimLength( self->localAnimIndex, (animNumber_t)self->client->ps.legsAnim );
1979 if ( self->client->ps.legsAnim == BOTH_WALL_RUN_LEFT && rightdot < 0 )
1980 {//I'm running on a wall to my left and the attack is on the left
1981 if ( animLength - self->client->ps.legsTimer > 400
1982 && self->client->ps.legsTimer > 400 )
1983 {//not at the beginning or end of the anim
1984 anim = BOTH_WALL_RUN_LEFT_FLIP;
1985 }
1986 }
1987 else if ( self->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT && rightdot > 0 )
1988 {//I'm running on a wall to my right and the attack is on the right
1989 if ( animLength - self->client->ps.legsTimer > 400
1990 && self->client->ps.legsTimer > 400 )
1991 {//not at the beginning or end of the anim
1992 anim = BOTH_WALL_RUN_RIGHT_FLIP;
1993 }
1994 }
1995 if ( anim != -1 )
1996 {//flip off the wall!
1997 int parts;
1998 //FIXME: check the direction we will flip towards for do-not-enter/walls/drops?
1999 //NOTE: we presume there is still a wall there!
2000 if ( anim == BOTH_WALL_RUN_LEFT_FLIP )
2001 {
2002 self->client->ps.velocity[0] *= 0.5f;
2003 self->client->ps.velocity[1] *= 0.5f;
2004 VectorMA( self->client->ps.velocity, 150, right, self->client->ps.velocity );
2005 }
2006 else if ( anim == BOTH_WALL_RUN_RIGHT_FLIP )
2007 {
2008 self->client->ps.velocity[0] *= 0.5f;
2009 self->client->ps.velocity[1] *= 0.5f;
2010 VectorMA( self->client->ps.velocity, -150, right, self->client->ps.velocity );
2011 }
2012 parts = SETANIM_LEGS;
2013 if ( !self->client->ps.weaponTime )
2014 {
2015 parts = SETANIM_BOTH;
2016 }
2017 NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
2018 //self->client->ps.pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL);
2019 //rwwFIXMEFIXME: Add these pm flags?
2020 G_AddEvent( self, EV_JUMP, 0 );
2021 return EVASION_OTHER;
2022 }
2023 }
2024 else if ( self->client->NPC_class != CLASS_DESANN //desann doesn't do these kind of frilly acrobatics
2025 && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank >= RANK_LT)
2026 && Q_irand( 0, 1 )
2027 && !BG_InRoll( &self->client->ps, self->client->ps.legsAnim )
2028 && !PM_InKnockDown( &self->client->ps )
2029 && !BG_SaberInSpecialAttack( self->client->ps.torsoAnim ) )
2030 {
2031 vec3_t fwd, right, traceto, mins, maxs, fwdAngles;
2032 trace_t trace;
2033 int parts, anim;
2034 float speed, checkDist;
2035 qboolean allowCartWheels = qtrue;
2036 qboolean allowWallFlips = qtrue;
2037
2038 if ( self->client->ps.weapon == WP_SABER )
2039 {
2040 if ( self->client->saber[0].model[0]
2041 && (self->client->saber[0].saberFlags&SFL_NO_CARTWHEELS) )
2042 {
2043 allowCartWheels = qfalse;
2044 }
2045 else if ( self->client->saber[1].model[0]
2046 && (self->client->saber[1].saberFlags&SFL_NO_CARTWHEELS) )
2047 {
2048 allowCartWheels = qfalse;
2049 }
2050 if ( self->client->saber[0].model[0]
2051 && (self->client->saber[0].saberFlags&SFL_NO_WALL_FLIPS) )
2052 {
2053 allowWallFlips = qfalse;
2054 }
2055 else if ( self->client->saber[1].model[0]
2056 && (self->client->saber[1].saberFlags&SFL_NO_WALL_FLIPS) )
2057 {
2058 allowWallFlips = qfalse;
2059 }
2060 }
2061
2062 VectorSet(mins, self->r.mins[0],self->r.mins[1],0);
2063 VectorSet(maxs, self->r.maxs[0],self->r.maxs[1],24);
2064 VectorSet(fwdAngles, 0, self->client->ps.viewangles[YAW], 0);
2065
2066 AngleVectors( fwdAngles, fwd, right, NULL );
2067
2068 parts = SETANIM_BOTH;
2069
2070 if ( BG_SaberInAttack( self->client->ps.saberMove )
2071 || PM_SaberInStart( self->client->ps.saberMove ) )
2072 {
2073 parts = SETANIM_LEGS;
2074 }
2075 if ( rightdot >= 0 )
2076 {
2077 if ( Q_irand( 0, 1 ) )
2078 {
2079 anim = BOTH_ARIAL_LEFT;
2080 }
2081 else
2082 {
2083 anim = BOTH_CARTWHEEL_LEFT;
2084 }
2085 checkDist = -128;
2086 speed = -200;
2087 }
2088 else
2089 {
2090 if ( Q_irand( 0, 1 ) )
2091 {
2092 anim = BOTH_ARIAL_RIGHT;
2093 }
2094 else
2095 {
2096 anim = BOTH_CARTWHEEL_RIGHT;
2097 }
2098 checkDist = 128;
2099 speed = 200;
2100 }
2101 //trace in the dir that we want to go
2102 VectorMA( self->r.currentOrigin, checkDist, right, traceto );
2103 trap->Trace( &trace, self->r.currentOrigin, mins, maxs, traceto, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP, qfalse, 0, 0 );
2104 if ( trace.fraction >= 1.0f && allowCartWheels )
2105 {//it's clear, let's do it
2106 //FIXME: check for drops?
2107 vec3_t jumpRt;
2108
2109 NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
2110 self->client->ps.weaponTime = self->client->ps.legsTimer;//don't attack again until this anim is done
2111 VectorCopy( self->client->ps.viewangles, fwdAngles );
2112 fwdAngles[PITCH] = fwdAngles[ROLL] = 0;
2113 //do the flip
2114 AngleVectors( fwdAngles, NULL, jumpRt, NULL );
2115 VectorScale( jumpRt, speed, self->client->ps.velocity );
2116 self->client->ps.fd.forceJumpCharge = 0;//so we don't play the force flip anim
2117 self->client->ps.velocity[2] = 200;
2118 self->client->ps.fd.forceJumpZStart = self->r.currentOrigin[2];//so we don't take damage if we land at same height
2119 //self->client->ps.pm_flags |= PMF_JUMPING;
2120 if ( self->client->NPC_class == CLASS_BOBAFETT )
2121 {
2122 G_AddEvent( self, EV_JUMP, 0 );
2123 }
2124 else
2125 {
2126 G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" );
2127 }
2128 //ucmd.upmove = 0;
2129 return EVASION_CARTWHEEL;
2130 }
2131 else if ( !(trace.contents&CONTENTS_BOTCLIP) )
2132 {//hit a wall, not a do-not-enter brush
2133 //FIXME: before we check any of these jump-type evasions, we should check for headroom, right?
2134 //Okay, see if we can flip *off* the wall and go the other way
2135 vec3_t idealNormal;
2136 gentity_t *traceEnt;
2137
2138 VectorSubtract( self->r.currentOrigin, traceto, idealNormal );
2139 VectorNormalize( idealNormal );
2140 traceEnt = &g_entities[trace.entityNum];
2141 if ( (trace.entityNum<ENTITYNUM_WORLD&&traceEnt&&traceEnt->s.solid!=SOLID_BMODEL) || DotProduct( trace.plane.normal, idealNormal ) > 0.7f )
2142 {//it's a ent of some sort or it's a wall roughly facing us
2143 float bestCheckDist = 0;
2144 //hmm, see if we're moving forward
2145 if ( DotProduct( self->client->ps.velocity, fwd ) < 200 )
2146 {//not running forward very fast
2147 //check to see if it's okay to move the other way
2148 if ( (trace.fraction*checkDist) <= 32 )
2149 {//wall on that side is close enough to wall-flip off of or wall-run on
2150 bestCheckDist = checkDist;
2151 checkDist *= -1.0f;
2152 VectorMA( self->r.currentOrigin, checkDist, right, traceto );
2153 //trace in the dir that we want to go
2154 trap->Trace( &trace, self->r.currentOrigin, mins, maxs, traceto, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP, qfalse, 0, 0 );
2155 if ( trace.fraction >= 1.0f )
2156 {//it's clear, let's do it
2157 if ( allowWallFlips )
2158 {//okay to do wall-flips with this saber
2159 //FIXME: check for drops?
2160 //turn the cartwheel into a wallflip in the other dir
2161 if ( rightdot > 0 )
2162 {
2163 anim = BOTH_WALL_FLIP_LEFT;
2164 self->client->ps.velocity[0] = self->client->ps.velocity[1] = 0;
2165 VectorMA( self->client->ps.velocity, 150, right, self->client->ps.velocity );
2166 }
2167 else
2168 {
2169 anim = BOTH_WALL_FLIP_RIGHT;
2170 self->client->ps.velocity[0] = self->client->ps.velocity[1] = 0;
2171 VectorMA( self->client->ps.velocity, -150, right, self->client->ps.velocity );
2172 }
2173 self->client->ps.velocity[2] = forceJumpStrength[FORCE_LEVEL_2]/2.25f;
2174 //animate me
2175 parts = SETANIM_LEGS;
2176 if ( !self->client->ps.weaponTime )
2177 {
2178 parts = SETANIM_BOTH;
2179 }
2180 NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
2181 self->client->ps.fd.forceJumpZStart = self->r.currentOrigin[2];//so we don't take damage if we land at same height
2182 //self->client->ps.pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL);
2183 if ( self->client->NPC_class == CLASS_BOBAFETT )
2184 {
2185 G_AddEvent( self, EV_JUMP, 0 );
2186 }
2187 else
2188 {
2189 G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" );
2190 }
2191 return EVASION_OTHER;
2192 }
2193 }
2194 else
2195 {//boxed in on both sides
2196 if ( DotProduct( self->client->ps.velocity, fwd ) < 0 )
2197 {//moving backwards
2198 return EVASION_NONE;
2199 }
2200 if ( (trace.fraction*checkDist) <= 32 && (trace.fraction*checkDist) < bestCheckDist )
2201 {
2202 bestCheckDist = checkDist;
2203 }
2204 }
2205 }
2206 else
2207 {//too far from that wall to flip or run off it, check other side
2208 checkDist *= -1.0f;
2209 VectorMA( self->r.currentOrigin, checkDist, right, traceto );
2210 //trace in the dir that we want to go
2211 trap->Trace( &trace, self->r.currentOrigin, mins, maxs, traceto, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP, qfalse, 0, 0 );
2212 if ( (trace.fraction*checkDist) <= 32 )
2213 {//wall on this side is close enough
2214 bestCheckDist = checkDist;
2215 }
2216 else
2217 {//neither side has a wall within 32
2218 return EVASION_NONE;
2219 }
2220 }
2221 }
2222 //Try wall run?
2223 if ( bestCheckDist )
2224 {//one of the walls was close enough to wall-run on
2225 qboolean allowWallRuns = qtrue;
2226 if ( self->client->ps.weapon == WP_SABER )
2227 {
2228 if ( self->client->saber[0].model[0]
2229 && (self->client->saber[0].saberFlags&SFL_NO_WALL_RUNS) )
2230 {
2231 allowWallRuns = qfalse;
2232 }
2233 else if ( self->client->saber[1].model[0]
2234 && (self->client->saber[1].saberFlags&SFL_NO_WALL_RUNS) )
2235 {
2236 allowWallRuns = qfalse;
2237 }
2238 }
2239 if ( allowWallRuns )
2240 {//okay to do wallruns with this saber
2241 //FIXME: check for long enough wall and a drop at the end?
2242 if ( bestCheckDist > 0 )
2243 {//it was to the right
2244 anim = BOTH_WALL_RUN_RIGHT;
2245 }
2246 else
2247 {//it was to the left
2248 anim = BOTH_WALL_RUN_LEFT;
2249 }
2250 self->client->ps.velocity[2] = forceJumpStrength[FORCE_LEVEL_2]/2.25f;
2251 //animate me
2252 parts = SETANIM_LEGS;
2253 if ( !self->client->ps.weaponTime )
2254 {
2255 parts = SETANIM_BOTH;
2256 }
2257 NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
2258 self->client->ps.fd.forceJumpZStart = self->r.currentOrigin[2];//so we don't take damage if we land at same height
2259 //self->client->ps.pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL);
2260 if ( self->client->NPC_class == CLASS_BOBAFETT )
2261 {
2262 G_AddEvent( self, EV_JUMP, 0 );
2263 }
2264 else
2265 {
2266 G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" );
2267 }
2268 return EVASION_OTHER;
2269 }
2270 }
2271 //else check for wall in front, do backflip off wall
2272 }
2273 }
2274 }
2275 return EVASION_NONE;
2276 }
2277
Jedi_ReCalcParryTime(gentity_t * self,evasionType_t evasionType)2278 int Jedi_ReCalcParryTime( gentity_t *self, evasionType_t evasionType )
2279 {
2280 if ( !self->client )
2281 {
2282 return 0;
2283 }
2284 if ( self->s.number >= 0 && self->s.number < MAX_CLIENTS )
2285 {//player
2286 return bg_parryDebounce[self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE]];
2287 }
2288 else if ( self->NPC )
2289 {
2290 if ( !g_saberRealisticCombat.integer
2291 && ( g_npcspskill.integer == 2 || (g_npcspskill.integer == 1 && self->client->NPC_class == CLASS_TAVION) ) )
2292 {
2293 if ( self->client->NPC_class == CLASS_TAVION )
2294 {
2295 return 0;
2296 }
2297 else
2298 {
2299 return Q_irand( 0, 150 );
2300 }
2301 }
2302 else
2303 {
2304 int baseTime;
2305 if ( evasionType == EVASION_DODGE )
2306 {
2307 baseTime = self->client->ps.torsoTimer;
2308 }
2309 else if ( evasionType == EVASION_CARTWHEEL )
2310 {
2311 baseTime = self->client->ps.torsoTimer;
2312 }
2313 else if ( self->client->ps.saberInFlight )
2314 {
2315 baseTime = Q_irand( 1, 3 ) * 50;
2316 }
2317 else
2318 {
2319 if ( g_saberRealisticCombat.integer )
2320 {
2321 baseTime = 500;
2322
2323 switch ( g_npcspskill.integer )
2324 {
2325 case 0:
2326 baseTime = 500;
2327 break;
2328 case 1:
2329 baseTime = 300;
2330 break;
2331 case 2:
2332 default:
2333 baseTime = 100;
2334 break;
2335 }
2336 }
2337 else
2338 {
2339 baseTime = 150;//500;
2340
2341 switch ( g_npcspskill.integer )
2342 {
2343 case 0:
2344 baseTime = 200;//500;
2345 break;
2346 case 1:
2347 baseTime = 100;//300;
2348 break;
2349 case 2:
2350 default:
2351 baseTime = 50;//100;
2352 break;
2353 }
2354 }
2355
2356 if ( self->client->NPC_class == CLASS_TAVION )
2357 {//Tavion is faster
2358 baseTime = ceil(baseTime/2.0f);
2359 }
2360 else if ( self->NPC->rank >= RANK_LT_JG )
2361 {//fencers, bosses, shadowtroopers, luke, desann, et al use the norm
2362 if ( !Q_irand( 0, 2 ) )
2363 {//with the occasional fast parry
2364 baseTime = ceil(baseTime/2.0f);
2365 }
2366 }
2367 else if ( self->NPC->rank == RANK_CIVILIAN )
2368 {//grunts are slowest
2369 baseTime = baseTime*Q_irand(1,3);
2370 }
2371 else if ( self->NPC->rank == RANK_CREWMAN )
2372 {//acrobats aren't so bad
2373 if ( evasionType == EVASION_PARRY
2374 || evasionType == EVASION_DUCK_PARRY
2375 || evasionType == EVASION_JUMP_PARRY )
2376 {//slower with parries
2377 baseTime = baseTime*Q_irand(1,2);
2378 }
2379 else
2380 {//faster with acrobatics
2381 //baseTime = baseTime;
2382 }
2383 }
2384 else
2385 {//force users are kinda slow
2386 baseTime = baseTime*Q_irand(1,2);
2387 }
2388 if ( evasionType == EVASION_DUCK || evasionType == EVASION_DUCK_PARRY )
2389 {
2390 baseTime += 100;
2391 }
2392 else if ( evasionType == EVASION_JUMP || evasionType == EVASION_JUMP_PARRY )
2393 {
2394 baseTime += 50;
2395 }
2396 else if ( evasionType == EVASION_OTHER )
2397 {
2398 baseTime += 100;
2399 }
2400 else if ( evasionType == EVASION_FJUMP )
2401 {
2402 baseTime += 100;
2403 }
2404 }
2405
2406 return baseTime;
2407 }
2408 }
2409 return 0;
2410 }
2411
Jedi_QuickReactions(gentity_t * self)2412 qboolean Jedi_QuickReactions( gentity_t *self )
2413 {
2414 if ( ( self->client->NPC_class == CLASS_JEDI && NPCS.NPCInfo->rank == RANK_COMMANDER ) ||
2415 self->client->NPC_class == CLASS_TAVION ||
2416 (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_1&&g_npcspskill.integer>1) ||
2417 (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_2&&g_npcspskill.integer>0) )
2418 {
2419 return qtrue;
2420 }
2421 return qfalse;
2422 }
2423
Jedi_SaberBusy(gentity_t * self)2424 qboolean Jedi_SaberBusy( gentity_t *self )
2425 {
2426 if ( self->client->ps.torsoTimer > 300
2427 && ( (BG_SaberInAttack( self->client->ps.saberMove )&&self->client->ps.fd.saberAnimLevel==FORCE_LEVEL_3)
2428 || BG_SpinningSaberAnim( self->client->ps.torsoAnim )
2429 || BG_SaberInSpecialAttack( self->client->ps.torsoAnim )
2430 //|| PM_SaberInBounce( self->client->ps.saberMove )
2431 || PM_SaberInBrokenParry( self->client->ps.saberMove )
2432 //|| PM_SaberInDeflect( self->client->ps.saberMove )
2433 || BG_FlippingAnim( self->client->ps.torsoAnim )
2434 || PM_RollingAnim( self->client->ps.torsoAnim ) ) )
2435 {//my saber is not in a parrying position
2436 return qtrue;
2437 }
2438 return qfalse;
2439 }
2440
2441 /*
2442 -------------------------
2443 Jedi_SaberBlock
2444
2445 Pick proper block anim
2446
2447 FIXME: Based on difficulty level/enemy saber combat skill, make this decision-making more/less effective
2448
2449 NOTE: always blocking projectiles in this func!
2450
2451 -------------------------
2452 */
2453 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)2454 evasionType_t Jedi_SaberBlockGo( gentity_t *self, usercmd_t *cmd, vec3_t pHitloc, vec3_t phitDir, gentity_t *incoming, float dist ) //dist = 0.0f
2455 {
2456 vec3_t hitloc, hitdir, diff, fwdangles={0,0,0}, right;
2457 float rightdot;
2458 float zdiff;
2459 int duckChance = 0;
2460 int dodgeAnim = -1;
2461 qboolean saberBusy = qfalse, doDodge = qfalse;
2462 evasionType_t evasionType = EVASION_NONE;
2463
2464 //FIXME: if we don't have our saber in hand, pick the force throw option or a jump or strafe!
2465 //FIXME: reborn don't block enough anymore
2466 if ( !incoming )
2467 {
2468 VectorCopy( pHitloc, hitloc );
2469 VectorCopy( phitDir, hitdir );
2470 //FIXME: maybe base this on rank some? And/or g_npcspskill?
2471 if ( self->client->ps.saberInFlight )
2472 {//DOH! do non-saber evasion!
2473 saberBusy = qtrue;
2474 }
2475 else if ( Jedi_QuickReactions( self ) )
2476 {//jedi trainer and tavion are must faster at parrying and can do it whenever they like
2477 //Also, on medium, all level 3 people can parry any time and on hard, all level 2 or 3 people can parry any time
2478 }
2479 else
2480 {
2481 saberBusy = Jedi_SaberBusy( self );
2482 }
2483 }
2484 else
2485 {
2486 if ( incoming->s.weapon == WP_SABER )
2487 {//flying lightsaber, face it!
2488 //FIXME: for this to actually work, we'd need to call update angles too?
2489 //Jedi_FaceEntity( self, incoming, qtrue );
2490 }
2491 VectorCopy( incoming->r.currentOrigin, hitloc );
2492 VectorNormalize2( incoming->s.pos.trDelta, hitdir );
2493 }
2494 if ( self->client && self->client->NPC_class == CLASS_BOBAFETT )
2495 {
2496 saberBusy = qtrue;
2497 }
2498
2499 VectorSubtract( hitloc, self->client->renderInfo.eyePoint, diff );
2500 diff[2] = 0;
2501 //VectorNormalize( diff );
2502 fwdangles[1] = self->client->ps.viewangles[1];
2503 // Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
2504 AngleVectors( fwdangles, NULL, right, NULL );
2505
2506 rightdot = DotProduct(right, diff);// + flrand(-0.10f,0.10f);
2507 //totalHeight = self->client->renderInfo.eyePoint[2] - self->r.absmin[2];
2508 zdiff = hitloc[2] - self->client->renderInfo.eyePoint[2];// + Q_irand(-6,6);
2509
2510 //see if we can dodge if need-be
2511 if ( (dist>16&&(Q_irand( 0, 2 )||saberBusy))
2512 || self->client->ps.saberInFlight
2513 || BG_SabersOff( &self->client->ps )
2514 || self->client->NPC_class == CLASS_BOBAFETT )
2515 {//either it will miss by a bit (and 25% chance) OR our saber is not in-hand OR saber is off
2516 if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank >= RANK_LT_JG) )
2517 {//acrobat or fencer or above
2518 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE &&//on the ground
2519 !(self->client->ps.pm_flags&PMF_DUCKED)&&cmd->upmove>=0&&TIMER_Done( self, "duck" )//not ducking
2520 && !BG_InRoll( &self->client->ps, self->client->ps.legsAnim )//not rolling
2521 && !PM_InKnockDown( &self->client->ps )//not knocked down
2522 && ( self->client->ps.saberInFlight ||
2523 self->client->NPC_class == CLASS_BOBAFETT ||
2524 (!BG_SaberInAttack( self->client->ps.saberMove )//not attacking
2525 && !PM_SaberInStart( self->client->ps.saberMove )//not starting an attack
2526 && !BG_SpinningSaberAnim( self->client->ps.torsoAnim )//not in a saber spin
2527 && !BG_SaberInSpecialAttack( self->client->ps.torsoAnim ))//not in a special attack
2528 )
2529 )
2530 {//need to check all these because it overrides both torso and legs with the dodge
2531 doDodge = qtrue;
2532 }
2533 }
2534 }
2535 // Figure out what quadrant the block was in.
2536 if ( d_JediAI.integer )
2537 {
2538 Com_Printf( "(%d) evading attack from height %4.2f, zdiff: %4.2f, rightdot: %4.2f\n", level.time, hitloc[2]-self->r.absmin[2],zdiff,rightdot);
2539 }
2540
2541 //UL = > -1//-6
2542 //UR = > -6//-9
2543 //TOP = > +6//+4
2544 //FIXME: take FP_SABER_DEFENSE into account here somehow?
2545 if ( zdiff >= -5 )//was 0
2546 {
2547 if ( incoming || !saberBusy )
2548 {
2549 if ( rightdot > 12
2550 || (rightdot > 3 && zdiff < 5)
2551 || (!incoming&&fabs(hitdir[2])<0.25f) )//was normalized, 0.3
2552 {//coming from right
2553 if ( doDodge )
2554 {
2555 if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) )
2556 {//roll!
2557 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
2558 TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) );
2559 TIMER_Set( self, "strafeRight", 0 );
2560 evasionType = EVASION_DUCK;
2561 }
2562 else if ( Q_irand( 0, 1 ) )
2563 {
2564 dodgeAnim = BOTH_DODGE_FL;
2565 }
2566 else
2567 {
2568 dodgeAnim = BOTH_DODGE_BL;
2569 }
2570 }
2571 else
2572 {
2573 self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
2574 evasionType = EVASION_PARRY;
2575 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
2576 {
2577 if ( zdiff > 5 )
2578 {
2579 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
2580 evasionType = EVASION_DUCK_PARRY;
2581 if ( d_JediAI.integer )
2582 {
2583 Com_Printf( "duck " );
2584 }
2585 }
2586 else
2587 {
2588 duckChance = 6;
2589 }
2590 }
2591 }
2592 if ( d_JediAI.integer )
2593 {
2594 Com_Printf( "UR block\n" );
2595 }
2596 }
2597 else if ( rightdot < -12
2598 || (rightdot < -3 && zdiff < 5)
2599 || (!incoming&&fabs(hitdir[2])<0.25f) )//was normalized, -0.3
2600 {//coming from left
2601 if ( doDodge )
2602 {
2603 if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) )
2604 {//roll!
2605 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
2606 TIMER_Start( self, "strafeRight", Q_irand( 500, 1500 ) );
2607 TIMER_Set( self, "strafeLeft", 0 );
2608 evasionType = EVASION_DUCK;
2609 }
2610 else if ( Q_irand( 0, 1 ) )
2611 {
2612 dodgeAnim = BOTH_DODGE_FR;
2613 }
2614 else
2615 {
2616 dodgeAnim = BOTH_DODGE_BR;
2617 }
2618 }
2619 else
2620 {
2621 self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
2622 evasionType = EVASION_PARRY;
2623 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
2624 {
2625 if ( zdiff > 5 )
2626 {
2627 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
2628 evasionType = EVASION_DUCK_PARRY;
2629 if ( d_JediAI.integer )
2630 {
2631 Com_Printf( "duck " );
2632 }
2633 }
2634 else
2635 {
2636 duckChance = 6;
2637 }
2638 }
2639 }
2640 if ( d_JediAI.integer )
2641 {
2642 Com_Printf( "UL block\n" );
2643 }
2644 }
2645 else
2646 {
2647 self->client->ps.saberBlocked = BLOCKED_TOP;
2648 evasionType = EVASION_PARRY;
2649 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
2650 {
2651 duckChance = 4;
2652 }
2653 if ( d_JediAI.integer )
2654 {
2655 Com_Printf( "TOP block\n" );
2656 }
2657 }
2658 }
2659 else
2660 {
2661 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
2662 {
2663 //duckChance = 2;
2664 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
2665 evasionType = EVASION_DUCK;
2666 if ( d_JediAI.integer )
2667 {
2668 Com_Printf( "duck " );
2669 }
2670 }
2671 }
2672 }
2673 //LL = -22//= -18 to -39
2674 //LR = -23//= -20 to -41
2675 else if ( zdiff > -22 )//was-15 )
2676 {
2677 if ( 1 )//zdiff < -10 )
2678 {//hmm, pretty low, but not low enough to use the low block, so we need to duck
2679 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
2680 {
2681 //duckChance = 2;
2682 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
2683 evasionType = EVASION_DUCK;
2684 if ( d_JediAI.integer )
2685 {
2686 Com_Printf( "duck " );
2687 }
2688 }
2689 else
2690 {//in air! Ducking does no good
2691 }
2692 }
2693 if ( incoming || !saberBusy )
2694 {
2695 if ( rightdot > 8 || (rightdot > 3 && zdiff < -11) )//was normalized, 0.2
2696 {
2697 if ( doDodge )
2698 {
2699 if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) )
2700 {//roll!
2701 TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) );
2702 TIMER_Set( self, "strafeRight", 0 );
2703 }
2704 else
2705 {
2706 dodgeAnim = BOTH_DODGE_L;
2707 }
2708 }
2709 else
2710 {
2711 self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
2712 if ( evasionType == EVASION_DUCK )
2713 {
2714 evasionType = EVASION_DUCK_PARRY;
2715 }
2716 else
2717 {
2718 evasionType = EVASION_PARRY;
2719 }
2720 }
2721 if ( d_JediAI.integer )
2722 {
2723 Com_Printf( "mid-UR block\n" );
2724 }
2725 }
2726 else if ( rightdot < -8 || (rightdot < -3 && zdiff < -11) )//was normalized, -0.2
2727 {
2728 if ( doDodge )
2729 {
2730 if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) )
2731 {//roll!
2732 TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) );
2733 TIMER_Set( self, "strafeRight", 0 );
2734 }
2735 else
2736 {
2737 dodgeAnim = BOTH_DODGE_R;
2738 }
2739 }
2740 else
2741 {
2742 self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
2743 if ( evasionType == EVASION_DUCK )
2744 {
2745 evasionType = EVASION_DUCK_PARRY;
2746 }
2747 else
2748 {
2749 evasionType = EVASION_PARRY;
2750 }
2751 }
2752 if ( d_JediAI.integer )
2753 {
2754 Com_Printf( "mid-UL block\n" );
2755 }
2756 }
2757 else
2758 {
2759 self->client->ps.saberBlocked = BLOCKED_TOP;
2760 if ( evasionType == EVASION_DUCK )
2761 {
2762 evasionType = EVASION_DUCK_PARRY;
2763 }
2764 else
2765 {
2766 evasionType = EVASION_PARRY;
2767 }
2768 if ( d_JediAI.integer )
2769 {
2770 Com_Printf( "mid-TOP block\n" );
2771 }
2772 }
2773 }
2774 }
2775 else if ( saberBusy || (zdiff < -36 && ( zdiff < -44 || !Q_irand( 0, 2 ) ) ) )//was -30 and -40//2nd one was -46
2776 {//jump!
2777 if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE )
2778 {//already in air, duck to pull up legs
2779 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
2780 evasionType = EVASION_DUCK;
2781 if ( d_JediAI.integer )
2782 {
2783 Com_Printf( "legs up\n" );
2784 }
2785 if ( incoming || !saberBusy )
2786 {
2787 //since the jump may be cleared if not safe, set a lower block too
2788 if ( rightdot >= 0 )
2789 {
2790 self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
2791 evasionType = EVASION_DUCK_PARRY;
2792 if ( d_JediAI.integer )
2793 {
2794 Com_Printf( "LR block\n" );
2795 }
2796 }
2797 else
2798 {
2799 self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
2800 evasionType = EVASION_DUCK_PARRY;
2801 if ( d_JediAI.integer )
2802 {
2803 Com_Printf( "LL block\n" );
2804 }
2805 }
2806 }
2807 }
2808 else
2809 {//gotta jump!
2810 if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank > RANK_LT_JG ) &&
2811 (!Q_irand( 0, 10 ) || (!Q_irand( 0, 2 ) && (cmd->forwardmove || cmd->rightmove))) )
2812 {//superjump
2813 //FIXME: check the jump, if can't, then block
2814 if ( self->NPC
2815 && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS)
2816 && self->client->ps.fd.forceRageRecoveryTime < level.time
2817 && !(self->client->ps.fd.forcePowersActive&(1<<FP_RAGE))
2818 && !PM_InKnockDown( &self->client->ps ) )
2819 {
2820 self->client->ps.fd.forceJumpCharge = 320;//FIXME: calc this intelligently
2821 evasionType = EVASION_FJUMP;
2822 if ( d_JediAI.integer )
2823 {
2824 Com_Printf( "force jump + " );
2825 }
2826 }
2827 }
2828 else
2829 {//normal jump
2830 //FIXME: check the jump, if can't, then block
2831 if ( self->NPC
2832 && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS)
2833 && self->client->ps.fd.forceRageRecoveryTime < level.time
2834 && !(self->client->ps.fd.forcePowersActive&(1<<FP_RAGE)) )
2835 {
2836 if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 1 ) )
2837 {//roll!
2838 if ( rightdot > 0 )
2839 {
2840 TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) );
2841 TIMER_Set( self, "strafeRight", 0 );
2842 TIMER_Set( self, "walking", 0 );
2843 }
2844 else
2845 {
2846 TIMER_Start( self, "strafeRight", Q_irand( 500, 1500 ) );
2847 TIMER_Set( self, "strafeLeft", 0 );
2848 TIMER_Set( self, "walking", 0 );
2849 }
2850 }
2851 else
2852 {
2853 if ( self == NPCS.NPC )
2854 {
2855 cmd->upmove = 127;
2856 }
2857 else
2858 {
2859 self->client->ps.velocity[2] = JUMP_VELOCITY;
2860 }
2861 }
2862 evasionType = EVASION_JUMP;
2863 if ( d_JediAI.integer )
2864 {
2865 Com_Printf( "jump + " );
2866 }
2867 }
2868 if ( self->client->NPC_class == CLASS_TAVION )
2869 {
2870 if ( !incoming
2871 && self->client->ps.groundEntityNum < ENTITYNUM_NONE
2872 && !Q_irand( 0, 2 ) )
2873 {
2874 if ( !BG_SaberInAttack( self->client->ps.saberMove )
2875 && !PM_SaberInStart( self->client->ps.saberMove )
2876 && !BG_InRoll( &self->client->ps, self->client->ps.legsAnim )
2877 && !PM_InKnockDown( &self->client->ps )
2878 && !BG_SaberInSpecialAttack( self->client->ps.torsoAnim ) )
2879 {//do the butterfly!
2880 int butterflyAnim;
2881 if ( Q_irand( 0, 1 ) )
2882 {
2883 butterflyAnim = BOTH_BUTTERFLY_LEFT;
2884 }
2885 else
2886 {
2887 butterflyAnim = BOTH_BUTTERFLY_RIGHT;
2888 }
2889 evasionType = EVASION_CARTWHEEL;
2890 NPC_SetAnim( self, SETANIM_BOTH, butterflyAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
2891 self->client->ps.velocity[2] = 225;
2892 self->client->ps.fd.forceJumpZStart = self->r.currentOrigin[2];//so we don't take damage if we land at same height
2893 // self->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL;
2894 // self->client->ps.SaberActivateTrail( 300 );//FIXME: reset this when done!
2895 //Ah well. No hacking from the server for now.
2896 if ( self->client->NPC_class == CLASS_BOBAFETT )
2897 {
2898 G_AddEvent( self, EV_JUMP, 0 );
2899 }
2900 else
2901 {
2902 G_Sound( self, CHAN_BODY, G_SoundIndex("sound/weapons/force/jump.wav") );
2903 }
2904 cmd->upmove = 0;
2905 saberBusy = qtrue;
2906 }
2907 }
2908 }
2909 }
2910 if ( ((evasionType = Jedi_CheckFlipEvasions( self, rightdot, zdiff ))!=EVASION_NONE) )
2911 {
2912 saberBusy = qtrue;
2913 }
2914 else if ( incoming || !saberBusy )
2915 {
2916 //since the jump may be cleared if not safe, set a lower block too
2917 if ( rightdot >= 0 )
2918 {
2919 self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
2920 if ( evasionType == EVASION_JUMP )
2921 {
2922 evasionType = EVASION_JUMP_PARRY;
2923 }
2924 else if ( evasionType == EVASION_NONE )
2925 {
2926 evasionType = EVASION_PARRY;
2927 }
2928 if ( d_JediAI.integer )
2929 {
2930 Com_Printf( "LR block\n" );
2931 }
2932 }
2933 else
2934 {
2935 self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
2936 if ( evasionType == EVASION_JUMP )
2937 {
2938 evasionType = EVASION_JUMP_PARRY;
2939 }
2940 else if ( evasionType == EVASION_NONE )
2941 {
2942 evasionType = EVASION_PARRY;
2943 }
2944 if ( d_JediAI.integer )
2945 {
2946 Com_Printf( "LL block\n" );
2947 }
2948 }
2949 }
2950 }
2951 }
2952 else
2953 {
2954 if ( incoming || !saberBusy )
2955 {
2956 if ( rightdot >= 0 )
2957 {
2958 self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
2959 evasionType = EVASION_PARRY;
2960 if ( d_JediAI.integer )
2961 {
2962 Com_Printf( "LR block\n" );
2963 }
2964 }
2965 else
2966 {
2967 self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
2968 evasionType = EVASION_PARRY;
2969 if ( d_JediAI.integer )
2970 {
2971 Com_Printf( "LL block\n" );
2972 }
2973 }
2974 if ( incoming && incoming->s.weapon == WP_SABER )
2975 {//thrown saber!
2976 if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank > RANK_LT_JG ) &&
2977 (!Q_irand( 0, 10 ) || (!Q_irand( 0, 2 ) && (cmd->forwardmove || cmd->rightmove))) )
2978 {//superjump
2979 //FIXME: check the jump, if can't, then block
2980 if ( self->NPC
2981 && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS)
2982 && self->client->ps.fd.forceRageRecoveryTime < level.time
2983 && !(self->client->ps.fd.forcePowersActive&(1<<FP_RAGE))
2984 && !PM_InKnockDown( &self->client->ps ) )
2985 {
2986 self->client->ps.fd.forceJumpCharge = 320;//FIXME: calc this intelligently
2987 evasionType = EVASION_FJUMP;
2988 if ( d_JediAI.integer )
2989 {
2990 Com_Printf( "force jump + " );
2991 }
2992 }
2993 }
2994 else
2995 {//normal jump
2996 //FIXME: check the jump, if can't, then block
2997 if ( self->NPC
2998 && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS)
2999 && self->client->ps.fd.forceRageRecoveryTime < level.time
3000 && !(self->client->ps.fd.forcePowersActive&(1<<FP_RAGE)))
3001 {
3002 if ( self == NPCS.NPC )
3003 {
3004 cmd->upmove = 127;
3005 }
3006 else
3007 {
3008 self->client->ps.velocity[2] = JUMP_VELOCITY;
3009 }
3010 evasionType = EVASION_JUMP_PARRY;
3011 if ( d_JediAI.integer )
3012 {
3013 Com_Printf( "jump + " );
3014 }
3015 }
3016 }
3017 }
3018 }
3019 }
3020
3021 if ( evasionType == EVASION_NONE )
3022 {
3023 return EVASION_NONE;
3024 }
3025 //stop taunting
3026 TIMER_Set( self, "taunting", 0 );
3027 //stop gripping
3028 TIMER_Set( self, "gripping", -level.time );
3029 WP_ForcePowerStop( self, FP_GRIP );
3030 //stop draining
3031 TIMER_Set( self, "draining", -level.time );
3032 WP_ForcePowerStop( self, FP_DRAIN );
3033
3034 if ( dodgeAnim != -1 )
3035 {//dodged
3036 evasionType = EVASION_DODGE;
3037 NPC_SetAnim( self, SETANIM_BOTH, dodgeAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
3038 self->client->ps.weaponTime = self->client->ps.torsoTimer;
3039 //force them to stop moving in this case
3040 self->client->ps.pm_time = self->client->ps.torsoTimer;
3041 //FIXME: maybe make a sound? Like a grunt? EV_JUMP?
3042 self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
3043 //dodged, not block
3044 }
3045 else
3046 {
3047 if ( duckChance )
3048 {
3049 if ( !Q_irand( 0, duckChance ) )
3050 {
3051 TIMER_Start( self, "duck", Q_irand( 500, 1500 ) );
3052 if ( evasionType == EVASION_PARRY )
3053 {
3054 evasionType = EVASION_DUCK_PARRY;
3055 }
3056 else
3057 {
3058 evasionType = EVASION_DUCK;
3059 }
3060 /*
3061 if ( d_JediAI.integer )
3062 {
3063 Com_Printf( "duck " );
3064 }
3065 */
3066 }
3067 }
3068
3069 if ( incoming )
3070 {
3071 self->client->ps.saberBlocked = WP_MissileBlockForBlock( self->client->ps.saberBlocked );
3072 }
3073
3074 }
3075 //if ( self->client->ps.saberBlocked != BLOCKED_NONE )
3076 {
3077 int parryReCalcTime = Jedi_ReCalcParryTime( self, evasionType );
3078 if ( self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] < level.time + parryReCalcTime )
3079 {
3080 self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + parryReCalcTime;
3081 }
3082 }
3083 return evasionType;
3084 }
3085
3086 extern float ShortestLineSegBewteen2LineSegs( vec3_t start1, vec3_t end1, vec3_t start2, vec3_t end2, vec3_t close_pnt1, vec3_t close_pnt2 );
3087 extern int WPDEBUG_SaberColor( saber_colors_t saberColor );
Jedi_SaberBlock(int saberNum,int bladeNum)3088 static qboolean Jedi_SaberBlock( int saberNum, int bladeNum ) //saberNum = 0, bladeNum = 0
3089 {
3090 vec3_t hitloc, saberTipOld, saberTip, top, bottom, axisPoint, saberPoint, dir;//saberBase,
3091 vec3_t pointDir, baseDir, tipDir, saberHitPoint, saberMins, saberMaxs;
3092 float pointDist, baseDirPerc, dist;
3093 float bladeLen = 0;
3094 trace_t tr;
3095 evasionType_t evasionType;
3096
3097 //FIXME: reborn don't block enough anymore
3098 /*
3099 //maybe do this on easy only... or only on grunt-level reborn
3100 if ( NPC->client->ps.weaponTime )
3101 {//i'm attacking right now
3102 return qfalse;
3103 }
3104 */
3105
3106 if ( !TIMER_Done( NPCS.NPC, "parryReCalcTime" ) )
3107 {//can't do our own re-think of which parry to use yet
3108 return qfalse;
3109 }
3110
3111 if ( NPCS.NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] > level.time )
3112 {//can't move the saber to another position yet
3113 return qfalse;
3114 }
3115
3116 /*
3117 if ( NPCInfo->rank < RANK_LT_JG && Q_irand( 0, (2 - g_npcspskill.integer) ) )
3118 {//lower rank reborn have a random chance of not doing it at all
3119 NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 300;
3120 return qfalse;
3121 }
3122 */
3123
3124 if ( NPCS.NPC->enemy->health <= 0 || !NPCS.NPC->enemy->client )
3125 {//don't keep blocking him once he's dead (or if not a client)
3126 return qfalse;
3127 }
3128 /*
3129 //VectorMA( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDir, saberTip );
3130 //VectorMA( NPC->enemy->client->renderInfo.muzzlePointNext, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDirNext, saberTipNext );
3131 VectorMA( NPC->enemy->client->renderInfo.muzzlePointOld, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDirOld, saberTipOld );
3132 VectorMA( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDir, saberTip );
3133
3134 VectorSubtract( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->renderInfo.muzzlePointOld, dir );//get the dir
3135 VectorAdd( dir, NPC->enemy->client->renderInfo.muzzlePoint, saberBase );//extrapolate
3136
3137 VectorSubtract( saberTip, saberTipOld, dir );//get the dir
3138 VectorAdd( dir, saberTip, saberTipOld );//extrapolate
3139
3140 VectorCopy( NPC->r.currentOrigin, top );
3141 top[2] = NPC->r.absmax[2];
3142 VectorCopy( NPC->r.currentOrigin, bottom );
3143 bottom[2] = NPC->r.absmin[2];
3144
3145 float dist = ShortestLineSegBewteen2LineSegs( saberBase, saberTipOld, bottom, top, saberPoint, axisPoint );
3146 if ( 0 )//dist > NPC->r.maxs[0]*4 )//was *3
3147 {//FIXME: sometimes he reacts when you're too far away to actually hit him
3148 if ( d_JediAI.integer )
3149 {
3150 Com_Printf( "enemy saber dist: %4.2f\n", dist );
3151 }
3152 TIMER_Set( NPC, "parryTime", -1 );
3153 return qfalse;
3154 }
3155
3156 //get the actual point of impact
3157 trace_t tr;
3158 trap->Trace( &tr, saberPoint, vec3_origin, vec3_origin, axisPoint, NPC->enemy->s.number, MASK_SHOT, G2_RETURNONHIT, 10 );
3159 if ( tr.allsolid || tr.startsolid )
3160 {//estimate
3161 VectorSubtract( saberPoint, axisPoint, dir );
3162 VectorNormalize( dir );
3163 VectorMA( axisPoint, NPC->r.maxs[0]*1.22, dir, hitloc );
3164 }
3165 else
3166 {
3167 VectorCopy( tr.endpos, hitloc );
3168 }
3169 */
3170 VectorSet(saberMins,-4,-4,-4);
3171 VectorSet(saberMaxs,4,4,4);
3172
3173 VectorMA( NPCS.NPC->enemy->client->saber[saberNum].blade[bladeNum].muzzlePointOld, NPCS.NPC->enemy->client->saber[saberNum].blade[bladeNum].length, NPCS.NPC->enemy->client->saber[saberNum].blade[bladeNum].muzzleDirOld, saberTipOld );
3174 VectorMA( NPCS.NPC->enemy->client->saber[saberNum].blade[bladeNum].muzzlePoint, NPCS.NPC->enemy->client->saber[saberNum].blade[bladeNum].length, NPCS.NPC->enemy->client->saber[saberNum].blade[bladeNum].muzzleDir, saberTip );
3175 // VectorCopy(NPC->enemy->client->lastSaberBase_Always, muzzlePoint);
3176 // VectorMA(muzzlePoint, GAME_SABER_LENGTH, NPC->enemy->client->lastSaberDir_Always, saberTip);
3177 // VectorCopy(saberTip, saberTipOld);
3178
3179 VectorCopy( NPCS.NPC->r.currentOrigin, top );
3180 top[2] = NPCS.NPC->r.absmax[2];
3181 VectorCopy( NPCS.NPC->r.currentOrigin, bottom );
3182 bottom[2] = NPCS.NPC->r.absmin[2];
3183
3184 dist = ShortestLineSegBewteen2LineSegs( NPCS.NPC->enemy->client->renderInfo.muzzlePoint, saberTip, bottom, top, saberPoint, axisPoint );
3185 if ( dist > NPCS.NPC->r.maxs[0]*5 )//was *3
3186 {//FIXME: sometimes he reacts when you're too far away to actually hit him
3187 if ( d_JediAI.integer )
3188 {
3189 Com_Printf( S_COLOR_RED"enemy saber dist: %4.2f\n", dist );
3190 }
3191 /*
3192 if ( dist < 300 //close
3193 && !Jedi_QuickReactions( NPC )//quick reaction people can interrupt themselves
3194 && (PM_SaberInStart( NPC->enemy->client->ps.saberMove ) || BG_SaberInAttack( NPC->enemy->client->ps.saberMove )) )//enemy is swinging at me
3195 {//he's swinging at me and close enough to be a threat, don't start an attack right now
3196 TIMER_Set( NPC, "parryTime", 100 );
3197 }
3198 else
3199 */
3200 {
3201 TIMER_Set( NPCS.NPC, "parryTime", -1 );
3202 }
3203 return qfalse;
3204 }
3205 if ( d_JediAI.integer )
3206 {
3207 Com_Printf( S_COLOR_GREEN"enemy saber dist: %4.2f\n", dist );
3208 }
3209
3210 VectorSubtract( saberPoint, NPCS.NPC->enemy->client->renderInfo.muzzlePoint, pointDir );
3211 pointDist = VectorLength( pointDir );
3212
3213 bladeLen = NPCS.NPC->enemy->client->saber[saberNum].blade[bladeNum].length;
3214
3215 if ( bladeLen <= 0 )
3216 {
3217 baseDirPerc = 0.5f;
3218 }
3219 else
3220 {
3221 baseDirPerc = pointDist/bladeLen;
3222 }
3223 VectorSubtract( NPCS.NPC->enemy->client->renderInfo.muzzlePoint, NPCS.NPC->enemy->client->renderInfo.muzzlePointOld, baseDir );
3224 VectorSubtract( saberTip, saberTipOld, tipDir );
3225 VectorScale( baseDir, baseDirPerc, baseDir );
3226 VectorMA( baseDir, 1.0f-baseDirPerc, tipDir, dir );
3227 VectorMA( saberPoint, 200, dir, hitloc );
3228
3229 //get the actual point of impact
3230 trap->Trace( &tr, saberPoint, saberMins, saberMaxs, hitloc, NPCS.NPC->enemy->s.number, CONTENTS_BODY, qfalse, 0, 0 );//, G2_RETURNONHIT, 10 );
3231 if ( tr.allsolid || tr.startsolid || tr.fraction >= 1.0f )
3232 {//estimate
3233 vec3_t dir2Me;
3234 VectorSubtract( axisPoint, saberPoint, dir2Me );
3235 dist = VectorNormalize( dir2Me );
3236 if ( DotProduct( dir, dir2Me ) < 0.2f )
3237 {//saber is not swinging in my direction
3238 /*
3239 if ( dist < 300 //close
3240 && !Jedi_QuickReactions( NPC )//quick reaction people can interrupt themselves
3241 && (PM_SaberInStart( NPC->enemy->client->ps.saberMove ) || BG_SaberInAttack( NPC->enemy->client->ps.saberMove )) )//enemy is swinging at me
3242 {//he's swinging at me and close enough to be a threat, don't start an attack right now
3243 TIMER_Set( NPC, "parryTime", 100 );
3244 }
3245 else
3246 */
3247 {
3248 TIMER_Set( NPCS.NPC, "parryTime", -1 );
3249 }
3250 return qfalse;
3251 }
3252 ShortestLineSegBewteen2LineSegs( saberPoint, hitloc, bottom, top, saberHitPoint, hitloc );
3253 /*
3254 VectorSubtract( saberPoint, axisPoint, dir );
3255 VectorNormalize( dir );
3256 VectorMA( axisPoint, NPC->r.maxs[0]*1.22, dir, hitloc );
3257 */
3258 }
3259 else
3260 {
3261 VectorCopy( tr.endpos, hitloc );
3262 }
3263
3264 if ( d_JediAI.integer )
3265 {
3266 //G_DebugLine( saberPoint, hitloc, FRAMETIME, WPDEBUG_SaberColor( NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].color ), qtrue );
3267 G_TestLine(saberPoint, hitloc, 0x0000ff, FRAMETIME);
3268 }
3269
3270 //FIXME: if saber is off and/or we have force speed and want to be really cocky,
3271 // and the swing misses by some amount, we can use the dodges here... :)
3272 if ( (evasionType=Jedi_SaberBlockGo( NPCS.NPC, &NPCS.ucmd, hitloc, dir, NULL, dist )) != EVASION_DODGE )
3273 {//we did block (not dodge)
3274 int parryReCalcTime;
3275
3276 if ( !NPCS.NPC->client->ps.saberInFlight )
3277 {//make sure saber is on
3278 WP_ActivateSaber(NPCS.NPC);
3279 }
3280
3281 //debounce our parry recalc time
3282 parryReCalcTime = Jedi_ReCalcParryTime( NPCS.NPC, evasionType );
3283 TIMER_Set( NPCS.NPC, "parryReCalcTime", Q_irand( 0, parryReCalcTime ) );
3284 if ( d_JediAI.integer )
3285 {
3286 Com_Printf( "Keep parry choice until: %d\n", level.time + parryReCalcTime );
3287 }
3288
3289 //determine how long to hold this anim
3290 if ( TIMER_Done( NPCS.NPC, "parryTime" ) )
3291 {
3292 if ( NPCS.NPC->client->NPC_class == CLASS_TAVION )
3293 {
3294 TIMER_Set( NPCS.NPC, "parryTime", Q_irand( parryReCalcTime/2, parryReCalcTime*1.5 ) );
3295 }
3296 else if ( NPCS.NPCInfo->rank >= RANK_LT_JG )
3297 {//fencers and higher hold a parry less
3298 TIMER_Set( NPCS.NPC, "parryTime", parryReCalcTime );
3299 }
3300 else
3301 {//others hold it longer
3302 TIMER_Set( NPCS.NPC, "parryTime", Q_irand( 1, 2 )*parryReCalcTime );
3303 }
3304 }
3305 }
3306 else
3307 {
3308 int dodgeTime = NPCS.NPC->client->ps.torsoTimer;
3309 if ( NPCS.NPCInfo->rank > RANK_LT_COMM && NPCS.NPC->client->NPC_class != CLASS_DESANN )
3310 {//higher-level guys can dodge faster
3311 dodgeTime -= 200;
3312 }
3313 TIMER_Set( NPCS.NPC, "parryReCalcTime", dodgeTime );
3314 TIMER_Set( NPCS.NPC, "parryTime", dodgeTime );
3315 }
3316 return qtrue;
3317 }
3318 /*
3319 -------------------------
3320 Jedi_EvasionSaber
3321
3322 defend if other is using saber and attacking me!
3323 -------------------------
3324 */
Jedi_EvasionSaber(vec3_t enemy_movedir,float enemy_dist,vec3_t enemy_dir)3325 static void Jedi_EvasionSaber( vec3_t enemy_movedir, float enemy_dist, vec3_t enemy_dir )
3326 {
3327 vec3_t dirEnemy2Me;
3328 int evasionChance = 30;//only step aside 30% if he's moving at me but not attacking
3329 qboolean enemy_attacking = qfalse;
3330 qboolean throwing_saber = qfalse;
3331 qboolean shooting_lightning = qfalse;
3332
3333 if ( !NPCS.NPC->enemy->client )
3334 {
3335 return;
3336 }
3337 else if ( NPCS.NPC->enemy->client
3338 && NPCS.NPC->enemy->s.weapon == WP_SABER
3339 && NPCS.NPC->enemy->client->ps.saberLockTime > level.time )
3340 {//don't try to block/evade an enemy who is in a saberLock
3341 return;
3342 }
3343 else if ( NPCS.NPC->client->ps.saberEventFlags&SEF_LOCK_WON && NPCS.NPC->enemy->painDebounceTime > level.time )
3344 {//pressing the advantage of winning a saber lock
3345 return;
3346 }
3347
3348 if ( NPCS.NPC->enemy->client->ps.saberInFlight && !TIMER_Done( NPCS.NPC, "taunting" ) )
3349 {//if he's throwing his saber, stop taunting
3350 TIMER_Set( NPCS.NPC, "taunting", -level.time );
3351 if ( !NPCS.NPC->client->ps.saberInFlight )
3352 {
3353 WP_ActivateSaber(NPCS.NPC);
3354 }
3355 }
3356
3357 if ( TIMER_Done( NPCS.NPC, "parryTime" ) )
3358 {
3359 if ( NPCS.NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE &&
3360 NPCS.NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
3361 {//wasn't blocked myself
3362 NPCS.NPC->client->ps.saberBlocked = BLOCKED_NONE;
3363 }
3364 }
3365
3366 if ( NPCS.NPC->enemy->client->ps.weaponTime && NPCS.NPC->enemy->client->ps.weaponstate == WEAPON_FIRING )
3367 {
3368 if ( !NPCS.NPC->client->ps.saberInFlight && Jedi_SaberBlock(0, 0) )
3369 {
3370 return;
3371 }
3372 }
3373
3374 VectorSubtract( NPCS.NPC->r.currentOrigin, NPCS.NPC->enemy->r.currentOrigin, dirEnemy2Me );
3375 VectorNormalize( dirEnemy2Me );
3376
3377 if ( NPCS.NPC->enemy->client->ps.weaponTime && NPCS.NPC->enemy->client->ps.weaponstate == WEAPON_FIRING )
3378 {//enemy is attacking
3379 enemy_attacking = qtrue;
3380 evasionChance = 90;
3381 }
3382
3383 if ( (NPCS.NPC->enemy->client->ps.fd.forcePowersActive&(1<<FP_LIGHTNING) ) )
3384 {//enemy is shooting lightning
3385 enemy_attacking = qtrue;
3386 shooting_lightning = qtrue;
3387 evasionChance = 50;
3388 }
3389
3390 if ( NPCS.NPC->enemy->client->ps.saberInFlight && NPCS.NPC->enemy->client->ps.saberEntityNum != ENTITYNUM_NONE && NPCS.NPC->enemy->client->ps.saberEntityState != SES_RETURNING )
3391 {//enemy is shooting lightning
3392 enemy_attacking = qtrue;
3393 throwing_saber = qtrue;
3394 }
3395
3396 //FIXME: this needs to take skill and rank(reborn type) into account much more
3397 if ( Q_irand( 0, 100 ) < evasionChance )
3398 {//check to see if he's coming at me
3399 float facingAmt;
3400 if ( VectorCompare( enemy_movedir, vec3_origin ) || shooting_lightning || throwing_saber )
3401 {//he's not moving (or he's using a ranged attack), see if he's facing me
3402 vec3_t enemy_fwd;
3403 AngleVectors( NPCS.NPC->enemy->client->ps.viewangles, enemy_fwd, NULL, NULL );
3404 facingAmt = DotProduct( enemy_fwd, dirEnemy2Me );
3405 }
3406 else
3407 {//he's moving
3408 facingAmt = DotProduct( enemy_movedir, dirEnemy2Me );
3409 }
3410
3411 if ( flrand( 0.25, 1 ) < facingAmt )
3412 {//coming at/facing me!
3413 int whichDefense = 0;
3414 if ( NPCS.NPC->client->ps.weaponTime || NPCS.NPC->client->ps.saberInFlight || NPCS.NPC->client->NPC_class == CLASS_BOBAFETT )
3415 {//I'm attacking or recovering from a parry, can only try to strafe/jump right now
3416 if ( Q_irand( 0, 10 ) < NPCS.NPCInfo->stats.aggression )
3417 {
3418 return;
3419 }
3420 whichDefense = 100;
3421 }
3422 else
3423 {
3424 if ( shooting_lightning )
3425 {//check for lightning attack
3426 //only valid defense is strafe and/or jump
3427 whichDefense = 100;
3428 }
3429 else if ( throwing_saber )
3430 {//he's thrown his saber! See if it's coming at me
3431 float saberDist;
3432 vec3_t saberDir2Me;
3433 vec3_t saberMoveDir;
3434 gentity_t *saber = &g_entities[NPCS.NPC->enemy->client->ps.saberEntityNum];
3435 VectorSubtract( NPCS.NPC->r.currentOrigin, saber->r.currentOrigin, saberDir2Me );
3436 saberDist = VectorNormalize( saberDir2Me );
3437 VectorCopy( saber->s.pos.trDelta, saberMoveDir );
3438 VectorNormalize( saberMoveDir );
3439 if ( !Q_irand( 0, 3 ) )
3440 {
3441 //Com_Printf( "(%d) raise agg - enemy threw saber\n", level.time );
3442 Jedi_Aggression( NPCS.NPC, 1 );
3443 }
3444 if ( DotProduct( saberMoveDir, saberDir2Me ) > 0.5 )
3445 {//it's heading towards me
3446 if ( saberDist < 100 )
3447 {//it's close
3448 whichDefense = Q_irand( 3, 6 );
3449 }
3450 else if ( saberDist < 200 )
3451 {//got some time, yet, try pushing
3452 whichDefense = Q_irand( 0, 8 );
3453 }
3454 }
3455 }
3456 if ( whichDefense )
3457 {//already chose one
3458 }
3459 else if ( enemy_dist > 80 || !enemy_attacking )
3460 {//he's pretty far, or not swinging, just strafe
3461 if ( VectorCompare( enemy_movedir, vec3_origin ) )
3462 {//if he's not moving, not swinging and far enough away, no evasion necc.
3463 return;
3464 }
3465 if ( Q_irand( 0, 10 ) < NPCS.NPCInfo->stats.aggression )
3466 {
3467 return;
3468 }
3469 whichDefense = 100;
3470 }
3471 else
3472 {//he's getting close and swinging at me
3473 vec3_t fwd;
3474 //see if I'm facing him
3475 AngleVectors( NPCS.NPC->client->ps.viewangles, fwd, NULL, NULL );
3476 if ( DotProduct( enemy_dir, fwd ) < 0.5 )
3477 {//I'm not really facing him, best option is to strafe
3478 whichDefense = Q_irand( 5, 16 );
3479 }
3480 else if ( enemy_dist < 56 )
3481 {//he's very close, maybe we should be more inclined to block or throw
3482 whichDefense = Q_irand( NPCS.NPCInfo->stats.aggression, 12 );
3483 }
3484 else
3485 {
3486 whichDefense = Q_irand( 2, 16 );
3487 }
3488 }
3489 }
3490
3491 if ( whichDefense >= 4 && whichDefense <= 12 )
3492 {//would try to block
3493 if ( NPCS.NPC->client->ps.saberInFlight )
3494 {//can't, saber in not in hand, so fall back to strafe/jump
3495 whichDefense = 100;
3496 }
3497 }
3498
3499 switch( whichDefense )
3500 {
3501 case 0:
3502 case 1:
3503 case 2:
3504 case 3:
3505 //use jedi force push?
3506 //FIXME: try to do this if health low or enemy back to a cliff?
3507 if ( (NPCS.NPCInfo->rank == RANK_ENSIGN || NPCS.NPCInfo->rank > RANK_LT_JG) && TIMER_Done( NPCS.NPC, "parryTime" ) )
3508 {//FIXME: check forcePushRadius[NPC->client->ps.fd.forcePowerLevel[FP_PUSH]]
3509 ForceThrow( NPCS.NPC, qfalse );
3510 }
3511 break;
3512 case 4:
3513 case 5:
3514 case 6:
3515 case 7:
3516 case 8:
3517 case 9:
3518 case 10:
3519 case 11:
3520 case 12:
3521 //try to parry the blow
3522 //Com_Printf( "blocking\n" );
3523 Jedi_SaberBlock(0, 0);
3524 break;
3525 default:
3526 //Evade!
3527 //start a strafe left/right if not already
3528 if ( !Q_irand( 0, 5 ) || !Jedi_Strafe( 300, 1000, 0, 1000, qfalse ) )
3529 {//certain chance they will pick an alternative evasion
3530 //if couldn't strafe, try a different kind of evasion...
3531 if ( shooting_lightning || throwing_saber || enemy_dist < 80 )
3532 {
3533 //FIXME: force-jump+forward - jump over the guy!
3534 if ( shooting_lightning || (!Q_irand( 0, 2 ) && NPCS.NPCInfo->stats.aggression < 4 && TIMER_Done( NPCS.NPC, "parryTime" ) ) )
3535 {
3536 if ( (NPCS.NPCInfo->rank == RANK_ENSIGN || NPCS.NPCInfo->rank > RANK_LT_JG) && !shooting_lightning && Q_irand( 0, 2 ) )
3537 {//FIXME: check forcePushRadius[NPC->client->ps.fd.forcePowerLevel[FP_PUSH]]
3538 ForceThrow( NPCS.NPC, qfalse );
3539 }
3540 else if ( (NPCS.NPCInfo->rank==RANK_CREWMAN||NPCS.NPCInfo->rank>RANK_LT_JG)
3541 && !(NPCS.NPCInfo->scriptFlags&SCF_NO_ACROBATICS)
3542 && NPCS.NPC->client->ps.fd.forceRageRecoveryTime < level.time
3543 && !(NPCS.NPC->client->ps.fd.forcePowersActive&(1<<FP_RAGE))
3544 && !PM_InKnockDown( &NPCS.NPC->client->ps ) )
3545 {//FIXME: make this a function call?
3546 //FIXME: check for clearance, safety of landing spot?
3547 NPCS.NPC->client->ps.fd.forceJumpCharge = 480;
3548 //Don't jump again for another 2 to 5 seconds
3549 TIMER_Set( NPCS.NPC, "jumpChaseDebounce", Q_irand( 2000, 5000 ) );
3550 if ( Q_irand( 0, 2 ) )
3551 {
3552 NPCS.ucmd.forwardmove = 127;
3553 VectorClear( NPCS.NPC->client->ps.moveDir );
3554 }
3555 else
3556 {
3557 NPCS.ucmd.forwardmove = -127;
3558 VectorClear( NPCS.NPC->client->ps.moveDir );
3559 }
3560 //FIXME: if this jump is cleared, we can't block... so pick a random lower block?
3561 if ( Q_irand( 0, 1 ) )//FIXME: make intelligent
3562 {
3563 NPCS.NPC->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
3564 }
3565 else
3566 {
3567 NPCS.NPC->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
3568 }
3569 }
3570 }
3571 else if ( enemy_attacking )
3572 {
3573 Jedi_SaberBlock(0, 0);
3574 }
3575 }
3576 }
3577 else
3578 {//strafed
3579 if ( d_JediAI.integer )
3580 {
3581 Com_Printf( "def strafe\n" );
3582 }
3583 if ( !(NPCS.NPCInfo->scriptFlags&SCF_NO_ACROBATICS)
3584 && NPCS.NPC->client->ps.fd.forceRageRecoveryTime < level.time
3585 && !(NPCS.NPC->client->ps.fd.forcePowersActive&(1<<FP_RAGE))
3586 && (NPCS.NPCInfo->rank == RANK_CREWMAN || NPCS.NPCInfo->rank > RANK_LT_JG )
3587 && !PM_InKnockDown( &NPCS.NPC->client->ps )
3588 && !Q_irand( 0, 5 ) )
3589 {//FIXME: make this a function call?
3590 //FIXME: check for clearance, safety of landing spot?
3591 if ( NPCS.NPC->client->NPC_class == CLASS_BOBAFETT )
3592 {
3593 NPCS.NPC->client->ps.fd.forceJumpCharge = 280;//FIXME: calc this intelligently?
3594 }
3595 else
3596 {
3597 NPCS.NPC->client->ps.fd.forceJumpCharge = 320;
3598 }
3599 //Don't jump again for another 2 to 5 seconds
3600 TIMER_Set( NPCS.NPC, "jumpChaseDebounce", Q_irand( 2000, 5000 ) );
3601 }
3602 }
3603 break;
3604 }
3605
3606 //turn off slow walking no matter what
3607 TIMER_Set( NPCS.NPC, "walking", -level.time );
3608 TIMER_Set( NPCS.NPC, "taunting", -level.time );
3609 }
3610 }
3611 }
3612 /*
3613 -------------------------
3614 Jedi_Flee
3615 -------------------------
3616 */
3617 /*
3618
3619 static qboolean Jedi_Flee( void )
3620 {
3621 return qfalse;
3622 }
3623 */
3624
3625
3626 /*
3627 ==========================================================================================
3628 INTERNAL AI ROUTINES
3629 ==========================================================================================
3630 */
Jedi_FindEnemyInCone(gentity_t * self,gentity_t * fallback,float minDot)3631 gentity_t *Jedi_FindEnemyInCone( gentity_t *self, gentity_t *fallback, float minDot )
3632 {
3633 vec3_t forward, mins, maxs, dir;
3634 float dist, bestDist = Q3_INFINITE;
3635 gentity_t *enemy = fallback;
3636 gentity_t *check = NULL;
3637 int entityList[MAX_GENTITIES];
3638 int e, numListedEntities;
3639 trace_t tr;
3640
3641 if ( !self->client )
3642 {
3643 return enemy;
3644 }
3645
3646 AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
3647
3648 for ( e = 0 ; e < 3 ; e++ )
3649 {
3650 mins[e] = self->r.currentOrigin[e] - 1024;
3651 maxs[e] = self->r.currentOrigin[e] + 1024;
3652 }
3653 numListedEntities = trap->EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
3654
3655 for ( e = 0 ; e < numListedEntities ; e++ )
3656 {
3657 check = &g_entities[entityList[e]];
3658 if ( check == self )
3659 {//me
3660 continue;
3661 }
3662 if ( !(check->inuse) )
3663 {//freed
3664 continue;
3665 }
3666 if ( !check->client )
3667 {//not a client - FIXME: what about turrets?
3668 continue;
3669 }
3670 if ( check->client->playerTeam != self->client->enemyTeam )
3671 {//not an enemy - FIXME: what about turrets?
3672 continue;
3673 }
3674 if ( check->health <= 0 )
3675 {//dead
3676 continue;
3677 }
3678
3679 if ( !trap->InPVS( check->r.currentOrigin, self->r.currentOrigin ) )
3680 {//can't potentially see them
3681 continue;
3682 }
3683
3684 VectorSubtract( check->r.currentOrigin, self->r.currentOrigin, dir );
3685 dist = VectorNormalize( dir );
3686
3687 if ( DotProduct( dir, forward ) < minDot )
3688 {//not in front
3689 continue;
3690 }
3691
3692 //really should have a clear LOS to this thing...
3693 trap->Trace( &tr, self->r.currentOrigin, vec3_origin, vec3_origin, check->r.currentOrigin, self->s.number, MASK_SHOT, qfalse, 0, 0 );
3694 if ( tr.fraction < 1.0f && tr.entityNum != check->s.number )
3695 {//must have clear shot
3696 continue;
3697 }
3698
3699 if ( dist < bestDist )
3700 {//closer than our last best one
3701 dist = bestDist;
3702 enemy = check;
3703 }
3704 }
3705 return enemy;
3706 }
3707
Jedi_SetEnemyInfo(vec3_t enemy_dest,vec3_t enemy_dir,float * enemy_dist,vec3_t enemy_movedir,float * enemy_movespeed,int prediction)3708 static void Jedi_SetEnemyInfo( vec3_t enemy_dest, vec3_t enemy_dir, float *enemy_dist, vec3_t enemy_movedir, float *enemy_movespeed, int prediction )
3709 {
3710 if ( !NPCS.NPC || !NPCS.NPC->enemy )
3711 {//no valid enemy
3712 return;
3713 }
3714 if ( !NPCS.NPC->enemy->client )
3715 {
3716 VectorClear( enemy_movedir );
3717 *enemy_movespeed = 0;
3718 VectorCopy( NPCS.NPC->enemy->r.currentOrigin, enemy_dest );
3719 enemy_dest[2] += NPCS.NPC->enemy->r.mins[2] + 24;//get it's origin to a height I can work with
3720 VectorSubtract( enemy_dest, NPCS.NPC->r.currentOrigin, enemy_dir );
3721 //FIXME: enemy_dist calc needs to include all blade lengths, and include distance from hand to start of blade....
3722 *enemy_dist = VectorNormalize( enemy_dir );// - (NPC->client->ps.saberLengthMax + NPC->r.maxs[0]*1.5 + 16);
3723 }
3724 else
3725 {//see where enemy is headed
3726 VectorCopy( NPCS.NPC->enemy->client->ps.velocity, enemy_movedir );
3727 *enemy_movespeed = VectorNormalize( enemy_movedir );
3728 //figure out where he'll be, say, 3 frames from now
3729 VectorMA( NPCS.NPC->enemy->r.currentOrigin, *enemy_movespeed * 0.001 * prediction, enemy_movedir, enemy_dest );
3730 //figure out what dir the enemy's estimated position is from me and how far from the tip of my saber he is
3731 VectorSubtract( enemy_dest, NPCS.NPC->r.currentOrigin, enemy_dir );//NPC->client->renderInfo.muzzlePoint
3732 //FIXME: enemy_dist calc needs to include all blade lengths, and include distance from hand to start of blade....
3733 *enemy_dist = VectorNormalize( enemy_dir ) - (NPCS.NPC->client->saber[0].blade[0].lengthMax + NPCS.NPC->r.maxs[0]*1.5 + 16); //just use the blade 0 len I guess
3734 //FIXME: keep a group of enemies around me and use that info to make decisions...
3735 // For instance, if there are multiple enemies, evade more, push them away
3736 // and use medium attacks. If enemies are using blasters, switch to fast.
3737 // If one jedi enemy, use strong attacks. Use grip when fighting one or
3738 // two enemies, use lightning spread when fighting multiple enemies, etc.
3739 // Also, when kill one, check rest of group instead of walking up to victim.
3740 }
3741 }
3742
3743 extern float WP_SpeedOfMissileForWeapon( int wp, qboolean alt_fire );
Jedi_FaceEnemy(qboolean doPitch)3744 static void Jedi_FaceEnemy( qboolean doPitch )
3745 {
3746 vec3_t enemy_eyes, eyes, angles;
3747
3748 if ( NPCS.NPC == NULL )
3749 return;
3750
3751 if ( NPCS.NPC->enemy == NULL )
3752 return;
3753
3754 if ( NPCS.NPC->client->ps.fd.forcePowersActive & (1<<FP_GRIP) &&
3755 NPCS.NPC->client->ps.fd.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
3756 {//don't update?
3757 NPCS.NPCInfo->desiredPitch = NPCS.NPC->client->ps.viewangles[PITCH];
3758 NPCS.NPCInfo->desiredYaw = NPCS.NPC->client->ps.viewangles[YAW];
3759 return;
3760 }
3761 CalcEntitySpot( NPCS.NPC, SPOT_HEAD, eyes );
3762
3763 CalcEntitySpot( NPCS.NPC->enemy, SPOT_HEAD, enemy_eyes );
3764
3765 if ( NPCS.NPC->client->NPC_class == CLASS_BOBAFETT
3766 && TIMER_Done( NPCS.NPC, "flameTime" )
3767 && NPCS.NPC->s.weapon != WP_NONE
3768 && NPCS.NPC->s.weapon != WP_DISRUPTOR
3769 && (NPCS.NPC->s.weapon != WP_ROCKET_LAUNCHER||!(NPCS.NPCInfo->scriptFlags&SCF_ALT_FIRE))
3770 && NPCS.NPC->s.weapon != WP_THERMAL
3771 && NPCS.NPC->s.weapon != WP_TRIP_MINE
3772 && NPCS.NPC->s.weapon != WP_DET_PACK
3773 && NPCS.NPC->s.weapon != WP_STUN_BATON
3774 /*&& NPC->s.weapon != WP_MELEE*/ )
3775 {//boba leads his enemy
3776 if ( NPCS.NPC->health < NPCS.NPC->client->pers.maxHealth*0.5f )
3777 {//lead
3778 float missileSpeed = WP_SpeedOfMissileForWeapon( NPCS.NPC->s.weapon, ((qboolean)(NPCS.NPCInfo->scriptFlags&SCF_ALT_FIRE)) );
3779 if ( missileSpeed )
3780 {
3781 float eDist = Distance( eyes, enemy_eyes );
3782 eDist /= missileSpeed;//How many seconds it will take to get to the enemy
3783 VectorMA( enemy_eyes, eDist*flrand(0.95f,1.25f), NPCS.NPC->enemy->client->ps.velocity, enemy_eyes );
3784 }
3785 }
3786 }
3787
3788 //Find the desired angles
3789 if ( !NPCS.NPC->client->ps.saberInFlight
3790 && (NPCS.NPC->client->ps.legsAnim == BOTH_A2_STABBACK1
3791 || NPCS.NPC->client->ps.legsAnim == BOTH_CROUCHATTACKBACK1
3792 || NPCS.NPC->client->ps.legsAnim == BOTH_ATTACK_BACK)
3793 )
3794 {//point *away*
3795 GetAnglesForDirection( enemy_eyes, eyes, angles );
3796 }
3797 else
3798 {//point towards him
3799 GetAnglesForDirection( eyes, enemy_eyes, angles );
3800 }
3801
3802 NPCS.NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] );
3803 /*
3804 if ( NPC->client->ps.saberBlocked == BLOCKED_UPPER_LEFT )
3805 {//temp hack- to make up for poor coverage on left side
3806 NPCInfo->desiredYaw += 30;
3807 }
3808 */
3809
3810 if ( doPitch )
3811 {
3812 NPCS.NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] );
3813 if ( NPCS.NPC->client->ps.saberInFlight )
3814 {//tilt down a little
3815 NPCS.NPCInfo->desiredPitch += 10;
3816 }
3817 }
3818 //FIXME: else desiredPitch = 0? Or keep previous?
3819 }
3820
Jedi_DebounceDirectionChanges(void)3821 static void Jedi_DebounceDirectionChanges( void )
3822 {
3823 //FIXME: check these before making fwd/back & right/left decisions?
3824 //Time-debounce changes in forward/back dir
3825 if ( NPCS.ucmd.forwardmove > 0 )
3826 {
3827 if ( !TIMER_Done( NPCS.NPC, "moveback" ) || !TIMER_Done( NPCS.NPC, "movenone" ) )
3828 {
3829 NPCS.ucmd.forwardmove = 0;
3830 //now we have to normalize the total movement again
3831 if ( NPCS.ucmd.rightmove > 0 )
3832 {
3833 NPCS.ucmd.rightmove = 127;
3834 }
3835 else if ( NPCS.ucmd.rightmove < 0 )
3836 {
3837 NPCS.ucmd.rightmove = -127;
3838 }
3839 VectorClear( NPCS.NPC->client->ps.moveDir );
3840 TIMER_Set( NPCS.NPC, "moveback", -level.time );
3841 if ( TIMER_Done( NPCS.NPC, "movenone" ) )
3842 {
3843 TIMER_Set( NPCS.NPC, "movenone", Q_irand( 1000, 2000 ) );
3844 }
3845 }
3846 else if ( TIMER_Done( NPCS.NPC, "moveforward" ) )
3847 {//FIXME: should be if it's zero?
3848 TIMER_Set( NPCS.NPC, "moveforward", Q_irand( 500, 2000 ) );
3849 }
3850 }
3851 else if ( NPCS.ucmd.forwardmove < 0 )
3852 {
3853 if ( !TIMER_Done( NPCS.NPC, "moveforward" ) || !TIMER_Done( NPCS.NPC, "movenone" ) )
3854 {
3855 NPCS.ucmd.forwardmove = 0;
3856 //now we have to normalize the total movement again
3857 if ( NPCS.ucmd.rightmove > 0 )
3858 {
3859 NPCS.ucmd.rightmove = 127;
3860 }
3861 else if ( NPCS.ucmd.rightmove < 0 )
3862 {
3863 NPCS.ucmd.rightmove = -127;
3864 }
3865 VectorClear( NPCS.NPC->client->ps.moveDir );
3866 TIMER_Set( NPCS.NPC, "moveforward", -level.time );
3867 if ( TIMER_Done( NPCS.NPC, "movenone" ) )
3868 {
3869 TIMER_Set( NPCS.NPC, "movenone", Q_irand( 1000, 2000 ) );
3870 }
3871 }
3872 else if ( TIMER_Done( NPCS.NPC, "moveback" ) )
3873 {//FIXME: should be if it's zero?
3874 TIMER_Set( NPCS.NPC, "moveback", Q_irand( 250, 1000 ) );
3875 }
3876 }
3877 else if ( !TIMER_Done( NPCS.NPC, "moveforward" ) )
3878 {//NOTE: edge checking should stop me if this is bad... but what if it sends us colliding into the enemy?
3879 NPCS.ucmd.forwardmove = 127;
3880 VectorClear( NPCS.NPC->client->ps.moveDir );
3881 }
3882 else if ( !TIMER_Done( NPCS.NPC, "moveback" ) )
3883 {//NOTE: edge checking should stop me if this is bad...
3884 NPCS.ucmd.forwardmove = -127;
3885 VectorClear( NPCS.NPC->client->ps.moveDir );
3886 }
3887 //Time-debounce changes in right/left dir
3888 if ( NPCS.ucmd.rightmove > 0 )
3889 {
3890 if ( !TIMER_Done( NPCS.NPC, "moveleft" ) || !TIMER_Done( NPCS.NPC, "movecenter" ) )
3891 {
3892 NPCS.ucmd.rightmove = 0;
3893 //now we have to normalize the total movement again
3894 if ( NPCS.ucmd.forwardmove > 0 )
3895 {
3896 NPCS.ucmd.forwardmove = 127;
3897 }
3898 else if ( NPCS.ucmd.forwardmove < 0 )
3899 {
3900 NPCS.ucmd.forwardmove = -127;
3901 }
3902 VectorClear( NPCS.NPC->client->ps.moveDir );
3903 TIMER_Set( NPCS.NPC, "moveleft", -level.time );
3904 if ( TIMER_Done( NPCS.NPC, "movecenter" ) )
3905 {
3906 TIMER_Set( NPCS.NPC, "movecenter", Q_irand( 1000, 2000 ) );
3907 }
3908 }
3909 else if ( TIMER_Done( NPCS.NPC, "moveright" ) )
3910 {//FIXME: should be if it's zero?
3911 TIMER_Set( NPCS.NPC, "moveright", Q_irand( 250, 1500 ) );
3912 }
3913 }
3914 else if ( NPCS.ucmd.rightmove < 0 )
3915 {
3916 if ( !TIMER_Done( NPCS.NPC, "moveright" ) || !TIMER_Done( NPCS.NPC, "movecenter" ) )
3917 {
3918 NPCS.ucmd.rightmove = 0;
3919 //now we have to normalize the total movement again
3920 if ( NPCS.ucmd.forwardmove > 0 )
3921 {
3922 NPCS.ucmd.forwardmove = 127;
3923 }
3924 else if ( NPCS.ucmd.forwardmove < 0 )
3925 {
3926 NPCS.ucmd.forwardmove = -127;
3927 }
3928 VectorClear( NPCS.NPC->client->ps.moveDir );
3929 TIMER_Set( NPCS.NPC, "moveright", -level.time );
3930 if ( TIMER_Done( NPCS.NPC, "movecenter" ) )
3931 {
3932 TIMER_Set( NPCS.NPC, "movecenter", Q_irand( 1000, 2000 ) );
3933 }
3934 }
3935 else if ( TIMER_Done( NPCS.NPC, "moveleft" ) )
3936 {//FIXME: should be if it's zero?
3937 TIMER_Set( NPCS.NPC, "moveleft", Q_irand( 250, 1500 ) );
3938 }
3939 }
3940 else if ( !TIMER_Done( NPCS.NPC, "moveright" ) )
3941 {//NOTE: edge checking should stop me if this is bad...
3942 NPCS.ucmd.rightmove = 127;
3943 VectorClear( NPCS.NPC->client->ps.moveDir );
3944 }
3945 else if ( !TIMER_Done( NPCS.NPC, "moveleft" ) )
3946 {//NOTE: edge checking should stop me if this is bad...
3947 NPCS.ucmd.rightmove = -127;
3948 VectorClear( NPCS.NPC->client->ps.moveDir );
3949 }
3950 }
3951
Jedi_TimersApply(void)3952 static void Jedi_TimersApply( void )
3953 {
3954 if ( !NPCS.ucmd.rightmove )
3955 {//only if not already strafing
3956 //FIXME: if enemy behind me and turning to face enemy, don't strafe in that direction, too
3957 if ( !TIMER_Done( NPCS.NPC, "strafeLeft" ) )
3958 {
3959 if ( NPCS.NPCInfo->desiredYaw > NPCS.NPC->client->ps.viewangles[YAW] + 60 )
3960 {//we want to turn left, don't apply the strafing
3961 }
3962 else
3963 {//go ahead and strafe left
3964 NPCS.ucmd.rightmove = -127;
3965 VectorClear( NPCS.NPC->client->ps.moveDir );
3966 }
3967 }
3968 else if ( !TIMER_Done( NPCS.NPC, "strafeRight" ) )
3969 {
3970 if ( NPCS.NPCInfo->desiredYaw < NPCS.NPC->client->ps.viewangles[YAW] - 60 )
3971 {//we want to turn right, don't apply the strafing
3972 }
3973 else
3974 {//go ahead and strafe left
3975 NPCS.ucmd.rightmove = 127;
3976 VectorClear( NPCS.NPC->client->ps.moveDir );
3977 }
3978 }
3979 }
3980
3981 Jedi_DebounceDirectionChanges();
3982
3983 //use careful anim/slower movement if not already moving
3984 if ( !NPCS.ucmd.forwardmove && !TIMER_Done( NPCS.NPC, "walking" ) )
3985 {
3986 NPCS.ucmd.buttons |= (BUTTON_WALKING);
3987 }
3988
3989 if ( !TIMER_Done( NPCS.NPC, "taunting" ) )
3990 {
3991 NPCS.ucmd.buttons |= (BUTTON_WALKING);
3992 }
3993
3994 if ( !TIMER_Done( NPCS.NPC, "gripping" ) )
3995 {//FIXME: what do we do if we ran out of power? NPC's can't?
3996 //FIXME: don't keep turning to face enemy or we'll end up spinning around
3997 NPCS.ucmd.buttons |= BUTTON_FORCEGRIP;
3998 }
3999
4000 if ( !TIMER_Done( NPCS.NPC, "draining" ) )
4001 {//FIXME: what do we do if we ran out of power? NPC's can't?
4002 //FIXME: don't keep turning to face enemy or we'll end up spinning around
4003 NPCS.ucmd.buttons |= BUTTON_FORCE_DRAIN;
4004 }
4005
4006 if ( !TIMER_Done( NPCS.NPC, "holdLightning" ) )
4007 {//hold down the lightning key
4008 NPCS.ucmd.buttons |= BUTTON_FORCE_LIGHTNING;
4009 }
4010 }
4011
Jedi_CombatTimersUpdate(int enemy_dist)4012 static void Jedi_CombatTimersUpdate( int enemy_dist )
4013 {
4014 if( Jedi_CultistDestroyer( NPCS.NPC ) )
4015 {
4016 Jedi_Aggression( NPCS.NPC, 5 );
4017 return;
4018 }
4019 if ( TIMER_Done( NPCS.NPC, "roamTime" ) )
4020 {
4021 TIMER_Set( NPCS.NPC, "roamTime", Q_irand( 2000, 5000 ) );
4022 //okay, now mess with agression
4023 if ( NPCS.NPC->client->ps.fd.forcePowersActive&(1<<FP_RAGE) )
4024 {//raging
4025 Jedi_Aggression( NPCS.NPC, Q_irand( 0, 3 ) );
4026 }
4027 else if ( NPCS.NPC->client->ps.fd.forceRageRecoveryTime > level.time )
4028 {//recovering
4029 Jedi_Aggression( NPCS.NPC, Q_irand( -2, 0 ) );
4030 }
4031 if ( NPCS.NPC->enemy && NPCS.NPC->enemy->client )
4032 {
4033 switch( NPCS.NPC->enemy->client->ps.weapon )
4034 {
4035 case WP_SABER:
4036 //If enemy has a lightsaber, always close in
4037 if ( BG_SabersOff( &NPCS.NPC->enemy->client->ps ) )
4038 {//fool! Standing around unarmed, charge!
4039 //Com_Printf( "(%d) raise agg - enemy saber off\n", level.time );
4040 Jedi_Aggression( NPCS.NPC, 2 );
4041 }
4042 else
4043 {
4044 //Com_Printf( "(%d) raise agg - enemy saber\n", level.time );
4045 Jedi_Aggression( NPCS.NPC, 1 );
4046 }
4047 break;
4048 case WP_BLASTER:
4049 case WP_BRYAR_PISTOL:
4050 case WP_DISRUPTOR:
4051 case WP_BOWCASTER:
4052 case WP_REPEATER:
4053 case WP_DEMP2:
4054 case WP_FLECHETTE:
4055 case WP_ROCKET_LAUNCHER:
4056 //if he has a blaster, move in when:
4057 //They're not shooting at me
4058 if ( NPCS.NPC->enemy->attackDebounceTime < level.time )
4059 {//does this apply to players?
4060 //Com_Printf( "(%d) raise agg - enemy not shooting ranged weap\n", level.time );
4061 Jedi_Aggression( NPCS.NPC, 1 );
4062 }
4063 //He's closer than a dist that gives us time to deflect
4064 if ( enemy_dist < 256 )
4065 {
4066 //Com_Printf( "(%d) raise agg - enemy ranged weap- too close\n", level.time );
4067 Jedi_Aggression( NPCS.NPC, 1 );
4068 }
4069 break;
4070 default:
4071 break;
4072 }
4073 }
4074 }
4075
4076 if ( TIMER_Done( NPCS.NPC, "noStrafe" ) && TIMER_Done( NPCS.NPC, "strafeLeft" ) && TIMER_Done( NPCS.NPC, "strafeRight" ) )
4077 {
4078 //FIXME: Maybe more likely to do this if aggression higher? Or some other stat?
4079 if ( !Q_irand( 0, 4 ) )
4080 {//start a strafe
4081 if ( Jedi_Strafe( 1000, 3000, 0, 4000, qtrue ) )
4082 {
4083 if ( d_JediAI.integer )
4084 {
4085 Com_Printf( "off strafe\n" );
4086 }
4087 }
4088 }
4089 else
4090 {//postpone any strafing for a while
4091 TIMER_Set( NPCS.NPC, "noStrafe", Q_irand( 1000, 3000 ) );
4092 }
4093 }
4094
4095 if ( NPCS.NPC->client->ps.saberEventFlags )
4096 {//some kind of saber combat event is still pending
4097 int newFlags = NPCS.NPC->client->ps.saberEventFlags;
4098 if ( NPCS.NPC->client->ps.saberEventFlags&SEF_PARRIED )
4099 {//parried
4100 TIMER_Set( NPCS.NPC, "parryTime", -1 );
4101 /*
4102 if ( NPCInfo->rank >= RANK_LT_JG )
4103 {
4104 NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 100;
4105 }
4106 else
4107 {
4108 NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500;
4109 }
4110 */
4111 if ( NPCS.NPC->enemy && PM_SaberInKnockaway( NPCS.NPC->enemy->client->ps.saberMove ) )
4112 {//advance!
4113 Jedi_Aggression( NPCS.NPC, 1 );//get closer
4114 Jedi_AdjustSaberAnimLevel( NPCS.NPC, (NPCS.NPC->client->ps.fd.saberAnimLevel-1) );//use a faster attack
4115 }
4116 else
4117 {
4118 if ( !Q_irand( 0, 1 ) )//FIXME: dependant on rank/diff?
4119 {
4120 //Com_Printf( "(%d) drop agg - we parried\n", level.time );
4121 Jedi_Aggression( NPCS.NPC, -1 );
4122 }
4123 if ( !Q_irand( 0, 1 ) )
4124 {
4125 Jedi_AdjustSaberAnimLevel( NPCS.NPC, (NPCS.NPC->client->ps.fd.saberAnimLevel-1) );
4126 }
4127 }
4128 if ( d_JediAI.integer )
4129 {
4130 Com_Printf( "(%d) PARRY: agg %d, no parry until %d\n", level.time, NPCS.NPCInfo->stats.aggression, level.time + 100 );
4131 }
4132 newFlags &= ~SEF_PARRIED;
4133 }
4134 if ( !NPCS.NPC->client->ps.weaponTime && (NPCS.NPC->client->ps.saberEventFlags&SEF_HITENEMY) )//hit enemy
4135 {//we hit our enemy last time we swung, drop our aggression
4136 if ( !Q_irand( 0, 1 ) )//FIXME: dependant on rank/diff?
4137 {
4138 //Com_Printf( "(%d) drop agg - we hit enemy\n", level.time );
4139 Jedi_Aggression( NPCS.NPC, -1 );
4140 if ( d_JediAI.integer )
4141 {
4142 Com_Printf( "(%d) HIT: agg %d\n", level.time, NPCS.NPCInfo->stats.aggression );
4143 }
4144 if ( !Q_irand( 0, 3 )
4145 && NPCS.NPCInfo->blockedSpeechDebounceTime < level.time
4146 && jediSpeechDebounceTime[NPCS.NPC->client->playerTeam] < level.time
4147 && NPCS.NPC->painDebounceTime < level.time - 1000 )
4148 {
4149 G_AddVoiceEvent( NPCS.NPC, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 3000 );
4150 jediSpeechDebounceTime[NPCS.NPC->client->playerTeam] = NPCS.NPCInfo->blockedSpeechDebounceTime = level.time + 3000;
4151 }
4152 }
4153 if ( !Q_irand( 0, 2 ) )
4154 {
4155 Jedi_AdjustSaberAnimLevel( NPCS.NPC, (NPCS.NPC->client->ps.fd.saberAnimLevel+1) );
4156 }
4157 newFlags &= ~SEF_HITENEMY;
4158 }
4159 if ( (NPCS.NPC->client->ps.saberEventFlags&SEF_BLOCKED) )
4160 {//was blocked whilst attacking
4161 if ( PM_SaberInBrokenParry( NPCS.NPC->client->ps.saberMove )
4162 || NPCS.NPC->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
4163 {
4164 //Com_Printf( "(%d) drop agg - we were knock-blocked\n", level.time );
4165 if ( NPCS.NPC->client->ps.saberInFlight )
4166 {//lost our saber, too!!!
4167 Jedi_Aggression( NPCS.NPC, -5 );//really really really should back off!!!
4168 }
4169 else
4170 {
4171 Jedi_Aggression( NPCS.NPC, -2 );//really should back off!
4172 }
4173 Jedi_AdjustSaberAnimLevel( NPCS.NPC, (NPCS.NPC->client->ps.fd.saberAnimLevel+1) );//use a stronger attack
4174 if ( d_JediAI.integer )
4175 {
4176 Com_Printf( "(%d) KNOCK-BLOCKED: agg %d\n", level.time, NPCS.NPCInfo->stats.aggression );
4177 }
4178 }
4179 else
4180 {
4181 if ( !Q_irand( 0, 2 ) )//FIXME: dependant on rank/diff?
4182 {
4183 //Com_Printf( "(%d) drop agg - we were blocked\n", level.time );
4184 Jedi_Aggression( NPCS.NPC, -1 );
4185 if ( d_JediAI.integer )
4186 {
4187 Com_Printf( "(%d) BLOCKED: agg %d\n", level.time, NPCS.NPCInfo->stats.aggression );
4188 }
4189 }
4190 if ( !Q_irand( 0, 1 ) )
4191 {
4192 Jedi_AdjustSaberAnimLevel( NPCS.NPC, (NPCS.NPC->client->ps.fd.saberAnimLevel+1) );
4193 }
4194 }
4195 newFlags &= ~SEF_BLOCKED;
4196 //FIXME: based on the type of parry the enemy is doing and my skill,
4197 // choose an attack that is likely to get around the parry?
4198 // right now that's generic in the saber animation code, auto-picks
4199 // a next anim for me, but really should be AI-controlled.
4200 }
4201 if ( NPCS.NPC->client->ps.saberEventFlags&SEF_DEFLECTED )
4202 {//deflected a shot
4203 newFlags &= ~SEF_DEFLECTED;
4204 if ( !Q_irand( 0, 3 ) )
4205 {
4206 Jedi_AdjustSaberAnimLevel( NPCS.NPC, (NPCS.NPC->client->ps.fd.saberAnimLevel-1) );
4207 }
4208 }
4209 if ( NPCS.NPC->client->ps.saberEventFlags&SEF_HITWALL )
4210 {//hit a wall
4211 newFlags &= ~SEF_HITWALL;
4212 }
4213 if ( NPCS.NPC->client->ps.saberEventFlags&SEF_HITOBJECT )
4214 {//hit some other damagable object
4215 if ( !Q_irand( 0, 3 ) )
4216 {
4217 Jedi_AdjustSaberAnimLevel( NPCS.NPC, (NPCS.NPC->client->ps.fd.saberAnimLevel-1) );
4218 }
4219 newFlags &= ~SEF_HITOBJECT;
4220 }
4221 NPCS.NPC->client->ps.saberEventFlags = newFlags;
4222 }
4223 }
4224
Jedi_CombatIdle(int enemy_dist)4225 static void Jedi_CombatIdle( int enemy_dist )
4226 {
4227 if ( !TIMER_Done( NPCS.NPC, "parryTime" ) )
4228 {
4229 return;
4230 }
4231 if ( NPCS.NPC->client->ps.saberInFlight )
4232 {//don't do this idle stuff if throwing saber
4233 return;
4234 }
4235 if ( NPCS.NPC->client->ps.fd.forcePowersActive&(1<<FP_RAGE)
4236 || NPCS.NPC->client->ps.fd.forceRageRecoveryTime > level.time )
4237 {//never taunt while raging or recovering from rage
4238 return;
4239 }
4240 //FIXME: make these distance numbers defines?
4241 if ( enemy_dist >= 64 )
4242 {//FIXME: only do this if standing still?
4243 //based on aggression, flaunt/taunt
4244 int chance = 20;
4245 if ( NPCS.NPC->client->NPC_class == CLASS_SHADOWTROOPER )
4246 {
4247 chance = 10;
4248 }
4249 //FIXME: possibly throw local objects at enemy?
4250 if ( Q_irand( 2, chance ) < NPCS.NPCInfo->stats.aggression )
4251 {
4252 if ( TIMER_Done( NPCS.NPC, "chatter" ) && NPCS.NPC->client->ps.forceHandExtend == HANDEXTEND_NONE )
4253 {//FIXME: add more taunt behaviors
4254 //FIXME: sometimes he turns it off, then turns it right back on again???
4255 if ( enemy_dist > 200
4256 && NPCS.NPC->client->NPC_class != CLASS_BOBAFETT
4257 && !NPCS.NPC->client->ps.saberHolstered
4258 && !Q_irand( 0, 5 ) )
4259 {//taunt even more, turn off the saber
4260 //FIXME: don't do this if health low?
4261 WP_DeactivateSaber( NPCS.NPC, qfalse );
4262 //Don't attack for a bit
4263 NPCS.NPCInfo->stats.aggression = 3;
4264 //FIXME: maybe start strafing?
4265 //debounce this
4266 if ( NPCS.NPC->client->playerTeam != NPCTEAM_PLAYER && !Q_irand( 0, 1 ))
4267 {
4268 //NPC->client->ps.taunting = level.time + 100;
4269 NPCS.NPC->client->ps.forceHandExtend = HANDEXTEND_JEDITAUNT;
4270 NPCS.NPC->client->ps.forceHandExtendTime = level.time + 5000;
4271
4272 TIMER_Set( NPCS.NPC, "chatter", Q_irand( 5000, 10000 ) );
4273 TIMER_Set( NPCS.NPC, "taunting", 5500 );
4274 }
4275 else
4276 {
4277 Jedi_BattleTaunt();
4278 TIMER_Set( NPCS.NPC, "taunting", Q_irand( 5000, 10000 ) );
4279 }
4280 }
4281 else if ( Jedi_BattleTaunt() )
4282 {//FIXME: pick some anims
4283 }
4284 }
4285 }
4286 }
4287 }
4288
Jedi_AttackDecide(int enemy_dist)4289 static qboolean Jedi_AttackDecide( int enemy_dist )
4290 {
4291 // Begin fixed cultist_destroyer AI
4292 if ( Jedi_CultistDestroyer( NPCS.NPC ) )
4293 { // destroyer
4294 if ( enemy_dist <= 32 )
4295 {//go boom!
4296 //float?
4297 //VectorClear( NPC->client->ps.velocity );
4298 //NPC->client->ps.gravity = 0;
4299 //NPC->svFlags |= SVF_CUSTOM_GRAVITY;
4300 //NPC->client->moveType = MT_FLYSWIM;
4301 //NPC->flags |= FL_NO_KNOCKBACK;
4302 NPCS.NPC->flags |= FL_GODMODE;
4303 NPCS.NPC->takedamage = qfalse;
4304
4305 NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, BOTH_FORCE_RAGE, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE );
4306 NPCS.NPC->client->ps.fd.forcePowersActive |= ( 1 << FP_RAGE );
4307 NPCS.NPC->painDebounceTime = NPCS.NPC->useDebounceTime = level.time + NPCS.NPC->client->ps.torsoTimer;
4308 return qtrue;
4309 }
4310 return qfalse;
4311 }
4312
4313 if ( NPCS.NPC->enemy->client
4314 && NPCS.NPC->enemy->s.weapon == WP_SABER
4315 && NPCS.NPC->enemy->client->ps.saberLockTime > level.time
4316 && NPCS.NPC->client->ps.saberLockTime < level.time )
4317 {//enemy is in a saberLock and we are not
4318 return qfalse;
4319 }
4320
4321 if ( NPCS.NPC->client->ps.saberEventFlags&SEF_LOCK_WON )
4322 {//we won a saber lock, press the advantage with an attack!
4323 int chance = 0;
4324 if ( NPCS.NPC->client->NPC_class == CLASS_DESANN || NPCS.NPC->client->NPC_class == CLASS_LUKE || !Q_stricmp("Yoda",NPCS.NPC->NPC_type) )
4325 {//desann and luke
4326 chance = 20;
4327 }
4328 else if ( NPCS.NPC->client->NPC_class == CLASS_TAVION )
4329 {//tavion
4330 chance = 10;
4331 }
4332 else if ( NPCS.NPC->client->NPC_class == CLASS_REBORN && NPCS.NPCInfo->rank == RANK_LT_JG )
4333 {//fencer
4334 chance = 5;
4335 }
4336 else
4337 {
4338 chance = NPCS.NPCInfo->rank;
4339 }
4340 if ( Q_irand( 0, 30 ) < chance )
4341 {//based on skill with some randomness
4342 NPCS.NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON;//clear this now that we are using the opportunity
4343 TIMER_Set( NPCS.NPC, "noRetreat", Q_irand( 500, 2000 ) );
4344 //FIXME: check enemy_dist?
4345 NPCS.NPC->client->ps.weaponTime = NPCS.NPCInfo->shotTime = NPCS.NPC->attackDebounceTime = 0;
4346 //NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500;
4347 NPCS.NPC->client->ps.saberBlocked = BLOCKED_NONE;
4348 WeaponThink( qtrue );
4349 return qtrue;
4350 }
4351 }
4352
4353 if ( NPCS.NPC->client->NPC_class == CLASS_TAVION ||
4354 ( NPCS.NPC->client->NPC_class == CLASS_REBORN && NPCS.NPCInfo->rank == RANK_LT_JG ) ||
4355 ( NPCS.NPC->client->NPC_class == CLASS_JEDI && NPCS.NPCInfo->rank == RANK_COMMANDER ) )
4356 {//tavion, fencers, jedi trainer are all good at following up a parry with an attack
4357 if ( ( PM_SaberInParry( NPCS.NPC->client->ps.saberMove ) || PM_SaberInKnockaway( NPCS.NPC->client->ps.saberMove ) )
4358 && NPCS.NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
4359 {//try to attack straight from a parry
4360 NPCS.NPC->client->ps.weaponTime = NPCS.NPCInfo->shotTime = NPCS.NPC->attackDebounceTime = 0;
4361 //NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500;
4362 NPCS.NPC->client->ps.saberBlocked = BLOCKED_NONE;
4363 Jedi_AdjustSaberAnimLevel( NPCS.NPC, FORCE_LEVEL_1 );//try to follow-up with a quick attack
4364 WeaponThink( qtrue );
4365 return qtrue;
4366 }
4367 }
4368
4369 //try to hit them if we can
4370 if ( enemy_dist >= 64 )
4371 {
4372 return qfalse;
4373 }
4374
4375 if ( !TIMER_Done( NPCS.NPC, "parryTime" ) )
4376 {
4377 return qfalse;
4378 }
4379
4380 if ( (NPCS.NPCInfo->scriptFlags&SCF_DONT_FIRE) )
4381 {//not allowed to attack
4382 return qfalse;
4383 }
4384
4385 if ( !(NPCS.ucmd.buttons&BUTTON_ATTACK) && !(NPCS.ucmd.buttons&BUTTON_ALT_ATTACK) )
4386 {//not already attacking
4387 //Try to attack
4388 WeaponThink( qtrue );
4389 }
4390
4391 //FIXME: Maybe try to push enemy off a ledge?
4392
4393 //close enough to step forward
4394
4395 //FIXME: an attack debounce timer other than the phaser debounce time?
4396 // or base it on aggression?
4397
4398 if ( NPCS.ucmd.buttons&BUTTON_ATTACK )
4399 {//attacking
4400 /*
4401 if ( enemy_dist > 32 && NPCInfo->stats.aggression >= 4 )
4402 {//move forward if we're too far away and we're chasing him
4403 ucmd.forwardmove = 127;
4404 }
4405 else if ( enemy_dist < 0 )
4406 {//move back if we're too close
4407 ucmd.forwardmove = -127;
4408 }
4409 */
4410 //FIXME: based on the type of parry/attack the enemy is doing and my skill,
4411 // choose an attack that is likely to get around the parry?
4412 // right now that's generic in the saber animation code, auto-picks
4413 // a next anim for me, but really should be AI-controlled.
4414 //FIXME: have this interact with/override above strafing code?
4415 if ( !NPCS.ucmd.rightmove )
4416 {//not already strafing
4417 if ( !Q_irand( 0, 3 ) )
4418 {//25% chance of doing this
4419 vec3_t right, dir2enemy;
4420
4421 AngleVectors( NPCS.NPC->r.currentAngles, NULL, right, NULL );
4422 VectorSubtract( NPCS.NPC->enemy->r.currentOrigin, NPCS.NPC->r.currentAngles, dir2enemy );
4423 if ( DotProduct( right, dir2enemy ) > 0 )
4424 {//he's to my right, strafe left
4425 NPCS.ucmd.rightmove = -127;
4426 VectorClear( NPCS.NPC->client->ps.moveDir );
4427 }
4428 else
4429 {//he's to my left, strafe right
4430 NPCS.ucmd.rightmove = 127;
4431 VectorClear( NPCS.NPC->client->ps.moveDir );
4432 }
4433 }
4434 }
4435 return qtrue;
4436 }
4437
4438 return qfalse;
4439 }
4440
4441 #define APEX_HEIGHT 200.0f
4442 #define PARA_WIDTH (sqrt(APEX_HEIGHT)+sqrt(APEX_HEIGHT))
4443 #define JUMP_SPEED 200.0f
4444
Jedi_Jump(vec3_t dest,int goalEntNum)4445 static qboolean Jedi_Jump( vec3_t dest, int goalEntNum )
4446 {//FIXME: if land on enemy, knock him down & jump off again
4447 /*
4448 if ( dest[2] - NPC->r.currentOrigin[2] < 64 && DistanceHorizontal( NPC->r.currentOrigin, dest ) > 256 )
4449 {//a pretty horizontal jump, easy to fake:
4450 vec3_t enemy_diff;
4451
4452 VectorSubtract( dest, NPC->r.currentOrigin, enemy_diff );
4453 float enemy_z_diff = enemy_diff[2];
4454 enemy_diff[2] = 0;
4455 float enemy_xy_diff = VectorNormalize( enemy_diff );
4456
4457 VectorScale( enemy_diff, enemy_xy_diff*0.8, NPC->client->ps.velocity );
4458 if ( enemy_z_diff < 64 )
4459 {
4460 NPC->client->ps.velocity[2] = enemy_xy_diff;
4461 }
4462 else
4463 {
4464 NPC->client->ps.velocity[2] = enemy_z_diff*2+enemy_xy_diff/2;
4465 }
4466 }
4467 else
4468 */
4469 if ( 1 )
4470 {
4471 float targetDist, shotSpeed = 300, travelTime, impactDist, bestImpactDist = Q3_INFINITE;//fireSpeed,
4472 vec3_t targetDir, shotVel, failCase;
4473 trace_t trace;
4474 trajectory_t tr;
4475 qboolean blocked;
4476 int elapsedTime, timeStep = 500, hitCount = 0, maxHits = 7;
4477 vec3_t lastPos, testPos, bottom;
4478
4479 while ( hitCount < maxHits )
4480 {
4481 VectorSubtract( dest, NPCS.NPC->r.currentOrigin, targetDir );
4482 targetDist = VectorNormalize( targetDir );
4483
4484 VectorScale( targetDir, shotSpeed, shotVel );
4485 travelTime = targetDist/shotSpeed;
4486 shotVel[2] += travelTime * 0.5 * NPCS.NPC->client->ps.gravity;
4487
4488 if ( !hitCount )
4489 {//save the first one as the worst case scenario
4490 VectorCopy( shotVel, failCase );
4491 }
4492
4493 if ( 1 )//tracePath )
4494 {//do a rough trace of the path
4495 blocked = qfalse;
4496
4497 VectorCopy( NPCS.NPC->r.currentOrigin, tr.trBase );
4498 VectorCopy( shotVel, tr.trDelta );
4499 tr.trType = TR_GRAVITY;
4500 tr.trTime = level.time;
4501 travelTime *= 1000.0f;
4502 VectorCopy( NPCS.NPC->r.currentOrigin, lastPos );
4503
4504 //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?
4505 for ( elapsedTime = timeStep; elapsedTime < floor(travelTime)+timeStep; elapsedTime += timeStep )
4506 {
4507 if ( (float)elapsedTime > travelTime )
4508 {//cap it
4509 elapsedTime = floor( travelTime );
4510 }
4511 BG_EvaluateTrajectory( &tr, level.time + elapsedTime, testPos );
4512 if ( testPos[2] < lastPos[2] )
4513 {//going down, ignore botclip
4514 trap->Trace( &trace, lastPos, NPCS.NPC->r.mins, NPCS.NPC->r.maxs, testPos, NPCS.NPC->s.number, NPCS.NPC->clipmask, qfalse, 0, 0 );
4515 }
4516 else
4517 {//going up, check for botclip
4518 trap->Trace( &trace, lastPos, NPCS.NPC->r.mins, NPCS.NPC->r.maxs, testPos, NPCS.NPC->s.number, NPCS.NPC->clipmask|CONTENTS_BOTCLIP, qfalse, 0, 0 );
4519 }
4520
4521 if ( trace.allsolid || trace.startsolid )
4522 {
4523 blocked = qtrue;
4524 break;
4525 }
4526 if ( trace.fraction < 1.0f )
4527 {//hit something
4528 if ( trace.entityNum == goalEntNum )
4529 {//hit the enemy, that's perfect!
4530 //Hmm, don't want to land on him, though...
4531 break;
4532 }
4533 else
4534 {
4535 if ( trace.contents & CONTENTS_BOTCLIP )
4536 {//hit a do-not-enter brush
4537 blocked = qtrue;
4538 break;
4539 }
4540 if ( trace.plane.normal[2] > 0.7 && DistanceSquared( trace.endpos, dest ) < 4096 )//hit within 64 of desired location, should be okay
4541 {//close enough!
4542 break;
4543 }
4544 else
4545 {//FIXME: maybe find the extents of this brush and go above or below it on next try somehow?
4546 impactDist = DistanceSquared( trace.endpos, dest );
4547 if ( impactDist < bestImpactDist )
4548 {
4549 bestImpactDist = impactDist;
4550 VectorCopy( shotVel, failCase );
4551 }
4552 blocked = qtrue;
4553 break;
4554 }
4555 }
4556 }
4557 if ( elapsedTime == floor( travelTime ) )
4558 {//reached end, all clear
4559 if ( trace.fraction >= 1.0f )
4560 {//hmm, make sure we'll land on the ground...
4561 //FIXME: do we care how far below ourselves or our dest we'll land?
4562 VectorCopy( trace.endpos, bottom );
4563 bottom[2] -= 128;
4564 trap->Trace( &trace, trace.endpos, NPCS.NPC->r.mins, NPCS.NPC->r.maxs, bottom, NPCS.NPC->s.number, NPCS.NPC->clipmask, qfalse, 0, 0 );
4565 if ( trace.fraction >= 1.0f )
4566 {//would fall too far
4567 blocked = qtrue;
4568 }
4569 }
4570 break;
4571 }
4572 else
4573 {
4574 //all clear, try next slice
4575 VectorCopy( testPos, lastPos );
4576 }
4577 }
4578 if ( blocked )
4579 {//hit something, adjust speed (which will change arc)
4580 hitCount++;
4581 shotSpeed = 300 + ((hitCount-2) * 100);//from 100 to 900 (skipping 300)
4582 if ( hitCount >= 2 )
4583 {//skip 300 since that was the first value we tested
4584 shotSpeed += 100;
4585 }
4586 }
4587 else
4588 {//made it!
4589 break;
4590 }
4591 }
4592 else
4593 {//no need to check the path, go with first calc
4594 break;
4595 }
4596 }
4597
4598 if ( hitCount >= maxHits )
4599 {//NOTE: worst case scenario, use the one that impacted closest to the target (or just use the first try...?)
4600 //NOTE: or try failcase?
4601 VectorCopy( failCase, NPCS.NPC->client->ps.velocity );
4602 }
4603 VectorCopy( shotVel, NPCS.NPC->client->ps.velocity );
4604 }
4605 else
4606 {//a more complicated jump
4607 vec3_t dir, p1, p2, apex;
4608 float time, height, forward, z, xy, dist, apexHeight;
4609
4610 if ( NPCS.NPC->r.currentOrigin[2] > dest[2] )//NPCInfo->goalEntity->r.currentOrigin
4611 {
4612 VectorCopy( NPCS.NPC->r.currentOrigin, p1 );
4613 VectorCopy( dest, p2 );//NPCInfo->goalEntity->r.currentOrigin
4614 }
4615 else if ( NPCS.NPC->r.currentOrigin[2] < dest[2] )//NPCInfo->goalEntity->r.currentOrigin
4616 {
4617 VectorCopy( dest, p1 );//NPCInfo->goalEntity->r.currentOrigin
4618 VectorCopy( NPCS.NPC->r.currentOrigin, p2 );
4619 }
4620 else
4621 {
4622 VectorCopy( NPCS.NPC->r.currentOrigin, p1 );
4623 VectorCopy( dest, p2 );//NPCInfo->goalEntity->r.currentOrigin
4624 }
4625
4626 //z = xy*xy
4627 VectorSubtract( p2, p1, dir );
4628 dir[2] = 0;
4629
4630 //Get xy and z diffs
4631 xy = VectorNormalize( dir );
4632 z = p1[2] - p2[2];
4633
4634 apexHeight = APEX_HEIGHT/2;
4635
4636 //Determine most desirable apex height
4637 //FIXME: length of xy will change curve of parabola, need to account for this
4638 //somewhere... PARA_WIDTH
4639 /*
4640 apexHeight = (APEX_HEIGHT * PARA_WIDTH/xy) + (APEX_HEIGHT * z/128);
4641 if ( apexHeight < APEX_HEIGHT * 0.5 )
4642 {
4643 apexHeight = APEX_HEIGHT*0.5;
4644 }
4645 else if ( apexHeight > APEX_HEIGHT * 2 )
4646 {
4647 apexHeight = APEX_HEIGHT*2;
4648 }
4649 */
4650
4651 z = (sqrt(apexHeight + z) - sqrt(apexHeight));
4652
4653 assert(z >= 0);
4654
4655 // Com_Printf("apex is %4.2f percent from p1: ", (xy-z)*0.5/xy*100.0f);
4656
4657 xy -= z;
4658 xy *= 0.5;
4659
4660 assert(xy > 0);
4661
4662 VectorMA( p1, xy, dir, apex );
4663 apex[2] += apexHeight;
4664
4665 VectorCopy(apex, NPCS.NPC->pos1);
4666
4667 //Now we have the apex, aim for it
4668 height = apex[2] - NPCS.NPC->r.currentOrigin[2];
4669 time = sqrt( height / ( .5 * NPCS.NPC->client->ps.gravity ) );//was 0.5, but didn't work well for very long jumps
4670 if ( !time )
4671 {
4672 //Com_Printf( S_COLOR_RED"ERROR: no time in jump\n" );
4673 return qfalse;
4674 }
4675
4676 VectorSubtract ( apex, NPCS.NPC->r.currentOrigin, NPCS.NPC->client->ps.velocity );
4677 NPCS.NPC->client->ps.velocity[2] = 0;
4678 dist = VectorNormalize( NPCS.NPC->client->ps.velocity );
4679
4680 forward = dist / time * 1.25;//er... probably bad, but...
4681 VectorScale( NPCS.NPC->client->ps.velocity, forward, NPCS.NPC->client->ps.velocity );
4682
4683 //FIXME: Uh.... should we trace/EvaluateTrajectory this to make sure we have clearance and we land where we want?
4684 NPCS.NPC->client->ps.velocity[2] = time * NPCS.NPC->client->ps.gravity;
4685
4686 //Com_Printf("Jump Velocity: %4.2f, %4.2f, %4.2f\n", NPC->client->ps.velocity[0], NPC->client->ps.velocity[1], NPC->client->ps.velocity[2] );
4687 }
4688 return qtrue;
4689 }
4690
Jedi_TryJump(gentity_t * goal)4691 static qboolean Jedi_TryJump( gentity_t *goal )
4692 {//FIXME: never does a simple short, regular jump...
4693 //FIXME: I need to be on ground too!
4694 if ( (NPCS.NPCInfo->scriptFlags&SCF_NO_ACROBATICS) )
4695 {
4696 return qfalse;
4697 }
4698 if ( TIMER_Done( NPCS.NPC, "jumpChaseDebounce" ) )
4699 {
4700 if ( (!goal->client || goal->client->ps.groundEntityNum != ENTITYNUM_NONE) )
4701 {
4702 if ( !PM_InKnockDown( &NPCS.NPC->client->ps ) && !BG_InRoll( &NPCS.NPC->client->ps, NPCS.NPC->client->ps.legsAnim ) )
4703 {//enemy is on terra firma
4704 vec3_t goal_diff;
4705 float goal_z_diff;
4706 float goal_xy_dist;
4707 VectorSubtract( goal->r.currentOrigin, NPCS.NPC->r.currentOrigin, goal_diff );
4708 goal_z_diff = goal_diff[2];
4709 goal_diff[2] = 0;
4710 goal_xy_dist = VectorNormalize( goal_diff );
4711 if ( goal_xy_dist < 550 && goal_z_diff > -400/*was -256*/ )//for now, jedi don't take falling damage && (NPC->health > 20 || goal_z_diff > 0 ) && (NPC->health >= 100 || goal_z_diff > -128 ))//closer than @512
4712 {
4713 qboolean debounce = qfalse;
4714 if ( NPCS.NPC->health < 150 && ((NPCS.NPC->health < 30 && goal_z_diff < 0) || goal_z_diff < -128 ) )
4715 {//don't jump, just walk off... doesn't help with ledges, though
4716 debounce = qtrue;
4717 }
4718 else if ( goal_z_diff < 32 && goal_xy_dist < 200 )
4719 {//what is their ideal jump height?
4720 NPCS.ucmd.upmove = 127;
4721 debounce = qtrue;
4722 }
4723 else
4724 {
4725 /*
4726 //NO! All Jedi can jump-navigate now...
4727 if ( NPCInfo->rank != RANK_CREWMAN && NPCInfo->rank <= RANK_LT_JG )
4728 {//can't do acrobatics
4729 return qfalse;
4730 }
4731 */
4732 if ( goal_z_diff > 0 || goal_xy_dist > 128 )
4733 {//Fake a force-jump
4734 //Screw it, just do my own calc & throw
4735 vec3_t dest;
4736 VectorCopy( goal->r.currentOrigin, dest );
4737 if ( goal == NPCS.NPC->enemy )
4738 {
4739 int sideTry = 0;
4740 while( sideTry < 10 )
4741 {//FIXME: make it so it doesn't try the same spot again?
4742 trace_t trace;
4743 vec3_t bottom;
4744
4745 if ( Q_irand( 0, 1 ) )
4746 {
4747 dest[0] += NPCS.NPC->enemy->r.maxs[0]*1.25;
4748 }
4749 else
4750 {
4751 dest[0] += NPCS.NPC->enemy->r.mins[0]*1.25;
4752 }
4753 if ( Q_irand( 0, 1 ) )
4754 {
4755 dest[1] += NPCS.NPC->enemy->r.maxs[1]*1.25;
4756 }
4757 else
4758 {
4759 dest[1] += NPCS.NPC->enemy->r.mins[1]*1.25;
4760 }
4761 VectorCopy( dest, bottom );
4762 bottom[2] -= 128;
4763 trap->Trace( &trace, dest, NPCS.NPC->r.mins, NPCS.NPC->r.maxs, bottom, goal->s.number, NPCS.NPC->clipmask, qfalse, 0, 0 );
4764 if ( trace.fraction < 1.0f )
4765 {//hit floor, okay to land here
4766 break;
4767 }
4768 sideTry++;
4769 }
4770 if ( sideTry >= 10 )
4771 {//screw it, just jump right at him?
4772 VectorCopy( goal->r.currentOrigin, dest );
4773 }
4774 }
4775 if ( Jedi_Jump( dest, goal->s.number ) )
4776 {
4777 //Com_Printf( "(%d) pre-checked force jump\n", level.time );
4778
4779 //FIXME: store the dir we;re going in in case something gets in the way of the jump?
4780 //? = vectoyaw( NPC->client->ps.velocity );
4781 /*
4782 if ( NPC->client->ps.velocity[2] < 320 )
4783 {
4784 NPC->client->ps.velocity[2] = 320;
4785 }
4786 else
4787 */
4788 {//FIXME: make this a function call
4789 int jumpAnim;
4790 //FIXME: this should be more intelligent, like the normal force jump anim logic
4791 if ( NPCS.NPC->client->NPC_class == CLASS_BOBAFETT
4792 ||( NPCS.NPCInfo->rank != RANK_CREWMAN && NPCS.NPCInfo->rank <= RANK_LT_JG ) )
4793 {//can't do acrobatics
4794 jumpAnim = BOTH_FORCEJUMP1;
4795 }
4796 else
4797 {
4798 jumpAnim = BOTH_FLIP_F;
4799 }
4800 NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, jumpAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
4801 }
4802
4803 NPCS.NPC->client->ps.fd.forceJumpZStart = NPCS.NPC->r.currentOrigin[2];
4804 //NPC->client->ps.pm_flags |= PMF_JUMPING;
4805
4806 NPCS.NPC->client->ps.weaponTime = NPCS.NPC->client->ps.torsoTimer;
4807 NPCS.NPC->client->ps.fd.forcePowersActive |= ( 1 << FP_LEVITATION );
4808 if ( NPCS.NPC->client->NPC_class == CLASS_BOBAFETT )
4809 {
4810 G_SoundOnEnt( NPCS.NPC, CHAN_ITEM, "sound/boba/jeton.wav" );
4811 NPCS.NPC->client->jetPackTime = level.time + Q_irand( 1000, 3000 );
4812 }
4813 else
4814 {
4815 G_SoundOnEnt( NPCS.NPC, CHAN_BODY, "sound/weapons/force/jump.wav" );
4816 }
4817
4818 TIMER_Set( NPCS.NPC, "forceJumpChasing", Q_irand( 2000, 3000 ) );
4819 debounce = qtrue;
4820 }
4821 }
4822 }
4823 if ( debounce )
4824 {
4825 //Don't jump again for another 2 to 5 seconds
4826 TIMER_Set( NPCS.NPC, "jumpChaseDebounce", Q_irand( 2000, 5000 ) );
4827 NPCS.ucmd.forwardmove = 127;
4828 VectorClear( NPCS.NPC->client->ps.moveDir );
4829 TIMER_Set( NPCS.NPC, "duck", -level.time );
4830 return qtrue;
4831 }
4832 }
4833 }
4834 }
4835 }
4836 return qfalse;
4837 }
4838
Jedi_Jumping(gentity_t * goal)4839 static qboolean Jedi_Jumping( gentity_t *goal )
4840 {
4841 if ( !TIMER_Done( NPCS.NPC, "forceJumpChasing" ) && goal )
4842 {//force-jumping at the enemy
4843 // if ( !(NPC->client->ps.pm_flags & PMF_JUMPING )//forceJumpZStart )
4844 // && !(NPC->client->ps.pm_flags&PMF_TRIGGER_PUSHED))
4845 if (NPCS.NPC->client->ps.groundEntityNum != ENTITYNUM_NONE) //rwwFIXMEFIXME: Not sure if this is gonna work, use the PM flags ideally.
4846 {//landed
4847 TIMER_Set( NPCS.NPC, "forceJumpChasing", 0 );
4848 }
4849 else
4850 {
4851 NPC_FaceEntity( goal, qtrue );
4852 //FIXME: push me torward where I was heading
4853 //FIXME: if hit a ledge as soon as we jumped, we need to push toward our goal... must remember original jump dir and/or original jump dest
4854 /*
4855 vec3_t viewangles_xy={0,0,0}, goal_dir, goal_xy_dir, forward, right;
4856 float goal_dist;
4857
4858 //gert horz dir to goal
4859 VectorSubtract( goal->r.currentOrigin, NPC->r.currentOrigin, goal_dir );
4860 VectorCopy( goal_dir, goal_xy_dir );
4861 goal_dist = VectorNormalize( goal_dir );
4862 goal_xy_dir[2] = 0;
4863 VectorNormalize( goal_xy_dir );
4864
4865 //get horz facing
4866 viewangles_xy[1] = NPC->client->ps.viewangles[1];
4867 AngleVectors( viewangles_xy, forward, right, NULL );
4868
4869 //get movement commands to push me toward enemy
4870 float fDot = DotProduct( forward, goal_dir ) * 127;
4871 float rDot = DotProduct( right, goal_dir ) * 127;
4872
4873 ucmd.forwardmove = floor(fDot);
4874 ucmd.rightmove = floor(rDot);
4875 ucmd.upmove = 0;//don't duck
4876 //Cheat:
4877 if ( goal_dist < 128 && goal->r.currentOrigin[2] > NPC->r.currentOrigin[2] && NPC->client->ps.velocity[2] <= 0 )
4878 {
4879 NPC->client->ps.velocity[2] += 320;
4880 }
4881 */
4882 return qtrue;
4883 }
4884 }
4885 return qfalse;
4886 }
4887
4888 extern void G_UcmdMoveForDir( gentity_t *self, usercmd_t *cmd, vec3_t dir );
Jedi_CheckEnemyMovement(float enemy_dist)4889 static void Jedi_CheckEnemyMovement( float enemy_dist )
4890 {
4891 if ( !NPCS.NPC->enemy || !NPCS.NPC->enemy->client )
4892 {
4893 return;
4894 }
4895
4896 if ( NPCS.NPC->client->NPC_class != CLASS_TAVION
4897 && NPCS.NPC->client->NPC_class != CLASS_DESANN
4898 && NPCS.NPC->client->NPC_class != CLASS_LUKE
4899 && Q_stricmp("Yoda",NPCS.NPC->NPC_type) )
4900 {
4901 if ( NPCS.NPC->enemy->enemy && NPCS.NPC->enemy->enemy == NPCS.NPC )
4902 {//enemy is mad at *me*
4903 if ( NPCS.NPC->enemy->client->ps.legsAnim == BOTH_JUMPFLIPSLASHDOWN1 ||
4904 NPCS.NPC->enemy->client->ps.legsAnim == BOTH_JUMPFLIPSTABDOWN )
4905 {//enemy is flipping over me
4906 if ( Q_irand( 0, NPCS.NPCInfo->rank ) < RANK_LT )
4907 {//be nice and stand still for him...
4908 NPCS.ucmd.forwardmove = NPCS.ucmd.rightmove = NPCS.ucmd.upmove = 0;
4909 VectorClear( NPCS.NPC->client->ps.moveDir );
4910 NPCS.NPC->client->ps.fd.forceJumpCharge = 0;
4911 TIMER_Set( NPCS.NPC, "strafeLeft", -1 );
4912 TIMER_Set( NPCS.NPC, "strafeRight", -1 );
4913 TIMER_Set( NPCS.NPC, "noStrafe", Q_irand( 500, 1000 ) );
4914 TIMER_Set( NPCS.NPC, "movenone", Q_irand( 500, 1000 ) );
4915 TIMER_Set( NPCS.NPC, "movecenter", Q_irand( 500, 1000 ) );
4916 }
4917 }
4918 else if ( NPCS.NPC->enemy->client->ps.legsAnim == BOTH_WALL_FLIP_BACK1
4919 || NPCS.NPC->enemy->client->ps.legsAnim == BOTH_WALL_FLIP_RIGHT
4920 || NPCS.NPC->enemy->client->ps.legsAnim == BOTH_WALL_FLIP_LEFT
4921 || NPCS.NPC->enemy->client->ps.legsAnim == BOTH_WALL_RUN_LEFT_FLIP
4922 || NPCS.NPC->enemy->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT_FLIP )
4923 {//he's flipping off a wall
4924 if ( NPCS.NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE )
4925 {//still in air
4926 if ( enemy_dist < 256 )
4927 {//close
4928 if ( Q_irand( 0, NPCS.NPCInfo->rank ) < RANK_LT )
4929 {//be nice and stand still for him...
4930 vec3_t enemyFwd, dest, dir;
4931
4932 /*
4933 ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0;
4934 VectorClear( NPC->client->ps.moveDir );
4935 NPC->client->ps.fd.forceJumpCharge = 0;
4936 TIMER_Set( NPC, "strafeLeft", -1 );
4937 TIMER_Set( NPC, "strafeRight", -1 );
4938 TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) );
4939 TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) );
4940 TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) );
4941 TIMER_Set( NPC, "noturn", Q_irand( 200, 500 ) );
4942 */
4943 //stop current movement
4944 NPCS.ucmd.forwardmove = NPCS.ucmd.rightmove = NPCS.ucmd.upmove = 0;
4945 VectorClear( NPCS.NPC->client->ps.moveDir );
4946 NPCS.NPC->client->ps.fd.forceJumpCharge = 0;
4947 TIMER_Set( NPCS.NPC, "strafeLeft", -1 );
4948 TIMER_Set( NPCS.NPC, "strafeRight", -1 );
4949 TIMER_Set( NPCS.NPC, "noStrafe", Q_irand( 500, 1000 ) );
4950 TIMER_Set( NPCS.NPC, "noturn", Q_irand( 250, 500 )*(3-g_npcspskill.integer) );
4951
4952 VectorCopy( NPCS.NPC->enemy->client->ps.velocity, enemyFwd );
4953 VectorNormalize( enemyFwd );
4954 VectorMA( NPCS.NPC->enemy->r.currentOrigin, -64, enemyFwd, dest );
4955 VectorSubtract( dest, NPCS.NPC->r.currentOrigin, dir );
4956 if ( VectorNormalize( dir ) > 32 )
4957 {
4958 G_UcmdMoveForDir( NPCS.NPC, &NPCS.ucmd, dir );
4959 }
4960 else
4961 {
4962 TIMER_Set( NPCS.NPC, "movenone", Q_irand( 500, 1000 ) );
4963 TIMER_Set( NPCS.NPC, "movecenter", Q_irand( 500, 1000 ) );
4964 }
4965 }
4966 }
4967 }
4968 }
4969 else if ( NPCS.NPC->enemy->client->ps.legsAnim == BOTH_A2_STABBACK1 )
4970 {//he's stabbing backwards
4971 if ( enemy_dist < 256 && enemy_dist > 64 )
4972 {//close
4973 if ( !InFront( NPCS.NPC->r.currentOrigin, NPCS.NPC->enemy->r.currentOrigin, NPCS.NPC->enemy->r.currentAngles, 0.0f ) )
4974 {//behind him
4975 if ( !Q_irand( 0, NPCS.NPCInfo->rank ) )
4976 {//be nice and stand still for him...
4977 vec3_t enemyFwd, dest, dir;
4978
4979 //stop current movement
4980 NPCS.ucmd.forwardmove = NPCS.ucmd.rightmove = NPCS.ucmd.upmove = 0;
4981 VectorClear( NPCS.NPC->client->ps.moveDir );
4982 NPCS.NPC->client->ps.fd.forceJumpCharge = 0;
4983 TIMER_Set( NPCS.NPC, "strafeLeft", -1 );
4984 TIMER_Set( NPCS.NPC, "strafeRight", -1 );
4985 TIMER_Set( NPCS.NPC, "noStrafe", Q_irand( 500, 1000 ) );
4986
4987 AngleVectors( NPCS.NPC->enemy->r.currentAngles, enemyFwd, NULL, NULL );
4988 VectorMA( NPCS.NPC->enemy->r.currentOrigin, -32, enemyFwd, dest );
4989 VectorSubtract( dest, NPCS.NPC->r.currentOrigin, dir );
4990 if ( VectorNormalize( dir ) > 64 )
4991 {
4992 G_UcmdMoveForDir( NPCS.NPC, &NPCS.ucmd, dir );
4993 }
4994 else
4995 {
4996 TIMER_Set( NPCS.NPC, "movenone", Q_irand( 500, 1000 ) );
4997 TIMER_Set( NPCS.NPC, "movecenter", Q_irand( 500, 1000 ) );
4998 }
4999 }
5000 }
5001 }
5002 }
5003 }
5004 }
5005 //FIXME: also:
5006 // If enemy doing wall flip, keep running forward
5007 // If enemy doing back-attack and we're behind him keep running forward toward his back, don't strafe
5008 }
5009
Jedi_CheckJumps(void)5010 static void Jedi_CheckJumps( void )
5011 {
5012 vec3_t jumpVel;
5013 trace_t trace;
5014 trajectory_t tr;
5015 vec3_t lastPos, testPos, bottom;
5016 int elapsedTime;
5017
5018 if ( (NPCS.NPCInfo->scriptFlags&SCF_NO_ACROBATICS) )
5019 {
5020 NPCS.NPC->client->ps.fd.forceJumpCharge = 0;
5021 NPCS.ucmd.upmove = 0;
5022 return;
5023 }
5024 //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
5025 //FIXME: all this work and he still jumps off ledges... *sigh*... need CONTENTS_BOTCLIP do-not-enter brushes...?
5026 VectorClear(jumpVel);
5027
5028 if ( NPCS.NPC->client->ps.fd.forceJumpCharge )
5029 {
5030 //Com_Printf( "(%d) force jump\n", level.time );
5031 WP_GetVelocityForForceJump( NPCS.NPC, jumpVel, &NPCS.ucmd );
5032 }
5033 else if ( NPCS.ucmd.upmove > 0 )
5034 {
5035 //Com_Printf( "(%d) regular jump\n", level.time );
5036 VectorCopy( NPCS.NPC->client->ps.velocity, jumpVel );
5037 jumpVel[2] = JUMP_VELOCITY;
5038 }
5039 else
5040 {
5041 return;
5042 }
5043
5044 //NOTE: for now, we clear ucmd.forwardmove & ucmd.rightmove while in air to avoid jumps going awry...
5045 if ( !jumpVel[0] && !jumpVel[1] )//FIXME: && !ucmd.forwardmove && !ucmd.rightmove?
5046 {//we assume a jump straight up is safe
5047 //Com_Printf( "(%d) jump straight up is safe\n", level.time );
5048 return;
5049 }
5050 //Now predict where this is going
5051 //in steps, keep evaluating the trajectory until the new z pos is <= than current z pos, trace down from there
5052
5053 VectorCopy( NPCS.NPC->r.currentOrigin, tr.trBase );
5054 VectorCopy( jumpVel, tr.trDelta );
5055 tr.trType = TR_GRAVITY;
5056 tr.trTime = level.time;
5057 VectorCopy( NPCS.NPC->r.currentOrigin, lastPos );
5058
5059 VectorClear(trace.endpos); //shut the compiler up
5060
5061 //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?
5062 for ( elapsedTime = 500; elapsedTime <= 4000; elapsedTime += 500 )
5063 {
5064 BG_EvaluateTrajectory( &tr, level.time + elapsedTime, testPos );
5065 //FIXME: account for PM_AirMove if ucmd.forwardmove and/or ucmd.rightmove is non-zero...
5066 if ( testPos[2] < lastPos[2] )
5067 {//going down, don't check for BOTCLIP
5068 trap->Trace( &trace, lastPos, NPCS.NPC->r.mins, NPCS.NPC->r.maxs, testPos, NPCS.NPC->s.number, NPCS.NPC->clipmask, qfalse, 0, 0 );//FIXME: include CONTENTS_BOTCLIP?
5069 }
5070 else
5071 {//going up, check for BOTCLIP
5072 trap->Trace( &trace, lastPos, NPCS.NPC->r.mins, NPCS.NPC->r.maxs, testPos, NPCS.NPC->s.number, NPCS.NPC->clipmask|CONTENTS_BOTCLIP, qfalse, 0, 0 );
5073 }
5074 if ( trace.allsolid || trace.startsolid )
5075 {//WTF?
5076 //FIXME: what do we do when we start INSIDE the CONTENTS_BOTCLIP? Do the trace again without that clipmask?
5077 goto jump_unsafe;
5078 }
5079 if ( trace.fraction < 1.0f )
5080 {//hit something
5081 if ( trace.contents & CONTENTS_BOTCLIP )
5082 {//hit a do-not-enter brush
5083 goto jump_unsafe;
5084 }
5085 //FIXME: trace through func_glass?
5086 break;
5087 }
5088 VectorCopy( testPos, lastPos );
5089 }
5090 //okay, reached end of jump, now trace down from here for a floor
5091 VectorCopy( trace.endpos, bottom );
5092 if ( bottom[2] > NPCS.NPC->r.currentOrigin[2] )
5093 {//only care about dist down from current height or lower
5094 bottom[2] = NPCS.NPC->r.currentOrigin[2];
5095 }
5096 else if ( NPCS.NPC->r.currentOrigin[2] - bottom[2] > 400 )
5097 {//whoa, long drop, don't do it!
5098 //probably no floor at end of jump, so don't jump
5099 goto jump_unsafe;
5100 }
5101 bottom[2] -= 128;
5102 trap->Trace( &trace, trace.endpos, NPCS.NPC->r.mins, NPCS.NPC->r.maxs, bottom, NPCS.NPC->s.number, NPCS.NPC->clipmask, qfalse, 0, 0 );
5103 if ( trace.allsolid || trace.startsolid || trace.fraction < 1.0f )
5104 {//hit ground!
5105 if ( trace.entityNum < ENTITYNUM_WORLD )
5106 {//landed on an ent
5107 gentity_t *groundEnt = &g_entities[trace.entityNum];
5108 if ( groundEnt->r.svFlags&SVF_GLASS_BRUSH )
5109 {//don't land on breakable glass!
5110 goto jump_unsafe;
5111 }
5112 }
5113 //Com_Printf( "(%d) jump is safe\n", level.time );
5114 return;
5115 }
5116 jump_unsafe:
5117 //probably no floor at end of jump, so don't jump
5118 //Com_Printf( "(%d) unsafe jump cleared\n", level.time );
5119 NPCS.NPC->client->ps.fd.forceJumpCharge = 0;
5120 NPCS.ucmd.upmove = 0;
5121 }
5122
Jedi_Combat(void)5123 static void Jedi_Combat( void )
5124 {
5125 vec3_t enemy_dir, enemy_movedir, enemy_dest;
5126 float enemy_dist, enemy_movespeed;
5127 qboolean enemy_lost = qfalse;
5128
5129 //See where enemy will be 300 ms from now
5130 Jedi_SetEnemyInfo( enemy_dest, enemy_dir, &enemy_dist, enemy_movedir, &enemy_movespeed, 300 );
5131
5132 if ( Jedi_Jumping( NPCS.NPC->enemy ) )
5133 {//I'm in the middle of a jump, so just see if I should attack
5134 Jedi_AttackDecide( enemy_dist );
5135 return;
5136 }
5137
5138 if ( !(NPCS.NPC->client->ps.fd.forcePowersActive&(1<<FP_GRIP)) || NPCS.NPC->client->ps.fd.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
5139 {//not gripping
5140 //If we can't get straight at him
5141 if ( !Jedi_ClearPathToSpot( enemy_dest, NPCS.NPC->enemy->s.number ) )
5142 {//hunt him down
5143 //Com_Printf( "No Clear Path\n" );
5144 if ( (NPC_ClearLOS4( NPCS.NPC->enemy )||NPCS.NPCInfo->enemyLastSeenTime>level.time-500) && NPC_FaceEnemy( qtrue ) )//( NPCInfo->rank == RANK_CREWMAN || NPCInfo->rank > RANK_LT_JG ) &&
5145 {
5146 //try to jump to him?
5147 /*
5148 vec3_t end;
5149 VectorCopy( NPC->r.currentOrigin, end );
5150 end[2] += 36;
5151 trap->Trace( &trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, end, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP );
5152 if ( !trace.allsolid && !trace.startsolid && trace.fraction >= 1.0 )
5153 {
5154 vec3_t angles, forward;
5155 VectorCopy( NPC->client->ps.viewangles, angles );
5156 angles[0] = 0;
5157 AngleVectors( angles, forward, NULL, NULL );
5158 VectorMA( end, 64, forward, end );
5159 trap->Trace( &trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, end, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP );
5160 if ( !trace.allsolid && !trace.startsolid )
5161 {
5162 if ( trace.fraction >= 1.0 || trace.plane.normal[2] > 0 )
5163 {
5164 ucmd.upmove = 127;
5165 ucmd.forwardmove = 127;
5166 return;
5167 }
5168 }
5169 }
5170 */
5171 //FIXME: about every 1 second calc a velocity,
5172 //run a loop of traces with evaluate trajectory
5173 //for gravity with my size, see if it makes it...
5174 //this will also catch misacalculations that send you off ledges!
5175 //Com_Printf( "Considering Jump\n" );
5176 if ( Jedi_TryJump( NPCS.NPC->enemy ) )
5177 {//FIXME: what about jumping to his enemyLastSeenLocation?
5178 return;
5179 }
5180 }
5181
5182 //Check for evasion
5183 if ( TIMER_Done( NPCS.NPC, "parryTime" ) )
5184 {//finished parrying
5185 if ( NPCS.NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE &&
5186 NPCS.NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
5187 {//wasn't blocked myself
5188 NPCS.NPC->client->ps.saberBlocked = BLOCKED_NONE;
5189 }
5190 }
5191 if ( Jedi_Hunt() && !(NPCS.NPCInfo->aiFlags&NPCAI_BLOCKED) )//FIXME: have to do this because they can ping-pong forever
5192 {//can macro-navigate to him
5193 if ( enemy_dist < 384 && !Q_irand( 0, 10 ) && NPCS.NPCInfo->blockedSpeechDebounceTime < level.time && jediSpeechDebounceTime[NPCS.NPC->client->playerTeam] < level.time && !NPC_ClearLOS4( NPCS.NPC->enemy ) )
5194 {
5195 G_AddVoiceEvent( NPCS.NPC, Q_irand( EV_JLOST1, EV_JLOST3 ), 3000 );
5196 jediSpeechDebounceTime[NPCS.NPC->client->playerTeam] = NPCS.NPCInfo->blockedSpeechDebounceTime = level.time + 3000;
5197 }
5198
5199 return;
5200 }
5201 //well, try to head for his last seen location
5202 /*
5203 else if ( Jedi_Track() )
5204 {
5205 return;
5206 }
5207 */ else
5208 {//FIXME: try to find a waypoint that can see enemy, jump from there
5209 if ( NPCS.NPCInfo->aiFlags & NPCAI_BLOCKED )
5210 {//try to jump to the blockedDest
5211 gentity_t *tempGoal = G_Spawn();//ugh, this is NOT good...?
5212 G_SetOrigin( tempGoal, NPCS.NPCInfo->blockedDest );
5213 trap->LinkEntity( (sharedEntity_t *)tempGoal );
5214 if ( Jedi_TryJump( tempGoal ) )
5215 {//going to jump to the dest
5216 G_FreeEntity( tempGoal );
5217 return;
5218 }
5219 G_FreeEntity( tempGoal );
5220 }
5221
5222 enemy_lost = qtrue;
5223 }
5224 }
5225 }
5226 //else, we can see him or we can't track him at all
5227
5228 //every few seconds, decide if we should we advance or retreat?
5229 Jedi_CombatTimersUpdate( enemy_dist );
5230
5231 //We call this even if lost enemy to keep him moving and to update the taunting behavior
5232 //maintain a distance from enemy appropriate for our aggression level
5233 Jedi_CombatDistance( enemy_dist );
5234
5235 if ( !enemy_lost )
5236 {
5237 //Update our seen enemy position
5238 if ( !NPCS.NPC->enemy->client || ( NPCS.NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE && NPCS.NPC->client->ps.groundEntityNum != ENTITYNUM_NONE ) )
5239 {
5240 VectorCopy( NPCS.NPC->enemy->r.currentOrigin, NPCS.NPCInfo->enemyLastSeenLocation );
5241 }
5242 NPCS.NPCInfo->enemyLastSeenTime = level.time;
5243 }
5244
5245 //Turn to face the enemy
5246 if ( TIMER_Done( NPCS.NPC, "noturn" ) )
5247 {
5248 Jedi_FaceEnemy( qtrue );
5249 }
5250 NPC_UpdateAngles( qtrue, qtrue );
5251
5252 //Check for evasion
5253 if ( TIMER_Done( NPCS.NPC, "parryTime" ) )
5254 {//finished parrying
5255 if ( NPCS.NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE &&
5256 NPCS.NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
5257 {//wasn't blocked myself
5258 NPCS.NPC->client->ps.saberBlocked = BLOCKED_NONE;
5259 }
5260 }
5261 if ( NPCS.NPC->enemy->s.weapon == WP_SABER )
5262 {
5263 Jedi_EvasionSaber( enemy_movedir, enemy_dist, enemy_dir );
5264 }
5265 else
5266 {//do we need to do any evasion for other kinds of enemies?
5267 }
5268
5269 //apply strafing/walking timers, etc.
5270 Jedi_TimersApply();
5271
5272 if ( !NPCS.NPC->client->ps.saberInFlight && (!(NPCS.NPC->client->ps.fd.forcePowersActive&(1<<FP_GRIP))||NPCS.NPC->client->ps.fd.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2) )
5273 {//not throwing saber or using force grip
5274 //see if we can attack
5275 if ( !Jedi_AttackDecide( enemy_dist ) )
5276 {//we're not attacking, decide what else to do
5277 Jedi_CombatIdle( enemy_dist );
5278 //FIXME: lower aggression when actually strike offensively? Or just when do damage?
5279 }
5280 else
5281 {//we are attacking
5282 //stop taunting
5283 TIMER_Set( NPCS.NPC, "taunting", -level.time );
5284 }
5285 }
5286 else
5287 {
5288 }
5289 if ( NPCS.NPC->client->NPC_class == CLASS_BOBAFETT )
5290 {
5291 Boba_FireDecide();
5292 }
5293
5294 //Check for certain enemy special moves
5295 Jedi_CheckEnemyMovement( enemy_dist );
5296 //Make sure that we don't jump off ledges over long drops
5297 Jedi_CheckJumps();
5298 //Just make sure we don't strafe into walls or off cliffs
5299 if ( !NPC_MoveDirClear( NPCS.ucmd.forwardmove, NPCS.ucmd.rightmove, qtrue ) )
5300 {//uh-oh, we are going to fall or hit something
5301 navInfo_t info;
5302 //Get the move info
5303 NAV_GetLastMove( &info );
5304 if ( !(info.flags & NIF_MACRO_NAV) )
5305 {//micro-navigation told us to step off a ledge, try using macronav for now
5306 NPC_MoveToGoal( qfalse );
5307 }
5308 //reset the timers.
5309 TIMER_Set( NPCS.NPC, "strafeLeft", 0 );
5310 TIMER_Set( NPCS.NPC, "strafeRight", 0 );
5311 }
5312 }
5313
5314 /*
5315 ==========================================================================================
5316 EXTERNALLY CALLED BEHAVIOR STATES
5317 ==========================================================================================
5318 */
5319
5320 /*
5321 -------------------------
5322 NPC_Jedi_Pain
5323 -------------------------
5324 */
5325
NPC_Jedi_Pain(gentity_t * self,gentity_t * attacker,int damage)5326 void NPC_Jedi_Pain(gentity_t *self, gentity_t *attacker, int damage)
5327 {
5328 gentity_t *other = attacker;
5329 vec3_t point;
5330
5331 VectorCopy(gPainPoint, point);
5332
5333 //FIXME: base the actual aggression add/subtract on health?
5334 //FIXME: don't do this more than once per frame?
5335 //FIXME: when take pain, stop force gripping....?
5336 if ( other->s.weapon == WP_SABER )
5337 {//back off
5338 TIMER_Set( self, "parryTime", -1 );
5339 if ( self->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",self->NPC_type) )
5340 {//less for Desann
5341 self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_npcspskill.integer)*50;
5342 }
5343 else if ( self->NPC->rank >= RANK_LT_JG )
5344 {
5345 self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_npcspskill.integer)*100;//300
5346 }
5347 else
5348 {
5349 self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_npcspskill.integer)*200;//500
5350 }
5351 if ( !Q_irand( 0, 3 ) )
5352 {//ouch... maybe switch up which saber power level we're using
5353 Jedi_AdjustSaberAnimLevel( self, Q_irand( FORCE_LEVEL_1, FORCE_LEVEL_3 ) );
5354 }
5355 if ( !Q_irand( 0, 1 ) )//damage > 20 || self->health < 40 ||
5356 {
5357 //Com_Printf( "(%d) drop agg - hit by saber\n", level.time );
5358 Jedi_Aggression( self, -1 );
5359 }
5360 if ( d_JediAI.integer )
5361 {
5362 Com_Printf( "(%d) PAIN: agg %d, no parry until %d\n", level.time, self->NPC->stats.aggression, level.time+500 );
5363 }
5364 //for testing only
5365 // Figure out what quadrant the hit was in.
5366 if ( d_JediAI.integer )
5367 {
5368 vec3_t diff, fwdangles, right;
5369 float rightdot, zdiff;
5370
5371 VectorSubtract( point, self->client->renderInfo.eyePoint, diff );
5372 diff[2] = 0;
5373 fwdangles[1] = self->client->ps.viewangles[1];
5374 AngleVectors( fwdangles, NULL, right, NULL );
5375 rightdot = DotProduct(right, diff);
5376 zdiff = point[2] - self->client->renderInfo.eyePoint[2];
5377
5378 Com_Printf( "(%d) saber hit at height %4.2f, zdiff: %4.2f, rightdot: %4.2f\n", level.time, point[2]-self->r.absmin[2],zdiff,rightdot);
5379 }
5380 }
5381 else
5382 {//attack
5383 //Com_Printf( "(%d) raise agg - hit by ranged\n", level.time );
5384 Jedi_Aggression( self, 1 );
5385 }
5386
5387 self->NPC->enemyCheckDebounceTime = 0;
5388
5389 WP_ForcePowerStop( self, FP_GRIP );
5390
5391 //NPC_Pain( self, inflictor, other, point, damage, mod );
5392 NPC_Pain(self, attacker, damage);
5393
5394 if ( !damage && self->health > 0 )
5395 {//FIXME: better way to know I was pushed
5396 G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
5397 }
5398
5399 //drop me from the ceiling if I'm on it
5400 if ( Jedi_WaitingAmbush( self ) )
5401 {
5402 self->client->noclip = qfalse;
5403 }
5404 if ( self->client->ps.legsAnim == BOTH_CEILING_CLING )
5405 {
5406 NPC_SetAnim( self, SETANIM_LEGS, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
5407 }
5408 if ( self->client->ps.torsoAnim == BOTH_CEILING_CLING )
5409 {
5410 NPC_SetAnim( self, SETANIM_TORSO, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
5411 }
5412 }
5413
Jedi_CheckDanger(void)5414 qboolean Jedi_CheckDanger( void )
5415 {
5416 int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_MINOR );
5417
5418 if ( level.alertEvents[alertEvent].level >= AEL_DANGER )
5419 {//run away!
5420 if ( !level.alertEvents[alertEvent].owner
5421 || !level.alertEvents[alertEvent].owner->client
5422 || (level.alertEvents[alertEvent].owner!=NPCS.NPC&&level.alertEvents[alertEvent].owner->client->playerTeam!=NPCS.NPC->client->playerTeam) )
5423 {//no owner
5424 return qfalse;
5425 }
5426 G_SetEnemy( NPCS.NPC, level.alertEvents[alertEvent].owner );
5427 NPCS.NPCInfo->enemyLastSeenTime = level.time;
5428 TIMER_Set( NPCS.NPC, "attackDelay", Q_irand( 500, 2500 ) );
5429 return qtrue;
5430 }
5431 return qfalse;
5432 }
5433
Jedi_CheckAmbushPlayer(void)5434 qboolean Jedi_CheckAmbushPlayer( void )
5435 {
5436 int i = 0;
5437 gentity_t *player;
5438 float target_dist;
5439 float zDiff;
5440
5441 for ( i = 0; i < MAX_CLIENTS; i++ )
5442 {
5443 player = &g_entities[i];
5444
5445 if ( !player || !player->client )
5446 {
5447 continue;
5448 }
5449
5450 if ( !NPC_ValidEnemy( player ) )
5451 {
5452 continue;
5453 }
5454
5455 // if ( NPC->client->ps.powerups[PW_CLOAKED] || g_crosshairEntNum != NPC->s.number )
5456 if (NPCS.NPC->client->ps.powerups[PW_CLOAKED] || !NPC_SomeoneLookingAtMe(NPCS.NPC)) //rwwFIXMEFIXME: Need to pay attention to who is under crosshair for each player or something.
5457 {//if I'm not cloaked and the player's crosshair is on me, I will wake up, otherwise do this stuff down here...
5458 if ( !trap->InPVS( player->r.currentOrigin, NPCS.NPC->r.currentOrigin ) )
5459 {//must be in same room
5460 continue;
5461 }
5462 else
5463 {
5464 if ( !NPCS.NPC->client->ps.powerups[PW_CLOAKED] )
5465 {
5466 NPC_SetLookTarget( NPCS.NPC, 0, 0 );
5467 }
5468 }
5469 zDiff = NPCS.NPC->r.currentOrigin[2]-player->r.currentOrigin[2];
5470 if ( zDiff <= 0 || zDiff > 512 )
5471 {//never ambush if they're above me or way way below me
5472 continue;
5473 }
5474
5475 //If the target is this close, then wake up regardless
5476 if ( (target_dist = DistanceHorizontalSquared( player->r.currentOrigin, NPCS.NPC->r.currentOrigin )) > 4096 )
5477 {//closer than 64 - always ambush
5478 if ( target_dist > 147456 )
5479 {//> 384, not close enough to ambush
5480 continue;
5481 }
5482 //Check FOV first
5483 if ( NPCS.NPC->client->ps.powerups[PW_CLOAKED] )
5484 {
5485 if ( InFOV( player, NPCS.NPC, 30, 90 ) == qfalse )
5486 {
5487 continue;
5488 }
5489 }
5490 else
5491 {
5492 if ( InFOV( player, NPCS.NPC, 45, 90 ) == qfalse )
5493 {
5494 continue;
5495 }
5496 }
5497 }
5498
5499 if ( !NPC_ClearLOS4( player ) )
5500 {
5501 continue;
5502 }
5503 }
5504
5505 //Got him, return true;
5506 G_SetEnemy( NPCS.NPC, player );
5507 NPCS.NPCInfo->enemyLastSeenTime = level.time;
5508 TIMER_Set( NPCS.NPC, "attackDelay", Q_irand( 500, 2500 ) );
5509 return qtrue;
5510 }
5511
5512 //Didn't get anyone.
5513 return qfalse;
5514 }
5515
Jedi_Ambush(gentity_t * self)5516 void Jedi_Ambush( gentity_t *self )
5517 {
5518 self->client->noclip = qfalse;
5519 // self->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL;
5520 NPC_SetAnim( self, SETANIM_BOTH, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
5521 self->client->ps.weaponTime = self->client->ps.torsoTimer; //NPC->client->ps.torsoTimer; //what the?
5522 if ( self->client->NPC_class != CLASS_BOBAFETT )
5523 {
5524 WP_ActivateSaber(self);
5525 }
5526 Jedi_Decloak( self );
5527 G_AddVoiceEvent( self, Q_irand( EV_ANGER1, EV_ANGER3 ), 1000 );
5528 }
5529
Jedi_WaitingAmbush(gentity_t * self)5530 qboolean Jedi_WaitingAmbush( gentity_t *self )
5531 {
5532 if ( (self->spawnflags&JSF_AMBUSH) && self->client->noclip )
5533 {
5534 return qtrue;
5535 }
5536 return qfalse;
5537 }
5538 /*
5539 -------------------------
5540 Jedi_Patrol
5541 -------------------------
5542 */
5543
Jedi_Patrol(void)5544 static void Jedi_Patrol( void )
5545 {
5546 NPCS.NPC->client->ps.saberBlocked = BLOCKED_NONE;
5547
5548 if ( Jedi_WaitingAmbush( NPCS.NPC ) )
5549 {//hiding on the ceiling
5550 NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, BOTH_CEILING_CLING, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
5551 if ( NPCS.NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
5552 {//look for enemies
5553 if ( Jedi_CheckAmbushPlayer() || Jedi_CheckDanger() )
5554 {//found him!
5555 Jedi_Ambush( NPCS.NPC );
5556 NPC_UpdateAngles( qtrue, qtrue );
5557 return;
5558 }
5559 }
5560 }
5561 else if ( NPCS.NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )//NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
5562 {//look for enemies
5563 gentity_t *best_enemy = NULL;
5564 float best_enemy_dist = Q3_INFINITE;
5565 int i;
5566 for ( i = 0; i < ENTITYNUM_WORLD; i++ )
5567 {
5568 gentity_t *enemy = &g_entities[i];
5569 float enemy_dist;
5570 if ( enemy && enemy->client && NPC_ValidEnemy( enemy ) && enemy->client->playerTeam == NPCS.NPC->client->enemyTeam )
5571 {
5572 if ( trap->InPVS( NPCS.NPC->r.currentOrigin, enemy->r.currentOrigin ) )
5573 {//we could potentially see him
5574 enemy_dist = DistanceSquared( NPCS.NPC->r.currentOrigin, enemy->r.currentOrigin );
5575 if ( enemy->s.eType == ET_PLAYER || enemy_dist < best_enemy_dist )
5576 {
5577 //if the enemy is close enough, or threw his saber, take him as the enemy
5578 //FIXME: what if he throws a thermal detonator?
5579 if ( enemy_dist < (220*220) || ( NPCS.NPCInfo->investigateCount>= 3 && !NPCS.NPC->client->ps.saberHolstered ) )
5580 {
5581 G_SetEnemy( NPCS.NPC, enemy );
5582 //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now
5583 NPCS.NPCInfo->stats.aggression = 3;
5584 break;
5585 }
5586 else if ( enemy->client->ps.saberInFlight && !enemy->client->ps.saberHolstered )
5587 {//threw his saber, see if it's heading toward me and close enough to consider a threat
5588 float saberDist;
5589 vec3_t saberDir2Me;
5590 vec3_t saberMoveDir;
5591 gentity_t *saber = &g_entities[enemy->client->ps.saberEntityNum];
5592 VectorSubtract( NPCS.NPC->r.currentOrigin, saber->r.currentOrigin, saberDir2Me );
5593 saberDist = VectorNormalize( saberDir2Me );
5594 VectorCopy( saber->s.pos.trDelta, saberMoveDir );
5595 VectorNormalize( saberMoveDir );
5596 if ( DotProduct( saberMoveDir, saberDir2Me ) > 0.5 )
5597 {//it's heading towards me
5598 if ( saberDist < 200 )
5599 {//incoming!
5600 G_SetEnemy( NPCS.NPC, enemy );
5601 //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now
5602 NPCS.NPCInfo->stats.aggression = 3;
5603 break;
5604 }
5605 }
5606 }
5607 best_enemy_dist = enemy_dist;
5608 best_enemy = enemy;
5609 }
5610 }
5611 }
5612 }
5613 if ( !NPCS.NPC->enemy )
5614 {//still not mad
5615 if ( !best_enemy )
5616 {
5617 //Com_Printf( "(%d) drop agg - no enemy (patrol)\n", level.time );
5618 Jedi_AggressionErosion(-1);
5619 //FIXME: what about alerts? But not if ignore alerts
5620 }
5621 else
5622 {//have one to consider
5623 if ( NPC_ClearLOS4( best_enemy ) )
5624 {//we have a clear (of architecture) LOS to him
5625 if ( best_enemy->s.number )
5626 {//just attack
5627 G_SetEnemy( NPCS.NPC, best_enemy );
5628 NPCS.NPCInfo->stats.aggression = 3;
5629 }
5630 else if ( NPCS.NPC->client->NPC_class != CLASS_BOBAFETT )
5631 {//the player, toy with him
5632 //get progressively more interested over time
5633 if ( TIMER_Done( NPCS.NPC, "watchTime" ) )
5634 {//we want to pick him up in stages
5635 if ( TIMER_Get( NPCS.NPC, "watchTime" ) == -1 )
5636 {//this is the first time, we'll ignore him for a couple seconds
5637 TIMER_Set( NPCS.NPC, "watchTime", Q_irand( 3000, 5000 ) );
5638 goto finish;
5639 }
5640 else
5641 {//okay, we've ignored him, now start to notice him
5642 if ( !NPCS.NPCInfo->investigateCount )
5643 {
5644 G_AddVoiceEvent( NPCS.NPC, Q_irand( EV_JDETECTED1, EV_JDETECTED3 ), 3000 );
5645 }
5646 NPCS.NPCInfo->investigateCount++;
5647 TIMER_Set( NPCS.NPC, "watchTime", Q_irand( 4000, 10000 ) );
5648 }
5649 }
5650 //while we're waiting, do what we need to do
5651 if ( best_enemy_dist < (440*440) || NPCS.NPCInfo->investigateCount >= 2 )
5652 {//stage three: keep facing him
5653 NPC_FaceEntity( best_enemy, qtrue );
5654 if ( best_enemy_dist < (330*330) )
5655 {//stage four: turn on the saber
5656 if ( !NPCS.NPC->client->ps.saberInFlight )
5657 {
5658 WP_ActivateSaber(NPCS.NPC);
5659 }
5660 }
5661 }
5662 else if ( best_enemy_dist < (550*550) || NPCS.NPCInfo->investigateCount == 1 )
5663 {//stage two: stop and face him every now and then
5664 if ( TIMER_Done( NPCS.NPC, "watchTime" ) )
5665 {
5666 NPC_FaceEntity( best_enemy, qtrue );
5667 }
5668 }
5669 else
5670 {//stage one: look at him.
5671 NPC_SetLookTarget( NPCS.NPC, best_enemy->s.number, 0 );
5672 }
5673 }
5674 }
5675 else if ( TIMER_Done( NPCS.NPC, "watchTime" ) )
5676 {//haven't seen him in a bit, clear the lookTarget
5677 NPC_ClearLookTarget( NPCS.NPC );
5678 }
5679 }
5680 }
5681 }
5682 finish:
5683 //If we have somewhere to go, then do that
5684 if ( UpdateGoal() )
5685 {
5686 NPCS.ucmd.buttons |= BUTTON_WALKING;
5687 //Jedi_Move( NPCInfo->goalEntity );
5688 NPC_MoveToGoal( qtrue );
5689 }
5690
5691 NPC_UpdateAngles( qtrue, qtrue );
5692
5693 if ( NPCS.NPC->enemy )
5694 {//just picked one up
5695 NPCS.NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 );
5696 }
5697 }
5698
Jedi_CanPullBackSaber(gentity_t * self)5699 qboolean Jedi_CanPullBackSaber( gentity_t *self )
5700 {
5701 if ( self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN && !TIMER_Done( self, "parryTime" ) )
5702 {
5703 return qfalse;
5704 }
5705
5706 if ( self->client->NPC_class == CLASS_SHADOWTROOPER
5707 || self->client->NPC_class == CLASS_TAVION
5708 || self->client->NPC_class == CLASS_LUKE
5709 || self->client->NPC_class == CLASS_DESANN
5710 || !Q_stricmp( "Yoda", self->NPC_type ) )
5711 {
5712 return qtrue;
5713 }
5714
5715 if ( self->painDebounceTime > level.time )//|| self->client->ps.weaponTime > 0 )
5716 {
5717 return qfalse;
5718 }
5719
5720 return qtrue;
5721 }
5722 /*
5723 -------------------------
5724 NPC_BSJedi_FollowLeader
5725 -------------------------
5726 */
NPC_BSJedi_FollowLeader(void)5727 void NPC_BSJedi_FollowLeader( void )
5728 {
5729 NPCS.NPC->client->ps.saberBlocked = BLOCKED_NONE;
5730 if ( !NPCS.NPC->enemy )
5731 {
5732 //Com_Printf( "(%d) drop agg - no enemy (follow)\n", level.time );
5733 Jedi_AggressionErosion(-1);
5734 }
5735
5736 //did we drop our saber? If so, go after it!
5737 if ( NPCS.NPC->client->ps.saberInFlight )
5738 {//saber is not in hand
5739 if ( NPCS.NPC->client->ps.saberEntityNum < ENTITYNUM_NONE && NPCS.NPC->client->ps.saberEntityNum > 0 )//player is 0
5740 {//
5741 if ( g_entities[NPCS.NPC->client->ps.saberEntityNum].s.pos.trType == TR_STATIONARY )
5742 {//fell to the ground, try to pick it up...
5743 if ( Jedi_CanPullBackSaber( NPCS.NPC ) )
5744 {
5745 //FIXME: if it's on the ground and we just pulled it back to us, should we
5746 // stand still for a bit to make sure it gets to us...?
5747 // otherwise we could end up running away from it while it's on its
5748 // way back to us and we could lose it again.
5749 NPCS.NPC->client->ps.saberBlocked = BLOCKED_NONE;
5750 NPCS.NPCInfo->goalEntity = &g_entities[NPCS.NPC->client->ps.saberEntityNum];
5751 NPCS.ucmd.buttons |= BUTTON_ATTACK;
5752 if ( NPCS.NPC->enemy && NPCS.NPC->enemy->health > 0 )
5753 {//get our saber back NOW!
5754 if ( !NPC_MoveToGoal( qtrue ) )//Jedi_Move( NPCInfo->goalEntity, qfalse );
5755 {//can't nav to it, try jumping to it
5756 NPC_FaceEntity( NPCS.NPCInfo->goalEntity, qtrue );
5757 Jedi_TryJump( NPCS.NPCInfo->goalEntity );
5758 }
5759 NPC_UpdateAngles( qtrue, qtrue );
5760 return;
5761 }
5762 }
5763 }
5764 }
5765 }
5766
5767 if ( NPCS.NPCInfo->goalEntity )
5768 {
5769 trace_t trace;
5770
5771 if ( Jedi_Jumping( NPCS.NPCInfo->goalEntity ) )
5772 {//in mid-jump
5773 return;
5774 }
5775
5776 if ( !NAV_CheckAhead( NPCS.NPC, NPCS.NPCInfo->goalEntity->r.currentOrigin, &trace, ( NPCS.NPC->clipmask & ~CONTENTS_BODY )|CONTENTS_BOTCLIP ) )
5777 {//can't get straight to him
5778 if ( NPC_ClearLOS4( NPCS.NPCInfo->goalEntity ) && NPC_FaceEntity( NPCS.NPCInfo->goalEntity, qtrue ) )
5779 {//no line of sight
5780 if ( Jedi_TryJump( NPCS.NPCInfo->goalEntity ) )
5781 {//started a jump
5782 return;
5783 }
5784 }
5785 }
5786 if ( NPCS.NPCInfo->aiFlags & NPCAI_BLOCKED )
5787 {//try to jump to the blockedDest
5788 if ( fabs(NPCS.NPCInfo->blockedDest[2]-NPCS.NPC->r.currentOrigin[2]) > 64 )
5789 {
5790 gentity_t *tempGoal = G_Spawn();//ugh, this is NOT good...?
5791 G_SetOrigin( tempGoal, NPCS.NPCInfo->blockedDest );
5792 trap->LinkEntity( (sharedEntity_t *)tempGoal );
5793 TIMER_Set( NPCS.NPC, "jumpChaseDebounce", -1 );
5794 if ( Jedi_TryJump( tempGoal ) )
5795 {//going to jump to the dest
5796 G_FreeEntity( tempGoal );
5797 return;
5798 }
5799 G_FreeEntity( tempGoal );
5800 }
5801 }
5802 }
5803 //try normal movement
5804 NPC_BSFollowLeader();
5805 }
5806
5807
5808 /*
5809 -------------------------
5810 Jedi_Attack
5811 -------------------------
5812 */
5813
Jedi_Attack(void)5814 static void Jedi_Attack( void )
5815 {
5816 //Don't do anything if we're in a pain anim
5817 if ( NPCS.NPC->painDebounceTime > level.time )
5818 {
5819 if ( Q_irand( 0, 1 ) )
5820 {
5821 Jedi_FaceEnemy( qtrue );
5822 }
5823 NPC_UpdateAngles( qtrue, qtrue );
5824 return;
5825 }
5826
5827 if ( NPCS.NPC->client->ps.saberLockTime > level.time )
5828 {
5829 //FIXME: maybe if I'm losing I should try to force-push out of it? Very rarely, though...
5830 if ( NPCS.NPC->client->ps.fd.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2
5831 && NPCS.NPC->client->ps.saberLockTime < level.time + 5000
5832 && !Q_irand( 0, 10 ))
5833 {
5834 ForceThrow( NPCS.NPC, qfalse );
5835 }
5836 //based on my skill, hit attack button every other to every several frames in order to push enemy back
5837 else
5838 {
5839 float chance;
5840
5841 if ( NPCS.NPC->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",NPCS.NPC->NPC_type) )
5842 {
5843 if ( g_npcspskill.integer )
5844 {
5845 chance = 4.0f;//he pushes *hard*
5846 }
5847 else
5848 {
5849 chance = 3.0f;//he pushes *hard*
5850 }
5851 }
5852 else if ( NPCS.NPC->client->NPC_class == CLASS_TAVION )
5853 {
5854 chance = 2.0f+g_npcspskill.value;//from 2 to 4
5855 }
5856 else
5857 {//the escalation in difficulty is nice, here, but cap it so it doesn't get *impossible* on hard
5858 float maxChance = (float)(RANK_LT)/2.0f+3.0f;//5?
5859 if ( !g_npcspskill.value )
5860 {
5861 chance = (float)(NPCS.NPCInfo->rank)/2.0f;
5862 }
5863 else
5864 {
5865 chance = (float)(NPCS.NPCInfo->rank)/2.0f+1.0f;
5866 }
5867 if ( chance > maxChance )
5868 {
5869 chance = maxChance;
5870 }
5871 }
5872 // if ( flrand( -4.0f, chance ) >= 0.0f && !(NPC->client->ps.pm_flags&PMF_ATTACK_HELD) )
5873 // {
5874 // ucmd.buttons |= BUTTON_ATTACK;
5875 // }
5876 if ( flrand( -4.0f, chance ) >= 0.0f )
5877 {
5878 NPCS.ucmd.buttons |= BUTTON_ATTACK;
5879 }
5880 //rwwFIXMEFIXME: support for PMF_ATTACK_HELD
5881 }
5882 NPC_UpdateAngles( qtrue, qtrue );
5883 return;
5884 }
5885 //did we drop our saber? If so, go after it!
5886 if ( NPCS.NPC->client->ps.saberInFlight )
5887 {//saber is not in hand
5888 // if ( NPC->client->ps.saberEntityNum < ENTITYNUM_NONE && NPC->client->ps.saberEntityNum > 0 )//player is 0
5889 if (!NPCS.NPC->client->ps.saberEntityNum && NPCS.NPC->client->saberStoredIndex) //this is valid, it's 0 when our saber is gone -rww (mp-specific)
5890 {//
5891 //if ( g_entities[NPC->client->ps.saberEntityNum].s.pos.trType == TR_STATIONARY )
5892 if (1) //no matter
5893 {//fell to the ground, try to pick it up
5894 // if ( Jedi_CanPullBackSaber( NPC ) )
5895 if (1) //no matter
5896 {
5897 NPCS.NPC->client->ps.saberBlocked = BLOCKED_NONE;
5898 NPCS.NPCInfo->goalEntity = &g_entities[NPCS.NPC->client->saberStoredIndex];
5899 NPCS.ucmd.buttons |= BUTTON_ATTACK;
5900 if ( NPCS.NPC->enemy && NPCS.NPC->enemy->health > 0 )
5901 {//get our saber back NOW!
5902 Jedi_Move( NPCS.NPCInfo->goalEntity, qfalse );
5903 NPC_UpdateAngles( qtrue, qtrue );
5904 if ( NPCS.NPC->enemy->s.weapon == WP_SABER )
5905 {//be sure to continue evasion
5906 vec3_t enemy_dir, enemy_movedir, enemy_dest;
5907 float enemy_dist, enemy_movespeed;
5908 Jedi_SetEnemyInfo( enemy_dest, enemy_dir, &enemy_dist, enemy_movedir, &enemy_movespeed, 300 );
5909 Jedi_EvasionSaber( enemy_movedir, enemy_dist, enemy_dir );
5910 }
5911 return;
5912 }
5913 }
5914 }
5915 }
5916 }
5917 //see if our enemy was killed by us, gloat and turn off saber after cool down.
5918 //FIXME: don't do this if we have other enemies to fight...?
5919 if ( NPCS.NPC->enemy )
5920 {
5921 if ( NPCS.NPC->enemy->health <= 0 && NPCS.NPC->enemy->enemy == NPCS.NPC && NPCS.NPC->client->playerTeam != NPCTEAM_PLAYER )//good guys don't gloat
5922 {//my enemy is dead and I killed him
5923 NPCS.NPCInfo->enemyCheckDebounceTime = 0;//keep looking for others
5924
5925 if ( NPCS.NPC->client->NPC_class == CLASS_BOBAFETT )
5926 {
5927 if ( NPCS.NPCInfo->walkDebounceTime < level.time && NPCS.NPCInfo->walkDebounceTime >= 0 )
5928 {
5929 TIMER_Set( NPCS.NPC, "gloatTime", 10000 );
5930 NPCS.NPCInfo->walkDebounceTime = -1;
5931 }
5932 if ( !TIMER_Done( NPCS.NPC, "gloatTime" ) )
5933 {
5934 if ( DistanceHorizontalSquared( NPCS.NPC->client->renderInfo.eyePoint, NPCS.NPC->enemy->r.currentOrigin ) > 4096 && (NPCS.NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared
5935 {
5936 NPCS.NPCInfo->goalEntity = NPCS.NPC->enemy;
5937 Jedi_Move( NPCS.NPC->enemy, qfalse );
5938 NPCS.ucmd.buttons |= BUTTON_WALKING;
5939 }
5940 else
5941 {
5942 TIMER_Set( NPCS.NPC, "gloatTime", 0 );
5943 }
5944 }
5945 else if ( NPCS.NPCInfo->walkDebounceTime == -1 )
5946 {
5947 NPCS.NPCInfo->walkDebounceTime = -2;
5948 G_AddVoiceEvent( NPCS.NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 );
5949 jediSpeechDebounceTime[NPCS.NPC->client->playerTeam] = level.time + 3000;
5950 NPCS.NPCInfo->desiredPitch = 0;
5951 NPCS.NPCInfo->goalEntity = NULL;
5952 }
5953 Jedi_FaceEnemy( qtrue );
5954 NPC_UpdateAngles( qtrue, qtrue );
5955 return;
5956 }
5957 else
5958 {
5959 if ( !TIMER_Done( NPCS.NPC, "parryTime" ) )
5960 {
5961 TIMER_Set( NPCS.NPC, "parryTime", -1 );
5962 NPCS.NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500;
5963 }
5964 NPCS.NPC->client->ps.saberBlocked = BLOCKED_NONE;
5965 if ( !NPCS.NPC->client->ps.saberHolstered && NPCS.NPC->client->ps.saberInFlight )
5966 {//saber is still on (or we're trying to pull it back), count down erosion and keep facing the enemy
5967 //FIXME: need to stop this from happening over and over again when they're blocking their victim's saber
5968 //FIXME: turn off saber sooner so we get cool walk anim?
5969 //Com_Printf( "(%d) drop agg - enemy dead\n", level.time );
5970 Jedi_AggressionErosion(-3);
5971 if ( BG_SabersOff( &NPCS.NPC->client->ps ) && !NPCS.NPC->client->ps.saberInFlight )
5972 {//turned off saber (in hand), gloat
5973 G_AddVoiceEvent( NPCS.NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 );
5974 jediSpeechDebounceTime[NPCS.NPC->client->playerTeam] = level.time + 3000;
5975 NPCS.NPCInfo->desiredPitch = 0;
5976 NPCS.NPCInfo->goalEntity = NULL;
5977 }
5978 TIMER_Set( NPCS.NPC, "gloatTime", 10000 );
5979 }
5980 if ( !NPCS.NPC->client->ps.saberHolstered || NPCS.NPC->client->ps.saberInFlight || !TIMER_Done( NPCS.NPC, "gloatTime" ) )
5981 {//keep walking
5982 if ( DistanceHorizontalSquared( NPCS.NPC->client->renderInfo.eyePoint, NPCS.NPC->enemy->r.currentOrigin ) > 4096 && (NPCS.NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared
5983 {
5984 NPCS.NPCInfo->goalEntity = NPCS.NPC->enemy;
5985 Jedi_Move( NPCS.NPC->enemy, qfalse );
5986 NPCS.ucmd.buttons |= BUTTON_WALKING;
5987 }
5988 else
5989 {//got there
5990 if ( NPCS.NPC->health < NPCS.NPC->client->pers.maxHealth
5991 && (NPCS.NPC->client->ps.fd.forcePowersKnown&(1<<FP_HEAL)) != 0
5992 && (NPCS.NPC->client->ps.fd.forcePowersActive&(1<<FP_HEAL)) == 0 )
5993 {
5994 ForceHeal( NPCS.NPC );
5995 }
5996 }
5997 Jedi_FaceEnemy( qtrue );
5998 NPC_UpdateAngles( qtrue, qtrue );
5999 return;
6000 }
6001 }
6002 }
6003 }
6004
6005 //If we don't have an enemy, just idle
6006 if ( NPCS.NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPCS.NPC->enemy->classname ) )
6007 {
6008 if ( NPCS.NPC->enemy->count <= 0 )
6009 {//it's out of ammo
6010 if ( NPCS.NPC->enemy->activator && NPC_ValidEnemy( NPCS.NPC->enemy->activator ) )
6011 {
6012 gentity_t *turretOwner = NPCS.NPC->enemy->activator;
6013 G_ClearEnemy( NPCS.NPC );
6014 G_SetEnemy( NPCS.NPC, turretOwner );
6015 }
6016 else
6017 {
6018 G_ClearEnemy( NPCS.NPC );
6019 }
6020 }
6021 }
6022 NPC_CheckEnemy( qtrue, qtrue, qtrue );
6023
6024 if ( !NPCS.NPC->enemy )
6025 {
6026 NPCS.NPC->client->ps.saberBlocked = BLOCKED_NONE;
6027 if ( NPCS.NPCInfo->tempBehavior == BS_HUNT_AND_KILL )
6028 {//lost him, go back to what we were doing before
6029 NPCS.NPCInfo->tempBehavior = BS_DEFAULT;
6030 NPC_UpdateAngles( qtrue, qtrue );
6031 return;
6032 }
6033 Jedi_Patrol();//was calling Idle... why?
6034 return;
6035 }
6036
6037 //always face enemy if have one
6038 NPCS.NPCInfo->combatMove = qtrue;
6039
6040 //Track the player and kill them if possible
6041 Jedi_Combat();
6042
6043 if ( !(NPCS.NPCInfo->scriptFlags&SCF_CHASE_ENEMIES)
6044 || ((NPCS.NPC->client->ps.fd.forcePowersActive&(1<<FP_HEAL))&&NPCS.NPC->client->ps.fd.forcePowerLevel[FP_HEAL]<FORCE_LEVEL_2))
6045 {//this is really stupid, but okay...
6046 NPCS.ucmd.forwardmove = 0;
6047 NPCS.ucmd.rightmove = 0;
6048 if ( NPCS.ucmd.upmove > 0 )
6049 {
6050 NPCS.ucmd.upmove = 0;
6051 }
6052 NPCS.NPC->client->ps.fd.forceJumpCharge = 0;
6053 VectorClear( NPCS.NPC->client->ps.moveDir );
6054 }
6055
6056 //NOTE: for now, we clear ucmd.forwardmove & ucmd.rightmove while in air to avoid jumps going awry...
6057 if ( NPCS.NPC->client->ps.groundEntityNum == ENTITYNUM_NONE )
6058 {//don't push while in air, throws off jumps!
6059 //FIXME: if we are in the air over a drop near a ledge, should we try to push back towards the ledge?
6060 NPCS.ucmd.forwardmove = 0;
6061 NPCS.ucmd.rightmove = 0;
6062 VectorClear( NPCS.NPC->client->ps.moveDir );
6063 }
6064
6065 if ( !TIMER_Done( NPCS.NPC, "duck" ) )
6066 {
6067 NPCS.ucmd.upmove = -127;
6068 }
6069
6070 if ( NPCS.NPC->client->NPC_class != CLASS_BOBAFETT )
6071 {
6072 if ( PM_SaberInBrokenParry( NPCS.NPC->client->ps.saberMove ) || NPCS.NPC->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
6073 {//just make sure they don't pull their saber to them if they're being blocked
6074 NPCS.ucmd.buttons &= ~BUTTON_ATTACK;
6075 }
6076 }
6077
6078 if( (NPCS.NPCInfo->scriptFlags&SCF_DONT_FIRE) //not allowed to attack
6079 || ((NPCS.NPC->client->ps.fd.forcePowersActive&(1<<FP_HEAL))&&NPCS.NPC->client->ps.fd.forcePowerLevel[FP_HEAL]<FORCE_LEVEL_3)
6080 || ((NPCS.NPC->client->ps.saberEventFlags&SEF_INWATER)&&!NPCS.NPC->client->ps.saberInFlight) )//saber in water
6081 {
6082 NPCS.ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK);
6083 }
6084
6085 if ( NPCS.NPCInfo->scriptFlags&SCF_NO_ACROBATICS )
6086 {
6087 NPCS.ucmd.upmove = 0;
6088 NPCS.NPC->client->ps.fd.forceJumpCharge = 0;
6089 }
6090
6091 if ( NPCS.NPC->client->NPC_class != CLASS_BOBAFETT )
6092 {
6093 Jedi_CheckDecreaseSaberAnimLevel();
6094 }
6095
6096 if ( NPCS.ucmd.buttons & BUTTON_ATTACK && NPCS.NPC->client->playerTeam == NPCTEAM_ENEMY )
6097 {
6098 if ( Q_irand( 0, NPCS.NPC->client->ps.fd.saberAnimLevel ) > 0
6099 && Q_irand( 0, NPCS.NPC->client->pers.maxHealth+10 ) > NPCS.NPC->health
6100 && !Q_irand( 0, 3 ))
6101 {//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
6102 G_AddVoiceEvent( NPCS.NPC, Q_irand( EV_COMBAT1, EV_COMBAT3 ), 1000 );
6103 }
6104 }
6105
6106 if ( NPCS.NPC->client->NPC_class != CLASS_BOBAFETT )
6107 {
6108 if ( NPCS.NPC->client->NPC_class == CLASS_TAVION
6109 || (g_npcspskill.integer && ( NPCS.NPC->client->NPC_class == CLASS_DESANN || NPCS.NPCInfo->rank >= Q_irand( RANK_CREWMAN, RANK_CAPTAIN ))))
6110 {//Tavion will kick in force speed if the player does...
6111 if ( NPCS.NPC->enemy
6112 && NPCS.NPC->enemy->s.number >= 0 && NPCS.NPC->enemy->s.number < MAX_CLIENTS
6113 && NPCS.NPC->enemy->client
6114 && (NPCS.NPC->enemy->client->ps.fd.forcePowersActive & (1<<FP_SPEED))
6115 && !(NPCS.NPC->client->ps.fd.forcePowersActive & (1<<FP_SPEED)) )
6116 {
6117 int chance = 0;
6118 switch ( g_npcspskill.integer )
6119 {
6120 case 0:
6121 chance = 9;
6122 break;
6123 case 1:
6124 chance = 3;
6125 break;
6126 case 2:
6127 chance = 1;
6128 break;
6129 }
6130 if ( !Q_irand( 0, chance ) )
6131 {
6132 ForceSpeed( NPCS.NPC, 0 );
6133 }
6134 }
6135 }
6136 }
6137 }
6138
6139 extern void WP_Explode( gentity_t *self );
Jedi_InSpecialMove(void)6140 qboolean Jedi_InSpecialMove( void )
6141 {
6142 if ( NPCS.NPC->client->ps.torsoAnim == BOTH_KYLE_PA_1
6143 || NPCS.NPC->client->ps.torsoAnim == BOTH_KYLE_PA_2
6144 || NPCS.NPC->client->ps.torsoAnim == BOTH_KYLE_PA_3
6145 || NPCS.NPC->client->ps.torsoAnim == BOTH_PLAYER_PA_1
6146 || NPCS.NPC->client->ps.torsoAnim == BOTH_PLAYER_PA_2
6147 || NPCS.NPC->client->ps.torsoAnim == BOTH_PLAYER_PA_3
6148 || NPCS.NPC->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_END
6149 || NPCS.NPC->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRABBED )
6150 {
6151 NPC_UpdateAngles( qtrue, qtrue );
6152 return qtrue;
6153 }
6154
6155 /*if ( Jedi_InNoAIAnim( NPC ) )
6156 {//in special anims, don't do force powers or attacks, just face the enemy
6157 if ( NPC->enemy )
6158 {
6159 NPC_FaceEnemy( qtrue );
6160 }
6161 else
6162 {
6163 NPC_UpdateAngles( qtrue, qtrue );
6164 }
6165 return qtrue;
6166 }*/
6167
6168 if ( NPCS.NPC->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_START
6169 || NPCS.NPC->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_HOLD )
6170 {
6171 if ( !TIMER_Done( NPCS.NPC, "draining" ) )
6172 {//FIXME: what do we do if we ran out of power? NPC's can't?
6173 //FIXME: don't keep turning to face enemy or we'll end up spinning around
6174 NPCS.ucmd.buttons |= BUTTON_FORCE_DRAIN;
6175 }
6176 NPC_UpdateAngles( qtrue, qtrue );
6177 return qtrue;
6178 }
6179
6180 if ( NPCS.NPC->client->ps.torsoAnim == BOTH_TAVION_SWORDPOWER )
6181 {
6182 NPCS.NPC->health += Q_irand( 1, 2 );
6183 if ( NPCS.NPC->health > NPCS.NPC->client->ps.stats[STAT_MAX_HEALTH] )
6184 {
6185 NPCS.NPC->health = NPCS.NPC->client->ps.stats[STAT_MAX_HEALTH];
6186 }
6187 NPC_UpdateAngles( qtrue, qtrue );
6188 return qtrue;
6189 }
6190 /*else if ( NPC->client->ps.torsoAnim == BOTH_SCEPTER_HOLD )
6191 {
6192 if ( NPC->client->ps.torsoTimer <= 100 )
6193 {
6194 NPC->s.loopSound = 0;
6195 G_StopEffect( G_EffectIndex( "scepter/beam.efx" ), NPC->weaponModel[1], NPC->genericBolt1, NPC->s.number );
6196 NPC->client->ps.legsTimer = NPC->client->ps.torsoTimer = 0;
6197 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_SCEPTER_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
6198 NPC->painDebounceTime = level.time + NPC->client->ps.torsoTimer;
6199 NPC->client->ps.pm_time = NPC->client->ps.torsoTimer;
6200 NPC->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
6201 VectorClear( NPC->client->ps.velocity );
6202 VectorClear( NPC->client->ps.moveDir );
6203 }
6204 else
6205 {
6206 Tavion_ScepterDamage();
6207 }
6208 if ( NPC->enemy )
6209 {
6210 NPC_FaceEnemy( qtrue );
6211 }
6212 else
6213 {
6214 NPC_UpdateAngles( qtrue, qtrue );
6215 }
6216 return qtrue;
6217 }
6218 else if ( NPC->client->ps.torsoAnim == BOTH_SCEPTER_STOP )
6219 {
6220 if ( NPC->enemy )
6221 {
6222 NPC_FaceEnemy( qtrue );
6223 }
6224 else
6225 {
6226 NPC_UpdateAngles( qtrue, qtrue );
6227 }
6228 return qtrue;
6229 }
6230 else if ( NPC->client->ps.torsoAnim == BOTH_TAVION_SCEPTERGROUND )
6231 {
6232 if ( NPC->client->ps.torsoTimer <= 1200
6233 && !NPC->count )
6234 {
6235 Tavion_ScepterSlam();
6236 NPC->count = 1;
6237 }
6238 NPC_UpdateAngles( qtrue, qtrue );
6239 return qtrue;
6240 }*/
6241
6242 if ( Jedi_CultistDestroyer( NPCS.NPC ) )
6243 {
6244 if ( !NPCS.NPC->takedamage )
6245 {//ready to explode
6246 if ( NPCS.NPC->useDebounceTime <= level.time )
6247 {
6248 //this should damage everyone - FIXME: except other destroyers?
6249 NPCS.NPC->client->playerTeam = NPCTEAM_FREE;//FIXME: will this destroy wampas, tusken & rancors?
6250 NPCS.NPC->splashDamage = 200; // rough match to SP
6251 NPCS.NPC->splashRadius = 512; // see above
6252 WP_Explode( NPCS.NPC );
6253 return qtrue;
6254 }
6255 if ( NPCS.NPC->enemy )
6256 {
6257 NPC_FaceEnemy( qfalse );
6258 }
6259 return qtrue;
6260 }
6261 }
6262 return qfalse;
6263 }
6264
6265 extern void NPC_BSST_Patrol( void );
6266 extern void NPC_BSSniper_Default( void );
NPC_BSJedi_Default(void)6267 void NPC_BSJedi_Default( void )
6268 {
6269 if ( Jedi_InSpecialMove() )
6270 {
6271 return;
6272 }
6273
6274 Jedi_CheckCloak();
6275
6276 if( !NPCS.NPC->enemy )
6277 {//don't have an enemy, look for one
6278 if ( NPCS.NPC->client->NPC_class == CLASS_BOBAFETT )
6279 {
6280 NPC_BSST_Patrol();
6281 }
6282 else
6283 {
6284 Jedi_Patrol();
6285 }
6286 }
6287 else//if ( NPC->enemy )
6288 {//have an enemy
6289 if ( Jedi_WaitingAmbush( NPCS.NPC ) )
6290 {//we were still waiting to drop down - must have had enemy set on me outside my AI
6291 Jedi_Ambush( NPCS.NPC );
6292 }
6293
6294 if ( Jedi_CultistDestroyer( NPCS.NPC )
6295 && !NPCS.NPCInfo->charmedTime )
6296 {//destroyer
6297 //permanent effect
6298 NPCS.NPCInfo->charmedTime = Q3_INFINITE;
6299 NPCS.NPC->client->ps.fd.forcePowersActive |= ( 1 << FP_RAGE );
6300 NPCS.NPC->client->ps.fd.forcePowerDuration[FP_RAGE] = Q3_INFINITE;
6301 //NPC->client->ps.eFlags |= EF_FORCE_DRAINED;
6302 //FIXME: precache me!
6303 NPCS.NPC->s.loopSound = G_SoundIndex( "sound/movers/objects/green_beam_lp2.wav" );//test/charm.wav" );
6304 }
6305
6306 if ( NPCS.NPC->client->NPC_class == CLASS_BOBAFETT )
6307 {
6308 if ( NPCS.NPC->enemy->enemy != NPCS.NPC && NPCS.NPC->health == NPCS.NPC->client->pers.maxHealth && DistanceSquared( NPCS.NPC->r.currentOrigin, NPCS.NPC->enemy->r.currentOrigin )>(800*800) )
6309 {
6310 NPCS.NPCInfo->scriptFlags |= SCF_ALT_FIRE;
6311 Boba_ChangeWeapon( WP_DISRUPTOR );
6312 NPC_BSSniper_Default();
6313 return;
6314 }
6315 }
6316 Jedi_Attack();
6317 //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...
6318 if ( ((!NPCS.ucmd.buttons&&!NPCS.NPC->client->ps.fd.forcePowersActive)||(NPCS.NPC->enemy&&NPCS.NPC->enemy->health<=0)) && NPCS.NPCInfo->enemyCheckDebounceTime < level.time )
6319 {//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
6320 //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?
6321 gentity_t *sav_enemy = NPCS.NPC->enemy;//FIXME: what about NPC->lastEnemy?
6322 gentity_t *newEnemy;
6323
6324 NPCS.NPC->enemy = NULL;
6325 newEnemy = NPC_CheckEnemy( (qboolean)(NPCS.NPCInfo->confusionTime < level.time), qfalse, qfalse );
6326 NPCS.NPC->enemy = sav_enemy;
6327 if ( newEnemy && newEnemy != sav_enemy )
6328 {//picked up a new enemy!
6329 NPCS.NPC->lastEnemy = NPCS.NPC->enemy;
6330 G_SetEnemy( NPCS.NPC, newEnemy );
6331 }
6332 NPCS.NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 1000, 3000 );
6333 }
6334 }
6335 }
6336