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