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_local.h"
24 #include "anims.h"
25 #include "b_local.h"
26 #include "bg_local.h"
27 #include "g_functions.h"
28 #include "wp_saber.h"
29 #include "g_vehicles.h"
30 #include "../qcommon/tri_coll_test.h"
31 #include "../cgame/cg_local.h"
32 
33 #define JK2_RAGDOLL_GRIPNOHEALTH
34 
35 #define MAX_SABER_VICTIMS 16
36 static int		victimEntityNum[MAX_SABER_VICTIMS];
37 static float	totalDmg[MAX_SABER_VICTIMS];
38 static vec3_t	dmgDir[MAX_SABER_VICTIMS];
39 static vec3_t	dmgNormal[MAX_SABER_VICTIMS];
40 static vec3_t	dmgBladeVec[MAX_SABER_VICTIMS];
41 static vec3_t	dmgSpot[MAX_SABER_VICTIMS];
42 static float	dmgFraction[MAX_SABER_VICTIMS];
43 static int		hitLoc[MAX_SABER_VICTIMS];
44 static qboolean	hitDismember[MAX_SABER_VICTIMS];
45 static int		hitDismemberLoc[MAX_SABER_VICTIMS];
46 static vec3_t	saberHitLocation, saberHitNormal={0,0,1.0};
47 static float	saberHitFraction;
48 static float	sabersCrossed;
49 static int		saberHitEntity;
50 static int		numVictims = 0;
51 
52 extern cvar_t	*g_sex;
53 extern cvar_t	*g_timescale;
54 extern cvar_t	*g_dismemberment;
55 extern cvar_t	*g_debugSaberLock;
56 extern cvar_t	*g_saberLockRandomNess;
57 extern cvar_t	*d_slowmodeath;
58 extern cvar_t	*g_cheats;
59 extern cvar_t	*g_debugMelee;
60 extern cvar_t	*g_saberRestrictForce;
61 extern cvar_t	*g_saberPickuppableDroppedSabers;
62 extern cvar_t	*debug_subdivision;
63 
64 
65 extern qboolean WP_SaberBladeUseSecondBladeStyle( saberInfo_t *saber, int bladeNum );
66 extern qboolean WP_SaberBladeDoTransitionDamage( saberInfo_t *saber, int bladeNum );
67 extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType );
68 extern qboolean		G_ClearViewEntity( gentity_t *ent );
69 extern void			G_SetViewEntity( gentity_t *self, gentity_t *viewEntity );
70 extern qboolean G_ControlledByPlayer( gentity_t *self );
71 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
72 extern void CG_ChangeWeapon( int num );
73 extern void CG_SaberDoWeaponHitMarks( gclient_t *client, gentity_t *saberEnt, gentity_t *hitEnt, int saberNum, int bladeNum, vec3_t hitPos, vec3_t hitDir, vec3_t uaxis, vec3_t splashBackDir, float sizeTimeScale );
74 extern void G_AngerAlert( gentity_t *self );
75 extern void G_ReflectMissile( gentity_t *ent, gentity_t *missile, vec3_t forward );
76 extern int G_CheckLedgeDive( gentity_t *self, float checkDist, const vec3_t checkVel, qboolean tryOpposite, qboolean tryPerp );
77 extern void G_BounceMissile( gentity_t *ent, trace_t *trace );
78 extern qboolean G_PointInBounds( const vec3_t point, const vec3_t mins, const vec3_t maxs );
79 extern void NPC_UseResponse( gentity_t *self, gentity_t *user, qboolean useWhenDone );
80 extern void WP_FireDreadnoughtBeam( gentity_t *ent );
81 extern void G_MissileImpacted( gentity_t *ent, gentity_t *other, vec3_t impactPos, vec3_t normal, int hitLoc=HL_NONE );
82 extern evasionType_t Jedi_SaberBlockGo( gentity_t *self, usercmd_t *cmd, vec3_t pHitloc, vec3_t phitDir, gentity_t *incoming, float dist = 0.0f );
83 extern void Jedi_RageStop( gentity_t *self );
84 extern int PM_PickAnim( gentity_t *self, int minAnim, int maxAnim );
85 extern void NPC_SetPainEvent( gentity_t *self );
86 extern qboolean PM_SwimmingAnim( int anim );
87 extern qboolean PM_InAnimForSaberMove( int anim, int saberMove );
88 extern qboolean PM_SpinningSaberAnim( int anim );
89 extern qboolean PM_SaberInSpecialAttack( int anim );
90 extern qboolean PM_SaberInAttack( int move );
91 extern qboolean PM_SaberInAttackPure( int move );
92 extern qboolean PM_SaberInTransition( int move );
93 extern qboolean PM_SaberInStart( int move );
94 extern qboolean PM_SaberInTransitionAny( int move );
95 extern qboolean PM_SaberInReturn( int move );
96 extern qboolean PM_SaberInBounce( int move );
97 extern qboolean PM_SaberInParry( int move );
98 extern qboolean PM_SaberInKnockaway( int move );
99 extern qboolean PM_SaberInBrokenParry( int move );
100 extern qboolean PM_SpinningSaberAnim( int anim );
101 extern saberMoveName_t PM_SaberBounceForAttack( int move );
102 extern saberMoveName_t PM_BrokenParryForAttack( int move );
103 extern saberMoveName_t PM_KnockawayForParry( int move );
104 extern qboolean PM_FlippingAnim( int anim );
105 extern qboolean PM_RollingAnim( int anim );
106 extern qboolean PM_CrouchAnim( int anim );
107 extern qboolean PM_SaberInIdle( int move );
108 extern qboolean PM_SaberInReflect( int move );
109 extern qboolean PM_InSpecialJump( int anim );
110 extern qboolean PM_InKnockDown( playerState_t *ps );
111 extern qboolean PM_ForceUsingSaberAnim( int anim );
112 extern qboolean PM_SuperBreakLoseAnim( int anim );
113 extern qboolean PM_SuperBreakWinAnim( int anim );
114 extern qboolean PM_SaberLockBreakAnim( int anim );
115 extern qboolean PM_InOnGroundAnim ( playerState_t *ps );
116 extern qboolean PM_KnockDownAnim( int anim );
117 extern qboolean PM_SaberInKata( saberMoveName_t saberMove );
118 extern qboolean PM_StabDownAnim( int anim );
119 extern int PM_PowerLevelForSaberAnim( playerState_t *ps, int saberNum = 0 );
120 extern void PM_VelocityForSaberMove( playerState_t *ps, vec3_t throwDir );
121 extern qboolean PM_VelocityForBlockedMove( playerState_t *ps, vec3_t throwDir );
122 extern qboolean PM_SaberCanInterruptMove( int move, int anim );
123 extern int Jedi_ReCalcParryTime( gentity_t *self, evasionType_t evasionType );
124 extern qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc );
125 extern void Jedi_PlayDeflectSound( gentity_t *self );
126 extern void Jedi_PlayBlockedPushSound( gentity_t *self );
127 extern qboolean Jedi_WaitingAmbush( gentity_t *self );
128 extern void Jedi_Ambush( gentity_t *self );
129 extern qboolean Jedi_SaberBusy( gentity_t *self );
130 extern qboolean Jedi_CultistDestroyer( gentity_t *self );
131 extern qboolean Boba_Flying( gentity_t *self );
132 extern void JET_FlyStart( gentity_t *self );
133 extern void Boba_DoFlameThrower( gentity_t *self );
134 extern void Boba_StopFlameThrower( gentity_t *self );
135 
136 extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent );
137 extern int SaberDroid_PowerLevelForSaberAnim( gentity_t *self );
138 extern qboolean G_ValidEnemy( gentity_t *self, gentity_t *enemy );
139 extern void G_StartMatrixEffect( gentity_t *ent, int meFlags = 0, int length = 1000, float timeScale = 0.0f, int spinTime = 0 );
140 extern int PM_AnimLength( int index, animNumber_t anim );
141 extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock );
142 extern void G_KnockOffVehicle( gentity_t *pRider, gentity_t *self, qboolean bPull );
143 extern qboolean PM_LockedAnim( int anim );
144 extern qboolean Rosh_BeingHealed( gentity_t *self );
145 extern qboolean G_OkayToLean( playerState_t *ps, usercmd_t *cmd, qboolean interruptOkay );
146 
147 int WP_AbsorbConversion(gentity_t *attacked, int atdAbsLevel, gentity_t *attacker, int atPower, int atPowerLevel, int atForceSpent);
148 void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
149 void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower );
150 qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
151 void WP_SaberInFlightReflectCheck( gentity_t *self, usercmd_t *ucmd  );
152 
153 void WP_SaberDrop( gentity_t *self, gentity_t *saber );
154 qboolean WP_SaberLose( gentity_t *self, vec3_t throwDir );
155 void WP_SaberReturn( gentity_t *self, gentity_t *saber );
156 void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock );
157 qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
158 void WP_ForcePowerDrain( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
159 void WP_DeactivateSaber( gentity_t *self, qboolean clearLength = qfalse );
160 qboolean FP_ForceDrainGrippableEnt( gentity_t *victim );
161 
162 extern cvar_t	*g_saberAutoBlocking;
163 extern cvar_t	*g_saberRealisticCombat;
164 extern cvar_t	*g_saberDamageCapping;
165 extern cvar_t	*g_saberNewControlScheme;
166 extern int g_crosshairEntNum;
167 
168 qboolean g_saberNoEffects = qfalse;
169 qboolean g_noClashFlare = qfalse;
170 int		g_saberFlashTime = 0;
171 vec3_t	g_saberFlashPos = {0,0,0};
172 
173 int forcePowerDarkLight[NUM_FORCE_POWERS] = //0 == neutral
174 { //nothing should be usable at rank 0..
175 	FORCE_LIGHTSIDE,//FP_HEAL,//instant
176 	0,//FP_LEVITATION,//hold/duration
177 	0,//FP_SPEED,//duration
178 	0,//FP_PUSH,//hold/duration
179 	0,//FP_PULL,//hold/duration
180 	FORCE_LIGHTSIDE,//FP_TELEPATHY,//instant
181 	FORCE_DARKSIDE,//FP_GRIP,//hold/duration
182 	FORCE_DARKSIDE,//FP_LIGHTNING,//hold/duration
183 	0,//FP_SABERATTACK,
184 	0,//FP_SABERDEFEND,
185 	0,//FP_SABERTHROW,
186 	//new Jedi Academy powers
187 	FORCE_DARKSIDE,//FP_RAGE,//duration
188 	FORCE_LIGHTSIDE,//FP_PROTECT,//duration
189 	FORCE_LIGHTSIDE,//FP_ABSORB,//duration
190 	FORCE_DARKSIDE,//FP_DRAIN,//hold/duration
191 	0,//FP_SEE,//duration
192 	//NUM_FORCE_POWERS
193 };
194 
195 int forcePowerNeeded[NUM_FORCE_POWERS] =
196 {
197 	0,//FP_HEAL,//instant
198 	10,//FP_LEVITATION,//hold/duration
199 	50,//FP_SPEED,//duration
200 	15,//FP_PUSH,//hold/duration
201 	15,//FP_PULL,//hold/duration
202 	20,//FP_TELEPATHY,//instant
203 	1,//FP_GRIP,//hold/duration - FIXME: 30?
204 	1,//FP_LIGHTNING,//hold/duration
205 	20,//FP_SABERTHROW,
206 	1,//FP_SABER_DEFENSE,
207 	0,//FP_SABER_OFFENSE,
208 	//new Jedi Academy powers
209 	50,//FP_RAGE,//duration - speed, invincibility and extra damage for short period, drains your health and leaves you weak and slow afterwards.
210 	30,//FP_PROTECT,//duration - protect against physical/energy (level 1 stops blaster/energy bolts, level 2 stops projectiles, level 3 protects against explosions)
211 	30,//FP_ABSORB,//duration - protect against dark force powers (grip, lightning, drain)
212 	1,//FP_DRAIN,//hold/duration - drain force power for health
213 	20//FP_SEE,//duration - detect/see hidden enemies
214 	//NUM_FORCE_POWERS
215 };
216 
217 float forceJumpStrength[NUM_FORCE_POWER_LEVELS] =
218 {
219 	JUMP_VELOCITY,//normal jump
220 	420,
221 	590,
222 	840
223 };
224 
225 float forceJumpHeight[NUM_FORCE_POWER_LEVELS] =
226 {
227 	32,//normal jump (+stepheight+crouchdiff = 66)
228 	96,//(+stepheight+crouchdiff = 130)
229 	192,//(+stepheight+crouchdiff = 226)
230 	384//(+stepheight+crouchdiff = 418)
231 };
232 
233 float forceJumpHeightMax[NUM_FORCE_POWER_LEVELS] =
234 {
235 	66,//normal jump (32+stepheight(18)+crouchdiff(24) = 74)
236 	130,//(96+stepheight(18)+crouchdiff(24) = 138)
237 	226,//(192+stepheight(18)+crouchdiff(24) = 234)
238 	418//(384+stepheight(18)+crouchdiff(24) = 426)
239 };
240 
241 float forcePushPullRadius[NUM_FORCE_POWER_LEVELS] =
242 {
243 	0,//none
244 	384,//256,
245 	448,//384,
246 	512
247 };
248 
249 float forcePushCone[NUM_FORCE_POWER_LEVELS] =
250 {
251 	1.0f,//none
252 	1.0f,
253 	0.8f,
254 	0.6f
255 };
256 
257 float forcePullCone[NUM_FORCE_POWER_LEVELS] =
258 {
259 	1.0f,//none
260 	1.0f,
261 	1.0f,
262 	0.8f
263 };
264 
265 float forceSpeedValue[NUM_FORCE_POWER_LEVELS] =
266 {
267 	1.0f,//none
268 	0.75f,
269 	0.5f,
270 	0.25f
271 };
272 
273 float forceSpeedRangeMod[NUM_FORCE_POWER_LEVELS] =
274 {
275 	0.0f,//none
276 	30.0f,
277 	45.0f,
278 	60.0f
279 };
280 
281 float forceSpeedFOVMod[NUM_FORCE_POWER_LEVELS] =
282 {
283 	0.0f,//none
284 	20.0f,
285 	30.0f,
286 	40.0f
287 };
288 
289 int forceGripDamage[NUM_FORCE_POWER_LEVELS] =
290 {
291 	0,//none
292 	0,
293 	6,
294 	9
295 };
296 
297 int mindTrickTime[NUM_FORCE_POWER_LEVELS] =
298 {
299 	0,//none
300 	10000,//5000,
301 	15000,//10000,
302 	30000//15000
303 };
304 
305 //NOTE: keep in synch with table below!!!
306 int saberThrowDist[NUM_FORCE_POWER_LEVELS] =
307 {
308 	0,//none
309 	256,
310 	400,
311 	400
312 };
313 
314 //NOTE: keep in synch with table above!!!
315 int saberThrowDistSquared[NUM_FORCE_POWER_LEVELS] =
316 {
317 	0,//none
318 	65536,
319 	160000,
320 	160000
321 };
322 
323 int parryDebounce[NUM_FORCE_POWER_LEVELS] =
324 {
325 	500,//if don't even have defense, can't use defense!
326 	300,
327 	150,
328 	50
329 };
330 
331 float saberAnimSpeedMod[NUM_FORCE_POWER_LEVELS] =
332 {
333 	0.0f,//if don't even have offense, can't use offense!
334 	0.75f,
335 	1.0f,
336 	2.0f
337 };
338 
339 stringID_table_t SaberStyleTable[] =
340 {
341 	{ "NULL",SS_NONE },
342 	ENUM2STRING(SS_FAST),
343 	{ "fast",SS_FAST },
344 	ENUM2STRING(SS_MEDIUM),
345 	{ "medium",SS_MEDIUM },
346 	ENUM2STRING(SS_STRONG),
347 	{ "strong",SS_STRONG },
348 	ENUM2STRING(SS_DESANN),
349 	{ "desann",SS_DESANN },
350 	ENUM2STRING(SS_TAVION),
351 	{ "tavion",SS_TAVION },
352 	ENUM2STRING(SS_DUAL),
353 	{ "dual",SS_DUAL },
354 	ENUM2STRING(SS_STAFF),
355 	{ "staff",SS_STAFF },
356 	{ "", 0 },
357 };
358 
359 //SABER INITIALIZATION======================================================================
360 
G_CreateG2AttachedWeaponModel(gentity_t * ent,const char * psWeaponModel,int boltNum,int weaponNum)361 void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *psWeaponModel, int boltNum, int weaponNum )
362 {
363 	if (!psWeaponModel)
364 	{
365 		assert (psWeaponModel);
366 		return;
367 	}
368 	if ( ent->playerModel == -1 )
369 	{
370 		return;
371 	}
372 	if ( boltNum == -1 )
373 	{
374 		return;
375 	}
376 
377 	if ( ent && ent->client && ent->client->NPC_class == CLASS_GALAKMECH )
378 	{//hack for galakmech, no weaponmodel
379 		ent->weaponModel[0] = ent->weaponModel[1] = -1;
380 		return;
381 	}
382 	if ( weaponNum < 0 || weaponNum >= MAX_INHAND_WEAPONS )
383 	{
384 		return;
385 	}
386 	char weaponModel[64];
387 
388 	strcpy (weaponModel, psWeaponModel);
389 	if (char *spot = strstr(weaponModel, ".md3") ) {
390 		*spot = 0;
391 		spot = strstr(weaponModel, "_w");//i'm using the in view weapon array instead of scanning the item list, so put the _w back on
392 		if (!spot&&!strstr(weaponModel, "noweap"))
393 		{
394 			strcat (weaponModel, "_w");
395 		}
396 		strcat (weaponModel, ".glm");	//and change to ghoul2
397 	}
398 
399 	// give us a saber model
400 	int wModelIndex = G_ModelIndex( weaponModel );
401 	if ( wModelIndex )
402 	{
403 		ent->weaponModel[weaponNum] = gi.G2API_InitGhoul2Model(ent->ghoul2, weaponModel, wModelIndex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
404 		if ( ent->weaponModel[weaponNum] != -1 )
405 		{
406 			// attach it to the hand
407 			gi.G2API_AttachG2Model(&ent->ghoul2[ent->weaponModel[weaponNum]], &ent->ghoul2[ent->playerModel],
408 						boltNum, ent->playerModel);
409 			// set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0
410 			gi.G2API_AddBolt(&ent->ghoul2[ent->weaponModel[weaponNum]], "*flash");
411 	  		//gi.G2API_SetLodBias( &ent->ghoul2[ent->weaponModel[weaponNum]], 0 );
412 		}
413 	}
414 
415 }
416 
WP_SaberAddG2SaberModels(gentity_t * ent,int specificSaberNum)417 void WP_SaberAddG2SaberModels( gentity_t *ent, int specificSaberNum )
418 {
419 	int saberNum = 0, maxSaber = 1;
420 	if ( specificSaberNum != -1 && specificSaberNum <= maxSaber )
421 	{
422 		saberNum = maxSaber = specificSaberNum;
423 	}
424 	for ( ; saberNum <= maxSaber; saberNum++ )
425 	{
426 		if ( ent->weaponModel[saberNum] > 0 )
427 		{//we already have a weapon model in this slot
428 			//remove it
429 			gi.G2API_SetSkin( &ent->ghoul2[ent->weaponModel[saberNum]], -1, 0 );
430 			gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->weaponModel[saberNum] );
431 			ent->weaponModel[saberNum] = -1;
432 		}
433 		if ( saberNum > 0 )
434 		{//second saber
435 			if ( !ent->client->ps.dualSabers
436 				|| G_IsRidingVehicle( ent ) )
437 			{//only have one saber or riding a vehicle and can only use one saber
438 				return;
439 			}
440 		}
441 		else if ( saberNum == 0 )
442 		{//first saber
443 			if ( ent->client->ps.saberInFlight )
444 			{//it's still out there somewhere, don't add it
445 				//FIXME: call it back?
446 				continue;
447 			}
448 		}
449 		int handBolt = ((saberNum == 0) ? ent->handRBolt : ent->handLBolt);
450 		if ( (ent->client->ps.saber[saberNum].saberFlags&SFL_BOLT_TO_WRIST) )
451 		{//special case, bolt to forearm
452 			if ( saberNum == 0 )
453 			{
454 				handBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*r_hand_cap_r_arm" );
455 			}
456 			else
457 			{
458 				handBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*l_hand_cap_l_arm" );
459 			}
460 		}
461 		G_CreateG2AttachedWeaponModel( ent, ent->client->ps.saber[saberNum].model, handBolt, saberNum );
462 
463 		if ( ent->client->ps.saber[saberNum].skin != NULL )
464 		{//if this saber has a customSkin, use it
465 			// lets see if it's out there
466 			int saberSkin = gi.RE_RegisterSkin( ent->client->ps.saber[saberNum].skin );
467 			if ( saberSkin )
468 			{
469 				// put it in the config strings
470 				// and set the ghoul2 model to use it
471 				gi.G2API_SetSkin( &ent->ghoul2[ent->weaponModel[saberNum]], G_SkinIndex( ent->client->ps.saber[saberNum].skin ), saberSkin );
472 			}
473 		}
474 	}
475 }
476 
477 //----------------------------------------------------------
G_Throw(gentity_t * targ,const vec3_t newDir,float push)478 void G_Throw( gentity_t *targ, const vec3_t newDir, float push )
479 //----------------------------------------------------------
480 {
481 	vec3_t	kvel;
482 	float	mass;
483 
484 	if ( targ
485 		&& targ->client
486 		&& ( targ->client->NPC_class == CLASS_ATST
487 			|| targ->client->NPC_class == CLASS_RANCOR
488 			|| targ->client->NPC_class == CLASS_SAND_CREATURE ) )
489 	{//much to large to *ever* throw
490 		return;
491 	}
492 
493 	if ( targ->physicsBounce > 0 )	//overide the mass
494 	{
495 		mass = targ->physicsBounce;
496 	}
497 	else
498 	{
499 		mass = 200;
500 	}
501 
502 	if ( g_gravity->value > 0 )
503 	{
504 		VectorScale( newDir, g_knockback->value * (float)push / mass * 0.8, kvel );
505 		if ( !targ->client || targ->client->ps.groundEntityNum != ENTITYNUM_NONE )
506 		{//give them some z lift to get them off the ground
507 			kvel[2] = newDir[2] * g_knockback->value * (float)push / mass * 1.5;
508 		}
509 	}
510 	else
511 	{
512 		VectorScale( newDir, g_knockback->value * (float)push / mass, kvel );
513 	}
514 
515 	if ( targ->client )
516 	{
517 		VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity );
518 	}
519 	else if ( targ->s.pos.trType != TR_STATIONARY && targ->s.pos.trType != TR_LINEAR_STOP && targ->s.pos.trType != TR_NONLINEAR_STOP )
520 	{
521 		VectorAdd( targ->s.pos.trDelta, kvel, targ->s.pos.trDelta );
522 		VectorCopy( targ->currentOrigin, targ->s.pos.trBase );
523 		targ->s.pos.trTime = level.time;
524 	}
525 
526 	// set the timer so that the other client can't cancel
527 	// out the movement immediately
528 	if ( targ->client && !targ->client->ps.pm_time )
529 	{
530 		int		t;
531 
532 		t = push * 2;
533 
534 		if ( t < 50 )
535 		{
536 			t = 50;
537 		}
538 		if ( t > 200 )
539 		{
540 			t = 200;
541 		}
542 		targ->client->ps.pm_time = t;
543 		targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
544 	}
545 }
546 
WP_SetSaberModel(gclient_t * client,class_t npcClass)547 int WP_SetSaberModel( gclient_t *client, class_t npcClass )
548 {//FIXME: read from NPCs.cfg
549 	if ( client )
550 	{
551 		switch ( npcClass )
552 		{
553 		case CLASS_DESANN://Desann
554 			client->ps.saber[0].model = "models/weapons2/saber_desann/saber_w.glm";
555 			break;
556 		case CLASS_LUKE://Luke
557 			client->ps.saber[0].model = "models/weapons2/saber_luke/saber_w.glm";
558 			break;
559 		case CLASS_PLAYER://Kyle NPC and player
560 		case CLASS_KYLE://Kyle NPC and player
561 			client->ps.saber[0].model = "models/weapons2/saber/saber_w.glm";
562 			break;
563 		default://reborn and tavion and everyone else
564 			client->ps.saber[0].model = "models/weapons2/saber_reborn/saber_w.glm";
565 			break;
566 		}
567 		return ( G_ModelIndex( client->ps.saber[0].model ) );
568 	}
569 	else
570 	{
571 		switch ( npcClass )
572 		{
573 		case CLASS_DESANN://Desann
574 			return ( G_ModelIndex( "models/weapons2/saber_desann/saber_w.glm" ) );
575 			break;
576 		case CLASS_LUKE://Luke
577 			return ( G_ModelIndex( "models/weapons2/saber_luke/saber_w.glm" ) );
578 			break;
579 		case CLASS_PLAYER://Kyle NPC and player
580 		case CLASS_KYLE://Kyle NPC and player
581 			return ( G_ModelIndex( "models/weapons2/saber/saber_w.glm" ) );
582 			break;
583 		default://reborn and tavion and everyone else
584 			return ( G_ModelIndex( "models/weapons2/saber_reborn/saber_w.glm" ) );
585 			break;
586 		}
587 	}
588 }
589 
WP_SetSaberEntModelSkin(gentity_t * ent,gentity_t * saberent)590 void WP_SetSaberEntModelSkin( gentity_t *ent, gentity_t *saberent )
591 {
592 	int	saberModel = 0;
593 	qboolean	newModel = qfalse;
594 	//FIXME: get saberModel from NPCs.cfg
595 	if ( !ent->client->ps.saber[0].model )
596 	{
597 		saberModel = WP_SetSaberModel( ent->client, ent->client->NPC_class );
598 	}
599 	else
600 	{
601 		//got saberModel from NPCs.cfg
602 		saberModel = G_ModelIndex( ent->client->ps.saber[0].model );
603 	}
604 	if ( saberModel && saberent->s.modelindex != saberModel )
605 	{
606 		if ( saberent->playerModel >= 0 )
607 		{//remove the old one, if there is one
608 			gi.G2API_RemoveGhoul2Model( saberent->ghoul2, saberent->playerModel );
609 		}
610 		//add the new one
611 		saberent->playerModel = gi.G2API_InitGhoul2Model( saberent->ghoul2, ent->client->ps.saber[0].model, saberModel, NULL_HANDLE, NULL_HANDLE, 0, 0);
612 		saberent->s.modelindex = saberModel;
613 		newModel = qtrue;
614 	}
615 	//set skin, too
616 	if ( ent->client->ps.saber[0].skin == NULL )
617 	{
618 		gi.G2API_SetSkin( &saberent->ghoul2[0], -1, 0 );
619 	}
620 	else
621 	{//if this saber has a customSkin, use it
622 		// lets see if it's out there
623 		int saberSkin = gi.RE_RegisterSkin( ent->client->ps.saber[0].skin );
624 		if ( saberSkin && (newModel || saberent->s.modelindex2 != saberSkin) )
625 		{
626 			// put it in the config strings
627 			// and set the ghoul2 model to use it
628 			gi.G2API_SetSkin( &saberent->ghoul2[0], G_SkinIndex( ent->client->ps.saber[0].skin ), saberSkin );
629 			saberent->s.modelindex2 = saberSkin;
630 		}
631 	}
632 }
633 
WP_SaberFallSound(gentity_t * owner,gentity_t * saber)634 void WP_SaberFallSound( gentity_t *owner, gentity_t *saber )
635 {
636 	if ( !saber )
637 	{
638 		return;
639 	}
640 	if ( owner && owner->client )
641 	{//have an owner, use their data (assume saberNum is 0 because only the 0 saber can be thrown)
642 		if ( owner->client->ps.saber[0].fallSound[0] )
643 		{//have an override
644 			G_Sound( saber, owner->client->ps.saber[0].fallSound[Q_irand( 0, 2 )] );
645 		}
646 		else if ( owner->client->ps.saber[0].type == SABER_SITH_SWORD )
647 		{//is a sith sword
648 			G_Sound( saber, G_SoundIndex( va( "sound/weapons/sword/fall%d.wav", Q_irand( 1, 7 ) ) ) );
649 		}
650 		else
651 		{//normal saber
652 			G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/bounce%d.wav", Q_irand( 1, 3 ) ) ) );
653 		}
654 	}
655 	else if ( saber->NPC_type && saber->NPC_type[0] )
656 	{//have a saber name to look up
657 		saberInfo_t saberInfo;
658 		if ( WP_SaberParseParms( saber->NPC_type, &saberInfo ) )
659 		{//found it
660 			if ( saberInfo.fallSound[0] )
661 			{//have an override sound
662 				G_Sound( saber, saberInfo.fallSound[Q_irand( 0, 2 )] );
663 			}
664 			else if ( saberInfo.type == SABER_SITH_SWORD )
665 			{//is a sith sword
666 				G_Sound( saber, G_SoundIndex( va( "sound/weapons/sword/fall%d.wav", Q_irand( 1, 7 ) ) ) );
667 			}
668 			else
669 			{//normal saber
670 				G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/bounce%d.wav", Q_irand( 1, 3 ) ) ) );
671 			}
672 		}
673 		else
674 		{//can't find it
675 			G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/bounce%d.wav", Q_irand( 1, 3 ) ) ) );
676 		}
677 	}
678 	else
679 	{//no saber name specified
680 		G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/bounce%d.wav", Q_irand( 1, 3 ) ) ) );
681 	}
682 }
683 
WP_SaberSwingSound(gentity_t * ent,int saberNum,swingType_t swingType)684 void WP_SaberSwingSound( gentity_t *ent, int saberNum, swingType_t swingType )
685 {
686 	int index = 1;
687 	if ( !ent || !ent->client )
688 	{
689 		return;
690 	}
691 	if ( swingType == SWING_FAST )
692 	{
693 		index = Q_irand( 1, 3 );
694 	}
695 	else if ( swingType == SWING_MEDIUM )
696 	{
697 		index = Q_irand( 4, 6 );
698 	}
699 	else if ( swingType == SWING_STRONG )
700 	{
701 		index = Q_irand( 7, 9 );
702 	}
703 
704 	if ( ent->client->ps.saber[saberNum].swingSound[0] )
705 	{
706 		G_SoundIndexOnEnt( ent, CHAN_WEAPON, ent->client->ps.saber[saberNum].swingSound[Q_irand( 0, 2 )] );
707 	}
708 	else if ( ent->client->ps.saber[saberNum].type == SABER_SITH_SWORD )
709 	{
710 		G_SoundOnEnt( ent, CHAN_WEAPON, va( "sound/weapons/sword/swing%d.wav", Q_irand( 1, 4 ) ) );
711 	}
712 	else
713 	{
714 		G_SoundOnEnt( ent, CHAN_WEAPON, va( "sound/weapons/saber/saberhup%d.wav", index ) );
715 	}
716 }
717 
WP_SaberHitSound(gentity_t * ent,int saberNum,int bladeNum)718 void WP_SaberHitSound( gentity_t *ent, int saberNum, int bladeNum )
719 {
720 	int index = 1;
721 	if ( !ent || !ent->client )
722 	{
723 		return;
724 	}
725 	index = Q_irand( 1, 3 );
726 
727 	if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
728 		&& ent->client->ps.saber[saberNum].hitSound[0] )
729 	{
730 		G_Sound( ent, ent->client->ps.saber[saberNum].hitSound[Q_irand( 0, 2 )] );
731 	}
732 	else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
733 		&& ent->client->ps.saber[saberNum].hit2Sound[0] )
734 	{
735 		G_Sound( ent, ent->client->ps.saber[saberNum].hit2Sound[Q_irand( 0, 2 )] );
736 	}
737 	else if ( ent->client->ps.saber[saberNum].type == SABER_SITH_SWORD )
738 	{
739 		G_Sound( ent, G_SoundIndex( va( "sound/weapons/sword/stab%d.wav", Q_irand( 1, 4 ) ) ) );
740 	}
741 	else
742 	{
743 		G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberhit%d.wav", index ) ) );
744 	}
745 }
746 
WP_SaberBlockSound(gentity_t * ent,gentity_t * hitEnt,int saberNum,int bladeNum)747 void WP_SaberBlockSound( gentity_t *ent, gentity_t *hitEnt, int saberNum, int bladeNum )
748 {
749 	int index = 1;
750 	if ( !ent || !ent->client )
751 	{
752 		return;
753 	}
754 	index = Q_irand( 1, 9 );
755 
756 	if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
757 		&& ent->client->ps.saber[saberNum].blockSound[0] )
758 	{
759 		G_Sound( ent, ent->client->ps.saber[saberNum].blockSound[Q_irand( 0, 2 )] );
760 	}
761 	else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
762 		&& ent->client->ps.saber[saberNum].block2Sound[0] )
763 	{
764 		G_Sound( ent, ent->client->ps.saber[saberNum].block2Sound[Q_irand( 0, 2 )] );
765 	}
766 	else
767 	{
768 		G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", index ) ) );
769 	}
770 }
771 
772 
WP_SaberBounceOnWallSound(gentity_t * ent,int saberNum,int bladeNum)773 void WP_SaberBounceOnWallSound( gentity_t *ent, int saberNum, int bladeNum )
774 {
775 	int index = 1;
776 	if ( !ent || !ent->client )
777 	{
778 		return;
779 	}
780 	index = Q_irand( 1, 9 );
781 
782 	if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
783 		&& ent->client->ps.saber[saberNum].bounceSound[0] )
784 	{
785 		G_Sound( ent, ent->client->ps.saber[saberNum].bounceSound[Q_irand( 0, 2 )] );
786 	}
787 	else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
788 		&& ent->client->ps.saber[saberNum].bounce2Sound[0] )
789 	{
790 		G_Sound( ent, ent->client->ps.saber[saberNum].bounce2Sound[Q_irand( 0, 2 )] );
791 	}
792 	else if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
793 		&& ent->client->ps.saber[saberNum].blockSound[0] )
794 	{
795 		G_Sound( ent, ent->client->ps.saber[saberNum].blockSound[Q_irand( 0, 2 )] );
796 	}
797 	else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
798 		&& ent->client->ps.saber[saberNum].block2Sound[0] )
799 	{
800 		G_Sound( ent, ent->client->ps.saber[saberNum].block2Sound[Q_irand( 0, 2 )] );
801 	}
802 	else
803 	{
804 		G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", index ) ) );
805 	}
806 }
807 
WP_SaberBounceSound(gentity_t * ent,gentity_t * hitEnt,gentity_t * playOnEnt,int saberNum,int bladeNum,qboolean doForce)808 void WP_SaberBounceSound( gentity_t *ent, gentity_t *hitEnt, gentity_t *playOnEnt, int saberNum, int bladeNum, qboolean doForce )
809 {
810 	int index = 1;
811 	if ( !ent || !ent->client )
812 	{
813 		return;
814 	}
815 	index = Q_irand( 1, 3 );
816 
817 	if ( !playOnEnt )
818 	{
819 		playOnEnt = ent;
820 	}
821 	//NOTE: we don't allow overriding of the saberbounce sound, but since it's just a variant on the saberblock sound, we use that as the override
822 	if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
823 		&& ent->client->ps.saber[saberNum].blockSound[0] )
824 	{
825 		G_Sound( playOnEnt, ent->client->ps.saber[saberNum].blockSound[Q_irand( 0, 2 )] );
826 	}
827 	else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
828 		&& ent->client->ps.saber[saberNum].block2Sound[0] )
829 	{
830 		G_Sound( playOnEnt, ent->client->ps.saber[saberNum].block2Sound[Q_irand( 0, 2 )] );
831 	}
832 	else
833 	{
834 		G_Sound( playOnEnt, G_SoundIndex( va( "sound/weapons/saber/saberbounce%d.wav", index ) ) );
835 	}
836 }
837 
WP_SaberInitBladeData(gentity_t * ent)838 int WP_SaberInitBladeData( gentity_t *ent )
839 {
840 	if ( !ent->client )
841 	{
842 		return 0;
843 	}
844 	if ( 1 )
845 	{
846 		VectorClear( ent->client->renderInfo.muzzlePoint );
847 		VectorClear( ent->client->renderInfo.muzzlePointOld );
848 		//VectorClear( ent->client->renderInfo.muzzlePointNext );
849 		VectorClear( ent->client->renderInfo.muzzleDir );
850 		VectorClear( ent->client->renderInfo.muzzleDirOld );
851 		//VectorClear( ent->client->renderInfo.muzzleDirNext );
852 		for ( int saberNum = 0; saberNum < MAX_SABERS; saberNum++ )
853 		{
854 			for ( int bladeNum = 0; bladeNum < MAX_BLADES; bladeNum++ )
855 			{
856 				VectorClear( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint );
857 				VectorClear( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld );
858 				VectorClear( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir );
859 				VectorClear( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld );
860 				ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld = ent->client->ps.saber[saberNum].blade[bladeNum].length = 0;
861 				if ( !ent->client->ps.saber[saberNum].blade[bladeNum].lengthMax )
862 				{
863 					if ( ent->client->NPC_class == CLASS_DESANN )
864 					{//longer saber
865 						ent->client->ps.saber[saberNum].blade[bladeNum].lengthMax = 48;
866 					}
867 					else if ( ent->client->NPC_class == CLASS_REBORN )
868 					{//shorter saber
869 						ent->client->ps.saber[saberNum].blade[bladeNum].lengthMax = 32;
870 					}
871 					else
872 					{//standard saber length
873 						ent->client->ps.saber[saberNum].blade[bladeNum].lengthMax = 40;
874 					}
875 				}
876 			}
877 		}
878 		ent->client->ps.saberLockEnemy = ENTITYNUM_NONE;
879 		ent->client->ps.saberLockTime = 0;
880 		if ( ent->s.number )
881 		{
882 			if ( !ent->client->ps.saberAnimLevel )
883 			{
884 				if ( ent->client->NPC_class == CLASS_DESANN )
885 				{
886 					ent->client->ps.saberAnimLevel = SS_DESANN;
887 				}
888 				else if ( ent->client->NPC_class == CLASS_TAVION )
889 				{
890 					ent->client->ps.saberAnimLevel = SS_TAVION;
891 				}
892 				else if ( ent->client->NPC_class == CLASS_ALORA )
893 				{
894 					ent->client->ps.saberAnimLevel = SS_DUAL;
895 				}
896 				//FIXME: CLASS_CULTIST instead of this Q_stricmpn?
897 				else if ( !Q_stricmpn( "cultist", ent->NPC_type, 7 ) )
898 				{//should already be set in the .npc file
899 					ent->client->ps.saberAnimLevel = Q_irand( SS_FAST, SS_STRONG );
900 				}
901 				else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && (ent->NPC->rank == RANK_CIVILIAN || ent->NPC->rank == RANK_LT_JG) )
902 				{//grunt and fencer always uses quick attacks
903 					ent->client->ps.saberAnimLevel = SS_FAST;
904 				}
905 				else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && (ent->NPC->rank == RANK_CREWMAN || ent->NPC->rank == RANK_ENSIGN) )
906 				{//acrobat & force-users always use medium attacks
907 					ent->client->ps.saberAnimLevel = SS_MEDIUM;
908 				}
909 				else if ( ent->client->playerTeam == TEAM_ENEMY && ent->client->NPC_class == CLASS_SHADOWTROOPER )
910 				{//shadowtroopers
911 					ent->client->ps.saberAnimLevel = Q_irand( SS_FAST, SS_STRONG );
912 				}
913 				else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && ent->NPC->rank == RANK_LT )
914 				{//boss always starts with strong attacks
915 					ent->client->ps.saberAnimLevel = SS_STRONG;
916 				}
917 				else if ( ent->client->NPC_class == CLASS_PLAYER )
918 				{
919 					ent->client->ps.saberAnimLevel = g_entities[0].client->ps.saberAnimLevel;
920 				}
921 				else
922 				{//?
923 					ent->client->ps.saberAnimLevel = Q_irand( SS_FAST, SS_STRONG );
924 				}
925 			}
926 		}
927 		else
928 		{
929 			if ( !ent->client->ps.saberAnimLevel )
930 			{//initialize, but don't reset
931 				if (ent->s.number < MAX_CLIENTS)
932 				{
933 					if (!ent->client->ps.saberStylesKnown)
934 					{
935 						ent->client->ps.saberStylesKnown = (1<<SS_MEDIUM);
936 					}
937 
938 
939 					if (ent->client->ps.saberStylesKnown & (1<<SS_FAST))
940 					{
941 						ent->client->ps.saberAnimLevel = SS_FAST;
942 					}
943 					else if (ent->client->ps.saberStylesKnown & (1<<SS_STRONG))
944 					{
945 						ent->client->ps.saberAnimLevel = SS_STRONG;
946 					}
947 					else
948 					{
949 						ent->client->ps.saberAnimLevel = SS_MEDIUM;
950 					}
951 
952 				}
953 				else
954 				{
955 					ent->client->ps.saberAnimLevel = SS_MEDIUM;
956 				}
957 			}
958 
959 			cg.saberAnimLevelPending = ent->client->ps.saberAnimLevel;
960 			if ( ent->client->sess.missionStats.weaponUsed[WP_SABER] <= 0 )
961 			{//let missionStats know that we actually do have the saber, even if we never use it
962 				ent->client->sess.missionStats.weaponUsed[WP_SABER] = 1;
963 			}
964 		}
965 		ent->client->ps.saberAttackChainCount = 0;
966 
967 		if ( ent->client->ps.saberEntityNum <= 0 || ent->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
968 		{//FIXME: if you do have a saber already, be sure to re-set the model if it's changed (say, via a script).
969 			gentity_t *saberent = G_Spawn();
970 			ent->client->ps.saberEntityNum = saberent->s.number;
971 			saberent->classname = "lightsaber";
972 
973 			saberent->s.eType = ET_GENERAL;
974 			saberent->svFlags = SVF_USE_CURRENT_ORIGIN;
975 			saberent->s.weapon = WP_SABER;
976 			saberent->owner = ent;
977 			saberent->s.otherEntityNum = ent->s.number;
978 			//clear the enemy
979 			saberent->enemy = NULL;
980 
981 			saberent->clipmask = MASK_SOLID | CONTENTS_LIGHTSABER;
982 			saberent->contents = CONTENTS_LIGHTSABER;//|CONTENTS_SHOTCLIP;
983 
984 			VectorSet( saberent->mins, -3.0f, -3.0f, -3.0f );
985 			VectorSet( saberent->maxs, 3.0f, 3.0f, 3.0f );
986 			saberent->mass = 10;//necc?
987 
988 			saberent->s.eFlags |= EF_NODRAW;
989 			saberent->svFlags |= SVF_NOCLIENT;
990 /*
991 Ghoul2 Insert Start
992 */
993 			saberent->playerModel = -1;
994 			WP_SetSaberEntModelSkin( ent, saberent );
995 
996 			// set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0
997 			gi.G2API_AddBolt( &saberent->ghoul2[0], "*flash" );
998 			//gi.G2API_SetLodBias( &saberent->ghoul2[0], 0 );
999 			if ( ent->client->ps.dualSabers )
1000 			{
1001 				//int saber2 =
1002 				G_ModelIndex( ent->client->ps.saber[1].model );
1003 				//gi.G2API_InitGhoul2Model( saberent->ghoul2, ent->client->ps.saber[1].model, saber2 );
1004 				// set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0
1005 				//gi.G2API_AddBolt( &saberent->ghoul2[0], "*flash" );
1006 				//gi.G2API_SetLodBias( &saberent->ghoul2[0], 0 );
1007 			}
1008 
1009 /*
1010 Ghoul2 Insert End
1011 */
1012 
1013 			ent->client->ps.saberInFlight = qfalse;
1014 			ent->client->ps.saberEntityDist = 0;
1015 			ent->client->ps.saberEntityState = SES_LEAVING;
1016 
1017 			ent->client->ps.saberMove = ent->client->ps.saberMoveNext = LS_NONE;
1018 
1019 			//FIXME: need a think function to create alerts when turned on or is on, etc.
1020 		}
1021 		else
1022 		{//already have one, might just be changing sabers, register the model and skin and use them if different from what we're using now.
1023 			WP_SetSaberEntModelSkin( ent, &g_entities[ent->client->ps.saberEntityNum] );
1024 		}
1025 	}
1026 	else
1027 	{
1028 		ent->client->ps.saberEntityNum = ENTITYNUM_NONE;
1029 		ent->client->ps.saberInFlight = qfalse;
1030 		ent->client->ps.saberEntityDist = 0;
1031 		ent->client->ps.saberEntityState = SES_LEAVING;
1032 	}
1033 
1034 	if ( ent->client->ps.dualSabers )
1035 	{
1036 		return 2;
1037 	}
1038 
1039 	return 1;
1040 }
1041 
WP_SaberUpdateOldBladeData(gentity_t * ent)1042 void WP_SaberUpdateOldBladeData( gentity_t *ent )
1043 {
1044 	if ( ent->client )
1045 	{
1046 		qboolean didEvent = qfalse;
1047 		for ( int saberNum = 0; saberNum < 2; saberNum++ )
1048 		{
1049 			for ( int bladeNum = 0; bladeNum < ent->client->ps.saber[saberNum].numBlades; bladeNum++ )
1050 			{
1051 				VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld );
1052 				VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld );
1053 				if ( !didEvent )
1054 				{
1055 					if ( ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld <= 0 && ent->client->ps.saber[saberNum].blade[bladeNum].length > 0 )
1056 					{//just turned on
1057 						//do sound event
1058 						vec3_t	saberOrg;
1059 						VectorCopy( g_entities[ent->client->ps.saberEntityNum].currentOrigin, saberOrg );
1060 						if ( (!ent->client->ps.saberInFlight && ent->client->ps.groundEntityNum == ENTITYNUM_WORLD)//holding saber and on ground
1061 							|| g_entities[ent->client->ps.saberEntityNum].s.pos.trType == TR_STATIONARY )//saber out there somewhere and on ground
1062 						{//a ground alert
1063 							AddSoundEvent( ent, saberOrg, 256, AEL_SUSPICIOUS, qfalse, qtrue );
1064 						}
1065 						else
1066 						{//an in-air alert
1067 							AddSoundEvent( ent, saberOrg, 256, AEL_SUSPICIOUS );
1068 						}
1069 						didEvent = qtrue;
1070 					}
1071 				}
1072 				ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld = ent->client->ps.saber[saberNum].blade[bladeNum].length;
1073 			}
1074 		}
1075 		VectorCopy( ent->client->renderInfo.muzzlePoint, ent->client->renderInfo.muzzlePointOld );
1076 		VectorCopy( ent->client->renderInfo.muzzleDir, ent->client->renderInfo.muzzleDirOld );
1077 	}
1078 }
1079 
1080 
1081 
1082 //SABER DAMAGE==============================================================================
1083 //SABER DAMAGE==============================================================================
1084 //SABER DAMAGE==============================================================================
1085 //SABER DAMAGE==============================================================================
1086 //SABER DAMAGE==============================================================================
1087 //SABER DAMAGE==============================================================================
WPDEBUG_SaberColor(saber_colors_t saberColor)1088 int WPDEBUG_SaberColor( saber_colors_t saberColor )
1089 {
1090 	switch( (int)(saberColor) )
1091 	{
1092 		case SABER_RED:
1093 			return 0x000000ff;
1094 			break;
1095 		case SABER_ORANGE:
1096 			return 0x000088ff;
1097 			break;
1098 		case SABER_YELLOW:
1099 			return 0x0000ffff;
1100 			break;
1101 		case SABER_GREEN:
1102 			return 0x0000ff00;
1103 			break;
1104 		case SABER_BLUE:
1105 			return 0x00ff0000;
1106 			break;
1107 		case SABER_PURPLE:
1108 			return 0x00ff00ff;
1109 			break;
1110 		default:
1111 			return 0x00ffffff;//white
1112 			break;
1113 	}
1114 }
1115 
WP_GetSaberDeflectionAngle(gentity_t * attacker,gentity_t * defender)1116 qboolean WP_GetSaberDeflectionAngle( gentity_t *attacker, gentity_t *defender )
1117 {
1118 	vec3_t	temp, att_SaberBase, att_StartPos, saberMidNext, att_HitDir, att_HitPos, def_BladeDir;
1119 	float	att_SaberHitLength, hitDot;
1120 
1121 	if ( !attacker || !attacker->client || attacker->client->ps.saberInFlight || attacker->client->ps.SaberLength() <= 0 )
1122 	{
1123 		return qfalse;
1124 	}
1125 	if ( !defender || !defender->client || defender->client->ps.saberInFlight || defender->client->ps.SaberLength() <= 0 )
1126 	{
1127 		return qfalse;
1128 	}
1129 	if ( PM_SuperBreakLoseAnim( attacker->client->ps.torsoAnim )
1130 		|| PM_SuperBreakWinAnim( attacker->client->ps.torsoAnim ) )
1131 	{
1132 		return qfalse;
1133 	}
1134 	attacker->client->ps.saberBounceMove = LS_NONE;
1135 
1136 	//get the attacker's saber base pos at time of impact
1137 	VectorSubtract( attacker->client->renderInfo.muzzlePoint, attacker->client->renderInfo.muzzlePointOld, temp );
1138 	VectorMA( attacker->client->renderInfo.muzzlePointOld, saberHitFraction, temp, att_SaberBase );
1139 
1140 	//get the position along the length of the blade where the hit occured
1141 	att_SaberHitLength = Distance( saberHitLocation, att_SaberBase )/attacker->client->ps.SaberLength();
1142 
1143 	//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?)
1144 	VectorMA( attacker->client->renderInfo.muzzlePointOld, att_SaberHitLength, attacker->client->renderInfo.muzzleDirOld, att_StartPos );
1145 	VectorMA( attacker->client->renderInfo.muzzlePoint, att_SaberHitLength, attacker->client->renderInfo.muzzleDir, saberMidNext );
1146 	VectorSubtract( saberMidNext, att_StartPos, att_HitDir );
1147 	VectorMA( att_StartPos, saberHitFraction, att_HitDir, att_HitPos );
1148 	VectorNormalize( att_HitDir );
1149 
1150 	//get the defender's saber dir at time of impact
1151 	VectorSubtract( defender->client->renderInfo.muzzleDirOld, defender->client->renderInfo.muzzleDir, temp );
1152 	VectorMA( defender->client->renderInfo.muzzleDirOld, saberHitFraction, temp, def_BladeDir );
1153 
1154 	//now compare
1155 	hitDot = DotProduct( att_HitDir, def_BladeDir );
1156 	if ( hitDot < 0.25f && hitDot > -0.25f )
1157 	{//hit pretty much perpendicular, pop straight back
1158 		attacker->client->ps.saberBounceMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove );
1159 		return qfalse;
1160 	}
1161 	else
1162 	{//a deflection
1163 		vec3_t	att_Right, att_Up, att_DeflectionDir;
1164 		float	swingRDot, swingUDot;
1165 
1166 		//get the direction of the deflection
1167 		VectorScale( def_BladeDir, hitDot, att_DeflectionDir );
1168 		//get our bounce straight back direction
1169 		VectorScale( att_HitDir, -1.0f, temp );
1170 		//add the bounce back and deflection
1171 		VectorAdd( att_DeflectionDir, temp, att_DeflectionDir );
1172 		//normalize the result to determine what direction our saber should bounce back toward
1173 		VectorNormalize( att_DeflectionDir );
1174 
1175 		//need to know the direction of the deflectoin relative to the attacker's facing
1176 		VectorSet( temp, 0, attacker->client->ps.viewangles[YAW], 0 );//presumes no pitch!
1177 		AngleVectors( temp, NULL, att_Right, att_Up );
1178 		swingRDot = DotProduct( att_Right, att_DeflectionDir );
1179 		swingUDot = DotProduct( att_Up, att_DeflectionDir );
1180 
1181 		if ( swingRDot > 0.25f )
1182 		{//deflect to right
1183 			if ( swingUDot > 0.25f )
1184 			{//deflect to top
1185 				attacker->client->ps.saberBounceMove = LS_D1_TR;
1186 			}
1187 			else if ( swingUDot < -0.25f )
1188 			{//deflect to bottom
1189 				attacker->client->ps.saberBounceMove = LS_D1_BR;
1190 			}
1191 			else
1192 			{//deflect horizontally
1193 				attacker->client->ps.saberBounceMove = LS_D1__R;
1194 			}
1195 		}
1196 		else if ( swingRDot < -0.25f )
1197 		{//deflect to left
1198 			if ( swingUDot > 0.25f )
1199 			{//deflect to top
1200 				attacker->client->ps.saberBounceMove = LS_D1_TL;
1201 			}
1202 			else if ( swingUDot < -0.25f )
1203 			{//deflect to bottom
1204 				attacker->client->ps.saberBounceMove = LS_D1_BL;
1205 			}
1206 			else
1207 			{//deflect horizontally
1208 				attacker->client->ps.saberBounceMove = LS_D1__L;
1209 			}
1210 		}
1211 		else
1212 		{//deflect in middle
1213 			if ( swingUDot > 0.25f )
1214 			{//deflect to top
1215 				attacker->client->ps.saberBounceMove = LS_D1_T_;
1216 			}
1217 			else if ( swingUDot < -0.25f )
1218 			{//deflect to bottom
1219 				attacker->client->ps.saberBounceMove = LS_D1_B_;
1220 			}
1221 			else
1222 			{//deflect horizontally?  Well, no such thing as straight back in my face, so use top
1223 				if ( swingRDot > 0 )
1224 				{
1225 					attacker->client->ps.saberBounceMove = LS_D1_TR;
1226 				}
1227 				else if ( swingRDot < 0 )
1228 				{
1229 					attacker->client->ps.saberBounceMove = LS_D1_TL;
1230 				}
1231 				else
1232 				{
1233 					attacker->client->ps.saberBounceMove = LS_D1_T_;
1234 				}
1235 			}
1236 		}
1237 #ifndef FINAL_BUILD
1238 		if ( d_saberCombat->integer )
1239 		{
1240 			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 );
1241 		}
1242 #endif
1243 		return qtrue;
1244 	}
1245 }
1246 
1247 
WP_SaberClearDamageForEntNum(gentity_t * attacker,int entityNum,int saberNum,int bladeNum)1248 void WP_SaberClearDamageForEntNum( gentity_t *attacker, int entityNum, int saberNum, int bladeNum )
1249 {
1250 #ifndef FINAL_BUILD
1251 	if ( d_saberCombat->integer )
1252 	{
1253 		if ( entityNum )
1254 		{
1255 			Com_Printf( "clearing damage for entnum %d\n", entityNum );
1256 		}
1257 	}
1258 #endif// FINAL_BUILD
1259 	if ( g_saberRealisticCombat->integer > 1 )
1260 	{
1261 		return;
1262 	}
1263 
1264 	//FIXME: if hit their saber in WP_SaberDamageForTrace, need to still do knockback on them...
1265 	float knockBackScale = 0.0f;
1266 	if ( attacker && attacker->client )
1267 	{
1268 		if ( !WP_SaberBladeUseSecondBladeStyle( &attacker->client->ps.saber[saberNum], bladeNum )
1269 			&& attacker->client->ps.saber[saberNum].knockbackScale > 0.0f )
1270 		{
1271 			knockBackScale = attacker->client->ps.saber[saberNum].knockbackScale;
1272 		}
1273 		else if ( WP_SaberBladeUseSecondBladeStyle( &attacker->client->ps.saber[saberNum], bladeNum )
1274 			&& attacker->client->ps.saber[saberNum].knockbackScale2 > 0.0f )
1275 		{
1276 			knockBackScale = attacker->client->ps.saber[saberNum].knockbackScale2;
1277 		}
1278 	}
1279 
1280 	for ( int i = 0; i < numVictims; i++ )
1281 	{
1282 		if ( victimEntityNum[i] == entityNum )
1283 		{
1284 			//hold on a sec, let's still do any accumulated knockback
1285 			if ( knockBackScale )
1286 			{
1287 				gentity_t *victim = &g_entities[victimEntityNum[i]];
1288 				if ( victim && victim->client )
1289 				{
1290 					vec3_t center, dirToCenter;
1291 					float	knockDownThreshHold, knockback = knockBackScale * totalDmg[i] * 0.5f;
1292 
1293 					VectorAdd( victim->absmin, victim->absmax, center );
1294 					VectorScale( center, 0.5, center );
1295 					VectorSubtract( victim->currentOrigin, saberHitLocation, dirToCenter );
1296 					VectorNormalize( dirToCenter );
1297 					G_Throw( victim, dirToCenter, knockback );
1298 					if ( victim->client->ps.groundEntityNum != ENTITYNUM_NONE
1299 						&& dirToCenter[2] <= 0  )
1300 					{//hit downward on someone who is standing on firm ground, so more likely to knock them down
1301 						knockDownThreshHold = Q_irand( 25, 50 );
1302 					}
1303 					else
1304 					{
1305 						knockDownThreshHold = Q_irand( 75, 125 );
1306 					}
1307 
1308 					if ( knockback > knockDownThreshHold )
1309 					{
1310 						G_Knockdown( victim, attacker, dirToCenter, 350, qtrue );
1311 					}
1312 				}
1313 			}
1314 			//now clear everything
1315 			totalDmg[i] = 0;//no damage
1316 			hitLoc[i] = HL_NONE;
1317 			hitDismemberLoc[i] = HL_NONE;
1318 			hitDismember[i] = qfalse;
1319 			victimEntityNum[i] = ENTITYNUM_NONE;//like we never hit him
1320 		}
1321 	}
1322 }
1323 
1324 extern float damageModifier[];
1325 extern float hitLocHealthPercentage[];
WP_SaberApplyDamage(gentity_t * ent,float baseDamage,int baseDFlags,qboolean brokenParry,int saberNum,int bladeNum,qboolean thrownSaber)1326 qboolean WP_SaberApplyDamage( gentity_t *ent, float baseDamage, int baseDFlags,
1327 							 qboolean brokenParry, int saberNum, int bladeNum, qboolean thrownSaber )
1328 {
1329 	qboolean	didDamage = qfalse;
1330 	gentity_t	*victim;
1331 	int			dFlags = baseDFlags;
1332 	float		maxDmg;
1333 	saberType_t saberType = ent->client->ps.saber[saberNum].type;
1334 
1335 	if ( !numVictims )
1336 	{
1337 		return qfalse;
1338 	}
1339 	for ( int i = 0; i < numVictims; i++ )
1340 	{
1341 		dFlags = baseDFlags|DAMAGE_DEATH_KNOCKBACK|DAMAGE_NO_HIT_LOC;
1342 		if ( victimEntityNum[i] != ENTITYNUM_NONE && &g_entities[victimEntityNum[i]] != NULL )
1343 		{	// Don't bother with this damage if the fraction is higher than the saber's fraction
1344 			if ( dmgFraction[i] < saberHitFraction || brokenParry )
1345 			{
1346 				victim = &g_entities[victimEntityNum[i]];
1347 				if ( !victim )
1348 				{
1349 					continue;
1350 				}
1351 
1352 				if ( victim->e_DieFunc == dieF_maglock_die )
1353 				{//*sigh*, special check for maglocks
1354 					vec3_t testFrom;
1355 					if ( ent->client->ps.saberInFlight )
1356 					{
1357 						VectorCopy( g_entities[ent->client->ps.saberEntityNum].currentOrigin, testFrom );
1358 					}
1359 					else
1360 					{
1361 						VectorCopy( ent->currentOrigin, testFrom );
1362 					}
1363 					testFrom[2] = victim->currentOrigin[2];
1364 					trace_t testTrace;
1365 					gi.trace( &testTrace, testFrom, vec3_origin, vec3_origin, victim->currentOrigin, ent->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
1366 					if ( testTrace.entityNum != victim->s.number )
1367 					{//can only damage maglocks if have a clear trace to the thing's origin
1368 						continue;
1369 					}
1370 				}
1371 				if ( totalDmg[i] > 0 )
1372 				{//actually want to do *some* damage here
1373 					if ( victim->client
1374 						&& victim->client->NPC_class==CLASS_WAMPA
1375 						&& victim->activator == ent )
1376 					{
1377 					}
1378 					else if ( PM_SuperBreakWinAnim( ent->client->ps.torsoAnim )
1379 						|| PM_StabDownAnim( ent->client->ps.torsoAnim ) )
1380 					{//never cap the superbreak wins
1381 					}
1382 					else
1383 					{
1384 						if ( victim->client
1385 							&& (victim->s.weapon == WP_SABER || (victim->client->NPC_class==CLASS_REBORN) || (victim->client->NPC_class==CLASS_WAMPA))
1386 							&& !g_saberRealisticCombat->integer )
1387 						{//dmg vs other saber fighters is modded by hitloc and capped
1388 							totalDmg[i] *= damageModifier[hitLoc[i]];
1389 							if ( hitLoc[i] == HL_NONE )
1390 							{
1391 								maxDmg = 33*baseDamage;
1392 							}
1393 							else
1394 							{
1395 								maxDmg = 50*hitLocHealthPercentage[hitLoc[i]]*baseDamage;//*victim->client->ps.stats[STAT_MAX_HEALTH]*2.0f;
1396 							}
1397 							if ( maxDmg < totalDmg[i] )
1398 							{
1399 								totalDmg[i] = maxDmg;
1400 							}
1401 							//dFlags |= DAMAGE_NO_HIT_LOC;
1402 						}
1403 						//clamp the dmg
1404 						if ( victim->s.weapon != WP_SABER )
1405 						{//clamp the dmg between 25 and maxhealth
1406 							/*
1407 							if ( totalDmg[i] > victim->max_health )
1408 							{
1409 								totalDmg[i] = victim->max_health;
1410 							}
1411 							else */if ( totalDmg[i] < 25 )
1412 							{
1413 								totalDmg[i] = 25;
1414 							}
1415 							if ( totalDmg[i] > 100 )//+(50*g_spskill->integer) )
1416 							{//clamp using same adjustment as in NPC_Begin
1417 								totalDmg[i] = 100;//+(50*g_spskill->integer);
1418 							}
1419 						}
1420 						else
1421 						{//clamp the dmg between 5 and 100
1422 							if ( !victim->s.number && totalDmg[i] > 50 )
1423 							{//never do more than half full health damage to player
1424 								//prevents one-hit kills
1425 								totalDmg[i] = 50;
1426 							}
1427 							else if ( totalDmg[i] > 100 )
1428 							{
1429 								totalDmg[i] = 100;
1430 							}
1431 							else
1432 							{
1433 								if ( totalDmg[i] < 5 )
1434 								{
1435 									totalDmg[i] = 5;
1436 								}
1437 							}
1438 						}
1439 					}
1440 
1441 					if ( totalDmg[i] > 0 )
1442 					{
1443 						gentity_t *inflictor = ent;
1444 						didDamage = qtrue;
1445 						qboolean vicWasDismembered = qtrue;
1446 						qboolean vicWasAlive = (qboolean)(victim->health>0);
1447 
1448 						if ( baseDamage <= 0.1f )
1449 						{//just get their attention?
1450 							dFlags |= DAMAGE_NO_DAMAGE;
1451 						}
1452 
1453 						if( victim->client )
1454 						{
1455 							if ( victim->client->ps.pm_time > 0 && victim->client->ps.pm_flags & PMF_TIME_KNOCKBACK && victim->client->ps.velocity[2] > 0 )
1456 							{//already being knocked around
1457 								dFlags |= DAMAGE_NO_KNOCKBACK;
1458 							}
1459 							if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
1460 								&& (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_DISMEMBERMENT) )
1461 							{//no dismemberment! (blunt/stabbing weapon?)
1462 							}
1463 							else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
1464 								&& (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_DISMEMBERMENT2) )
1465 							{//no dismemberment! (blunt/stabbing weapon?)
1466 							}
1467 							else
1468 							{
1469 								if ( debug_subdivision->integer || g_saberRealisticCombat->integer )
1470 								{
1471 									dFlags |= DAMAGE_DISMEMBER;
1472 									if ( hitDismember[i] )
1473 									{
1474 										victim->client->dismembered = false;
1475 									}
1476 								}
1477 								else if ( hitDismember[i] )
1478 								{
1479 									dFlags |= DAMAGE_DISMEMBER;
1480 								}
1481 								if ( !victim->client->dismembered )
1482 								{
1483 									vicWasDismembered = qfalse;
1484 								}
1485 							}
1486 							if ( baseDamage <= 1.0f )
1487 							{//very mild damage
1488 								if ( victim->s.number == 0 || victim->client->ps.weapon == WP_SABER || victim->client->NPC_class == CLASS_GALAKMECH )
1489 								{//if it's the player or a saber-user, don't kill them with this blow
1490 									dFlags |= DAMAGE_NO_KILL;
1491 								}
1492 							}
1493 						}
1494 						else
1495 						{
1496 							if ( victim->takedamage )
1497 							{//some other breakable thing
1498 								//create a flash here
1499 								if ( !g_noClashFlare )
1500 								{
1501 									g_saberFlashTime = level.time-50;
1502 									VectorCopy( dmgSpot[i], g_saberFlashPos );
1503 								}
1504 							}
1505 						}
1506 						if ( !PM_SuperBreakWinAnim( ent->client->ps.torsoAnim )
1507 							&& !PM_StabDownAnim( ent->client->ps.torsoAnim )
1508 							&& !g_saberRealisticCombat->integer
1509 							&& g_saberDamageCapping->integer )
1510 						{//never cap the superbreak wins
1511 							if ( victim->client
1512 								&& victim->s.number >= MAX_CLIENTS )
1513 							{
1514 								if ( victim->client->NPC_class == CLASS_SHADOWTROOPER
1515 									|| ( victim->NPC && (victim->NPC->aiFlags&NPCAI_BOSS_CHARACTER) ) )
1516 								{//hit a boss character
1517 									int maxDmg = ((3-g_spskill->integer)*5)+10;
1518 									if ( totalDmg[i] > maxDmg )
1519 									{
1520 										totalDmg[i] = maxDmg;
1521 									}
1522 								}
1523 								else if ( victim->client->ps.weapon == WP_SABER
1524 									|| victim->client->NPC_class == CLASS_REBORN
1525 									|| victim->client->NPC_class == CLASS_JEDI )
1526 								{//hit a non-boss saber-user
1527 									int maxDmg = ((3-g_spskill->integer)*15)+30;
1528 									if ( totalDmg[i] > maxDmg )
1529 									{
1530 										totalDmg[i] = maxDmg;
1531 									}
1532 								}
1533 							}
1534 							if ( victim->s.number < MAX_CLIENTS
1535 								&& ent->NPC )
1536 							{
1537                                 if ( (ent->NPC->aiFlags&NPCAI_BOSS_CHARACTER)
1538 									|| (ent->NPC->aiFlags&NPCAI_SUBBOSS_CHARACTER)
1539 									|| ent->client->NPC_class == CLASS_SHADOWTROOPER )
1540 								{//player hit by a boss character
1541 									int maxDmg = ((g_spskill->integer+1)*4)+3;
1542 									if ( totalDmg[i] > maxDmg )
1543 									{
1544 										totalDmg[i] = maxDmg;
1545 									}
1546 								}
1547 								else if ( g_spskill->integer < 3 ) //was < 2
1548 								{//player hit by any enemy //on easy or medium?
1549 									int maxDmg = ((g_spskill->integer+1)*10)+20;
1550 									if ( totalDmg[i] > maxDmg )
1551 									{
1552 										totalDmg[i] = maxDmg;
1553 									}
1554 								}
1555 							}
1556 						}
1557 						//victim->hitLoc = hitLoc[i];
1558 
1559 						dFlags |= DAMAGE_NO_KNOCKBACK;//okay, let's try no knockback whatsoever...
1560 						dFlags &= ~DAMAGE_DEATH_KNOCKBACK;
1561 						if ( g_saberRealisticCombat->integer )
1562 						{
1563 							dFlags |= DAMAGE_NO_KNOCKBACK;
1564 							dFlags &= ~DAMAGE_DEATH_KNOCKBACK;
1565 							dFlags &= ~DAMAGE_NO_KILL;
1566 						}
1567 						if ( ent->client && !ent->s.number )
1568 						{
1569 							switch( hitLoc[i] )
1570 							{
1571 							case HL_FOOT_RT:
1572 							case HL_FOOT_LT:
1573 							case HL_LEG_RT:
1574 							case HL_LEG_LT:
1575 								ent->client->sess.missionStats.legAttacksCnt++;
1576 								break;
1577 							case HL_WAIST:
1578 							case HL_BACK_RT:
1579 							case HL_BACK_LT:
1580 							case HL_BACK:
1581 							case HL_CHEST_RT:
1582 							case HL_CHEST_LT:
1583 							case HL_CHEST:
1584 								ent->client->sess.missionStats.torsoAttacksCnt++;
1585 								break;
1586 							case HL_ARM_RT:
1587 							case HL_ARM_LT:
1588 							case HL_HAND_RT:
1589 							case HL_HAND_LT:
1590 								ent->client->sess.missionStats.armAttacksCnt++;
1591 								break;
1592 							default:
1593 								ent->client->sess.missionStats.otherAttacksCnt++;
1594 								break;
1595 							}
1596 						}
1597 
1598 						if ( saberType == SABER_SITH_SWORD )
1599 						{//do knockback
1600 							dFlags &= ~(DAMAGE_NO_KNOCKBACK|DAMAGE_DEATH_KNOCKBACK);
1601 						}
1602 						if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
1603 							&& ent->client->ps.saber[saberNum].knockbackScale > 0.0f )
1604 						{
1605 							dFlags &= ~(DAMAGE_NO_KNOCKBACK|DAMAGE_DEATH_KNOCKBACK);
1606 							if ( saberNum < 1 )
1607 							{
1608 								dFlags |= DAMAGE_SABER_KNOCKBACK1;
1609 							}
1610 							else
1611 							{
1612 								dFlags |= DAMAGE_SABER_KNOCKBACK2;
1613 							}
1614 						}
1615 						else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
1616 							&& ent->client->ps.saber[saberNum].knockbackScale2 > 0.0f )
1617 						{
1618 							dFlags &= ~(DAMAGE_NO_KNOCKBACK|DAMAGE_DEATH_KNOCKBACK);
1619 							if ( saberNum < 1 )
1620 							{
1621 								dFlags |= DAMAGE_SABER_KNOCKBACK1_B2;
1622 							}
1623 							else
1624 							{
1625 								dFlags |= DAMAGE_SABER_KNOCKBACK2_B2;
1626 							}
1627 						}
1628 						if ( thrownSaber )
1629 						{
1630 							inflictor = &g_entities[ent->client->ps.saberEntityNum];
1631 						}
1632 						int damage = 0;
1633 						if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
1634 							&& ent->client->ps.saber[saberNum].damageScale != 1.0f )
1635 						{
1636 							damage = ceil(totalDmg[i]*ent->client->ps.saber[saberNum].damageScale);
1637 						}
1638 						else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
1639 							&& ent->client->ps.saber[saberNum].damageScale2 != 1.0f )
1640 						{
1641 							damage = ceil(totalDmg[i]*ent->client->ps.saber[saberNum].damageScale2);
1642 						}
1643 						else
1644 						{
1645 							damage = ceil(totalDmg[i]);
1646 						}
1647 						G_Damage( victim, inflictor, ent, dmgDir[i], dmgSpot[i], damage, dFlags, MOD_SABER, hitDismemberLoc[i] );
1648 						if ( damage > 0 && cg.time )
1649 						{
1650 							float sizeTimeScale = 1.0f;
1651 							if ( (vicWasAlive
1652 								  && victim->health <= 0 )
1653 								|| (!vicWasDismembered
1654 									&& victim->client->dismembered
1655 									&& hitDismemberLoc[i] != HL_NONE
1656 									&& hitDismember[i]) )
1657 							{
1658 								sizeTimeScale = 3.0f;
1659 							}
1660 							//FIXME: if not hitting the first model on the enemy, don't do this!
1661 							CG_SaberDoWeaponHitMarks( ent->client,
1662 								(ent->client->ps.saberInFlight?&g_entities[ent->client->ps.saberEntityNum]:NULL),
1663 								victim,
1664 								saberNum,
1665 								bladeNum,
1666 								dmgSpot[i],
1667 								dmgDir[i],
1668 								dmgBladeVec[i],
1669 								dmgNormal[i],
1670 								sizeTimeScale );
1671 						}
1672 #ifndef FINAL_BUILD
1673 						if ( d_saberCombat->integer )
1674 						{
1675 							if ( (dFlags&DAMAGE_NO_DAMAGE) )
1676 							{
1677 								gi.Printf( S_COLOR_RED"damage: fake, hitLoc %d\n", hitLoc[i] );
1678 							}
1679 							else
1680 							{
1681 								gi.Printf( S_COLOR_RED"damage: %4.2f, hitLoc %d\n", totalDmg[i], hitLoc[i] );
1682 							}
1683 						}
1684 #endif
1685 						//do the effect
1686 						//vec3_t splashBackDir;
1687 						//VectorScale( dmgNormal[i], -1, splashBackDir );
1688 						//G_PlayEffect( G_EffectIndex( "blood_sparks" ), dmgSpot[i], splashBackDir );
1689 						if ( ent->s.number == 0 )
1690 						{
1691 							AddSoundEvent( victim->owner, dmgSpot[i], 256, AEL_DISCOVERED );
1692 							AddSightEvent( victim->owner, dmgSpot[i], 512, AEL_DISCOVERED, 50 );
1693 						}
1694 						if ( ent->client )
1695 						{
1696 							if ( ent->enemy && ent->enemy == victim )
1697 							{//just so Jedi knows that he hit his enemy
1698 								ent->client->ps.saberEventFlags |= SEF_HITENEMY;
1699 							}
1700 							else
1701 							{
1702 								ent->client->ps.saberEventFlags |= SEF_HITOBJECT;
1703 							}
1704 						}
1705 					}
1706 				}
1707 			}
1708 		}
1709 	}
1710 	return didDamage;
1711 }
1712 
WP_SaberDamageAdd(float trDmg,int trVictimEntityNum,vec3_t trDmgDir,vec3_t trDmgBladeVec,vec3_t trDmgNormal,vec3_t trDmgSpot,float dmg,float fraction,int trHitLoc,qboolean trDismember,int trDismemberLoc)1713 void WP_SaberDamageAdd( float trDmg, int trVictimEntityNum, vec3_t trDmgDir, vec3_t trDmgBladeVec, vec3_t trDmgNormal, vec3_t trDmgSpot, float dmg, float fraction, int trHitLoc, qboolean trDismember, int trDismemberLoc )
1714 {
1715 	int curVictim = 0;
1716 	int i;
1717 
1718 	if ( trVictimEntityNum < 0 || trVictimEntityNum >= ENTITYNUM_WORLD )
1719 	{
1720 		return;
1721 	}
1722 	if ( trDmg * dmg < 10.0f )
1723 	{//too piddly an amount of damage to really count?
1724 		//FIXME: but already did the effect, didn't we... sigh...
1725 		//return;
1726 	}
1727 	if ( trDmg )
1728 	{//did some damage to something
1729 		for ( i = 0; i < numVictims; i++ )
1730 		{
1731 			if ( victimEntityNum[i] == trVictimEntityNum )
1732 			{//already hit this guy before
1733 				curVictim = i;
1734 				break;
1735 			}
1736 		}
1737 		if ( i == numVictims )
1738 		{//haven't hit his guy before
1739 			if ( numVictims + 1 >= MAX_SABER_VICTIMS )
1740 			{//can't add another victim at this time
1741 				return;
1742 			}
1743 			//add a new victim to the list
1744 			curVictim = numVictims;
1745 			victimEntityNum[numVictims++] = trVictimEntityNum;
1746 		}
1747 
1748 		float addDmg = trDmg*dmg;
1749 		if ( trHitLoc!=HL_NONE && (hitLoc[curVictim]==HL_NONE||hitLocHealthPercentage[trHitLoc]>hitLocHealthPercentage[hitLoc[curVictim]]) )
1750 		{//this hitLoc is more critical than the previous one this frame
1751 			hitLoc[curVictim] = trHitLoc;
1752 		}
1753 
1754 		totalDmg[curVictim] += addDmg;
1755 		if ( !VectorLengthSquared( dmgDir[curVictim] ) )
1756 		{
1757 			VectorCopy( trDmgDir, dmgDir[curVictim] );
1758 		}
1759 		if ( !VectorLengthSquared( dmgBladeVec[curVictim] ) )
1760 		{
1761 			VectorCopy( trDmgBladeVec, dmgBladeVec[curVictim] );
1762 		}
1763 		if ( !VectorLengthSquared( dmgNormal[curVictim] ) )
1764 		{
1765 			VectorCopy( trDmgNormal, dmgNormal[curVictim] );
1766 		}
1767 		if ( !VectorLengthSquared( dmgSpot[curVictim] ) )
1768 		{
1769 			VectorCopy( trDmgSpot, dmgSpot[curVictim] );
1770 		}
1771 
1772 		// Make sure we keep track of the fraction.  Why?
1773 		// Well, if the saber hits something that stops it, the damage isn't done past that point.
1774 		dmgFraction[curVictim] = fraction;
1775 		if ( (trDismemberLoc != HL_NONE && hitDismemberLoc[curVictim] == HL_NONE)
1776 			|| (!hitDismember[curVictim] && trDismember) )
1777 		{//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
1778 			hitDismemberLoc[curVictim] = trDismemberLoc;
1779 		}
1780 		if ( trDismember )
1781 		{//we scored a dismemberment hit...
1782 			hitDismember[curVictim] = trDismember;
1783 		}
1784 	}
1785 }
1786 
1787 /*
1788 WP_SabersIntersect
1789 
1790 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
1791 
1792 FIXME: subdivide the arc into a consistant increment
1793 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)?
1794 */
1795 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,int ent1SaberNum,int ent1BladeNum,gentity_t * ent2,qboolean checkDir)1796 qboolean WP_SabersIntersect( gentity_t *ent1, int ent1SaberNum, int ent1BladeNum, gentity_t *ent2, qboolean checkDir )
1797 {
1798 	vec3_t	saberBase1, saberTip1, saberBaseNext1, saberTipNext1;
1799 	vec3_t	saberBase2, saberTip2, saberBaseNext2, saberTipNext2;
1800 	int		ent2SaberNum = 0, ent2BladeNum = 0;
1801 	vec3_t	dir;
1802 
1803 	/*
1804 #ifndef FINAL_BUILD
1805 	if ( d_saberCombat->integer )
1806 	{
1807 		gi.Printf( S_COLOR_GREEN"Doing precise saber intersection check\n" );
1808 	}
1809 #endif
1810 	*/
1811 
1812 	if ( !ent1 || !ent2 )
1813 	{
1814 		return qfalse;
1815 	}
1816 	if ( !ent1->client || !ent2->client )
1817 	{
1818 		return qfalse;
1819 	}
1820 	if ( ent1->client->ps.SaberLength() <= 0 || ent2->client->ps.SaberLength() <= 0 )
1821 	{
1822 		return qfalse;
1823 	}
1824 
1825 	for ( ent2SaberNum = 0; ent2SaberNum < MAX_SABERS; ent2SaberNum++ )
1826 	{
1827 		for ( ent2BladeNum = 0; ent2BladeNum < ent2->client->ps.saber[ent2SaberNum].numBlades; ent2BladeNum++ )
1828 		{
1829 			if ( ent2->client->ps.saber[ent2SaberNum].type != SABER_NONE
1830 				&& ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length > 0 )
1831 			{//valid saber and this blade is on
1832 				//if ( ent1->client->ps.saberInFlight )
1833 				{
1834 					VectorCopy( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointOld, saberBase1 );
1835 					VectorCopy( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, saberBaseNext1 );
1836 
1837 					VectorSubtract( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointOld, dir );
1838 					VectorNormalize( dir );
1839 					VectorMA( saberBaseNext1, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext1 );
1840 
1841 					VectorMA( saberBase1, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].length, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDirOld, saberTip1 );
1842 					VectorMA( saberBaseNext1, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].length, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, saberTipNext1 );
1843 
1844 					VectorSubtract( saberTipNext1, saberTip1, dir );
1845 					VectorNormalize( dir );
1846 					VectorMA( saberTipNext1, SABER_EXTRAPOLATE_DIST, dir, saberTipNext1 );
1847 				}
1848 				/*
1849 				else
1850 				{
1851 					VectorCopy( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, saberBase1 );
1852 					VectorCopy( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointNext, saberBaseNext1 );
1853 					VectorMA( saberBase1, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].length, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, saberTip1 );
1854 					VectorMA( saberBaseNext1, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].length, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDirNext, saberTipNext1 );
1855 				}
1856 				*/
1857 
1858 				//if ( ent2->client->ps.saberInFlight )
1859 				{
1860 					VectorCopy( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointOld, saberBase2 );
1861 					VectorCopy( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, saberBaseNext2 );
1862 
1863 					VectorSubtract( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointOld, dir );
1864 					VectorNormalize( dir );
1865 					VectorMA( saberBaseNext2, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext2 );
1866 
1867 					VectorMA( saberBase2, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDirOld, saberTip2 );
1868 					VectorMA( saberBaseNext2, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir, saberTipNext2 );
1869 
1870 					VectorSubtract( saberTipNext2, saberTip2, dir );
1871 					VectorNormalize( dir );
1872 					VectorMA( saberTipNext2, SABER_EXTRAPOLATE_DIST, dir, saberTipNext2 );
1873 				}
1874 				/*
1875 				else
1876 				{
1877 					VectorCopy( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, saberBase2 );
1878 					VectorCopy( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointNext, saberBaseNext2 );
1879 					VectorMA( saberBase2, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir, saberTip2 );
1880 					VectorMA( saberBaseNext2, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDirNext, saberTipNext2 );
1881 				}
1882 				*/
1883 				if ( checkDir )
1884 				{//check the direction of the two swings to make sure the sabers are swinging towards each other
1885 					vec3_t saberDir1, saberDir2;
1886 
1887 					VectorSubtract( saberTipNext1, saberTip1, saberDir1 );
1888 					VectorSubtract( saberTipNext2, saberTip2, saberDir2 );
1889 					VectorNormalize( saberDir1 );
1890 					VectorNormalize( saberDir2 );
1891 					if ( DotProduct( saberDir1, saberDir2 ) > 0.6f )
1892 					{//sabers moving in same dir, probably didn't actually hit
1893 						continue;
1894 					}
1895 					//now check orientation of sabers, make sure they're not parallel or close to it
1896 					float dot = DotProduct( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir );
1897 					if ( dot > 0.9f || dot < -0.9f )
1898 					{//too parallel to really block effectively?
1899 						continue;
1900 					}
1901 				}
1902 
1903 				if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberBaseNext2 ) )
1904 				{
1905 					return qtrue;
1906 				}
1907 				if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberTipNext2 ) )
1908 				{
1909 					return qtrue;
1910 				}
1911 				if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberBaseNext2 ) )
1912 				{
1913 					return qtrue;
1914 				}
1915 				if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberTipNext2 ) )
1916 				{
1917 					return qtrue;
1918 				}
1919 			}
1920 		}
1921 	}
1922 	return qfalse;
1923 }
1924 
WP_SabersDistance(gentity_t * ent1,gentity_t * ent2)1925 float WP_SabersDistance( gentity_t *ent1, gentity_t *ent2 )
1926 {
1927 	vec3_t	saberBaseNext1, saberTipNext1, saberPoint1;
1928 	vec3_t	saberBaseNext2, saberTipNext2, saberPoint2;
1929 
1930 	/*
1931 #ifndef FINAL_BUILD
1932 	if ( d_saberCombat->integer )
1933 	{
1934 		gi.Printf( S_COLOR_GREEN"Doing precise saber intersection check\n" );
1935 	}
1936 #endif
1937 	*/
1938 
1939 	if ( !ent1 || !ent2 )
1940 	{
1941 		return qfalse;
1942 	}
1943 	if ( !ent1->client || !ent2->client )
1944 	{
1945 		return qfalse;
1946 	}
1947 	if ( ent1->client->ps.SaberLength() <= 0 || ent2->client->ps.SaberLength() <= 0 )
1948 	{
1949 		return qfalse;
1950 	}
1951 
1952 	//FIXME: UGH, how do we make this work for multiply-bladed sabers?
1953 
1954 	//if ( ent1->client->ps.saberInFlight )
1955 	{
1956 		VectorCopy( ent1->client->ps.saber[0].blade[0].muzzlePoint, saberBaseNext1 );
1957 		VectorMA( saberBaseNext1, ent1->client->ps.saber[0].blade[0].length, ent1->client->ps.saber[0].blade[0].muzzleDir, saberTipNext1 );
1958 	}
1959 	/*
1960 	else
1961 	{
1962 		VectorCopy( ent1->client->ps.saber[0].blade[0].muzzlePointNext, saberBaseNext1 );
1963 		VectorMA( saberBaseNext1, ent1->client->ps.saber[0].blade[0].length, ent1->client->ps.saber[0].blade[0].muzzleDirNext, saberTipNext1 );
1964 	}
1965 	*/
1966 
1967 	//if ( ent2->client->ps.saberInFlight )
1968 	{
1969 		VectorCopy( ent2->client->ps.saber[0].blade[0].muzzlePoint, saberBaseNext2 );
1970 		VectorMA( saberBaseNext2, ent2->client->ps.saber[0].blade[0].length, ent2->client->ps.saber[0].blade[0].muzzleDir, saberTipNext2 );
1971 	}
1972 	/*
1973 	else
1974 	{
1975 		VectorCopy( ent2->client->ps.saber[0].blade[0].muzzlePointNext, saberBaseNext2 );
1976 		VectorMA( saberBaseNext2, ent2->client->ps.saber[0].blade[0].length, ent2->client->ps.saber[0].blade[0].muzzleDirNext, saberTipNext2 );
1977 	}
1978 	*/
1979 
1980 	float sabersDist = ShortestLineSegBewteen2LineSegs( saberBaseNext1, saberTipNext1, saberBaseNext2, saberTipNext2, saberPoint1, saberPoint2 );
1981 
1982 	//okay, this is a super hack, but makes saber collisions look better from the player point of view
1983 	/*
1984 	if ( sabersDist < 16.0f )
1985 	{
1986 		vec3_t	saberDistDir, saberMidPoint, camLookDir;
1987 
1988 		VectorSubtract( saberPoint2, saberPoint1, saberDistDir );
1989 		VectorMA( saberPoint1, 0.5f, saberDistDir, saberMidPoint );
1990 		VectorSubtract( saberMidPoint, cg.refdef.vieworg, camLookDir );
1991 		VectorNormalize( saberDistDir );
1992 		VectorNormalize( camLookDir );
1993 		float dot = fabs(DotProduct( camLookDir, saberDistDir ));
1994 		sabersDist -= 8.0f*dot;
1995 		if ( sabersDist < 0.0f )
1996 		{
1997 			sabersDist = 0.0f;
1998 		}
1999 	}
2000 	*/
2001 
2002 #ifndef FINAL_BUILD
2003 	if ( d_saberCombat->integer > 2 )
2004 	{
2005 		G_DebugLine( saberPoint1, saberPoint2, FRAMETIME, 0x00ffffff, qtrue );
2006 	}
2007 #endif
2008 	return sabersDist;
2009 }
2010 
WP_SabersIntersection(gentity_t * ent1,gentity_t * ent2,vec3_t intersect)2011 qboolean WP_SabersIntersection( gentity_t *ent1, gentity_t *ent2, vec3_t intersect )
2012 {
2013 	vec3_t	saberBaseNext1, saberTipNext1, saberPoint1;
2014 	vec3_t	saberBaseNext2, saberTipNext2, saberPoint2;
2015 	int		saberNum1, saberNum2, bladeNum1, bladeNum2;
2016 	float	lineSegLength, bestLineSegLength = Q3_INFINITE;
2017 
2018 	if ( !ent1 || !ent2 )
2019 	{
2020 		return qfalse;
2021 	}
2022 	if ( !ent1->client || !ent2->client )
2023 	{
2024 		return qfalse;
2025 	}
2026 	if ( ent1->client->ps.SaberLength() <= 0 || ent2->client->ps.SaberLength() <= 0 )
2027 	{
2028 		return qfalse;
2029 	}
2030 
2031 	//UGH, had to make this work for multiply-bladed sabers
2032 	for ( saberNum1 = 0; saberNum1 < MAX_SABERS; saberNum1++ )
2033 	{
2034 		for ( bladeNum1 = 0; bladeNum1 < ent1->client->ps.saber[saberNum1].numBlades; bladeNum1++ )
2035 		{
2036 			if ( ent1->client->ps.saber[saberNum1].type != SABER_NONE
2037 				&& ent1->client->ps.saber[saberNum1].blade[bladeNum1].length > 0 )
2038 			{//valid saber and this blade is on
2039 				for ( saberNum2 = 0; saberNum2 < MAX_SABERS; saberNum2++ )
2040 				{
2041 					for ( bladeNum2 = 0; bladeNum2 < ent2->client->ps.saber[saberNum2].numBlades; bladeNum2++ )
2042 					{
2043 						if ( ent2->client->ps.saber[saberNum2].type != SABER_NONE
2044 							&& ent2->client->ps.saber[saberNum2].blade[bladeNum2].length > 0 )
2045 						{//valid saber and this blade is on
2046 							VectorCopy( ent1->client->ps.saber[saberNum1].blade[bladeNum1].muzzlePoint, saberBaseNext1 );
2047 							VectorMA( saberBaseNext1, ent1->client->ps.saber[saberNum1].blade[bladeNum1].length, ent1->client->ps.saber[saberNum1].blade[bladeNum1].muzzleDir, saberTipNext1 );
2048 
2049 							VectorCopy( ent2->client->ps.saber[saberNum2].blade[bladeNum2].muzzlePoint, saberBaseNext2 );
2050 							VectorMA( saberBaseNext2, ent2->client->ps.saber[saberNum2].blade[bladeNum2].length, ent2->client->ps.saber[saberNum2].blade[bladeNum2].muzzleDir, saberTipNext2 );
2051 
2052 							lineSegLength = ShortestLineSegBewteen2LineSegs( saberBaseNext1, saberTipNext1, saberBaseNext2, saberTipNext2, saberPoint1, saberPoint2 );
2053 							if ( lineSegLength < bestLineSegLength )
2054 							{
2055 								bestLineSegLength = lineSegLength;
2056 								VectorAdd( saberPoint1, saberPoint2, intersect );
2057 								VectorScale( intersect, 0.5, intersect );
2058 							}
2059 						}
2060 					}
2061 				}
2062 			}
2063 		}
2064 	}
2065 	return qtrue;
2066 }
2067 
2068 const char *hit_blood_sparks = "sparks/blood_sparks2"; // could have changed this effect directly, but this is just safer in case anyone anywhere else is using the old one for something?
2069 const char *hit_sparks = "saber/saber_cut";
2070 
WP_SaberDamageEffects(trace_t * tr,const vec3_t start,float length,float dmg,vec3_t dmgDir,vec3_t bladeVec,int enemyTeam,saberType_t saberType,saberInfo_t * saber,int bladeNum)2071 qboolean WP_SaberDamageEffects( trace_t *tr, const vec3_t start, float length, float dmg, vec3_t dmgDir, vec3_t bladeVec, int enemyTeam, saberType_t saberType, saberInfo_t *saber, int bladeNum )
2072 {
2073 
2074 	int			hitEntNum[MAX_G2_COLLISIONS];
2075 	for ( int hen = 0; hen < MAX_G2_COLLISIONS; hen++ )
2076 	{
2077 		hitEntNum[hen] = ENTITYNUM_NONE;
2078 	}
2079 	//NOTE: = {0} does NOT work on anything but bytes?
2080 	float		hitEntDmgAdd[MAX_G2_COLLISIONS] = {0};
2081 	float		hitEntDmgSub[MAX_G2_COLLISIONS] = {0};
2082 	vec3_t		hitEntPoint[MAX_G2_COLLISIONS];
2083 	vec3_t		hitEntNormal[MAX_G2_COLLISIONS];
2084 	vec3_t		bladeDir;
2085 	float		hitEntStartFrac[MAX_G2_COLLISIONS] = {0};
2086 	int			trHitLoc[MAX_G2_COLLISIONS] = {HL_NONE};//same as 0
2087 	int			trDismemberLoc[MAX_G2_COLLISIONS] = {HL_NONE};//same as 0
2088 	qboolean	trDismember[MAX_G2_COLLISIONS] = {qfalse};//same as 0
2089 	int			i,z;
2090 	int			numHitEnts = 0;
2091 	float		distFromStart,doDmg;
2092 	int			hitEffect = 0;
2093 	const char	*trSurfName;
2094 	gentity_t	*hitEnt;
2095 
2096 	VectorNormalize2( bladeVec, bladeDir );
2097 
2098 	for (z=0; z < MAX_G2_COLLISIONS; z++)
2099 	{
2100 		if ( tr->G2CollisionMap[z].mEntityNum == -1 )
2101 		{//actually, completely break out of this for loop since nothing after this in the aray should ever be valid either
2102 			continue;//break;//
2103 		}
2104 
2105 		CCollisionRecord &coll	= tr->G2CollisionMap[z];
2106 		//distFromStart			= Distance( start, coll.mCollisionPosition );
2107 		distFromStart			= coll.mDistance;
2108 
2109 		/*
2110 		//FIXME: (distFromStart/length) is not guaranteed to be from 0 to 1... *sigh*...
2111 		if ( length && saberHitFraction < 1.0f && (distFromStart/length) < 1.0f && (distFromStart/length) > saberHitFraction )
2112 		{//a saber was hit before this point, don't count it
2113 #ifndef FINAL_BUILD
2114 			if ( d_saberCombat->integer )
2115 			{
2116 				gi.Printf( S_COLOR_MAGENTA"rejecting G2 collision- %4.2f farther than saberHitFraction %4.2f\n", (distFromStart/length), saberHitFraction  );
2117 			}
2118 #endif
2119 			continue;
2120 		}
2121 		*/
2122 
2123 		for ( i = 0; i < numHitEnts; i++ )
2124 		{
2125 			if ( hitEntNum[i] == coll.mEntityNum )
2126 			{//we hit this ent before
2127 				//we'll want to add this dist
2128 				hitEntDmgAdd[i] = distFromStart;
2129 				break;
2130 			}
2131 		}
2132 		if ( i == numHitEnts )
2133 		{//first time we hit this ent
2134 			if ( numHitEnts == MAX_G2_COLLISIONS )
2135 			{//hit too many damn ents!
2136 				continue;
2137 			}
2138 			hitEntNum[numHitEnts] = coll.mEntityNum;
2139 			if ( !coll.mFlags )
2140 			{//hmm, we came out first, so we must have started inside
2141 				//we'll want to subtract this dist
2142 				hitEntDmgAdd[numHitEnts] = distFromStart;
2143 			}
2144 			else
2145 			{//we're entering the model
2146 				//we'll want to subtract this dist
2147 				hitEntDmgSub[numHitEnts] = distFromStart;
2148 			}
2149 			//keep track of how far in the damage was done
2150 			hitEntStartFrac[numHitEnts] = hitEntDmgSub[numHitEnts]/length;
2151 			//remember the entrance point
2152 			VectorCopy( coll.mCollisionPosition, hitEntPoint[numHitEnts] );
2153 			//remember the normal of the face we hit
2154 			VectorCopy( coll.mCollisionNormal, hitEntNormal[numHitEnts] );
2155 			VectorNormalize( hitEntNormal[numHitEnts] );
2156 
2157 			//do the effect
2158 
2159 			//FIXME: check material rather than team?
2160 			hitEnt = &g_entities[hitEntNum[numHitEnts]];
2161 			if ( hitEnt
2162 				&& hitEnt->client
2163 				&& coll.mModelIndex > 0 )
2164 			{//hit a submodel on the enemy, not their actual body!
2165 				if ( !WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2166 					&& saber->hitOtherEffect )
2167 				{
2168 					hitEffect = saber->hitOtherEffect;
2169 				}
2170 				else if ( WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2171 					&& saber->hitOtherEffect2 )
2172 				{
2173 					hitEffect = saber->hitOtherEffect2;
2174 				}
2175 				else
2176 				{
2177 					hitEffect = G_EffectIndex( hit_sparks );
2178 				}
2179 			}
2180 			else
2181 			{
2182 				if ( !WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2183 					&& saber->hitPersonEffect )
2184 				{
2185 					hitEffect = saber->hitPersonEffect;
2186 				}
2187 				else if ( WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2188 					&& saber->hitPersonEffect2 )
2189 				{
2190 					hitEffect = saber->hitPersonEffect2;
2191 				}
2192 				else
2193 				{
2194 					hitEffect = G_EffectIndex( hit_blood_sparks );
2195 				}
2196 			}
2197 			if ( hitEnt != NULL )
2198 			{
2199 				if ( hitEnt->client )
2200 				{
2201 					class_t npc_class = hitEnt->client->NPC_class;
2202 					if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE || npc_class == CLASS_REMOTE ||
2203 					     npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 ||
2204 					     npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 ||
2205 					     npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY )
2206 					{
2207 						if ( !WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2208 							&& saber->hitOtherEffect )
2209 						{
2210 							hitEffect = saber->hitOtherEffect;
2211 						}
2212 						else if ( WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2213 							&& saber->hitOtherEffect2 )
2214 						{
2215 							hitEffect = saber->hitOtherEffect2;
2216 						}
2217 						else
2218 						{
2219 							hitEffect = G_EffectIndex( hit_sparks );
2220 						}
2221 					}
2222 				}
2223 				else
2224 				{
2225 					// So sue me, this is the easiest way to check to see if this is the turbo laser from t2_wedge,
2226 					// in which case I don't want the saber effects goin off on it.
2227 					if ( (hitEnt->flags&FL_DMG_BY_HEAVY_WEAP_ONLY)
2228 						&& hitEnt->takedamage == qfalse
2229 						&& Q_stricmp( hitEnt->classname, "misc_turret" ) == 0 )
2230 					{
2231 						continue;
2232 					}
2233 					else
2234 					{
2235 						if ( dmg )
2236 						{//only do these effects if actually trying to damage the thing...
2237 							if ( (hitEnt->svFlags&SVF_BBRUSH)//a breakable brush
2238 								&& ( (hitEnt->spawnflags&1)//INVINCIBLE
2239 									||(hitEnt->flags&FL_DMG_BY_HEAVY_WEAP_ONLY) )//HEAVY weapon damage only
2240 								)
2241 							{//no hit effect (besides regular client-side one)
2242 								hitEffect = 0;
2243 							}
2244 							else
2245 							{
2246 								if ( !WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2247 									&& saber->hitOtherEffect )
2248 								{
2249 									hitEffect = saber->hitOtherEffect;
2250 								}
2251 								else if ( WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2252 									&& saber->hitOtherEffect2 )
2253 								{
2254 									hitEffect = saber->hitOtherEffect2;
2255 								}
2256 								else
2257 								{
2258 									hitEffect = G_EffectIndex( hit_sparks );
2259 								}
2260 							}
2261 						}
2262 					}
2263 				}
2264 			}
2265 
2266 			//FIXME: play less if damage is less?
2267 			if ( !g_saberNoEffects )
2268 			{
2269 				if ( hitEffect != 0 )
2270 				{
2271 					//FIXME: when you have multiple blades hitting someone for many sequential server frames, this can get a bit chuggy!
2272 					G_PlayEffect( hitEffect, coll.mCollisionPosition, coll.mCollisionNormal );
2273 				}
2274 			}
2275 
2276 			//Get the hit location based on surface name
2277 			if ( (hitLoc[numHitEnts] == HL_NONE && trHitLoc[numHitEnts] == HL_NONE)
2278 				|| (hitDismemberLoc[numHitEnts] == HL_NONE && trDismemberLoc[numHitEnts] == HL_NONE)
2279 				|| (!hitDismember[numHitEnts] && !trDismember[numHitEnts]) )
2280 			{//no hit loc set for this ent this damage cycle yet
2281 				//FIXME: find closest impact surf *first* (per ent), then call G_GetHitLocFromSurfName?
2282 				//FIXED: if hit multiple ents in this collision record, these trSurfName, trDismember and trDismemberLoc will get stomped/confused over the multiple ents I hit
2283 				trSurfName = gi.G2API_GetSurfaceName( &g_entities[coll.mEntityNum].ghoul2[coll.mModelIndex], coll.mSurfaceIndex );
2284 				trDismember[numHitEnts] = G_GetHitLocFromSurfName( &g_entities[coll.mEntityNum], trSurfName, &trHitLoc[numHitEnts], coll.mCollisionPosition, dmgDir, bladeDir, MOD_SABER, saberType );
2285 				if ( trDismember[numHitEnts] )
2286 				{
2287 					trDismemberLoc[numHitEnts] = trHitLoc[numHitEnts];
2288 				}
2289 				/*
2290 				if ( trDismember[numHitEnts] )
2291 				{
2292 					Com_Printf( S_COLOR_RED"Okay to dismember %s on ent %d\n", hitLocName[trDismemberLoc[numHitEnts]], hitEntNum[numHitEnts] );
2293 				}
2294 				else
2295 				{
2296 					Com_Printf( "Hit (no dismember) %s on ent %d\n", hitLocName[trHitLoc[numHitEnts]], hitEntNum[numHitEnts] );
2297 				}
2298 				*/
2299 			}
2300 			numHitEnts++;
2301 		}
2302 	}
2303 
2304 	//now go through all the ents we hit and do the damage
2305 	for ( i = 0; i < numHitEnts; i++ )
2306 	{
2307 		doDmg = dmg;
2308 		if ( hitEntNum[i] != ENTITYNUM_NONE )
2309 		{
2310 			if ( doDmg < 10 )
2311 			{//base damage is less than 10
2312 				if ( hitEntNum[i] != 0 )
2313 				{//not the player
2314 					hitEnt = &g_entities[hitEntNum[i]];
2315 					if ( !hitEnt->client || (hitEnt->client->ps.weapon!=WP_SABER&&hitEnt->client->NPC_class!=CLASS_GALAKMECH&&hitEnt->client->playerTeam==enemyTeam) )
2316 					{//did *not* hit a jedi and did *not* hit the player
2317 						//make sure the base damage is high against non-jedi, feels better
2318 						doDmg = 10;
2319 					}
2320 				}
2321 			}
2322 			if ( !hitEntDmgAdd[i] && !hitEntDmgSub[i] )
2323 			{//spent entire time in model
2324 				//NOTE: will we even get a collision then?
2325 				doDmg *= length;
2326 			}
2327 			else if ( hitEntDmgAdd[i] && hitEntDmgSub[i] )
2328 			{//we did enter and exit
2329 				doDmg *= hitEntDmgAdd[i] - hitEntDmgSub[i];
2330 			}
2331 			else if ( !hitEntDmgAdd[i] )
2332 			{//we didn't exit, just entered
2333 				doDmg *= length - hitEntDmgSub[i];
2334 			}
2335 			else if ( !hitEntDmgSub[i] )
2336 			{//we didn't enter, only exited
2337 				doDmg *= hitEntDmgAdd[i];
2338 			}
2339 			if ( doDmg > 0 )
2340 			{
2341 				WP_SaberDamageAdd( 1.0, hitEntNum[i], dmgDir, bladeVec, hitEntNormal[i], hitEntPoint[i], ceil(doDmg), hitEntStartFrac[i], trHitLoc[i], trDismember[i], trDismemberLoc[i] );
2342 			}
2343 		}
2344 	}
2345 	return (qboolean)(numHitEnts>0);
2346 }
2347 
WP_SaberBlockEffect(gentity_t * attacker,int saberNum,int bladeNum,vec3_t position,vec3_t normal,qboolean cutNotBlock)2348 void WP_SaberBlockEffect( gentity_t *attacker, int saberNum, int bladeNum, vec3_t position, vec3_t normal, qboolean cutNotBlock )
2349 {
2350 	saberInfo_t *saber = NULL;
2351 
2352 	if ( attacker && attacker->client )
2353 	{
2354 		saber = &attacker->client->ps.saber[saberNum];
2355 	}
2356 
2357 	if ( saber
2358 		&& !WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2359 		&& saber->blockEffect )
2360 	{
2361 		if ( normal )
2362 		{
2363 			G_PlayEffect( saber->blockEffect, position, normal );
2364 		}
2365 		else
2366 		{
2367 			G_PlayEffect( saber->blockEffect, position );
2368 		}
2369 	}
2370 	else if ( saber
2371 		&& WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2372 		&& saber->blockEffect2 )
2373 	{
2374 		if ( normal )
2375 		{
2376 			G_PlayEffect( saber->blockEffect2, position, normal );
2377 		}
2378 		else
2379 		{
2380 			G_PlayEffect( saber->blockEffect2, position );
2381 		}
2382 	}
2383 	else if ( cutNotBlock )
2384 	{
2385 		if ( normal )
2386 		{
2387 			G_PlayEffect( "saber/saber_cut", position, normal );
2388 		}
2389 		else
2390 		{
2391 			G_PlayEffect( "saber/saber_cut", position );
2392 		}
2393 	}
2394 	else
2395 	{
2396 
2397 		if ( normal )
2398 		{
2399 			G_PlayEffect( "saber/saber_block", position, normal );
2400 		}
2401 		else
2402 		{
2403 			G_PlayEffect( "saber/saber_block", position );
2404 		}
2405 	}
2406 }
2407 
WP_SaberKnockaway(gentity_t * attacker,trace_t * tr)2408 void WP_SaberKnockaway( gentity_t *attacker, trace_t *tr )
2409 {
2410 	WP_SaberDrop( attacker, &g_entities[attacker->client->ps.saberEntityNum] );
2411 	WP_SaberBlockSound( attacker, NULL, 0, 0 );
2412 	//G_Sound( &g_entities[attacker->client->ps.saberEntityNum], G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
2413 	WP_SaberBlockEffect( attacker, 0, 0, tr->endpos, NULL, qfalse );
2414 	saberHitFraction = tr->fraction;
2415 #ifndef FINAL_BUILD
2416 	if ( d_saberCombat->integer )
2417 	{
2418 		gi.Printf( S_COLOR_MAGENTA"WP_SaberKnockaway: saberHitFraction %4.2f\n", saberHitFraction );
2419 	}
2420 #endif
2421 	VectorCopy( tr->endpos, saberHitLocation );
2422 	saberHitEntity = tr->entityNum;
2423 	if ( !g_noClashFlare )
2424 	{
2425 		g_saberFlashTime = level.time-50;
2426 		VectorCopy( saberHitLocation, g_saberFlashPos );
2427 	}
2428 
2429 	//FIXME: make hitEnt play an attack anim or some other special anim when this happens
2430 	//gentity_t *hitEnt = &g_entities[tr->entityNum];
2431 	//NPC_SetAnim( hitEnt, SETANIM_BOTH, BOTH_KNOCKSABER, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
2432 }
2433 
G_InCinematicSaberAnim(gentity_t * self)2434 qboolean G_InCinematicSaberAnim( gentity_t *self )
2435 {
2436 	if ( self->NPC
2437 		&& self->NPC->behaviorState == BS_CINEMATIC
2438 		&& (self->client->ps.torsoAnim == BOTH_CIN_16 ||self->client->ps.torsoAnim == BOTH_CIN_17) )
2439 	{
2440 		return qtrue;
2441 	}
2442 	return qfalse;
2443 }
2444 
2445 #define SABER_COLLISION_DIST 6//was 2//was 4//was 8//was 16
2446 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,saberType_t saberType,qboolean extrapolate,int saberNum,int bladeNum)2447 qboolean WP_SaberDamageForTrace( int ignore, vec3_t start, vec3_t end, float dmg,
2448 								vec3_t bladeDir, qboolean noGhoul, int attackStrength,
2449 								saberType_t saberType, qboolean extrapolate,
2450 								int saberNum, int bladeNum )
2451 {
2452 	trace_t		tr;
2453 	vec3_t		dir;
2454 	int			mask = (MASK_SHOT|CONTENTS_LIGHTSABER);
2455 	gentity_t	*attacker = &g_entities[ignore];
2456 
2457 	vec3_t		end2;
2458 	VectorCopy( end, end2 );
2459 	if ( extrapolate )
2460 	{
2461 		//NOTE: since we can no longer use the predicted point, extrapolate the trace some.
2462 		//		this may allow saber hits that aren't actually hits, but it doesn't look too bad
2463 		vec3_t diff;
2464 		VectorSubtract( end, start, diff );
2465 		VectorNormalize( diff );
2466 		VectorMA( end2, SABER_EXTRAPOLATE_DIST, diff, end2 );
2467 	}
2468 
2469 	if ( !noGhoul )
2470 	{
2471 		float useRadiusForDamage = 0;
2472 
2473 		if ( attacker
2474 			&& attacker->client )
2475 		{//see if we're not drawing the blade, if so, do a trace based on radius of blade (because the radius is being used to simulate a larger/smaller piece of a solid weapon)...
2476 			if ( !WP_SaberBladeUseSecondBladeStyle( &attacker->client->ps.saber[saberNum], bladeNum )
2477 				&& (attacker->client->ps.saber[saberNum].saberFlags2&SFL2_NO_BLADE) )
2478 			{//not drawing blade
2479 				useRadiusForDamage = attacker->client->ps.saber[saberNum].blade[bladeNum].radius;
2480 			}
2481 			else if ( WP_SaberBladeUseSecondBladeStyle( &attacker->client->ps.saber[saberNum], bladeNum )
2482 				&& (attacker->client->ps.saber[saberNum].saberFlags2&SFL2_NO_BLADE2) )
2483 			{//not drawing blade
2484 				useRadiusForDamage = attacker->client->ps.saber[saberNum].blade[bladeNum].radius;
2485 			}
2486 		}
2487 		if ( !useRadiusForDamage )
2488 		{//do normal check for larger-size saber traces
2489 			if ( !attacker->s.number
2490 				|| (attacker->client
2491 					&& (attacker->client->playerTeam==TEAM_PLAYER
2492 						|| attacker->client->NPC_class==CLASS_SHADOWTROOPER
2493 						|| attacker->client->NPC_class==CLASS_ALORA
2494 						|| (attacker->NPC && (attacker->NPC->aiFlags&NPCAI_BOSS_CHARACTER))
2495 						)
2496 					)
2497 				)//&&attackStrength==FORCE_LEVEL_3)
2498 			{
2499 				useRadiusForDamage = 2;
2500 			}
2501 		}
2502 
2503 		if ( useRadiusForDamage > 0 )//&&attackStrength==FORCE_LEVEL_3)
2504 		{//player,. player allies, shadowtroopers, tavion and desann use larger traces
2505 			vec3_t	traceMins = {-useRadiusForDamage,-useRadiusForDamage,-useRadiusForDamage}, traceMaxs = {useRadiusForDamage,useRadiusForDamage,useRadiusForDamage};
2506 			gi.trace( &tr, start, traceMins, traceMaxs, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX
2507 		}
2508 		/*
2509 		else if ( !attacker->s.number )
2510 		{
2511 			vec3_t	traceMins = {-1,-1,-1}, traceMaxs = {1,1,1};
2512 			gi.trace( &tr, start, traceMins, traceMaxs, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX
2513 		}
2514 		*/
2515 		else
2516 		{//reborn use smaller traces
2517 			gi.trace( &tr, start, NULL, NULL, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX
2518 		}
2519 	}
2520 	else
2521 	{
2522 		gi.trace( &tr, start, NULL, NULL, end2, ignore, mask, G2_NOCOLLIDE, 10 );
2523 	}
2524 
2525 
2526 #ifndef FINAL_BUILD
2527 	if ( d_saberCombat->integer > 1 )
2528 	{
2529 		if ( attacker != NULL && attacker->client != NULL )
2530 		{
2531 			G_DebugLine(start, end2, FRAMETIME, WPDEBUG_SaberColor( attacker->client->ps.saber[0].blade[0].color ), qtrue);
2532 		}
2533 	}
2534 #endif
2535 
2536 	if ( tr.entityNum == ENTITYNUM_NONE )
2537 	{
2538 		return qfalse;
2539 	}
2540 
2541 	if ( tr.entityNum == ENTITYNUM_WORLD )
2542 	{
2543 		if ( attacker && attacker->client && (attacker->client->ps.saber[saberNum].saberFlags&SFL_BOUNCE_ON_WALLS) )
2544 		{
2545 			VectorCopy( tr.endpos, saberHitLocation );
2546 			VectorCopy( tr.plane.normal, saberHitNormal );
2547 		}
2548 		return qtrue;
2549 	}
2550 
2551 	if ( &g_entities[tr.entityNum] )
2552 	{
2553 		gentity_t *hitEnt = &g_entities[tr.entityNum];
2554 		gentity_t *owner = g_entities[tr.entityNum].owner;
2555 		if ( hitEnt->contents & CONTENTS_LIGHTSABER )
2556 		{
2557 			if ( attacker && attacker->client && attacker->client->ps.saberInFlight )
2558 			{//thrown saber hit something
2559 				 if ( owner
2560 					 && owner->s.number
2561 					 && owner->client
2562 					 && owner->NPC
2563 					 && owner->health > 0 )
2564 				 {
2565 					 if ( owner->client->NPC_class == CLASS_ALORA )
2566 					 {//alora takes less damage
2567 						dmg *= 0.25f;
2568 					 }
2569 					 else if ( owner->client->NPC_class == CLASS_TAVION
2570 							/*|| (owner->client->NPC_class == CLASS_SHADOWTROOPER && !Q_irand( 0, g_spskill->integer*3 ))
2571 							|| (Q_irand( -5, owner->NPC->rank ) > RANK_CIVILIAN && !Q_irand( 0, g_spskill->integer*3 ))*/ )
2572 					{//Tavion can toss a blocked thrown saber aside
2573 						WP_SaberKnockaway( attacker, &tr );
2574 						Jedi_PlayDeflectSound( owner );
2575 						return qfalse;
2576 					}
2577 				}
2578 			}
2579 			//FIXME: take target FP_SABER_DEFENSE and attacker FP_SABER_OFFENSE into account here somehow?
2580 			qboolean sabersIntersect = WP_SabersIntersect( attacker, saberNum, bladeNum, owner, qfalse );//qtrue );
2581 			float sabersDist;
2582 			if ( attacker && attacker->client && attacker->client->ps.saberInFlight
2583 				&& owner && owner->s.number == 0 && (g_saberAutoBlocking->integer||attacker->client->ps.saberBlockingTime>level.time) )//NPC flying saber hit player's saber bounding box
2584 			{//players have g_saberAutoBlocking, do the more generous check against flying sabers
2585 				//FIXME: instead of hitting the player's saber bounding box
2586 				//and picking an anim afterwards, have him use AI similar
2587 				//to the AI the jedi use for picking a saber melee block...?
2588 				sabersDist = 0;
2589 			}
2590 			else
2591 			{//sabers must actually collide with the attacking saber
2592 				sabersDist = WP_SabersDistance( attacker, owner );
2593 				if ( attacker && attacker->client && attacker->client->ps.saberInFlight )
2594 				{
2595 					sabersDist /= 2.0f;
2596 					if ( sabersDist <= 16.0f )
2597 					{
2598 						sabersIntersect = qtrue;
2599 					}
2600 				}
2601 #ifndef FINAL_BUILD
2602 				if ( d_saberCombat->integer > 1 )
2603 				{
2604 					gi.Printf( "sabersDist: %4.2f\n", sabersDist );
2605 				}
2606 #endif//FINAL_BUILD
2607 			}
2608 			if ( sabersCrossed == -1 || sabersCrossed > sabersDist )
2609 			{
2610 				sabersCrossed = sabersDist;
2611 			}
2612 			float	collisionDist;
2613 			if ( g_saberRealisticCombat->integer )
2614 			{
2615 				collisionDist = SABER_COLLISION_DIST;
2616 			}
2617 			else
2618 			{
2619 				collisionDist = SABER_COLLISION_DIST+6+g_spskill->integer*4;
2620 			}
2621 			{
2622 			if ( G_InCinematicSaberAnim( owner )
2623 				&& G_InCinematicSaberAnim( attacker ) )
2624 			{
2625 				sabersIntersect = qtrue;
2626 			}
2627 			}
2628 			if ( owner && owner->client && (attacker != NULL)
2629 				&& (sabersDist > collisionDist )//|| !InFront( attacker->currentOrigin, owner->currentOrigin, owner->client->ps.viewangles, 0.35f ))
2630 				&& !sabersIntersect )//was qtrue, but missed too much?
2631 			{//swing came from behind and/or was not stopped by a lightsaber
2632 				//re-try the trace without checking for lightsabers
2633 				gi.trace ( &tr, start, NULL, NULL, end2, ignore, mask&~CONTENTS_LIGHTSABER, G2_NOCOLLIDE, 10 );
2634 				if ( tr.entityNum == ENTITYNUM_WORLD )
2635 				{
2636  					return qtrue;
2637 				}
2638 				if ( tr.entityNum == ENTITYNUM_NONE || &g_entities[tr.entityNum] == NULL )
2639 				{//didn't hit the owner
2640 					/*
2641 					if ( attacker
2642 						&& attacker->client
2643 						&& (PM_SaberInAttack( attacker->client->ps.saberMove ) || PM_SaberInStart( attacker->client->ps.saberMove ))
2644 						&& DistanceSquared( tr.endpos, owner->currentOrigin ) < 10000 )
2645 					{
2646 						if ( owner->NPC
2647 							&& !owner->client->ps.saberInFlight
2648 							&& owner->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN
2649 							&& !Jedi_SaberBusy( owner ) )
2650 						{//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
2651 							if ( g_spskill->integer && (g_spskill->integer > 1 || Q_irand( 0, 1 )))
2652 							{//if on easy, they don't cheat like this, if on medium, they cheat 50% of the time, if on hard, they always cheat
2653 								//FIXME: also take into account the owner's FP_DEFENSE?
2654 								if ( Q_irand( 0, owner->NPC->rank ) >= RANK_CIVILIAN )
2655 								{//lower-rank Jedi aren't as good blockers
2656 									vec3_t attDir;
2657 									VectorSubtract( end2, start, attDir );
2658 									VectorNormalize( attDir );
2659 									Jedi_SaberBlockGo( owner, owner->NPC->last_ucmd, start, attDir, NULL );
2660 								}
2661 							}
2662 						}
2663 					}
2664 					*/
2665   					return qfalse;	// Exit, but we didn't hit the wall.
2666 				}
2667 #ifndef FINAL_BUILD
2668 				if ( d_saberCombat->integer > 1 )
2669 				{
2670 					if ( !attacker->s.number )
2671 					{
2672 						gi.Printf( S_COLOR_MAGENTA"%d saber hit owner through saber %4.2f, dist = %4.2f\n", level.time, saberHitFraction, sabersDist );
2673 					}
2674 				}
2675 #endif//FINAL_BUILD
2676 				hitEnt = &g_entities[tr.entityNum];
2677 				owner = g_entities[tr.entityNum].owner;
2678 			}
2679 			else
2680 			{//hit a lightsaber
2681 				if ( (tr.fraction < saberHitFraction || tr.startsolid)
2682 					&& sabersDist < (8.0f+g_spskill->value)*4.0f// 50.0f//16.0f
2683 					&& (sabersIntersect || sabersDist < (4.0f+g_spskill->value)*2.0f) )//32.0f) )
2684 				{	// This saber hit closer than the last one.
2685 					if ( (tr.allsolid || tr.startsolid) && owner && owner->client )
2686 					{//tr.fraction will be 0, unreliable... so calculate actual
2687 						float dist = Distance( start, end2 );
2688 						if ( dist )
2689 						{
2690 							float hitFrac = WP_SabersDistance( attacker, owner )/dist;
2691 							if ( hitFrac > 1.0f )
2692 							{//umm... minimum distance between sabers was longer than trace...?
2693 								hitFrac = 1.0f;
2694 							}
2695 							if ( hitFrac < saberHitFraction )
2696 							{
2697 								saberHitFraction = hitFrac;
2698 							}
2699 						}
2700 						else
2701 						{
2702 							saberHitFraction = 0.0f;
2703 						}
2704 #ifndef FINAL_BUILD
2705 						if ( d_saberCombat->integer > 1 )
2706 						{
2707 							if ( !attacker->s.number )
2708 							{
2709 								gi.Printf( S_COLOR_GREEN"%d saber hit saber dist %4.2f allsolid %4.2f\n", level.time, sabersDist, saberHitFraction );
2710 							}
2711 						}
2712 #endif//FINAL_BUILD
2713 					}
2714 					else
2715 					{
2716 #ifndef FINAL_BUILD
2717 						if ( d_saberCombat->integer > 1 )
2718 						{
2719 							if ( !attacker->s.number )
2720 							{
2721 								gi.Printf( S_COLOR_BLUE"%d saber hit saber dist %4.2f, frac %4.2f\n", level.time, sabersDist, saberHitFraction );
2722 							}
2723 							saberHitFraction = tr.fraction;
2724 						}
2725 #endif//FINAL_BUILD
2726 					}
2727 #ifndef FINAL_BUILD
2728 					if ( d_saberCombat->integer )
2729 					{
2730 						gi.Printf( S_COLOR_MAGENTA"hit saber: saberHitFraction %4.2f, allsolid %d, startsolid %d\n", saberHitFraction, tr.allsolid, tr.startsolid );
2731 					}
2732 #endif//FINAL_BUILD
2733 					VectorCopy(tr.endpos, saberHitLocation);
2734 					saberHitEntity = tr.entityNum;
2735 				}
2736 				/*
2737 				if ( owner
2738 					&& owner->client
2739 					&& attacker
2740 					&& attacker->client
2741 					&& (PM_SaberInAttack( attacker->client->ps.saberMove ) || PM_SaberInStart( attacker->client->ps.saberMove ))
2742 					&& DistanceSquared( tr.endpos, owner->currentOrigin ) < 10000 )
2743 				{
2744 					if ( owner->NPC
2745 						&& !owner->client->ps.saberInFlight
2746 						&& owner->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN
2747 						&& !Jedi_SaberBusy( owner ) )
2748 					{//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
2749 						if ( g_spskill->integer && (g_spskill->integer > 1 || Q_irand( 0, 1 )))
2750 						{//if on easy, they don't cheat like this, if on medium, they cheat 50% of the time, if on hard, they always cheat
2751 							//FIXME: also take into account the owner's FP_DEFENSE?
2752 							if ( Q_irand( 0, owner->NPC->rank ) >= RANK_CIVILIAN )
2753 							{//lower-rank Jedi aren't as good blockers
2754 								vec3_t attDir;
2755 								VectorSubtract( end2, start, attDir );
2756 								VectorNormalize( attDir );
2757 								Jedi_SaberBlockGo( owner, owner->NPC->last_ucmd, start, attDir, NULL );
2758 							}
2759 						}
2760 					}
2761 				}
2762 				*/
2763 				//FIXME: check to see if we broke the saber
2764 				//		go through the impacted surfaces and call WP_BreakSaber
2765 				//		PROBLEM: saberEnt doesn't actually have a saber g2 model
2766 				//		and/or isn't in same location as saber model attached
2767 				//		to the client.  We'd have to fake it somehow...
2768   				return qfalse;	// Exit, but we didn't hit the wall.
2769 			}
2770 		}
2771 		else
2772 		{
2773 #ifndef FINAL_BUILD
2774 			if ( d_saberCombat->integer > 1 )
2775 			{
2776 				if ( !attacker->s.number )
2777 				{
2778 					gi.Printf( S_COLOR_RED"%d saber hit owner directly %4.2f\n", level.time, saberHitFraction );
2779 				}
2780 			}
2781 #endif//FINAL_BUILD
2782 		}
2783 
2784 		if ( attacker && attacker->client && attacker->client->ps.saberInFlight )
2785 		{//thrown saber hit something
2786 			if ( ( hitEnt && hitEnt->client && hitEnt->health > 0 && ( hitEnt->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",hitEnt->NPC_type) || hitEnt->client->NPC_class == CLASS_LUKE || hitEnt->client->NPC_class == CLASS_BOBAFETT || (hitEnt->client->ps.powerups[PW_GALAK_SHIELD] > 0) ) ) ||
2787 				 ( owner && owner->client && owner->health > 0 && ( owner->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",owner->NPC_type) || owner->client->NPC_class == CLASS_LUKE || (owner->client->ps.powerups[PW_GALAK_SHIELD] > 0) ) ) )
2788 			{//Luke and Desann slap thrown sabers aside
2789 				//FIXME: control the direction of the thrown saber... if hit Galak's shield, bounce directly away from his origin?
2790 				WP_SaberKnockaway( attacker, &tr );
2791 				if ( hitEnt->client )
2792 				{
2793 					Jedi_PlayDeflectSound( hitEnt );
2794 				}
2795 				else
2796 				{
2797 					Jedi_PlayDeflectSound( owner );
2798 				}
2799   				return qfalse;	// Exit, but we didn't hit the wall.
2800 			}
2801 		}
2802 
2803 		if ( hitEnt->takedamage )
2804 		{
2805 			//no team damage: if ( !hitEnt->client || attacker == NULL || !attacker->client || (hitEnt->client->playerTeam != attacker->client->playerTeam) )
2806 			{
2807 				vec3_t bladeVec={0};
2808 				if ( attacker && attacker->client )
2809 				{
2810 					VectorScale( bladeDir, attacker->client->ps.saber[saberNum].blade[bladeNum].length, bladeVec );
2811 				}
2812 				//multiply the damage by the total distance of the swipe
2813 				VectorSubtract( end2, start, dir );
2814 				float len = VectorNormalize( dir );//VectorLength( dir );
2815 				if ( noGhoul || !hitEnt->ghoul2.size() )
2816 				{//we weren't doing a ghoul trace
2817 					int hitEffect = 0;
2818 					if ( dmg >= 1.0 && hitEnt->bmodel )
2819 					{
2820 						dmg = 1.0;
2821 					}
2822 					if ( len > 1 )
2823 					{
2824 						dmg *= len;
2825 					}
2826 #ifndef FINAL_BUILD
2827 					if ( d_saberCombat->integer > 1 )
2828 					{
2829 						if ( !(hitEnt->contents & CONTENTS_LIGHTSABER) )
2830 						{
2831 							gi.Printf( S_COLOR_GREEN"Hit ent, but no ghoul collisions\n" );
2832 						}
2833 					}
2834 #endif
2835 					float	trFrac, dmgFrac;
2836 					if ( tr.allsolid )
2837 					{//totally inside them
2838 						trFrac = 1.0;
2839 						dmgFrac = 0.0;
2840 					}
2841 					else if ( tr.startsolid )
2842 					{//started inside them
2843 						//we don't know how much was inside, we know it's less than all, so use half?
2844 						trFrac = 0.5;
2845 						dmgFrac = 0.0;
2846 					}
2847 					else
2848 					{//started outside them and hit them
2849 						//yeah. this doesn't account for coming out the other wide, but we can worry about that later (use ghoul2)
2850 						trFrac = (1.0f - tr.fraction);
2851 						dmgFrac = tr.fraction;
2852 					}
2853 					vec3_t backdir;
2854 					VectorScale( dir, -1, backdir );
2855 					WP_SaberDamageAdd( trFrac, tr.entityNum, dir, bladeVec, backdir, tr.endpos, dmg, dmgFrac, HL_NONE, qfalse, HL_NONE );
2856 					if ( !tr.allsolid && !tr.startsolid )
2857 					{
2858 						VectorScale( dir, -1, dir );
2859 					}
2860 					if ( hitEnt != NULL )
2861 					{
2862 						if ( hitEnt->client )
2863 						{
2864 							//don't do blood sparks on non-living things
2865 							class_t npc_class = hitEnt->client->NPC_class;
2866 							if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE ||
2867 								 npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 || npc_class == CLASS_REMOTE ||
2868 								 npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 ||
2869 							     npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY )
2870 							{
2871 								if ( !WP_SaberBladeUseSecondBladeStyle( &attacker->client->ps.saber[saberNum], bladeNum )
2872 									&& attacker->client->ps.saber[saberNum].hitOtherEffect )
2873 								{
2874 									hitEffect = attacker->client->ps.saber[saberNum].hitOtherEffect;
2875 								}
2876 								else if ( WP_SaberBladeUseSecondBladeStyle( &attacker->client->ps.saber[saberNum], bladeNum )
2877 									&& attacker->client->ps.saber[saberNum].hitOtherEffect2 )
2878 								{
2879 									hitEffect = attacker->client->ps.saber[saberNum].hitOtherEffect2;
2880 								}
2881 								else
2882 								{
2883 									hitEffect = G_EffectIndex( hit_sparks );
2884 								}
2885 							}
2886 						}
2887 						else
2888 						{
2889 							if ( dmg )
2890 							{//only do these effects if actually trying to damage the thing...
2891 								if ( (hitEnt->svFlags&SVF_BBRUSH)//a breakable brush
2892 									&& ( (hitEnt->spawnflags&1)//INVINCIBLE
2893 										||(hitEnt->flags&FL_DMG_BY_HEAVY_WEAP_ONLY)//HEAVY weapon damage only
2894 										||(hitEnt->NPC_targetname&&attacker&&attacker->targetname&&Q_stricmp(attacker->targetname,hitEnt->NPC_targetname)) ) )//only breakable by an entity who is not the attacker
2895 								{//no hit effect (besides regular client-side one)
2896 								}
2897 								else
2898 								{
2899 									if ( !WP_SaberBladeUseSecondBladeStyle( &attacker->client->ps.saber[saberNum], bladeNum )
2900 										&& attacker->client->ps.saber[saberNum].hitOtherEffect )
2901 									{
2902 										hitEffect = attacker->client->ps.saber[saberNum].hitOtherEffect;
2903 									}
2904 									else if ( WP_SaberBladeUseSecondBladeStyle( &attacker->client->ps.saber[saberNum], bladeNum )
2905 										&& attacker->client->ps.saber[saberNum].hitOtherEffect2 )
2906 									{
2907 										hitEffect = attacker->client->ps.saber[saberNum].hitOtherEffect2;
2908 									}
2909 									else
2910 									{
2911 										hitEffect = G_EffectIndex( hit_sparks );
2912 									}
2913 								}
2914 							}
2915 						}
2916 					}
2917 
2918 					if ( !g_saberNoEffects && hitEffect != 0 )
2919 					{
2920 						G_PlayEffect( hitEffect, tr.endpos, dir );//"saber_cut"
2921 					}
2922 				}
2923 				else
2924 				{//we were doing a ghoul trace
2925 					if ( !attacker
2926 						|| !attacker->client
2927 						|| attacker->client->ps.saberLockTime < level.time )
2928 					{
2929 						if ( !WP_SaberDamageEffects( &tr, start, len, dmg, dir, bladeVec, attacker->client->enemyTeam, saberType, &attacker->client->ps.saber[saberNum], bladeNum ) )
2930 						{//didn't hit a ghoul ent
2931 							/*
2932 							if ( && hitEnt->ghoul2.size() )
2933 							{//it was a ghoul2 model so we should have hit it
2934 								return qfalse;
2935 							}
2936 							*/
2937 						}
2938 					}
2939 				}
2940 			}
2941 		}
2942 	}
2943 
2944 	return qfalse;
2945 }
2946 
2947 #define LOCK_IDEAL_DIST_TOP 32.0f
2948 #define LOCK_IDEAL_DIST_CIRCLE 48.0f
2949 #define LOCK_IDEAL_DIST_JKA 46.0f//all of the new saberlocks are 46.08 from each other because Richard Lico is da MAN
2950 extern void PM_SetAnimFrame( gentity_t *gent, int frame, qboolean torso, qboolean legs );
2951 extern qboolean ValidAnimFileIndex ( int index );
G_SaberLockAnim(int attackerSaberStyle,int defenderSaberStyle,int topOrSide,int lockOrBreakOrSuperBreak,int winOrLose)2952 int G_SaberLockAnim( int attackerSaberStyle, int defenderSaberStyle, int topOrSide, int lockOrBreakOrSuperBreak, int winOrLose )
2953 {
2954 	int baseAnim = -1;
2955 	if ( lockOrBreakOrSuperBreak == SABERLOCK_LOCK )
2956 	{//special case: if we're using the same style and locking
2957 		if ( attackerSaberStyle == defenderSaberStyle
2958 			|| (attackerSaberStyle>=SS_FAST&&attackerSaberStyle<=SS_TAVION&&defenderSaberStyle>=SS_FAST&&defenderSaberStyle<=SS_TAVION) )
2959 		{//using same style
2960 			if ( winOrLose == SABERLOCK_LOSE )
2961 			{//you want the defender's stance...
2962 				switch ( defenderSaberStyle )
2963 				{
2964 				case SS_DUAL:
2965 					if ( topOrSide == SABERLOCK_TOP )
2966 					{
2967 						baseAnim = BOTH_LK_DL_DL_T_L_2;
2968 					}
2969 					else
2970 					{
2971 						baseAnim = BOTH_LK_DL_DL_S_L_2;
2972 					}
2973 					break;
2974 				case SS_STAFF:
2975 					if ( topOrSide == SABERLOCK_TOP )
2976 					{
2977 						baseAnim = BOTH_LK_ST_ST_T_L_2;
2978 					}
2979 					else
2980 					{
2981 						baseAnim = BOTH_LK_ST_ST_S_L_2;
2982 					}
2983 					break;
2984 				default:
2985 					if ( topOrSide == SABERLOCK_TOP )
2986 					{
2987 						baseAnim = BOTH_LK_S_S_T_L_2;
2988 					}
2989 					else
2990 					{
2991 						baseAnim = BOTH_LK_S_S_S_L_2;
2992 					}
2993 					break;
2994 				}
2995 			}
2996 		}
2997 	}
2998 	if ( baseAnim == -1 )
2999 	{
3000 		switch ( attackerSaberStyle )
3001 		{
3002 		case SS_DUAL:
3003 			switch ( defenderSaberStyle )
3004 			{
3005 				case SS_DUAL:
3006 					baseAnim = BOTH_LK_DL_DL_S_B_1_L;
3007 					break;
3008 				case SS_STAFF:
3009 					baseAnim = BOTH_LK_DL_ST_S_B_1_L;
3010 					break;
3011 				default://single
3012 					baseAnim = BOTH_LK_DL_S_S_B_1_L;
3013 					break;
3014 			}
3015 			break;
3016 		case SS_STAFF:
3017 			switch ( defenderSaberStyle )
3018 			{
3019 				case SS_DUAL:
3020 					baseAnim = BOTH_LK_ST_DL_S_B_1_L;
3021 					break;
3022 				case SS_STAFF:
3023 					baseAnim = BOTH_LK_ST_ST_S_B_1_L;
3024 					break;
3025 				default://single
3026 					baseAnim = BOTH_LK_ST_S_S_B_1_L;
3027 					break;
3028 			}
3029 			break;
3030 		default://single
3031 			switch ( defenderSaberStyle )
3032 			{
3033 				case SS_DUAL:
3034 					baseAnim = BOTH_LK_S_DL_S_B_1_L;
3035 					break;
3036 				case SS_STAFF:
3037 					baseAnim = BOTH_LK_S_ST_S_B_1_L;
3038 					break;
3039 				default://single
3040 					baseAnim = BOTH_LK_S_S_S_B_1_L;
3041 					break;
3042 			}
3043 			break;
3044 		}
3045 		//side lock or top lock?
3046 		if ( topOrSide == SABERLOCK_TOP )
3047 		{
3048 			baseAnim += 5;
3049 		}
3050 		//lock, break or superbreak?
3051 		if ( lockOrBreakOrSuperBreak == SABERLOCK_LOCK )
3052 		{
3053 			baseAnim += 2;
3054 		}
3055 		else
3056 		{//a break or superbreak
3057 			if ( lockOrBreakOrSuperBreak == SABERLOCK_SUPERBREAK )
3058 			{
3059 				baseAnim += 3;
3060 			}
3061 			//winner or loser?
3062 			if ( winOrLose == SABERLOCK_WIN )
3063 			{
3064 				baseAnim += 1;
3065 			}
3066 		}
3067 	}
3068 	return baseAnim;
3069 }
3070 
G_CheckIncrementLockAnim(int anim,int winOrLose)3071 qboolean G_CheckIncrementLockAnim( int anim, int winOrLose )
3072 {
3073 	qboolean increment = qfalse;//???
3074 	//RULE: if you are the first style in the lock anim, you advance from LOSING position to WINNING position
3075 	//		if you are the second style in the lock anim, you advance from WINNING position to LOSING position
3076 	switch ( anim )
3077 	{
3078 	//increment to win:
3079 	case BOTH_LK_DL_DL_S_L_1:	//lock if I'm using dual vs. dual and I initiated
3080 	case BOTH_LK_DL_DL_S_L_2:	//lock if I'm using dual vs. dual and other initiated
3081 	case BOTH_LK_DL_DL_T_L_1:	//lock if I'm using dual vs. dual and I initiated
3082 	case BOTH_LK_DL_DL_T_L_2:	//lock if I'm using dual vs. dual and other initiated
3083 	case BOTH_LK_DL_S_S_L_1:		//lock if I'm using dual vs. a single
3084 	case BOTH_LK_DL_S_T_L_1:		//lock if I'm using dual vs. a single
3085 	case BOTH_LK_DL_ST_S_L_1:	//lock if I'm using dual vs. a staff
3086 	case BOTH_LK_DL_ST_T_L_1:	//lock if I'm using dual vs. a staff
3087 	case BOTH_LK_S_S_S_L_1:		//lock if I'm using single vs. a single and I initiated
3088 	case BOTH_LK_S_S_T_L_2:		//lock if I'm using single vs. a single and other initiated
3089 	case BOTH_LK_ST_S_S_L_1:		//lock if I'm using staff vs. a single
3090 	case BOTH_LK_ST_S_T_L_1:		//lock if I'm using staff vs. a single
3091 	case BOTH_LK_ST_ST_T_L_1:	//lock if I'm using staff vs. a staff and I initiated
3092 	case BOTH_LK_ST_ST_T_L_2:	//lock if I'm using staff vs. a staff and other initiated
3093 		if ( winOrLose == SABERLOCK_WIN )
3094 		{
3095 			increment = qtrue;
3096 		}
3097 		else
3098 		{
3099 			increment = qfalse;
3100 		}
3101 		break;
3102 
3103 	//decrement to win:
3104 	case BOTH_LK_S_DL_S_L_1:		//lock if I'm using single vs. a dual
3105 	case BOTH_LK_S_DL_T_L_1:		//lock if I'm using single vs. a dual
3106 	case BOTH_LK_S_S_S_L_2:		//lock if I'm using single vs. a single and other intitiated
3107 	case BOTH_LK_S_S_T_L_1:		//lock if I'm using single vs. a single and I initiated
3108 	case BOTH_LK_S_ST_S_L_1:		//lock if I'm using single vs. a staff
3109 	case BOTH_LK_S_ST_T_L_1:		//lock if I'm using single vs. a staff
3110 	case BOTH_LK_ST_DL_S_L_1:	//lock if I'm using staff vs. dual
3111 	case BOTH_LK_ST_DL_T_L_1:	//lock if I'm using staff vs. dual
3112 	case BOTH_LK_ST_ST_S_L_1:	//lock if I'm using staff vs. a staff and I initiated
3113 	case BOTH_LK_ST_ST_S_L_2:	//lock if I'm using staff vs. a staff and other initiated
3114 		if ( winOrLose == SABERLOCK_WIN )
3115 		{
3116 			increment = qfalse;
3117 		}
3118 		else
3119 		{
3120 			increment = qtrue;
3121 		}
3122 		break;
3123 	default:
3124 #ifndef FINAL_BUILD
3125 		Com_Printf( S_COLOR_RED"ERROR: unknown Saber Lock Anim: %s!!!\n", animTable[anim].name );
3126 #endif
3127 		break;
3128 	}
3129 	return increment;
3130 }
3131 
WP_SabersCheckLock2(gentity_t * attacker,gentity_t * defender,sabersLockMode_t lockMode)3132 qboolean WP_SabersCheckLock2( gentity_t *attacker, gentity_t *defender, sabersLockMode_t lockMode )
3133 {
3134 	animation_t *anim;
3135 	int		attAnim, defAnim, advance = 0;
3136 	float	attStart = 0.5f, defStart = 0.5f;
3137 	float	idealDist = 48.0f;
3138 	//FIXME: this distances need to be modified by the lengths of the sabers involved...
3139 	//MATCH ANIMS
3140 	if ( lockMode == LOCK_KYLE_GRAB1
3141 		|| lockMode == LOCK_KYLE_GRAB2
3142 		|| lockMode == LOCK_KYLE_GRAB3 )
3143 	{
3144 		float	numSpins = 1.0f;
3145 		idealDist = 46.0f;//42.0f;
3146 		attStart = defStart = 0.0f;
3147 
3148 		switch ( lockMode )
3149 		{
3150 		default:
3151 		case LOCK_KYLE_GRAB1:
3152 			attAnim = BOTH_KYLE_PA_1;
3153 			defAnim = BOTH_PLAYER_PA_1;
3154 			numSpins = 2.0f;
3155 			break;
3156 		case LOCK_KYLE_GRAB2:
3157 			attAnim = BOTH_KYLE_PA_3;
3158 			defAnim = BOTH_PLAYER_PA_3;
3159 			numSpins = 1.0f;
3160 			break;
3161 		case LOCK_KYLE_GRAB3:
3162 			attAnim = BOTH_KYLE_PA_2;
3163 			defAnim = BOTH_PLAYER_PA_2;
3164 			defender->forcePushTime = level.time + PM_AnimLength( defender->client->clientInfo.animFileIndex, BOTH_PLAYER_PA_2 );
3165 			numSpins = 3.0f;
3166 			break;
3167 		}
3168 		attacker->client->ps.SaberDeactivate();
3169 		defender->client->ps.SaberDeactivate();
3170 		if ( d_slowmodeath->integer > 3
3171 			&& ( defender->s.number < MAX_CLIENTS
3172 				|| attacker->s.number < MAX_CLIENTS ) )
3173 		{
3174 			if( ValidAnimFileIndex( attacker->client->clientInfo.animFileIndex ) )
3175 			{
3176 				int effectTime = PM_AnimLength( attacker->client->clientInfo.animFileIndex, (animNumber_t)attAnim );
3177 				int spinTime = floor((float)effectTime/numSpins);
3178 				int meFlags = (MEF_MULTI_SPIN);//MEF_NO_TIMESCALE|MEF_NO_VERTBOB|
3179 				if ( Q_irand( 0, 1 ) )
3180 				{
3181 					meFlags |= MEF_REVERSE_SPIN;
3182 				}
3183 				G_StartMatrixEffect( attacker, meFlags, effectTime, 0.75f, spinTime );
3184 			}
3185 		}
3186 	}
3187 	else if ( lockMode == LOCK_FORCE_DRAIN )
3188 	{
3189 		idealDist = 46.0f;//42.0f;
3190 		attStart = defStart = 0.0f;
3191 
3192 		attAnim = BOTH_FORCE_DRAIN_GRAB_START;
3193 		defAnim = BOTH_FORCE_DRAIN_GRABBED;
3194 		attacker->client->ps.SaberDeactivate();
3195 		defender->client->ps.SaberDeactivate();
3196 	}
3197 	else
3198 	{
3199 		if ( lockMode == LOCK_RANDOM )
3200 		{
3201 			lockMode = (sabersLockMode_t)Q_irand( (int)LOCK_FIRST, (int)(LOCK_RANDOM)-1 );
3202 		}
3203 		//FIXME: attStart% and idealDist will change per saber lock anim pairing... do we need a big table like in bg_panimate.cpp?
3204 		if ( attacker->client->ps.saberAnimLevel >= SS_FAST
3205 			&& attacker->client->ps.saberAnimLevel <= SS_TAVION
3206 			&& defender->client->ps.saberAnimLevel >= SS_FAST
3207 			&& defender->client->ps.saberAnimLevel <= SS_TAVION )
3208 		{//2 single sabers?  Just do it the old way...
3209 			switch ( lockMode )
3210 			{
3211 			case LOCK_TOP:
3212 				attAnim = BOTH_BF2LOCK;// - starts in middle
3213 				defAnim = BOTH_BF1LOCK;// - starts in middle
3214 				attStart = defStart = 0.5f;
3215 				idealDist = LOCK_IDEAL_DIST_TOP;
3216 				break;
3217 			case LOCK_DIAG_TR:
3218 				attAnim = BOTH_CCWCIRCLELOCK; //- starts in middle
3219 				defAnim = BOTH_CWCIRCLELOCK;// - starts in middle
3220 				attStart = defStart = 0.5f;
3221 				idealDist = LOCK_IDEAL_DIST_CIRCLE;
3222 				break;
3223 			case LOCK_DIAG_TL:
3224 				attAnim = BOTH_CWCIRCLELOCK;// - starts in middle
3225 				defAnim = BOTH_CCWCIRCLELOCK;// - starts in middle
3226 				attStart = defStart = 0.5f;
3227 				idealDist = LOCK_IDEAL_DIST_CIRCLE;
3228 				break;
3229 			case LOCK_DIAG_BR:
3230 				attAnim = BOTH_CWCIRCLELOCK;// - starts on left, to right
3231 				defAnim = BOTH_CCWCIRCLELOCK;// - starts on right, to left
3232 				attStart = defStart = 0.85f;//move to end of anim
3233 				idealDist = LOCK_IDEAL_DIST_CIRCLE;
3234 				break;
3235 			case LOCK_DIAG_BL:
3236 				attAnim = BOTH_CCWCIRCLELOCK;// - starts on right, to left
3237 				defAnim = BOTH_CWCIRCLELOCK;// - starts on left, to right
3238 				attStart = defStart = 0.85f;//move to end of anim
3239 				idealDist = LOCK_IDEAL_DIST_CIRCLE;
3240 				break;
3241 			case LOCK_R:
3242 				attAnim = BOTH_CWCIRCLELOCK;// - starts on left, to right
3243 				defAnim = BOTH_CCWCIRCLELOCK;// - starts on right, to left
3244 				attStart = defStart = 0.75f;//move to end of anim
3245 				idealDist = LOCK_IDEAL_DIST_CIRCLE;
3246 				break;
3247 			case LOCK_L:
3248 				attAnim = BOTH_CCWCIRCLELOCK;// - starts on right, to left
3249 				defAnim = BOTH_CWCIRCLELOCK;// - starts on left, to right
3250 				attStart = defStart = 0.75f;//move to end of anim
3251 				idealDist = LOCK_IDEAL_DIST_CIRCLE;
3252 				break;
3253 			default:
3254 				return qfalse;
3255 				break;
3256 			}
3257 		}
3258 		else
3259 		{//use the new system
3260 			idealDist = LOCK_IDEAL_DIST_JKA;//all of the new saberlocks are 46.08 from each other because Richard Lico is da MAN
3261 			if ( lockMode == LOCK_TOP )
3262 			{//top lock
3263 				attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_TOP, SABERLOCK_LOCK, SABERLOCK_WIN );
3264 				defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_TOP, SABERLOCK_LOCK, SABERLOCK_LOSE );
3265 				attStart = defStart = 0.5f;
3266 			}
3267 			else
3268 			{//side lock
3269 				switch ( lockMode )
3270 				{
3271 				case LOCK_DIAG_TR:
3272 					attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
3273 					defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
3274 					attStart = defStart = 0.5f;
3275 					break;
3276 				case LOCK_DIAG_TL:
3277 					attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
3278 					defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
3279 					attStart = defStart = 0.5f;
3280 					break;
3281 				case LOCK_DIAG_BR:
3282 					attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
3283 					defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
3284 					if ( G_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
3285 					{
3286 						attStart = 0.85f;//move to end of anim
3287 					}
3288 					else
3289 					{
3290 						attStart = 0.15f;//start at beginning of anim
3291 					}
3292 					if ( G_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
3293 					{
3294 						defStart = 0.85f;//start at end of anim
3295 					}
3296 					else
3297 					{
3298 						defStart = 0.15f;//start at beginning of anim
3299 					}
3300 					break;
3301 				case LOCK_DIAG_BL:
3302 					attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
3303 					defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
3304 					if ( G_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
3305 					{
3306 						attStart = 0.85f;//move to end of anim
3307 					}
3308 					else
3309 					{
3310 						attStart = 0.15f;//start at beginning of anim
3311 					}
3312 					if ( G_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
3313 					{
3314 						defStart = 0.85f;//start at end of anim
3315 					}
3316 					else
3317 					{
3318 						defStart = 0.15f;//start at beginning of anim
3319 					}
3320 					break;
3321 				case LOCK_R:
3322 					attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
3323 					defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
3324 					if ( G_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
3325 					{
3326 						attStart = 0.75f;//move to end of anim
3327 					}
3328 					else
3329 					{
3330 						attStart = 0.25f;//start at beginning of anim
3331 					}
3332 					if ( G_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
3333 					{
3334 						defStart = 0.75f;//start at end of anim
3335 					}
3336 					else
3337 					{
3338 						defStart = 0.25f;//start at beginning of anim
3339 					}
3340 					break;
3341 				case LOCK_L:
3342 					attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
3343 					defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
3344 					//attacker starts with advantage
3345 					if ( G_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
3346 					{
3347 						attStart = 0.75f;//move to end of anim
3348 					}
3349 					else
3350 					{
3351 						attStart = 0.25f;//start at beginning of anim
3352 					}
3353 					if ( G_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
3354 					{
3355 						defStart = 0.75f;//start at end of anim
3356 					}
3357 					else
3358 					{
3359 						defStart = 0.25f;//start at beginning of anim
3360 					}
3361 					break;
3362 				default:
3363 					return qfalse;
3364 					break;
3365 				}
3366 			}
3367 		}
3368 	}
3369 	//set the proper anims
3370 	NPC_SetAnim( attacker, SETANIM_BOTH, attAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
3371 	NPC_SetAnim( defender, SETANIM_BOTH, defAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
3372 	//don't let them store a kick for the whole saberlock....
3373 	attacker->client->ps.saberMoveNext = defender->client->ps.saberMoveNext = LS_NONE;
3374 	//
3375 	if ( attStart > 0.0f )
3376 	{
3377 		if( ValidAnimFileIndex( attacker->client->clientInfo.animFileIndex ) )
3378 		{
3379 			anim = &level.knownAnimFileSets[attacker->client->clientInfo.animFileIndex].animations[attAnim];
3380 			advance = floor( anim->numFrames*attStart );
3381 			PM_SetAnimFrame( attacker, anim->firstFrame + advance, qtrue, qtrue );
3382 #ifndef FINAL_BUILD
3383 			if ( d_saberCombat->integer )
3384 			{
3385 				Com_Printf( "%s starting saber lock, anim = %s, %d frames to go!\n", attacker->NPC_type, animTable[attAnim].name, anim->numFrames-advance );
3386 			}
3387 #endif
3388 		}
3389 	}
3390 	if ( defStart > 0.0f )
3391 	{
3392 		if( ValidAnimFileIndex( defender->client->clientInfo.animFileIndex ) )
3393 		{
3394 			anim = &level.knownAnimFileSets[defender->client->clientInfo.animFileIndex].animations[defAnim];
3395 			advance = ceil( anim->numFrames*defStart );
3396 			PM_SetAnimFrame( defender, anim->firstFrame + advance, qtrue, qtrue );//was anim->firstFrame + anim->numFrames - advance, but that's wrong since they are matched anims
3397 #ifndef FINAL_BUILD
3398 			if ( d_saberCombat->integer )
3399 			{
3400 				Com_Printf( "%s starting saber lock, anim = %s, %d frames to go!\n", defender->NPC_type, animTable[defAnim].name, advance );
3401 			}
3402 #endif
3403 		}
3404 	}
3405 	VectorClear( attacker->client->ps.velocity );
3406 	VectorClear( attacker->client->ps.moveDir );
3407 	VectorClear( defender->client->ps.velocity );
3408 	VectorClear( defender->client->ps.moveDir );
3409 	if ( lockMode == LOCK_KYLE_GRAB1
3410 		|| lockMode == LOCK_KYLE_GRAB2
3411 		|| lockMode == LOCK_KYLE_GRAB3
3412 		|| lockMode == LOCK_FORCE_DRAIN )
3413 	{//not a real lock, just freeze them both in place
3414 		//can't move or attack
3415 		attacker->client->ps.pm_time = attacker->client->ps.weaponTime = attacker->client->ps.legsAnimTimer;
3416 		attacker->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
3417 		attacker->painDebounceTime = level.time + attacker->client->ps.pm_time;
3418 		if ( lockMode != LOCK_FORCE_DRAIN )
3419 		{
3420 			defender->client->ps.torsoAnimTimer += 200;
3421 			defender->client->ps.legsAnimTimer += 200;
3422 		}
3423 		defender->client->ps.pm_time = defender->client->ps.weaponTime = defender->client->ps.legsAnimTimer;
3424 		defender->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
3425 		if ( lockMode != LOCK_FORCE_DRAIN )
3426 		{
3427 			attacker->aimDebounceTime = level.time + attacker->client->ps.pm_time;
3428 		}
3429 	}
3430 	else
3431 	{
3432 		attacker->client->ps.saberLockTime = defender->client->ps.saberLockTime = level.time + SABER_LOCK_TIME;
3433 		attacker->client->ps.legsAnimTimer = attacker->client->ps.torsoAnimTimer = defender->client->ps.legsAnimTimer = defender->client->ps.torsoAnimTimer = SABER_LOCK_TIME;
3434 		//attacker->client->ps.weaponTime = defender->client->ps.weaponTime = SABER_LOCK_TIME;
3435 		attacker->client->ps.saberLockEnemy = defender->s.number;
3436 		defender->client->ps.saberLockEnemy = attacker->s.number;
3437 	}
3438 
3439 	//MATCH ANGLES
3440 	if ( lockMode == LOCK_KYLE_GRAB1
3441 		|| lockMode == LOCK_KYLE_GRAB2
3442 		|| lockMode == LOCK_KYLE_GRAB3 )
3443 	{//not a real lock, just set pitch to 0
3444 		attacker->client->ps.viewangles[PITCH] = defender->client->ps.viewangles[PITCH] = 0;
3445 	}
3446 	else
3447 	{
3448 		//FIXME: if zDiff in elevation, make lower look up and upper look down and move them closer?
3449 		float defPitchAdd = 0, zDiff = ((attacker->currentOrigin[2]+attacker->client->standheight)-(defender->currentOrigin[2]+defender->client->standheight));
3450 		if ( zDiff > 24 )
3451 		{
3452 			defPitchAdd = -30;
3453 		}
3454 		else if ( zDiff < -24 )
3455 		{
3456 			defPitchAdd = 30;
3457 		}
3458 		else
3459 		{
3460 			defPitchAdd = zDiff/24.0f*-30.0f;
3461 		}
3462 		if ( attacker->NPC && defender->NPC )
3463 		{//if 2 NPCs, just set pitch to 0
3464 			attacker->client->ps.viewangles[PITCH] = -defPitchAdd;
3465 			defender->client->ps.viewangles[PITCH] = defPitchAdd;
3466 		}
3467 		else
3468 		{//if a player is involved, clamp player's pitch and match NPC's to player
3469 			if ( !attacker->s.number )
3470 			{
3471 				//clamp to defPitch
3472 				if ( attacker->client->ps.viewangles[PITCH] > -defPitchAdd + 10 )
3473 				{
3474 					attacker->client->ps.viewangles[PITCH] = -defPitchAdd + 10;
3475 				}
3476 				else if ( attacker->client->ps.viewangles[PITCH] < -defPitchAdd-10 )
3477 				{
3478 					attacker->client->ps.viewangles[PITCH] = -defPitchAdd-10;
3479 				}
3480 				//clamp to sane numbers
3481 				if ( attacker->client->ps.viewangles[PITCH] > 50 )
3482 				{
3483 					attacker->client->ps.viewangles[PITCH] = 50;
3484 				}
3485 				else if ( attacker->client->ps.viewangles[PITCH] < -50 )
3486 				{
3487 					attacker->client->ps.viewangles[PITCH] = -50;
3488 				}
3489 				defender->client->ps.viewangles[PITCH] = attacker->client->ps.viewangles[PITCH]*-1;
3490 				defPitchAdd = defender->client->ps.viewangles[PITCH];
3491 			}
3492 			else if ( !defender->s.number )
3493 			{
3494 				//clamp to defPitch
3495 				if ( defender->client->ps.viewangles[PITCH] > defPitchAdd + 10 )
3496 				{
3497 					defender->client->ps.viewangles[PITCH] = defPitchAdd + 10;
3498 				}
3499 				else if ( defender->client->ps.viewangles[PITCH] < defPitchAdd-10 )
3500 				{
3501 					defender->client->ps.viewangles[PITCH] = defPitchAdd-10;
3502 				}
3503 				//clamp to sane numbers
3504 				if ( defender->client->ps.viewangles[PITCH] > 50 )
3505 				{
3506 					defender->client->ps.viewangles[PITCH] = 50;
3507 				}
3508 				else if ( defender->client->ps.viewangles[PITCH] < -50 )
3509 				{
3510 					defender->client->ps.viewangles[PITCH] = -50;
3511 				}
3512 				defPitchAdd = defender->client->ps.viewangles[PITCH];
3513 				attacker->client->ps.viewangles[PITCH] = defender->client->ps.viewangles[PITCH]*-1;
3514 			}
3515 		}
3516 	}
3517 	vec3_t	attAngles, defAngles, defDir;
3518 	VectorSubtract( defender->currentOrigin, attacker->currentOrigin, defDir );
3519 	VectorCopy( attacker->client->ps.viewangles, attAngles );
3520 	attAngles[YAW] = vectoyaw( defDir );
3521 	SetClientViewAngle( attacker, attAngles );
3522 	defAngles[PITCH] = attAngles[PITCH]*-1;
3523 	defAngles[YAW] = AngleNormalize180( attAngles[YAW] + 180);
3524 	defAngles[ROLL] = 0;
3525 	SetClientViewAngle( defender, defAngles );
3526 
3527 	//MATCH POSITIONS
3528 	vec3_t	newOrg;
3529 	/*
3530 	idealDist -= fabs(defPitchAdd)/8.0f;
3531 	*/
3532 	float scale = (attacker->s.modelScale[0]+attacker->s.modelScale[1])*0.5f;
3533 	if ( scale && scale != 1.0f )
3534 	{
3535 		idealDist += 8*(scale-1.0f);
3536 	}
3537 	scale = (defender->s.modelScale[0]+defender->s.modelScale[1])*0.5f;
3538 	if ( scale && scale != 1.0f )
3539 	{
3540 		idealDist += 8*(scale-1.0f);
3541 	}
3542 
3543 	float diff = VectorNormalize( defDir ) - idealDist;//diff will be the total error in dist
3544 	//try to move attacker half the diff towards the defender
3545 	VectorMA( attacker->currentOrigin, diff*0.5f, defDir, newOrg );
3546 	trace_t	trace;
3547 	gi.trace( &trace, attacker->currentOrigin, attacker->mins, attacker->maxs, newOrg, attacker->s.number, attacker->clipmask, (EG2_Collision)0, 0 );
3548 	if ( !trace.startsolid && !trace.allsolid )
3549 	{
3550 		G_SetOrigin( attacker, trace.endpos );
3551 		gi.linkentity( attacker );
3552 	}
3553 	//now get the defender's dist and do it for him too
3554 	vec3_t	attDir;
3555 	VectorSubtract( attacker->currentOrigin, defender->currentOrigin, attDir );
3556 	diff = VectorNormalize( attDir ) - idealDist;//diff will be the total error in dist
3557 	//try to move defender all of the remaining diff towards the attacker
3558 	VectorMA( defender->currentOrigin, diff, attDir, newOrg );
3559 	gi.trace( &trace, defender->currentOrigin, defender->mins, defender->maxs, newOrg, defender->s.number, defender->clipmask, (EG2_Collision)0, 0 );
3560 	if ( !trace.startsolid && !trace.allsolid )
3561 	{
3562 		G_SetOrigin( defender, trace.endpos );
3563 		gi.linkentity( defender );
3564 	}
3565 
3566 	//DONE!
3567 
3568 	return qtrue;
3569 }
3570 
WP_SabersCheckLock(gentity_t * ent1,gentity_t * ent2)3571 qboolean WP_SabersCheckLock( gentity_t *ent1, gentity_t *ent2 )
3572 {
3573 	if ( ent1->client->playerTeam == ent2->client->playerTeam )
3574 	{
3575 		return qfalse;
3576 	}
3577 	if ( ent1->client->NPC_class == CLASS_SABER_DROID
3578 		|| ent2->client->NPC_class == CLASS_SABER_DROID )
3579 	{//they don't have saberlock anims
3580 		return qfalse;
3581 	}
3582 	if ( ent1->client->ps.groundEntityNum == ENTITYNUM_NONE ||
3583 		ent2->client->ps.groundEntityNum == ENTITYNUM_NONE )
3584 	{
3585 		return qfalse;
3586 	}
3587 	if ( (ent1->client->ps.saber[0].saberFlags&SFL_NOT_LOCKABLE)
3588 		|| (ent2->client->ps.saber[0].saberFlags&SFL_NOT_LOCKABLE) )
3589 	{//one of these sabers cannot lock (like a lance)
3590 		return qfalse;
3591 	}
3592 	if ( ent1->client->ps.dualSabers
3593 		&& ent1->client->ps.saber[1].Active()
3594 		&& (ent1->client->ps.saber[1].saberFlags&SFL_NOT_LOCKABLE) )
3595 	{//one of these sabers cannot lock (like a lance)
3596 		return qfalse;
3597 	}
3598 	if ( ent2->client->ps.dualSabers
3599 		&& ent2->client->ps.saber[1].Active()
3600 		&& (ent2->client->ps.saber[1].saberFlags&SFL_NOT_LOCKABLE) )
3601 	{//one of these sabers cannot lock (like a lance)
3602 		return qfalse;
3603 	}
3604 	if ( ent1->painDebounceTime > level.time-1000 || ent2->painDebounceTime > level.time-1000 )
3605 	{//can't saberlock if you're not ready
3606 		return qfalse;
3607 	}
3608 	if ( fabs( ent1->currentOrigin[2]-ent2->currentOrigin[2]) > 18 )
3609 	{
3610 		return qfalse;
3611 	}
3612 	float dist = DistanceSquared(ent1->currentOrigin,ent2->currentOrigin);
3613 	if ( dist < 64 || dist > 6400 )//( dist < 128 || dist > 2304 )
3614 	{//between 8 and 80 from each other//was 16 and 48
3615 		return qfalse;
3616 	}
3617 	if ( !InFOV( ent1, ent2, 40, 180 ) || !InFOV( ent2, ent1, 40, 180 ) )
3618 	{
3619 		return qfalse;
3620 	}
3621 	//Check for certain anims that *cannot* lock
3622 	//FIXME: there should probably be a whole *list* of these, but I'll put them in as they come up
3623 	if ( ent1->client->ps.torsoAnim == BOTH_A2_STABBACK1 && ent1->client->ps.torsoAnimTimer > 300 )
3624 	{//can't lock when saber is behind you
3625 		return qfalse;
3626 	}
3627 	if ( ent2->client->ps.torsoAnim == BOTH_A2_STABBACK1 && ent2->client->ps.torsoAnimTimer > 300 )
3628 	{//can't lock when saber is behind you
3629 		return qfalse;
3630 	}
3631 	if ( PM_LockedAnim( ent1->client->ps.torsoAnim )
3632 		|| PM_LockedAnim( ent2->client->ps.torsoAnim ) )
3633 	{//stuck doing something else
3634 		return qfalse;
3635 	}
3636 	if ( PM_SaberLockBreakAnim( ent1->client->ps.torsoAnim )
3637 		|| PM_SaberLockBreakAnim( ent2->client->ps.torsoAnim ) )
3638 	{//still finishing the last lock break!
3639 		return qfalse;
3640 	}
3641 	//BR to TL lock
3642 	if ( ent1->client->ps.torsoAnim == BOTH_A1_BR_TL ||
3643 		ent1->client->ps.torsoAnim == BOTH_A2_BR_TL ||
3644 		ent1->client->ps.torsoAnim == BOTH_A3_BR_TL ||
3645 		ent1->client->ps.torsoAnim == BOTH_A4_BR_TL ||
3646 		ent1->client->ps.torsoAnim == BOTH_A5_BR_TL ||
3647 		ent1->client->ps.torsoAnim == BOTH_A6_BR_TL ||
3648 		ent1->client->ps.torsoAnim == BOTH_A7_BR_TL )
3649 	{//ent1 is attacking in the opposite diagonal
3650 		return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BR );
3651 	}
3652 	if ( ent2->client->ps.torsoAnim == BOTH_A1_BR_TL ||
3653 		ent2->client->ps.torsoAnim == BOTH_A2_BR_TL ||
3654 		ent2->client->ps.torsoAnim == BOTH_A3_BR_TL ||
3655 		ent2->client->ps.torsoAnim == BOTH_A4_BR_TL ||
3656 		ent2->client->ps.torsoAnim == BOTH_A5_BR_TL ||
3657 		ent2->client->ps.torsoAnim == BOTH_A6_BR_TL ||
3658 		ent2->client->ps.torsoAnim == BOTH_A7_BR_TL )
3659 	{//ent2 is attacking in the opposite diagonal
3660 		return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BR );
3661 	}
3662 	//BL to TR lock
3663 	if ( ent1->client->ps.torsoAnim == BOTH_A1_BL_TR ||
3664 		ent1->client->ps.torsoAnim == BOTH_A2_BL_TR ||
3665 		ent1->client->ps.torsoAnim == BOTH_A3_BL_TR ||
3666 		ent1->client->ps.torsoAnim == BOTH_A4_BL_TR ||
3667 		ent1->client->ps.torsoAnim == BOTH_A5_BL_TR ||
3668 		ent1->client->ps.torsoAnim == BOTH_A6_BL_TR ||
3669 		ent1->client->ps.torsoAnim == BOTH_A7_BL_TR )
3670 	{//ent1 is attacking in the opposite diagonal
3671 		return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BL );
3672 	}
3673 	if ( ent2->client->ps.torsoAnim == BOTH_A1_BL_TR ||
3674 		ent2->client->ps.torsoAnim == BOTH_A2_BL_TR ||
3675 		ent2->client->ps.torsoAnim == BOTH_A3_BL_TR ||
3676 		ent2->client->ps.torsoAnim == BOTH_A4_BL_TR ||
3677 		ent2->client->ps.torsoAnim == BOTH_A5_BL_TR ||
3678 		ent2->client->ps.torsoAnim == BOTH_A6_BL_TR ||
3679 		ent2->client->ps.torsoAnim == BOTH_A7_BL_TR )
3680 	{//ent2 is attacking in the opposite diagonal
3681 		return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BL );
3682 	}
3683 	//L to R lock
3684 	if ( ent1->client->ps.torsoAnim == BOTH_A1__L__R ||
3685 		ent1->client->ps.torsoAnim == BOTH_A2__L__R ||
3686 		ent1->client->ps.torsoAnim == BOTH_A3__L__R ||
3687 		ent1->client->ps.torsoAnim == BOTH_A4__L__R ||
3688 		ent1->client->ps.torsoAnim == BOTH_A5__L__R ||
3689 		ent1->client->ps.torsoAnim == BOTH_A6__L__R ||
3690 		ent1->client->ps.torsoAnim == BOTH_A7__L__R )
3691 	{//ent1 is attacking l to r
3692 		return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
3693 		/*
3694 		if ( ent2BlockingPlayer )
3695 		{//player will block this anyway
3696 			return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
3697 		}
3698 		if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
3699 			ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
3700 			ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
3701 			ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
3702 			ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
3703 			ent2->client->ps.torsoAnim == BOTH_A6_TL_BR ||
3704 			ent2->client->ps.torsoAnim == BOTH_A7_TL_BR ||
3705 			ent2->client->ps.torsoAnim == BOTH_P1_S1_TR ||
3706 			ent2->client->ps.torsoAnim == BOTH_P1_S1_BL )
3707 		{//ent2 is attacking or blocking on the r
3708 			return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
3709 		}
3710 		if ( ent2Boss && !Q_irand( 0, 3 ) )
3711 		{
3712 			return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
3713 		}
3714 		*/
3715 	}
3716 	if ( ent2->client->ps.torsoAnim == BOTH_A1__L__R ||
3717 		ent2->client->ps.torsoAnim == BOTH_A2__L__R ||
3718 		ent2->client->ps.torsoAnim == BOTH_A3__L__R ||
3719 		ent2->client->ps.torsoAnim == BOTH_A4__L__R ||
3720 		ent2->client->ps.torsoAnim == BOTH_A5__L__R ||
3721 		ent2->client->ps.torsoAnim == BOTH_A6__L__R ||
3722 		ent2->client->ps.torsoAnim == BOTH_A7__L__R )
3723 	{//ent2 is attacking l to r
3724 		return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
3725 		/*
3726 		if ( ent1BlockingPlayer )
3727 		{//player will block this anyway
3728 			return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
3729 		}
3730 		if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
3731 			ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
3732 			ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
3733 			ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
3734 			ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
3735 			ent1->client->ps.torsoAnim == BOTH_A6_TL_BR ||
3736 			ent1->client->ps.torsoAnim == BOTH_A7_TL_BR ||
3737 			ent1->client->ps.torsoAnim == BOTH_P1_S1_TR ||
3738 			ent1->client->ps.torsoAnim == BOTH_P1_S1_BL )
3739 		{//ent1 is attacking or blocking on the r
3740 			return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
3741 		}
3742 		if ( ent1Boss && !Q_irand( 0, 3 ) )
3743 		{
3744 			return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
3745 		}
3746 		*/
3747 	}
3748 	//R to L lock
3749 	if ( ent1->client->ps.torsoAnim == BOTH_A1__R__L ||
3750 		ent1->client->ps.torsoAnim == BOTH_A2__R__L ||
3751 		ent1->client->ps.torsoAnim == BOTH_A3__R__L ||
3752 		ent1->client->ps.torsoAnim == BOTH_A4__R__L ||
3753 		ent1->client->ps.torsoAnim == BOTH_A5__R__L ||
3754 		ent1->client->ps.torsoAnim == BOTH_A6__R__L ||
3755 		ent1->client->ps.torsoAnim == BOTH_A7__R__L )
3756 	{//ent1 is attacking r to l
3757 		return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
3758 		/*
3759 		if ( ent2BlockingPlayer )
3760 		{//player will block this anyway
3761 			return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
3762 		}
3763 		if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
3764 			ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
3765 			ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
3766 			ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
3767 			ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
3768 			ent2->client->ps.torsoAnim == BOTH_A6_TR_BL ||
3769 			ent2->client->ps.torsoAnim == BOTH_A7_TR_BL ||
3770 			ent2->client->ps.torsoAnim == BOTH_P1_S1_TL ||
3771 			ent2->client->ps.torsoAnim == BOTH_P1_S1_BR )
3772 		{//ent2 is attacking or blocking on the l
3773 			return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
3774 		}
3775 		if ( ent2Boss && !Q_irand( 0, 3 ) )
3776 		{
3777 			return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
3778 		}
3779 		*/
3780 	}
3781 	if ( ent2->client->ps.torsoAnim == BOTH_A1__R__L ||
3782 		ent2->client->ps.torsoAnim == BOTH_A2__R__L ||
3783 		ent2->client->ps.torsoAnim == BOTH_A3__R__L ||
3784 		ent2->client->ps.torsoAnim == BOTH_A4__R__L ||
3785 		ent2->client->ps.torsoAnim == BOTH_A5__R__L ||
3786 		ent2->client->ps.torsoAnim == BOTH_A6__R__L ||
3787 		ent2->client->ps.torsoAnim == BOTH_A7__R__L )
3788 	{//ent2 is attacking r to l
3789 		return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
3790 		/*
3791 		if ( ent1BlockingPlayer )
3792 		{//player will block this anyway
3793 			return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
3794 		}
3795 		if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
3796 			ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
3797 			ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
3798 			ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
3799 			ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
3800 			ent1->client->ps.torsoAnim == BOTH_A6_TR_BL ||
3801 			ent1->client->ps.torsoAnim == BOTH_A7_TR_BL ||
3802 			ent1->client->ps.torsoAnim == BOTH_P1_S1_TL ||
3803 			ent1->client->ps.torsoAnim == BOTH_P1_S1_BR )
3804 		{//ent1 is attacking or blocking on the l
3805 			return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
3806 		}
3807 		if ( ent1Boss && !Q_irand( 0, 3 ) )
3808 		{
3809 			return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
3810 		}
3811 		*/
3812 	}
3813 	//TR to BL lock
3814 	if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
3815 		ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
3816 		ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
3817 		ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
3818 		ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
3819 		ent1->client->ps.torsoAnim == BOTH_A6_TR_BL ||
3820 		ent1->client->ps.torsoAnim == BOTH_A7_TR_BL )
3821 	{//ent1 is attacking diagonally
3822 		return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
3823 		/*
3824 		if ( ent2BlockingPlayer )
3825 		{//player will block this anyway
3826 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
3827 		}
3828 		if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
3829 			ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
3830 			ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
3831 			ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
3832 			ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
3833 			ent2->client->ps.torsoAnim == BOTH_A6_TR_BL ||
3834 			ent2->client->ps.torsoAnim == BOTH_A7_TR_BL ||
3835 			ent2->client->ps.torsoAnim == BOTH_P1_S1_TL )
3836 		{//ent2 is attacking in the opposite diagonal
3837 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
3838 		}
3839 		if ( ent2->client->ps.torsoAnim == BOTH_A1_BR_TL ||
3840 			ent2->client->ps.torsoAnim == BOTH_A2_BR_TL ||
3841 			ent2->client->ps.torsoAnim == BOTH_A3_BR_TL ||
3842 			ent2->client->ps.torsoAnim == BOTH_A4_BR_TL ||
3843 			ent2->client->ps.torsoAnim == BOTH_A5_BR_TL ||
3844 			ent2->client->ps.torsoAnim == BOTH_A6_BR_TL ||
3845 			ent2->client->ps.torsoAnim == BOTH_A7_BR_TL ||
3846 			ent2->client->ps.torsoAnim == BOTH_P1_S1_BL )
3847 		{//ent2 is attacking in the opposite diagonal
3848 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BL );
3849 		}
3850 		if ( ent2Boss && !Q_irand( 0, 3 ) )
3851 		{
3852 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
3853 		}
3854 		*/
3855 	}
3856 
3857 	if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
3858 		ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
3859 		ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
3860 		ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
3861 		ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
3862 		ent2->client->ps.torsoAnim == BOTH_A6_TR_BL ||
3863 		ent2->client->ps.torsoAnim == BOTH_A7_TR_BL )
3864 	{//ent2 is attacking diagonally
3865 		return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
3866 		/*
3867 		if ( ent1BlockingPlayer )
3868 		{//player will block this anyway
3869 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
3870 		}
3871 		if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
3872 			ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
3873 			ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
3874 			ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
3875 			ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
3876 			ent1->client->ps.torsoAnim == BOTH_A6_TR_BL ||
3877 			ent1->client->ps.torsoAnim == BOTH_A7_TR_BL ||
3878 			ent1->client->ps.torsoAnim == BOTH_P1_S1_TL )
3879 		{//ent1 is attacking in the opposite diagonal
3880 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
3881 		}
3882 		if ( ent1->client->ps.torsoAnim == BOTH_A1_BR_TL ||
3883 			ent1->client->ps.torsoAnim == BOTH_A2_BR_TL ||
3884 			ent1->client->ps.torsoAnim == BOTH_A3_BR_TL ||
3885 			ent1->client->ps.torsoAnim == BOTH_A4_BR_TL ||
3886 			ent1->client->ps.torsoAnim == BOTH_A5_BR_TL ||
3887 			ent1->client->ps.torsoAnim == BOTH_A6_BR_TL ||
3888 			ent1->client->ps.torsoAnim == BOTH_A7_BR_TL ||
3889 			ent1->client->ps.torsoAnim == BOTH_P1_S1_BL )
3890 		{//ent1 is attacking in the opposite diagonal
3891 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BL );
3892 		}
3893 		if ( ent1Boss && !Q_irand( 0, 3 ) )
3894 		{
3895 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
3896 		}
3897 		*/
3898 	}
3899 
3900 	//TL to BR lock
3901 	if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
3902 		ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
3903 		ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
3904 		ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
3905 		ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
3906 		ent1->client->ps.torsoAnim == BOTH_A6_TL_BR ||
3907 		ent1->client->ps.torsoAnim == BOTH_A7_TL_BR )
3908 	{//ent1 is attacking diagonally
3909 		return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
3910 		/*
3911 		if ( ent2BlockingPlayer )
3912 		{//player will block this anyway
3913 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
3914 		}
3915 		if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
3916 			ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
3917 			ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
3918 			ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
3919 			ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
3920 			ent2->client->ps.torsoAnim == BOTH_A6_TL_BR ||
3921 			ent2->client->ps.torsoAnim == BOTH_A7_TL_BR ||
3922 			ent2->client->ps.torsoAnim == BOTH_P1_S1_TR )
3923 		{//ent2 is attacking in the opposite diagonal
3924 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
3925 		}
3926 		if ( ent2->client->ps.torsoAnim == BOTH_A1_BL_TR ||
3927 			ent2->client->ps.torsoAnim == BOTH_A2_BL_TR ||
3928 			ent2->client->ps.torsoAnim == BOTH_A3_BL_TR ||
3929 			ent2->client->ps.torsoAnim == BOTH_A4_BL_TR ||
3930 			ent2->client->ps.torsoAnim == BOTH_A5_BL_TR ||
3931 			ent2->client->ps.torsoAnim == BOTH_A6_BL_TR ||
3932 			ent2->client->ps.torsoAnim == BOTH_A7_BL_TR ||
3933 			ent2->client->ps.torsoAnim == BOTH_P1_S1_BR )
3934 		{//ent2 is attacking in the opposite diagonal
3935 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BR );
3936 		}
3937 		if ( ent2Boss && !Q_irand( 0, 3 ) )
3938 		{
3939 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
3940 		}
3941 		*/
3942 	}
3943 
3944 	if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
3945 		ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
3946 		ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
3947 		ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
3948 		ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
3949 		ent2->client->ps.torsoAnim == BOTH_A6_TL_BR ||
3950 		ent2->client->ps.torsoAnim == BOTH_A7_TL_BR )
3951 	{//ent2 is attacking diagonally
3952 		return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
3953 		/*
3954 		if ( ent1BlockingPlayer )
3955 		{//player will block this anyway
3956 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
3957 		}
3958 		if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
3959 			ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
3960 			ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
3961 			ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
3962 			ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
3963 			ent1->client->ps.torsoAnim == BOTH_A6_TL_BR ||
3964 			ent1->client->ps.torsoAnim == BOTH_A7_TL_BR ||
3965 			ent1->client->ps.torsoAnim == BOTH_P1_S1_TR )
3966 		{//ent1 is attacking in the opposite diagonal
3967 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
3968 		}
3969 		if ( ent1->client->ps.torsoAnim == BOTH_A1_BL_TR ||
3970 			ent1->client->ps.torsoAnim == BOTH_A2_BL_TR ||
3971 			ent1->client->ps.torsoAnim == BOTH_A3_BL_TR ||
3972 			ent1->client->ps.torsoAnim == BOTH_A4_BL_TR ||
3973 			ent1->client->ps.torsoAnim == BOTH_A5_BL_TR ||
3974 			ent1->client->ps.torsoAnim == BOTH_A6_BL_TR ||
3975 			ent1->client->ps.torsoAnim == BOTH_A7_BL_TR ||
3976 			ent1->client->ps.torsoAnim == BOTH_P1_S1_BR )
3977 		{//ent1 is attacking in the opposite diagonal
3978 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BR );
3979 		}
3980 		if ( ent1Boss && !Q_irand( 0, 3 ) )
3981 		{
3982 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
3983 		}
3984 		*/
3985 	}
3986 	//T to B lock
3987 	if ( ent1->client->ps.torsoAnim == BOTH_A1_T__B_ ||
3988 		ent1->client->ps.torsoAnim == BOTH_A2_T__B_ ||
3989 		ent1->client->ps.torsoAnim == BOTH_A3_T__B_ ||
3990 		ent1->client->ps.torsoAnim == BOTH_A4_T__B_ ||
3991 		ent1->client->ps.torsoAnim == BOTH_A5_T__B_ ||
3992 		ent1->client->ps.torsoAnim == BOTH_A6_T__B_ ||
3993 		ent1->client->ps.torsoAnim == BOTH_A7_T__B_ )
3994 	{//ent1 is attacking top-down
3995 		/*
3996 		if ( ent2->client->ps.torsoAnim == BOTH_P1_S1_T_ ||
3997 			ent2->client->ps.torsoAnim == BOTH_K1_S1_T_ )
3998 		*/
3999 		{//ent2 is blocking at top
4000 			return WP_SabersCheckLock2( ent1, ent2, LOCK_TOP );
4001 		}
4002 	}
4003 
4004 	if ( ent2->client->ps.torsoAnim == BOTH_A1_T__B_ ||
4005 		ent2->client->ps.torsoAnim == BOTH_A2_T__B_ ||
4006 		ent2->client->ps.torsoAnim == BOTH_A3_T__B_ ||
4007 		ent2->client->ps.torsoAnim == BOTH_A4_T__B_ ||
4008 		ent2->client->ps.torsoAnim == BOTH_A5_T__B_ ||
4009 		ent2->client->ps.torsoAnim == BOTH_A6_T__B_ ||
4010 		ent2->client->ps.torsoAnim == BOTH_A7_T__B_ )
4011 	{//ent2 is attacking top-down
4012 		/*
4013 		if ( ent1->client->ps.torsoAnim == BOTH_P1_S1_T_ ||
4014 			ent1->client->ps.torsoAnim == BOTH_K1_S1_T_ )
4015 		*/
4016 		{//ent1 is blocking at top
4017 			return WP_SabersCheckLock2( ent2, ent1, LOCK_TOP );
4018 		}
4019 	}
4020 	/*
4021 	if ( !Q_irand( 0, 10 ) )
4022 	{
4023 		return WP_SabersCheckLock2( ent1, ent2, LOCK_RANDOM );
4024 	}
4025 	*/
4026 	return qfalse;
4027 }
4028 
WP_SaberParry(gentity_t * victim,gentity_t * attacker,int saberNum,int bladeNum)4029 qboolean WP_SaberParry( gentity_t *victim, gentity_t *attacker, int saberNum, int bladeNum )
4030 {
4031 	if ( !victim || !victim->client || !attacker )
4032 	{
4033 		return qfalse;
4034 	}
4035 	if ( Rosh_BeingHealed( victim ) )
4036 	{
4037 		return qfalse;
4038 	}
4039 	if ( G_InCinematicSaberAnim( victim ) )
4040 	{
4041 		return qfalse;
4042 	}
4043 	if ( PM_SuperBreakLoseAnim( victim->client->ps.torsoAnim )
4044 		|| PM_SuperBreakWinAnim( victim->client->ps.torsoAnim ) )
4045 	{
4046 		return qfalse;
4047 	}
4048 	if ( victim->s.number || g_saberAutoBlocking->integer || victim->client->ps.saberBlockingTime > level.time )
4049 	{//either an NPC or a player who is blocking
4050 		if ( !PM_SaberInTransitionAny( victim->client->ps.saberMove )
4051 			&& !PM_SaberInBounce( victim->client->ps.saberMove )
4052 			&& !PM_SaberInKnockaway( victim->client->ps.saberMove ) )
4053 		{//I'm not attacking, in transition or in a bounce or knockaway, so play a parry
4054 			WP_SaberBlockNonRandom( victim, saberHitLocation, qfalse );
4055 		}
4056 		victim->client->ps.saberEventFlags |= SEF_PARRIED;
4057 
4058 		//since it was parried, take away any damage done
4059 		//FIXME: what if the damage was done before the parry?
4060 		WP_SaberClearDamageForEntNum( attacker, victim->s.number, saberNum, bladeNum );
4061 
4062 		//tell the victim to get mad at me
4063 		if ( victim->enemy != attacker && victim->client->playerTeam != attacker->client->playerTeam )
4064 		{//they're not mad at me and they're not on my team
4065 			G_ClearEnemy( victim );
4066 			G_SetEnemy( victim, attacker );
4067 		}
4068 		return qtrue;
4069 	}
4070 	return qfalse;
4071 }
4072 
WP_BrokenParryKnockDown(gentity_t * victim)4073 qboolean WP_BrokenParryKnockDown( gentity_t *victim )
4074 {
4075 	if ( !victim || !victim->client )
4076 	{
4077 		return qfalse;
4078 	}
4079 	if ( PM_SuperBreakLoseAnim( victim->client->ps.torsoAnim )
4080 		|| PM_SuperBreakWinAnim( victim->client->ps.torsoAnim ) )
4081 	{
4082 		return qfalse;
4083 	}
4084 	if ( victim->client->ps.saberMove == LS_PARRY_UP
4085 		|| victim->client->ps.saberMove == LS_PARRY_UR
4086 		|| victim->client->ps.saberMove == LS_PARRY_UL
4087 		|| victim->client->ps.saberMove == LS_H1_BR
4088 		|| victim->client->ps.saberMove == LS_H1_B_
4089 		|| victim->client->ps.saberMove == LS_H1_BL )
4090 	{//knock their asses down!
4091 		int knockAnim = BOTH_KNOCKDOWN1;
4092 		if ( PM_CrouchAnim( victim->client->ps.legsAnim ) )
4093 		{
4094 			knockAnim = BOTH_KNOCKDOWN4;
4095 		}
4096 		NPC_SetAnim( victim, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
4097 		G_AddEvent( victim, EV_PAIN, victim->health );
4098 		return qtrue;
4099 	}
4100 	return qfalse;
4101 }
4102 
G_TryingKataAttack(gentity_t * self,usercmd_t * cmd)4103 qboolean G_TryingKataAttack( gentity_t *self, usercmd_t *cmd )
4104 {
4105 	if ( g_saberNewControlScheme->integer )
4106 	{//use the new control scheme: force focus button
4107 		if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
4108 		{
4109 			return qtrue;
4110 		}
4111 		else
4112 		{
4113 			return qfalse;
4114 		}
4115 	}
4116 	else //if ( self && self->client )
4117 	{//use the old control scheme
4118 		if ( (cmd->buttons&BUTTON_ALT_ATTACK) )
4119 		{//pressing alt-attack
4120 			//if ( !(self->client->ps.pm_flags&PMF_ALT_ATTACK_HELD) )
4121 			{//haven't been holding alt-attack
4122 				if ( (cmd->buttons&BUTTON_ATTACK) )
4123 				{//pressing attack
4124 					return qtrue;
4125 				}
4126 			}
4127 		}
4128 	}
4129 	return qfalse;
4130 }
4131 
G_TryingPullAttack(gentity_t * self,usercmd_t * cmd,qboolean amPulling)4132 qboolean G_TryingPullAttack( gentity_t *self, usercmd_t *cmd, qboolean amPulling )
4133 {
4134 	if ( g_saberNewControlScheme->integer )
4135 	{//use the new control scheme: force focus button
4136 		if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
4137 		{
4138 			if ( self && self->client )
4139 			{
4140 				if ( self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 )
4141 				{//force pull 3
4142 					if ( amPulling
4143 						|| (self->client->ps.forcePowersActive&(1<<FP_PULL))
4144 						|| self->client->ps.forcePowerDebounce[FP_PULL] > level.time ) //force-pulling
4145 					{//pulling
4146 						return qtrue;
4147 					}
4148 				}
4149 			}
4150 		}
4151 		else
4152 		{
4153 			return qfalse;
4154 		}
4155 	}
4156 	else
4157 	{//use the old control scheme
4158 		if ( (cmd->buttons&BUTTON_ATTACK) )
4159 		{//pressing attack
4160 			if ( self && self->client )
4161 			{
4162 				if ( self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 )
4163 				{//force pull 3
4164 					if ( amPulling
4165 						|| (self->client->ps.forcePowersActive&(1<<FP_PULL))
4166 						|| self->client->ps.forcePowerDebounce[FP_PULL] > level.time ) //force-pulling
4167 					{//pulling
4168 						return qtrue;
4169 					}
4170 				}
4171 			}
4172 		}
4173 	}
4174 	return qfalse;
4175 }
4176 
G_TryingCartwheel(gentity_t * self,usercmd_t * cmd)4177 qboolean G_TryingCartwheel( gentity_t *self, usercmd_t *cmd )
4178 {
4179 	if ( g_saberNewControlScheme->integer )
4180 	{//use the new control scheme: force focus button
4181 		if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
4182 		{
4183 			return qtrue;
4184 		}
4185 		else
4186 		{
4187 			return qfalse;
4188 		}
4189 	}
4190 	else
4191 	{//use the old control scheme
4192 		if ( (cmd->buttons&BUTTON_ATTACK) )
4193 		{//pressing attack
4194 			if ( cmd->rightmove )
4195 			{
4196 				if ( self && self->client )
4197 				{
4198 					if ( cmd->upmove>0 //)
4199 						&& self->client->ps.groundEntityNum != ENTITYNUM_NONE )
4200 					{//on ground, pressing jump
4201 						return qtrue;
4202 					}
4203 					else
4204 					{//just jumped?
4205 						if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE
4206 							&& level.time - self->client->ps.lastOnGround <= 50//250
4207 							&& (self->client->ps.pm_flags&PMF_JUMPING) )//jumping
4208 						{//just jumped this or last frame
4209 							return qtrue;
4210 						}
4211 					}
4212 				}
4213 			}
4214 		}
4215 	}
4216 	return qfalse;
4217 }
4218 
G_TryingSpecial(gentity_t * self,usercmd_t * cmd)4219 qboolean G_TryingSpecial( gentity_t *self, usercmd_t *cmd )
4220 {
4221 	if ( g_saberNewControlScheme->integer )
4222 	{//use the new control scheme: force focus button
4223 		if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
4224 		{
4225 			return qtrue;
4226 		}
4227 		else
4228 		{
4229 			return qfalse;
4230 		}
4231 	}
4232 	else
4233 	{//use the old control scheme
4234 		return qfalse;
4235 	}
4236 }
4237 
G_TryingJumpAttack(gentity_t * self,usercmd_t * cmd)4238 qboolean G_TryingJumpAttack( gentity_t *self, usercmd_t *cmd )
4239 {
4240 	if ( g_saberNewControlScheme->integer )
4241 	{//use the new control scheme: force focus button
4242 		if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
4243 		{
4244 			return qtrue;
4245 		}
4246 		else
4247 		{
4248 			return qfalse;
4249 		}
4250 	}
4251 	else
4252 	{//use the old control scheme
4253 		if ( (cmd->buttons&BUTTON_ATTACK) )
4254 		{//pressing attack
4255 			if ( cmd->upmove>0 )
4256 			{//pressing jump
4257 				return qtrue;
4258 			}
4259 			else if ( self && self->client )
4260 			{//just jumped?
4261 				if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE
4262 					&& level.time - self->client->ps.lastOnGround <= 250
4263 					&& (self->client->ps.pm_flags&PMF_JUMPING) )//jumping
4264 				{//jumped within the last quarter second
4265 					return qtrue;
4266 				}
4267 			}
4268 		}
4269 	}
4270 	return qfalse;
4271 }
4272 
G_TryingJumpForwardAttack(gentity_t * self,usercmd_t * cmd)4273 qboolean G_TryingJumpForwardAttack( gentity_t *self, usercmd_t *cmd )
4274 {
4275 	if ( g_saberNewControlScheme->integer )
4276 	{//use the new control scheme: force focus button
4277 		if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
4278 		{
4279 			return qtrue;
4280 		}
4281 		else
4282 		{
4283 			return qfalse;
4284 		}
4285 	}
4286 	else
4287 	{//use the old control scheme
4288 		if ( (cmd->buttons&BUTTON_ATTACK) )
4289 		{//pressing attack
4290 			if ( cmd->forwardmove > 0 )
4291 			{//moving forward
4292 				if ( self && self->client )
4293 				{
4294 					if ( cmd->upmove>0
4295 						&& self->client->ps.groundEntityNum != ENTITYNUM_NONE )
4296 					{//pressing jump
4297 						return qtrue;
4298 					}
4299 					else
4300 					{//no slop on forward jumps - must be precise!
4301 						if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE
4302 							&& level.time - self->client->ps.lastOnGround <= 50
4303 							&& (self->client->ps.pm_flags&PMF_JUMPING) )//jumping
4304 						{//just jumped this or last frame
4305 							return qtrue;
4306 						}
4307 					}
4308 				}
4309 			}
4310 		}
4311 	}
4312 	return qfalse;
4313 }
4314 
G_TryingLungeAttack(gentity_t * self,usercmd_t * cmd)4315 qboolean G_TryingLungeAttack( gentity_t *self, usercmd_t *cmd )
4316 {
4317 	if ( g_saberNewControlScheme->integer )
4318 	{//use the new control scheme: force focus button
4319 		if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
4320 		{
4321 			return qtrue;
4322 		}
4323 		else
4324 		{
4325 			return qfalse;
4326 		}
4327 	}
4328 	else
4329 	{//use the old control scheme
4330 		if ( (cmd->buttons&BUTTON_ATTACK) )
4331 		{//pressing attack
4332 			if ( cmd->upmove<0 )
4333 			{//pressing crouch
4334 				return qtrue;
4335 			}
4336 			else if ( self && self->client )
4337 			{//just unducked?
4338 				if ( (self->client->ps.pm_flags&PMF_DUCKED) )
4339 				{//just unducking
4340 					return qtrue;
4341 				}
4342 			}
4343 		}
4344 	}
4345 	return qfalse;
4346 }
4347 
4348 
4349 //FIXME: for these below funcs, maybe in the old control scheme some moves should still cost power... if so, pass in the saberMove and use a switch statement
G_EnoughPowerForSpecialMove(int forcePower,int cost,qboolean kataMove)4350 qboolean G_EnoughPowerForSpecialMove( int forcePower, int cost, qboolean kataMove )
4351 {
4352 	if ( g_saberNewControlScheme->integer || kataMove )
4353 	{//special moves cost power
4354 		if ( forcePower >= cost )
4355 		{
4356 			return qtrue;
4357 		}
4358 		else
4359 		{
4360 			cg.forceHUDTotalFlashTime = level.time + 1000;
4361 			return qfalse;
4362 		}
4363 	}
4364 	else
4365 	{//old control scheme: uses no power, so just do it
4366 		return qtrue;
4367 	}
4368 }
4369 
G_DrainPowerForSpecialMove(gentity_t * self,forcePowers_t fp,int cost,qboolean kataMove)4370 void G_DrainPowerForSpecialMove( gentity_t *self, forcePowers_t fp, int cost, qboolean kataMove )
4371 {
4372 	if ( !self || !self->client || self->s.number >= MAX_CLIENTS )
4373 	{
4374 		return;
4375 	}
4376 	if ( g_saberNewControlScheme->integer || kataMove )
4377 	{//special moves cost power
4378 		WP_ForcePowerDrain( self, fp, cost );//drain the required force power
4379 	}
4380 	else
4381 	{//old control scheme: uses no power, so just do it
4382 	}
4383 }
4384 
G_CostForSpecialMove(int cost,qboolean kataMove)4385 int G_CostForSpecialMove( int cost, qboolean kataMove )
4386 {
4387 	if ( g_saberNewControlScheme->integer || kataMove )
4388 	{//special moves cost power
4389 		return cost;
4390 	}
4391 	else
4392 	{//old control scheme: uses no power, so just do it
4393 		return 0;
4394 	}
4395 }
4396 
4397 extern qboolean G_EntIsBreakable( int entityNum, gentity_t *breaker );
WP_SaberRadiusDamage(gentity_t * ent,vec3_t point,float radius,int damage,float knockBack)4398 void WP_SaberRadiusDamage( gentity_t *ent, vec3_t point, float radius, int damage, float knockBack )
4399 {
4400 	if ( !ent || !ent->client )
4401 	{
4402 		return;
4403 	}
4404 	else if ( radius <= 0.0f || (damage <= 0 && knockBack <= 0) )
4405 	{
4406 		return;
4407 	}
4408 	else
4409 	{
4410 		vec3_t		mins, maxs, entDir;
4411 		gentity_t	*radiusEnts[128];
4412 		int			numEnts, i;
4413 		float		dist;
4414 
4415 		//Setup the bbox to search in
4416 		for ( i = 0; i < 3; i++ )
4417 		{
4418 			mins[i] = point[i] - radius;
4419 			maxs[i] = point[i] + radius;
4420 		}
4421 
4422 		//Get the number of entities in a given space
4423 		numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, 128 );
4424 
4425 		for ( i = 0; i < numEnts; i++ )
4426 		{
4427 			if ( !radiusEnts[i]->inuse )
4428 			{
4429 				continue;
4430 			}
4431 
4432 			if ( radiusEnts[i] == ent )
4433 			{//Skip myself
4434 				continue;
4435 			}
4436 
4437 			if ( radiusEnts[i]->client == NULL )
4438 			{//must be a client
4439 				if ( G_EntIsBreakable( radiusEnts[i]->s.number, ent ) )
4440 				{//damage breakables within range, but not as much
4441 					G_Damage( radiusEnts[i], ent, ent, vec3_origin, radiusEnts[i]->currentOrigin, 10, 0, MOD_EXPLOSIVE_SPLASH );
4442 				}
4443 				continue;
4444 			}
4445 
4446 			if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR)
4447 				|| (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_WAMPA) )
4448 			{//can't be one being held
4449 				continue;
4450 			}
4451 
4452 			VectorSubtract( radiusEnts[i]->currentOrigin, point, entDir );
4453 			dist = VectorNormalize( entDir );
4454 			if ( dist <= radius )
4455 			{//in range
4456 				if ( damage > 0 )
4457 				{//do damage
4458 					int points = ceil((float)damage*dist/radius);
4459 					G_Damage( radiusEnts[i], ent, ent, vec3_origin, radiusEnts[i]->currentOrigin, points, DAMAGE_NO_KNOCKBACK, MOD_EXPLOSIVE_SPLASH );
4460 				}
4461 				if ( knockBack > 0 )
4462 				{//do knockback
4463 					if ( radiusEnts[i]->client
4464 						&& radiusEnts[i]->client->NPC_class != CLASS_RANCOR
4465 						&& radiusEnts[i]->client->NPC_class != CLASS_ATST
4466 						&& !(radiusEnts[i]->flags&FL_NO_KNOCKBACK) )//don't throw them back
4467 					{
4468 						float knockbackStr = knockBack*dist/radius;
4469 						entDir[2] += 0.1f;
4470 						VectorNormalize( entDir );
4471 						G_Throw( radiusEnts[i], entDir, knockbackStr );
4472 						if ( radiusEnts[i]->health > 0 )
4473 						{//still alive
4474 							if ( knockbackStr > 50 )
4475 							{//close enough and knockback high enough to possibly knock down
4476 								if ( dist < (radius*0.5f)
4477 									|| radiusEnts[i]->client->ps.groundEntityNum != ENTITYNUM_NONE )
4478 								{//within range of my fist or within ground-shaking range and not in the air
4479 									G_Knockdown( radiusEnts[i], ent, entDir, 500, qtrue );
4480 								}
4481 							}
4482 						}
4483 					}
4484 				}
4485 			}
4486 		}
4487 	}
4488 }
4489 /*
4490 ---------------------------------------------------------
4491 void WP_SaberDamageTrace( gentity_t *ent, int saberNum, int bladeNum )
4492 
4493   Constantly trace from the old blade pos to new, down the saber beam and do damage
4494 
4495   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!
4496 ---------------------------------------------------------
4497 */
4498 #define MAX_SABER_SWING_INC 0.33f
WP_SaberDamageTrace(gentity_t * ent,int saberNum,int bladeNum)4499 void WP_SaberDamageTrace( gentity_t *ent, int saberNum, int bladeNum )
4500 {
4501 	vec3_t		mp1, mp2, md1, md2, baseOld, baseNew, baseDiff, endOld, endNew, bladePointOld, bladePointNew;
4502 	float		tipDmgMod = 1.0f;
4503 	float		baseDamage;
4504 	int			baseDFlags = 0;
4505 	qboolean	hit_wall = qfalse;
4506 	qboolean	brokenParry = qfalse;
4507 
4508 	for ( int ven = 0; ven < MAX_SABER_VICTIMS; ven++ )
4509 	{
4510 		victimEntityNum[ven] = ENTITYNUM_NONE;
4511 	}
4512 	memset( totalDmg, 0, sizeof( totalDmg) );
4513 	memset( dmgDir, 0, sizeof( dmgDir ) );
4514 	memset( dmgNormal, 0, sizeof( dmgNormal ) );
4515 	memset( dmgSpot, 0, sizeof( dmgSpot ) );
4516 	memset( dmgFraction, 0, sizeof( dmgFraction ) );
4517 	memset( hitLoc, HL_NONE, sizeof( hitLoc ) );
4518 	memset( hitDismemberLoc, HL_NONE, sizeof( hitDismemberLoc ) );
4519 	memset( hitDismember, qfalse, sizeof( hitDismember ) );
4520 	numVictims = 0;
4521 	VectorClear(saberHitLocation);
4522 	VectorClear(saberHitNormal);
4523 	saberHitFraction = 1.0;	// Closest saber hit.  The saber can do no damage past this point.
4524 	saberHitEntity = ENTITYNUM_NONE;
4525 	sabersCrossed = -1;
4526 
4527 	if ( !ent->client )
4528 	{
4529 		return;
4530 	}
4531 
4532 	if ( !ent->s.number )
4533 	{//player never uses these
4534 		ent->client->ps.saberEventFlags &= ~SEF_EVENTS;
4535 	}
4536 
4537 	if ( ent->client->ps.saber[saberNum].blade[bladeNum].length <= 1 )//cen get down to 1 when in a wall
4538 	{//saber is not on
4539 		return;
4540 	}
4541 
4542 	if ( VectorCompare( ent->client->renderInfo.muzzlePointOld, vec3_origin ) || VectorCompare( ent->client->renderInfo.muzzleDirOld, vec3_origin ) )
4543 	{
4544 		//just started up the saber?
4545 		return;
4546 	}
4547 
4548 	int saberContents = 0;
4549 	if ( !(ent->client->ps.saber[saberNum].saberFlags&SFL_ON_IN_WATER) )
4550 	{//saber can't stay on underwater
4551 		saberContents = gi.pointcontents( ent->client->renderInfo.muzzlePoint, ent->client->ps.saberEntityNum );
4552 	}
4553 	if ( (saberContents&CONTENTS_WATER)||
4554 		(saberContents&CONTENTS_SLIME)||
4555 		(saberContents&CONTENTS_LAVA) )
4556 	{//um... turn off?  Or just set length to 1?
4557 		//FIXME: short-out effect/sound?
4558 		ent->client->ps.saber[saberNum].blade[bladeNum].active = qfalse;
4559 		return;
4560 	}
4561 	else if (!g_saberNoEffects && gi.WE_IsOutside(ent->client->renderInfo.muzzlePoint))
4562 	{
4563 		float chanceOfFizz = gi.WE_GetChanceOfSaberFizz();
4564 		if (chanceOfFizz>0 && Q_flrand(0.0f, 1.0f)<chanceOfFizz)
4565 		{
4566 			vec3_t	end; /*normal = {0,0,1};//FIXME: opposite of rain angles?*/
4567 			VectorMA( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, ent->client->ps.saber[saberNum].blade[bladeNum].length*Q_flrand(0, 1), ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, end );
4568 			G_PlayEffect( "saber/fizz", end );
4569 		}
4570 	}
4571 
4572 	//FIXMEFIXMEFIXME: When in force speed (esp. lvl 3), need to interpolate this because
4573 	//		we animate so much faster that the arc is pretty much flat...
4574 
4575 	int entPowerLevel = 0;
4576 	if ( ent->client->NPC_class == CLASS_SABER_DROID )
4577 	{
4578 		entPowerLevel = SaberDroid_PowerLevelForSaberAnim( ent );
4579 	}
4580 	else if ( !ent->s.number && (ent->client->ps.forcePowersActive&(1<<FP_SPEED)) )
4581 	{
4582 		entPowerLevel = FORCE_LEVEL_3;
4583 	}
4584 	else
4585 	{
4586 		entPowerLevel = PM_PowerLevelForSaberAnim( &ent->client->ps, saberNum );
4587 	}
4588 
4589 	if ( entPowerLevel )
4590 	{
4591 		if ( ent->client->ps.forceRageRecoveryTime > level.time )
4592 		{
4593 			entPowerLevel = FORCE_LEVEL_1;
4594 		}
4595 		else if ( ent->client->ps.forcePowersActive & (1 << FP_RAGE) )
4596 		{
4597 			entPowerLevel += ent->client->ps.forcePowerLevel[FP_RAGE];
4598 		}
4599 	}
4600 
4601 	if ( ent->client->ps.saberInFlight )
4602 	{//flying sabers are much more deadly
4603 		//unless you're dead
4604 		if ( ent->health <= 0 && g_saberRealisticCombat->integer < 2 )
4605 		{//so enemies don't keep trying to block it
4606 			//FIXME: still do damage, just not to humanoid clients who should try to avoid it
4607 			//baseDamage = 0.0f;
4608 			return;
4609 		}
4610 		//or unless returning
4611 		else if ( ent->client->ps.saberEntityState == SES_RETURNING
4612 			&& !(ent->client->ps.saber[0].saberFlags&SFL_RETURN_DAMAGE) )//type != SABER_STAR )
4613 		{//special case, since we're returning, chances are if we hit something
4614 			//it's going to be butt-first.  So do less damage.
4615 			baseDamage = 0.1f;
4616 		}
4617 		else
4618 		{
4619 			if ( !ent->s.number )
4620 			{//cheat for player
4621 				baseDamage = 10.0f;
4622 			}
4623 			else
4624 			{
4625 				baseDamage = 2.5f;
4626 			}
4627 		}
4628 		//Use old to current since can't predict it
4629 		VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld, mp1 );
4630 		VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld, md1 );
4631 		VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, mp2 );
4632 		VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, md2 );
4633 	}
4634 	else
4635 	{
4636 		if ( ent->client->ps.torsoAnim == BOTH_A7_HILT )
4637 		{//no effects, no damage
4638 			return;
4639 		}
4640 		else if ( G_InCinematicSaberAnim( ent ) )
4641 		{
4642 			baseDFlags = DAMAGE_NO_KILL;
4643 			baseDamage = 0.1f;
4644 		}
4645 		else if ( ent->client->ps.saberMove == LS_READY
4646 			&& !PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) )
4647 		{//just do effects
4648 			if ( g_saberRealisticCombat->integer < 2 )
4649 			{//don't kill with this hit
4650 				baseDFlags = DAMAGE_NO_KILL;
4651 			}
4652 			if (  (!WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4653 						&& (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_IDLE_EFFECT) )
4654 				|| ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4655 						&& (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_IDLE_EFFECT2) )
4656 				)
4657 			{//do nothing at all when idle
4658 				return;
4659 			}
4660 			baseDamage = 0;
4661 		}
4662 		else if ( ent->client->ps.saberLockTime > level.time )
4663 		{//just do effects
4664 			if (  (!WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4665 						&& (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_IDLE_EFFECT) )
4666 				|| ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4667 						&& (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_IDLE_EFFECT2) )
4668 				)
4669 			{//do nothing at all when idle
4670 				return;
4671 			}
4672 			baseDamage = 0;
4673 		}
4674 		else if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4675 			&& ent->client->ps.saber[saberNum].damageScale <= 0.0f
4676 			&& ent->client->ps.saber[saberNum].knockbackScale <= 0.0f )
4677 		{//this blade does no damage and no knockback (only for blocking?)
4678 			baseDamage = 0;
4679 		}
4680 		else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4681 			&& ent->client->ps.saber[saberNum].damageScale2 <= 0.0f
4682 			&& ent->client->ps.saber[saberNum].knockbackScale2 <= 0.0f )
4683 		{//this blade does no damage and no knockback (only for blocking?)
4684 			baseDamage = 0;
4685 		}
4686 		else if ( ent->client->ps.saberBlocked > BLOCKED_NONE
4687 				 || ( !PM_SaberInAttack( ent->client->ps.saberMove )
4688 					  && !PM_SaberInSpecialAttack( ent->client->ps.torsoAnim )
4689 					  && !PM_SaberInTransitionAny( ent->client->ps.saberMove )
4690 					)
4691 				)
4692 		{//don't do damage if parrying/reflecting/bouncing/deflecting or not actually attacking or in a transition to/from/between attacks
4693 			if (  (!WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4694 						&& (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_IDLE_EFFECT) )
4695 				|| ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4696 						&& (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_IDLE_EFFECT2) )
4697 				)
4698 			{//do nothing at all when idle
4699 				return;
4700 			}
4701 			baseDamage = 0;
4702 		}
4703 		else
4704 		{//okay, in a saberMove that does damage
4705 			//make sure we're in the right anim
4706 			if ( !PM_SaberInSpecialAttack( ent->client->ps.torsoAnim )
4707 				&& !PM_InAnimForSaberMove( ent->client->ps.torsoAnim, ent->client->ps.saberMove ) )
4708 			{//forced into some other animation somehow, like a pain or death?
4709 				if (  (!WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4710 							&& (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_IDLE_EFFECT) )
4711 					|| ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4712 							&& (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_IDLE_EFFECT2) )
4713 					)
4714 				{//do nothing at all when idle
4715 					return;
4716 				}
4717 				baseDamage = 0;
4718 			}
4719 			else if ( ent->client->ps.weaponstate == WEAPON_FIRING && ent->client->ps.saberBlocked == BLOCKED_NONE &&
4720 				( PM_SaberInAttack(ent->client->ps.saberMove) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) || PM_SpinningSaberAnim(ent->client->ps.torsoAnim) || entPowerLevel > FORCE_LEVEL_2 || (WP_SaberBladeDoTransitionDamage( &ent->client->ps.saber[saberNum], bladeNum )&&PM_SaberInTransitionAny(ent->client->ps.saberMove)) ) )// || ent->client->ps.saberAnimLevel == SS_STAFF ) )
4721 			{//normal attack swing swinging/spinning (or if using strong set), do normal damage //FIXME: or if using staff?
4722 				//FIXME: more damage for higher attack power levels?
4723 				//		More damage based on length/color of saber?
4724 				//FIXME: Desann does double damage?
4725 				if ( g_saberRealisticCombat->integer )
4726 				{
4727 					switch ( entPowerLevel )
4728 					{
4729 					default:
4730 					case FORCE_LEVEL_3:
4731 						baseDamage = 10.0f;
4732 						break;
4733 					case FORCE_LEVEL_2:
4734 						baseDamage = 5.0f;
4735 						break;
4736 					case FORCE_LEVEL_0:
4737 					case FORCE_LEVEL_1:
4738 						baseDamage = 2.5f;
4739 						break;
4740 					}
4741 				}
4742 				else
4743 				{
4744 					if ( g_spskill->integer > 0
4745 						&& ent->s.number < MAX_CLIENTS
4746 						&& ( ent->client->ps.torsoAnim == BOTH_ROLL_STAB
4747 							|| ent->client->ps.torsoAnim == BOTH_SPINATTACK6
4748 							|| ent->client->ps.torsoAnim == BOTH_SPINATTACK7
4749 							|| ent->client->ps.torsoAnim == BOTH_LUNGE2_B__T_ ) )
4750 					{//*sigh*, these anim do less damage since they're so easy to do
4751 						baseDamage = 2.5f;
4752 					}
4753 					else
4754 					{
4755 						baseDamage = 2.5f * (float)entPowerLevel;
4756 					}
4757 				}
4758 			}
4759 			else
4760 			{//saber is transitioning, defending or idle, don't do as much damage
4761 				//FIXME: strong attacks and returns should do damage and be unblockable
4762 				if ( g_timescale->value < 1.0 )
4763 				{//in slow mo or force speed, we need to do damage during the transitions
4764 					if ( g_saberRealisticCombat->integer )
4765 					{
4766 						switch ( entPowerLevel )
4767 						{
4768 						case FORCE_LEVEL_3:
4769 							baseDamage = 10.0f;
4770 							break;
4771 						case FORCE_LEVEL_2:
4772 							baseDamage = 5.0f;
4773 							break;
4774 						default:
4775 						case FORCE_LEVEL_1:
4776 							baseDamage = 2.5f;
4777 							break;
4778 						}
4779 					}
4780 					else
4781 					{
4782 						baseDamage = 2.5f * (float)entPowerLevel;
4783 					}
4784 				}
4785 				else// if ( !ent->s.number )
4786 				{//I have to do *some* damage in transitions or else you feel like a total gimp
4787 					baseDamage = 0.1f;
4788 				}
4789 				/*
4790 				else
4791 				{
4792 					baseDamage = 0;//was 1.0f;//was 0.25
4793 				}
4794 				*/
4795 			}
4796 		}
4797 
4798 		//Use current to next since can predict it
4799 		//FIXME: if they're closer than the saber blade start, we don't want the
4800 		//		arm to pass through them without any damage... so check the radius
4801 		//		and push them away (do pain & knockback)
4802 		//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?
4803 		//VectorCopy( ent->client->renderInfo.muzzlePoint, mp1 );
4804 		//VectorCopy( ent->client->renderInfo.muzzleDir, md1 );
4805 		//VectorCopy( ent->client->renderInfo.muzzlePointNext, mp2 );
4806 		//VectorCopy( ent->client->renderInfo.muzzleDirNext, md2 );
4807 		//prediction was causing gaps in swing (G2 problem) so *don't* predict
4808 		if ( ent->client->ps.saberDamageDebounceTime > level.time )
4809 		{//really only used when a saber attack start anim starts, not actually for stopping damage
4810 			//we just want to not use the old position to trace the attack from...
4811 			VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld );
4812 			VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld );
4813 		}
4814 		//do the damage trace from the last position...
4815 		VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld, mp1 );
4816 		VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld, md1 );
4817 		//...to the current one.
4818 		VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, mp2 );
4819 		VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, md2 );
4820 
4821 		//NOTE: this is a test, may not be necc, as I can still swing right through someone without hitting them, somehow...
4822 		//see if anyone is so close that they're within the dist from my origin to the start of the saber
4823 		if ( ent->health > 0 && !ent->client->ps.saberLockTime && saberNum == 0 && bladeNum == 0
4824 			&& !G_InCinematicSaberAnim( ent ) )
4825 		{//only do once - for first blade
4826 			trace_t trace;
4827 			gi.trace( &trace, ent->currentOrigin, vec3_origin, vec3_origin, mp1, ent->s.number, (MASK_SHOT&~(CONTENTS_CORPSE|CONTENTS_ITEM)), (EG2_Collision)0, 0 );
4828 			if ( trace.entityNum < ENTITYNUM_WORLD && (trace.entityNum > 0||ent->client->NPC_class == CLASS_DESANN) )//NPCs don't push player away, unless it's Desann
4829 			{//a valid ent
4830 				gentity_t *traceEnt = &g_entities[trace.entityNum];
4831 				if ( traceEnt
4832 					&& traceEnt->client
4833 					&& traceEnt->client->NPC_class != CLASS_RANCOR
4834 					&& traceEnt->client->NPC_class != CLASS_ATST
4835 					&& traceEnt->client->NPC_class != CLASS_WAMPA
4836 					&& traceEnt->client->NPC_class != CLASS_SAND_CREATURE
4837 					&& traceEnt->health > 0
4838 					&& traceEnt->client->playerTeam != ent->client->playerTeam
4839 					&& !PM_SuperBreakLoseAnim( traceEnt->client->ps.legsAnim )
4840 					&& !PM_SuperBreakLoseAnim( traceEnt->client->ps.torsoAnim )
4841 					&& !PM_SuperBreakWinAnim( traceEnt->client->ps.legsAnim )
4842 					&& !PM_SuperBreakWinAnim( traceEnt->client->ps.torsoAnim )
4843 					&& !PM_InKnockDown( &traceEnt->client->ps )
4844 					&& !PM_LockedAnim( traceEnt->client->ps.legsAnim )
4845 					&& !PM_LockedAnim( traceEnt->client->ps.torsoAnim )
4846 					&& !G_InCinematicSaberAnim( traceEnt ))
4847 				{//enemy client, push them away
4848 					if ( !traceEnt->client->ps.saberLockTime
4849 						&& !traceEnt->message
4850 						&& !(traceEnt->flags&FL_NO_KNOCKBACK)
4851 						&& (!traceEnt->NPC||traceEnt->NPC->jumpState!=JS_JUMPING) )
4852 					{//don't push people in saberlock or with security keys or who are in BS_JUMP
4853 						vec3_t hitDir;
4854 						VectorSubtract( trace.endpos, ent->currentOrigin, hitDir );
4855 						float totalDist = Distance( mp1, ent->currentOrigin );
4856 						float knockback = (totalDist-VectorNormalize( hitDir ))/totalDist * 200.0f;
4857 						hitDir[2] = 0;
4858 						//FIXME: do we need to call G_Throw?  Seems unfair to put actual knockback on them, stops the attack
4859 						//G_Throw( traceEnt, hitDir, knockback );
4860 						VectorMA( traceEnt->client->ps.velocity, knockback, hitDir, traceEnt->client->ps.velocity );
4861 						traceEnt->client->ps.pm_time = 200;
4862 						traceEnt->client->ps.pm_flags |= PMF_TIME_NOFRICTION;
4863 #ifndef FINAL_BUILD
4864 						if ( d_saberCombat->integer )
4865 						{
4866 							gi.Printf( "%s pushing away %s at %s\n", ent->NPC_type, traceEnt->NPC_type, vtos( traceEnt->client->ps.velocity ) );
4867 						}
4868 #endif
4869 					}
4870 				}
4871 			}
4872 		}
4873 	}
4874 
4875 	//the thicker the blade, the more damage... the thinner, the less damage
4876 	baseDamage *= ent->client->ps.saber[saberNum].blade[bladeNum].radius/SABER_RADIUS_STANDARD;
4877 
4878 	if ( g_saberRealisticCombat->integer > 1 )
4879 	{//always do damage, and lots of it
4880 		if ( g_saberRealisticCombat->integer > 2 )
4881 		{//always do damage, and lots of it
4882 			baseDamage = 25.0f;
4883 		}
4884 		else if ( baseDamage > 0.1f )
4885 		{//only do super damage if we would have done damage according to normal rules
4886 			baseDamage = 25.0f;
4887 		}
4888 	}
4889 	else if ( ((!ent->s.number&&ent->client->ps.forcePowersActive&(1<<FP_SPEED))||ent->client->ps.forcePowersActive&(1<<FP_RAGE))
4890 		&& g_timescale->value < 1.0f )
4891 	{
4892 		baseDamage *= (1.0f-g_timescale->value);
4893 	}
4894 	if ( baseDamage > 0.1f )
4895 	{
4896 		if ( (ent->client->ps.forcePowersActive&(1<<FP_RAGE)) )
4897 		{//add some damage if raged
4898 			baseDamage += ent->client->ps.forcePowerLevel[FP_RAGE] * 5.0f;
4899 		}
4900 		else if ( ent->client->ps.forceRageRecoveryTime )
4901 		{//halve it if recovering
4902 			baseDamage *= 0.5f;
4903 		}
4904 	}
4905 	// Get the old state of the blade
4906 	VectorCopy( mp1, baseOld );
4907 	VectorMA( baseOld, ent->client->ps.saber[saberNum].blade[bladeNum].length, md1, endOld );
4908 	// Get the future state of the blade
4909 	VectorCopy( mp2, baseNew );
4910 	VectorMA( baseNew, ent->client->ps.saber[saberNum].blade[bladeNum].length, md2, endNew );
4911 
4912 	sabersCrossed = -1;
4913 	if ( VectorCompare2( baseOld, baseNew ) && VectorCompare2( endOld, endNew ) )
4914 	{
4915 		hit_wall = WP_SaberDamageForTrace( ent->s.number, mp2, endNew, baseDamage*4, md2,
4916 			qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qfalse,
4917 			saberNum, bladeNum );
4918 	}
4919 	else
4920 	{
4921 		float aveLength, step = 8, stepsize = 8;
4922 		vec3_t	ma1, ma2, md2ang, curBase1, curBase2;
4923 		int	xx;
4924 		//do the trace at the base first
4925 		hit_wall = WP_SaberDamageForTrace( ent->s.number, baseOld, baseNew, baseDamage, md2,
4926 			qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qtrue,
4927 			saberNum, bladeNum );
4928 
4929 		//if hit a saber, shorten rest of traces to match
4930 		if ( saberHitFraction < 1.0 )
4931 		{
4932 			//adjust muzzleDir...
4933 			vec3_t ma1, ma2;
4934 			vectoangles( md1, ma1 );
4935 			vectoangles( md2, ma2 );
4936 			for ( xx = 0; xx < 3; xx++ )
4937 			{
4938 				md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], saberHitFraction );
4939 			}
4940 			AngleVectors( md2ang, md2, NULL, NULL );
4941 			//shorten the base pos
4942 			VectorSubtract( mp2, mp1, baseDiff );
4943 			VectorMA( mp1, saberHitFraction, baseDiff, baseNew );
4944 			VectorMA( baseNew, ent->client->ps.saber[saberNum].blade[bladeNum].length, md2, endNew );
4945 		}
4946 
4947 		//If the angle diff in the blade is high, need to do it in chunks of 33 to avoid flattening of the arc
4948 		float dirInc, curDirFrac;
4949 		if ( PM_SaberInAttack( ent->client->ps.saberMove )
4950 			|| PM_SaberInSpecialAttack( ent->client->ps.torsoAnim )
4951 			|| PM_SpinningSaberAnim( ent->client->ps.torsoAnim )
4952 			|| PM_InSpecialJump( ent->client->ps.torsoAnim )
4953 			|| (g_timescale->value<1.0f&&PM_SaberInTransitionAny( ent->client->ps.saberMove )) )
4954 		{
4955 			curDirFrac = DotProduct( md1, md2 );
4956 		}
4957 		else
4958 		{
4959 			curDirFrac = 1.0f;
4960 		}
4961 		//NOTE: if saber spun at least 180 degrees since last damage trace, this is not reliable...!
4962 		if ( fabs(curDirFrac) < 1.0f - MAX_SABER_SWING_INC )
4963 		{//the saber blade spun more than 33 degrees since the last damage trace
4964 			curDirFrac = dirInc = 1.0f/((1.0f - curDirFrac)/MAX_SABER_SWING_INC);
4965 		}
4966 		else
4967 		{
4968 			curDirFrac = 1.0f;
4969 			dirInc = 0.0f;
4970 		}
4971 		qboolean hit_saber = qfalse;
4972 
4973 		vectoangles( md1, ma1 );
4974 		vectoangles( md2, ma2 );
4975 
4976 		vec3_t curMD1, curMD2;//, mdDiff, dirDiff;
4977 		//VectorSubtract( md2, md1, mdDiff );
4978 		VectorCopy( md1, curMD2 );
4979 		VectorCopy( baseOld, curBase2 );
4980 
4981 		while ( 1 )
4982 		{
4983 			VectorCopy( curMD2, curMD1 );
4984 			VectorCopy( curBase2, curBase1 );
4985 			if ( curDirFrac >= 1.0f )
4986 			{
4987 				VectorCopy( md2, curMD2 );
4988 				VectorCopy( baseNew, curBase2 );
4989 			}
4990 			else
4991 			{
4992 				for ( xx = 0; xx < 3; xx++ )
4993 				{
4994 					md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], curDirFrac );
4995 				}
4996 				AngleVectors( md2ang, curMD2, NULL, NULL );
4997 				//VectorMA( md1, curDirFrac, mdDiff, curMD2 );
4998 				VectorSubtract( baseNew, baseOld, baseDiff );
4999 				VectorMA( baseOld, curDirFrac, baseDiff, curBase2 );
5000 			}
5001 			// Move up the blade in intervals of stepsize
5002 			for ( step = stepsize; step < ent->client->ps.saber[saberNum].blade[bladeNum].length && step < ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld; step+=12 )
5003 			{
5004 				VectorMA( curBase1, step, curMD1, bladePointOld );
5005 				VectorMA( curBase2, step, curMD2, bladePointNew );
5006 				if ( WP_SaberDamageForTrace( ent->s.number, bladePointOld, bladePointNew, baseDamage, curMD2,
5007 					qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qtrue,
5008 					saberNum, bladeNum ) )
5009 				{
5010 					hit_wall = qtrue;
5011 				}
5012 
5013 				//if hit a saber, shorten rest of traces to match
5014 				if ( saberHitFraction < 1.0 )
5015 				{
5016 					//adjust muzzle endpoint
5017 					VectorSubtract( mp2, mp1, baseDiff );
5018 					VectorMA( mp1, saberHitFraction, baseDiff, baseNew );
5019 					VectorMA( baseNew, ent->client->ps.saber[saberNum].blade[bladeNum].length, curMD2, endNew );
5020 					//adjust muzzleDir...
5021 					vec3_t curMA1, curMA2;
5022 					vectoangles( curMD1, curMA1 );
5023 					vectoangles( curMD2, curMA2 );
5024 					for ( xx = 0; xx < 3; xx++ )
5025 					{
5026 						md2ang[xx] = LerpAngle( curMA1[xx], curMA2[xx], saberHitFraction );
5027 					}
5028 					AngleVectors( md2ang, curMD2, NULL, NULL );
5029 					/*
5030 					VectorSubtract( curMD2, curMD1, dirDiff );
5031 					VectorMA( curMD1, saberHitFraction, dirDiff, curMD2 );
5032 					*/
5033 					hit_saber = qtrue;
5034 				}
5035 				if (hit_wall)
5036 				{
5037 					break;
5038 				}
5039 			}
5040 			if ( hit_wall || hit_saber )
5041 			{
5042 				break;
5043 			}
5044 			if ( curDirFrac >= 1.0f )
5045 			{
5046 				break;
5047 			}
5048 			else
5049 			{
5050 				curDirFrac += dirInc;
5051 				if ( curDirFrac >= 1.0f )
5052 				{
5053 					curDirFrac = 1.0f;
5054 				}
5055 			}
5056 		}
5057 
5058 		//do the trace at the end last
5059 		//Special check- adjust for length of blade not being a multiple of 12
5060 		aveLength = (ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld + ent->client->ps.saber[saberNum].blade[bladeNum].length)/2;
5061 		if ( step > aveLength )
5062 		{//less dmg if the last interval was not stepsize
5063 			tipDmgMod = (stepsize-(step-aveLength))/stepsize;
5064 		}
5065 		//NOTE: since this is the tip, we do not extrapolate the extra 16
5066 		if ( WP_SaberDamageForTrace( ent->s.number, endOld, endNew, tipDmgMod*baseDamage, md2,
5067 			qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qfalse,
5068 			saberNum, bladeNum ) )
5069 		{
5070 			hit_wall = qtrue;
5071 		}
5072 	}
5073 
5074 	if ( (saberHitFraction < 1.0f||(sabersCrossed>=0&&sabersCrossed<=32.0f)) && (ent->client->ps.weaponstate == WEAPON_FIRING || ent->client->ps.saberInFlight || G_InCinematicSaberAnim( ent ) ) )
5075 	{// The saber (in-hand) hit another saber, mano.
5076 		qboolean inFlightSaberBlocked = qfalse;
5077 		qboolean collisionResolved = qfalse;
5078 		qboolean deflected = qfalse;
5079 
5080 		gentity_t *hitEnt = &g_entities[saberHitEntity];
5081 		gentity_t *hitOwner = NULL;
5082 		int hitOwnerPowerLevel = FORCE_LEVEL_0;
5083 
5084 		if ( hitEnt )
5085 		{
5086 			hitOwner = hitEnt->owner;
5087 		}
5088 		if ( hitOwner && hitOwner->client )
5089 		{
5090 			hitOwnerPowerLevel = PM_PowerLevelForSaberAnim( &hitOwner->client->ps );
5091 			/*
5092 			if ( entPowerLevel >= FORCE_LEVEL_3
5093 				&& PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) )
5094 			{//a special "unblockable" attack
5095 				if ( hitOwner->client->NPC_class == CLASS_ALORA
5096 					|| hitOwner->client->NPC_class == CLASS_SHADOWTROOPER
5097 					|| (hitOwner->NPC&&(hitOwner->NPC->aiFlags&NPCAI_BOSS_CHARACTER)) )
5098 				{//these masters can even block unblockables (stops cheap kills)
5099 					entPowerLevel = FORCE_LEVEL_2;
5100 				}
5101 			}
5102 			*/
5103 		}
5104 
5105 		//FIXME: check for certain anims, facing, etc, to make them lock into a sabers-locked pose
5106 		//SEF_LOCKED
5107 
5108 		if ( ent->client->ps.saberInFlight && saberNum == 0 &&
5109 			ent->client->ps.saber[saberNum].blade[bladeNum].active &&
5110 			ent->client->ps.saberEntityNum != ENTITYNUM_NONE &&
5111 			ent->client->ps.saberEntityState != SES_RETURNING )
5112 		{//saber was blocked, return it
5113 			inFlightSaberBlocked = qtrue;
5114 		}
5115 
5116 		//FIXME: based on strength, position and angle of attack & defense, decide if:
5117 		//	defender and attacker lock sabers
5118 		//	*defender's parry should hold and attack bounces (or deflects, based on angle of sabers)
5119 		//	*defender's parry is somewhat broken and both bounce (or deflect)
5120 		//	*defender's parry is broken and they bounce while attacker's attack deflects or carries through (especially if they're dead)
5121 		//	defender is knocked down and attack goes through
5122 
5123 		//Check deflections and broken parries
5124 		if ( hitOwner && hitOwner->health > 0 && ent->health > 0 //both are alive
5125 			&& !inFlightSaberBlocked && hitOwner->client && !hitOwner->client->ps.saberInFlight && !ent->client->ps.saberInFlight//both have sabers in-hand
5126 			&& ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN
5127 			&& ent->client->ps.saberLockTime < level.time
5128 			&& hitOwner->client->ps.saberLockTime < level.time )
5129 		{//2 in-hand sabers hit
5130 			//FIXME: defender should not parry or block at all if not in a saber anim... like, if in a roll or knockdown...
5131 			if ( baseDamage )
5132 			{//there is damage involved, not just effects
5133 				qboolean entAttacking = qfalse;
5134 				qboolean hitOwnerAttacking = qfalse;
5135 				qboolean entDefending = qfalse;
5136 				qboolean hitOwnerDefending = qfalse;
5137 				qboolean forceLock = qfalse;
5138 
5139 				if ( (ent->client->NPC_class == CLASS_KYLE && (ent->spawnflags&1) && hitOwner->s.number < MAX_CLIENTS )
5140 					|| (hitOwner->client->NPC_class == CLASS_KYLE && (hitOwner->spawnflags&1) && ent->s.number < MAX_CLIENTS ) )
5141 				{//Player vs. Kyle Boss == lots of saberlocks
5142 					if ( !Q_irand( 0, 2 ) )
5143 					{
5144 						forceLock = qtrue;
5145 					}
5146 				}
5147 
5148 				if ( PM_SaberInAttack( ent->client->ps.saberMove ) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) )
5149 				{
5150 					entAttacking = qtrue;
5151 				}
5152 				else if ( entPowerLevel > FORCE_LEVEL_2 )
5153 				{//stronger styles count as attacking even if in a transition
5154 					if ( PM_SaberInTransitionAny( ent->client->ps.saberMove ) )
5155 					{
5156 						entAttacking = qtrue;
5157 					}
5158 				}
5159 				if ( PM_SaberInParry( ent->client->ps.saberMove )
5160 					|| ent->client->ps.saberMove == LS_READY )
5161 				{
5162 					entDefending = qtrue;
5163 				}
5164 
5165 				if ( ent->client->ps.torsoAnim == BOTH_A1_SPECIAL
5166 					|| ent->client->ps.torsoAnim == BOTH_A2_SPECIAL
5167 					|| ent->client->ps.torsoAnim == BOTH_A3_SPECIAL )
5168 				{//parry/block/break-parry bonus for single-style kata moves
5169 					entPowerLevel++;
5170 				}
5171 				if ( entAttacking )
5172 				{//add twoHanded bonus and breakParryBonus to entPowerLevel here
5173 					//This makes staff too powerful
5174 					if ( (ent->client->ps.saber[saberNum].saberFlags&SFL_TWO_HANDED) )
5175 					{
5176 						entPowerLevel++;
5177 					}
5178 					//FIXME: what if dualSabers && both sabers are hitting at same time?
5179 					if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum ) )
5180 					{
5181 						entPowerLevel += ent->client->ps.saber[saberNum].breakParryBonus;
5182 					}
5183 					else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum ) )
5184 					{
5185 						entPowerLevel += ent->client->ps.saber[saberNum].breakParryBonus2;
5186 					}
5187 				}
5188 				else if ( entDefending )
5189 				{//add twoHanded bonus and dualSaber bonus and parryBonus to entPowerLevel here
5190 					if ( (ent->client->ps.saber[saberNum].saberFlags&SFL_TWO_HANDED)
5191 						|| (ent->client->ps.dualSabers && ent->client->ps.saber[1].Active()) )
5192 					{
5193 						entPowerLevel++;
5194 					}
5195 					//FIXME: what about second saber if dualSabers?
5196 					entPowerLevel += ent->client->ps.saber[saberNum].parryBonus;
5197 				}
5198 
5199 				if ( PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) )
5200 				{
5201 					hitOwnerAttacking = qtrue;
5202 				}
5203 				else if ( hitOwnerPowerLevel > FORCE_LEVEL_2 )
5204 				{//stronger styles count as attacking even if in a transition
5205 					if ( PM_SaberInTransitionAny( hitOwner->client->ps.saberMove ) )
5206 					{
5207 						hitOwnerAttacking = qtrue;
5208 					}
5209 				}
5210 				if ( PM_SaberInParry( hitOwner->client->ps.saberMove )
5211 					 || hitOwner->client->ps.saberMove == LS_READY )
5212 				{
5213 					hitOwnerDefending = qtrue;
5214 				}
5215 
5216 				if ( hitOwner->client->ps.torsoAnim == BOTH_A1_SPECIAL
5217 					|| hitOwner->client->ps.torsoAnim == BOTH_A2_SPECIAL
5218 					|| hitOwner->client->ps.torsoAnim == BOTH_A3_SPECIAL )
5219 				{//parry/block/break-parry bonus for single-style kata moves
5220 					hitOwnerPowerLevel++;
5221 				}
5222 				if ( hitOwnerAttacking )
5223 				{//add twoHanded bonus and breakParryBonus to entPowerLevel here
5224 					if ( (hitOwner->client->ps.saber[0].saberFlags&SFL_TWO_HANDED) )
5225 					{
5226 						hitOwnerPowerLevel++;
5227 					}
5228 					hitOwnerPowerLevel += hitOwner->client->ps.saber[0].breakParryBonus;
5229 					if ( hitOwner->client->ps.dualSabers && Q_irand( 0, 1 ) )
5230 					{//FIXME: assumes both sabers are hitting at same time...?
5231 						hitOwnerPowerLevel += 1 + hitOwner->client->ps.saber[1].breakParryBonus;
5232 					}
5233 				}
5234 				else if ( hitOwnerDefending )
5235 				{//add twoHanded bonus and dualSaber bonus and parryBonus to entPowerLevel here
5236 					if ( (hitOwner->client->ps.saber[0].saberFlags&SFL_TWO_HANDED)
5237 						|| (hitOwner->client->ps.dualSabers && hitOwner->client->ps.saber[1].Active()) )
5238 					{
5239 						hitOwnerPowerLevel++;
5240 					}
5241 					hitOwnerPowerLevel += hitOwner->client->ps.saber[0].parryBonus;
5242 					if ( hitOwner->client->ps.dualSabers && Q_irand( 0, 1 ) )
5243 					{//FIXME: assumes both sabers are defending at same time...?
5244 						hitOwnerPowerLevel += 1 + hitOwner->client->ps.saber[1].parryBonus;
5245 					}
5246 				}
5247 
5248 				if ( PM_SuperBreakLoseAnim( ent->client->ps.torsoAnim )
5249 					|| PM_SuperBreakWinAnim( ent->client->ps.torsoAnim )
5250 					|| PM_SuperBreakLoseAnim( hitOwner->client->ps.torsoAnim )
5251 					|| PM_SuperBreakWinAnim( hitOwner->client->ps.torsoAnim ) )
5252 				{//don't mess with this
5253 					collisionResolved = qtrue;
5254 				}
5255 				else if ( entAttacking
5256 					&& hitOwnerAttacking
5257 					&& !Q_irand( 0, g_saberLockRandomNess->integer )
5258 					&& ( g_debugSaberLock->integer || forceLock
5259 						|| entPowerLevel == hitOwnerPowerLevel
5260 						|| (entPowerLevel > FORCE_LEVEL_2 && hitOwnerPowerLevel > FORCE_LEVEL_2 )
5261 						|| (entPowerLevel < FORCE_LEVEL_3 && hitOwnerPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_2 && Q_irand( 0, 3 ))
5262 						|| (entPowerLevel < FORCE_LEVEL_2 && hitOwnerPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_1 && Q_irand( 0, 2 ))
5263 						|| (hitOwnerPowerLevel < FORCE_LEVEL_3 && entPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_2 && !Q_irand( 0, 1 ))
5264 						|| (hitOwnerPowerLevel < FORCE_LEVEL_2 && entPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_1 && !Q_irand( 0, 1 )))
5265 					&& WP_SabersCheckLock( ent, hitOwner ) )
5266 				{
5267 					collisionResolved = qtrue;
5268 				}
5269 				else if ( hitOwnerAttacking
5270 					&& entDefending
5271 					&& !Q_irand( 0, g_saberLockRandomNess->integer*3 )
5272 					&& (g_debugSaberLock->integer || forceLock ||
5273 						((ent->client->ps.saberMove != LS_READY || (hitOwnerPowerLevel-ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) < Q_irand( -6, 0 ) )
5274 							&& ((hitOwnerPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )||
5275 								(hitOwnerPowerLevel < FORCE_LEVEL_2 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_1 )||
5276 								(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 ))) ))
5277 					&& WP_SabersCheckLock( hitOwner, ent ) )
5278 				{
5279 					collisionResolved = qtrue;
5280 				}
5281 				else if ( entAttacking && hitOwnerDefending )
5282 				{//I'm attacking hit, they're parrying
5283 					qboolean activeDefense = (qboolean)(hitOwner->s.number||g_saberAutoBlocking->integer||hitOwner->client->ps.saberBlockingTime > level.time);
5284 					if ( !Q_irand( 0, g_saberLockRandomNess->integer*3 )
5285 						&& activeDefense
5286 						&& (g_debugSaberLock->integer || forceLock ||
5287 							((hitOwner->client->ps.saberMove != LS_READY || (entPowerLevel-hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) < Q_irand( -6, 0 ) )
5288 								&& ( ( entPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )
5289 									|| ( entPowerLevel < FORCE_LEVEL_2 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_1 )
5290 									|| ( 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 )) )  ))
5291 						&& WP_SabersCheckLock( ent, hitOwner ) )
5292 					{
5293 						collisionResolved = qtrue;
5294 					}
5295 					else if ( saberHitFraction < 1.0f )
5296 					{//an actual collision
5297 						if ( entPowerLevel < FORCE_LEVEL_3 && activeDefense )
5298 						{//strong attacks cannot be deflected
5299 							//based on angle of attack & angle of defensive saber, see if I should deflect off in another dir rather than bounce back
5300 							deflected = WP_GetSaberDeflectionAngle( ent, hitOwner );
5301 							//just so Jedi knows that he was blocked
5302 							ent->client->ps.saberEventFlags |= SEF_BLOCKED;
5303 						}
5304 						//base parry breaks on animation (saber attack level), not FP_SABER_OFFENSE
5305 						if ( entPowerLevel < FORCE_LEVEL_3
5306 							//&& 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?
5307 							//&& hitOwner->client->ps.saberAnimLevel != FORCE_LEVEL_5
5308 							&& activeDefense
5309 							&& (hitOwnerPowerLevel > FORCE_LEVEL_2||(hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_2&&Q_irand(0,hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE]))) )
5310 						{//knockaways can make fast-attacker go into a broken parry anim if the ent is using fast or med (but not Tavion)
5311 							//make me parry
5312 							WP_SaberParry( hitOwner, ent, saberNum, bladeNum );
5313 							//turn the parry into a knockaway
5314 							hitOwner->client->ps.saberBounceMove = PM_KnockawayForParry( hitOwner->client->ps.saberBlocked );
5315 							//make them go into a broken parry
5316 							ent->client->ps.saberBounceMove = PM_BrokenParryForAttack( ent->client->ps.saberMove );
5317 							ent->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
5318 							if ( saberNum == 0 )
5319 							{//FIXME: can only lose right-hand saber for now
5320 								if ( !(ent->client->ps.saber[saberNum].saberFlags&SFL_NOT_DISARMABLE)
5321 									&& ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_2
5322 									//&& (ent->s.number||g_saberRealisticCombat->integer)
5323 									&& Q_irand( 0, hitOwner->client->ps.SaberDisarmBonus( 0 ) ) > 0
5324 									&& (hitOwner->s.number || g_saberAutoBlocking->integer || !Q_irand( 0, 2 )) )//if player defending and autoblocking is on, this is less likely to happen, so don't do the random check
5325 								{//knocked the saber right out of his hand! (never happens to player)
5326 									//Get a good velocity to send the saber in based on my parry move
5327 									vec3_t	throwDir;
5328 									if ( !PM_VelocityForBlockedMove( &hitOwner->client->ps, throwDir ) )
5329 									{
5330 										PM_VelocityForSaberMove( &ent->client->ps, throwDir );
5331 									}
5332 									WP_SaberLose( ent, throwDir );
5333 								}
5334 							}
5335 							//just so Jedi knows that he was blocked
5336 							ent->client->ps.saberEventFlags |= SEF_BLOCKED;
5337 #ifndef FINAL_BUILD
5338 							if ( d_saberCombat->integer )
5339 							{
5340 								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 );
5341 							}
5342 #endif
5343 						}
5344 						else if ( !activeDefense//they're not defending
5345 							|| (entPowerLevel > FORCE_LEVEL_2 //I hit hard
5346 								&& hitOwnerPowerLevel < entPowerLevel)//they are defending, but their defense strength is lower than my attack...
5347 							|| (!deflected && Q_irand( 0, Q_max(0, PM_PowerLevelForSaberAnim( &ent->client->ps, saberNum ) - hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE])/*PM_PowerLevelForSaberAnim( &hitOwner->client->ps )*/ ) > 0 ) )
5348 						{//broke their parry altogether
5349 							if ( entPowerLevel > FORCE_LEVEL_2 || Q_irand( 0, Q_max(0, ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] - hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) ) )
5350 							{//chance of continuing with the attack (not bouncing back)
5351 								ent->client->ps.saberEventFlags &= ~SEF_BLOCKED;
5352 								ent->client->ps.saberBounceMove = LS_NONE;
5353 								brokenParry = qtrue;
5354 							}
5355 							//do some time-consuming saber-knocked-aside broken parry anim
5356 							hitOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
5357 							hitOwner->client->ps.saberBounceMove = LS_NONE;
5358 							//FIXME: for now, you always disarm the right-hand saber
5359 							if ( !(hitOwner->client->ps.saber[0].saberFlags&SFL_NOT_DISARMABLE)
5360 								&& hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_2
5361 								//&& (ent->s.number||g_saberRealisticCombat->integer)
5362 								&& Q_irand( 0, 2-ent->client->ps.SaberDisarmBonus( bladeNum ) ) <= 0 )
5363 							{//knocked the saber right out of his hand!
5364 								//get the right velocity for my attack direction
5365 								vec3_t	throwDir;
5366 								PM_VelocityForSaberMove( &ent->client->ps, throwDir );
5367 								WP_SaberLose( hitOwner, throwDir );
5368 								if ( (ent->client->ps.saberAnimLevel == SS_STRONG && !Q_irand(0,3) )
5369 									|| ( ent->client->ps.saberAnimLevel==SS_DESANN&&!Q_irand(0,1) ) )
5370 								{// a strong attack
5371 									if ( WP_BrokenParryKnockDown( hitOwner ) )
5372 									{
5373 										hitOwner->client->ps.saberBlocked = BLOCKED_NONE;
5374 										hitOwner->client->ps.saberBounceMove = LS_NONE;
5375 									}
5376 								}
5377 							}
5378 							else
5379 							{
5380 								if ( (ent->client->ps.saberAnimLevel == SS_STRONG && !Q_irand(0,5) )
5381 									|| ( ent->client->ps.saberAnimLevel==SS_DESANN&&!Q_irand(0,3) ) )
5382 								{// a strong attack
5383 									if ( WP_BrokenParryKnockDown( hitOwner ) )
5384 									{
5385 										hitOwner->client->ps.saberBlocked = BLOCKED_NONE;
5386 										hitOwner->client->ps.saberBounceMove = LS_NONE;
5387 									}
5388 								}
5389 							}
5390 #ifndef FINAL_BUILD
5391 							if ( d_saberCombat->integer )
5392 							{
5393 								if ( ent->client->ps.saberEventFlags&SEF_BLOCKED )
5394 								{
5395 									gi.Printf( S_COLOR_RED"%s parry broken (bounce/deflect)!\n", hitOwner->targetname );
5396 								}
5397 								else
5398 								{
5399 									gi.Printf( S_COLOR_RED"%s parry broken (follow-through)!\n", hitOwner->targetname );
5400 								}
5401 							}
5402 #endif
5403 						}
5404 						else
5405 						{//just a parry, possibly the hitOwner can knockaway the ent
5406 							WP_SaberParry( hitOwner, ent, saberNum, bladeNum );
5407 							if ( PM_SaberInBounce( ent->client->ps.saberMove ) //FIXME: saberMove not set until pmove!
5408 								&& activeDefense
5409 								&& hitOwner->client->ps.saberAnimLevel != SS_FAST //&& hitOwner->client->ps.saberAnimLevel != FORCE_LEVEL_5
5410 								&& hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )
5411 							{//attacker bounced off, and defender has ability to do knockaways, so do one unless we're using fast attacks
5412 								//turn the parry into a knockaway
5413 								hitOwner->client->ps.saberBounceMove = PM_KnockawayForParry( hitOwner->client->ps.saberBlocked );
5414 							}
5415 							else if ( (ent->client->ps.saberAnimLevel == SS_STRONG && !Q_irand(0,6) )
5416 									|| ( ent->client->ps.saberAnimLevel==SS_DESANN && !Q_irand(0,3) ) )
5417 							{// a strong attack can sometimes do a knockdown
5418 								//HMM... maybe only if they're moving backwards?
5419 								if ( WP_BrokenParryKnockDown( hitOwner ) )
5420 								{
5421 									hitOwner->client->ps.saberBlocked = BLOCKED_NONE;
5422 									hitOwner->client->ps.saberBounceMove = LS_NONE;
5423 								}
5424 							}
5425 						}
5426 						collisionResolved = qtrue;
5427 					}
5428 				}
5429 				/*
5430 				else if ( entDefending && hitOwnerAttacking )
5431 				{//I'm parrying, they're attacking
5432 					if ( hitOwnerPowerLevel < FORCE_LEVEL_3 )
5433 					{//strong attacks cannot be deflected
5434 						//based on angle of attack & angle of defensive saber, see if I should deflect off in another dir rather than bounce back
5435 						deflected = WP_GetSaberDeflectionAngle( hitOwner, ent );
5436 						//just so Jedi knows that he was blocked
5437 						hitOwner->client->ps.saberEventFlags |= SEF_BLOCKED;
5438 					}
5439 					//FIXME: base parry breaks on animation (saber attack level), not FP_SABER_OFFENSE
5440 					if ( hitOwnerPowerLevel > FORCE_LEVEL_2 || (!deflected && Q_irand( 0, PM_PowerLevelForSaberAnim( &hitOwner->client->ps ) - ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) > 0 ) )
5441 					{//broke my parry altogether
5442 						if ( hitOwnerPowerLevel > FORCE_LEVEL_2 || Q_irand( 0, hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] - ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) )
5443 						{//chance of continuing with the attack (not bouncing back)
5444 							hitOwner->client->ps.saberEventFlags &= ~SEF_BLOCKED;
5445 							hitOwner->client->ps.saberBounceMove = LS_NONE;
5446 						}
5447 						//do some time-consuming saber-knocked-aside broken parry anim
5448 						ent->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
5449 #ifndef FINAL_BUILD
5450 						if ( d_saberCombat->integer )
5451 						{
5452 							if ( hitOwner->client->ps.saberEventFlags&SEF_BLOCKED )
5453 							{
5454 								gi.Printf( S_COLOR_RED"%s parry broken (bounce/deflect)!\n", ent->targetname );
5455 							}
5456 							else
5457 							{
5458 								gi.Printf( S_COLOR_RED"%s parry broken (follow-through)!\n", ent->targetname );
5459 							}
5460 						}
5461 #endif
5462 					}
5463 					else
5464 					{
5465 						WP_SaberParry( ent, hitOwner, saberNum, bladeNum );
5466 					}
5467 					collisionResolved = qtrue;
5468 				}
5469 				*/
5470 				else
5471 				{//some other kind of in-hand saber collision
5472 				}
5473 			}
5474 		}
5475 		else
5476 		{//some kind of in-flight collision
5477 		}
5478 
5479 		if ( saberHitFraction < 1.0f )
5480 		{
5481 			if ( !collisionResolved && baseDamage )
5482 			{//some other kind of in-hand saber collision
5483 				//handle my reaction
5484 				if ( !ent->client->ps.saberInFlight
5485 					&& ent->client->ps.saberLockTime < level.time )
5486 				{//my saber is in hand
5487 					if ( ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
5488 					{
5489 						if ( PM_SaberInAttack( ent->client->ps.saberMove ) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) ||
5490 							(entPowerLevel > FORCE_LEVEL_2&&!PM_SaberInIdle(ent->client->ps.saberMove)&&!PM_SaberInParry(ent->client->ps.saberMove)&&!PM_SaberInReflect(ent->client->ps.saberMove)) )
5491 						{//in the middle of attacking
5492 							if ( entPowerLevel < FORCE_LEVEL_3 && hitOwner->health > 0 )
5493 							{//don't deflect/bounce in strong attack or when enemy is dead
5494 								WP_GetSaberDeflectionAngle( ent, hitOwner );
5495 								ent->client->ps.saberEventFlags |= SEF_BLOCKED;
5496 								//since it was blocked/deflected, take away any damage done
5497 								//FIXME: what if the damage was done before the parry?
5498 								WP_SaberClearDamageForEntNum( ent, hitOwner->s.number, saberNum, bladeNum );
5499 							}
5500 						}
5501 						else
5502 						{//saber collided when not attacking, parry it
5503 							//since it was blocked/deflected, take away any damage done
5504 							//FIXME: what if the damage was done before the parry?
5505 							WP_SaberClearDamageForEntNum( ent, hitOwner->s.number, saberNum, bladeNum );
5506 							/*
5507 							if ( ent->s.number || g_saberAutoBlocking->integer || ent->client->ps.saberBlockingTime > level.time )
5508 							{//either an NPC or a player who has blocking
5509 								if ( !PM_SaberInTransitionAny( ent->client->ps.saberMove ) && !PM_SaberInBounce( ent->client->ps.saberMove ) )
5510 								{//I'm not attacking, in transition or in a bounce, so play a parry
5511 									//just so Jedi knows that he parried something
5512 									WP_SaberBlockNonRandom( ent, saberHitLocation, qfalse );
5513 								}
5514 								ent->client->ps.saberEventFlags |= SEF_PARRIED;
5515 							}
5516 							*/
5517 						}
5518 					}
5519 					else
5520 					{
5521 						//since it was blocked/deflected, take away any damage done
5522 						//FIXME: what if the damage was done before the parry?
5523 						WP_SaberClearDamageForEntNum( ent, hitOwner->s.number, saberNum, bladeNum );
5524 					}
5525 				}
5526 				else
5527 				{//nothing happens to *me* when my inFlight saber hits something
5528 				}
5529 				//handle their reaction
5530 				if ( hitOwner
5531 					&& hitOwner->health > 0
5532 					&& hitOwner->client
5533 					&& !hitOwner->client->ps.saberInFlight
5534 					&& hitOwner->client->ps.saberLockTime < level.time )
5535 				{//their saber is in hand
5536 					if ( PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) ||
5537 						(hitOwner->client->ps.saberAnimLevel > SS_MEDIUM&&!PM_SaberInIdle(hitOwner->client->ps.saberMove)&&!PM_SaberInParry(hitOwner->client->ps.saberMove)&&!PM_SaberInReflect(hitOwner->client->ps.saberMove)) )
5538 					{//in the middle of attacking
5539 						/*
5540 						if ( hitOwner->client->ps.saberAnimLevel < SS_STRONG )
5541 						{//don't deflect/bounce in strong attack
5542 							WP_GetSaberDeflectionAngle( hitOwner, ent );
5543 							hitOwner->client->ps.saberEventFlags |= SEF_BLOCKED;
5544 						}
5545 						*/
5546 					}
5547 					else
5548 					{//saber collided when not attacking, parry it
5549 						if ( !PM_SaberInBrokenParry( hitOwner->client->ps.saberMove ) )
5550 						{//not currently in a broken parry
5551 							if ( !WP_SaberParry( hitOwner, ent, saberNum, bladeNum ) )
5552 							{//FIXME: hitOwner can't parry, do some time-consuming saber-knocked-aside broken parry anim?
5553 								//hitOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
5554 							}
5555 						}
5556 					}
5557 				}
5558 				else
5559 				{//nothing happens to *hitOwner* when their inFlight saber hits something
5560 				}
5561 			}
5562 
5563 			//collision must have been handled by now
5564 			//Set the blocked attack bounce value in saberBlocked so we actually play our saberBounceMove anim
5565 			if ( ent->client->ps.saberEventFlags & SEF_BLOCKED )
5566 			{
5567 				if ( ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
5568 				{
5569 					ent->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
5570 				}
5571 			}
5572 			/*
5573 			if ( hitOwner && hitOwner->client->ps.saberEventFlags & SEF_BLOCKED )
5574 			{
5575 				hitOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
5576 			}
5577 			*/
5578 		}
5579 
5580 		if ( saberHitFraction < 1.0f || collisionResolved )
5581 		{//either actually hit or locked
5582 			if ( ent->client->ps.saberLockTime < level.time )
5583 			{
5584 				if ( inFlightSaberBlocked )
5585 				{//FIXME: never hear this sound
5586 					WP_SaberBounceSound( ent, hitOwner, &g_entities[ent->client->ps.saberEntityNum], 0, 0, qfalse );
5587 				}
5588 				else
5589 				{
5590 					if ( deflected )
5591 					{
5592 						WP_SaberBounceSound( ent, hitOwner, NULL, saberNum, bladeNum, qtrue );
5593 					}
5594 					else
5595 					{
5596 						WP_SaberBlockSound( ent, hitOwner, saberNum, bladeNum );
5597 					}
5598 				}
5599 				if ( !g_saberNoEffects )
5600 				{
5601 					WP_SaberBlockEffect( ent, saberNum, bladeNum, saberHitLocation, saberHitNormal, qfalse );
5602 				}
5603 			}
5604 			// Set the little screen flash - only when an attack is blocked
5605 			if ( !g_noClashFlare )
5606 			{
5607 				g_saberFlashTime = level.time-50;
5608 				VectorCopy( saberHitLocation, g_saberFlashPos );
5609 			}
5610 		}
5611 
5612 		if ( saberHitFraction < 1.0f )
5613 		{
5614 			if ( inFlightSaberBlocked )
5615 			{//we threw a saber and it was blocked, do any effects, etc.
5616 				int	knockAway = 5;
5617 				if ( hitEnt
5618 					&& hitOwner
5619 					&& hitOwner->client
5620 					&& (PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) || PM_SpinningSaberAnim( hitOwner->client->ps.torsoAnim )) )
5621 				{//if hit someone who was in an attack or spin anim, more likely to have in-flight saber knocked away
5622 					if ( hitOwnerPowerLevel > FORCE_LEVEL_2 )
5623 					{//strong attacks almost always knock it aside!
5624 						knockAway = 1;
5625 					}
5626 					else
5627 					{//33% chance
5628 						knockAway = 2;
5629 					}
5630 					knockAway -= hitOwner->client->ps.SaberDisarmBonus( 0 );
5631 				}
5632 				if ( Q_irand( 0, knockAway ) <= 0 || //random
5633 						( hitOwner
5634 							&& hitOwner->client
5635 							&& hitOwner->NPC
5636 							&& (hitOwner->NPC->aiFlags&NPCAI_BOSS_CHARACTER)
5637 						) //or if blocked by a Boss character FIXME: or base on defense level?
5638 					)//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
5639 				{//knock it aside and turn it off
5640 					if ( !g_saberNoEffects )
5641 					{
5642 						if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
5643 							&& ent->client->ps.saber[saberNum].hitOtherEffect )
5644 						{
5645 							G_PlayEffect( ent->client->ps.saber[saberNum].hitOtherEffect, saberHitLocation, saberHitNormal );
5646 						}
5647 						else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
5648 							&& ent->client->ps.saber[saberNum].hitOtherEffect2 )
5649 						{
5650 							G_PlayEffect( ent->client->ps.saber[saberNum].hitOtherEffect2, saberHitLocation, saberHitNormal );
5651 						}
5652 						else
5653 						{
5654 							G_PlayEffect( "saber/saber_cut", saberHitLocation, saberHitNormal );
5655 						}
5656 					}
5657 					if ( hitEnt )
5658 					{
5659 						vec3_t newDir;
5660 
5661 						VectorSubtract( g_entities[ent->client->ps.saberEntityNum].currentOrigin, hitEnt->currentOrigin, newDir );
5662 						VectorNormalize( newDir );
5663 						G_ReflectMissile( ent, &g_entities[ent->client->ps.saberEntityNum], newDir );
5664 					}
5665 					Jedi_PlayDeflectSound( hitOwner );
5666 					WP_SaberDrop( ent, &g_entities[ent->client->ps.saberEntityNum] );
5667 				}
5668 				else
5669 				{
5670 					if ( !Q_irand( 0, 2 ) && hitEnt )
5671 					{
5672 						vec3_t newDir;
5673 						VectorSubtract( g_entities[ent->client->ps.saberEntityNum].currentOrigin, hitEnt->currentOrigin, newDir );
5674 						VectorNormalize( newDir );
5675 						G_ReflectMissile( ent, &g_entities[ent->client->ps.saberEntityNum], newDir );
5676 					}
5677 					WP_SaberReturn( ent, &g_entities[ent->client->ps.saberEntityNum] );
5678 				}
5679 			}
5680 		}
5681 	}
5682 
5683 	if ( ent->client->ps.saberLockTime > level.time )
5684 	{
5685 		if ( ent->s.number < ent->client->ps.saberLockEnemy
5686 			&& !Q_irand( 0, 3 ) )
5687 		{//need to make some kind of effect
5688 			vec3_t	hitNorm = {0,0,1};
5689 			if ( WP_SabersIntersection( ent, &g_entities[ent->client->ps.saberLockEnemy], g_saberFlashPos ) )
5690 			{
5691 				if ( Q_irand( 0, 10 ) )
5692 				{
5693 					if ( !g_saberNoEffects )
5694 					{
5695 						WP_SaberBlockEffect( ent, saberNum, bladeNum, g_saberFlashPos, hitNorm, qfalse );
5696 					}
5697 				}
5698 				else
5699 				{
5700 					if ( !g_noClashFlare )
5701 					{
5702 						g_saberFlashTime = level.time-50;
5703 					}
5704 					if ( !g_saberNoEffects )
5705 					{
5706 						WP_SaberBlockEffect( ent, saberNum, bladeNum, g_saberFlashPos, hitNorm, qtrue );
5707 					}
5708 				}
5709 				WP_SaberBlockSound( ent, &g_entities[ent->client->ps.saberLockEnemy], 0, 0 );
5710 			}
5711 		}
5712 	}
5713 	else
5714 	{
5715 		if ( hit_wall
5716 			&& (ent->client->ps.saber[saberNum].saberFlags&SFL_BOUNCE_ON_WALLS)
5717 			&& (PM_SaberInAttackPure( ent->client->ps.saberMove ) //only in a normal attack anim
5718 				|| ent->client->ps.saberMove == LS_A_JUMP_T__B_ ) //or in the strong jump-fwd-attack "death from above" move
5719 			)
5720 		{//bounce off walls
5721 			//do anim
5722 			ent->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
5723 			ent->client->ps.saberBounceMove = LS_D1_BR+(saberMoveData[ent->client->ps.saberMove].startQuad-Q_BR);
5724 			//do bounce sound & force feedback
5725 			WP_SaberBounceOnWallSound( ent, saberNum, bladeNum );
5726 			//do hit effect
5727 			if ( !g_saberNoEffects )
5728 			{
5729 				if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
5730 					&& ent->client->ps.saber[saberNum].hitOtherEffect )
5731 				{
5732 					G_PlayEffect( ent->client->ps.saber[saberNum].hitOtherEffect, saberHitLocation, saberHitNormal );
5733 				}
5734 				else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
5735 					&& ent->client->ps.saber[saberNum].hitOtherEffect2 )
5736 				{
5737 					G_PlayEffect( ent->client->ps.saber[saberNum].hitOtherEffect2, saberHitLocation, saberHitNormal );
5738 				}
5739 				else
5740 				{
5741 					G_PlayEffect( "saber/saber_cut", saberHitLocation, saberHitNormal );
5742 				}
5743 			}
5744 			//do radius damage/knockback, if any
5745 			if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum ) )
5746 			{
5747 				WP_SaberRadiusDamage( ent, saberHitLocation, ent->client->ps.saber[saberNum].splashRadius, ent->client->ps.saber[saberNum].splashDamage, ent->client->ps.saber[saberNum].splashKnockback );
5748 			}
5749 			else
5750 			{
5751 				WP_SaberRadiusDamage( ent, saberHitLocation, ent->client->ps.saber[saberNum].splashRadius2, ent->client->ps.saber[saberNum].splashDamage2, ent->client->ps.saber[saberNum].splashKnockback2 );
5752 			}
5753 		}
5754 	}
5755 
5756 	if ( WP_SaberApplyDamage( ent, baseDamage, baseDFlags, brokenParry, saberNum, bladeNum, (qboolean)(saberNum==0&&ent->client->ps.saberInFlight) ) )
5757 	{//actually did damage to something
5758 #ifndef FINAL_BUILD
5759 		if ( d_saberCombat->integer )
5760 		{
5761 			gi.Printf( "base damage was %4.2f\n", baseDamage );
5762 		}
5763 #endif
5764 		WP_SaberHitSound( ent, saberNum, bladeNum );
5765 	}
5766 
5767 	if ( hit_wall )
5768 	{
5769 		//just so Jedi knows that he hit a wall
5770 		ent->client->ps.saberEventFlags |= SEF_HITWALL;
5771 		if ( ent->s.number == 0 )
5772 		{
5773 			AddSoundEvent( ent, ent->currentOrigin, 128, AEL_DISCOVERED, qfalse, qtrue );//FIXME: is this impact on ground or not?
5774 			AddSightEvent( ent, ent->currentOrigin, 256, AEL_DISCOVERED, 50 );
5775 		}
5776 	}
5777 }
5778 
WP_SabersDamageTrace(gentity_t * ent,qboolean noEffects)5779 void WP_SabersDamageTrace( gentity_t *ent, qboolean noEffects )
5780 {
5781 	if ( !ent->client )
5782 	{
5783 		return;
5784 	}
5785 	if ( PM_SuperBreakLoseAnim( ent->client->ps.torsoAnim ) )
5786 	{
5787 		return;
5788 	}
5789 	// Saber 1.
5790 	g_saberNoEffects = noEffects;
5791 	for ( int i = 0; i < ent->client->ps.saber[0].numBlades; i++ )
5792 	{
5793 		// If the Blade is not active and the length is 0, don't trace it, try the next blade...
5794 		if ( !ent->client->ps.saber[0].blade[i].active && ent->client->ps.saber[0].blade[i].length == 0 )
5795 			continue;
5796 
5797 		if ( i != 0 )
5798 		{//not first blade
5799 			if ( ent->client->ps.saber[0].type == SABER_BROAD ||
5800 				ent->client->ps.saber[0].type == SABER_SAI ||
5801 				ent->client->ps.saber[0].type == SABER_CLAW )
5802 			{
5803 				g_saberNoEffects = qtrue;
5804 			}
5805 		}
5806 		g_noClashFlare = qfalse;
5807 		if ( (!WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[0], i ) && (ent->client->ps.saber[0].saberFlags2&SFL2_NO_CLASH_FLARE) )
5808 			|| ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[0], i ) && (ent->client->ps.saber[0].saberFlags2&SFL2_NO_CLASH_FLARE2) ) )
5809 		{
5810 			g_noClashFlare = qtrue;
5811 		}
5812 		WP_SaberDamageTrace( ent, 0, i );
5813 	}
5814 	// Saber 2.
5815 	g_saberNoEffects = noEffects;
5816 	if ( ent->client->ps.dualSabers )
5817 	{
5818 		for ( int i = 0; i < ent->client->ps.saber[1].numBlades; i++ )
5819 		{
5820 			// If the Blade is not active and the length is 0, don't trace it, try the next blade...
5821 			if ( !ent->client->ps.saber[1].blade[i].active && ent->client->ps.saber[1].blade[i].length == 0 )
5822 				continue;
5823 
5824 			if ( i != 0 )
5825 			{//not first blade
5826 				if ( ent->client->ps.saber[1].type == SABER_BROAD ||
5827 					ent->client->ps.saber[1].type == SABER_SAI ||
5828 					ent->client->ps.saber[1].type == SABER_CLAW )
5829 				{
5830 					g_saberNoEffects = qtrue;
5831 				}
5832 			}
5833 			g_noClashFlare = qfalse;
5834 			if ( (!WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[1], i ) && (ent->client->ps.saber[1].saberFlags2&SFL2_NO_CLASH_FLARE) )
5835 				|| ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[1], i ) && (ent->client->ps.saber[1].saberFlags2&SFL2_NO_CLASH_FLARE2) ) )
5836 			{
5837 				g_noClashFlare = qtrue;
5838 			}
5839 			WP_SaberDamageTrace( ent, 1, i );
5840 		}
5841 	}
5842 	g_saberNoEffects = qfalse;
5843 	g_noClashFlare = qfalse;
5844 }
5845 
5846 //SABER THROWING============================================================================
5847 //SABER THROWING============================================================================
5848 //SABER THROWING============================================================================
5849 //SABER THROWING============================================================================
5850 //SABER THROWING============================================================================
5851 //SABER THROWING============================================================================
5852 
5853 /*
5854 ================
5855 WP_SaberImpact
5856 
5857 ================
5858 */
WP_SaberImpact(gentity_t * owner,gentity_t * saber,trace_t * trace)5859 void WP_SaberImpact( gentity_t *owner, gentity_t *saber, trace_t *trace )
5860 {
5861 	gentity_t		*other;
5862 
5863 	other = &g_entities[trace->entityNum];
5864 
5865 	if ( other->takedamage && (other->svFlags&SVF_BBRUSH) )
5866 	{//a breakable brush?  break it!
5867 		if ( (other->spawnflags&1)//INVINCIBLE
5868 			||(other->flags&FL_DMG_BY_HEAVY_WEAP_ONLY) )//HEAVY weapon damage only
5869 		{//can't actually break it
5870 			//no hit effect (besides regular client-side one)
5871 		}
5872 		else if ( other->NPC_targetname &&
5873 			(!owner||!owner->targetname||Q_stricmp(owner->targetname,other->NPC_targetname)) )
5874 		{//only breakable by an entity who is not the attacker
5875 			//no hit effect (besides regular client-side one)
5876 		}
5877 		else
5878 		{
5879 			vec3_t dir;
5880 			VectorCopy( saber->s.pos.trDelta, dir );
5881 			VectorNormalize( dir );
5882 
5883 			int dmg = other->health*2;
5884 			if ( other->health > 50 && dmg > 20 && !(other->svFlags&SVF_GLASS_BRUSH) )
5885 			{
5886 				dmg = 20;
5887 			}
5888 			G_Damage( other, saber, owner, dir, trace->endpos, dmg, 0, MOD_SABER );
5889 			if ( owner
5890 				&& owner->client
5891 				&& owner->client->ps.saber[0].hitOtherEffect )
5892 			{
5893 				G_PlayEffect( owner->client->ps.saber[0].hitOtherEffect, trace->endpos, dir );
5894 			}
5895 			else
5896 			{
5897 				G_PlayEffect( "saber/saber_cut", trace->endpos, dir );
5898 			}
5899 			if ( owner->s.number == 0 )
5900 			{
5901 				AddSoundEvent( owner, trace->endpos, 256, AEL_DISCOVERED );
5902 				AddSightEvent( owner, trace->endpos, 512, AEL_DISCOVERED, 50 );
5903 			}
5904 			return;
5905 		}
5906 	}
5907 
5908 	if ( saber->s.pos.trType == TR_LINEAR )
5909 	{
5910 		//hit a wall? send it back
5911 		WP_SaberReturn( saber->owner, saber );
5912 	}
5913 
5914 	if ( other && !other->client && (other->contents&CONTENTS_LIGHTSABER) )//&& other->s.weapon == WP_SABER )
5915 	{//2 in-flight sabers collided!
5916 		//Big flash
5917 		//FIXME: bigger effect/sound?
5918 		//FIXME: STILL DOESNT WORK!!!
5919 		WP_SaberBlockSound( saber->owner, NULL, 0, 0 );
5920 		//G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
5921 		WP_SaberBlockEffect( saber->owner, 0, 0, trace->endpos, NULL, qfalse);
5922 		qboolean noFlare = qfalse;
5923 		if ( saber->owner
5924 			&& saber->owner->client
5925 			&& (saber->owner->client->ps.saber[0].saberFlags2&SFL2_NO_CLASH_FLARE) )
5926 		{
5927 			noFlare = qtrue;
5928 		}
5929 		if ( !noFlare )
5930 		{
5931 			g_saberFlashTime = level.time-50;
5932 			VectorCopy( trace->endpos, g_saberFlashPos );
5933 		}
5934 	}
5935 
5936 	if ( owner && owner->s.number == 0 && owner->client )
5937 	{
5938 		//Add the event
5939 		if ( owner->client->ps.SaberLength() > 0 )
5940 		{//saber is on, very suspicious
5941 			if ( (!owner->client->ps.saberInFlight && owner->client->ps.groundEntityNum == ENTITYNUM_WORLD)//holding saber and on ground
5942 				|| saber->s.pos.trType == TR_STATIONARY )//saber out there somewhere and on ground
5943 			{//an on-ground alert
5944 				AddSoundEvent( owner, saber->currentOrigin, 128, AEL_DISCOVERED, qfalse, qtrue );
5945 			}
5946 			else
5947 			{//an in-air alert
5948 				AddSoundEvent( owner, saber->currentOrigin, 128, AEL_DISCOVERED );
5949 			}
5950 			AddSightEvent( owner, saber->currentOrigin, 256, AEL_DISCOVERED, 50 );
5951 		}
5952 		else
5953 		{//saber is off, not as suspicious
5954 			AddSoundEvent( owner, saber->currentOrigin, 128, AEL_SUSPICIOUS );
5955 			AddSightEvent( owner, saber->currentOrigin, 256, AEL_SUSPICIOUS );
5956 		}
5957 	}
5958 
5959 	// check for bounce
5960 	if ( !other->takedamage && ( saber->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) )
5961 	{
5962 		// Check to see if there is a bounce count
5963 		if ( saber->bounceCount ) {
5964 			// decrement number of bounces and then see if it should be done bouncing
5965 			if ( --saber->bounceCount <= 0 ) {
5966 				// He (or she) will bounce no more (after this current bounce, that is).
5967 				saber->s.eFlags &= ~( EF_BOUNCE | EF_BOUNCE_HALF );
5968 				if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING )
5969 				{
5970 					WP_SaberDrop( saber->owner, saber );
5971 				}
5972 				return;
5973 			}
5974 			else
5975 			{//bounced and still have bounces left
5976 				if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING )
5977 				{//under telekinetic control
5978 					if ( !gi.inPVS( saber->currentOrigin, owner->client->renderInfo.handRPoint ) )
5979 					{//not in the PVS of my master
5980 						saber->bounceCount -= 25;
5981 					}
5982 				}
5983 			}
5984 		}
5985 
5986 		if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING )
5987 		{
5988 			//don't home for a few frames so we can get around this thing
5989 			trace_t	bounceTr;
5990 			vec3_t	end;
5991 			float owner_dist = Distance( owner->client->renderInfo.handRPoint, saber->currentOrigin );
5992 
5993 			VectorMA( saber->currentOrigin, 10, trace->plane.normal, end );
5994 			gi.trace( &bounceTr, saber->currentOrigin, saber->mins, saber->maxs, end, owner->s.number, saber->clipmask, (EG2_Collision)0, 0 );
5995 			VectorCopy( bounceTr.endpos, saber->currentOrigin );
5996 			if ( owner_dist > 0 )
5997 			{
5998 				if ( owner_dist > 50 )
5999 				{
6000 					owner->client->ps.saberEntityDist = owner_dist-50;
6001 				}
6002 				else
6003 				{
6004 					owner->client->ps.saberEntityDist = 0;
6005 				}
6006 			}
6007 			return;
6008 		}
6009 
6010 		G_BounceMissile( saber, trace );
6011 
6012 		if ( saber->s.pos.trType == TR_GRAVITY )
6013 		{//bounced
6014 			//play a bounce sound
6015 			WP_SaberFallSound( owner, saber );
6016 			//change rotation
6017 			VectorCopy( saber->currentAngles, saber->s.apos.trBase );
6018 			saber->s.apos.trType = TR_LINEAR;
6019 			saber->s.apos.trTime = level.time;
6020 			VectorSet( saber->s.apos.trDelta, Q_irand( -300, 300 ), Q_irand( -300, 300 ), Q_irand( -300, 300 ) );
6021 		}
6022 		//see if we stopped
6023 		else if ( saber->s.pos.trType == TR_STATIONARY )
6024 		{//stopped
6025 			//play a bounce sound
6026 			WP_SaberFallSound( owner, saber );
6027 			//stop rotation
6028 			VectorClear( saber->s.apos.trDelta );
6029 			pitch_roll_for_slope( saber, trace->plane.normal, saber->currentAngles );
6030 			saber->currentAngles[0] += SABER_PITCH_HACK;
6031 			VectorCopy( saber->currentAngles, saber->s.apos.trBase );
6032 			//remember when it fell so it can return automagically
6033 			saber->aimDebounceTime = level.time;
6034 		}
6035 	}
6036 	else if ( other->client && other->health > 0
6037 		&& ( (other->NPC && (other->NPC->aiFlags&NPCAI_BOSS_CHARACTER))
6038 			//|| other->client->NPC_class == CLASS_ALORA
6039 			|| other->client->NPC_class == CLASS_BOBAFETT
6040 			|| ( other->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) ) )
6041 	{//Luke, Desann and Tavion slap thrown sabers aside
6042 		WP_SaberDrop( owner, saber );
6043 		WP_SaberBlockSound( owner, other, 0, 0 );
6044 		//G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
6045 		WP_SaberBlockEffect( owner, 0, 0, trace->endpos, NULL, qfalse );
6046 		qboolean noFlare = qfalse;
6047 		if ( owner
6048 			&& owner->client
6049 			&& (owner->client->ps.saber[0].saberFlags2&SFL2_NO_CLASH_FLARE) )
6050 		{
6051 			noFlare = qtrue;
6052 		}
6053 		if ( !noFlare )
6054 		{
6055 			g_saberFlashTime = level.time-50;
6056 			VectorCopy( trace->endpos, g_saberFlashPos );
6057 		}
6058 		//FIXME: make Luke/Desann/Tavion play an attack anim or some other special anim when this happens
6059 		Jedi_PlayDeflectSound( other );
6060 	}
6061 }
6062 
6063 extern float G_PointDistFromLineSegment( const vec3_t start, const vec3_t end, const vec3_t from );
WP_SaberInFlightReflectCheck(gentity_t * self,usercmd_t * ucmd)6064 void WP_SaberInFlightReflectCheck( gentity_t *self, usercmd_t *ucmd  )
6065 {
6066 	gentity_t	*ent;
6067 	gentity_t	*entityList[MAX_GENTITIES];
6068 	gentity_t	*missile_list[MAX_GENTITIES];
6069 	int			numListedEntities;
6070 	vec3_t		mins, maxs;
6071 	int			i, e, numSabers;
6072 	int			ent_count = 0;
6073 	int			radius = 180;
6074 	vec3_t		center;
6075 	vec3_t		tip;
6076 	vec3_t		up = {0,0,1};
6077 	qboolean	willHit = qfalse;
6078 
6079 	if ( self->NPC && (self->NPC->scriptFlags&SCF_IGNORE_ALERTS) )
6080 	{//don't react to things flying at me...
6081 		return;
6082 	}
6083 	//sanity checks: make sure we actually have a saberent
6084 	if ( self->client->ps.weapon != WP_SABER )
6085 	{
6086 		return;
6087 	}
6088 	if ( !self->client->ps.saberInFlight )
6089 	{
6090 		return;
6091 	}
6092 	if ( !self->client->ps.SaberLength() )
6093 	{
6094 		return;
6095 	}
6096 	if ( self->client->ps.saberEntityNum == ENTITYNUM_NONE )
6097 	{
6098 		return;
6099 	}
6100 	gentity_t *saberent = &g_entities[self->client->ps.saberEntityNum];
6101 	if ( !saberent )
6102 	{
6103 		return;
6104 	}
6105 	//okay, enough damn sanity checks
6106 
6107 	VectorCopy( saberent->currentOrigin, center );
6108 
6109 	for ( i = 0 ; i < 3 ; i++ )
6110 	{
6111 		mins[i] = center[i] - radius;
6112 		maxs[i] = center[i] + radius;
6113 	}
6114 
6115 	numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
6116 
6117 	//FIXME: check visibility?
6118 	for ( e = 0 ; e < numListedEntities ; e++ )
6119 	{
6120 		ent = entityList[ e ];
6121 
6122 		if (ent == self)
6123 			continue;
6124 		if (ent->owner == self)
6125 			continue;
6126 		if ( !(ent->inuse) )
6127 			continue;
6128 		if ( ent->s.eType != ET_MISSILE )
6129 		{
6130 			if ( ent->client || ent->s.weapon != WP_SABER )
6131 			{//FIXME: wake up bad guys?
6132 				continue;
6133 			}
6134 			if ( ent->s.eFlags & EF_NODRAW )
6135 			{
6136 				continue;
6137 			}
6138 			if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
6139 			{//not a lightsaber
6140 				continue;
6141 			}
6142 		}
6143 		else
6144 		{//FIXME: make exploding missiles explode?
6145 			if ( ent->s.pos.trType == TR_STATIONARY )
6146 			{//nothing you can do with a stationary missile
6147 				continue;
6148 			}
6149 			if ( ent->splashDamage || ent->splashRadius )
6150 			{//can't deflect exploding missiles
6151 				if ( DistanceSquared( ent->currentOrigin, center ) < 256 )//16 squared
6152 				{
6153 					G_MissileImpacted( ent, saberent, ent->currentOrigin, up );
6154 				}
6155 				continue;
6156 			}
6157 		}
6158 
6159 		//don't deflect it if it's not within 16 units of the blade
6160 		//do this for all blades
6161 		willHit = qfalse;
6162 		numSabers = 1;
6163 		if ( self->client->ps.dualSabers )
6164 		{
6165 			numSabers = 2;
6166 		}
6167 		for ( int saberNum = 0; saberNum < numSabers; saberNum++ )
6168 		{
6169 			for ( int bladeNum = 0; bladeNum < self->client->ps.saber[saberNum].numBlades; bladeNum++ )
6170 			{
6171 				VectorMA( self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, self->client->ps.saber[saberNum].blade[bladeNum].length, self->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, tip );
6172 
6173 				if( G_PointDistFromLineSegment( self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, tip, ent->currentOrigin ) <= 32 )
6174 				{
6175 					willHit = qtrue;
6176 					break;
6177 				}
6178 			}
6179 			if ( willHit )
6180 			{
6181 				break;
6182 			}
6183 		}
6184 		if ( !willHit )
6185 		{
6186 			continue;
6187 		}
6188 		// ok, we are within the radius, add us to the incoming list
6189 		missile_list[ent_count] = ent;
6190 		ent_count++;
6191 
6192 	}
6193 
6194 	if ( ent_count )
6195 	{
6196 		vec3_t	fx_dir;
6197 		// we are done, do we have any to deflect?
6198 		if ( ent_count )
6199 		{
6200 			for ( int x = 0; x < ent_count; x++ )
6201 			{
6202 				if ( missile_list[x]->s.weapon == WP_SABER )
6203 				{//just send it back
6204 					if ( missile_list[x]->owner && missile_list[x]->owner->client && missile_list[x]->owner->client->ps.saber[0].Active() && missile_list[x]->s.pos.trType == TR_LINEAR && missile_list[x]->owner->client->ps.saberEntityState != SES_RETURNING )
6205 					{//it's on and being controlled
6206 						//FIXME: prevent it from damaging me?
6207 						WP_SaberReturn( missile_list[x]->owner, missile_list[x] );
6208 						VectorNormalize2( missile_list[x]->s.pos.trDelta, fx_dir );
6209 						WP_SaberBlockEffect( self, 0, 0, missile_list[x]->currentOrigin, fx_dir, qfalse );
6210 						if ( missile_list[x]->owner->client->ps.saberInFlight && self->client->ps.saberInFlight )
6211 						{
6212 							WP_SaberBlockSound( self, missile_list[x]->owner, 0, 0 );
6213 							//G_Sound( missile_list[x], G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
6214 							qboolean noFlare = qfalse;
6215 							if ( (missile_list[x]->owner->client->ps.saber[0].saberFlags2&SFL2_NO_CLASH_FLARE)
6216 								&& (self->client->ps.saber[0].saberFlags2&SFL2_NO_CLASH_FLARE) )
6217 							{
6218 								noFlare = qtrue;
6219 							}
6220 							if ( !noFlare )
6221 							{
6222 								g_saberFlashTime = level.time-50;
6223 								gentity_t *saber = &g_entities[self->client->ps.saberEntityNum];
6224 								vec3_t	org;
6225 								VectorSubtract( missile_list[x]->currentOrigin, saber->currentOrigin, org );
6226 								VectorMA( saber->currentOrigin, 0.5, org, org );
6227 								VectorCopy( org, g_saberFlashPos );
6228 							}
6229 						}
6230 					}
6231 				}
6232 				else
6233 				{//bounce it
6234 					vec3_t	reflectAngle, forward;
6235 					if ( self->client && !self->s.number )
6236 					{
6237 						self->client->sess.missionStats.saberBlocksCnt++;
6238 					}
6239 					VectorCopy( saberent->s.apos.trBase, reflectAngle );
6240 					reflectAngle[PITCH] = Q_flrand( -90, 90 );
6241 					AngleVectors( reflectAngle, forward, NULL, NULL );
6242 
6243 					G_ReflectMissile( self, missile_list[x], forward );
6244 					//do an effect
6245 					VectorNormalize2( missile_list[x]->s.pos.trDelta, fx_dir );
6246 					G_PlayEffect( "blaster/deflect", missile_list[x]->currentOrigin, fx_dir );
6247 				}
6248 			}
6249 		}
6250 	}
6251 }
6252 
WP_SaberValidateEnemy(gentity_t * self,gentity_t * enemy)6253 qboolean WP_SaberValidateEnemy( gentity_t *self, gentity_t *enemy )
6254 {
6255 	if ( !enemy )
6256 	{
6257 		return qfalse;
6258 	}
6259 
6260 	if ( !enemy || enemy == self || !enemy->inuse || !enemy->client )
6261 	{//not valid
6262 		return qfalse;
6263 	}
6264 
6265 	if ( enemy->health <= 0 )
6266 	{//corpse
6267 		return qfalse;
6268 	}
6269 
6270 	/*
6271 	if ( enemy->client->ps.weapon == WP_SABER
6272 		&& enemy->client->ps.SaberActive() )
6273 	{//not other saber-users?
6274 		return qfalse;
6275 	}
6276 	*/
6277 	if ( enemy->s.number >= MAX_CLIENTS )
6278 	{//NPCs can cheat and use the homing saber throw 3 on the player
6279 		if ( enemy->client->ps.forcePowersKnown )
6280 		{//not other jedi?
6281 			return qfalse;
6282 		}
6283 	}
6284 
6285 	if ( DistanceSquared( self->client->renderInfo.handRPoint, enemy->currentOrigin ) > saberThrowDistSquared[self->client->ps.forcePowerLevel[FP_SABERTHROW]] )
6286 	{//too far
6287 		return qfalse;
6288 	}
6289 
6290 	if ( (!InFront( enemy->currentOrigin, self->currentOrigin, self->client->ps.viewangles, 0.0f) || !G_ClearLOS( self, self->client->renderInfo.eyePoint, enemy ) )
6291 		&& ( DistanceHorizontalSquared( enemy->currentOrigin, self->currentOrigin ) > 65536 || fabs(enemy->currentOrigin[2]-self->currentOrigin[2]) > 384 )  )
6292 	{//(not in front or not clear LOS) & greater than 256 away
6293 		return qfalse;
6294 	}
6295 
6296 	if ( enemy->client->playerTeam == self->client->playerTeam )
6297 	{//on same team
6298 		return qfalse;
6299 	}
6300 
6301 	//LOS?
6302 
6303 	return qtrue;
6304 }
6305 
WP_SaberRateEnemy(gentity_t * enemy,vec3_t center,vec3_t forward,float radius)6306 float WP_SaberRateEnemy( gentity_t *enemy, vec3_t center, vec3_t forward, float radius )
6307 {
6308 	float rating;
6309 	vec3_t	dir;
6310 
6311 	VectorSubtract( enemy->currentOrigin, center, dir );
6312 	rating = (1.0f-(VectorNormalize( dir )/radius));
6313 	rating *= DotProduct( forward, dir );
6314 	return rating;
6315 }
6316 
WP_SaberFindEnemy(gentity_t * self,gentity_t * saber)6317 gentity_t *WP_SaberFindEnemy( gentity_t *self, gentity_t *saber )
6318 {
6319 //FIXME: should be a more intelligent way of doing this, like auto aim?
6320 //closest, most in front... did damage to... took damage from?  How do we know who the player is focusing on?
6321 	gentity_t	*ent, *bestEnt = NULL;
6322 	gentity_t	*entityList[MAX_GENTITIES];
6323 	int			numListedEntities;
6324 	vec3_t		center, mins, maxs, fwdangles, forward;
6325 	int			i, e;
6326 	float		radius = 400;
6327 	float		rating, bestRating = 0.0f;
6328 
6329 	//FIXME: no need to do this in 1st person?
6330 	fwdangles[1] = self->client->ps.viewangles[1];
6331 	AngleVectors( fwdangles, forward, NULL, NULL );
6332 
6333 	VectorCopy( saber->currentOrigin, center );
6334 
6335 	for ( i = 0 ; i < 3 ; i++ )
6336 	{
6337 		mins[i] = center[i] - radius;
6338 		maxs[i] = center[i] + radius;
6339 	}
6340 
6341 	//if the saber has an enemy from the last time it looked, init to that one
6342 	if ( WP_SaberValidateEnemy( self, saber->enemy ) )
6343 	{
6344 		if ( gi.inPVS( self->currentOrigin, saber->enemy->currentOrigin ) )
6345 		{//potentially visible
6346 			if ( G_ClearLOS( self, self->client->renderInfo.eyePoint, saber->enemy ) )
6347 			{//can see him
6348 				bestEnt = saber->enemy;
6349 				bestRating = WP_SaberRateEnemy( bestEnt, center, forward, radius );
6350 			}
6351 		}
6352 	}
6353 
6354 	//If I have an enemy, see if that's even better
6355 	if ( WP_SaberValidateEnemy( self, self->enemy ) )
6356 	{
6357 		float myEnemyRating = WP_SaberRateEnemy( self->enemy, center, forward, radius );
6358 		if ( myEnemyRating > bestRating )
6359 		{
6360 			if ( gi.inPVS( self->currentOrigin, self->enemy->currentOrigin ) )
6361 			{//potentially visible
6362 				if ( G_ClearLOS( self, self->client->renderInfo.eyePoint, self->enemy ) )
6363 				{//can see him
6364 					bestEnt = self->enemy;
6365 					bestRating = myEnemyRating;
6366 				}
6367 			}
6368 		}
6369 	}
6370 
6371 	numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
6372 
6373 	if ( !numListedEntities )
6374 	{//should we clear the enemy?
6375 		return bestEnt;
6376 	}
6377 
6378 	for ( e = 0 ; e < numListedEntities ; e++ )
6379 	{
6380 		ent = entityList[ e ];
6381 
6382 		if ( ent == self || ent == saber || ent == bestEnt )
6383 		{
6384 			continue;
6385 		}
6386 		if ( !WP_SaberValidateEnemy( self, ent ) )
6387 		{//doesn't meet criteria of valid look enemy (don't check current since we would have done that before this func's call
6388 			continue;
6389 		}
6390 		if ( !gi.inPVS( self->currentOrigin, ent->currentOrigin ) )
6391 		{//not even potentially visible
6392 			continue;
6393 		}
6394 		if ( !G_ClearLOS( self, self->client->renderInfo.eyePoint, ent ) )
6395 		{//can't see him
6396 			continue;
6397 		}
6398 		//rate him based on how close & how in front he is
6399 		rating = WP_SaberRateEnemy( ent, center, forward, radius );
6400 		if ( rating > bestRating )
6401 		{
6402 			bestEnt = ent;
6403 			bestRating = rating;
6404 		}
6405 	}
6406 	return bestEnt;
6407 }
6408 
WP_RunSaber(gentity_t * self,gentity_t * saber)6409 void WP_RunSaber( gentity_t *self, gentity_t *saber )
6410 {
6411 	vec3_t		origin, oldOrg;
6412 	trace_t		tr;
6413 
6414 	VectorCopy( saber->currentOrigin, oldOrg );
6415 	// get current position
6416 	EvaluateTrajectory( &saber->s.pos, level.time, origin );
6417 	// get current angles
6418 	EvaluateTrajectory( &saber->s.apos, level.time, saber->currentAngles );
6419 
6420 	// trace a line from the previous position to the current position,
6421 	// ignoring interactions with the missile owner
6422 	int clipmask = saber->clipmask;
6423 	if ( !self || !self->client || self->client->ps.SaberLength() <= 0 )
6424 	{//don't keep hitting other sabers when turned off
6425 		clipmask &= ~CONTENTS_LIGHTSABER;
6426 	}
6427 	gi.trace( &tr, saber->currentOrigin, saber->mins, saber->maxs, origin,
6428 		saber->owner ? saber->owner->s.number : ENTITYNUM_NONE, clipmask, (EG2_Collision)0, 0 );
6429 
6430 	VectorCopy( tr.endpos, saber->currentOrigin );
6431 
6432 	if ( self->client->ps.SaberActive() )
6433 	{
6434 		if ( self->client->ps.saberInFlight || (self->client->ps.weaponTime&&!Q_irand( 0, 100 )) )
6435 		{//make enemies run from a lit saber in flight or from me when I'm attacking
6436 			if ( !Q_irand( 0, 10 ) )
6437 			{//not so often...
6438 				AddSightEvent( self, saber->currentOrigin, self->client->ps.SaberLength()*3, AEL_DANGER, 100 );
6439 			}
6440 		}
6441 	}
6442 
6443 	if ( tr.startsolid )
6444 	{
6445 		tr.fraction = 0;
6446 	}
6447 
6448 	gi.linkentity( saber );
6449 
6450 	//touch push triggers?
6451 
6452 	if ( tr.fraction != 1 )
6453 	{
6454 		WP_SaberImpact( self, saber, &tr );
6455 	}
6456 
6457 	if ( saber->s.pos.trType == TR_LINEAR )
6458 	{//home
6459 		//figure out where saber should be
6460 		vec3_t	forward, saberHome, saberDest, fwdangles = {0};
6461 
6462 		VectorCopy( self->client->ps.viewangles, fwdangles );
6463 		if ( self->s.number )
6464 		{
6465 			fwdangles[0] -= 8;
6466 		}
6467 		else if ( cg.renderingThirdPerson )
6468 		{
6469 			fwdangles[0] -= 5;
6470 		}
6471 
6472 		if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_1
6473 			|| self->client->ps.saberEntityState == SES_RETURNING
6474 			|| VectorCompare( saber->s.pos.trDelta, vec3_origin ) )
6475 		{//control if it's returning or just starting
6476 			float	saberSpeed = 500;//FIXME: based on force level?
6477 			float	dist;
6478 			gentity_t *enemy = NULL;
6479 
6480 			AngleVectors( fwdangles, forward, NULL, NULL );
6481 
6482 			if ( self->client->ps.saberEntityDist < 100 )
6483 			{//make the saber head to my hand- the bolt it was attached to
6484 				VectorCopy( self->client->renderInfo.handRPoint, saberHome );
6485 			}
6486 			else
6487 			{//aim saber from eyes
6488 				VectorCopy( self->client->renderInfo.eyePoint, saberHome );
6489 			}
6490 			VectorMA( saberHome, self->client->ps.saberEntityDist, forward, saberDest );
6491 
6492 			if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2
6493 				&& self->client->ps.saberEntityState == SES_LEAVING )
6494 			{//max level
6495 				if ( self->enemy &&
6496 					!WP_SaberValidateEnemy( self, self->enemy ) )
6497 				{//if my enemy isn't valid to auto-aim at, don't autoaim
6498 				}
6499 				else
6500 				{
6501 					//pick an enemy
6502 					enemy = WP_SaberFindEnemy( self, saber );
6503 					if ( enemy )
6504 					{//home in on enemy
6505 						float enemyDist = Distance( self->client->renderInfo.handRPoint, enemy->currentOrigin );
6506 						VectorCopy( enemy->currentOrigin, saberDest );
6507 						saberDest[2] += enemy->maxs[2]/2.0f;//FIXME: when in a knockdown anim, the saber float above them... do we care?
6508 						self->client->ps.saberEntityDist = enemyDist;
6509 						//once you pick an enemy, stay with it!
6510 						saber->enemy = enemy;
6511 						//FIXME: lock onto that enemy for a minimum amount of time (unless they become invalid?)
6512 					}
6513 				}
6514 			}
6515 
6516 
6517 			//Make the saber head there
6518 			VectorSubtract( saberDest, saber->currentOrigin, saber->s.pos.trDelta );
6519 			dist = VectorNormalize( saber->s.pos.trDelta );
6520 			if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 && self->client->ps.saberEntityState == SES_LEAVING && !enemy )
6521 			{
6522 				if ( dist < 200 )
6523 				{
6524 					saberSpeed = 400 - (dist*2);
6525 				}
6526 			}
6527 			else if ( self->client->ps.saberEntityState == SES_LEAVING && dist < 50 )
6528 			{
6529 				saberSpeed = dist * 2 + 30;
6530 				if ( (enemy && dist > enemy->maxs[0]) || (!enemy && dist > 24) )
6531 				{//auto-tracking an enemy and we can't hit him
6532 					if ( saberSpeed < 120 )
6533 					{//clamp to a minimum speed
6534 						saberSpeed = 120;
6535 					}
6536 				}
6537 			}
6538 			/*
6539 			if ( self->client->ps.saberEntityState == SES_RETURNING )
6540 			{//FIXME: if returning, move faster?
6541 				saberSpeed = 800;
6542 				if ( dist < 200 )
6543 				{
6544 					saberSpeed -= 400 - (dist*2);
6545 				}
6546 			}
6547 			*/
6548 			VectorScale( saber->s.pos.trDelta, saberSpeed, saber->s.pos.trDelta );
6549 			//SnapVector( saber->s.pos.trDelta );	// save net bandwidth
6550 			VectorCopy( saber->currentOrigin, saber->s.pos.trBase );
6551 			saber->s.pos.trTime = level.time;
6552 			saber->s.pos.trType = TR_LINEAR;
6553 		}
6554 		else
6555 		{
6556 			VectorCopy( saber->currentOrigin, saber->s.pos.trBase );
6557 			saber->s.pos.trTime = level.time;
6558 			saber->s.pos.trType = TR_LINEAR;
6559 		}
6560 
6561 		//if it's heading back, point it's base at us
6562 		if ( self->client->ps.saberEntityState == SES_RETURNING
6563 			&& !(self->client->ps.saber[0].saberFlags&SFL_RETURN_DAMAGE) )//type != SABER_STAR )
6564 		{
6565 			fwdangles[0] += SABER_PITCH_HACK;
6566 			VectorCopy( fwdangles, saber->s.apos.trBase );
6567 			saber->s.apos.trTime = level.time;
6568 			saber->s.apos.trType = TR_INTERPOLATE;
6569 			VectorClear( saber->s.apos.trDelta );
6570 		}
6571 	}
6572 }
6573 
6574 
WP_SaberLaunch(gentity_t * self,gentity_t * saber,qboolean thrown,qboolean noFail=qfalse)6575 qboolean WP_SaberLaunch( gentity_t *self, gentity_t *saber, qboolean thrown, qboolean noFail = qfalse )
6576 {//FIXME: probably need a debounce time
6577 	vec3_t	saberMins={-3.0f,-3.0f,-3.0f};
6578 	vec3_t	saberMaxs={3.0f,3.0f,3.0f};
6579 	trace_t	trace;
6580 
6581 	if ( self->client->NPC_class == CLASS_SABER_DROID )
6582 	{//saber droids can't drop their saber
6583 		return qfalse;
6584 	}
6585 	if ( !noFail )
6586 	{
6587 		if ( thrown )
6588 		{//this is a regular throw, so see if it's legal
6589 			if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
6590 			{
6591 				if ( !WP_ForcePowerUsable( self, FP_SABERTHROW, 20 ) )
6592 				{
6593 					return qfalse;
6594 				}
6595 			}
6596 			else
6597 			{
6598 				if ( !WP_ForcePowerUsable( self, FP_SABERTHROW, 0 ) )
6599 				{
6600 					return qfalse;
6601 				}
6602 			}
6603 		}
6604 		if ( !self->s.number && (cg.zoomMode || in_camera) )
6605 		{//can't saber throw when zoomed in or in cinematic
6606 			return qfalse;
6607 		}
6608 		//make sure it won't start in solid
6609 		gi.trace( &trace, self->client->renderInfo.handRPoint, saberMins, saberMaxs, self->client->renderInfo.handRPoint, saber->s.number, MASK_SOLID, (EG2_Collision)0, 0 );
6610 		if ( trace.startsolid || trace.allsolid )
6611 		{
6612 			return qfalse;
6613 		}
6614 		//make sure I'm not throwing it on the other side of a door or wall or whatever
6615 		gi.trace( &trace, self->currentOrigin, vec3_origin, vec3_origin, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID, (EG2_Collision)0, 0 );
6616 		if ( trace.startsolid || trace.allsolid || trace.fraction < 1.0f )
6617 		{
6618 			return qfalse;
6619 		}
6620 
6621 		if ( thrown )
6622 		{//this is a regular throw, so take force power
6623 			if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
6624 			{//at max skill, the cost increases as keep it out
6625 				WP_ForcePowerStart( self, FP_SABERTHROW, 10 );
6626 			}
6627 			else
6628 			{
6629 				WP_ForcePowerStart( self, FP_SABERTHROW, 0 );
6630 			}
6631 		}
6632 	}
6633 	//clear the enemy
6634 	saber->enemy = NULL;
6635 
6636 //===FIXME!!!==============================================================================================
6637 	//We should copy the right-hand saber's g2 instance to the thrown saber
6638 	//Then back again when you catch it!!!
6639 //===FIXME!!!==============================================================================================
6640 
6641 	//draw it
6642 	saber->s.eFlags &= ~EF_NODRAW;
6643 	saber->svFlags |= SVF_BROADCAST;
6644 	saber->svFlags &= ~SVF_NOCLIENT;
6645 
6646 	//place it
6647 	VectorCopy( self->client->renderInfo.handRPoint, saber->currentOrigin );//muzzlePoint
6648 	VectorCopy( saber->currentOrigin, saber->s.pos.trBase );
6649 	saber->s.pos.trTime = level.time;
6650 	saber->s.pos.trType = TR_LINEAR;
6651 	VectorClear( saber->s.pos.trDelta );
6652 	gi.linkentity( saber );
6653 
6654 	//spin it
6655 	VectorClear( saber->s.apos.trBase );
6656 	saber->s.apos.trTime = level.time;
6657 	saber->s.apos.trType = TR_LINEAR;
6658 	if ( self->health > 0 && thrown )
6659 	{//throwing it
6660 		saber->s.apos.trBase[1] = self->client->ps.viewangles[1];
6661 		saber->s.apos.trBase[0] = SABER_PITCH_HACK;
6662 	}
6663 	else
6664 	{//dropping it
6665 		vectoangles( self->client->renderInfo.muzzleDir, saber->s.apos.trBase );
6666 	}
6667 	VectorClear( saber->s.apos.trDelta );
6668 
6669 	switch ( self->client->ps.forcePowerLevel[FP_SABERTHROW] )
6670 	{//FIXME: make a table?
6671 	default:
6672 	case FORCE_LEVEL_1:
6673 		saber->s.apos.trDelta[1] = 600;
6674 		break;
6675 	case FORCE_LEVEL_2:
6676 		saber->s.apos.trDelta[1] = 800;
6677 		break;
6678 	case FORCE_LEVEL_3:
6679 		saber->s.apos.trDelta[1] = 1200;
6680 		break;
6681 	}
6682 
6683 	//Take it out of my hand
6684 	self->client->ps.saberInFlight = qtrue;
6685 	self->client->ps.saberEntityState = SES_LEAVING;
6686 	self->client->ps.saberEntityDist = saberThrowDist[self->client->ps.forcePowerLevel[FP_SABERTHROW]];
6687 	self->client->ps.saberThrowTime = level.time;
6688 	//if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
6689 	{
6690 		self->client->ps.forcePowerDebounce[FP_SABERTHROW] = level.time + 1000;//so we can keep it out for a minimum amount of time
6691 	}
6692 
6693 	if ( thrown )
6694 	{//this is a regular throw, so turn the saber on
6695 		//turn saber on
6696 		if ( (self->client->ps.saber[0].saberFlags&SFL_SINGLE_BLADE_THROWABLE) )//SaberStaff() )
6697 		{//only first blade can be on
6698 			if ( !self->client->ps.saber[0].blade[0].active )
6699 			{//turn on first one
6700 				self->client->ps.SaberBladeActivate( 0, 0 );
6701 			}
6702 			for ( int i = 1; i < self->client->ps.saber[0].numBlades; i++ )
6703 			{//turn off all others
6704 				if ( self->client->ps.saber[0].blade[i].active )
6705 				{
6706 					self->client->ps.SaberBladeActivate( 0, i, qfalse );
6707 				}
6708 			}
6709 		}
6710 		else
6711 		{//turn the sabers, all blades...?
6712 			self->client->ps.saber[0].Activate();
6713 			//self->client->ps.SaberActivate();
6714 		}
6715 		//turn on the saber trail
6716 		self->client->ps.saber[0].ActivateTrail( 150 );
6717 	}
6718 
6719 	//reset the mins
6720 	VectorCopy( saberMins, saber->mins );
6721 	VectorCopy( saberMaxs, saber->maxs );
6722 	saber->contents = 0;//CONTENTS_LIGHTSABER;
6723 	saber->clipmask = MASK_SOLID | CONTENTS_LIGHTSABER;
6724 
6725 	// remove the ghoul2 right-hand saber model on the player
6726 	if ( self->weaponModel[0] > 0 )
6727 	{
6728 		gi.G2API_RemoveGhoul2Model(self->ghoul2, self->weaponModel[0]);
6729 		self->weaponModel[0] = -1;
6730 	}
6731 
6732 	return qtrue;
6733 }
6734 
WP_SaberLose(gentity_t * self,vec3_t throwDir)6735 qboolean WP_SaberLose( gentity_t *self, vec3_t throwDir )
6736 {
6737 	if ( !self || !self->client || self->client->ps.saberEntityNum <= 0 )
6738 	{//WTF?!!  We lost it already?
6739 		return qfalse;
6740 	}
6741 	if ( self->client->NPC_class == CLASS_SABER_DROID )
6742 	{//saber droids can't drop their saber
6743 		return qfalse;
6744 	}
6745 	gentity_t *dropped = &g_entities[self->client->ps.saberEntityNum];
6746 	if ( !self->client->ps.saberInFlight )
6747 	{//not alreay in air
6748 		/*
6749 		qboolean noForceThrow = qfalse;
6750 		//make it so we can throw it
6751 		self->client->ps.forcePowersKnown |= (1<<FP_SABERTHROW);
6752 		if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] < FORCE_LEVEL_1 )
6753 		{
6754 			noForceThrow = qtrue;
6755 			self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_1;
6756 		}
6757 		*/
6758 		//throw it
6759 		if ( !WP_SaberLaunch( self, dropped, qfalse ) )
6760 		{//couldn't throw it
6761 			return qfalse;
6762 		}
6763 		/*
6764 		if ( noForceThrow )
6765 		{
6766 			self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_0;
6767 		}
6768 		*/
6769 	}
6770 	if ( self->client->ps.saber[0].Active() )
6771 	{//on
6772 		//drop it instantly
6773 		WP_SaberDrop( self, dropped );
6774 	}
6775 	//optionally give it some thrown velocity
6776 	if ( throwDir && !VectorCompare( throwDir, vec3_origin ) )
6777 	{
6778 		VectorCopy( throwDir, dropped->s.pos.trDelta );
6779 	}
6780 	//don't pull it back on the next frame
6781 	if ( self->NPC )
6782 	{
6783 		self->NPC->last_ucmd.buttons &= ~BUTTON_ATTACK;
6784 	}
6785 	return qtrue;
6786 }
6787 
WP_SetSaberOrigin(gentity_t * self,vec3_t newOrg)6788 void WP_SetSaberOrigin( gentity_t *self, vec3_t newOrg )
6789 {
6790 	if ( !self || !self->client )
6791 	{
6792 		return;
6793 	}
6794 	if ( self->client->ps.saberEntityNum <= 0 || self->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
6795 	{//no saber ent to reposition
6796 		return;
6797 	}
6798 	if ( self->client->NPC_class == CLASS_SABER_DROID )
6799 	{//saber droids can't drop their saber
6800 		return;
6801 	}
6802 	gentity_t *dropped = &g_entities[self->client->ps.saberEntityNum];
6803 	if ( !self->client->ps.saberInFlight )
6804 	{//not already in air
6805 		qboolean noForceThrow = qfalse;
6806 		//make it so we can throw it
6807 		self->client->ps.forcePowersKnown |= (1<<FP_SABERTHROW);
6808 		if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] < FORCE_LEVEL_1 )
6809 		{
6810 			noForceThrow = qtrue;
6811 			self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_1;
6812 		}
6813 		//throw it
6814 		if ( !WP_SaberLaunch( self, dropped, qfalse, qtrue ) )
6815 		{//couldn't throw it
6816 			return;
6817 		}
6818 		if ( noForceThrow )
6819 		{
6820 			self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_0;
6821 		}
6822 	}
6823 	VectorCopy( newOrg, dropped->s.origin );
6824 	VectorCopy( newOrg, dropped->currentOrigin );
6825 	VectorCopy( newOrg, dropped->s.pos.trBase );
6826 	//drop it instantly
6827 	WP_SaberDrop( self, dropped );
6828 	//don't pull it back on the next frame
6829 	if ( self->NPC )
6830 	{
6831 		self->NPC->last_ucmd.buttons &= ~BUTTON_ATTACK;
6832 	}
6833 }
6834 
WP_SaberCatch(gentity_t * self,gentity_t * saber,qboolean switchToSaber)6835 void WP_SaberCatch( gentity_t *self, gentity_t *saber, qboolean switchToSaber )
6836 {//FIXME: probably need a debounce time
6837 	if ( self->health > 0 && !PM_SaberInBrokenParry( self->client->ps.saberMove ) && self->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
6838 	{
6839 		//clear the enemy
6840 		saber->enemy = NULL;
6841 //===FIXME!!!==============================================================================================
6842 	//We should copy the thrown saber's g2 instance to the right-hand saber
6843 	//When you catch it, and vice-versa when you throw it!!!
6844 //===FIXME!!!==============================================================================================
6845 		//don't draw it
6846 		saber->s.eFlags |= EF_NODRAW;
6847 		saber->svFlags &= SVF_BROADCAST;
6848 		saber->svFlags |= SVF_NOCLIENT;
6849 
6850 		//take off any gravity stuff if we'd dropped it
6851 		saber->s.pos.trType = TR_LINEAR;
6852 		saber->s.eFlags &= ~EF_BOUNCE_HALF;
6853 
6854 		//Put it in my hand
6855 		self->client->ps.saberInFlight = qfalse;
6856 		self->client->ps.saberEntityState = SES_LEAVING;
6857 
6858 		//turn off the saber trail
6859 		self->client->ps.saber[0].DeactivateTrail( 75 );
6860 
6861 		//reset its contents/clipmask
6862 		saber->contents = CONTENTS_LIGHTSABER;// | CONTENTS_SHOTCLIP;
6863 		saber->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
6864 
6865 		//play catch sound
6866 		G_Sound( saber, G_SoundIndex( "sound/weapons/saber/saber_catch.wav" ) );
6867 		//FIXME: if an NPC, don't turn it back on if no enemy or enemy is dead...
6868 		//if it's not our current weapon, make it our current weapon
6869 		if ( self->client->ps.weapon == WP_SABER )
6870 		{//only do the first saber since we only throw the first one
6871 			WP_SaberAddG2SaberModels( self, qfalse );
6872 		}
6873 		if ( switchToSaber )
6874 		{
6875 			if ( self->client->ps.weapon != WP_SABER )
6876 			{
6877 				CG_ChangeWeapon( WP_SABER );
6878 			}
6879 			else
6880 			{//if it's not active, turn it on
6881 				if ( (self->client->ps.saber[0].saberFlags&SFL_SINGLE_BLADE_THROWABLE) )//SaberStaff() )
6882 				{//only first blade can be on
6883 					if ( !self->client->ps.saber[0].blade[0].active )
6884 					{//only turn it on if first blade is off, otherwise, leave as-is
6885 						self->client->ps.saber[0].Activate();
6886 					}
6887 				}
6888 				else
6889 				{//turn all blades on
6890 					self->client->ps.saber[0].Activate();
6891 				}
6892 			}
6893 		}
6894 	}
6895 }
6896 
6897 
WP_SaberReturn(gentity_t * self,gentity_t * saber)6898 void WP_SaberReturn( gentity_t *self, gentity_t *saber )
6899 {
6900 	if ( PM_SaberInBrokenParry( self->client->ps.saberMove ) || self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
6901 	{
6902 		return;
6903 	}
6904 	if ( self && self->client )
6905 	{//still alive and stuff
6906 		//FIXME: when it's returning, flies butt first, but seems to do a lot of damage when going through people... hmm...
6907 		self->client->ps.saberEntityState = SES_RETURNING;
6908 		//turn down the saber trail
6909 		if ( !(self->client->ps.saber[0].saberFlags&SFL_RETURN_DAMAGE) )//type != SABER_STAR )
6910 		{
6911 			self->client->ps.saber[0].DeactivateTrail( 75 );
6912 		}
6913 	}
6914 	if ( !(saber->s.eFlags&EF_BOUNCE) )
6915 	{
6916 		saber->s.eFlags |= EF_BOUNCE;
6917 		saber->bounceCount = 300;
6918 	}
6919 }
6920 
6921 
WP_SaberDrop(gentity_t * self,gentity_t * saber)6922 void WP_SaberDrop( gentity_t *self, gentity_t *saber )
6923 {
6924 	//clear the enemy
6925 	saber->enemy = NULL;
6926 	saber->s.eFlags &= ~EF_BOUNCE;
6927 	saber->bounceCount = 0;
6928 	//make it fall
6929 	saber->s.pos.trType = TR_GRAVITY;
6930 	//make it bounce some
6931 	saber->s.eFlags |= EF_BOUNCE_HALF;
6932 	//make it spin
6933 	VectorCopy( saber->currentAngles, saber->s.apos.trBase );
6934 	saber->s.apos.trType = TR_LINEAR;
6935 	saber->s.apos.trTime = level.time;
6936 	VectorSet( saber->s.apos.trDelta, Q_irand( -300, 300 ), saber->s.apos.trDelta[1], Q_irand( -300, 300 ) );
6937 	if ( !saber->s.apos.trDelta[1] )
6938 	{
6939 		saber->s.apos.trDelta[1] = Q_irand( -300, 300 );
6940 	}
6941 	//force it to be ready to return
6942 	self->client->ps.saberEntityDist = 0;
6943 	self->client->ps.saberEntityState = SES_RETURNING;
6944 	//turn it off
6945 	self->client->ps.saber[0].Deactivate();
6946 	//turn off the saber trail
6947 	self->client->ps.saber[0].DeactivateTrail( 75 );
6948 	//play the saber turning off sound
6949 	G_SoundIndexOnEnt( saber, CHAN_AUTO, self->client->ps.saber[0].soundOff );
6950 
6951 	if ( self->health <= 0 )
6952 	{//owner is dead!
6953 		saber->s.time = level.time;//will make us free ourselves after a time
6954 	}
6955 }
6956 
6957 
WP_SaberPull(gentity_t * self,gentity_t * saber)6958 void WP_SaberPull( gentity_t *self, gentity_t *saber )
6959 {
6960 	if ( PM_SaberInBrokenParry( self->client->ps.saberMove ) || self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
6961 	{
6962 		return;
6963 	}
6964 	if ( self->health > 0 )
6965 	{
6966 		//take off gravity
6967 		saber->s.pos.trType = TR_LINEAR;
6968 		//take off bounce
6969 		saber->s.eFlags &= EF_BOUNCE_HALF;
6970 		//play sound
6971 		G_Sound( self, G_SoundIndex( "sound/weapons/force/pull.wav" ) );
6972 	}
6973 }
6974 
6975 const char *saberColorStringForColor[SABER_PURPLE+1] =
6976 {
6977 	"red",//SABER_RED
6978 	"orange",//SABER_ORANGE
6979 	"yellow",//SABER_YELLOW
6980 	"green",//SABER_GREEN
6981 	"blue",//SABER_BLUE
6982 	"purple"//SABER_PURPLE
6983 };
6984 
6985 // Check if we are throwing it, launch it if needed, update position if needed.
WP_SaberThrow(gentity_t * self,usercmd_t * ucmd)6986 void WP_SaberThrow( gentity_t *self, usercmd_t *ucmd )
6987 {
6988 	vec3_t			saberDiff;
6989 	trace_t			tr;
6990 	//static float	SABER_SPEED = 10;
6991 
6992 	gentity_t *saberent;
6993 
6994 	if ( self->client->ps.saberEntityNum <= 0 || self->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
6995 	{//WTF?!!  We lost it?
6996 		return;
6997 	}
6998 
6999 	if ( self->client->ps.torsoAnim == BOTH_LOSE_SABER )
7000 	{//can't catch it while it's being yanked from your hand!
7001 		return;
7002 	}
7003 
7004 	if ( !g_saberNewControlScheme->integer )
7005 	{
7006 		if ( PM_SaberInKata( (saberMoveName_t)self->client->ps.saberMove ) )
7007 		{//don't throw saber when in special attack (alt+attack)
7008 			return;
7009 		}
7010 		if ( (ucmd->buttons&BUTTON_ATTACK)
7011 			&& (ucmd->buttons&BUTTON_ALT_ATTACK)
7012 			&& !self->client->ps.saberInFlight )
7013 		{//trying to do special attack, don't throw it
7014 			return;
7015 		}
7016 		else if ( self->client->ps.torsoAnim == BOTH_A1_SPECIAL
7017 			|| self->client->ps.torsoAnim == BOTH_A2_SPECIAL
7018 			|| self->client->ps.torsoAnim == BOTH_A3_SPECIAL )
7019 		{//don't throw in these anims!
7020 			return;
7021 		}
7022 	}
7023 	saberent = &g_entities[self->client->ps.saberEntityNum];
7024 
7025 	VectorSubtract( self->client->renderInfo.handRPoint, saberent->currentOrigin, saberDiff );
7026 
7027 	//is our saber in flight?
7028 	if ( !self->client->ps.saberInFlight )
7029 	{//saber is not in flight right now
7030 		if ( self->client->ps.weapon != WP_SABER )
7031 		{//don't even have it out
7032 			return;
7033 		}
7034 		else if ( (ucmd->buttons&BUTTON_ALT_ATTACK) && !(self->client->ps.pm_flags&PMF_ALT_ATTACK_HELD) )
7035 		{//still holding it, not still holding attack from a previous throw, so throw it.
7036 			if ( !(self->client->ps.saberEventFlags&SEF_INWATER) && WP_SaberLaunch( self, saberent, qtrue ) )
7037 			{
7038 				if ( self->client && !self->s.number )
7039 				{
7040 					self->client->sess.missionStats.saberThrownCnt++;
7041 				}
7042 				//need to recalc this because we just moved it
7043 				VectorSubtract( self->client->renderInfo.handRPoint, saberent->currentOrigin, saberDiff );
7044 			}
7045 			else
7046 			{//couldn't throw it
7047 				return;
7048 			}
7049 		}
7050 		else
7051 		{//holding it, don't want to throw it, go away.
7052 			return;
7053 		}
7054 	}
7055 	else
7056 	{//inflight
7057 		//is our saber currently on it's way back to us?
7058 		if ( self->client->ps.saberEntityState == SES_RETURNING )
7059 		{//see if we're close enough to pick it up
7060 			if ( VectorLengthSquared( saberDiff ) <= 256 )//16 squared//G_BoundsOverlap( self->absmin, self->absmax, saberent->absmin, saberent->absmax ) )//
7061 			{//caught it
7062 				vec3_t	axisPoint;
7063 				trace_t	trace;
7064 				VectorCopy( self->currentOrigin, axisPoint );
7065 				axisPoint[2] = self->client->renderInfo.handRPoint[2];
7066 				gi.trace( &trace, axisPoint, vec3_origin, vec3_origin, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID, (EG2_Collision)0, 0 );
7067 				if ( !trace.startsolid && trace.fraction >= 1.0f )
7068 				{//our hand isn't through a wall
7069 					WP_SaberCatch( self, saberent, qtrue );
7070 					//NPC_SetAnim( self, SETANIM_TORSO, TORSO_HANDRETRACT1, SETANIM_FLAG_OVERRIDE );
7071 				}
7072 				return;
7073 			}
7074 		}
7075 
7076 		if ( saberent->s.pos.trType != TR_STATIONARY )
7077 		{//saber is in flight, lerp it
7078 			if ( self->health <= 0 )//&& level.time > saberent->s.time + 5000 )
7079 			{//make us free ourselves after a time
7080 				if ( g_saberPickuppableDroppedSabers->integer
7081 					&& G_DropSaberItem( self->client->ps.saber[0].name, self->client->ps.saber[0].blade[0].color, saberent->currentOrigin, saberent->s.pos.trDelta, saberent->currentAngles ) != NULL )
7082 				{//dropped it
7083 					//free it
7084 					G_FreeEntity( saberent );
7085 					//forget it
7086 					self->client->ps.saberEntityNum = ENTITYNUM_NONE;
7087 					return;
7088 				}
7089 			}
7090 			WP_RunSaber( self, saberent );
7091 		}
7092 		else
7093 		{//it fell on the ground
7094 			if ( self->health <= 0 )//&& level.time > saberent->s.time + 5000 )
7095 			{//make us free ourselves after a time
7096 				if ( g_saberPickuppableDroppedSabers->integer )
7097 				{//spawn an item
7098 					G_DropSaberItem( self->client->ps.saber[0].name, self->client->ps.saber[0].blade[0].color, saberent->currentOrigin, saberent->s.pos.trDelta, saberent->currentAngles );
7099 				}
7100 				//free it
7101 				G_FreeEntity( saberent );
7102 				//forget it
7103 				self->client->ps.saberEntityNum = ENTITYNUM_NONE;
7104 				return;
7105 			}
7106 			if ( (!self->s.number && level.time - saberent->aimDebounceTime > 15000)
7107 				|| (self->s.number && level.time - saberent->aimDebounceTime > 5000) )
7108 			{//(only for player) been missing for 15 seconds, automagicially return
7109 				WP_SaberCatch( self, saberent, qfalse );
7110 				return;
7111 			}
7112 		}
7113 	}
7114 
7115 	//are we still trying to use the saber?
7116 	if ( self->client->ps.weapon != WP_SABER )
7117 	{//switched away
7118 		if ( !self->client->ps.saberInFlight )
7119 		{//wasn't throwing saber
7120 			return;
7121 		}
7122 		else if ( saberent->s.pos.trType == TR_LINEAR )
7123 		{//switched away while controlling it, just drop the saber
7124 			WP_SaberDrop( self, saberent );
7125 			return;
7126 		}
7127 		else
7128 		{//it's on the ground, see if it's inside us (touching)
7129 			if ( G_PointInBounds( saberent->currentOrigin, self->absmin, self->absmax ) )
7130 			{//it's in us, pick it up automatically
7131 				WP_SaberPull( self, saberent );
7132 			}
7133 		}
7134 	}
7135 	else if ( saberent->s.pos.trType != TR_LINEAR )
7136 	{//weapon is saber and not flying
7137 		if ( self->client->ps.saberInFlight )
7138 		{//we dropped it
7139 			if ( ucmd->buttons & BUTTON_ATTACK )//|| self->client->ps.weaponstate == WEAPON_RAISING )//ucmd->buttons & BUTTON_ALT_ATTACK ||
7140 			{//we actively want to pick it up or we just switched to it, so pull it back
7141 				gi.trace( &tr, saberent->currentOrigin, saberent->mins, saberent->maxs, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID, (EG2_Collision)0, 0 );
7142 
7143 				if ( tr.allsolid || tr.startsolid || tr.fraction < 1.0f )
7144 				{//can't pick it up yet, no LOS
7145 					return;
7146 				}
7147 				//clear LOS, pick it up
7148 				WP_SaberPull( self, saberent );
7149 			}
7150 			else
7151 			{//see if it's inside us (touching)
7152 				if ( G_PointInBounds( saberent->currentOrigin, self->absmin, self->absmax ) )
7153 				{//it's in us, pick it up automatically
7154 					WP_SaberPull( self, saberent );
7155 				}
7156 			}
7157 		}
7158 	}
7159 	else if ( self->health <= 0 && self->client->ps.saberInFlight )
7160 	{//we died, drop it
7161 		WP_SaberDrop( self, saberent );
7162 		return;
7163 	}
7164 	else if ( !self->client->ps.saber[0].Active() && self->client->ps.saberEntityState != SES_RETURNING )
7165 	{//we turned it off, drop it
7166 		WP_SaberDrop( self, saberent );
7167 		return;
7168 	}
7169 
7170 	//TODO: if deactivate saber in flight, should it drop?
7171 
7172 	if ( saberent->s.pos.trType != TR_LINEAR )
7173 	{//don't home
7174 		return;
7175 	}
7176 
7177 	float saberDist = VectorLength( saberDiff );
7178 	if ( self->client->ps.saberEntityState == SES_LEAVING )
7179 	{//saber still flying forward
7180 		if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
7181 		{//still holding it out
7182 			if ( !(ucmd->buttons&BUTTON_ALT_ATTACK) && self->client->ps.forcePowerDebounce[FP_SABERTHROW] < level.time )
7183 			{//done throwing, return to me
7184 				if ( self->client->ps.saber[0].Active() )
7185 				{//still on
7186 					WP_SaberReturn( self, saberent );
7187 				}
7188 			}
7189 			else if ( level.time - self->client->ps.saberThrowTime >= 100 )
7190 			{
7191 				if ( WP_ForcePowerAvailable( self, FP_SABERTHROW, 1 ) )
7192 				{
7193 					WP_ForcePowerDrain( self, FP_SABERTHROW, 1 );
7194 					self->client->ps.saberThrowTime = level.time;
7195 				}
7196 				else
7197 				{//out of force power, return to me
7198 					WP_SaberReturn( self, saberent );
7199 				}
7200 			}
7201 		}
7202 		else
7203 		{
7204 			if ( !(ucmd->buttons&BUTTON_ALT_ATTACK) && self->client->ps.forcePowerDebounce[FP_SABERTHROW] < level.time )
7205 			{//not holding button and has been out at least 1 second, return to me
7206 				if ( self->client->ps.saber[0].Active() )
7207 				{//still on
7208 					WP_SaberReturn( self, saberent );
7209 				}
7210 			}
7211 			else if ( level.time - self->client->ps.saberThrowTime > 3000
7212 				|| (self->client->ps.forcePowerLevel[FP_SABERTHROW]==FORCE_LEVEL_1&&saberDist>=self->client->ps.saberEntityDist) )
7213 			{//been out too long, or saber throw 1 went too far, return to me
7214 				if ( self->client->ps.saber[0].Active() )
7215 				{//still on
7216 					WP_SaberReturn( self, saberent );
7217 				}
7218 			}
7219 		}
7220 	}
7221 	if ( self->client->ps.saberEntityState == SES_RETURNING )
7222 	{
7223 		if ( self->client->ps.saberEntityDist > 0 )
7224 		{
7225 			self->client->ps.saberEntityDist -= 25;
7226 		}
7227 		if ( self->client->ps.saberEntityDist < 0 )
7228 		{
7229 			self->client->ps.saberEntityDist = 0;
7230 		}
7231 		else if ( saberDist < self->client->ps.saberEntityDist )
7232 		{//if it's coming back to me, never push it away
7233 			self->client->ps.saberEntityDist = saberDist;
7234 		}
7235 	}
7236 }
7237 
7238 
7239 //SABER BLOCKING============================================================================
7240 //SABER BLOCKING============================================================================
7241 //SABER BLOCKING============================================================================
7242 //SABER BLOCKING============================================================================
7243 //SABER BLOCKING============================================================================
WP_MissileBlockForBlock(int saberBlock)7244 int WP_MissileBlockForBlock( int saberBlock )
7245 {
7246 	switch( saberBlock )
7247 	{
7248 	case BLOCKED_UPPER_RIGHT:
7249 		return BLOCKED_UPPER_RIGHT_PROJ;
7250 		break;
7251 	case BLOCKED_UPPER_LEFT:
7252 		return BLOCKED_UPPER_LEFT_PROJ;
7253 		break;
7254 	case BLOCKED_LOWER_RIGHT:
7255 		return BLOCKED_LOWER_RIGHT_PROJ;
7256 		break;
7257 	case BLOCKED_LOWER_LEFT:
7258 		return BLOCKED_LOWER_LEFT_PROJ;
7259 		break;
7260 	case BLOCKED_TOP:
7261 		return BLOCKED_TOP_PROJ;
7262 		break;
7263 	}
7264 	return saberBlock;
7265 }
7266 
WP_SaberBlockNonRandom(gentity_t * self,vec3_t hitloc,qboolean missileBlock)7267 void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock )
7268 {
7269 	vec3_t diff, fwdangles={0,0,0}, right;
7270 	float rightdot;
7271 	float zdiff;
7272 
7273 	if ( self->client->ps.weaponstate == WEAPON_DROPPING ||
7274 		self->client->ps.weaponstate == WEAPON_RAISING )
7275 	{//don't block while changing weapons
7276 		return;
7277 	}
7278 	if ( PM_SuperBreakLoseAnim( self->client->ps.torsoAnim )
7279 		|| PM_SuperBreakWinAnim( self->client->ps.torsoAnim ) )
7280 	{
7281 		return;
7282 	}
7283 	//NPCs don't auto-block
7284 	if ( !missileBlock && self->s.number != 0 && self->client->ps.saberBlocked != BLOCKED_NONE )
7285 	{
7286 		return;
7287 	}
7288 
7289 	VectorSubtract( hitloc, self->client->renderInfo.eyePoint, diff );
7290 	diff[2] = 0;
7291 	VectorNormalize( diff );
7292 
7293 	fwdangles[1] = self->client->ps.viewangles[1];
7294 	// Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
7295 	AngleVectors( fwdangles, NULL, right, NULL );
7296 
7297 	rightdot = DotProduct(right, diff);
7298 	zdiff = hitloc[2] - self->client->renderInfo.eyePoint[2];
7299 
7300 	//FIXME: take torsoAngles into account?
7301 	if ( zdiff > -5 )//0 )//40 )
7302 	{
7303 		if ( rightdot > 0.3 )
7304 		{
7305 			self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
7306 		}
7307 		else if ( rightdot < -0.3 )
7308 		{
7309 			self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
7310 		}
7311 		else
7312 		{
7313 			self->client->ps.saberBlocked = BLOCKED_TOP;
7314 		}
7315 	}
7316 	else if ( zdiff > -22 )//-20 )//20 )
7317 	{
7318 		if ( zdiff < -10 )//30 )
7319 		{//hmm, pretty low, but not low enough to use the low block, so we need to duck
7320 			//NPC should duck, but NPC should never get here
7321 		}
7322 		if ( rightdot > 0.1 )
7323 		{
7324 			self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
7325 		}
7326 		else if ( rightdot < -0.1 )
7327 		{
7328 			self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
7329 		}
7330 		else
7331 		{//FIXME: this looks really weird if the shot is too low!
7332 			self->client->ps.saberBlocked = BLOCKED_TOP;
7333 		}
7334 	}
7335 	else
7336 	{
7337 		if ( rightdot >= 0 )
7338 		{
7339 			self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
7340 		}
7341 		else
7342 		{
7343 			self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
7344 		}
7345 	}
7346 
7347 #ifndef FINAL_BUILD
7348 	if ( d_saberCombat->integer )
7349 	{
7350 		if ( !self->s.number )
7351 		{
7352 			gi.Printf( "EyeZ: %4.2f  HitZ: %4.2f  zdiff: %4.2f  rdot: %4.2f\n", self->client->renderInfo.eyePoint[2], hitloc[2], zdiff, rightdot );
7353 			switch ( self->client->ps.saberBlocked )
7354 			{
7355 			case BLOCKED_TOP:
7356 				gi.Printf( "BLOCKED_TOP\n" );
7357 				break;
7358 			case BLOCKED_UPPER_RIGHT:
7359 				gi.Printf( "BLOCKED_UPPER_RIGHT\n" );
7360 				break;
7361 			case BLOCKED_UPPER_LEFT:
7362 				gi.Printf( "BLOCKED_UPPER_LEFT\n" );
7363 				break;
7364 			case BLOCKED_LOWER_RIGHT:
7365 				gi.Printf( "BLOCKED_LOWER_RIGHT\n" );
7366 				break;
7367 			case BLOCKED_LOWER_LEFT:
7368 				gi.Printf( "BLOCKED_LOWER_LEFT\n" );
7369 				break;
7370 			default:
7371 				break;
7372 			}
7373 		}
7374 	}
7375 #endif
7376 
7377 	if ( missileBlock )
7378 	{
7379 		self->client->ps.saberBlocked = WP_MissileBlockForBlock( self->client->ps.saberBlocked );
7380 	}
7381 
7382 	if ( self->client->ps.saberBlocked != BLOCKED_NONE )
7383 	{
7384 		int parryReCalcTime = Jedi_ReCalcParryTime( self, EVASION_PARRY );
7385 		if ( self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] < level.time + parryReCalcTime )
7386 		{
7387 			self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + parryReCalcTime;
7388 		}
7389 	}
7390 }
7391 
WP_SaberStartMissileBlockCheck(gentity_t * self,usercmd_t * ucmd)7392 void WP_SaberStartMissileBlockCheck( gentity_t *self, usercmd_t *ucmd  )
7393 {
7394 	float		dist;
7395 	gentity_t	*ent, *incoming = NULL;
7396 	gentity_t	*entityList[MAX_GENTITIES];
7397 	int			numListedEntities;
7398 	vec3_t		mins, maxs;
7399 	int			i, e;
7400 	float		closestDist, radius = 256;
7401 	vec3_t		forward, dir, missile_dir, fwdangles = {0};
7402 	trace_t		trace;
7403 	vec3_t		traceTo, entDir;
7404 	qboolean	dodgeOnlySabers = qfalse;
7405 
7406 
7407 	if ( self->NPC && (self->NPC->scriptFlags&SCF_IGNORE_ALERTS) )
7408 	{//don't react to things flying at me...
7409 		return;
7410 	}
7411 	if ( self->health <= 0 )
7412 	{//dead don't try to block (NOTE: actual deflection happens in missile code)
7413 		return;
7414 	}
7415 
7416 	if ( PM_InKnockDown( &self->client->ps ) )
7417 	{//can't block when knocked down
7418 		return;
7419 	}
7420 
7421 	if ( PM_SuperBreakLoseAnim( self->client->ps.torsoAnim )
7422 		|| PM_SuperBreakWinAnim( self->client->ps.torsoAnim ) )
7423 	{//can't block while in break anim
7424 		return;
7425 	}
7426 
7427 	if ( Rosh_BeingHealed( self ) )
7428 	{
7429 		return;
7430 	}
7431 
7432 	if ( self->client->NPC_class == CLASS_ROCKETTROOPER )
7433 	{//rockettrooper
7434 		if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
7435 		{//must be in air
7436 			return;
7437 		}
7438 		if ( Q_irand( 0, 4-(g_spskill->integer*2) ) )
7439 		{//easier level guys do this less
7440 			return;
7441 		}
7442 		if ( Q_irand( 0, 3 ) )
7443 		{//base level: 25% chance of looking for something to dodge
7444 			if ( Q_irand( 0, 1 ) )
7445 			{//dodge sabers twice as frequently as other projectiles
7446 				dodgeOnlySabers = qtrue;
7447 			}
7448 			else
7449 			{
7450 				return;
7451 			}
7452 		}
7453 	}
7454 
7455 	if ( self->client->NPC_class == CLASS_BOBAFETT )
7456 	{//Boba doesn't dodge quite as much
7457 		if ( Q_irand( 0, 2-g_spskill->integer) )
7458 		{//easier level guys do this less
7459 			return;
7460 		}
7461 	}
7462 
7463 	if ( self->client->NPC_class != CLASS_BOBAFETT
7464 		&& (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER)
7465 		&& (self->client->NPC_class != CLASS_ROCKETTROOPER||!self->NPC||self->NPC->rank<RANK_LT)//if a rockettrooper, but not an officer, do these normal checks
7466 		)
7467 	{
7468 		if ( g_debugMelee->integer
7469 			&& (ucmd->buttons & BUTTON_USE)
7470 			&& cg.renderingThirdPerson
7471 			&& G_OkayToLean( &self->client->ps, ucmd, qfalse )
7472 			&& (self->client->ps.forcePowersActive&(1<<FP_SPEED)) )
7473 		{
7474 		}
7475 		else
7476 		{
7477 			if ( self->client->ps.weapon != WP_SABER )
7478 			{
7479 				return;
7480 			}
7481 
7482 			if ( self->client->ps.saberInFlight )
7483 			{
7484 				return;
7485 			}
7486 
7487 			if ( self->s.number < MAX_CLIENTS )
7488 			{
7489 				if ( !self->client->ps.SaberLength() )
7490 				{//player doesn't auto-activate
7491 					return;
7492 				}
7493 
7494 				if ( !g_saberAutoBlocking->integer && self->client->ps.saberBlockingTime<level.time )
7495 				{
7496 					return;
7497 				}
7498 			}
7499 
7500 			if ( (self->client->ps.saber[0].saberFlags&SFL_NOT_ACTIVE_BLOCKING) )
7501 			{//can't actively block with this saber type
7502 				return;
7503 			}
7504 		}
7505 
7506 		if ( !self->s.number )
7507 		{//don't do this if already attacking!
7508 			if ( ucmd->buttons & BUTTON_ATTACK
7509 				|| PM_SaberInAttack( self->client->ps.saberMove )
7510 				|| PM_SaberInSpecialAttack( self->client->ps.torsoAnim )
7511 				|| PM_SaberInTransitionAny( self->client->ps.saberMove ))
7512 			{
7513 				return;
7514 			}
7515 		}
7516 
7517 		if ( self->client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_1 )
7518 		{//you have not the SKILLZ
7519 			return;
7520 		}
7521 
7522 		if ( self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] > level.time )
7523 		{//can't block while already blocking
7524 			return;
7525 		}
7526 
7527 		if ( self->client->ps.forcePowersActive&(1<<FP_LIGHTNING) )
7528 		{//can't block while zapping
7529 			return;
7530 		}
7531 
7532 		if ( self->client->ps.forcePowersActive&(1<<FP_DRAIN) )
7533 		{//can't block while draining
7534 			return;
7535 		}
7536 
7537 		if ( self->client->ps.forcePowersActive&(1<<FP_PUSH) )
7538 		{//can't block while shoving
7539 			return;
7540 		}
7541 
7542 		if ( self->client->ps.forcePowersActive&(1<<FP_GRIP) )
7543 		{//can't block while gripping (FIXME: or should it break the grip?  Pain should break the grip, I think...)
7544 			return;
7545 		}
7546 	}
7547 
7548 	fwdangles[1] = self->client->ps.viewangles[1];
7549 	AngleVectors( fwdangles, forward, NULL, NULL );
7550 
7551 	for ( i = 0 ; i < 3 ; i++ )
7552 	{
7553 		mins[i] = self->currentOrigin[i] - radius;
7554 		maxs[i] = self->currentOrigin[i] + radius;
7555 	}
7556 
7557 	numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
7558 
7559 	closestDist = radius;
7560 
7561 	for ( e = 0 ; e < numListedEntities ; e++ )
7562 	{
7563 		ent = entityList[ e ];
7564 
7565 		if (ent == self)
7566 			continue;
7567 		if (ent->owner == self)
7568 			continue;
7569 		if ( !(ent->inuse) )
7570 			continue;
7571 		if ( dodgeOnlySabers )
7572 		{//only care about thrown sabers
7573 			if ( ent->client
7574 				|| ent->s.weapon != WP_SABER
7575 				|| !ent->classname
7576 				|| !ent->classname[0]
7577 				|| Q_stricmp( "lightsaber", ent->classname ) )
7578 			{//not a lightsaber, ignore it
7579 				continue;
7580 			}
7581 		}
7582 		if ( ent->s.eType != ET_MISSILE && !(ent->s.eFlags&EF_MISSILE_STICK) )
7583 		{//not a normal projectile
7584 			if ( ent->client || ent->s.weapon != WP_SABER )
7585 			{//FIXME: wake up bad guys?
7586 				continue;
7587 			}
7588 			if ( ent->s.eFlags & EF_NODRAW )
7589 			{
7590 				continue;
7591 			}
7592 			if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
7593 			{//not a lightsaber
7594 				//FIXME: what about general objects that are small in size- like rocks, etc...
7595 				continue;
7596 			}
7597 			//a lightsaber.. make sure it's on and inFlight
7598 			if ( !ent->owner || !ent->owner->client )
7599 			{
7600 				continue;
7601 			}
7602 			if ( !ent->owner->client->ps.saberInFlight )
7603 			{//not in flight
7604 				continue;
7605 			}
7606 			if ( ent->owner->client->ps.SaberLength() <= 0 )
7607 			{//not on
7608 				continue;
7609 			}
7610 			if ( ent->owner->health <= 0 && g_saberRealisticCombat->integer < 2 )
7611 			{//it's not doing damage, so ignore it
7612 				continue;
7613 			}
7614 		}
7615 		else
7616 		{
7617 			if ( ent->s.pos.trType == TR_STATIONARY && !self->s.number )
7618 			{//nothing you can do with a stationary missile if you're the player
7619 				continue;
7620 			}
7621 		}
7622 
7623 		float		dot1, dot2;
7624 		//see if they're in front of me
7625 		VectorSubtract( ent->currentOrigin, self->currentOrigin, dir );
7626 		dist = VectorNormalize( dir );
7627 		//FIXME: handle detpacks, proximity mines and tripmines
7628 		if ( ent->s.weapon == WP_THERMAL )
7629 		{//thermal detonator!
7630 			if ( self->NPC && dist < ent->splashRadius )
7631 			{
7632 				if ( dist < ent->splashRadius &&
7633 					ent->nextthink < level.time + 600 &&
7634 					ent->count &&
7635 					self->client->ps.groundEntityNum != ENTITYNUM_NONE &&
7636 						(ent->s.pos.trType == TR_STATIONARY||
7637 						ent->s.pos.trType == TR_INTERPOLATE||
7638 						(dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE||
7639 						!WP_ForcePowerUsable( self, FP_PUSH, 0 )) )
7640 				{//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!
7641 					//FIXME: sometimes this might make me just jump into it...?
7642 					self->client->ps.forceJumpCharge = 480;
7643 				}
7644 				else if ( self->client->NPC_class != CLASS_BOBAFETT
7645 					&& (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER)
7646 					&& self->client->NPC_class != CLASS_ROCKETTROOPER )
7647 				{//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
7648 					if ( !ent->owner || !OnSameTeam( self, ent->owner ) )
7649 					{
7650 						ForceThrow( self, qfalse );
7651 					}
7652 				}
7653 			}
7654 			continue;
7655 		}
7656 		else if ( ent->splashDamage && ent->splashRadius )
7657 		{//exploding missile
7658 			//FIXME: handle tripmines and detpacks somehow...
7659 			//			maybe do a force-gesture that makes them explode?
7660 			//			But what if we're within it's splashradius?
7661 			if ( !self->s.number )
7662 			{//players don't auto-handle these at all
7663 				continue;
7664 			}
7665 			else
7666 			{
7667 				if ( self->client->NPC_class == CLASS_BOBAFETT
7668 					|| self->client->NPC_class == CLASS_ROCKETTROOPER )
7669 				{
7670 					/*
7671 					if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) )
7672 					{//sorry, you're scrooged here
7673 						//FIXME: maybe jump or go up if on ground?
7674 						continue;
7675 					}
7676 					//else it's a rocket, try to evade it
7677 					*/
7678 					//HMM... let's see what happens if these guys try to avoid tripmines and detpacks, too...?
7679 				}
7680 				else
7681 				{//normal Jedi
7682 					if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK)
7683 						&& (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER) )
7684 					{//a placed explosive like a tripmine or detpack
7685 						if ( InFOV( ent->currentOrigin, self->client->renderInfo.eyePoint, self->client->ps.viewangles, 90, 90 ) )
7686 						{//in front of me
7687 							if ( G_ClearLOS( self, ent ) )
7688 							{//can see it
7689 								vec3_t throwDir;
7690 								//make the gesture
7691 								ForceThrow( self, qfalse );
7692 								//take it off the wall and toss it
7693 								ent->s.pos.trType = TR_GRAVITY;
7694 								ent->s.eType = ET_MISSILE;
7695 								ent->s.eFlags &= ~EF_MISSILE_STICK;
7696 								ent->s.eFlags |= EF_BOUNCE_HALF;
7697 								AngleVectors( ent->currentAngles, throwDir, NULL, NULL );
7698 								VectorMA( ent->currentOrigin, ent->maxs[0]+4, throwDir, ent->currentOrigin );
7699 								VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
7700 								VectorScale( throwDir, 300, ent->s.pos.trDelta );
7701 								ent->s.pos.trDelta[2] += 150;
7702 								VectorMA( ent->s.pos.trDelta, 800, dir, ent->s.pos.trDelta );
7703 								ent->s.pos.trTime = level.time;		// move a bit on the very first frame
7704 								VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
7705 								ent->owner = self;
7706 								// make it explode, but with less damage
7707 								ent->splashDamage /= 3;
7708 								ent->splashRadius /= 3;
7709 								ent->e_ThinkFunc = thinkF_WP_Explode;
7710 								ent->nextthink = level.time + Q_irand( 500, 3000 );
7711 							}
7712 						}
7713 					}
7714 					else if ( dist < ent->splashRadius
7715 						&& self->client->ps.groundEntityNum != ENTITYNUM_NONE
7716 						&& ( DotProduct( dir, forward ) < SABER_REFLECT_MISSILE_CONE
7717 							|| !WP_ForcePowerUsable( self, FP_PUSH, 0 ) ) )
7718 					{//NPCs try to evade it
7719 						self->client->ps.forceJumpCharge = 480;
7720 					}
7721 					else if ( (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER) )
7722 					{//else, try to force-throw it away
7723 						if ( !ent->owner || !OnSameTeam( self, ent->owner ) )
7724 						{
7725 							//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
7726 							ForceThrow( self, qfalse );
7727 						}
7728 					}
7729 					//otherwise, can't block it, so we're screwed
7730 					continue;
7731 				}
7732 			}
7733 		}
7734 
7735 		if ( ent->s.weapon != WP_SABER )
7736 		{//only block shots coming from behind
7737 			if ( (dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE )
7738 				continue;
7739 		}
7740 		else if ( !self->s.number )
7741 		{//player never auto-blocks thrown sabers
7742 			continue;
7743 		}//NPCs always try to block sabers coming from behind!
7744 
7745 		//see if they're heading towards me
7746 		VectorCopy( ent->s.pos.trDelta, missile_dir );
7747 		VectorNormalize( missile_dir );
7748 		if ( (dot2 = DotProduct( dir, missile_dir )) > 0 )
7749 			continue;
7750 
7751 		//FIXME: must have a clear trace to me, too...
7752 		if ( dist < closestDist )
7753 		{
7754 			VectorCopy( self->currentOrigin, traceTo );
7755 			traceTo[2] = self->absmax[2] - 4;
7756 			gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, traceTo, ent->s.number, ent->clipmask, (EG2_Collision)0, 0 );
7757 			if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) )
7758 			{//okay, try one more check
7759 				VectorNormalize2( ent->s.pos.trDelta, entDir );
7760 				VectorMA( ent->currentOrigin, radius, entDir, traceTo );
7761 				gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, traceTo, ent->s.number, ent->clipmask, (EG2_Collision)0, 0 );
7762 				if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) )
7763 				{//can't hit me, ignore it
7764 					continue;
7765 				}
7766 			}
7767 			if ( self->s.number != 0 )
7768 			{//An NPC
7769 				if ( self->NPC && !self->enemy && ent->owner )
7770 				{
7771 					if ( ent->owner->health >= 0 && (!ent->owner->client || ent->owner->client->playerTeam != self->client->playerTeam) )
7772 					{
7773 						G_SetEnemy( self, ent->owner );
7774 					}
7775 				}
7776 			}
7777 			//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?
7778 			closestDist = dist;
7779 			incoming = ent;
7780 		}
7781 	}
7782 
7783 	if ( incoming )
7784 	{
7785 		if ( self->NPC && !G_ControlledByPlayer( self ) )
7786 		{
7787 			if ( Jedi_WaitingAmbush( self ) )
7788 			{
7789 				Jedi_Ambush( self );
7790 			}
7791 			if ( ( self->client->NPC_class == CLASS_BOBAFETT || self->client->NPC_class == CLASS_ROCKETTROOPER )
7792 				&& self->client->moveType == MT_FLYSWIM
7793 				&& incoming->methodOfDeath != MOD_ROCKET_ALT )
7794 			{//a hovering Boba Fett, not a tracking rocket
7795 				if ( !Q_irand( 0, 1 ) )
7796 				{//strafe
7797 					self->NPC->standTime = 0;
7798 					self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + Q_irand( 1000, 2000 );
7799 				}
7800 				if ( !Q_irand( 0, 1 ) )
7801 				{//go up/down
7802 					TIMER_Set( self, "heightChange", Q_irand( 1000, 3000 ) );
7803 					self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + Q_irand( 1000, 2000 );
7804 				}
7805 			}
7806 			else if ( self->client->NPC_class != CLASS_ROCKETTROOPER
7807 				&& Jedi_SaberBlockGo( self, &self->NPC->last_ucmd, NULL, NULL, incoming ) != EVASION_NONE )
7808 			{//make sure to turn on your saber if it's not on
7809 				if ( self->client->NPC_class != CLASS_BOBAFETT
7810 					&& (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER) )
7811 				{
7812 					self->client->ps.SaberActivate();
7813 				}
7814 			}
7815 		}
7816 		else//player
7817 		{
7818 			if ( !(ucmd->buttons & BUTTON_USE) )//self->s.weapon == WP_SABER && self->client->ps.SaberActive() )
7819 			{
7820 				WP_SaberBlockNonRandom( self, incoming->currentOrigin, qtrue );
7821 			}
7822 			else
7823 			{
7824 				vec3_t diff, start, end;
7825 				float dist;
7826 				VectorSubtract( incoming->currentOrigin, self->currentOrigin, diff );
7827 				dist = VectorLength( diff );
7828 				VectorNormalize2( incoming->s.pos.trDelta, entDir );
7829 				VectorMA( incoming->currentOrigin, dist, entDir, start );
7830 				VectorCopy( self->currentOrigin, end );
7831 				end[2] += self->maxs[2]*0.75f;
7832 				gi.trace( &trace, start, incoming->mins, incoming->maxs, end, incoming->s.number, MASK_SHOT, G2_COLLIDE, 10 );
7833 
7834 				Jedi_DodgeEvasion( self, incoming->owner, &trace, HL_NONE );
7835 			}
7836 			if ( incoming->owner && incoming->owner->client && (!self->enemy || self->enemy->s.weapon != WP_SABER) )//keep enemy jedi over shooters
7837 			{
7838 				self->enemy = incoming->owner;
7839 				NPC_SetLookTarget( self, incoming->owner->s.number, level.time+1000 );
7840 			}
7841 		}
7842 	}
7843 }
7844 
7845 
7846 //GENERAL SABER============================================================================
7847 //GENERAL SABER============================================================================
7848 //GENERAL SABER============================================================================
7849 //GENERAL SABER============================================================================
7850 //GENERAL SABER============================================================================
7851 
WP_SetSaberMove(gentity_t * self,short blocked)7852 void WP_SetSaberMove(gentity_t *self, short blocked)
7853 {
7854 	self->client->ps.saberBlocked = blocked;
7855 }
7856 
7857 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)7858 void WP_SaberUpdate( gentity_t *self, usercmd_t *ucmd )
7859 {
7860 	//float	swap;
7861 	float	minsize = 16;
7862 
7863 	if(0)//	if ( self->s.number != 0 )
7864 	{//for now only the player can do this		// not anymore
7865 		return;
7866 	}
7867 
7868 	if ( !self->client )
7869 	{
7870 		return;
7871 	}
7872 
7873 	if ( self->client->ps.saberEntityNum < 0 || self->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
7874 	{//never got one
7875 		return;
7876 	}
7877 
7878 	// Check if we are throwing it, launch it if needed, update position if needed.
7879 	WP_SaberThrow(self, ucmd);
7880 
7881 
7882 	//vec3_t saberloc;
7883 	//vec3_t sabermins={-8,-8,-8}, sabermaxs={8,8,8};
7884 
7885 	gentity_t *saberent;
7886 
7887 	if (self->client->ps.saberEntityNum <= 0)
7888 	{//WTF?!!  We lost it?
7889 		return;
7890 	}
7891 
7892 	saberent = &g_entities[self->client->ps.saberEntityNum];
7893 
7894 	//FIXME: Based on difficulty level/jedi saber combat skill, make this bounding box fatter/smaller
7895 	if ( self->client->ps.saberBlocked != BLOCKED_NONE )
7896 	{//we're blocking, increase min size
7897 		minsize = 32;
7898 	}
7899 
7900 	if ( G_InCinematicSaberAnim( self ) )
7901 	{//fake some blocking
7902 		self->client->ps.saberBlocking = BLK_TIGHT;
7903 		if ( self->client->ps.saber[0].Active() )
7904 		{
7905 			self->client->ps.saber[0].ActivateTrail( 150 );
7906 		}
7907 		if ( self->client->ps.saber[1].Active() )
7908 		{
7909 			self->client->ps.saber[1].ActivateTrail( 150 );
7910 		}
7911 	}
7912 
7913 	//is our saber in flight?
7914 	if ( !self->client->ps.saberInFlight )
7915 	{	// It isn't, which means we can update its position as we will.
7916 		qboolean alwaysBlock[MAX_SABERS][MAX_BLADES];
7917 		qboolean forceBlock = qfalse;
7918 		qboolean noBlocking = qfalse;
7919 
7920 		//clear out last frame's numbers
7921 		VectorClear(saberent->mins);
7922 		VectorClear(saberent->maxs);
7923 
7924 		Vehicle_t *pVeh = G_IsRidingVehicle( self );
7925 		if ( !self->client->ps.SaberActive()
7926 			|| !self->client->ps.saberBlocking
7927 			|| PM_InKnockDown( &self->client->ps )
7928 			|| PM_SuperBreakLoseAnim( self->client->ps.torsoAnim )
7929 			|| (pVeh && pVeh->m_pVehicleInfo && pVeh->m_pVehicleInfo->type != VH_ANIMAL && pVeh->m_pVehicleInfo->type != VH_FLIER) )//riding a vehicle that you cannot block shots on
7930 		{//can't block if saber isn't on
7931 			int i, j;
7932 			for ( i = 0; i < MAX_SABERS; i++ )
7933 			{
7934 				//initialize to not blocking
7935 				for ( j = 0; j < MAX_BLADES; j++ )
7936 				{
7937 					alwaysBlock[i][j] = qfalse;
7938 				}
7939 				if ( i > 0 && !self->client->ps.dualSabers )
7940 				{//not using a second saber, leave it not blocking
7941 				}
7942 				else
7943 				{
7944 					if ( (self->client->ps.saber[i].saberFlags2&SFL2_ALWAYS_BLOCK) )
7945 					{
7946 						for ( j = 0; j < self->client->ps.saber[i].numBlades; j++ )
7947 						{
7948 							alwaysBlock[i][j] = qtrue;
7949 							forceBlock = qtrue;
7950 						}
7951 					}
7952 					if ( self->client->ps.saber[i].bladeStyle2Start > 0 )
7953 					{
7954 						for ( j = self->client->ps.saber[i].bladeStyle2Start; j < self->client->ps.saber[i].numBlades; j++ )
7955 						{
7956 							if ( (self->client->ps.saber[i].saberFlags2&SFL2_ALWAYS_BLOCK2) )
7957 							{
7958 								alwaysBlock[i][j] = qtrue;
7959 								forceBlock = qtrue;
7960 							}
7961 							else
7962 							{
7963 								alwaysBlock[i][j] = qfalse;
7964 							}
7965 						}
7966 					}
7967 				}
7968 			}
7969 			if ( !forceBlock )
7970 			{
7971 				noBlocking = qtrue;
7972 			}
7973 			else if ( !self->client->ps.saberBlocking )
7974 			{//turn blocking on!
7975 				self->client->ps.saberBlocking = BLK_TIGHT;
7976 			}
7977 		}
7978 		if ( noBlocking )
7979 		{
7980 			//VectorClear(saberent->mins);
7981 			//VectorClear(saberent->maxs);
7982 			G_SetOrigin(saberent, self->currentOrigin);
7983 		}
7984 		else if ( self->client->ps.saberBlocking == BLK_TIGHT
7985 			|| self->client->ps.saberBlocking == BLK_WIDE )
7986 		{//FIXME: keep bbox in front of player, even when wide?
7987 			vec3_t	saberOrg;
7988 			if ( !forceBlock
7989 				&& ( (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)) )
7990 				&& self->client->ps.weaponTime <= 0
7991 				&& !G_InCinematicSaberAnim( self ) )
7992 			{//full-size blocking for non-attacking player with g_saberAutoBlocking on
7993 				vec3_t saberang={0,0,0}, fwd, sabermins={-8,-8,-8}, sabermaxs={8,8,8};
7994 
7995 				saberang[YAW] = self->client->ps.viewangles[YAW];
7996 				AngleVectors( saberang, fwd, NULL, NULL );
7997 
7998 				VectorMA( self->currentOrigin, 12, fwd, saberOrg );
7999 
8000 				VectorAdd( self->mins, sabermins, saberent->mins );
8001 				VectorAdd( self->maxs, sabermaxs, saberent->maxs );
8002 
8003 				saberent->contents = CONTENTS_LIGHTSABER;
8004 
8005 				G_SetOrigin( saberent, saberOrg );
8006 			}
8007 			else
8008 			{
8009 				vec3_t	saberBase, saberTip;
8010 				int numSabers = 1;
8011 				if ( self->client->ps.dualSabers )
8012 				{
8013 					numSabers = 2;
8014 				}
8015 				for ( int saberNum = 0; saberNum < numSabers; saberNum++ )
8016 				{
8017 					for ( int bladeNum = 0; bladeNum < self->client->ps.saber[saberNum].numBlades; bladeNum++ )
8018 					{
8019 						if ( self->client->ps.saber[saberNum].blade[bladeNum].length <= 0.0f )
8020 						{//don't include blades that are not on...
8021 							continue;
8022 						}
8023 						if ( forceBlock )
8024 						{//doing blade-specific bbox-sizing only, see if this blade should be counted
8025 							if ( !alwaysBlock[saberNum][bladeNum] )
8026 							{//this blade doesn't count right now
8027 								continue;
8028 							}
8029 						}
8030 						VectorCopy( self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, saberBase );
8031 						VectorMA( saberBase, self->client->ps.saber[saberNum].blade[bladeNum].length, self->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, saberTip );
8032 						VectorMA( saberBase, self->client->ps.saber[saberNum].blade[bladeNum].length*0.5, self->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, saberOrg );
8033 						for ( int i = 0; i < 3; i++ )
8034 						{
8035 							/*
8036 							if ( saberTip[i] > self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint[i] )
8037 							{
8038 								saberent->maxs[i] = saberTip[i] - saberOrg[i] + 8;//self->client->renderInfo.muzzlePoint[i];
8039 								saberent->mins[i] = self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint[i] - saberOrg[i] - 8;
8040 							}
8041 							else //if ( saberTip[i] < self->client->renderInfo.muzzlePoint[i] )
8042 							{
8043 								saberent->maxs[i] = self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint[i] - saberOrg[i] + 8;
8044 								saberent->mins[i] = saberTip[i] - saberOrg[i] - 8;//self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint[i];
8045 							}
8046 							*/
8047 							float newSizeTip = (saberTip[i] - saberOrg[i]);
8048 							newSizeTip += (newSizeTip>=0)?8:-8;
8049 							float newSizeBase = (saberBase[i] - saberOrg[i]);
8050 							newSizeBase += (newSizeBase>=0)?8:-8;
8051 							if ( newSizeTip > saberent->maxs[i] )
8052 							{
8053 								saberent->maxs[i] = newSizeTip;
8054 							}
8055 							if ( newSizeBase > saberent->maxs[i] )
8056 							{
8057 								saberent->maxs[i] = newSizeBase;
8058 							}
8059 							if ( newSizeTip < saberent->mins[i] )
8060 							{
8061 								saberent->mins[i] = newSizeTip;
8062 							}
8063 							if ( newSizeBase < saberent->mins[i] )
8064 							{
8065 								saberent->mins[i] = newSizeBase;
8066 							}
8067 						}
8068 					}
8069 				}
8070 				if ( !forceBlock )
8071 				{//not doing special "alwaysBlock" bbox
8072 					if ( self->client->ps.weaponTime > 0
8073 						|| self->s.number
8074 						|| g_saberAutoBlocking->integer
8075 						|| self->client->ps.saberBlockingTime > level.time )
8076 					{//if attacking or blocking (or an NPC), inflate to a minimum size
8077 						for ( int i = 0; i < 3; i++ )
8078 						{
8079 							if ( saberent->maxs[i] < minsize )
8080 							{
8081 								saberent->maxs[i] = minsize;
8082 							}
8083 							if ( saberent->mins[i] > -minsize )
8084 							{
8085 								saberent->mins[i] = -minsize;
8086 							}
8087 						}
8088 					}
8089 				}
8090 				saberent->contents = CONTENTS_LIGHTSABER;
8091 				G_SetOrigin( saberent, saberOrg );
8092 			}
8093 		}
8094 		/*
8095 		else if (self->client->ps.saberBlocking == BLK_WIDE)
8096 		{	// Assuming that we are not swinging, the saber's bounding box should be around the player.
8097 			vec3_t saberang={0,0,0}, fwd;
8098 
8099 			saberang[YAW] = self->client->ps.viewangles[YAW];
8100 			AngleVectors( saberang, fwd, NULL, NULL );
8101 
8102 			VectorMA(self->currentOrigin, 12, fwd, saberloc);
8103 
8104 			VectorAdd(self->mins, sabermins, saberent->mins);
8105 			VectorAdd(self->maxs, sabermaxs, saberent->maxs);
8106 
8107 			saberent->contents = CONTENTS_LIGHTSABER;
8108 
8109 			G_SetOrigin( saberent, saberloc);
8110 		}
8111 		else if (self->client->ps.saberBlocking == BLK_TIGHT)
8112 		{	// If the player is swinging, the bbox is around just the saber
8113 			VectorCopy(self->client->renderInfo.muzzlePoint, sabermins);
8114 			// Put the limits of the bbox around the saber size.
8115 			VectorMA(sabermins, self->client->ps.saberLength, self->client->renderInfo.muzzleDir, sabermaxs);
8116 
8117 			// Now make the mins into mins and the maxs into maxs
8118 			if (sabermins[0] > sabermaxs[0])
8119 			{
8120 				swap = sabermins[0];
8121 				sabermins[0] = sabermaxs[0];
8122 				sabermaxs[0] = swap;
8123 			}
8124 			if (sabermins[1] > sabermaxs[1])
8125 			{
8126 				swap = sabermins[1];
8127 				sabermins[1] = sabermaxs[1];
8128 				sabermaxs[1] = swap;
8129 			}
8130 			if (sabermins[2] > sabermaxs[2])
8131 			{
8132 				swap = sabermins[2];
8133 				sabermins[2] = sabermaxs[2];
8134 				sabermaxs[2] = swap;
8135 			}
8136 
8137 			// Now the loc is halfway between the (absolute) mins and maxs
8138 			VectorAdd(sabermins, sabermaxs, saberloc);
8139 			VectorScale(saberloc, 0.5, saberloc);
8140 
8141 			// Finally, turn the mins and maxs, which are absolute, into relative mins and maxs.
8142 			VectorSubtract(sabermins, saberloc, saberent->mins);
8143 			VectorSubtract(sabermaxs, saberloc, saberent->maxs);
8144 
8145 			saberent->contents = CONTENTS_LIGHTSABER;// | CONTENTS_SHOTCLIP;
8146 
8147 			G_SetOrigin( saberent, saberloc);
8148 		}
8149 		*/
8150 		else
8151 		{	// Otherwise there is no blocking possible.
8152 			//VectorClear(saberent->mins);
8153 			//VectorClear(saberent->maxs);
8154 			G_SetOrigin(saberent, self->currentOrigin);
8155 		}
8156 		saberent->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
8157 		gi.linkentity(saberent);
8158 	}
8159 	else
8160 	{
8161 		WP_SaberInFlightReflectCheck( self, ucmd );
8162 	}
8163 #ifndef FINAL_BUILD
8164 	if ( d_saberCombat->integer > 2 )
8165 	{
8166 		CG_CubeOutline( saberent->absmin, saberent->absmax, 50, WPDEBUG_SaberColor( self->client->ps.saber[0].blade[0].color ), 1 );
8167 	}
8168 #endif
8169 }
8170 
8171 #define	MAX_RADIUS_ENTS			256	//NOTE: This can cause entities to be lost
G_CheckEnemyPresence(gentity_t * ent,int dir,float radius,float tolerance)8172 qboolean G_CheckEnemyPresence( gentity_t *ent, int dir, float radius, float tolerance )
8173 {
8174 	gentity_t	*radiusEnts[ MAX_RADIUS_ENTS ];
8175 	vec3_t		mins, maxs;
8176 	int			numEnts;
8177 	vec3_t		checkDir, dir2checkEnt;
8178 	float		dist;
8179 	int			i;
8180 
8181 	switch( dir )
8182 	{
8183 	case DIR_RIGHT:
8184 		AngleVectors( ent->currentAngles, NULL, checkDir, NULL );
8185 		break;
8186 	case DIR_LEFT:
8187 		AngleVectors( ent->currentAngles, NULL, checkDir, NULL );
8188 		VectorScale( checkDir, -1, checkDir );
8189 		break;
8190 	case DIR_FRONT:
8191 		AngleVectors( ent->currentAngles, checkDir, NULL, NULL );
8192 		break;
8193 	case DIR_BACK:
8194 		AngleVectors( ent->currentAngles, checkDir, NULL, NULL );
8195 		VectorScale( checkDir, -1, checkDir );
8196 		break;
8197 	}
8198 	//Get all ents in range, see if they're living clients and enemies, then check dot to them...
8199 
8200 	//Setup the bbox to search in
8201 	for ( i = 0; i < 3; i++ )
8202 	{
8203 		mins[i] = ent->currentOrigin[i] - radius;
8204 		maxs[i] = ent->currentOrigin[i] + radius;
8205 	}
8206 
8207 	//Get a number of entities in a given space
8208 	numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, MAX_RADIUS_ENTS );
8209 
8210 	for ( i = 0; i < numEnts; i++ )
8211 	{
8212 		//Don't consider self
8213 		if ( radiusEnts[i] == ent )
8214 			continue;
8215 
8216 		//Must be valid
8217 		if ( G_ValidEnemy( ent, radiusEnts[i] ) == qfalse )
8218 			continue;
8219 
8220 		VectorSubtract( radiusEnts[i]->currentOrigin, ent->currentOrigin, dir2checkEnt );
8221 		dist = VectorNormalize( dir2checkEnt );
8222 		if ( dist <= radius
8223 			&& DotProduct( dir2checkEnt, checkDir ) >= tolerance )
8224 		{
8225 			//stop on the first one
8226 			return qtrue;
8227 		}
8228 	}
8229 
8230 	return qfalse;
8231 }
8232 //OTHER JEDI POWERS=========================================================================
8233 //OTHER JEDI POWERS=========================================================================
8234 //OTHER JEDI POWERS=========================================================================
8235 //OTHER JEDI POWERS=========================================================================
8236 //OTHER JEDI POWERS=========================================================================
8237 extern gentity_t *TossClientItems( gentity_t *self );
8238 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
WP_DropWeapon(gentity_t * dropper,vec3_t velocity)8239 void WP_DropWeapon( gentity_t *dropper, vec3_t velocity )
8240 {
8241 	if ( !dropper || !dropper->client )
8242 	{
8243 		return;
8244 	}
8245 	int	replaceWeap = WP_NONE;
8246 	int oldWeap = dropper->s.weapon;
8247 	gentity_t *weapon = TossClientItems( dropper );
8248 	if ( oldWeap == WP_THERMAL && dropper->NPC )
8249 	{//Hmm, maybe all NPCs should go into melee?  Not too many, though, or they mob you and look silly
8250 		replaceWeap = WP_MELEE;
8251 	}
8252 	if ( dropper->ghoul2.IsValid() )
8253 	{
8254 		if ( dropper->weaponModel[0] > 0 )
8255 		{//NOTE: guess you never drop the left-hand weapon, eh?
8256 			gi.G2API_RemoveGhoul2Model( dropper->ghoul2, dropper->weaponModel[0] );
8257 			dropper->weaponModel[0] = -1;
8258 		}
8259 	}
8260 	//FIXME: does this work on the player?
8261 	dropper->client->ps.stats[STAT_WEAPONS] |= ( 1 << replaceWeap );
8262 	if ( !dropper->s.number )
8263 	{
8264 		if ( oldWeap == WP_THERMAL )
8265 		{
8266 			dropper->client->ps.ammo[weaponData[oldWeap].ammoIndex] -= weaponData[oldWeap].energyPerShot;
8267 		}
8268 		else
8269 		{
8270 			dropper->client->ps.stats[STAT_WEAPONS] &= ~( 1 << oldWeap );
8271 		}
8272 		CG_ChangeWeapon( replaceWeap );
8273 	}
8274 	else
8275 	{
8276 		dropper->client->ps.stats[STAT_WEAPONS] &= ~( 1 << oldWeap );
8277 	}
8278 	ChangeWeapon( dropper, replaceWeap );
8279 	dropper->s.weapon = replaceWeap;
8280 	if ( dropper->NPC )
8281 	{
8282 		dropper->NPC->last_ucmd.weapon = replaceWeap;
8283 	}
8284 	if ( weapon != NULL && velocity && !VectorCompare( velocity, vec3_origin ) )
8285 	{//weapon should have a direction to it's throw
8286 		VectorScale( velocity, 3, weapon->s.pos.trDelta );//NOTE: Presumes it is moving already...?
8287 		if ( weapon->s.pos.trDelta[2] < 150 )
8288 		{//this is presuming you don't want them to drop the weapon down on you...
8289 			weapon->s.pos.trDelta[2] = 150;
8290 		}
8291 		//FIXME: gets stuck inside it's former owner...
8292 		weapon->forcePushTime = level.time + 600; // let the push effect last for 600 ms
8293 	}
8294 }
8295 
WP_KnockdownTurret(gentity_t * self,gentity_t * pas)8296 void WP_KnockdownTurret( gentity_t *self, gentity_t *pas )
8297 {
8298 	//knock it over
8299 	VectorCopy( pas->currentOrigin, pas->s.pos.trBase );
8300 	pas->s.pos.trType = TR_LINEAR_STOP;
8301 	pas->s.pos.trDuration = 250;
8302 	pas->s.pos.trTime = level.time;
8303 	pas->s.pos.trDelta[2] = ( 12.0f / ( pas->s.pos.trDuration * 0.001f ) );
8304 
8305 	VectorCopy( pas->currentAngles, pas->s.apos.trBase );
8306 	pas->s.apos.trType = TR_LINEAR_STOP;
8307 	pas->s.apos.trDuration = 250;
8308 	pas->s.apos.trTime = level.time;
8309 	//FIXME: pick pitch/roll that always tilts it directly away from pusher
8310 	pas->s.apos.trDelta[PITCH] = ( 100.0f / ( pas->s.apos.trDuration * 0.001f ) );
8311 
8312 	//kill it
8313 	pas->count = 0;
8314 	pas->nextthink = -1;
8315 	G_Sound( pas, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
8316 	//push effect?
8317 	pas->forcePushTime = level.time + 600; // let the push effect last for 600 ms
8318 }
8319 
WP_ForceThrowHazardTrooper(gentity_t * self,gentity_t * trooper,qboolean pull)8320 void WP_ForceThrowHazardTrooper( gentity_t *self, gentity_t *trooper, qboolean pull )
8321 {
8322 	if ( !self || !self->client )
8323 	{
8324 		return;
8325 	}
8326 	if ( !trooper || !trooper->client )
8327 	{
8328 		return;
8329 	}
8330 
8331 	//all levels: see effect on them, they notice us
8332 	trooper->forcePushTime = level.time + 600; // let the push effect last for 600 ms
8333 
8334 	if ( (pull&&self->client->ps.forcePowerLevel[FP_PULL]>FORCE_LEVEL_1)
8335 		|| (!pull&&self->client->ps.forcePowerLevel[FP_PUSH]>FORCE_LEVEL_1) )
8336 	{//level 2: they stop for a couple seconds and make a sound
8337 		trooper->painDebounceTime = level.time + Q_irand( 1500, 2500 );
8338 		G_AddVoiceEvent( trooper, Q_irand(EV_PUSHED1, EV_PUSHED3), Q_irand( 1000, 3000 ) );
8339 		GEntity_PainFunc( trooper, self, self, trooper->currentOrigin, 0, MOD_MELEE );
8340 
8341 		if ( (pull&&self->client->ps.forcePowerLevel[FP_PULL]>FORCE_LEVEL_2)
8342 			|| (!pull&&self->client->ps.forcePowerLevel[FP_PUSH]>FORCE_LEVEL_2) )
8343 		{//level 3: they actually play a pushed anim and stumble a bit
8344 			vec3_t hazAngles = {0,trooper->currentAngles[YAW],0};
8345 			int anim = -1;
8346 			if ( InFront( self->currentOrigin, trooper->currentOrigin, hazAngles ) )
8347 			{//I'm on front of him
8348 				if ( pull )
8349 				{
8350 					anim = BOTH_PAIN4;
8351 				}
8352 				else
8353 				{
8354 					anim = BOTH_PAIN1;
8355 				}
8356 			}
8357 			else
8358 			{//I'm behind him
8359 				if ( pull )
8360 				{
8361 					anim = BOTH_PAIN1;
8362 				}
8363 				else
8364 				{
8365 					anim = BOTH_PAIN4;
8366 				}
8367 			}
8368 			if ( anim != -1 )
8369 			{
8370 				if ( anim == BOTH_PAIN1 )
8371 				{//make them take a couple steps back
8372 					AngleVectors( hazAngles, trooper->client->ps.velocity, NULL, NULL );
8373 					VectorScale( trooper->client->ps.velocity, -40.0f, trooper->client->ps.velocity );
8374 					trooper->client->ps.pm_flags |= PMF_TIME_NOFRICTION;
8375 				}
8376 				else if ( anim == BOTH_PAIN4 )
8377 				{//make them stumble forward
8378 					AngleVectors( hazAngles, trooper->client->ps.velocity, NULL, NULL );
8379 					VectorScale( trooper->client->ps.velocity, 80.0f, trooper->client->ps.velocity );
8380 					trooper->client->ps.pm_flags |= PMF_TIME_NOFRICTION;
8381 				}
8382 				NPC_SetAnim( trooper, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
8383 				trooper->painDebounceTime += trooper->client->ps.torsoAnimTimer;
8384 				trooper->client->ps.pm_time = trooper->client->ps.torsoAnimTimer;
8385 			}
8386 		}
8387 		if ( trooper->NPC )
8388 		{
8389 			if ( trooper->NPC->shotTime < trooper->painDebounceTime )
8390 			{
8391 				trooper->NPC->shotTime = trooper->painDebounceTime;
8392 			}
8393 		}
8394 		trooper->client->ps.weaponTime = trooper->painDebounceTime-level.time;
8395 	}
8396 	else
8397 	{//level 1: no pain reaction, but they should still notice
8398 		if ( trooper->enemy == NULL//not mad at anyone
8399 			&& trooper->client->playerTeam != self->client->playerTeam//not on our team
8400 			&& !(trooper->svFlags&SVF_LOCKEDENEMY)//not locked on an enemy
8401 			&& !(trooper->svFlags&SVF_IGNORE_ENEMIES)//not ignoring enemie
8402 			&& !(self->flags&FL_NOTARGET) )//I'm not in notarget
8403 		{//not already mad at them and can get mad at them, do so
8404 			G_SetEnemy( trooper, self );
8405 		}
8406 	}
8407 }
8408 
WP_ResistForcePush(gentity_t * self,gentity_t * pusher,qboolean noPenalty)8409 void WP_ResistForcePush( gentity_t *self, gentity_t *pusher, qboolean noPenalty )
8410 {
8411 	int parts;
8412 	qboolean runningResist = qfalse;
8413 
8414 	if ( !self || self->health <= 0 || !self->client || !pusher || !pusher->client )
8415 	{
8416 		return;
8417 	}
8418 
8419 	//NOTE: don't interrupt big anims with this!
8420 	if ( !PM_SaberCanInterruptMove( self->client->ps.saberMove, self->client->ps.torsoAnim ) )
8421 	{//can't interrupt my current torso anim/sabermove with this, so ignore it entirely!
8422 		return;
8423 	}
8424 
8425 	if ( (!self->s.number
8426 			||( self->NPC && (self->NPC->aiFlags&NPCAI_BOSS_CHARACTER) )
8427 			||( self->client && self->client->NPC_class == CLASS_SHADOWTROOPER )
8428 			/*
8429 			|| self->client->NPC_class == CLASS_DESANN
8430 			|| !Q_stricmp("Yoda",self->NPC_type)
8431 			|| self->client->NPC_class == CLASS_LUKE*/
8432 		  )
8433 		&& (VectorLengthSquared( self->client->ps.velocity ) > 10000 || self->client->ps.forcePowerLevel[FP_PUSH] >= FORCE_LEVEL_3 || self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 ) )
8434 	{
8435 		runningResist = qtrue;
8436 	}
8437 
8438 	if ( !runningResist
8439 		&& self->client->ps.groundEntityNum != ENTITYNUM_NONE
8440 		&& !PM_SpinningSaberAnim( self->client->ps.legsAnim )
8441 		&& !PM_FlippingAnim( self->client->ps.legsAnim )
8442 		&& !PM_RollingAnim( self->client->ps.legsAnim )
8443 		&& !PM_InKnockDown( &self->client->ps )
8444 		&& !PM_CrouchAnim( self->client->ps.legsAnim ))
8445 	{//if on a surface and not in a spin or flip, play full body resist
8446 		parts = SETANIM_BOTH;
8447 	}
8448 	else
8449 	{//play resist just in torso
8450 		parts = SETANIM_TORSO;
8451 	}
8452 	NPC_SetAnim( self, parts, BOTH_RESISTPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
8453 
8454 	if ( !noPenalty )
8455 	{
8456 		if ( !runningResist )
8457 		{
8458 			VectorClear( self->client->ps.velocity );
8459 			//still stop them from attacking or moving for a bit, though
8460 			//FIXME: maybe push just a little (like, slide)?
8461 			self->client->ps.weaponTime = 1000;
8462 			if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
8463 			{
8464 				self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
8465 			}
8466 			self->client->ps.pm_time = self->client->ps.weaponTime;
8467 			self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
8468 			//play the full body push effect on me
8469 			self->forcePushTime = level.time + 600; // let the push effect last for 600 ms
8470 		}
8471 		else
8472 		{
8473 			self->client->ps.weaponTime = 600;
8474 			if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
8475 			{
8476 				self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
8477 			}
8478 		}
8479 	}
8480 	//play my force push effect on my hand
8481 	//self->client->ps.powerups[PW_FORCE_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500;
8482 
8483 	//reset to 0 in case it's still > 0 from a previous push
8484 	//self->client->pushEffectFadeTime = 0;
8485 	if ( !pusher //???
8486 		|| pusher == self->enemy//my enemy tried to push me
8487 		|| (pusher->client && pusher->client->playerTeam != self->client->playerTeam) )//someone not on my team tried to push me
8488 	{
8489 		Jedi_PlayBlockedPushSound( self );
8490 	}
8491 }
8492 
8493 extern qboolean Boba_StopKnockdown( gentity_t *self, gentity_t *pusher, const vec3_t pushDir, qboolean forceKnockdown );
8494 extern qboolean Jedi_StopKnockdown( gentity_t *self, gentity_t *pusher, const vec3_t pushDir );
WP_ForceKnockdown(gentity_t * self,gentity_t * pusher,qboolean pull,qboolean strongKnockdown,qboolean breakSaberLock)8495 void WP_ForceKnockdown( gentity_t *self, gentity_t *pusher, qboolean pull, qboolean strongKnockdown, qboolean breakSaberLock )
8496 {
8497 	if ( !self || !self->client || !pusher || !pusher->client )
8498 	{
8499 		return;
8500 	}
8501 
8502 	if ( self->client->NPC_class == CLASS_ROCKETTROOPER )
8503 	{
8504 		return;
8505 	}
8506 	else if ( PM_LockedAnim( self->client->ps.legsAnim ) )
8507 	{//stuck doing something else
8508 		return;
8509 	}
8510 	else if ( Rosh_BeingHealed( self ) )
8511 	{
8512 		return;
8513 	}
8514 
8515 	//break out of a saberLock?
8516 	if ( self->client->ps.saberLockTime > level.time )
8517 	{
8518 		if ( breakSaberLock
8519 			|| (pusher && self->client->ps.saberLockEnemy == pusher->s.number) )
8520 		{
8521 			self->client->ps.saberLockTime = 0;
8522 			self->client->ps.saberLockEnemy = ENTITYNUM_NONE;
8523 		}
8524 		else
8525 		{
8526 			return;
8527 		}
8528 	}
8529 
8530 	if ( self->health > 0 )
8531 	{
8532 		if ( !self->s.number )
8533 		{
8534 			NPC_SetPainEvent( self );
8535 		}
8536 		else
8537 		{
8538 			GEntity_PainFunc( self, pusher, pusher, self->currentOrigin, 0, MOD_MELEE );
8539 		}
8540 
8541 		vec3_t	pushDir;
8542 		if ( pull )
8543 		{
8544 			VectorSubtract( pusher->currentOrigin, self->currentOrigin, pushDir );
8545 		}
8546 		else
8547 		{
8548 			VectorSubtract( self->currentOrigin, pusher->currentOrigin, pushDir );
8549 		}
8550 
8551 		//FIXME: sometimes do this for some NPC force-users, too!
8552 		if ( Boba_StopKnockdown( self, pusher, pushDir, qtrue ) )
8553 		{//He can backflip instead of be knocked down
8554 			return;
8555 		}
8556 		else if ( Jedi_StopKnockdown( self, pusher, pushDir ) )
8557 		{//They can backflip instead of be knocked down
8558 			return;
8559 		}
8560 
8561 		G_CheckLedgeDive( self, 72, pushDir, qfalse, qfalse );
8562 
8563 		if ( !PM_SpinningSaberAnim( self->client->ps.legsAnim )
8564 			&& !PM_FlippingAnim( self->client->ps.legsAnim )
8565 			&& !PM_RollingAnim( self->client->ps.legsAnim )
8566 			&& !PM_InKnockDown( &self->client->ps ) )
8567 		{
8568 			int knockAnim = BOTH_KNOCKDOWN1;//default knockdown
8569 			if ( pusher->client->NPC_class == CLASS_DESANN && self->client->NPC_class != CLASS_LUKE )
8570 			{//desann always knocks down, unless you're Luke
8571 				strongKnockdown = qtrue;
8572 			}
8573 			if ( !self->s.number
8574 				&& !strongKnockdown
8575 				&& ( (!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)) )	)
8576 			{//player only knocked down if pushed *hard*
8577 				if ( self->s.weapon == WP_SABER )
8578 				{//temp HACK: these are the only 2 pain anims that look good when holding a saber
8579 					knockAnim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 );
8580 				}
8581 				else
8582 				{
8583 					knockAnim = PM_PickAnim( self, BOTH_PAIN1, BOTH_PAIN18 );
8584 				}
8585 			}
8586 			else if ( PM_CrouchAnim( self->client->ps.legsAnim ) )
8587 			{//crouched knockdown
8588 				knockAnim = BOTH_KNOCKDOWN4;
8589 			}
8590 			else
8591 			{//plain old knockdown
8592 				vec3_t pLFwd, pLAngles = {0,self->client->ps.viewangles[YAW],0};
8593 				vec3_t sFwd, sAngles = {0,pusher->client->ps.viewangles[YAW],0};
8594 				AngleVectors( pLAngles, pLFwd, NULL, NULL );
8595 				AngleVectors( sAngles, sFwd, NULL, NULL );
8596 				if ( DotProduct( sFwd, pLFwd ) > 0.2f )
8597 				{//pushing him from behind
8598 					//FIXME: check to see if we're aiming below or above the waist?
8599 					if ( pull )
8600 					{
8601 						knockAnim = BOTH_KNOCKDOWN1;
8602 					}
8603 					else
8604 					{
8605 						knockAnim = BOTH_KNOCKDOWN3;
8606 					}
8607 				}
8608 				else
8609 				{//pushing him from front
8610 					if ( pull )
8611 					{
8612 						knockAnim = BOTH_KNOCKDOWN3;
8613 					}
8614 					else
8615 					{
8616 						knockAnim = BOTH_KNOCKDOWN1;
8617 					}
8618 				}
8619 			}
8620 			if ( knockAnim == BOTH_KNOCKDOWN1 && strongKnockdown )
8621 			{//push *hard*
8622 				knockAnim = BOTH_KNOCKDOWN2;
8623 			}
8624 			NPC_SetAnim( self, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
8625 			if ( self->s.number >= MAX_CLIENTS )
8626 			{//randomize getup times - but not for boba
8627 				int addTime;
8628 				if ( self->client->NPC_class == CLASS_BOBAFETT )
8629 				{
8630 					addTime = Q_irand( -500, 0 );
8631 				}
8632 				else
8633 				{
8634 					addTime = Q_irand( -300, 300 );
8635 				}
8636 				self->client->ps.legsAnimTimer += addTime;
8637 				self->client->ps.torsoAnimTimer += addTime;
8638 			}
8639 			else
8640 			{//player holds extra long so you have more time to decide to do the quick getup
8641 				if ( PM_KnockDownAnim( self->client->ps.legsAnim ) )
8642 				{
8643 					self->client->ps.legsAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME;
8644 					self->client->ps.torsoAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME;
8645 				}
8646 			}
8647 			//
8648 			if ( pusher->NPC && pusher->enemy == self )
8649 			{//pushed pushed down his enemy
8650 				G_AddVoiceEvent( pusher, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 3000 );
8651 				pusher->NPC->blockedSpeechDebounceTime = level.time + 3000;
8652 			}
8653 		}
8654 	}
8655 	self->forcePushTime = level.time + 600; // let the push effect last for 600 ms
8656 }
8657 
WP_ForceThrowable(gentity_t * ent,gentity_t * forwardEnt,gentity_t * self,qboolean pull,float cone,float radius,vec3_t forward)8658 qboolean WP_ForceThrowable( gentity_t *ent, gentity_t *forwardEnt, gentity_t *self, qboolean pull, float cone, float radius, vec3_t forward )
8659 {
8660 	if (ent == self)
8661 		return qfalse;
8662 	if ( ent->owner == self && ent->s.weapon != WP_THERMAL )//can push your own thermals
8663 		return qfalse;
8664 	if ( !(ent->inuse) )
8665 		return qfalse;
8666 	if ( ent->NPC && ent->NPC->scriptFlags & SCF_NO_FORCE )
8667 	{
8668 		if ( ent->s.weapon == WP_SABER )
8669 		{//Hmm, should jedi do the resist behavior?  If this is on, perhaps it's because of a cinematic?
8670 			WP_ResistForcePush( ent, self, qtrue );
8671 		}
8672 		return qfalse;
8673 	}
8674 	if ( (ent->flags&FL_FORCE_PULLABLE_ONLY) && !pull )
8675 	{//simple HACK: cannot force-push ammo rack items (because they may start in solid)
8676 		return qfalse;
8677 	}
8678 	//FIXME: don't push it if I already pushed it a little while ago
8679 	if ( ent->s.eType != ET_MISSILE )
8680 	{
8681 		if ( ent->client )
8682 		{
8683 			if ( ent->client->ps.pullAttackTime > level.time )
8684 			{
8685 				return qfalse;
8686 			}
8687 		}
8688 		if ( cone >= 1.0f )
8689 		{//must be pointing right at them
8690 			if ( ent != forwardEnt )
8691 			{//must be the person I'm looking right at
8692 				if ( ent->client && !pull
8693 					&& ent->client->ps.forceGripEntityNum == self->s.number
8694 					&& (self->s.eFlags&EF_FORCE_GRIPPED) )
8695 				{//this is the guy that's force-gripping me, use a wider cone regardless of force power level
8696 				}
8697 				else
8698 				{
8699 					if ( ent->client && !pull
8700 						&& ent->client->ps.forceDrainEntityNum == self->s.number
8701 						&& (self->s.eFlags&EF_FORCE_DRAINED) )
8702 					{//this is the guy that's force-draining me, use a wider cone regardless of force power level
8703 					}
8704 					else
8705 					{
8706 						return qfalse;
8707 					}
8708 				}
8709 			}
8710 		}
8711 		if ( ent->s.eType != ET_ITEM && ent->e_ThinkFunc != thinkF_G_RunObject )//|| !(ent->flags&FL_DROPPED_ITEM) )//was only dropped items
8712 		{
8713 			//FIXME: need pushable objects
8714 			if ( ent->s.eFlags & EF_NODRAW )
8715 			{
8716 				return qfalse;
8717 			}
8718 			if ( !ent->client )
8719 			{
8720 				if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
8721 				{//not a lightsaber
8722 					if ( !(ent->svFlags&SVF_GLASS_BRUSH) )
8723 					{//and not glass
8724 						if ( Q_stricmp( "func_door", ent->classname ) != 0 || !(ent->spawnflags & 2/*MOVER_FORCE_ACTIVATE*/) )
8725 						{//not a force-usable door
8726 							if ( Q_stricmp( "func_static", ent->classname ) != 0 || (!(ent->spawnflags&1/*F_PUSH*/)&&!(ent->spawnflags&2/*F_PULL*/)) || (ent->spawnflags&32/*SOLITARY*/) )
8727 							{//not a force-usable func_static or, it is one, but it's solitary, so you only press it when looking right at it
8728 								if ( Q_stricmp( "limb", ent->classname ) )
8729 								{//not a limb
8730 									if ( ent->s.weapon == WP_TURRET && !Q_stricmp( "PAS", ent->classname ) && ent->s.apos.trType == TR_STATIONARY )
8731 									{//can knock over placed turrets
8732 										if ( !self->s.number || self->enemy != ent )
8733 										{//only NPCs who are actively mad at this turret can push it over
8734 											return qfalse;
8735 										}
8736 									}
8737 									else
8738 									{
8739 										return qfalse;
8740 									}
8741 								}
8742 							}
8743 						}
8744 						else if ( ent->moverState != MOVER_POS1 && ent->moverState != MOVER_POS2 )
8745 						{//not at rest
8746 							return qfalse;
8747 						}
8748 					}
8749 				}
8750 				//return qfalse;
8751 			}
8752 			else if ( ent->client->NPC_class == CLASS_MARK1 )
8753 			{//can't push Mark1 unless push 3
8754 				if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 )
8755 				{
8756 					return qfalse;
8757 				}
8758 			}
8759 			else if ( ent->client->NPC_class == CLASS_GALAKMECH
8760 				|| ent->client->NPC_class == CLASS_ATST
8761 				|| ent->client->NPC_class == CLASS_RANCOR
8762 				|| ent->client->NPC_class == CLASS_WAMPA
8763 				|| ent->client->NPC_class == CLASS_SAND_CREATURE )
8764 			{//can't push ATST or Galak or Rancor or Wampa
8765 				return qfalse;
8766 			}
8767 			else if ( ent->s.weapon == WP_EMPLACED_GUN )
8768 			{//FIXME: maybe can pull them out?
8769 				return qfalse;
8770 			}
8771 			else if ( ent->client->playerTeam == self->client->playerTeam && self->enemy && self->enemy != ent )
8772 			{//can't accidently push a teammate while in combat
8773 				return qfalse;
8774 			}
8775 			else if ( G_IsRidingVehicle( ent )
8776 				&& (ent->s.eFlags&EF_NODRAW) )
8777 			{//can't push/pull anyone riding *inside* vehicle
8778 				return qfalse;
8779 			}
8780 		}
8781 		else if ( ent->s.eType == ET_ITEM )
8782 		{
8783 			if ( (ent->flags&FL_NO_KNOCKBACK) )
8784 			{
8785 				return qfalse;
8786 			}
8787 			if ( ent->item
8788 				&& ent->item->giType == IT_HOLDABLE
8789 				&& ent->item->giTag == INV_SECURITY_KEY )
8790 				//&& (ent->flags&FL_DROPPED_ITEM) ???
8791 			{//dropped security keys can't be pushed?  But placed ones can...?  does this make any sense?
8792 				if ( !pull || self->s.number )
8793 				{//can't push, NPC's can't do anything to it
8794 					return qfalse;
8795 				}
8796 				else
8797 				{
8798 					if ( g_crosshairEntNum != ent->s.number )
8799 					{//player can pull it if looking *right* at it
8800 						if ( cone >= 1.0f )
8801 						{//we did a forwardEnt trace
8802 							if ( forwardEnt != ent )
8803 							{//must be pointing right at them
8804 								return qfalse;
8805 							}
8806 						}
8807 						else if ( forward )
8808 						{//do a forwardEnt trace
8809 							trace_t tr;
8810 							vec3_t end;
8811 							VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
8812 							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, (EG2_Collision)0, 0 );//was MASK_SHOT, changed to match crosshair trace
8813 							if ( tr.entityNum != ent->s.number )
8814 							{//last chance
8815 								return qfalse;
8816 							}
8817 						}
8818 					}
8819 				}
8820 			}
8821 		}
8822 	}
8823 	else
8824 	{
8825 		switch ( ent->s.weapon )
8826 		{//only missiles with mass are force-pushable
8827 		case WP_SABER:
8828 		case WP_FLECHETTE:
8829 		case WP_ROCKET_LAUNCHER:
8830 		case WP_CONCUSSION:
8831 		case WP_THERMAL:
8832 		case WP_TRIP_MINE:
8833 		case WP_DET_PACK:
8834 			break;
8835 		//only alt-fire of this weapon is force-pushable
8836 		case WP_REPEATER:
8837 			if ( ent->methodOfDeath != MOD_REPEATER_ALT )
8838 			{//not an alt-fire missile
8839 				return qfalse;
8840 			}
8841 			break;
8842 		//everything else cannot be pushed
8843 		case WP_ATST_SIDE:
8844 			if ( ent->methodOfDeath != MOD_EXPLOSIVE )
8845 			{//not a rocket
8846 				return qfalse;
8847 			}
8848 			break;
8849 		default:
8850 			return qfalse;
8851 			break;
8852 		}
8853 
8854 		if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) )
8855 		{//can't force-push/pull stuck missiles (detpacks, tripmines)
8856 			return qfalse;
8857 		}
8858 		if ( ent->s.pos.trType == TR_STATIONARY && ent->s.weapon != WP_THERMAL )
8859 		{//only thermal detonators can be pushed once stopped
8860 			return qfalse;
8861 		}
8862 	}
8863 	return qtrue;
8864 }
8865 
ShouldPlayerResistForceThrow(gentity_t * player,gentity_t * attacker,qboolean pull)8866 static qboolean ShouldPlayerResistForceThrow( gentity_t *player, gentity_t *attacker, qboolean pull )
8867 {
8868 	if ( player->health <= 0 )
8869 	{
8870 		return qfalse;
8871 	}
8872 
8873 	if ( !player->client )
8874 	{
8875 		return qfalse;
8876 	}
8877 
8878 	if ( player->client->ps.forceRageRecoveryTime >= level.time )
8879 	{
8880 		return qfalse;
8881 	}
8882 
8883 	//wasn't trying to grip/drain anyone
8884 	if ( player->client->ps.torsoAnim == BOTH_FORCEGRIP_HOLD ||
8885 			player->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_START ||
8886 			player->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_HOLD )
8887 	{
8888 		return qfalse;
8889 	}
8890 
8891 	//only 30% chance of resisting a Desann or yoda push
8892 	if ( (attacker->client->NPC_class == CLASS_DESANN || Q_stricmp("Yoda",attacker->NPC_type) == 0) && Q_irand( 0, 2 ) > 0 )
8893 	{
8894 		return qfalse;
8895 	}
8896 
8897 	//on the ground
8898 	if ( player->client->ps.groundEntityNum == ENTITYNUM_NONE )
8899 	{
8900 		return qfalse;
8901 	}
8902 
8903 	//not knocked down already
8904 	if ( PM_InKnockDown( &player->client->ps ) )
8905 	{
8906 		return qfalse;
8907 	}
8908 
8909 	//not involved in a saberLock
8910 	if ( player->client->ps.saberLockTime >= level.time )
8911 	{
8912 		return qfalse;
8913 	}
8914 
8915 	//not attacking or otherwise busy
8916 	if ( player->client->ps.weaponTime >= level.time )
8917 	{
8918 		return qfalse;
8919 	}
8920 
8921 	//using saber or fists
8922 	if ( player->client->ps.weapon != WP_SABER && player->client->ps.weapon != WP_MELEE )
8923 	{
8924 		return qfalse;
8925 	}
8926 
8927 	forcePowers_t forcePower = (pull ? FP_PULL : FP_PUSH);
8928 	int attackingForceLevel = attacker->client->ps.forcePowerLevel[forcePower];
8929 	int defendingForceLevel = player->client->ps.forcePowerLevel[forcePower];
8930 
8931 	if ( player->client->ps.powerups[PW_FORCE_PUSH] > level.time ||
8932 		Q_irand( 0, Q_max(0, defendingForceLevel - attackingForceLevel)*2 + 1 ) > 0 )
8933 	{
8934 		// player was pushing, or player's force push/pull is high enough to try to stop me
8935 		if ( InFront( attacker->currentOrigin, player->client->renderInfo.eyePoint, player->client->ps.viewangles, 0.3f ) )
8936 		{
8937 			//I'm in front of player
8938 			return qtrue;
8939 		}
8940 	}
8941 
8942 	return qfalse;
8943 }
8944 
ForceThrow(gentity_t * self,qboolean pull,qboolean fake)8945 void ForceThrow( gentity_t *self, qboolean pull, qboolean fake )
8946 {//FIXME: pass in a target ent so we (an NPC) can push/pull just one targeted ent.
8947 	//shove things in front of you away
8948 	float		dist;
8949 	gentity_t	*ent, *forwardEnt = NULL;
8950 	gentity_t	*entityList[MAX_GENTITIES];
8951 	gentity_t	*push_list[MAX_GENTITIES];
8952 	int			numListedEntities = 0;
8953 	vec3_t		mins, maxs;
8954 	vec3_t		v;
8955 	int			i, e;
8956 	int			ent_count = 0;
8957 	int			radius;
8958 	vec3_t		center, ent_org, size, forward, right, end, dir, fwdangles = {0};
8959 	float		dot1, cone;
8960 	trace_t		tr;
8961 	int			anim, hold, soundIndex, cost, actualCost;
8962 	qboolean	noResist = qfalse;
8963 
8964 	if ( self->health <= 0 )
8965 	{
8966 		return;
8967 	}
8968 	if ( self->client->ps.leanofs )
8969 	{//can't force-throw while leaning
8970 		return;
8971 	}
8972 	if ( self->client->ps.forcePowerDebounce[FP_PUSH] > level.time )
8973 	{//already pushing- now you can't haul someone across the room, sorry
8974 		return;
8975 	}
8976 	if ( self->client->ps.forcePowerDebounce[FP_PULL] > level.time )
8977 	{//already pulling- now you can't haul someone across the room, sorry
8978 		return;
8979 	}
8980 	if ( self->client->ps.pullAttackTime > level.time )
8981 	{//already pull-attacking
8982 		return;
8983 	}
8984 	if ( !self->s.number && (cg.zoomMode || in_camera) )
8985 	{//can't force throw/pull when zoomed in or in cinematic
8986 		return;
8987 	}
8988 	if ( self->client->ps.saberLockTime > level.time )
8989 	{
8990 		if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 )
8991 		{//this can be a way to break out
8992 			return;
8993 		}
8994 		//else, I'm breaking my half of the saberlock
8995 		self->client->ps.saberLockTime = 0;
8996 		self->client->ps.saberLockEnemy = ENTITYNUM_NONE;
8997 	}
8998 
8999 	if ( self->client->ps.legsAnim == BOTH_KNOCKDOWN3
9000 		|| (self->client->ps.torsoAnim == BOTH_FORCE_GETUP_F1 && self->client->ps.torsoAnimTimer > 400)
9001 		|| (self->client->ps.torsoAnim == BOTH_FORCE_GETUP_F2 && self->client->ps.torsoAnimTimer > 900)
9002 		|| (self->client->ps.torsoAnim == BOTH_GETUP3 && self->client->ps.torsoAnimTimer > 500)
9003 		|| (self->client->ps.torsoAnim == BOTH_GETUP4 && self->client->ps.torsoAnimTimer > 300)
9004 		|| (self->client->ps.torsoAnim == BOTH_GETUP5 && self->client->ps.torsoAnimTimer > 500) )
9005 	{//we're face-down, so we'd only be force-push/pulling the floor
9006 		return;
9007 	}
9008 	if ( pull )
9009 	{
9010 		radius = forcePushPullRadius[self->client->ps.forcePowerLevel[FP_PULL]];
9011 	}
9012 	else
9013 	{
9014 		radius = forcePushPullRadius[self->client->ps.forcePowerLevel[FP_PUSH]];
9015 	}
9016 
9017 	if ( !radius )
9018 	{//no ability to do this yet
9019 		return;
9020 	}
9021 
9022 	if ( pull )
9023 	{
9024 		cost = forcePowerNeeded[FP_PULL];
9025 		if ( !WP_ForcePowerUsable( self, FP_PULL, cost ) )
9026 		{
9027 			return;
9028 		}
9029 		//make sure this plays and that you cannot press fire for about 200ms after this
9030 		anim = BOTH_FORCEPULL;
9031 		soundIndex = G_SoundIndex( "sound/weapons/force/pull.wav" );
9032 		hold = 200;
9033 	}
9034 	else
9035 	{
9036 		cost = forcePowerNeeded[FP_PUSH];
9037 		if ( !WP_ForcePowerUsable( self, FP_PUSH, cost ) )
9038 		{
9039 			return;
9040 		}
9041 		//make sure this plays and that you cannot press fire for about 1 second after this
9042 		anim = BOTH_FORCEPUSH;
9043 		soundIndex = G_SoundIndex( "sound/weapons/force/push.wav" );
9044 		hold = 650;
9045 	}
9046 
9047 	int parts = SETANIM_TORSO;
9048 	if ( !PM_InKnockDown( &self->client->ps ) )
9049 	{
9050 		if ( self->client->ps.saberLockTime > level.time )
9051 		{
9052 			self->client->ps.saberLockTime = 0;
9053 			self->painDebounceTime = level.time + 2000;
9054 			hold += 1000;
9055 			parts = SETANIM_BOTH;
9056 		}
9057 		else if ( !VectorLengthSquared( self->client->ps.velocity ) && !(self->client->ps.pm_flags&PMF_DUCKED))
9058 		{
9059 			parts = SETANIM_BOTH;
9060 		}
9061 	}
9062 	NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART );
9063 	self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
9064 	self->client->ps.saberBlocked = BLOCKED_NONE;
9065 	if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
9066 	{
9067 		hold = floor( hold*g_timescale->value );
9068 	}
9069 	self->client->ps.weaponTime = hold;//was 1000, but want to swing sooner
9070 	//do effect... FIXME: build-up or delay this until in proper part of anim
9071 	self->client->ps.powerups[PW_FORCE_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500;
9072 	//reset to 0 in case it's still > 0 from a previous push
9073 	self->client->pushEffectFadeTime = 0;
9074 
9075 	G_Sound( self, soundIndex );
9076 
9077 	if ( (!pull && self->client->ps.forcePowersForced&(1<<FP_PUSH))
9078 		|| (pull && self->client->ps.forcePowersForced&(1<<FP_PULL))
9079 		|| (pull&&self->client->NPC_class==CLASS_KYLE&&(self->spawnflags&1)&&TIMER_Done( self, "kyleTakesSaber" )) )
9080 	{
9081 		noResist = qtrue;
9082 	}
9083 
9084 	VectorCopy( self->client->ps.viewangles, fwdangles );
9085 	//fwdangles[1] = self->client->ps.viewangles[1];
9086 	AngleVectors( fwdangles, forward, right, NULL );
9087 	VectorCopy( self->currentOrigin, center );
9088 
9089 	if ( pull )
9090 	{
9091 		cone = forcePullCone[self->client->ps.forcePowerLevel[FP_PULL]];
9092 	}
9093 	else
9094 	{
9095 		cone = forcePushCone[self->client->ps.forcePowerLevel[FP_PUSH]];
9096 	}
9097 
9098 	//	if ( cone >= 1.0f )
9099 	{//must be pointing right at them
9100 		VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
9101 		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, (EG2_Collision)0, 0 );//was MASK_SHOT, changed to match crosshair trace
9102 		if ( tr.entityNum < ENTITYNUM_WORLD )
9103 		{//found something right in front of self,
9104 			forwardEnt = &g_entities[tr.entityNum];
9105 			if ( !forwardEnt->client && !Q_stricmp( "func_static", forwardEnt->classname ) )
9106 			{
9107 				if ( (forwardEnt->spawnflags&1/*F_PUSH*/)||(forwardEnt->spawnflags&2/*F_PULL*/) )
9108 				{//push/pullable
9109 					if ( (forwardEnt->spawnflags&32/*SOLITARY*/) )
9110 					{//can only push/pull ME, ignore all others
9111 						if ( forwardEnt->NPC_targetname == NULL
9112 							|| (self->targetname&&Q_stricmp( forwardEnt->NPC_targetname, self->targetname ) == 0) )
9113 						{//anyone can push it or only 1 person can push it and it's me
9114 							push_list[0] = forwardEnt;
9115 							ent_count = numListedEntities = 1;
9116 						}
9117 					}
9118 				}
9119 			}
9120 		}
9121 	}
9122 
9123 	if ( forwardEnt )
9124 	{
9125 		if ( G_TryingPullAttack( self, &self->client->usercmd, qtrue ) )
9126 		{//we're going to try to do a pull attack on our forwardEnt
9127 			if ( WP_ForceThrowable( forwardEnt, forwardEnt, self, pull, cone, radius, forward ) )
9128 			{//we will actually pull-attack him, so don't pull him or anything else here
9129 				//activate the power, here, though, so the later check that actually does the pull attack knows we tried to pull
9130 				self->client->ps.forcePowersActive |= (1<<FP_PULL);
9131 				self->client->ps.forcePowerDebounce[FP_PULL] = level.time + 100; //force-pulling
9132 				return;
9133 			}
9134 		}
9135 	}
9136 
9137 	if ( !numListedEntities )
9138 	{
9139 		for ( i = 0 ; i < 3 ; i++ )
9140 		{
9141 			mins[i] = center[i] - radius;
9142 			maxs[i] = center[i] + radius;
9143 		}
9144 
9145 		numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
9146 
9147 		for ( e = 0 ; e < numListedEntities ; e++ )
9148 		{
9149 			ent = entityList[ e ];
9150 
9151 			if ( !WP_ForceThrowable( ent, forwardEnt, self, pull, cone, radius, forward ) )
9152 			{
9153 				continue;
9154 			}
9155 
9156 			//this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
9157 			// find the distance from the edge of the bounding box
9158 			for ( i = 0 ; i < 3 ; i++ )
9159 			{
9160 				if ( center[i] < ent->absmin[i] )
9161 				{
9162 					v[i] = ent->absmin[i] - center[i];
9163 				} else if ( center[i] > ent->absmax[i] )
9164 				{
9165 					v[i] = center[i] - ent->absmax[i];
9166 				} else
9167 				{
9168 					v[i] = 0;
9169 				}
9170 			}
9171 
9172 			VectorSubtract( ent->absmax, ent->absmin, size );
9173 			VectorMA( ent->absmin, 0.5, size, ent_org );
9174 
9175 			//see if they're in front of me
9176 			VectorSubtract( ent_org, center, dir );
9177 			VectorNormalize( dir );
9178 			if ( cone < 1.0f )
9179 			{//must be within the forward cone
9180 				if ( ent->client && !pull
9181 					&& ent->client->ps.forceGripEntityNum == self->s.number
9182 					&& (self->s.eFlags&EF_FORCE_GRIPPED) )
9183 				{//this is the guy that's force-gripping me, use a wider cone regardless of force power level
9184 					if ( (dot1 = DotProduct( dir, forward )) < cone-0.3f )
9185 						continue;
9186 				}
9187 				else if ( ent->client && !pull
9188 					&& ent->client->ps.forceDrainEntityNum == self->s.number
9189 					&& (self->s.eFlags&EF_FORCE_DRAINED) )
9190 				{//this is the guy that's force-draining me, use a wider cone regardless of force power level
9191 					if ( (dot1 = DotProduct( dir, forward )) < cone-0.3f )
9192 						continue;
9193 				}
9194 				else if ( ent->s.eType == ET_MISSILE )//&& ent->s.eType != ET_ITEM && ent->e_ThinkFunc != thinkF_G_RunObject )
9195 				{//missiles are easier to force-push, never require direct trace (FIXME: maybe also items and general physics objects)
9196 					if ( (dot1 = DotProduct( dir, forward )) < cone-0.3f )
9197 						continue;
9198 				}
9199 				else if ( (dot1 = DotProduct( dir, forward )) < cone )
9200 				{
9201 					continue;
9202 				}
9203 			}
9204 			else if ( ent->s.eType == ET_MISSILE )
9205 			{//a missile and we're at force level 1... just use a small cone, but not ridiculously small
9206 				if ( (dot1 = DotProduct( dir, forward )) < 0.75f )
9207 				{
9208 					continue;
9209 				}
9210 			}//else is an NPC or brush entity that our forward trace would have to hit
9211 
9212 			dist = VectorLength( v );
9213 
9214 			//Now check and see if we can actually deflect it
9215 			//method1
9216 			//if within a certain range, deflect it
9217 			if ( ent->s.eType == ET_MISSILE && cone >= 1.0f )
9218 			{//smaller radius on missile checks at force push 1
9219 				if ( dist >= 192 )
9220 				{
9221 					continue;
9222 				}
9223 			}
9224 			else if ( dist >= radius )
9225 			{
9226 				continue;
9227 			}
9228 
9229 			//in PVS?
9230 			if ( !ent->bmodel && !gi.inPVS( ent_org, self->client->renderInfo.eyePoint ) )
9231 			{//must be in PVS
9232 				continue;
9233 			}
9234 
9235 			if ( ent != forwardEnt )
9236 			{//don't need to trace against forwardEnt again
9237 			//really should have a clear LOS to this thing...
9238 				gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_FORCE_PUSH, (EG2_Collision)0, 0 );//was MASK_SHOT, but changed to match above trace and crosshair trace
9239 				if ( tr.fraction < 1.0f && tr.entityNum != ent->s.number )
9240 				{//must have clear LOS
9241 					continue;
9242 				}
9243 			}
9244 
9245 			// ok, we are within the radius, add us to the incoming list
9246 			push_list[ent_count] = ent;
9247 			ent_count++;
9248 		}
9249 	}
9250 
9251 	if ( ent_count )
9252 	{
9253 		for ( int x = 0; x < ent_count; x++ )
9254 		{
9255 			if ( push_list[x]->client )
9256 			{
9257 				vec3_t	pushDir;
9258 				float	knockback = pull?0:200;
9259 
9260 				//SIGH band-aid...
9261 				if ( push_list[x]->s.number >= MAX_CLIENTS
9262 					&& self->s.number < MAX_CLIENTS )
9263 				{
9264 					if ( (push_list[x]->client->ps.forcePowersActive&(1<<FP_GRIP))
9265 						//&& push_list[x]->client->ps.forcePowerDebounce[FP_GRIP] < level.time
9266 						&& push_list[x]->client->ps.forceGripEntityNum == self->s.number )
9267 					{
9268 						WP_ForcePowerStop( push_list[x], FP_GRIP );
9269 					}
9270 					if ( (push_list[x]->client->ps.forcePowersActive&(1<<FP_DRAIN))
9271 						//&& push_list[x]->client->ps.forcePowerDebounce[FP_DRAIN] < level.time
9272 						&& push_list[x]->client->ps.forceDrainEntityNum == self->s.number )
9273 					{
9274 						WP_ForcePowerStop( push_list[x], FP_DRAIN );
9275 					}
9276 				}
9277 
9278 				if ( Rosh_BeingHealed( push_list[x] ) )
9279 				{
9280 					continue;
9281 				}
9282 				if ( push_list[x]->client->NPC_class == CLASS_HAZARD_TROOPER
9283 					&& push_list[x]->health > 0 )
9284 				{//living hazard troopers resist push/pull
9285 					WP_ForceThrowHazardTrooper( self, push_list[x], pull );
9286 					continue;
9287 				}
9288 				if ( fake )
9289 				{//always resist
9290 					WP_ResistForcePush( push_list[x], self, qfalse );
9291 					continue;
9292 				}
9293 //FIXMEFIXMEFIXMEFIXMEFIXME: extern a lot of this common code when I have the time!!!
9294 				int powerLevel, powerUse;
9295 				if (pull)
9296 				{
9297 					powerLevel = self->client->ps.forcePowerLevel[FP_PULL];
9298 					powerUse = FP_PULL;
9299 				}
9300 				else
9301 				{
9302 					powerLevel = self->client->ps.forcePowerLevel[FP_PUSH];
9303 					powerUse = FP_PUSH;
9304 				}
9305 				int modPowerLevel = WP_AbsorbConversion( push_list[x], push_list[x]->client->ps.forcePowerLevel[FP_ABSORB], self, powerUse, powerLevel, forcePowerNeeded[self->client->ps.forcePowerLevel[powerUse]] );
9306 				if (push_list[x]->client->NPC_class==CLASS_ASSASSIN_DROID ||
9307 					push_list[x]->client->NPC_class==CLASS_HAZARD_TROOPER)
9308 				{
9309 					modPowerLevel = 0;	// devides throw by 10
9310 				}
9311 
9312 				//First, if this is the player we're push/pulling, see if he can counter it
9313 				if ( modPowerLevel != -1
9314 					&& !noResist
9315 					&& InFront( self->currentOrigin, push_list[x]->client->renderInfo.eyePoint, push_list[x]->client->ps.viewangles, 0.3f ) )
9316 				{//absorbed and I'm in front of them
9317 					//counter it
9318 					if ( push_list[x]->client->ps.forcePowerLevel[FP_ABSORB] > FORCE_LEVEL_2 )
9319 					{//no reaction at all
9320 					}
9321 					else
9322 					{
9323 						WP_ResistForcePush( push_list[x], self, qfalse );
9324 						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
9325 						push_list[x]->client->ps.saberBlocked = BLOCKED_NONE;
9326 					}
9327 					continue;
9328 				}
9329 				else if ( !push_list[x]->s.number )
9330 				{//player
9331 					if ( !noResist && ShouldPlayerResistForceThrow(push_list[x], self, pull) )
9332 					{
9333 						WP_ResistForcePush( push_list[x], self, qfalse );
9334 						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
9335 						push_list[x]->client->ps.saberBlocked = BLOCKED_NONE;
9336 						continue;
9337 					}
9338 				}
9339 				else if ( push_list[x]->client && Jedi_WaitingAmbush( push_list[x] ) )
9340 				{
9341 					WP_ForceKnockdown( push_list[x], self, pull, qtrue, qfalse );
9342 					continue;
9343 				}
9344 
9345 				G_KnockOffVehicle( push_list[x], self, pull );
9346 
9347 				if ( !pull
9348 					&& push_list[x]->client->ps.forceDrainEntityNum == self->s.number
9349 					&& (self->s.eFlags&EF_FORCE_DRAINED) )
9350 				{//stop them from draining me now, dammit!
9351 					WP_ForcePowerStop( push_list[x], FP_DRAIN );
9352 				}
9353 
9354 				//okay, everyone else (or player who couldn't resist it)...
9355 				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
9356 						&& push_list[x]->client->ps.weapon == WP_SABER //Jedi
9357 						&& push_list[x]->health > 0 //alive
9358 						&& push_list[x]->client->ps.forceRageRecoveryTime < level.time //not recobering from rage
9359 						&& ((self->client->NPC_class != CLASS_DESANN&&Q_stricmp("Yoda",self->NPC_type)) || !Q_irand( 0, 2 ) )//only 30% chance of resisting a Desann push
9360 						&& push_list[x]->client->ps.groundEntityNum != ENTITYNUM_NONE //on the ground
9361 						&& InFront( self->currentOrigin, push_list[x]->currentOrigin, push_list[x]->client->ps.viewangles, 0.3f ) //I'm in front of him
9362 						&& ( push_list[x]->client->ps.powerups[PW_FORCE_PUSH] > level.time ||//he's pushing too
9363 								(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)
9364 						   )
9365 					)
9366 				{//Jedi don't get pushed, they resist as long as they aren't already attacking and are on the ground
9367 					if ( push_list[x]->client->ps.saberLockTime > level.time )
9368 					{//they're in a lock
9369 						if ( push_list[x]->client->ps.saberLockEnemy != self->s.number )
9370 						{//they're not in a lock with me
9371 							continue;
9372 						}
9373 						else if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 ||
9374 							push_list[x]->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
9375 						{//they're in a lock with me, but my push is too weak
9376 							continue;
9377 						}
9378 						else
9379 						{//we will knock them down
9380 							self->painDebounceTime = 0;
9381 							self->client->ps.weaponTime = 500;
9382 							if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
9383 							{
9384 								self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
9385 							}
9386 						}
9387 					}
9388 					int resistChance = Q_irand(0, 2);
9389 					if ( push_list[x]->s.number >= MAX_CLIENTS )
9390 					{//NPC
9391 						if ( g_spskill->integer == 1 )
9392 						{//stupid tweak for graham
9393 							resistChance = Q_irand(0, 3);
9394 						}
9395 					}
9396 					if ( noResist ||
9397 							( !pull
9398 							&& modPowerLevel == -1
9399 							&& self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2
9400 							&& !resistChance
9401 							&& push_list[x]->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 )
9402 						)
9403 					{//a level 3 push can even knock down a jedi
9404 						if ( PM_InKnockDown( &push_list[x]->client->ps ) )
9405 						{//can't knock them down again
9406 							continue;
9407 						}
9408 						WP_ForceKnockdown( push_list[x], self, pull, qfalse, qtrue );
9409 					}
9410 					else
9411 					{
9412 						WP_ResistForcePush( push_list[x], self, qfalse );
9413 					}
9414 				}
9415 				else
9416 				{
9417 					//UGH: FIXME: for enemy jedi, they should probably always do force pull 3, and not your weapon (if player?)!
9418 					//shove them
9419 					if ( push_list[x]->NPC
9420 						&& push_list[x]->NPC->jumpState == JS_JUMPING )
9421 					{//don't interrupt a scripted jump
9422 						//WP_ResistForcePush( push_list[x], self, qfalse );
9423 						push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
9424 						continue;
9425 					}
9426 
9427 					if ( push_list[x]->s.number
9428 						&& (push_list[x]->message || (push_list[x]->flags&FL_NO_KNOCKBACK)) )
9429 					{//an NPC who has a key
9430 						//don't push me... FIXME: maybe can pull the key off me?
9431 						WP_ForceKnockdown( push_list[x], self, pull, qfalse, qfalse );
9432 						continue;
9433 					}
9434 					if ( pull )
9435 					{
9436 						VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, pushDir );
9437 						if ( self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3
9438 							&& self->client->NPC_class == CLASS_KYLE
9439 							&& (self->spawnflags&1)
9440 							&& TIMER_Done( self, "kyleTakesSaber" )
9441 							&& push_list[x]->client
9442 							&& push_list[x]->client->ps.weapon == WP_SABER
9443 							&& !push_list[x]->client->ps.saberInFlight
9444 							&& push_list[x]->client->ps.saberEntityNum < ENTITYNUM_WORLD
9445 							&& !PM_InOnGroundAnim( &push_list[x]->client->ps ) )
9446 						{
9447 							vec3_t throwVec;
9448 							VectorScale( pushDir, 10.0f, throwVec );
9449 							WP_SaberLose( push_list[x], throwVec );
9450 							NPC_SetAnim( push_list[x], SETANIM_BOTH, BOTH_LOSE_SABER, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
9451 							push_list[x]->client->ps.torsoAnimTimer += 500;
9452 							push_list[x]->client->ps.pm_time = push_list[x]->client->ps.weaponTime = push_list[x]->client->ps.torsoAnimTimer;
9453 							push_list[x]->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
9454 							push_list[x]->client->ps.saberMove = LS_NONE;
9455 							push_list[x]->aimDebounceTime = level.time + push_list[x]->client->ps.torsoAnimTimer;
9456 							VectorClear( push_list[x]->client->ps.velocity );
9457 							VectorClear( push_list[x]->client->ps.moveDir );
9458 							//Kyle will stand around for a bit, too...
9459 							self->client->ps.pm_time = self->client->ps.weaponTime = 2000;
9460 							self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
9461 							self->painDebounceTime = level.time + self->client->ps.weaponTime;
9462 							TIMER_Set( self, "kyleTakesSaber", Q_irand( 60000, 180000 ) );//don't do this again for a while
9463 							G_AddVoiceEvent( self, Q_irand(EV_TAUNT1,EV_TAUNT3), Q_irand( 4000, 6000 ) );
9464 							VectorClear( self->client->ps.velocity );
9465 							VectorClear( self->client->ps.moveDir );
9466 							continue;
9467 						}
9468 						else if ( push_list[x]->NPC
9469 							&& (push_list[x]->NPC->scriptFlags&SCF_DONT_FLEE) )
9470 						{//*SIGH*... if an NPC can't flee, they can't run after and pick up their weapon, do don't drop it
9471 						}
9472 						else if ( self->client->ps.forcePowerLevel[FP_PULL] > FORCE_LEVEL_1
9473 							&& push_list[x]->client->NPC_class != CLASS_ROCKETTROOPER//rockettroopers never drop their weapon
9474 							&& push_list[x]->client->NPC_class != CLASS_VEHICLE
9475 							&& push_list[x]->client->NPC_class != CLASS_BOBAFETT
9476 							&& push_list[x]->client->NPC_class != CLASS_TUSKEN
9477 							&& push_list[x]->client->NPC_class != CLASS_HAZARD_TROOPER
9478 							&& push_list[x]->client->NPC_class != CLASS_ASSASSIN_DROID
9479 							&& push_list[x]->s.weapon != WP_SABER
9480 							&& push_list[x]->s.weapon != WP_MELEE
9481 							&& push_list[x]->s.weapon != WP_THERMAL
9482 							&& push_list[x]->s.weapon != WP_CONCUSSION	// so rax can't drop his
9483 							)
9484 						{//yank the weapon - NOTE: level 1 just knocks them down, not take weapon
9485 							//FIXME: weapon yank anim if not a knockdown?
9486 							if ( InFront( self->currentOrigin, push_list[x]->currentOrigin, push_list[x]->client->ps.viewangles, 0.0f ) )
9487 							{//enemy has to be facing me, too...
9488 								WP_DropWeapon( push_list[x], pushDir );
9489 							}
9490 						}
9491 						knockback += VectorNormalize( pushDir );
9492 						if ( knockback > 200 )
9493 						{
9494 							knockback = 200;
9495 						}
9496 						if ( self->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_3 )
9497 						{//maybe just knock them down
9498 							knockback /= 3;
9499 						}
9500 					}
9501 					else
9502 					{
9503 						VectorSubtract( push_list[x]->currentOrigin, self->currentOrigin, pushDir );
9504 						knockback -= VectorNormalize( pushDir );
9505 						if ( knockback < 100 )
9506 						{
9507 							knockback = 100;
9508 						}
9509 						//scale for push level
9510 						if ( self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_2 )
9511 						{//maybe just knock them down
9512 							knockback /= 3;
9513 						}
9514 						else if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
9515 						{//super-hard push
9516 							//Hmm, maybe in this case can even nudge/knockdown a jedi?  Especially if close?
9517 							//knockback *= 5;
9518 						}
9519 					}
9520 
9521 					if ( modPowerLevel != -1 )
9522 					{
9523 						if ( !modPowerLevel )
9524 						{
9525 							knockback /= 10.0f;
9526 						}
9527 						else if ( modPowerLevel == 1 )
9528 						{
9529 							knockback /= 6.0f;
9530 						}
9531 						else// if ( modPowerLevel == 2 )
9532 						{
9533 							knockback /= 2.0f;
9534 						}
9535 					}
9536 					//actually push/pull the enemy
9537 					G_Throw( push_list[x], pushDir, knockback );
9538 					//make it so they don't actually hurt me when pulled at me...
9539 					push_list[x]->forcePuller = self->s.number;
9540 
9541 					if ( push_list[x]->client->ps.groundEntityNum != ENTITYNUM_NONE )
9542 					{//if on the ground, make sure they get shoved up some
9543 						if ( push_list[x]->client->ps.velocity[2] < knockback )
9544 						{
9545 							push_list[x]->client->ps.velocity[2] = knockback;
9546 						}
9547 					}
9548 
9549 					if ( push_list[x]->health > 0 )
9550 					{//target is still alive
9551 						if ( (push_list[x]->s.number||(cg.renderingThirdPerson&&!cg.zoomMode)) //NPC or 3rd person player
9552 							&& ((!pull&&self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_2 && push_list[x]->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_1) //level 1 push
9553 								|| (pull && self->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_2 && push_list[x]->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_1)) )//level 1 pull
9554 						{//NPC or third person player (without force push/pull skill), and force push/pull level is at 1
9555 							WP_ForceKnockdown( push_list[x], self, pull, (qboolean)(!pull&&knockback>150), qfalse );
9556 						}
9557 						else if ( !push_list[x]->s.number )
9558 						{//player, have to force an anim on him
9559 							WP_ForceKnockdown( push_list[x], self, pull, (qboolean)(!pull&&knockback>150), qfalse );
9560 						}
9561 						else
9562 						{//NPC and force-push/pull at level 2 or higher
9563 							WP_ForceKnockdown( push_list[x], self, pull, (qboolean)(!pull&&knockback>100), qfalse );
9564 						}
9565 					}
9566 					push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
9567 				}
9568 			}
9569 			else if ( !fake )
9570 			{//not a fake push/pull
9571 				if ( push_list[x]->s.weapon == WP_SABER && (push_list[x]->contents&CONTENTS_LIGHTSABER) )
9572 				{//a thrown saber, just send it back
9573 					/*
9574 					if ( pull )
9575 					{//steal it?
9576 					}
9577 					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 )
9578 					{//it's on and being controlled
9579 						//FIXME: prevent it from damaging me?
9580 						if ( self->s.number == 0 || Q_irand( 0, 2 ) )
9581 						{//certain chance of throwing it aside and turning it off?
9582 							//give it some velocity away from me
9583 							//FIXME: maybe actually push or pull it?
9584 							if ( Q_irand( 0, 1 ) )
9585 							{
9586 								VectorScale( right, -1, right );
9587 							}
9588 							G_ReflectMissile( self, push_list[x], right );
9589 							//FIXME: isn't turning off!!!
9590 							WP_SaberDrop( push_list[x]->owner, push_list[x] );
9591 						}
9592 						else
9593 						{
9594 							WP_SaberReturn( push_list[x]->owner, push_list[x] );
9595 						}
9596 						//different effect?
9597 					}
9598 				}
9599 				else if ( push_list[x]->s.eType == ET_MISSILE
9600 					&& push_list[x]->s.pos.trType != TR_STATIONARY
9601 					&& (push_list[x]->s.pos.trType != TR_INTERPOLATE||push_list[x]->s.weapon != WP_THERMAL) )//rolling and stationary thermal detonators are dealt with below
9602 				{
9603 					vec3_t dir2Me;
9604 					VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, dir2Me );
9605 					float dot = DotProduct( push_list[x]->s.pos.trDelta, dir2Me );
9606 					if ( pull )
9607 					{//deflect rather than reflect?
9608 					}
9609 					else
9610 					{
9611 						if ( push_list[x]->s.eFlags&EF_MISSILE_STICK )
9612 						{//caught a sticky in-air
9613 							push_list[x]->s.eType = ET_MISSILE;
9614 							push_list[x]->s.eFlags &= ~EF_MISSILE_STICK;
9615 							push_list[x]->s.eFlags |= EF_BOUNCE_HALF;
9616 							push_list[x]->splashDamage /= 3;
9617 							push_list[x]->splashRadius /= 3;
9618 							push_list[x]->e_ThinkFunc = thinkF_WP_Explode;
9619 							push_list[x]->nextthink = level.time + Q_irand( 500, 3000 );
9620 						}
9621 						if ( dot >= 0 )
9622 						{//it's heading towards me
9623 							G_ReflectMissile( self, push_list[x], forward );
9624 						}
9625 						else
9626 						{
9627 							VectorScale( push_list[x]->s.pos.trDelta, 1.25f, push_list[x]->s.pos.trDelta );
9628 						}
9629 						//deflect sound
9630 						//G_Sound( push_list[x], G_SoundIndex( va("sound/weapons/blaster/reflect%d.wav", Q_irand( 1, 3 ) ) ) );
9631 						//push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
9632 					}
9633 					if ( push_list[x]->s.eType == ET_MISSILE
9634 						&& push_list[x]->s.weapon == WP_ROCKET_LAUNCHER
9635 						&& push_list[x]->damage < 60 )
9636 					{//pushing away a rocket raises it's damage to the max for NPCs
9637 						push_list[x]->damage = 60;
9638 					}
9639 				}
9640 				else if ( push_list[x]->svFlags & SVF_GLASS_BRUSH )
9641 				{//break the glass
9642 					trace_t tr;
9643 					vec3_t	pushDir;
9644 					float	damage = 800;
9645 
9646 					AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
9647 					VectorNormalize( forward );
9648 					VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
9649 					gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
9650 					if ( tr.entityNum != push_list[x]->s.number || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
9651 					{//must be pointing right at it
9652 						continue;
9653 					}
9654 
9655 					if ( pull )
9656 					{
9657 						VectorSubtract( self->client->renderInfo.eyePoint, tr.endpos, pushDir );
9658 					}
9659 					else
9660 					{
9661 						VectorSubtract( tr.endpos, self->client->renderInfo.eyePoint, pushDir );
9662 					}
9663 					/*
9664 					VectorSubtract( push_list[x]->absmax, push_list[x]->absmin, size );
9665 					VectorMA( push_list[x]->absmin, 0.5, size, center );
9666 					if ( pull )
9667 					{
9668 						VectorSubtract( self->client->renderInfo.eyePoint, center, pushDir );
9669 					}
9670 					else
9671 					{
9672 						VectorSubtract( center, self->client->renderInfo.eyePoint, pushDir );
9673 					}
9674 					*/
9675 					damage -= VectorNormalize( pushDir );
9676 					if ( damage < 200 )
9677 					{
9678 						damage = 200;
9679 					}
9680 					VectorScale( pushDir, damage, pushDir );
9681 
9682 					G_Damage( push_list[x], self, self, pushDir, tr.endpos, damage, 0, MOD_UNKNOWN );
9683 				}
9684 				else if ( !Q_stricmp( "func_static", push_list[x]->classname ) )
9685 				{//force-usable func_static
9686 					if ( !pull && (push_list[x]->spawnflags&1/*F_PUSH*/) )
9687 					{
9688 						if ( push_list[x]->NPC_targetname == NULL
9689 							|| (self->targetname&&Q_stricmp( push_list[x]->NPC_targetname, self->targetname ) == 0) )
9690 						{//anyone can pull it or only 1 person can push it and it's me
9691 							GEntity_UseFunc( push_list[x], self, self );
9692 						}
9693 					}
9694 					else if ( pull && (push_list[x]->spawnflags&2/*F_PULL*/) )
9695 					{
9696 						if ( push_list[x]->NPC_targetname == NULL
9697 							|| (self->targetname&&Q_stricmp( push_list[x]->NPC_targetname, self->NPC_targetname ) == 0) )
9698 						{//anyone can push it or only 1 person can push it and it's me
9699 							GEntity_UseFunc( push_list[x], self, self );
9700 						}
9701 					}
9702 				}
9703 				else if ( !Q_stricmp( "func_door", push_list[x]->classname ) && (push_list[x]->spawnflags&2/*MOVER_FORCE_ACTIVATE*/) )
9704 				{//push/pull the door
9705 					vec3_t	pos1, pos2;
9706 
9707 					AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
9708 					VectorNormalize( forward );
9709 					VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
9710 					gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
9711 					if ( tr.entityNum != push_list[x]->s.number || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
9712 					{//must be pointing right at it
9713 						continue;
9714 					}
9715 
9716 					if ( VectorCompare( vec3_origin, push_list[x]->s.origin ) )
9717 					{//does not have an origin brush, so pos1 & pos2 are relative to world origin, need to calc center
9718 						VectorSubtract( push_list[x]->absmax, push_list[x]->absmin, size );
9719 						VectorMA( push_list[x]->absmin, 0.5, size, center );
9720 						if ( (push_list[x]->spawnflags&1) && push_list[x]->moverState == MOVER_POS1 )
9721 						{//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
9722 							VectorSubtract( center, push_list[x]->pos1, center );
9723 						}
9724 						else if ( !(push_list[x]->spawnflags&1) && push_list[x]->moverState == MOVER_POS2 )
9725 						{//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
9726 							VectorSubtract( center, push_list[x]->pos2, center );
9727 						}
9728 						VectorAdd( center, push_list[x]->pos1, pos1 );
9729 						VectorAdd( center, push_list[x]->pos2, pos2 );
9730 					}
9731 					else
9732 					{//actually has an origin, pos1 and pos2 are absolute
9733 						VectorCopy( push_list[x]->currentOrigin, center );
9734 						VectorCopy( push_list[x]->pos1, pos1 );
9735 						VectorCopy( push_list[x]->pos2, pos2 );
9736 					}
9737 
9738 					if ( Distance( pos1, self->client->renderInfo.eyePoint ) < Distance( pos2, self->client->renderInfo.eyePoint ) )
9739 					{//pos1 is closer
9740 						if ( push_list[x]->moverState == MOVER_POS1 )
9741 						{//at the closest pos
9742 							if ( pull )
9743 							{//trying to pull, but already at closest point, so screw it
9744 								continue;
9745 							}
9746 						}
9747 						else if ( push_list[x]->moverState == MOVER_POS2 )
9748 						{//at farthest pos
9749 							if ( !pull )
9750 							{//trying to push, but already at farthest point, so screw it
9751 								continue;
9752 							}
9753 						}
9754 					}
9755 					else
9756 					{//pos2 is closer
9757 						if ( push_list[x]->moverState == MOVER_POS1 )
9758 						{//at the farthest pos
9759 							if ( !pull )
9760 							{//trying to push, but already at farthest point, so screw it
9761 								continue;
9762 							}
9763 						}
9764 						else if ( push_list[x]->moverState == MOVER_POS2 )
9765 						{//at closest pos
9766 							if ( pull )
9767 							{//trying to pull, but already at closest point, so screw it
9768 								continue;
9769 							}
9770 						}
9771 					}
9772 					GEntity_UseFunc( push_list[x], self, self );
9773 				}
9774 				else if ( push_list[x]->s.eType == ET_MISSILE/*thermal resting on ground*/
9775 					|| push_list[x]->s.eType == ET_ITEM
9776 					|| push_list[x]->e_ThinkFunc == thinkF_G_RunObject || Q_stricmp( "limb", push_list[x]->classname ) == 0 )
9777 				{//general object, toss it
9778 					vec3_t	pushDir, kvel;
9779 					float	knockback = pull?0:200;
9780 					float	mass = 200;
9781 
9782 					if ( pull )
9783 					{
9784 						if ( push_list[x]->s.eType == ET_ITEM )
9785 						{//pull it to a little higher point
9786 							vec3_t	adjustedOrg;
9787 							VectorCopy( self->currentOrigin, adjustedOrg );
9788 							adjustedOrg[2] += self->maxs[2]/3;
9789 							VectorSubtract( adjustedOrg, push_list[x]->currentOrigin, pushDir );
9790 						}
9791 						else if ( self->enemy //I have an enemy
9792 							//&& push_list[x]->s.eType != ET_ITEM //not an item
9793 							&& self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 //have push 3 or greater
9794 							&& InFront(push_list[x]->currentOrigin, self->currentOrigin, self->currentAngles, 0.25f)//object is generally in front of me
9795 							&& InFront(self->enemy->currentOrigin, self->currentOrigin, self->currentAngles, 0.75f)//enemy is pretty much right in front of me
9796 							&& !InFront(push_list[x]->currentOrigin, self->enemy->currentOrigin, self->enemy->currentAngles, -0.25f)//object is generally behind enemy
9797 							//FIXME: check dist to enemy and clear LOS to enemy and clear Path between object and enemy?
9798 							&& ( (self->NPC&&(noResist||Q_irand(0,RANK_CAPTAIN)<self->NPC->rank) )//NPC with enough skill
9799 								||( self->s.number<MAX_CLIENTS ) )
9800 							)
9801 						{//if I have an auto-enemy & he's in front of me, push it toward him!
9802 							/*
9803 							if ( targetedObjectMassTotal + push_list[x]->mass > TARGETED_OBJECT_PUSH_MASS_MAX )
9804 							{//already pushed too many things
9805 								//FIXME: pick closest?
9806 								continue;
9807 							}
9808 							targetedObjectMassTotal += push_list[x]->mass;
9809 							*/
9810 							VectorSubtract( self->enemy->currentOrigin, push_list[x]->currentOrigin, pushDir );
9811 						}
9812 						else
9813 						{
9814 							VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, pushDir );
9815 						}
9816 						knockback += VectorNormalize( pushDir );
9817 						if ( knockback > 200 )
9818 						{
9819 							knockback = 200;
9820 						}
9821 						if ( push_list[x]->s.eType == ET_ITEM
9822 							&& push_list[x]->item
9823 							&& push_list[x]->item->giType == IT_HOLDABLE
9824 							&& push_list[x]->item->giTag == INV_SECURITY_KEY )
9825 						{//security keys are pulled with less enthusiasm
9826 							if ( knockback > 100 )
9827 							{
9828 								knockback = 100;
9829 							}
9830 						}
9831 						else if ( knockback > 200 )
9832 						{
9833 							knockback = 200;
9834 						}
9835 					}
9836 					else
9837 					{
9838 						if ( self->enemy //I have an enemy
9839 							&& push_list[x]->s.eType != ET_ITEM //not an item
9840 							&& self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 //have push 3 or greater
9841 							&& InFront(push_list[x]->currentOrigin, self->currentOrigin, self->currentAngles, 0.25f)//object is generally in front of me
9842 							&& InFront(self->enemy->currentOrigin, self->currentOrigin, self->currentAngles, 0.75f)//enemy is pretty much right in front of me
9843 							&& InFront(push_list[x]->currentOrigin, self->enemy->currentOrigin, self->enemy->currentAngles, 0.25f)//object is generally in front of enemy
9844 							//FIXME: check dist to enemy and clear LOS to enemy and clear Path between object and enemy?
9845 							&& ( (self->NPC&&(noResist||Q_irand(0,RANK_CAPTAIN)<self->NPC->rank) )//NPC with enough skill
9846 								||( self->s.number<MAX_CLIENTS ) )
9847 							)
9848 						{//if I have an auto-enemy & he's in front of me, push it toward him!
9849 							/*
9850 							if ( targetedObjectMassTotal + push_list[x]->mass > TARGETED_OBJECT_PUSH_MASS_MAX )
9851 							{//already pushed too many things
9852 								//FIXME: pick closest?
9853 								continue;
9854 							}
9855 							targetedObjectMassTotal += push_list[x]->mass;
9856 							*/
9857 							VectorSubtract( self->enemy->currentOrigin, push_list[x]->currentOrigin, pushDir );
9858 						}
9859 						else
9860 						{
9861 							VectorSubtract( push_list[x]->currentOrigin, self->currentOrigin, pushDir );
9862 						}
9863 						knockback -= VectorNormalize( pushDir );
9864 						if ( knockback < 100 )
9865 						{
9866 							knockback = 100;
9867 						}
9868 					}
9869 					//FIXME: if pull a FL_FORCE_PULLABLE_ONLY, clear the flag, assuming it's no longer in solid?  or check?
9870 					VectorCopy( push_list[x]->currentOrigin, push_list[x]->s.pos.trBase );
9871 					push_list[x]->s.pos.trTime = level.time;								// move a bit on the very first frame
9872 					if ( push_list[x]->s.pos.trType != TR_INTERPOLATE )
9873 					{//don't do this to rolling missiles
9874 						push_list[x]->s.pos.trType = TR_GRAVITY;
9875 					}
9876 
9877 					if ( push_list[x]->e_ThinkFunc == thinkF_G_RunObject && push_list[x]->physicsBounce )
9878 					{//it's a pushable misc_model_breakable, use it's mass instead of our one-size-fits-all mass
9879 						mass = push_list[x]->physicsBounce;//same as push_list[x]->mass, right?
9880 					}
9881 					if ( mass < 50 )
9882 					{//???
9883 						mass = 50;
9884 					}
9885 					if ( g_gravity->value > 0 )
9886 					{
9887 						VectorScale( pushDir, g_knockback->value * knockback / mass * 0.8, kvel );
9888 						kvel[2] = pushDir[2] * g_knockback->value * knockback / mass * 1.5;
9889 					}
9890 					else
9891 					{
9892 						VectorScale( pushDir, g_knockback->value * knockback / mass, kvel );
9893 					}
9894 					VectorAdd( push_list[x]->s.pos.trDelta, kvel, push_list[x]->s.pos.trDelta );
9895 					if ( g_gravity->value > 0 )
9896 					{
9897 						if ( push_list[x]->s.pos.trDelta[2] < knockback )
9898 						{
9899 							push_list[x]->s.pos.trDelta[2] = knockback;
9900 						}
9901 					}
9902 					//no trDuration?
9903 					if ( push_list[x]->e_ThinkFunc != thinkF_G_RunObject )
9904 					{//objects spin themselves?
9905 						//spin it
9906 						//FIXME: messing with roll ruins the rotational center???
9907 						push_list[x]->s.apos.trTime = level.time;
9908 						push_list[x]->s.apos.trType = TR_LINEAR;
9909 						VectorClear( push_list[x]->s.apos.trDelta );
9910 						push_list[x]->s.apos.trDelta[1] = Q_irand( -800, 800 );
9911 					}
9912 
9913 					if ( Q_stricmp( "limb", push_list[x]->classname ) == 0 )
9914 					{//make sure it runs it's physics
9915 						push_list[x]->e_ThinkFunc = thinkF_LimbThink;
9916 						push_list[x]->nextthink = level.time + FRAMETIME;
9917 					}
9918 					push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
9919 					push_list[x]->forcePuller = self->s.number;//remember this regardless
9920 					if ( push_list[x]->item && push_list[x]->item->giTag == INV_SECURITY_KEY )
9921 					{
9922 						AddSightEvent( player, push_list[x]->currentOrigin, 128, AEL_DISCOVERED );//security keys are more important
9923 					}
9924 					else
9925 					{
9926 						AddSightEvent( player, push_list[x]->currentOrigin, 128, AEL_SUSPICIOUS );//hmm... or should this always be discovered?
9927 					}
9928 				}
9929 				else if ( push_list[x]->s.weapon == WP_TURRET
9930 					&& !Q_stricmp( "PAS", push_list[x]->classname )
9931 					&& push_list[x]->s.apos.trType == TR_STATIONARY )
9932 				{//a portable turret
9933 					WP_KnockdownTurret( self, push_list[x] );
9934 				}
9935 			}
9936 		}
9937 		if ( pull )
9938 		{
9939 			if ( self->client->ps.forcePowerLevel[FP_PULL] > FORCE_LEVEL_2 )
9940 			{//at level 3, can pull multiple, so it costs more
9941 				actualCost = forcePowerNeeded[FP_PULL]*ent_count;
9942 				if ( actualCost > 50 )
9943 				{
9944 					actualCost = 50;
9945 				}
9946 				else if ( actualCost < cost )
9947 				{
9948 					actualCost = cost;
9949 				}
9950 			}
9951 			else
9952 			{
9953 				actualCost = cost;
9954 			}
9955 			WP_ForcePowerStart( self, FP_PULL, actualCost );
9956 		}
9957 		else
9958 		{
9959 			if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
9960 			{//at level 3, can push multiple, so costs more
9961 				actualCost = forcePowerNeeded[FP_PUSH]*ent_count;
9962 				if ( actualCost > 50 )
9963 				{
9964 					actualCost = 50;
9965 				}
9966 				else if ( actualCost < cost )
9967 				{
9968 					actualCost = cost;
9969 				}
9970 			}
9971 			else if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_1 )
9972 			{//at level 2, can push multiple, so costs more
9973 				actualCost = floor(forcePowerNeeded[FP_PUSH]*ent_count/1.5f);
9974 				if ( actualCost > 50 )
9975 				{
9976 					actualCost = 50;
9977 				}
9978 				else if ( actualCost < cost )
9979 				{
9980 					actualCost = cost;
9981 				}
9982 			}
9983 			else
9984 			{
9985 				actualCost = cost;
9986 			}
9987 			WP_ForcePowerStart( self, FP_PUSH, actualCost );
9988 		}
9989 	}
9990 	else
9991 	{//didn't push or pull anything?  don't penalize them too much
9992 		if ( pull )
9993 		{
9994 			WP_ForcePowerStart( self, FP_PULL, 5 );
9995 		}
9996 		else
9997 		{
9998 			WP_ForcePowerStart( self, FP_PUSH, 5 );
9999 		}
10000 	}
10001 	if ( pull )
10002 	{
10003 		if ( self->NPC )
10004 		{//NPCs can push more often
10005 			//FIXME: vary by rank and game skill?
10006 			self->client->ps.forcePowerDebounce[FP_PULL] = level.time + 200;
10007 		}
10008 		else
10009 		{
10010 			self->client->ps.forcePowerDebounce[FP_PULL] = level.time + self->client->ps.torsoAnimTimer + 500;
10011 		}
10012 	}
10013 	else
10014 	{
10015 		if ( self->NPC )
10016 		{//NPCs can push more often
10017 			//FIXME: vary by rank and game skill?
10018 			self->client->ps.forcePowerDebounce[FP_PUSH] = level.time + 200;
10019 		}
10020 		else
10021 		{
10022 			self->client->ps.forcePowerDebounce[FP_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500;
10023 		}
10024 	}
10025 }
10026 
WP_DebounceForceDeactivateTime(gentity_t * self)10027 void WP_DebounceForceDeactivateTime( gentity_t *self )
10028 {
10029 	//FIXME: if these are interruptable, should they also drain power at a constant rate
10030 	//		rather than just taking one lump sum of force power upfront?
10031 	if ( self && self->client )
10032 	{
10033 		if ( self->client->ps.forcePowersActive&(1<<FP_SPEED)
10034 			|| self->client->ps.forcePowersActive&(1<<FP_PROTECT)
10035 			|| self->client->ps.forcePowersActive&(1<<FP_ABSORB)
10036 			|| self->client->ps.forcePowersActive&(1<<FP_RAGE)
10037 			|| self->client->ps.forcePowersActive&(1<<FP_SEE) )
10038 		{//already running another power that can be manually, stopped don't debounce so long
10039 			self->client->ps.forceAllowDeactivateTime = level.time + 500;
10040 		}
10041 		else
10042 		{//not running one of the interruptable powers
10043 			//FIXME: this should be shorter for force speed and rage (because of timescaling)
10044 			self->client->ps.forceAllowDeactivateTime = level.time + 1500;
10045 		}
10046 	}
10047 }
10048 
ForceSpeed(gentity_t * self,int duration)10049 void ForceSpeed( gentity_t *self, int duration )
10050 {
10051 	if ( self->health <= 0 )
10052 	{
10053 		return;
10054 	}
10055 	if (self->client->ps.forceAllowDeactivateTime < level.time &&
10056 		(self->client->ps.forcePowersActive & (1 << FP_SPEED)) )
10057 	{//stop using it
10058 		WP_ForcePowerStop( self, FP_SPEED );
10059 		return;
10060 	}
10061 	if ( !WP_ForcePowerUsable( self, FP_SPEED, 0 ) )
10062 	{
10063 		return;
10064 	}
10065 	if ( self->client->ps.saberLockTime > level.time )
10066 	{//FIXME: can this be a way to break out?
10067 		return;
10068 	}
10069 
10070 	WP_DebounceForceDeactivateTime( self );
10071 
10072 	WP_ForcePowerStart( self, FP_SPEED, 0 );
10073 	if ( duration )
10074 	{
10075 		self->client->ps.forcePowerDuration[FP_SPEED] = level.time + duration;
10076 	}
10077 	G_Sound( self, G_SoundIndex( "sound/weapons/force/speed.wav" ) );
10078 }
10079 
WP_StartForceHealEffects(gentity_t * self)10080 void WP_StartForceHealEffects( gentity_t *self )
10081 {
10082 	if ( self->ghoul2.size() )
10083 	{
10084 		if ( self->chestBolt != -1 )
10085 		{
10086 			G_PlayEffect( G_EffectIndex( "force/heal2" ), self->playerModel, self->chestBolt, self->s.number, self->currentOrigin, 3000, qtrue );
10087 		}
10088 		/*
10089 		if ( self->headBolt != -1 )
10090 		{
10091 			G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->headBolt, self->s.number, self->currentOrigin, 3000, qtrue );
10092 		}
10093 		if ( self->cervicalBolt != -1 )
10094 		{
10095 			G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->cervicalBolt, self->s.number, self->currentOrigin, 3000, qtrue );
10096 		}
10097 		if ( self->chestBolt != -1 )
10098 		{
10099 			G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->chestBolt, self->s.number, self->currentOrigin, 3000, qtrue );
10100 		}
10101 		if ( self->gutBolt != -1 )
10102 		{
10103 			G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->gutBolt, self->s.number, self->currentOrigin, 3000, qtrue );
10104 		}
10105 		if ( self->kneeLBolt != -1 )
10106 		{
10107 			G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->kneeLBolt, self->s.number, self->currentOrigin, 3000, qtrue );
10108 		}
10109 		if ( self->kneeRBolt != -1 )
10110 		{
10111 			G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->kneeRBolt, self->s.number, self->currentOrigin, 3000, qtrue );
10112 		}
10113 		if ( self->elbowLBolt != -1 )
10114 		{
10115 			G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->elbowLBolt, self->s.number, self->currentOrigin, 3000, qtrue );
10116 		}
10117 		if ( self->elbowRBolt != -1 )
10118 		{
10119 			G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->elbowRBolt, self->s.number, self->currentOrigin, 3000, qtrue );
10120 		}
10121 		*/
10122 	}
10123 }
10124 
WP_StopForceHealEffects(gentity_t * self)10125 void WP_StopForceHealEffects( gentity_t *self )
10126 {
10127 	if ( self->ghoul2.size() )
10128 	{
10129 		if ( self->chestBolt != -1 )
10130 		{
10131 			G_StopEffect( G_EffectIndex( "force/heal2" ), self->playerModel, self->chestBolt, self->s.number );
10132 		}
10133 		/*
10134 		if ( self->headBolt != -1 )
10135 		{
10136 			G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->headBolt, self->s.number );
10137 		}
10138 		if ( self->cervicalBolt != -1 )
10139 		{
10140 			G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->cervicalBolt, self->s.number );
10141 		}
10142 		if ( self->chestBolt != -1 )
10143 		{
10144 			G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->chestBolt, self->s.number );
10145 		}
10146 		if ( self->gutBolt != -1 )
10147 		{
10148 			G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->gutBolt, self->s.number );
10149 		}
10150 		if ( self->kneeLBolt != -1 )
10151 		{
10152 			G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->kneeLBolt, self->s.number );
10153 		}
10154 		if ( self->kneeRBolt != -1 )
10155 		{
10156 			G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->kneeRBolt, self->s.number );
10157 		}
10158 		if ( self->elbowLBolt != -1 )
10159 		{
10160 			G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->elbowLBolt, self->s.number );
10161 		}
10162 		if ( self->elbowRBolt != -1 )
10163 		{
10164 			G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->elbowRBolt, self->s.number );
10165 		}
10166 		*/
10167 	}
10168 }
10169 
FP_MaxForceHeal(gentity_t * self)10170 int FP_MaxForceHeal( gentity_t *self )
10171 {
10172 	if ( self->s.number >= MAX_CLIENTS )
10173 	{
10174 		return MAX_FORCE_HEAL_HARD;
10175 	}
10176 	switch ( g_spskill->integer )
10177 	{
10178 	case 0://easy
10179 		return MAX_FORCE_HEAL_EASY;
10180 		break;
10181 	case 1://medium
10182 		return MAX_FORCE_HEAL_MEDIUM;
10183 		break;
10184 	case 2://hard
10185 	default:
10186 		return MAX_FORCE_HEAL_HARD;
10187 		break;
10188 	}
10189 }
10190 
FP_ForceHealInterval(gentity_t * self)10191 int FP_ForceHealInterval( gentity_t *self )
10192 {
10193 	return (self->client->ps.forcePowerLevel[FP_HEAL]>FORCE_LEVEL_2)?50:FORCE_HEAL_INTERVAL;
10194 }
10195 
ForceHeal(gentity_t * self)10196 void ForceHeal( gentity_t *self )
10197 {
10198 	if ( self->health <= 0 || self->client->ps.stats[STAT_MAX_HEALTH] <= self->health )
10199 	{
10200 		return;
10201 	}
10202 
10203 	if ( !WP_ForcePowerUsable( self, FP_HEAL, 20 ) )
10204 	{//must have enough force power for at least 5 points of health
10205 		return;
10206 	}
10207 
10208 	if ( self->painDebounceTime > level.time || (self->client->ps.weaponTime&&self->client->ps.weapon!=WP_NONE) )
10209 	{//can't initiate a heal while taking pain or attacking
10210 		return;
10211 	}
10212 
10213 	if ( self->client->ps.saberLockTime > level.time )
10214 	{//FIXME: can this be a way to break out?
10215 		return;
10216 	}
10217 	/*
10218 	if ( self->client->ps.forcePowerLevel[FP_HEAL] > FORCE_LEVEL_2 )
10219 	{//instant heal
10220 		//no more than available force power
10221 		int max = self->client->ps.forcePower;
10222 		if ( max > MAX_FORCE_HEAL )
10223 		{//no more than max allowed
10224 			max = MAX_FORCE_HEAL;
10225 		}
10226 		if ( max > self->client->ps.stats[STAT_MAX_HEALTH] - self->health )
10227 		{//no more than what's missing
10228 			max = self->client->ps.stats[STAT_MAX_HEALTH] - self->health;
10229 		}
10230 		self->health += max;
10231 		WP_ForcePowerDrain( self, FP_HEAL, max );
10232 		G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d.mp3", Q_irand( 1, 4 ) ) );
10233 	}
10234 	else
10235 	*/
10236 	{
10237 		//start health going up
10238 		//NPC_SetAnim( self, SETANIM_TORSO, ?, SETANIM_FLAG_OVERRIDE );
10239 		WP_ForcePowerStart( self, FP_HEAL, 0 );
10240 		if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 )
10241 		{//must meditate
10242 			//FIXME: holster weapon (select WP_NONE?)
10243 			//FIXME: BOTH_FORCEHEAL_START
10244 			NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCEHEAL_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
10245 			self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
10246 			self->client->ps.saberBlocked = BLOCKED_NONE;
10247 			self->client->ps.torsoAnimTimer = self->client->ps.legsAnimTimer = FP_ForceHealInterval(self)*FP_MaxForceHeal(self) + 2000;//???
10248 			WP_DeactivateSaber( self );//turn off saber when meditating
10249 		}
10250 		else
10251 		{//just a quick gesture
10252 			/*
10253 			//Can't get an anim that looks good...
10254 			NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEHEAL_QUICK, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
10255 			self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
10256 			self->client->ps.saberBlocked = BLOCKED_NONE;
10257 			*/
10258 		}
10259 	}
10260 
10261 	//FIXME: always play healing effect
10262 	G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/heal.mp3" );
10263 }
10264 
10265 extern void NPC_PlayConfusionSound( gentity_t *self );
10266 extern void NPC_Jedi_PlayConfusionSound( gentity_t *self );
WP_CheckBreakControl(gentity_t * self)10267 qboolean WP_CheckBreakControl( gentity_t *self )
10268 {
10269 	if ( !self )
10270 	{
10271 		return qfalse;
10272 	}
10273 	if ( !self->s.number )
10274 	{//player
10275 		if ( self->client && self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
10276 		{//control-level
10277 			if ( self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_WORLD )
10278 			{//we are in a viewentity
10279 				gentity_t *controlled = &g_entities[self->client->ps.viewEntity];
10280 				if ( controlled->NPC && controlled->NPC->controlledTime > level.time )
10281 				{//it is an NPC we controlled
10282 					//clear it and return
10283 					G_ClearViewEntity( self );
10284 					return qtrue;
10285 				}
10286 			}
10287 		}
10288 	}
10289 	else
10290 	{//NPC
10291 		if ( self->NPC && self->NPC->controlledTime > level.time )
10292 		{//being controlled
10293 			gentity_t *controller = &g_entities[0];
10294 			if ( controller->client && controller->client->ps.viewEntity == self->s.number )
10295 			{//we are being controlled by player
10296 				if ( controller->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
10297 				{//control-level mind trick
10298 					//clear the control and return
10299 					G_ClearViewEntity( controller );
10300 					return qtrue;
10301 				}
10302 			}
10303 		}
10304 	}
10305 	return qfalse;
10306 }
10307 extern bool	Pilot_AnyVehiclesRegistered();
10308 
ForceTelepathy(gentity_t * self)10309 void ForceTelepathy( gentity_t *self )
10310 {
10311 	trace_t	tr;
10312 	vec3_t	end, forward;
10313 	gentity_t	*traceEnt;
10314 	qboolean	targetLive = qfalse;
10315 
10316 	if ( WP_CheckBreakControl( self ) )
10317 	{
10318 		return;
10319 	}
10320 	if ( self->health <= 0 )
10321 	{
10322 		return;
10323 	}
10324 	//FIXME: if mind trick 3 and aiming at an enemy need more force power
10325 	if ( !WP_ForcePowerUsable( self, FP_TELEPATHY, 0 ) )
10326 	{
10327 		return;
10328 	}
10329 
10330 	if ( self->client->ps.weaponTime >= 800 )
10331 	{//just did one!
10332 		return;
10333 	}
10334 	if ( self->client->ps.saberLockTime > level.time )
10335 	{//FIXME: can this be a way to break out?
10336 		return;
10337 	}
10338 
10339 	AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
10340 	VectorNormalize( forward );
10341 	VectorMA( self->client->renderInfo.eyePoint, 2048, forward, end );
10342 
10343 	//Cause a distraction if enemy is not fighting
10344 	gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_OPAQUE|CONTENTS_BODY, (EG2_Collision)0, 0 );
10345 	if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
10346 	{
10347 		return;
10348 	}
10349 
10350 	traceEnt = &g_entities[tr.entityNum];
10351 
10352 	if( traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE )
10353 	{
10354 		return;
10355 	}
10356 
10357 	if ( traceEnt && traceEnt->client  )
10358 	{
10359 		switch ( traceEnt->client->NPC_class )
10360 		{
10361 		case CLASS_GALAKMECH://cant grip him, he's in armor
10362 		case CLASS_ATST://much too big to grip!
10363 		//no droids either
10364 		case CLASS_PROBE:
10365 		case CLASS_GONK:
10366 		case CLASS_R2D2:
10367 		case CLASS_R5D2:
10368 		case CLASS_MARK1:
10369 		case CLASS_MARK2:
10370 		case CLASS_MOUSE:
10371 		case CLASS_SEEKER:
10372 		case CLASS_REMOTE:
10373 		case CLASS_PROTOCOL:
10374 		case CLASS_ASSASSIN_DROID:
10375 		case CLASS_SABER_DROID:
10376 		case CLASS_BOBAFETT:
10377 			break;
10378 		case CLASS_RANCOR:
10379 			if ( !(traceEnt->spawnflags&1) )
10380 			{
10381 				targetLive = qtrue;
10382 			}
10383 			break;
10384 		default:
10385 			targetLive = qtrue;
10386 			break;
10387 		}
10388 	}
10389 	if ( targetLive
10390 		&& traceEnt->NPC
10391 		&& traceEnt->health > 0 )
10392 	{//hit an organic non-player
10393 		if ( G_ActivateBehavior( traceEnt, BSET_MINDTRICK ) )
10394 		{//activated a script on him
10395 			//FIXME: do the visual sparkles effect on their heads, still?
10396 			WP_ForcePowerStart( self, FP_TELEPATHY, 0 );
10397 		}
10398 		else if ( traceEnt->client->playerTeam != self->client->playerTeam )
10399 		{//an enemy
10400 			int override = 0;
10401 			if ( (traceEnt->NPC->scriptFlags&SCF_NO_MIND_TRICK) )
10402 			{
10403 				if ( traceEnt->client->NPC_class == CLASS_GALAKMECH )
10404 				{
10405 					G_AddVoiceEvent( traceEnt, Q_irand( EV_CONFUSE1, EV_CONFUSE3 ), Q_irand( 3000, 5000 ) );
10406 				}
10407 			}
10408 			else if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
10409 			{//control them, even jedi
10410 				G_SetViewEntity( self, traceEnt );
10411 				traceEnt->NPC->controlledTime = level.time + 30000;
10412 			}
10413 			else if ( traceEnt->s.weapon != WP_SABER
10414 				&& traceEnt->client->NPC_class != CLASS_REBORN )
10415 			{//haha!  Jedi aren't easily confused!
10416 				if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_2
10417 					&& traceEnt->s.weapon != WP_NONE		//don't charm people who aren't capable of fighting... like ugnaughts and droids, just confuse them
10418 					&& traceEnt->client->NPC_class != CLASS_TUSKEN//don't charm them, just confuse them
10419 					&& traceEnt->client->NPC_class != CLASS_NOGHRI//don't charm them, just confuse them
10420 					&& !Pilot_AnyVehiclesRegistered()		//also, don't charm guys when bikes are near
10421 					)
10422 				{//turn them to our side
10423 					//if mind trick 3 and aiming at an enemy need more force power
10424 					override = 50;
10425 					if ( self->client->ps.forcePower < 50 )
10426 					{
10427 						return;
10428 					}
10429 					if ( traceEnt->enemy )
10430 					{
10431 						G_ClearEnemy( traceEnt );
10432 					}
10433 					if ( traceEnt->NPC )
10434 					{
10435 						//traceEnt->NPC->tempBehavior = BS_FOLLOW_LEADER;
10436 						traceEnt->client->leader = self;
10437 					}
10438 					//FIXME: maybe pick an enemy right here?
10439 					//FIXME: does nothing to TEAM_FREE and TEAM_NEUTRALs!!!
10440 					team_t	saveTeam = traceEnt->client->enemyTeam;
10441 					traceEnt->client->enemyTeam = traceEnt->client->playerTeam;
10442 					traceEnt->client->playerTeam = saveTeam;
10443 					//FIXME: need a *charmed* timer on this...?  Or do TEAM_PLAYERS assume that "confusion" means they should switch to team_enemy when done?
10444 					traceEnt->NPC->charmedTime = level.time + mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]];
10445 					if ( traceEnt->ghoul2.size() && traceEnt->headBolt != -1 )
10446 					{//FIXME: what if already playing effect?
10447 						G_PlayEffect( G_EffectIndex( "force/confusion" ), traceEnt->playerModel, traceEnt->headBolt, traceEnt->s.number, traceEnt->currentOrigin, mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]], qtrue );
10448 					}
10449 				}
10450 				else
10451 				{//just confuse them
10452 					//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?
10453 					traceEnt->NPC->confusionTime = level.time + mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]];//confused for about 10 seconds
10454 					if ( traceEnt->ghoul2.size() && traceEnt->headBolt != -1 )
10455 					{//FIXME: what if already playing effect?
10456 						G_PlayEffect( G_EffectIndex( "force/confusion" ), traceEnt->playerModel, traceEnt->headBolt, traceEnt->s.number, traceEnt->currentOrigin, mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]], qtrue );
10457 					}
10458 					NPC_PlayConfusionSound( traceEnt );
10459 					if ( traceEnt->enemy )
10460 					{
10461 						G_ClearEnemy( traceEnt );
10462 					}
10463 				}
10464 			}
10465 			else
10466 			{
10467 				NPC_Jedi_PlayConfusionSound( traceEnt );
10468 			}
10469 			WP_ForcePowerStart( self, FP_TELEPATHY, override );
10470 		}
10471 		else if ( traceEnt->client->playerTeam == self->client->playerTeam )
10472 		{//an ally
10473 			//maybe just have him look at you?  Respond?  Take your enemy?
10474 			if ( traceEnt->client->ps.pm_type < PM_DEAD && traceEnt->NPC!=NULL && !(traceEnt->NPC->scriptFlags&SCF_NO_RESPONSE) )
10475 			{
10476 				NPC_UseResponse( traceEnt, self, qfalse );
10477 				WP_ForcePowerStart( self, FP_TELEPATHY, 1 );
10478 			}
10479 		}//NOTE: no effect on TEAM_NEUTRAL?
10480 		vec3_t	eyeDir;
10481 		AngleVectors( traceEnt->client->renderInfo.eyeAngles, eyeDir, NULL, NULL );
10482 		VectorNormalize( eyeDir );
10483 		G_PlayEffect( "force/force_touch", traceEnt->client->renderInfo.eyePoint, eyeDir );
10484 
10485 		//make sure this plays and that you cannot press fire for about 1 second after this
10486 		//FIXME: BOTH_FORCEMINDTRICK or BOTH_FORCEDISTRACT
10487 		NPC_SetAnim( self, SETANIM_TORSO, BOTH_MINDTRICK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD );
10488 		//FIXME: build-up or delay this until in proper part of anim
10489 	}
10490 	else
10491 	{
10492 		if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_1 && tr.fraction * 2048 > 64 )
10493 		{//don't create a diversion less than 64 from you of if at power level 1
10494 			//use distraction anim instead
10495 			G_PlayEffect( G_EffectIndex( "force/force_touch" ), tr.endpos, tr.plane.normal );
10496 			//FIXME: these events don't seem to always be picked up...?
10497 			AddSoundEvent( self, tr.endpos, 512, AEL_SUSPICIOUS, qtrue, qtrue );
10498 			AddSightEvent( self, tr.endpos, 512, AEL_SUSPICIOUS, 50 );
10499 			WP_ForcePowerStart( self, FP_TELEPATHY, 0 );
10500 		}
10501 		NPC_SetAnim( self, SETANIM_TORSO, BOTH_MINDTRICK2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD );
10502 	}
10503 	self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
10504 	self->client->ps.saberBlocked = BLOCKED_NONE;
10505 	self->client->ps.weaponTime = 1000;
10506 	if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
10507 	{
10508 		self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
10509 	}
10510 }
10511 
10512 //rww - RAGDOLL_BEGIN
10513 //#define JK2_RAGDOLL_GRIPNOHEALTH
10514 //rww - RAGDOLL_END
10515 
ForceGrip(gentity_t * self)10516 void ForceGrip( gentity_t *self )
10517 {//FIXME: make enemy Jedi able to use this
10518 	trace_t	tr;
10519 	vec3_t	end, forward;
10520 	gentity_t	*traceEnt = NULL;
10521 
10522 	if ( self->health <= 0 )
10523 	{
10524 		return;
10525 	}
10526 	if ( !self->s.number && (cg.zoomMode || in_camera) )
10527 	{//can't force grip when zoomed in or in cinematic
10528 		return;
10529 	}
10530 	if ( self->client->ps.leanofs )
10531 	{//can't force-grip while leaning
10532 		return;
10533 	}
10534 
10535 	if ( self->client->ps.forceGripEntityNum <= ENTITYNUM_WORLD )
10536 	{//already gripping
10537 		if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
10538 		{
10539 			self->client->ps.forcePowerDuration[FP_GRIP] = level.time + 100;
10540 			self->client->ps.weaponTime = 1000;
10541 			if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
10542 			{
10543 				self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
10544 			}
10545 		}
10546 		return;
10547 	}
10548 
10549 	if ( !WP_ForcePowerUsable( self, FP_GRIP, 0 ) )
10550 	{//can't use it right now
10551 		return;
10552 	}
10553 
10554 	if ( self->client->ps.forcePower < 26 )
10555 	{//need 20 to start, 6 to hold it for any decent amount of time...
10556 		return;
10557 	}
10558 
10559 	if ( self->client->ps.weaponTime )
10560 	{//busy
10561 		return;
10562 	}
10563 
10564 	if ( self->client->ps.saberLockTime > level.time )
10565 	{//FIXME: can this be a way to break out?
10566 		return;
10567 	}
10568 	//Cause choking anim + health drain in ent in front of me
10569 	NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEGRIP_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
10570 	self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
10571 	self->client->ps.saberBlocked = BLOCKED_NONE;
10572 
10573 	self->client->ps.weaponTime = 1000;
10574 	if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
10575 	{
10576 		self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
10577 	}
10578 
10579 	AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
10580 	VectorNormalize( forward );
10581 	VectorMA( self->client->renderInfo.handLPoint, FORCE_GRIP_DIST, forward, end );
10582 
10583 	if ( self->enemy )
10584 	{//I have an enemy
10585 		if ( !self->enemy->message
10586 			&& !(self->flags&FL_NO_KNOCKBACK) )
10587 		{//don't auto-pickup guys with keys
10588 			if ( DistanceSquared( self->enemy->currentOrigin, self->currentOrigin ) < FORCE_GRIP_DIST_SQUARED )
10589 			{//close enough to grab
10590 				float minDot = 0.5f;
10591 				if ( self->s.number < MAX_CLIENTS )
10592 				{//player needs to be facing more directly
10593 					minDot = 0.2f;
10594 				}
10595 				if ( InFront( self->enemy->currentOrigin, self->client->renderInfo.eyePoint, self->client->ps.viewangles, minDot ) ) //self->s.number || //NPCs can always lift enemy since we assume they're looking at them...?
10596 				{//need to be facing the enemy
10597 					if ( gi.inPVS( self->enemy->currentOrigin, self->client->renderInfo.eyePoint ) )
10598 					{//must be in PVS
10599 						gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, self->enemy->currentOrigin, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
10600 						if ( tr.fraction == 1.0f || tr.entityNum == self->enemy->s.number )
10601 						{//must have clear LOS
10602 							traceEnt = self->enemy;
10603 						}
10604 					}
10605 				}
10606 			}
10607 		}
10608 	}
10609 	if ( !traceEnt )
10610 	{//okay, trace straight ahead and see what's there
10611 		gi.trace( &tr, self->client->renderInfo.handLPoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
10612 		if ( tr.entityNum >= ENTITYNUM_WORLD || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
10613 		{
10614 			return;
10615 		}
10616 
10617 		traceEnt = &g_entities[tr.entityNum];
10618 	}
10619 //rww - RAGDOLL_BEGIN
10620 #ifdef JK2_RAGDOLL_GRIPNOHEALTH
10621 	if ( !traceEnt || traceEnt == self/*???*/ || traceEnt->bmodel || (traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE) )
10622 	{
10623 		return;
10624 	}
10625 #else
10626 //rww - RAGDOLL_END
10627 	if ( !traceEnt || traceEnt == self/*???*/ || traceEnt->bmodel || (traceEnt->health <= 0 && traceEnt->takedamage) || (traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE) )
10628 	{
10629 		return;
10630 	}
10631 //rww - RAGDOLL_BEGIN
10632 #endif
10633 //rww - RAGDOLL_END
10634 
10635 	if ( traceEnt->m_pVehicle != NULL )
10636 	{//is it a vehicle
10637 		//grab pilot if there is one
10638 		if ( traceEnt->m_pVehicle->m_pPilot != NULL
10639 			&& traceEnt->m_pVehicle->m_pPilot->client != NULL )
10640 		{//grip the pilot
10641 			traceEnt = traceEnt->m_pVehicle->m_pPilot;
10642 		}
10643 		else
10644 		{//can't grip a vehicle
10645 			return;
10646 		}
10647 	}
10648 	if ( traceEnt->client )
10649 	{
10650 		if ( traceEnt->client->ps.forceJumpZStart )
10651 		{//can't catch them in mid force jump - FIXME: maybe base it on velocity?
10652 			return;
10653 		}
10654 		if ( traceEnt->client->ps.pullAttackTime > level.time )
10655 		{//can't grip someone who is being pull-attacked or is pull-attacking
10656 			return;
10657 		}
10658 		if ( !Q_stricmp("Yoda",traceEnt->NPC_type) )
10659 		{
10660 			Jedi_PlayDeflectSound( traceEnt );
10661 			ForceThrow( traceEnt, qfalse );
10662 			return;
10663 		}
10664 
10665 		if ( G_IsRidingVehicle( traceEnt )
10666 			&& (traceEnt->s.eFlags&EF_NODRAW) )
10667 		{//riding *inside* vehicle
10668 			return;
10669 		}
10670 
10671 		switch ( traceEnt->client->NPC_class )
10672 		{
10673 		case CLASS_GALAKMECH://cant grip him, he's in armor
10674 			G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), Q_irand( 3000, 5000 ) );
10675 			return;
10676 			break;
10677 		case CLASS_HAZARD_TROOPER://cant grip him, he's in armor
10678 			return;
10679 			break;
10680 		case CLASS_ATST://much too big to grip!
10681 		case CLASS_RANCOR://much too big to grip!
10682 		case CLASS_WAMPA://much too big to grip!
10683 		case CLASS_SAND_CREATURE://much too big to grip!
10684 			return;
10685 			break;
10686 		//no droids either...?
10687 		case CLASS_GONK:
10688 		case CLASS_R2D2:
10689 		case CLASS_R5D2:
10690 		case CLASS_MARK1:
10691 		case CLASS_MARK2:
10692 		case CLASS_MOUSE://?
10693 		case CLASS_PROTOCOL:
10694 			//*sigh*... in JK3, you'll be able to grab and move *anything*...
10695 			return;
10696 			break;
10697 		//not even combat droids?  (No animation for being gripped...)
10698 		case CLASS_SABER_DROID:
10699 		case CLASS_ASSASSIN_DROID:
10700 			//*sigh*... in JK3, you'll be able to grab and move *anything*...
10701 			return;
10702 			break;
10703 		case CLASS_PROBE:
10704 		case CLASS_SEEKER:
10705 		case CLASS_REMOTE:
10706 		case CLASS_SENTRY:
10707 		case CLASS_INTERROGATOR:
10708 			//*sigh*... in JK3, you'll be able to grab and move *anything*...
10709 			return;
10710 			break;
10711 		case CLASS_DESANN://Desann cannot be gripped, he just pushes you back instantly
10712 		case CLASS_KYLE:
10713 		case CLASS_TAVION:
10714 		case CLASS_LUKE:
10715 			Jedi_PlayDeflectSound( traceEnt );
10716 			ForceThrow( traceEnt, qfalse );
10717 			return;
10718 			break;
10719 		case CLASS_REBORN:
10720 		case CLASS_SHADOWTROOPER:
10721 		case CLASS_ALORA:
10722 		case CLASS_JEDI:
10723 			if ( traceEnt->NPC && traceEnt->NPC->rank > RANK_CIVILIAN && self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
10724 			{
10725 				Jedi_PlayDeflectSound( traceEnt );
10726 				ForceThrow( traceEnt, qfalse );
10727 				return;
10728 			}
10729 			break;
10730 		default:
10731 			break;
10732 		}
10733 		if ( traceEnt->s.weapon == WP_EMPLACED_GUN )
10734 		{//FIXME: maybe can pull them out?
10735 			return;
10736 		}
10737 		if ( self->enemy && traceEnt != self->enemy && traceEnt->client->playerTeam == self->client->playerTeam )
10738 		{//can't accidently grip your teammate in combat
10739 			return;
10740 		}
10741 //=CHECKABSORB===
10742 		if ( -1 != WP_AbsorbConversion( traceEnt, traceEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_GRIP, self->client->ps.forcePowerLevel[FP_GRIP], forcePowerNeeded[self->client->ps.forcePowerLevel[FP_GRIP]]) )
10743 		{
10744 			//WP_ForcePowerStop( self, FP_GRIP );
10745 			return;
10746 		}
10747 //===============
10748 	}
10749 	else
10750 	{//can't grip non-clients... right?
10751 		//FIXME: Make it so objects flagged as "grabbable" are let through
10752 		//if ( Q_stricmp( "misc_model_breakable", traceEnt->classname ) || !(traceEnt->s.eFlags&EF_BOUNCE_HALF) || !traceEnt->physicsBounce )
10753 		{
10754 			return;
10755 		}
10756 	}
10757 
10758 	// Make sure to turn off Force Protection and Force Absorb.
10759 	if (self->client->ps.forcePowersActive & (1 << FP_PROTECT) )
10760 	{
10761 		WP_ForcePowerStop( self, FP_PROTECT );
10762 	}
10763 	if (self->client->ps.forcePowersActive & (1 << FP_ABSORB) )
10764 	{
10765 		WP_ForcePowerStop( self, FP_ABSORB );
10766 	}
10767 
10768 	WP_ForcePowerStart( self, FP_GRIP, 20 );
10769 	//FIXME: rule out other things?
10770 	//FIXME: Jedi resist, like the push and pull?
10771 	self->client->ps.forceGripEntityNum = traceEnt->s.number;
10772 	if ( traceEnt->client )
10773 	{
10774 		Vehicle_t *pVeh;
10775 		if ( ( pVeh = G_IsRidingVehicle( traceEnt ) ) != NULL )
10776 		{//riding vehicle? pull him off!
10777 			//FIXME: if in an AT-ST or X-Wing, shouldn't do this... :)
10778 			//pull him off of it
10779 			//((CVehicleNPC *)traceEnt->NPC)->Eject( traceEnt );
10780 			pVeh->m_pVehicleInfo->Eject( pVeh, traceEnt, qtrue );
10781 			//G_DriveVehicle( traceEnt, NULL, NULL );
10782 		}
10783 		G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
10784 		if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 || traceEnt->s.weapon == WP_SABER )
10785 		{//if we pick up & carry, drop their weap
10786 			if ( traceEnt->s.weapon
10787 				&& traceEnt->client->NPC_class != CLASS_ROCKETTROOPER
10788 				&& traceEnt->client->NPC_class != CLASS_VEHICLE
10789 				&& traceEnt->client->NPC_class != CLASS_HAZARD_TROOPER
10790 				&& traceEnt->client->NPC_class != CLASS_TUSKEN
10791 				&& traceEnt->client->NPC_class != CLASS_BOBAFETT
10792 				&& traceEnt->client->NPC_class != CLASS_ASSASSIN_DROID
10793 				&& traceEnt->s.weapon != WP_CONCUSSION	// so rax can't drop his
10794 				)
10795 			{
10796 				if ( traceEnt->client->NPC_class == CLASS_BOBAFETT )
10797 				{//he doesn't drop them, just puts it away
10798 					ChangeWeapon( traceEnt, WP_MELEE );
10799 				}
10800 				else if ( traceEnt->s.weapon == WP_MELEE )
10801 				{//they can't take that away from me, oh no...
10802 				}
10803 				else if ( traceEnt->NPC
10804 					&& (traceEnt->NPC->scriptFlags&SCF_DONT_FLEE) )
10805 				{//*SIGH*... if an NPC can't flee, they can't run after and pick up their weapon, do don't drop it
10806 				}
10807 				else if ( traceEnt->s.weapon != WP_SABER )
10808 				{
10809 					WP_DropWeapon( traceEnt, NULL );
10810 				}
10811 				else
10812 				{
10813 					//turn it off?
10814 					traceEnt->client->ps.SaberDeactivate();
10815 					G_SoundOnEnt( traceEnt, CHAN_WEAPON, "sound/weapons/saber/saberoffquick.wav" );
10816 				}
10817 			}
10818 		}
10819 		//else FIXME: need a one-armed choke if we're not on a high enough level to make them drop their gun
10820 		VectorCopy( traceEnt->client->renderInfo.headPoint, self->client->ps.forceGripOrg );
10821 	}
10822 	else
10823 	{
10824 		VectorCopy( traceEnt->currentOrigin, self->client->ps.forceGripOrg );
10825 	}
10826 	self->client->ps.forceGripOrg[2] += 48;//FIXME: define?
10827 	if ( self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
10828 	{//just a duration
10829 		self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 250;
10830 		self->client->ps.forcePowerDuration[FP_GRIP] = level.time + 5000;
10831 
10832 		if ( self->m_pVehicle && self->m_pVehicle->m_pVehicleInfo->Inhabited( self->m_pVehicle ) )
10833 		{//empty vehicles don't make gripped noise
10834 			traceEnt->s.loopSound = G_SoundIndex( "sound/weapons/force/grip.mp3" );
10835 		}
10836 	}
10837 	else
10838 	{
10839 		if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_2 )
10840 		{//lifting sound?  or always?
10841 		}
10842 		//if ( traceEnt->s.number )
10843 		{//picks them up for a second first
10844 			self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 1000;
10845 		}
10846 		/*
10847 		else
10848 		{//player should take damage right away
10849 			self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 250;
10850 		}
10851 		*/
10852 	// force grip sound should only play when the target is alive?
10853 	//	if (traceEnt->health>0)
10854 	//	{
10855 			self->s.loopSound = G_SoundIndex( "sound/weapons/force/grip.mp3" );
10856 	//	}
10857 	}
10858 }
10859 
ForceLightningCheck2Handed(gentity_t * self)10860 qboolean ForceLightningCheck2Handed( gentity_t *self )
10861 {
10862 	if ( self && self->client )
10863 	{
10864 		if ( self->s.weapon == WP_NONE
10865 			||  self->s.weapon == WP_MELEE
10866 			|| (self->s.weapon == WP_SABER && !self->client->ps.SaberActive()) )
10867 		{
10868 			return qtrue;
10869 		}
10870 	}
10871 	return qfalse;
10872 }
10873 
ForceLightningAnim(gentity_t * self)10874 void ForceLightningAnim( gentity_t *self )
10875 {
10876 	if ( !self || !self->client )
10877 	{
10878 		return;
10879 	}
10880 
10881 	//one-handed lightning 2 and above
10882 	int startAnim = BOTH_FORCELIGHTNING_START;
10883 	int holdAnim = BOTH_FORCELIGHTNING_HOLD;
10884 
10885 	if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] >= FORCE_LEVEL_3
10886 		&& ForceLightningCheck2Handed( self ) )
10887 	{//empty handed lightning 3
10888 		startAnim = BOTH_FORCE_2HANDEDLIGHTNING_START;
10889 		holdAnim = BOTH_FORCE_2HANDEDLIGHTNING_HOLD;
10890 	}
10891 
10892 	//FIXME: if standing still, play on whole body?  Especially 2-handed version
10893 	if ( self->client->ps.torsoAnim == startAnim )
10894 	{
10895 		if ( !self->client->ps.torsoAnimTimer )
10896 		{
10897 			NPC_SetAnim( self, SETANIM_TORSO, holdAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
10898 		}
10899 		else
10900 		{
10901 			NPC_SetAnim( self, SETANIM_TORSO, startAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
10902 		}
10903 	}
10904 	else
10905 	{
10906 		NPC_SetAnim( self, SETANIM_TORSO, holdAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
10907 	}
10908 }
10909 
ForceLightning(gentity_t * self)10910 void ForceLightning( gentity_t *self )
10911 {
10912 	if ( self->health <= 0 )
10913 	{
10914 		return;
10915 	}
10916 	if ( !self->s.number && (cg.zoomMode || in_camera) )
10917 	{//can't force lightning when zoomed in or in cinematic
10918 		return;
10919 	}
10920 	if ( self->client->ps.leanofs )
10921 	{//can't force-lightning while leaning
10922 		return;
10923 	}
10924 	if ( self->client->ps.forcePower < 25 || !WP_ForcePowerUsable( self, FP_LIGHTNING, 0 ) )
10925 	{
10926 		return;
10927 	}
10928 	if ( self->client->ps.forcePowerDebounce[FP_LIGHTNING] > level.time )
10929 	{//stops it while using it and also after using it, up to 3 second delay
10930 		return;
10931 	}
10932 	if ( self->client->ps.saberLockTime > level.time )
10933 	{//FIXME: can this be a way to break out?
10934 		return;
10935 	}
10936 	// Make sure to turn off Force Protection and Force Absorb.
10937 	if (self->client->ps.forcePowersActive & (1 << FP_PROTECT) )
10938 	{
10939 		WP_ForcePowerStop( self, FP_PROTECT );
10940 	}
10941 	if (self->client->ps.forcePowersActive & (1 << FP_ABSORB) )
10942 	{
10943 		WP_ForcePowerStop( self, FP_ABSORB );
10944 	}
10945 	//Shoot lightning from hand
10946 	//make sure this plays and that you cannot press fire for about 1 second after this
10947 	if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
10948 	{
10949 		NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
10950 	}
10951 	else
10952 	{
10953 		ForceLightningAnim( self );
10954 		/*
10955 		if ( ForceLightningCheck2Handed( self ) )
10956 		{//empty handed lightning 3
10957 			NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_2HANDEDLIGHTNING_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
10958 		}
10959 		else
10960 		{//one-handed lightning 3
10961 			NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
10962 		}
10963 		*/
10964 	}
10965 	self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
10966 	self->client->ps.saberBlocked = BLOCKED_NONE;
10967 
10968 	G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/lightning.wav" );
10969 	if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
10970 	{//short burst
10971 		//G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/lightning.wav" );
10972 	}
10973 	else
10974 	{//holding it
10975 		self->s.loopSound = G_SoundIndex( "sound/weapons/force/lightning2.wav" );
10976 	}
10977 
10978 	//FIXME: build-up or delay this until in proper part of anim
10979 	self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
10980 	WP_ForcePowerStart( self, FP_LIGHTNING, self->client->ps.torsoAnimTimer );
10981 }
10982 
ForceLightningDamage(gentity_t * self,gentity_t * traceEnt,vec3_t dir,float dist,float dot,vec3_t impactPoint)10983 void ForceLightningDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, float dist, float dot, vec3_t impactPoint )
10984 {
10985 	if( traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE )
10986 	{
10987 		return;
10988 	}
10989 
10990 	if ( traceEnt && traceEnt->takedamage )
10991 	{
10992 		if ( !traceEnt->client || traceEnt->client->playerTeam != self->client->playerTeam || self->enemy == traceEnt || traceEnt->enemy == self )
10993 		{//an enemy or object
10994 			int	dmg;
10995 			//FIXME: check for client using FP_ABSORB
10996 			if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 )
10997 			{//more damage if closer and more in front
10998 				dmg = 1;
10999 				if ( self->client->NPC_class == CLASS_REBORN
11000 					&& self->client->ps.weapon == WP_NONE )
11001 				{//Cultist: looks fancy, but does less damage
11002 				}
11003 				else
11004 				{
11005 					if ( dist < 100 )
11006 					{
11007 						dmg += 2;
11008 					}
11009 					else if ( dist < 200 )
11010 					{
11011 						dmg += 1;
11012 					}
11013 					if ( dot > 0.9f )
11014 					{
11015 						dmg += 2;
11016 					}
11017 					else if ( dot > 0.7f )
11018 					{
11019 						dmg += 1;
11020 					}
11021 				}
11022 				if ( self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING
11023 					|| self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_START
11024 					|| self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_HOLD
11025 					|| self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_RELEASE )
11026 				{//jackin' 'em up, Palpatine-style
11027 					dmg *= 2;
11028 				}
11029 			}
11030 			else
11031 			{
11032 				dmg = Q_irand( 1, 3 );//*self->client->ps.forcePowerLevel[FP_LIGHTNING];
11033 			}
11034 
11035 			if ( traceEnt->client
11036 				&& traceEnt->health > 0
11037 				&& traceEnt->NPC
11038 				&& (traceEnt->NPC->aiFlags&NPCAI_BOSS_CHARACTER) )
11039 			{//Luke, Desann Tavion and Kyle can shield themselves from the attack
11040 				//FIXME: shield effect or something?
11041 				int parts;
11042 				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 ) )
11043 				{//if on a surface and not in a spin or flip, play full body resist
11044 					parts = SETANIM_BOTH;
11045 				}
11046 				else
11047 				{//play resist just in torso
11048 					parts = SETANIM_TORSO;
11049 				}
11050 				//FIXME: don't interrupt big anims with this!
11051 				NPC_SetAnim( traceEnt, parts, BOTH_RESISTPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
11052 				Jedi_PlayDeflectSound( traceEnt );
11053 				dmg = Q_irand(0,1);
11054 			}
11055 			else if ( traceEnt->s.weapon == WP_SABER )
11056 			{//saber can block lightning
11057 				if ( traceEnt->client //a client
11058 					&& !traceEnt->client->ps.saberInFlight//saber in hand
11059 					&& ( traceEnt->client->ps.saberMove == LS_READY || PM_SaberInParry( traceEnt->client->ps.saberMove ) || PM_SaberInReturn( traceEnt->client->ps.saberMove ) )//not attacking with saber
11060 					&& InFOV( self->currentOrigin, traceEnt->currentOrigin, traceEnt->client->ps.viewangles, 20, 35 ) //I'm in front of them
11061 					&& !PM_InKnockDown( &traceEnt->client->ps ) //they're not in a knockdown
11062 					&& !PM_SuperBreakLoseAnim( traceEnt->client->ps.torsoAnim )
11063 					&& !PM_SuperBreakWinAnim( traceEnt->client->ps.torsoAnim )
11064 					&& !PM_SaberInSpecialAttack( traceEnt->client->ps.torsoAnim )
11065 					&& !PM_InSpecialJump( traceEnt->client->ps.torsoAnim )
11066 					&& (!traceEnt->s.number||(traceEnt->NPC&&traceEnt->NPC->rank>=RANK_LT_COMM)) )//the player or a tough jedi/reborn
11067 				{
11068 					if ( Q_irand( 0, traceEnt->client->ps.forcePowerLevel[FP_SABER_DEFENSE]*3 ) > 0 )//more of a chance of defending if saber defense is high
11069 					{
11070 						dmg = 0;
11071 					}
11072 					if ( (traceEnt->client->ps.forcePowersActive&(1<<FP_ABSORB))
11073 						&& traceEnt->client->ps.forcePowerLevel[FP_ABSORB] > FORCE_LEVEL_2 )
11074 					{//no parry, just absorb
11075 					}
11076 					else
11077 					{
11078 						//make them do a parry
11079 						traceEnt->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
11080 						int parryReCalcTime = Jedi_ReCalcParryTime( traceEnt, EVASION_PARRY );
11081 						if ( traceEnt->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] < level.time + parryReCalcTime )
11082 						{
11083 							traceEnt->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + parryReCalcTime;
11084 						}
11085 						traceEnt->client->ps.weaponTime = Q_irand( 100, 300 );//hold this move - can't attack! - FIXME: unless dual sabers?
11086 					}
11087 				}
11088 				else if ( Q_irand( 0, 1 ) )
11089 				{//jedi less likely to be damaged
11090 					dmg = 0;
11091 				}
11092 				else
11093 				{
11094 					dmg = 1;
11095 				}
11096 			}
11097 			if ( traceEnt && traceEnt->client && traceEnt->client->ps.powerups[PW_GALAK_SHIELD] )
11098 			{
11099 				//has shield up
11100 				dmg = 0;
11101 			}
11102 			int modPowerLevel = -1;
11103 
11104 			if (traceEnt->client)
11105 			{
11106 				modPowerLevel = WP_AbsorbConversion(traceEnt, traceEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_LIGHTNING, self->client->ps.forcePowerLevel[FP_LIGHTNING], 1);
11107 			}
11108 
11109 			if (modPowerLevel != -1)
11110 			{
11111 				if ( !modPowerLevel )
11112 				{
11113 					dmg = 0;
11114 				}
11115 				else if ( modPowerLevel == 1 )
11116 				{
11117 					dmg = floor((float)dmg/4.0f);
11118 				}
11119 				else if ( modPowerLevel == 2 )
11120 				{
11121 					dmg = floor((float)dmg/2.0f);
11122 				}
11123 			}
11124 			//FIXME: if ForceDrain, sap force power and add health to self, use different sound & effects
11125 			if ( dmg )
11126 			{
11127 				G_Damage( traceEnt, self, self, dir, impactPoint, dmg, 0, MOD_FORCE_LIGHTNING );
11128 			}
11129 			if ( traceEnt->client )
11130 			{
11131 				if ( !Q_irand( 0, 2 ) )
11132 				{
11133 					G_Sound( traceEnt, G_SoundIndex( va( "sound/weapons/force/lightninghit%d.wav", Q_irand( 1, 3 ) ) ) );
11134 				}
11135 				traceEnt->s.powerups |= ( 1 << PW_SHOCKED );
11136 
11137 				// If we are dead or we are a bot, we can do the full version
11138 				class_t npc_class = traceEnt->client->NPC_class;
11139 				if ( traceEnt->health <= 0 || ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE ||
11140 					 npc_class == CLASS_MOUSE || npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_REMOTE ||
11141 					 npc_class == CLASS_R5D2 || npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 ||
11142 					 npc_class == CLASS_MARK2 || npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST ) ||
11143 					 npc_class == CLASS_SENTRY )
11144 				{
11145 					traceEnt->client->ps.powerups[PW_SHOCKED] = level.time + 4000;
11146 				}
11147 				else //short version
11148 				{
11149 					traceEnt->client->ps.powerups[PW_SHOCKED] = level.time + 500;
11150 				}
11151 			}
11152 		}
11153 	}
11154 }
11155 
ForceShootLightning(gentity_t * self)11156 void ForceShootLightning( gentity_t *self )
11157 {
11158 	trace_t	tr;
11159 	vec3_t	end, forward;
11160 	gentity_t	*traceEnt;
11161 
11162 	if ( self->health <= 0 )
11163 	{
11164 		return;
11165 	}
11166 	if ( !self->s.number && cg.zoomMode )
11167 	{//can't force lightning when zoomed in
11168 		return;
11169 	}
11170 
11171 	AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
11172 	VectorNormalize( forward );
11173 
11174 	//FIXME: if lightning hits water, do water-only-flagged radius damage from that point
11175 	if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 )
11176 	{//arc
11177 		vec3_t	center, mins, maxs, dir, ent_org, size, v;
11178 		float	radius = 512, dot, dist;
11179 		gentity_t	*entityList[MAX_GENTITIES];
11180 		int		e, numListedEntities, i;
11181 
11182 		VectorCopy( self->currentOrigin, center );
11183 		for ( i = 0 ; i < 3 ; i++ )
11184 		{
11185 			mins[i] = center[i] - radius;
11186 			maxs[i] = center[i] + radius;
11187 		}
11188 		numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
11189 
11190 		for ( e = 0 ; e < numListedEntities ; e++ )
11191 		{
11192 			traceEnt = entityList[e];
11193 
11194 			if ( !traceEnt )
11195 				continue;
11196 			if ( traceEnt == self )
11197 				continue;
11198 			if ( traceEnt->owner == self && traceEnt->s.weapon != WP_THERMAL )//can push your own thermals
11199 				continue;
11200 			if ( !traceEnt->inuse )
11201 				continue;
11202 			if ( !traceEnt->takedamage )
11203 				continue;
11204 			/*
11205 			if ( traceEnt->health <= 0 )//no torturing corpses
11206 				continue;
11207 			*/
11208 			//this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
11209 			// find the distance from the edge of the bounding box
11210 			for ( i = 0 ; i < 3 ; i++ )
11211 			{
11212 				if ( center[i] < traceEnt->absmin[i] )
11213 				{
11214 					v[i] = traceEnt->absmin[i] - center[i];
11215 				} else if ( center[i] > traceEnt->absmax[i] )
11216 				{
11217 					v[i] = center[i] - traceEnt->absmax[i];
11218 				} else
11219 				{
11220 					v[i] = 0;
11221 				}
11222 			}
11223 
11224 			VectorSubtract( traceEnt->absmax, traceEnt->absmin, size );
11225 			VectorMA( traceEnt->absmin, 0.5, size, ent_org );
11226 
11227 			//see if they're in front of me
11228 			//must be within the forward cone
11229 			VectorSubtract( ent_org, center, dir );
11230 			VectorNormalize( dir );
11231 			if ( (dot = DotProduct( dir, forward )) < 0.5 )
11232 				continue;
11233 
11234 			//must be close enough
11235 			dist = VectorLength( v );
11236 			if ( dist >= radius )
11237 			{
11238 				continue;
11239 			}
11240 
11241 			//in PVS?
11242 			if ( !traceEnt->bmodel && !gi.inPVS( ent_org, self->client->renderInfo.handLPoint ) )
11243 			{//must be in PVS
11244 				continue;
11245 			}
11246 
11247 			//Now check and see if we can actually hit it
11248 			gi.trace( &tr, self->client->renderInfo.handLPoint, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
11249 			if ( tr.fraction < 1.0f && tr.entityNum != traceEnt->s.number )
11250 			{//must have clear LOS
11251 				continue;
11252 			}
11253 
11254 			// ok, we are within the radius, add us to the incoming list
11255 			//FIXME: maybe add up the ents and do more damage the less ents there are
11256 			//		as if we're spreading out the damage?
11257 			ForceLightningDamage( self, traceEnt, dir, dist, dot, ent_org );
11258 		}
11259 
11260 	}
11261 	else
11262 	{//trace-line
11263 		int ignore = self->s.number;
11264 		int traces = 0;
11265 		vec3_t	start;
11266 
11267 		VectorCopy( self->client->renderInfo.handLPoint, start );
11268 		VectorMA( self->client->renderInfo.handLPoint, 2048, forward, end );
11269 
11270 		while ( traces < 10 )
11271 		{//need to loop this in case we hit a Jedi who dodges the shot
11272 			gi.trace( &tr, start, vec3_origin, vec3_origin, end, ignore, MASK_SHOT, G2_RETURNONHIT, 10 );
11273 			if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
11274 			{
11275 				return;
11276 			}
11277 
11278 			traceEnt = &g_entities[tr.entityNum];
11279 			//NOTE: only NPCs do this auto-dodge
11280 			if ( traceEnt
11281 				&& traceEnt->s.number >= MAX_CLIENTS
11282 				&& traceEnt->client
11283 				&& traceEnt->client->ps.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 )//&& traceEnt->NPC
11284 			{//FIXME: need a more reliable way to know we hit a jedi?
11285 				if ( !Jedi_DodgeEvasion( traceEnt, self, &tr, HL_NONE ) )
11286 				{//act like we didn't even hit him
11287 					VectorCopy( tr.endpos, start );
11288 					ignore = tr.entityNum;
11289 					traces++;
11290 					continue;
11291 				}
11292 			}
11293 			//a Jedi is not dodging this shot
11294 			break;
11295 		}
11296 
11297 		traceEnt = &g_entities[tr.entityNum];
11298 		ForceLightningDamage( self, traceEnt, forward, 0, 0, tr.endpos );
11299 	}
11300 }
11301 
WP_DeactivateSaber(gentity_t * self,qboolean clearLength)11302 void WP_DeactivateSaber( gentity_t *self, qboolean clearLength )
11303 {
11304 	if ( !self || !self->client )
11305 	{
11306 		return;
11307 	}
11308 	//keep my saber off!
11309 	if ( self->client->ps.SaberActive() )
11310 	{
11311 		self->client->ps.SaberDeactivate();
11312 		if ( clearLength )
11313 		{
11314 			self->client->ps.SetSaberLength( 0 );
11315 		}
11316 		G_SoundIndexOnEnt( self, CHAN_WEAPON, self->client->ps.saber[0].soundOff );
11317 	}
11318 }
11319 
11320 static void ForceShootDrain( gentity_t *self );
11321 
ForceDrainGrabStart(gentity_t * self)11322 void ForceDrainGrabStart( gentity_t *self )
11323 {
11324 	NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRAB_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
11325 	self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
11326 	self->client->ps.saberBlocked = BLOCKED_NONE;
11327 
11328 	self->client->ps.weaponTime = 1000;
11329 	if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
11330 	{
11331 		self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
11332 	}
11333 	//actually grabbing someone, so turn off the saber!
11334 	WP_DeactivateSaber( self, qtrue );
11335 }
11336 
ForceDrain2(gentity_t * self)11337 qboolean ForceDrain2( gentity_t *self )
11338 {//FIXME: make enemy Jedi able to use this
11339 	trace_t	tr;
11340 	vec3_t	end, forward;
11341 	gentity_t	*traceEnt = NULL;
11342 
11343 	if ( self->health <= 0 )
11344 	{
11345 		return qtrue;
11346 	}
11347 
11348 	if ( !self->s.number && (cg.zoomMode || in_camera) )
11349 	{//can't force grip when zoomed in or in cinematic
11350 		return qtrue;
11351 	}
11352 
11353 	if ( self->client->ps.leanofs )
11354 	{//can't force-drain while leaning
11355 		return qtrue;
11356 	}
11357 
11358 	/*
11359 	if ( self->client->ps.SaberLength() > 0 )
11360 	{//can't do this if saber is on!
11361 		return qfalse;
11362 	}
11363 	*/
11364 
11365 	if ( self->client->ps.forceDrainEntityNum <= ENTITYNUM_WORLD )
11366 	{//already draining
11367 		//keep my saber off!
11368 		WP_DeactivateSaber( self, qtrue );
11369 		if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_1 )
11370 		{
11371 			self->client->ps.forcePowerDuration[FP_DRAIN] = level.time + 100;
11372 			self->client->ps.weaponTime = 1000;
11373 			if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
11374 			{
11375 				self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
11376 			}
11377 		}
11378 		return qtrue;
11379 	}
11380 
11381 	if ( self->client->ps.forcePowerDebounce[FP_DRAIN] > level.time )
11382 	{//stops it while using it and also after using it, up to 3 second delay
11383 		return qtrue;
11384 	}
11385 
11386 	if ( self->client->ps.weaponTime > 0 )
11387 	{//busy
11388 		return qtrue;
11389 	}
11390 
11391 	if ( self->client->ps.forcePower < 25 || !WP_ForcePowerUsable( self, FP_DRAIN, 0 ) )
11392 	{
11393 		return qtrue;
11394 	}
11395 
11396 	if ( self->client->ps.saberLockTime > level.time )
11397 	{//in saberlock
11398 		return qtrue;
11399 	}
11400 
11401 	//NOTE: from here on, if it fails, it's okay to try a normal drain, so return qfalse
11402 	if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE )
11403 	{//in air
11404 		return qfalse;
11405 	}
11406 
11407 	//Cause choking anim + health drain in ent in front of me
11408 	AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
11409 	VectorNormalize( forward );
11410 	VectorMA( self->client->renderInfo.eyePoint, FORCE_DRAIN_DIST, forward, end );
11411 
11412 	//okay, trace straight ahead and see what's there
11413 	gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
11414 	if ( tr.entityNum >= ENTITYNUM_WORLD || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
11415 	{
11416 		return qfalse;
11417 	}
11418 	traceEnt = &g_entities[tr.entityNum];
11419 	if ( !traceEnt || traceEnt == self/*???*/ || traceEnt->bmodel || (traceEnt->health <= 0 && traceEnt->takedamage) || (traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE) )
11420 	{
11421 		return qfalse;
11422 	}
11423 
11424 	if ( traceEnt->client )
11425 	{
11426 		if ( traceEnt->client->ps.forceJumpZStart )
11427 		{//can't catch them in mid force jump - FIXME: maybe base it on velocity?
11428 			return qfalse;
11429 		}
11430 		if ( traceEnt->client->ps.groundEntityNum == ENTITYNUM_NONE )
11431 		{//can't catch them in mid air
11432 			return qfalse;
11433 		}
11434 		if ( !Q_stricmp("Yoda",traceEnt->NPC_type) )
11435 		{
11436 			Jedi_PlayDeflectSound( traceEnt );
11437 			ForceThrow( traceEnt, qfalse );
11438 			return qtrue;
11439 		}
11440 		switch ( traceEnt->client->NPC_class )
11441 		{
11442 		case CLASS_GALAKMECH://cant grab him, he's in armor
11443 			G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), Q_irand( 3000, 5000 ) );
11444 			return qfalse;
11445 			break;
11446 		case CLASS_ROCKETTROOPER://cant grab him, he's in armor
11447 		case CLASS_HAZARD_TROOPER://cant grab him, he's in armor
11448 			return qfalse;
11449 			break;
11450 		case CLASS_ATST://much too big to grab!
11451 			return qfalse;
11452 			break;
11453 		//no droids either
11454 		case CLASS_GONK:
11455 		case CLASS_R2D2:
11456 		case CLASS_R5D2:
11457 		case CLASS_MARK1:
11458 		case CLASS_MARK2:
11459 		case CLASS_MOUSE:
11460 		case CLASS_PROTOCOL:
11461 		case CLASS_SABER_DROID:
11462 		case CLASS_ASSASSIN_DROID:
11463 			return qfalse;
11464 			break;
11465 		case CLASS_PROBE:
11466 		case CLASS_SEEKER:
11467 		case CLASS_REMOTE:
11468 		case CLASS_SENTRY:
11469 		case CLASS_INTERROGATOR:
11470 			return qfalse;
11471 			break;
11472 		case CLASS_DESANN://Desann cannot be gripped, he just pushes you back instantly
11473 		case CLASS_KYLE:
11474 		case CLASS_TAVION:
11475 		case CLASS_LUKE:
11476 			Jedi_PlayDeflectSound( traceEnt );
11477 			ForceThrow( traceEnt, qfalse );
11478 			return qtrue;
11479 			break;
11480 		case CLASS_REBORN:
11481 		case CLASS_SHADOWTROOPER:
11482 		//case CLASS_ALORA:
11483 		case CLASS_JEDI:
11484 			if ( traceEnt->NPC
11485 				&& traceEnt->NPC->rank > RANK_CIVILIAN
11486 				&& self->client->ps.forcePowerLevel[FP_DRAIN] < FORCE_LEVEL_2
11487 				&& traceEnt->client->ps.weaponTime <= 0 )
11488 			{
11489 				ForceDrainGrabStart( self );
11490 				Jedi_PlayDeflectSound( traceEnt );
11491 				ForceThrow( traceEnt, qfalse );
11492 				return qtrue;
11493 			}
11494 			break;
11495 		default:
11496 			break;
11497 		}
11498 		if ( traceEnt->s.weapon == WP_EMPLACED_GUN )
11499 		{//FIXME: maybe can pull them out?
11500 			return qfalse;
11501 		}
11502 		if ( traceEnt != self->enemy && OnSameTeam(self, traceEnt) )
11503 		{//can't accidently grip-drain your teammate
11504 			return qfalse;
11505 		}
11506 //=CHECKABSORB===
11507 		/*
11508 		if ( -1 != WP_AbsorbConversion( traceEnt, traceEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_DRAIN, self->client->ps.forcePowerLevel[FP_DRAIN], forcePowerNeeded[self->client->ps.forcePowerLevel[FP_DRAIN]]) )
11509 		{
11510 			//WP_ForcePowerStop( self, FP_DRAIN );
11511 			return;
11512 		}
11513 		*/
11514 //===============
11515 		if ( !FP_ForceDrainGrippableEnt( traceEnt ) )
11516 		{
11517 			return qfalse;
11518 		}
11519 	}
11520 	else
11521 	{//can't drain non-clients
11522 		return qfalse;
11523 	}
11524 
11525 	ForceDrainGrabStart( self );
11526 
11527 	WP_ForcePowerStart( self, FP_DRAIN, 10 );
11528 	self->client->ps.forceDrainEntityNum = traceEnt->s.number;
11529 
11530 //	G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
11531 	G_AddVoiceEvent( traceEnt, Q_irand(EV_CHOKE1, EV_CHOKE3), 2000 );
11532 	if ( /*self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_2 ||*/ traceEnt->s.weapon == WP_SABER )
11533 	{//if we pick up, turn off their weapon
11534 		WP_DeactivateSaber( traceEnt, qtrue );
11535 	}
11536 
11537 	/*
11538 	if ( self->client->ps.forcePowerLevel[FP_DRAIN] < FORCE_LEVEL_2 )
11539 	{//just a duration
11540 		self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 250;
11541 		self->client->ps.forcePowerDuration[FP_DRAIN] = level.time + 5000;
11542 	}
11543 	*/
11544 
11545 	G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/drain.mp3" );
11546 
11547 //	NPC_SetAnim( traceEnt, SETANIM_BOTH, BOTH_HUGGEE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
11548 	NPC_SetAnim( traceEnt, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRABBED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
11549 
11550 	WP_SabersCheckLock2( self, traceEnt, LOCK_FORCE_DRAIN );
11551 
11552 	return qtrue;
11553 }
11554 
ForceDrain(gentity_t * self,qboolean triedDrain2)11555 void ForceDrain( gentity_t *self, qboolean triedDrain2 )
11556 {
11557 	if ( self->health <= 0 )
11558 	{
11559 		return;
11560 	}
11561 
11562 	if ( !triedDrain2 && self->client->ps.weaponTime > 0 )
11563 	{
11564 		return;
11565 	}
11566 
11567 	if ( self->client->ps.forcePower < 25 || !WP_ForcePowerUsable( self, FP_DRAIN, 0 ) )
11568 	{
11569 		return;
11570 	}
11571 	if ( self->client->ps.forcePowerDebounce[FP_DRAIN] > level.time )
11572 	{//stops it while using it and also after using it, up to 3 second delay
11573 		return;
11574 	}
11575 
11576 	if ( self->client->ps.saberLockTime > level.time )
11577 	{//FIXME: can this be a way to break out?
11578 		return;
11579 	}
11580 
11581 	// Make sure to turn off Force Protection and Force Absorb.
11582 	if ( self->client->ps.forcePowersActive & (1 << FP_PROTECT) )
11583 	{
11584 		WP_ForcePowerStop( self, FP_PROTECT );
11585 	}
11586 	if ( self->client->ps.forcePowersActive & (1 << FP_ABSORB) )
11587 	{
11588 		WP_ForcePowerStop( self, FP_ABSORB );
11589 	}
11590 
11591 	G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/drain.mp3" );
11592 
11593 	WP_ForcePowerStart( self, FP_DRAIN, 0 );
11594 }
11595 
11596 
FP_ForceDrainableEnt(gentity_t * victim)11597 qboolean FP_ForceDrainableEnt( gentity_t *victim )
11598 {
11599 	if ( !victim || !victim->client )
11600 	{
11601 		return qfalse;
11602 	}
11603 	switch ( victim->client->NPC_class )
11604 	{
11605 	case CLASS_SAND_CREATURE://??
11606 	case CLASS_ATST:				// technically droid...
11607 	case CLASS_GONK:				// droid
11608 	case CLASS_INTERROGATOR:		// droid
11609 	case CLASS_MARK1:			// droid
11610 	case CLASS_MARK2:			// droid
11611 	case CLASS_GALAKMECH:		// droid
11612 	case CLASS_MINEMONSTER:
11613 	case CLASS_MOUSE:			// droid
11614 	case CLASS_PROBE:			// droid
11615 	case CLASS_PROTOCOL:			// droid
11616 	case CLASS_R2D2:				// droid
11617 	case CLASS_R5D2:				// droid
11618 	case CLASS_REMOTE:
11619 	case CLASS_SEEKER:			// droid
11620 	case CLASS_SENTRY:
11621 	case CLASS_SABER_DROID:
11622 	case CLASS_ASSASSIN_DROID:
11623 	case CLASS_VEHICLE:
11624 		return qfalse;
11625 	default:
11626 		break;
11627 	}
11628 	return qtrue;
11629 }
11630 
FP_ForceDrainGrippableEnt(gentity_t * victim)11631 qboolean FP_ForceDrainGrippableEnt( gentity_t *victim )
11632 {
11633 	if ( !victim || !victim->client )
11634 	{
11635 		return qfalse;
11636 	}
11637 	if ( !FP_ForceDrainableEnt( victim ) )
11638 	{
11639 		return qfalse;
11640 	}
11641 	switch ( victim->client->NPC_class )
11642 	{
11643 	case CLASS_RANCOR:
11644 	case CLASS_SAND_CREATURE:
11645 	case CLASS_WAMPA:
11646 	case CLASS_LIZARD:
11647 	case CLASS_MINEMONSTER:
11648 	case CLASS_MURJJ:
11649 	case CLASS_SWAMP:
11650 	case CLASS_ROCKETTROOPER:
11651 	case CLASS_HAZARD_TROOPER:
11652 		return qfalse;
11653 	default:
11654 		break;
11655 	}
11656 	return qtrue;
11657 }
11658 
ForceDrainDamage(gentity_t * self,gentity_t * traceEnt,vec3_t dir,vec3_t impactPoint)11659 void ForceDrainDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, vec3_t impactPoint )
11660 {
11661 	if ( traceEnt
11662 		&& traceEnt->health > 0
11663 		&& traceEnt->takedamage
11664 		&& FP_ForceDrainableEnt( traceEnt ) )
11665 	{
11666 		if ( traceEnt->client
11667 			&& (!OnSameTeam(self, traceEnt)||self->enemy==traceEnt)//don't drain an ally unless that is actually my current enemy
11668 			&& self->client->ps.forceDrainTime < level.time )
11669 		{//an enemy or object
11670 			int modPowerLevel = -1;
11671 			int	dmg = self->client->ps.forcePowerLevel[FP_DRAIN] + 1;
11672 			int dflags = (DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC);//|DAMAGE_NO_KILL);
11673 			if ( traceEnt->s.number == self->client->ps.forceDrainEntityNum )
11674 			{//grabbing hold of them does more damage/drains more, and can actually kill them
11675 				dmg += 3;
11676 				dflags |= DAMAGE_IGNORE_TEAM;
11677 				//dflags &= ~DAMAGE_NO_KILL;
11678 			}
11679 
11680 			if (traceEnt->client)
11681 			{
11682 				//check for client using FP_ABSORB
11683 				modPowerLevel = WP_AbsorbConversion(traceEnt, traceEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_DRAIN, self->client->ps.forcePowerLevel[FP_DRAIN], 0);
11684 				//Since this is drain, don't absorb any power, but nullify the affect it has
11685 			}
11686 
11687 			if ( modPowerLevel != -1 )
11688 			{
11689 				if ( !modPowerLevel )
11690 				{
11691 					dmg = 0;
11692 				}
11693 				else if ( modPowerLevel == 1 )
11694 				{
11695 					dmg = 1;
11696 				}
11697 				else if ( modPowerLevel == 2 )
11698 				{
11699 					dmg = 2;
11700 				}
11701 			}
11702 
11703 			if ( dmg )
11704 			{
11705 				int	drain = 0;
11706 				if ( traceEnt->client->ps.forcePower )
11707 				{
11708 					if ( dmg > traceEnt->client->ps.forcePower )
11709 					{
11710 						drain = traceEnt->client->ps.forcePower;
11711 						dmg -= drain;
11712 						traceEnt->client->ps.forcePower = 0;
11713 					}
11714 					else
11715 					{
11716 						drain = dmg;
11717 						traceEnt->client->ps.forcePower -= (dmg);
11718 						dmg = 0;
11719 					}
11720 				}
11721 
11722 				/*
11723 				if ( (dflags&DAMAGE_NO_KILL) )
11724 				{//must cap damage
11725 					if ( traceEnt->health <= 1 )
11726 					{//can't drain more than they have
11727 						dmg = 0;
11728 					}
11729 					else if ( dmg >= traceEnt->health )
11730 					{//no more than they have, leaving one for them
11731 						dmg = traceEnt->health-1;
11732 					}
11733 				}
11734 				*/
11735 
11736 				int maxHealth = self->client->ps.stats[STAT_MAX_HEALTH];
11737 				if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_2 )
11738 				{//overcharge health
11739 					maxHealth = floor( (float)self->client->ps.stats[STAT_MAX_HEALTH] * 1.25f );
11740 				}
11741 				if (self->client->ps.stats[STAT_HEALTH] < maxHealth &&
11742 					self->health > 0 && self->client->ps.stats[STAT_HEALTH] > 0)
11743 				{
11744 					self->health += (drain+dmg);
11745 					if (self->health > maxHealth )
11746 					{
11747 						self->health = maxHealth;
11748 					}
11749 					self->client->ps.stats[STAT_HEALTH] = self->health;
11750 					if ( self->health > self->client->ps.stats[STAT_MAX_HEALTH] )
11751 					{
11752 						self->flags |= FL_OVERCHARGED_HEALTH;
11753 					}
11754 				}
11755 
11756 				if ( dmg )
11757 				{//do damage, too
11758 					G_Damage( traceEnt, self, self, dir, impactPoint, dmg, dflags, MOD_FORCE_DRAIN );
11759 				}
11760 				else if ( drain )
11761 				{
11762 					/*
11763 					if ( traceEnt->s.number == self->client->ps.forceDrainEntityNum
11764 						|| traceEnt->s.number < MAX_CLIENTS )
11765 					{//grip-draining (or player - only does sound)
11766 					*/
11767 						NPC_SetPainEvent( traceEnt );
11768 					/*
11769 					}
11770 					else
11771 					{
11772 						GEntity_PainFunc( traceEnt, self, self, impactPoint, 0, MOD_FORCE_DRAIN );
11773 					}
11774 					*/
11775 				}
11776 
11777 				if ( !Q_irand( 0, 2 ) )
11778 				{
11779 					G_Sound( traceEnt, G_SoundIndex( "sound/weapons/force/drained.mp3" ) );
11780 				}
11781 
11782 				traceEnt->client->ps.forcePowerRegenDebounceTime = level.time + 800; //don't let the client being drained get force power back right away
11783 			}
11784 		}
11785 	}
11786 }
11787 
WP_CheckForceDraineeStopMe(gentity_t * self,gentity_t * drainee)11788 qboolean WP_CheckForceDraineeStopMe( gentity_t *self, gentity_t *drainee )
11789 {
11790 	if ( drainee->NPC
11791 		&& drainee->client
11792 		&& (drainee->client->ps.forcePowersKnown&(1<<FP_PUSH))
11793 		&& level.time-(self->client->ps.forcePowerDebounce[FP_DRAIN]>self->client->ps.forcePowerLevel[FP_DRAIN]*500)//at level 1, I always get at least 500ms of drain, at level 3 I get 1500ms
11794 		&& !Q_irand( 0, 100-(drainee->NPC->stats.evasion*10)-(g_spskill->integer*12) ) )
11795 	{//a jedi who broke free
11796 		ForceThrow( drainee, qfalse );
11797 		//FIXME: I need to go into some pushed back anim...
11798 		WP_ForcePowerStop( self, FP_DRAIN );
11799 		//can't drain again for 2 seconds
11800 		self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 4000;
11801 		return qtrue;
11802 	}
11803 	return qfalse;
11804 }
11805 
ForceShootDrain(gentity_t * self)11806 void ForceShootDrain( gentity_t *self )
11807 {
11808 	trace_t	tr;
11809 	vec3_t	end, forward;
11810 	gentity_t	*traceEnt;
11811 	int			numDrained = 0;
11812 
11813 	if ( self->health <= 0 )
11814 	{
11815 		return;
11816 	}
11817 
11818 	if ( self->client->ps.forcePowerDebounce[FP_DRAIN] <= level.time )
11819 	{
11820 		AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
11821 		VectorNormalize( forward );
11822 
11823 		if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_2 )
11824 		{//arc
11825 			vec3_t	center, mins, maxs, dir, ent_org, size, v;
11826 			float	radius = MAX_DRAIN_DISTANCE, dot, dist;
11827 			gentity_t	*entityList[MAX_GENTITIES];
11828 			int		e, numListedEntities, i;
11829 
11830 			VectorCopy( self->client->ps.origin, center );
11831 			for ( i = 0 ; i < 3 ; i++ )
11832 			{
11833 				mins[i] = center[i] - radius;
11834 				maxs[i] = center[i] + radius;
11835 			}
11836 			numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
11837 
11838 			for ( e = 0 ; e < numListedEntities ; e++ )
11839 			{
11840 				traceEnt = entityList[e];
11841 
11842 				if ( !traceEnt )
11843 					continue;
11844 				if ( traceEnt == self )
11845 					continue;
11846 				if ( !traceEnt->inuse )
11847 					continue;
11848 				if ( !traceEnt->takedamage )
11849 					continue;
11850 				if ( traceEnt->health <= 0 )//no torturing corpses
11851 					continue;
11852 				if ( !traceEnt->client )
11853 					continue;
11854 				/*
11855 				if ( !traceEnt->client->ps.forcePower )
11856 					continue;
11857 				*/
11858 	//			if (traceEnt->client->ps.forceSide == FORCE_DARKSIDE)	// We no longer care if the victim is dark or light
11859 	//				continue;
11860 				if (self->enemy != traceEnt//not my enemy
11861 					&& OnSameTeam(self, traceEnt))//on my team
11862 					continue;
11863 				//this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
11864 				// find the distance from the edge of the bounding box
11865 				for ( i = 0 ; i < 3 ; i++ )
11866 				{
11867 					if ( center[i] < traceEnt->absmin[i] )
11868 					{
11869 						v[i] = traceEnt->absmin[i] - center[i];
11870 					} else if ( center[i] > traceEnt->absmax[i] )
11871 					{
11872 						v[i] = center[i] - traceEnt->absmax[i];
11873 					} else
11874 					{
11875 						v[i] = 0;
11876 					}
11877 				}
11878 
11879 				VectorSubtract( traceEnt->absmax, traceEnt->absmin, size );
11880 				VectorMA( traceEnt->absmin, 0.5, size, ent_org );
11881 
11882 				//see if they're in front of me
11883 				//must be within the forward cone
11884 				VectorSubtract( ent_org, center, dir );
11885 				VectorNormalize( dir );
11886 				if ( (dot = DotProduct( dir, forward )) < 0.5 )
11887 					continue;
11888 
11889 				//must be close enough
11890 				dist = VectorLength( v );
11891 				if ( dist >= radius )
11892 				{
11893 					continue;
11894 				}
11895 
11896 				//in PVS?
11897 				if ( !traceEnt->bmodel && !gi.inPVS( ent_org, self->client->renderInfo.handLPoint ) )
11898 				{//must be in PVS
11899 					continue;
11900 				}
11901 
11902 				//Now check and see if we can actually hit it
11903 				gi.trace( &tr, self->client->ps.origin, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_SHOT, G2_RETURNONHIT, 10 );
11904 				if ( tr.fraction < 1.0f && tr.entityNum != traceEnt->s.number )
11905 				{//must have clear LOS
11906 					continue;
11907 				}
11908 
11909 				if ( traceEnt
11910 					&& traceEnt->s.number >= MAX_CLIENTS
11911 					&& traceEnt->client
11912 					&& traceEnt->client->ps.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 )//&& traceEnt->NPC
11913 				{
11914 					if ( !Q_irand( 0, 4 ) && !Jedi_DodgeEvasion( traceEnt, self, &tr, HL_NONE ) )
11915 					{//act like we didn't even hit him
11916 						continue;
11917 					}
11918 				}
11919 
11920 				// ok, we are within the radius, add us to the incoming list
11921 				if ( WP_CheckForceDraineeStopMe( self, traceEnt ) )
11922 				{
11923 					continue;
11924 				}
11925 				ForceDrainDamage( self, traceEnt, dir, ent_org );
11926 				numDrained++;
11927 			}
11928 
11929 		}
11930 		else
11931 		{//trace-line
11932 			int ignore = self->s.number;
11933 			int traces = 0;
11934 			vec3_t	start;
11935 
11936 			VectorCopy( self->client->renderInfo.handLPoint, start );
11937 			VectorMA( start, MAX_DRAIN_DISTANCE, forward, end );
11938 
11939 			while ( traces < 10 )
11940 			{//need to loop this in case we hit a Jedi who dodges the shot
11941 				gi.trace( &tr, start, vec3_origin, vec3_origin, end, ignore, MASK_SHOT, G2_RETURNONHIT, 10 );
11942 				if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
11943 				{
11944 					//always take 1 force point per frame that we're shooting this
11945 					WP_ForcePowerDrain( self, FP_DRAIN, 1 );
11946 					return;
11947 				}
11948 
11949 				traceEnt = &g_entities[tr.entityNum];
11950 				//NOTE: only NPCs do this auto-dodge
11951 				if ( traceEnt
11952 					&& traceEnt->s.number >= MAX_CLIENTS
11953 					&& traceEnt->client
11954 					&& traceEnt->client->ps.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 )//&& traceEnt->NPC
11955 				{
11956 					if ( !Q_irand( 0, 2 ) && !Jedi_DodgeEvasion( traceEnt, self, &tr, HL_NONE ) )
11957 					{//act like we didn't even hit him
11958 						VectorCopy( tr.endpos, start );
11959 						ignore = tr.entityNum;
11960 						traces++;
11961 						continue;
11962 					}
11963 				}
11964 				//a Jedi is not dodging this shot
11965 				break;
11966 			}
11967 			traceEnt = &g_entities[tr.entityNum];
11968 			if ( !WP_CheckForceDraineeStopMe( self, traceEnt ) )
11969 			{
11970 				ForceDrainDamage( self, traceEnt, forward, tr.endpos );
11971 			}
11972 			numDrained = 1;
11973 		}
11974 
11975 		self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 200;//so we don't drain so damn fast!
11976 	}
11977 	self->client->ps.forcePowerRegenDebounceTime = level.time + 500;
11978 
11979 	if ( !numDrained )
11980 	{//always take 1 force point per frame that we're shooting this
11981 		WP_ForcePowerDrain( self, FP_DRAIN, 1 );
11982 	}
11983 	else
11984 	{
11985 		WP_ForcePowerDrain( self, FP_DRAIN, numDrained );//was 2, but...
11986 	}
11987 
11988 	return;
11989 }
11990 
ForceDrainEnt(gentity_t * self,gentity_t * drainEnt)11991 void ForceDrainEnt( gentity_t *self, gentity_t *drainEnt )
11992 {
11993 	if ( self->health <= 0 )
11994 	{
11995 		return;
11996 	}
11997 
11998 	if ( self->client->ps.forcePowerDebounce[FP_DRAIN] <= level.time )
11999 	{
12000 		if ( !drainEnt )
12001 			return;
12002 		if ( drainEnt == self )
12003 			return;
12004 		if ( !drainEnt->inuse )
12005 			return;
12006 		if ( !drainEnt->takedamage )
12007 			return;
12008 		if ( drainEnt->health <= 0 )//no torturing corpses
12009 			return;
12010 		if ( !drainEnt->client )
12011 			return;
12012 		if (OnSameTeam(self, drainEnt))
12013 			return;
12014 
12015 		vec3_t fwd;
12016 		AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
12017 
12018 		drainEnt->painDebounceTime = 0;
12019 		ForceDrainDamage( self, drainEnt, fwd, drainEnt->currentOrigin );
12020 		drainEnt->painDebounceTime = level.time + 2000;
12021 
12022 		if ( drainEnt->s.number )
12023 		{
12024 			if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_2 )
12025 			{//do damage faster at level 3
12026 				self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + Q_irand( 100, 500 );
12027 			}
12028 			else
12029 			{
12030 				self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + Q_irand( 200, 800 );
12031 			}
12032 		}
12033 		else
12034 		{//player takes damage faster
12035 			self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + Q_irand( 100, 500 );
12036 		}
12037 	}
12038 
12039 	self->client->ps.forcePowerRegenDebounceTime = level.time + 500;
12040 }
12041 
ForceSeeing(gentity_t * self)12042 void ForceSeeing( gentity_t *self )
12043 {
12044 	if ( self->health <= 0 )
12045 	{
12046 		return;
12047 	}
12048 
12049 	if (self->client->ps.forceAllowDeactivateTime < level.time &&
12050 		(self->client->ps.forcePowersActive & (1 << FP_SEE)) )
12051 	{
12052 		WP_ForcePowerStop( self, FP_SEE );
12053 		return;
12054 	}
12055 
12056 	if ( !WP_ForcePowerUsable( self, FP_SEE, 0 ) )
12057 	{
12058 		return;
12059 	}
12060 
12061 	WP_DebounceForceDeactivateTime( self );
12062 
12063 	WP_ForcePowerStart( self, FP_SEE, 0 );
12064 
12065 	G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/see.wav" );
12066 }
12067 
ForceProtect(gentity_t * self)12068 void ForceProtect( gentity_t *self )
12069 {
12070 	if ( self->health <= 0 )
12071 	{
12072 		return;
12073 	}
12074 
12075 	if (self->client->ps.forceAllowDeactivateTime < level.time &&
12076 		(self->client->ps.forcePowersActive & (1 << FP_PROTECT)) )
12077 	{
12078 		WP_ForcePowerStop( self, FP_PROTECT );
12079 		return;
12080 	}
12081 
12082 	if ( !WP_ForcePowerUsable( self, FP_PROTECT, 0 ) )
12083 	{
12084 		return;
12085 	}
12086 
12087 	// Make sure to turn off Force Rage and Force Absorb.
12088 	if (self->client->ps.forcePowersActive & (1 << FP_RAGE) )
12089 	{
12090 		WP_ForcePowerStop( self, FP_RAGE );
12091 	}
12092 
12093 	WP_DebounceForceDeactivateTime( self );
12094 
12095 	WP_ForcePowerStart( self, FP_PROTECT, 0 );
12096 
12097 	if ( self->client->ps.saberLockTime < level.time )
12098 	{
12099 		if ( self->client->ps.forcePowerLevel[FP_PROTECT] < FORCE_LEVEL_3 )
12100 		{//animate
12101 			int parts = SETANIM_BOTH;
12102 			int anim = BOTH_FORCE_PROTECT;
12103 			if ( self->client->ps.forcePowerLevel[FP_PROTECT] > FORCE_LEVEL_1 )
12104 			{//level 2 only does it on torso (can keep running)
12105 				parts = SETANIM_TORSO;
12106 				anim = BOTH_FORCE_PROTECT_FAST;
12107 			}
12108 			else
12109 			{
12110 				if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
12111 				{
12112 					VectorClear( self->client->ps.velocity );
12113 				}
12114 				if ( self->NPC )
12115 				{
12116 					VectorClear( self->client->ps.moveDir );
12117 					self->client->ps.speed = 0;
12118 				}
12119 				//FIXME: what if in air?
12120 			}
12121 			NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
12122 			//don't move or attack during this anim
12123 			if ( self->client->ps.forcePowerLevel[FP_PROTECT] < FORCE_LEVEL_2 )
12124 			{
12125 				self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
12126 				self->client->ps.pm_time = self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
12127 				if ( self->s.number )
12128 				{//NPC
12129 					self->painDebounceTime = level.time + self->client->ps.torsoAnimTimer;
12130 				}
12131 				else
12132 				{//player
12133 					self->aimDebounceTime = level.time + self->client->ps.torsoAnimTimer;
12134 				}
12135 			}
12136 			else
12137 			{
12138 				self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
12139 			}
12140 		}
12141 	}
12142 }
12143 
ForceAbsorb(gentity_t * self)12144 void ForceAbsorb( gentity_t *self )
12145 {
12146 	if ( self->health <= 0 )
12147 	{
12148 		return;
12149 	}
12150 
12151 	if (self->client->ps.forceAllowDeactivateTime < level.time &&
12152 		(self->client->ps.forcePowersActive & (1 << FP_ABSORB)) )
12153 	{
12154 		WP_ForcePowerStop( self, FP_ABSORB );
12155 		return;
12156 	}
12157 
12158 	if ( !WP_ForcePowerUsable( self, FP_ABSORB, 0 ) )
12159 	{
12160 		return;
12161 	}
12162 
12163 	// Make sure to turn off Force Rage and Force Protection.
12164 	if (self->client->ps.forcePowersActive & (1 << FP_RAGE) )
12165 	{
12166 		WP_ForcePowerStop( self, FP_RAGE );
12167 	}
12168 
12169 	WP_DebounceForceDeactivateTime( self );
12170 
12171 	WP_ForcePowerStart( self, FP_ABSORB, 0 );
12172 
12173 	if ( self->client->ps.saberLockTime < level.time )
12174 	{
12175 		if ( self->client->ps.forcePowerLevel[FP_ABSORB] < FORCE_LEVEL_3 )
12176 		{//must animate
12177 			int parts = SETANIM_BOTH;
12178 			if ( self->client->ps.forcePowerLevel[FP_ABSORB] > FORCE_LEVEL_1 )
12179 			{//level 2 only does it on torso (can keep running)
12180 				parts = SETANIM_TORSO;
12181 			}
12182 			else
12183 			{
12184 				if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
12185 				{
12186 					VectorClear( self->client->ps.velocity );
12187 				}
12188 				if ( self->NPC )
12189 				{
12190 					VectorClear( self->client->ps.moveDir );
12191 					self->client->ps.speed = 0;
12192 				}
12193 				//FIXME: what if in air?
12194 			}
12195 			/*
12196 			//if in air, only do on torso - NOTE: or moving?
12197 			if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE )//|| !VectorCompare( self->client->ps.velocity, vec3_origin ) )
12198 			{
12199 				parts = SETANIM_TORSO;
12200 			}
12201 			*/
12202 			NPC_SetAnim( self, parts, BOTH_FORCE_ABSORB, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
12203 			self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
12204 			if ( parts == SETANIM_BOTH )
12205 			{//can't move
12206 				self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
12207 				self->client->ps.pm_time = self->client->ps.legsAnimTimer = self->client->ps.torsoAnimTimer;// = self->client->ps.forcePowerDuration[FP_ABSORB];
12208 				if ( self->s.number )
12209 				{//NPC
12210 					self->painDebounceTime = level.time + self->client->ps.pm_time;//self->client->ps.forcePowerDuration[FP_ABSORB];
12211 				}
12212 				else
12213 				{//player
12214 					self->aimDebounceTime = level.time + self->client->ps.pm_time;//self->client->ps.forcePowerDuration[FP_ABSORB];
12215 				}
12216 			}
12217 			//stop saber
12218 			//WP_DeactivateSaber( self );//turn off saber when meditating
12219 			self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
12220 			self->client->ps.saberBlocked = BLOCKED_NONE;
12221 		}
12222 	}
12223 }
12224 
ForceRage(gentity_t * self)12225 void ForceRage( gentity_t *self )
12226 {
12227 	if ( self->health <= 0 )
12228 	{
12229 		return;
12230 	}
12231 
12232 	//FIXME: prevent them from using any other force powers when raging?
12233 
12234 	if (self->client->ps.forceAllowDeactivateTime < level.time &&
12235 		(self->client->ps.forcePowersActive & (1 << FP_RAGE)) )
12236 	{
12237 		WP_ForcePowerStop( self, FP_RAGE );
12238 		return;
12239 	}
12240 
12241 	if ( !WP_ForcePowerUsable( self, FP_RAGE, 0 ) )
12242 	{
12243 		return;
12244 	}
12245 
12246 	if (self->client->ps.forceRageRecoveryTime >= level.time)
12247 	{
12248 		return;
12249 	}
12250 
12251 	if ( self->s.number < MAX_CLIENTS
12252 		&& self->health < 25 )
12253 	{//have to have at least 25 health to start it
12254 		return;
12255 	}
12256 
12257 	if (self->health < 10)
12258 	{
12259 		return;
12260 	}
12261 
12262 	// Make sure to turn off Force Protection and Force Absorb.
12263 	if (self->client->ps.forcePowersActive & (1 << FP_PROTECT) )
12264 	{
12265 		WP_ForcePowerStop( self, FP_PROTECT );
12266 	}
12267 	if (self->client->ps.forcePowersActive & (1 << FP_ABSORB) )
12268 	{
12269 		WP_ForcePowerStop( self, FP_ABSORB );
12270 	}
12271 
12272 	WP_DebounceForceDeactivateTime( self );
12273 
12274 	WP_ForcePowerStart( self, FP_RAGE, 0 );
12275 
12276 	if ( self->client->ps.saberLockTime < level.time )
12277 	{
12278 		if ( self->client->ps.forcePowerLevel[FP_RAGE] < FORCE_LEVEL_3 )
12279 		{//must animate
12280 			if ( self->client->ps.forcePowerLevel[FP_RAGE] < FORCE_LEVEL_2 )
12281 			{//have to stand still for whole length of anim
12282 				//FIXME: if in air, only do on torso?
12283 				NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCE_RAGE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
12284 				//don't attack during this anim
12285 				self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
12286 				self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
12287 				self->client->ps.pm_time = self->client->ps.torsoAnimTimer;
12288 				if ( self->s.number )
12289 				{//NPC
12290 					self->painDebounceTime = level.time + self->client->ps.torsoAnimTimer;
12291 				}
12292 				else
12293 				{//player
12294 					self->aimDebounceTime = level.time + self->client->ps.torsoAnimTimer;
12295 				}
12296 			}
12297 			else
12298 			{
12299 				NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_RAGE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
12300 				//don't attack during this anim
12301 				self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
12302 			}
12303 			//stop saber
12304 			self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
12305 			self->client->ps.saberBlocked = BLOCKED_NONE;
12306 		}
12307 	}
12308 }
12309 
ForceJumpCharge(gentity_t * self,usercmd_t * ucmd)12310 void ForceJumpCharge( gentity_t *self, usercmd_t *ucmd )
12311 {
12312 	float forceJumpChargeInterval = forceJumpStrength[0] / (FORCE_JUMP_CHARGE_TIME/FRAMETIME);
12313 
12314 	if ( self->health <= 0 )
12315 	{
12316 		return;
12317 	}
12318 	if ( !self->s.number && cg.zoomMode )
12319 	{//can't force jump when zoomed in
12320 		return;
12321 	}
12322 
12323 	//need to play sound
12324 	if ( !self->client->ps.forceJumpCharge )
12325 	{//FIXME: this should last only as long as the actual charge-up
12326 		G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jumpbuild.wav" );
12327 	}
12328 	//Increment
12329 	self->client->ps.forceJumpCharge += forceJumpChargeInterval;
12330 
12331 	//clamp to max strength for current level
12332 	if ( self->client->ps.forceJumpCharge > forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]] )
12333 	{
12334 		self->client->ps.forceJumpCharge = forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]];
12335 	}
12336 
12337 	//clamp to max available force power
12338 	if ( self->client->ps.forceJumpCharge/forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME)*forcePowerNeeded[FP_LEVITATION] > self->client->ps.forcePower )
12339 	{//can't use more than you have
12340 		self->client->ps.forceJumpCharge = self->client->ps.forcePower*forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME);
12341 	}
12342 	//FIXME: a simple tap should always do at least a normal height's jump?
12343 }
12344 
WP_GetVelocityForForceJump(gentity_t * self,vec3_t jumpVel,usercmd_t * ucmd)12345 int WP_GetVelocityForForceJump( gentity_t *self, vec3_t jumpVel, usercmd_t *ucmd )
12346 {
12347 	float pushFwd = 0, pushRt = 0;
12348 	vec3_t	view, forward, right;
12349 	VectorCopy( self->client->ps.viewangles, view );
12350 	view[0] = 0;
12351 	AngleVectors( view, forward, right, NULL );
12352 	if ( ucmd->forwardmove && ucmd->rightmove )
12353 	{
12354 		if ( ucmd->forwardmove > 0 )
12355 		{
12356 			pushFwd = 50;
12357 		}
12358 		else
12359 		{
12360 			pushFwd = -50;
12361 		}
12362 		if ( ucmd->rightmove > 0 )
12363 		{
12364 			pushRt = 50;
12365 		}
12366 		else
12367 		{
12368 			pushRt = -50;
12369 		}
12370 	}
12371 	else if ( ucmd->forwardmove || ucmd->rightmove )
12372 	{
12373 		if ( ucmd->forwardmove > 0 )
12374 		{
12375 			pushFwd = 100;
12376 		}
12377 		else if ( ucmd->forwardmove < 0 )
12378 		{
12379 			pushFwd = -100;
12380 		}
12381 		else if ( ucmd->rightmove > 0 )
12382 		{
12383 			pushRt = 100;
12384 		}
12385 		else if ( ucmd->rightmove < 0 )
12386 		{
12387 			pushRt = -100;
12388 		}
12389 	}
12390 	VectorMA( self->client->ps.velocity, pushFwd, forward, jumpVel );
12391 	VectorMA( self->client->ps.velocity, pushRt, right, jumpVel );
12392 	jumpVel[2] += self->client->ps.forceJumpCharge;//forceJumpStrength;
12393 	if ( pushFwd > 0 && self->client->ps.forceJumpCharge > 200 )
12394 	{
12395 		return FJ_FORWARD;
12396 	}
12397 	else if ( pushFwd < 0 && self->client->ps.forceJumpCharge > 200 )
12398 	{
12399 		return FJ_BACKWARD;
12400 	}
12401 	else if ( pushRt > 0 && self->client->ps.forceJumpCharge > 200 )
12402 	{
12403 		return FJ_RIGHT;
12404 	}
12405 	else if ( pushRt < 0 && self->client->ps.forceJumpCharge > 200 )
12406 	{
12407 		return FJ_LEFT;
12408 	}
12409 	else
12410 	{//FIXME: jump straight up anim
12411 		return FJ_UP;
12412 	}
12413 }
12414 
ForceJump(gentity_t * self,usercmd_t * ucmd)12415 void ForceJump( gentity_t *self, usercmd_t *ucmd )
12416 {
12417 	if ( self->client->ps.forcePowerDuration[FP_LEVITATION] > level.time )
12418 	{
12419 		return;
12420 	}
12421 	if ( !WP_ForcePowerUsable( self, FP_LEVITATION, 0 ) )
12422 	{
12423 		return;
12424 	}
12425 	if ( self->s.groundEntityNum == ENTITYNUM_NONE )
12426 	{
12427 		return;
12428 	}
12429 	if ( self->client->ps.pm_flags&PMF_JUMP_HELD )
12430 	{
12431 		return;
12432 	}
12433 	if ( self->health <= 0 )
12434 	{
12435 		return;
12436 	}
12437 	if ( !self->s.number && (cg.zoomMode || in_camera) )
12438 	{//can't force jump when zoomed in or in cinematic
12439 		return;
12440 	}
12441 	if ( self->client->ps.saberLockTime > level.time )
12442 	{//FIXME: can this be a way to break out?
12443 		return;
12444 	}
12445 
12446 	if ( self->client->NPC_class == CLASS_BOBAFETT
12447 		|| self->client->NPC_class == CLASS_ROCKETTROOPER )
12448 	{
12449 		if ( self->client->ps.forceJumpCharge > 300 )
12450 		{
12451 			JET_FlyStart(NPC);
12452 		}
12453 		else
12454 		{
12455 			G_AddEvent( self, EV_JUMP, 0 );
12456 		}
12457 	}
12458 	else
12459 	{
12460 		G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" );
12461 	}
12462 
12463 	float forceJumpChargeInterval = forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]]/(FORCE_JUMP_CHARGE_TIME/FRAMETIME);
12464 
12465 	int anim;
12466 	vec3_t	jumpVel;
12467 
12468 	switch( WP_GetVelocityForForceJump( self, jumpVel, ucmd ) )
12469 	{
12470 	case FJ_FORWARD:
12471 		if ( ((self->client->NPC_class == CLASS_BOBAFETT||self->client->NPC_class == CLASS_ROCKETTROOPER) && self->client->ps.forceJumpCharge > 300 )
12472 			|| (self->client->ps.saber[0].saberFlags&SFL_NO_FLIPS)
12473 			|| (self->client->ps.dualSabers && (self->client->ps.saber[1].saberFlags&SFL_NO_FLIPS) )
12474 			|| ( self->NPC &&
12475 				self->NPC->rank != RANK_CREWMAN &&
12476 				self->NPC->rank <= RANK_LT_JG ) )
12477 		{//can't do acrobatics
12478 			anim = BOTH_FORCEJUMP1;
12479 		}
12480 		else
12481 		{
12482 			if ( self->client->NPC_class == CLASS_ALORA && Q_irand( 0, 3 ) )
12483 			{
12484 				anim = Q_irand( BOTH_ALORA_FLIP_1, BOTH_ALORA_FLIP_3 );
12485 			}
12486 			else
12487 			{
12488 				anim = BOTH_FLIP_F;
12489 			}
12490 		}
12491 		break;
12492 	case FJ_BACKWARD:
12493 		if ( ((self->client->NPC_class == CLASS_BOBAFETT||self->client->NPC_class == CLASS_ROCKETTROOPER) && self->client->ps.forceJumpCharge > 300 )
12494 			|| (self->client->ps.saber[0].saberFlags&SFL_NO_FLIPS)
12495 			|| (self->client->ps.dualSabers && (self->client->ps.saber[1].saberFlags&SFL_NO_FLIPS) )
12496 			|| ( self->NPC &&
12497 				self->NPC->rank != RANK_CREWMAN &&
12498 				self->NPC->rank <= RANK_LT_JG ) )
12499 		{//can't do acrobatics
12500 			anim = BOTH_FORCEJUMPBACK1;
12501 		}
12502 		else
12503 		{
12504 			anim = BOTH_FLIP_B;
12505 		}
12506 		break;
12507 	case FJ_RIGHT:
12508 		if ( ((self->client->NPC_class == CLASS_BOBAFETT||self->client->NPC_class == CLASS_ROCKETTROOPER) && self->client->ps.forceJumpCharge > 300 )
12509 			|| (self->client->ps.saber[0].saberFlags&SFL_NO_FLIPS)
12510 			|| (self->client->ps.dualSabers && (self->client->ps.saber[1].saberFlags&SFL_NO_FLIPS) )
12511 			|| ( self->NPC &&
12512 				self->NPC->rank != RANK_CREWMAN &&
12513 				self->NPC->rank <= RANK_LT_JG ) )
12514 		{//can't do acrobatics
12515 			anim = BOTH_FORCEJUMPRIGHT1;
12516 		}
12517 		else
12518 		{
12519 			anim = BOTH_FLIP_R;
12520 		}
12521 		break;
12522 	case FJ_LEFT:
12523 		if ( ((self->client->NPC_class == CLASS_BOBAFETT||self->client->NPC_class == CLASS_ROCKETTROOPER) && self->client->ps.forceJumpCharge > 300 )
12524 			|| (self->client->ps.saber[0].saberFlags&SFL_NO_FLIPS)
12525 			|| (self->client->ps.dualSabers && (self->client->ps.saber[1].saberFlags&SFL_NO_FLIPS) )
12526 			|| ( self->NPC &&
12527 				self->NPC->rank != RANK_CREWMAN &&
12528 				self->NPC->rank <= RANK_LT_JG ) )
12529 		{//can't do acrobatics
12530 			anim = BOTH_FORCEJUMPLEFT1;
12531 		}
12532 		else
12533 		{
12534 			anim = BOTH_FLIP_L;
12535 		}
12536 		break;
12537 	default:
12538 	case FJ_UP:
12539 		anim = BOTH_JUMP1;
12540 		break;
12541 	}
12542 
12543 	int	parts = SETANIM_BOTH;
12544 	if ( self->client->ps.weaponTime )
12545 	{//FIXME: really only care if we're in a saber attack anim.. maybe trail length?
12546 		parts = SETANIM_LEGS;
12547 	}
12548 
12549 	NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
12550 
12551 	//FIXME: sound effect
12552 	self->client->ps.forceJumpZStart = self->currentOrigin[2];//remember this for when we land
12553 	VectorCopy( jumpVel, self->client->ps.velocity );
12554 	//wasn't allowing them to attack when jumping, but that was annoying
12555 	//self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
12556 
12557 	WP_ForcePowerStart( self, FP_LEVITATION, self->client->ps.forceJumpCharge/forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME)*forcePowerNeeded[FP_LEVITATION] );
12558 	//self->client->ps.forcePowerDuration[FP_LEVITATION] = level.time + self->client->ps.weaponTime;
12559 	self->client->ps.forceJumpCharge = 0;
12560 }
12561 
WP_AbsorbConversion(gentity_t * attacked,int atdAbsLevel,gentity_t * attacker,int atPower,int atPowerLevel,int atForceSpent)12562 int WP_AbsorbConversion(gentity_t *attacked, int atdAbsLevel, gentity_t *attacker, int atPower, int atPowerLevel, int atForceSpent)
12563 {
12564 	int getLevel = 0;
12565 	int addTot = 0;
12566 
12567 	if (atPower != FP_LIGHTNING &&
12568 		atPower != FP_DRAIN &&
12569 		atPower != FP_GRIP &&
12570 		atPower != FP_PUSH &&
12571 		atPower != FP_PULL)
12572 	{ //Only these powers can be absorbed
12573 		return -1;
12574 	}
12575 
12576 	if (!atdAbsLevel)
12577 	{ //looks like attacker doesn't have any absorb power
12578 		return -1;
12579 	}
12580 
12581 	if (!(attacked->client->ps.forcePowersActive & (1 << FP_ABSORB)))
12582 	{ //absorb is not active
12583 		return -1;
12584 	}
12585 
12586 	//Subtract absorb power level from the offensive force power
12587 	getLevel = atPowerLevel;
12588 	getLevel -= atdAbsLevel;
12589 
12590 	if (getLevel < 0)
12591 	{
12592 		getLevel = 0;
12593 	}
12594 
12595 	//let the attacker absorb an amount of force used in this attack based on his level of absorb
12596 	addTot = (atForceSpent/3)*attacked->client->ps.forcePowerLevel[FP_ABSORB];
12597 
12598 	if (addTot < 1 && atForceSpent >= 1)
12599 	{
12600 		addTot = 1;
12601 	}
12602 	attacked->client->ps.forcePower += addTot;
12603 	if (attacked->client->ps.forcePower > attacked->client->ps.forcePowerMax)
12604 	{
12605 		attacked->client->ps.forcePower = attacked->client->ps.forcePowerMax;
12606 	}
12607 
12608 	G_SoundOnEnt( attacked, CHAN_ITEM, "sound/weapons/force/absorbhit.wav" );
12609 
12610 	return getLevel;
12611 }
12612 
WP_ForcePowerRegenerate(gentity_t * self,int overrideAmt)12613 void WP_ForcePowerRegenerate( gentity_t *self, int overrideAmt )
12614 {
12615 	if ( !self->client )
12616 	{
12617 		return;
12618 	}
12619 
12620 	if ( self->client->ps.forcePower < self->client->ps.forcePowerMax )
12621 	{
12622 		if ( overrideAmt )
12623 		{
12624 			self->client->ps.forcePower += overrideAmt;
12625 		}
12626 		else
12627 		{
12628 			self->client->ps.forcePower++;
12629 		}
12630 		if ( self->client->ps.forcePower > self->client->ps.forcePowerMax )
12631 		{
12632 			self->client->ps.forcePower = self->client->ps.forcePowerMax;
12633 		}
12634 	}
12635 }
12636 
WP_ForcePowerDrain(gentity_t * self,forcePowers_t forcePower,int overrideAmt)12637 void WP_ForcePowerDrain( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
12638 {
12639 	if ( self->NPC )
12640 	{//For now, NPCs have infinite force power
12641 		return;
12642 	}
12643 	//take away the power
12644 	int	drain = overrideAmt;
12645 	if ( !drain )
12646 	{
12647 		drain = forcePowerNeeded[forcePower];
12648 	}
12649 	if ( !drain )
12650 	{
12651 		return;
12652 	}
12653 	self->client->ps.forcePower -= drain;
12654 	if ( self->client->ps.forcePower < 0 )
12655 	{
12656 		self->client->ps.forcePower = 0;
12657 	}
12658 }
12659 
WP_ForcePowerStart(gentity_t * self,forcePowers_t forcePower,int overrideAmt)12660 void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
12661 {
12662 	int	duration = 0;
12663 
12664 	//FIXME: debounce some of these?
12665 	self->client->ps.forcePowerDebounce[forcePower] = 0;
12666 
12667 	//and it in
12668 	//set up duration time
12669 	switch( (int)forcePower )
12670 	{
12671 	case FP_HEAL:
12672 		self->client->ps.forcePowersActive |= ( 1 << forcePower );
12673 		self->client->ps.forceHealCount = 0;
12674 		WP_StartForceHealEffects( self );
12675 		break;
12676 	case FP_LEVITATION:
12677 		self->client->ps.forcePowersActive |= ( 1 << forcePower );
12678 		break;
12679 	case FP_SPEED:
12680 		//duration is always 5 seconds, player time
12681 		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...
12682 		self->client->ps.forcePowersActive |= ( 1 << forcePower );
12683 		self->s.loopSound = G_SoundIndex( "sound/weapons/force/speedloop.wav" );
12684 		if ( self->client->ps.forcePowerLevel[FP_SPEED] > FORCE_LEVEL_2 )
12685 		{//HACK: just using this as a timestamp for when the power started, setting debounce to current time shouldn't adversely affect anything else
12686 			self->client->ps.forcePowerDebounce[FP_SPEED] = level.time;
12687 		}
12688 		break;
12689 	case FP_PUSH:
12690 		break;
12691 	case FP_PULL:
12692 		self->client->ps.forcePowersActive |= ( 1 << forcePower );
12693 		break;
12694 	case FP_TELEPATHY:
12695 		break;
12696 	case FP_GRIP:
12697 		duration = 1000;
12698 		self->client->ps.forcePowersActive |= ( 1 << forcePower );
12699 		//HACK: just using this as a timestamp for when the power started, setting debounce to current time shouldn't adversely affect anything else
12700 		//self->client->ps.forcePowerDebounce[forcePower] = level.time;
12701 		break;
12702 	case FP_LIGHTNING:
12703 		duration = overrideAmt;
12704 		overrideAmt = 0;
12705 		self->client->ps.forcePowersActive |= ( 1 << forcePower );
12706 		break;
12707 	//new Jedi Academy force powers
12708 	case FP_RAGE:
12709 		//duration is always 5 seconds, player time
12710 		duration = ceil(FORCE_RAGE_DURATION*forceSpeedValue[self->client->ps.forcePowerLevel[FP_RAGE]-1]);//FIXME: because the timescale scales down (not instant), this doesn't end up being exactly right...
12711 		self->client->ps.forcePowersActive |= ( 1 << forcePower );
12712 		G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/rage.mp3" );
12713 		self->s.loopSound = G_SoundIndex( "sound/weapons/force/rageloop.wav" );
12714 		if ( self->chestBolt != -1 )
12715 		{
12716 			G_PlayEffect( G_EffectIndex( "force/rage2" ), self->playerModel, self->chestBolt, self->s.number, self->currentOrigin, duration, qtrue );
12717 		}
12718 		break;
12719 	case FP_DRAIN:
12720 		if ( self->client->ps.forcePowerLevel[forcePower] > FORCE_LEVEL_1
12721 			&& self->client->ps.forceDrainEntityNum >= ENTITYNUM_WORLD )
12722 		{
12723 			duration = overrideAmt;
12724 			overrideAmt = 0;
12725 			//HACK: just using this as a timestamp for when the power started, setting debounce to current time shouldn't adversely affect anything else
12726 			self->client->ps.forcePowerDebounce[forcePower] = level.time;
12727 		}
12728 		else
12729 		{
12730 			duration = 1000;
12731 		}
12732 		self->client->ps.forcePowersActive |= ( 1 << forcePower );
12733 		break;
12734 	case FP_PROTECT:
12735 		switch ( self->client->ps.forcePowerLevel[FP_PROTECT] )
12736 		{
12737 		case FORCE_LEVEL_3:
12738 			duration = 20000;
12739 			break;
12740 		case FORCE_LEVEL_2:
12741 			duration = 15000;
12742 			break;
12743 		case FORCE_LEVEL_1:
12744 		default:
12745 
12746 			duration = 10000;
12747 			break;
12748 		}
12749 		self->client->ps.forcePowersActive |= ( 1 << forcePower );
12750 		G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/protect.mp3" );
12751 		self->s.loopSound = G_SoundIndex( "sound/weapons/force/protectloop.wav" );
12752 		break;
12753 	case FP_ABSORB:
12754 		duration = 20000;
12755 		self->client->ps.forcePowersActive |= ( 1 << forcePower );
12756 		G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/absorb.mp3" );
12757 		self->s.loopSound = G_SoundIndex( "sound/weapons/force/absorbloop.wav" );
12758 		break;
12759 	case FP_SEE:
12760 		if ( self->client->ps.forcePowerLevel[FP_SEE] == FORCE_LEVEL_1 )
12761 		{
12762 			duration = 5000;
12763 		}
12764 		else if ( self->client->ps.forcePowerLevel[FP_SEE] == FORCE_LEVEL_2 )
12765 		{
12766 			duration = 10000;
12767 		}
12768 		else// if ( self->client->ps.forcePowerLevel[FP_SEE] == FORCE_LEVEL_3 )
12769 		{
12770 			duration = 20000;
12771 		}
12772 
12773 		self->client->ps.forcePowersActive |= ( 1 << forcePower );
12774 		G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/see.mp3" );
12775 		self->s.loopSound = G_SoundIndex( "sound/weapons/force/seeloop.wav" );
12776 		break;
12777 	default:
12778 		break;
12779 	}
12780 	if ( duration )
12781 	{
12782 		self->client->ps.forcePowerDuration[forcePower] = level.time + duration;
12783 	}
12784 	else
12785 	{
12786 		self->client->ps.forcePowerDuration[forcePower] = 0;
12787 	}
12788 
12789 	WP_ForcePowerDrain( self, forcePower, overrideAmt );
12790 
12791 	if ( !self->s.number )
12792 	{
12793 		self->client->sess.missionStats.forceUsed[(int)forcePower]++;
12794 	}
12795 }
12796 
WP_ForcePowerAvailable(gentity_t * self,forcePowers_t forcePower,int overrideAmt)12797 qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
12798 {
12799 	if ( forcePower == FP_LEVITATION )
12800 	{
12801 		return qtrue;
12802 	}
12803 	int	drain = overrideAmt?overrideAmt:forcePowerNeeded[forcePower];
12804 	if ( !drain )
12805 	{
12806 		return qtrue;
12807 	}
12808 	if ( self->client->ps.forcePower < drain )
12809 	{
12810 		//G_AddEvent( self, EV_NOAMMO, 0 );
12811 		return qfalse;
12812 	}
12813 	return qtrue;
12814 }
12815 
12816 extern void CG_PlayerLockedWeaponSpeech( int jumping );
12817 extern qboolean Rosh_TwinNearBy( gentity_t *self );
WP_ForcePowerUsable(gentity_t * self,forcePowers_t forcePower,int overrideAmt)12818 qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
12819 {
12820 	if ( !(self->client->ps.forcePowersKnown & ( 1 << forcePower )) )
12821 	{//don't know this power
12822 		return qfalse;
12823 	}
12824 	else if ( self->NPC && (self->NPC->aiFlags&NPCAI_ROSH) )
12825 	{
12826 		if ( ((1<<forcePower)&FORCE_POWERS_ROSH_FROM_TWINS) )
12827 		{//this is a force power we can only use when a twin is near us
12828 			if ( !Rosh_TwinNearBy( self ) )
12829 			{
12830 				return qfalse;
12831 			}
12832 		}
12833 	}
12834 
12835 	if ( self->client->ps.forcePowerLevel[forcePower] <= 0 )
12836 	{//can't use this power
12837 		return qfalse;
12838 	}
12839 
12840 	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
12841 	{
12842 		if ( self->s.number < MAX_CLIENTS )
12843 		{
12844 			CG_PlayerLockedWeaponSpeech( qfalse );
12845 		}
12846 		return qfalse;
12847 	}
12848 
12849 	if ( in_camera && self->s.number < MAX_CLIENTS )
12850 	{//player can't turn on force powers duing cinematics
12851 		return qfalse;
12852 	}
12853 
12854 	if ( PM_LockedAnim( self->client->ps.torsoAnim ) && self->client->ps.torsoAnimTimer )
12855 	{//no force powers during these special anims
12856 		return qfalse;
12857 	}
12858 	if ( PM_SuperBreakLoseAnim( self->client->ps.torsoAnim )
12859 		|| PM_SuperBreakWinAnim( self->client->ps.torsoAnim ) )
12860 	{
12861 		return qfalse;
12862 	}
12863 
12864 	if ( (self->client->ps.forcePowersActive & ( 1 << forcePower )) )
12865 	{//already using this power
12866 		return qfalse;
12867 	}
12868 	/*
12869 	if ( !self->client->ps.forcePowerLevel[(int)(forcePower)] )
12870 	{
12871 		return qfalse;
12872 	}
12873 	*/
12874 	if ( self->client->NPC_class == CLASS_ATST )
12875 	{//Doh!  No force powers in an AT-ST!
12876 		return qfalse;
12877 	}
12878 	Vehicle_t *pVeh = NULL;
12879 	if ( (pVeh = G_IsRidingVehicle( self )) != NULL )
12880 	{//Doh!  No force powers when flying a vehicle!
12881 		if ( pVeh->m_pVehicleInfo->numHands > 1 )
12882 		{//if in a two-handed vehicle
12883 			return qfalse;
12884 		}
12885 	}
12886 	if ( self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_WORLD )
12887 	{//Doh!  No force powers when controlling an NPC
12888 		return qfalse;
12889 	}
12890 	if ( self->client->ps.eFlags & EF_LOCKED_TO_WEAPON )
12891 	{//Doh!  No force powers when in an emplaced gun!
12892 		return qfalse;
12893 	}
12894 
12895 	if ( (self->client->ps.saber[0].saberFlags&SFL_SINGLE_BLADE_THROWABLE)//SaberStaff() //using staff
12896 		&& !self->client->ps.dualSabers //only 1, in right hand
12897 		&& !self->client->ps.saber[0].blade[1].active )//only first blade is on
12898 	{//allow power
12899 		//FIXME: externalize this condition seperately?
12900 	}
12901 	else
12902 	{
12903 		if ( forcePower == FP_SABERTHROW && (self->client->ps.saber[0].saberFlags&SFL_NOT_THROWABLE) )
12904 		{//cannot throw this kind of saber
12905 			return qfalse;
12906 		}
12907 
12908 		if ( self->client->ps.saber[0].Active() )
12909 		{
12910 			if ( (self->client->ps.saber[0].saberFlags&SFL_TWO_HANDED) )
12911 			{
12912 				if ( g_saberRestrictForce->integer )
12913 				{
12914 					switch ( forcePower )
12915 					{
12916 					case FP_PUSH:
12917 					case FP_PULL:
12918 					case FP_TELEPATHY:
12919 					case FP_GRIP:
12920 					case FP_LIGHTNING:
12921 					case FP_DRAIN:
12922 						return qfalse;
12923 					default:
12924 						break;
12925 					}
12926 				}
12927 			}
12928 			if ( (self->client->ps.saber[0].saberFlags&SFL_TWO_HANDED)
12929 				|| (self->client->ps.dualSabers && self->client->ps.saber[1].Active()) )
12930 			{//this saber requires the use of two hands OR our other hand is using an active saber too
12931 				if ( (self->client->ps.saber[0].forceRestrictions&(1<<forcePower)) )
12932 				{//this power is verboten when using this saber
12933 					return qfalse;
12934 				}
12935 			}
12936 		}
12937 		if ( self->client->ps.dualSabers && self->client->ps.saber[1].Active() )
12938 		{
12939 			if ( g_saberRestrictForce->integer )
12940 			{
12941 				switch ( forcePower )
12942 				{
12943 				case FP_PUSH:
12944 				case FP_PULL:
12945 				case FP_TELEPATHY:
12946 				case FP_GRIP:
12947 				case FP_LIGHTNING:
12948 				case FP_DRAIN:
12949 					return qfalse;
12950 				default:
12951 					break;
12952 				}
12953 			}
12954 			if ( (self->client->ps.saber[1].forceRestrictions&(1<<forcePower)) )
12955 			{//this power is verboten when using this saber
12956 				return qfalse;
12957 			}
12958 		}
12959 	}
12960 
12961 	return WP_ForcePowerAvailable( self, forcePower, overrideAmt );
12962 }
12963 
WP_ForcePowerStop(gentity_t * self,forcePowers_t forcePower)12964 void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower )
12965 {
12966 	gentity_t	*gripEnt;
12967 	gentity_t	*drainEnt;
12968 
12969 	if ( !(self->client->ps.forcePowersActive&(1<<forcePower)) )
12970 	{//umm, wasn't doing it, so...
12971 		return;
12972 	}
12973 
12974 	self->client->ps.forcePowersActive &= ~( 1 << forcePower );
12975 
12976 	switch( (int)forcePower )
12977 	{
12978 	case FP_HEAL:
12979 		//if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_3 )
12980 		{//wasn't an instant heal and heal is now done
12981 			if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 )
12982 			{//if in meditation pose, must come out of it
12983 				//FIXME: BOTH_FORCEHEAL_STOP
12984 				if ( self->client->ps.legsAnim == BOTH_FORCEHEAL_START )
12985 				{
12986 					NPC_SetAnim( self, SETANIM_LEGS, BOTH_FORCEHEAL_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
12987 				}
12988 				if ( self->client->ps.torsoAnim == BOTH_FORCEHEAL_START )
12989 				{
12990 					NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEHEAL_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
12991 				}
12992 				self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
12993 				self->client->ps.saberBlocked = BLOCKED_NONE;
12994 			}
12995 		}
12996 		WP_StopForceHealEffects( self );
12997 		if (self->health >= self->client->ps.stats[STAT_MAX_HEALTH]/3)
12998 		{
12999 			gi.G2API_ClearSkinGore(self->ghoul2);
13000 		}
13001 		break;
13002 	case FP_LEVITATION:
13003 		self->client->ps.forcePowerDebounce[FP_LEVITATION] = 0;
13004 		break;
13005 	case FP_SPEED:
13006 		if ( !self->s.number )
13007 		{//player using force speed
13008 			if ( g_timescale->value != 1.0 )
13009 			{
13010 				if ( !(self->client->ps.forcePowersActive&(1<<FP_RAGE))||self->client->ps.forcePowerLevel[FP_RAGE] < FORCE_LEVEL_2 )
13011 				{//not slowed down because of force rage
13012 					gi.cvar_set("timescale", "1");
13013 				}
13014 			}
13015 		}
13016 		//FIXME: reset my current anim, keeping current frame, but with proper anim speed
13017 		//		otherwise, the anim will continue playing at high speed
13018 		self->s.loopSound = 0;
13019 		break;
13020 	case FP_PUSH:
13021 		break;
13022 	case FP_PULL:
13023 		break;
13024 	case FP_TELEPATHY:
13025 		break;
13026 	case FP_GRIP:
13027 		if ( self->NPC )
13028 		{
13029 			TIMER_Set( self, "gripping", -level.time );
13030 		}
13031 		if ( self->client->ps.forceGripEntityNum < ENTITYNUM_WORLD )
13032 		{
13033 			gripEnt = &g_entities[self->client->ps.forceGripEntityNum];
13034 			if ( gripEnt )
13035 			{
13036 				gripEnt->s.loopSound = 0;
13037 				if ( gripEnt->client )
13038 				{
13039 					gripEnt->client->ps.eFlags &= ~EF_FORCE_GRIPPED;
13040 					if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
13041 					{//sanity-cap the velocity
13042 						float gripVel = VectorNormalize( gripEnt->client->ps.velocity );
13043 						if ( gripVel > 500.0f )
13044 						{
13045 							gripVel = 500.0f;
13046 						}
13047 						VectorScale( gripEnt->client->ps.velocity, gripVel, gripEnt->client->ps.velocity );
13048 					}
13049 
13050 					//FIXME: they probably dropped their weapon, should we make them flee?  Or should AI handle no-weapon behavior?
13051 //rww - RAGDOLL_BEGIN
13052 #ifndef JK2_RAGDOLL_GRIPNOHEALTH
13053 //rww - RAGDOLL_END
13054 					if ( gripEnt->health > 0 )
13055 //rww - RAGDOLL_BEGIN
13056 #endif
13057 //rww - RAGDOLL_END
13058 					{
13059 						int	holdTime = 500;
13060 						if ( gripEnt->health > 0 )
13061 						{
13062 							G_AddEvent( gripEnt, EV_WATER_CLEAR, 0 );
13063 						}
13064 						if ( gripEnt->client->ps.forcePowerDebounce[FP_PUSH] > level.time )
13065 						{//they probably pushed out of it
13066 							holdTime = 0;
13067 						}
13068 						else if ( gripEnt->s.weapon == WP_SABER )
13069 						{//jedi recover faster
13070 							holdTime = self->client->ps.forcePowerLevel[FP_GRIP]*200;
13071 						}
13072 						else
13073 						{
13074 							holdTime = self->client->ps.forcePowerLevel[FP_GRIP]*500;
13075 						}
13076 						//stop the anims soon, keep them locked in place for a bit
13077 						if ( gripEnt->client->ps.torsoAnim == BOTH_CHOKE1 || gripEnt->client->ps.torsoAnim == BOTH_CHOKE3 )
13078 						{//stop choking anim on torso
13079 							if ( gripEnt->client->ps.torsoAnimTimer > holdTime )
13080 							{
13081 								gripEnt->client->ps.torsoAnimTimer = holdTime;
13082 							}
13083 						}
13084 						if ( gripEnt->client->ps.legsAnim == BOTH_CHOKE1 || gripEnt->client->ps.legsAnim == BOTH_CHOKE3 )
13085 						{//stop choking anim on legs
13086 							gripEnt->client->ps.legsAnimTimer = 0;
13087 							if ( holdTime )
13088 							{
13089 								//lock them in place for a bit
13090 								gripEnt->client->ps.pm_time = gripEnt->client->ps.torsoAnimTimer;
13091 								gripEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
13092 								if ( gripEnt->s.number )
13093 								{//NPC
13094 									gripEnt->painDebounceTime = level.time + gripEnt->client->ps.torsoAnimTimer;
13095 								}
13096 								else
13097 								{//player
13098 									gripEnt->aimDebounceTime = level.time + gripEnt->client->ps.torsoAnimTimer;
13099 								}
13100 							}
13101 						}
13102 						if ( gripEnt->NPC )
13103 						{
13104 							if ( !(gripEnt->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
13105 							{//not falling to their death
13106 								gripEnt->NPC->nextBStateThink = level.time + holdTime;
13107 							}
13108 							//if still alive after stopped gripping, let them wake others up
13109 							if ( gripEnt->health > 0 )
13110 							{
13111 								G_AngerAlert( gripEnt );
13112 							}
13113 						}
13114 					}
13115 				}
13116 				else
13117 				{
13118 					gripEnt->s.eFlags &= ~EF_FORCE_GRIPPED;
13119 					if ( gripEnt->s.eType == ET_MISSILE )
13120 					{//continue normal movement
13121 						if ( gripEnt->s.weapon == WP_THERMAL )
13122 						{
13123 							gripEnt->s.pos.trType = TR_INTERPOLATE;
13124 						}
13125 						else
13126 						{
13127 							gripEnt->s.pos.trType = TR_LINEAR;//FIXME: what about gravity-effected projectiles?
13128 						}
13129 						VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase );
13130 						gripEnt->s.pos.trTime = level.time;
13131 					}
13132 					else
13133 					{//drop it
13134 						gripEnt->e_ThinkFunc = thinkF_G_RunObject;
13135 						gripEnt->nextthink = level.time + FRAMETIME;
13136 						gripEnt->s.pos.trType = TR_GRAVITY;
13137 						VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase );
13138 						gripEnt->s.pos.trTime = level.time;
13139 					}
13140 				}
13141 			}
13142 			self->s.loopSound = 0;
13143 			self->client->ps.forceGripEntityNum = ENTITYNUM_NONE;
13144 		}
13145 		if ( self->client->ps.torsoAnim == BOTH_FORCEGRIP_HOLD )
13146 		{
13147 			NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCEGRIP_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13148 		}
13149 		break;
13150 	case FP_LIGHTNING:
13151 		if ( self->NPC )
13152 		{
13153 			TIMER_Set( self, "holdLightning", -level.time );
13154 		}
13155 		if ( self->client->ps.torsoAnim == BOTH_FORCELIGHTNING_HOLD
13156 			|| self->client->ps.torsoAnim == BOTH_FORCELIGHTNING_START )
13157 		{
13158 			NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13159 		}
13160 		else if ( self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_HOLD
13161 			|| self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_START )
13162 		{
13163 			NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_2HANDEDLIGHTNING_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13164 		}
13165 		if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
13166 		{//don't do it again for 3 seconds, minimum... FIXME: this should be automatic once regeneration is slower (normal)
13167 			self->client->ps.forcePowerDebounce[FP_LIGHTNING] = level.time + 3000;//FIXME: define?
13168 		}
13169 		else
13170 		{//stop the looping sound
13171 			self->client->ps.forcePowerDebounce[FP_LIGHTNING] = level.time + 1000;//FIXME: define?
13172 			self->s.loopSound = 0;
13173 		}
13174 		break;
13175 	case FP_RAGE:
13176 		self->client->ps.forceRageRecoveryTime = level.time + 10000;//recover for 10 seconds
13177 		if ( self->client->ps.forcePowerDuration[FP_RAGE] > level.time )
13178 		{//still had time left, we cut it short
13179 			self->client->ps.forceRageRecoveryTime -= (self->client->ps.forcePowerDuration[FP_RAGE] - level.time);//minus however much time you had left when you cut it short
13180 		}
13181 		if ( !self->s.number )
13182 		{//player using force speed
13183 			if ( g_timescale->value != 1.0 )
13184 			{
13185 				if ( !(self->client->ps.forcePowersActive&(1<<FP_SPEED)) )
13186 				{//not slowed down because of force speed
13187 					gi.cvar_set("timescale", "1");
13188 				}
13189 			}
13190 		}
13191 		//FIXME: reset my current anim, keeping current frame, but with proper anim speed
13192 		//		otherwise, the anim will continue playing at high speed
13193 		self->s.loopSound = 0;
13194 		if ( self->NPC )
13195 		{
13196 			Jedi_RageStop( self );
13197 		}
13198 		if ( self->chestBolt != -1 )
13199 		{
13200 			G_StopEffect("force/rage2", self->playerModel, self->chestBolt, self->s.number );
13201 		}
13202 		break;
13203 	case FP_DRAIN:
13204 		if ( self->NPC )
13205 		{
13206 			TIMER_Set( self, "draining", -level.time );
13207 		}
13208 		if ( self->client->ps.forcePowerLevel[FP_DRAIN] < FORCE_LEVEL_2 )
13209 		{//don't do it again for 3 seconds, minimum... FIXME: this should be automatic once regeneration is slower (normal)
13210 			self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 3000;//FIXME: define?
13211 		}
13212 		else
13213 		{//stop the looping sound
13214 			self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 1000;//FIXME: define?
13215 			self->s.loopSound = 0;
13216 		}
13217 		//drop them
13218 		if ( self->client->ps.forceDrainEntityNum < ENTITYNUM_WORLD )
13219 		{
13220 			drainEnt = &g_entities[self->client->ps.forceDrainEntityNum];
13221 			if ( drainEnt )
13222 			{
13223 				if ( drainEnt->client )
13224 				{
13225 					drainEnt->client->ps.eFlags &= ~EF_FORCE_DRAINED;
13226 					//VectorClear( drainEnt->client->ps.velocity );
13227 					if ( drainEnt->health > 0 )
13228 					{
13229 						if ( drainEnt->client->ps.forcePowerDebounce[FP_PUSH] > level.time )
13230 						{//they probably pushed out of it
13231 						}
13232 						else
13233 						{
13234 							//NPC_SetAnim( drainEnt, SETANIM_BOTH, BOTH_HUGGEESTOP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13235 							if ( drainEnt->client->ps.torsoAnim != BOTH_FORCEPUSH )
13236 							{//don't stop the push
13237 								drainEnt->client->ps.torsoAnimTimer = 0;
13238 							}
13239 							drainEnt->client->ps.legsAnimTimer = 0;
13240 						}
13241 						if ( drainEnt->NPC )
13242 						{//if still alive after stopped draining, let them wake others up
13243 							G_AngerAlert( drainEnt );
13244 						}
13245 					}
13246 					else
13247 					{//leave the effect playing on them for a few seconds
13248 						//drainEnt->client->ps.eFlags |= EF_FORCE_DRAINED;
13249 						drainEnt->s.powerups |= ( 1 << PW_DRAINED );
13250 						drainEnt->client->ps.powerups[PW_DRAINED] = level.time + Q_irand( 1000, 4000 );
13251 					}
13252 				}
13253 			}
13254 			self->client->ps.forceDrainEntityNum = ENTITYNUM_NONE;
13255 		}
13256 		if ( self->client->ps.torsoAnim == BOTH_HUGGER1 )
13257 		{//old anim
13258 			NPC_SetAnim( self, SETANIM_BOTH, BOTH_HUGGERSTOP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13259 		}
13260 		else if ( self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_START
13261 			|| self->client->ps.torsoAnim ==  BOTH_FORCE_DRAIN_GRAB_HOLD )
13262 		{//new anim
13263 			NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRAB_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13264 		}
13265 		else if ( self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_HOLD
13266 			|| self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_START )
13267 		{
13268 			NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_DRAIN_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13269 		}
13270 		break;
13271 	case FP_PROTECT:
13272 		self->s.loopSound = 0;
13273 		break;
13274 	case FP_ABSORB:
13275 		self->s.loopSound = 0;
13276 		if ( self->client->ps.legsAnim == BOTH_FORCE_ABSORB_START )
13277 		{
13278 			NPC_SetAnim( self, SETANIM_LEGS, BOTH_FORCE_ABSORB_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13279 		}
13280 		if ( self->client->ps.torsoAnim == BOTH_FORCE_ABSORB_START )
13281 		{
13282 			NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_ABSORB_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13283 		}
13284 		if ( self->client->ps.forcePowerLevel[FP_ABSORB] < FORCE_LEVEL_2 )
13285 		{//was stuck, free us in case we interrupted it or something
13286 			self->client->ps.weaponTime = 0;
13287 			self->client->ps.pm_flags &= ~PMF_TIME_KNOCKBACK;
13288 			self->client->ps.pm_time = 0;
13289 			if ( self->s.number )
13290 			{//NPC
13291 				self->painDebounceTime = 0;
13292 			}
13293 			else
13294 			{//player
13295 				self->aimDebounceTime = 0;
13296 			}
13297 		}
13298 		break;
13299 	case FP_SEE:
13300 		self->s.loopSound = 0;
13301 		break;
13302 	default:
13303 		break;
13304 	}
13305 }
13306 
WP_ForceForceThrow(gentity_t * thrower)13307 void WP_ForceForceThrow( gentity_t *thrower )
13308 {
13309 	if ( !thrower || !thrower->client )
13310 	{
13311 		return;
13312 	}
13313 	qboolean relock = qfalse;
13314 	if ( !(thrower->client->ps.forcePowersKnown&(1<<FP_PUSH)) )
13315 	{//give them push just for this specific purpose
13316 		thrower->client->ps.forcePowersKnown |= (1<<FP_PUSH);
13317 		thrower->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_1;
13318 	}
13319 
13320 	if ( thrower->NPC
13321 		&& (thrower->NPC->aiFlags&NPCAI_HEAL_ROSH)
13322 		&& (thrower->flags&FL_LOCK_PLAYER_WEAPONS) )
13323 	{
13324 		thrower->flags &= ~FL_LOCK_PLAYER_WEAPONS;
13325 		relock = qtrue;
13326 	}
13327 
13328 	ForceThrow( thrower, qfalse );
13329 
13330 	if ( relock )
13331 	{
13332 		thrower->flags |= FL_LOCK_PLAYER_WEAPONS;
13333 	}
13334 
13335 	if ( thrower )
13336 	{//take it back off
13337 		thrower->client->ps.forcePowersKnown &= ~(1<<FP_PUSH);
13338 		thrower->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_0;
13339 	}
13340 }
13341 
13342 extern qboolean PM_ForceJumpingUp( gentity_t *gent );
WP_ForcePowerRun(gentity_t * self,forcePowers_t forcePower,usercmd_t * cmd)13343 static void WP_ForcePowerRun( gentity_t *self, forcePowers_t forcePower, usercmd_t *cmd )
13344 {
13345 	float				speed, newSpeed;
13346 	gentity_t			*gripEnt, *drainEnt;
13347 	vec3_t				angles, dir, gripOrg, gripEntOrg;
13348 	float				dist;
13349 	extern usercmd_t	ucmd;
13350 
13351 	switch( (int)forcePower )
13352 	{
13353 	case FP_HEAL:
13354 		if ( self->client->ps.forceHealCount >= FP_MaxForceHeal(self) || self->health >= self->client->ps.stats[STAT_MAX_HEALTH] )
13355 		{//fully healed or used up all 25
13356 			if ( !Q3_TaskIDPending( self, TID_CHAN_VOICE ) )
13357 			{
13358 				int index = Q_irand( 1, 4 );
13359 				if ( self->s.number < MAX_CLIENTS )
13360 				{
13361 					G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d_%c.mp3", index, g_sex->string[0] ) );
13362 				}
13363 				else if ( self->NPC )
13364 				{
13365 					if ( self->NPC->blockedSpeechDebounceTime <= level.time )
13366 					{//enough time has passed since our last speech
13367 						if ( Q3_TaskIDPending( self, TID_CHAN_VOICE ) )
13368 						{//not playing a scripted line
13369 							//say "Ahhh...."
13370 							if ( self->NPC->stats.sex == SEX_MALE
13371 								|| self->NPC->stats.sex == SEX_NEUTRAL )
13372 							{
13373 								G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d_m.mp3", index ) );
13374 							}
13375 							else//all other sexes use female sounds
13376 							{
13377 								G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d_f.mp3", index ) );
13378 							}
13379 						}
13380 					}
13381 				}
13382 			}
13383 			WP_ForcePowerStop( self, forcePower );
13384 		}
13385 		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) ) )
13386 		{//attacked or was hit while healing...
13387 			//stop healing
13388 			WP_ForcePowerStop( self, forcePower );
13389 		}
13390 		else if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 && ( cmd->rightmove || cmd->forwardmove || cmd->upmove > 0 ) )
13391 		{//moved while healing... FIXME: also, in WP_ForcePowerStart, stop healing if any other force power is used
13392 			//stop healing
13393 			WP_ForcePowerStop( self, forcePower );
13394 		}
13395 		else if ( self->client->ps.forcePowerDebounce[FP_HEAL] < level.time )
13396 		{//time to heal again
13397 			if ( WP_ForcePowerAvailable( self, forcePower, 4 ) )
13398 			{//have available power
13399 				int healInterval = FP_ForceHealInterval( self );
13400 				int	healAmount = 1;//hard, normal healing rate
13401 				if ( self->s.number < MAX_CLIENTS )
13402 				{
13403 					if ( g_spskill->integer == 1 )
13404 					{//medium, heal twice as fast
13405 						healAmount *= 2;
13406 					}
13407 					else if ( g_spskill->integer == 0 )
13408 					{//easy, heal 3 times as fast...
13409 						healAmount *= 3;
13410 					}
13411 				}
13412 				if ( self->health + healAmount > self->client->ps.stats[STAT_MAX_HEALTH] )
13413 				{
13414 					healAmount = self->client->ps.stats[STAT_MAX_HEALTH] - self->health;
13415 				}
13416 				self->health += healAmount;
13417 				self->client->ps.forceHealCount += healAmount;
13418 				self->client->ps.forcePowerDebounce[FP_HEAL] = level.time + healInterval;
13419 				WP_ForcePowerDrain( self, forcePower, 4 );
13420 			}
13421 			else
13422 			{//stop
13423 				WP_ForcePowerStop( self, forcePower );
13424 			}
13425 		}
13426 		break;
13427 	case FP_LEVITATION:
13428 		if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE && !self->client->ps.forceJumpZStart )
13429 		{//done with jump
13430 			WP_ForcePowerStop( self, forcePower );
13431 		}
13432 		else
13433 		{
13434 			if ( PM_ForceJumpingUp( self ) )
13435 			{//holding jump in air
13436 				if ( cmd->upmove > 10 )
13437 				{//still trying to go up
13438 					if ( WP_ForcePowerAvailable( self, FP_LEVITATION, 1 ) )
13439 					{
13440 						if ( self->client->ps.forcePowerDebounce[FP_LEVITATION] < level.time )
13441 						{
13442 							WP_ForcePowerDrain( self, FP_LEVITATION, 5 );
13443 							self->client->ps.forcePowerDebounce[FP_LEVITATION] = level.time + 100;
13444 						}
13445 						self->client->ps.forcePowersActive |= ( 1 << FP_LEVITATION );
13446 						self->client->ps.forceJumpCharge = 1;//just used as a flag for the player, cleared when he lands
13447 					}
13448 					else
13449 					{//cut the jump short
13450 						WP_ForcePowerStop( self, forcePower );
13451 					}
13452 				}
13453 				else
13454 				{//cut the jump short
13455 					WP_ForcePowerStop( self, forcePower );
13456 				}
13457 			}
13458 			else
13459 			{
13460 				WP_ForcePowerStop( self, forcePower );
13461 			}
13462 		}
13463 		break;
13464 	case FP_SPEED:
13465 		speed = forceSpeedValue[self->client->ps.forcePowerLevel[FP_SPEED]];
13466 		if ( !self->s.number )
13467 		{//player using force speed
13468 			if ( !(self->client->ps.forcePowersActive&(1<<FP_RAGE))
13469 				|| self->client->ps.forcePowerLevel[FP_SPEED] >= self->client->ps.forcePowerLevel[FP_RAGE] )
13470 			{//either not using rage or rage is at a lower level than speed
13471 				gi.cvar_set("timescale", va("%4.2f", speed));
13472 				if ( g_timescale->value > speed )
13473 				{
13474 					newSpeed = g_timescale->value - 0.05;
13475 					if ( newSpeed < speed )
13476 					{
13477 						newSpeed = speed;
13478 					}
13479 					gi.cvar_set("timescale", va("%4.2f", newSpeed));
13480 				}
13481 			}
13482 		}
13483 		break;
13484 	case FP_PUSH:
13485 		break;
13486 	case FP_PULL:
13487 		break;
13488 	case FP_TELEPATHY:
13489 		break;
13490 	case FP_GRIP:
13491 		if ( !WP_ForcePowerAvailable( self, FP_GRIP, 0 )
13492 			|| (self->client->ps.forcePowerLevel[FP_GRIP]>FORCE_LEVEL_1&&!self->s.number&&!(cmd->buttons&BUTTON_FORCEGRIP)) )
13493 		{
13494 			WP_ForcePowerStop( self, FP_GRIP );
13495 			return;
13496 		}
13497 		else if ( self->client->ps.forceGripEntityNum >= 0 && self->client->ps.forceGripEntityNum < ENTITYNUM_WORLD )
13498 		{
13499 			gripEnt = &g_entities[self->client->ps.forceGripEntityNum];
13500 
13501 			if ( !gripEnt || !gripEnt->inuse )
13502 			{//invalid or freed ent
13503 				WP_ForcePowerStop( self, FP_GRIP );
13504 				return;
13505 			}
13506 			else
13507 //rww - RAGDOLL_BEGIN
13508 #ifndef JK2_RAGDOLL_GRIPNOHEALTH
13509 //rww - RAGDOLL_END
13510 			if ( gripEnt->health <= 0 && gripEnt->takedamage )//FIXME: what about things that never had health or lose takedamage when they die?
13511 			{//either invalid ent, or dead ent
13512 				WP_ForcePowerStop( self, FP_GRIP );
13513 				return;
13514 			}
13515 			else
13516 //rww - RAGDOLL_BEGIN
13517 #endif
13518 //rww - RAGDOLL_END
13519 			if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_1
13520 				&& gripEnt->client
13521 				&& gripEnt->client->ps.groundEntityNum == ENTITYNUM_NONE
13522 				&& gripEnt->client->moveType != MT_FLYSWIM )
13523 			{
13524 				WP_ForcePowerStop( self, FP_GRIP );
13525 				return;
13526 			}
13527 			else if ( gripEnt->client && gripEnt->client->moveType == MT_FLYSWIM && VectorLengthSquared( gripEnt->client->ps.velocity ) > (300*300) )
13528 			{//flying creature broke free
13529 				WP_ForcePowerStop( self, FP_GRIP );
13530 				return;
13531 			}
13532 			else if ( gripEnt->client
13533 				&& gripEnt->health>0	//dead dudes don't fly
13534 				&& (gripEnt->client->NPC_class == CLASS_BOBAFETT || gripEnt->client->NPC_class == CLASS_ROCKETTROOPER)
13535 				&& self->client->ps.forcePowerDebounce[FP_GRIP] < level.time
13536 				&& !Q_irand( 0, 3 )
13537 				)
13538 			{//boba fett - fly away!
13539 				gripEnt->client->ps.forceJumpCharge = 0;//so we don't play the force flip anim
13540 				gripEnt->client->ps.velocity[2] = 250;
13541 				gripEnt->client->ps.forceJumpZStart = gripEnt->currentOrigin[2];//so we don't take damage if we land at same height
13542 				gripEnt->client->ps.pm_flags |= PMF_JUMPING;
13543 				G_AddEvent( gripEnt, EV_JUMP, 0 );
13544 				JET_FlyStart( gripEnt );
13545 				WP_ForcePowerStop( self, FP_GRIP );
13546 				return;
13547 			}
13548 			else if ( gripEnt->NPC
13549 				&& gripEnt->client
13550 				&& gripEnt->client->ps.forcePowersKnown
13551 				&& (gripEnt->client->NPC_class==CLASS_REBORN||gripEnt->client->ps.weapon==WP_SABER)
13552 				&& !Jedi_CultistDestroyer(gripEnt)
13553 				&& !Q_irand( 0, 100-(gripEnt->NPC->stats.evasion*8)-(g_spskill->integer*20) ) )
13554 			{//a jedi who broke free FIXME: maybe have some minimum grip length- a reaction time?
13555 				WP_ForceForceThrow( gripEnt );
13556 				//FIXME: I need to go into some pushed back anim...
13557 				WP_ForcePowerStop( self, FP_GRIP );
13558 				return;
13559 			}
13560 			else if ( PM_SaberInAttack( self->client->ps.saberMove )
13561 				|| PM_SaberInStart( self->client->ps.saberMove ) )
13562 			{//started an attack
13563 				WP_ForcePowerStop( self, FP_GRIP );
13564 				return;
13565 			}
13566 			else
13567 			{
13568 				int gripLevel = self->client->ps.forcePowerLevel[FP_GRIP];
13569 				if ( gripEnt->client )
13570 				{
13571 					gripLevel = WP_AbsorbConversion( gripEnt, gripEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_GRIP, self->client->ps.forcePowerLevel[FP_GRIP], forcePowerNeeded[gripLevel] );
13572 				}
13573 				if ( !gripLevel )
13574 				{
13575 					WP_ForcePowerStop( self, forcePower );
13576 					return;
13577 				}
13578 
13579 				if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
13580 				{//holding it
13581 					NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEGRIP_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13582 					if ( self->client->ps.torsoAnimTimer < 100 ){//we were already playing this anim, we didn't want to restart it, but we want to hold it for at least 100ms, sooo....
13583 
13584 						self->client->ps.torsoAnimTimer = 100;
13585 					}
13586 				}
13587 				//get their org
13588 				VectorCopy( self->client->ps.viewangles, angles );
13589 				angles[0] -= 10;
13590 				AngleVectors( angles, dir, NULL, NULL );
13591 				if ( gripEnt->client )
13592 				{//move
13593 					VectorCopy( gripEnt->client->renderInfo.headPoint, gripEntOrg );
13594 				}
13595 				else
13596 				{
13597 					VectorCopy( gripEnt->currentOrigin, gripEntOrg );
13598 				}
13599 
13600 				//how far are they
13601 				dist = Distance( self->client->renderInfo.handLPoint, gripEntOrg );
13602 				if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_2 &&
13603 					(!InFront( gripEntOrg, self->client->renderInfo.handLPoint, self->client->ps.viewangles, 0.3f ) ||
13604 						DistanceSquared( gripEntOrg, self->client->renderInfo.handLPoint ) > FORCE_GRIP_DIST_SQUARED))
13605 				{//must face them
13606 					WP_ForcePowerStop( self, FP_GRIP );
13607 					return;
13608 				}
13609 
13610 				//check for lift or carry
13611 				if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2
13612 					&& (!gripEnt->client || (!gripEnt->message&&!(gripEnt->flags&FL_NO_KNOCKBACK))) )
13613 				{//carry
13614 					//cap dist
13615 					if ( dist > FORCE_GRIP_3_MAX_DIST )
13616 					{
13617 						dist = FORCE_GRIP_3_MAX_DIST;
13618 					}
13619 					else if ( dist < FORCE_GRIP_3_MIN_DIST )
13620 					{
13621 						dist = FORCE_GRIP_3_MIN_DIST;
13622 					}
13623 					VectorMA( self->client->renderInfo.handLPoint, dist, dir, gripOrg );
13624 				}
13625 				else if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
13626 				{//just lift
13627 					VectorCopy( self->client->ps.forceGripOrg, gripOrg );
13628 				}
13629 				else
13630 				{
13631 					VectorCopy( gripEnt->currentOrigin, gripOrg );
13632 				}
13633 				if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
13634 				{//if holding him, make sure there's a clear LOS between my hand and him
13635 					trace_t gripTrace;
13636 					gi.trace( &gripTrace, self->client->renderInfo.handLPoint, NULL, NULL, gripEntOrg, ENTITYNUM_NONE, MASK_FORCE_PUSH, (EG2_Collision)0, 0 );
13637 					if ( gripTrace.startsolid
13638 						|| gripTrace.allsolid
13639 						|| gripTrace.fraction < 1.0f )
13640 					{//no clear trace, drop them
13641 						WP_ForcePowerStop( self, FP_GRIP );
13642 						return;
13643 					}
13644 				}
13645 				//now move them
13646 				if ( gripEnt->client )
13647 				{
13648 					if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
13649 					{//level 1 just holds them
13650  						VectorSubtract( gripOrg, gripEntOrg, gripEnt->client->ps.velocity );
13651 						if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2
13652 							&& (!gripEnt->client || (!gripEnt->message&&!(gripEnt->flags&FL_NO_KNOCKBACK)) ) )
13653 						{//level 2 just lifts them
13654 							float gripDist = VectorNormalize( gripEnt->client->ps.velocity )/3.0f;
13655 							if ( gripDist < 20.0f )
13656 							{
13657 								if (gripDist<2.0f)
13658 								{
13659 									VectorClear(gripEnt->client->ps.velocity);
13660 								}
13661 								else
13662 								{
13663 									VectorScale( gripEnt->client->ps.velocity, (gripDist*gripDist), gripEnt->client->ps.velocity );
13664 								}
13665 							}
13666 							else
13667 							{
13668 								VectorScale( gripEnt->client->ps.velocity, (gripDist*gripDist), gripEnt->client->ps.velocity );
13669 							}
13670 						}
13671 					}
13672 					//stop them from thinking
13673 					gripEnt->client->ps.pm_time = 2000;
13674 					gripEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
13675 					if ( gripEnt->NPC )
13676 					{
13677 						if ( !(gripEnt->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
13678 						{//not falling to their death
13679 							gripEnt->NPC->nextBStateThink = level.time + 2000;
13680 						}
13681 						if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
13682 						{//level 1 just holds them
13683 							vectoangles( dir, angles );
13684 							gripEnt->NPC->desiredYaw = AngleNormalize180(angles[YAW]+180);
13685 							gripEnt->NPC->desiredPitch = -angles[PITCH];
13686 							SaveNPCGlobals();
13687 							SetNPCGlobals( gripEnt );
13688 							NPC_UpdateAngles( qtrue, qtrue );
13689 							gripEnt->NPC->last_ucmd.angles[0] = ucmd.angles[0];
13690 							gripEnt->NPC->last_ucmd.angles[1] = ucmd.angles[1];
13691 							gripEnt->NPC->last_ucmd.angles[2] = ucmd.angles[2];
13692 							RestoreNPCGlobals();
13693 							//FIXME: why does he turn back to his original angles once he dies or is let go?
13694 						}
13695 					}
13696 					else if ( !gripEnt->s.number )
13697 					{
13698 						//vectoangles( dir, angles );
13699 						//gripEnt->client->ps.viewangles[0] = -angles[0];
13700 						//gripEnt->client->ps.viewangles[1] = AngleNormalize180(angles[YAW]+180);
13701 						gripEnt->enemy = self;
13702 						NPC_SetLookTarget( gripEnt, self->s.number, level.time+1000 );
13703 					}
13704 
13705 					gripEnt->client->ps.eFlags |= EF_FORCE_GRIPPED;
13706 					//dammit!  Make sure that saber stays off!
13707 					WP_DeactivateSaber( gripEnt );
13708 				}
13709 				else
13710 				{//move
13711 					if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
13712 					{//level 1 just holds them
13713 						VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase );
13714 						VectorSubtract( gripOrg, gripEntOrg, gripEnt->s.pos.trDelta );
13715 						if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2
13716 							&& (!gripEnt->client || (!gripEnt->message&&!(gripEnt->flags&FL_NO_KNOCKBACK))) )
13717 						{//level 2 just lifts them
13718 							VectorScale( gripEnt->s.pos.trDelta, 10, gripEnt->s.pos.trDelta );
13719 						}
13720 						gripEnt->s.pos.trType = TR_LINEAR;
13721 						gripEnt->s.pos.trTime = level.time;
13722 					}
13723 
13724 					gripEnt->s.eFlags |= EF_FORCE_GRIPPED;
13725 				}
13726 
13727 				//Shouldn't this be discovered?
13728 				//AddSightEvent( self, gripOrg, 128, AEL_DANGER, 20 );
13729 				AddSightEvent( self, gripOrg, 128, AEL_DISCOVERED, 20 );
13730 
13731 				if ( self->client->ps.forcePowerDebounce[FP_GRIP] < level.time )
13732 				{
13733 					//GEntity_PainFunc( gripEnt, self, self, gripOrg, 0, MOD_CRUSH );
13734 					if ( !gripEnt->client
13735 						|| gripEnt->client->NPC_class != CLASS_VEHICLE
13736 						|| (gripEnt->m_pVehicle
13737 							&& gripEnt->m_pVehicle->m_pVehicleInfo
13738 							&& gripEnt->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL) )
13739 					{//we don't damage the empty vehicle
13740 						gripEnt->painDebounceTime = 0;
13741 						int gripDmg = forceGripDamage[self->client->ps.forcePowerLevel[FP_GRIP]];
13742 						if ( gripLevel != -1 )
13743 						{
13744 							if ( gripLevel == 1 )
13745 							{
13746 								gripDmg = floor((float)gripDmg/3.0f);
13747 							}
13748 							else //if ( gripLevel == 2 )
13749 							{
13750 								gripDmg = floor((float)gripDmg/1.5f);
13751 							}
13752 						}
13753 						G_Damage( gripEnt, self, self, dir, gripOrg, gripDmg, DAMAGE_NO_ARMOR, MOD_CRUSH );//MOD_???
13754 					}
13755 					if ( gripEnt->s.number )
13756 					{
13757 						if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 )
13758 						{//do damage faster at level 3
13759 							self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 150, 750 );
13760 						}
13761 						else
13762 						{
13763 							self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 250, 1000 );
13764 						}
13765 					}
13766 					else
13767 					{//player takes damage faster
13768 						self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 100, 600 );
13769 					}
13770 					if ( forceGripDamage[self->client->ps.forcePowerLevel[FP_GRIP]] > 0 )
13771 					{//no damage at level 1
13772 						WP_ForcePowerDrain( self, FP_GRIP, 3 );
13773 					}
13774 					if ( self->client->NPC_class == CLASS_KYLE
13775 						&& (self->spawnflags&1) )
13776 					{//"Boss" Kyle
13777 						if ( gripEnt->client )
13778 						{
13779 							if ( !Q_irand( 0, 2 ) )
13780 							{//toss him aside!
13781 								vec3_t vRt;
13782 								AngleVectors( self->currentAngles, NULL, vRt, NULL );
13783 								//stop gripping
13784 								TIMER_Set( self, "gripping", -level.time );
13785 								WP_ForcePowerStop( self, FP_GRIP );
13786 								//now toss him
13787 								if ( Q_irand( 0, 1 ) )
13788 								{//throw him to my left
13789 									NPC_SetAnim( self, SETANIM_BOTH, BOTH_TOSS1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13790 									VectorScale( vRt, -1500.0f, gripEnt->client->ps.velocity );
13791 									G_Knockdown( gripEnt, self, vRt, 500, qfalse );
13792 								}
13793 								else
13794 								{//throw him to my right
13795 									NPC_SetAnim( self, SETANIM_BOTH, BOTH_TOSS2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13796 									VectorScale( vRt, 1500.0f, gripEnt->client->ps.velocity );
13797 									G_Knockdown( gripEnt, self, vRt, 500, qfalse );
13798 								}
13799 								//don't do anything for a couple seconds
13800 								self->client->ps.weaponTime = self->client->ps.torsoAnimTimer + 2000;
13801 								self->painDebounceTime = level.time + self->client->ps.weaponTime;
13802 								//stop moving
13803 								VectorClear( self->client->ps.velocity );
13804 								VectorClear( self->client->ps.moveDir );
13805 								return;
13806 							}
13807 						}
13808 					}
13809 				}
13810 				else
13811 				{
13812 					//WP_ForcePowerDrain( self, FP_GRIP, 0 );
13813 					if ( !gripEnt->enemy )
13814 					{
13815 						if ( gripEnt->client
13816 							&& gripEnt->client->playerTeam == TEAM_PLAYER
13817 							&& self->s.number < MAX_CLIENTS
13818 							&& self->client
13819 							&& self->client->playerTeam == TEAM_PLAYER )
13820 						{//this shouldn't make allies instantly turn on you, let the damage->pain routine determine how allies should react to this
13821 						}
13822 						else
13823 						{
13824 							G_SetEnemy( gripEnt, self );
13825 						}
13826 					}
13827 				}
13828 				if ( gripEnt->client && gripEnt->health > 0 )
13829 				{
13830 					int anim = BOTH_CHOKE3; //left-handed choke
13831 					if ( gripEnt->client->ps.weapon == WP_NONE || gripEnt->client->ps.weapon == WP_MELEE )
13832 					{
13833 						anim = BOTH_CHOKE1; //two-handed choke
13834 					}
13835 					if ( self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
13836 					{//still on ground, only set anim on torso
13837 						NPC_SetAnim( gripEnt, SETANIM_TORSO, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13838 					}
13839 					else
13840 					{//in air, set on whole body
13841 						NPC_SetAnim( gripEnt, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13842 					}
13843 					gripEnt->painDebounceTime = level.time + 2000;
13844 				}
13845 			}
13846 		}
13847 		break;
13848 	case FP_LIGHTNING:
13849 		if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 )
13850 		{//higher than level 1
13851 			if ( cmd->buttons & BUTTON_FORCE_LIGHTNING )
13852 			{//holding it keeps it going
13853 				self->client->ps.forcePowerDuration[FP_LIGHTNING] = level.time + 500;
13854 				ForceLightningAnim( self );
13855 			}
13856 		}
13857 		if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) )
13858 		{
13859 			WP_ForcePowerStop( self, forcePower );
13860 		}
13861 		else
13862 		{
13863 			ForceShootLightning( self );
13864 			if ( self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING
13865 				|| self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_START
13866 				|| self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_HOLD
13867 				|| self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_RELEASE )
13868 			{//jackin' 'em up, Palpatine-style
13869 				//extra cost
13870 				WP_ForcePowerDrain( self, forcePower, 0 );
13871 			}
13872 			WP_ForcePowerDrain( self, forcePower, 0 );
13873 		}
13874 		break;
13875 	//new Jedi Academy force powers
13876 	case FP_RAGE:
13877 		if (self->health < 1)
13878 		{
13879 			WP_ForcePowerStop(self, forcePower);
13880 			break;
13881 		}
13882 		if (self->client->ps.forceRageDrainTime < level.time)
13883 		{
13884 			int addTime = 400;
13885 
13886 			self->health -= 2;
13887 
13888 			if (self->client->ps.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_1)
13889 			{
13890 				addTime = 100;
13891 			}
13892 			else if (self->client->ps.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_2)
13893 			{
13894 				addTime = 250;
13895 			}
13896 			else if (self->client->ps.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_3)
13897 			{
13898 				addTime = 500;
13899 			}
13900 			self->client->ps.forceRageDrainTime = level.time + addTime;
13901 		}
13902 
13903 		if ( self->health < 1 )
13904 		{
13905 			self->health = 1;
13906 			//WP_ForcePowerStop( self, forcePower );
13907 		}
13908 		else
13909 		{
13910 			self->client->ps.stats[STAT_HEALTH] = self->health;
13911 
13912 			speed = forceSpeedValue[self->client->ps.forcePowerLevel[FP_RAGE]-1];
13913 			if ( !self->s.number )
13914 			{//player using force rage
13915 				if ( !(self->client->ps.forcePowersActive&(1<<FP_SPEED))
13916 					|| self->client->ps.forcePowerLevel[FP_RAGE] > self->client->ps.forcePowerLevel[FP_SPEED]+1 )
13917 				{//either not using speed or speed is at a lower level than rage
13918 					gi.cvar_set("timescale", va("%4.2f", speed));
13919 					if ( g_timescale->value > speed )
13920 					{
13921 						newSpeed = g_timescale->value - 0.05;
13922 						if ( newSpeed < speed )
13923 						{
13924 							newSpeed = speed;
13925 						}
13926 						gi.cvar_set("timescale", va("%4.2f", newSpeed));
13927 					}
13928 				}
13929 			}
13930 		}
13931 		break;
13932 	case FP_DRAIN:
13933 		if ( cmd->buttons & BUTTON_FORCE_DRAIN )
13934 		{//holding it keeps it going
13935 			self->client->ps.forcePowerDuration[FP_DRAIN] = level.time + 500;
13936 		}
13937 		if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) )
13938 		{//no more force power, stop
13939 			WP_ForcePowerStop( self, forcePower );
13940 		}
13941 		else if ( self->client->ps.forceDrainEntityNum >= 0 && self->client->ps.forceDrainEntityNum < ENTITYNUM_WORLD )
13942 		{//holding someone
13943 			if ( !WP_ForcePowerAvailable( self, FP_DRAIN, 0 )
13944 				|| (self->client->ps.forcePowerLevel[FP_DRAIN]>FORCE_LEVEL_1
13945 					&& !self->s.number
13946 					&& !(cmd->buttons&BUTTON_FORCE_DRAIN)
13947 					&& self->client->ps.forcePowerDuration[FP_DRAIN]<level.time) )
13948 			{
13949 				WP_ForcePowerStop( self, FP_DRAIN );
13950 				return;
13951 			}
13952 			else
13953 			{
13954 				drainEnt = &g_entities[self->client->ps.forceDrainEntityNum];
13955 
13956 				if ( !drainEnt )
13957 				{//invalid ent
13958 					WP_ForcePowerStop( self, FP_DRAIN );
13959 					return;
13960 				}
13961 				else if ( (drainEnt->health <= 0&&drainEnt->takedamage) )//FIXME: what about things that never had health or lose takedamage when they die?
13962 				{//dead ent
13963 					WP_ForcePowerStop( self, FP_DRAIN );
13964 					return;
13965 				}
13966 				else if ( drainEnt->client && drainEnt->client->moveType == MT_FLYSWIM && VectorLengthSquared( NPC->client->ps.velocity ) > (300*300) )
13967 				{//flying creature broke free
13968 					WP_ForcePowerStop( self, FP_DRAIN );
13969 					return;
13970 				}
13971 				else if ( drainEnt->client
13972 					&& drainEnt->health>0	//dead dudes don't fly
13973 					&& (drainEnt->client->NPC_class == CLASS_BOBAFETT || drainEnt->client->NPC_class == CLASS_ROCKETTROOPER)
13974 					&& self->client->ps.forcePowerDebounce[FP_DRAIN] < level.time
13975 					&& !Q_irand( 0, 10 ) )
13976 				{//boba fett - fly away!
13977 					drainEnt->client->ps.forceJumpCharge = 0;//so we don't play the force flip anim
13978 					drainEnt->client->ps.velocity[2] = 250;
13979 					drainEnt->client->ps.forceJumpZStart = drainEnt->currentOrigin[2];//so we don't take damage if we land at same height
13980 					drainEnt->client->ps.pm_flags |= PMF_JUMPING;
13981 					G_AddEvent( drainEnt, EV_JUMP, 0 );
13982 					JET_FlyStart( drainEnt );
13983 					WP_ForcePowerStop( self, FP_DRAIN );
13984 					return;
13985 				}
13986 				else if ( drainEnt->NPC
13987 					&& drainEnt->client
13988 					&& drainEnt->client->ps.forcePowersKnown
13989 					&& (drainEnt->client->NPC_class==CLASS_REBORN||drainEnt->client->ps.weapon==WP_SABER)
13990 					&& !Jedi_CultistDestroyer(drainEnt)
13991 					&& level.time-(self->client->ps.forcePowerDebounce[FP_DRAIN]>self->client->ps.forcePowerLevel[FP_DRAIN]*500)//at level 1, I always get at least 500ms of drain, at level 3 I get 1500ms
13992 					&& !Q_irand( 0, 100-(drainEnt->NPC->stats.evasion*8)-(g_spskill->integer*15) ) )
13993 				{//a jedi who broke free FIXME: maybe have some minimum grip length- a reaction time?
13994 					WP_ForceForceThrow( drainEnt );
13995 					//FIXME: I need to go into some pushed back anim...
13996 					WP_ForcePowerStop( self, FP_DRAIN );
13997 					//can't drain again for 2 seconds
13998 					self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 4000;
13999 					return;
14000 				}
14001 				else
14002 				{
14003 					/*
14004 					int drainLevel = WP_AbsorbConversion( drainEnt, drainEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_DRAIN, self->client->ps.forcePowerLevel[FP_DRAIN], forcePowerNeeded[self->client->ps.forcePowerLevel[FP_DRAIN]] );
14005 					if ( !drainLevel )
14006 					{
14007 						WP_ForcePowerStop( self, forcePower );
14008 						return;
14009 					}
14010 					*/
14011 
14012 					//NPC_SetAnim( self, SETANIM_BOTH, BOTH_HUGGER1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
14013 					if ( self->client->ps.torsoAnim != BOTH_FORCE_DRAIN_GRAB_START
14014 						|| !self->client->ps.torsoAnimTimer )
14015 					{
14016 						NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRAB_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
14017 					}
14018 					if ( self->handLBolt != -1 )
14019 					{
14020 						G_PlayEffect( G_EffectIndex( "force/drain_hand" ), self->playerModel, self->handLBolt, self->s.number, self->currentOrigin, 200, qtrue );
14021 					}
14022 					if ( self->handRBolt != -1 )
14023 					{
14024 						G_PlayEffect( G_EffectIndex( "force/drain_hand" ), self->playerModel, self->handRBolt, self->s.number, self->currentOrigin, 200, qtrue );
14025 					}
14026 
14027 					//how far are they
14028 					dist = Distance( self->client->renderInfo.eyePoint, drainEnt->currentOrigin );
14029 					if ( DistanceSquared( drainEnt->currentOrigin, self->currentOrigin ) > FORCE_DRAIN_DIST_SQUARED )
14030 					{//must be close, got away somehow!
14031 						WP_ForcePowerStop( self, FP_DRAIN );
14032 						return;
14033 					}
14034 
14035 					//keep my saber off!
14036 					WP_DeactivateSaber( self, qtrue );
14037 					if ( drainEnt->client )
14038 					{
14039 						//now move them
14040 						VectorCopy( self->client->ps.viewangles, angles );
14041 						angles[0] = 0;
14042 						AngleVectors( angles, dir, NULL, NULL );
14043 						/*
14044 						VectorMA( self->currentOrigin, self->maxs[0], dir, drainEnt->client->ps.forceDrainOrg );
14045 						trace_t	trace;
14046 						gi.trace( &trace, drainEnt->currentOrigin, drainEnt->mins, drainEnt->maxs, drainEnt->client->ps.forceDrainOrg, drainEnt->s.number, drainEnt->clipmask );
14047 						if ( !trace.startsolid && !trace.allsolid )
14048 						{
14049 							G_SetOrigin( drainEnt, trace.endpos );
14050 							gi.linkentity( drainEnt );
14051 							VectorClear( drainEnt->client->ps.velocity );
14052 						}
14053 						VectorMA( self->currentOrigin, self->maxs[0]*0.5f, dir, drainEnt->client->ps.forceDrainOrg );
14054 						*/
14055 						//stop them from thinking
14056 						drainEnt->client->ps.pm_time = 2000;
14057 						drainEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
14058 						if ( drainEnt->NPC )
14059 						{
14060 							if ( !(drainEnt->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
14061 							{//not falling to their death
14062 								drainEnt->NPC->nextBStateThink = level.time + 2000;
14063 							}
14064 							vectoangles( dir, angles );
14065 							drainEnt->NPC->desiredYaw = AngleNormalize180(angles[YAW]+180);
14066 							drainEnt->NPC->desiredPitch = -angles[PITCH];
14067 							SaveNPCGlobals();
14068 							SetNPCGlobals( drainEnt );
14069 							NPC_UpdateAngles( qtrue, qtrue );
14070 							drainEnt->NPC->last_ucmd.angles[0] = ucmd.angles[0];
14071 							drainEnt->NPC->last_ucmd.angles[1] = ucmd.angles[1];
14072 							drainEnt->NPC->last_ucmd.angles[2] = ucmd.angles[2];
14073 							RestoreNPCGlobals();
14074 							//FIXME: why does he turn back to his original angles once he dies or is let go?
14075 						}
14076 						else if ( !drainEnt->s.number )
14077 						{
14078 							drainEnt->enemy = self;
14079 							NPC_SetLookTarget( drainEnt, self->s.number, level.time+1000 );
14080 						}
14081 
14082 						drainEnt->client->ps.eFlags |= EF_FORCE_DRAINED;
14083 						//dammit!  Make sure that saber stays off!
14084 						WP_DeactivateSaber( drainEnt, qtrue );
14085 					}
14086 					//Shouldn't this be discovered?
14087 					AddSightEvent( self, drainEnt->currentOrigin, 128, AEL_DISCOVERED, 20 );
14088 
14089 					if ( self->client->ps.forcePowerDebounce[FP_DRAIN] < level.time )
14090 					{
14091 						int drainLevel = WP_AbsorbConversion( drainEnt, drainEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_DRAIN, self->client->ps.forcePowerLevel[FP_DRAIN], forcePowerNeeded[self->client->ps.forcePowerLevel[FP_DRAIN]] );
14092 						if ( (drainLevel && drainLevel == -1)
14093 							|| Q_irand( drainLevel, 3 ) < 3 )
14094 						{//the drain is being absorbed
14095 							ForceDrainEnt( self, drainEnt );
14096 						}
14097 						WP_ForcePowerDrain( self, FP_DRAIN, 3 );
14098 					}
14099 					else
14100 					{
14101 						if ( !Q_irand( 0, 4 ) )
14102 						{
14103 							WP_ForcePowerDrain( self, FP_DRAIN, 1 );
14104 						}
14105 						if ( !drainEnt->enemy )
14106 						{
14107 							G_SetEnemy( drainEnt, self );
14108 						}
14109 					}
14110 					if ( drainEnt->health > 0 )
14111 					{//still alive
14112 						//NPC_SetAnim( drainEnt, SETANIM_BOTH, BOTH_HUGGEE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
14113 						NPC_SetAnim( drainEnt, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRABBED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
14114 					}
14115 				}
14116 			}
14117 		}
14118 		else if ( self->client->ps.forcePowerLevel[forcePower] > FORCE_LEVEL_1 )
14119 		{//regular distance-drain
14120 			if ( cmd->buttons & BUTTON_FORCE_DRAIN )
14121 			{//holding it keeps it going
14122 				self->client->ps.forcePowerDuration[FP_DRAIN] = level.time + 500;
14123 				if ( self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_START )
14124 				{
14125 					if ( !self->client->ps.torsoAnimTimer )
14126 					{
14127 						NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_DRAIN_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
14128 					}
14129 					else
14130 					{
14131 						NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_DRAIN_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
14132 					}
14133 				}
14134 				else
14135 				{
14136 					NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_DRAIN_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
14137 				}
14138 			}
14139 			if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) )
14140 			{
14141 				WP_ForcePowerStop( self, forcePower );
14142 			}
14143 			else
14144 			{
14145 				ForceShootDrain( self );
14146 			}
14147 		}
14148 		break;
14149 	case FP_PROTECT:
14150 		break;
14151 	case FP_ABSORB:
14152 		break;
14153 	case FP_SEE:
14154 		break;
14155 	default:
14156 		break;
14157 	}
14158 }
14159 
WP_CheckForcedPowers(gentity_t * self,usercmd_t * ucmd)14160 void WP_CheckForcedPowers( gentity_t *self, usercmd_t *ucmd )
14161 {
14162 	for ( int forcePower = FP_FIRST; forcePower < NUM_FORCE_POWERS; forcePower++ )
14163 	{
14164 		if ( (self->client->ps.forcePowersForced&(1<<forcePower)) )
14165 		{
14166 			switch ( forcePower )
14167 			{
14168 			case FP_HEAL:
14169 				ForceHeal( self );
14170 				//do only once
14171 				self->client->ps.forcePowersForced &= ~(1<<forcePower);
14172 				break;
14173 			case FP_LEVITATION:
14174 				//nothing
14175 				break;
14176 			case FP_SPEED:
14177 				ForceSpeed( self );
14178 				//do only once
14179 				self->client->ps.forcePowersForced &= ~(1<<forcePower);
14180 				break;
14181 			case FP_PUSH:
14182 				ForceThrow( self, qfalse );
14183 				//do only once
14184 				self->client->ps.forcePowersForced &= ~(1<<forcePower);
14185 				break;
14186 			case FP_PULL:
14187 				ForceThrow( self, qtrue );
14188 				//do only once
14189 				self->client->ps.forcePowersForced &= ~(1<<forcePower);
14190 				break;
14191 			case FP_TELEPATHY:
14192 				//FIXME: target at enemy?
14193 				ForceTelepathy( self );
14194 				//do only once
14195 				self->client->ps.forcePowersForced &= ~(1<<forcePower);
14196 				break;
14197 			case FP_GRIP:
14198 				ucmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS|BUTTON_FORCE_DRAIN|BUTTON_FORCE_LIGHTNING);
14199 				ucmd->buttons |= BUTTON_FORCEGRIP;
14200 				//holds until cleared
14201 				break;
14202 			case FP_LIGHTNING:
14203 				ucmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS|BUTTON_FORCEGRIP|BUTTON_FORCE_DRAIN);
14204 				ucmd->buttons |= BUTTON_FORCE_LIGHTNING;
14205 				//holds until cleared
14206 				break;
14207 			case FP_SABERTHROW:
14208 				ucmd->buttons &= ~(BUTTON_ATTACK|BUTTON_FORCE_FOCUS|BUTTON_FORCEGRIP|BUTTON_FORCE_DRAIN|BUTTON_FORCE_LIGHTNING);
14209 				ucmd->buttons |= BUTTON_ALT_ATTACK;
14210 				//holds until cleared?
14211 				break;
14212 			case FP_SABER_DEFENSE:
14213 				//nothing
14214 				break;
14215 			case FP_SABER_OFFENSE:
14216 				//nothing
14217 				break;
14218 			case FP_RAGE:
14219 				ForceRage( self );
14220 				//do only once
14221 				self->client->ps.forcePowersForced &= ~(1<<forcePower);
14222 				break;
14223 			case FP_PROTECT:
14224 				ForceProtect( self );
14225 				//do only once
14226 				self->client->ps.forcePowersForced &= ~(1<<forcePower);
14227 				break;
14228 			case FP_ABSORB:
14229 				ForceAbsorb( self );
14230 				//do only once
14231 				self->client->ps.forcePowersForced &= ~(1<<forcePower);
14232 				break;
14233 			case FP_DRAIN:
14234 				ucmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS|BUTTON_FORCEGRIP|BUTTON_FORCE_LIGHTNING);
14235 				ucmd->buttons |= BUTTON_FORCE_DRAIN;
14236 				//holds until cleared
14237 				break;
14238 			case FP_SEE:
14239 				//nothing
14240 				break;
14241 			}
14242 		}
14243 	}
14244 }
14245 
WP_ForcePowersUpdate(gentity_t * self,usercmd_t * ucmd)14246 void WP_ForcePowersUpdate( gentity_t *self, usercmd_t *ucmd )
14247 {
14248 	qboolean	usingForce = qfalse;
14249 	int			i;
14250 	//see if any force powers are running
14251 	if ( !self )
14252 	{
14253 		return;
14254 	}
14255 	if ( !self->client )
14256 	{
14257 		return;
14258 	}
14259 
14260 	if ( self->health <= 0 )
14261 	{//if dead, deactivate any active force powers
14262 		for ( i = 0; i < NUM_FORCE_POWERS; i++ )
14263 		{
14264 			if ( self->client->ps.forcePowerDuration[i] || (self->client->ps.forcePowersActive&( 1 << i )) )
14265 			{
14266 				WP_ForcePowerStop( self, (forcePowers_t)i );
14267 				self->client->ps.forcePowerDuration[i] = 0;
14268 			}
14269 		}
14270 		return;
14271 	}
14272 
14273 	WP_CheckForcedPowers( self, ucmd );
14274 
14275 	if ( !self->s.number )
14276 	{//player uses different kind of force-jump
14277 	}
14278 	else
14279 	{
14280 		/*
14281 		if ( ucmd->buttons & BUTTON_FORCEJUMP )
14282 		{//just charging up
14283 			ForceJumpCharge( self, ucmd );
14284 		}
14285 		else */
14286 		if ( self->client->ps.forceJumpCharge )
14287 		{//let go of charge button, have charge
14288 			//if leave the ground by some other means, cancel the force jump so we don't suddenly jump when we land.
14289 			if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE
14290 				&& !PM_SwimmingAnim( self->client->ps.legsAnim ) )
14291 			{//FIXME: stop sound?
14292 				//self->client->ps.forceJumpCharge = 0;
14293 				//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.
14294 			}
14295 			else
14296 			{//still on ground, so jump
14297 				ForceJump( self, ucmd );
14298 				return;
14299 			}
14300 		}
14301 	}
14302 
14303 	if ( ucmd->buttons & BUTTON_FORCEGRIP )
14304 	{
14305 		ForceGrip( self );
14306 	}
14307 
14308 	if ( !self->s.number
14309 		&& self->client->NPC_class == CLASS_BOBAFETT )
14310 	{//Boba Fett
14311 		if ( ucmd->buttons & BUTTON_FORCE_LIGHTNING )
14312 		{//start flamethrower
14313 			Boba_DoFlameThrower( self );
14314 			return;
14315 		}
14316 		else if ( self->client->ps.forcePowerDuration[FP_LIGHTNING] )
14317 		{
14318 			self->client->ps.forcePowerDuration[FP_LIGHTNING] = 0;
14319 			Boba_StopFlameThrower( self );
14320 			return;
14321 		}
14322 	}
14323 	else if ( ucmd->buttons & BUTTON_FORCE_LIGHTNING )
14324 	{
14325 		ForceLightning( self );
14326 	}
14327 
14328 	if ( ucmd->buttons & BUTTON_FORCE_DRAIN )
14329 	{
14330 		if ( !ForceDrain2( self ) )
14331 		{//can't drain-grip someone right in front
14332 			if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_1 )
14333 			{//try ranged
14334 				ForceDrain( self, qtrue );
14335 			}
14336 		}
14337 	}
14338 
14339 	for ( i = 0; i < NUM_FORCE_POWERS; i++ )
14340 	{
14341 		if ( self->client->ps.forcePowerDuration[i] )
14342 		{
14343 			if ( self->client->ps.forcePowerDuration[i] < level.time )
14344 			{
14345 				if ( (self->client->ps.forcePowersActive&( 1 << i )) )
14346 				{//turn it off
14347 					WP_ForcePowerStop( self, (forcePowers_t)i );
14348 				}
14349 				self->client->ps.forcePowerDuration[i] = 0;
14350 			}
14351 		}
14352 		if ( (self->client->ps.forcePowersActive&( 1 << i )) )
14353 		{
14354 			usingForce = qtrue;
14355 			WP_ForcePowerRun( self, (forcePowers_t)i, ucmd );
14356 		}
14357 	}
14358 	if ( self->client->ps.saberInFlight )
14359 	{//don't regen force power while throwing saber
14360 		if ( self->client->ps.saberEntityNum < ENTITYNUM_NONE && self->client->ps.saberEntityNum > 0 )//player is 0
14361 		{//
14362 			if ( &g_entities[self->client->ps.saberEntityNum] != NULL && g_entities[self->client->ps.saberEntityNum].s.pos.trType == TR_LINEAR )
14363 			{//fell to the ground and we're trying to pull it back
14364 				usingForce = qtrue;
14365 			}
14366 		}
14367 	}
14368 	if ( PM_ForceUsingSaberAnim( self->client->ps.torsoAnim ) )
14369 	{
14370 		usingForce = qtrue;
14371 	}
14372 	if ( !usingForce )
14373 	{//when not using the force, regenerate at 10 points per second
14374 		if ( self->client->ps.forcePowerRegenDebounceTime < level.time )
14375 		{
14376 			WP_ForcePowerRegenerate( self, self->client->ps.forcePowerRegenAmount );
14377 			self->client->ps.forcePowerRegenDebounceTime = level.time + self->client->ps.forcePowerRegenRate;
14378 			if ( self->client->ps.forceRageRecoveryTime >= level.time )
14379 			{//regen half as fast
14380 				self->client->ps.forcePowerRegenDebounceTime += self->client->ps.forcePowerRegenRate;
14381 			}
14382 		}
14383 	}
14384 }
14385 
WP_InitForcePowers(gentity_t * ent)14386 void WP_InitForcePowers( gentity_t *ent )
14387 {
14388 	if ( !ent || !ent->client )
14389 	{
14390 		return;
14391 	}
14392 
14393 	if ( !ent->client->ps.forcePowerMax )
14394 	{
14395 		ent->client->ps.forcePowerMax = FORCE_POWER_MAX;
14396 	}
14397 	if ( !ent->client->ps.forcePowerRegenRate )
14398 	{
14399 		ent->client->ps.forcePowerRegenRate = 100;
14400 	}
14401 	ent->client->ps.forcePower = ent->client->ps.forcePowerMax;
14402 	ent->client->ps.forcePowerRegenDebounceTime = level.time;
14403 
14404 	ent->client->ps.forceGripEntityNum = ent->client->ps.forceDrainEntityNum = ent->client->ps.pullAttackEntNum = ENTITYNUM_NONE;
14405 	ent->client->ps.forceRageRecoveryTime = 0;
14406 	ent->client->ps.forceDrainTime = 0;
14407 	ent->client->ps.pullAttackTime = 0;
14408 
14409 	if ( ent->s.number < MAX_CLIENTS )
14410 	{//player
14411 		if ( !g_cheats->integer )//devmaps give you all the FP
14412 		{
14413 			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_1;
14414 			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_1;
14415 		}
14416 		else
14417 		{
14418 			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 )|( 1<< FP_RAGE )|( 1<< FP_DRAIN )|( 1<< FP_PROTECT )|( 1<< FP_ABSORB )|( 1<< FP_SEE );
14419 			ent->client->ps.forcePowerLevel[FP_HEAL] = FORCE_LEVEL_2;
14420 			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_2;
14421 			ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_1;
14422 			ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_1;
14423 			ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_2;
14424 			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_2;
14425 			ent->client->ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_1;
14426 			ent->client->ps.forcePowerLevel[FP_TELEPATHY] = FORCE_LEVEL_2;
14427 
14428 			ent->client->ps.forcePowerLevel[FP_RAGE] = FORCE_LEVEL_1;
14429 			ent->client->ps.forcePowerLevel[FP_PROTECT] = FORCE_LEVEL_1;
14430 			ent->client->ps.forcePowerLevel[FP_ABSORB] = FORCE_LEVEL_1;
14431 			ent->client->ps.forcePowerLevel[FP_DRAIN] = FORCE_LEVEL_1;
14432 			ent->client->ps.forcePowerLevel[FP_SEE] = FORCE_LEVEL_1;
14433 
14434 			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
14435 			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
14436 			ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_2;
14437 		}
14438 	}
14439 }
14440 
WP_DoingMoronicForcedAnimationForForcePowers(gentity_t * ent)14441 bool WP_DoingMoronicForcedAnimationForForcePowers(gentity_t *ent)
14442 {
14443 	// :P --eez
14444 	if( !ent->client ) return false;
14445 	if( ent->client->ps.legsAnim == BOTH_FORCE_ABSORB_START ||
14446 		ent->client->ps.legsAnim == BOTH_FORCE_ABSORB_END ||
14447 		ent->client->ps.legsAnim == BOTH_FORCE_ABSORB ||
14448 		ent->client->ps.torsoAnim == BOTH_FORCE_RAGE ||
14449 		ent->client->ps.legsAnim == BOTH_FORCE_PROTECT )
14450 		return true;
14451 	return false;
14452 }
14453