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 "g_headers.h"
24 
25 #include "g_local.h"
26 #include "anims.h"
27 #include "b_local.h"
28 #include "bg_local.h"
29 #include "g_functions.h"
30 #include "wp_saber.h"
31 #include "../../code/qcommon/tri_coll_test.h"
32 
33 #define MAX_SABER_VICTIMS 16
34 static int		victimEntityNum[MAX_SABER_VICTIMS];
35 static float	totalDmg[MAX_SABER_VICTIMS];
36 static vec3_t	dmgDir[MAX_SABER_VICTIMS];
37 static vec3_t	dmgSpot[MAX_SABER_VICTIMS];
38 static float	dmgFraction[MAX_SABER_VICTIMS];
39 static int		hitLoc[MAX_SABER_VICTIMS];
40 static qboolean	hitDismember[MAX_SABER_VICTIMS];
41 static int		hitDismemberLoc[MAX_SABER_VICTIMS];
42 static vec3_t	saberHitLocation, saberHitNormal={0,0,1.0};
43 static float	saberHitFraction;
44 static float	sabersCrossed;
45 static int		saberHitEntity;
46 static int		numVictims = 0;
47 
48 #define SABER_PITCH_HACK 90
49 
50 
51 extern cvar_t	*g_timescale;
52 extern cvar_t	*g_dismemberment;
53 
54 extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType );
55 extern qboolean		G_ClearViewEntity( gentity_t *ent );
56 extern void			G_SetViewEntity( gentity_t *self, gentity_t *viewEntity );
57 extern qboolean G_ControlledByPlayer( gentity_t *self );
58 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
59 extern void CG_ChangeWeapon( int num );
60 extern void G_AngerAlert( gentity_t *self );
61 extern void G_ReflectMissile( gentity_t *ent, gentity_t *missile, vec3_t forward );
62 extern int G_CheckLedgeDive( gentity_t *self, float checkDist, vec3_t checkVel, qboolean tryOpposite, qboolean tryPerp );
63 extern void G_BounceMissile( gentity_t *ent, trace_t *trace );
64 extern qboolean G_PointInBounds( const vec3_t point, const vec3_t mins, const vec3_t maxs );
65 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
66 extern void NPC_UseResponse( gentity_t *self, gentity_t *user, qboolean useWhenDone );
67 extern void WP_FireDreadnoughtBeam( gentity_t *ent );
68 extern void G_MissileImpacted( gentity_t *ent, gentity_t *other, vec3_t impactPos, vec3_t normal, int hitLoc=HL_NONE );
69 extern evasionType_t Jedi_SaberBlockGo( gentity_t *self, usercmd_t *cmd, vec3_t pHitloc, vec3_t phitDir, gentity_t *incoming, float dist = 0.0f );
70 extern int PM_PickAnim( gentity_t *self, int minAnim, int maxAnim );
71 extern void NPC_SetPainEvent( gentity_t *self );
72 extern qboolean PM_SwimmingAnim( int anim );
73 extern qboolean PM_InAnimForSaberMove( int anim, int saberMove );
74 extern qboolean PM_SpinningSaberAnim( int anim );
75 extern qboolean PM_SaberInSpecialAttack( int anim );
76 extern qboolean PM_SaberInAttack( int move );
77 extern qboolean PM_SaberInTransition( int move );
78 extern qboolean PM_SaberInStart( int move );
79 extern qboolean PM_SaberInTransitionAny( int move );
80 extern qboolean PM_SaberInBounce( int move );
81 extern qboolean PM_SaberInParry( int move );
82 extern qboolean PM_SaberInKnockaway( int move );
83 extern qboolean PM_SaberInBrokenParry( int move );
84 extern qboolean PM_SpinningSaberAnim( int anim );
85 extern int PM_SaberBounceForAttack( int move );
86 extern int PM_BrokenParryForAttack( int move );
87 extern int PM_KnockawayForParry( int move );
88 extern qboolean PM_FlippingAnim( int anim );
89 extern qboolean PM_RollingAnim( int anim );
90 extern qboolean PM_CrouchAnim( int anim );
91 extern qboolean PM_SaberInIdle( int move );
92 extern qboolean PM_SaberInReflect( int move );
93 extern qboolean PM_InSpecialJump( int anim );
94 extern qboolean PM_InKnockDown( playerState_t *ps );
95 extern int PM_PowerLevelForSaberAnim( playerState_t *ps );
96 extern void PM_VelocityForSaberMove( playerState_t *ps, vec3_t throwDir );
97 extern qboolean PM_VelocityForBlockedMove( playerState_t *ps, vec3_t throwDir );
98 extern int Jedi_ReCalcParryTime( gentity_t *self, evasionType_t evasionType );
99 extern qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc );
100 extern void Jedi_PlayDeflectSound( gentity_t *self );
101 extern void Jedi_PlayBlockedPushSound( gentity_t *self );
102 extern qboolean Jedi_WaitingAmbush( gentity_t *self );
103 extern void Jedi_Ambush( gentity_t *self );
104 extern qboolean Jedi_SaberBusy( gentity_t *self );
105 
106 void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
107 qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
108 void WP_SaberInFlightReflectCheck( gentity_t *self, usercmd_t *ucmd  );
109 
110 void WP_SaberDrop( gentity_t *self, gentity_t *saber );
111 qboolean WP_SaberLose( gentity_t *self, vec3_t throwDir );
112 void WP_SaberReturn( gentity_t *self, gentity_t *saber );
113 void WP_SaberBlock( gentity_t *saber, vec3_t hitloc, qboolean missleBlock );
114 void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock );
115 void ForceThrow( gentity_t *self, qboolean pull );
116 qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
117 void WP_ForcePowerDrain( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
118 
119 extern cvar_t	*g_saberAutoBlocking;
120 extern cvar_t	*g_saberRealisticCombat;
121 extern int g_crosshairEntNum;
122 
123 int		g_saberFlashTime = 0;
124 vec3_t	g_saberFlashPos = {0,0,0};
125 
126 int forcePowerNeeded[NUM_FORCE_POWERS] =
127 {
128 	0,//FP_HEAL,//instant
129 	10,//FP_LEVITATION,//hold/duration
130 	50,//FP_SPEED,//duration
131 	15,//FP_PUSH,//hold/duration
132 	15,//FP_PULL,//hold/duration
133 	20,//FP_TELEPATHY,//instant
134 	1,//FP_GRIP,//hold/duration - FIXME: 30?
135 	1,//FP_LIGHTNING,//hold/duration
136 	20,//FP_SABERTHROW,
137 	1,//FP_SABER_DEFENSE,
138 	0,//FP_SABER_OFFENSE,
139 	//NUM_FORCE_POWERS
140 };
141 
142 float forceJumpStrength[NUM_FORCE_POWER_LEVELS] =
143 {
144 	JUMP_VELOCITY,//normal jump
145 	420,
146 	590,
147 	840
148 };
149 
150 float forceJumpHeight[NUM_FORCE_POWER_LEVELS] =
151 {
152 	32,//normal jump (+stepheight+crouchdiff = 66)
153 	96,//(+stepheight+crouchdiff = 130)
154 	192,//(+stepheight+crouchdiff = 226)
155 	384//(+stepheight+crouchdiff = 418)
156 };
157 
158 float forceJumpHeightMax[NUM_FORCE_POWER_LEVELS] =
159 {
160 	66,//normal jump (32+stepheight(18)+crouchdiff(24) = 74)
161 	130,//(96+stepheight(18)+crouchdiff(24) = 138)
162 	226,//(192+stepheight(18)+crouchdiff(24) = 234)
163 	418//(384+stepheight(18)+crouchdiff(24) = 426)
164 };
165 
166 float forcePushPullRadius[NUM_FORCE_POWER_LEVELS] =
167 {
168 	0,//none
169 	384,//256,
170 	448,//384,
171 	512
172 };
173 
174 float forcePushCone[NUM_FORCE_POWER_LEVELS] =
175 {
176 	1.0f,//none
177 	1.0f,
178 	0.8f,
179 	0.6f
180 };
181 
182 float forcePullCone[NUM_FORCE_POWER_LEVELS] =
183 {
184 	1.0f,//none
185 	1.0f,
186 	1.0f,
187 	0.8f
188 };
189 
190 float forceSpeedValue[NUM_FORCE_POWER_LEVELS] =
191 {
192 	1.0f,//none
193 	0.75f,
194 	0.5f,
195 	0.25f
196 };
197 
198 float forceSpeedRangeMod[NUM_FORCE_POWER_LEVELS] =
199 {
200 	0.0f,//none
201 	30.0f,
202 	45.0f,
203 	60.0f
204 };
205 
206 float forceSpeedFOVMod[NUM_FORCE_POWER_LEVELS] =
207 {
208 	0.0f,//none
209 	20.0f,
210 	30.0f,
211 	40.0f
212 };
213 
214 int forceGripDamage[NUM_FORCE_POWER_LEVELS] =
215 {
216 	0,//none
217 	0,
218 	6,
219 	9
220 };
221 
222 int mindTrickTime[NUM_FORCE_POWER_LEVELS] =
223 {
224 	0,//none
225 	5000,
226 	10000,
227 	15000
228 };
229 
230 //NOTE: keep in synch with table below!!!
231 int saberThrowDist[NUM_FORCE_POWER_LEVELS] =
232 {
233 	0,//none
234 	256,
235 	400,
236 	400
237 };
238 
239 //NOTE: keep in synch with table above!!!
240 int saberThrowDistSquared[NUM_FORCE_POWER_LEVELS] =
241 {
242 	0,//none
243 	65536,
244 	160000,
245 	160000
246 };
247 
248 int parryDebounce[NUM_FORCE_POWER_LEVELS] =
249 {
250 	1000000,//if don't even have defense, can't use defense!
251 	300,
252 	150,
253 	50
254 };
255 
256 float saberAnimSpeedMod[NUM_FORCE_POWER_LEVELS] =
257 {
258 	0.0f,//if don't even have offense, can't use offense!
259 	0.75f,
260 	1.0f,
261 	2.0f
262 };
263 //SABER INITIALIZATION======================================================================
264 
G_CreateG2AttachedWeaponModel(gentity_t * ent,const char * psWeaponModel)265 void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *psWeaponModel )
266 {
267 	if (!psWeaponModel)
268 	{
269 		assert (psWeaponModel);
270 		return;
271 	}
272 	if ( ent && ent->client && ent->client->NPC_class == CLASS_GALAKMECH )
273 	{//hack for galakmech, no weaponmodel
274 		ent->weaponModel = -1;
275 		return;
276 	}
277 
278 	char weaponModel[MAX_QPATH];
279 	Q_strncpyz(weaponModel, psWeaponModel, sizeof(weaponModel));
280 	if (char *spot = (char*)Q_stristr(weaponModel, ".md3") ) {
281         *spot = 0;
282 		spot = (char*)Q_stristr(weaponModel, "_w");//i'm using the in view weapon array instead of scanning the item list, so put the _w back on
283 		if (!spot&&!Q_stristr(weaponModel, "noweap"))
284 		{
285 			Q_strcat (weaponModel, sizeof(weaponModel), "_w");
286 		}
287 		Q_strcat (weaponModel, sizeof(weaponModel), ".glm");	//and change to ghoul2
288 	}
289 
290 	if ( ent->playerModel == -1 )
291 	{
292 		return;
293 	}
294 	// give us a sabre model
295 	ent->weaponModel = gi.G2API_InitGhoul2Model(ent->ghoul2, weaponModel, G_ModelIndex( weaponModel ), NULL_HANDLE, NULL_HANDLE, 0, 0);
296 	if ( ent->weaponModel != -1 )
297 	{
298 		// attach it to the hand
299 		gi.G2API_AttachG2Model(&ent->ghoul2[ent->weaponModel], &ent->ghoul2[ent->playerModel],
300 					ent->handRBolt, ent->playerModel);
301 		// set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0
302 		gi.G2API_AddBolt(&ent->ghoul2[ent->weaponModel], "*flash");
303 	  	//gi.G2API_SetLodBias( &ent->ghoul2[ent->weaponModel], 0 );
304 	}
305 }
306 
307 //----------------------------------------------------------
G_Throw(gentity_t * targ,vec3_t newDir,float push)308 void G_Throw( gentity_t *targ, vec3_t newDir, float push )
309 //----------------------------------------------------------
310 {
311 	vec3_t	kvel;
312 	float	mass;
313 
314 	if ( targ->physicsBounce > 0 )	//overide the mass
315 	{
316 		mass = targ->physicsBounce;
317 	}
318 	else
319 	{
320 		mass = 200;
321 	}
322 
323 	if ( g_gravity->value > 0 )
324 	{
325 		VectorScale( newDir, g_knockback->value * (float)push / mass * 0.8, kvel );
326 		kvel[2] = newDir[2] * g_knockback->value * (float)push / mass * 1.5;
327 	}
328 	else
329 	{
330 		VectorScale( newDir, g_knockback->value * (float)push / mass, kvel );
331 	}
332 
333 	if ( targ->client )
334 	{
335 		VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity );
336 	}
337 	else if ( targ->s.pos.trType != TR_STATIONARY && targ->s.pos.trType != TR_LINEAR_STOP && targ->s.pos.trType != TR_NONLINEAR_STOP )
338 	{
339 		VectorAdd( targ->s.pos.trDelta, kvel, targ->s.pos.trDelta );
340 		VectorCopy( targ->currentOrigin, targ->s.pos.trBase );
341 		targ->s.pos.trTime = level.time;
342 	}
343 
344 	// set the timer so that the other client can't cancel
345 	// out the movement immediately
346 	if ( targ->client && !targ->client->ps.pm_time )
347 	{
348 		int		t;
349 
350 		t = push * 2;
351 
352 		if ( t < 50 )
353 		{
354 			t = 50;
355 		}
356 		if ( t > 200 )
357 		{
358 			t = 200;
359 		}
360 		targ->client->ps.pm_time = t;
361 		targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
362 	}
363 }
364 
WP_SetSaberModel(gclient_t * client,class_t npcClass)365 int WP_SetSaberModel( gclient_t *client, class_t npcClass )
366 {
367 	if ( client )
368 	{
369 		switch ( npcClass )
370 		{
371 		case CLASS_DESANN://Desann
372 			client->ps.saberModel = "models/weapons2/saber_desann/saber_w.glm";
373 			break;
374 		case CLASS_LUKE://Luke
375 			client->ps.saberModel = "models/weapons2/saber_luke/saber_w.glm";
376 			break;
377 		case CLASS_KYLE://Kyle NPC and player
378 			client->ps.saberModel = "models/weapons2/saber/saber_w.glm";
379 			break;
380 		default://reborn and tavion and everyone else
381 			client->ps.saberModel = "models/weapons2/saber_reborn/saber_w.glm";
382 			break;
383 		}
384 		return ( G_ModelIndex( client->ps.saberModel ) );
385 	}
386 	else
387 	{
388 		switch ( npcClass )
389 		{
390 		case CLASS_DESANN://Desann
391 			return ( G_ModelIndex( "models/weapons2/saber_desann/saber_w.glm" ) );
392 			break;
393 		case CLASS_LUKE://Luke
394 			return ( G_ModelIndex( "models/weapons2/saber_luke/saber_w.glm" ) );
395 			break;
396 		case CLASS_KYLE://Kyle NPC and player
397 			return ( G_ModelIndex( "models/weapons2/saber/saber_w.glm" ) );
398 			break;
399 		default://reborn and tavion and everyone else
400 			return ( G_ModelIndex( "models/weapons2/saber_reborn/saber_w.glm" ) );
401 			break;
402 		}
403 	}
404 }
405 
WP_SaberInitBladeData(gentity_t * ent)406 void WP_SaberInitBladeData( gentity_t *ent )
407 {
408 	gentity_t *saberent;
409 
410 	if ( ent->client )
411 	{
412 		VectorClear( ent->client->renderInfo.muzzlePoint );
413 		VectorClear( ent->client->renderInfo.muzzlePointOld );
414 		//VectorClear( ent->client->renderInfo.muzzlePointNext );
415 		VectorClear( ent->client->renderInfo.muzzleDir );
416 		VectorClear( ent->client->renderInfo.muzzleDirOld );
417 		//VectorClear( ent->client->renderInfo.muzzleDirNext );
418 		ent->client->ps.saberLengthOld = ent->client->ps.saberLength = 0;
419 		ent->client->ps.saberLockEnemy = ENTITYNUM_NONE;
420 		ent->client->ps.saberLockTime = 0;
421 		if ( ent->s.number )
422 		{
423 			if ( ent->client->NPC_class == CLASS_DESANN )
424 			{
425 				ent->client->ps.saberAnimLevel = FORCE_LEVEL_4;
426 			}
427 			else if ( ent->client->NPC_class == CLASS_TAVION )
428 			{
429 				ent->client->ps.saberAnimLevel = FORCE_LEVEL_5;
430 			}
431 			else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && (ent->NPC->rank == RANK_CIVILIAN || ent->NPC->rank == RANK_LT_JG) )
432 			{//grunt and fencer always uses quick attacks
433 				ent->client->ps.saberAnimLevel = FORCE_LEVEL_1;
434 			}
435 			else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && (ent->NPC->rank == RANK_CREWMAN || ent->NPC->rank == RANK_ENSIGN) )
436 			{//acrobat & force-users always use medium attacks
437 				ent->client->ps.saberAnimLevel = FORCE_LEVEL_2;
438 			}
439 			else if ( ent->client->playerTeam == TEAM_ENEMY && ent->client->NPC_class == CLASS_SHADOWTROOPER )
440 			{//shadowtroopers
441 				ent->client->ps.saberAnimLevel = Q_irand( FORCE_LEVEL_1, FORCE_LEVEL_3 );
442 			}
443 			else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && ent->NPC->rank == RANK_LT )
444 			{//boss always starts with strong attacks
445 				ent->client->ps.saberAnimLevel = FORCE_LEVEL_3;
446 			}
447 			else if ( ent->client->NPC_class == CLASS_KYLE )
448 			{
449 				ent->client->ps.saberAnimLevel = g_entities[0].client->ps.saberAnimLevel;
450 			}
451 			else
452 			{//?
453 				ent->client->ps.saberAnimLevel = Q_irand( FORCE_LEVEL_1, FORCE_LEVEL_3 );
454 			}
455 		}
456 		else
457 		{
458 			if ( !ent->client->ps.saberAnimLevel )
459 			{//initialize, but don't reset
460 				ent->client->ps.saberAnimLevel = FORCE_LEVEL_2;
461 			}
462 			cg.saberAnimLevelPending = ent->client->ps.saberAnimLevel;
463 			if ( ent->client->sess.missionStats.weaponUsed[WP_SABER] <= 0 )
464 			{//let missionStats know that we actually do have the saber, even if we never use it
465 				ent->client->sess.missionStats.weaponUsed[WP_SABER] = 1;
466 			}
467 		}
468 		ent->client->ps.saberAttackChainCount = 0;
469 		if ( ent->client->NPC_class == CLASS_DESANN )
470 		{//longer saber
471 			ent->client->ps.saberLengthMax = 48;
472 		}
473 		else if ( ent->client->NPC_class == CLASS_REBORN )
474 		{//shorter saber
475 			ent->client->ps.saberLengthMax = 32;
476 		}
477 		else
478 		{//standard saber length
479 			ent->client->ps.saberLengthMax = 40;
480 		}
481 
482 		if ( ent->client->ps.saberEntityNum <= 0 || ent->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
483 		{
484 			saberent = G_Spawn();
485 			ent->client->ps.saberEntityNum = saberent->s.number;
486 			saberent->classname = "lightsaber";
487 
488 			saberent->s.eType = ET_GENERAL;
489 			saberent->svFlags = SVF_USE_CURRENT_ORIGIN;
490 			saberent->s.weapon = WP_SABER;
491 			saberent->owner = ent;
492 			saberent->s.otherEntityNum = ent->s.number;
493 
494 			saberent->clipmask = MASK_SOLID | CONTENTS_LIGHTSABER;
495 			saberent->contents = CONTENTS_LIGHTSABER;//|CONTENTS_SHOTCLIP;
496 
497 			VectorSet( saberent->mins, -3.0f, -3.0f, -3.0f );
498 			VectorSet( saberent->maxs, 3.0f, 3.0f, 3.0f );
499 			saberent->mass = 10;//necc?
500 
501 			saberent->s.eFlags |= EF_NODRAW;
502 			saberent->svFlags |= SVF_NOCLIENT;
503 /*
504 Ghoul2 Insert Start
505 */
506 			//FIXME: get saberModel from NPCs.cfg for NPCs?
507 			saberent->s.modelindex = WP_SetSaberModel( ent->client, ent->client->NPC_class );
508 			gi.G2API_InitGhoul2Model( saberent->ghoul2, ent->client->ps.saberModel, saberent->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
509 			// set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0
510 			gi.G2API_AddBolt( &saberent->ghoul2[0], "*flash" );
511 			//gi.G2API_SetLodBias( &saberent->ghoul2[0], 0 );
512 
513 /*
514 Ghoul2 Insert End
515 */
516 
517 			ent->client->ps.saberInFlight = qfalse;
518 			ent->client->ps.saberEntityDist = 0;
519 			ent->client->ps.saberEntityState = SES_LEAVING;
520 
521 			ent->client->ps.saberMove = 0;
522 
523 			//FIXME: need a think function to create alerts when turned on or is on, etc.
524 		}
525 	}
526 	else
527 	{
528 		ent->client->ps.saberEntityNum = ENTITYNUM_NONE;
529 		ent->client->ps.saberInFlight = qfalse;
530 		ent->client->ps.saberEntityDist = 0;
531 		ent->client->ps.saberEntityState = SES_LEAVING;
532 	}
533 }
534 
WP_SaberUpdateOldBladeData(gentity_t * ent)535 void WP_SaberUpdateOldBladeData( gentity_t *ent )
536 {
537 	if ( ent->client )
538 	{
539 		VectorCopy( ent->client->renderInfo.muzzlePoint, ent->client->renderInfo.muzzlePointOld );
540 		VectorCopy( ent->client->renderInfo.muzzleDir, ent->client->renderInfo.muzzleDirOld );
541 		if ( ent->client->ps.saberLengthOld <= 0 && ent->client->ps.saberLength > 0 )
542 		{//just turned on
543 			//do sound event
544 			vec3_t	saberOrg;
545 			VectorCopy( g_entities[ent->client->ps.saberEntityNum].currentOrigin, saberOrg );
546 			AddSoundEvent( ent, saberOrg, 256, AEL_SUSPICIOUS );
547 		}
548 		ent->client->ps.saberLengthOld = ent->client->ps.saberLength;
549 	}
550 }
551 
552 
553 
554 //SABER DAMAGE==============================================================================
555 //SABER DAMAGE==============================================================================
556 //SABER DAMAGE==============================================================================
557 //SABER DAMAGE==============================================================================
558 //SABER DAMAGE==============================================================================
559 //SABER DAMAGE==============================================================================
WPDEBUG_SaberColor(saber_colors_t saberColor)560 int WPDEBUG_SaberColor( saber_colors_t saberColor )
561 {
562 	switch( (int)(saberColor) )
563 	{
564 		case SABER_RED:
565 			return 0x000000ff;
566 			break;
567 		case SABER_ORANGE:
568 			return 0x000088ff;
569 			break;
570 		case SABER_YELLOW:
571 			return 0x0000ffff;
572 			break;
573 		case SABER_GREEN:
574 			return 0x0000ff00;
575 			break;
576 		case SABER_BLUE:
577 			return 0x00ff0000;
578 			break;
579 		case SABER_PURPLE:
580 			return 0x00ff00ff;
581 			break;
582 		default:
583 			return 0x00ffffff;//white
584 			break;
585 	}
586 }
587 
WP_GetSaberDeflectionAngle(gentity_t * attacker,gentity_t * defender)588 qboolean WP_GetSaberDeflectionAngle( gentity_t *attacker, gentity_t *defender )
589 {
590 	vec3_t	temp, att_SaberBase, att_StartPos, saberMidNext, att_HitDir, att_HitPos, def_BladeDir;
591 	float	att_SaberHitLength, hitDot;
592 
593 	if ( !attacker || !attacker->client || attacker->client->ps.saberInFlight || attacker->client->ps.saberLength <= 0 )
594 	{
595 		return qfalse;
596 	}
597 	if ( !defender || !defender->client || defender->client->ps.saberInFlight || defender->client->ps.saberLength <= 0 )
598 	{
599 		return qfalse;
600 	}
601 
602 	attacker->client->ps.saberBounceMove = LS_NONE;
603 
604 	//get the attacker's saber base pos at time of impact
605 	VectorSubtract( attacker->client->renderInfo.muzzlePoint, attacker->client->renderInfo.muzzlePointOld, temp );
606 	VectorMA( attacker->client->renderInfo.muzzlePointOld, saberHitFraction, temp, att_SaberBase );
607 
608 	//get the position along the length of the blade where the hit occured
609 	att_SaberHitLength = Distance( saberHitLocation, att_SaberBase )/attacker->client->ps.saberLength;
610 
611 	//now get the start of that midpoint in the swing and the actual impact point in the swing (shouldn't the latter just be saberHitLocation?)
612 	VectorMA( attacker->client->renderInfo.muzzlePointOld, att_SaberHitLength, attacker->client->renderInfo.muzzleDirOld, att_StartPos );
613 	VectorMA( attacker->client->renderInfo.muzzlePoint, att_SaberHitLength, attacker->client->renderInfo.muzzleDir, saberMidNext );
614 	VectorSubtract( saberMidNext, att_StartPos, att_HitDir );
615 	VectorMA( att_StartPos, saberHitFraction, att_HitDir, att_HitPos );
616 	VectorNormalize( att_HitDir );
617 
618 	//get the defender's saber dir at time of impact
619 	VectorSubtract( defender->client->renderInfo.muzzleDirOld, defender->client->renderInfo.muzzleDir, temp );
620 	VectorMA( defender->client->renderInfo.muzzleDirOld, saberHitFraction, temp, def_BladeDir );
621 
622 	//now compare
623 	hitDot = DotProduct( att_HitDir, def_BladeDir );
624 	if ( hitDot < 0.25f && hitDot > -0.25f )
625 	{//hit pretty much perpendicular, pop straight back
626 		attacker->client->ps.saberBounceMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove );
627 		return qfalse;
628 	}
629 	else
630 	{//a deflection
631 		vec3_t	att_Right, att_Up, att_DeflectionDir;
632 		float	swingRDot, swingUDot;
633 
634 		//get the direction of the deflection
635 		VectorScale( def_BladeDir, hitDot, att_DeflectionDir );
636 		//get our bounce straight back direction
637 		VectorScale( att_HitDir, -1.0f, temp );
638 		//add the bounce back and deflection
639 		VectorAdd( att_DeflectionDir, temp, att_DeflectionDir );
640 		//normalize the result to determine what direction our saber should bounce back toward
641 		VectorNormalize( att_DeflectionDir );
642 
643 		//need to know the direction of the deflectoin relative to the attacker's facing
644 		VectorSet( temp, 0, attacker->client->ps.viewangles[YAW], 0 );//presumes no pitch!
645 		AngleVectors( temp, NULL, att_Right, att_Up );
646 		swingRDot = DotProduct( att_Right, att_DeflectionDir );
647 		swingUDot = DotProduct( att_Up, att_DeflectionDir );
648 
649 		if ( swingRDot > 0.25f )
650 		{//deflect to right
651 			if ( swingUDot > 0.25f )
652 			{//deflect to top
653 				attacker->client->ps.saberBounceMove = LS_D1_TR;
654 			}
655 			else if ( swingUDot < -0.25f )
656 			{//deflect to bottom
657 				attacker->client->ps.saberBounceMove = LS_D1_BR;
658 			}
659 			else
660 			{//deflect horizontally
661 				attacker->client->ps.saberBounceMove = LS_D1__R;
662 			}
663 		}
664 		else if ( swingRDot < -0.25f )
665 		{//deflect to left
666 			if ( swingUDot > 0.25f )
667 			{//deflect to top
668 				attacker->client->ps.saberBounceMove = LS_D1_TL;
669 			}
670 			else if ( swingUDot < -0.25f )
671 			{//deflect to bottom
672 				attacker->client->ps.saberBounceMove = LS_D1_BL;
673 			}
674 			else
675 			{//deflect horizontally
676 				attacker->client->ps.saberBounceMove = LS_D1__L;
677 			}
678 		}
679 		else
680 		{//deflect in middle
681 			if ( swingUDot > 0.25f )
682 			{//deflect to top
683 				attacker->client->ps.saberBounceMove = LS_D1_T_;
684 			}
685 			else if ( swingUDot < -0.25f )
686 			{//deflect to bottom
687 				attacker->client->ps.saberBounceMove = LS_D1_B_;
688 			}
689 			else
690 			{//deflect horizontally?  Well, no such thing as straight back in my face, so use top
691 				if ( swingRDot > 0 )
692 				{
693 					attacker->client->ps.saberBounceMove = LS_D1_TR;
694 				}
695 				else if ( swingRDot < 0 )
696 				{
697 					attacker->client->ps.saberBounceMove = LS_D1_TL;
698 				}
699 				else
700 				{
701 					attacker->client->ps.saberBounceMove = LS_D1_T_;
702 				}
703 			}
704 		}
705 #ifndef FINAL_BUILD
706 		if ( d_saberCombat->integer )
707 		{
708 			gi.Printf( S_COLOR_BLUE"%s deflected from %s to %s\n", attacker->targetname, saberMoveData[attacker->client->ps.saberMove].name, saberMoveData[attacker->client->ps.saberBounceMove].name );
709 		}
710 #endif
711 		return qtrue;
712 	}
713 }
714 
715 
WP_SaberClearDamageForEntNum(int entityNum)716 void WP_SaberClearDamageForEntNum( int entityNum )
717 {
718 #ifndef FINAL_BUILD
719 	if ( d_saberCombat->integer )
720 	{
721 		if ( entityNum )
722 		{
723 			Com_Printf( "clearing damage for entnum %d\n", entityNum );
724 		}
725 	}
726 #endif// FINAL_BUILD
727 	if ( g_saberRealisticCombat->integer > 1 )
728 	{
729 		return;
730 	}
731 	for ( int i = 0; i < numVictims; i++ )
732 	{
733 		if ( victimEntityNum[i] == entityNum )
734 		{
735 			totalDmg[i] = 0;//no damage
736 			hitLoc[i] = HL_NONE;
737 			hitDismemberLoc[i] = HL_NONE;
738 			hitDismember[i] = qfalse;
739 			victimEntityNum[i] = ENTITYNUM_NONE;//like we never hit him
740 		}
741 	}
742 }
743 
744 extern float damageModifier[];
745 extern float hitLocHealthPercentage[];
WP_SaberApplyDamage(gentity_t * ent,float baseDamage,int baseDFlags,qboolean brokenParry)746 qboolean WP_SaberApplyDamage( gentity_t *ent, float baseDamage, int baseDFlags, qboolean brokenParry )
747 {
748 	qboolean	didDamage = qfalse;
749 	gentity_t	*victim;
750 	int			dFlags = baseDFlags;
751 	float		maxDmg;
752 
753 
754 	if ( !numVictims )
755 	{
756 		return qfalse;
757 	}
758 	for ( int i = 0; i < numVictims; i++ )
759 	{
760 		dFlags = baseDFlags|DAMAGE_DEATH_KNOCKBACK|DAMAGE_NO_HIT_LOC;
761 		if ( victimEntityNum[i] != ENTITYNUM_NONE && &g_entities[victimEntityNum[i]] != NULL )
762 		{	// Don't bother with this damage if the fraction is higher than the saber's fraction
763 			if ( dmgFraction[i] < saberHitFraction || brokenParry )
764 			{
765 				victim = &g_entities[victimEntityNum[i]];
766 				if ( !victim )
767 				{
768 					continue;
769 				}
770 
771 				if ( victim->e_DieFunc == dieF_maglock_die )
772 				{//*sigh*, special check for maglocks
773 					vec3_t testFrom;
774 					if ( ent->client->ps.saberInFlight )
775 					{
776 						VectorCopy( g_entities[ent->client->ps.saberEntityNum].currentOrigin, testFrom );
777 					}
778 					else
779 					{
780 						VectorCopy( ent->currentOrigin, testFrom );
781 					}
782 					testFrom[2] = victim->currentOrigin[2];
783 					trace_t testTrace;
784 					gi.trace( &testTrace, testFrom, vec3_origin, vec3_origin, victim->currentOrigin, ent->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
785 					if ( testTrace.entityNum != victim->s.number )
786 					{//can only damage maglocks if have a clear trace to the thing's origin
787 						continue;
788 					}
789 				}
790 				if ( totalDmg[i] > 0 )
791 				{//actually want to do *some* damage here
792 					if ( victim->s.weapon == WP_SABER && victim->client && !g_saberRealisticCombat->integer )
793 					{//dmg vs other saber fighters is modded by hitloc and capped
794 						totalDmg[i] *= damageModifier[hitLoc[i]];
795 						if ( hitLoc[i] == HL_NONE )
796 						{
797 							maxDmg = 33*baseDamage;
798 						}
799 						else
800 						{
801 							maxDmg = 50*hitLocHealthPercentage[hitLoc[i]]*baseDamage;//*victim->client->ps.stats[STAT_MAX_HEALTH]*2.0f;
802 						}
803 						if ( maxDmg < totalDmg[i] )
804 						{
805 							totalDmg[i] = maxDmg;
806 						}
807 						//dFlags |= DAMAGE_NO_HIT_LOC;
808 					}
809 					//clamp the dmg
810 					if ( victim->s.weapon != WP_SABER )
811 					{//clamp the dmg between 25 and maxhealth
812 						/*
813 						if ( totalDmg[i] > victim->max_health )
814 						{
815 							totalDmg[i] = victim->max_health;
816 						}
817 						else */if ( totalDmg[i] < 25 )
818 						{
819 							totalDmg[i] = 25;
820 						}
821 						if ( totalDmg[i] > 100 )//+(50*g_spskill->integer) )
822 						{//clamp using same adjustment as in NPC_Begin
823 							totalDmg[i] = 100;//+(50*g_spskill->integer);
824 						}
825 					}
826 					else
827 					{//clamp the dmg between 5 and 100
828 						if ( !victim->s.number && totalDmg[i] > 50 )
829 						{//never do more than half full health damage to player
830 							//prevents one-hit kills
831 							totalDmg[i] = 50;
832 						}
833 						else if ( totalDmg[i] > 100 )
834 						{
835 							totalDmg[i] = 100;
836 						}
837 						else
838 						{
839 							if ( totalDmg[i] < 5 )
840 							{
841 								totalDmg[i] = 5;
842 							}
843 						}
844 					}
845 
846 					if ( totalDmg[i] > 0 )
847 					{
848 						didDamage = qtrue;
849 						if( victim->client )
850 						{
851 							if ( victim->client->ps.pm_time > 0 && victim->client->ps.pm_flags & PMF_TIME_KNOCKBACK && victim->client->ps.velocity[2] > 0 )
852 							{//already being knocked around
853 								dFlags |= DAMAGE_NO_KNOCKBACK;
854 							}
855 							if ( g_dismemberment->integer >= 11381138 || g_saberRealisticCombat->integer )
856 							{
857 								dFlags |= DAMAGE_DISMEMBER;
858 								if ( hitDismember[i] )
859 								{
860 									victim->client->dismembered = qfalse;
861 								}
862 							}
863 							else if ( hitDismember[i] )
864 							{
865 								dFlags |= DAMAGE_DISMEMBER;
866 							}
867 							if ( baseDamage <= 1.0f )
868 							{//very mild damage
869 								if ( victim->s.number == 0 || victim->client->ps.weapon == WP_SABER || victim->client->NPC_class == CLASS_GALAKMECH )
870 								{//if it's the player or a saber-user, don't kill them with this blow
871 									dFlags |= DAMAGE_NO_KILL;
872 								}
873 							}
874 						}
875 						else
876 						{
877 							if ( victim->takedamage )
878 							{//some other breakable thing
879 								//create a flash here
880 								g_saberFlashTime = level.time-50;
881 								VectorCopy( dmgSpot[i], g_saberFlashPos );
882 							}
883 						}
884 						//victim->hitLoc = hitLoc[i];
885 
886 						dFlags |= DAMAGE_NO_KNOCKBACK;//okay, let's try no knockback whatsoever...
887 						dFlags &= ~DAMAGE_DEATH_KNOCKBACK;
888 						if ( g_saberRealisticCombat->integer )
889 						{
890 							dFlags |= DAMAGE_NO_KNOCKBACK;
891 							dFlags &= ~DAMAGE_DEATH_KNOCKBACK;
892 							dFlags &= ~DAMAGE_NO_KILL;
893 						}
894 						if ( ent->client && !ent->s.number )
895 						{
896 							switch( hitLoc[i] )
897 							{
898 							case HL_FOOT_RT:
899 							case HL_FOOT_LT:
900 							case HL_LEG_RT:
901 							case HL_LEG_LT:
902 								ent->client->sess.missionStats.legAttacksCnt++;
903 								break;
904 							case HL_WAIST:
905 							case HL_BACK_RT:
906 							case HL_BACK_LT:
907 							case HL_BACK:
908 							case HL_CHEST_RT:
909 							case HL_CHEST_LT:
910 							case HL_CHEST:
911 								ent->client->sess.missionStats.torsoAttacksCnt++;
912 								break;
913 							case HL_ARM_RT:
914 							case HL_ARM_LT:
915 							case HL_HAND_RT:
916 							case HL_HAND_LT:
917 								ent->client->sess.missionStats.armAttacksCnt++;
918 								break;
919 							default:
920 								ent->client->sess.missionStats.otherAttacksCnt++;
921 								break;
922 							}
923 						}
924 						G_Damage( victim, ent, ent, dmgDir[i], dmgSpot[i], ceil(totalDmg[i]), dFlags, MOD_SABER, hitDismemberLoc[i] );
925 #ifndef FINAL_BUILD
926 						if ( d_saberCombat->integer )
927 						{
928 							gi.Printf( S_COLOR_RED"damage: %4.2f, hitLoc %d\n", totalDmg[i], hitLoc[i] );
929 						}
930 #endif
931 						//do the effect
932 						//G_PlayEffect( G_EffectIndex( "blood_sparks" ), dmgSpot[i], dmgDir[i] );
933 						if ( ent->s.number == 0 )
934 						{
935 							AddSoundEvent( victim->owner, dmgSpot[i], 256, AEL_DISCOVERED );
936 							AddSightEvent( victim->owner, dmgSpot[i], 512, AEL_DISCOVERED, 50 );
937 						}
938 						if ( ent->client )
939 						{
940 							if ( ent->enemy && ent->enemy == victim )
941 							{//just so Jedi knows that he hit his enemy
942 								ent->client->ps.saberEventFlags |= SEF_HITENEMY;
943 							}
944 							else
945 							{
946 								ent->client->ps.saberEventFlags |= SEF_HITOBJECT;
947 							}
948 						}
949 					}
950 				}
951 			}
952 		}
953 	}
954 	return didDamage;
955 }
956 
WP_SaberDamageAdd(float trDmg,int trVictimEntityNum,vec3_t trDmgDir,vec3_t trDmgSpot,float dmg,float fraction,int trHitLoc,qboolean trDismember,int trDismemberLoc)957 void WP_SaberDamageAdd( float trDmg, int trVictimEntityNum, vec3_t trDmgDir, vec3_t trDmgSpot, float dmg, float fraction, int trHitLoc, qboolean trDismember, int trDismemberLoc )
958 {
959 	int curVictim = 0;
960 	int i;
961 
962 	if ( trVictimEntityNum < 0 || trVictimEntityNum >= ENTITYNUM_WORLD )
963 	{
964 		return;
965 	}
966 	if ( trDmg * dmg < 10.0f )
967 	{//too piddly an amount of damage to really count?
968 		//FIXME: but already did the effect, didn't we... sigh...
969 		//return;
970 	}
971 	if ( trDmg )
972 	{//did some damage to something
973 		for ( i = 0; i < numVictims; i++ )
974 		{
975 			if ( victimEntityNum[i] == trVictimEntityNum )
976 			{//already hit this guy before
977 				curVictim = i;
978 				break;
979 			}
980 		}
981 		if ( i == numVictims )
982 		{//haven't hit his guy before
983 			if ( numVictims + 1 >= MAX_SABER_VICTIMS )
984 			{//can't add another victim at this time
985 				return;
986 			}
987 			//add a new victim to the list
988 			curVictim = numVictims;
989 			victimEntityNum[numVictims++] = trVictimEntityNum;
990 		}
991 
992 		float addDmg = trDmg*dmg;
993 		if ( trHitLoc!=HL_NONE && (hitLoc[curVictim]==HL_NONE||hitLocHealthPercentage[trHitLoc]>hitLocHealthPercentage[hitLoc[curVictim]]) )
994 		{//this hitLoc is more critical than the previous one this frame
995 			hitLoc[curVictim] = trHitLoc;
996 		}
997 
998 		totalDmg[curVictim] += addDmg;
999 		if ( !VectorLengthSquared( dmgDir[curVictim] ) )
1000 		{
1001 			VectorCopy( trDmgDir, dmgDir[curVictim] );
1002 		}
1003 		if ( !VectorLengthSquared( dmgSpot[curVictim] ) )
1004 		{
1005 			VectorCopy( trDmgSpot, dmgSpot[curVictim] );
1006 		}
1007 
1008 		// Make sure we keep track of the fraction.  Why?
1009 		// Well, if the saber hits something that stops it, the damage isn't done past that point.
1010 		dmgFraction[curVictim] = fraction;
1011 		if ( (trDismemberLoc != HL_NONE && hitDismemberLoc[curVictim] == HL_NONE)
1012 			|| (!hitDismember[curVictim] && trDismember) )
1013 		{//either this is the first dismember loc we got or we got a loc before, but it wasn't a dismember loc, so take the new one
1014 			hitDismemberLoc[curVictim] = trDismemberLoc;
1015 		}
1016 		if ( trDismember )
1017 		{//we scored a dismemberment hit...
1018 			hitDismember[curVictim] = trDismember;
1019 		}
1020 	}
1021 }
1022 
1023 /*
1024 WP_SabersIntersect
1025 
1026 Breaks the two saber paths into 2 tris each and tests each tri for the first saber path against each of the other saber path's tris
1027 
1028 FIXME: subdivide the arc into a consistant increment
1029 FIXME: test the intersection to see if the sabers really did intersect (weren't going in the same direction and/or passed through same point at different times)?
1030 */
1031 extern qboolean tri_tri_intersect(vec3_t V0,vec3_t V1,vec3_t V2,vec3_t U0,vec3_t U1,vec3_t U2);
WP_SabersIntersect(gentity_t * ent1,gentity_t * ent2,qboolean checkDir)1032 qboolean WP_SabersIntersect( gentity_t *ent1, gentity_t *ent2, qboolean checkDir )
1033 {
1034 	vec3_t	saberBase1, saberTip1, saberBaseNext1, saberTipNext1;
1035 	vec3_t	saberBase2, saberTip2, saberBaseNext2, saberTipNext2;
1036 	vec3_t	dir;
1037 
1038 	/*
1039 #ifndef FINAL_BUILD
1040 	if ( d_saberCombat->integer )
1041 	{
1042 		gi.Printf( S_COLOR_GREEN"Doing precise saber intersection check\n" );
1043 	}
1044 #endif
1045 	*/
1046 
1047 	if ( !ent1 || !ent2 )
1048 	{
1049 		return qfalse;
1050 	}
1051 	if ( !ent1->client || !ent2->client )
1052 	{
1053 		return qfalse;
1054 	}
1055 	if ( ent1->client->ps.saberLength <= 0 || ent2->client->ps.saberLength <= 0 )
1056 	{
1057 		return qfalse;
1058 	}
1059 
1060 	//if ( ent1->client->ps.saberInFlight )
1061 	{
1062 		VectorCopy( ent1->client->renderInfo.muzzlePointOld, saberBase1 );
1063 		VectorCopy( ent1->client->renderInfo.muzzlePoint, saberBaseNext1 );
1064 
1065 		VectorSubtract( ent1->client->renderInfo.muzzlePoint, ent1->client->renderInfo.muzzlePointOld, dir );
1066 		VectorNormalize( dir );
1067 		VectorMA( saberBaseNext1, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext1 );
1068 
1069 		VectorMA( saberBase1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDirOld, saberTip1 );
1070 		VectorMA( saberBaseNext1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDir, saberTipNext1 );
1071 
1072 		VectorSubtract( saberTipNext1, saberTip1, dir );
1073 		VectorNormalize( dir );
1074 		VectorMA( saberTipNext1, SABER_EXTRAPOLATE_DIST, dir, saberTipNext1 );
1075 	}
1076 	/*
1077 	else
1078 	{
1079 		VectorCopy( ent1->client->renderInfo.muzzlePoint, saberBase1 );
1080 		VectorCopy( ent1->client->renderInfo.muzzlePointNext, saberBaseNext1 );
1081 		VectorMA( saberBase1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDir, saberTip1 );
1082 		VectorMA( saberBaseNext1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDirNext, saberTipNext1 );
1083 	}
1084 	*/
1085 
1086 	//if ( ent2->client->ps.saberInFlight )
1087 	{
1088 		VectorCopy( ent2->client->renderInfo.muzzlePointOld, saberBase2 );
1089 		VectorCopy( ent2->client->renderInfo.muzzlePoint, saberBaseNext2 );
1090 
1091 		VectorSubtract( ent2->client->renderInfo.muzzlePoint, ent2->client->renderInfo.muzzlePointOld, dir );
1092 		VectorNormalize( dir );
1093 		VectorMA( saberBaseNext2, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext2 );
1094 
1095 		VectorMA( saberBase2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDirOld, saberTip2 );
1096 		VectorMA( saberBaseNext2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDir, saberTipNext2 );
1097 
1098 		VectorSubtract( saberTipNext2, saberTip2, dir );
1099 		VectorNormalize( dir );
1100 		VectorMA( saberTipNext2, SABER_EXTRAPOLATE_DIST, dir, saberTipNext2 );
1101 	}
1102 	/*
1103 	else
1104 	{
1105 		VectorCopy( ent2->client->renderInfo.muzzlePoint, saberBase2 );
1106 		VectorCopy( ent2->client->renderInfo.muzzlePointNext, saberBaseNext2 );
1107 		VectorMA( saberBase2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDir, saberTip2 );
1108 		VectorMA( saberBaseNext2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDirNext, saberTipNext2 );
1109 	}
1110 	*/
1111 	if ( checkDir )
1112 	{//check the direction of the two swings to make sure the sabers are swinging towards each other
1113 		vec3_t saberDir1, saberDir2;
1114 
1115 		VectorSubtract( saberTipNext1, saberTip1, saberDir1 );
1116 		VectorSubtract( saberTipNext2, saberTip2, saberDir2 );
1117 		VectorNormalize( saberDir1 );
1118 		VectorNormalize( saberDir2 );
1119 		if ( DotProduct( saberDir1, saberDir2 ) > 0.6f )
1120 		{//sabers moving in same dir, probably didn't actually hit
1121 			return qfalse;
1122 		}
1123 		//now check orientation of sabers, make sure they're not parallel or close to it
1124 		float dot = DotProduct( ent1->client->renderInfo.muzzleDir, ent2->client->renderInfo.muzzleDir );
1125 		if ( dot > 0.9f || dot < -0.9f )
1126 		{//too parallel to really block effectively?
1127 			return qfalse;
1128 		}
1129 	}
1130 
1131 	if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberBaseNext2 ) )
1132 	{
1133 		return qtrue;
1134 	}
1135 	if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberTipNext2 ) )
1136 	{
1137 		return qtrue;
1138 	}
1139 	if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberBaseNext2 ) )
1140 	{
1141 		return qtrue;
1142 	}
1143 	if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberTipNext2 ) )
1144 	{
1145 		return qtrue;
1146 	}
1147 	return qfalse;
1148 }
1149 
WP_SabersDistance(gentity_t * ent1,gentity_t * ent2)1150 float WP_SabersDistance( gentity_t *ent1, gentity_t *ent2 )
1151 {
1152 	vec3_t	saberBaseNext1, saberTipNext1, saberPoint1;
1153 	vec3_t	saberBaseNext2, saberTipNext2, saberPoint2;
1154 
1155 	/*
1156 #ifndef FINAL_BUILD
1157 	if ( d_saberCombat->integer )
1158 	{
1159 		gi.Printf( S_COLOR_GREEN"Doing precise saber intersection check\n" );
1160 	}
1161 #endif
1162 	*/
1163 
1164 	if ( !ent1 || !ent2 )
1165 	{
1166 		return qfalse;
1167 	}
1168 	if ( !ent1->client || !ent2->client )
1169 	{
1170 		return qfalse;
1171 	}
1172 	if ( ent1->client->ps.saberLength <= 0 || ent2->client->ps.saberLength <= 0 )
1173 	{
1174 		return qfalse;
1175 	}
1176 
1177 	//if ( ent1->client->ps.saberInFlight )
1178 	{
1179 		VectorCopy( ent1->client->renderInfo.muzzlePoint, saberBaseNext1 );
1180 		VectorMA( saberBaseNext1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDir, saberTipNext1 );
1181 	}
1182 	/*
1183 	else
1184 	{
1185 		VectorCopy( ent1->client->renderInfo.muzzlePointNext, saberBaseNext1 );
1186 		VectorMA( saberBaseNext1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDirNext, saberTipNext1 );
1187 	}
1188 	*/
1189 
1190 	//if ( ent2->client->ps.saberInFlight )
1191 	{
1192 		VectorCopy( ent2->client->renderInfo.muzzlePoint, saberBaseNext2 );
1193 		VectorMA( saberBaseNext2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDir, saberTipNext2 );
1194 	}
1195 	/*
1196 	else
1197 	{
1198 		VectorCopy( ent2->client->renderInfo.muzzlePointNext, saberBaseNext2 );
1199 		VectorMA( saberBaseNext2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDirNext, saberTipNext2 );
1200 	}
1201 	*/
1202 
1203 	float sabersDist = ShortestLineSegBewteen2LineSegs( saberBaseNext1, saberTipNext1, saberBaseNext2, saberTipNext2, saberPoint1, saberPoint2 );
1204 
1205 	//okay, this is a super hack, but makes saber collisions look better from the player point of view
1206 	/*
1207 	if ( sabersDist < 16.0f )
1208 	{
1209 		vec3_t	saberDistDir, saberMidPoint, camLookDir;
1210 
1211 		VectorSubtract( saberPoint2, saberPoint1, saberDistDir );
1212 		VectorMA( saberPoint1, 0.5f, saberDistDir, saberMidPoint );
1213 		VectorSubtract( saberMidPoint, cg.refdef.vieworg, camLookDir );
1214 		VectorNormalize( saberDistDir );
1215 		VectorNormalize( camLookDir );
1216 		float dot = fabs(DotProduct( camLookDir, saberDistDir ));
1217 		sabersDist -= 8.0f*dot;
1218 		if ( sabersDist < 0.0f )
1219 		{
1220 			sabersDist = 0.0f;
1221 		}
1222 	}
1223 	*/
1224 
1225 #ifndef FINAL_BUILD
1226 	if ( d_saberCombat->integer > 2 )
1227 	{
1228 		G_DebugLine( saberPoint1, saberPoint2, FRAMETIME, 0x00ffffff, qtrue );
1229 	}
1230 #endif
1231 	return sabersDist;
1232 }
1233 
WP_SabersIntersection(gentity_t * ent1,gentity_t * ent2,vec3_t intersect)1234 qboolean WP_SabersIntersection( gentity_t *ent1, gentity_t *ent2, vec3_t intersect )
1235 {
1236 	vec3_t	saberBaseNext1, saberTipNext1, saberPoint1;
1237 	vec3_t	saberBaseNext2, saberTipNext2, saberPoint2;
1238 
1239 	if ( !ent1 || !ent2 )
1240 	{
1241 		return qfalse;
1242 	}
1243 	if ( !ent1->client || !ent2->client )
1244 	{
1245 		return qfalse;
1246 	}
1247 	if ( ent1->client->ps.saberLength <= 0 || ent2->client->ps.saberLength <= 0 )
1248 	{
1249 		return qfalse;
1250 	}
1251 
1252 	VectorCopy( ent1->client->renderInfo.muzzlePoint, saberBaseNext1 );
1253 	VectorMA( saberBaseNext1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDir, saberTipNext1 );
1254 
1255 	VectorCopy( ent2->client->renderInfo.muzzlePoint, saberBaseNext2 );
1256 	VectorMA( saberBaseNext2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDir, saberTipNext2 );
1257 
1258 	ShortestLineSegBewteen2LineSegs( saberBaseNext1, saberTipNext1, saberBaseNext2, saberTipNext2, saberPoint1, saberPoint2 );
1259 	VectorAdd( saberPoint1, saberPoint2, intersect );
1260 	VectorScale( intersect, 0.5, intersect );
1261 	return qtrue;
1262 }
1263 
1264 char *hit_blood_sparks = "blood_sparks";
1265 char *hit_sparks = "saber_cut";
1266 
1267 extern qboolean G_GetHitLocFromSurfName( gentity_t *ent, const char *surfName, int *hitLoc, vec3_t point, vec3_t dir, vec3_t bladeDir, int mod );
WP_SaberDamageEffects(trace_t * tr,const vec3_t start,float length,float dmg,vec3_t dmgDir,vec3_t bladeDir,int enemyTeam)1268 qboolean WP_SaberDamageEffects( trace_t *tr, const vec3_t start, float length, float dmg, vec3_t dmgDir, vec3_t bladeDir, int enemyTeam )
1269 {
1270 
1271 	int			hitEntNum[MAX_G2_COLLISIONS] = {ENTITYNUM_NONE};
1272 	float		hitEntDmgAdd[MAX_G2_COLLISIONS] = {0};
1273 	float		hitEntDmgSub[MAX_G2_COLLISIONS] = {0};
1274 	vec3_t		hitEntPoint[MAX_G2_COLLISIONS];
1275 	vec3_t		hitEntDir[MAX_G2_COLLISIONS];
1276 	float		hitEntStartFrac[MAX_G2_COLLISIONS] = {0};
1277 	int			trHitLoc = HL_NONE;
1278 	int			trDismemberLoc = HL_NONE;
1279 	qboolean	trDismember = qfalse;
1280 	int			i,z;
1281 	int			numHitEnts = 0;
1282 	float		distFromStart,doDmg;
1283 	char		*hitEffect;
1284 	gentity_t	*hitEnt;
1285 
1286 	for (z=0; z < MAX_G2_COLLISIONS; z++)
1287 	{
1288 		if ( tr->G2CollisionMap[z].mEntityNum == -1 )
1289 		{//actually, completely break out of this for loop since nothing after this in the aray should ever be valid either
1290 			continue;//break;//
1291 		}
1292 
1293 		CCollisionRecord &coll	= tr->G2CollisionMap[z];
1294 		//distFromStart			= Distance( start, coll.mCollisionPosition );
1295 		distFromStart			= coll.mDistance;
1296 
1297 		/*
1298 		//FIXME: (distFromStart/length) is not guaranteed to be from 0 to 1... *sigh*...
1299 		if ( length && saberHitFraction < 1.0f && (distFromStart/length) < 1.0f && (distFromStart/length) > saberHitFraction )
1300 		{//a saber was hit before this point, don't count it
1301 #ifndef FINAL_BUILD
1302 			if ( d_saberCombat->integer )
1303 			{
1304 				gi.Printf( S_COLOR_MAGENTA"rejecting G2 collision- %4.2f farther than saberHitFraction %4.2f\n", (distFromStart/length), saberHitFraction  );
1305 			}
1306 #endif
1307 			continue;
1308 		}
1309 		*/
1310 
1311 		for ( i = 0; i < numHitEnts; i++ )
1312 		{
1313 			if ( hitEntNum[i] != ENTITYNUM_NONE )
1314 			{//we hit this ent before
1315 				//we'll want to add this dist
1316 				hitEntDmgAdd[i] = distFromStart;
1317 				break;
1318 			}
1319 		}
1320 		if ( i == numHitEnts )
1321 		{//first time we hit this ent
1322 			if ( numHitEnts == MAX_G2_COLLISIONS )
1323 			{//hit too many damn ents!
1324 				continue;
1325 			}
1326 			hitEntNum[numHitEnts] = coll.mEntityNum;
1327 			if ( !coll.mFlags )
1328 			{//hmm, we came out first, so we must have started inside
1329 				//we'll want to subtract this dist
1330 				hitEntDmgAdd[numHitEnts] = distFromStart;
1331 			}
1332 			else
1333 			{//we're entering the model
1334 				//we'll want to subtract this dist
1335 				hitEntDmgSub[numHitEnts] = distFromStart;
1336 			}
1337 			//keep track of how far in the damage was done
1338 			hitEntStartFrac[numHitEnts] = hitEntDmgSub[numHitEnts]/length;
1339 			//remember the entrance point
1340 			VectorCopy( coll.mCollisionPosition, hitEntPoint[numHitEnts] );
1341 			//remember the entrance dir
1342 			VectorCopy( coll.mCollisionNormal, hitEntDir[numHitEnts] );
1343 			VectorNormalize( hitEntDir[numHitEnts] );
1344 
1345 			//do the effect
1346 
1347 			//FIXME: check material rather than team?
1348 			hitEnt = &g_entities[hitEntNum[numHitEnts]];
1349 			hitEffect = hit_blood_sparks;
1350 			if ( hitEnt != NULL )
1351 			{
1352 				if ( hitEnt->client )
1353 				{
1354 					class_t npc_class = hitEnt->client->NPC_class;
1355 					if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE || npc_class == CLASS_REMOTE ||
1356 					     npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 ||
1357 					     npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 ||
1358 					     npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY )
1359 					{
1360 						hitEffect = hit_sparks;
1361 					}
1362 				}
1363 				else
1364 				{
1365 					hitEffect = hit_sparks;
1366 				}
1367 			}
1368 
1369 			//FIXME: play less if damage is less?
1370 			G_PlayEffect( hitEffect, coll.mCollisionPosition, coll.mCollisionNormal );
1371 
1372 			//Get the hit location based on surface name
1373 			if ( (hitLoc[numHitEnts] == HL_NONE && trHitLoc == HL_NONE)
1374 				|| (hitDismemberLoc[numHitEnts] == HL_NONE && trDismemberLoc == HL_NONE)
1375 				|| (!hitDismember[numHitEnts] && !trDismember) )
1376 			{//no hit loc set for this ent this damage cycle yet
1377 				//FIXME: find closest impact surf *first* (per ent), then call G_GetHitLocFromSurfName?
1378 				trDismember = G_GetHitLocFromSurfName( &g_entities[coll.mEntityNum], gi.G2API_GetSurfaceName( &g_entities[coll.mEntityNum].ghoul2[coll.mModelIndex], coll.mSurfaceIndex ), &trHitLoc, coll.mCollisionPosition, dmgDir, bladeDir, MOD_SABER );
1379 				trDismemberLoc = trHitLoc;
1380 			}
1381 			numHitEnts++;
1382 		}
1383 	}
1384 
1385 	//now go through all the ents we hit and do the damage
1386 	for ( i = 0; i < numHitEnts; i++ )
1387 	{
1388 		doDmg = dmg;
1389 		if ( hitEntNum[i] != ENTITYNUM_NONE )
1390 		{
1391 			if ( doDmg < 10 )
1392 			{//base damage is less than 10
1393 				if ( hitEntNum[i] != 0 )
1394 				{//not the player
1395 					hitEnt = &g_entities[hitEntNum[i]];
1396 					if ( !hitEnt->client || (hitEnt->client->ps.weapon!=WP_SABER&&hitEnt->client->NPC_class!=CLASS_GALAKMECH&&hitEnt->client->playerTeam==enemyTeam) )
1397 					{//did *not* hit a jedi and did *not* hit the player
1398 						//make sure the base damage is high against non-jedi, feels better
1399 						doDmg = 10;
1400 					}
1401 				}
1402 			}
1403 			if ( !hitEntDmgAdd[i] && !hitEntDmgSub[i] )
1404 			{//spent entire time in model
1405 				//NOTE: will we even get a collision then?
1406 				doDmg *= length;
1407 			}
1408 			else if ( hitEntDmgAdd[i] && hitEntDmgSub[i] )
1409 			{//we did enter and exit
1410 				doDmg *= hitEntDmgAdd[i] - hitEntDmgSub[i];
1411 			}
1412 			else if ( !hitEntDmgAdd[i] )
1413 			{//we didn't exit, just entered
1414 				doDmg *= length - hitEntDmgSub[i];
1415 			}
1416 			else if ( !hitEntDmgSub[i] )
1417 			{//we didn't enter, only exited
1418 				doDmg *= hitEntDmgAdd[i];
1419 			}
1420 			if ( doDmg > 0 )
1421 			{
1422 				WP_SaberDamageAdd( 1.0, hitEntNum[i], hitEntDir[i], hitEntPoint[i], ceil(doDmg), hitEntStartFrac[i], trHitLoc, trDismember, trDismemberLoc );
1423 			}
1424 		}
1425 	}
1426 	return (qboolean)(numHitEnts > 0);
1427 }
1428 
WP_SaberKnockaway(gentity_t * attacker,trace_t * tr)1429 void WP_SaberKnockaway( gentity_t *attacker, trace_t *tr )
1430 {
1431 	WP_SaberDrop( attacker, &g_entities[attacker->client->ps.saberEntityNum] );
1432 	G_Sound( &g_entities[attacker->client->ps.saberEntityNum], G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
1433 	G_PlayEffect( "saber_block", tr->endpos );
1434 	saberHitFraction = tr->fraction;
1435 #ifndef FINAL_BUILD
1436 	if ( d_saberCombat->integer )
1437 	{
1438 		gi.Printf( S_COLOR_MAGENTA"WP_SaberKnockaway: saberHitFraction %4.2f\n", saberHitFraction );
1439 	}
1440 #endif
1441 	VectorCopy( tr->endpos, saberHitLocation );
1442 	saberHitEntity = tr->entityNum;
1443 	g_saberFlashTime = level.time-50;
1444 	VectorCopy( saberHitLocation, g_saberFlashPos );
1445 
1446 	//FIXME: make hitEnt play an attack anim or some other special anim when this happens
1447 	//gentity_t *hitEnt = &g_entities[tr->entityNum];
1448 	//NPC_SetAnim( hitEnt, SETANIM_BOTH, BOTH_KNOCKSABER, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
1449 }
1450 
1451 #define SABER_COLLISION_DIST 6//was 2//was 4//was 8//was 16
1452 extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f );
WP_SaberDamageForTrace(int ignore,vec3_t start,vec3_t end,float dmg,vec3_t bladeDir,qboolean noGhoul,int attackStrength,qboolean extrapolate=qtrue)1453 qboolean WP_SaberDamageForTrace( int ignore, vec3_t start, vec3_t end, float dmg,
1454 								vec3_t bladeDir, qboolean noGhoul, int attackStrength,
1455 								qboolean extrapolate = qtrue )
1456 {
1457 	trace_t		tr;
1458 	vec3_t		dir;
1459 	int			mask = (MASK_SHOT|CONTENTS_LIGHTSABER);
1460 	gentity_t	*attacker = &g_entities[ignore];
1461 
1462 	vec3_t		end2;
1463 	VectorCopy( end, end2 );
1464 	if ( extrapolate )
1465 	{
1466 		//NOTE: since we can no longer use the predicted point, extrapolate the trace some.
1467 		//		this may allow saber hits that aren't actually hits, but it doesn't look too bad
1468 		vec3_t diff;
1469 		VectorSubtract( end, start, diff );
1470 		VectorNormalize( diff );
1471 		VectorMA( end2, SABER_EXTRAPOLATE_DIST, diff, end2 );
1472 	}
1473 
1474 	if ( !noGhoul )
1475 	{
1476 		if ( !attacker->s.number || (attacker->client&&(attacker->client->playerTeam==TEAM_PLAYER||attacker->client->NPC_class==CLASS_SHADOWTROOPER||attacker->client->NPC_class==CLASS_TAVION||attacker->client->NPC_class==CLASS_DESANN) ) )//&&attackStrength==FORCE_LEVEL_3)
1477 		{//player,. player allies, shadowtroopers, tavion and desann use larger traces
1478 			vec3_t	traceMins = {-2,-2,-2}, traceMaxs = {2,2,2};
1479 			gi.trace( &tr, start, traceMins, traceMaxs, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX
1480 		}
1481 		/*
1482 		else if ( !attacker->s.number )
1483 		{
1484 			vec3_t	traceMins = {-1,-1,-1}, traceMaxs = {1,1,1};
1485 			gi.trace( &tr, start, traceMins, traceMaxs, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX
1486 		}
1487 		*/
1488 		else
1489 		{//reborn use smaller traces
1490 			gi.trace( &tr, start, NULL, NULL, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX
1491 		}
1492 	}
1493 	else
1494 	{
1495 		gi.trace( &tr, start, NULL, NULL, end2, ignore, mask, G2_NOCOLLIDE, 10 );
1496 	}
1497 
1498 
1499 #ifndef FINAL_BUILD
1500 	if ( d_saberCombat->integer > 1 )
1501 	{
1502 		if ( attacker != NULL && attacker->client != NULL )
1503 		{
1504 			G_DebugLine(start, end2, FRAMETIME, WPDEBUG_SaberColor( attacker->client->ps.saberColor ), qtrue);
1505 		}
1506 	}
1507 #endif
1508 
1509 	if ( tr.entityNum == ENTITYNUM_NONE )
1510 	{
1511 		return qfalse;
1512 	}
1513 
1514 	if ( tr.entityNum == ENTITYNUM_WORLD )
1515 	{
1516 		return qtrue;
1517 	}
1518 
1519 	if ( &g_entities[tr.entityNum] )
1520 	{
1521 		gentity_t *hitEnt = &g_entities[tr.entityNum];
1522 		gentity_t *owner = g_entities[tr.entityNum].owner;
1523 		if ( hitEnt->contents & CONTENTS_LIGHTSABER )
1524 		{
1525 			if ( attacker && attacker->client && attacker->client->ps.saberInFlight )
1526 			{//thrown saber hit something
1527 				 if ( owner
1528 					 && owner->s.number
1529 					 && owner->client
1530 					 && owner->NPC
1531 					 && owner->health > 0
1532 					 && ( owner->client->NPC_class == CLASS_TAVION
1533 							/*|| (owner->client->NPC_class == CLASS_SHADOWTROOPER && !Q_irand( 0, g_spskill->integer*3 ))
1534 							|| (Q_irand( -5, owner->NPC->rank ) > RANK_CIVILIAN && !Q_irand( 0, g_spskill->integer*3 ))*/ )
1535 					 )
1536 				 {//Tavion can toss a blocked thrown saber aside
1537 					WP_SaberKnockaway( attacker, &tr );
1538 					Jedi_PlayDeflectSound( owner );
1539 					return qfalse;
1540 				 }
1541 			}
1542 			//FIXME: take target FP_SABER_DEFENSE and attacker FP_SABER_OFFENSE into account here somehow?
1543 			qboolean sabersIntersect = WP_SabersIntersect( attacker, owner, qfalse );//qtrue );
1544 			float sabersDist;
1545 			if ( attacker && attacker->client && attacker->client->ps.saberInFlight
1546 				&& owner && owner->s.number == 0 && (g_saberAutoBlocking->integer||attacker->client->ps.saberBlockingTime>level.time) )//NPC flying saber hit player's saber bounding box
1547 			{//players have g_saberAutoBlocking, do the more generous check against flying sabers
1548 				//FIXME: instead of hitting the player's saber bounding box
1549 				//and picking an anim afterwards, have him use AI similar
1550 				//to the AI the jedi use for picking a saber melee block...?
1551 				sabersDist = 0;
1552 			}
1553 			else
1554 			{//sabers must actually collide with the attacking saber
1555 				sabersDist = WP_SabersDistance( attacker, owner );
1556 				if ( attacker && attacker->client && attacker->client->ps.saberInFlight )
1557 				{
1558 					sabersDist /= 2.0f;
1559 					if ( sabersDist <= 16.0f )
1560 					{
1561 						sabersIntersect = qtrue;
1562 					}
1563 				}
1564 #ifndef FINAL_BUILD
1565 				if ( d_saberCombat->integer > 1 )
1566 				{
1567 					gi.Printf( "sabersDist: %4.2f\n", sabersDist );
1568 				}
1569 #endif//FINAL_BUILD
1570 			}
1571 			if ( sabersCrossed == -1 || sabersCrossed > sabersDist )
1572 			{
1573 				sabersCrossed = sabersDist;
1574 			}
1575 			float	collisionDist;
1576 			if ( g_saberRealisticCombat->integer )
1577 			{
1578 				collisionDist = SABER_COLLISION_DIST;
1579 			}
1580 			else
1581 			{
1582 				collisionDist = SABER_COLLISION_DIST+6+g_spskill->integer*4;
1583 			}
1584 			if ( owner && owner->client && (attacker != NULL)
1585 				&& (sabersDist > collisionDist )//|| !InFront( attacker->currentOrigin, owner->currentOrigin, owner->client->ps.viewangles, 0.35f ))
1586 				&& !sabersIntersect )//was qtrue, but missed too much?
1587 			{//swing came from behind and/or was not stopped by a lightsaber
1588 				//re-try the trace without checking for lightsabers
1589 				gi.trace ( &tr, start, NULL, NULL, end2, ignore, mask&~CONTENTS_LIGHTSABER, G2_NOCOLLIDE, 10 );
1590 				if ( tr.entityNum == ENTITYNUM_WORLD )
1591 				{
1592  					return qtrue;
1593 				}
1594 				if ( tr.entityNum == ENTITYNUM_NONE || &g_entities[tr.entityNum] == NULL )
1595 				{//didn't hit the owner
1596 					/*
1597 					if ( attacker
1598 						&& attacker->client
1599 						&& (PM_SaberInAttack( attacker->client->ps.saberMove ) || PM_SaberInStart( attacker->client->ps.saberMove ))
1600 						&& DistanceSquared( tr.endpos, owner->currentOrigin ) < 10000 )
1601 					{
1602 						if ( owner->NPC
1603 							&& !owner->client->ps.saberInFlight
1604 							&& owner->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN
1605 							&& !Jedi_SaberBusy( owner ) )
1606 						{//owner parried, just make sure they're saber is in the right spot - only does this if they're not already doing something with saber
1607 							if ( g_spskill->integer && (g_spskill->integer > 1 || Q_irand( 0, 1 )))
1608 							{//if on easy, they don't cheat like this, if on medium, they cheat 50% of the time, if on hard, they always cheat
1609 								//FIXME: also take into account the owner's FP_DEFENSE?
1610 								if ( Q_irand( 0, owner->NPC->rank ) >= RANK_CIVILIAN )
1611 								{//lower-rank Jedi aren't as good blockers
1612 									vec3_t attDir;
1613 									VectorSubtract( end2, start, attDir );
1614 									VectorNormalize( attDir );
1615 									Jedi_SaberBlockGo( owner, owner->NPC->last_ucmd, start, attDir, NULL );
1616 								}
1617 							}
1618 						}
1619 					}
1620 					*/
1621   					return qfalse;	// Exit, but we didn't hit the wall.
1622 				}
1623 #ifndef FINAL_BUILD
1624 				if ( d_saberCombat->integer > 1 )
1625 				{
1626 					if ( !attacker->s.number )
1627 					{
1628 						gi.Printf( S_COLOR_MAGENTA"%d saber hit owner through saber %4.2f, dist = %4.2f\n", level.time, saberHitFraction, sabersDist );
1629 					}
1630 				}
1631 #endif//FINAL_BUILD
1632 				hitEnt = &g_entities[tr.entityNum];
1633 				owner = g_entities[tr.entityNum].owner;
1634 			}
1635 			else
1636 			{//hit a lightsaber
1637 				if ( (tr.fraction < saberHitFraction || tr.startsolid)
1638 					&& sabersDist < (8.0f+g_spskill->value)*4.0f// 50.0f//16.0f
1639 					&& (sabersIntersect || sabersDist < (4.0f+g_spskill->value)*2.0f) )//32.0f) )
1640 				{	// This saber hit closer than the last one.
1641 					if ( (tr.allsolid || tr.startsolid) && owner && owner->client )
1642 					{//tr.fraction will be 0, unreliable... so calculate actual
1643 						float dist = Distance( start, end2 );
1644 						if ( dist )
1645 						{
1646 							float hitFrac = WP_SabersDistance( attacker, owner )/dist;
1647 							if ( hitFrac > 1.0f )
1648 							{//umm... minimum distance between sabers was longer than trace...?
1649 								hitFrac = 1.0f;
1650 							}
1651 							if ( hitFrac < saberHitFraction )
1652 							{
1653 								saberHitFraction = hitFrac;
1654 							}
1655 						}
1656 						else
1657 						{
1658 							saberHitFraction = 0.0f;
1659 						}
1660 #ifndef FINAL_BUILD
1661 						if ( d_saberCombat->integer > 1 )
1662 						{
1663 							if ( !attacker->s.number )
1664 							{
1665 								gi.Printf( S_COLOR_GREEN"%d saber hit saber dist %4.2f allsolid %4.2f\n", level.time, sabersDist, saberHitFraction );
1666 							}
1667 						}
1668 #endif//FINAL_BUILD
1669 					}
1670 					else
1671 					{
1672 #ifndef FINAL_BUILD
1673 						if ( d_saberCombat->integer > 1 )
1674 						{
1675 							if ( !attacker->s.number )
1676 							{
1677 								gi.Printf( S_COLOR_BLUE"%d saber hit saber dist %4.2f, frac %4.2f\n", level.time, sabersDist, saberHitFraction );
1678 							}
1679 							saberHitFraction = tr.fraction;
1680 						}
1681 #endif//FINAL_BUILD
1682 					}
1683 #ifndef FINAL_BUILD
1684 					if ( d_saberCombat->integer )
1685 					{
1686 						gi.Printf( S_COLOR_MAGENTA"hit saber: saberHitFraction %4.2f, allsolid %d, startsolid %d\n", saberHitFraction, tr.allsolid, tr.startsolid );
1687 					}
1688 #endif//FINAL_BUILD
1689 					VectorCopy(tr.endpos, saberHitLocation);
1690 					saberHitEntity = tr.entityNum;
1691 				}
1692 				/*
1693 				if ( owner
1694 					&& owner->client
1695 					&& attacker
1696 					&& attacker->client
1697 					&& (PM_SaberInAttack( attacker->client->ps.saberMove ) || PM_SaberInStart( attacker->client->ps.saberMove ))
1698 					&& DistanceSquared( tr.endpos, owner->currentOrigin ) < 10000 )
1699 				{
1700 					if ( owner->NPC
1701 						&& !owner->client->ps.saberInFlight
1702 						&& owner->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN
1703 						&& !Jedi_SaberBusy( owner ) )
1704 					{//owner parried, just make sure they're saber is in the right spot - only does this if they're not already doing something with saber
1705 						if ( g_spskill->integer && (g_spskill->integer > 1 || Q_irand( 0, 1 )))
1706 						{//if on easy, they don't cheat like this, if on medium, they cheat 50% of the time, if on hard, they always cheat
1707 							//FIXME: also take into account the owner's FP_DEFENSE?
1708 							if ( Q_irand( 0, owner->NPC->rank ) >= RANK_CIVILIAN )
1709 							{//lower-rank Jedi aren't as good blockers
1710 								vec3_t attDir;
1711 								VectorSubtract( end2, start, attDir );
1712 								VectorNormalize( attDir );
1713 								Jedi_SaberBlockGo( owner, owner->NPC->last_ucmd, start, attDir, NULL );
1714 							}
1715 						}
1716 					}
1717 				}
1718 				*/
1719   				return qfalse;	// Exit, but we didn't hit the wall.
1720 			}
1721 		}
1722 		else
1723 		{
1724 #ifndef FINAL_BUILD
1725 			if ( d_saberCombat->integer > 1 )
1726 			{
1727 				if ( !attacker->s.number )
1728 				{
1729 					gi.Printf( S_COLOR_RED"%d saber hit owner directly %4.2f\n", level.time, saberHitFraction );
1730 				}
1731 			}
1732 #endif//FINAL_BUILD
1733 		}
1734 
1735 		if ( attacker && attacker->client && attacker->client->ps.saberInFlight )
1736 		{//thrown saber hit something
1737 			if ( ( hitEnt && hitEnt->client && hitEnt->health > 0 && ( hitEnt->client->NPC_class == CLASS_DESANN || hitEnt->client->NPC_class == CLASS_LUKE || (hitEnt->client->NPC_class == CLASS_GALAKMECH&&hitEnt->client->ps.powerups[PW_GALAK_SHIELD] > 0) ) ) ||
1738 				 ( owner && owner->client && owner->health > 0 && ( owner->client->NPC_class == CLASS_DESANN || owner->client->NPC_class == CLASS_LUKE || (owner->client->NPC_class==CLASS_GALAKMECH&&owner->client->ps.powerups[PW_GALAK_SHIELD] > 0) ) ) )
1739 			{//Luke and Desann slap thrown sabers aside
1740 				//FIXME: control the direction of the thrown saber... if hit Galak's shield, bounce directly away from his origin?
1741 				WP_SaberKnockaway( attacker, &tr );
1742 				if ( hitEnt->client )
1743 				{
1744 					Jedi_PlayDeflectSound( hitEnt );
1745 				}
1746 				else
1747 				{
1748 					Jedi_PlayDeflectSound( owner );
1749 				}
1750   				return qfalse;	// Exit, but we didn't hit the wall.
1751 			}
1752 		}
1753 
1754 		if ( hitEnt->takedamage )
1755 		{
1756 			//no team damage: if ( !hitEnt->client || attacker == NULL || !attacker->client || (hitEnt->client->playerTeam != attacker->client->playerTeam) )
1757 			{
1758 				//multiply the damage by the total distance of the swipe
1759 				VectorSubtract( end2, start, dir );
1760 				float len = VectorNormalize( dir );//VectorLength( dir );
1761 				if ( noGhoul || !hitEnt->ghoul2.size() )
1762 				{//we weren't doing a ghoul trace
1763 					char	*hitEffect;
1764 					if ( dmg >= 1.0 && hitEnt->bmodel )
1765 					{
1766 						dmg = 1.0;
1767 					}
1768 					if ( len > 1 )
1769 					{
1770 						dmg *= len;
1771 					}
1772 #ifndef FINAL_BUILD
1773 					if ( d_saberCombat->integer > 1 )
1774 					{
1775 						if ( !(hitEnt->contents & CONTENTS_LIGHTSABER) )
1776 						{
1777 							gi.Printf( S_COLOR_GREEN"Hit ent, but no ghoul collisions\n" );
1778 						}
1779 					}
1780 #endif
1781 					float	trFrac, dmgFrac;
1782 					if ( tr.allsolid )
1783 					{//totally inside them
1784 						trFrac = 1.0;
1785 						dmgFrac = 0.0;
1786 					}
1787 					else if ( tr.startsolid )
1788 					{//started inside them
1789 						//we don't know how much was inside, we know it's less than all, so use half?
1790 						trFrac = 0.5;
1791 						dmgFrac = 0.0;
1792 					}
1793 					else
1794 					{//started outside them and hit them
1795 						//yeah. this doesn't account for coming out the other wide, but we can worry about that later (use ghoul2)
1796 						trFrac = (1.0f - tr.fraction);
1797 						dmgFrac = tr.fraction;
1798 					}
1799 					WP_SaberDamageAdd( trFrac, tr.entityNum, dir, tr.endpos, dmg, dmgFrac, HL_NONE, qfalse, HL_NONE );
1800 					if ( !tr.allsolid && !tr.startsolid )
1801 					{
1802 						VectorScale( dir, -1, dir );
1803 					}
1804 					//FIXME: don't do blood sparks on non-living things
1805 					hitEffect = hit_blood_sparks;
1806 					if ( hitEnt != NULL )
1807 					{
1808 						if ( hitEnt->client )
1809 						{
1810 							class_t npc_class = hitEnt->client->NPC_class;
1811 							if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE ||
1812 								 npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 || npc_class == CLASS_REMOTE ||
1813 								 npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 ||
1814 							     npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY )
1815 							{
1816 								hitEffect = hit_sparks;
1817 							}
1818 						}
1819 						else
1820 						{
1821 							hitEffect = hit_sparks;
1822 						}
1823 					}
1824 
1825 					G_PlayEffect( hitEffect, tr.endpos, dir );//"saber_cut"
1826 				}
1827 				else
1828 				{//we were doing a ghoul trace
1829 					if ( !WP_SaberDamageEffects( &tr, start, len, dmg, dir, bladeDir, attacker->client->enemyTeam ) )
1830 					{//didn't hit a ghoul ent
1831 						/*
1832 						if ( && hitEnt->ghoul2.size() )
1833 						{//it was a ghoul2 model so we should have hit it
1834 							return qfalse;
1835 						}
1836 						*/
1837 					}
1838 				}
1839 			}
1840 		}
1841 	}
1842 
1843 	return qfalse;
1844 }
1845 
1846 typedef enum
1847 {
1848 	LOCK_FIRST = 0,
1849 	LOCK_TOP = LOCK_FIRST,
1850 	LOCK_DIAG_TR,
1851 	LOCK_DIAG_TL,
1852 	LOCK_DIAG_BR,
1853 	LOCK_DIAG_BL,
1854 	LOCK_R,
1855 	LOCK_L,
1856 	LOCK_RANDOM
1857 } sabersLockMode_t;
1858 
1859 #define LOCK_IDEAL_DIST_TOP 32.0f
1860 #define LOCK_IDEAL_DIST_CIRCLE 48.0f
1861 extern void PM_SetAnimFrame( gentity_t *gent, int frame, qboolean torso, qboolean legs );
1862 extern qboolean ValidAnimFileIndex ( int index );
WP_SabersCheckLock2(gentity_t * attacker,gentity_t * defender,sabersLockMode_t lockMode)1863 qboolean WP_SabersCheckLock2( gentity_t *attacker, gentity_t *defender, sabersLockMode_t lockMode )
1864 {
1865 	animation_t *anim;
1866 	int		attAnim, defAnim, advance = 0;
1867 	float	attStart = 0.5f;
1868 	float	idealDist = 48.0f;
1869 	//MATCH ANIMS
1870 	if ( lockMode == LOCK_RANDOM )
1871 	{
1872 		lockMode = (sabersLockMode_t)Q_irand( (int)LOCK_FIRST, (int)(LOCK_RANDOM)-1 );
1873 	}
1874 	switch ( lockMode )
1875 	{
1876 	case LOCK_TOP:
1877 		attAnim = BOTH_BF2LOCK;
1878 		defAnim = BOTH_BF1LOCK;
1879 		attStart = 0.5f;
1880 		idealDist = LOCK_IDEAL_DIST_TOP;
1881 		break;
1882 	case LOCK_DIAG_TR:
1883 		attAnim = BOTH_CCWCIRCLELOCK;
1884 		defAnim = BOTH_CWCIRCLELOCK;
1885 		attStart = 0.5f;
1886 		idealDist = LOCK_IDEAL_DIST_CIRCLE;
1887 		break;
1888 	case LOCK_DIAG_TL:
1889 		attAnim = BOTH_CWCIRCLELOCK;
1890 		defAnim = BOTH_CCWCIRCLELOCK;
1891 		attStart = 0.5f;
1892 		idealDist = LOCK_IDEAL_DIST_CIRCLE;
1893 		break;
1894 	case LOCK_DIAG_BR:
1895 		attAnim = BOTH_CWCIRCLELOCK;
1896 		defAnim = BOTH_CCWCIRCLELOCK;
1897 		attStart = 0.85f;
1898 		idealDist = LOCK_IDEAL_DIST_CIRCLE;
1899 		break;
1900 	case LOCK_DIAG_BL:
1901 		attAnim = BOTH_CCWCIRCLELOCK;
1902 		defAnim = BOTH_CWCIRCLELOCK;
1903 		attStart = 0.85f;
1904 		idealDist = LOCK_IDEAL_DIST_CIRCLE;
1905 		break;
1906 	case LOCK_R:
1907 		attAnim = BOTH_CWCIRCLELOCK;
1908 		defAnim = BOTH_CCWCIRCLELOCK;
1909 		attStart = 0.75f;
1910 		idealDist = LOCK_IDEAL_DIST_CIRCLE;
1911 		break;
1912 	case LOCK_L:
1913 		attAnim = BOTH_CCWCIRCLELOCK;
1914 		defAnim = BOTH_CWCIRCLELOCK;
1915 		attStart = 0.75f;
1916 		idealDist = LOCK_IDEAL_DIST_CIRCLE;
1917 		break;
1918 	default:
1919 		return qfalse;
1920 		break;
1921 	}
1922 	NPC_SetAnim( attacker, SETANIM_BOTH, attAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
1923 	NPC_SetAnim( defender, SETANIM_BOTH, defAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
1924 	if( ValidAnimFileIndex( attacker->client->clientInfo.animFileIndex ) )
1925 	{
1926 		anim = &level.knownAnimFileSets[attacker->client->clientInfo.animFileIndex].animations[attAnim];
1927 		advance = floor( anim->numFrames*attStart );
1928 		PM_SetAnimFrame( attacker, anim->firstFrame + advance, qtrue, qtrue );
1929 #ifndef FINAL_BUILD
1930 		if ( d_saberCombat->integer )
1931 		{
1932 			Com_Printf( "%s starting saber lock, anim = %s, %d frames to go!\n", attacker->NPC_type, animTable[attAnim].name, anim->numFrames-advance );
1933 		}
1934 #endif
1935 	}
1936 	if( ValidAnimFileIndex( defender->client->clientInfo.animFileIndex ) )
1937 	{
1938 		anim = &level.knownAnimFileSets[defender->client->clientInfo.animFileIndex].animations[defAnim];
1939 		PM_SetAnimFrame( defender, anim->firstFrame + advance, qtrue, qtrue );//was anim->firstFrame + anim->numFrames - advance, but that's wrong since they are matched anims
1940 #ifndef FINAL_BUILD
1941 		if ( d_saberCombat->integer )
1942 		{
1943 			Com_Printf( "%s starting saber lock, anim = %s, %d frames to go!\n", defender->NPC_type, animTable[defAnim].name, advance );
1944 		}
1945 #endif
1946 	}
1947 	VectorClear( attacker->client->ps.velocity );
1948 	VectorClear( defender->client->ps.velocity );
1949 	attacker->client->ps.saberLockTime = defender->client->ps.saberLockTime = level.time + SABER_LOCK_TIME;
1950 	attacker->client->ps.legsAnimTimer = attacker->client->ps.torsoAnimTimer = defender->client->ps.legsAnimTimer = defender->client->ps.torsoAnimTimer = SABER_LOCK_TIME;
1951 	//attacker->client->ps.weaponTime = defender->client->ps.weaponTime = SABER_LOCK_TIME;
1952 	attacker->client->ps.saberLockEnemy = defender->s.number;
1953 	defender->client->ps.saberLockEnemy = attacker->s.number;
1954 
1955 	//MATCH ANGLES
1956 	//FIXME: if zDiff in elevation, make lower look up and upper look down and move them closer?
1957 	float defPitchAdd = 0, zDiff = ((attacker->currentOrigin[2]+attacker->client->standheight)-(defender->currentOrigin[2]+defender->client->standheight));
1958 	if ( zDiff > 24 )
1959 	{
1960 		defPitchAdd = -30;
1961 	}
1962 	else if ( zDiff < -24 )
1963 	{
1964 		defPitchAdd = 30;
1965 	}
1966 	else
1967 	{
1968 		defPitchAdd = zDiff/24.0f*-30.0f;
1969 	}
1970 	if ( attacker->NPC && defender->NPC )
1971 	{//if 2 NPCs, just set pitch to 0
1972 		attacker->client->ps.viewangles[PITCH] = -defPitchAdd;
1973 		defender->client->ps.viewangles[PITCH] = defPitchAdd;
1974 	}
1975 	else
1976 	{//if a player is involved, clamp player's pitch and match NPC's to player
1977 		if ( !attacker->s.number )
1978 		{
1979 			//clamp to defPitch
1980 			if ( attacker->client->ps.viewangles[PITCH] > -defPitchAdd + 10 )
1981 			{
1982 				attacker->client->ps.viewangles[PITCH] = -defPitchAdd + 10;
1983 			}
1984 			else if ( attacker->client->ps.viewangles[PITCH] < -defPitchAdd-10 )
1985 			{
1986 				attacker->client->ps.viewangles[PITCH] = -defPitchAdd-10;
1987 			}
1988 			//clamp to sane numbers
1989 			if ( attacker->client->ps.viewangles[PITCH] > 50 )
1990 			{
1991 				attacker->client->ps.viewangles[PITCH] = 50;
1992 			}
1993 			else if ( attacker->client->ps.viewangles[PITCH] < -50 )
1994 			{
1995 				attacker->client->ps.viewangles[PITCH] = -50;
1996 			}
1997 			defender->client->ps.viewangles[PITCH] = attacker->client->ps.viewangles[PITCH]*-1;
1998 			defPitchAdd = defender->client->ps.viewangles[PITCH];
1999 		}
2000 		else if ( !defender->s.number )
2001 		{
2002 			//clamp to defPitch
2003 			if ( defender->client->ps.viewangles[PITCH] > defPitchAdd + 10 )
2004 			{
2005 				defender->client->ps.viewangles[PITCH] = defPitchAdd + 10;
2006 			}
2007 			else if ( defender->client->ps.viewangles[PITCH] < defPitchAdd-10 )
2008 			{
2009 				defender->client->ps.viewangles[PITCH] = defPitchAdd-10;
2010 			}
2011 			//clamp to sane numbers
2012 			if ( defender->client->ps.viewangles[PITCH] > 50 )
2013 			{
2014 				defender->client->ps.viewangles[PITCH] = 50;
2015 			}
2016 			else if ( defender->client->ps.viewangles[PITCH] < -50 )
2017 			{
2018 				defender->client->ps.viewangles[PITCH] = -50;
2019 			}
2020 			defPitchAdd = defender->client->ps.viewangles[PITCH];
2021 			attacker->client->ps.viewangles[PITCH] = defender->client->ps.viewangles[PITCH]*-1;
2022 		}
2023 	}
2024 	vec3_t	attAngles, defAngles, defDir;
2025 	VectorSubtract( defender->currentOrigin, attacker->currentOrigin, defDir );
2026 	VectorCopy( attacker->client->ps.viewangles, attAngles );
2027 	attAngles[YAW] = vectoyaw( defDir );
2028 	SetClientViewAngle( attacker, attAngles );
2029 	defAngles[PITCH] = attAngles[PITCH]*-1;
2030 	defAngles[YAW] = AngleNormalize180( attAngles[YAW] + 180);
2031 	defAngles[ROLL] = 0;
2032 	SetClientViewAngle( defender, defAngles );
2033 
2034 	//MATCH POSITIONS
2035 	vec3_t	newOrg;
2036 	/*
2037 	idealDist -= fabs(defPitchAdd)/8.0f;
2038 	*/
2039 	float scale = VectorLength( attacker->s.modelScale );
2040 	if ( scale )
2041 	{
2042 		idealDist += 8*(scale-1.0f);
2043 	}
2044 	scale = VectorLength( defender->s.modelScale );
2045 	if ( scale )
2046 	{
2047 		idealDist += 8*(scale-1.0f);
2048 	}
2049 
2050 	float diff = VectorNormalize( defDir ) - idealDist;//diff will be the total error in dist
2051 	//try to move attacker half the diff towards the defender
2052 	VectorMA( attacker->currentOrigin, diff*0.5f, defDir, newOrg );
2053 	trace_t	trace;
2054 	gi.trace( &trace, attacker->currentOrigin, attacker->mins, attacker->maxs, newOrg, attacker->s.number, attacker->clipmask, G2_NOCOLLIDE, 0 );
2055 	if ( !trace.startsolid && !trace.allsolid )
2056 	{
2057 		G_SetOrigin( attacker, trace.endpos );
2058 		gi.linkentity( attacker );
2059 	}
2060 	//now get the defender's dist and do it for him too
2061 	vec3_t	attDir;
2062 	VectorSubtract( attacker->currentOrigin, defender->currentOrigin, attDir );
2063 	diff = VectorNormalize( attDir ) - idealDist;//diff will be the total error in dist
2064 	//try to move defender all of the remaining diff towards the attacker
2065 	VectorMA( defender->currentOrigin, diff, attDir, newOrg );
2066 	gi.trace( &trace, defender->currentOrigin, defender->mins, defender->maxs, newOrg, defender->s.number, defender->clipmask, G2_NOCOLLIDE, 0 );
2067 	if ( !trace.startsolid && !trace.allsolid )
2068 	{
2069 		G_SetOrigin( defender, trace.endpos );
2070 		gi.linkentity( defender );
2071 	}
2072 
2073 	//DONE!
2074 
2075 	return qtrue;
2076 }
2077 
WP_SabersCheckLock(gentity_t * ent1,gentity_t * ent2)2078 qboolean WP_SabersCheckLock( gentity_t *ent1, gentity_t *ent2 )
2079 {
2080 	if ( ent1->client->playerTeam == ent2->client->playerTeam )
2081 	{
2082 		return qfalse;
2083 	}
2084 	if ( ent1->client->ps.groundEntityNum == ENTITYNUM_NONE ||
2085 		ent2->client->ps.groundEntityNum == ENTITYNUM_NONE )
2086 	{
2087 		return qfalse;
2088 	}
2089 	if ( ent1->painDebounceTime > level.time-1000 || ent2->painDebounceTime > level.time-1000 )
2090 	{//can't saberlock if you're not ready
2091 		return qfalse;
2092 	}
2093 	if ( fabs( ent1->currentOrigin[2]-ent2->currentOrigin[2]) > 18 )
2094 	{
2095 		return qfalse;
2096 	}
2097 	float dist = DistanceSquared(ent1->currentOrigin,ent2->currentOrigin);
2098 	if ( dist < 64 || dist > 6400 )//( dist < 128 || dist > 2304 )
2099 	{//between 8 and 80 from each other//was 16 and 48
2100 		return qfalse;
2101 	}
2102 	if ( !InFOV( ent1, ent2, 40, 180 ) || !InFOV( ent2, ent1, 40, 180 ) )
2103 	{
2104 		return qfalse;
2105 	}
2106 	//Check for certain anims that *cannot* lock
2107 	//FIXME: there should probably be a whole *list* of these, but I'll put them in as they come up
2108 	if ( ent1->client->ps.torsoAnim == BOTH_A2_STABBACK1 && ent1->client->ps.torsoAnimTimer > 300 )
2109 	{//can't lock when saber is behind you
2110 		return qfalse;
2111 	}
2112 	if ( ent2->client->ps.torsoAnim == BOTH_A2_STABBACK1 && ent2->client->ps.torsoAnimTimer > 300 )
2113 	{//can't lock when saber is behind you
2114 		return qfalse;
2115 	}
2116 	//BR to TL lock
2117 	if ( ent1->client->ps.torsoAnim == BOTH_A1_BR_TL ||
2118 		ent1->client->ps.torsoAnim == BOTH_A2_BR_TL ||
2119 		ent1->client->ps.torsoAnim == BOTH_A3_BR_TL ||
2120 		ent1->client->ps.torsoAnim == BOTH_A4_BR_TL ||
2121 		ent1->client->ps.torsoAnim == BOTH_A5_BR_TL )
2122 	{//ent1 is attacking in the opposite diagonal
2123 		return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BR );
2124 	}
2125 	if ( ent2->client->ps.torsoAnim == BOTH_A1_BR_TL ||
2126 		ent2->client->ps.torsoAnim == BOTH_A2_BR_TL ||
2127 		ent2->client->ps.torsoAnim == BOTH_A3_BR_TL ||
2128 		ent2->client->ps.torsoAnim == BOTH_A4_BR_TL ||
2129 		ent2->client->ps.torsoAnim == BOTH_A5_BR_TL )
2130 	{//ent2 is attacking in the opposite diagonal
2131 		return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BR );
2132 	}
2133 	//BL to TR lock
2134 	if ( ent1->client->ps.torsoAnim == BOTH_A1_BL_TR ||
2135 		ent1->client->ps.torsoAnim == BOTH_A2_BL_TR ||
2136 		ent1->client->ps.torsoAnim == BOTH_A3_BL_TR ||
2137 		ent1->client->ps.torsoAnim == BOTH_A4_BL_TR ||
2138 		ent1->client->ps.torsoAnim == BOTH_A5_BL_TR )
2139 	{//ent1 is attacking in the opposite diagonal
2140 		return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BL );
2141 	}
2142 	if ( ent2->client->ps.torsoAnim == BOTH_A1_BL_TR ||
2143 		ent2->client->ps.torsoAnim == BOTH_A2_BL_TR ||
2144 		ent2->client->ps.torsoAnim == BOTH_A3_BL_TR ||
2145 		ent2->client->ps.torsoAnim == BOTH_A4_BL_TR ||
2146 		ent2->client->ps.torsoAnim == BOTH_A5_BL_TR )
2147 	{//ent2 is attacking in the opposite diagonal
2148 		return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BL );
2149 	}
2150 	//L to R lock
2151 	if ( ent1->client->ps.torsoAnim == BOTH_A1__L__R ||
2152 		ent1->client->ps.torsoAnim == BOTH_A2__L__R ||
2153 		ent1->client->ps.torsoAnim == BOTH_A3__L__R ||
2154 		ent1->client->ps.torsoAnim == BOTH_A4__L__R ||
2155 		ent1->client->ps.torsoAnim == BOTH_A5__L__R )
2156 	{//ent1 is attacking l to r
2157 		return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
2158 		/*
2159 		if ( ent2BlockingPlayer )
2160 		{//player will block this anyway
2161 			return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
2162 		}
2163 		if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
2164 			ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
2165 			ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
2166 			ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
2167 			ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
2168 			ent2->client->ps.torsoAnim == BOTH_P1_S1_TR ||
2169 			ent2->client->ps.torsoAnim == BOTH_P1_S1_BL )
2170 		{//ent2 is attacking or blocking on the r
2171 			return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
2172 		}
2173 		if ( ent2Boss && !Q_irand( 0, 3 ) )
2174 		{
2175 			return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
2176 		}
2177 		*/
2178 	}
2179 	if ( ent2->client->ps.torsoAnim == BOTH_A1__L__R ||
2180 		ent2->client->ps.torsoAnim == BOTH_A2__L__R ||
2181 		ent2->client->ps.torsoAnim == BOTH_A3__L__R ||
2182 		ent2->client->ps.torsoAnim == BOTH_A4__L__R ||
2183 		ent2->client->ps.torsoAnim == BOTH_A5__L__R )
2184 	{//ent2 is attacking l to r
2185 		return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
2186 		/*
2187 		if ( ent1BlockingPlayer )
2188 		{//player will block this anyway
2189 			return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
2190 		}
2191 		if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
2192 			ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
2193 			ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
2194 			ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
2195 			ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
2196 			ent1->client->ps.torsoAnim == BOTH_P1_S1_TR ||
2197 			ent1->client->ps.torsoAnim == BOTH_P1_S1_BL )
2198 		{//ent1 is attacking or blocking on the r
2199 			return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
2200 		}
2201 		if ( ent1Boss && !Q_irand( 0, 3 ) )
2202 		{
2203 			return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
2204 		}
2205 		*/
2206 	}
2207 	//R to L lock
2208 	if ( ent1->client->ps.torsoAnim == BOTH_A1__R__L ||
2209 		ent1->client->ps.torsoAnim == BOTH_A2__R__L ||
2210 		ent1->client->ps.torsoAnim == BOTH_A3__R__L ||
2211 		ent1->client->ps.torsoAnim == BOTH_A4__R__L ||
2212 		ent1->client->ps.torsoAnim == BOTH_A5__R__L )
2213 	{//ent1 is attacking r to l
2214 		return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
2215 		/*
2216 		if ( ent2BlockingPlayer )
2217 		{//player will block this anyway
2218 			return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
2219 		}
2220 		if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
2221 			ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
2222 			ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
2223 			ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
2224 			ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
2225 			ent2->client->ps.torsoAnim == BOTH_P1_S1_TL ||
2226 			ent2->client->ps.torsoAnim == BOTH_P1_S1_BR )
2227 		{//ent2 is attacking or blocking on the l
2228 			return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
2229 		}
2230 		if ( ent2Boss && !Q_irand( 0, 3 ) )
2231 		{
2232 			return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
2233 		}
2234 		*/
2235 	}
2236 	if ( ent2->client->ps.torsoAnim == BOTH_A1__R__L ||
2237 		ent2->client->ps.torsoAnim == BOTH_A2__R__L ||
2238 		ent2->client->ps.torsoAnim == BOTH_A3__R__L ||
2239 		ent2->client->ps.torsoAnim == BOTH_A4__R__L ||
2240 		ent2->client->ps.torsoAnim == BOTH_A5__R__L )
2241 	{//ent2 is attacking r to l
2242 		return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
2243 		/*
2244 		if ( ent1BlockingPlayer )
2245 		{//player will block this anyway
2246 			return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
2247 		}
2248 		if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
2249 			ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
2250 			ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
2251 			ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
2252 			ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
2253 			ent1->client->ps.torsoAnim == BOTH_P1_S1_TL ||
2254 			ent1->client->ps.torsoAnim == BOTH_P1_S1_BR )
2255 		{//ent1 is attacking or blocking on the l
2256 			return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
2257 		}
2258 		if ( ent1Boss && !Q_irand( 0, 3 ) )
2259 		{
2260 			return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
2261 		}
2262 		*/
2263 	}
2264 	//TR to BL lock
2265 	if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
2266 		ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
2267 		ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
2268 		ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
2269 		ent1->client->ps.torsoAnim == BOTH_A5_TR_BL )
2270 	{//ent1 is attacking diagonally
2271 		return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
2272 		/*
2273 		if ( ent2BlockingPlayer )
2274 		{//player will block this anyway
2275 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
2276 		}
2277 		if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
2278 			ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
2279 			ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
2280 			ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
2281 			ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
2282 			ent2->client->ps.torsoAnim == BOTH_P1_S1_TL )
2283 		{//ent2 is attacking in the opposite diagonal
2284 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
2285 		}
2286 		if ( ent2->client->ps.torsoAnim == BOTH_A1_BR_TL ||
2287 			ent2->client->ps.torsoAnim == BOTH_A2_BR_TL ||
2288 			ent2->client->ps.torsoAnim == BOTH_A3_BR_TL ||
2289 			ent2->client->ps.torsoAnim == BOTH_A4_BR_TL ||
2290 			ent2->client->ps.torsoAnim == BOTH_A5_BR_TL ||
2291 			ent2->client->ps.torsoAnim == BOTH_P1_S1_BL )
2292 		{//ent2 is attacking in the opposite diagonal
2293 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BL );
2294 		}
2295 		if ( ent2Boss && !Q_irand( 0, 3 ) )
2296 		{
2297 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
2298 		}
2299 		*/
2300 	}
2301 
2302 	if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
2303 		ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
2304 		ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
2305 		ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
2306 		ent2->client->ps.torsoAnim == BOTH_A5_TR_BL )
2307 	{//ent2 is attacking diagonally
2308 		return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
2309 		/*
2310 		if ( ent1BlockingPlayer )
2311 		{//player will block this anyway
2312 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
2313 		}
2314 		if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
2315 			ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
2316 			ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
2317 			ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
2318 			ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
2319 			ent1->client->ps.torsoAnim == BOTH_P1_S1_TL )
2320 		{//ent1 is attacking in the opposite diagonal
2321 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
2322 		}
2323 		if ( ent1->client->ps.torsoAnim == BOTH_A1_BR_TL ||
2324 			ent1->client->ps.torsoAnim == BOTH_A2_BR_TL ||
2325 			ent1->client->ps.torsoAnim == BOTH_A3_BR_TL ||
2326 			ent1->client->ps.torsoAnim == BOTH_A4_BR_TL ||
2327 			ent1->client->ps.torsoAnim == BOTH_A5_BR_TL ||
2328 			ent1->client->ps.torsoAnim == BOTH_P1_S1_BL )
2329 		{//ent1 is attacking in the opposite diagonal
2330 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BL );
2331 		}
2332 		if ( ent1Boss && !Q_irand( 0, 3 ) )
2333 		{
2334 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
2335 		}
2336 		*/
2337 	}
2338 
2339 	//TL to BR lock
2340 	if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
2341 		ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
2342 		ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
2343 		ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
2344 		ent1->client->ps.torsoAnim == BOTH_A5_TL_BR )
2345 	{//ent1 is attacking diagonally
2346 		return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
2347 		/*
2348 		if ( ent2BlockingPlayer )
2349 		{//player will block this anyway
2350 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
2351 		}
2352 		if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
2353 			ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
2354 			ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
2355 			ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
2356 			ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
2357 			ent2->client->ps.torsoAnim == BOTH_P1_S1_TR )
2358 		{//ent2 is attacking in the opposite diagonal
2359 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
2360 		}
2361 		if ( ent2->client->ps.torsoAnim == BOTH_A1_BL_TR ||
2362 			ent2->client->ps.torsoAnim == BOTH_A2_BL_TR ||
2363 			ent2->client->ps.torsoAnim == BOTH_A3_BL_TR ||
2364 			ent2->client->ps.torsoAnim == BOTH_A4_BL_TR ||
2365 			ent2->client->ps.torsoAnim == BOTH_A5_BL_TR ||
2366 			ent2->client->ps.torsoAnim == BOTH_P1_S1_BR )
2367 		{//ent2 is attacking in the opposite diagonal
2368 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BR );
2369 		}
2370 		if ( ent2Boss && !Q_irand( 0, 3 ) )
2371 		{
2372 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
2373 		}
2374 		*/
2375 	}
2376 
2377 	if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
2378 		ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
2379 		ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
2380 		ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
2381 		ent2->client->ps.torsoAnim == BOTH_A5_TL_BR )
2382 	{//ent2 is attacking diagonally
2383 		return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
2384 		/*
2385 		if ( ent1BlockingPlayer )
2386 		{//player will block this anyway
2387 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
2388 		}
2389 		if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
2390 			ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
2391 			ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
2392 			ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
2393 			ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
2394 			ent1->client->ps.torsoAnim == BOTH_P1_S1_TR )
2395 		{//ent1 is attacking in the opposite diagonal
2396 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
2397 		}
2398 		if ( ent1->client->ps.torsoAnim == BOTH_A1_BL_TR ||
2399 			ent1->client->ps.torsoAnim == BOTH_A2_BL_TR ||
2400 			ent1->client->ps.torsoAnim == BOTH_A3_BL_TR ||
2401 			ent1->client->ps.torsoAnim == BOTH_A4_BL_TR ||
2402 			ent1->client->ps.torsoAnim == BOTH_A5_BL_TR ||
2403 			ent1->client->ps.torsoAnim == BOTH_P1_S1_BR )
2404 		{//ent1 is attacking in the opposite diagonal
2405 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BR );
2406 		}
2407 		if ( ent1Boss && !Q_irand( 0, 3 ) )
2408 		{
2409 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
2410 		}
2411 		*/
2412 	}
2413 	//T to B lock
2414 	if ( ent1->client->ps.torsoAnim == BOTH_A1_T__B_ ||
2415 		ent1->client->ps.torsoAnim == BOTH_A2_T__B_ ||
2416 		ent1->client->ps.torsoAnim == BOTH_A3_T__B_ ||
2417 		ent1->client->ps.torsoAnim == BOTH_A4_T__B_ ||
2418 		ent1->client->ps.torsoAnim == BOTH_A5_T__B_ )
2419 	{//ent1 is attacking top-down
2420 		/*
2421 		if ( ent2->client->ps.torsoAnim == BOTH_P1_S1_T_ ||
2422 			ent2->client->ps.torsoAnim == BOTH_K1_S1_T_ )
2423 		*/
2424 		{//ent2 is blocking at top
2425 			return WP_SabersCheckLock2( ent1, ent2, LOCK_TOP );
2426 		}
2427 	}
2428 
2429 	if ( ent2->client->ps.torsoAnim == BOTH_A1_T__B_ ||
2430 		ent2->client->ps.torsoAnim == BOTH_A2_T__B_ ||
2431 		ent2->client->ps.torsoAnim == BOTH_A3_T__B_ ||
2432 		ent2->client->ps.torsoAnim == BOTH_A4_T__B_ ||
2433 		ent2->client->ps.torsoAnim == BOTH_A5_T__B_ )
2434 	{//ent2 is attacking top-down
2435 		/*
2436 		if ( ent1->client->ps.torsoAnim == BOTH_P1_S1_T_ ||
2437 			ent1->client->ps.torsoAnim == BOTH_K1_S1_T_ )
2438 		*/
2439 		{//ent1 is blocking at top
2440 			return WP_SabersCheckLock2( ent2, ent1, LOCK_TOP );
2441 		}
2442 	}
2443 	/*
2444 	if ( !Q_irand( 0, 10 ) )
2445 	{
2446 		return WP_SabersCheckLock2( ent1, ent2, LOCK_RANDOM );
2447 	}
2448 	*/
2449 	return qfalse;
2450 }
2451 
WP_SaberParry(gentity_t * victim,gentity_t * attacker)2452 qboolean WP_SaberParry( gentity_t *victim, gentity_t *attacker )
2453 {
2454 	if ( !victim || !victim->client || !attacker )
2455 	{
2456 		return qfalse;
2457 	}
2458 	if ( victim->s.number || g_saberAutoBlocking->integer || victim->client->ps.saberBlockingTime > level.time )
2459 	{//either an NPC or a player who is blocking
2460 		if ( !PM_SaberInTransitionAny( victim->client->ps.saberMove )
2461 			&& !PM_SaberInBounce( victim->client->ps.saberMove )
2462 			&& !PM_SaberInKnockaway( victim->client->ps.saberMove ) )
2463 		{//I'm not attacking, in transition or in a bounce or knockaway, so play a parry
2464 			WP_SaberBlockNonRandom( victim, saberHitLocation, qfalse );
2465 		}
2466 		victim->client->ps.saberEventFlags |= SEF_PARRIED;
2467 
2468 		//since it was parried, take away any damage done
2469 		//FIXME: what if the damage was done before the parry?
2470 		WP_SaberClearDamageForEntNum( victim->s.number );
2471 
2472 		//tell the victim to get mad at me
2473 		if ( victim->enemy != attacker && victim->client->playerTeam != attacker->client->playerTeam )
2474 		{//they're not mad at me and they're not on my team
2475 			G_ClearEnemy( victim );
2476 			G_SetEnemy( victim, attacker );
2477 		}
2478 		return qtrue;
2479 	}
2480 	return qfalse;
2481 }
2482 
WP_BrokenParryKnockDown(gentity_t * victim)2483 qboolean WP_BrokenParryKnockDown( gentity_t *victim )
2484 {
2485 	if ( !victim || !victim->client )
2486 	{
2487 		return qfalse;
2488 	}
2489 	if ( victim->client->ps.saberMove == LS_PARRY_UP
2490 		|| victim->client->ps.saberMove == LS_PARRY_UR
2491 		|| victim->client->ps.saberMove == LS_PARRY_UL
2492 		|| victim->client->ps.saberMove == LS_H1_BR
2493 		|| victim->client->ps.saberMove == LS_H1_B_
2494 		|| victim->client->ps.saberMove == LS_H1_BL )
2495 	{//knock their asses down!
2496 		int knockAnim = BOTH_KNOCKDOWN1;
2497 		if ( PM_CrouchAnim( victim->client->ps.legsAnim ) )
2498 		{
2499 			knockAnim = BOTH_KNOCKDOWN4;
2500 		}
2501 		NPC_SetAnim( victim, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
2502 		G_AddEvent( victim, EV_PAIN, victim->health );
2503 		return qtrue;
2504 	}
2505 	return qfalse;
2506 }
2507 /*
2508 ---------------------------------------------------------
2509 void WP_SaberDamageTrace( gentity_t *ent )
2510 
2511   Constantly trace from the old blade pos to new, down the saber beam and do damage
2512 
2513   FIXME: if the dot product of the old muzzle dir and the new muzzle dir is < 0.75, subdivide it and do multiple traces so we don't flatten out the arc!
2514 ---------------------------------------------------------
2515 */
2516 #define MAX_SABER_SWING_INC 0.33f
WP_SaberDamageTrace(gentity_t * ent)2517 void WP_SaberDamageTrace( gentity_t *ent )
2518 {
2519 	vec3_t		mp1, mp2, md1, md2, baseOld, baseNew, baseDiff, endOld, endNew, bladePointOld, bladePointNew;
2520 	float		tipDmgMod = 1.0f;
2521 	float		baseDamage;
2522 	int			baseDFlags = 0;
2523 	qboolean	hit_wall = qfalse;
2524 	qboolean	brokenParry = qfalse;
2525 
2526 	for ( int ven = 0; ven < MAX_SABER_VICTIMS; ven++ )
2527 	{
2528 		victimEntityNum[ven] = ENTITYNUM_NONE;
2529 	}
2530 	memset( totalDmg, 0, sizeof( totalDmg) );
2531 	memset( dmgDir, 0, sizeof( dmgDir ) );
2532 	memset( dmgSpot, 0, sizeof( dmgSpot ) );
2533 	memset( dmgFraction, 0, sizeof( dmgFraction ) );
2534 	memset( hitLoc, HL_NONE, sizeof( hitLoc ) );
2535 	memset( hitDismemberLoc, HL_NONE, sizeof( hitDismemberLoc ) );
2536 	memset( hitDismember, qfalse, sizeof( hitDismember ) );
2537 	numVictims = 0;
2538 	VectorClear(saberHitLocation);
2539 	VectorClear(saberHitNormal);
2540 	saberHitFraction = 1.0;	// Closest saber hit.  The saber can do no damage past this point.
2541 	saberHitEntity = ENTITYNUM_NONE;
2542 	sabersCrossed = -1;
2543 
2544 	if ( !ent->client )
2545 	{
2546 		return;
2547 	}
2548 
2549 	if ( !ent->s.number )
2550 	{//player never uses these
2551 		ent->client->ps.saberEventFlags &= ~SEF_EVENTS;
2552 	}
2553 
2554 	if ( ent->client->ps.saberLength <= 1 )//cen get down to 1 when in a wall
2555 	{//saber is not on
2556 		return;
2557 	}
2558 
2559 	if ( VectorCompare( ent->client->renderInfo.muzzlePointOld, vec3_origin ) || VectorCompare( ent->client->renderInfo.muzzleDirOld, vec3_origin ) )
2560 	{
2561 		//just started up the saber?
2562 		return;
2563 	}
2564 
2565 	int saberContents = gi.pointcontents( ent->client->renderInfo.muzzlePoint, ent->client->ps.saberEntityNum );
2566 	if ( (saberContents&CONTENTS_WATER)||
2567 		(saberContents&CONTENTS_SLIME)||
2568 		(saberContents&CONTENTS_LAVA) )
2569 	{//um... turn off?  Or just set length to 1?
2570 		//FIXME: short-out effect/sound?
2571 		ent->client->ps.saberActive = qfalse;
2572 		return;
2573 	}
2574 	else if ( saberContents&CONTENTS_OUTSIDE )
2575 	{
2576 		if ( (level.worldFlags&WF_RAINING) )
2577 		{
2578 			//add steam in rain
2579 			if ( Q_flrand( 0, 500 ) < ent->client->ps.saberLength )
2580 			{
2581 				vec3_t	end, normal = {0,0,1};//FIXME: opposite of rain angles?
2582 				VectorMA( ent->client->renderInfo.muzzlePoint, ent->client->ps.saberLength*Q_flrand(0, 1), ent->client->renderInfo.muzzleDir, end );
2583 				G_PlayEffect( "saber/fizz", end, normal );
2584 			}
2585 		}
2586 	}
2587 
2588 	//FIXMEFIXMEFIXME: When in force speed (esp. lvl 3), need to interpolate this because
2589 	//		we animate so much faster that the arc is pretty much flat...
2590 
2591 	int entPowerLevel;
2592 	if ( !ent->s.number && (ent->client->ps.forcePowersActive&(1<<FP_SPEED)) )
2593 	{
2594 		entPowerLevel = FORCE_LEVEL_3;
2595 	}
2596 	else
2597 	{
2598 		entPowerLevel = PM_PowerLevelForSaberAnim( &ent->client->ps );
2599 	}
2600 
2601 	if ( ent->client->ps.saberInFlight )
2602 	{//flying sabers are much more deadly
2603 		//unless you're dead
2604 		if ( ent->health <= 0 && !g_saberRealisticCombat->integer )
2605 		{//so enemies don't keep trying to block it
2606 			//FIXME: still do damage, just not to humanoid clients who should try to avoid it
2607 			//baseDamage = 0.0f;
2608 			return;
2609 		}
2610 		//or unless returning
2611 		else if ( ent->client->ps.saberEntityState == SES_RETURNING )
2612 		{//special case, since we're returning, chances are if we hit something
2613 			//it's going to be butt-first.  So do less damage.
2614 			baseDamage = 0.1f;
2615 		}
2616 		else
2617 		{
2618 			if ( !ent->s.number )
2619 			{//cheat for player
2620 				baseDamage = 10.0f;
2621 			}
2622 			else
2623 			{
2624 				baseDamage = 2.5f;
2625 			}
2626 		}
2627 		//Use old to current since can't predict it
2628 		VectorCopy( ent->client->renderInfo.muzzlePointOld, mp1 );
2629 		VectorCopy( ent->client->renderInfo.muzzleDirOld, md1 );
2630 		VectorCopy( ent->client->renderInfo.muzzlePoint, mp2 );
2631 		VectorCopy( ent->client->renderInfo.muzzleDir, md2 );
2632 	}
2633 	else
2634 	{
2635 		if ( ent->client->ps.saberMove == LS_READY )
2636 		{//just do effects
2637 			if ( g_saberRealisticCombat->integer < 2 )
2638 			{//don't kill with this hit
2639 				baseDFlags = DAMAGE_NO_KILL;
2640 			}
2641 			baseDamage = 0;
2642 		}
2643 		else if ( ent->client->ps.saberLockTime > level.time )
2644 		{//just do effects
2645 			baseDamage = 0;
2646 		}
2647 		else if ( ent->client->ps.saberBlocked > BLOCKED_NONE
2648 				 || ( !PM_SaberInAttack( ent->client->ps.saberMove )
2649 					  && !PM_SaberInSpecialAttack( ent->client->ps.torsoAnim )
2650 					  && !PM_SaberInTransitionAny( ent->client->ps.saberMove )
2651 					)
2652 				)
2653 		{//don't do damage if parrying/reflecting/bouncing/deflecting or not actually attacking or in a transition to/from/between attacks
2654 			baseDamage = 0;
2655 		}
2656 		else
2657 		{//okay, in a saberMove that does damage
2658 			//make sure we're in the right anim
2659 			if ( !PM_SaberInSpecialAttack( ent->client->ps.torsoAnim )
2660 				&& !PM_InAnimForSaberMove( ent->client->ps.torsoAnim, ent->client->ps.saberMove ) )
2661 			{//forced into some other animation somehow, like a pain or death?
2662 				baseDamage = 0;
2663 			}
2664 			else if ( ent->client->ps.weaponstate == WEAPON_FIRING && ent->client->ps.saberBlocked == BLOCKED_NONE &&
2665 				( PM_SaberInAttack(ent->client->ps.saberMove) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) || PM_SpinningSaberAnim(ent->client->ps.torsoAnim) || entPowerLevel > FORCE_LEVEL_2 ) )
2666 			{//normal attack swing swinging/spinning (or if using strong set), do normal damage
2667 				//FIXME: more damage for higher attack power levels?
2668 				//		More damage based on length/color of saber?
2669 				//FIXME: Desann does double damage?
2670 				if ( g_saberRealisticCombat->integer )
2671 				{
2672 					switch ( entPowerLevel )
2673 					{
2674 					case FORCE_LEVEL_3:
2675 						baseDamage = 10.0f;
2676 						break;
2677 					case FORCE_LEVEL_2:
2678 						baseDamage = 5.0f;
2679 						break;
2680 					default:
2681 					case FORCE_LEVEL_1:
2682 						baseDamage = 2.5f;
2683 						break;
2684 					}
2685 				}
2686 				else
2687 				{
2688 					baseDamage = 2.5f * (float)entPowerLevel;
2689 				}
2690 			}
2691 			else
2692 			{//saber is transitioning, defending or idle, don't do as much damage
2693 				//FIXME: strong attacks and returns should do damage and be unblockable
2694 				if ( g_timescale->value < 1.0 )
2695 				{//in slow mo or force speed, we need to do damage during the transitions
2696 					if ( g_saberRealisticCombat->integer )
2697 					{
2698 						switch ( entPowerLevel )
2699 						{
2700 						case FORCE_LEVEL_3:
2701 							baseDamage = 10.0f;
2702 							break;
2703 						case FORCE_LEVEL_2:
2704 							baseDamage = 5.0f;
2705 							break;
2706 						default:
2707 						case FORCE_LEVEL_1:
2708 							baseDamage = 2.5f;
2709 							break;
2710 						}
2711 					}
2712 					else
2713 					{
2714 						baseDamage = 2.5f * (float)entPowerLevel;
2715 					}
2716 				}
2717 				else// if ( !ent->s.number )
2718 				{//I have to do *some* damage in transitions or else you feel like a total gimp
2719 					baseDamage = 0.1f;
2720 				}
2721 				/*
2722 				else
2723 				{
2724 					baseDamage = 0;//was 1.0f;//was 0.25
2725 				}
2726 				*/
2727 			}
2728 		}
2729 
2730 		//Use current to next since can predict it
2731 		//FIXME: if they're closer than the saber blade start, we don't want the
2732 		//		arm to pass through them without any damage... so check the radius
2733 		//		and push them away (do pain & knockback)
2734 		//FIXME: if going into/coming from a parry/reflection or going into a deflection, don't use old mp & dir?  Otherwise, deflections will cut through?
2735 		//VectorCopy( ent->client->renderInfo.muzzlePoint, mp1 );
2736 		//VectorCopy( ent->client->renderInfo.muzzleDir, md1 );
2737 		//VectorCopy( ent->client->renderInfo.muzzlePointNext, mp2 );
2738 		//VectorCopy( ent->client->renderInfo.muzzleDirNext, md2 );
2739 		//prediction was causing gaps in swing (G2 problem) so *don't* predict
2740 		VectorCopy( ent->client->renderInfo.muzzlePointOld, mp1 );
2741 		VectorCopy( ent->client->renderInfo.muzzleDirOld, md1 );
2742 		VectorCopy( ent->client->renderInfo.muzzlePoint, mp2 );
2743 		VectorCopy( ent->client->renderInfo.muzzleDir, md2 );
2744 
2745 		//NOTE: this is a test, may not be necc, as I can still swing right through someone without hitting them, somehow...
2746 		//see if anyone is so close that they're within the dist from my origin to the start of the saber
2747 		if ( ent->health > 0 && !ent->client->ps.saberLockTime )
2748 		{
2749 			trace_t trace;
2750 			gi.trace( &trace, ent->currentOrigin, vec3_origin, vec3_origin, mp1, ent->s.number, (MASK_SHOT&~(CONTENTS_CORPSE|CONTENTS_ITEM)), G2_NOCOLLIDE, 0 );
2751 			if ( trace.entityNum < ENTITYNUM_WORLD && (trace.entityNum > 0||ent->client->NPC_class == CLASS_DESANN) )//NPCs don't push player away, unless it's Desann
2752 			{//a valid ent
2753 				gentity_t *traceEnt = &g_entities[trace.entityNum];
2754 				if ( traceEnt
2755 					&& traceEnt->client
2756 					&& traceEnt->health > 0
2757 					&& traceEnt->client->playerTeam != ent->client->playerTeam
2758 					&& !PM_InKnockDown( &traceEnt->client->ps ) )
2759 				{//enemy client, push them away
2760 					if ( !traceEnt->client->ps.saberLockTime && !traceEnt->message )
2761 					{//don't push people in saberlock or with security keys
2762 						vec3_t hitDir;
2763 						VectorSubtract( trace.endpos, ent->currentOrigin, hitDir );
2764 						float totalDist = Distance( mp1, ent->currentOrigin );
2765 						float knockback = (totalDist-VectorNormalize( hitDir ))/totalDist * 200.0f;
2766 						hitDir[2] = 0;
2767 						//FIXME: do we need to call G_Throw?  Seems unfair to put actual knockback on them, stops the attack
2768 						//G_Throw( traceEnt, hitDir, knockback );
2769 						VectorMA( traceEnt->client->ps.velocity, knockback, hitDir, traceEnt->client->ps.velocity );
2770 						traceEnt->client->ps.pm_time = 200;
2771 						traceEnt->client->ps.pm_flags |= PMF_TIME_NOFRICTION;
2772 #ifndef FINAL_BUILD
2773 						if ( d_saberCombat->integer )
2774 						{
2775 							gi.Printf( "%s pushing away %s at %s\n", ent->NPC_type, traceEnt->NPC_type, vtos( traceEnt->client->ps.velocity ) );
2776 						}
2777 #endif
2778 					}
2779 				}
2780 			}
2781 		}
2782 	}
2783 
2784 	if ( g_saberRealisticCombat->integer > 1 )
2785 	{//always do damage, and lots of it
2786 		if ( g_saberRealisticCombat->integer > 2 )
2787 		{//always do damage, and lots of it
2788 			baseDamage = 25.0f;
2789 		}
2790 		else if ( baseDamage > 0.1f )
2791 		{//only do super damage if we would have done damage according to normal rules
2792 			baseDamage = 25.0f;
2793 		}
2794 	}
2795 	else if ( (!ent->s.number&&ent->client->ps.forcePowersActive&(1<<FP_SPEED)) )
2796 	{
2797 		baseDamage *= (1.0f-g_timescale->value);
2798 	}
2799 	// Get the old state of the blade
2800 	VectorCopy( mp1, baseOld );
2801 	VectorMA( baseOld, ent->client->ps.saberLength, md1, endOld );
2802 	// Get the future state of the blade
2803 	VectorCopy( mp2, baseNew );
2804 	VectorMA( baseNew, ent->client->ps.saberLength, md2, endNew );
2805 
2806 	sabersCrossed = -1;
2807 	if ( VectorCompare2( baseOld, baseNew ) && VectorCompare2( endOld, endNew ) )
2808 	{
2809 		hit_wall = WP_SaberDamageForTrace( ent->s.number, mp2, endNew, baseDamage*4, md2, qfalse, entPowerLevel, qfalse );
2810 	}
2811 	else
2812 	{
2813 		float aveLength, step = 8, stepsize = 8;
2814 		vec3_t	ma1, ma2, md2ang, curBase1, curBase2;
2815 		int	xx;
2816 		//do the trace at the base first
2817 		hit_wall = WP_SaberDamageForTrace( ent->s.number, baseOld, baseNew, baseDamage, md2, qfalse, entPowerLevel );
2818 
2819 		//if hit a saber, shorten rest of traces to match
2820 		if ( saberHitFraction < 1.0 )
2821 		{
2822 			//adjust muzzleDir...
2823 			vec3_t ma1, ma2;
2824 			vectoangles( md1, ma1 );
2825 			vectoangles( md2, ma2 );
2826 			for ( xx = 0; xx < 3; xx++ )
2827 			{
2828 				md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], saberHitFraction );
2829 			}
2830 			AngleVectors( md2ang, md2, NULL, NULL );
2831 			//shorten the base pos
2832 			VectorSubtract( mp2, mp1, baseDiff );
2833 			VectorMA( mp1, saberHitFraction, baseDiff, baseNew );
2834 			VectorMA( baseNew, ent->client->ps.saberLength, md2, endNew );
2835 		}
2836 
2837 		//If the angle diff in the blade is high, need to do it in chunks of 33 to avoid flattening of the arc
2838 		float dirInc, curDirFrac;
2839 		if ( PM_SaberInAttack( ent->client->ps.saberMove )
2840 			|| PM_SaberInSpecialAttack( ent->client->ps.torsoAnim )
2841 			|| PM_SpinningSaberAnim( ent->client->ps.torsoAnim )
2842 			|| PM_InSpecialJump( ent->client->ps.torsoAnim )
2843 			|| (g_timescale->value<1.0f&&PM_SaberInTransitionAny( ent->client->ps.saberMove )) )
2844 		{
2845 			curDirFrac = DotProduct( md1, md2 );
2846 		}
2847 		else
2848 		{
2849 			curDirFrac = 1.0f;
2850 		}
2851 		//NOTE: if saber spun at least 180 degrees since last damage trace, this is not reliable...!
2852 		if ( fabs(curDirFrac) < 1.0f - MAX_SABER_SWING_INC )
2853 		{//the saber blade spun more than 33 degrees since the last damage trace
2854 			curDirFrac = dirInc = 1.0f/((1.0f - curDirFrac)/MAX_SABER_SWING_INC);
2855 		}
2856 		else
2857 		{
2858 			curDirFrac = 1.0f;
2859 			dirInc = 0.0f;
2860 		}
2861 		qboolean hit_saber = qfalse;
2862 
2863 		vectoangles( md1, ma1 );
2864 		vectoangles( md2, ma2 );
2865 
2866 		vec3_t curMD1, curMD2;//, mdDiff, dirDiff;
2867 		//VectorSubtract( md2, md1, mdDiff );
2868 		VectorCopy( md1, curMD2 );
2869 		VectorCopy( baseOld, curBase2 );
2870 
2871 		while ( 1 )
2872 		{
2873 			VectorCopy( curMD2, curMD1 );
2874 			VectorCopy( curBase2, curBase1 );
2875 			if ( curDirFrac >= 1.0f )
2876 			{
2877 				VectorCopy( md2, curMD2 );
2878 				VectorCopy( baseNew, curBase2 );
2879 			}
2880 			else
2881 			{
2882 				for ( xx = 0; xx < 3; xx++ )
2883 				{
2884 					md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], curDirFrac );
2885 				}
2886 				AngleVectors( md2ang, curMD2, NULL, NULL );
2887 				//VectorMA( md1, curDirFrac, mdDiff, curMD2 );
2888 				VectorSubtract( baseNew, baseOld, baseDiff );
2889 				VectorMA( baseOld, curDirFrac, baseDiff, curBase2 );
2890 			}
2891 			// Move up the blade in intervals of stepsize
2892 			for ( step = stepsize; step < ent->client->ps.saberLength && step < ent->client->ps.saberLengthOld; step+=12 )
2893 			{
2894 				VectorMA( curBase1, step, curMD1, bladePointOld );
2895 				VectorMA( curBase2, step, curMD2, bladePointNew );
2896 				if ( WP_SaberDamageForTrace( ent->s.number, bladePointOld, bladePointNew, baseDamage, curMD2, qfalse, entPowerLevel ) )
2897 				{
2898 					hit_wall = qtrue;
2899 				}
2900 
2901 				//if hit a saber, shorten rest of traces to match
2902 				if ( saberHitFraction < 1.0 )
2903 				{
2904 					//adjust muzzle endpoint
2905 					VectorSubtract( mp2, mp1, baseDiff );
2906 					VectorMA( mp1, saberHitFraction, baseDiff, baseNew );
2907 					VectorMA( baseNew, ent->client->ps.saberLength, curMD2, endNew );
2908 					//adjust muzzleDir...
2909 					vec3_t curMA1, curMA2;
2910 					vectoangles( curMD1, curMA1 );
2911 					vectoangles( curMD2, curMA2 );
2912 					for ( xx = 0; xx < 3; xx++ )
2913 					{
2914 						md2ang[xx] = LerpAngle( curMA1[xx], curMA2[xx], saberHitFraction );
2915 					}
2916 					AngleVectors( md2ang, curMD2, NULL, NULL );
2917 					/*
2918 					VectorSubtract( curMD2, curMD1, dirDiff );
2919 					VectorMA( curMD1, saberHitFraction, dirDiff, curMD2 );
2920 					*/
2921 					hit_saber = qtrue;
2922 				}
2923 				if (hit_wall)
2924 				{
2925 					break;
2926 				}
2927 			}
2928 			if ( hit_wall || hit_saber )
2929 			{
2930 				break;
2931 			}
2932 			if ( curDirFrac >= 1.0f )
2933 			{
2934 				break;
2935 			}
2936 			else
2937 			{
2938 				curDirFrac += dirInc;
2939 				if ( curDirFrac >= 1.0f )
2940 				{
2941 					curDirFrac = 1.0f;
2942 				}
2943 			}
2944 		}
2945 
2946 		//do the trace at the end last
2947 		//Special check- adjust for length of blade not being a multiple of 12
2948 		aveLength = (ent->client->ps.saberLengthOld + ent->client->ps.saberLength)/2;
2949 		if ( step > aveLength )
2950 		{//less dmg if the last interval was not stepsize
2951 			tipDmgMod = (stepsize-(step-aveLength))/stepsize;
2952 		}
2953 		//NOTE: since this is the tip, we do not extrapolate the extra 16
2954 		if ( WP_SaberDamageForTrace( ent->s.number, endOld, endNew, tipDmgMod*baseDamage, md2, qfalse, entPowerLevel, qfalse ) )
2955 		{
2956 			hit_wall = qtrue;
2957 		}
2958 	}
2959 
2960 	if ( (saberHitFraction < 1.0f||(sabersCrossed>=0&&sabersCrossed<=32.0f)) && (ent->client->ps.weaponstate == WEAPON_FIRING || ent->client->ps.saberInFlight) )
2961 	{// The saber (in-hand) hit another saber, mano.
2962 		qboolean inFlightSaberBlocked = qfalse;
2963 		qboolean collisionResolved = qfalse;
2964 		qboolean deflected = qfalse;
2965 
2966 		gentity_t *hitEnt = &g_entities[saberHitEntity];
2967 		gentity_t *hitOwner = NULL;
2968 		int hitOwnerPowerLevel = FORCE_LEVEL_0;
2969 
2970 		if ( hitEnt )
2971 		{
2972 			hitOwner = hitEnt->owner;
2973 		}
2974 		if ( hitOwner && hitOwner->client )
2975 		{
2976 			hitOwnerPowerLevel = PM_PowerLevelForSaberAnim( &hitOwner->client->ps );
2977 			if ( entPowerLevel == FORCE_LEVEL_3 && PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) )
2978 			{//a special "unblockable" attack
2979 				if ( hitOwner->client->NPC_class == CLASS_DESANN
2980 					|| hitOwner->client->NPC_class == CLASS_TAVION
2981 					|| hitOwner->client->NPC_class == CLASS_LUKE )
2982 				{//these masters can even block unblockables (stops cheap kills)
2983 					entPowerLevel = FORCE_LEVEL_2;
2984 				}
2985 			}
2986 		}
2987 
2988 		//FIXME: check for certain anims, facing, etc, to make them lock into a sabers-locked pose
2989 		//SEF_LOCKED
2990 
2991 		if ( ent->client->ps.saberInFlight &&
2992 			ent->client->ps.saberActive &&
2993 			ent->client->ps.saberEntityNum != ENTITYNUM_NONE &&
2994 			ent->client->ps.saberEntityState != SES_RETURNING )
2995 		{//saber was blocked, return it
2996 			inFlightSaberBlocked = qtrue;
2997 		}
2998 
2999 		//FIXME: based on strength, position and angle of attack & defense, decide if:
3000 		//	defender and attacker lock sabers
3001 		//	*defender's parry should hold and attack bounces (or deflects, based on angle of sabers)
3002 		//	*defender's parry is somewhat broken and both bounce (or deflect)
3003 		//	*defender's parry is broken and they bounce while attacker's attack deflects or carries through (especially if they're dead)
3004 		//	defender is knocked down and attack goes through
3005 
3006 		//Check deflections and broken parries
3007 		if ( hitOwner && hitOwner->health > 0 && ent->health > 0 //both are alive
3008 			&& !inFlightSaberBlocked && hitOwner->client && !hitOwner->client->ps.saberInFlight && !ent->client->ps.saberInFlight//both have sabers in-hand
3009 			&& ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN
3010 			&& ent->client->ps.saberLockTime < level.time
3011 			&& hitOwner->client->ps.saberLockTime < level.time )
3012 		{//2 in-hand sabers hit
3013 			//FIXME: defender should not parry or block at all if not in a saber anim... like, if in a roll or knockdown...
3014 			if ( baseDamage )
3015 			{//there is damage involved, not just effects
3016 				qboolean entAttacking = qfalse;
3017 				qboolean hitOwnerAttacking = qfalse;
3018 				qboolean entDefending = qfalse;
3019 				qboolean hitOwnerDefending = qfalse;
3020 
3021 				if ( PM_SaberInAttack( ent->client->ps.saberMove ) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) )
3022 				{
3023 					entAttacking = qtrue;
3024 				}
3025 				else if ( entPowerLevel > FORCE_LEVEL_2 )
3026 				{
3027 					if ( PM_SaberInAttack( ent->client->ps.saberMove ) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) || PM_SaberInTransitionAny( ent->client->ps.saberMove ) )
3028 					{
3029 						entAttacking = qtrue;
3030 					}
3031 				}
3032 				if ( PM_SaberInParry( ent->client->ps.saberMove )
3033 					|| ent->client->ps.saberMove == LS_READY )
3034 				{
3035 					entDefending = qtrue;
3036 				}
3037 
3038 				if ( PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) )
3039 				{
3040 					hitOwnerAttacking = qtrue;
3041 				}
3042 				else if ( hitOwnerPowerLevel > FORCE_LEVEL_2 )
3043 				{
3044 					if ( PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) || PM_SaberInTransitionAny( hitOwner->client->ps.saberMove ) )
3045 					{
3046 						hitOwnerAttacking = qtrue;
3047 					}
3048 				}
3049 				if ( PM_SaberInParry( hitOwner->client->ps.saberMove )
3050 					 || hitOwner->client->ps.saberMove == LS_READY )
3051 				{
3052 					hitOwnerDefending = qtrue;
3053 				}
3054 
3055 				if ( entAttacking
3056 					&& hitOwnerAttacking
3057 					&& ( entPowerLevel == hitOwnerPowerLevel
3058 						|| (entPowerLevel > FORCE_LEVEL_2 && hitOwnerPowerLevel > FORCE_LEVEL_2 )
3059 						|| (entPowerLevel < FORCE_LEVEL_3 && hitOwnerPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_2 && Q_irand( 0, 2 ))
3060 						|| (entPowerLevel < FORCE_LEVEL_2 && hitOwnerPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_1 && Q_irand( 0, 1 ))
3061 						|| (hitOwnerPowerLevel < FORCE_LEVEL_3 && entPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_2 && !Q_irand( 0, 1 ))
3062 						|| (hitOwnerPowerLevel < FORCE_LEVEL_2 && entPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_1 && !Q_irand( 0, 1 )))
3063 					&& WP_SabersCheckLock( ent, hitOwner ) )
3064 				{
3065 					collisionResolved = qtrue;
3066 				}
3067 				else if ( hitOwnerAttacking
3068 					&& entDefending
3069 					&& !Q_irand( 0, 2 )
3070 					&& (ent->client->ps.saberMove != LS_READY || (hitOwnerPowerLevel-ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) < Q_irand( -6, 0 ) )
3071 					&& ((hitOwnerPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )||
3072 						(hitOwnerPowerLevel < FORCE_LEVEL_2 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_1 )||
3073 						(hitOwnerPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_0 && !Q_irand( 0, (hitOwnerPowerLevel-ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE]+1)*2 )))
3074 					&& WP_SabersCheckLock( hitOwner, ent ) )
3075 				{
3076 					collisionResolved = qtrue;
3077 				}
3078 				else if ( entAttacking && hitOwnerDefending )
3079 				{//I'm attacking hit, they're parrying
3080 					qboolean activeDefense = (qboolean)(
3081 						hitOwner->s.number ||
3082 						g_saberAutoBlocking->integer ||
3083 						hitOwner->client->ps.saberBlockingTime > level.time);
3084 
3085 					if ( !Q_irand( 0, 2 )
3086 						&& activeDefense
3087 						&& (hitOwner->client->ps.saberMove != LS_READY || (entPowerLevel-hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) < Q_irand( -6, 0 ) )
3088 						&& ( ( entPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )
3089 							|| ( entPowerLevel < FORCE_LEVEL_2 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_1 )
3090 							|| ( entPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_0 && !Q_irand( 0, (entPowerLevel-hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]+1)*2 )) )
3091 						&& WP_SabersCheckLock( ent, hitOwner ) )
3092 					{
3093 						collisionResolved = qtrue;
3094 					}
3095 					else if ( saberHitFraction < 1.0f )
3096 					{//an actual collision
3097 						if ( entPowerLevel < FORCE_LEVEL_3 && activeDefense )
3098 						{//strong attacks cannot be deflected
3099 							//based on angle of attack & angle of defensive saber, see if I should deflect off in another dir rather than bounce back
3100 							deflected = WP_GetSaberDeflectionAngle( ent, hitOwner );
3101 							//just so Jedi knows that he was blocked
3102 							ent->client->ps.saberEventFlags |= SEF_BLOCKED;
3103 						}
3104 						//base parry breaks on animation (saber attack level), not FP_SABER_OFFENSE
3105 						if ( entPowerLevel < FORCE_LEVEL_3
3106 							//&& ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] < FORCE_LEVEL_3//if you have high saber offense, you cannot have your attack knocked away, regardless of what style you're using?
3107 							&& hitOwner->client->ps.saberAnimLevel != FORCE_LEVEL_5
3108 							&& activeDefense
3109 							&& (hitOwnerPowerLevel > FORCE_LEVEL_2||(hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_2&&Q_irand(0,hitOwner->client->ps.saberAnimLevel))) )
3110 						{//knockaways can make fast-attacker go into a broken parry anim if the ent is using fast or med (but not Tavion)
3111 							//make me parry
3112 							WP_SaberParry( hitOwner, ent );
3113 							//turn the parry into a knockaway
3114 							hitOwner->client->ps.saberBounceMove = PM_KnockawayForParry( hitOwner->client->ps.saberBlocked );
3115 							//make them go into a broken parry
3116 							ent->client->ps.saberBounceMove = PM_BrokenParryForAttack( ent->client->ps.saberMove );
3117 							ent->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
3118 							if ( ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_2
3119 								&& (ent->s.number||g_saberRealisticCombat->integer) )//&& !Q_irand( 0, 3 ) )
3120 							{//knocked the saber right out of his hand! (never happens to player)
3121 								//Get a good velocity to send the saber in based on my parry move
3122 								vec3_t	throwDir;
3123 								if ( !PM_VelocityForBlockedMove( &hitOwner->client->ps, throwDir ) )
3124 								{
3125 									PM_VelocityForSaberMove( &ent->client->ps, throwDir );
3126 								}
3127 								WP_SaberLose( ent, throwDir );
3128 							}
3129 							//just so Jedi knows that he was blocked
3130 							ent->client->ps.saberEventFlags |= SEF_BLOCKED;
3131 #ifndef FINAL_BUILD
3132 							if ( d_saberCombat->integer )
3133 							{
3134 								gi.Printf( S_COLOR_RED"%s knockaway %s's attack, new move = %s, anim = %s\n", hitOwner->NPC_type, ent->NPC_type, saberMoveData[ent->client->ps.saberBounceMove].name, animTable[saberMoveData[ent->client->ps.saberBounceMove].animToUse].name );
3135 							}
3136 #endif
3137 						}
3138 						else if ( entPowerLevel > FORCE_LEVEL_2
3139 							|| !activeDefense
3140 							|| (!deflected && Q_irand( 0, PM_PowerLevelForSaberAnim( &ent->client->ps ) - hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]/*PM_PowerLevelForSaberAnim( &hitOwner->client->ps )*/ ) > 0 ) )
3141 						{//broke their parry altogether
3142 							if ( entPowerLevel > FORCE_LEVEL_2 || Q_irand( 0, ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] - hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) )
3143 							{//chance of continuing with the attack (not bouncing back)
3144 								ent->client->ps.saberEventFlags &= ~SEF_BLOCKED;
3145 								ent->client->ps.saberBounceMove = LS_NONE;
3146 								brokenParry = qtrue;
3147 							}
3148 							//do some time-consuming saber-knocked-aside broken parry anim
3149 							hitOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
3150 							hitOwner->client->ps.saberBounceMove = LS_NONE;
3151 							if ( hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_2
3152 								&& (ent->s.number||g_saberRealisticCombat->integer)
3153 								&& !Q_irand( 0, 2 ) )
3154 							{//knocked the saber right out of his hand!
3155 								//get the right velocity for my attack direction
3156 								vec3_t	throwDir;
3157 								PM_VelocityForSaberMove( &ent->client->ps, throwDir );
3158 								WP_SaberLose( hitOwner, throwDir );
3159 								if ( (ent->client->ps.saberAnimLevel == FORCE_LEVEL_3 && !Q_irand(0,3) )
3160 									|| ( ent->client->ps.saberAnimLevel==FORCE_LEVEL_4&&!Q_irand(0,1) ) )
3161 								{// a strong attack
3162 									if ( WP_BrokenParryKnockDown( hitOwner ) )
3163 									{
3164 										hitOwner->client->ps.saberBlocked = BLOCKED_NONE;
3165 										hitOwner->client->ps.saberBounceMove = LS_NONE;
3166 									}
3167 								}
3168 							}
3169 							else
3170 							{
3171 								if ( (ent->client->ps.saberAnimLevel == FORCE_LEVEL_3 && !Q_irand(0,5) )
3172 									|| ( ent->client->ps.saberAnimLevel==FORCE_LEVEL_4&&!Q_irand(0,3) ) )
3173 								{// a strong attack
3174 									if ( WP_BrokenParryKnockDown( hitOwner ) )
3175 									{
3176 										hitOwner->client->ps.saberBlocked = BLOCKED_NONE;
3177 										hitOwner->client->ps.saberBounceMove = LS_NONE;
3178 									}
3179 								}
3180 							}
3181 #ifndef FINAL_BUILD
3182 							if ( d_saberCombat->integer )
3183 							{
3184 								if ( ent->client->ps.saberEventFlags&SEF_BLOCKED )
3185 								{
3186 									gi.Printf( S_COLOR_RED"%s parry broken (bounce/deflect)!\n", hitOwner->targetname );
3187 								}
3188 								else
3189 								{
3190 									gi.Printf( S_COLOR_RED"%s parry broken (follow-through)!\n", hitOwner->targetname );
3191 								}
3192 							}
3193 #endif
3194 						}
3195 						else
3196 						{
3197 							WP_SaberParry( hitOwner, ent );
3198 							if ( PM_SaberInBounce( ent->client->ps.saberMove ) //FIXME: saberMove not set until pmove!
3199 								&& activeDefense
3200 								&& hitOwner->client->ps.saberAnimLevel != FORCE_LEVEL_1 && hitOwner->client->ps.saberAnimLevel != FORCE_LEVEL_5
3201 								&& hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )
3202 							{//attacker bounced off, and defender has ability to do knockaways, so do one unless we're using fast attacks
3203 								//turn the parry into a knockaway
3204 								hitOwner->client->ps.saberBounceMove = PM_KnockawayForParry( hitOwner->client->ps.saberBlocked );
3205 							}
3206 							else if ( (ent->client->ps.saberAnimLevel == FORCE_LEVEL_3 && !Q_irand(0,6) )
3207 									|| ( ent->client->ps.saberAnimLevel==FORCE_LEVEL_4 && !Q_irand(0,3) ) )
3208 							{// a strong attack can sometimes do a knockdown
3209 								//HMM... maybe only if they're moving backwards?
3210 								if ( WP_BrokenParryKnockDown( hitOwner ) )
3211 								{
3212 									hitOwner->client->ps.saberBlocked = BLOCKED_NONE;
3213 									hitOwner->client->ps.saberBounceMove = LS_NONE;
3214 								}
3215 							}
3216 						}
3217 						collisionResolved = qtrue;
3218 					}
3219 				}
3220 				/*
3221 				else if ( entDefending && hitOwnerAttacking )
3222 				{//I'm parrying, they're attacking
3223 					if ( hitOwnerPowerLevel < FORCE_LEVEL_3 )
3224 					{//strong attacks cannot be deflected
3225 						//based on angle of attack & angle of defensive saber, see if I should deflect off in another dir rather than bounce back
3226 						deflected = WP_GetSaberDeflectionAngle( hitOwner, ent );
3227 						//just so Jedi knows that he was blocked
3228 						hitOwner->client->ps.saberEventFlags |= SEF_BLOCKED;
3229 					}
3230 					//FIXME: base parry breaks on animation (saber attack level), not FP_SABER_OFFENSE
3231 					if ( hitOwnerPowerLevel > FORCE_LEVEL_2 || (!deflected && Q_irand( 0, PM_PowerLevelForSaberAnim( &hitOwner->client->ps ) - ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) > 0 ) )
3232 					{//broke my parry altogether
3233 						if ( hitOwnerPowerLevel > FORCE_LEVEL_2 || Q_irand( 0, hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] - ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) )
3234 						{//chance of continuing with the attack (not bouncing back)
3235 							hitOwner->client->ps.saberEventFlags &= ~SEF_BLOCKED;
3236 							hitOwner->client->ps.saberBounceMove = LS_NONE;
3237 						}
3238 						//do some time-consuming saber-knocked-aside broken parry anim
3239 						ent->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
3240 #ifndef FINAL_BUILD
3241 						if ( d_saberCombat->integer )
3242 						{
3243 							if ( hitOwner->client->ps.saberEventFlags&SEF_BLOCKED )
3244 							{
3245 								gi.Printf( S_COLOR_RED"%s parry broken (bounce/deflect)!\n", ent->targetname );
3246 							}
3247 							else
3248 							{
3249 								gi.Printf( S_COLOR_RED"%s parry broken (follow-through)!\n", ent->targetname );
3250 							}
3251 						}
3252 #endif
3253 					}
3254 					else
3255 					{
3256 						WP_SaberParry( ent, hitOwner );
3257 					}
3258 					collisionResolved = qtrue;
3259 				}
3260 				*/
3261 				else
3262 				{//some other kind of in-hand saber collision
3263 				}
3264 			}
3265 		}
3266 		else
3267 		{//some kind of in-flight collision
3268 		}
3269 
3270 		if ( saberHitFraction < 1.0f )
3271 		{
3272 			if ( !collisionResolved && baseDamage )
3273 			{//some other kind of in-hand saber collision
3274 				//handle my reaction
3275 				if ( !ent->client->ps.saberInFlight
3276 					&& ent->client->ps.saberLockTime < level.time )
3277 				{//my saber is in hand
3278 					if ( ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
3279 					{
3280 						if ( PM_SaberInAttack( ent->client->ps.saberMove ) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) ||
3281 							(entPowerLevel > FORCE_LEVEL_2&&!PM_SaberInIdle(ent->client->ps.saberMove)&&!PM_SaberInParry(ent->client->ps.saberMove)&&!PM_SaberInReflect(ent->client->ps.saberMove)) )
3282 						{//in the middle of attacking
3283 							if ( entPowerLevel < FORCE_LEVEL_3 && hitOwner->health > 0 )
3284 							{//don't deflect/bounce in strong attack or when enemy is dead
3285 								WP_GetSaberDeflectionAngle( ent, hitOwner );
3286 								ent->client->ps.saberEventFlags |= SEF_BLOCKED;
3287 								//since it was blocked/deflected, take away any damage done
3288 								//FIXME: what if the damage was done before the parry?
3289 								WP_SaberClearDamageForEntNum( hitOwner->s.number );
3290 							}
3291 						}
3292 						else
3293 						{//saber collided when not attacking, parry it
3294 							//since it was blocked/deflected, take away any damage done
3295 							//FIXME: what if the damage was done before the parry?
3296 							WP_SaberClearDamageForEntNum( hitOwner->s.number );
3297 							/*
3298 							if ( ent->s.number || g_saberAutoBlocking->integer || ent->client->ps.saberBlockingTime > level.time )
3299 							{//either an NPC or a player who has blocking
3300 								if ( !PM_SaberInTransitionAny( ent->client->ps.saberMove ) && !PM_SaberInBounce( ent->client->ps.saberMove ) )
3301 								{//I'm not attacking, in transition or in a bounce, so play a parry
3302 									//just so Jedi knows that he parried something
3303 									WP_SaberBlockNonRandom( ent, saberHitLocation, qfalse );
3304 								}
3305 								ent->client->ps.saberEventFlags |= SEF_PARRIED;
3306 							}
3307 							*/
3308 						}
3309 					}
3310 					else
3311 					{
3312 						//since it was blocked/deflected, take away any damage done
3313 						//FIXME: what if the damage was done before the parry?
3314 						WP_SaberClearDamageForEntNum( hitOwner->s.number );
3315 					}
3316 				}
3317 				else
3318 				{//nothing happens to *me* when my inFlight saber hits something
3319 				}
3320 				//handle their reaction
3321 				if ( hitOwner
3322 					&& hitOwner->health > 0
3323 					&& hitOwner->client
3324 					&& !hitOwner->client->ps.saberInFlight
3325 					&& hitOwner->client->ps.saberLockTime < level.time )
3326 				{//their saber is in hand
3327 					if ( PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) ||
3328 						(hitOwner->client->ps.saberAnimLevel > FORCE_LEVEL_2&&!PM_SaberInIdle(hitOwner->client->ps.saberMove)&&!PM_SaberInParry(hitOwner->client->ps.saberMove)&&!PM_SaberInReflect(hitOwner->client->ps.saberMove)) )
3329 					{//in the middle of attacking
3330 						/*
3331 						if ( hitOwner->client->ps.saberAnimLevel < FORCE_LEVEL_3 )
3332 						{//don't deflect/bounce in strong attack
3333 							WP_GetSaberDeflectionAngle( hitOwner, ent );
3334 							hitOwner->client->ps.saberEventFlags |= SEF_BLOCKED;
3335 						}
3336 						*/
3337 					}
3338 					else
3339 					{//saber collided when not attacking, parry it
3340 						if ( !PM_SaberInBrokenParry( hitOwner->client->ps.saberMove ) )
3341 						{//not currently in a broken parry
3342 							if ( !WP_SaberParry( hitOwner, ent ) )
3343 							{//FIXME: hitOwner can't parry, do some time-consuming saber-knocked-aside broken parry anim?
3344 								//hitOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
3345 							}
3346 						}
3347 					}
3348 				}
3349 				else
3350 				{//nothing happens to *hitOwner* when their inFlight saber hits something
3351 				}
3352 			}
3353 
3354 			//collision must have been handled by now
3355 			//Set the blocked attack bounce value in saberBlocked so we actually play our saberBounceMove anim
3356 			if ( ent->client->ps.saberEventFlags & SEF_BLOCKED )
3357 			{
3358 				if ( ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
3359 				{
3360 					ent->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
3361 				}
3362 			}
3363 			/*
3364 			if ( hitOwner && hitOwner->client->ps.saberEventFlags & SEF_BLOCKED )
3365 			{
3366 				hitOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
3367 			}
3368 			*/
3369 		}
3370 
3371 		if ( saberHitFraction < 1.0f || collisionResolved )
3372 		{//either actually hit or locked
3373 			if ( ent->client->ps.saberLockTime < level.time )
3374 			{
3375 				if ( inFlightSaberBlocked )
3376 				{//FIXME: never hear this sound
3377 					G_Sound( &g_entities[ent->client->ps.saberEntityNum], G_SoundIndex( va( "sound/weapons/saber/saberbounce%d.wav", Q_irand(1,3) ) ) );
3378 				}
3379 				else
3380 				{
3381 					if ( deflected )
3382 					{
3383 						G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberbounce%d.wav", Q_irand(1,3) ) ) );
3384 					}
3385 					else
3386 					{
3387 						G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
3388 					}
3389 				}
3390 				G_PlayEffect( "saber_block", saberHitLocation, saberHitNormal );
3391 			}
3392 			// Set the little screen flash - only when an attack is blocked
3393 			g_saberFlashTime = level.time-50;
3394 			VectorCopy( saberHitLocation, g_saberFlashPos );
3395 		}
3396 
3397 		if ( saberHitFraction < 1.0f )
3398 		{
3399 			if ( inFlightSaberBlocked )
3400 			{//we threw a saber and it was blocked, do any effects, etc.
3401 				int	knockAway = 5;
3402 				if ( hitEnt
3403 					&& hitOwner
3404 					&& hitOwner->client
3405 					&& (PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) || PM_SpinningSaberAnim( hitOwner->client->ps.torsoAnim )) )
3406 				{//if hit someone who was in an attack or spin anim, more likely to have in-flight saber knocked away
3407 					if ( hitOwnerPowerLevel > FORCE_LEVEL_2 )
3408 					{//string attacks almost always knock it aside!
3409 						knockAway = 1;
3410 					}
3411 					else
3412 					{//33% chance
3413 						knockAway = 2;
3414 					}
3415 				}
3416 				if ( !Q_irand( 0, knockAway ) || //random
3417 						( hitOwner && hitOwner->client &&
3418 							(hitOwner->client->NPC_class==CLASS_DESANN||hitOwner->client->NPC_class==CLASS_TAVION||hitOwner->client->NPC_class==CLASS_LUKE)
3419 						) //or if blocked by a Boss character FIXME: or base on defense level?
3420 					)//FIXME: player should not auto-block a flying saber, let him override the parry with an attack to knock the saber from the air, rather than this random chance
3421 				{//knock it aside and turn it off
3422 					G_PlayEffect( "saber_cut", saberHitLocation, saberHitNormal );
3423 					if ( hitEnt )
3424 					{
3425 						vec3_t newDir;
3426 
3427 						VectorSubtract( g_entities[ent->client->ps.saberEntityNum].currentOrigin, hitEnt->currentOrigin, newDir );
3428 						VectorNormalize( newDir );
3429 						G_ReflectMissile( ent, &g_entities[ent->client->ps.saberEntityNum], newDir );
3430 					}
3431 					Jedi_PlayDeflectSound( hitOwner );
3432 					WP_SaberDrop( ent, &g_entities[ent->client->ps.saberEntityNum] );
3433 				}
3434 				else
3435 				{
3436 					if ( !Q_irand( 0, 2 ) && hitEnt )
3437 					{
3438 						vec3_t newDir;
3439 						VectorSubtract( g_entities[ent->client->ps.saberEntityNum].currentOrigin, hitEnt->currentOrigin, newDir );
3440 						VectorNormalize( newDir );
3441 						G_ReflectMissile( ent, &g_entities[ent->client->ps.saberEntityNum], newDir );
3442 					}
3443 					WP_SaberReturn( ent, &g_entities[ent->client->ps.saberEntityNum] );
3444 				}
3445 			}
3446 		}
3447 	}
3448 
3449 	if ( ent->client->ps.saberLockTime > level.time
3450 		&& ent->s.number < ent->client->ps.saberLockEnemy
3451 		&& !Q_irand( 0, 3 ) )
3452 	{//need to make some kind of effect
3453 		vec3_t	hitNorm = {0,0,1};
3454 		if ( WP_SabersIntersection( ent, &g_entities[ent->client->ps.saberLockEnemy], g_saberFlashPos ) )
3455 		{
3456 			if ( Q_irand( 0, 10 ) )
3457 			{
3458 				G_PlayEffect( "saber_block", g_saberFlashPos, hitNorm );
3459 			}
3460 			else
3461 			{
3462 				g_saberFlashTime = level.time-50;
3463 				G_PlayEffect( "saber_cut", g_saberFlashPos, hitNorm );
3464 			}
3465 			G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
3466 		}
3467 	}
3468 
3469 	if ( WP_SaberApplyDamage( ent, baseDamage, baseDFlags, brokenParry ) )
3470 	{//actually did damage to something
3471 #ifndef FINAL_BUILD
3472 		if ( d_saberCombat->integer )
3473 		{
3474 			gi.Printf( "base damage was %4.2f\n", baseDamage );
3475 		}
3476 #endif
3477 		G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberhit%d.wav", Q_irand( 1, 3 ) ) ) );
3478 	}
3479 
3480 	if ( hit_wall )
3481 	{
3482 		//just so Jedi knows that he hit a wall
3483 		ent->client->ps.saberEventFlags |= SEF_HITWALL;
3484 		if ( ent->s.number == 0 )
3485 		{
3486 			AddSoundEvent( ent, ent->currentOrigin, 128, AEL_DISCOVERED );
3487 			AddSightEvent( ent, ent->currentOrigin, 256, AEL_DISCOVERED, 50 );
3488 		}
3489 	}
3490 }
3491 
3492 //SABER THROWING============================================================================
3493 //SABER THROWING============================================================================
3494 //SABER THROWING============================================================================
3495 //SABER THROWING============================================================================
3496 //SABER THROWING============================================================================
3497 //SABER THROWING============================================================================
3498 
3499 /*
3500 ================
3501 WP_SaberImpact
3502 
3503 ================
3504 */
WP_SaberImpact(gentity_t * owner,gentity_t * saber,trace_t * trace)3505 void WP_SaberImpact( gentity_t *owner, gentity_t *saber, trace_t *trace )
3506 {
3507 	gentity_t		*other;
3508 
3509 	other = &g_entities[trace->entityNum];
3510 
3511 	if ( other->takedamage && (other->svFlags&SVF_BBRUSH) )
3512 	{//a breakable brush?  break it!
3513 		vec3_t dir;
3514 		VectorCopy( saber->s.pos.trDelta, dir );
3515 		VectorNormalize( dir );
3516 
3517 		int dmg = other->health*2;
3518 		if ( other->health > 50 && dmg > 20 && !(other->svFlags&SVF_GLASS_BRUSH) )
3519 		{
3520 			dmg = 20;
3521 		}
3522 		G_Damage( other, owner, saber, dir, trace->endpos, dmg, 0, MOD_SABER );
3523 		G_PlayEffect( "saber_cut", trace->endpos, dir );
3524 		if ( owner->s.number == 0 )
3525 		{
3526 			AddSoundEvent( owner, trace->endpos, 256, AEL_DISCOVERED );
3527 			AddSightEvent( owner, trace->endpos, 512, AEL_DISCOVERED, 50 );
3528 		}
3529 		return;
3530 	}
3531 
3532 	if ( saber->s.pos.trType == TR_LINEAR )
3533 	{
3534 		//hit a wall? send it back
3535 		WP_SaberReturn( saber->owner, saber );
3536 	}
3537 
3538 	if ( other && !other->client && (other->contents&CONTENTS_LIGHTSABER) )//&& other->s.weapon == WP_SABER )
3539 	{//2 in-flight sabers collided!
3540 		//Big flash
3541 		//FIXME: bigger effect/sound?
3542 		//FIXME: STILL DOESNT WORK!!!
3543 		G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
3544 		G_PlayEffect( "saber_block", trace->endpos );
3545 		g_saberFlashTime = level.time-50;
3546 		VectorCopy( trace->endpos, g_saberFlashPos );
3547 	}
3548 
3549 	if ( owner && owner->s.number == 0 && owner->client )
3550 	{
3551 		//Add the event
3552 		if ( owner->client->ps.saberLength > 0 )
3553 		{//saber is on, very suspicious
3554 			AddSoundEvent( owner, saber->currentOrigin, 128, AEL_DISCOVERED );
3555 			AddSightEvent( owner, saber->currentOrigin, 256, AEL_DISCOVERED, 50 );
3556 		}
3557 		else
3558 		{//saber is off, not as suspicious
3559 			AddSoundEvent( owner, saber->currentOrigin, 128, AEL_SUSPICIOUS );
3560 			AddSightEvent( owner, saber->currentOrigin, 256, AEL_SUSPICIOUS );
3561 		}
3562 	}
3563 
3564 	// check for bounce
3565 	if ( !other->takedamage && ( saber->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) )
3566 	{
3567 		// Check to see if there is a bounce count
3568 		if ( saber->bounceCount ) {
3569 			// decrement number of bounces and then see if it should be done bouncing
3570 			if ( --saber->bounceCount <= 0 ) {
3571 				// He (or she) will bounce no more (after this current bounce, that is).
3572 				saber->s.eFlags &= ~(EF_BOUNCE | EF_BOUNCE_HALF);
3573 				if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING )
3574 				{
3575 					WP_SaberDrop( saber->owner, saber );
3576 				}
3577 				return;
3578 			}
3579 			else
3580 			{//bounced and still have bounces left
3581 				if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING )
3582 				{//under telekinetic control
3583 					if ( !gi.inPVS( saber->currentOrigin, owner->client->renderInfo.handRPoint ) )
3584 					{//not in the PVS of my master
3585 						saber->bounceCount -= 25;
3586 					}
3587 				}
3588 			}
3589 		}
3590 
3591 		if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING )
3592 		{
3593 			//don't home for a few frames so we can get around this thing
3594 			trace_t	bounceTr;
3595 			vec3_t	end;
3596 			float owner_dist = Distance( owner->client->renderInfo.handRPoint, saber->currentOrigin );
3597 
3598 			VectorMA( saber->currentOrigin, 10, trace->plane.normal, end );
3599 			gi.trace( &bounceTr, saber->currentOrigin, saber->mins, saber->maxs, end, owner->s.number, saber->clipmask, G2_NOCOLLIDE, 0 );
3600 			VectorCopy( bounceTr.endpos, saber->currentOrigin );
3601 			if ( owner_dist > 0 )
3602 			{
3603 				if ( owner_dist > 50 )
3604 				{
3605 					owner->client->ps.saberEntityDist = owner_dist-50;
3606 				}
3607 				else
3608 				{
3609 					owner->client->ps.saberEntityDist = 0;
3610 				}
3611 			}
3612 			return;
3613 		}
3614 
3615 		G_BounceMissile( saber, trace );
3616 
3617 		if ( saber->s.pos.trType == TR_GRAVITY )
3618 		{//bounced
3619 			//play a bounce sound
3620 			G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/bounce%d.wav", Q_irand( 1, 3 ) ) ) );
3621 			//change rotation
3622 			VectorCopy( saber->currentAngles, saber->s.apos.trBase );
3623 			saber->s.apos.trType = TR_LINEAR;
3624 			saber->s.apos.trTime = level.time;
3625 			VectorSet( saber->s.apos.trDelta, Q_irand( -300, 300 ), Q_irand( -300, 300 ), Q_irand( -300, 300 ) );
3626 		}
3627 		//see if we stopped
3628 		else if ( saber->s.pos.trType == TR_STATIONARY )
3629 		{//stopped
3630 			//play a bounce sound
3631 			G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/bounce%d.wav", Q_irand( 1, 3 ) ) ) );
3632 			//stop rotation
3633 			VectorClear( saber->s.apos.trDelta );
3634 			saber->currentAngles[0] = SABER_PITCH_HACK;
3635 			VectorCopy( saber->currentAngles, saber->s.apos.trBase );
3636 			//remember when it fell so it can return automagically
3637 			saber->aimDebounceTime = level.time;
3638 		}
3639 	}
3640 	else if ( other->client && other->health > 0
3641 		&& ( other->client->NPC_class == CLASS_DESANN || other->client->NPC_class == CLASS_TAVION || other->client->NPC_class == CLASS_LUKE || ( other->client->NPC_class == CLASS_GALAKMECH && other->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) ) )
3642 	{//Luke, Desann and Tavion slap thrown sabers aside
3643 		WP_SaberDrop( owner, saber );
3644 		G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
3645 		G_PlayEffect( "saber_block", trace->endpos );
3646 		g_saberFlashTime = level.time-50;
3647 		VectorCopy( trace->endpos, g_saberFlashPos );
3648 		//FIXME: make Luke/Desann/Tavion play an attack anim or some other special anim when this happens
3649 		Jedi_PlayDeflectSound( other );
3650 	}
3651 }
3652 
3653 extern float G_PointDistFromLineSegment( const vec3_t start, const vec3_t end, const vec3_t from );
WP_SaberInFlightReflectCheck(gentity_t * self,usercmd_t * ucmd)3654 void WP_SaberInFlightReflectCheck( gentity_t *self, usercmd_t *ucmd  )
3655 {
3656 	gentity_t	*ent;
3657 	gentity_t	*entityList[MAX_GENTITIES];
3658 	gentity_t	*missile_list[MAX_GENTITIES];
3659 	int			numListedEntities;
3660 	vec3_t		mins, maxs;
3661 	int			i, e;
3662 	int			ent_count = 0;
3663 	int			radius = 180;
3664 	vec3_t		center, forward;
3665 	vec3_t		tip;
3666 	vec3_t		up = {0,0,1};
3667 
3668 	if ( self->NPC && (self->NPC->scriptFlags&SCF_IGNORE_ALERTS) )
3669 	{//don't react to things flying at me...
3670 		return;
3671 	}
3672 	//sanity checks: make sure we actually have a saberent
3673 	if ( self->client->ps.weapon != WP_SABER )
3674 	{
3675 		return;
3676 	}
3677 	if ( !self->client->ps.saberInFlight )
3678 	{
3679 		return;
3680 	}
3681 	if ( !self->client->ps.saberLength )
3682 	{
3683 		return;
3684 	}
3685 	if ( self->client->ps.saberEntityNum == ENTITYNUM_NONE )
3686 	{
3687 		return;
3688 	}
3689 	gentity_t *saberent = &g_entities[self->client->ps.saberEntityNum];
3690 	if ( !saberent )
3691 	{
3692 		return;
3693 	}
3694 	//okay, enough damn sanity checks
3695 
3696 	VectorCopy( saberent->currentOrigin, center );
3697 
3698 	for ( i = 0 ; i < 3 ; i++ )
3699 	{
3700 		mins[i] = center[i] - radius;
3701 		maxs[i] = center[i] + radius;
3702 	}
3703 
3704 	numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
3705 
3706 	//FIXME: check visibility?
3707 	for ( e = 0 ; e < numListedEntities ; e++ )
3708 	{
3709 		ent = entityList[ e ];
3710 
3711 		if (ent == self)
3712 			continue;
3713 		if (ent->owner == self)
3714 			continue;
3715 		if ( !(ent->inuse) )
3716 			continue;
3717 		if ( ent->s.eType != ET_MISSILE )
3718 		{
3719 			if ( ent->client || ent->s.weapon != WP_SABER )
3720 			{//FIXME: wake up bad guys?
3721 				continue;
3722 			}
3723 			if ( ent->s.eFlags & EF_NODRAW )
3724 			{
3725 				continue;
3726 			}
3727 			if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
3728 			{//not a lightsaber
3729 				continue;
3730 			}
3731 		}
3732 		else
3733 		{//FIXME: make exploding missiles explode?
3734 			if ( ent->s.pos.trType == TR_STATIONARY )
3735 			{//nothing you can do with a stationary missile
3736 				continue;
3737 			}
3738 			if ( ent->splashDamage || ent->splashRadius )
3739 			{//can't deflect exploding missiles
3740 				if ( DistanceSquared( ent->currentOrigin, center ) < 256 )//16 squared
3741 				{
3742 					G_MissileImpacted( ent, saberent, ent->currentOrigin, up );
3743 				}
3744 				continue;
3745 			}
3746 		}
3747 
3748 		//don't deflect it if it's not within 16 units of the blade
3749 		VectorMA( self->client->renderInfo.muzzlePoint, self->client->ps.saberLength, self->client->renderInfo.muzzleDir, tip );
3750 
3751 		if( G_PointDistFromLineSegment( self->client->renderInfo.muzzlePoint, tip, ent->currentOrigin ) > 32 )
3752 		{
3753 			continue;
3754 		}
3755 		// ok, we are within the radius, add us to the incoming list
3756 		missile_list[ent_count] = ent;
3757 		ent_count++;
3758 
3759 	}
3760 
3761 	if ( ent_count )
3762 	{
3763 		vec3_t	fx_dir;
3764 		// we are done, do we have any to deflect?
3765 		if ( ent_count )
3766 		{
3767 			for ( int x = 0; x < ent_count; x++ )
3768 			{
3769 				if ( missile_list[x]->s.weapon == WP_SABER )
3770 				{//just send it back
3771 					if ( missile_list[x]->owner && missile_list[x]->owner->client && missile_list[x]->owner->client->ps.saberActive && missile_list[x]->s.pos.trType == TR_LINEAR && missile_list[x]->owner->client->ps.saberEntityState != SES_RETURNING )
3772 					{//it's on and being controlled
3773 						//FIXME: prevent it from damaging me?
3774 						WP_SaberReturn( missile_list[x]->owner, missile_list[x] );
3775 						VectorNormalize2( missile_list[x]->s.pos.trDelta, fx_dir );
3776 						G_PlayEffect( "saber_block", missile_list[x]->currentOrigin, fx_dir );
3777 						if ( missile_list[x]->owner->client->ps.saberInFlight && self->client->ps.saberInFlight )
3778 						{
3779 							G_Sound( missile_list[x], G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
3780 							g_saberFlashTime = level.time-50;
3781 							gentity_t *saber = &g_entities[self->client->ps.saberEntityNum];
3782 							vec3_t	org;
3783 							VectorSubtract( missile_list[x]->currentOrigin, saber->currentOrigin, org );
3784 							VectorMA( saber->currentOrigin, 0.5, org, org );
3785 							VectorCopy( org, g_saberFlashPos );
3786 						}
3787 					}
3788 				}
3789 				else
3790 				{//bounce it
3791 					if ( self->client && !self->s.number )
3792 					{
3793 						self->client->sess.missionStats.saberBlocksCnt++;
3794 					}
3795 
3796 					G_ReflectMissile( self, missile_list[x], forward );
3797 					//do an effect
3798 					VectorNormalize2( missile_list[x]->s.pos.trDelta, fx_dir );
3799 					G_PlayEffect( "blaster/deflect", missile_list[x]->currentOrigin, fx_dir );
3800 				}
3801 			}
3802 		}
3803 	}
3804 }
3805 
WP_SaberValidateEnemy(gentity_t * self,gentity_t * enemy)3806 qboolean WP_SaberValidateEnemy( gentity_t *self, gentity_t *enemy )
3807 {
3808 	if ( !enemy )
3809 	{
3810 		return qfalse;
3811 	}
3812 
3813 	if ( !enemy || enemy == self || !enemy->inuse || !enemy->client )
3814 	{//not valid
3815 		return qfalse;
3816 	}
3817 
3818 	if ( enemy->health <= 0 )
3819 	{//corpse
3820 		return qfalse;
3821 	}
3822 
3823 	if ( DistanceSquared( self->client->renderInfo.handRPoint, enemy->currentOrigin ) > saberThrowDistSquared[self->client->ps.forcePowerLevel[FP_SABERTHROW]] )
3824 	{//too far
3825 		return qfalse;
3826 	}
3827 
3828 	if ( (!InFront( enemy->currentOrigin, self->currentOrigin, self->client->ps.viewangles, 0.0f) || !G_ClearLOS( self, self->client->renderInfo.eyePoint, enemy ) )
3829 		&& ( DistanceHorizontalSquared( enemy->currentOrigin, self->currentOrigin ) > 65536 || fabs(enemy->currentOrigin[2]-self->currentOrigin[2]) > 384 )  )
3830 	{//(not in front or not clear LOS) & greater than 256 away
3831 		return qfalse;
3832 	}
3833 
3834 	if ( enemy->client->playerTeam == self->client->playerTeam )
3835 	{//on same team
3836 		return qfalse;
3837 	}
3838 
3839 	//LOS?
3840 
3841 	return qtrue;
3842 }
3843 
WP_SaberRateEnemy(gentity_t * enemy,vec3_t center,vec3_t forward,float radius)3844 float WP_SaberRateEnemy( gentity_t *enemy, vec3_t center, vec3_t forward, float radius )
3845 {
3846 	float rating;
3847 	vec3_t	dir;
3848 
3849 	VectorSubtract( enemy->currentOrigin, center, dir );
3850 	rating = (1.0f-(VectorNormalize( dir )/radius));
3851 	rating *= DotProduct( forward, dir );
3852 	return rating;
3853 }
3854 
WP_SaberFindEnemy(gentity_t * self,gentity_t * saber)3855 gentity_t *WP_SaberFindEnemy( gentity_t *self, gentity_t *saber )
3856 {
3857 //FIXME: should be a more intelligent way of doing this, like auto aim?
3858 //closest, most in front... did damage to... took damage from?  How do we know who the player is focusing on?
3859 	gentity_t	*ent, *bestEnt = NULL;
3860 	gentity_t	*entityList[MAX_GENTITIES];
3861 	int			numListedEntities;
3862 	vec3_t		center, mins, maxs, fwdangles, forward;
3863 	int			i, e;
3864 	float		radius = 400;
3865 	float		rating, bestRating = 0.0f;
3866 
3867 	//FIXME: no need to do this in 1st person?
3868 	fwdangles[1] = self->client->ps.viewangles[1];
3869 	AngleVectors( fwdangles, forward, NULL, NULL );
3870 
3871 	VectorCopy( saber->currentOrigin, center );
3872 
3873 	for ( i = 0 ; i < 3 ; i++ )
3874 	{
3875 		mins[i] = center[i] - radius;
3876 		maxs[i] = center[i] + radius;
3877 	}
3878 
3879 	if ( WP_SaberValidateEnemy( self, self->enemy ) )
3880 	{
3881 		bestEnt = self->enemy;
3882 		bestRating = WP_SaberRateEnemy( bestEnt, center, forward, radius );
3883 	}
3884 
3885 	numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
3886 
3887 	if ( !numListedEntities )
3888 	{//should we clear the enemy?
3889 		return bestEnt;
3890 	}
3891 
3892 	for ( e = 0 ; e < numListedEntities ; e++ )
3893 	{
3894 		ent = entityList[ e ];
3895 
3896 		if ( ent == self || ent == saber || ent == bestEnt )
3897 		{
3898 			continue;
3899 		}
3900 		if ( !WP_SaberValidateEnemy( self, ent ) )
3901 		{//doesn't meet criteria of valid look enemy (don't check current since we would have done that before this func's call
3902 			continue;
3903 		}
3904 		if ( !gi.inPVS( self->currentOrigin, ent->currentOrigin ) )
3905 		{//not even potentially visible
3906 			continue;
3907 		}
3908 		if ( !G_ClearLOS( self, self->client->renderInfo.eyePoint, ent ) )
3909 		{//can't see him
3910 			continue;
3911 		}
3912 		//rate him based on how close & how in front he is
3913 		rating = WP_SaberRateEnemy( ent, center, forward, radius );
3914 		if ( rating > bestRating )
3915 		{
3916 			bestEnt = ent;
3917 			bestRating = rating;
3918 		}
3919 	}
3920 	return bestEnt;
3921 }
3922 
WP_RunSaber(gentity_t * self,gentity_t * saber)3923 void WP_RunSaber( gentity_t *self, gentity_t *saber )
3924 {
3925 	vec3_t		origin, oldOrg;
3926 	trace_t		tr;
3927 
3928 	VectorCopy( saber->currentOrigin, oldOrg );
3929 	// get current position
3930 	EvaluateTrajectory( &saber->s.pos, level.time, origin );
3931 	// get current angles
3932 	EvaluateTrajectory( &saber->s.apos, level.time, saber->currentAngles );
3933 
3934 	// trace a line from the previous position to the current position,
3935 	// ignoring interactions with the missile owner
3936 	int clipmask = saber->clipmask;
3937 	if ( !self || !self->client || self->client->ps.saberLength <= 0 )
3938 	{//don't keep hitting other sabers when turned off
3939 		clipmask &= ~CONTENTS_LIGHTSABER;
3940 	}
3941 	gi.trace( &tr, saber->currentOrigin, saber->mins, saber->maxs, origin,
3942 		saber->owner ? saber->owner->s.number : ENTITYNUM_NONE, clipmask, G2_NOCOLLIDE, 0 );
3943 
3944 	VectorCopy( tr.endpos, saber->currentOrigin );
3945 
3946 	if ( self->client->ps.saberActive )
3947 	{
3948 		if ( self->client->ps.saberInFlight || (self->client->ps.weaponTime&&!Q_irand( 0, 100 )) )
3949 		{//make enemies run from a lit saber in flight or from me when I'm attacking
3950 			if ( !Q_irand( 0, 10 ) )
3951 			{//not so often...
3952 				AddSightEvent( self, saber->currentOrigin, self->client->ps.saberLength*3, AEL_DANGER, 100 );
3953 			}
3954 		}
3955 	}
3956 
3957 	if ( tr.startsolid )
3958 	{
3959 		tr.fraction = 0;
3960 	}
3961 
3962 	gi.linkentity( saber );
3963 
3964 	//touch push triggers?
3965 
3966 	if ( tr.fraction != 1 )
3967 	{
3968 		WP_SaberImpact( self, saber, &tr );
3969 	}
3970 
3971 	if ( saber->s.pos.trType == TR_LINEAR )
3972 	{//home
3973 		//figure out where saber should be
3974 		vec3_t	forward, saberHome, saberDest, fwdangles = {0};
3975 
3976 		VectorCopy( self->client->ps.viewangles, fwdangles );
3977 		if ( self->s.number )
3978 		{
3979 			fwdangles[0] -= 8;
3980 		}
3981 		else if ( cg.renderingThirdPerson )
3982 		{
3983 			fwdangles[0] -= 5;
3984 		}
3985 
3986 		if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_1
3987 			|| self->client->ps.saberEntityState == SES_RETURNING
3988 			|| VectorCompare( saber->s.pos.trDelta, vec3_origin ) )
3989 		{//control if it's returning or just starting
3990 			float	saberSpeed = 500;//FIXME: based on force level?
3991 			float	dist;
3992 			gentity_t *enemy = NULL;
3993 
3994 			AngleVectors( fwdangles, forward, NULL, NULL );
3995 
3996 			if ( self->client->ps.saberEntityDist < 100 )
3997 			{//make the saber head to my hand- the bolt it was attached to
3998 				VectorCopy( self->client->renderInfo.handRPoint, saberHome );
3999 			}
4000 			else
4001 			{//aim saber from eyes
4002 				VectorCopy( self->client->renderInfo.eyePoint, saberHome );
4003 			}
4004 			VectorMA( saberHome, self->client->ps.saberEntityDist, forward, saberDest );
4005 
4006 			if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 && self->client->ps.saberEntityState == SES_LEAVING )
4007 			{//max level
4008 				//pick an enemy
4009 				enemy = WP_SaberFindEnemy( self, saber );
4010 				if ( enemy )
4011 				{//home in on enemy
4012 					float enemyDist = Distance( self->client->renderInfo.handRPoint, enemy->currentOrigin );
4013 					VectorCopy( enemy->currentOrigin, saberDest );
4014 					saberDest[2] += enemy->maxs[2]/2.0f;//FIXME: when in a knockdown anim, the saber float above them... do we care?
4015 					self->client->ps.saberEntityDist = enemyDist;
4016 				}
4017 			}
4018 
4019 
4020 			//Make the saber head there
4021 			VectorSubtract( saberDest, saber->currentOrigin, saber->s.pos.trDelta );
4022 			dist = VectorNormalize( saber->s.pos.trDelta );
4023 			if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 && self->client->ps.saberEntityState == SES_LEAVING && !enemy )
4024 			{
4025 				if ( dist < 200 )
4026 				{
4027 					saberSpeed = 400 - (dist*2);
4028 				}
4029 			}
4030 			else if ( self->client->ps.saberEntityState == SES_LEAVING && dist < 50 )
4031 			{
4032 				saberSpeed = dist * 2 + 30;
4033 				if ( (enemy && dist > enemy->maxs[0]) || (!enemy && dist > 24) )
4034 				{//auto-tracking an enemy and we can't hit him
4035 					if ( saberSpeed < 120 )
4036 					{//clamp to a minimum speed
4037 						saberSpeed = 120;
4038 					}
4039 				}
4040 			}
4041 			/*
4042 			if ( self->client->ps.saberEntityState == SES_RETURNING )
4043 			{//FIXME: if returning, move faster?
4044 				saberSpeed = 800;
4045 				if ( dist < 200 )
4046 				{
4047 					saberSpeed -= 400 - (dist*2);
4048 				}
4049 			}
4050 			*/
4051 			VectorScale( saber->s.pos.trDelta, saberSpeed, saber->s.pos.trDelta );
4052 			//SnapVector( saber->s.pos.trDelta );	// save net bandwidth
4053 			VectorCopy( saber->currentOrigin, saber->s.pos.trBase );
4054 			saber->s.pos.trTime = level.time;
4055 			saber->s.pos.trType = TR_LINEAR;
4056 		}
4057 		else
4058 		{
4059 			VectorCopy( saber->currentOrigin, saber->s.pos.trBase );
4060 			saber->s.pos.trTime = level.time;
4061 			saber->s.pos.trType = TR_LINEAR;
4062 		}
4063 
4064 		//if it's heading back, point it's base at us
4065 		if ( self->client->ps.saberEntityState == SES_RETURNING )
4066 		{
4067 			fwdangles[0] += SABER_PITCH_HACK;
4068 			VectorCopy( fwdangles, saber->s.apos.trBase );
4069 			saber->s.apos.trTime = level.time;
4070 			saber->s.apos.trType = TR_INTERPOLATE;
4071 			VectorClear( saber->s.apos.trDelta );
4072 		}
4073 	}
4074 }
4075 
4076 
WP_SaberLaunch(gentity_t * self,gentity_t * saber,qboolean thrown)4077 qboolean WP_SaberLaunch( gentity_t *self, gentity_t *saber, qboolean thrown )
4078 {//FIXME: probably need a debounce time
4079 	vec3_t	saberMins={-3.0f,-3.0f,-3.0f};
4080 	vec3_t	saberMaxs={3.0f,3.0f,3.0f};
4081 	trace_t	trace;
4082 
4083 	if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
4084 	{
4085 		if ( !WP_ForcePowerUsable( self, FP_SABERTHROW, 20 ) )
4086 		{
4087 			return qfalse;
4088 		}
4089 	}
4090 	else
4091 	{
4092 		if ( !WP_ForcePowerUsable( self, FP_SABERTHROW, 0 ) )
4093 		{
4094 			return qfalse;
4095 		}
4096 	}
4097 	if ( !self->s.number && (cg.zoomMode || in_camera) )
4098 	{//can't saber throw when zoomed in or in cinematic
4099 		return qfalse;
4100 	}
4101 	//make sure it won't start in solid
4102 	gi.trace( &trace, self->client->renderInfo.handRPoint, saberMins, saberMaxs, self->client->renderInfo.handRPoint, saber->s.number, MASK_SOLID, G2_NOCOLLIDE, 0 );
4103 	if ( trace.startsolid || trace.allsolid )
4104 	{
4105 		return qfalse;
4106 	}
4107 	//make sure I'm not throwing it on the other side of a door or wall or whatever
4108 	gi.trace( &trace, self->currentOrigin, vec3_origin, vec3_origin, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID, G2_NOCOLLIDE, 0 );
4109 	if ( trace.startsolid || trace.allsolid || trace.fraction < 1.0f )
4110 	{
4111 		return qfalse;
4112 	}
4113 
4114 	if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
4115 	{//at max skill, the cost increases as keep it out
4116 		WP_ForcePowerStart( self, FP_SABERTHROW, 10 );
4117 	}
4118 	else
4119 	{
4120 		WP_ForcePowerStart( self, FP_SABERTHROW, 0 );
4121 	}
4122 	//draw it
4123 	saber->s.eFlags &= ~EF_NODRAW;
4124 	saber->svFlags |= SVF_BROADCAST;
4125 	saber->svFlags &= ~SVF_NOCLIENT;
4126 
4127 	//place it
4128 	VectorCopy( self->client->renderInfo.handRPoint, saber->currentOrigin );//muzzlePoint
4129 	VectorCopy( saber->currentOrigin, saber->s.pos.trBase );
4130 	saber->s.pos.trTime = level.time;
4131 	saber->s.pos.trType = TR_LINEAR;
4132 	VectorClear( saber->s.pos.trDelta );
4133 	gi.linkentity( saber );
4134 
4135 	//spin it
4136 	VectorClear( saber->s.apos.trBase );
4137 	saber->s.apos.trTime = level.time;
4138 	saber->s.apos.trType = TR_LINEAR;
4139 	if ( self->health > 0 && thrown )
4140 	{//throwing it
4141 		saber->s.apos.trBase[1] = self->client->ps.viewangles[1];
4142 		saber->s.apos.trBase[0] = SABER_PITCH_HACK;
4143 	}
4144 	else
4145 	{//dropping it
4146 		vectoangles( self->client->renderInfo.muzzleDir, saber->s.apos.trBase );
4147 	}
4148 	VectorClear( saber->s.apos.trDelta );
4149 
4150 	switch ( self->client->ps.forcePowerLevel[FP_SABERTHROW] )
4151 	{//FIXME: make a table?
4152 	default:
4153 	case FORCE_LEVEL_1:
4154 		saber->s.apos.trDelta[1] = 600;
4155 		break;
4156 	case FORCE_LEVEL_2:
4157 		saber->s.apos.trDelta[1] = 800;
4158 		break;
4159 	case FORCE_LEVEL_3:
4160 		saber->s.apos.trDelta[1] = 1200;
4161 		break;
4162 	}
4163 
4164 	//Take it out of my hand
4165 	self->client->ps.saberInFlight = qtrue;
4166 	self->client->ps.saberEntityState = SES_LEAVING;
4167 	self->client->ps.saberEntityDist = saberThrowDist[self->client->ps.forcePowerLevel[FP_SABERTHROW]];
4168 	self->client->ps.saberThrowTime = level.time;
4169 	//if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
4170 	{
4171 		self->client->ps.forcePowerDebounce[FP_SABERTHROW] = level.time + 1000;//so we can keep it out for a minimum amount of time
4172 	}
4173 
4174 	//if it's not active, turn it on
4175 	self->client->ps.saberActive = qtrue;
4176 
4177 	//turn on the saber trail
4178 	self->client->saberTrail.inAction = qtrue;
4179 	self->client->saberTrail.duration = 150;
4180 
4181 	//reset the mins
4182 	VectorCopy( saberMins, saber->mins );
4183 	VectorCopy( saberMaxs, saber->maxs );
4184 	saber->contents = 0;//CONTENTS_LIGHTSABER;
4185 	saber->clipmask = MASK_SOLID | CONTENTS_LIGHTSABER;
4186 
4187 	// remove the ghoul2 sabre model on the player
4188 	if ( self->weaponModel >= 0 )
4189 	{
4190 		gi.G2API_RemoveGhoul2Model(self->ghoul2, self->weaponModel);
4191 		self->weaponModel = -1;
4192 	}
4193 
4194 	return qtrue;
4195 }
4196 
WP_SaberLose(gentity_t * self,vec3_t throwDir)4197 qboolean WP_SaberLose( gentity_t *self, vec3_t throwDir )
4198 {
4199 	if ( !self || !self->client || self->client->ps.saberEntityNum <= 0 )
4200 	{//WTF?!!  We lost it already?
4201 		return qfalse;
4202 	}
4203 	gentity_t *dropped = &g_entities[self->client->ps.saberEntityNum];
4204 	if ( !self->client->ps.saberInFlight )
4205 	{//not alreay in air
4206 		//make it so we can throw it
4207 		self->client->ps.forcePowersKnown |= (1<<FP_SABERTHROW);
4208 		self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_1;
4209 		//throw it
4210 		if ( !WP_SaberLaunch( self, dropped, qfalse ) )
4211 		{//couldn't throw it
4212 			return qfalse;
4213 		}
4214 	}
4215 	if ( self->client->ps.saberActive )
4216 	{//on
4217 		//drop it instantly
4218 		WP_SaberDrop( self, dropped );
4219 	}
4220 	//optionally give it some thrown velocity
4221 	if ( throwDir && !VectorCompare( throwDir, vec3_origin ) )
4222 	{
4223 		VectorCopy( throwDir, dropped->s.pos.trDelta );
4224 	}
4225 	//don't pull it back on the next frame
4226 	if ( self->NPC )
4227 	{
4228 		self->NPC->last_ucmd.buttons &= ~BUTTON_ATTACK;
4229 	}
4230 	return qtrue;
4231 }
4232 
WP_SaberCatch(gentity_t * self,gentity_t * saber,qboolean switchToSaber)4233 void WP_SaberCatch( gentity_t *self, gentity_t *saber, qboolean switchToSaber )
4234 {//FIXME: probably need a debounce time
4235 	if ( self->health > 0 && !PM_SaberInBrokenParry( self->client->ps.saberMove ) && self->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
4236 	{
4237 		//don't draw it
4238 		saber->s.eFlags |= EF_NODRAW;
4239 		saber->svFlags &= SVF_BROADCAST;
4240 		saber->svFlags |= SVF_NOCLIENT;
4241 
4242 		//take off any gravity stuff if we'd dropped it
4243 		saber->s.pos.trType = TR_LINEAR;
4244 		saber->s.eFlags &= ~EF_BOUNCE_HALF;
4245 
4246 		//Put it in my hand
4247 		self->client->ps.saberInFlight = qfalse;
4248 		self->client->ps.saberEntityState = SES_LEAVING;
4249 
4250 		//turn off the saber trail
4251 		self->client->saberTrail.inAction = qfalse;
4252 		self->client->saberTrail.duration = 75;
4253 
4254 		//reset its contents/clipmask
4255 		saber->contents = CONTENTS_LIGHTSABER;// | CONTENTS_SHOTCLIP;
4256 		saber->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
4257 
4258 		//play catch sound
4259 		G_Sound( saber, G_SoundIndex( "sound/weapons/saber/saber_catch.wav" ) );
4260 		//FIXME: if an NPC, don't turn it back on if no enemy or enemy is dead...
4261 		//if it's not our current weapon, make it our current weapon
4262 		if ( self->client->ps.weapon == WP_SABER )
4263 		{
4264 			G_CreateG2AttachedWeaponModel( self, self->client->ps.saberModel );
4265 		}
4266 		if ( switchToSaber )
4267 		{
4268 			if ( self->client->ps.weapon != WP_SABER )
4269 			{
4270 				CG_ChangeWeapon( WP_SABER );
4271 			}
4272 			else
4273 			{//if it's not active, turn it on
4274 				self->client->ps.saberActive = qtrue;
4275 			}
4276 		}
4277 	}
4278 }
4279 
4280 
WP_SaberReturn(gentity_t * self,gentity_t * saber)4281 void WP_SaberReturn( gentity_t *self, gentity_t *saber )
4282 {
4283 	if ( PM_SaberInBrokenParry( self->client->ps.saberMove ) || self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
4284 	{
4285 		return;
4286 	}
4287 	if ( self && self->client )
4288 	{//still alive and stuff
4289 		//FIXME: when it's returning, flies butt first, but seems to do a lot of damage when going through people... hmm...
4290 		self->client->ps.saberEntityState = SES_RETURNING;
4291 		//turn down the saber trail
4292 		self->client->saberTrail.inAction = qfalse;
4293 		self->client->saberTrail.duration = 75;
4294 	}
4295 	if ( !(saber->s.eFlags&EF_BOUNCE) )
4296 	{
4297 		saber->s.eFlags |= EF_BOUNCE;
4298 		saber->bounceCount = 300;
4299 	}
4300 }
4301 
4302 
WP_SaberDrop(gentity_t * self,gentity_t * saber)4303 void WP_SaberDrop( gentity_t *self, gentity_t *saber )
4304 {
4305 	saber->s.eFlags &= ~EF_BOUNCE;
4306 	saber->bounceCount = 0;
4307 	//make it fall
4308 	saber->s.pos.trType = TR_GRAVITY;
4309 	//make it bounce some
4310 	saber->s.eFlags |= EF_BOUNCE_HALF;
4311 	//make it spin
4312 	VectorCopy( saber->currentAngles, saber->s.apos.trBase );
4313 	saber->s.apos.trType = TR_LINEAR;
4314 	saber->s.apos.trTime = level.time;
4315 	VectorSet( saber->s.apos.trDelta, Q_irand( -300, 300 ), saber->s.apos.trDelta[1], Q_irand( -300, 300 ) );
4316 	if ( !saber->s.apos.trDelta[1] )
4317 	{
4318 		saber->s.apos.trDelta[1] = Q_irand( -300, 300 );
4319 	}
4320 	//force it to be ready to return
4321 	self->client->ps.saberEntityDist = 0;
4322 	self->client->ps.saberEntityState = SES_RETURNING;
4323 	//turn it off
4324 	self->client->ps.saberActive = qfalse;
4325 	//turn off the saber trail
4326 	self->client->saberTrail.inAction = qfalse;
4327 	self->client->saberTrail.duration = 75;
4328 	//play the saber turning off sound
4329 	if ( self->client->playerTeam == TEAM_PLAYER )
4330 	{
4331 		G_SoundOnEnt( saber, CHAN_AUTO, "sound/weapons/saber/saberoff.wav" );
4332 	}
4333 	else
4334 	{
4335 		G_SoundOnEnt( saber, CHAN_AUTO, "sound/weapons/saber/enemy_saber_off.wav" );
4336 	}
4337 
4338 	if ( self->health <= 0 )
4339 	{//owner is dead!
4340 		saber->s.time = level.time;//will make us free ourselves after a time
4341 	}
4342 }
4343 
4344 
WP_SaberPull(gentity_t * self,gentity_t * saber)4345 void WP_SaberPull( gentity_t *self, gentity_t *saber )
4346 {
4347 	if ( PM_SaberInBrokenParry( self->client->ps.saberMove ) || self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
4348 	{
4349 		return;
4350 	}
4351 	if ( self->health > 0 )
4352 	{
4353 		//take off gravity
4354 		saber->s.pos.trType = TR_LINEAR;
4355 		//take off bounce
4356 		saber->s.eFlags &= EF_BOUNCE_HALF;
4357 		//play sound
4358 		G_Sound( self, G_SoundIndex( "sound/weapons/force/pull.wav" ) );
4359 	}
4360 }
4361 
4362 
4363 // Check if we are throwing it, launch it if needed, update position if needed.
WP_SaberThrow(gentity_t * self,usercmd_t * ucmd)4364 void WP_SaberThrow( gentity_t *self, usercmd_t *ucmd )
4365 {
4366 	vec3_t			saberDiff;
4367 	trace_t			tr;
4368 	//static float	SABER_SPEED = 10;
4369 
4370 	gentity_t *saberent;
4371 
4372 	if ( self->client->ps.saberEntityNum <= 0 || self->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
4373 	{//WTF?!!  We lost it?
4374 		return;
4375 	}
4376 
4377 	saberent = &g_entities[self->client->ps.saberEntityNum];
4378 
4379 	VectorSubtract( self->client->renderInfo.handRPoint, saberent->currentOrigin, saberDiff );
4380 
4381 	//is our saber in flight?
4382 	if ( !self->client->ps.saberInFlight )
4383 	{//saber is not in flight right now
4384 		if ( self->client->ps.weapon != WP_SABER )
4385 		{//don't even have it out
4386 			return;
4387 		}
4388 		else if ( ucmd->buttons & BUTTON_ALT_ATTACK && !(self->client->ps.pm_flags&PMF_ALT_ATTACK_HELD) )
4389 		{//still holding it, not still holding attack from a previous throw, so throw it.
4390 			if ( !(self->client->ps.saberEventFlags&SEF_INWATER) && WP_SaberLaunch( self, saberent, qtrue ) )
4391 			{
4392 				if ( self->client && !self->s.number )
4393 				{
4394 					self->client->sess.missionStats.saberThrownCnt++;
4395 				}
4396 				//need to recalc this because we just moved it
4397 				VectorSubtract( self->client->renderInfo.handRPoint, saberent->currentOrigin, saberDiff );
4398 			}
4399 			else
4400 			{//couldn't throw it
4401 				return;
4402 			}
4403 		}
4404 		else
4405 		{//holding it, don't want to throw it, go away.
4406 			return;
4407 		}
4408 	}
4409 	else
4410 	{//inflight
4411 		//is our saber currently on it's way back to us?
4412 		if ( self->client->ps.saberEntityState == SES_RETURNING )
4413 		{//see if we're close enough to pick it up
4414 			if ( VectorLengthSquared( saberDiff ) <= 256 )//16 squared//G_BoundsOverlap( self->absmin, self->absmax, saberent->absmin, saberent->absmax ) )//
4415 			{//caught it
4416 				vec3_t	axisPoint;
4417 				trace_t	trace;
4418 				VectorCopy( self->currentOrigin, axisPoint );
4419 				axisPoint[2] = self->client->renderInfo.handRPoint[2];
4420 				gi.trace( &trace, axisPoint, vec3_origin, vec3_origin, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID, G2_NOCOLLIDE, 0 );
4421 				if ( !trace.startsolid && trace.fraction >= 1.0f )
4422 				{//our hand isn't through a wall
4423 					WP_SaberCatch( self, saberent, qtrue );
4424 					NPC_SetAnim( self, SETANIM_TORSO, TORSO_HANDRETRACT1, SETANIM_FLAG_OVERRIDE );
4425 				}
4426 				return;
4427 			}
4428 		}
4429 
4430 		if ( saberent->s.pos.trType != TR_STATIONARY )
4431 		{//saber is in flight, lerp it
4432 			WP_RunSaber( self, saberent );
4433 		}
4434 		else
4435 		{//it fell on the ground
4436 			if ( self->health <= 0 && level.time > saberent->s.time + 5000 )
4437 			{//make us free ourselves after a time
4438 				G_FreeEntity( saberent );
4439 				self->client->ps.saberEntityNum = ENTITYNUM_NONE;
4440 				return;
4441 			}
4442 			if ( (!self->s.number && level.time - saberent->aimDebounceTime > 15000)
4443 				|| (self->s.number && level.time - saberent->aimDebounceTime > 5000) )
4444 			{//(only for player) been missing for 15 seconds, automagicially return
4445 				WP_SaberCatch( self, saberent, qfalse );
4446 				return;
4447 			}
4448 		}
4449 	}
4450 
4451 	//are we still trying to use the saber?
4452 	if ( self->client->ps.weapon != WP_SABER )
4453 	{//switched away
4454 		if ( !self->client->ps.saberInFlight )
4455 		{//wasn't throwing saber
4456 			return;
4457 		}
4458 		else if ( saberent->s.pos.trType == TR_LINEAR )
4459 		{//switched away while controlling it, just drop the saber
4460 			WP_SaberDrop( self, saberent );
4461 			return;
4462 		}
4463 		else
4464 		{//it's on the ground, see if it's inside us (touching)
4465 			if ( G_PointInBounds( saberent->currentOrigin, self->absmin, self->absmax ) )
4466 			{//it's in us, pick it up automatically
4467 				WP_SaberPull( self, saberent );
4468 			}
4469 		}
4470 	}
4471 	else if ( saberent->s.pos.trType != TR_LINEAR )
4472 	{//weapon is saber and not flying
4473 		if ( self->client->ps.saberInFlight )
4474 		{//we dropped it
4475 			if ( ucmd->buttons & BUTTON_ATTACK )//|| self->client->ps.weaponstate == WEAPON_RAISING )//ucmd->buttons & BUTTON_ALT_ATTACK ||
4476 			{//we actively want to pick it up or we just switched to it, so pull it back
4477 				gi.trace( &tr, saberent->currentOrigin, saberent->mins, saberent->maxs, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID, G2_NOCOLLIDE, 0 );
4478 
4479 				if ( tr.allsolid || tr.startsolid || tr.fraction < 1.0f )
4480 				{//can't pick it up yet, no LOS
4481 					return;
4482 				}
4483 				//clear LOS, pick it up
4484 				WP_SaberPull( self, saberent );
4485 			}
4486 			else
4487 			{//see if it's inside us (touching)
4488 				if ( G_PointInBounds( saberent->currentOrigin, self->absmin, self->absmax ) )
4489 				{//it's in us, pick it up automatically
4490 					WP_SaberPull( self, saberent );
4491 				}
4492 			}
4493 		}
4494 	}
4495 	else if ( self->health <= 0 && self->client->ps.saberInFlight )
4496 	{//we died, drop it
4497 		WP_SaberDrop( self, saberent );
4498 		return;
4499 	}
4500 	else if ( !self->client->ps.saberActive && self->client->ps.saberEntityState != SES_RETURNING )
4501 	{//we turned it off, drop it
4502 		WP_SaberDrop( self, saberent );
4503 		return;
4504 	}
4505 
4506 	//TODO: if deactivate saber in flight, should it drop?
4507 
4508 	if ( saberent->s.pos.trType != TR_LINEAR )
4509 	{//don't home
4510 		return;
4511 	}
4512 
4513 	float saberDist = VectorLength( saberDiff );
4514 	if ( self->client->ps.saberEntityState == SES_LEAVING )
4515 	{//saber still flying forward
4516 		if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
4517 		{//still holding it out
4518 			if ( !(ucmd->buttons&BUTTON_ALT_ATTACK) && self->client->ps.forcePowerDebounce[FP_SABERTHROW] < level.time )
4519 			{//done throwing, return to me
4520 				if ( self->client->ps.saberActive )
4521 				{//still on
4522 					WP_SaberReturn( self, saberent );
4523 				}
4524 			}
4525 			else if ( level.time - self->client->ps.saberThrowTime >= 100 )
4526 			{
4527 				if ( WP_ForcePowerAvailable( self, FP_SABERTHROW, 1 ) )
4528 				{
4529 					WP_ForcePowerDrain( self, FP_SABERTHROW, 1 );
4530 					self->client->ps.saberThrowTime = level.time;
4531 				}
4532 				else
4533 				{//out of force power, return to me
4534 					WP_SaberReturn( self, saberent );
4535 				}
4536 			}
4537 		}
4538 		else
4539 		{
4540 			if ( !(ucmd->buttons&BUTTON_ALT_ATTACK) && self->client->ps.forcePowerDebounce[FP_SABERTHROW] < level.time )
4541 			{//not holding button and has been out at least 1 second, return to me
4542 				if ( self->client->ps.saberActive )
4543 				{//still on
4544 					WP_SaberReturn( self, saberent );
4545 				}
4546 			}
4547 			else if ( level.time - self->client->ps.saberThrowTime > 3000
4548 				|| (self->client->ps.forcePowerLevel[FP_SABERTHROW]==FORCE_LEVEL_1&&saberDist>=self->client->ps.saberEntityDist) )
4549 			{//been out too long, or saber throw 1 went too far, return to me
4550 				if ( self->client->ps.saberActive )
4551 				{//still on
4552 					WP_SaberReturn( self, saberent );
4553 				}
4554 			}
4555 		}
4556 	}
4557 	if ( self->client->ps.saberEntityState == SES_RETURNING )
4558 	{
4559 		if ( self->client->ps.saberEntityDist > 0 )
4560 		{
4561 			self->client->ps.saberEntityDist -= 25;
4562 		}
4563 		if ( self->client->ps.saberEntityDist < 0 )
4564 		{
4565 			self->client->ps.saberEntityDist = 0;
4566 		}
4567 		else if ( saberDist < self->client->ps.saberEntityDist )
4568 		{//if it's coming back to me, never push it away
4569 			self->client->ps.saberEntityDist = saberDist;
4570 		}
4571 	}
4572 }
4573 
4574 
4575 //SABER BLOCKING============================================================================
4576 //SABER BLOCKING============================================================================
4577 //SABER BLOCKING============================================================================
4578 //SABER BLOCKING============================================================================
4579 //SABER BLOCKING============================================================================
WP_MissileBlockForBlock(int saberBlock)4580 int WP_MissileBlockForBlock( int saberBlock )
4581 {
4582 	switch( saberBlock )
4583 	{
4584 	case BLOCKED_UPPER_RIGHT:
4585 		return BLOCKED_UPPER_RIGHT_PROJ;
4586 		break;
4587 	case BLOCKED_UPPER_LEFT:
4588 		return BLOCKED_UPPER_LEFT_PROJ;
4589 		break;
4590 	case BLOCKED_LOWER_RIGHT:
4591 		return BLOCKED_LOWER_RIGHT_PROJ;
4592 		break;
4593 	case BLOCKED_LOWER_LEFT:
4594 		return BLOCKED_LOWER_LEFT_PROJ;
4595 		break;
4596 	case BLOCKED_TOP:
4597 		return BLOCKED_TOP_PROJ;
4598 		break;
4599 	}
4600 	return saberBlock;
4601 }
4602 
WP_SaberBlockNonRandom(gentity_t * self,vec3_t hitloc,qboolean missileBlock)4603 void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock )
4604 {
4605 	vec3_t diff, fwdangles={0,0,0}, right;
4606 	float rightdot;
4607 	float zdiff;
4608 
4609 	if ( self->client->ps.weaponstate == WEAPON_DROPPING ||
4610 		self->client->ps.weaponstate == WEAPON_RAISING )
4611 	{//don't block while changing weapons
4612 		return;
4613 	}
4614 	//NPCs don't auto-block
4615 	if ( !missileBlock && self->s.number != 0 && self->client->ps.saberBlocked != BLOCKED_NONE )
4616 	{
4617 		return;
4618 	}
4619 
4620 	VectorSubtract( hitloc, self->client->renderInfo.eyePoint, diff );
4621 	diff[2] = 0;
4622 	VectorNormalize( diff );
4623 
4624 	fwdangles[1] = self->client->ps.viewangles[1];
4625 	// Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
4626 	AngleVectors( fwdangles, NULL, right, NULL );
4627 
4628 	rightdot = DotProduct(right, diff);
4629 	zdiff = hitloc[2] - self->client->renderInfo.eyePoint[2];
4630 
4631 	//FIXME: take torsoAngles into account?
4632 	if ( zdiff > -5 )//0 )//40 )
4633 	{
4634 		if ( rightdot > 0.3 )
4635 		{
4636 			self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
4637 		}
4638 		else if ( rightdot < -0.3 )
4639 		{
4640 			self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
4641 		}
4642 		else
4643 		{
4644 			self->client->ps.saberBlocked = BLOCKED_TOP;
4645 		}
4646 	}
4647 	else if ( zdiff > -22 )//-20 )//20 )
4648 	{
4649 		if ( zdiff < -10 )//30 )
4650 		{//hmm, pretty low, but not low enough to use the low block, so we need to duck
4651 			//NPC should duck, but NPC should never get here
4652 		}
4653 		if ( rightdot > 0.1 )
4654 		{
4655 			self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
4656 		}
4657 		else if ( rightdot < -0.1 )
4658 		{
4659 			self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
4660 		}
4661 		else
4662 		{//FIXME: this looks really weird if the shot is too low!
4663 			self->client->ps.saberBlocked = BLOCKED_TOP;
4664 		}
4665 	}
4666 	else
4667 	{
4668 		if ( rightdot >= 0 )
4669 		{
4670 			self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
4671 		}
4672 		else
4673 		{
4674 			self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
4675 		}
4676 	}
4677 
4678 #ifndef FINAL_BUILD
4679 	if ( d_saberCombat->integer )
4680 	{
4681 		if ( !self->s.number )
4682 		{
4683 			gi.Printf( "EyeZ: %4.2f  HitZ: %4.2f  zdiff: %4.2f  rdot: %4.2f\n", self->client->renderInfo.eyePoint[2], hitloc[2], zdiff, rightdot );
4684 			switch ( self->client->ps.saberBlocked )
4685 			{
4686 			case BLOCKED_TOP:
4687 				gi.Printf( "BLOCKED_TOP\n" );
4688 				break;
4689 			case BLOCKED_UPPER_RIGHT:
4690 				gi.Printf( "BLOCKED_UPPER_RIGHT\n" );
4691 				break;
4692 			case BLOCKED_UPPER_LEFT:
4693 				gi.Printf( "BLOCKED_UPPER_LEFT\n" );
4694 				break;
4695 			case BLOCKED_LOWER_RIGHT:
4696 				gi.Printf( "BLOCKED_LOWER_RIGHT\n" );
4697 				break;
4698 			case BLOCKED_LOWER_LEFT:
4699 				gi.Printf( "BLOCKED_LOWER_LEFT\n" );
4700 				break;
4701 			default:
4702 				break;
4703 			}
4704 		}
4705 	}
4706 #endif
4707 
4708 	if ( missileBlock )
4709 	{
4710 		self->client->ps.saberBlocked = WP_MissileBlockForBlock( self->client->ps.saberBlocked );
4711 	}
4712 
4713 	if ( self->client->ps.saberBlocked != BLOCKED_NONE )
4714 	{
4715 		int parryReCalcTime = Jedi_ReCalcParryTime( self, EVASION_PARRY );
4716 		if ( self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] < level.time + parryReCalcTime )
4717 		{
4718 			self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + parryReCalcTime;
4719 		}
4720 	}
4721 }
4722 
WP_SaberBlock(gentity_t * saber,vec3_t hitloc,qboolean missileBlock)4723 void WP_SaberBlock( gentity_t *saber, vec3_t hitloc, qboolean missileBlock )
4724 {
4725 	gentity_t *playerent;
4726 	vec3_t diff, fwdangles={0,0,0}, right;
4727 	float rightdot;
4728 	float zdiff;
4729 
4730 	if (saber && saber->owner)
4731 	{
4732 		playerent = saber->owner;
4733 		if (!playerent->client)
4734 		{
4735 			return;
4736 		}
4737 		if ( playerent->client->ps.weaponstate == WEAPON_DROPPING ||
4738 			playerent->client->ps.weaponstate == WEAPON_RAISING )
4739 		{//don't block while changing weapons
4740 			return;
4741 		}
4742 	}
4743 	else
4744 	{	// Bad entity passed.
4745 		return;
4746 	}
4747 
4748 	//temporarily disabling auto-blocking for NPCs...
4749 	if ( !missileBlock && playerent->s.number != 0 && playerent->client->ps.saberBlocked != BLOCKED_NONE )
4750 	{
4751 		return;
4752 	}
4753 
4754 	VectorSubtract(hitloc, playerent->currentOrigin, diff);
4755 	VectorNormalize(diff);
4756 
4757 	fwdangles[1] = playerent->client->ps.viewangles[1];
4758 	// Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
4759 	AngleVectors( fwdangles, NULL, right, NULL );
4760 
4761 	rightdot = DotProduct(right, diff) + Q_flrand(-0.2f,0.2f);
4762 	zdiff = hitloc[2] - playerent->currentOrigin[2] + Q_irand(-8,8);
4763 
4764 	// Figure out what quadrant the block was in.
4765 	if (zdiff > 24)
4766 	{	// Attack from above
4767 		if (Q_irand(0,1))
4768 		{
4769 			playerent->client->ps.saberBlocked = BLOCKED_TOP;
4770 		}
4771 		else
4772 		{
4773 			playerent->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
4774 		}
4775 	}
4776 	else if (zdiff > 13)
4777 	{	// The upper half has three viable blocks...
4778 		if (rightdot > 0.25)
4779 		{	// In the right quadrant...
4780 			if (Q_irand(0,1))
4781 			{
4782 				playerent->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
4783 			}
4784 			else
4785 			{
4786 				playerent->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
4787 			}
4788 		}
4789 		else
4790 		{
4791 			switch(Q_irand(0,3))
4792 			{
4793 			case 0:
4794 				playerent->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
4795 				break;
4796 			case 1:
4797 			case 2:
4798 				playerent->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
4799 				break;
4800 			case 3:
4801 				playerent->client->ps.saberBlocked = BLOCKED_TOP;
4802 				break;
4803 			}
4804 		}
4805 	}
4806 	else
4807 	{	// The lower half is a bit iffy as far as block coverage.  Pick one of the "low" ones at random.
4808 		if (Q_irand(0,1))
4809 		{
4810 			playerent->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
4811 		}
4812 		else
4813 		{
4814 			playerent->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
4815 		}
4816 	}
4817 
4818 	if ( missileBlock )
4819 	{
4820 		playerent->client->ps.saberBlocked = WP_MissileBlockForBlock( playerent->client->ps.saberBlocked );
4821 	}
4822 }
4823 
WP_SaberStartMissileBlockCheck(gentity_t * self,usercmd_t * ucmd)4824 void WP_SaberStartMissileBlockCheck( gentity_t *self, usercmd_t *ucmd  )
4825 {
4826 	float		dist;
4827 	gentity_t	*ent, *incoming = NULL;
4828 	gentity_t	*entityList[MAX_GENTITIES];
4829 	int			numListedEntities;
4830 	vec3_t		mins, maxs;
4831 	int			i, e;
4832 	float		closestDist, radius = 256;
4833 	vec3_t		forward, dir, missile_dir, fwdangles = {0};
4834 	trace_t		trace;
4835 	vec3_t		traceTo, entDir;
4836 
4837 
4838 	if ( self->client->ps.weapon != WP_SABER )
4839 	{
4840 		return;
4841 	}
4842 
4843 	if ( self->client->ps.saberInFlight )
4844 	{
4845 		return;
4846 	}
4847 
4848 	if ( self->client->ps.forcePowersActive&(1<<FP_LIGHTNING) )
4849 	{//can't block while zapping
4850 		return;
4851 	}
4852 
4853 	if ( self->client->ps.forcePowersActive&(1<<FP_PUSH) )
4854 	{//can't block while shoving
4855 		return;
4856 	}
4857 
4858 	if ( self->client->ps.forcePowersActive&(1<<FP_GRIP) )
4859 	{//can't block while gripping (FIXME: or should it break the grip?  Pain should break the grip, I think...)
4860 		return;
4861 	}
4862 
4863 	if ( self->health <= 0 )
4864 	{//dead don't try to block (NOTE: actual deflection happens in missile code)
4865 		return;
4866 	}
4867 
4868 	if ( PM_InKnockDown( &self->client->ps ) )
4869 	{//can't block when knocked down
4870 		return;
4871 	}
4872 
4873 	if ( !self->client->ps.saberLength )
4874 	{
4875 		if ( self->s.number == 0 )
4876 		{//player doesn't auto-activate
4877 			return;
4878 		}
4879 	}
4880 
4881 	if ( !self->s.number )
4882 	{//don't do this if already attacking!
4883 		if ( ucmd->buttons & BUTTON_ATTACK
4884 			|| PM_SaberInAttack( self->client->ps.saberMove )
4885 			|| PM_SaberInSpecialAttack( self->client->ps.torsoAnim )
4886 			|| PM_SaberInTransitionAny( self->client->ps.saberMove ))
4887 		{
4888 			return;
4889 		}
4890 	}
4891 
4892 	if ( self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] > level.time )
4893 	{//can't block while gripping (FIXME: or should it break the grip?  Pain should break the grip, I think...)
4894 		return;
4895 	}
4896 
4897 	if ( !self->s.number && !g_saberAutoBlocking->integer && self->client->ps.saberBlockingTime<level.time )
4898 	{
4899 		return;
4900 	}
4901 
4902 	fwdangles[1] = self->client->ps.viewangles[1];
4903 	AngleVectors( fwdangles, forward, NULL, NULL );
4904 
4905 	for ( i = 0 ; i < 3 ; i++ )
4906 	{
4907 		mins[i] = self->currentOrigin[i] - radius;
4908 		maxs[i] = self->currentOrigin[i] + radius;
4909 	}
4910 
4911 	numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
4912 
4913 	closestDist = radius;
4914 
4915 	for ( e = 0 ; e < numListedEntities ; e++ )
4916 	{
4917 		ent = entityList[ e ];
4918 
4919 		if (ent == self)
4920 			continue;
4921 		if (ent->owner == self)
4922 			continue;
4923 		if ( !(ent->inuse) )
4924 			continue;
4925 		if ( ent->s.eType != ET_MISSILE && !(ent->s.eFlags&EF_MISSILE_STICK) )
4926 		{//not a normal projectile
4927 			if ( ent->client || ent->s.weapon != WP_SABER )
4928 			{//FIXME: wake up bad guys?
4929 				continue;
4930 			}
4931 			if ( ent->s.eFlags & EF_NODRAW )
4932 			{
4933 				continue;
4934 			}
4935 			if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
4936 			{//not a lightsaber
4937 				//FIXME: what about general objects that are small in size- like rocks, etc...
4938 				continue;
4939 			}
4940 			//a lightsaber.. make sure it's on and inFlight
4941 			if ( !ent->owner || !ent->owner->client )
4942 			{
4943 				continue;
4944 			}
4945 			if ( !ent->owner->client->ps.saberInFlight )
4946 			{//not in flight
4947 				continue;
4948 			}
4949 			if ( ent->owner->client->ps.saberLength <= 0 )
4950 			{//not on
4951 				continue;
4952 			}
4953 			if ( ent->owner->health <= 0 && !g_saberRealisticCombat->integer )
4954 			{//it's not doing damage, so ignore it
4955 				continue;
4956 			}
4957 		}
4958 		else
4959 		{
4960 			if ( ent->s.pos.trType == TR_STATIONARY && !self->s.number )
4961 			{//nothing you can do with a stationary missile if you're the player
4962 				continue;
4963 			}
4964 		}
4965 
4966 		float		dot1, dot2;
4967 		//see if they're in front of me
4968 		VectorSubtract( ent->currentOrigin, self->currentOrigin, dir );
4969 		dist = VectorNormalize( dir );
4970 		//FIXME: handle detpacks, proximity mines and tripmines
4971 		if ( ent->s.weapon == WP_THERMAL )
4972 		{//thermal detonator!
4973 			if ( self->NPC && dist < ent->splashRadius )
4974 			{
4975 				if ( dist < ent->splashRadius &&
4976 					ent->nextthink < level.time + 600 &&
4977 					ent->count &&
4978 					self->client->ps.groundEntityNum != ENTITYNUM_NONE &&
4979 						(ent->s.pos.trType == TR_STATIONARY||
4980 						ent->s.pos.trType == TR_INTERPOLATE||
4981 						(dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE||
4982 						!WP_ForcePowerUsable( self, FP_PUSH, 0 )) )
4983 				{//TD is close enough to hurt me, I'm on the ground and the thing is at rest or behind me and about to blow up, or I don't have force-push so force-jump!
4984 					//FIXME: sometimes this might make me just jump into it...?
4985 					self->client->ps.forceJumpCharge = 480;
4986 				}
4987 				else
4988 				{//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
4989 					ForceThrow( self, qfalse );
4990 				}
4991 			}
4992 			continue;
4993 		}
4994 		else if ( ent->splashDamage && ent->splashRadius )
4995 		{//exploding missile
4996 			//FIXME: handle tripmines and detpacks somehow...
4997 			//			maybe do a force-gesture that makes them explode?
4998 			//			But what if we're within it's splashradius?
4999 			if ( !self->s.number )
5000 			{//players don't auto-handle these at all
5001 				continue;
5002 			}
5003 			else
5004 			{
5005 				if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) )
5006 				{//a placed explosive like a tripmine or detpack
5007 					if ( InFOV( ent->currentOrigin, self->client->renderInfo.eyePoint, self->client->ps.viewangles, 90, 90 ) )
5008 					{//in front of me
5009 						if ( G_ClearLOS( self, ent ) )
5010 						{//can see it
5011 							vec3_t throwDir;
5012 							//make the gesture
5013 							ForceThrow( self, qfalse );
5014 							//take it off the wall and toss it
5015 							ent->s.pos.trType = TR_GRAVITY;
5016 							ent->s.eType = ET_MISSILE;
5017 							ent->s.eFlags &= ~EF_MISSILE_STICK;
5018 							ent->s.eFlags |= EF_BOUNCE_HALF;
5019 							AngleVectors( ent->currentAngles, throwDir, NULL, NULL );
5020 							VectorMA( ent->currentOrigin, ent->maxs[0]+4, throwDir, ent->currentOrigin );
5021 							VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
5022 							VectorScale( throwDir, 300, ent->s.pos.trDelta );
5023 							ent->s.pos.trDelta[2] += 150;
5024 							VectorMA( ent->s.pos.trDelta, 800, dir, ent->s.pos.trDelta );
5025 							ent->s.pos.trTime = level.time;		// move a bit on the very first frame
5026 							VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
5027 							ent->owner = self;
5028 							// make it explode, but with less damage
5029 							ent->splashDamage /= 3;
5030 							ent->splashRadius /= 3;
5031 							ent->e_ThinkFunc = thinkF_WP_Explode;
5032 							ent->nextthink = level.time + Q_irand( 500, 3000 );
5033 						}
5034 					}
5035 				}
5036 				else if ( dist < ent->splashRadius &&
5037 				self->client->ps.groundEntityNum != ENTITYNUM_NONE &&
5038 					(DotProduct( dir, forward ) < SABER_REFLECT_MISSILE_CONE||
5039 					!WP_ForcePowerUsable( self, FP_PUSH, 0 )) )
5040 				{//NPCs try to evade it
5041 					self->client->ps.forceJumpCharge = 480;
5042 				}
5043 				else
5044 				{//else, try to force-throw it away
5045 					//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
5046 					ForceThrow( self, qfalse );
5047 				}
5048 			}
5049 			//otherwise, can't block it, so we're screwed
5050 			continue;
5051 		}
5052 
5053 		if ( ent->s.weapon != WP_SABER )
5054 		{//only block shots coming from behind
5055 			if ( (dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE )
5056 				continue;
5057 		}
5058 		else if ( !self->s.number )
5059 		{//player never auto-blocks thrown sabers
5060 			continue;
5061 		}//NPCs always try to block sabers coming from behind!
5062 
5063 		//see if they're heading towards me
5064 		VectorCopy( ent->s.pos.trDelta, missile_dir );
5065 		VectorNormalize( missile_dir );
5066 		if ( (dot2 = DotProduct( dir, missile_dir )) > 0 )
5067 			continue;
5068 
5069 		//FIXME: must have a clear trace to me, too...
5070 		if ( dist < closestDist )
5071 		{
5072 			VectorCopy( self->currentOrigin, traceTo );
5073 			traceTo[2] = self->absmax[2] - 4;
5074 			gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, traceTo, ent->s.number, ent->clipmask, G2_NOCOLLIDE, 0 );
5075 			if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) )
5076 			{//okay, try one more check
5077 				VectorNormalize2( ent->s.pos.trDelta, entDir );
5078 				VectorMA( ent->currentOrigin, radius, entDir, traceTo );
5079 				gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, traceTo, ent->s.number, ent->clipmask, G2_NOCOLLIDE, 0 );
5080 				if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) )
5081 				{//can't hit me, ignore it
5082 					continue;
5083 				}
5084 			}
5085 			if ( self->s.number != 0 )
5086 			{//An NPC
5087 				if ( self->NPC && !self->enemy && ent->owner )
5088 				{
5089 					if ( ent->owner->health >= 0 && (!ent->owner->client || ent->owner->client->playerTeam != self->client->playerTeam) )
5090 					{
5091 						G_SetEnemy( self, ent->owner );
5092 					}
5093 				}
5094 			}
5095 			//FIXME: if NPC, predict the intersection between my current velocity/path and the missile's, see if it intersects my bounding box (+/-saberLength?), don't try to deflect unless it does?
5096 			closestDist = dist;
5097 			incoming = ent;
5098 		}
5099 	}
5100 
5101 	if ( incoming )
5102 	{
5103 		if ( self->NPC && !G_ControlledByPlayer( self ) )
5104 		{
5105 			if ( Jedi_WaitingAmbush( self ) )
5106 			{
5107 				Jedi_Ambush( self );
5108 			}
5109 			if ( Jedi_SaberBlockGo( self, &self->NPC->last_ucmd, NULL, NULL, incoming ) != EVASION_NONE )
5110 			{//make sure to turn on your saber if it's not on
5111 				self->client->ps.saberActive = qtrue;
5112 			}
5113 		}
5114 		else//player
5115 		{
5116 			WP_SaberBlockNonRandom( self, incoming->currentOrigin, qtrue );
5117 			if ( incoming->owner && incoming->owner->client && (!self->enemy || self->enemy->s.weapon != WP_SABER) )//keep enemy jedi over shooters
5118 			{
5119 				self->enemy = incoming->owner;
5120 				NPC_SetLookTarget( self, incoming->owner->s.number, level.time+1000 );
5121 			}
5122 		}
5123 	}
5124 }
5125 
5126 
5127 //GENERAL SABER============================================================================
5128 //GENERAL SABER============================================================================
5129 //GENERAL SABER============================================================================
5130 //GENERAL SABER============================================================================
5131 //GENERAL SABER============================================================================
5132 
5133 
WP_SetSaberMove(gentity_t * self,short blocked)5134 void WP_SetSaberMove(gentity_t *self, short blocked)
5135 {
5136 	self->client->ps.saberBlocked = blocked;
5137 }
5138 
5139 extern void CG_CubeOutline( vec3_t mins, vec3_t maxs, int time, unsigned int color, float alpha );
WP_SaberUpdate(gentity_t * self,usercmd_t * ucmd)5140 void WP_SaberUpdate( gentity_t *self, usercmd_t *ucmd )
5141 {
5142 	//float	swap;
5143 	float	minsize = 16;
5144 
5145 	if(0)//	if ( self->s.number != 0 )
5146 	{//for now only the player can do this		// not anymore
5147 		return;
5148 	}
5149 
5150 	if ( !self->client )
5151 	{
5152 		return;
5153 	}
5154 
5155 	if ( self->client->ps.saberEntityNum < 0 || self->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
5156 	{//never got one
5157 		return;
5158 	}
5159 
5160 	// Check if we are throwing it, launch it if needed, update position if needed.
5161 	WP_SaberThrow(self, ucmd);
5162 
5163 
5164 	//vec3_t saberloc;
5165 	//vec3_t sabermins={-8,-8,-8}, sabermaxs={8,8,8};
5166 
5167 	gentity_t *saberent;
5168 
5169 	if (self->client->ps.saberEntityNum <= 0)
5170 	{//WTF?!!  We lost it?
5171 		return;
5172 	}
5173 
5174 	saberent = &g_entities[self->client->ps.saberEntityNum];
5175 
5176 	//FIXME: Based on difficulty level/jedi saber combat skill, make this bounding box fatter/smaller
5177 	if ( self->client->ps.saberBlocked != BLOCKED_NONE )
5178 	{//we're blocking, increase min size
5179 		minsize = 32;
5180 	}
5181 
5182 	//is our saber in flight?
5183 	if ( !self->client->ps.saberInFlight )
5184 	{	// It isn't, which means we can update its position as we will.
5185 		if ( !self->client->ps.saberActive || PM_InKnockDown( &self->client->ps ) )
5186 		{//can't block if saber isn't on
5187 			VectorClear(saberent->mins);
5188 			VectorClear(saberent->maxs);
5189 			G_SetOrigin(saberent, self->currentOrigin);
5190 		}
5191 		else if ( self->client->ps.saberBlocking == BLK_TIGHT || self->client->ps.saberBlocking == BLK_WIDE )
5192 		{//FIXME: keep bbox in front of player, even when wide?
5193 			vec3_t	saberOrg;
5194 			if ( ( (self->s.number&&!Jedi_SaberBusy(self)&&!g_saberRealisticCombat->integer) || (self->s.number == 0 && self->client->ps.saberBlocking == BLK_WIDE && (g_saberAutoBlocking->integer||self->client->ps.saberBlockingTime>level.time)) )
5195 				&& self->client->ps.weaponTime <= 0 )
5196 			{//full-size blocking for non-attacking player with g_saberAutoBlocking on
5197 				vec3_t saberang={0,0,0}, fwd, sabermins={-8,-8,-8}, sabermaxs={8,8,8};
5198 
5199 				saberang[YAW] = self->client->ps.viewangles[YAW];
5200 				AngleVectors( saberang, fwd, NULL, NULL );
5201 
5202 				VectorMA( self->currentOrigin, 12, fwd, saberOrg );
5203 
5204 				VectorAdd( self->mins, sabermins, saberent->mins );
5205 				VectorAdd( self->maxs, sabermaxs, saberent->maxs );
5206 
5207 				saberent->contents = CONTENTS_LIGHTSABER;
5208 
5209 				G_SetOrigin( saberent, saberOrg );
5210 			}
5211 			else
5212 			{
5213 				vec3_t	saberTip;
5214 				VectorMA( self->client->renderInfo.muzzlePoint, self->client->ps.saberLength, self->client->renderInfo.muzzleDir, saberTip );
5215 				VectorMA( self->client->renderInfo.muzzlePoint, self->client->ps.saberLength*0.5, self->client->renderInfo.muzzleDir, saberOrg );
5216 				for ( int i = 0; i < 3; i++ )
5217 				{
5218 					if ( saberTip[i] > self->client->renderInfo.muzzlePoint[i] )
5219 					{
5220 						saberent->maxs[i] = saberTip[i] - saberOrg[i] + 8;//self->client->renderInfo.muzzlePoint[i];
5221 						saberent->mins[i] = self->client->renderInfo.muzzlePoint[i] - saberOrg[i] - 8;
5222 					}
5223 					else //if ( saberTip[i] < self->client->renderInfo.muzzlePoint[i] )
5224 					{
5225 						saberent->maxs[i] = self->client->renderInfo.muzzlePoint[i] - saberOrg[i] + 8;
5226 						saberent->mins[i] = saberTip[i] - saberOrg[i] - 8;//self->client->renderInfo.muzzlePoint[i];
5227 					}
5228 					if ( self->client->ps.weaponTime > 0 || self->s.number || g_saberAutoBlocking->integer || self->client->ps.saberBlockingTime > level.time )
5229 					{//if attacking or blocking (or an NPC), inflate to a minimum size
5230 						if ( saberent->maxs[i] < minsize )
5231 						{
5232 							saberent->maxs[i] = minsize;
5233 						}
5234 						if ( saberent->mins[i] > -minsize )
5235 						{
5236 							saberent->mins[i] = -minsize;
5237 						}
5238 					}
5239 				}
5240 				saberent->contents = CONTENTS_LIGHTSABER;
5241 				G_SetOrigin( saberent, saberOrg );
5242 			}
5243 		}
5244 		/*
5245 		else if (self->client->ps.saberBlocking == BLK_WIDE)
5246 		{	// Assuming that we are not swinging, the saber's bounding box should be around the player.
5247 			vec3_t saberang={0,0,0}, fwd;
5248 
5249 			saberang[YAW] = self->client->ps.viewangles[YAW];
5250 			AngleVectors( saberang, fwd, NULL, NULL );
5251 
5252 			VectorMA(self->currentOrigin, 12, fwd, saberloc);
5253 
5254 			VectorAdd(self->mins, sabermins, saberent->mins);
5255 			VectorAdd(self->maxs, sabermaxs, saberent->maxs);
5256 
5257 			saberent->contents = CONTENTS_LIGHTSABER;
5258 
5259 			G_SetOrigin( saberent, saberloc);
5260 		}
5261 		else if (self->client->ps.saberBlocking == BLK_TIGHT)
5262 		{	// If the player is swinging, the bbox is around just the saber
5263 			VectorCopy(self->client->renderInfo.muzzlePoint, sabermins);
5264 			// Put the limits of the bbox around the saber size.
5265 			VectorMA(sabermins, self->client->ps.saberLength, self->client->renderInfo.muzzleDir, sabermaxs);
5266 
5267 			// Now make the mins into mins and the maxs into maxs
5268 			if (sabermins[0] > sabermaxs[0])
5269 			{
5270 				swap = sabermins[0];
5271 				sabermins[0] = sabermaxs[0];
5272 				sabermaxs[0] = swap;
5273 			}
5274 			if (sabermins[1] > sabermaxs[1])
5275 			{
5276 				swap = sabermins[1];
5277 				sabermins[1] = sabermaxs[1];
5278 				sabermaxs[1] = swap;
5279 			}
5280 			if (sabermins[2] > sabermaxs[2])
5281 			{
5282 				swap = sabermins[2];
5283 				sabermins[2] = sabermaxs[2];
5284 				sabermaxs[2] = swap;
5285 			}
5286 
5287 			// Now the loc is halfway between the (absolute) mins and maxs
5288 			VectorAdd(sabermins, sabermaxs, saberloc);
5289 			VectorScale(saberloc, 0.5, saberloc);
5290 
5291 			// Finally, turn the mins and maxs, which are absolute, into relative mins and maxs.
5292 			VectorSubtract(sabermins, saberloc, saberent->mins);
5293 			VectorSubtract(sabermaxs, saberloc, saberent->maxs);
5294 
5295 			saberent->contents = CONTENTS_LIGHTSABER;// | CONTENTS_SHOTCLIP;
5296 
5297 			G_SetOrigin( saberent, saberloc);
5298 		}
5299 		*/
5300 		else
5301 		{	// Otherwise there is no blocking possible.
5302 			VectorClear(saberent->mins);
5303 			VectorClear(saberent->maxs);
5304 			G_SetOrigin(saberent, self->currentOrigin);
5305 		}
5306 		saberent->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
5307 		gi.linkentity(saberent);
5308 	}
5309 	else
5310 	{
5311 		WP_SaberInFlightReflectCheck( self, ucmd );
5312 	}
5313 #ifndef FINAL_BUILD
5314 	if ( d_saberCombat->integer > 2 )
5315 	{
5316 		CG_CubeOutline( saberent->absmin, saberent->absmax, 50, WPDEBUG_SaberColor( self->client->ps.saberColor ), 1 );
5317 	}
5318 #endif
5319 }
5320 
5321 
5322 //OTHER JEDI POWERS=========================================================================
5323 //OTHER JEDI POWERS=========================================================================
5324 //OTHER JEDI POWERS=========================================================================
5325 //OTHER JEDI POWERS=========================================================================
5326 //OTHER JEDI POWERS=========================================================================
5327 extern gentity_t *TossClientItems( gentity_t *self );
5328 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
WP_DropWeapon(gentity_t * dropper,vec3_t velocity)5329 void WP_DropWeapon( gentity_t *dropper, vec3_t velocity )
5330 {
5331 	if ( !dropper || !dropper->client )
5332 	{
5333 		return;
5334 	}
5335 	int	replaceWeap = WP_NONE;
5336 	int oldWeap = dropper->s.weapon;
5337 	gentity_t *weapon = TossClientItems( dropper );
5338 	if ( oldWeap == WP_THERMAL && dropper->NPC )
5339 	{//Hmm, maybe all NPCs should go into melee?  Not too many, though, or they mob you and look silly
5340 		replaceWeap = WP_MELEE;
5341 	}
5342 	if (dropper->ghoul2.IsValid()&& dropper->weaponModel >= 0 )
5343 	{
5344 		gi.G2API_RemoveGhoul2Model( dropper->ghoul2, dropper->weaponModel );
5345 		dropper->weaponModel = -1;
5346 	}
5347 	//FIXME: does this work on the player?
5348 	dropper->client->ps.stats[STAT_WEAPONS] |= ( 1 << replaceWeap );
5349 	if ( !dropper->s.number )
5350 	{
5351 		if ( oldWeap == WP_THERMAL )
5352 		{
5353 			dropper->client->ps.ammo[weaponData[oldWeap].ammoIndex] -= weaponData[oldWeap].energyPerShot;
5354 		}
5355 		else
5356 		{
5357 			dropper->client->ps.stats[STAT_WEAPONS] &= ~( 1 << oldWeap );
5358 		}
5359 		CG_ChangeWeapon( replaceWeap );
5360 	}
5361 	else
5362 	{
5363 		dropper->client->ps.stats[STAT_WEAPONS] &= ~( 1 << oldWeap );
5364 	}
5365 	ChangeWeapon( dropper, replaceWeap );
5366 	dropper->s.weapon = replaceWeap;
5367 	if ( dropper->NPC )
5368 	{
5369 		dropper->NPC->last_ucmd.weapon = replaceWeap;
5370 	}
5371 	if ( weapon != NULL && velocity && !VectorCompare( velocity, vec3_origin ) )
5372 	{//weapon should have a direction to it's throw
5373 		VectorScale( velocity, 3, weapon->s.pos.trDelta );//NOTE: Presumes it is moving already...?
5374 		if ( weapon->s.pos.trDelta[2] < 150 )
5375 		{//this is presuming you don't want them to drop the weapon down on you...
5376 			weapon->s.pos.trDelta[2] = 150;
5377 		}
5378 		//FIXME: gets stuck inside it's former owner...
5379 		weapon->forcePushTime = level.time + 600; // let the push effect last for 600 ms
5380 	}
5381 }
5382 
WP_KnockdownTurret(gentity_t * self,gentity_t * pas)5383 void WP_KnockdownTurret( gentity_t *self, gentity_t *pas )
5384 {
5385 	//knock it over
5386 	VectorCopy( pas->currentOrigin, pas->s.pos.trBase );
5387 	pas->s.pos.trType = TR_LINEAR_STOP;
5388 	pas->s.pos.trDuration = 250;
5389 	pas->s.pos.trTime = level.time;
5390 	pas->s.pos.trDelta[2] = ( 12.0f / ( pas->s.pos.trDuration * 0.001f ) );
5391 
5392 	VectorCopy( pas->currentAngles, pas->s.apos.trBase );
5393 	pas->s.apos.trType = TR_LINEAR_STOP;
5394 	pas->s.apos.trDuration = 250;
5395 	pas->s.apos.trTime = level.time;
5396 	//FIXME: pick pitch/roll that always tilts it directly away from pusher
5397 	pas->s.apos.trDelta[PITCH] = ( 100.0f / ( pas->s.apos.trDuration * 0.001f ) );
5398 
5399 	//kill it
5400 	pas->count = 0;
5401 	pas->nextthink = -1;
5402 	G_Sound( pas, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
5403 	//push effect?
5404 	pas->forcePushTime = level.time + 600; // let the push effect last for 600 ms
5405 }
5406 
WP_ResistForcePush(gentity_t * self,gentity_t * pusher,qboolean noPenalty)5407 void WP_ResistForcePush( gentity_t *self, gentity_t *pusher, qboolean noPenalty )
5408 {
5409 	int parts;
5410 	qboolean runningResist = qfalse;
5411 
5412 	if ( !self || self->health <= 0 || !self->client || !pusher || !pusher->client )
5413 	{
5414 		return;
5415 	}
5416 	if ( (!self->s.number || self->client->NPC_class == CLASS_DESANN || self->client->NPC_class == CLASS_LUKE)
5417 		&& (VectorLengthSquared( self->client->ps.velocity ) > 10000 || self->client->ps.forcePowerLevel[FP_PUSH] >= FORCE_LEVEL_3 || self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 ) )
5418 	{
5419 		runningResist = qtrue;
5420 	}
5421 	if ( !runningResist
5422 		&& self->client->ps.groundEntityNum != ENTITYNUM_NONE
5423 		&& !PM_SpinningSaberAnim( self->client->ps.legsAnim )
5424 		&& !PM_FlippingAnim( self->client->ps.legsAnim )
5425 		&& !PM_RollingAnim( self->client->ps.legsAnim )
5426 		&& !PM_InKnockDown( &self->client->ps )
5427 		&& !PM_CrouchAnim( self->client->ps.legsAnim ))
5428 	{//if on a surface and not in a spin or flip, play full body resist
5429 		parts = SETANIM_BOTH;
5430 	}
5431 	else
5432 	{//play resist just in torso
5433 		parts = SETANIM_TORSO;
5434 	}
5435 	NPC_SetAnim( self, parts, BOTH_RESISTPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
5436 	if ( !noPenalty )
5437 	{
5438 		if ( !runningResist )
5439 		{
5440 			VectorClear( self->client->ps.velocity );
5441 			//still stop them from attacking or moving for a bit, though
5442 			//FIXME: maybe push just a little (like, slide)?
5443 			self->client->ps.weaponTime = 1000;
5444 			if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
5445 			{
5446 				self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
5447 			}
5448 			self->client->ps.pm_time = self->client->ps.weaponTime;
5449 			self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
5450 			//play the full body push effect on me
5451 			self->forcePushTime = level.time + 600; // let the push effect last for 600 ms
5452 		}
5453 		else
5454 		{
5455 			self->client->ps.weaponTime = 600;
5456 			if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
5457 			{
5458 				self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
5459 			}
5460 		}
5461 	}
5462 	//play my force push effect on my hand
5463 	self->client->ps.powerups[PW_FORCE_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500;
5464 	Jedi_PlayBlockedPushSound( self );
5465 }
5466 
WP_ForceKnockdown(gentity_t * self,gentity_t * pusher,qboolean pull,qboolean strongKnockdown,qboolean breakSaberLock)5467 void WP_ForceKnockdown( gentity_t *self, gentity_t *pusher, qboolean pull, qboolean strongKnockdown, qboolean breakSaberLock )
5468 {
5469 	if ( !self || !self->client || !pusher || !pusher->client )
5470 	{
5471 		return;
5472 	}
5473 
5474 	//break out of a saberLock?
5475 	if ( breakSaberLock )
5476 	{
5477 		self->client->ps.saberLockTime = 0;
5478 		self->client->ps.saberLockEnemy = ENTITYNUM_NONE;
5479 	}
5480 
5481 	if ( self->health > 0 )
5482 	{
5483 		if ( !self->s.number )
5484 		{
5485 			NPC_SetPainEvent( self );
5486 		}
5487 		else
5488 		{
5489 			GEntity_PainFunc( self, pusher, pusher, self->currentOrigin, 0, MOD_MELEE );
5490 		}
5491 		vec3_t	pushDir;
5492 		if ( pull )
5493 		{
5494 			VectorSubtract( pusher->currentOrigin, self->currentOrigin, pushDir );
5495 		}
5496 		else
5497 		{
5498 			VectorSubtract( self->currentOrigin, pusher->currentOrigin, pushDir );
5499 		}
5500 		G_CheckLedgeDive( self, 72, pushDir, qfalse, qfalse );
5501 
5502 		if ( !PM_SpinningSaberAnim( self->client->ps.legsAnim )
5503 			&& !PM_FlippingAnim( self->client->ps.legsAnim )
5504 			&& !PM_RollingAnim( self->client->ps.legsAnim )
5505 			&& !PM_InKnockDown( &self->client->ps ) )
5506 		{
5507 			int knockAnim = BOTH_KNOCKDOWN1;//default knockdown
5508 			if ( pusher->client->NPC_class == CLASS_DESANN && self->client->NPC_class != CLASS_LUKE )
5509 			{//desann always knocks down, unless you're Luke
5510 				strongKnockdown = qtrue;
5511 			}
5512 			if ( !self->s.number
5513 				&& !strongKnockdown
5514 				&& ( (!pull&&(self->client->ps.forcePowerLevel[FP_PUSH]>FORCE_LEVEL_1||!g_spskill->integer)) || (pull&&(self->client->ps.forcePowerLevel[FP_PULL]>FORCE_LEVEL_1||!g_spskill->integer)) )	)
5515 			{//player only knocked down if pushed *hard*
5516 				if ( self->s.weapon == WP_SABER )
5517 				{//temp HACK: these are the only 2 pain anims that look good when holding a saber
5518 					knockAnim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 );
5519 				}
5520 				else
5521 				{
5522 					knockAnim = PM_PickAnim( self, BOTH_PAIN1, BOTH_PAIN19 );
5523 				}
5524 			}
5525 			else if ( PM_CrouchAnim( self->client->ps.legsAnim ) )
5526 			{//crouched knockdown
5527 				knockAnim = BOTH_KNOCKDOWN4;
5528 			}
5529 			else
5530 			{//plain old knockdown
5531 				vec3_t pLFwd, pLAngles = {0,self->client->ps.viewangles[YAW],0};
5532 				vec3_t sFwd, sAngles = {0,pusher->client->ps.viewangles[YAW],0};
5533 				AngleVectors( pLAngles, pLFwd, NULL, NULL );
5534 				AngleVectors( sAngles, sFwd, NULL, NULL );
5535 				if ( DotProduct( sFwd, pLFwd ) > 0.2f )
5536 				{//pushing him from behind
5537 					//FIXME: check to see if we're aiming below or above the waist?
5538 					if ( pull )
5539 					{
5540 						knockAnim = BOTH_KNOCKDOWN1;
5541 					}
5542 					else
5543 					{
5544 						knockAnim = BOTH_KNOCKDOWN3;
5545 					}
5546 				}
5547 				else
5548 				{//pushing him from front
5549 					if ( pull )
5550 					{
5551 						knockAnim = BOTH_KNOCKDOWN3;
5552 					}
5553 					else
5554 					{
5555 						knockAnim = BOTH_KNOCKDOWN1;
5556 					}
5557 				}
5558 			}
5559 			if ( knockAnim == BOTH_KNOCKDOWN1 && strongKnockdown )
5560 			{//push *hard*
5561 				knockAnim = BOTH_KNOCKDOWN2;
5562 			}
5563 			NPC_SetAnim( self, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
5564 			if ( self->s.number )
5565 			{//randomize getup times
5566 				int addTime = Q_irand( -300, 1000 );
5567 				self->client->ps.legsAnimTimer += addTime;
5568 				self->client->ps.torsoAnimTimer += addTime;
5569 			}
5570 			//
5571 			if ( pusher->NPC && pusher->enemy == self )
5572 			{//pushed pushed down his enemy
5573 				G_AddVoiceEvent( pusher, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 3000 );
5574 				pusher->NPC->blockedSpeechDebounceTime = level.time + 3000;
5575 			}
5576 		}
5577 	}
5578 	self->forcePushTime = level.time + 600; // let the push effect last for 600 ms
5579 }
5580 
ForceThrow(gentity_t * self,qboolean pull)5581 void ForceThrow( gentity_t *self, qboolean pull )
5582 {//FIXME: pass in a target ent so we (an NPC) can push/pull just one targeted ent.
5583 	//shove things in front of you away
5584 	float		dist;
5585 	gentity_t	*ent, *forwardEnt = NULL;
5586 	gentity_t	*entityList[MAX_GENTITIES];
5587 	gentity_t	*push_list[MAX_GENTITIES];
5588 	int			numListedEntities;
5589 	vec3_t		mins, maxs;
5590 	vec3_t		v;
5591 	int			i, e;
5592 	int			ent_count = 0;
5593 	int			radius;
5594 	vec3_t		center, ent_org, size, forward, right, end, dir, fwdangles = {0};
5595 	float		dot1, cone;
5596 	trace_t		tr;
5597 	int			anim, hold, soundIndex, cost, actualCost;
5598 
5599 	if ( self->health <= 0 )
5600 	{
5601 		return;
5602 	}
5603 	if ( self->client->ps.leanofs )
5604 	{//can't force-throw while leaning
5605 		return;
5606 	}
5607 	if ( self->client->ps.forcePowerDebounce[FP_PUSH] > level.time )//self->client->ps.powerups[PW_FORCE_PUSH] > level.time )
5608 	{//already pushing- now you can't haul someone across the room, sorry
5609 		return;
5610 	}
5611 	if ( !self->s.number && (cg.zoomMode || in_camera) )
5612 	{//can't force throw/pull when zoomed in or in cinematic
5613 		return;
5614 	}
5615 	if ( self->client->ps.saberLockTime > level.time )
5616 	{
5617 		if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 )
5618 		{//this can be a way to break out
5619 			return;
5620 		}
5621 		//else, I'm breaking my half of the saberlock
5622 		self->client->ps.saberLockTime = 0;
5623 		self->client->ps.saberLockEnemy = ENTITYNUM_NONE;
5624 	}
5625 
5626 	if ( self->client->ps.legsAnim == BOTH_KNOCKDOWN3
5627 		|| (self->client->ps.torsoAnim == BOTH_FORCE_GETUP_F1 && self->client->ps.torsoAnimTimer > 400)
5628 		|| (self->client->ps.torsoAnim == BOTH_FORCE_GETUP_F2 && self->client->ps.torsoAnimTimer > 900)
5629 		|| (self->client->ps.torsoAnim == BOTH_GETUP3 && self->client->ps.torsoAnimTimer > 500)
5630 		|| (self->client->ps.torsoAnim == BOTH_GETUP4 && self->client->ps.torsoAnimTimer > 300)
5631 		|| (self->client->ps.torsoAnim == BOTH_GETUP5 && self->client->ps.torsoAnimTimer > 500) )
5632 	{//we're face-down, so we'd only be force-push/pulling the floor
5633 		return;
5634 	}
5635 	if ( pull )
5636 	{
5637 		radius = forcePushPullRadius[self->client->ps.forcePowerLevel[FP_PULL]];
5638 	}
5639 	else
5640 	{
5641 		radius = forcePushPullRadius[self->client->ps.forcePowerLevel[FP_PUSH]];
5642 	}
5643 
5644 	if ( !radius )
5645 	{//no ability to do this yet
5646 		return;
5647 	}
5648 
5649 	if ( pull )
5650 	{
5651 		cost = forcePowerNeeded[FP_PULL];
5652 		if ( !WP_ForcePowerUsable( self, FP_PULL, cost ) )
5653 		{
5654 			return;
5655 		}
5656 		//make sure this plays and that you cannot press fire for about 200ms after this
5657 		anim = BOTH_FORCEPULL;
5658 		soundIndex = G_SoundIndex( "sound/weapons/force/pull.wav" );
5659 		hold = 200;
5660 	}
5661 	else
5662 	{
5663 		cost = forcePowerNeeded[FP_PUSH];
5664 		if ( !WP_ForcePowerUsable( self, FP_PUSH, cost ) )
5665 		{
5666 			return;
5667 		}
5668 		//make sure this plays and that you cannot press fire for about 1 second after this
5669 		anim = BOTH_FORCEPUSH;
5670 		soundIndex = G_SoundIndex( "sound/weapons/force/push.wav" );
5671 		hold = 650;
5672 	}
5673 
5674 	int parts = SETANIM_TORSO;
5675 	if ( !PM_InKnockDown( &self->client->ps ) )
5676 	{
5677 		if ( self->client->ps.saberLockTime > level.time )
5678 		{
5679 			self->client->ps.saberLockTime = 0;
5680 			self->painDebounceTime = level.time + 2000;
5681 			hold += 1000;
5682 			parts = SETANIM_BOTH;
5683 		}
5684 		else if ( !VectorLengthSquared( self->client->ps.velocity ) && !(self->client->ps.pm_flags&PMF_DUCKED))
5685 		{
5686 			parts = SETANIM_BOTH;
5687 		}
5688 	}
5689 	NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART );
5690 	self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
5691 	self->client->ps.saberBlocked = BLOCKED_NONE;
5692 	if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
5693 	{
5694 		hold = floor( hold*g_timescale->value );
5695 	}
5696 	self->client->ps.weaponTime = hold;//was 1000, but want to swing sooner
5697 	//do effect... FIXME: build-up or delay this until in proper part of anim
5698 	self->client->ps.powerups[PW_FORCE_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500;
5699 
5700 	G_Sound( self, soundIndex );
5701 
5702 	VectorCopy( self->client->ps.viewangles, fwdangles );
5703 	//fwdangles[1] = self->client->ps.viewangles[1];
5704 	AngleVectors( fwdangles, forward, right, NULL );
5705 	VectorCopy( self->currentOrigin, center );
5706 
5707 	for ( i = 0 ; i < 3 ; i++ )
5708 	{
5709 		mins[i] = center[i] - radius;
5710 		maxs[i] = center[i] + radius;
5711 	}
5712 
5713 	numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
5714 
5715 	if ( pull )
5716 	{
5717 		cone = forcePullCone[self->client->ps.forcePowerLevel[FP_PULL]];
5718 	}
5719 	else
5720 	{
5721 		cone = forcePushCone[self->client->ps.forcePowerLevel[FP_PUSH]];
5722 	}
5723 
5724 	if ( cone >= 1.0f )
5725 	{//must be pointing right at them
5726 		VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
5727 		gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_OPAQUE|CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE, G2_NOCOLLIDE, 0 );//was MASK_SHOT, changed to match crosshair trace
5728 		/*
5729 		//FIXME: can't just return, need to be able to push missiles
5730 		if ( tr.entityNum >= ENTITYNUM_WORLD )
5731 		{//no-one right in front of self, so short out
5732 			return;
5733 		}
5734 		*/
5735 		forwardEnt = &g_entities[tr.entityNum];
5736 	}
5737 
5738 	for ( e = 0 ; e < numListedEntities ; e++ )
5739 	{
5740 		ent = entityList[ e ];
5741 
5742 		if (ent == self)
5743 			continue;
5744 		if ( ent->owner == self && ent->s.weapon != WP_THERMAL )//can push your own thermals
5745 			continue;
5746 		if ( !(ent->inuse) )
5747 			continue;
5748 		if ( ent->NPC && ent->NPC->scriptFlags & SCF_NO_FORCE )
5749 		{
5750 			if ( ent->s.weapon == WP_SABER )
5751 			{//Hmm, should jedi do the resist behavior?  If this is on, perhaps it's because of a cinematic?
5752 				WP_ResistForcePush( ent, self, qtrue );
5753 			}
5754 			continue;
5755 		}
5756 		if ( (ent->flags&FL_FORCE_PULLABLE_ONLY) && !pull )
5757 		{//simple HACK: cannot force-push ammo rack items (because they may start in solid)
5758 			continue;
5759 		}
5760 		//FIXME: don't push it if I already pushed it a little while ago
5761 		if ( ent->s.eType != ET_MISSILE )
5762 		{
5763 			if ( cone >= 1.0f )
5764 			{//must be pointing right at them
5765 				if ( ent != forwardEnt )
5766 				{//must be the person I'm looking right at
5767 					if ( ent->client && !pull
5768 						&& ent->client->ps.forceGripEntityNum == self->s.number
5769 						&& (self->s.eFlags&EF_FORCE_GRIPPED) )
5770 					{//this is the guy that's force-gripping me, use a wider cone regardless of force power level
5771 					}
5772 					else
5773 					{
5774 						continue;
5775 					}
5776 				}
5777 			}
5778 			if ( ent->s.eType != ET_ITEM && ent->e_ThinkFunc != thinkF_G_RunObject )//|| !(ent->flags&FL_DROPPED_ITEM) )//was only dropped items
5779 			{
5780 				//FIXME: need pushable objects
5781 				if ( ent->s.eFlags & EF_NODRAW )
5782 				{
5783 					continue;
5784 				}
5785 				if ( !ent->client )
5786 				{
5787 					if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
5788 					{//not a lightsaber
5789 						if ( !(ent->svFlags&SVF_GLASS_BRUSH) )
5790 						{//and not glass
5791 							if ( Q_stricmp( "func_door", ent->classname ) != 0 || !(ent->spawnflags & 2/*MOVER_FORCE_ACTIVATE*/) )
5792 							{//not a force-usable door
5793 								if ( Q_stricmp( "func_static", ent->classname ) != 0 || (!(ent->spawnflags&1/*F_PUSH*/)&&!(ent->spawnflags&2/*F_PULL*/)) )
5794 								{//not a force-usable func_static
5795 									if ( Q_stricmp( "limb", ent->classname ) )
5796 									{//not a limb
5797 										if ( ent->s.weapon == WP_TURRET && !Q_stricmp( "PAS", ent->classname ) && ent->s.apos.trType == TR_STATIONARY )
5798 										{//can knock over placed turrets
5799 											if ( !self->s.number || self->enemy != ent )
5800 											{//only NPCs who are actively mad at this turret can push it over
5801 												continue;
5802 											}
5803 										}
5804 										else
5805 										{
5806 											continue;
5807 										}
5808 									}
5809 								}
5810 							}
5811 							else if ( ent->moverState != MOVER_POS1 && ent->moverState != MOVER_POS2 )
5812 							{//not at rest
5813 								continue;
5814 							}
5815 						}
5816 					}
5817 					//continue;
5818 				}
5819 				else if ( ent->client->NPC_class == CLASS_MARK1 )
5820 				{//can't push Mark1 unless push 3
5821 					if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 )
5822 					{
5823 						continue;
5824 					}
5825 				}
5826 				else if ( ent->client->NPC_class == CLASS_GALAKMECH || ent->client->NPC_class == CLASS_ATST )
5827 				{//can't push ATST or Galak
5828 					continue;
5829 				}
5830 				else if ( ent->s.weapon == WP_EMPLACED_GUN )
5831 				{//FIXME: maybe can pull them out?
5832 					continue;
5833 				}
5834 				else if ( ent->client->playerTeam == self->client->playerTeam && self->enemy && self->enemy != ent )
5835 				{//can't accidently push a teammate while in combat
5836 					continue;
5837 				}
5838 			}
5839 			else if ( ent->s.eType == ET_ITEM
5840 				&& ent->item
5841 				&& ent->item->giType == IT_HOLDABLE
5842 				&& ent->item->giTag == INV_SECURITY_KEY )
5843 				//&& (ent->flags&FL_DROPPED_ITEM) ???
5844 			{//dropped security keys can't be pushed?  But placed ones can...?  does this make any sense?
5845 				if ( !pull || self->s.number )
5846 				{//can't push, NPC's can't do anything to it
5847 					continue;
5848 				}
5849 				else
5850 				{
5851 					if ( g_crosshairEntNum != ent->s.number )
5852 					{//player can pull it if looking *right* at it
5853 						if ( cone >= 1.0f )
5854 						{//we did a forwardEnt trace
5855 							if ( forwardEnt != ent )
5856 							{//must be pointing right at them
5857 								continue;
5858 							}
5859 						}
5860 						else
5861 						{//do a forwardEnt trace
5862 							VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
5863 							gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_OPAQUE|CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE, G2_NOCOLLIDE, 0 );//was MASK_SHOT, changed to match crosshair trace
5864 							if ( tr.entityNum != ent->s.number )
5865 							{//last chance
5866 								continue;
5867 							}
5868 						}
5869 					}
5870 				}
5871 			}
5872 		}
5873 		else
5874 		{
5875 			if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) )
5876 			{//can't force-push/pull stuck missiles (detpacks, tripmines)
5877 				continue;
5878 			}
5879 			if ( ent->s.pos.trType == TR_STATIONARY && ent->s.weapon != WP_THERMAL )
5880 			{//only thermal detonators can be pushed once stopped
5881 				continue;
5882 			}
5883 		}
5884 
5885 		//this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
5886 		// find the distance from the edge of the bounding box
5887 		for ( i = 0 ; i < 3 ; i++ )
5888 		{
5889 			if ( center[i] < ent->absmin[i] )
5890 			{
5891 				v[i] = ent->absmin[i] - center[i];
5892 			} else if ( center[i] > ent->absmax[i] )
5893 			{
5894 				v[i] = center[i] - ent->absmax[i];
5895 			} else
5896 			{
5897 				v[i] = 0;
5898 			}
5899 		}
5900 
5901 		VectorSubtract( ent->absmax, ent->absmin, size );
5902 		VectorMA( ent->absmin, 0.5, size, ent_org );
5903 
5904 		//see if they're in front of me
5905 		VectorSubtract( ent_org, center, dir );
5906 		VectorNormalize( dir );
5907 		if ( cone < 1.0f )
5908 		{//must be within the forward cone
5909 			if ( ent->client && !pull
5910 				&& ent->client->ps.forceGripEntityNum == self->s.number
5911 				&& self->s.eFlags&EF_FORCE_GRIPPED )
5912 			{//this is the guy that's force-gripping me, use a wider cone regardless of force power level
5913 				if ( (dot1 = DotProduct( dir, forward )) < cone-0.3f )
5914 					continue;
5915 			}
5916 			else if ( ent->s.eType == ET_MISSILE )//&& ent->s.eType != ET_ITEM && ent->e_ThinkFunc != thinkF_G_RunObject )
5917 			{//missiles are easier to force-push, never require direct trace (FIXME: maybe also items and general physics objects)
5918 				if ( (dot1 = DotProduct( dir, forward )) < cone-0.3f )
5919 					continue;
5920 			}
5921 			else if ( (dot1 = DotProduct( dir, forward )) < cone )
5922 			{
5923 				continue;
5924 			}
5925 		}
5926 		else if ( ent->s.eType == ET_MISSILE )
5927 		{//a missile and we're at force level 1... just use a small cone, but not ridiculously small
5928 			if ( (dot1 = DotProduct( dir, forward )) < 0.75f )
5929 			{
5930 				continue;
5931 			}
5932 		}//else is an NPC or brush entity that our forward trace would have to hit
5933 
5934 		dist = VectorLength( v );
5935 
5936 		//Now check and see if we can actually deflect it
5937 		//method1
5938 		//if within a certain range, deflect it
5939 		if ( ent->s.eType == ET_MISSILE && cone >= 1.0f )
5940 		{//smaller radius on missile checks at force push 1
5941 			if ( dist >= 192 )
5942 			{
5943 				continue;
5944 			}
5945 		}
5946 		else if ( dist >= radius )
5947 		{
5948 			continue;
5949 		}
5950 
5951 		//in PVS?
5952 		if ( !ent->bmodel && !gi.inPVS( ent_org, self->client->renderInfo.eyePoint ) )
5953 		{//must be in PVS
5954 			continue;
5955 		}
5956 
5957 		if ( ent != forwardEnt )
5958 		{//don't need to trace against forwardEnt again
5959 		//really should have a clear LOS to this thing...
5960 			gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_OPAQUE|CONTENTS_SOLID, G2_NOCOLLIDE, 0 );//was MASK_SHOT, but changed to match above trace and crosshair trace
5961 			if ( tr.fraction < 1.0f && tr.entityNum != ent->s.number )
5962 			{//must have clear LOS
5963 				continue;
5964 			}
5965 		}
5966 
5967 		// ok, we are within the radius, add us to the incoming list
5968 		push_list[ent_count] = ent;
5969 		ent_count++;
5970 	}
5971 
5972 	if ( ent_count )
5973 	{
5974 		for ( int x = 0; x < ent_count; x++ )
5975 		{
5976 			if ( push_list[x]->client )
5977 			{
5978 				vec3_t	pushDir;
5979 				float	knockback = pull?0:200;
5980 
5981 //FIXMEFIXMEFIXMEFIXMEFIXME: extern a lot of this common code when I have the time!!!
5982 
5983 				//First, if this is the player we're push/pulling, see if he can counter it
5984 				if ( !push_list[x]->s.number )
5985 				{//player
5986 					if ( push_list[x]->health > 0 //alive
5987 						&& push_list[x]->client //client
5988 						&& push_list[x]->client->ps.torsoAnim != BOTH_FORCEGRIP_HOLD// BOTH_FORCEGRIP1//wasn't trying to grip anyone
5989 						&& (self->client->NPC_class != CLASS_DESANN || !Q_irand( 0, 2 ) )//only 30% chance of resisting a Desann push
5990 						&& push_list[x]->client->ps.groundEntityNum != ENTITYNUM_NONE//on the ground
5991 						&& !PM_InKnockDown( &push_list[x]->client->ps )//not knocked down already
5992 						&& push_list[x]->client->ps.saberLockTime < level.time//not involved in a saberLock
5993 						&& push_list[x]->client->ps.weaponTime < level.time//not attacking or otherwise busy
5994 						&& (push_list[x]->client->ps.weapon == WP_SABER||push_list[x]->client->ps.weapon == WP_MELEE) )//using saber or fists
5995 					{//trying to push or pull the player!
5996 						if ( push_list[x]->client->ps.powerups[PW_FORCE_PUSH] > level.time//player was pushing/pulling too
5997 							||( pull && Q_irand( 0, (push_list[x]->client->ps.forcePowerLevel[FP_PULL] - self->client->ps.forcePowerLevel[FP_PULL])*2+1 ) > 0 )//player's pull is high enough
5998 							||( !pull && Q_irand( 0, (push_list[x]->client->ps.forcePowerLevel[FP_PUSH] - self->client->ps.forcePowerLevel[FP_PUSH])*2+1 ) > 0 ) )//player's push is high enough
5999 						{//player's force push/pull is high enough to try to stop me
6000 							if ( InFront( self->currentOrigin, push_list[x]->client->renderInfo.eyePoint, push_list[x]->client->ps.viewangles, 0.3f ) )
6001 							{//I'm in front of player
6002 								WP_ResistForcePush( push_list[x], self, qfalse );
6003 								push_list[x]->client->ps.saberMove = push_list[x]->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
6004 								push_list[x]->client->ps.saberBlocked = BLOCKED_NONE;
6005 								continue;
6006 							}
6007 						}
6008 					}
6009 				}
6010 				else if ( push_list[x]->client && Jedi_WaitingAmbush( push_list[x] ) )
6011 				{
6012 					WP_ForceKnockdown( push_list[x], self, pull, qtrue, qfalse );
6013 					continue;
6014 				}
6015 
6016 
6017 				//okay, everyone else (or player who couldn't resist it)...
6018 				if ( ((self->s.number == 0 && Q_irand( 0, 2 ) ) || Q_irand( 0, 2 ) ) && push_list[x]->client && push_list[x]->health > 0 //a living client
6019 						&& push_list[x]->client->ps.weapon == WP_SABER //Jedi
6020 						&& push_list[x]->health > 0 //alive
6021 						&& (self->client->NPC_class != CLASS_DESANN || !Q_irand( 0, 2 ) )//only 30% chance of resisting a Desann push
6022 						&& push_list[x]->client->ps.groundEntityNum != ENTITYNUM_NONE //on the ground
6023 						&& InFront( self->currentOrigin, push_list[x]->currentOrigin, push_list[x]->client->ps.viewangles, 0.3f ) //I'm in front of him
6024 						&& ( push_list[x]->client->ps.powerups[PW_FORCE_PUSH] > level.time ||//he's pushing too
6025 								(push_list[x]->s.number != 0 && push_list[x]->client->ps.weaponTime < level.time)//not the player and not attacking (NPC jedi auto-defend against pushes)
6026 						   )
6027 					)
6028 				{//Jedi don't get pushed, they resist as long as they aren't already attacking and are on the ground
6029 					if ( push_list[x]->client->ps.saberLockTime > level.time )
6030 					{//they're in a lock
6031 						if ( push_list[x]->client->ps.saberLockEnemy != self->s.number )
6032 						{//they're not in a lock with me
6033 							continue;
6034 						}
6035 						else if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 ||
6036 							push_list[x]->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
6037 						{//they're in a lock with me, but my push is too weak
6038 							continue;
6039 						}
6040 						else
6041 						{//we will knock them down
6042 							self->painDebounceTime = 0;
6043 							self->client->ps.weaponTime = 500;
6044 							if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
6045 							{
6046 								self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
6047 							}
6048 						}
6049 					}
6050 					if ( !pull && self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 && !Q_irand(0,2) &&
6051 							push_list[x]->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 )
6052 					{//a level 3 push can even knock down a jedi
6053 						if ( PM_InKnockDown( &push_list[x]->client->ps ) )
6054 						{//can't knock them down again
6055 							continue;
6056 						}
6057 						WP_ForceKnockdown( push_list[x], self, pull, qfalse, qtrue );
6058 					}
6059 					else
6060 					{
6061 						WP_ResistForcePush( push_list[x], self, qfalse );
6062 					}
6063 				}
6064 				else
6065 				{
6066 					//UGH: FIXME: for enemy jedi, they should probably always do force pull 3, and not your weapon (if player?)!
6067 					//shove them
6068 					if ( push_list[x]->s.number && push_list[x]->message )
6069 					{//an NPC who has a key
6070 						//don't push me... FIXME: maybe can pull the key off me?
6071 						WP_ForceKnockdown( push_list[x], self, pull, qfalse, qfalse );
6072 						continue;
6073 					}
6074 					if ( pull )
6075 					{
6076 						VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, pushDir );
6077 						if ( self->client->ps.forcePowerLevel[FP_PULL] > FORCE_LEVEL_1
6078 							&& push_list[x]->s.weapon != WP_SABER
6079 							&& push_list[x]->s.weapon != WP_MELEE
6080 							&& push_list[x]->s.weapon != WP_THERMAL )
6081 						{//yank the weapon - NOTE: level 1 just knocks them down, not take weapon
6082 							//FIXME: weapon yank anim if not a knockdown?
6083 							if ( InFront( self->currentOrigin, push_list[x]->currentOrigin, push_list[x]->client->ps.viewangles, 0.0f ) )
6084 							{//enemy has to be facing me, too...
6085 								WP_DropWeapon( push_list[x], pushDir );
6086 							}
6087 						}
6088 						knockback += VectorNormalize( pushDir );
6089 						if ( knockback > 200 )
6090 						{
6091 							knockback = 200;
6092 						}
6093 						if ( self->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_3 )
6094 						{//maybe just knock them down
6095 							knockback /= 3;
6096 						}
6097 					}
6098 					else
6099 					{
6100 						VectorSubtract( push_list[x]->currentOrigin, self->currentOrigin, pushDir );
6101 						knockback -= VectorNormalize( pushDir );
6102 						if ( knockback < 100 )
6103 						{
6104 							knockback = 100;
6105 						}
6106 						//scale for push level
6107 						if ( self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_2 )
6108 						{//maybe just knock them down
6109 							knockback /= 3;
6110 						}
6111 						else if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
6112 						{//super-hard push
6113 							//Hmm, maybe in this case can even nudge/knockdown a jedi?  Especially if close?
6114 							//knockback *= 5;
6115 						}
6116 					}
6117 					//actually push/pull the enemy
6118 					G_Throw( push_list[x], pushDir, knockback );
6119 					//make it so they don't actually hurt me when pulled at me...
6120 					push_list[x]->forcePuller = self->s.number;
6121 
6122 					if ( push_list[x]->client->ps.velocity[2] < knockback )
6123 					{
6124 						push_list[x]->client->ps.velocity[2] = knockback;
6125 					}
6126 
6127 					if ( push_list[x]->health > 0 )
6128 					{//target is still alive
6129 						if ( (push_list[x]->s.number||(cg.renderingThirdPerson&&!cg.zoomMode)) //NPC or 3rd person player
6130 							&& ((!pull&&self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_2 && push_list[x]->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_1) //level 1 push
6131 								|| (pull && self->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_2 && push_list[x]->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_1)) )//level 1 pull
6132 						{//NPC or third person player (without force push/pull skill), and force push/pull level is at 1
6133 							WP_ForceKnockdown(
6134 								push_list[x], self, pull,
6135 								(qboolean)(!pull && knockback > 150), qfalse );
6136 						}
6137 						else if ( !push_list[x]->s.number )
6138 						{//player, have to force an anim on him
6139 							WP_ForceKnockdown(
6140 								push_list[x], self, pull,
6141 								(qboolean)(!pull && knockback > 150), qfalse );
6142 						}
6143 						else
6144 						{//NPC and force-push/pull at level 2 or higher
6145 							WP_ForceKnockdown(
6146 								push_list[x], self, pull,
6147 								(qboolean)(!pull && knockback > 100), qfalse );
6148 						}
6149 					}
6150 					push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
6151 				}
6152 			}
6153 			else if ( push_list[x]->s.weapon == WP_SABER && (push_list[x]->contents&CONTENTS_LIGHTSABER) )
6154 			{//a thrown saber, just send it back
6155 				/*
6156 				if ( pull )
6157 				{//steal it?
6158 				}
6159 				else */if ( push_list[x]->owner && push_list[x]->owner->client && push_list[x]->owner->client->ps.saberActive && push_list[x]->s.pos.trType == TR_LINEAR && push_list[x]->owner->client->ps.saberEntityState != SES_RETURNING )
6160 				{//it's on and being controlled
6161 					//FIXME: prevent it from damaging me?
6162 					if ( self->s.number == 0 || Q_irand( 0, 2 ) )
6163 					{//certain chance of throwing it aside and turning it off?
6164 						//give it some velocity away from me
6165 						//FIXME: maybe actually push or pull it?
6166 						if ( Q_irand( 0, 1 ) )
6167 						{
6168 							VectorScale( right, -1, right );
6169 						}
6170 						G_ReflectMissile( self, push_list[x], right );
6171 						//FIXME: isn't turning off!!!
6172 						WP_SaberDrop( push_list[x]->owner, push_list[x] );
6173 					}
6174 					else
6175 					{
6176 						WP_SaberReturn( push_list[x]->owner, push_list[x] );
6177 					}
6178 					//different effect?
6179 				}
6180 			}
6181 			else if ( push_list[x]->s.eType == ET_MISSILE
6182 				&& push_list[x]->s.pos.trType != TR_STATIONARY
6183 				&& (push_list[x]->s.pos.trType != TR_INTERPOLATE||push_list[x]->s.weapon != WP_THERMAL) )//rolling and stationary thermal detonators are dealt with below
6184 			{
6185 				vec3_t dir2Me;
6186 				VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, dir2Me );
6187 				float dot = DotProduct( push_list[x]->s.pos.trDelta, dir2Me );
6188 				if ( pull )
6189 				{//deflect rather than reflect?
6190 				}
6191 				else
6192 				{
6193 					if ( push_list[x]->s.eFlags&EF_MISSILE_STICK )
6194 					{//caught a sticky in-air
6195 						push_list[x]->s.eType = ET_MISSILE;
6196 						push_list[x]->s.eFlags &= ~EF_MISSILE_STICK;
6197 						push_list[x]->s.eFlags |= EF_BOUNCE_HALF;
6198 						push_list[x]->splashDamage /= 3;
6199 						push_list[x]->splashRadius /= 3;
6200 						push_list[x]->e_ThinkFunc = thinkF_WP_Explode;
6201 						push_list[x]->nextthink = level.time + Q_irand( 500, 3000 );
6202 					}
6203 					if ( dot >= 0 )
6204 					{//it's heading towards me
6205 						G_ReflectMissile( self, push_list[x], forward );
6206 					}
6207 					else
6208 					{
6209 						VectorScale( push_list[x]->s.pos.trDelta, 1.25f, push_list[x]->s.pos.trDelta );
6210 					}
6211 					//deflect sound
6212 					//G_Sound( push_list[x], G_SoundIndex( va("sound/weapons/blaster/reflect%d.wav", Q_irand( 1, 3 ) ) ) );
6213 					//push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
6214 				}
6215 			}
6216 			else if ( push_list[x]->svFlags & SVF_GLASS_BRUSH )
6217 			{//break the glass
6218 				trace_t tr;
6219 				vec3_t	pushDir;
6220 				float	damage = 800;
6221 
6222 				AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
6223 				VectorNormalize( forward );
6224 				VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
6225 				gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
6226 				if ( tr.entityNum != push_list[x]->s.number || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
6227 				{//must be pointing right at it
6228 					continue;
6229 				}
6230 
6231 				if ( pull )
6232 				{
6233 					VectorSubtract( self->client->renderInfo.eyePoint, tr.endpos, pushDir );
6234 				}
6235 				else
6236 				{
6237 					VectorSubtract( tr.endpos, self->client->renderInfo.eyePoint, pushDir );
6238 				}
6239 				/*
6240 				VectorSubtract( push_list[x]->absmax, push_list[x]->absmin, size );
6241 				VectorMA( push_list[x]->absmin, 0.5, size, center );
6242 				if ( pull )
6243 				{
6244 					VectorSubtract( self->client->renderInfo.eyePoint, center, pushDir );
6245 				}
6246 				else
6247 				{
6248 					VectorSubtract( center, self->client->renderInfo.eyePoint, pushDir );
6249 				}
6250 				*/
6251 				damage -= VectorNormalize( pushDir );
6252 				if ( damage < 200 )
6253 				{
6254 					damage = 200;
6255 				}
6256 				VectorScale( pushDir, damage, pushDir );
6257 
6258 				G_Damage( push_list[x], self, self, pushDir, tr.endpos, damage, 0, MOD_UNKNOWN );
6259 			}
6260 			else if ( !Q_stricmp( "func_static", push_list[x]->classname ) )
6261 			{//force-usable func_static
6262 				if ( !pull && (push_list[x]->spawnflags&1/*F_PUSH*/) )
6263 				{
6264 					GEntity_UseFunc( push_list[x], self, self );
6265 				}
6266 				else if ( pull && (push_list[x]->spawnflags&2/*F_PULL*/) )
6267 				{
6268 					GEntity_UseFunc( push_list[x], self, self );
6269 				}
6270 			}
6271 			else if ( !Q_stricmp( "func_door", push_list[x]->classname ) && (push_list[x]->spawnflags&2/*MOVER_FORCE_ACTIVATE*/) )
6272 			{//push/pull the door
6273 				vec3_t	pos1, pos2;
6274 
6275 				AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
6276 				VectorNormalize( forward );
6277 				VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
6278 				gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
6279 				if ( tr.entityNum != push_list[x]->s.number || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
6280 				{//must be pointing right at it
6281 					continue;
6282 				}
6283 
6284 				if ( VectorCompare( vec3_origin, push_list[x]->s.origin ) )
6285 				{//does not have an origin brush, so pos1 & pos2 are relative to world origin, need to calc center
6286 					VectorSubtract( push_list[x]->absmax, push_list[x]->absmin, size );
6287 					VectorMA( push_list[x]->absmin, 0.5, size, center );
6288 					if ( (push_list[x]->spawnflags&1) && push_list[x]->moverState == MOVER_POS1 )
6289 					{//if at pos1 and started open, make sure we get the center where it *started* because we're going to add back in the relative values pos1 and pos2
6290 						VectorSubtract( center, push_list[x]->pos1, center );
6291 					}
6292 					else if ( !(push_list[x]->spawnflags&1) && push_list[x]->moverState == MOVER_POS2 )
6293 					{//if at pos2, make sure we get the center where it *started* because we're going to add back in the relative values pos1 and pos2
6294 						VectorSubtract( center, push_list[x]->pos2, center );
6295 					}
6296 					VectorAdd( center, push_list[x]->pos1, pos1 );
6297 					VectorAdd( center, push_list[x]->pos2, pos2 );
6298 				}
6299 				else
6300 				{//actually has an origin, pos1 and pos2 are absolute
6301 					VectorCopy( push_list[x]->currentOrigin, center );
6302 					VectorCopy( push_list[x]->pos1, pos1 );
6303 					VectorCopy( push_list[x]->pos2, pos2 );
6304 				}
6305 
6306 				if ( Distance( pos1, self->client->renderInfo.eyePoint ) < Distance( pos2, self->client->renderInfo.eyePoint ) )
6307 				{//pos1 is closer
6308 					if ( push_list[x]->moverState == MOVER_POS1 )
6309 					{//at the closest pos
6310 						if ( pull )
6311 						{//trying to pull, but already at closest point, so screw it
6312 							continue;
6313 						}
6314 					}
6315 					else if ( push_list[x]->moverState == MOVER_POS2 )
6316 					{//at farthest pos
6317 						if ( !pull )
6318 						{//trying to push, but already at farthest point, so screw it
6319 							continue;
6320 						}
6321 					}
6322 				}
6323 				else
6324 				{//pos2 is closer
6325 					if ( push_list[x]->moverState == MOVER_POS1 )
6326 					{//at the farthest pos
6327 						if ( !pull )
6328 						{//trying to push, but already at farthest point, so screw it
6329 							continue;
6330 						}
6331 					}
6332 					else if ( push_list[x]->moverState == MOVER_POS2 )
6333 					{//at closest pos
6334 						if ( pull )
6335 						{//trying to pull, but already at closest point, so screw it
6336 							continue;
6337 						}
6338 					}
6339 				}
6340 				GEntity_UseFunc( push_list[x], self, self );
6341 			}
6342 			else if ( push_list[x]->s.eType == ET_MISSILE/*thermal resting on ground*/
6343 				|| push_list[x]->s.eType == ET_ITEM
6344 				|| push_list[x]->e_ThinkFunc == thinkF_G_RunObject || Q_stricmp( "limb", push_list[x]->classname ) == 0 )
6345 			{//general object, toss it
6346 				vec3_t	pushDir, kvel;
6347 				float	knockback = pull?0:200;
6348 				float	mass = 200;
6349 				if ( pull )
6350 				{
6351 					if ( push_list[x]->s.eType == ET_ITEM )
6352 					{//pull it to a little higher point
6353 						vec3_t	adjustedOrg;
6354 						VectorCopy( self->currentOrigin, adjustedOrg );
6355 						adjustedOrg[2] += self->maxs[2]/3;
6356 						VectorSubtract( adjustedOrg, push_list[x]->currentOrigin, pushDir );
6357 					}
6358 					else
6359 					{
6360 						VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, pushDir );
6361 					}
6362 					knockback += VectorNormalize( pushDir );
6363 					if ( knockback > 200 )
6364 					{
6365 						knockback = 200;
6366 					}
6367 					if ( push_list[x]->s.eType == ET_ITEM
6368 						&& push_list[x]->item
6369 						&& push_list[x]->item->giType == IT_HOLDABLE
6370 						&& push_list[x]->item->giTag == INV_SECURITY_KEY )
6371 					{//security keys are pulled with less enthusiasm
6372 						if ( knockback > 100 )
6373 						{
6374 							knockback = 100;
6375 						}
6376 					}
6377 					else if ( knockback > 200 )
6378 					{
6379 						knockback = 200;
6380 					}
6381 				}
6382 				else
6383 				{
6384 					//HMM, if I have an auto-enemy & he's in front of me, push it toward him?
6385 					VectorSubtract( push_list[x]->currentOrigin, self->currentOrigin, pushDir );
6386 					knockback -= VectorNormalize( pushDir );
6387 					if ( knockback < 100 )
6388 					{
6389 						knockback = 100;
6390 					}
6391 				}
6392 				//FIXME: if pull a FL_FORCE_PULLABLE_ONLY, clear the flag, assuming it's no longer in solid?  or check?
6393 				VectorCopy( push_list[x]->currentOrigin, push_list[x]->s.pos.trBase );
6394 				push_list[x]->s.pos.trTime = level.time;								// move a bit on the very first frame
6395 				if ( push_list[x]->s.pos.trType != TR_INTERPOLATE )
6396 				{//don't do this to rolling missiles
6397 					push_list[x]->s.pos.trType = TR_GRAVITY;
6398 				}
6399 
6400 				if ( push_list[x]->e_ThinkFunc == thinkF_G_RunObject && push_list[x]->physicsBounce )
6401 				{
6402 					mass = push_list[x]->physicsBounce;
6403 				}
6404 				if ( mass < 50 )
6405 				{//???
6406 					mass = 50;
6407 				}
6408 				if ( g_gravity->value > 0 )
6409 				{
6410 					VectorScale( pushDir, g_knockback->value * knockback / mass * 0.8, kvel );
6411 					kvel[2] = pushDir[2] * g_knockback->value * knockback / mass * 1.5;
6412 				}
6413 				else
6414 				{
6415 					VectorScale( pushDir, g_knockback->value * knockback / mass, kvel );
6416 				}
6417 				VectorAdd( push_list[x]->s.pos.trDelta, kvel, push_list[x]->s.pos.trDelta );
6418 				if ( g_gravity->value > 0 )
6419 				{
6420 					if ( push_list[x]->s.pos.trDelta[2] < knockback )
6421 					{
6422 						push_list[x]->s.pos.trDelta[2] = knockback;
6423 					}
6424 				}
6425 				//no trDuration?
6426 				if ( push_list[x]->e_ThinkFunc != thinkF_G_RunObject )
6427 				{//objects spin themselves?
6428 					//spin it
6429 					//FIXME: messing with roll ruins the rotational center???
6430 					push_list[x]->s.apos.trTime = level.time;
6431 					push_list[x]->s.apos.trType = TR_LINEAR;
6432 					VectorClear( push_list[x]->s.apos.trDelta );
6433 					push_list[x]->s.apos.trDelta[1] = Q_irand( -800, 800 );
6434 				}
6435 
6436 				if ( Q_stricmp( "limb", push_list[x]->classname ) == 0 )
6437 				{//make sure it runs it's physics
6438 					push_list[x]->e_ThinkFunc = thinkF_LimbThink;
6439 					push_list[x]->nextthink = level.time + FRAMETIME;
6440 				}
6441 				push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
6442 				if ( push_list[x]->item && push_list[x]->item->giTag == INV_SECURITY_KEY )
6443 				{
6444 					AddSightEvent( player, push_list[x]->currentOrigin, 128, AEL_DISCOVERED );//security keys are more important
6445 				}
6446 				else
6447 				{
6448 					AddSightEvent( player, push_list[x]->currentOrigin, 128, AEL_SUSPICIOUS );//hmm... or should this always be discovered?
6449 				}
6450 			}
6451 			else if ( push_list[x]->s.weapon == WP_TURRET
6452 				&& !Q_stricmp( "PAS", push_list[x]->classname )
6453 				&& push_list[x]->s.apos.trType == TR_STATIONARY )
6454 			{//a portable turret
6455 				WP_KnockdownTurret( self, push_list[x] );
6456 			}
6457 		}
6458 		if ( pull )
6459 		{
6460 			if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
6461 			{//at level 3, can pull multiple, so it costs more
6462 				actualCost = forcePowerNeeded[FP_PUSH]*ent_count;
6463 				if ( actualCost > 50 )
6464 				{
6465 					actualCost = 50;
6466 				}
6467 				else if ( actualCost < cost )
6468 				{
6469 					actualCost = cost;
6470 				}
6471 			}
6472 			else
6473 			{
6474 				actualCost = cost;
6475 			}
6476 			WP_ForcePowerStart( self, FP_PULL, actualCost );
6477 		}
6478 		else
6479 		{
6480 			if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
6481 			{//at level 3, can push multiple, so costs more
6482 				actualCost = forcePowerNeeded[FP_PUSH]*ent_count;
6483 				if ( actualCost > 50 )
6484 				{
6485 					actualCost = 50;
6486 				}
6487 				else if ( actualCost < cost )
6488 				{
6489 					actualCost = cost;
6490 				}
6491 			}
6492 			else if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_1 )
6493 			{//at level 3, can push multiple, so costs more
6494 				actualCost = floor(forcePowerNeeded[FP_PUSH]*ent_count/1.5f);
6495 				if ( actualCost > 50 )
6496 				{
6497 					actualCost = 50;
6498 				}
6499 				else if ( actualCost < cost )
6500 				{
6501 					actualCost = cost;
6502 				}
6503 			}
6504 			else
6505 			{
6506 				actualCost = cost;
6507 			}
6508 			WP_ForcePowerStart( self, FP_PUSH, actualCost );
6509 		}
6510 	}
6511 	else
6512 	{//didn't push or pull anything?  don't penalize them too much
6513 		if ( pull )
6514 		{
6515 			WP_ForcePowerStart( self, FP_PULL, 5 );
6516 		}
6517 		else
6518 		{
6519 			WP_ForcePowerStart( self, FP_PUSH, 5 );
6520 		}
6521 	}
6522 	if ( self->NPC )
6523 	{//NPCs can push more often
6524 		//FIXME: vary by rank and game skill?
6525 		self->client->ps.forcePowerDebounce[FP_PUSH] = level.time + 200;
6526 	}
6527 	else
6528 	{
6529 		self->client->ps.forcePowerDebounce[FP_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500;
6530 	}
6531 }
6532 
ForceSpeed(gentity_t * self,int duration)6533 void ForceSpeed( gentity_t *self, int duration )
6534 {
6535 	if ( self->health <= 0 )
6536 	{
6537 		return;
6538 	}
6539 	if ( !WP_ForcePowerUsable( self, FP_SPEED, 0 ) )
6540 	{
6541 		return;
6542 	}
6543 	if ( self->client->ps.saberLockTime > level.time )
6544 	{//FIXME: can this be a way to break out?
6545 		return;
6546 	}
6547 	if ( !self->s.number && in_camera )
6548 	{//player can't use force powers in cinematic
6549 		return;
6550 	}
6551 	WP_ForcePowerStart( self, FP_SPEED, 0 );
6552 	if ( duration )
6553 	{
6554 		self->client->ps.forcePowerDuration[FP_SPEED] = level.time + duration;
6555 	}
6556 	G_Sound( self, G_SoundIndex( "sound/weapons/force/speed.wav" ) );
6557 }
6558 
ForceHeal(gentity_t * self)6559 void ForceHeal( gentity_t *self )
6560 {
6561 	if ( self->health <= 0 || self->client->ps.stats[STAT_MAX_HEALTH] <= self->health )
6562 	{
6563 		return;
6564 	}
6565 
6566 	if ( !WP_ForcePowerUsable( self, FP_HEAL, 20 ) )
6567 	{//must have enough force power for at least 5 points of health
6568 		return;
6569 	}
6570 
6571 	if ( self->painDebounceTime > level.time || (self->client->ps.weaponTime&&self->client->ps.weapon!=WP_NONE) )
6572 	{//can't initiate a heal while taking pain or attacking
6573 		return;
6574 	}
6575 
6576 	if ( self->client->ps.saberLockTime > level.time )
6577 	{//FIXME: can this be a way to break out?
6578 		return;
6579 	}
6580 	if ( !self->s.number && in_camera )
6581 	{//player can't use force powers in cinematic
6582 		return;
6583 	}
6584 	/*
6585 	if ( self->client->ps.forcePowerLevel[FP_HEAL] > FORCE_LEVEL_2 )
6586 	{//instant heal
6587 		//no more than available force power
6588 		int max = self->client->ps.forcePower;
6589 		if ( max > MAX_FORCE_HEAL )
6590 		{//no more than max allowed
6591 			max = MAX_FORCE_HEAL;
6592 		}
6593 		if ( max > self->client->ps.stats[STAT_MAX_HEALTH] - self->health )
6594 		{//no more than what's missing
6595 			max = self->client->ps.stats[STAT_MAX_HEALTH] - self->health;
6596 		}
6597 		self->health += max;
6598 		WP_ForcePowerDrain( self, FP_HEAL, max );
6599 		G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d.mp3", Q_irand( 1, 4 ) ) );
6600 	}
6601 	else
6602 	*/
6603 	{
6604 		//start health going up
6605 		//NPC_SetAnim( self, SETANIM_TORSO, ?, SETANIM_FLAG_OVERRIDE );
6606 		WP_ForcePowerStart( self, FP_HEAL, 0 );
6607 		if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 )
6608 		{//must meditate
6609 			//FIXME: holster weapon (select WP_NONE?)
6610 			//FIXME: BOTH_FORCEHEAL_START
6611 			NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCEHEAL_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
6612 			self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
6613 			self->client->ps.saberBlocked = BLOCKED_NONE;
6614 			self->client->ps.torsoAnimTimer = self->client->ps.legsAnimTimer = FORCE_HEAL_INTERVAL*MAX_FORCE_HEAL + 2000;//???
6615 			if ( self->client->ps.saberActive )
6616 			{
6617 				self->client->ps.saberActive = qfalse;//turn off saber when meditating
6618 				if ( self->client->playerTeam == TEAM_PLAYER )
6619 				{
6620 					G_SoundOnEnt( self, CHAN_WEAPON, "sound/weapons/saber/saberoff.wav" );
6621 				}
6622 				else
6623 				{
6624 					G_SoundOnEnt( self, CHAN_WEAPON, "sound/weapons/saber/enemy_saber_off.wav" );
6625 				}
6626 			}
6627 		}
6628 		else
6629 		{//just a quick gesture
6630 			/*
6631 			//Can't get an anim that looks good...
6632 			NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEHEAL_QUICK, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
6633 			self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
6634 			self->client->ps.saberBlocked = BLOCKED_NONE;
6635 			*/
6636 		}
6637 	}
6638 
6639 	//FIXME: always play healing effect
6640 	G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/heal.mp3" );
6641 }
6642 
6643 extern void NPC_PlayConfusionSound( gentity_t *self );
6644 extern void NPC_Jedi_PlayConfusionSound( gentity_t *self );
WP_CheckBreakControl(gentity_t * self)6645 qboolean WP_CheckBreakControl( gentity_t *self )
6646 {
6647 	if ( !self )
6648 	{
6649 		return qfalse;
6650 	}
6651 	if ( !self->s.number )
6652 	{//player
6653 		if ( self->client && self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
6654 		{//control-level
6655 			if ( self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_WORLD )
6656 			{//we are in a viewentity
6657 				gentity_t *controlled = &g_entities[self->client->ps.viewEntity];
6658 				if ( controlled->NPC && controlled->NPC->controlledTime > level.time )
6659 				{//it is an NPC we controlled
6660 					//clear it and return
6661 					G_ClearViewEntity( self );
6662 					return qtrue;
6663 				}
6664 			}
6665 		}
6666 	}
6667 	else
6668 	{//NPC
6669 		if ( self->NPC && self->NPC->controlledTime > level.time )
6670 		{//being controlled
6671 			gentity_t *controller = &g_entities[0];
6672 			if ( controller->client && controller->client->ps.viewEntity == self->s.number )
6673 			{//we are being controlled by player
6674 				if ( controller->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
6675 				{//control-level mind trick
6676 					//clear the control and return
6677 					G_ClearViewEntity( controller );
6678 					return qtrue;
6679 				}
6680 			}
6681 		}
6682 	}
6683 	return qfalse;
6684 }
6685 
ForceTelepathy(gentity_t * self)6686 void ForceTelepathy( gentity_t *self )
6687 {
6688 	trace_t	tr;
6689 	vec3_t	end, forward;
6690 	gentity_t	*traceEnt;
6691 	qboolean	targetLive = qfalse;
6692 
6693 	if ( WP_CheckBreakControl( self ) )
6694 	{
6695 		return;
6696 	}
6697 	if ( self->health <= 0 )
6698 	{
6699 		return;
6700 	}
6701 	//FIXME: if mind trick 3 and aiming at an enemy need more force power
6702 	if ( !WP_ForcePowerUsable( self, FP_TELEPATHY, 0 ) )
6703 	{
6704 		return;
6705 	}
6706 
6707 	if ( self->client->ps.weaponTime >= 800 )
6708 	{//just did one!
6709 		return;
6710 	}
6711 	if ( self->client->ps.saberLockTime > level.time )
6712 	{//FIXME: can this be a way to break out?
6713 		return;
6714 	}
6715 	if ( !self->s.number && in_camera )
6716 	{//player can't use force powers in cinematic
6717 		return;
6718 	}
6719 
6720 	AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
6721 	VectorNormalize( forward );
6722 	VectorMA( self->client->renderInfo.eyePoint, 2048, forward, end );
6723 
6724 	//Cause a distraction if enemy is not fighting
6725 	gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_OPAQUE|CONTENTS_BODY, G2_NOCOLLIDE, 0 );
6726 	if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
6727 	{
6728 		return;
6729 	}
6730 
6731 	traceEnt = &g_entities[tr.entityNum];
6732 
6733 	if( traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE )
6734 	{
6735 		return;
6736 	}
6737 
6738 	if ( traceEnt && traceEnt->client  )
6739 	{
6740 		switch ( traceEnt->client->NPC_class )
6741 		{
6742 		case CLASS_GALAKMECH://cant grip him, he's in armor
6743 		case CLASS_ATST://much too big to grip!
6744 		//no droids either
6745 		case CLASS_PROBE:
6746 		case CLASS_GONK:
6747 		case CLASS_R2D2:
6748 		case CLASS_R5D2:
6749 		case CLASS_MARK1:
6750 		case CLASS_MARK2:
6751 		case CLASS_MOUSE:
6752 		case CLASS_SEEKER:
6753 		case CLASS_REMOTE:
6754 		case CLASS_PROTOCOL:
6755 			break;
6756 		default:
6757 			targetLive = qtrue;
6758 			break;
6759 		}
6760 	}
6761 	if ( targetLive && traceEnt->NPC )
6762 	{//hit an organic non-player
6763 		if ( G_ActivateBehavior( traceEnt, BSET_MINDTRICK ) )
6764 		{//activated a script on him
6765 			//FIXME: do the visual sparkles effect on their heads, still?
6766 			WP_ForcePowerStart( self, FP_TELEPATHY, 0 );
6767 		}
6768 		else if ( traceEnt->client->playerTeam != self->client->playerTeam )
6769 		{//an enemy
6770 			int override = 0;
6771 			if ( (traceEnt->NPC->scriptFlags&SCF_NO_MIND_TRICK) )
6772 			{
6773 				if ( traceEnt->client->NPC_class == CLASS_GALAKMECH )
6774 				{
6775 					G_AddVoiceEvent( NPC, Q_irand( EV_CONFUSE1, EV_CONFUSE3 ), Q_irand( 3000, 5000 ) );
6776 				}
6777 			}
6778 			else if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
6779 			{//control them, even jedi
6780 				G_SetViewEntity( self, traceEnt );
6781 				traceEnt->NPC->controlledTime = level.time + 30000;
6782 			}
6783 			else if ( traceEnt->s.weapon != WP_SABER )
6784 			{//haha!  Jedi aren't easily confused!
6785 				if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_2 )
6786 				{//turn them to our side
6787 					//if mind trick 3 and aiming at an enemy need more force power
6788 					override = 50;
6789 					if ( self->client->ps.forcePower < 50 )
6790 					{
6791 						return;
6792 					}
6793 					if ( traceEnt->s.weapon != WP_NONE )
6794 					{//don't charm people who aren't capable of fighting... like ugnaughts and droids
6795 						if ( traceEnt->enemy )
6796 						{
6797 							G_ClearEnemy( traceEnt );
6798 						}
6799 						if ( traceEnt->NPC )
6800 						{
6801 							//traceEnt->NPC->tempBehavior = BS_FOLLOW_LEADER;
6802 							traceEnt->client->leader = self;
6803 						}
6804 						//FIXME: maybe pick an enemy right here?
6805 						team_t	saveTeam = traceEnt->client->enemyTeam;
6806 						traceEnt->client->enemyTeam = traceEnt->client->playerTeam;
6807 						traceEnt->client->playerTeam = saveTeam;
6808 						//FIXME: need a *charmed* timer on this...?  Or do TEAM_PLAYERS assume that "confusion" means they should switch to team_enemy when done?
6809 						traceEnt->NPC->charmedTime = level.time + mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]];
6810 					}
6811 				}
6812 				else
6813 				{//just confuse them
6814 					//somehow confuse them?  Set don't fire to true for a while?  Drop their aggression?  Maybe just take their enemy away and don't let them pick one up for a while unless shot?
6815 					traceEnt->NPC->confusionTime = level.time + mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]];//confused for about 10 seconds
6816 					NPC_PlayConfusionSound( traceEnt );
6817 					if ( traceEnt->enemy )
6818 					{
6819 						G_ClearEnemy( traceEnt );
6820 					}
6821 				}
6822 			}
6823 			else
6824 			{
6825 				NPC_Jedi_PlayConfusionSound( traceEnt );
6826 			}
6827 			WP_ForcePowerStart( self, FP_TELEPATHY, override );
6828 		}
6829 		else if ( traceEnt->client->playerTeam == self->client->playerTeam )
6830 		{//an ally
6831 			//maybe just have him look at you?  Respond?  Take your enemy?
6832 			if ( traceEnt->client->ps.pm_type < PM_DEAD && traceEnt->NPC!=NULL && !(traceEnt->NPC->scriptFlags&SCF_NO_RESPONSE) )
6833 			{
6834 				NPC_UseResponse( traceEnt, self, qfalse );
6835 				WP_ForcePowerStart( self, FP_TELEPATHY, 1 );
6836 			}
6837 		}//NOTE: no effect on TEAM_NEUTRAL?
6838 		vec3_t	eyeDir;
6839 		AngleVectors( traceEnt->client->renderInfo.eyeAngles, eyeDir, NULL, NULL );
6840 		VectorNormalize( eyeDir );
6841 		G_PlayEffect( "force_touch", traceEnt->client->renderInfo.eyePoint, eyeDir );
6842 
6843 		//make sure this plays and that you cannot press fire for about 1 second after this
6844 		//FIXME: BOTH_FORCEMINDTRICK or BOTH_FORCEDISTRACT
6845 		NPC_SetAnim( self, SETANIM_TORSO, BOTH_MINDTRICK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD );
6846 		//FIXME: build-up or delay this until in proper part of anim
6847 	}
6848 	else
6849 	{
6850 		if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_1 && tr.fraction * 2048 > 64 )
6851 		{//don't create a diversion less than 64 from you of if at power level 1
6852 			//use distraction anim instead
6853 			G_PlayEffect( G_EffectIndex( "force_touch" ), tr.endpos, tr.plane.normal );
6854 			//FIXME: these events don't seem to always be picked up...?
6855 			AddSoundEvent( self, tr.endpos, 512, AEL_SUSPICIOUS, qtrue );
6856 			AddSightEvent( self, tr.endpos, 512, AEL_SUSPICIOUS, 50 );
6857 			WP_ForcePowerStart( self, FP_TELEPATHY, 0 );
6858 		}
6859 		NPC_SetAnim( self, SETANIM_TORSO, BOTH_MINDTRICK2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD );
6860 	}
6861 	self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
6862 	self->client->ps.saberBlocked = BLOCKED_NONE;
6863 	self->client->ps.weaponTime = 1000;
6864 	if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
6865 	{
6866 		self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
6867 	}
6868 }
6869 
ForceGrip(gentity_t * self)6870 void ForceGrip( gentity_t *self )
6871 {//FIXME: make enemy Jedi able to use this
6872 	trace_t	tr;
6873 	vec3_t	end, forward;
6874 	gentity_t	*traceEnt = NULL;
6875 
6876 	if ( self->health <= 0 )
6877 	{
6878 		return;
6879 	}
6880 	if ( !self->s.number && (cg.zoomMode || in_camera) )
6881 	{//can't force grip when zoomed in or in cinematic
6882 		return;
6883 	}
6884 	if ( self->client->ps.leanofs )
6885 	{//can't force-grip while leaning
6886 		return;
6887 	}
6888 
6889 	if ( self->client->ps.forceGripEntityNum <= ENTITYNUM_WORLD )
6890 	{//already gripping
6891 		if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
6892 		{
6893 			self->client->ps.forcePowerDuration[FP_GRIP] = level.time + 100;
6894 			self->client->ps.weaponTime = 1000;
6895 			if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
6896 			{
6897 				self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
6898 			}
6899 		}
6900 		return;
6901 	}
6902 
6903 	if ( !WP_ForcePowerUsable( self, FP_GRIP, 0 ) )
6904 	{//can't use it right now
6905 		return;
6906 	}
6907 
6908 	if ( self->client->ps.forcePower < 26 )
6909 	{//need 20 to start, 6 to hold it for any decent amount of time...
6910 		return;
6911 	}
6912 
6913 	if ( self->client->ps.weaponTime )
6914 	{//busy
6915 		return;
6916 	}
6917 
6918 	if ( self->client->ps.saberLockTime > level.time )
6919 	{//FIXME: can this be a way to break out?
6920 		return;
6921 	}
6922 	//Cause choking anim + health drain in ent in front of me
6923 	NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEGRIP_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
6924 	self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
6925 	self->client->ps.saberBlocked = BLOCKED_NONE;
6926 
6927 	self->client->ps.weaponTime = 1000;
6928 	if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
6929 	{
6930 		self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
6931 	}
6932 
6933 	AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
6934 	VectorNormalize( forward );
6935 	VectorMA( self->client->renderInfo.handLPoint, FORCE_GRIP_DIST, forward, end );
6936 
6937 	if ( self->enemy && (self->s.number || InFront( self->enemy->currentOrigin, self->client->renderInfo.eyePoint, self->client->ps.viewangles, 0.2f ) ) )
6938 	{//NPCs can always lift enemy since we assume they're looking at them, players need to be facing the enemy
6939 		if ( gi.inPVS( self->enemy->currentOrigin, self->client->renderInfo.eyePoint ) )
6940 		{//must be in PVS
6941 			gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, self->enemy->currentOrigin, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
6942 			if ( tr.fraction == 1.0f || tr.entityNum == self->enemy->s.number )
6943 			{//must have clear LOS
6944 				traceEnt = self->enemy;
6945 			}
6946 		}
6947 	}
6948 	if ( !traceEnt )
6949 	{//okay, trace straight ahead and see what's there
6950 		gi.trace( &tr, self->client->renderInfo.handLPoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
6951 		if ( tr.entityNum >= ENTITYNUM_WORLD || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
6952 		{
6953 			return;
6954 		}
6955 
6956 		traceEnt = &g_entities[tr.entityNum];
6957 	}
6958 
6959 	if ( !traceEnt || traceEnt == self/*???*/ || traceEnt->bmodel || (traceEnt->health <= 0 && traceEnt->takedamage) || (traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE) )
6960 	{
6961 		return;
6962 	}
6963 
6964 	if ( traceEnt->client )
6965 	{
6966 		if ( traceEnt->client->ps.forceJumpZStart )
6967 		{//can't catch them in mid force jump - FIXME: maybe base it on velocity?
6968 			return;
6969 		}
6970 		switch ( traceEnt->client->NPC_class )
6971 		{
6972 		case CLASS_GALAKMECH://cant grip him, he's in armor
6973 			G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), Q_irand( 3000, 5000 ) );
6974 			return;
6975 			break;
6976 		case CLASS_ATST://much too big to grip!
6977 			return;
6978 		//no droids either...?
6979 		case CLASS_GONK:
6980 		case CLASS_R2D2:
6981 		case CLASS_R5D2:
6982 		case CLASS_MARK1:
6983 		case CLASS_MARK2:
6984 		case CLASS_MOUSE://?
6985 		case CLASS_PROTOCOL:
6986 			//*sigh*... in JK3, you'll be able to grab and move *anything*...
6987 			return;
6988 			break;
6989 		case CLASS_PROBE:
6990 		case CLASS_SEEKER:
6991 		case CLASS_REMOTE:
6992 		case CLASS_SENTRY:
6993 		case CLASS_INTERROGATOR:
6994 			//*sigh*... in JK3, you'll be able to grab and move *anything*...
6995 			return;
6996 			break;
6997 		case CLASS_DESANN://Desann cannot be gripped, he just pushes you back instantly
6998 			Jedi_PlayDeflectSound( traceEnt );
6999 			ForceThrow( traceEnt, qfalse );
7000 			return;
7001 			break;
7002 		case CLASS_REBORN:
7003 		case CLASS_SHADOWTROOPER:
7004 		case CLASS_TAVION:
7005 		case CLASS_JEDI:
7006 		case CLASS_LUKE:
7007 			if ( traceEnt->NPC && traceEnt->NPC->rank > RANK_CIVILIAN && self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
7008 			{
7009 				Jedi_PlayDeflectSound( traceEnt );
7010 				ForceThrow( traceEnt, qfalse );
7011 				return;
7012 			}
7013 			break;
7014 		default:
7015 			break;
7016 		}
7017 		if ( traceEnt->s.weapon == WP_EMPLACED_GUN )
7018 		{//FIXME: maybe can pull them out?
7019 			return;
7020 		}
7021 		if ( self->enemy && traceEnt != self->enemy && traceEnt->client->playerTeam == self->client->playerTeam )
7022 		{//can't accidently grip your teammate in combat
7023 			return;
7024 		}
7025 	}
7026 	else
7027 	{//can't grip non-clients... right?
7028 		return;
7029 	}
7030 	WP_ForcePowerStart( self, FP_GRIP, 20 );
7031 	//FIXME: rule out other things?
7032 	//FIXME: Jedi resist, like the push and pull?
7033 	self->client->ps.forceGripEntityNum = traceEnt->s.number;
7034 	//if ( traceEnt->client )
7035 	{
7036 		G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
7037 		if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 || traceEnt->s.weapon == WP_SABER )
7038 		{//if we pick up & carry, drop their weap
7039 			if ( traceEnt->s.weapon )
7040 			{
7041 				if ( traceEnt->s.weapon != WP_SABER )
7042 				{
7043 					WP_DropWeapon( traceEnt, NULL );
7044 				}
7045 				else
7046 				{
7047 					//turn it off?
7048 					traceEnt->client->ps.saberActive = qfalse;
7049 					G_SoundOnEnt( traceEnt, CHAN_WEAPON, "sound/weapons/saber/saberoffquick.wav" );
7050 				}
7051 			}
7052 		}
7053 		//else FIXME: need a one-armed choke if we're not on a high enough level to make them drop their gun
7054 		VectorCopy( traceEnt->client->renderInfo.headPoint, self->client->ps.forceGripOrg );
7055 	}
7056 	/*
7057 	else
7058 	{
7059 		VectorCopy( traceEnt->currentOrigin, self->client->ps.forceGripOrg );
7060 	}
7061 	*/
7062 	self->client->ps.forceGripOrg[2] += 48;//FIXME: define?
7063 	if ( self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
7064 	{//just a duration
7065 		self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 250;
7066 		self->client->ps.forcePowerDuration[FP_GRIP] = level.time + 5000;
7067 		traceEnt->s.loopSound = G_SoundIndex( "sound/weapons/force/grip.mp3" );
7068 	}
7069 	else
7070 	{
7071 		if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_2 )
7072 		{//lifting sound?  or always?
7073 		}
7074 		//if ( traceEnt->s.number )
7075 		{//picks them up for a second first
7076 			self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 1000;
7077 		}
7078 		/*
7079 		else
7080 		{//player should take damage right away
7081 			self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 250;
7082 		}
7083 		*/
7084 		self->s.loopSound = G_SoundIndex( "sound/weapons/force/grip.mp3" );
7085 	}
7086 }
7087 
ForceLightning(gentity_t * self)7088 void ForceLightning( gentity_t *self )
7089 {
7090 	if ( self->health <= 0 )
7091 	{
7092 		return;
7093 	}
7094 	if ( !self->s.number && (cg.zoomMode || in_camera) )
7095 	{//can't force lightning when zoomed in or in cinematic
7096 		return;
7097 	}
7098 	if ( self->client->ps.leanofs )
7099 	{//can't force-lightning while leaning
7100 		return;
7101 	}
7102 	if ( self->client->ps.forcePower < 25 || !WP_ForcePowerUsable( self, FP_LIGHTNING, 0 ) )
7103 	{
7104 		return;
7105 	}
7106 	if ( self->client->ps.forcePowerDebounce[FP_LIGHTNING] > level.time )
7107 	{//stops it while using it and also after using it, up to 3 second delay
7108 		return;
7109 	}
7110 	if ( self->client->ps.saberLockTime > level.time )
7111 	{//FIXME: can this be a way to break out?
7112 		return;
7113 	}
7114 	//Shoot lightning from hand
7115 	//make sure this plays and that you cannot press fire for about 1 second after this
7116 	if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
7117 	{
7118 		NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7119 	}
7120 	else
7121 	{
7122 		NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7123 	}
7124 	self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
7125 	self->client->ps.saberBlocked = BLOCKED_NONE;
7126 
7127 	G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/lightning.wav" );
7128 	if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
7129 	{//short burst
7130 		//G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/lightning.wav" );
7131 	}
7132 	else
7133 	{//holding it
7134 		self->s.loopSound = G_SoundIndex( "sound/weapons/force/lightning2.wav" );
7135 	}
7136 
7137 	//FIXME: build-up or delay this until in proper part of anim
7138 	self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
7139 	WP_ForcePowerStart( self, FP_LIGHTNING, self->client->ps.torsoAnimTimer );
7140 }
7141 
ForceLightningDamage(gentity_t * self,gentity_t * traceEnt,vec3_t dir,float dist,float dot,vec3_t impactPoint)7142 void ForceLightningDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, float dist, float dot, vec3_t impactPoint )
7143 {
7144 	if( traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE )
7145 	{
7146 		return;
7147 	}
7148 
7149 	if ( traceEnt && traceEnt->takedamage )
7150 	{
7151 		if ( !traceEnt->client || traceEnt->client->playerTeam != self->client->playerTeam || self->enemy == traceEnt || traceEnt->enemy == self )
7152 		{//an enemy or object
7153 			int	dmg;
7154 			if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 )
7155 			{//more damage if closer and more in front
7156 				dmg = 1;
7157 				if ( dist < 100 )
7158 				{
7159 					dmg += 2;
7160 				}
7161 				else if ( dist < 200 )
7162 				{
7163 					dmg += 1;
7164 				}
7165 				if ( dot > 0.9f )
7166 				{
7167 					dmg += 2;
7168 				}
7169 				else if ( dot > 0.7f )
7170 				{
7171 					dmg += 1;
7172 				}
7173 			}
7174 			else
7175 			{
7176 				dmg = Q_irand( 1, 3 );//*self->client->ps.forcePowerLevel[FP_LIGHTNING];
7177 			}
7178 			if ( traceEnt->client && traceEnt->health > 0 && ( traceEnt->client->NPC_class == CLASS_DESANN || traceEnt->client->NPC_class == CLASS_LUKE ) )
7179 			{//Luke and Desann can shield themselves from the attack
7180 				//FIXME: shield effect or something?
7181 				int parts;
7182 				if ( traceEnt->client->ps.groundEntityNum != ENTITYNUM_NONE && !PM_SpinningSaberAnim( traceEnt->client->ps.legsAnim ) && !PM_FlippingAnim( traceEnt->client->ps.legsAnim ) && !PM_RollingAnim( traceEnt->client->ps.legsAnim ) )
7183 				{//if on a surface and not in a spin or flip, play full body resist
7184 					parts = SETANIM_BOTH;
7185 				}
7186 				else
7187 				{//play resist just in torso
7188 					parts = SETANIM_TORSO;
7189 				}
7190 				NPC_SetAnim( traceEnt, parts, BOTH_RESISTPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7191 				Jedi_PlayDeflectSound( traceEnt );
7192 				dmg = 0;
7193 			}
7194 			else if ( traceEnt->s.weapon == WP_SABER )
7195 			{
7196 				if ( Q_irand( 0, 1 ) )
7197 				{//jedi less likely to be damaged
7198 					dmg = 0;
7199 				}
7200 				else
7201 				{
7202 					dmg = 1;
7203 				}
7204 			}
7205 			if ( traceEnt && traceEnt->client && traceEnt->client->NPC_class == CLASS_GALAK )
7206 			{
7207 				if ( traceEnt->client->ps.powerups[PW_GALAK_SHIELD] )
7208 				{//has shield up
7209 					dmg = 0;
7210 				}
7211 			}
7212 			G_Damage( traceEnt, self, self, dir, impactPoint, dmg, 0, MOD_ELECTROCUTE );
7213 			if ( traceEnt->client )
7214 			{
7215 				if ( !Q_irand( 0, 2 ) )
7216 				{
7217 					G_Sound( traceEnt, G_SoundIndex( va( "sound/weapons/force/lightninghit%d.wav", Q_irand( 1, 3 ) ) ) );
7218 				}
7219 				traceEnt->s.powerups |= ( 1 << PW_SHOCKED );
7220 
7221 				// If we are dead or we are a bot, we can do the full version
7222 				class_t npc_class = traceEnt->client->NPC_class;
7223 				if ( traceEnt->health <= 0 || ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE ||
7224 					 npc_class == CLASS_MOUSE || npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_REMOTE ||
7225 					 npc_class == CLASS_R5D2 || npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 ||
7226 					 npc_class == CLASS_MARK2 || npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST ) ||
7227 					 npc_class == CLASS_SENTRY )
7228 				{
7229 					traceEnt->client->ps.powerups[PW_SHOCKED] = level.time + 4000;
7230 				}
7231 				else //short version
7232 				{
7233 					traceEnt->client->ps.powerups[PW_SHOCKED] = level.time + 500;
7234 				}
7235 			}
7236 		}
7237 	}
7238 }
7239 
ForceShootLightning(gentity_t * self)7240 void ForceShootLightning( gentity_t *self )
7241 {
7242 	trace_t	tr;
7243 	vec3_t	end, forward;
7244 	gentity_t	*traceEnt;
7245 
7246 	if ( self->health <= 0 )
7247 	{
7248 		return;
7249 	}
7250 	if ( !self->s.number && cg.zoomMode )
7251 	{//can't force lightning when zoomed in
7252 		return;
7253 	}
7254 
7255 	AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
7256 	VectorNormalize( forward );
7257 
7258 	//FIXME: if lightning hits water, do water-only-flagged radius damage from that point
7259 	if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 )
7260 	{//arc
7261 		vec3_t	center, mins, maxs, dir, ent_org, size, v;
7262 		float	radius = 512, dot, dist;
7263 		gentity_t	*entityList[MAX_GENTITIES];
7264 		int		e, numListedEntities, i;
7265 
7266 		VectorCopy( self->currentOrigin, center );
7267 		for ( i = 0 ; i < 3 ; i++ )
7268 		{
7269 			mins[i] = center[i] - radius;
7270 			maxs[i] = center[i] + radius;
7271 		}
7272 		numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
7273 
7274 		for ( e = 0 ; e < numListedEntities ; e++ )
7275 		{
7276 			traceEnt = entityList[e];
7277 
7278 			if ( !traceEnt )
7279 				continue;
7280 			if ( traceEnt == self )
7281 				continue;
7282 			if ( traceEnt->owner == self && traceEnt->s.weapon != WP_THERMAL )//can push your own thermals
7283 				continue;
7284 			if ( !traceEnt->inuse )
7285 				continue;
7286 			if ( !traceEnt->takedamage )
7287 				continue;
7288 			if ( traceEnt->health <= 0 )//no torturing corpses
7289 				continue;
7290 			//this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
7291 			// find the distance from the edge of the bounding box
7292 			for ( i = 0 ; i < 3 ; i++ )
7293 			{
7294 				if ( center[i] < traceEnt->absmin[i] )
7295 				{
7296 					v[i] = traceEnt->absmin[i] - center[i];
7297 				} else if ( center[i] > traceEnt->absmax[i] )
7298 				{
7299 					v[i] = center[i] - traceEnt->absmax[i];
7300 				} else
7301 				{
7302 					v[i] = 0;
7303 				}
7304 			}
7305 
7306 			VectorSubtract( traceEnt->absmax, traceEnt->absmin, size );
7307 			VectorMA( traceEnt->absmin, 0.5, size, ent_org );
7308 
7309 			//see if they're in front of me
7310 			//must be within the forward cone
7311 			VectorSubtract( ent_org, center, dir );
7312 			VectorNormalize( dir );
7313 			if ( (dot = DotProduct( dir, forward )) < 0.5 )
7314 				continue;
7315 
7316 			//must be close enough
7317 			dist = VectorLength( v );
7318 			if ( dist >= radius )
7319 			{
7320 				continue;
7321 			}
7322 
7323 			//in PVS?
7324 			if ( !traceEnt->bmodel && !gi.inPVS( ent_org, self->client->renderInfo.handLPoint ) )
7325 			{//must be in PVS
7326 				continue;
7327 			}
7328 
7329 			//Now check and see if we can actually hit it
7330 			gi.trace( &tr, self->client->renderInfo.handLPoint, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
7331 			if ( tr.fraction < 1.0f && tr.entityNum != traceEnt->s.number )
7332 			{//must have clear LOS
7333 				continue;
7334 			}
7335 
7336 			// ok, we are within the radius, add us to the incoming list
7337 			//FIXME: maybe add up the ents and do more damage the less ents there are
7338 			//		as if we're spreading out the damage?
7339 			ForceLightningDamage( self, traceEnt, dir, dist, dot, ent_org );
7340 		}
7341 
7342 	}
7343 	else
7344 	{//trace-line
7345 		int ignore = self->s.number;
7346 		int traces = 0;
7347 		vec3_t	start;
7348 
7349 		VectorCopy( self->client->renderInfo.handLPoint, start );
7350 		VectorMA( self->client->renderInfo.handLPoint, 2048, forward, end );
7351 
7352 		while ( traces < 10 )
7353 		{//need to loop this in case we hit a Jedi who dodges the shot
7354 			gi.trace( &tr, start, vec3_origin, vec3_origin, end, ignore, MASK_SHOT, G2_RETURNONHIT, 0 );
7355 			if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
7356 			{
7357 				return;
7358 			}
7359 
7360 			traceEnt = &g_entities[tr.entityNum];
7361 			//NOTE: only NPCs do this auto-dodge
7362 			if ( traceEnt && traceEnt->s.number && traceEnt->s.weapon == WP_SABER )//&& traceEnt->NPC
7363 			{//FIXME: need a more reliable way to know we hit a jedi?
7364 				if ( !Jedi_DodgeEvasion( traceEnt, self, &tr, HL_NONE ) )
7365 				{//act like we didn't even hit him
7366 					VectorCopy( tr.endpos, start );
7367 					ignore = tr.entityNum;
7368 					traces++;
7369 					continue;
7370 				}
7371 			}
7372 			//a Jedi is not dodging this shot
7373 			break;
7374 		}
7375 
7376 		traceEnt = &g_entities[tr.entityNum];
7377 		ForceLightningDamage( self, traceEnt, forward, 0, 0, tr.endpos );
7378 	}
7379 }
7380 
ForceJumpCharge(gentity_t * self,usercmd_t * ucmd)7381 void ForceJumpCharge( gentity_t *self, usercmd_t *ucmd )
7382 {
7383 	float forceJumpChargeInterval = forceJumpStrength[0] / (FORCE_JUMP_CHARGE_TIME/FRAMETIME);
7384 
7385 	if ( self->health <= 0 )
7386 	{
7387 		return;
7388 	}
7389 	if ( !self->s.number && cg.zoomMode )
7390 	{//can't force jump when zoomed in
7391 		return;
7392 	}
7393 
7394 	//need to play sound
7395 	if ( !self->client->ps.forceJumpCharge )
7396 	{//FIXME: this should last only as long as the actual charge-up
7397 		G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jumpbuild.wav" );
7398 	}
7399 	//Increment
7400 	self->client->ps.forceJumpCharge += forceJumpChargeInterval;
7401 
7402 	//clamp to max strength for current level
7403 	if ( self->client->ps.forceJumpCharge > forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]] )
7404 	{
7405 		self->client->ps.forceJumpCharge = forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]];
7406 	}
7407 
7408 	//clamp to max available force power
7409 	if ( self->client->ps.forceJumpCharge/forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME)*forcePowerNeeded[FP_LEVITATION] > self->client->ps.forcePower )
7410 	{//can't use more than you have
7411 		self->client->ps.forceJumpCharge = self->client->ps.forcePower*forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME);
7412 	}
7413 	//FIXME: a simple tap should always do at least a normal height's jump?
7414 }
7415 
WP_GetVelocityForForceJump(gentity_t * self,vec3_t jumpVel,usercmd_t * ucmd)7416 int WP_GetVelocityForForceJump( gentity_t *self, vec3_t jumpVel, usercmd_t *ucmd )
7417 {
7418 	float pushFwd = 0, pushRt = 0;
7419 	vec3_t	view, forward, right;
7420 	VectorCopy( self->client->ps.viewangles, view );
7421 	view[0] = 0;
7422 	AngleVectors( view, forward, right, NULL );
7423 	if ( ucmd->forwardmove && ucmd->rightmove )
7424 	{
7425 		if ( ucmd->forwardmove > 0 )
7426 		{
7427 			pushFwd = 50;
7428 		}
7429 		else
7430 		{
7431 			pushFwd = -50;
7432 		}
7433 		if ( ucmd->rightmove > 0 )
7434 		{
7435 			pushRt = 50;
7436 		}
7437 		else
7438 		{
7439 			pushRt = -50;
7440 		}
7441 	}
7442 	else if ( ucmd->forwardmove || ucmd->rightmove )
7443 	{
7444 		if ( ucmd->forwardmove > 0 )
7445 		{
7446 			pushFwd = 100;
7447 		}
7448 		else if ( ucmd->forwardmove < 0 )
7449 		{
7450 			pushFwd = -100;
7451 		}
7452 		else if ( ucmd->rightmove > 0 )
7453 		{
7454 			pushRt = 100;
7455 		}
7456 		else if ( ucmd->rightmove < 0 )
7457 		{
7458 			pushRt = -100;
7459 		}
7460 	}
7461 	VectorMA( self->client->ps.velocity, pushFwd, forward, jumpVel );
7462 	VectorMA( self->client->ps.velocity, pushRt, right, jumpVel );
7463 	jumpVel[2] += self->client->ps.forceJumpCharge;//forceJumpStrength;
7464 	if ( pushFwd > 0 && self->client->ps.forceJumpCharge > 200 )
7465 	{
7466 		return FJ_FORWARD;
7467 	}
7468 	else if ( pushFwd < 0 && self->client->ps.forceJumpCharge > 200 )
7469 	{
7470 		return FJ_BACKWARD;
7471 	}
7472 	else if ( pushRt > 0 && self->client->ps.forceJumpCharge > 200 )
7473 	{
7474 		return FJ_RIGHT;
7475 	}
7476 	else if ( pushRt < 0 && self->client->ps.forceJumpCharge > 200 )
7477 	{
7478 		return FJ_LEFT;
7479 	}
7480 	else
7481 	{//FIXME: jump straight up anim
7482 		return FJ_UP;
7483 	}
7484 }
7485 
ForceJump(gentity_t * self,usercmd_t * ucmd)7486 void ForceJump( gentity_t *self, usercmd_t *ucmd )
7487 {
7488 	if ( self->client->ps.forcePowerDuration[FP_LEVITATION] > level.time )
7489 	{
7490 		return;
7491 	}
7492 	if ( !WP_ForcePowerUsable( self, FP_LEVITATION, 0 ) )
7493 	{
7494 		return;
7495 	}
7496 	if ( self->s.groundEntityNum == ENTITYNUM_NONE )
7497 	{
7498 		return;
7499 	}
7500 	if ( self->client->ps.pm_flags&PMF_JUMP_HELD )
7501 	{
7502 		return;
7503 	}
7504 	if ( self->health <= 0 )
7505 	{
7506 		return;
7507 	}
7508 	if ( !self->s.number && (cg.zoomMode || in_camera) )
7509 	{//can't force jump when zoomed in or in cinematic
7510 		return;
7511 	}
7512 	if ( self->client->ps.saberLockTime > level.time )
7513 	{//FIXME: can this be a way to break out?
7514 		return;
7515 	}
7516 
7517 	G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" );
7518 
7519 	float forceJumpChargeInterval = forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]]/(FORCE_JUMP_CHARGE_TIME/FRAMETIME);
7520 
7521 	int anim;
7522 	vec3_t	jumpVel;
7523 
7524 	switch( WP_GetVelocityForForceJump( self, jumpVel, ucmd ) )
7525 	{
7526 	case FJ_FORWARD:
7527 		if ( self->NPC && self->NPC->rank != RANK_CREWMAN && self->NPC->rank <= RANK_LT_JG )
7528 		{//can't do acrobatics
7529 			anim = BOTH_FORCEJUMP1;
7530 		}
7531 		else
7532 		{
7533 			anim = BOTH_FLIP_F;
7534 		}
7535 		break;
7536 	case FJ_BACKWARD:
7537 		if ( self->NPC && self->NPC->rank != RANK_CREWMAN && self->NPC->rank <= RANK_LT_JG )
7538 		{//can't do acrobatics
7539 			anim = BOTH_FORCEJUMPBACK1;
7540 		}
7541 		else
7542 		{
7543 			anim = BOTH_FLIP_B;
7544 		}
7545 		break;
7546 	case FJ_RIGHT:
7547 		if ( self->NPC && self->NPC->rank != RANK_CREWMAN && self->NPC->rank <= RANK_LT_JG )
7548 		{//can't do acrobatics
7549 			anim = BOTH_FORCEJUMPRIGHT1;
7550 		}
7551 		else
7552 		{
7553 			anim = BOTH_FLIP_R;
7554 		}
7555 		break;
7556 	case FJ_LEFT:
7557 		if ( self->NPC && self->NPC->rank != RANK_CREWMAN && self->NPC->rank <= RANK_LT_JG )
7558 		{//can't do acrobatics
7559 			anim = BOTH_FORCEJUMPLEFT1;
7560 		}
7561 		else
7562 		{
7563 			anim = BOTH_FLIP_L;
7564 		}
7565 		break;
7566 	default:
7567 	case FJ_UP:
7568 		anim = BOTH_JUMP1;
7569 		break;
7570 	}
7571 
7572 	int	parts = SETANIM_BOTH;
7573 	if ( self->client->ps.weaponTime )
7574 	{//FIXME: really only care if we're in a saber attack anim.. maybe trail length?
7575 		parts = SETANIM_LEGS;
7576 	}
7577 
7578 	NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7579 
7580 	//FIXME: sound effect
7581 	self->client->ps.forceJumpZStart = self->currentOrigin[2];//remember this for when we land
7582 	VectorCopy( jumpVel, self->client->ps.velocity );
7583 	//wasn't allowing them to attack when jumping, but that was annoying
7584 	//self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
7585 
7586 	WP_ForcePowerStart( self, FP_LEVITATION, self->client->ps.forceJumpCharge/forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME)*forcePowerNeeded[FP_LEVITATION] );
7587 	//self->client->ps.forcePowerDuration[FP_LEVITATION] = level.time + self->client->ps.weaponTime;
7588 	self->client->ps.forceJumpCharge = 0;
7589 }
7590 
WP_ForcePowerRegenerate(gentity_t * self,int overrideAmt)7591 void WP_ForcePowerRegenerate( gentity_t *self, int overrideAmt )
7592 {
7593 	if ( !self->client )
7594 	{
7595 		return;
7596 	}
7597 
7598 	if ( self->client->ps.forcePower < self->client->ps.forcePowerMax )
7599 	{
7600 		if ( overrideAmt )
7601 		{
7602 			self->client->ps.forcePower += overrideAmt;
7603 		}
7604 		else
7605 		{
7606 			self->client->ps.forcePower++;
7607 		}
7608 		if ( self->client->ps.forcePower > self->client->ps.forcePowerMax )
7609 		{
7610 			self->client->ps.forcePower = self->client->ps.forcePowerMax;
7611 		}
7612 	}
7613 }
7614 
WP_ForcePowerDrain(gentity_t * self,forcePowers_t forcePower,int overrideAmt)7615 void WP_ForcePowerDrain( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
7616 {
7617 	if ( self->NPC )
7618 	{//For now, NPCs have infinite force power
7619 		return;
7620 	}
7621 	//take away the power
7622 	int	drain = overrideAmt;
7623 	if ( !drain )
7624 	{
7625 		drain = forcePowerNeeded[forcePower];
7626 	}
7627 	if ( !drain )
7628 	{
7629 		return;
7630 	}
7631 	self->client->ps.forcePower -= drain;
7632 	if ( self->client->ps.forcePower < 0 )
7633 	{
7634 		self->client->ps.forcePower = 0;
7635 	}
7636 }
7637 
WP_ForcePowerStart(gentity_t * self,forcePowers_t forcePower,int overrideAmt)7638 void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
7639 {
7640 	int	duration = 0;
7641 
7642 	//FIXME: debounce some of these
7643 
7644 	//and it in
7645 	//set up duration time
7646 	switch( (int)forcePower )
7647 	{
7648 	case FP_HEAL:
7649 		self->client->ps.forcePowersActive |= ( 1 << forcePower );
7650 		self->client->ps.forceHealCount = 0;
7651 		break;
7652 	case FP_LEVITATION:
7653 		self->client->ps.forcePowersActive |= ( 1 << forcePower );
7654 		break;
7655 	case FP_SPEED:
7656 		//duration is always 5 seconds, player time
7657 		duration = ceil(FORCE_SPEED_DURATION*forceSpeedValue[self->client->ps.forcePowerLevel[FP_SPEED]]);//FIXME: because the timescale scales down (not instant), this doesn't end up being exactly right...
7658 		self->client->ps.forcePowersActive |= ( 1 << forcePower );
7659 		self->s.loopSound = G_SoundIndex( "sound/weapons/force/speedloop.wav" );
7660 		break;
7661 	case FP_PUSH:
7662 		break;
7663 	case FP_PULL:
7664 		break;
7665 	case FP_TELEPATHY:
7666 		break;
7667 	case FP_GRIP:
7668 		duration = 1000;
7669 		self->client->ps.forcePowersActive |= ( 1 << forcePower );
7670 		break;
7671 	case FP_LIGHTNING:
7672 		duration = overrideAmt;
7673 		overrideAmt = 0;
7674 		self->client->ps.forcePowersActive |= ( 1 << forcePower );
7675 		break;
7676 	default:
7677 		break;
7678 	}
7679 	if ( duration )
7680 	{
7681 		self->client->ps.forcePowerDuration[forcePower] = level.time + duration;
7682 	}
7683 	else
7684 	{
7685 		self->client->ps.forcePowerDuration[forcePower] = 0;
7686 	}
7687 	self->client->ps.forcePowerDebounce[forcePower] = 0;
7688 
7689 	WP_ForcePowerDrain( self, forcePower, overrideAmt );
7690 
7691 	if ( !self->s.number )
7692 	{
7693 		self->client->sess.missionStats.forceUsed[(int)forcePower]++;
7694 	}
7695 }
7696 
WP_ForcePowerAvailable(gentity_t * self,forcePowers_t forcePower,int overrideAmt)7697 qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
7698 {
7699 	if ( forcePower == FP_LEVITATION )
7700 	{
7701 		return qtrue;
7702 	}
7703 	int	drain = overrideAmt?overrideAmt:forcePowerNeeded[forcePower];
7704 	if ( !drain )
7705 	{
7706 		return qtrue;
7707 	}
7708 	if ( self->client->ps.forcePower < drain )
7709 	{
7710 		//G_AddEvent( self, EV_NOAMMO, 0 );
7711 		return qfalse;
7712 	}
7713 	return qtrue;
7714 }
7715 
7716 extern void CG_PlayerLockedWeaponSpeech( int jumping );
WP_ForcePowerUsable(gentity_t * self,forcePowers_t forcePower,int overrideAmt)7717 qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
7718 {
7719 
7720 	if ( !(self->client->ps.forcePowersKnown & ( 1 << forcePower )) )
7721 	{//don't know this power
7722 		return qfalse;
7723 	}
7724 
7725 	if ( self->client->ps.forcePowerLevel[forcePower] <= 0 )
7726 	{//can't use this power
7727 		return qfalse;
7728 	}
7729 
7730 	if( self->flags & FL_LOCK_PLAYER_WEAPONS ) // yes this locked weapons check also includes force powers, if we need a separate check later I'll make one
7731 	{
7732 		CG_PlayerLockedWeaponSpeech( qfalse );
7733 		return qfalse;
7734 	}
7735 
7736 	if ( (self->client->ps.forcePowersActive & ( 1 << forcePower )) )
7737 	{//already using this power
7738 		return qfalse;
7739 	}
7740 	/*
7741 	if ( !self->client->ps.forcePowerLevel[(int)(forcePower)] )
7742 	{
7743 		return qfalse;
7744 	}
7745 	*/
7746 	if ( self->client->NPC_class == CLASS_ATST )
7747 	{//Doh!  No force powers in an AT-ST!
7748 		return qfalse;
7749 	}
7750 	if ( self->client->ps.vehicleModel != 0 )
7751 	{//Doh!  No force powers when flying a vehicle!
7752 		return qfalse;
7753 	}
7754 	if ( self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_WORLD )
7755 	{//Doh!  No force powers when controlling an NPC
7756 		return qfalse;
7757 	}
7758 	if ( self->client->ps.eFlags & EF_LOCKED_TO_WEAPON )
7759 	{//Doh!  No force powers when in an emplaced gun!
7760 		return qfalse;
7761 	}
7762 
7763 	return WP_ForcePowerAvailable( self, forcePower, overrideAmt );
7764 }
7765 
WP_ForcePowerStop(gentity_t * self,forcePowers_t forcePower)7766 void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower )
7767 {
7768 	gentity_t	*gripEnt;
7769 
7770 	self->client->ps.forcePowersActive &= ~( 1 << forcePower );
7771 
7772 	switch( (int)forcePower )
7773 	{
7774 	case FP_HEAL:
7775 		//if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_3 )
7776 		{//wasn't an instant heal and heal is now done
7777 			if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 )
7778 			{//if in meditation pose, must come out of it
7779 				//FIXME: BOTH_FORCEHEAL_STOP
7780 				if ( self->client->ps.legsAnim == BOTH_FORCEHEAL_START )
7781 				{
7782 					NPC_SetAnim( self, SETANIM_LEGS, BOTH_FORCEHEAL_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7783 				}
7784 				if ( self->client->ps.torsoAnim == BOTH_FORCEHEAL_START )
7785 				{
7786 					NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEHEAL_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7787 				}
7788 				self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
7789 				self->client->ps.saberBlocked = BLOCKED_NONE;
7790 			}
7791 		}
7792 		break;
7793 	case FP_LEVITATION:
7794 		self->client->ps.forcePowerDebounce[FP_LEVITATION] = 0;
7795 		break;
7796 	case FP_SPEED:
7797 		if ( !self->s.number )
7798 		{//player using force speed
7799 			if ( g_timescale->value != 1.0 )
7800 			{
7801 				gi.cvar_set("timescale", "1");
7802 			}
7803 		}
7804 		//FIXME: reset my current anim, keeping current frame, but with proper anim speed
7805 		//		otherwise, the anim will continue playing at high speed
7806 		self->s.loopSound = 0;
7807 		break;
7808 	case FP_PUSH:
7809 		break;
7810 	case FP_PULL:
7811 		break;
7812 	case FP_TELEPATHY:
7813 		break;
7814 	case FP_GRIP:
7815 		if ( self->client->ps.forceGripEntityNum < ENTITYNUM_WORLD )
7816 		{
7817 			gripEnt = &g_entities[self->client->ps.forceGripEntityNum];
7818 			if ( gripEnt )
7819 			{
7820 				gripEnt->s.loopSound = 0;
7821 				if ( gripEnt->client )
7822 				{
7823 					gripEnt->client->ps.eFlags &= ~EF_FORCE_GRIPPED;
7824 					if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
7825 					{//sanity-cap the velocity
7826 						float gripVel = VectorNormalize( gripEnt->client->ps.velocity );
7827 						if ( gripVel > 500.0f )
7828 						{
7829 							gripVel = 500.0f;
7830 						}
7831 						VectorScale( gripEnt->client->ps.velocity, gripVel, gripEnt->client->ps.velocity );
7832 					}
7833 
7834 					//FIXME: they probably dropped their weapon, should we make them flee?  Or should AI handle no-weapon behavior?
7835 					if ( gripEnt->health > 0 )
7836 					{
7837 						int	holdTime = 500;
7838 						G_AddEvent( gripEnt, EV_WATER_CLEAR, 0 );
7839 						if ( gripEnt->client->ps.forcePowerDebounce[FP_PUSH] > level.time )
7840 						{//they probably pushed out of it
7841 							holdTime = 0;
7842 						}
7843 						else if ( gripEnt->s.weapon == WP_SABER )
7844 						{//jedi recover faster
7845 							holdTime = self->client->ps.forcePowerLevel[FP_GRIP]*200;
7846 						}
7847 						else
7848 						{
7849 							holdTime = self->client->ps.forcePowerLevel[FP_GRIP]*500;
7850 						}
7851 						//stop the anims soon, keep them locked in place for a bit
7852 						if ( gripEnt->client->ps.torsoAnim == BOTH_CHOKE1 || gripEnt->client->ps.torsoAnim == BOTH_CHOKE3 )
7853 						{//stop choking anim on torso
7854 							if ( gripEnt->client->ps.torsoAnimTimer > holdTime )
7855 							{
7856 								gripEnt->client->ps.torsoAnimTimer = holdTime;
7857 							}
7858 						}
7859 						if ( gripEnt->client->ps.legsAnim == BOTH_CHOKE1 || gripEnt->client->ps.legsAnim == BOTH_CHOKE3 )
7860 						{//stop choking anim on legs
7861 							gripEnt->client->ps.legsAnimTimer = 0;
7862 							if ( holdTime )
7863 							{
7864 								//lock them in place for a bit
7865 								gripEnt->client->ps.pm_time = gripEnt->client->ps.torsoAnimTimer;
7866 								gripEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
7867 								if ( gripEnt->s.number )
7868 								{//NPC
7869 									gripEnt->painDebounceTime = level.time + gripEnt->client->ps.torsoAnimTimer;
7870 								}
7871 								else
7872 								{//player
7873 									gripEnt->aimDebounceTime = level.time + gripEnt->client->ps.torsoAnimTimer;
7874 								}
7875 							}
7876 						}
7877 						if ( gripEnt->NPC )
7878 						{
7879 							if ( !(gripEnt->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
7880 							{//not falling to their death
7881 								gripEnt->NPC->nextBStateThink = level.time + holdTime;
7882 							}
7883 							//if still alive after stopped gripping, let them wake others up
7884 							G_AngerAlert( gripEnt );
7885 						}
7886 					}
7887 				}
7888 				else
7889 				{
7890 					gripEnt->s.eFlags &= ~EF_FORCE_GRIPPED;
7891 					if ( gripEnt->s.eType == ET_MISSILE )
7892 					{//continue normal movement
7893 						if ( gripEnt->s.weapon == WP_THERMAL )
7894 						{
7895 							gripEnt->s.pos.trType = TR_INTERPOLATE;
7896 						}
7897 						else
7898 						{
7899 							gripEnt->s.pos.trType = TR_LINEAR;//FIXME: what about gravity-effected projectiles?
7900 						}
7901 						VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase );
7902 						gripEnt->s.pos.trTime = level.time;
7903 					}
7904 					else
7905 					{//drop it
7906 						gripEnt->e_ThinkFunc = thinkF_G_RunObject;
7907 						gripEnt->nextthink = level.time + FRAMETIME;
7908 						gripEnt->s.pos.trType = TR_GRAVITY;
7909 						VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase );
7910 						gripEnt->s.pos.trTime = level.time;
7911 					}
7912 				}
7913 			}
7914 			self->s.loopSound = 0;
7915 			self->client->ps.forceGripEntityNum = ENTITYNUM_NONE;
7916 		}
7917 		if ( self->client->ps.torsoAnim == BOTH_FORCEGRIP_HOLD )
7918 		{
7919 			NPC_SetAnim( self, BOTH_FORCEGRIP_RELEASE, BOTH_FORCELIGHTNING_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7920 		}
7921 		break;
7922 	case FP_LIGHTNING:
7923 		if ( self->client->ps.torsoAnim == BOTH_FORCELIGHTNING_HOLD
7924 			|| self->client->ps.torsoAnim == BOTH_FORCELIGHTNING_START )
7925 		{
7926 			NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7927 		}
7928 		if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
7929 		{//don't do it again for 3 seconds, minimum... FIXME: this should be automatic once regeneration is slower (normal)
7930 			self->client->ps.forcePowerDebounce[FP_LIGHTNING] = level.time + 3000;//FIXME: define?
7931 		}
7932 		else
7933 		{//stop the looping sound
7934 			self->client->ps.forcePowerDebounce[FP_LIGHTNING] = level.time + 1000;//FIXME: define?
7935 			self->s.loopSound = 0;
7936 		}
7937 		break;
7938 	default:
7939 		break;
7940 	}
7941 }
7942 
7943 extern qboolean PM_ForceJumpingUp( gentity_t *gent );
WP_ForcePowerRun(gentity_t * self,forcePowers_t forcePower,usercmd_t * cmd)7944 static void WP_ForcePowerRun( gentity_t *self, forcePowers_t forcePower, usercmd_t *cmd )
7945 {
7946 	float				speed, newSpeed;
7947 	gentity_t			*gripEnt;
7948 	vec3_t				angles, dir, gripOrg, gripEntOrg;
7949 	float				dist;
7950 	extern usercmd_t	ucmd;
7951 
7952 	switch( (int)forcePower )
7953 	{
7954 	case FP_HEAL:
7955 		if ( self->client->ps.forceHealCount >= MAX_FORCE_HEAL || self->health >= self->client->ps.stats[STAT_MAX_HEALTH] )
7956 		{//fully healed or used up all 25
7957 			if ( !Q3_TaskIDPending( self, TID_CHAN_VOICE ) )
7958 			{
7959 				G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d.mp3", Q_irand( 1, 4 ) ) );
7960 			}
7961 			WP_ForcePowerStop( self, forcePower );
7962 		}
7963 		else if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_3 && ( (cmd->buttons&BUTTON_ATTACK) || (cmd->buttons&BUTTON_ALT_ATTACK) || self->painDebounceTime > level.time || (self->client->ps.weaponTime&&self->client->ps.weapon!=WP_NONE) ) )
7964 		{//attacked or was hit while healing...
7965 			//stop healing
7966 			WP_ForcePowerStop( self, forcePower );
7967 		}
7968 		else if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 && ( cmd->rightmove || cmd->forwardmove || cmd->upmove > 0 ) )
7969 		{//moved while healing... FIXME: also, in WP_ForcePowerStart, stop healing if any other force power is used
7970 			//stop healing
7971 			WP_ForcePowerStop( self, forcePower );
7972 		}
7973 		else if ( self->client->ps.forcePowerDebounce[FP_HEAL] < level.time )
7974 		{//time to heal again
7975 			if ( WP_ForcePowerAvailable( self, forcePower, 4 ) )
7976 			{//have available power
7977 				self->health++;
7978 				self->client->ps.forceHealCount++;
7979 				if ( self->client->ps.forcePowerLevel[FP_HEAL] > FORCE_LEVEL_2 )
7980 				{
7981 					self->client->ps.forcePowerDebounce[FP_HEAL] = level.time + 50;
7982 				}
7983 				else
7984 				{
7985 					self->client->ps.forcePowerDebounce[FP_HEAL] = level.time + FORCE_HEAL_INTERVAL;
7986 				}
7987 				WP_ForcePowerDrain( self, forcePower, 4 );
7988 			}
7989 			else
7990 			{//stop
7991 				WP_ForcePowerStop( self, forcePower );
7992 			}
7993 		}
7994 		break;
7995 	case FP_LEVITATION:
7996 		if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE && !self->client->ps.forceJumpZStart )
7997 		{//done with jump
7998 			WP_ForcePowerStop( self, forcePower );
7999 		}
8000 		else
8001 		{
8002 			if ( PM_ForceJumpingUp( self ) )
8003 			{//holding jump in air
8004 				if ( cmd->upmove > 10 )
8005 				{//still trying to go up
8006 					if ( WP_ForcePowerAvailable( self, FP_LEVITATION, 1 ) )
8007 					{
8008 						if ( self->client->ps.forcePowerDebounce[FP_LEVITATION] < level.time )
8009 						{
8010 							WP_ForcePowerDrain( self, FP_LEVITATION, 5 );
8011 							self->client->ps.forcePowerDebounce[FP_LEVITATION] = level.time + 100;
8012 						}
8013 						self->client->ps.forcePowersActive |= ( 1 << FP_LEVITATION );
8014 						self->client->ps.forceJumpCharge = 1;//just used as a flag for the player, cleared when he lands
8015 					}
8016 					else
8017 					{//cut the jump short
8018 						WP_ForcePowerStop( self, forcePower );
8019 					}
8020 				}
8021 				else
8022 				{//cut the jump short
8023 					WP_ForcePowerStop( self, forcePower );
8024 				}
8025 			}
8026 			else
8027 			{
8028 				WP_ForcePowerStop( self, forcePower );
8029 			}
8030 		}
8031 		break;
8032 	case FP_SPEED:
8033 		speed = forceSpeedValue[self->client->ps.forcePowerLevel[FP_SPEED]];
8034 		if ( !self->s.number )
8035 		{//player using force speed
8036 			gi.cvar_set("timescale", va("%4.2f", speed));
8037 			if ( g_timescale->value > speed )
8038 			{
8039 				newSpeed = g_timescale->value - 0.05;
8040 				if ( newSpeed < speed )
8041 				{
8042 					newSpeed = speed;
8043 				}
8044 				gi.cvar_set("timescale", va("%4.2f", newSpeed));
8045 			}
8046 		}
8047 		break;
8048 	case FP_PUSH:
8049 		break;
8050 	case FP_PULL:
8051 		break;
8052 	case FP_TELEPATHY:
8053 		break;
8054 	case FP_GRIP:
8055 		if ( !WP_ForcePowerAvailable( self, FP_GRIP, 0 )
8056 			|| (self->client->ps.forcePowerLevel[FP_GRIP]>FORCE_LEVEL_1&&!self->s.number&&!(cmd->buttons&BUTTON_FORCEGRIP)) )
8057 		{
8058 			WP_ForcePowerStop( self, FP_GRIP );
8059 			return;
8060 		}
8061 		else if ( self->client->ps.forceGripEntityNum >= 0 && self->client->ps.forceGripEntityNum < ENTITYNUM_WORLD )
8062 		{
8063 			gripEnt = &g_entities[self->client->ps.forceGripEntityNum];
8064 
8065 			if ( !gripEnt || (gripEnt->health <= 0&&gripEnt->takedamage) )//FIXME: what about things that never had health or lose takedamage when they die?
8066 			{//either invalid ent, or dead ent
8067 				WP_ForcePowerStop( self, FP_GRIP );
8068 				return;
8069 			}
8070 			else if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_1
8071 				&& gripEnt->client
8072 				&& gripEnt->client->ps.groundEntityNum == ENTITYNUM_NONE )
8073 			{
8074 				WP_ForcePowerStop( self, FP_GRIP );
8075 				return;
8076 			}
8077 			else if ( gripEnt->s.weapon == WP_SABER && gripEnt->NPC && gripEnt->client && gripEnt->client->ps.forcePowersKnown&(1<<FP_PUSH) && !Q_irand( 0, 100-(gripEnt->NPC->stats.evasion*10)-(g_spskill->integer*10) ) )
8078 			{//a jedi who broke free FIXME: maybe have some minimum grip length- a reaction time?
8079 				ForceThrow( gripEnt, qfalse );
8080 				//FIXME: I need to go into some pushed back anim...
8081 				WP_ForcePowerStop( self, FP_GRIP );
8082 				return;
8083 			}
8084 			else
8085 			{
8086 				if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
8087 				{//holding it
8088 					NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEGRIP_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
8089 				}
8090 				//get their org
8091 				VectorCopy( self->client->ps.viewangles, angles );
8092 				angles[0] -= 10;
8093 				AngleVectors( angles, dir, NULL, NULL );
8094 				if ( gripEnt->client )
8095 				{//move
8096 					VectorCopy( gripEnt->client->renderInfo.headPoint, gripEntOrg );
8097 				}
8098 				else
8099 				{
8100 					VectorCopy( gripEnt->currentOrigin, gripEntOrg );
8101 				}
8102 
8103 				//how far are they
8104 				dist = Distance( self->client->renderInfo.handLPoint, gripEntOrg );
8105 				if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_2 &&
8106 					(!InFront( gripEntOrg, self->client->renderInfo.handLPoint, self->client->ps.viewangles, 0.3f ) ||
8107 						DistanceSquared( gripEntOrg, self->client->renderInfo.handLPoint ) > FORCE_GRIP_DIST_SQUARED))
8108 				{//must face them
8109 					WP_ForcePowerStop( self, FP_GRIP );
8110 					return;
8111 				}
8112 
8113 				//check for lift or carry
8114 				if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 )
8115 				{//carry
8116 					//cap dist
8117 					if ( dist > 256 )
8118 					{
8119 						dist = 256;
8120 					}
8121 					else if ( dist < 128 )
8122 					{
8123 						dist = 128;
8124 					}
8125 					VectorMA( self->client->renderInfo.handLPoint, dist, dir, gripOrg );
8126 				}
8127 				else if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
8128 				{//just lift
8129 					VectorCopy( self->client->ps.forceGripOrg, gripOrg );
8130 				}
8131 				else
8132 				{
8133 					VectorCopy( gripEnt->currentOrigin, gripOrg );
8134 				}
8135 				//now move them
8136 				if ( gripEnt->client )
8137 				{
8138 					if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
8139 					{//level 1 just holds them
8140 						VectorSubtract( gripOrg, gripEntOrg, gripEnt->client->ps.velocity );
8141 						if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 )
8142 						{//level 2 just lifts them
8143 							float gripDist = VectorNormalize( gripEnt->client->ps.velocity )/3.0f;
8144 							if ( gripDist < 5.0f )
8145 							{
8146 								gripDist = 5.0f;
8147 							}
8148 							VectorScale( gripEnt->client->ps.velocity, (gripDist*gripDist), gripEnt->client->ps.velocity );
8149 						}
8150 					}
8151 					//stop them from thinking
8152 					gripEnt->client->ps.pm_time = 2000;
8153 					gripEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
8154 					if ( gripEnt->NPC )
8155 					{
8156 						if ( !(gripEnt->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
8157 						{//not falling to their death
8158 							gripEnt->NPC->nextBStateThink = level.time + 2000;
8159 						}
8160 						if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
8161 						{//level 1 just holds them
8162 							vectoangles( dir, angles );
8163 							gripEnt->NPC->desiredYaw = AngleNormalize180(angles[YAW]+180);
8164 							gripEnt->NPC->desiredPitch = -angles[PITCH];
8165 							SaveNPCGlobals();
8166 							SetNPCGlobals( gripEnt );
8167 							NPC_UpdateAngles( qtrue, qtrue );
8168 							gripEnt->NPC->last_ucmd.angles[0] = ucmd.angles[0];
8169 							gripEnt->NPC->last_ucmd.angles[1] = ucmd.angles[1];
8170 							gripEnt->NPC->last_ucmd.angles[2] = ucmd.angles[2];
8171 							RestoreNPCGlobals();
8172 							//FIXME: why does he turn back to his original angles once he dies or is let go?
8173 						}
8174 					}
8175 					else if ( !gripEnt->s.number )
8176 					{
8177 						//vectoangles( dir, angles );
8178 						//gripEnt->client->ps.viewangles[0] = -angles[0];
8179 						//gripEnt->client->ps.viewangles[1] = AngleNormalize180(angles[YAW]+180);
8180 						gripEnt->enemy = self;
8181 						NPC_SetLookTarget( gripEnt, self->s.number, level.time+1000 );
8182 					}
8183 
8184 					gripEnt->client->ps.eFlags |= EF_FORCE_GRIPPED;
8185 					//dammit!  Make sure that saber stays off!
8186 					gripEnt->client->ps.saberActive = qfalse;
8187 				}
8188 				else
8189 				{//move
8190 					if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
8191 					{//level 1 just holds them
8192 						VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase );
8193 						VectorSubtract( gripOrg, gripEntOrg, gripEnt->s.pos.trDelta );
8194 						if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 )
8195 						{//level 2 just lifts them
8196 							VectorScale( gripEnt->s.pos.trDelta, 10, gripEnt->s.pos.trDelta );
8197 						}
8198 						gripEnt->s.pos.trType = TR_LINEAR;
8199 						gripEnt->s.pos.trTime = level.time;
8200 					}
8201 
8202 					gripEnt->s.eFlags |= EF_FORCE_GRIPPED;
8203 				}
8204 
8205 				//Shouldn't this be discovered?
8206 				//AddSightEvent( self, gripOrg, 128, AEL_DANGER, 20 );
8207 				AddSightEvent( self, gripOrg, 128, AEL_DISCOVERED, 20 );
8208 
8209 				if ( self->client->ps.forcePowerDebounce[FP_GRIP] < level.time )
8210 				{
8211 					//GEntity_PainFunc( gripEnt, self, self, gripOrg, 0, MOD_CRUSH );
8212 					gripEnt->painDebounceTime = 0;
8213 					G_Damage( gripEnt, self, self, dir, gripOrg, forceGripDamage[self->client->ps.forcePowerLevel[FP_GRIP]], DAMAGE_NO_ARMOR, MOD_CRUSH );//MOD_???
8214 					if ( gripEnt->s.number )
8215 					{
8216 						if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 )
8217 						{//do damage faster at level 3
8218 							self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 150, 750 );
8219 						}
8220 						else
8221 						{
8222 							self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 250, 1000 );
8223 						}
8224 					}
8225 					else
8226 					{//player takes damage faster
8227 						self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 100, 600 );
8228 					}
8229 					if ( forceGripDamage[self->client->ps.forcePowerLevel[FP_GRIP]] > 0 )
8230 					{//no damage at level 1
8231 						WP_ForcePowerDrain( self, FP_GRIP, 3 );
8232 					}
8233 				}
8234 				else
8235 				{
8236 					//WP_ForcePowerDrain( self, FP_GRIP, 0 );
8237 					if ( !gripEnt->enemy )
8238 					{
8239 						G_SetEnemy( gripEnt, self );
8240 					}
8241 				}
8242 				if ( gripEnt->client && gripEnt->health > 0 )
8243 				{
8244 					int anim = BOTH_CHOKE3; //left-handed choke
8245 					if ( gripEnt->client->ps.weapon == WP_NONE || gripEnt->client->ps.weapon == WP_MELEE )
8246 					{
8247 						anim = BOTH_CHOKE1; //two-handed choke
8248 					}
8249 					if ( self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
8250 					{//still on ground, only set anim on torso
8251 						NPC_SetAnim( gripEnt, SETANIM_TORSO, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
8252 					}
8253 					else
8254 					{//in air, set on whole body
8255 						NPC_SetAnim( gripEnt, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
8256 					}
8257 					gripEnt->painDebounceTime = level.time + 2000;
8258 				}
8259 			}
8260 		}
8261 		break;
8262 	case FP_LIGHTNING:
8263 		if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 )
8264 		{//higher than level 1
8265 			if ( cmd->buttons & BUTTON_FORCE_LIGHTNING )
8266 			{//holding it keeps it going
8267 				self->client->ps.forcePowerDuration[FP_LIGHTNING] = level.time + 500;
8268 				if ( self->client->ps.torsoAnim == BOTH_FORCELIGHTNING_START )
8269 				{
8270 					if ( !self->client->ps.torsoAnimTimer )
8271 					{
8272 						NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
8273 					}
8274 					else
8275 					{
8276 						NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
8277 					}
8278 				}
8279 				else
8280 				{
8281 					NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
8282 				}
8283 			}
8284 		}
8285 		if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) )
8286 		{
8287 			WP_ForcePowerStop( self, forcePower );
8288 		}
8289 		else
8290 		{
8291 			ForceShootLightning( self );
8292 			WP_ForcePowerDrain( self, forcePower, 0 );
8293 		}
8294 		break;
8295 	default:
8296 		break;
8297 	}
8298 }
8299 
WP_ForcePowersUpdate(gentity_t * self,usercmd_t * ucmd)8300 void WP_ForcePowersUpdate( gentity_t *self, usercmd_t *ucmd )
8301 {
8302 	qboolean	usingForce = qfalse;
8303 	int			i;
8304 	//see if any force powers are running
8305 	if ( !self )
8306 	{
8307 		return;
8308 	}
8309 	if ( !self->client )
8310 	{
8311 		return;
8312 	}
8313 
8314 	if ( self->health <= 0 )
8315 	{//if dead, deactivate any active force powers
8316 		for ( i = 0; i < NUM_FORCE_POWERS; i++ )
8317 		{
8318 			if ( self->client->ps.forcePowerDuration[i] || (self->client->ps.forcePowersActive&( 1 << i )) )
8319 			{
8320 				WP_ForcePowerStop( self, (forcePowers_t)i );
8321 				self->client->ps.forcePowerDuration[i] = 0;
8322 			}
8323 		}
8324 		return;
8325 	}
8326 
8327 	if ( !self->s.number )
8328 	{//player uses different kind of force-jump
8329 	}
8330 	else
8331 	{
8332 		/*
8333 		if ( ucmd->buttons & BUTTON_FORCEJUMP )
8334 		{//just charging up
8335 			ForceJumpCharge( self, ucmd );
8336 		}
8337 		else */
8338 		if ( self->client->ps.forceJumpCharge )
8339 		{//let go of charge button, have charge
8340 			//if leave the ground by some other means, cancel the force jump so we don't suddenly jump when we land.
8341 			if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE
8342 				&& !PM_SwimmingAnim( self->client->ps.legsAnim ) )
8343 			{//FIXME: stop sound?
8344 				//self->client->ps.forceJumpCharge = 0;
8345 				//FIXME: actually, we want this to still be cleared... don't clear it if the button isn't being pressed, but clear it if not holding button and not on ground.
8346 			}
8347 			else
8348 			{//still on ground, so jump
8349 				ForceJump( self, ucmd );
8350 				return;
8351 			}
8352 		}
8353 	}
8354 
8355 	if ( ucmd->buttons & BUTTON_FORCEGRIP )
8356 	{
8357 		ForceGrip( self );
8358 	}
8359 
8360 	if ( ucmd->buttons & BUTTON_FORCE_LIGHTNING )
8361 	{
8362 		ForceLightning( self );
8363 	}
8364 
8365 	for ( i = 0; i < NUM_FORCE_POWERS; i++ )
8366 	{
8367 		if ( self->client->ps.forcePowerDuration[i] )
8368 		{
8369 			if ( self->client->ps.forcePowerDuration[i] < level.time )
8370 			{
8371 				if ( (self->client->ps.forcePowersActive&( 1 << i )) )
8372 				{//turn it off
8373 					WP_ForcePowerStop( self, (forcePowers_t)i );
8374 				}
8375 				self->client->ps.forcePowerDuration[i] = 0;
8376 			}
8377 		}
8378 		if ( (self->client->ps.forcePowersActive&( 1 << i )) )
8379 		{
8380 			usingForce = qtrue;
8381 			WP_ForcePowerRun( self, (forcePowers_t)i, ucmd );
8382 		}
8383 	}
8384 	if ( self->client->ps.saberInFlight )
8385 	{//don't regen force power while throwing saber
8386 		if ( self->client->ps.saberEntityNum < ENTITYNUM_NONE && self->client->ps.saberEntityNum > 0 )//player is 0
8387 		{//
8388 			if ( &g_entities[self->client->ps.saberEntityNum] != NULL && g_entities[self->client->ps.saberEntityNum].s.pos.trType == TR_LINEAR )
8389 			{//fell to the ground and we're trying to pull it back
8390 				usingForce = qtrue;
8391 			}
8392 		}
8393 	}
8394 	if ( !usingForce )
8395 	{//when not using the force, regenerate at 10 points per second
8396 		if ( self->client->ps.forcePowerRegenDebounceTime < level.time )
8397 		{
8398 			WP_ForcePowerRegenerate( self, 0 );
8399 			self->client->ps.forcePowerRegenDebounceTime = level.time + 100;
8400 		}
8401 	}
8402 }
8403 
WP_InitForcePowers(gentity_t * ent)8404 void WP_InitForcePowers( gentity_t *ent )
8405 {
8406 	if ( !ent || !ent->client )
8407 	{
8408 		return;
8409 	}
8410 
8411 	if( ent->client->NPC_class == CLASS_TAVION ||
8412 		ent->client->NPC_class == CLASS_REBORN ||
8413 		ent->client->NPC_class == CLASS_DESANN ||
8414 		ent->client->NPC_class == CLASS_SHADOWTROOPER ||
8415 		ent->client->NPC_class == CLASS_JEDI ||
8416 		ent->client->NPC_class == CLASS_LUKE )
8417 	{//an NPC jedi
8418 		ent->client->ps.forcePower = ent->client->ps.forcePowerMax = FORCE_POWER_MAX;
8419 		ent->client->ps.forcePowerRegenDebounceTime = 0;
8420 		ent->client->ps.forceGripEntityNum = ENTITYNUM_NONE;
8421 
8422 		if ( ent->client->NPC_class == CLASS_DESANN )
8423 		{
8424 			ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_GRIP )|( 1 << FP_LIGHTNING)|( 1 << FP_SABERTHROW)|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8425 			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_3;
8426 			ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_3;
8427 			ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_3;
8428 			ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_3;
8429 			ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_3;
8430 			ent->client->ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_3;
8431 			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_3;
8432 			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
8433 			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
8434 		}
8435 		else if ( ent->client->NPC_class == CLASS_LUKE )
8436 		{
8437 			ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_SABERTHROW)|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8438 			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_3;
8439 			ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_3;
8440 			ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_3;
8441 			ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_3;
8442 			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_3;
8443 			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
8444 			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
8445 		}
8446 		else if ( ent->client->NPC_class == CLASS_TAVION || ( ent->client->NPC_class == CLASS_JEDI && ent->NPC->rank == RANK_COMMANDER ) )
8447 		{//Tavia or trainer Jedi
8448 			ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_SABERTHROW)|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8449 			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_3;
8450 			ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_3;
8451 			ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_2;
8452 			ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_3;
8453 			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_3;
8454 			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
8455 			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
8456 			if ( ent->client->NPC_class == CLASS_TAVION )
8457 			{
8458 				ent->client->ps.forcePowersKnown |= ( 1 << FP_LIGHTNING)|( 1 << FP_GRIP );
8459 				ent->client->ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_2;
8460 				ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_2;
8461 			}
8462 		}
8463 		else if ( ent->client->NPC_class == CLASS_SHADOWTROOPER )
8464 		{//Shadow Trooper
8465 			ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_SABERTHROW)|( 1 << FP_GRIP )|( 1 << FP_LIGHTNING)|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8466 			ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_2;
8467 			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_3;
8468 			ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_3;
8469 			ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_2;
8470 			ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_2;
8471 			ent->client->ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_1;
8472 			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_3;
8473 			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
8474 			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
8475 		}
8476 		else if ( ent->NPC->rank == RANK_LT || ent->client->NPC_class == CLASS_JEDI )
8477 		{//Reborn Boss or ally Jedi
8478 			ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_SABERTHROW)|( 1 << FP_GRIP )|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8479 			ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_2;
8480 			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_2;
8481 			ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_2;
8482 			ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_1;
8483 			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_2;
8484 			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
8485 			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
8486 			if ( ent->client->NPC_class != CLASS_JEDI )
8487 			{
8488 				ent->client->ps.forcePowersKnown |= ( 1 << FP_GRIP );
8489 				ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_2;
8490 			}
8491 		}
8492 		else if ( ent->NPC->rank == RANK_LT_JG )
8493 		{//Reborn Fencer
8494 			ent->client->ps.forcePowersKnown = ( 1 << FP_PUSH )|( 1 << FP_SABERTHROW )|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8495 			ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_2;//FIXME: maybe make him only use it in defense- to throw away grenades
8496 			ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_2;
8497 			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_1;
8498 			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
8499 			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_2;
8500 		}
8501 		else if ( ent->NPC->rank == RANK_ENSIGN )
8502 		{//Force User
8503 			ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8504 			ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_2;
8505 			ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_1;
8506 			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_1;
8507 			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_1;
8508 			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_1;
8509 			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_1;
8510 		}
8511 		else if ( ent->NPC->rank == RANK_CREWMAN )
8512 		{//Acrobat
8513 			ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8514 			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_2;
8515 			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_1;
8516 			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_1;
8517 			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_1;
8518 			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_2;
8519 		}
8520 		else if ( ent->NPC->rank == RANK_CIVILIAN )
8521 		{//Grunt (NOTE: grunt turns slower and has less health)
8522 			ent->client->ps.forcePowersKnown = ( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8523 			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_1;
8524 			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_1;
8525 			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_1;
8526 			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_1;
8527 		}
8528 	}
8529 	else
8530 	{//player
8531 		ent->client->ps.forcePowersKnown = ( 1 << FP_HEAL )|( 1 << FP_LEVITATION )|( 1 << FP_SPEED )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_TELEPATHY )|( 1 << FP_GRIP )|( 1 << FP_LIGHTNING)|( 1 << FP_SABERTHROW)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8532 		ent->client->ps.forcePower = ent->client->ps.forcePowerMax = FORCE_POWER_MAX;
8533 		ent->client->ps.forcePowerRegenDebounceTime = 0;
8534 		ent->client->ps.forcePowerLevel[FP_HEAL] = FORCE_LEVEL_2;
8535 		ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_2;
8536 		ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_1;
8537 		ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_1;
8538 		ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_2;
8539 		ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_2;
8540 		ent->client->ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_1;
8541 		ent->client->ps.forcePowerLevel[FP_TELEPATHY] = FORCE_LEVEL_2;
8542 		ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
8543 		ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
8544 		if ( ent->NPC )
8545 		{//???
8546 			ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_3;
8547 		}
8548 		else
8549 		{
8550 			ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_2;
8551 		}
8552 		ent->client->ps.forceGripEntityNum = ENTITYNUM_NONE;
8553 	}
8554 }
8555