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