/*
===========================================================================
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2013 - 2015, OpenJK contributors

This file is part of the OpenJK source code.

OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

#include "g_headers.h"

#include "g_local.h"
#include "anims.h"
#include "b_local.h"
#include "bg_local.h"
#include "g_functions.h"
#include "wp_saber.h"
#include "../../code/qcommon/tri_coll_test.h"

#define MAX_SABER_VICTIMS 16
static int		victimEntityNum[MAX_SABER_VICTIMS];
static float	totalDmg[MAX_SABER_VICTIMS];
static vec3_t	dmgDir[MAX_SABER_VICTIMS];
static vec3_t	dmgSpot[MAX_SABER_VICTIMS];
static float	dmgFraction[MAX_SABER_VICTIMS];
static int		hitLoc[MAX_SABER_VICTIMS];
static qboolean	hitDismember[MAX_SABER_VICTIMS];
static int		hitDismemberLoc[MAX_SABER_VICTIMS];
static vec3_t	saberHitLocation, saberHitNormal={0,0,1.0};
static float	saberHitFraction;
static float	sabersCrossed;
static int		saberHitEntity;
static int		numVictims = 0;

#define SABER_PITCH_HACK 90


extern cvar_t	*g_timescale;
extern cvar_t	*g_dismemberment;

extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType );
extern qboolean		G_ClearViewEntity( gentity_t *ent );
extern void			G_SetViewEntity( gentity_t *self, gentity_t *viewEntity );
extern qboolean G_ControlledByPlayer( gentity_t *self );
extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
extern void CG_ChangeWeapon( int num );
extern void G_AngerAlert( gentity_t *self );
extern void G_ReflectMissile( gentity_t *ent, gentity_t *missile, vec3_t forward );
extern int G_CheckLedgeDive( gentity_t *self, float checkDist, vec3_t checkVel, qboolean tryOpposite, qboolean tryPerp );
extern void G_BounceMissile( gentity_t *ent, trace_t *trace );
extern qboolean G_PointInBounds( const vec3_t point, const vec3_t mins, const vec3_t maxs );
extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
extern void NPC_UseResponse( gentity_t *self, gentity_t *user, qboolean useWhenDone );
extern void WP_FireDreadnoughtBeam( gentity_t *ent );
extern void G_MissileImpacted( gentity_t *ent, gentity_t *other, vec3_t impactPos, vec3_t normal, int hitLoc=HL_NONE );
extern evasionType_t Jedi_SaberBlockGo( gentity_t *self, usercmd_t *cmd, vec3_t pHitloc, vec3_t phitDir, gentity_t *incoming, float dist = 0.0f );
extern int PM_PickAnim( gentity_t *self, int minAnim, int maxAnim );
extern void NPC_SetPainEvent( gentity_t *self );
extern qboolean PM_SwimmingAnim( int anim );
extern qboolean PM_InAnimForSaberMove( int anim, int saberMove );
extern qboolean PM_SpinningSaberAnim( int anim );
extern qboolean PM_SaberInSpecialAttack( int anim );
extern qboolean PM_SaberInAttack( int move );
extern qboolean PM_SaberInTransition( int move );
extern qboolean PM_SaberInStart( int move );
extern qboolean PM_SaberInTransitionAny( int move );
extern qboolean PM_SaberInBounce( int move );
extern qboolean PM_SaberInParry( int move );
extern qboolean PM_SaberInKnockaway( int move );
extern qboolean PM_SaberInBrokenParry( int move );
extern qboolean PM_SpinningSaberAnim( int anim );
extern int PM_SaberBounceForAttack( int move );
extern int PM_BrokenParryForAttack( int move );
extern int PM_KnockawayForParry( int move );
extern qboolean PM_FlippingAnim( int anim );
extern qboolean PM_RollingAnim( int anim );
extern qboolean PM_CrouchAnim( int anim );
extern qboolean PM_SaberInIdle( int move );
extern qboolean PM_SaberInReflect( int move );
extern qboolean PM_InSpecialJump( int anim );
extern qboolean PM_InKnockDown( playerState_t *ps );
extern int PM_PowerLevelForSaberAnim( playerState_t *ps );
extern void PM_VelocityForSaberMove( playerState_t *ps, vec3_t throwDir );
extern qboolean PM_VelocityForBlockedMove( playerState_t *ps, vec3_t throwDir );
extern int Jedi_ReCalcParryTime( gentity_t *self, evasionType_t evasionType );
extern qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc );
extern void Jedi_PlayDeflectSound( gentity_t *self );
extern void Jedi_PlayBlockedPushSound( gentity_t *self );
extern qboolean Jedi_WaitingAmbush( gentity_t *self );
extern void Jedi_Ambush( gentity_t *self );
extern qboolean Jedi_SaberBusy( gentity_t *self );

void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
void WP_SaberInFlightReflectCheck( gentity_t *self, usercmd_t *ucmd  );

void WP_SaberDrop( gentity_t *self, gentity_t *saber );
qboolean WP_SaberLose( gentity_t *self, vec3_t throwDir );
void WP_SaberReturn( gentity_t *self, gentity_t *saber );
void WP_SaberBlock( gentity_t *saber, vec3_t hitloc, qboolean missleBlock );
void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock );
void ForceThrow( gentity_t *self, qboolean pull );
qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
void WP_ForcePowerDrain( gentity_t *self, forcePowers_t forcePower, int overrideAmt );

extern cvar_t	*g_saberAutoBlocking;
extern cvar_t	*g_saberRealisticCombat;
extern int g_crosshairEntNum;

int		g_saberFlashTime = 0;
vec3_t	g_saberFlashPos = {0,0,0};

int forcePowerNeeded[NUM_FORCE_POWERS] =
{
	0,//FP_HEAL,//instant
	10,//FP_LEVITATION,//hold/duration
	50,//FP_SPEED,//duration
	15,//FP_PUSH,//hold/duration
	15,//FP_PULL,//hold/duration
	20,//FP_TELEPATHY,//instant
	1,//FP_GRIP,//hold/duration - FIXME: 30?
	1,//FP_LIGHTNING,//hold/duration
	20,//FP_SABERTHROW,
	1,//FP_SABER_DEFENSE,
	0,//FP_SABER_OFFENSE,
	//NUM_FORCE_POWERS
};

float forceJumpStrength[NUM_FORCE_POWER_LEVELS] =
{
	JUMP_VELOCITY,//normal jump
	420,
	590,
	840
};

float forceJumpHeight[NUM_FORCE_POWER_LEVELS] =
{
	32,//normal jump (+stepheight+crouchdiff = 66)
	96,//(+stepheight+crouchdiff = 130)
	192,//(+stepheight+crouchdiff = 226)
	384//(+stepheight+crouchdiff = 418)
};

float forceJumpHeightMax[NUM_FORCE_POWER_LEVELS] =
{
	66,//normal jump (32+stepheight(18)+crouchdiff(24) = 74)
	130,//(96+stepheight(18)+crouchdiff(24) = 138)
	226,//(192+stepheight(18)+crouchdiff(24) = 234)
	418//(384+stepheight(18)+crouchdiff(24) = 426)
};

float forcePushPullRadius[NUM_FORCE_POWER_LEVELS] =
{
	0,//none
	384,//256,
	448,//384,
	512
};

float forcePushCone[NUM_FORCE_POWER_LEVELS] =
{
	1.0f,//none
	1.0f,
	0.8f,
	0.6f
};

float forcePullCone[NUM_FORCE_POWER_LEVELS] =
{
	1.0f,//none
	1.0f,
	1.0f,
	0.8f
};

float forceSpeedValue[NUM_FORCE_POWER_LEVELS] =
{
	1.0f,//none
	0.75f,
	0.5f,
	0.25f
};

float forceSpeedRangeMod[NUM_FORCE_POWER_LEVELS] =
{
	0.0f,//none
	30.0f,
	45.0f,
	60.0f
};

float forceSpeedFOVMod[NUM_FORCE_POWER_LEVELS] =
{
	0.0f,//none
	20.0f,
	30.0f,
	40.0f
};

int forceGripDamage[NUM_FORCE_POWER_LEVELS] =
{
	0,//none
	0,
	6,
	9
};

int mindTrickTime[NUM_FORCE_POWER_LEVELS] =
{
	0,//none
	5000,
	10000,
	15000
};

//NOTE: keep in synch with table below!!!
int saberThrowDist[NUM_FORCE_POWER_LEVELS] =
{
	0,//none
	256,
	400,
	400
};

//NOTE: keep in synch with table above!!!
int saberThrowDistSquared[NUM_FORCE_POWER_LEVELS] =
{
	0,//none
	65536,
	160000,
	160000
};

int parryDebounce[NUM_FORCE_POWER_LEVELS] =
{
	1000000,//if don't even have defense, can't use defense!
	300,
	150,
	50
};

float saberAnimSpeedMod[NUM_FORCE_POWER_LEVELS] =
{
	0.0f,//if don't even have offense, can't use offense!
	0.75f,
	1.0f,
	2.0f
};
//SABER INITIALIZATION======================================================================

void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *psWeaponModel )
{
	if (!psWeaponModel)
	{
		assert (psWeaponModel);
		return;
	}
	if ( ent && ent->client && ent->client->NPC_class == CLASS_GALAKMECH )
	{//hack for galakmech, no weaponmodel
		ent->weaponModel = -1;
		return;
	}

	char weaponModel[MAX_QPATH];
	Q_strncpyz(weaponModel, psWeaponModel, sizeof(weaponModel));
	if (char *spot = (char*)Q_stristr(weaponModel, ".md3") ) {
        *spot = 0;
		spot = (char*)Q_stristr(weaponModel, "_w");//i'm using the in view weapon array instead of scanning the item list, so put the _w back on
		if (!spot&&!Q_stristr(weaponModel, "noweap"))
		{
			Q_strcat (weaponModel, sizeof(weaponModel), "_w");
		}
		Q_strcat (weaponModel, sizeof(weaponModel), ".glm");	//and change to ghoul2
	}

	if ( ent->playerModel == -1 )
	{
		return;
	}
	// give us a sabre model
	ent->weaponModel = gi.G2API_InitGhoul2Model(ent->ghoul2, weaponModel, G_ModelIndex( weaponModel ), NULL_HANDLE, NULL_HANDLE, 0, 0);
	if ( ent->weaponModel != -1 )
	{
		// attach it to the hand
		gi.G2API_AttachG2Model(&ent->ghoul2[ent->weaponModel], &ent->ghoul2[ent->playerModel],
					ent->handRBolt, ent->playerModel);
		// set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0
		gi.G2API_AddBolt(&ent->ghoul2[ent->weaponModel], "*flash");
	  	//gi.G2API_SetLodBias( &ent->ghoul2[ent->weaponModel], 0 );
	}
}

//----------------------------------------------------------
void G_Throw( gentity_t *targ, vec3_t newDir, float push )
//----------------------------------------------------------
{
	vec3_t	kvel;
	float	mass;

	if ( targ->physicsBounce > 0 )	//overide the mass
	{
		mass = targ->physicsBounce;
	}
	else
	{
		mass = 200;
	}

	if ( g_gravity->value > 0 )
	{
		VectorScale( newDir, g_knockback->value * (float)push / mass * 0.8, kvel );
		kvel[2] = newDir[2] * g_knockback->value * (float)push / mass * 1.5;
	}
	else
	{
		VectorScale( newDir, g_knockback->value * (float)push / mass, kvel );
	}

	if ( targ->client )
	{
		VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity );
	}
	else if ( targ->s.pos.trType != TR_STATIONARY && targ->s.pos.trType != TR_LINEAR_STOP && targ->s.pos.trType != TR_NONLINEAR_STOP )
	{
		VectorAdd( targ->s.pos.trDelta, kvel, targ->s.pos.trDelta );
		VectorCopy( targ->currentOrigin, targ->s.pos.trBase );
		targ->s.pos.trTime = level.time;
	}

	// set the timer so that the other client can't cancel
	// out the movement immediately
	if ( targ->client && !targ->client->ps.pm_time )
	{
		int		t;

		t = push * 2;

		if ( t < 50 )
		{
			t = 50;
		}
		if ( t > 200 )
		{
			t = 200;
		}
		targ->client->ps.pm_time = t;
		targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
	}
}

int WP_SetSaberModel( gclient_t *client, class_t npcClass )
{
	if ( client )
	{
		switch ( npcClass )
		{
		case CLASS_DESANN://Desann
			client->ps.saberModel = "models/weapons2/saber_desann/saber_w.glm";
			break;
		case CLASS_LUKE://Luke
			client->ps.saberModel = "models/weapons2/saber_luke/saber_w.glm";
			break;
		case CLASS_KYLE://Kyle NPC and player
			client->ps.saberModel = "models/weapons2/saber/saber_w.glm";
			break;
		default://reborn and tavion and everyone else
			client->ps.saberModel = "models/weapons2/saber_reborn/saber_w.glm";
			break;
		}
		return ( G_ModelIndex( client->ps.saberModel ) );
	}
	else
	{
		switch ( npcClass )
		{
		case CLASS_DESANN://Desann
			return ( G_ModelIndex( "models/weapons2/saber_desann/saber_w.glm" ) );
			break;
		case CLASS_LUKE://Luke
			return ( G_ModelIndex( "models/weapons2/saber_luke/saber_w.glm" ) );
			break;
		case CLASS_KYLE://Kyle NPC and player
			return ( G_ModelIndex( "models/weapons2/saber/saber_w.glm" ) );
			break;
		default://reborn and tavion and everyone else
			return ( G_ModelIndex( "models/weapons2/saber_reborn/saber_w.glm" ) );
			break;
		}
	}
}

void WP_SaberInitBladeData( gentity_t *ent )
{
	gentity_t *saberent;

	if ( ent->client )
	{
		VectorClear( ent->client->renderInfo.muzzlePoint );
		VectorClear( ent->client->renderInfo.muzzlePointOld );
		//VectorClear( ent->client->renderInfo.muzzlePointNext );
		VectorClear( ent->client->renderInfo.muzzleDir );
		VectorClear( ent->client->renderInfo.muzzleDirOld );
		//VectorClear( ent->client->renderInfo.muzzleDirNext );
		ent->client->ps.saberLengthOld = ent->client->ps.saberLength = 0;
		ent->client->ps.saberLockEnemy = ENTITYNUM_NONE;
		ent->client->ps.saberLockTime = 0;
		if ( ent->s.number )
		{
			if ( ent->client->NPC_class == CLASS_DESANN )
			{
				ent->client->ps.saberAnimLevel = FORCE_LEVEL_4;
			}
			else if ( ent->client->NPC_class == CLASS_TAVION )
			{
				ent->client->ps.saberAnimLevel = FORCE_LEVEL_5;
			}
			else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && (ent->NPC->rank == RANK_CIVILIAN || ent->NPC->rank == RANK_LT_JG) )
			{//grunt and fencer always uses quick attacks
				ent->client->ps.saberAnimLevel = FORCE_LEVEL_1;
			}
			else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && (ent->NPC->rank == RANK_CREWMAN || ent->NPC->rank == RANK_ENSIGN) )
			{//acrobat & force-users always use medium attacks
				ent->client->ps.saberAnimLevel = FORCE_LEVEL_2;
			}
			else if ( ent->client->playerTeam == TEAM_ENEMY && ent->client->NPC_class == CLASS_SHADOWTROOPER )
			{//shadowtroopers
				ent->client->ps.saberAnimLevel = Q_irand( FORCE_LEVEL_1, FORCE_LEVEL_3 );
			}
			else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && ent->NPC->rank == RANK_LT )
			{//boss always starts with strong attacks
				ent->client->ps.saberAnimLevel = FORCE_LEVEL_3;
			}
			else if ( ent->client->NPC_class == CLASS_KYLE )
			{
				ent->client->ps.saberAnimLevel = g_entities[0].client->ps.saberAnimLevel;
			}
			else
			{//?
				ent->client->ps.saberAnimLevel = Q_irand( FORCE_LEVEL_1, FORCE_LEVEL_3 );
			}
		}
		else
		{
			if ( !ent->client->ps.saberAnimLevel )
			{//initialize, but don't reset
				ent->client->ps.saberAnimLevel = FORCE_LEVEL_2;
			}
			cg.saberAnimLevelPending = ent->client->ps.saberAnimLevel;
			if ( ent->client->sess.missionStats.weaponUsed[WP_SABER] <= 0 )
			{//let missionStats know that we actually do have the saber, even if we never use it
				ent->client->sess.missionStats.weaponUsed[WP_SABER] = 1;
			}
		}
		ent->client->ps.saberAttackChainCount = 0;
		if ( ent->client->NPC_class == CLASS_DESANN )
		{//longer saber
			ent->client->ps.saberLengthMax = 48;
		}
		else if ( ent->client->NPC_class == CLASS_REBORN )
		{//shorter saber
			ent->client->ps.saberLengthMax = 32;
		}
		else
		{//standard saber length
			ent->client->ps.saberLengthMax = 40;
		}

		if ( ent->client->ps.saberEntityNum <= 0 || ent->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
		{
			saberent = G_Spawn();
			ent->client->ps.saberEntityNum = saberent->s.number;
			saberent->classname = "lightsaber";

			saberent->s.eType = ET_GENERAL;
			saberent->svFlags = SVF_USE_CURRENT_ORIGIN;
			saberent->s.weapon = WP_SABER;
			saberent->owner = ent;
			saberent->s.otherEntityNum = ent->s.number;

			saberent->clipmask = MASK_SOLID | CONTENTS_LIGHTSABER;
			saberent->contents = CONTENTS_LIGHTSABER;//|CONTENTS_SHOTCLIP;

			VectorSet( saberent->mins, -3.0f, -3.0f, -3.0f );
			VectorSet( saberent->maxs, 3.0f, 3.0f, 3.0f );
			saberent->mass = 10;//necc?

			saberent->s.eFlags |= EF_NODRAW;
			saberent->svFlags |= SVF_NOCLIENT;
/*
Ghoul2 Insert Start
*/
			//FIXME: get saberModel from NPCs.cfg for NPCs?
			saberent->s.modelindex = WP_SetSaberModel( ent->client, ent->client->NPC_class );
			gi.G2API_InitGhoul2Model( saberent->ghoul2, ent->client->ps.saberModel, saberent->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
			// set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0
			gi.G2API_AddBolt( &saberent->ghoul2[0], "*flash" );
			//gi.G2API_SetLodBias( &saberent->ghoul2[0], 0 );

/*
Ghoul2 Insert End
*/

			ent->client->ps.saberInFlight = qfalse;
			ent->client->ps.saberEntityDist = 0;
			ent->client->ps.saberEntityState = SES_LEAVING;

			ent->client->ps.saberMove = 0;

			//FIXME: need a think function to create alerts when turned on or is on, etc.
		}
	}
	else
	{
		ent->client->ps.saberEntityNum = ENTITYNUM_NONE;
		ent->client->ps.saberInFlight = qfalse;
		ent->client->ps.saberEntityDist = 0;
		ent->client->ps.saberEntityState = SES_LEAVING;
	}
}

void WP_SaberUpdateOldBladeData( gentity_t *ent )
{
	if ( ent->client )
	{
		VectorCopy( ent->client->renderInfo.muzzlePoint, ent->client->renderInfo.muzzlePointOld );
		VectorCopy( ent->client->renderInfo.muzzleDir, ent->client->renderInfo.muzzleDirOld );
		if ( ent->client->ps.saberLengthOld <= 0 && ent->client->ps.saberLength > 0 )
		{//just turned on
			//do sound event
			vec3_t	saberOrg;
			VectorCopy( g_entities[ent->client->ps.saberEntityNum].currentOrigin, saberOrg );
			AddSoundEvent( ent, saberOrg, 256, AEL_SUSPICIOUS );
		}
		ent->client->ps.saberLengthOld = ent->client->ps.saberLength;
	}
}



//SABER DAMAGE==============================================================================
//SABER DAMAGE==============================================================================
//SABER DAMAGE==============================================================================
//SABER DAMAGE==============================================================================
//SABER DAMAGE==============================================================================
//SABER DAMAGE==============================================================================
int WPDEBUG_SaberColor( saber_colors_t saberColor )
{
	switch( (int)(saberColor) )
	{
		case SABER_RED:
			return 0x000000ff;
			break;
		case SABER_ORANGE:
			return 0x000088ff;
			break;
		case SABER_YELLOW:
			return 0x0000ffff;
			break;
		case SABER_GREEN:
			return 0x0000ff00;
			break;
		case SABER_BLUE:
			return 0x00ff0000;
			break;
		case SABER_PURPLE:
			return 0x00ff00ff;
			break;
		default:
			return 0x00ffffff;//white
			break;
	}
}

qboolean WP_GetSaberDeflectionAngle( gentity_t *attacker, gentity_t *defender )
{
	vec3_t	temp, att_SaberBase, att_StartPos, saberMidNext, att_HitDir, att_HitPos, def_BladeDir;
	float	att_SaberHitLength, hitDot;

	if ( !attacker || !attacker->client || attacker->client->ps.saberInFlight || attacker->client->ps.saberLength <= 0 )
	{
		return qfalse;
	}
	if ( !defender || !defender->client || defender->client->ps.saberInFlight || defender->client->ps.saberLength <= 0 )
	{
		return qfalse;
	}

	attacker->client->ps.saberBounceMove = LS_NONE;

	//get the attacker's saber base pos at time of impact
	VectorSubtract( attacker->client->renderInfo.muzzlePoint, attacker->client->renderInfo.muzzlePointOld, temp );
	VectorMA( attacker->client->renderInfo.muzzlePointOld, saberHitFraction, temp, att_SaberBase );

	//get the position along the length of the blade where the hit occured
	att_SaberHitLength = Distance( saberHitLocation, att_SaberBase )/attacker->client->ps.saberLength;

	//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?)
	VectorMA( attacker->client->renderInfo.muzzlePointOld, att_SaberHitLength, attacker->client->renderInfo.muzzleDirOld, att_StartPos );
	VectorMA( attacker->client->renderInfo.muzzlePoint, att_SaberHitLength, attacker->client->renderInfo.muzzleDir, saberMidNext );
	VectorSubtract( saberMidNext, att_StartPos, att_HitDir );
	VectorMA( att_StartPos, saberHitFraction, att_HitDir, att_HitPos );
	VectorNormalize( att_HitDir );

	//get the defender's saber dir at time of impact
	VectorSubtract( defender->client->renderInfo.muzzleDirOld, defender->client->renderInfo.muzzleDir, temp );
	VectorMA( defender->client->renderInfo.muzzleDirOld, saberHitFraction, temp, def_BladeDir );

	//now compare
	hitDot = DotProduct( att_HitDir, def_BladeDir );
	if ( hitDot < 0.25f && hitDot > -0.25f )
	{//hit pretty much perpendicular, pop straight back
		attacker->client->ps.saberBounceMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove );
		return qfalse;
	}
	else
	{//a deflection
		vec3_t	att_Right, att_Up, att_DeflectionDir;
		float	swingRDot, swingUDot;

		//get the direction of the deflection
		VectorScale( def_BladeDir, hitDot, att_DeflectionDir );
		//get our bounce straight back direction
		VectorScale( att_HitDir, -1.0f, temp );
		//add the bounce back and deflection
		VectorAdd( att_DeflectionDir, temp, att_DeflectionDir );
		//normalize the result to determine what direction our saber should bounce back toward
		VectorNormalize( att_DeflectionDir );

		//need to know the direction of the deflectoin relative to the attacker's facing
		VectorSet( temp, 0, attacker->client->ps.viewangles[YAW], 0 );//presumes no pitch!
		AngleVectors( temp, NULL, att_Right, att_Up );
		swingRDot = DotProduct( att_Right, att_DeflectionDir );
		swingUDot = DotProduct( att_Up, att_DeflectionDir );

		if ( swingRDot > 0.25f )
		{//deflect to right
			if ( swingUDot > 0.25f )
			{//deflect to top
				attacker->client->ps.saberBounceMove = LS_D1_TR;
			}
			else if ( swingUDot < -0.25f )
			{//deflect to bottom
				attacker->client->ps.saberBounceMove = LS_D1_BR;
			}
			else
			{//deflect horizontally
				attacker->client->ps.saberBounceMove = LS_D1__R;
			}
		}
		else if ( swingRDot < -0.25f )
		{//deflect to left
			if ( swingUDot > 0.25f )
			{//deflect to top
				attacker->client->ps.saberBounceMove = LS_D1_TL;
			}
			else if ( swingUDot < -0.25f )
			{//deflect to bottom
				attacker->client->ps.saberBounceMove = LS_D1_BL;
			}
			else
			{//deflect horizontally
				attacker->client->ps.saberBounceMove = LS_D1__L;
			}
		}
		else
		{//deflect in middle
			if ( swingUDot > 0.25f )
			{//deflect to top
				attacker->client->ps.saberBounceMove = LS_D1_T_;
			}
			else if ( swingUDot < -0.25f )
			{//deflect to bottom
				attacker->client->ps.saberBounceMove = LS_D1_B_;
			}
			else
			{//deflect horizontally?  Well, no such thing as straight back in my face, so use top
				if ( swingRDot > 0 )
				{
					attacker->client->ps.saberBounceMove = LS_D1_TR;
				}
				else if ( swingRDot < 0 )
				{
					attacker->client->ps.saberBounceMove = LS_D1_TL;
				}
				else
				{
					attacker->client->ps.saberBounceMove = LS_D1_T_;
				}
			}
		}
#ifndef FINAL_BUILD
		if ( d_saberCombat->integer )
		{
			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 );
		}
#endif
		return qtrue;
	}
}


void WP_SaberClearDamageForEntNum( int entityNum )
{
#ifndef FINAL_BUILD
	if ( d_saberCombat->integer )
	{
		if ( entityNum )
		{
			Com_Printf( "clearing damage for entnum %d\n", entityNum );
		}
	}
#endif// FINAL_BUILD
	if ( g_saberRealisticCombat->integer > 1 )
	{
		return;
	}
	for ( int i = 0; i < numVictims; i++ )
	{
		if ( victimEntityNum[i] == entityNum )
		{
			totalDmg[i] = 0;//no damage
			hitLoc[i] = HL_NONE;
			hitDismemberLoc[i] = HL_NONE;
			hitDismember[i] = qfalse;
			victimEntityNum[i] = ENTITYNUM_NONE;//like we never hit him
		}
	}
}

extern float damageModifier[];
extern float hitLocHealthPercentage[];
qboolean WP_SaberApplyDamage( gentity_t *ent, float baseDamage, int baseDFlags, qboolean brokenParry )
{
	qboolean	didDamage = qfalse;
	gentity_t	*victim;
	int			dFlags = baseDFlags;
	float		maxDmg;


	if ( !numVictims )
	{
		return qfalse;
	}
	for ( int i = 0; i < numVictims; i++ )
	{
		dFlags = baseDFlags|DAMAGE_DEATH_KNOCKBACK|DAMAGE_NO_HIT_LOC;
		if ( victimEntityNum[i] != ENTITYNUM_NONE && &g_entities[victimEntityNum[i]] != NULL )
		{	// Don't bother with this damage if the fraction is higher than the saber's fraction
			if ( dmgFraction[i] < saberHitFraction || brokenParry )
			{
				victim = &g_entities[victimEntityNum[i]];
				if ( !victim )
				{
					continue;
				}

				if ( victim->e_DieFunc == dieF_maglock_die )
				{//*sigh*, special check for maglocks
					vec3_t testFrom;
					if ( ent->client->ps.saberInFlight )
					{
						VectorCopy( g_entities[ent->client->ps.saberEntityNum].currentOrigin, testFrom );
					}
					else
					{
						VectorCopy( ent->currentOrigin, testFrom );
					}
					testFrom[2] = victim->currentOrigin[2];
					trace_t testTrace;
					gi.trace( &testTrace, testFrom, vec3_origin, vec3_origin, victim->currentOrigin, ent->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
					if ( testTrace.entityNum != victim->s.number )
					{//can only damage maglocks if have a clear trace to the thing's origin
						continue;
					}
				}
				if ( totalDmg[i] > 0 )
				{//actually want to do *some* damage here
					if ( victim->s.weapon == WP_SABER && victim->client && !g_saberRealisticCombat->integer )
					{//dmg vs other saber fighters is modded by hitloc and capped
						totalDmg[i] *= damageModifier[hitLoc[i]];
						if ( hitLoc[i] == HL_NONE )
						{
							maxDmg = 33*baseDamage;
						}
						else
						{
							maxDmg = 50*hitLocHealthPercentage[hitLoc[i]]*baseDamage;//*victim->client->ps.stats[STAT_MAX_HEALTH]*2.0f;
						}
						if ( maxDmg < totalDmg[i] )
						{
							totalDmg[i] = maxDmg;
						}
						//dFlags |= DAMAGE_NO_HIT_LOC;
					}
					//clamp the dmg
					if ( victim->s.weapon != WP_SABER )
					{//clamp the dmg between 25 and maxhealth
						/*
						if ( totalDmg[i] > victim->max_health )
						{
							totalDmg[i] = victim->max_health;
						}
						else */if ( totalDmg[i] < 25 )
						{
							totalDmg[i] = 25;
						}
						if ( totalDmg[i] > 100 )//+(50*g_spskill->integer) )
						{//clamp using same adjustment as in NPC_Begin
							totalDmg[i] = 100;//+(50*g_spskill->integer);
						}
					}
					else
					{//clamp the dmg between 5 and 100
						if ( !victim->s.number && totalDmg[i] > 50 )
						{//never do more than half full health damage to player
							//prevents one-hit kills
							totalDmg[i] = 50;
						}
						else if ( totalDmg[i] > 100 )
						{
							totalDmg[i] = 100;
						}
						else
						{
							if ( totalDmg[i] < 5 )
							{
								totalDmg[i] = 5;
							}
						}
					}

					if ( totalDmg[i] > 0 )
					{
						didDamage = qtrue;
						if( victim->client )
						{
							if ( victim->client->ps.pm_time > 0 && victim->client->ps.pm_flags & PMF_TIME_KNOCKBACK && victim->client->ps.velocity[2] > 0 )
							{//already being knocked around
								dFlags |= DAMAGE_NO_KNOCKBACK;
							}
							if ( g_dismemberment->integer >= 11381138 || g_saberRealisticCombat->integer )
							{
								dFlags |= DAMAGE_DISMEMBER;
								if ( hitDismember[i] )
								{
									victim->client->dismembered = qfalse;
								}
							}
							else if ( hitDismember[i] )
							{
								dFlags |= DAMAGE_DISMEMBER;
							}
							if ( baseDamage <= 1.0f )
							{//very mild damage
								if ( victim->s.number == 0 || victim->client->ps.weapon == WP_SABER || victim->client->NPC_class == CLASS_GALAKMECH )
								{//if it's the player or a saber-user, don't kill them with this blow
									dFlags |= DAMAGE_NO_KILL;
								}
							}
						}
						else
						{
							if ( victim->takedamage )
							{//some other breakable thing
								//create a flash here
								g_saberFlashTime = level.time-50;
								VectorCopy( dmgSpot[i], g_saberFlashPos );
							}
						}
						//victim->hitLoc = hitLoc[i];

						dFlags |= DAMAGE_NO_KNOCKBACK;//okay, let's try no knockback whatsoever...
						dFlags &= ~DAMAGE_DEATH_KNOCKBACK;
						if ( g_saberRealisticCombat->integer )
						{
							dFlags |= DAMAGE_NO_KNOCKBACK;
							dFlags &= ~DAMAGE_DEATH_KNOCKBACK;
							dFlags &= ~DAMAGE_NO_KILL;
						}
						if ( ent->client && !ent->s.number )
						{
							switch( hitLoc[i] )
							{
							case HL_FOOT_RT:
							case HL_FOOT_LT:
							case HL_LEG_RT:
							case HL_LEG_LT:
								ent->client->sess.missionStats.legAttacksCnt++;
								break;
							case HL_WAIST:
							case HL_BACK_RT:
							case HL_BACK_LT:
							case HL_BACK:
							case HL_CHEST_RT:
							case HL_CHEST_LT:
							case HL_CHEST:
								ent->client->sess.missionStats.torsoAttacksCnt++;
								break;
							case HL_ARM_RT:
							case HL_ARM_LT:
							case HL_HAND_RT:
							case HL_HAND_LT:
								ent->client->sess.missionStats.armAttacksCnt++;
								break;
							default:
								ent->client->sess.missionStats.otherAttacksCnt++;
								break;
							}
						}
						G_Damage( victim, ent, ent, dmgDir[i], dmgSpot[i], ceil(totalDmg[i]), dFlags, MOD_SABER, hitDismemberLoc[i] );
#ifndef FINAL_BUILD
						if ( d_saberCombat->integer )
						{
							gi.Printf( S_COLOR_RED"damage: %4.2f, hitLoc %d\n", totalDmg[i], hitLoc[i] );
						}
#endif
						//do the effect
						//G_PlayEffect( G_EffectIndex( "blood_sparks" ), dmgSpot[i], dmgDir[i] );
						if ( ent->s.number == 0 )
						{
							AddSoundEvent( victim->owner, dmgSpot[i], 256, AEL_DISCOVERED );
							AddSightEvent( victim->owner, dmgSpot[i], 512, AEL_DISCOVERED, 50 );
						}
						if ( ent->client )
						{
							if ( ent->enemy && ent->enemy == victim )
							{//just so Jedi knows that he hit his enemy
								ent->client->ps.saberEventFlags |= SEF_HITENEMY;
							}
							else
							{
								ent->client->ps.saberEventFlags |= SEF_HITOBJECT;
							}
						}
					}
				}
			}
		}
	}
	return didDamage;
}

void WP_SaberDamageAdd( float trDmg, int trVictimEntityNum, vec3_t trDmgDir, vec3_t trDmgSpot, float dmg, float fraction, int trHitLoc, qboolean trDismember, int trDismemberLoc )
{
	int curVictim = 0;
	int i;

	if ( trVictimEntityNum < 0 || trVictimEntityNum >= ENTITYNUM_WORLD )
	{
		return;
	}
	if ( trDmg * dmg < 10.0f )
	{//too piddly an amount of damage to really count?
		//FIXME: but already did the effect, didn't we... sigh...
		//return;
	}
	if ( trDmg )
	{//did some damage to something
		for ( i = 0; i < numVictims; i++ )
		{
			if ( victimEntityNum[i] == trVictimEntityNum )
			{//already hit this guy before
				curVictim = i;
				break;
			}
		}
		if ( i == numVictims )
		{//haven't hit his guy before
			if ( numVictims + 1 >= MAX_SABER_VICTIMS )
			{//can't add another victim at this time
				return;
			}
			//add a new victim to the list
			curVictim = numVictims;
			victimEntityNum[numVictims++] = trVictimEntityNum;
		}

		float addDmg = trDmg*dmg;
		if ( trHitLoc!=HL_NONE && (hitLoc[curVictim]==HL_NONE||hitLocHealthPercentage[trHitLoc]>hitLocHealthPercentage[hitLoc[curVictim]]) )
		{//this hitLoc is more critical than the previous one this frame
			hitLoc[curVictim] = trHitLoc;
		}

		totalDmg[curVictim] += addDmg;
		if ( !VectorLengthSquared( dmgDir[curVictim] ) )
		{
			VectorCopy( trDmgDir, dmgDir[curVictim] );
		}
		if ( !VectorLengthSquared( dmgSpot[curVictim] ) )
		{
			VectorCopy( trDmgSpot, dmgSpot[curVictim] );
		}

		// Make sure we keep track of the fraction.  Why?
		// Well, if the saber hits something that stops it, the damage isn't done past that point.
		dmgFraction[curVictim] = fraction;
		if ( (trDismemberLoc != HL_NONE && hitDismemberLoc[curVictim] == HL_NONE)
			|| (!hitDismember[curVictim] && trDismember) )
		{//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
			hitDismemberLoc[curVictim] = trDismemberLoc;
		}
		if ( trDismember )
		{//we scored a dismemberment hit...
			hitDismember[curVictim] = trDismember;
		}
	}
}

/*
WP_SabersIntersect

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

FIXME: subdivide the arc into a consistant increment
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)?
*/
extern qboolean tri_tri_intersect(vec3_t V0,vec3_t V1,vec3_t V2,vec3_t U0,vec3_t U1,vec3_t U2);
qboolean WP_SabersIntersect( gentity_t *ent1, gentity_t *ent2, qboolean checkDir )
{
	vec3_t	saberBase1, saberTip1, saberBaseNext1, saberTipNext1;
	vec3_t	saberBase2, saberTip2, saberBaseNext2, saberTipNext2;
	vec3_t	dir;

	/*
#ifndef FINAL_BUILD
	if ( d_saberCombat->integer )
	{
		gi.Printf( S_COLOR_GREEN"Doing precise saber intersection check\n" );
	}
#endif
	*/

	if ( !ent1 || !ent2 )
	{
		return qfalse;
	}
	if ( !ent1->client || !ent2->client )
	{
		return qfalse;
	}
	if ( ent1->client->ps.saberLength <= 0 || ent2->client->ps.saberLength <= 0 )
	{
		return qfalse;
	}

	//if ( ent1->client->ps.saberInFlight )
	{
		VectorCopy( ent1->client->renderInfo.muzzlePointOld, saberBase1 );
		VectorCopy( ent1->client->renderInfo.muzzlePoint, saberBaseNext1 );

		VectorSubtract( ent1->client->renderInfo.muzzlePoint, ent1->client->renderInfo.muzzlePointOld, dir );
		VectorNormalize( dir );
		VectorMA( saberBaseNext1, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext1 );

		VectorMA( saberBase1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDirOld, saberTip1 );
		VectorMA( saberBaseNext1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDir, saberTipNext1 );

		VectorSubtract( saberTipNext1, saberTip1, dir );
		VectorNormalize( dir );
		VectorMA( saberTipNext1, SABER_EXTRAPOLATE_DIST, dir, saberTipNext1 );
	}
	/*
	else
	{
		VectorCopy( ent1->client->renderInfo.muzzlePoint, saberBase1 );
		VectorCopy( ent1->client->renderInfo.muzzlePointNext, saberBaseNext1 );
		VectorMA( saberBase1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDir, saberTip1 );
		VectorMA( saberBaseNext1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDirNext, saberTipNext1 );
	}
	*/

	//if ( ent2->client->ps.saberInFlight )
	{
		VectorCopy( ent2->client->renderInfo.muzzlePointOld, saberBase2 );
		VectorCopy( ent2->client->renderInfo.muzzlePoint, saberBaseNext2 );

		VectorSubtract( ent2->client->renderInfo.muzzlePoint, ent2->client->renderInfo.muzzlePointOld, dir );
		VectorNormalize( dir );
		VectorMA( saberBaseNext2, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext2 );

		VectorMA( saberBase2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDirOld, saberTip2 );
		VectorMA( saberBaseNext2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDir, saberTipNext2 );

		VectorSubtract( saberTipNext2, saberTip2, dir );
		VectorNormalize( dir );
		VectorMA( saberTipNext2, SABER_EXTRAPOLATE_DIST, dir, saberTipNext2 );
	}
	/*
	else
	{
		VectorCopy( ent2->client->renderInfo.muzzlePoint, saberBase2 );
		VectorCopy( ent2->client->renderInfo.muzzlePointNext, saberBaseNext2 );
		VectorMA( saberBase2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDir, saberTip2 );
		VectorMA( saberBaseNext2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDirNext, saberTipNext2 );
	}
	*/
	if ( checkDir )
	{//check the direction of the two swings to make sure the sabers are swinging towards each other
		vec3_t saberDir1, saberDir2;

		VectorSubtract( saberTipNext1, saberTip1, saberDir1 );
		VectorSubtract( saberTipNext2, saberTip2, saberDir2 );
		VectorNormalize( saberDir1 );
		VectorNormalize( saberDir2 );
		if ( DotProduct( saberDir1, saberDir2 ) > 0.6f )
		{//sabers moving in same dir, probably didn't actually hit
			return qfalse;
		}
		//now check orientation of sabers, make sure they're not parallel or close to it
		float dot = DotProduct( ent1->client->renderInfo.muzzleDir, ent2->client->renderInfo.muzzleDir );
		if ( dot > 0.9f || dot < -0.9f )
		{//too parallel to really block effectively?
			return qfalse;
		}
	}

	if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberBaseNext2 ) )
	{
		return qtrue;
	}
	if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberTipNext2 ) )
	{
		return qtrue;
	}
	if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberBaseNext2 ) )
	{
		return qtrue;
	}
	if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberTipNext2 ) )
	{
		return qtrue;
	}
	return qfalse;
}

float WP_SabersDistance( gentity_t *ent1, gentity_t *ent2 )
{
	vec3_t	saberBaseNext1, saberTipNext1, saberPoint1;
	vec3_t	saberBaseNext2, saberTipNext2, saberPoint2;

	/*
#ifndef FINAL_BUILD
	if ( d_saberCombat->integer )
	{
		gi.Printf( S_COLOR_GREEN"Doing precise saber intersection check\n" );
	}
#endif
	*/

	if ( !ent1 || !ent2 )
	{
		return qfalse;
	}
	if ( !ent1->client || !ent2->client )
	{
		return qfalse;
	}
	if ( ent1->client->ps.saberLength <= 0 || ent2->client->ps.saberLength <= 0 )
	{
		return qfalse;
	}

	//if ( ent1->client->ps.saberInFlight )
	{
		VectorCopy( ent1->client->renderInfo.muzzlePoint, saberBaseNext1 );
		VectorMA( saberBaseNext1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDir, saberTipNext1 );
	}
	/*
	else
	{
		VectorCopy( ent1->client->renderInfo.muzzlePointNext, saberBaseNext1 );
		VectorMA( saberBaseNext1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDirNext, saberTipNext1 );
	}
	*/

	//if ( ent2->client->ps.saberInFlight )
	{
		VectorCopy( ent2->client->renderInfo.muzzlePoint, saberBaseNext2 );
		VectorMA( saberBaseNext2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDir, saberTipNext2 );
	}
	/*
	else
	{
		VectorCopy( ent2->client->renderInfo.muzzlePointNext, saberBaseNext2 );
		VectorMA( saberBaseNext2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDirNext, saberTipNext2 );
	}
	*/

	float sabersDist = ShortestLineSegBewteen2LineSegs( saberBaseNext1, saberTipNext1, saberBaseNext2, saberTipNext2, saberPoint1, saberPoint2 );

	//okay, this is a super hack, but makes saber collisions look better from the player point of view
	/*
	if ( sabersDist < 16.0f )
	{
		vec3_t	saberDistDir, saberMidPoint, camLookDir;

		VectorSubtract( saberPoint2, saberPoint1, saberDistDir );
		VectorMA( saberPoint1, 0.5f, saberDistDir, saberMidPoint );
		VectorSubtract( saberMidPoint, cg.refdef.vieworg, camLookDir );
		VectorNormalize( saberDistDir );
		VectorNormalize( camLookDir );
		float dot = fabs(DotProduct( camLookDir, saberDistDir ));
		sabersDist -= 8.0f*dot;
		if ( sabersDist < 0.0f )
		{
			sabersDist = 0.0f;
		}
	}
	*/

#ifndef FINAL_BUILD
	if ( d_saberCombat->integer > 2 )
	{
		G_DebugLine( saberPoint1, saberPoint2, FRAMETIME, 0x00ffffff, qtrue );
	}
#endif
	return sabersDist;
}

qboolean WP_SabersIntersection( gentity_t *ent1, gentity_t *ent2, vec3_t intersect )
{
	vec3_t	saberBaseNext1, saberTipNext1, saberPoint1;
	vec3_t	saberBaseNext2, saberTipNext2, saberPoint2;

	if ( !ent1 || !ent2 )
	{
		return qfalse;
	}
	if ( !ent1->client || !ent2->client )
	{
		return qfalse;
	}
	if ( ent1->client->ps.saberLength <= 0 || ent2->client->ps.saberLength <= 0 )
	{
		return qfalse;
	}

	VectorCopy( ent1->client->renderInfo.muzzlePoint, saberBaseNext1 );
	VectorMA( saberBaseNext1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDir, saberTipNext1 );

	VectorCopy( ent2->client->renderInfo.muzzlePoint, saberBaseNext2 );
	VectorMA( saberBaseNext2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDir, saberTipNext2 );

	ShortestLineSegBewteen2LineSegs( saberBaseNext1, saberTipNext1, saberBaseNext2, saberTipNext2, saberPoint1, saberPoint2 );
	VectorAdd( saberPoint1, saberPoint2, intersect );
	VectorScale( intersect, 0.5, intersect );
	return qtrue;
}

char *hit_blood_sparks = "blood_sparks";
char *hit_sparks = "saber_cut";

extern qboolean G_GetHitLocFromSurfName( gentity_t *ent, const char *surfName, int *hitLoc, vec3_t point, vec3_t dir, vec3_t bladeDir, int mod );
qboolean WP_SaberDamageEffects( trace_t *tr, const vec3_t start, float length, float dmg, vec3_t dmgDir, vec3_t bladeDir, int enemyTeam )
{

	int			hitEntNum[MAX_G2_COLLISIONS] = {ENTITYNUM_NONE};
	float		hitEntDmgAdd[MAX_G2_COLLISIONS] = {0};
	float		hitEntDmgSub[MAX_G2_COLLISIONS] = {0};
	vec3_t		hitEntPoint[MAX_G2_COLLISIONS];
	vec3_t		hitEntDir[MAX_G2_COLLISIONS];
	float		hitEntStartFrac[MAX_G2_COLLISIONS] = {0};
	int			trHitLoc = HL_NONE;
	int			trDismemberLoc = HL_NONE;
	qboolean	trDismember = qfalse;
	int			i,z;
	int			numHitEnts = 0;
	float		distFromStart,doDmg;
	char		*hitEffect;
	gentity_t	*hitEnt;

	for (z=0; z < MAX_G2_COLLISIONS; z++)
	{
		if ( tr->G2CollisionMap[z].mEntityNum == -1 )
		{//actually, completely break out of this for loop since nothing after this in the aray should ever be valid either
			continue;//break;//
		}

		CCollisionRecord &coll	= tr->G2CollisionMap[z];
		//distFromStart			= Distance( start, coll.mCollisionPosition );
		distFromStart			= coll.mDistance;

		/*
		//FIXME: (distFromStart/length) is not guaranteed to be from 0 to 1... *sigh*...
		if ( length && saberHitFraction < 1.0f && (distFromStart/length) < 1.0f && (distFromStart/length) > saberHitFraction )
		{//a saber was hit before this point, don't count it
#ifndef FINAL_BUILD
			if ( d_saberCombat->integer )
			{
				gi.Printf( S_COLOR_MAGENTA"rejecting G2 collision- %4.2f farther than saberHitFraction %4.2f\n", (distFromStart/length), saberHitFraction  );
			}
#endif
			continue;
		}
		*/

		for ( i = 0; i < numHitEnts; i++ )
		{
			if ( hitEntNum[i] != ENTITYNUM_NONE )
			{//we hit this ent before
				//we'll want to add this dist
				hitEntDmgAdd[i] = distFromStart;
				break;
			}
		}
		if ( i == numHitEnts )
		{//first time we hit this ent
			if ( numHitEnts == MAX_G2_COLLISIONS )
			{//hit too many damn ents!
				continue;
			}
			hitEntNum[numHitEnts] = coll.mEntityNum;
			if ( !coll.mFlags )
			{//hmm, we came out first, so we must have started inside
				//we'll want to subtract this dist
				hitEntDmgAdd[numHitEnts] = distFromStart;
			}
			else
			{//we're entering the model
				//we'll want to subtract this dist
				hitEntDmgSub[numHitEnts] = distFromStart;
			}
			//keep track of how far in the damage was done
			hitEntStartFrac[numHitEnts] = hitEntDmgSub[numHitEnts]/length;
			//remember the entrance point
			VectorCopy( coll.mCollisionPosition, hitEntPoint[numHitEnts] );
			//remember the entrance dir
			VectorCopy( coll.mCollisionNormal, hitEntDir[numHitEnts] );
			VectorNormalize( hitEntDir[numHitEnts] );

			//do the effect

			//FIXME: check material rather than team?
			hitEnt = &g_entities[hitEntNum[numHitEnts]];
			hitEffect = hit_blood_sparks;
			if ( hitEnt != NULL )
			{
				if ( hitEnt->client )
				{
					class_t npc_class = hitEnt->client->NPC_class;
					if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE || npc_class == CLASS_REMOTE ||
					     npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 ||
					     npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 ||
					     npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY )
					{
						hitEffect = hit_sparks;
					}
				}
				else
				{
					hitEffect = hit_sparks;
				}
			}

			//FIXME: play less if damage is less?
			G_PlayEffect( hitEffect, coll.mCollisionPosition, coll.mCollisionNormal );

			//Get the hit location based on surface name
			if ( (hitLoc[hitEntNum[numHitEnts]] == HL_NONE && trHitLoc == HL_NONE)
				|| (hitDismemberLoc[hitEntNum[numHitEnts]] == HL_NONE && trDismemberLoc == HL_NONE)
				|| (!hitDismember[hitEntNum[numHitEnts]] && !trDismember) )
			{//no hit loc set for this ent this damage cycle yet
				//FIXME: find closest impact surf *first* (per ent), then call G_GetHitLocFromSurfName?
				trDismember = G_GetHitLocFromSurfName( &g_entities[coll.mEntityNum], gi.G2API_GetSurfaceName( &g_entities[coll.mEntityNum].ghoul2[coll.mModelIndex], coll.mSurfaceIndex ), &trHitLoc, coll.mCollisionPosition, dmgDir, bladeDir, MOD_SABER );
				trDismemberLoc = trHitLoc;
			}
			numHitEnts++;
		}
	}

	//now go through all the ents we hit and do the damage
	for ( i = 0; i < numHitEnts; i++ )
	{
		doDmg = dmg;
		if ( hitEntNum[i] != ENTITYNUM_NONE )
		{
			if ( doDmg < 10 )
			{//base damage is less than 10
				if ( hitEntNum[i] != 0 )
				{//not the player
					hitEnt = &g_entities[hitEntNum[i]];
					if ( !hitEnt->client || (hitEnt->client->ps.weapon!=WP_SABER&&hitEnt->client->NPC_class!=CLASS_GALAKMECH&&hitEnt->client->playerTeam==enemyTeam) )
					{//did *not* hit a jedi and did *not* hit the player
						//make sure the base damage is high against non-jedi, feels better
						doDmg = 10;
					}
				}
			}
			if ( !hitEntDmgAdd[i] && !hitEntDmgSub[i] )
			{//spent entire time in model
				//NOTE: will we even get a collision then?
				doDmg *= length;
			}
			else if ( hitEntDmgAdd[i] && hitEntDmgSub[i] )
			{//we did enter and exit
				doDmg *= hitEntDmgAdd[i] - hitEntDmgSub[i];
			}
			else if ( !hitEntDmgAdd[i] )
			{//we didn't exit, just entered
				doDmg *= length - hitEntDmgSub[i];
			}
			else if ( !hitEntDmgSub[i] )
			{//we didn't enter, only exited
				doDmg *= hitEntDmgAdd[i];
			}
			if ( doDmg > 0 )
			{
				WP_SaberDamageAdd( 1.0, hitEntNum[i], hitEntDir[i], hitEntPoint[i], ceil(doDmg), hitEntStartFrac[i], trHitLoc, trDismember, trDismemberLoc );
			}
		}
	}
	return (qboolean)(numHitEnts > 0);
}

void WP_SaberKnockaway( gentity_t *attacker, trace_t *tr )
{
	WP_SaberDrop( attacker, &g_entities[attacker->client->ps.saberEntityNum] );
	G_Sound( &g_entities[attacker->client->ps.saberEntityNum], G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
	G_PlayEffect( "saber_block", tr->endpos );
	saberHitFraction = tr->fraction;
#ifndef FINAL_BUILD
	if ( d_saberCombat->integer )
	{
		gi.Printf( S_COLOR_MAGENTA"WP_SaberKnockaway: saberHitFraction %4.2f\n", saberHitFraction );
	}
#endif
	VectorCopy( tr->endpos, saberHitLocation );
	saberHitEntity = tr->entityNum;
	g_saberFlashTime = level.time-50;
	VectorCopy( saberHitLocation, g_saberFlashPos );

	//FIXME: make hitEnt play an attack anim or some other special anim when this happens
	//gentity_t *hitEnt = &g_entities[tr->entityNum];
	//NPC_SetAnim( hitEnt, SETANIM_BOTH, BOTH_KNOCKSABER, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
}

#define SABER_COLLISION_DIST 6//was 2//was 4//was 8//was 16
extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f );
qboolean WP_SaberDamageForTrace( int ignore, vec3_t start, vec3_t end, float dmg,
								vec3_t bladeDir, qboolean noGhoul, int attackStrength,
								qboolean extrapolate = qtrue )
{
	trace_t		tr;
	vec3_t		dir;
	int			mask = (MASK_SHOT|CONTENTS_LIGHTSABER);
	gentity_t	*attacker = &g_entities[ignore];

	vec3_t		end2;
	VectorCopy( end, end2 );
	if ( extrapolate )
	{
		//NOTE: since we can no longer use the predicted point, extrapolate the trace some.
		//		this may allow saber hits that aren't actually hits, but it doesn't look too bad
		vec3_t diff;
		VectorSubtract( end, start, diff );
		VectorNormalize( diff );
		VectorMA( end2, SABER_EXTRAPOLATE_DIST, diff, end2 );
	}

	if ( !noGhoul )
	{
		if ( !attacker->s.number || (attacker->client&&(attacker->client->playerTeam==TEAM_PLAYER||attacker->client->NPC_class==CLASS_SHADOWTROOPER||attacker->client->NPC_class==CLASS_TAVION||attacker->client->NPC_class==CLASS_DESANN) ) )//&&attackStrength==FORCE_LEVEL_3)
		{//player,. player allies, shadowtroopers, tavion and desann use larger traces
			vec3_t	traceMins = {-2,-2,-2}, traceMaxs = {2,2,2};
			gi.trace( &tr, start, traceMins, traceMaxs, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX
		}
		/*
		else if ( !attacker->s.number )
		{
			vec3_t	traceMins = {-1,-1,-1}, traceMaxs = {1,1,1};
			gi.trace( &tr, start, traceMins, traceMaxs, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX
		}
		*/
		else
		{//reborn use smaller traces
			gi.trace( &tr, start, NULL, NULL, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX
		}
	}
	else
	{
		gi.trace( &tr, start, NULL, NULL, end2, ignore, mask, G2_NOCOLLIDE, 10 );
	}


#ifndef FINAL_BUILD
	if ( d_saberCombat->integer > 1 )
	{
		if ( attacker != NULL && attacker->client != NULL )
		{
			G_DebugLine(start, end2, FRAMETIME, WPDEBUG_SaberColor( attacker->client->ps.saberColor ), qtrue);
		}
	}
#endif

	if ( tr.entityNum == ENTITYNUM_NONE )
	{
		return qfalse;
	}

	if ( tr.entityNum == ENTITYNUM_WORLD )
	{
		return qtrue;
	}

	if ( &g_entities[tr.entityNum] )
	{
		gentity_t *hitEnt = &g_entities[tr.entityNum];
		gentity_t *owner = g_entities[tr.entityNum].owner;
		if ( hitEnt->contents & CONTENTS_LIGHTSABER )
		{
			if ( attacker && attacker->client && attacker->client->ps.saberInFlight )
			{//thrown saber hit something
				 if ( owner
					 && owner->s.number
					 && owner->client
					 && owner->NPC
					 && owner->health > 0
					 && ( owner->client->NPC_class == CLASS_TAVION
							/*|| (owner->client->NPC_class == CLASS_SHADOWTROOPER && !Q_irand( 0, g_spskill->integer*3 ))
							|| (Q_irand( -5, owner->NPC->rank ) > RANK_CIVILIAN && !Q_irand( 0, g_spskill->integer*3 ))*/ )
					 )
				 {//Tavion can toss a blocked thrown saber aside
					WP_SaberKnockaway( attacker, &tr );
					Jedi_PlayDeflectSound( owner );
					return qfalse;
				 }
			}
			//FIXME: take target FP_SABER_DEFENSE and attacker FP_SABER_OFFENSE into account here somehow?
			qboolean sabersIntersect = WP_SabersIntersect( attacker, owner, qfalse );//qtrue );
			float sabersDist;
			if ( attacker && attacker->client && attacker->client->ps.saberInFlight
				&& owner && owner->s.number == 0 && (g_saberAutoBlocking->integer||attacker->client->ps.saberBlockingTime>level.time) )//NPC flying saber hit player's saber bounding box
			{//players have g_saberAutoBlocking, do the more generous check against flying sabers
				//FIXME: instead of hitting the player's saber bounding box
				//and picking an anim afterwards, have him use AI similar
				//to the AI the jedi use for picking a saber melee block...?
				sabersDist = 0;
			}
			else
			{//sabers must actually collide with the attacking saber
				sabersDist = WP_SabersDistance( attacker, owner );
				if ( attacker && attacker->client && attacker->client->ps.saberInFlight )
				{
					sabersDist /= 2.0f;
					if ( sabersDist <= 16.0f )
					{
						sabersIntersect = qtrue;
					}
				}
#ifndef FINAL_BUILD
				if ( d_saberCombat->integer > 1 )
				{
					gi.Printf( "sabersDist: %4.2f\n", sabersDist );
				}
#endif//FINAL_BUILD
			}
			if ( sabersCrossed == -1 || sabersCrossed > sabersDist )
			{
				sabersCrossed = sabersDist;
			}
			float	collisionDist;
			if ( g_saberRealisticCombat->integer )
			{
				collisionDist = SABER_COLLISION_DIST;
			}
			else
			{
				collisionDist = SABER_COLLISION_DIST+6+g_spskill->integer*4;
			}
			if ( owner && owner->client && (attacker != NULL)
				&& (sabersDist > collisionDist )//|| !InFront( attacker->currentOrigin, owner->currentOrigin, owner->client->ps.viewangles, 0.35f ))
				&& !sabersIntersect )//was qtrue, but missed too much?
			{//swing came from behind and/or was not stopped by a lightsaber
				//re-try the trace without checking for lightsabers
				gi.trace ( &tr, start, NULL, NULL, end2, ignore, mask&~CONTENTS_LIGHTSABER, G2_NOCOLLIDE, 10 );
				if ( tr.entityNum == ENTITYNUM_WORLD )
				{
 					return qtrue;
				}
				if ( tr.entityNum == ENTITYNUM_NONE || &g_entities[tr.entityNum] == NULL )
				{//didn't hit the owner
					/*
					if ( attacker
						&& attacker->client
						&& (PM_SaberInAttack( attacker->client->ps.saberMove ) || PM_SaberInStart( attacker->client->ps.saberMove ))
						&& DistanceSquared( tr.endpos, owner->currentOrigin ) < 10000 )
					{
						if ( owner->NPC
							&& !owner->client->ps.saberInFlight
							&& owner->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN
							&& !Jedi_SaberBusy( owner ) )
						{//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
							if ( g_spskill->integer && (g_spskill->integer > 1 || Q_irand( 0, 1 )))
							{//if on easy, they don't cheat like this, if on medium, they cheat 50% of the time, if on hard, they always cheat
								//FIXME: also take into account the owner's FP_DEFENSE?
								if ( Q_irand( 0, owner->NPC->rank ) >= RANK_CIVILIAN )
								{//lower-rank Jedi aren't as good blockers
									vec3_t attDir;
									VectorSubtract( end2, start, attDir );
									VectorNormalize( attDir );
									Jedi_SaberBlockGo( owner, owner->NPC->last_ucmd, start, attDir, NULL );
								}
							}
						}
					}
					*/
  					return qfalse;	// Exit, but we didn't hit the wall.
				}
#ifndef FINAL_BUILD
				if ( d_saberCombat->integer > 1 )
				{
					if ( !attacker->s.number )
					{
						gi.Printf( S_COLOR_MAGENTA"%d saber hit owner through saber %4.2f, dist = %4.2f\n", level.time, saberHitFraction, sabersDist );
					}
				}
#endif//FINAL_BUILD
				hitEnt = &g_entities[tr.entityNum];
				owner = g_entities[tr.entityNum].owner;
			}
			else
			{//hit a lightsaber
				if ( (tr.fraction < saberHitFraction || tr.startsolid)
					&& sabersDist < (8.0f+g_spskill->value)*4.0f// 50.0f//16.0f
					&& (sabersIntersect || sabersDist < (4.0f+g_spskill->value)*2.0f) )//32.0f) )
				{	// This saber hit closer than the last one.
					if ( (tr.allsolid || tr.startsolid) && owner && owner->client )
					{//tr.fraction will be 0, unreliable... so calculate actual
						float dist = Distance( start, end2 );
						if ( dist )
						{
							float hitFrac = WP_SabersDistance( attacker, owner )/dist;
							if ( hitFrac > 1.0f )
							{//umm... minimum distance between sabers was longer than trace...?
								hitFrac = 1.0f;
							}
							if ( hitFrac < saberHitFraction )
							{
								saberHitFraction = hitFrac;
							}
						}
						else
						{
							saberHitFraction = 0.0f;
						}
#ifndef FINAL_BUILD
						if ( d_saberCombat->integer > 1 )
						{
							if ( !attacker->s.number )
							{
								gi.Printf( S_COLOR_GREEN"%d saber hit saber dist %4.2f allsolid %4.2f\n", level.time, sabersDist, saberHitFraction );
							}
						}
#endif//FINAL_BUILD
					}
					else
					{
#ifndef FINAL_BUILD
						if ( d_saberCombat->integer > 1 )
						{
							if ( !attacker->s.number )
							{
								gi.Printf( S_COLOR_BLUE"%d saber hit saber dist %4.2f, frac %4.2f\n", level.time, sabersDist, saberHitFraction );
							}
							saberHitFraction = tr.fraction;
						}
#endif//FINAL_BUILD
					}
#ifndef FINAL_BUILD
					if ( d_saberCombat->integer )
					{
						gi.Printf( S_COLOR_MAGENTA"hit saber: saberHitFraction %4.2f, allsolid %d, startsolid %d\n", saberHitFraction, tr.allsolid, tr.startsolid );
					}
#endif//FINAL_BUILD
					VectorCopy(tr.endpos, saberHitLocation);
					saberHitEntity = tr.entityNum;
				}
				/*
				if ( owner
					&& owner->client
					&& attacker
					&& attacker->client
					&& (PM_SaberInAttack( attacker->client->ps.saberMove ) || PM_SaberInStart( attacker->client->ps.saberMove ))
					&& DistanceSquared( tr.endpos, owner->currentOrigin ) < 10000 )
				{
					if ( owner->NPC
						&& !owner->client->ps.saberInFlight
						&& owner->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN
						&& !Jedi_SaberBusy( owner ) )
					{//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
						if ( g_spskill->integer && (g_spskill->integer > 1 || Q_irand( 0, 1 )))
						{//if on easy, they don't cheat like this, if on medium, they cheat 50% of the time, if on hard, they always cheat
							//FIXME: also take into account the owner's FP_DEFENSE?
							if ( Q_irand( 0, owner->NPC->rank ) >= RANK_CIVILIAN )
							{//lower-rank Jedi aren't as good blockers
								vec3_t attDir;
								VectorSubtract( end2, start, attDir );
								VectorNormalize( attDir );
								Jedi_SaberBlockGo( owner, owner->NPC->last_ucmd, start, attDir, NULL );
							}
						}
					}
				}
				*/
  				return qfalse;	// Exit, but we didn't hit the wall.
			}
		}
		else
		{
#ifndef FINAL_BUILD
			if ( d_saberCombat->integer > 1 )
			{
				if ( !attacker->s.number )
				{
					gi.Printf( S_COLOR_RED"%d saber hit owner directly %4.2f\n", level.time, saberHitFraction );
				}
			}
#endif//FINAL_BUILD
		}

		if ( attacker && attacker->client && attacker->client->ps.saberInFlight )
		{//thrown saber hit something
			if ( ( hitEnt && hitEnt->client && hitEnt->health > 0 && ( hitEnt->client->NPC_class == CLASS_DESANN || hitEnt->client->NPC_class == CLASS_LUKE || (hitEnt->client->NPC_class == CLASS_GALAKMECH&&hitEnt->client->ps.powerups[PW_GALAK_SHIELD] > 0) ) ) ||
				 ( owner && owner->client && owner->health > 0 && ( owner->client->NPC_class == CLASS_DESANN || owner->client->NPC_class == CLASS_LUKE || (owner->client->NPC_class==CLASS_GALAKMECH&&owner->client->ps.powerups[PW_GALAK_SHIELD] > 0) ) ) )
			{//Luke and Desann slap thrown sabers aside
				//FIXME: control the direction of the thrown saber... if hit Galak's shield, bounce directly away from his origin?
				WP_SaberKnockaway( attacker, &tr );
				if ( hitEnt->client )
				{
					Jedi_PlayDeflectSound( hitEnt );
				}
				else
				{
					Jedi_PlayDeflectSound( owner );
				}
  				return qfalse;	// Exit, but we didn't hit the wall.
			}
		}

		if ( hitEnt->takedamage )
		{
			//no team damage: if ( !hitEnt->client || attacker == NULL || !attacker->client || (hitEnt->client->playerTeam != attacker->client->playerTeam) )
			{
				//multiply the damage by the total distance of the swipe
				VectorSubtract( end2, start, dir );
				float len = VectorNormalize( dir );//VectorLength( dir );
				if ( noGhoul || !hitEnt->ghoul2.size() )
				{//we weren't doing a ghoul trace
					char	*hitEffect;
					if ( dmg >= 1.0 && hitEnt->bmodel )
					{
						dmg = 1.0;
					}
					if ( len > 1 )
					{
						dmg *= len;
					}
#ifndef FINAL_BUILD
					if ( d_saberCombat->integer > 1 )
					{
						if ( !(hitEnt->contents & CONTENTS_LIGHTSABER) )
						{
							gi.Printf( S_COLOR_GREEN"Hit ent, but no ghoul collisions\n" );
						}
					}
#endif
					float	trFrac, dmgFrac;
					if ( tr.allsolid )
					{//totally inside them
						trFrac = 1.0;
						dmgFrac = 0.0;
					}
					else if ( tr.startsolid )
					{//started inside them
						//we don't know how much was inside, we know it's less than all, so use half?
						trFrac = 0.5;
						dmgFrac = 0.0;
					}
					else
					{//started outside them and hit them
						//yeah. this doesn't account for coming out the other wide, but we can worry about that later (use ghoul2)
						trFrac = (1.0f - tr.fraction);
						dmgFrac = tr.fraction;
					}
					WP_SaberDamageAdd( trFrac, tr.entityNum, dir, tr.endpos, dmg, dmgFrac, HL_NONE, qfalse, HL_NONE );
					if ( !tr.allsolid && !tr.startsolid )
					{
						VectorScale( dir, -1, dir );
					}
					//FIXME: don't do blood sparks on non-living things
					hitEffect = hit_blood_sparks;
					if ( hitEnt != NULL )
					{
						if ( hitEnt->client )
						{
							class_t npc_class = hitEnt->client->NPC_class;
							if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE ||
								 npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 || npc_class == CLASS_REMOTE ||
								 npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 ||
							     npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY )
							{
								hitEffect = hit_sparks;
							}
						}
						else
						{
							hitEffect = hit_sparks;
						}
					}

					G_PlayEffect( hitEffect, tr.endpos, dir );//"saber_cut"
				}
				else
				{//we were doing a ghoul trace
					if ( !WP_SaberDamageEffects( &tr, start, len, dmg, dir, bladeDir, attacker->client->enemyTeam ) )
					{//didn't hit a ghoul ent
						/*
						if ( && hitEnt->ghoul2.size() )
						{//it was a ghoul2 model so we should have hit it
							return qfalse;
						}
						*/
					}
				}
			}
		}
	}

	return qfalse;
}

typedef enum
{
	LOCK_FIRST = 0,
	LOCK_TOP = LOCK_FIRST,
	LOCK_DIAG_TR,
	LOCK_DIAG_TL,
	LOCK_DIAG_BR,
	LOCK_DIAG_BL,
	LOCK_R,
	LOCK_L,
	LOCK_RANDOM
} sabersLockMode_t;

#define LOCK_IDEAL_DIST_TOP 32.0f
#define LOCK_IDEAL_DIST_CIRCLE 48.0f
extern void PM_SetAnimFrame( gentity_t *gent, int frame, qboolean torso, qboolean legs );
extern qboolean ValidAnimFileIndex ( int index );
qboolean WP_SabersCheckLock2( gentity_t *attacker, gentity_t *defender, sabersLockMode_t lockMode )
{
	animation_t *anim;
	int		attAnim, defAnim, advance = 0;
	float	attStart = 0.5f;
	float	idealDist = 48.0f;
	//MATCH ANIMS
	if ( lockMode == LOCK_RANDOM )
	{
		lockMode = (sabersLockMode_t)Q_irand( (int)LOCK_FIRST, (int)(LOCK_RANDOM)-1 );
	}
	switch ( lockMode )
	{
	case LOCK_TOP:
		attAnim = BOTH_BF2LOCK;
		defAnim = BOTH_BF1LOCK;
		attStart = 0.5f;
		idealDist = LOCK_IDEAL_DIST_TOP;
		break;
	case LOCK_DIAG_TR:
		attAnim = BOTH_CCWCIRCLELOCK;
		defAnim = BOTH_CWCIRCLELOCK;
		attStart = 0.5f;
		idealDist = LOCK_IDEAL_DIST_CIRCLE;
		break;
	case LOCK_DIAG_TL:
		attAnim = BOTH_CWCIRCLELOCK;
		defAnim = BOTH_CCWCIRCLELOCK;
		attStart = 0.5f;
		idealDist = LOCK_IDEAL_DIST_CIRCLE;
		break;
	case LOCK_DIAG_BR:
		attAnim = BOTH_CWCIRCLELOCK;
		defAnim = BOTH_CCWCIRCLELOCK;
		attStart = 0.85f;
		idealDist = LOCK_IDEAL_DIST_CIRCLE;
		break;
	case LOCK_DIAG_BL:
		attAnim = BOTH_CCWCIRCLELOCK;
		defAnim = BOTH_CWCIRCLELOCK;
		attStart = 0.85f;
		idealDist = LOCK_IDEAL_DIST_CIRCLE;
		break;
	case LOCK_R:
		attAnim = BOTH_CWCIRCLELOCK;
		defAnim = BOTH_CCWCIRCLELOCK;
		attStart = 0.75f;
		idealDist = LOCK_IDEAL_DIST_CIRCLE;
		break;
	case LOCK_L:
		attAnim = BOTH_CCWCIRCLELOCK;
		defAnim = BOTH_CWCIRCLELOCK;
		attStart = 0.75f;
		idealDist = LOCK_IDEAL_DIST_CIRCLE;
		break;
	default:
		return qfalse;
		break;
	}
	NPC_SetAnim( attacker, SETANIM_BOTH, attAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
	NPC_SetAnim( defender, SETANIM_BOTH, defAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
	if( ValidAnimFileIndex( attacker->client->clientInfo.animFileIndex ) )
	{
		anim = &level.knownAnimFileSets[attacker->client->clientInfo.animFileIndex].animations[attAnim];
		advance = floor( anim->numFrames*attStart );
		PM_SetAnimFrame( attacker, anim->firstFrame + advance, qtrue, qtrue );
#ifndef FINAL_BUILD
		if ( d_saberCombat->integer )
		{
			Com_Printf( "%s starting saber lock, anim = %s, %d frames to go!\n", attacker->NPC_type, animTable[attAnim].name, anim->numFrames-advance );
		}
#endif
	}
	if( ValidAnimFileIndex( defender->client->clientInfo.animFileIndex ) )
	{
		anim = &level.knownAnimFileSets[defender->client->clientInfo.animFileIndex].animations[defAnim];
		PM_SetAnimFrame( defender, anim->firstFrame + advance, qtrue, qtrue );//was anim->firstFrame + anim->numFrames - advance, but that's wrong since they are matched anims
#ifndef FINAL_BUILD
		if ( d_saberCombat->integer )
		{
			Com_Printf( "%s starting saber lock, anim = %s, %d frames to go!\n", defender->NPC_type, animTable[defAnim].name, advance );
		}
#endif
	}
	VectorClear( attacker->client->ps.velocity );
	VectorClear( defender->client->ps.velocity );
	attacker->client->ps.saberLockTime = defender->client->ps.saberLockTime = level.time + SABER_LOCK_TIME;
	attacker->client->ps.legsAnimTimer = attacker->client->ps.torsoAnimTimer = defender->client->ps.legsAnimTimer = defender->client->ps.torsoAnimTimer = SABER_LOCK_TIME;
	//attacker->client->ps.weaponTime = defender->client->ps.weaponTime = SABER_LOCK_TIME;
	attacker->client->ps.saberLockEnemy = defender->s.number;
	defender->client->ps.saberLockEnemy = attacker->s.number;

	//MATCH ANGLES
	//FIXME: if zDiff in elevation, make lower look up and upper look down and move them closer?
	float defPitchAdd = 0, zDiff = ((attacker->currentOrigin[2]+attacker->client->standheight)-(defender->currentOrigin[2]+defender->client->standheight));
	if ( zDiff > 24 )
	{
		defPitchAdd = -30;
	}
	else if ( zDiff < -24 )
	{
		defPitchAdd = 30;
	}
	else
	{
		defPitchAdd = zDiff/24.0f*-30.0f;
	}
	if ( attacker->NPC && defender->NPC )
	{//if 2 NPCs, just set pitch to 0
		attacker->client->ps.viewangles[PITCH] = -defPitchAdd;
		defender->client->ps.viewangles[PITCH] = defPitchAdd;
	}
	else
	{//if a player is involved, clamp player's pitch and match NPC's to player
		if ( !attacker->s.number )
		{
			//clamp to defPitch
			if ( attacker->client->ps.viewangles[PITCH] > -defPitchAdd + 10 )
			{
				attacker->client->ps.viewangles[PITCH] = -defPitchAdd + 10;
			}
			else if ( attacker->client->ps.viewangles[PITCH] < -defPitchAdd-10 )
			{
				attacker->client->ps.viewangles[PITCH] = -defPitchAdd-10;
			}
			//clamp to sane numbers
			if ( attacker->client->ps.viewangles[PITCH] > 50 )
			{
				attacker->client->ps.viewangles[PITCH] = 50;
			}
			else if ( attacker->client->ps.viewangles[PITCH] < -50 )
			{
				attacker->client->ps.viewangles[PITCH] = -50;
			}
			defender->client->ps.viewangles[PITCH] = attacker->client->ps.viewangles[PITCH]*-1;
			defPitchAdd = defender->client->ps.viewangles[PITCH];
		}
		else if ( !defender->s.number )
		{
			//clamp to defPitch
			if ( defender->client->ps.viewangles[PITCH] > defPitchAdd + 10 )
			{
				defender->client->ps.viewangles[PITCH] = defPitchAdd + 10;
			}
			else if ( defender->client->ps.viewangles[PITCH] < defPitchAdd-10 )
			{
				defender->client->ps.viewangles[PITCH] = defPitchAdd-10;
			}
			//clamp to sane numbers
			if ( defender->client->ps.viewangles[PITCH] > 50 )
			{
				defender->client->ps.viewangles[PITCH] = 50;
			}
			else if ( defender->client->ps.viewangles[PITCH] < -50 )
			{
				defender->client->ps.viewangles[PITCH] = -50;
			}
			defPitchAdd = defender->client->ps.viewangles[PITCH];
			attacker->client->ps.viewangles[PITCH] = defender->client->ps.viewangles[PITCH]*-1;
		}
	}
	vec3_t	attAngles, defAngles, defDir;
	VectorSubtract( defender->currentOrigin, attacker->currentOrigin, defDir );
	VectorCopy( attacker->client->ps.viewangles, attAngles );
	attAngles[YAW] = vectoyaw( defDir );
	SetClientViewAngle( attacker, attAngles );
	defAngles[PITCH] = attAngles[PITCH]*-1;
	defAngles[YAW] = AngleNormalize180( attAngles[YAW] + 180);
	defAngles[ROLL] = 0;
	SetClientViewAngle( defender, defAngles );

	//MATCH POSITIONS
	vec3_t	newOrg;
	/*
	idealDist -= fabs(defPitchAdd)/8.0f;
	*/
	float scale = VectorLength( attacker->s.modelScale );
	if ( scale )
	{
		idealDist += 8*(scale-1.0f);
	}
	scale = VectorLength( defender->s.modelScale );
	if ( scale )
	{
		idealDist += 8*(scale-1.0f);
	}

	float diff = VectorNormalize( defDir ) - idealDist;//diff will be the total error in dist
	//try to move attacker half the diff towards the defender
	VectorMA( attacker->currentOrigin, diff*0.5f, defDir, newOrg );
	trace_t	trace;
	gi.trace( &trace, attacker->currentOrigin, attacker->mins, attacker->maxs, newOrg, attacker->s.number, attacker->clipmask, G2_NOCOLLIDE, 0 );
	if ( !trace.startsolid && !trace.allsolid )
	{
		G_SetOrigin( attacker, trace.endpos );
		gi.linkentity( attacker );
	}
	//now get the defender's dist and do it for him too
	vec3_t	attDir;
	VectorSubtract( attacker->currentOrigin, defender->currentOrigin, attDir );
	diff = VectorNormalize( attDir ) - idealDist;//diff will be the total error in dist
	//try to move defender all of the remaining diff towards the attacker
	VectorMA( defender->currentOrigin, diff, attDir, newOrg );
	gi.trace( &trace, defender->currentOrigin, defender->mins, defender->maxs, newOrg, defender->s.number, defender->clipmask, G2_NOCOLLIDE, 0 );
	if ( !trace.startsolid && !trace.allsolid )
	{
		G_SetOrigin( defender, trace.endpos );
		gi.linkentity( defender );
	}

	//DONE!

	return qtrue;
}

qboolean WP_SabersCheckLock( gentity_t *ent1, gentity_t *ent2 )
{
	if ( ent1->client->playerTeam == ent2->client->playerTeam )
	{
		return qfalse;
	}
	if ( ent1->client->ps.groundEntityNum == ENTITYNUM_NONE ||
		ent2->client->ps.groundEntityNum == ENTITYNUM_NONE )
	{
		return qfalse;
	}
	if ( ent1->painDebounceTime > level.time-1000 || ent2->painDebounceTime > level.time-1000 )
	{//can't saberlock if you're not ready
		return qfalse;
	}
	if ( fabs( ent1->currentOrigin[2]-ent2->currentOrigin[2]) > 18 )
	{
		return qfalse;
	}
	float dist = DistanceSquared(ent1->currentOrigin,ent2->currentOrigin);
	if ( dist < 64 || dist > 6400 )//( dist < 128 || dist > 2304 )
	{//between 8 and 80 from each other//was 16 and 48
		return qfalse;
	}
	if ( !InFOV( ent1, ent2, 40, 180 ) || !InFOV( ent2, ent1, 40, 180 ) )
	{
		return qfalse;
	}
	//Check for certain anims that *cannot* lock
	//FIXME: there should probably be a whole *list* of these, but I'll put them in as they come up
	if ( ent1->client->ps.torsoAnim == BOTH_A2_STABBACK1 && ent1->client->ps.torsoAnimTimer > 300 )
	{//can't lock when saber is behind you
		return qfalse;
	}
	if ( ent2->client->ps.torsoAnim == BOTH_A2_STABBACK1 && ent2->client->ps.torsoAnimTimer > 300 )
	{//can't lock when saber is behind you
		return qfalse;
	}
	//BR to TL lock
	if ( ent1->client->ps.torsoAnim == BOTH_A1_BR_TL ||
		ent1->client->ps.torsoAnim == BOTH_A2_BR_TL ||
		ent1->client->ps.torsoAnim == BOTH_A3_BR_TL ||
		ent1->client->ps.torsoAnim == BOTH_A4_BR_TL ||
		ent1->client->ps.torsoAnim == BOTH_A5_BR_TL )
	{//ent1 is attacking in the opposite diagonal
		return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BR );
	}
	if ( ent2->client->ps.torsoAnim == BOTH_A1_BR_TL ||
		ent2->client->ps.torsoAnim == BOTH_A2_BR_TL ||
		ent2->client->ps.torsoAnim == BOTH_A3_BR_TL ||
		ent2->client->ps.torsoAnim == BOTH_A4_BR_TL ||
		ent2->client->ps.torsoAnim == BOTH_A5_BR_TL )
	{//ent2 is attacking in the opposite diagonal
		return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BR );
	}
	//BL to TR lock
	if ( ent1->client->ps.torsoAnim == BOTH_A1_BL_TR ||
		ent1->client->ps.torsoAnim == BOTH_A2_BL_TR ||
		ent1->client->ps.torsoAnim == BOTH_A3_BL_TR ||
		ent1->client->ps.torsoAnim == BOTH_A4_BL_TR ||
		ent1->client->ps.torsoAnim == BOTH_A5_BL_TR )
	{//ent1 is attacking in the opposite diagonal
		return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BL );
	}
	if ( ent2->client->ps.torsoAnim == BOTH_A1_BL_TR ||
		ent2->client->ps.torsoAnim == BOTH_A2_BL_TR ||
		ent2->client->ps.torsoAnim == BOTH_A3_BL_TR ||
		ent2->client->ps.torsoAnim == BOTH_A4_BL_TR ||
		ent2->client->ps.torsoAnim == BOTH_A5_BL_TR )
	{//ent2 is attacking in the opposite diagonal
		return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BL );
	}
	//L to R lock
	if ( ent1->client->ps.torsoAnim == BOTH_A1__L__R ||
		ent1->client->ps.torsoAnim == BOTH_A2__L__R ||
		ent1->client->ps.torsoAnim == BOTH_A3__L__R ||
		ent1->client->ps.torsoAnim == BOTH_A4__L__R ||
		ent1->client->ps.torsoAnim == BOTH_A5__L__R )
	{//ent1 is attacking l to r
		return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
		/*
		if ( ent2BlockingPlayer )
		{//player will block this anyway
			return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
		}
		if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
			ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
			ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
			ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
			ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
			ent2->client->ps.torsoAnim == BOTH_P1_S1_TR ||
			ent2->client->ps.torsoAnim == BOTH_P1_S1_BL )
		{//ent2 is attacking or blocking on the r
			return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
		}
		if ( ent2Boss && !Q_irand( 0, 3 ) )
		{
			return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
		}
		*/
	}
	if ( ent2->client->ps.torsoAnim == BOTH_A1__L__R ||
		ent2->client->ps.torsoAnim == BOTH_A2__L__R ||
		ent2->client->ps.torsoAnim == BOTH_A3__L__R ||
		ent2->client->ps.torsoAnim == BOTH_A4__L__R ||
		ent2->client->ps.torsoAnim == BOTH_A5__L__R )
	{//ent2 is attacking l to r
		return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
		/*
		if ( ent1BlockingPlayer )
		{//player will block this anyway
			return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
		}
		if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
			ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
			ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
			ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
			ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
			ent1->client->ps.torsoAnim == BOTH_P1_S1_TR ||
			ent1->client->ps.torsoAnim == BOTH_P1_S1_BL )
		{//ent1 is attacking or blocking on the r
			return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
		}
		if ( ent1Boss && !Q_irand( 0, 3 ) )
		{
			return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
		}
		*/
	}
	//R to L lock
	if ( ent1->client->ps.torsoAnim == BOTH_A1__R__L ||
		ent1->client->ps.torsoAnim == BOTH_A2__R__L ||
		ent1->client->ps.torsoAnim == BOTH_A3__R__L ||
		ent1->client->ps.torsoAnim == BOTH_A4__R__L ||
		ent1->client->ps.torsoAnim == BOTH_A5__R__L )
	{//ent1 is attacking r to l
		return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
		/*
		if ( ent2BlockingPlayer )
		{//player will block this anyway
			return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
		}
		if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
			ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
			ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
			ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
			ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
			ent2->client->ps.torsoAnim == BOTH_P1_S1_TL ||
			ent2->client->ps.torsoAnim == BOTH_P1_S1_BR )
		{//ent2 is attacking or blocking on the l
			return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
		}
		if ( ent2Boss && !Q_irand( 0, 3 ) )
		{
			return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
		}
		*/
	}
	if ( ent2->client->ps.torsoAnim == BOTH_A1__R__L ||
		ent2->client->ps.torsoAnim == BOTH_A2__R__L ||
		ent2->client->ps.torsoAnim == BOTH_A3__R__L ||
		ent2->client->ps.torsoAnim == BOTH_A4__R__L ||
		ent2->client->ps.torsoAnim == BOTH_A5__R__L )
	{//ent2 is attacking r to l
		return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
		/*
		if ( ent1BlockingPlayer )
		{//player will block this anyway
			return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
		}
		if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
			ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
			ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
			ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
			ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
			ent1->client->ps.torsoAnim == BOTH_P1_S1_TL ||
			ent1->client->ps.torsoAnim == BOTH_P1_S1_BR )
		{//ent1 is attacking or blocking on the l
			return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
		}
		if ( ent1Boss && !Q_irand( 0, 3 ) )
		{
			return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
		}
		*/
	}
	//TR to BL lock
	if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
		ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
		ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
		ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
		ent1->client->ps.torsoAnim == BOTH_A5_TR_BL )
	{//ent1 is attacking diagonally
		return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
		/*
		if ( ent2BlockingPlayer )
		{//player will block this anyway
			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
		}
		if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
			ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
			ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
			ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
			ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
			ent2->client->ps.torsoAnim == BOTH_P1_S1_TL )
		{//ent2 is attacking in the opposite diagonal
			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
		}
		if ( ent2->client->ps.torsoAnim == BOTH_A1_BR_TL ||
			ent2->client->ps.torsoAnim == BOTH_A2_BR_TL ||
			ent2->client->ps.torsoAnim == BOTH_A3_BR_TL ||
			ent2->client->ps.torsoAnim == BOTH_A4_BR_TL ||
			ent2->client->ps.torsoAnim == BOTH_A5_BR_TL ||
			ent2->client->ps.torsoAnim == BOTH_P1_S1_BL )
		{//ent2 is attacking in the opposite diagonal
			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BL );
		}
		if ( ent2Boss && !Q_irand( 0, 3 ) )
		{
			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
		}
		*/
	}

	if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
		ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
		ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
		ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
		ent2->client->ps.torsoAnim == BOTH_A5_TR_BL )
	{//ent2 is attacking diagonally
		return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
		/*
		if ( ent1BlockingPlayer )
		{//player will block this anyway
			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
		}
		if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
			ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
			ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
			ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
			ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
			ent1->client->ps.torsoAnim == BOTH_P1_S1_TL )
		{//ent1 is attacking in the opposite diagonal
			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
		}
		if ( ent1->client->ps.torsoAnim == BOTH_A1_BR_TL ||
			ent1->client->ps.torsoAnim == BOTH_A2_BR_TL ||
			ent1->client->ps.torsoAnim == BOTH_A3_BR_TL ||
			ent1->client->ps.torsoAnim == BOTH_A4_BR_TL ||
			ent1->client->ps.torsoAnim == BOTH_A5_BR_TL ||
			ent1->client->ps.torsoAnim == BOTH_P1_S1_BL )
		{//ent1 is attacking in the opposite diagonal
			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BL );
		}
		if ( ent1Boss && !Q_irand( 0, 3 ) )
		{
			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
		}
		*/
	}

	//TL to BR lock
	if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
		ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
		ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
		ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
		ent1->client->ps.torsoAnim == BOTH_A5_TL_BR )
	{//ent1 is attacking diagonally
		return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
		/*
		if ( ent2BlockingPlayer )
		{//player will block this anyway
			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
		}
		if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
			ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
			ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
			ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
			ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
			ent2->client->ps.torsoAnim == BOTH_P1_S1_TR )
		{//ent2 is attacking in the opposite diagonal
			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
		}
		if ( ent2->client->ps.torsoAnim == BOTH_A1_BL_TR ||
			ent2->client->ps.torsoAnim == BOTH_A2_BL_TR ||
			ent2->client->ps.torsoAnim == BOTH_A3_BL_TR ||
			ent2->client->ps.torsoAnim == BOTH_A4_BL_TR ||
			ent2->client->ps.torsoAnim == BOTH_A5_BL_TR ||
			ent2->client->ps.torsoAnim == BOTH_P1_S1_BR )
		{//ent2 is attacking in the opposite diagonal
			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BR );
		}
		if ( ent2Boss && !Q_irand( 0, 3 ) )
		{
			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
		}
		*/
	}

	if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
		ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
		ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
		ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
		ent2->client->ps.torsoAnim == BOTH_A5_TL_BR )
	{//ent2 is attacking diagonally
		return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
		/*
		if ( ent1BlockingPlayer )
		{//player will block this anyway
			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
		}
		if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
			ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
			ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
			ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
			ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
			ent1->client->ps.torsoAnim == BOTH_P1_S1_TR )
		{//ent1 is attacking in the opposite diagonal
			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
		}
		if ( ent1->client->ps.torsoAnim == BOTH_A1_BL_TR ||
			ent1->client->ps.torsoAnim == BOTH_A2_BL_TR ||
			ent1->client->ps.torsoAnim == BOTH_A3_BL_TR ||
			ent1->client->ps.torsoAnim == BOTH_A4_BL_TR ||
			ent1->client->ps.torsoAnim == BOTH_A5_BL_TR ||
			ent1->client->ps.torsoAnim == BOTH_P1_S1_BR )
		{//ent1 is attacking in the opposite diagonal
			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BR );
		}
		if ( ent1Boss && !Q_irand( 0, 3 ) )
		{
			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
		}
		*/
	}
	//T to B lock
	if ( ent1->client->ps.torsoAnim == BOTH_A1_T__B_ ||
		ent1->client->ps.torsoAnim == BOTH_A2_T__B_ ||
		ent1->client->ps.torsoAnim == BOTH_A3_T__B_ ||
		ent1->client->ps.torsoAnim == BOTH_A4_T__B_ ||
		ent1->client->ps.torsoAnim == BOTH_A5_T__B_ )
	{//ent1 is attacking top-down
		/*
		if ( ent2->client->ps.torsoAnim == BOTH_P1_S1_T_ ||
			ent2->client->ps.torsoAnim == BOTH_K1_S1_T_ )
		*/
		{//ent2 is blocking at top
			return WP_SabersCheckLock2( ent1, ent2, LOCK_TOP );
		}
	}

	if ( ent2->client->ps.torsoAnim == BOTH_A1_T__B_ ||
		ent2->client->ps.torsoAnim == BOTH_A2_T__B_ ||
		ent2->client->ps.torsoAnim == BOTH_A3_T__B_ ||
		ent2->client->ps.torsoAnim == BOTH_A4_T__B_ ||
		ent2->client->ps.torsoAnim == BOTH_A5_T__B_ )
	{//ent2 is attacking top-down
		/*
		if ( ent1->client->ps.torsoAnim == BOTH_P1_S1_T_ ||
			ent1->client->ps.torsoAnim == BOTH_K1_S1_T_ )
		*/
		{//ent1 is blocking at top
			return WP_SabersCheckLock2( ent2, ent1, LOCK_TOP );
		}
	}
	/*
	if ( !Q_irand( 0, 10 ) )
	{
		return WP_SabersCheckLock2( ent1, ent2, LOCK_RANDOM );
	}
	*/
	return qfalse;
}

qboolean WP_SaberParry( gentity_t *victim, gentity_t *attacker )
{
	if ( !victim || !victim->client || !attacker )
	{
		return qfalse;
	}
	if ( victim->s.number || g_saberAutoBlocking->integer || victim->client->ps.saberBlockingTime > level.time )
	{//either an NPC or a player who is blocking
		if ( !PM_SaberInTransitionAny( victim->client->ps.saberMove )
			&& !PM_SaberInBounce( victim->client->ps.saberMove )
			&& !PM_SaberInKnockaway( victim->client->ps.saberMove ) )
		{//I'm not attacking, in transition or in a bounce or knockaway, so play a parry
			WP_SaberBlockNonRandom( victim, saberHitLocation, qfalse );
		}
		victim->client->ps.saberEventFlags |= SEF_PARRIED;

		//since it was parried, take away any damage done
		//FIXME: what if the damage was done before the parry?
		WP_SaberClearDamageForEntNum( victim->s.number );

		//tell the victim to get mad at me
		if ( victim->enemy != attacker && victim->client->playerTeam != attacker->client->playerTeam )
		{//they're not mad at me and they're not on my team
			G_ClearEnemy( victim );
			G_SetEnemy( victim, attacker );
		}
		return qtrue;
	}
	return qfalse;
}

qboolean WP_BrokenParryKnockDown( gentity_t *victim )
{
	if ( !victim || !victim->client )
	{
		return qfalse;
	}
	if ( victim->client->ps.saberMove == LS_PARRY_UP
		|| victim->client->ps.saberMove == LS_PARRY_UR
		|| victim->client->ps.saberMove == LS_PARRY_UL
		|| victim->client->ps.saberMove == LS_H1_BR
		|| victim->client->ps.saberMove == LS_H1_B_
		|| victim->client->ps.saberMove == LS_H1_BL )
	{//knock their asses down!
		int knockAnim = BOTH_KNOCKDOWN1;
		if ( PM_CrouchAnim( victim->client->ps.legsAnim ) )
		{
			knockAnim = BOTH_KNOCKDOWN4;
		}
		NPC_SetAnim( victim, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
		G_AddEvent( victim, EV_PAIN, victim->health );
		return qtrue;
	}
	return qfalse;
}
/*
---------------------------------------------------------
void WP_SaberDamageTrace( gentity_t *ent )

  Constantly trace from the old blade pos to new, down the saber beam and do damage

  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!
---------------------------------------------------------
*/
#define MAX_SABER_SWING_INC 0.33f
void WP_SaberDamageTrace( gentity_t *ent )
{
	vec3_t		mp1, mp2, md1, md2, baseOld, baseNew, baseDiff, endOld, endNew, bladePointOld, bladePointNew;
	float		tipDmgMod = 1.0f;
	float		baseDamage;
	int			baseDFlags = 0;
	qboolean	hit_wall = qfalse;
	qboolean	brokenParry = qfalse;

	for ( int ven = 0; ven < MAX_SABER_VICTIMS; ven++ )
	{
		victimEntityNum[ven] = ENTITYNUM_NONE;
	}
	memset( totalDmg, 0, sizeof( totalDmg) );
	memset( dmgDir, 0, sizeof( dmgDir ) );
	memset( dmgSpot, 0, sizeof( dmgSpot ) );
	memset( dmgFraction, 0, sizeof( dmgFraction ) );
	memset( hitLoc, HL_NONE, sizeof( hitLoc ) );
	memset( hitDismemberLoc, HL_NONE, sizeof( hitDismemberLoc ) );
	memset( hitDismember, qfalse, sizeof( hitDismember ) );
	numVictims = 0;
	VectorClear(saberHitLocation);
	VectorClear(saberHitNormal);
	saberHitFraction = 1.0;	// Closest saber hit.  The saber can do no damage past this point.
	saberHitEntity = ENTITYNUM_NONE;
	sabersCrossed = -1;

	if ( !ent->client )
	{
		return;
	}

	if ( !ent->s.number )
	{//player never uses these
		ent->client->ps.saberEventFlags &= ~SEF_EVENTS;
	}

	if ( ent->client->ps.saberLength <= 1 )//cen get down to 1 when in a wall
	{//saber is not on
		return;
	}

	if ( VectorCompare( ent->client->renderInfo.muzzlePointOld, vec3_origin ) || VectorCompare( ent->client->renderInfo.muzzleDirOld, vec3_origin ) )
	{
		//just started up the saber?
		return;
	}

	int saberContents = gi.pointcontents( ent->client->renderInfo.muzzlePoint, ent->client->ps.saberEntityNum );
	if ( (saberContents&CONTENTS_WATER)||
		(saberContents&CONTENTS_SLIME)||
		(saberContents&CONTENTS_LAVA) )
	{//um... turn off?  Or just set length to 1?
		//FIXME: short-out effect/sound?
		ent->client->ps.saberActive = qfalse;
		return;
	}
	else if ( saberContents&CONTENTS_OUTSIDE )
	{
		if ( (level.worldFlags&WF_RAINING) )
		{
			//add steam in rain
			if ( Q_flrand( 0, 500 ) < ent->client->ps.saberLength )
			{
				vec3_t	end, normal = {0,0,1};//FIXME: opposite of rain angles?
				VectorMA( ent->client->renderInfo.muzzlePoint, ent->client->ps.saberLength*Q_flrand(0, 1), ent->client->renderInfo.muzzleDir, end );
				G_PlayEffect( "saber/fizz", end, normal );
			}
		}
	}

	//FIXMEFIXMEFIXME: When in force speed (esp. lvl 3), need to interpolate this because
	//		we animate so much faster that the arc is pretty much flat...

	int entPowerLevel;
	if ( !ent->s.number && (ent->client->ps.forcePowersActive&(1<<FP_SPEED)) )
	{
		entPowerLevel = FORCE_LEVEL_3;
	}
	else
	{
		entPowerLevel = PM_PowerLevelForSaberAnim( &ent->client->ps );
	}

	if ( ent->client->ps.saberInFlight )
	{//flying sabers are much more deadly
		//unless you're dead
		if ( ent->health <= 0 && !g_saberRealisticCombat->integer )
		{//so enemies don't keep trying to block it
			//FIXME: still do damage, just not to humanoid clients who should try to avoid it
			//baseDamage = 0.0f;
			return;
		}
		//or unless returning
		else if ( ent->client->ps.saberEntityState == SES_RETURNING )
		{//special case, since we're returning, chances are if we hit something
			//it's going to be butt-first.  So do less damage.
			baseDamage = 0.1f;
		}
		else
		{
			if ( !ent->s.number )
			{//cheat for player
				baseDamage = 10.0f;
			}
			else
			{
				baseDamage = 2.5f;
			}
		}
		//Use old to current since can't predict it
		VectorCopy( ent->client->renderInfo.muzzlePointOld, mp1 );
		VectorCopy( ent->client->renderInfo.muzzleDirOld, md1 );
		VectorCopy( ent->client->renderInfo.muzzlePoint, mp2 );
		VectorCopy( ent->client->renderInfo.muzzleDir, md2 );
	}
	else
	{
		if ( ent->client->ps.saberMove == LS_READY )
		{//just do effects
			if ( g_saberRealisticCombat->integer < 2 )
			{//don't kill with this hit
				baseDFlags = DAMAGE_NO_KILL;
			}
			baseDamage = 0;
		}
		else if ( ent->client->ps.saberLockTime > level.time )
		{//just do effects
			baseDamage = 0;
		}
		else if ( ent->client->ps.saberBlocked > BLOCKED_NONE
				 || ( !PM_SaberInAttack( ent->client->ps.saberMove )
					  && !PM_SaberInSpecialAttack( ent->client->ps.torsoAnim )
					  && !PM_SaberInTransitionAny( ent->client->ps.saberMove )
					)
				)
		{//don't do damage if parrying/reflecting/bouncing/deflecting or not actually attacking or in a transition to/from/between attacks
			baseDamage = 0;
		}
		else
		{//okay, in a saberMove that does damage
			//make sure we're in the right anim
			if ( !PM_SaberInSpecialAttack( ent->client->ps.torsoAnim )
				&& !PM_InAnimForSaberMove( ent->client->ps.torsoAnim, ent->client->ps.saberMove ) )
			{//forced into some other animation somehow, like a pain or death?
				baseDamage = 0;
			}
			else if ( ent->client->ps.weaponstate == WEAPON_FIRING && ent->client->ps.saberBlocked == BLOCKED_NONE &&
				( PM_SaberInAttack(ent->client->ps.saberMove) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) || PM_SpinningSaberAnim(ent->client->ps.torsoAnim) || entPowerLevel > FORCE_LEVEL_2 ) )
			{//normal attack swing swinging/spinning (or if using strong set), do normal damage
				//FIXME: more damage for higher attack power levels?
				//		More damage based on length/color of saber?
				//FIXME: Desann does double damage?
				if ( g_saberRealisticCombat->integer )
				{
					switch ( entPowerLevel )
					{
					case FORCE_LEVEL_3:
						baseDamage = 10.0f;
						break;
					case FORCE_LEVEL_2:
						baseDamage = 5.0f;
						break;
					default:
					case FORCE_LEVEL_1:
						baseDamage = 2.5f;
						break;
					}
				}
				else
				{
					baseDamage = 2.5f * (float)entPowerLevel;
				}
			}
			else
			{//saber is transitioning, defending or idle, don't do as much damage
				//FIXME: strong attacks and returns should do damage and be unblockable
				if ( g_timescale->value < 1.0 )
				{//in slow mo or force speed, we need to do damage during the transitions
					if ( g_saberRealisticCombat->integer )
					{
						switch ( entPowerLevel )
						{
						case FORCE_LEVEL_3:
							baseDamage = 10.0f;
							break;
						case FORCE_LEVEL_2:
							baseDamage = 5.0f;
							break;
						default:
						case FORCE_LEVEL_1:
							baseDamage = 2.5f;
							break;
						}
					}
					else
					{
						baseDamage = 2.5f * (float)entPowerLevel;
					}
				}
				else// if ( !ent->s.number )
				{//I have to do *some* damage in transitions or else you feel like a total gimp
					baseDamage = 0.1f;
				}
				/*
				else
				{
					baseDamage = 0;//was 1.0f;//was 0.25
				}
				*/
			}
		}

		//Use current to next since can predict it
		//FIXME: if they're closer than the saber blade start, we don't want the
		//		arm to pass through them without any damage... so check the radius
		//		and push them away (do pain & knockback)
		//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?
		//VectorCopy( ent->client->renderInfo.muzzlePoint, mp1 );
		//VectorCopy( ent->client->renderInfo.muzzleDir, md1 );
		//VectorCopy( ent->client->renderInfo.muzzlePointNext, mp2 );
		//VectorCopy( ent->client->renderInfo.muzzleDirNext, md2 );
		//prediction was causing gaps in swing (G2 problem) so *don't* predict
		VectorCopy( ent->client->renderInfo.muzzlePointOld, mp1 );
		VectorCopy( ent->client->renderInfo.muzzleDirOld, md1 );
		VectorCopy( ent->client->renderInfo.muzzlePoint, mp2 );
		VectorCopy( ent->client->renderInfo.muzzleDir, md2 );

		//NOTE: this is a test, may not be necc, as I can still swing right through someone without hitting them, somehow...
		//see if anyone is so close that they're within the dist from my origin to the start of the saber
		if ( ent->health > 0 && !ent->client->ps.saberLockTime )
		{
			trace_t trace;
			gi.trace( &trace, ent->currentOrigin, vec3_origin, vec3_origin, mp1, ent->s.number, (MASK_SHOT&~(CONTENTS_CORPSE|CONTENTS_ITEM)), G2_NOCOLLIDE, 0 );
			if ( trace.entityNum < ENTITYNUM_WORLD && (trace.entityNum > 0||ent->client->NPC_class == CLASS_DESANN) )//NPCs don't push player away, unless it's Desann
			{//a valid ent
				gentity_t *traceEnt = &g_entities[trace.entityNum];
				if ( traceEnt
					&& traceEnt->client
					&& traceEnt->health > 0
					&& traceEnt->client->playerTeam != ent->client->playerTeam
					&& !PM_InKnockDown( &traceEnt->client->ps ) )
				{//enemy client, push them away
					if ( !traceEnt->client->ps.saberLockTime && !traceEnt->message )
					{//don't push people in saberlock or with security keys
						vec3_t hitDir;
						VectorSubtract( trace.endpos, ent->currentOrigin, hitDir );
						float totalDist = Distance( mp1, ent->currentOrigin );
						float knockback = (totalDist-VectorNormalize( hitDir ))/totalDist * 200.0f;
						hitDir[2] = 0;
						//FIXME: do we need to call G_Throw?  Seems unfair to put actual knockback on them, stops the attack
						//G_Throw( traceEnt, hitDir, knockback );
						VectorMA( traceEnt->client->ps.velocity, knockback, hitDir, traceEnt->client->ps.velocity );
						traceEnt->client->ps.pm_time = 200;
						traceEnt->client->ps.pm_flags |= PMF_TIME_NOFRICTION;
#ifndef FINAL_BUILD
						if ( d_saberCombat->integer )
						{
							gi.Printf( "%s pushing away %s at %s\n", ent->NPC_type, traceEnt->NPC_type, vtos( traceEnt->client->ps.velocity ) );
						}
#endif
					}
				}
			}
		}
	}

	if ( g_saberRealisticCombat->integer > 1 )
	{//always do damage, and lots of it
		if ( g_saberRealisticCombat->integer > 2 )
		{//always do damage, and lots of it
			baseDamage = 25.0f;
		}
		else if ( baseDamage > 0.1f )
		{//only do super damage if we would have done damage according to normal rules
			baseDamage = 25.0f;
		}
	}
	else if ( (!ent->s.number&&ent->client->ps.forcePowersActive&(1<<FP_SPEED)) )
	{
		baseDamage *= (1.0f-g_timescale->value);
	}
	// Get the old state of the blade
	VectorCopy( mp1, baseOld );
	VectorMA( baseOld, ent->client->ps.saberLength, md1, endOld );
	// Get the future state of the blade
	VectorCopy( mp2, baseNew );
	VectorMA( baseNew, ent->client->ps.saberLength, md2, endNew );

	sabersCrossed = -1;
	if ( VectorCompare2( baseOld, baseNew ) && VectorCompare2( endOld, endNew ) )
	{
		hit_wall = WP_SaberDamageForTrace( ent->s.number, mp2, endNew, baseDamage*4, md2, qfalse, entPowerLevel, qfalse );
	}
	else
	{
		float aveLength, step = 8, stepsize = 8;
		vec3_t	ma1, ma2, md2ang, curBase1, curBase2;
		int	xx;
		//do the trace at the base first
		hit_wall = WP_SaberDamageForTrace( ent->s.number, baseOld, baseNew, baseDamage, md2, qfalse, entPowerLevel );

		//if hit a saber, shorten rest of traces to match
		if ( saberHitFraction < 1.0 )
		{
			//adjust muzzleDir...
			vec3_t ma1, ma2;
			vectoangles( md1, ma1 );
			vectoangles( md2, ma2 );
			for ( xx = 0; xx < 3; xx++ )
			{
				md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], saberHitFraction );
			}
			AngleVectors( md2ang, md2, NULL, NULL );
			//shorten the base pos
			VectorSubtract( mp2, mp1, baseDiff );
			VectorMA( mp1, saberHitFraction, baseDiff, baseNew );
			VectorMA( baseNew, ent->client->ps.saberLength, md2, endNew );
		}

		//If the angle diff in the blade is high, need to do it in chunks of 33 to avoid flattening of the arc
		float dirInc, curDirFrac;
		if ( PM_SaberInAttack( ent->client->ps.saberMove )
			|| PM_SaberInSpecialAttack( ent->client->ps.torsoAnim )
			|| PM_SpinningSaberAnim( ent->client->ps.torsoAnim )
			|| PM_InSpecialJump( ent->client->ps.torsoAnim )
			|| (g_timescale->value<1.0f&&PM_SaberInTransitionAny( ent->client->ps.saberMove )) )
		{
			curDirFrac = DotProduct( md1, md2 );
		}
		else
		{
			curDirFrac = 1.0f;
		}
		//NOTE: if saber spun at least 180 degrees since last damage trace, this is not reliable...!
		if ( fabs(curDirFrac) < 1.0f - MAX_SABER_SWING_INC )
		{//the saber blade spun more than 33 degrees since the last damage trace
			curDirFrac = dirInc = 1.0f/((1.0f - curDirFrac)/MAX_SABER_SWING_INC);
		}
		else
		{
			curDirFrac = 1.0f;
			dirInc = 0.0f;
		}
		qboolean hit_saber = qfalse;

		vectoangles( md1, ma1 );
		vectoangles( md2, ma2 );

		vec3_t curMD1, curMD2;//, mdDiff, dirDiff;
		//VectorSubtract( md2, md1, mdDiff );
		VectorCopy( md1, curMD2 );
		VectorCopy( baseOld, curBase2 );

		while ( 1 )
		{
			VectorCopy( curMD2, curMD1 );
			VectorCopy( curBase2, curBase1 );
			if ( curDirFrac >= 1.0f )
			{
				VectorCopy( md2, curMD2 );
				VectorCopy( baseNew, curBase2 );
			}
			else
			{
				for ( xx = 0; xx < 3; xx++ )
				{
					md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], curDirFrac );
				}
				AngleVectors( md2ang, curMD2, NULL, NULL );
				//VectorMA( md1, curDirFrac, mdDiff, curMD2 );
				VectorSubtract( baseNew, baseOld, baseDiff );
				VectorMA( baseOld, curDirFrac, baseDiff, curBase2 );
			}
			// Move up the blade in intervals of stepsize
			for ( step = stepsize; step < ent->client->ps.saberLength && step < ent->client->ps.saberLengthOld; step+=12 )
			{
				VectorMA( curBase1, step, curMD1, bladePointOld );
				VectorMA( curBase2, step, curMD2, bladePointNew );
				if ( WP_SaberDamageForTrace( ent->s.number, bladePointOld, bladePointNew, baseDamage, curMD2, qfalse, entPowerLevel ) )
				{
					hit_wall = qtrue;
				}

				//if hit a saber, shorten rest of traces to match
				if ( saberHitFraction < 1.0 )
				{
					//adjust muzzle endpoint
					VectorSubtract( mp2, mp1, baseDiff );
					VectorMA( mp1, saberHitFraction, baseDiff, baseNew );
					VectorMA( baseNew, ent->client->ps.saberLength, curMD2, endNew );
					//adjust muzzleDir...
					vec3_t curMA1, curMA2;
					vectoangles( curMD1, curMA1 );
					vectoangles( curMD2, curMA2 );
					for ( xx = 0; xx < 3; xx++ )
					{
						md2ang[xx] = LerpAngle( curMA1[xx], curMA2[xx], saberHitFraction );
					}
					AngleVectors( md2ang, curMD2, NULL, NULL );
					/*
					VectorSubtract( curMD2, curMD1, dirDiff );
					VectorMA( curMD1, saberHitFraction, dirDiff, curMD2 );
					*/
					hit_saber = qtrue;
				}
				if (hit_wall)
				{
					break;
				}
			}
			if ( hit_wall || hit_saber )
			{
				break;
			}
			if ( curDirFrac >= 1.0f )
			{
				break;
			}
			else
			{
				curDirFrac += dirInc;
				if ( curDirFrac >= 1.0f )
				{
					curDirFrac = 1.0f;
				}
			}
		}

		//do the trace at the end last
		//Special check- adjust for length of blade not being a multiple of 12
		aveLength = (ent->client->ps.saberLengthOld + ent->client->ps.saberLength)/2;
		if ( step > aveLength )
		{//less dmg if the last interval was not stepsize
			tipDmgMod = (stepsize-(step-aveLength))/stepsize;
		}
		//NOTE: since this is the tip, we do not extrapolate the extra 16
		if ( WP_SaberDamageForTrace( ent->s.number, endOld, endNew, tipDmgMod*baseDamage, md2, qfalse, entPowerLevel, qfalse ) )
		{
			hit_wall = qtrue;
		}
	}

	if ( (saberHitFraction < 1.0f||(sabersCrossed>=0&&sabersCrossed<=32.0f)) && (ent->client->ps.weaponstate == WEAPON_FIRING || ent->client->ps.saberInFlight) )
	{// The saber (in-hand) hit another saber, mano.
		qboolean inFlightSaberBlocked = qfalse;
		qboolean collisionResolved = qfalse;
		qboolean deflected = qfalse;

		gentity_t *hitEnt = &g_entities[saberHitEntity];
		gentity_t *hitOwner = NULL;
		int hitOwnerPowerLevel = FORCE_LEVEL_0;

		if ( hitEnt )
		{
			hitOwner = hitEnt->owner;
		}
		if ( hitOwner && hitOwner->client )
		{
			hitOwnerPowerLevel = PM_PowerLevelForSaberAnim( &hitOwner->client->ps );
			if ( entPowerLevel == FORCE_LEVEL_3 && PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) )
			{//a special "unblockable" attack
				if ( hitOwner->client->NPC_class == CLASS_DESANN
					|| hitOwner->client->NPC_class == CLASS_TAVION
					|| hitOwner->client->NPC_class == CLASS_LUKE )
				{//these masters can even block unblockables (stops cheap kills)
					entPowerLevel = FORCE_LEVEL_2;
				}
			}
		}

		//FIXME: check for certain anims, facing, etc, to make them lock into a sabers-locked pose
		//SEF_LOCKED

		if ( ent->client->ps.saberInFlight &&
			ent->client->ps.saberActive &&
			ent->client->ps.saberEntityNum != ENTITYNUM_NONE &&
			ent->client->ps.saberEntityState != SES_RETURNING )
		{//saber was blocked, return it
			inFlightSaberBlocked = qtrue;
		}

		//FIXME: based on strength, position and angle of attack & defense, decide if:
		//	defender and attacker lock sabers
		//	*defender's parry should hold and attack bounces (or deflects, based on angle of sabers)
		//	*defender's parry is somewhat broken and both bounce (or deflect)
		//	*defender's parry is broken and they bounce while attacker's attack deflects or carries through (especially if they're dead)
		//	defender is knocked down and attack goes through

		//Check deflections and broken parries
		if ( hitOwner && hitOwner->health > 0 && ent->health > 0 //both are alive
			&& !inFlightSaberBlocked && hitOwner->client && !hitOwner->client->ps.saberInFlight && !ent->client->ps.saberInFlight//both have sabers in-hand
			&& ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN
			&& ent->client->ps.saberLockTime < level.time
			&& hitOwner->client->ps.saberLockTime < level.time )
		{//2 in-hand sabers hit
			//FIXME: defender should not parry or block at all if not in a saber anim... like, if in a roll or knockdown...
			if ( baseDamage )
			{//there is damage involved, not just effects
				qboolean entAttacking = qfalse;
				qboolean hitOwnerAttacking = qfalse;
				qboolean entDefending = qfalse;
				qboolean hitOwnerDefending = qfalse;

				if ( PM_SaberInAttack( ent->client->ps.saberMove ) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) )
				{
					entAttacking = qtrue;
				}
				else if ( entPowerLevel > FORCE_LEVEL_2 )
				{
					if ( PM_SaberInAttack( ent->client->ps.saberMove ) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) || PM_SaberInTransitionAny( ent->client->ps.saberMove ) )
					{
						entAttacking = qtrue;
					}
				}
				if ( PM_SaberInParry( ent->client->ps.saberMove )
					|| ent->client->ps.saberMove == LS_READY )
				{
					entDefending = qtrue;
				}

				if ( PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) )
				{
					hitOwnerAttacking = qtrue;
				}
				else if ( hitOwnerPowerLevel > FORCE_LEVEL_2 )
				{
					if ( PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) || PM_SaberInTransitionAny( hitOwner->client->ps.saberMove ) )
					{
						hitOwnerAttacking = qtrue;
					}
				}
				if ( PM_SaberInParry( hitOwner->client->ps.saberMove )
					 || hitOwner->client->ps.saberMove == LS_READY )
				{
					hitOwnerDefending = qtrue;
				}

				if ( entAttacking
					&& hitOwnerAttacking
					&& ( entPowerLevel == hitOwnerPowerLevel
						|| (entPowerLevel > FORCE_LEVEL_2 && hitOwnerPowerLevel > FORCE_LEVEL_2 )
						|| (entPowerLevel < FORCE_LEVEL_3 && hitOwnerPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_2 && Q_irand( 0, 2 ))
						|| (entPowerLevel < FORCE_LEVEL_2 && hitOwnerPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_1 && Q_irand( 0, 1 ))
						|| (hitOwnerPowerLevel < FORCE_LEVEL_3 && entPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_2 && !Q_irand( 0, 1 ))
						|| (hitOwnerPowerLevel < FORCE_LEVEL_2 && entPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_1 && !Q_irand( 0, 1 )))
					&& WP_SabersCheckLock( ent, hitOwner ) )
				{
					collisionResolved = qtrue;
				}
				else if ( hitOwnerAttacking
					&& entDefending
					&& !Q_irand( 0, 2 )
					&& (ent->client->ps.saberMove != LS_READY || (hitOwnerPowerLevel-ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) < Q_irand( -6, 0 ) )
					&& ((hitOwnerPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )||
						(hitOwnerPowerLevel < FORCE_LEVEL_2 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_1 )||
						(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 )))
					&& WP_SabersCheckLock( hitOwner, ent ) )
				{
					collisionResolved = qtrue;
				}
				else if ( entAttacking && hitOwnerDefending )
				{//I'm attacking hit, they're parrying
					qboolean activeDefense = (qboolean)(
						hitOwner->s.number ||
						g_saberAutoBlocking->integer ||
						hitOwner->client->ps.saberBlockingTime > level.time);

					if ( !Q_irand( 0, 2 )
						&& activeDefense
						&& (hitOwner->client->ps.saberMove != LS_READY || (entPowerLevel-hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) < Q_irand( -6, 0 ) )
						&& ( ( entPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )
							|| ( entPowerLevel < FORCE_LEVEL_2 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_1 )
							|| ( 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 )) )
						&& WP_SabersCheckLock( ent, hitOwner ) )
					{
						collisionResolved = qtrue;
					}
					else if ( saberHitFraction < 1.0f )
					{//an actual collision
						if ( entPowerLevel < FORCE_LEVEL_3 && activeDefense )
						{//strong attacks cannot be deflected
							//based on angle of attack & angle of defensive saber, see if I should deflect off in another dir rather than bounce back
							deflected = WP_GetSaberDeflectionAngle( ent, hitOwner );
							//just so Jedi knows that he was blocked
							ent->client->ps.saberEventFlags |= SEF_BLOCKED;
						}
						//base parry breaks on animation (saber attack level), not FP_SABER_OFFENSE
						if ( entPowerLevel < FORCE_LEVEL_3
							//&& 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?
							&& hitOwner->client->ps.saberAnimLevel != FORCE_LEVEL_5
							&& activeDefense
							&& (hitOwnerPowerLevel > FORCE_LEVEL_2||(hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_2&&Q_irand(0,hitOwner->client->ps.saberAnimLevel))) )
						{//knockaways can make fast-attacker go into a broken parry anim if the ent is using fast or med (but not Tavion)
							//make me parry
							WP_SaberParry( hitOwner, ent );
							//turn the parry into a knockaway
							hitOwner->client->ps.saberBounceMove = PM_KnockawayForParry( hitOwner->client->ps.saberBlocked );
							//make them go into a broken parry
							ent->client->ps.saberBounceMove = PM_BrokenParryForAttack( ent->client->ps.saberMove );
							ent->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
							if ( ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_2
								&& (ent->s.number||g_saberRealisticCombat->integer) )//&& !Q_irand( 0, 3 ) )
							{//knocked the saber right out of his hand! (never happens to player)
								//Get a good velocity to send the saber in based on my parry move
								vec3_t	throwDir;
								if ( !PM_VelocityForBlockedMove( &hitOwner->client->ps, throwDir ) )
								{
									PM_VelocityForSaberMove( &ent->client->ps, throwDir );
								}
								WP_SaberLose( ent, throwDir );
							}
							//just so Jedi knows that he was blocked
							ent->client->ps.saberEventFlags |= SEF_BLOCKED;
#ifndef FINAL_BUILD
							if ( d_saberCombat->integer )
							{
								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 );
							}
#endif
						}
						else if ( entPowerLevel > FORCE_LEVEL_2
							|| !activeDefense
							|| (!deflected && Q_irand( 0, PM_PowerLevelForSaberAnim( &ent->client->ps ) - hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]/*PM_PowerLevelForSaberAnim( &hitOwner->client->ps )*/ ) > 0 ) )
						{//broke their parry altogether
							if ( entPowerLevel > FORCE_LEVEL_2 || Q_irand( 0, ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] - hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) )
							{//chance of continuing with the attack (not bouncing back)
								ent->client->ps.saberEventFlags &= ~SEF_BLOCKED;
								ent->client->ps.saberBounceMove = LS_NONE;
								brokenParry = qtrue;
							}
							//do some time-consuming saber-knocked-aside broken parry anim
							hitOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
							hitOwner->client->ps.saberBounceMove = LS_NONE;
							if ( hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_2
								&& (ent->s.number||g_saberRealisticCombat->integer)
								&& !Q_irand( 0, 2 ) )
							{//knocked the saber right out of his hand!
								//get the right velocity for my attack direction
								vec3_t	throwDir;
								PM_VelocityForSaberMove( &ent->client->ps, throwDir );
								WP_SaberLose( hitOwner, throwDir );
								if ( (ent->client->ps.saberAnimLevel == FORCE_LEVEL_3 && !Q_irand(0,3) )
									|| ( ent->client->ps.saberAnimLevel==FORCE_LEVEL_4&&!Q_irand(0,1) ) )
								{// a strong attack
									if ( WP_BrokenParryKnockDown( hitOwner ) )
									{
										hitOwner->client->ps.saberBlocked = BLOCKED_NONE;
										hitOwner->client->ps.saberBounceMove = LS_NONE;
									}
								}
							}
							else
							{
								if ( (ent->client->ps.saberAnimLevel == FORCE_LEVEL_3 && !Q_irand(0,5) )
									|| ( ent->client->ps.saberAnimLevel==FORCE_LEVEL_4&&!Q_irand(0,3) ) )
								{// a strong attack
									if ( WP_BrokenParryKnockDown( hitOwner ) )
									{
										hitOwner->client->ps.saberBlocked = BLOCKED_NONE;
										hitOwner->client->ps.saberBounceMove = LS_NONE;
									}
								}
							}
#ifndef FINAL_BUILD
							if ( d_saberCombat->integer )
							{
								if ( ent->client->ps.saberEventFlags&SEF_BLOCKED )
								{
									gi.Printf( S_COLOR_RED"%s parry broken (bounce/deflect)!\n", hitOwner->targetname );
								}
								else
								{
									gi.Printf( S_COLOR_RED"%s parry broken (follow-through)!\n", hitOwner->targetname );
								}
							}
#endif
						}
						else
						{
							WP_SaberParry( hitOwner, ent );
							if ( PM_SaberInBounce( ent->client->ps.saberMove ) //FIXME: saberMove not set until pmove!
								&& activeDefense
								&& hitOwner->client->ps.saberAnimLevel != FORCE_LEVEL_1 && hitOwner->client->ps.saberAnimLevel != FORCE_LEVEL_5
								&& hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )
							{//attacker bounced off, and defender has ability to do knockaways, so do one unless we're using fast attacks
								//turn the parry into a knockaway
								hitOwner->client->ps.saberBounceMove = PM_KnockawayForParry( hitOwner->client->ps.saberBlocked );
							}
							else if ( (ent->client->ps.saberAnimLevel == FORCE_LEVEL_3 && !Q_irand(0,6) )
									|| ( ent->client->ps.saberAnimLevel==FORCE_LEVEL_4 && !Q_irand(0,3) ) )
							{// a strong attack can sometimes do a knockdown
								//HMM... maybe only if they're moving backwards?
								if ( WP_BrokenParryKnockDown( hitOwner ) )
								{
									hitOwner->client->ps.saberBlocked = BLOCKED_NONE;
									hitOwner->client->ps.saberBounceMove = LS_NONE;
								}
							}
						}
						collisionResolved = qtrue;
					}
				}
				/*
				else if ( entDefending && hitOwnerAttacking )
				{//I'm parrying, they're attacking
					if ( hitOwnerPowerLevel < FORCE_LEVEL_3 )
					{//strong attacks cannot be deflected
						//based on angle of attack & angle of defensive saber, see if I should deflect off in another dir rather than bounce back
						deflected = WP_GetSaberDeflectionAngle( hitOwner, ent );
						//just so Jedi knows that he was blocked
						hitOwner->client->ps.saberEventFlags |= SEF_BLOCKED;
					}
					//FIXME: base parry breaks on animation (saber attack level), not FP_SABER_OFFENSE
					if ( hitOwnerPowerLevel > FORCE_LEVEL_2 || (!deflected && Q_irand( 0, PM_PowerLevelForSaberAnim( &hitOwner->client->ps ) - ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) > 0 ) )
					{//broke my parry altogether
						if ( hitOwnerPowerLevel > FORCE_LEVEL_2 || Q_irand( 0, hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] - ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) )
						{//chance of continuing with the attack (not bouncing back)
							hitOwner->client->ps.saberEventFlags &= ~SEF_BLOCKED;
							hitOwner->client->ps.saberBounceMove = LS_NONE;
						}
						//do some time-consuming saber-knocked-aside broken parry anim
						ent->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
#ifndef FINAL_BUILD
						if ( d_saberCombat->integer )
						{
							if ( hitOwner->client->ps.saberEventFlags&SEF_BLOCKED )
							{
								gi.Printf( S_COLOR_RED"%s parry broken (bounce/deflect)!\n", ent->targetname );
							}
							else
							{
								gi.Printf( S_COLOR_RED"%s parry broken (follow-through)!\n", ent->targetname );
							}
						}
#endif
					}
					else
					{
						WP_SaberParry( ent, hitOwner );
					}
					collisionResolved = qtrue;
				}
				*/
				else
				{//some other kind of in-hand saber collision
				}
			}
		}
		else
		{//some kind of in-flight collision
		}

		if ( saberHitFraction < 1.0f )
		{
			if ( !collisionResolved && baseDamage )
			{//some other kind of in-hand saber collision
				//handle my reaction
				if ( !ent->client->ps.saberInFlight
					&& ent->client->ps.saberLockTime < level.time )
				{//my saber is in hand
					if ( ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
					{
						if ( PM_SaberInAttack( ent->client->ps.saberMove ) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) ||
							(entPowerLevel > FORCE_LEVEL_2&&!PM_SaberInIdle(ent->client->ps.saberMove)&&!PM_SaberInParry(ent->client->ps.saberMove)&&!PM_SaberInReflect(ent->client->ps.saberMove)) )
						{//in the middle of attacking
							if ( entPowerLevel < FORCE_LEVEL_3 && hitOwner->health > 0 )
							{//don't deflect/bounce in strong attack or when enemy is dead
								WP_GetSaberDeflectionAngle( ent, hitOwner );
								ent->client->ps.saberEventFlags |= SEF_BLOCKED;
								//since it was blocked/deflected, take away any damage done
								//FIXME: what if the damage was done before the parry?
								WP_SaberClearDamageForEntNum( hitOwner->s.number );
							}
						}
						else
						{//saber collided when not attacking, parry it
							//since it was blocked/deflected, take away any damage done
							//FIXME: what if the damage was done before the parry?
							WP_SaberClearDamageForEntNum( hitOwner->s.number );
							/*
							if ( ent->s.number || g_saberAutoBlocking->integer || ent->client->ps.saberBlockingTime > level.time )
							{//either an NPC or a player who has blocking
								if ( !PM_SaberInTransitionAny( ent->client->ps.saberMove ) && !PM_SaberInBounce( ent->client->ps.saberMove ) )
								{//I'm not attacking, in transition or in a bounce, so play a parry
									//just so Jedi knows that he parried something
									WP_SaberBlockNonRandom( ent, saberHitLocation, qfalse );
								}
								ent->client->ps.saberEventFlags |= SEF_PARRIED;
							}
							*/
						}
					}
					else
					{
						//since it was blocked/deflected, take away any damage done
						//FIXME: what if the damage was done before the parry?
						WP_SaberClearDamageForEntNum( hitOwner->s.number );
					}
				}
				else
				{//nothing happens to *me* when my inFlight saber hits something
				}
				//handle their reaction
				if ( hitOwner
					&& hitOwner->health > 0
					&& hitOwner->client
					&& !hitOwner->client->ps.saberInFlight
					&& hitOwner->client->ps.saberLockTime < level.time )
				{//their saber is in hand
					if ( PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) ||
						(hitOwner->client->ps.saberAnimLevel > FORCE_LEVEL_2&&!PM_SaberInIdle(hitOwner->client->ps.saberMove)&&!PM_SaberInParry(hitOwner->client->ps.saberMove)&&!PM_SaberInReflect(hitOwner->client->ps.saberMove)) )
					{//in the middle of attacking
						/*
						if ( hitOwner->client->ps.saberAnimLevel < FORCE_LEVEL_3 )
						{//don't deflect/bounce in strong attack
							WP_GetSaberDeflectionAngle( hitOwner, ent );
							hitOwner->client->ps.saberEventFlags |= SEF_BLOCKED;
						}
						*/
					}
					else
					{//saber collided when not attacking, parry it
						if ( !PM_SaberInBrokenParry( hitOwner->client->ps.saberMove ) )
						{//not currently in a broken parry
							if ( !WP_SaberParry( hitOwner, ent ) )
							{//FIXME: hitOwner can't parry, do some time-consuming saber-knocked-aside broken parry anim?
								//hitOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
							}
						}
					}
				}
				else
				{//nothing happens to *hitOwner* when their inFlight saber hits something
				}
			}

			//collision must have been handled by now
			//Set the blocked attack bounce value in saberBlocked so we actually play our saberBounceMove anim
			if ( ent->client->ps.saberEventFlags & SEF_BLOCKED )
			{
				if ( ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
				{
					ent->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
				}
			}
			/*
			if ( hitOwner && hitOwner->client->ps.saberEventFlags & SEF_BLOCKED )
			{
				hitOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
			}
			*/
		}

		if ( saberHitFraction < 1.0f || collisionResolved )
		{//either actually hit or locked
			if ( ent->client->ps.saberLockTime < level.time )
			{
				if ( inFlightSaberBlocked )
				{//FIXME: never hear this sound
					G_Sound( &g_entities[ent->client->ps.saberEntityNum], G_SoundIndex( va( "sound/weapons/saber/saberbounce%d.wav", Q_irand(1,3) ) ) );
				}
				else
				{
					if ( deflected )
					{
						G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberbounce%d.wav", Q_irand(1,3) ) ) );
					}
					else
					{
						G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
					}
				}
				G_PlayEffect( "saber_block", saberHitLocation, saberHitNormal );
			}
			// Set the little screen flash - only when an attack is blocked
			g_saberFlashTime = level.time-50;
			VectorCopy( saberHitLocation, g_saberFlashPos );
		}

		if ( saberHitFraction < 1.0f )
		{
			if ( inFlightSaberBlocked )
			{//we threw a saber and it was blocked, do any effects, etc.
				int	knockAway = 5;
				if ( hitEnt
					&& hitOwner
					&& hitOwner->client
					&& (PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) || PM_SpinningSaberAnim( hitOwner->client->ps.torsoAnim )) )
				{//if hit someone who was in an attack or spin anim, more likely to have in-flight saber knocked away
					if ( hitOwnerPowerLevel > FORCE_LEVEL_2 )
					{//string attacks almost always knock it aside!
						knockAway = 1;
					}
					else
					{//33% chance
						knockAway = 2;
					}
				}
				if ( !Q_irand( 0, knockAway ) || //random
						( hitOwner && hitOwner->client &&
							(hitOwner->client->NPC_class==CLASS_DESANN||hitOwner->client->NPC_class==CLASS_TAVION||hitOwner->client->NPC_class==CLASS_LUKE)
						) //or if blocked by a Boss character FIXME: or base on defense level?
					)//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
				{//knock it aside and turn it off
					G_PlayEffect( "saber_cut", saberHitLocation, saberHitNormal );
					if ( hitEnt )
					{
						vec3_t newDir;

						VectorSubtract( g_entities[ent->client->ps.saberEntityNum].currentOrigin, hitEnt->currentOrigin, newDir );
						VectorNormalize( newDir );
						G_ReflectMissile( ent, &g_entities[ent->client->ps.saberEntityNum], newDir );
					}
					Jedi_PlayDeflectSound( hitOwner );
					WP_SaberDrop( ent, &g_entities[ent->client->ps.saberEntityNum] );
				}
				else
				{
					if ( !Q_irand( 0, 2 ) && hitEnt )
					{
						vec3_t newDir;
						VectorSubtract( g_entities[ent->client->ps.saberEntityNum].currentOrigin, hitEnt->currentOrigin, newDir );
						VectorNormalize( newDir );
						G_ReflectMissile( ent, &g_entities[ent->client->ps.saberEntityNum], newDir );
					}
					WP_SaberReturn( ent, &g_entities[ent->client->ps.saberEntityNum] );
				}
			}
		}
	}

	if ( ent->client->ps.saberLockTime > level.time
		&& ent->s.number < ent->client->ps.saberLockEnemy
		&& !Q_irand( 0, 3 ) )
	{//need to make some kind of effect
		vec3_t	hitNorm = {0,0,1};
		if ( WP_SabersIntersection( ent, &g_entities[ent->client->ps.saberLockEnemy], g_saberFlashPos ) )
		{
			if ( Q_irand( 0, 10 ) )
			{
				G_PlayEffect( "saber_block", g_saberFlashPos, hitNorm );
			}
			else
			{
				g_saberFlashTime = level.time-50;
				G_PlayEffect( "saber_cut", g_saberFlashPos, hitNorm );
			}
			G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
		}
	}

	if ( WP_SaberApplyDamage( ent, baseDamage, baseDFlags, brokenParry ) )
	{//actually did damage to something
#ifndef FINAL_BUILD
		if ( d_saberCombat->integer )
		{
			gi.Printf( "base damage was %4.2f\n", baseDamage );
		}
#endif
		G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberhit%d.wav", Q_irand( 1, 3 ) ) ) );
	}

	if ( hit_wall )
	{
		//just so Jedi knows that he hit a wall
		ent->client->ps.saberEventFlags |= SEF_HITWALL;
		if ( ent->s.number == 0 )
		{
			AddSoundEvent( ent, ent->currentOrigin, 128, AEL_DISCOVERED );
			AddSightEvent( ent, ent->currentOrigin, 256, AEL_DISCOVERED, 50 );
		}
	}
}

//SABER THROWING============================================================================
//SABER THROWING============================================================================
//SABER THROWING============================================================================
//SABER THROWING============================================================================
//SABER THROWING============================================================================
//SABER THROWING============================================================================

/*
================
WP_SaberImpact

================
*/
void WP_SaberImpact( gentity_t *owner, gentity_t *saber, trace_t *trace )
{
	gentity_t		*other;

	other = &g_entities[trace->entityNum];

	if ( other->takedamage && (other->svFlags&SVF_BBRUSH) )
	{//a breakable brush?  break it!
		vec3_t dir;
		VectorCopy( saber->s.pos.trDelta, dir );
		VectorNormalize( dir );

		int dmg = other->health*2;
		if ( other->health > 50 && dmg > 20 && !(other->svFlags&SVF_GLASS_BRUSH) )
		{
			dmg = 20;
		}
		G_Damage( other, owner, saber, dir, trace->endpos, dmg, 0, MOD_SABER );
		G_PlayEffect( "saber_cut", trace->endpos, dir );
		if ( owner->s.number == 0 )
		{
			AddSoundEvent( owner, trace->endpos, 256, AEL_DISCOVERED );
			AddSightEvent( owner, trace->endpos, 512, AEL_DISCOVERED, 50 );
		}
		return;
	}

	if ( saber->s.pos.trType == TR_LINEAR )
	{
		//hit a wall? send it back
		WP_SaberReturn( saber->owner, saber );
	}

	if ( other && !other->client && (other->contents&CONTENTS_LIGHTSABER) )//&& other->s.weapon == WP_SABER )
	{//2 in-flight sabers collided!
		//Big flash
		//FIXME: bigger effect/sound?
		//FIXME: STILL DOESNT WORK!!!
		G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
		G_PlayEffect( "saber_block", trace->endpos );
		g_saberFlashTime = level.time-50;
		VectorCopy( trace->endpos, g_saberFlashPos );
	}

	if ( owner && owner->s.number == 0 && owner->client )
	{
		//Add the event
		if ( owner->client->ps.saberLength > 0 )
		{//saber is on, very suspicious
			AddSoundEvent( owner, saber->currentOrigin, 128, AEL_DISCOVERED );
			AddSightEvent( owner, saber->currentOrigin, 256, AEL_DISCOVERED, 50 );
		}
		else
		{//saber is off, not as suspicious
			AddSoundEvent( owner, saber->currentOrigin, 128, AEL_SUSPICIOUS );
			AddSightEvent( owner, saber->currentOrigin, 256, AEL_SUSPICIOUS );
		}
	}

	// check for bounce
	if ( !other->takedamage && ( saber->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) )
	{
		// Check to see if there is a bounce count
		if ( saber->bounceCount ) {
			// decrement number of bounces and then see if it should be done bouncing
			if ( --saber->bounceCount <= 0 ) {
				// He (or she) will bounce no more (after this current bounce, that is).
				saber->s.eFlags &= ~(EF_BOUNCE | EF_BOUNCE_HALF);
				if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING )
				{
					WP_SaberDrop( saber->owner, saber );
				}
				return;
			}
			else
			{//bounced and still have bounces left
				if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING )
				{//under telekinetic control
					if ( !gi.inPVS( saber->currentOrigin, owner->client->renderInfo.handRPoint ) )
					{//not in the PVS of my master
						saber->bounceCount -= 25;
					}
				}
			}
		}

		if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING )
		{
			//don't home for a few frames so we can get around this thing
			trace_t	bounceTr;
			vec3_t	end;
			float owner_dist = Distance( owner->client->renderInfo.handRPoint, saber->currentOrigin );

			VectorMA( saber->currentOrigin, 10, trace->plane.normal, end );
			gi.trace( &bounceTr, saber->currentOrigin, saber->mins, saber->maxs, end, owner->s.number, saber->clipmask, G2_NOCOLLIDE, 0 );
			VectorCopy( bounceTr.endpos, saber->currentOrigin );
			if ( owner_dist > 0 )
			{
				if ( owner_dist > 50 )
				{
					owner->client->ps.saberEntityDist = owner_dist-50;
				}
				else
				{
					owner->client->ps.saberEntityDist = 0;
				}
			}
			return;
		}

		G_BounceMissile( saber, trace );

		if ( saber->s.pos.trType == TR_GRAVITY )
		{//bounced
			//play a bounce sound
			G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/bounce%d.wav", Q_irand( 1, 3 ) ) ) );
			//change rotation
			VectorCopy( saber->currentAngles, saber->s.apos.trBase );
			saber->s.apos.trType = TR_LINEAR;
			saber->s.apos.trTime = level.time;
			VectorSet( saber->s.apos.trDelta, Q_irand( -300, 300 ), Q_irand( -300, 300 ), Q_irand( -300, 300 ) );
		}
		//see if we stopped
		else if ( saber->s.pos.trType == TR_STATIONARY )
		{//stopped
			//play a bounce sound
			G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/bounce%d.wav", Q_irand( 1, 3 ) ) ) );
			//stop rotation
			VectorClear( saber->s.apos.trDelta );
			saber->currentAngles[0] = SABER_PITCH_HACK;
			VectorCopy( saber->currentAngles, saber->s.apos.trBase );
			//remember when it fell so it can return automagically
			saber->aimDebounceTime = level.time;
		}
	}
	else if ( other->client && other->health > 0
		&& ( other->client->NPC_class == CLASS_DESANN || other->client->NPC_class == CLASS_TAVION || other->client->NPC_class == CLASS_LUKE || ( other->client->NPC_class == CLASS_GALAKMECH && other->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) ) )
	{//Luke, Desann and Tavion slap thrown sabers aside
		WP_SaberDrop( owner, saber );
		G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
		G_PlayEffect( "saber_block", trace->endpos );
		g_saberFlashTime = level.time-50;
		VectorCopy( trace->endpos, g_saberFlashPos );
		//FIXME: make Luke/Desann/Tavion play an attack anim or some other special anim when this happens
		Jedi_PlayDeflectSound( other );
	}
}

extern float G_PointDistFromLineSegment( const vec3_t start, const vec3_t end, const vec3_t from );
void WP_SaberInFlightReflectCheck( gentity_t *self, usercmd_t *ucmd  )
{
	gentity_t	*ent;
	gentity_t	*entityList[MAX_GENTITIES];
	gentity_t	*missile_list[MAX_GENTITIES];
	int			numListedEntities;
	vec3_t		mins, maxs;
	int			i, e;
	int			ent_count = 0;
	int			radius = 180;
	vec3_t		center, forward;
	vec3_t		tip;
	vec3_t		up = {0,0,1};

	if ( self->NPC && (self->NPC->scriptFlags&SCF_IGNORE_ALERTS) )
	{//don't react to things flying at me...
		return;
	}
	//sanity checks: make sure we actually have a saberent
	if ( self->client->ps.weapon != WP_SABER )
	{
		return;
	}
	if ( !self->client->ps.saberInFlight )
	{
		return;
	}
	if ( !self->client->ps.saberLength )
	{
		return;
	}
	if ( self->client->ps.saberEntityNum == ENTITYNUM_NONE )
	{
		return;
	}
	gentity_t *saberent = &g_entities[self->client->ps.saberEntityNum];
	if ( !saberent )
	{
		return;
	}
	//okay, enough damn sanity checks

	VectorCopy( saberent->currentOrigin, center );

	for ( i = 0 ; i < 3 ; i++ )
	{
		mins[i] = center[i] - radius;
		maxs[i] = center[i] + radius;
	}

	numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );

	//FIXME: check visibility?
	for ( e = 0 ; e < numListedEntities ; e++ )
	{
		ent = entityList[ e ];

		if (ent == self)
			continue;
		if (ent->owner == self)
			continue;
		if ( !(ent->inuse) )
			continue;
		if ( ent->s.eType != ET_MISSILE )
		{
			if ( ent->client || ent->s.weapon != WP_SABER )
			{//FIXME: wake up bad guys?
				continue;
			}
			if ( ent->s.eFlags & EF_NODRAW )
			{
				continue;
			}
			if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
			{//not a lightsaber
				continue;
			}
		}
		else
		{//FIXME: make exploding missiles explode?
			if ( ent->s.pos.trType == TR_STATIONARY )
			{//nothing you can do with a stationary missile
				continue;
			}
			if ( ent->splashDamage || ent->splashRadius )
			{//can't deflect exploding missiles
				if ( DistanceSquared( ent->currentOrigin, center ) < 256 )//16 squared
				{
					G_MissileImpacted( ent, saberent, ent->currentOrigin, up );
				}
				continue;
			}
		}

		//don't deflect it if it's not within 16 units of the blade
		VectorMA( self->client->renderInfo.muzzlePoint, self->client->ps.saberLength, self->client->renderInfo.muzzleDir, tip );

		if( G_PointDistFromLineSegment( self->client->renderInfo.muzzlePoint, tip, ent->currentOrigin ) > 32 )
		{
			continue;
		}
		// ok, we are within the radius, add us to the incoming list
		missile_list[ent_count] = ent;
		ent_count++;

	}

	if ( ent_count )
	{
		vec3_t	fx_dir;
		// we are done, do we have any to deflect?
		if ( ent_count )
		{
			for ( int x = 0; x < ent_count; x++ )
			{
				if ( missile_list[x]->s.weapon == WP_SABER )
				{//just send it back
					if ( missile_list[x]->owner && missile_list[x]->owner->client && missile_list[x]->owner->client->ps.saberActive && missile_list[x]->s.pos.trType == TR_LINEAR && missile_list[x]->owner->client->ps.saberEntityState != SES_RETURNING )
					{//it's on and being controlled
						//FIXME: prevent it from damaging me?
						WP_SaberReturn( missile_list[x]->owner, missile_list[x] );
						VectorNormalize2( missile_list[x]->s.pos.trDelta, fx_dir );
						G_PlayEffect( "saber_block", missile_list[x]->currentOrigin, fx_dir );
						if ( missile_list[x]->owner->client->ps.saberInFlight && self->client->ps.saberInFlight )
						{
							G_Sound( missile_list[x], G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
							g_saberFlashTime = level.time-50;
							gentity_t *saber = &g_entities[self->client->ps.saberEntityNum];
							vec3_t	org;
							VectorSubtract( missile_list[x]->currentOrigin, saber->currentOrigin, org );
							VectorMA( saber->currentOrigin, 0.5, org, org );
							VectorCopy( org, g_saberFlashPos );
						}
					}
				}
				else
				{//bounce it
					if ( self->client && !self->s.number )
					{
						self->client->sess.missionStats.saberBlocksCnt++;
					}

					G_ReflectMissile( self, missile_list[x], forward );
					//do an effect
					VectorNormalize2( missile_list[x]->s.pos.trDelta, fx_dir );
					G_PlayEffect( "blaster/deflect", missile_list[x]->currentOrigin, fx_dir );
				}
			}
		}
	}
}

qboolean WP_SaberValidateEnemy( gentity_t *self, gentity_t *enemy )
{
	if ( !enemy )
	{
		return qfalse;
	}

	if ( !enemy || enemy == self || !enemy->inuse || !enemy->client )
	{//not valid
		return qfalse;
	}

	if ( enemy->health <= 0 )
	{//corpse
		return qfalse;
	}

	if ( DistanceSquared( self->client->renderInfo.handRPoint, enemy->currentOrigin ) > saberThrowDistSquared[self->client->ps.forcePowerLevel[FP_SABERTHROW]] )
	{//too far
		return qfalse;
	}

	if ( (!InFront( enemy->currentOrigin, self->currentOrigin, self->client->ps.viewangles, 0.0f) || !G_ClearLOS( self, self->client->renderInfo.eyePoint, enemy ) )
		&& ( DistanceHorizontalSquared( enemy->currentOrigin, self->currentOrigin ) > 65536 || fabs(enemy->currentOrigin[2]-self->currentOrigin[2]) > 384 )  )
	{//(not in front or not clear LOS) & greater than 256 away
		return qfalse;
	}

	if ( enemy->client->playerTeam == self->client->playerTeam )
	{//on same team
		return qfalse;
	}

	//LOS?

	return qtrue;
}

float WP_SaberRateEnemy( gentity_t *enemy, vec3_t center, vec3_t forward, float radius )
{
	float rating;
	vec3_t	dir;

	VectorSubtract( enemy->currentOrigin, center, dir );
	rating = (1.0f-(VectorNormalize( dir )/radius));
	rating *= DotProduct( forward, dir );
	return rating;
}

gentity_t *WP_SaberFindEnemy( gentity_t *self, gentity_t *saber )
{
//FIXME: should be a more intelligent way of doing this, like auto aim?
//closest, most in front... did damage to... took damage from?  How do we know who the player is focusing on?
	gentity_t	*ent, *bestEnt = NULL;
	gentity_t	*entityList[MAX_GENTITIES];
	int			numListedEntities;
	vec3_t		center, mins, maxs, fwdangles, forward;
	int			i, e;
	float		radius = 400;
	float		rating, bestRating = 0.0f;

	//FIXME: no need to do this in 1st person?
	fwdangles[1] = self->client->ps.viewangles[1];
	AngleVectors( fwdangles, forward, NULL, NULL );

	VectorCopy( saber->currentOrigin, center );

	for ( i = 0 ; i < 3 ; i++ )
	{
		mins[i] = center[i] - radius;
		maxs[i] = center[i] + radius;
	}

	if ( WP_SaberValidateEnemy( self, self->enemy ) )
	{
		bestEnt = self->enemy;
		bestRating = WP_SaberRateEnemy( bestEnt, center, forward, radius );
	}

	numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );

	if ( !numListedEntities )
	{//should we clear the enemy?
		return bestEnt;
	}

	for ( e = 0 ; e < numListedEntities ; e++ )
	{
		ent = entityList[ e ];

		if ( ent == self || ent == saber || ent == bestEnt )
		{
			continue;
		}
		if ( !WP_SaberValidateEnemy( self, ent ) )
		{//doesn't meet criteria of valid look enemy (don't check current since we would have done that before this func's call
			continue;
		}
		if ( !gi.inPVS( self->currentOrigin, ent->currentOrigin ) )
		{//not even potentially visible
			continue;
		}
		if ( !G_ClearLOS( self, self->client->renderInfo.eyePoint, ent ) )
		{//can't see him
			continue;
		}
		//rate him based on how close & how in front he is
		rating = WP_SaberRateEnemy( ent, center, forward, radius );
		if ( rating > bestRating )
		{
			bestEnt = ent;
			bestRating = rating;
		}
	}
	return bestEnt;
}

void WP_RunSaber( gentity_t *self, gentity_t *saber )
{
	vec3_t		origin, oldOrg;
	trace_t		tr;

	VectorCopy( saber->currentOrigin, oldOrg );
	// get current position
	EvaluateTrajectory( &saber->s.pos, level.time, origin );
	// get current angles
	EvaluateTrajectory( &saber->s.apos, level.time, saber->currentAngles );

	// trace a line from the previous position to the current position,
	// ignoring interactions with the missile owner
	int clipmask = saber->clipmask;
	if ( !self || !self->client || self->client->ps.saberLength <= 0 )
	{//don't keep hitting other sabers when turned off
		clipmask &= ~CONTENTS_LIGHTSABER;
	}
	gi.trace( &tr, saber->currentOrigin, saber->mins, saber->maxs, origin,
		saber->owner ? saber->owner->s.number : ENTITYNUM_NONE, clipmask, G2_NOCOLLIDE, 0 );

	VectorCopy( tr.endpos, saber->currentOrigin );

	if ( self->client->ps.saberActive )
	{
		if ( self->client->ps.saberInFlight || (self->client->ps.weaponTime&&!Q_irand( 0, 100 )) )
		{//make enemies run from a lit saber in flight or from me when I'm attacking
			if ( !Q_irand( 0, 10 ) )
			{//not so often...
				AddSightEvent( self, saber->currentOrigin, self->client->ps.saberLength*3, AEL_DANGER, 100 );
			}
		}
	}

	if ( tr.startsolid )
	{
		tr.fraction = 0;
	}

	gi.linkentity( saber );

	//touch push triggers?

	if ( tr.fraction != 1 )
	{
		WP_SaberImpact( self, saber, &tr );
	}

	if ( saber->s.pos.trType == TR_LINEAR )
	{//home
		//figure out where saber should be
		vec3_t	forward, saberHome, saberDest, fwdangles = {0};

		VectorCopy( self->client->ps.viewangles, fwdangles );
		if ( self->s.number )
		{
			fwdangles[0] -= 8;
		}
		else if ( cg.renderingThirdPerson )
		{
			fwdangles[0] -= 5;
		}

		if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_1
			|| self->client->ps.saberEntityState == SES_RETURNING
			|| VectorCompare( saber->s.pos.trDelta, vec3_origin ) )
		{//control if it's returning or just starting
			float	saberSpeed = 500;//FIXME: based on force level?
			float	dist;
			gentity_t *enemy = NULL;

			AngleVectors( fwdangles, forward, NULL, NULL );

			if ( self->client->ps.saberEntityDist < 100 )
			{//make the saber head to my hand- the bolt it was attached to
				VectorCopy( self->client->renderInfo.handRPoint, saberHome );
			}
			else
			{//aim saber from eyes
				VectorCopy( self->client->renderInfo.eyePoint, saberHome );
			}
			VectorMA( saberHome, self->client->ps.saberEntityDist, forward, saberDest );

			if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 && self->client->ps.saberEntityState == SES_LEAVING )
			{//max level
				//pick an enemy
				enemy = WP_SaberFindEnemy( self, saber );
				if ( enemy )
				{//home in on enemy
					float enemyDist = Distance( self->client->renderInfo.handRPoint, enemy->currentOrigin );
					VectorCopy( enemy->currentOrigin, saberDest );
					saberDest[2] += enemy->maxs[2]/2.0f;//FIXME: when in a knockdown anim, the saber float above them... do we care?
					self->client->ps.saberEntityDist = enemyDist;
				}
			}


			//Make the saber head there
			VectorSubtract( saberDest, saber->currentOrigin, saber->s.pos.trDelta );
			dist = VectorNormalize( saber->s.pos.trDelta );
			if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 && self->client->ps.saberEntityState == SES_LEAVING && !enemy )
			{
				if ( dist < 200 )
				{
					saberSpeed = 400 - (dist*2);
				}
			}
			else if ( self->client->ps.saberEntityState == SES_LEAVING && dist < 50 )
			{
				saberSpeed = dist * 2 + 30;
				if ( (enemy && dist > enemy->maxs[0]) || (!enemy && dist > 24) )
				{//auto-tracking an enemy and we can't hit him
					if ( saberSpeed < 120 )
					{//clamp to a minimum speed
						saberSpeed = 120;
					}
				}
			}
			/*
			if ( self->client->ps.saberEntityState == SES_RETURNING )
			{//FIXME: if returning, move faster?
				saberSpeed = 800;
				if ( dist < 200 )
				{
					saberSpeed -= 400 - (dist*2);
				}
			}
			*/
			VectorScale( saber->s.pos.trDelta, saberSpeed, saber->s.pos.trDelta );
			//SnapVector( saber->s.pos.trDelta );	// save net bandwidth
			VectorCopy( saber->currentOrigin, saber->s.pos.trBase );
			saber->s.pos.trTime = level.time;
			saber->s.pos.trType = TR_LINEAR;
		}
		else
		{
			VectorCopy( saber->currentOrigin, saber->s.pos.trBase );
			saber->s.pos.trTime = level.time;
			saber->s.pos.trType = TR_LINEAR;
		}

		//if it's heading back, point it's base at us
		if ( self->client->ps.saberEntityState == SES_RETURNING )
		{
			fwdangles[0] += SABER_PITCH_HACK;
			VectorCopy( fwdangles, saber->s.apos.trBase );
			saber->s.apos.trTime = level.time;
			saber->s.apos.trType = TR_INTERPOLATE;
			VectorClear( saber->s.apos.trDelta );
		}
	}
}


qboolean WP_SaberLaunch( gentity_t *self, gentity_t *saber, qboolean thrown )
{//FIXME: probably need a debounce time
	vec3_t	saberMins={-3.0f,-3.0f,-3.0f};
	vec3_t	saberMaxs={3.0f,3.0f,3.0f};
	trace_t	trace;

	if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
	{
		if ( !WP_ForcePowerUsable( self, FP_SABERTHROW, 20 ) )
		{
			return qfalse;
		}
	}
	else
	{
		if ( !WP_ForcePowerUsable( self, FP_SABERTHROW, 0 ) )
		{
			return qfalse;
		}
	}
	if ( !self->s.number && (cg.zoomMode || in_camera) )
	{//can't saber throw when zoomed in or in cinematic
		return qfalse;
	}
	//make sure it won't start in solid
	gi.trace( &trace, self->client->renderInfo.handRPoint, saberMins, saberMaxs, self->client->renderInfo.handRPoint, saber->s.number, MASK_SOLID, G2_NOCOLLIDE, 0 );
	if ( trace.startsolid || trace.allsolid )
	{
		return qfalse;
	}
	//make sure I'm not throwing it on the other side of a door or wall or whatever
	gi.trace( &trace, self->currentOrigin, vec3_origin, vec3_origin, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID, G2_NOCOLLIDE, 0 );
	if ( trace.startsolid || trace.allsolid || trace.fraction < 1.0f )
	{
		return qfalse;
	}

	if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
	{//at max skill, the cost increases as keep it out
		WP_ForcePowerStart( self, FP_SABERTHROW, 10 );
	}
	else
	{
		WP_ForcePowerStart( self, FP_SABERTHROW, 0 );
	}
	//draw it
	saber->s.eFlags &= ~EF_NODRAW;
	saber->svFlags |= SVF_BROADCAST;
	saber->svFlags &= ~SVF_NOCLIENT;

	//place it
	VectorCopy( self->client->renderInfo.handRPoint, saber->currentOrigin );//muzzlePoint
	VectorCopy( saber->currentOrigin, saber->s.pos.trBase );
	saber->s.pos.trTime = level.time;
	saber->s.pos.trType = TR_LINEAR;
	VectorClear( saber->s.pos.trDelta );
	gi.linkentity( saber );

	//spin it
	VectorClear( saber->s.apos.trBase );
	saber->s.apos.trTime = level.time;
	saber->s.apos.trType = TR_LINEAR;
	if ( self->health > 0 && thrown )
	{//throwing it
		saber->s.apos.trBase[1] = self->client->ps.viewangles[1];
		saber->s.apos.trBase[0] = SABER_PITCH_HACK;
	}
	else
	{//dropping it
		vectoangles( self->client->renderInfo.muzzleDir, saber->s.apos.trBase );
	}
	VectorClear( saber->s.apos.trDelta );

	switch ( self->client->ps.forcePowerLevel[FP_SABERTHROW] )
	{//FIXME: make a table?
	default:
	case FORCE_LEVEL_1:
		saber->s.apos.trDelta[1] = 600;
		break;
	case FORCE_LEVEL_2:
		saber->s.apos.trDelta[1] = 800;
		break;
	case FORCE_LEVEL_3:
		saber->s.apos.trDelta[1] = 1200;
		break;
	}

	//Take it out of my hand
	self->client->ps.saberInFlight = qtrue;
	self->client->ps.saberEntityState = SES_LEAVING;
	self->client->ps.saberEntityDist = saberThrowDist[self->client->ps.forcePowerLevel[FP_SABERTHROW]];
	self->client->ps.saberThrowTime = level.time;
	//if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
	{
		self->client->ps.forcePowerDebounce[FP_SABERTHROW] = level.time + 1000;//so we can keep it out for a minimum amount of time
	}

	//if it's not active, turn it on
	self->client->ps.saberActive = qtrue;

	//turn on the saber trail
	self->client->saberTrail.inAction = qtrue;
	self->client->saberTrail.duration = 150;

	//reset the mins
	VectorCopy( saberMins, saber->mins );
	VectorCopy( saberMaxs, saber->maxs );
	saber->contents = 0;//CONTENTS_LIGHTSABER;
	saber->clipmask = MASK_SOLID | CONTENTS_LIGHTSABER;

	// remove the ghoul2 sabre model on the player
	if ( self->weaponModel >= 0 )
	{
		gi.G2API_RemoveGhoul2Model(self->ghoul2, self->weaponModel);
		self->weaponModel = -1;
	}

	return qtrue;
}

qboolean WP_SaberLose( gentity_t *self, vec3_t throwDir )
{
	if ( !self || !self->client || self->client->ps.saberEntityNum <= 0 )
	{//WTF?!!  We lost it already?
		return qfalse;
	}
	gentity_t *dropped = &g_entities[self->client->ps.saberEntityNum];
	if ( !self->client->ps.saberInFlight )
	{//not alreay in air
		//make it so we can throw it
		self->client->ps.forcePowersKnown |= (1<<FP_SABERTHROW);
		self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_1;
		//throw it
		if ( !WP_SaberLaunch( self, dropped, qfalse ) )
		{//couldn't throw it
			return qfalse;
		}
	}
	if ( self->client->ps.saberActive )
	{//on
		//drop it instantly
		WP_SaberDrop( self, dropped );
	}
	//optionally give it some thrown velocity
	if ( throwDir && !VectorCompare( throwDir, vec3_origin ) )
	{
		VectorCopy( throwDir, dropped->s.pos.trDelta );
	}
	//don't pull it back on the next frame
	if ( self->NPC )
	{
		self->NPC->last_ucmd.buttons &= ~BUTTON_ATTACK;
	}
	return qtrue;
}

void WP_SaberCatch( gentity_t *self, gentity_t *saber, qboolean switchToSaber )
{//FIXME: probably need a debounce time
	if ( self->health > 0 && !PM_SaberInBrokenParry( self->client->ps.saberMove ) && self->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
	{
		//don't draw it
		saber->s.eFlags |= EF_NODRAW;
		saber->svFlags &= SVF_BROADCAST;
		saber->svFlags |= SVF_NOCLIENT;

		//take off any gravity stuff if we'd dropped it
		saber->s.pos.trType = TR_LINEAR;
		saber->s.eFlags &= ~EF_BOUNCE_HALF;

		//Put it in my hand
		self->client->ps.saberInFlight = qfalse;
		self->client->ps.saberEntityState = SES_LEAVING;

		//turn off the saber trail
		self->client->saberTrail.inAction = qfalse;
		self->client->saberTrail.duration = 75;

		//reset its contents/clipmask
		saber->contents = CONTENTS_LIGHTSABER;// | CONTENTS_SHOTCLIP;
		saber->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;

		//play catch sound
		G_Sound( saber, G_SoundIndex( "sound/weapons/saber/saber_catch.wav" ) );
		//FIXME: if an NPC, don't turn it back on if no enemy or enemy is dead...
		//if it's not our current weapon, make it our current weapon
		if ( self->client->ps.weapon == WP_SABER )
		{
			G_CreateG2AttachedWeaponModel( self, self->client->ps.saberModel );
		}
		if ( switchToSaber )
		{
			if ( self->client->ps.weapon != WP_SABER )
			{
				CG_ChangeWeapon( WP_SABER );
			}
			else
			{//if it's not active, turn it on
				self->client->ps.saberActive = qtrue;
			}
		}
	}
}


void WP_SaberReturn( gentity_t *self, gentity_t *saber )
{
	if ( PM_SaberInBrokenParry( self->client->ps.saberMove ) || self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
	{
		return;
	}
	if ( self && self->client )
	{//still alive and stuff
		//FIXME: when it's returning, flies butt first, but seems to do a lot of damage when going through people... hmm...
		self->client->ps.saberEntityState = SES_RETURNING;
		//turn down the saber trail
		self->client->saberTrail.inAction = qfalse;
		self->client->saberTrail.duration = 75;
	}
	if ( !(saber->s.eFlags&EF_BOUNCE) )
	{
		saber->s.eFlags |= EF_BOUNCE;
		saber->bounceCount = 300;
	}
}


void WP_SaberDrop( gentity_t *self, gentity_t *saber )
{
	saber->s.eFlags &= ~EF_BOUNCE;
	saber->bounceCount = 0;
	//make it fall
	saber->s.pos.trType = TR_GRAVITY;
	//make it bounce some
	saber->s.eFlags |= EF_BOUNCE_HALF;
	//make it spin
	VectorCopy( saber->currentAngles, saber->s.apos.trBase );
	saber->s.apos.trType = TR_LINEAR;
	saber->s.apos.trTime = level.time;
	VectorSet( saber->s.apos.trDelta, Q_irand( -300, 300 ), saber->s.apos.trDelta[1], Q_irand( -300, 300 ) );
	if ( !saber->s.apos.trDelta[1] )
	{
		saber->s.apos.trDelta[1] = Q_irand( -300, 300 );
	}
	//force it to be ready to return
	self->client->ps.saberEntityDist = 0;
	self->client->ps.saberEntityState = SES_RETURNING;
	//turn it off
	self->client->ps.saberActive = qfalse;
	//turn off the saber trail
	self->client->saberTrail.inAction = qfalse;
	self->client->saberTrail.duration = 75;
	//play the saber turning off sound
	if ( self->client->playerTeam == TEAM_PLAYER )
	{
		G_SoundOnEnt( saber, CHAN_AUTO, "sound/weapons/saber/saberoff.wav" );
	}
	else
	{
		G_SoundOnEnt( saber, CHAN_AUTO, "sound/weapons/saber/enemy_saber_off.wav" );
	}

	if ( self->health <= 0 )
	{//owner is dead!
		saber->s.time = level.time;//will make us free ourselves after a time
	}
}


void WP_SaberPull( gentity_t *self, gentity_t *saber )
{
	if ( PM_SaberInBrokenParry( self->client->ps.saberMove ) || self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
	{
		return;
	}
	if ( self->health > 0 )
	{
		//take off gravity
		saber->s.pos.trType = TR_LINEAR;
		//take off bounce
		saber->s.eFlags &= EF_BOUNCE_HALF;
		//play sound
		G_Sound( self, G_SoundIndex( "sound/weapons/force/pull.wav" ) );
	}
}


// Check if we are throwing it, launch it if needed, update position if needed.
void WP_SaberThrow( gentity_t *self, usercmd_t *ucmd )
{
	vec3_t			saberDiff;
	trace_t			tr;
	//static float	SABER_SPEED = 10;

	gentity_t *saberent;

	if ( self->client->ps.saberEntityNum <= 0 || self->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
	{//WTF?!!  We lost it?
		return;
	}

	saberent = &g_entities[self->client->ps.saberEntityNum];

	VectorSubtract( self->client->renderInfo.handRPoint, saberent->currentOrigin, saberDiff );

	//is our saber in flight?
	if ( !self->client->ps.saberInFlight )
	{//saber is not in flight right now
		if ( self->client->ps.weapon != WP_SABER )
		{//don't even have it out
			return;
		}
		else if ( ucmd->buttons & BUTTON_ALT_ATTACK && !(self->client->ps.pm_flags&PMF_ALT_ATTACK_HELD) )
		{//still holding it, not still holding attack from a previous throw, so throw it.
			if ( !(self->client->ps.saberEventFlags&SEF_INWATER) && WP_SaberLaunch( self, saberent, qtrue ) )
			{
				if ( self->client && !self->s.number )
				{
					self->client->sess.missionStats.saberThrownCnt++;
				}
				//need to recalc this because we just moved it
				VectorSubtract( self->client->renderInfo.handRPoint, saberent->currentOrigin, saberDiff );
			}
			else
			{//couldn't throw it
				return;
			}
		}
		else
		{//holding it, don't want to throw it, go away.
			return;
		}
	}
	else
	{//inflight
		//is our saber currently on it's way back to us?
		if ( self->client->ps.saberEntityState == SES_RETURNING )
		{//see if we're close enough to pick it up
			if ( VectorLengthSquared( saberDiff ) <= 256 )//16 squared//G_BoundsOverlap( self->absmin, self->absmax, saberent->absmin, saberent->absmax ) )//
			{//caught it
				vec3_t	axisPoint;
				trace_t	trace;
				VectorCopy( self->currentOrigin, axisPoint );
				axisPoint[2] = self->client->renderInfo.handRPoint[2];
				gi.trace( &trace, axisPoint, vec3_origin, vec3_origin, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID, G2_NOCOLLIDE, 0 );
				if ( !trace.startsolid && trace.fraction >= 1.0f )
				{//our hand isn't through a wall
					WP_SaberCatch( self, saberent, qtrue );
					NPC_SetAnim( self, SETANIM_TORSO, TORSO_HANDRETRACT1, SETANIM_FLAG_OVERRIDE );
				}
				return;
			}
		}

		if ( saberent->s.pos.trType != TR_STATIONARY )
		{//saber is in flight, lerp it
			WP_RunSaber( self, saberent );
		}
		else
		{//it fell on the ground
			if ( self->health <= 0 && level.time > saberent->s.time + 5000 )
			{//make us free ourselves after a time
				G_FreeEntity( saberent );
				self->client->ps.saberEntityNum = ENTITYNUM_NONE;
				return;
			}
			if ( (!self->s.number && level.time - saberent->aimDebounceTime > 15000)
				|| (self->s.number && level.time - saberent->aimDebounceTime > 5000) )
			{//(only for player) been missing for 15 seconds, automagicially return
				WP_SaberCatch( self, saberent, qfalse );
				return;
			}
		}
	}

	//are we still trying to use the saber?
	if ( self->client->ps.weapon != WP_SABER )
	{//switched away
		if ( !self->client->ps.saberInFlight )
		{//wasn't throwing saber
			return;
		}
		else if ( saberent->s.pos.trType == TR_LINEAR )
		{//switched away while controlling it, just drop the saber
			WP_SaberDrop( self, saberent );
			return;
		}
		else
		{//it's on the ground, see if it's inside us (touching)
			if ( G_PointInBounds( saberent->currentOrigin, self->absmin, self->absmax ) )
			{//it's in us, pick it up automatically
				WP_SaberPull( self, saberent );
			}
		}
	}
	else if ( saberent->s.pos.trType != TR_LINEAR )
	{//weapon is saber and not flying
		if ( self->client->ps.saberInFlight )
		{//we dropped it
			if ( ucmd->buttons & BUTTON_ATTACK )//|| self->client->ps.weaponstate == WEAPON_RAISING )//ucmd->buttons & BUTTON_ALT_ATTACK ||
			{//we actively want to pick it up or we just switched to it, so pull it back
				gi.trace( &tr, saberent->currentOrigin, saberent->mins, saberent->maxs, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID, G2_NOCOLLIDE, 0 );

				if ( tr.allsolid || tr.startsolid || tr.fraction < 1.0f )
				{//can't pick it up yet, no LOS
					return;
				}
				//clear LOS, pick it up
				WP_SaberPull( self, saberent );
			}
			else
			{//see if it's inside us (touching)
				if ( G_PointInBounds( saberent->currentOrigin, self->absmin, self->absmax ) )
				{//it's in us, pick it up automatically
					WP_SaberPull( self, saberent );
				}
			}
		}
	}
	else if ( self->health <= 0 && self->client->ps.saberInFlight )
	{//we died, drop it
		WP_SaberDrop( self, saberent );
		return;
	}
	else if ( !self->client->ps.saberActive && self->client->ps.saberEntityState != SES_RETURNING )
	{//we turned it off, drop it
		WP_SaberDrop( self, saberent );
		return;
	}

	//TODO: if deactivate saber in flight, should it drop?

	if ( saberent->s.pos.trType != TR_LINEAR )
	{//don't home
		return;
	}

	float saberDist = VectorLength( saberDiff );
	if ( self->client->ps.saberEntityState == SES_LEAVING )
	{//saber still flying forward
		if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
		{//still holding it out
			if ( !(ucmd->buttons&BUTTON_ALT_ATTACK) && self->client->ps.forcePowerDebounce[FP_SABERTHROW] < level.time )
			{//done throwing, return to me
				if ( self->client->ps.saberActive )
				{//still on
					WP_SaberReturn( self, saberent );
				}
			}
			else if ( level.time - self->client->ps.saberThrowTime >= 100 )
			{
				if ( WP_ForcePowerAvailable( self, FP_SABERTHROW, 1 ) )
				{
					WP_ForcePowerDrain( self, FP_SABERTHROW, 1 );
					self->client->ps.saberThrowTime = level.time;
				}
				else
				{//out of force power, return to me
					WP_SaberReturn( self, saberent );
				}
			}
		}
		else
		{
			if ( !(ucmd->buttons&BUTTON_ALT_ATTACK) && self->client->ps.forcePowerDebounce[FP_SABERTHROW] < level.time )
			{//not holding button and has been out at least 1 second, return to me
				if ( self->client->ps.saberActive )
				{//still on
					WP_SaberReturn( self, saberent );
				}
			}
			else if ( level.time - self->client->ps.saberThrowTime > 3000
				|| (self->client->ps.forcePowerLevel[FP_SABERTHROW]==FORCE_LEVEL_1&&saberDist>=self->client->ps.saberEntityDist) )
			{//been out too long, or saber throw 1 went too far, return to me
				if ( self->client->ps.saberActive )
				{//still on
					WP_SaberReturn( self, saberent );
				}
			}
		}
	}
	if ( self->client->ps.saberEntityState == SES_RETURNING )
	{
		if ( self->client->ps.saberEntityDist > 0 )
		{
			self->client->ps.saberEntityDist -= 25;
		}
		if ( self->client->ps.saberEntityDist < 0 )
		{
			self->client->ps.saberEntityDist = 0;
		}
		else if ( saberDist < self->client->ps.saberEntityDist )
		{//if it's coming back to me, never push it away
			self->client->ps.saberEntityDist = saberDist;
		}
	}
}


//SABER BLOCKING============================================================================
//SABER BLOCKING============================================================================
//SABER BLOCKING============================================================================
//SABER BLOCKING============================================================================
//SABER BLOCKING============================================================================
int WP_MissileBlockForBlock( int saberBlock )
{
	switch( saberBlock )
	{
	case BLOCKED_UPPER_RIGHT:
		return BLOCKED_UPPER_RIGHT_PROJ;
		break;
	case BLOCKED_UPPER_LEFT:
		return BLOCKED_UPPER_LEFT_PROJ;
		break;
	case BLOCKED_LOWER_RIGHT:
		return BLOCKED_LOWER_RIGHT_PROJ;
		break;
	case BLOCKED_LOWER_LEFT:
		return BLOCKED_LOWER_LEFT_PROJ;
		break;
	case BLOCKED_TOP:
		return BLOCKED_TOP_PROJ;
		break;
	}
	return saberBlock;
}

void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock )
{
	vec3_t diff, fwdangles={0,0,0}, right;
	float rightdot;
	float zdiff;

	if ( self->client->ps.weaponstate == WEAPON_DROPPING ||
		self->client->ps.weaponstate == WEAPON_RAISING )
	{//don't block while changing weapons
		return;
	}
	//NPCs don't auto-block
	if ( !missileBlock && self->s.number != 0 && self->client->ps.saberBlocked != BLOCKED_NONE )
	{
		return;
	}

	VectorSubtract( hitloc, self->client->renderInfo.eyePoint, diff );
	diff[2] = 0;
	VectorNormalize( diff );

	fwdangles[1] = self->client->ps.viewangles[1];
	// Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
	AngleVectors( fwdangles, NULL, right, NULL );

	rightdot = DotProduct(right, diff);
	zdiff = hitloc[2] - self->client->renderInfo.eyePoint[2];

	//FIXME: take torsoAngles into account?
	if ( zdiff > -5 )//0 )//40 )
	{
		if ( rightdot > 0.3 )
		{
			self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
		}
		else if ( rightdot < -0.3 )
		{
			self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
		}
		else
		{
			self->client->ps.saberBlocked = BLOCKED_TOP;
		}
	}
	else if ( zdiff > -22 )//-20 )//20 )
	{
		if ( zdiff < -10 )//30 )
		{//hmm, pretty low, but not low enough to use the low block, so we need to duck
			//NPC should duck, but NPC should never get here
		}
		if ( rightdot > 0.1 )
		{
			self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
		}
		else if ( rightdot < -0.1 )
		{
			self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
		}
		else
		{//FIXME: this looks really weird if the shot is too low!
			self->client->ps.saberBlocked = BLOCKED_TOP;
		}
	}
	else
	{
		if ( rightdot >= 0 )
		{
			self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
		}
		else
		{
			self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
		}
	}

#ifndef FINAL_BUILD
	if ( d_saberCombat->integer )
	{
		if ( !self->s.number )
		{
			gi.Printf( "EyeZ: %4.2f  HitZ: %4.2f  zdiff: %4.2f  rdot: %4.2f\n", self->client->renderInfo.eyePoint[2], hitloc[2], zdiff, rightdot );
			switch ( self->client->ps.saberBlocked )
			{
			case BLOCKED_TOP:
				gi.Printf( "BLOCKED_TOP\n" );
				break;
			case BLOCKED_UPPER_RIGHT:
				gi.Printf( "BLOCKED_UPPER_RIGHT\n" );
				break;
			case BLOCKED_UPPER_LEFT:
				gi.Printf( "BLOCKED_UPPER_LEFT\n" );
				break;
			case BLOCKED_LOWER_RIGHT:
				gi.Printf( "BLOCKED_LOWER_RIGHT\n" );
				break;
			case BLOCKED_LOWER_LEFT:
				gi.Printf( "BLOCKED_LOWER_LEFT\n" );
				break;
			default:
				break;
			}
		}
	}
#endif

	if ( missileBlock )
	{
		self->client->ps.saberBlocked = WP_MissileBlockForBlock( self->client->ps.saberBlocked );
	}

	if ( self->client->ps.saberBlocked != BLOCKED_NONE )
	{
		int parryReCalcTime = Jedi_ReCalcParryTime( self, EVASION_PARRY );
		if ( self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] < level.time + parryReCalcTime )
		{
			self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + parryReCalcTime;
		}
	}
}

void WP_SaberBlock( gentity_t *saber, vec3_t hitloc, qboolean missileBlock )
{
	gentity_t *playerent;
	vec3_t diff, fwdangles={0,0,0}, right;
	float rightdot;
	float zdiff;

	if (saber && saber->owner)
	{
		playerent = saber->owner;
		if (!playerent->client)
		{
			return;
		}
		if ( playerent->client->ps.weaponstate == WEAPON_DROPPING ||
			playerent->client->ps.weaponstate == WEAPON_RAISING )
		{//don't block while changing weapons
			return;
		}
	}
	else
	{	// Bad entity passed.
		return;
	}

	//temporarily disabling auto-blocking for NPCs...
	if ( !missileBlock && playerent->s.number != 0 && playerent->client->ps.saberBlocked != BLOCKED_NONE )
	{
		return;
	}

	VectorSubtract(hitloc, playerent->currentOrigin, diff);
	VectorNormalize(diff);

	fwdangles[1] = playerent->client->ps.viewangles[1];
	// Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
	AngleVectors( fwdangles, NULL, right, NULL );

	rightdot = DotProduct(right, diff) + Q_flrand(-0.2f,0.2f);
	zdiff = hitloc[2] - playerent->currentOrigin[2] + Q_irand(-8,8);

	// Figure out what quadrant the block was in.
	if (zdiff > 24)
	{	// Attack from above
		if (Q_irand(0,1))
		{
			playerent->client->ps.saberBlocked = BLOCKED_TOP;
		}
		else
		{
			playerent->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
		}
	}
	else if (zdiff > 13)
	{	// The upper half has three viable blocks...
		if (rightdot > 0.25)
		{	// In the right quadrant...
			if (Q_irand(0,1))
			{
				playerent->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
			}
			else
			{
				playerent->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
			}
		}
		else
		{
			switch(Q_irand(0,3))
			{
			case 0:
				playerent->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
				break;
			case 1:
			case 2:
				playerent->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
				break;
			case 3:
				playerent->client->ps.saberBlocked = BLOCKED_TOP;
				break;
			}
		}
	}
	else
	{	// The lower half is a bit iffy as far as block coverage.  Pick one of the "low" ones at random.
		if (Q_irand(0,1))
		{
			playerent->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
		}
		else
		{
			playerent->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
		}
	}

	if ( missileBlock )
	{
		playerent->client->ps.saberBlocked = WP_MissileBlockForBlock( playerent->client->ps.saberBlocked );
	}
}

void WP_SaberStartMissileBlockCheck( gentity_t *self, usercmd_t *ucmd  )
{
	float		dist;
	gentity_t	*ent, *incoming = NULL;
	gentity_t	*entityList[MAX_GENTITIES];
	int			numListedEntities;
	vec3_t		mins, maxs;
	int			i, e;
	float		closestDist, radius = 256;
	vec3_t		forward, dir, missile_dir, fwdangles = {0};
	trace_t		trace;
	vec3_t		traceTo, entDir;


	if ( self->client->ps.weapon != WP_SABER )
	{
		return;
	}

	if ( self->client->ps.saberInFlight )
	{
		return;
	}

	if ( self->client->ps.forcePowersActive&(1<<FP_LIGHTNING) )
	{//can't block while zapping
		return;
	}

	if ( self->client->ps.forcePowersActive&(1<<FP_PUSH) )
	{//can't block while shoving
		return;
	}

	if ( self->client->ps.forcePowersActive&(1<<FP_GRIP) )
	{//can't block while gripping (FIXME: or should it break the grip?  Pain should break the grip, I think...)
		return;
	}

	if ( self->health <= 0 )
	{//dead don't try to block (NOTE: actual deflection happens in missile code)
		return;
	}

	if ( PM_InKnockDown( &self->client->ps ) )
	{//can't block when knocked down
		return;
	}

	if ( !self->client->ps.saberLength )
	{
		if ( self->s.number == 0 )
		{//player doesn't auto-activate
			return;
		}
	}

	if ( !self->s.number )
	{//don't do this if already attacking!
		if ( ucmd->buttons & BUTTON_ATTACK
			|| PM_SaberInAttack( self->client->ps.saberMove )
			|| PM_SaberInSpecialAttack( self->client->ps.torsoAnim )
			|| PM_SaberInTransitionAny( self->client->ps.saberMove ))
		{
			return;
		}
	}

	if ( self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] > level.time )
	{//can't block while gripping (FIXME: or should it break the grip?  Pain should break the grip, I think...)
		return;
	}

	if ( !self->s.number && !g_saberAutoBlocking->integer && self->client->ps.saberBlockingTime<level.time )
	{
		return;
	}

	fwdangles[1] = self->client->ps.viewangles[1];
	AngleVectors( fwdangles, forward, NULL, NULL );

	for ( i = 0 ; i < 3 ; i++ )
	{
		mins[i] = self->currentOrigin[i] - radius;
		maxs[i] = self->currentOrigin[i] + radius;
	}

	numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );

	closestDist = radius;

	for ( e = 0 ; e < numListedEntities ; e++ )
	{
		ent = entityList[ e ];

		if (ent == self)
			continue;
		if (ent->owner == self)
			continue;
		if ( !(ent->inuse) )
			continue;
		if ( ent->s.eType != ET_MISSILE && !(ent->s.eFlags&EF_MISSILE_STICK) )
		{//not a normal projectile
			if ( ent->client || ent->s.weapon != WP_SABER )
			{//FIXME: wake up bad guys?
				continue;
			}
			if ( ent->s.eFlags & EF_NODRAW )
			{
				continue;
			}
			if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
			{//not a lightsaber
				//FIXME: what about general objects that are small in size- like rocks, etc...
				continue;
			}
			//a lightsaber.. make sure it's on and inFlight
			if ( !ent->owner || !ent->owner->client )
			{
				continue;
			}
			if ( !ent->owner->client->ps.saberInFlight )
			{//not in flight
				continue;
			}
			if ( ent->owner->client->ps.saberLength <= 0 )
			{//not on
				continue;
			}
			if ( ent->owner->health <= 0 && !g_saberRealisticCombat->integer )
			{//it's not doing damage, so ignore it
				continue;
			}
		}
		else
		{
			if ( ent->s.pos.trType == TR_STATIONARY && !self->s.number )
			{//nothing you can do with a stationary missile if you're the player
				continue;
			}
		}

		float		dot1, dot2;
		//see if they're in front of me
		VectorSubtract( ent->currentOrigin, self->currentOrigin, dir );
		dist = VectorNormalize( dir );
		//FIXME: handle detpacks, proximity mines and tripmines
		if ( ent->s.weapon == WP_THERMAL )
		{//thermal detonator!
			if ( self->NPC && dist < ent->splashRadius )
			{
				if ( dist < ent->splashRadius &&
					ent->nextthink < level.time + 600 &&
					ent->count &&
					self->client->ps.groundEntityNum != ENTITYNUM_NONE &&
						(ent->s.pos.trType == TR_STATIONARY||
						ent->s.pos.trType == TR_INTERPOLATE||
						(dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE||
						!WP_ForcePowerUsable( self, FP_PUSH, 0 )) )
				{//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!
					//FIXME: sometimes this might make me just jump into it...?
					self->client->ps.forceJumpCharge = 480;
				}
				else
				{//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
					ForceThrow( self, qfalse );
				}
			}
			continue;
		}
		else if ( ent->splashDamage && ent->splashRadius )
		{//exploding missile
			//FIXME: handle tripmines and detpacks somehow...
			//			maybe do a force-gesture that makes them explode?
			//			But what if we're within it's splashradius?
			if ( !self->s.number )
			{//players don't auto-handle these at all
				continue;
			}
			else
			{
				if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) )
				{//a placed explosive like a tripmine or detpack
					if ( InFOV( ent->currentOrigin, self->client->renderInfo.eyePoint, self->client->ps.viewangles, 90, 90 ) )
					{//in front of me
						if ( G_ClearLOS( self, ent ) )
						{//can see it
							vec3_t throwDir;
							//make the gesture
							ForceThrow( self, qfalse );
							//take it off the wall and toss it
							ent->s.pos.trType = TR_GRAVITY;
							ent->s.eType = ET_MISSILE;
							ent->s.eFlags &= ~EF_MISSILE_STICK;
							ent->s.eFlags |= EF_BOUNCE_HALF;
							AngleVectors( ent->currentAngles, throwDir, NULL, NULL );
							VectorMA( ent->currentOrigin, ent->maxs[0]+4, throwDir, ent->currentOrigin );
							VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
							VectorScale( throwDir, 300, ent->s.pos.trDelta );
							ent->s.pos.trDelta[2] += 150;
							VectorMA( ent->s.pos.trDelta, 800, dir, ent->s.pos.trDelta );
							ent->s.pos.trTime = level.time;		// move a bit on the very first frame
							VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
							ent->owner = self;
							// make it explode, but with less damage
							ent->splashDamage /= 3;
							ent->splashRadius /= 3;
							ent->e_ThinkFunc = thinkF_WP_Explode;
							ent->nextthink = level.time + Q_irand( 500, 3000 );
						}
					}
				}
				else if ( dist < ent->splashRadius &&
				self->client->ps.groundEntityNum != ENTITYNUM_NONE &&
					(DotProduct( dir, forward ) < SABER_REFLECT_MISSILE_CONE||
					!WP_ForcePowerUsable( self, FP_PUSH, 0 )) )
				{//NPCs try to evade it
					self->client->ps.forceJumpCharge = 480;
				}
				else
				{//else, try to force-throw it away
					//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
					ForceThrow( self, qfalse );
				}
			}
			//otherwise, can't block it, so we're screwed
			continue;
		}

		if ( ent->s.weapon != WP_SABER )
		{//only block shots coming from behind
			if ( (dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE )
				continue;
		}
		else if ( !self->s.number )
		{//player never auto-blocks thrown sabers
			continue;
		}//NPCs always try to block sabers coming from behind!

		//see if they're heading towards me
		VectorCopy( ent->s.pos.trDelta, missile_dir );
		VectorNormalize( missile_dir );
		if ( (dot2 = DotProduct( dir, missile_dir )) > 0 )
			continue;

		//FIXME: must have a clear trace to me, too...
		if ( dist < closestDist )
		{
			VectorCopy( self->currentOrigin, traceTo );
			traceTo[2] = self->absmax[2] - 4;
			gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, traceTo, ent->s.number, ent->clipmask, G2_NOCOLLIDE, 0 );
			if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) )
			{//okay, try one more check
				VectorNormalize2( ent->s.pos.trDelta, entDir );
				VectorMA( ent->currentOrigin, radius, entDir, traceTo );
				gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, traceTo, ent->s.number, ent->clipmask, G2_NOCOLLIDE, 0 );
				if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) )
				{//can't hit me, ignore it
					continue;
				}
			}
			if ( self->s.number != 0 )
			{//An NPC
				if ( self->NPC && !self->enemy && ent->owner )
				{
					if ( ent->owner->health >= 0 && (!ent->owner->client || ent->owner->client->playerTeam != self->client->playerTeam) )
					{
						G_SetEnemy( self, ent->owner );
					}
				}
			}
			//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?
			closestDist = dist;
			incoming = ent;
		}
	}

	if ( incoming )
	{
		if ( self->NPC && !G_ControlledByPlayer( self ) )
		{
			if ( Jedi_WaitingAmbush( self ) )
			{
				Jedi_Ambush( self );
			}
			if ( Jedi_SaberBlockGo( self, &self->NPC->last_ucmd, NULL, NULL, incoming ) != EVASION_NONE )
			{//make sure to turn on your saber if it's not on
				self->client->ps.saberActive = qtrue;
			}
		}
		else//player
		{
			WP_SaberBlockNonRandom( self, incoming->currentOrigin, qtrue );
			if ( incoming->owner && incoming->owner->client && (!self->enemy || self->enemy->s.weapon != WP_SABER) )//keep enemy jedi over shooters
			{
				self->enemy = incoming->owner;
				NPC_SetLookTarget( self, incoming->owner->s.number, level.time+1000 );
			}
		}
	}
}


//GENERAL SABER============================================================================
//GENERAL SABER============================================================================
//GENERAL SABER============================================================================
//GENERAL SABER============================================================================
//GENERAL SABER============================================================================


void WP_SetSaberMove(gentity_t *self, short blocked)
{
	self->client->ps.saberBlocked = blocked;
}

extern void CG_CubeOutline( vec3_t mins, vec3_t maxs, int time, unsigned int color, float alpha );
void WP_SaberUpdate( gentity_t *self, usercmd_t *ucmd )
{
	//float	swap;
	float	minsize = 16;

	if(0)//	if ( self->s.number != 0 )
	{//for now only the player can do this		// not anymore
		return;
	}

	if ( !self->client )
	{
		return;
	}

	if ( self->client->ps.saberEntityNum < 0 || self->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
	{//never got one
		return;
	}

	// Check if we are throwing it, launch it if needed, update position if needed.
	WP_SaberThrow(self, ucmd);


	//vec3_t saberloc;
	//vec3_t sabermins={-8,-8,-8}, sabermaxs={8,8,8};

	gentity_t *saberent;

	if (self->client->ps.saberEntityNum <= 0)
	{//WTF?!!  We lost it?
		return;
	}

	saberent = &g_entities[self->client->ps.saberEntityNum];

	//FIXME: Based on difficulty level/jedi saber combat skill, make this bounding box fatter/smaller
	if ( self->client->ps.saberBlocked != BLOCKED_NONE )
	{//we're blocking, increase min size
		minsize = 32;
	}

	//is our saber in flight?
	if ( !self->client->ps.saberInFlight )
	{	// It isn't, which means we can update its position as we will.
		if ( !self->client->ps.saberActive || PM_InKnockDown( &self->client->ps ) )
		{//can't block if saber isn't on
			VectorClear(saberent->mins);
			VectorClear(saberent->maxs);
			G_SetOrigin(saberent, self->currentOrigin);
		}
		else if ( self->client->ps.saberBlocking == BLK_TIGHT || self->client->ps.saberBlocking == BLK_WIDE )
		{//FIXME: keep bbox in front of player, even when wide?
			vec3_t	saberOrg;
			if ( ( (self->s.number&&!Jedi_SaberBusy(self)&&!g_saberRealisticCombat->integer) || (self->s.number == 0 && self->client->ps.saberBlocking == BLK_WIDE && (g_saberAutoBlocking->integer||self->client->ps.saberBlockingTime>level.time)) )
				&& self->client->ps.weaponTime <= 0 )
			{//full-size blocking for non-attacking player with g_saberAutoBlocking on
				vec3_t saberang={0,0,0}, fwd, sabermins={-8,-8,-8}, sabermaxs={8,8,8};

				saberang[YAW] = self->client->ps.viewangles[YAW];
				AngleVectors( saberang, fwd, NULL, NULL );

				VectorMA( self->currentOrigin, 12, fwd, saberOrg );

				VectorAdd( self->mins, sabermins, saberent->mins );
				VectorAdd( self->maxs, sabermaxs, saberent->maxs );

				saberent->contents = CONTENTS_LIGHTSABER;

				G_SetOrigin( saberent, saberOrg );
			}
			else
			{
				vec3_t	saberTip;
				VectorMA( self->client->renderInfo.muzzlePoint, self->client->ps.saberLength, self->client->renderInfo.muzzleDir, saberTip );
				VectorMA( self->client->renderInfo.muzzlePoint, self->client->ps.saberLength*0.5, self->client->renderInfo.muzzleDir, saberOrg );
				for ( int i = 0; i < 3; i++ )
				{
					if ( saberTip[i] > self->client->renderInfo.muzzlePoint[i] )
					{
						saberent->maxs[i] = saberTip[i] - saberOrg[i] + 8;//self->client->renderInfo.muzzlePoint[i];
						saberent->mins[i] = self->client->renderInfo.muzzlePoint[i] - saberOrg[i] - 8;
					}
					else //if ( saberTip[i] < self->client->renderInfo.muzzlePoint[i] )
					{
						saberent->maxs[i] = self->client->renderInfo.muzzlePoint[i] - saberOrg[i] + 8;
						saberent->mins[i] = saberTip[i] - saberOrg[i] - 8;//self->client->renderInfo.muzzlePoint[i];
					}
					if ( self->client->ps.weaponTime > 0 || self->s.number || g_saberAutoBlocking->integer || self->client->ps.saberBlockingTime > level.time )
					{//if attacking or blocking (or an NPC), inflate to a minimum size
						if ( saberent->maxs[i] < minsize )
						{
							saberent->maxs[i] = minsize;
						}
						if ( saberent->mins[i] > -minsize )
						{
							saberent->mins[i] = -minsize;
						}
					}
				}
				saberent->contents = CONTENTS_LIGHTSABER;
				G_SetOrigin( saberent, saberOrg );
			}
		}
		/*
		else if (self->client->ps.saberBlocking == BLK_WIDE)
		{	// Assuming that we are not swinging, the saber's bounding box should be around the player.
			vec3_t saberang={0,0,0}, fwd;

			saberang[YAW] = self->client->ps.viewangles[YAW];
			AngleVectors( saberang, fwd, NULL, NULL );

			VectorMA(self->currentOrigin, 12, fwd, saberloc);

			VectorAdd(self->mins, sabermins, saberent->mins);
			VectorAdd(self->maxs, sabermaxs, saberent->maxs);

			saberent->contents = CONTENTS_LIGHTSABER;

			G_SetOrigin( saberent, saberloc);
		}
		else if (self->client->ps.saberBlocking == BLK_TIGHT)
		{	// If the player is swinging, the bbox is around just the saber
			VectorCopy(self->client->renderInfo.muzzlePoint, sabermins);
			// Put the limits of the bbox around the saber size.
			VectorMA(sabermins, self->client->ps.saberLength, self->client->renderInfo.muzzleDir, sabermaxs);

			// Now make the mins into mins and the maxs into maxs
			if (sabermins[0] > sabermaxs[0])
			{
				swap = sabermins[0];
				sabermins[0] = sabermaxs[0];
				sabermaxs[0] = swap;
			}
			if (sabermins[1] > sabermaxs[1])
			{
				swap = sabermins[1];
				sabermins[1] = sabermaxs[1];
				sabermaxs[1] = swap;
			}
			if (sabermins[2] > sabermaxs[2])
			{
				swap = sabermins[2];
				sabermins[2] = sabermaxs[2];
				sabermaxs[2] = swap;
			}

			// Now the loc is halfway between the (absolute) mins and maxs
			VectorAdd(sabermins, sabermaxs, saberloc);
			VectorScale(saberloc, 0.5, saberloc);

			// Finally, turn the mins and maxs, which are absolute, into relative mins and maxs.
			VectorSubtract(sabermins, saberloc, saberent->mins);
			VectorSubtract(sabermaxs, saberloc, saberent->maxs);

			saberent->contents = CONTENTS_LIGHTSABER;// | CONTENTS_SHOTCLIP;

			G_SetOrigin( saberent, saberloc);
		}
		*/
		else
		{	// Otherwise there is no blocking possible.
			VectorClear(saberent->mins);
			VectorClear(saberent->maxs);
			G_SetOrigin(saberent, self->currentOrigin);
		}
		saberent->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
		gi.linkentity(saberent);
	}
	else
	{
		WP_SaberInFlightReflectCheck( self, ucmd );
	}
#ifndef FINAL_BUILD
	if ( d_saberCombat->integer > 2 )
	{
		CG_CubeOutline( saberent->absmin, saberent->absmax, 50, WPDEBUG_SaberColor( self->client->ps.saberColor ), 1 );
	}
#endif
}


//OTHER JEDI POWERS=========================================================================
//OTHER JEDI POWERS=========================================================================
//OTHER JEDI POWERS=========================================================================
//OTHER JEDI POWERS=========================================================================
//OTHER JEDI POWERS=========================================================================
extern gentity_t *TossClientItems( gentity_t *self );
extern void ChangeWeapon( gentity_t *ent, int newWeapon );
void WP_DropWeapon( gentity_t *dropper, vec3_t velocity )
{
	if ( !dropper || !dropper->client )
	{
		return;
	}
	int	replaceWeap = WP_NONE;
	int oldWeap = dropper->s.weapon;
	gentity_t *weapon = TossClientItems( dropper );
	if ( oldWeap == WP_THERMAL && dropper->NPC )
	{//Hmm, maybe all NPCs should go into melee?  Not too many, though, or they mob you and look silly
		replaceWeap = WP_MELEE;
	}
	if (dropper->ghoul2.IsValid()&& dropper->weaponModel >= 0 )
	{
		gi.G2API_RemoveGhoul2Model( dropper->ghoul2, dropper->weaponModel );
		dropper->weaponModel = -1;
	}
	//FIXME: does this work on the player?
	dropper->client->ps.stats[STAT_WEAPONS] |= ( 1 << replaceWeap );
	if ( !dropper->s.number )
	{
		if ( oldWeap == WP_THERMAL )
		{
			dropper->client->ps.ammo[weaponData[oldWeap].ammoIndex] -= weaponData[oldWeap].energyPerShot;
		}
		else
		{
			dropper->client->ps.stats[STAT_WEAPONS] &= ~( 1 << oldWeap );
		}
		CG_ChangeWeapon( replaceWeap );
	}
	else
	{
		dropper->client->ps.stats[STAT_WEAPONS] &= ~( 1 << oldWeap );
	}
	ChangeWeapon( dropper, replaceWeap );
	dropper->s.weapon = replaceWeap;
	if ( dropper->NPC )
	{
		dropper->NPC->last_ucmd.weapon = replaceWeap;
	}
	if ( weapon != NULL && velocity && !VectorCompare( velocity, vec3_origin ) )
	{//weapon should have a direction to it's throw
		VectorScale( velocity, 3, weapon->s.pos.trDelta );//NOTE: Presumes it is moving already...?
		if ( weapon->s.pos.trDelta[2] < 150 )
		{//this is presuming you don't want them to drop the weapon down on you...
			weapon->s.pos.trDelta[2] = 150;
		}
		//FIXME: gets stuck inside it's former owner...
		weapon->forcePushTime = level.time + 600; // let the push effect last for 600 ms
	}
}

void WP_KnockdownTurret( gentity_t *self, gentity_t *pas )
{
	//knock it over
	VectorCopy( pas->currentOrigin, pas->s.pos.trBase );
	pas->s.pos.trType = TR_LINEAR_STOP;
	pas->s.pos.trDuration = 250;
	pas->s.pos.trTime = level.time;
	pas->s.pos.trDelta[2] = ( 12.0f / ( pas->s.pos.trDuration * 0.001f ) );

	VectorCopy( pas->currentAngles, pas->s.apos.trBase );
	pas->s.apos.trType = TR_LINEAR_STOP;
	pas->s.apos.trDuration = 250;
	pas->s.apos.trTime = level.time;
	//FIXME: pick pitch/roll that always tilts it directly away from pusher
	pas->s.apos.trDelta[PITCH] = ( 100.0f / ( pas->s.apos.trDuration * 0.001f ) );

	//kill it
	pas->count = 0;
	pas->nextthink = -1;
	G_Sound( pas, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
	//push effect?
	pas->forcePushTime = level.time + 600; // let the push effect last for 600 ms
}

void WP_ResistForcePush( gentity_t *self, gentity_t *pusher, qboolean noPenalty )
{
	int parts;
	qboolean runningResist = qfalse;

	if ( !self || self->health <= 0 || !self->client || !pusher || !pusher->client )
	{
		return;
	}
	if ( (!self->s.number || self->client->NPC_class == CLASS_DESANN || self->client->NPC_class == CLASS_LUKE)
		&& (VectorLengthSquared( self->client->ps.velocity ) > 10000 || self->client->ps.forcePowerLevel[FP_PUSH] >= FORCE_LEVEL_3 || self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 ) )
	{
		runningResist = qtrue;
	}
	if ( !runningResist
		&& self->client->ps.groundEntityNum != ENTITYNUM_NONE
		&& !PM_SpinningSaberAnim( self->client->ps.legsAnim )
		&& !PM_FlippingAnim( self->client->ps.legsAnim )
		&& !PM_RollingAnim( self->client->ps.legsAnim )
		&& !PM_InKnockDown( &self->client->ps )
		&& !PM_CrouchAnim( self->client->ps.legsAnim ))
	{//if on a surface and not in a spin or flip, play full body resist
		parts = SETANIM_BOTH;
	}
	else
	{//play resist just in torso
		parts = SETANIM_TORSO;
	}
	NPC_SetAnim( self, parts, BOTH_RESISTPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
	if ( !noPenalty )
	{
		if ( !runningResist )
		{
			VectorClear( self->client->ps.velocity );
			//still stop them from attacking or moving for a bit, though
			//FIXME: maybe push just a little (like, slide)?
			self->client->ps.weaponTime = 1000;
			if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
			{
				self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
			}
			self->client->ps.pm_time = self->client->ps.weaponTime;
			self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
			//play the full body push effect on me
			self->forcePushTime = level.time + 600; // let the push effect last for 600 ms
		}
		else
		{
			self->client->ps.weaponTime = 600;
			if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
			{
				self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
			}
		}
	}
	//play my force push effect on my hand
	self->client->ps.powerups[PW_FORCE_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500;
	Jedi_PlayBlockedPushSound( self );
}

void WP_ForceKnockdown( gentity_t *self, gentity_t *pusher, qboolean pull, qboolean strongKnockdown, qboolean breakSaberLock )
{
	if ( !self || !self->client || !pusher || !pusher->client )
	{
		return;
	}

	//break out of a saberLock?
	if ( breakSaberLock )
	{
		self->client->ps.saberLockTime = 0;
		self->client->ps.saberLockEnemy = ENTITYNUM_NONE;
	}

	if ( self->health > 0 )
	{
		if ( !self->s.number )
		{
			NPC_SetPainEvent( self );
		}
		else
		{
			GEntity_PainFunc( self, pusher, pusher, self->currentOrigin, 0, MOD_MELEE );
		}
		vec3_t	pushDir;
		if ( pull )
		{
			VectorSubtract( pusher->currentOrigin, self->currentOrigin, pushDir );
		}
		else
		{
			VectorSubtract( self->currentOrigin, pusher->currentOrigin, pushDir );
		}
		G_CheckLedgeDive( self, 72, pushDir, qfalse, qfalse );

		if ( !PM_SpinningSaberAnim( self->client->ps.legsAnim )
			&& !PM_FlippingAnim( self->client->ps.legsAnim )
			&& !PM_RollingAnim( self->client->ps.legsAnim )
			&& !PM_InKnockDown( &self->client->ps ) )
		{
			int knockAnim = BOTH_KNOCKDOWN1;//default knockdown
			if ( pusher->client->NPC_class == CLASS_DESANN && self->client->NPC_class != CLASS_LUKE )
			{//desann always knocks down, unless you're Luke
				strongKnockdown = qtrue;
			}
			if ( !self->s.number
				&& !strongKnockdown
				&& ( (!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)) )	)
			{//player only knocked down if pushed *hard*
				if ( self->s.weapon == WP_SABER )
				{//temp HACK: these are the only 2 pain anims that look good when holding a saber
					knockAnim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 );
				}
				else
				{
					knockAnim = PM_PickAnim( self, BOTH_PAIN1, BOTH_PAIN19 );
				}
			}
			else if ( PM_CrouchAnim( self->client->ps.legsAnim ) )
			{//crouched knockdown
				knockAnim = BOTH_KNOCKDOWN4;
			}
			else
			{//plain old knockdown
				vec3_t pLFwd, pLAngles = {0,self->client->ps.viewangles[YAW],0};
				vec3_t sFwd, sAngles = {0,pusher->client->ps.viewangles[YAW],0};
				AngleVectors( pLAngles, pLFwd, NULL, NULL );
				AngleVectors( sAngles, sFwd, NULL, NULL );
				if ( DotProduct( sFwd, pLFwd ) > 0.2f )
				{//pushing him from behind
					//FIXME: check to see if we're aiming below or above the waist?
					if ( pull )
					{
						knockAnim = BOTH_KNOCKDOWN1;
					}
					else
					{
						knockAnim = BOTH_KNOCKDOWN3;
					}
				}
				else
				{//pushing him from front
					if ( pull )
					{
						knockAnim = BOTH_KNOCKDOWN3;
					}
					else
					{
						knockAnim = BOTH_KNOCKDOWN1;
					}
				}
			}
			if ( knockAnim == BOTH_KNOCKDOWN1 && strongKnockdown )
			{//push *hard*
				knockAnim = BOTH_KNOCKDOWN2;
			}
			NPC_SetAnim( self, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
			if ( self->s.number )
			{//randomize getup times
				int addTime = Q_irand( -300, 1000 );
				self->client->ps.legsAnimTimer += addTime;
				self->client->ps.torsoAnimTimer += addTime;
			}
			//
			if ( pusher->NPC && pusher->enemy == self )
			{//pushed pushed down his enemy
				G_AddVoiceEvent( pusher, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 3000 );
				pusher->NPC->blockedSpeechDebounceTime = level.time + 3000;
			}
		}
	}
	self->forcePushTime = level.time + 600; // let the push effect last for 600 ms
}

void ForceThrow( gentity_t *self, qboolean pull )
{//FIXME: pass in a target ent so we (an NPC) can push/pull just one targeted ent.
	//shove things in front of you away
	float		dist;
	gentity_t	*ent, *forwardEnt = NULL;
	gentity_t	*entityList[MAX_GENTITIES];
	gentity_t	*push_list[MAX_GENTITIES];
	int			numListedEntities;
	vec3_t		mins, maxs;
	vec3_t		v;
	int			i, e;
	int			ent_count = 0;
	int			radius;
	vec3_t		center, ent_org, size, forward, right, end, dir, fwdangles = {0};
	float		dot1, cone;
	trace_t		tr;
	int			anim, hold, soundIndex, cost, actualCost;

	if ( self->health <= 0 )
	{
		return;
	}
	if ( self->client->ps.leanofs )
	{//can't force-throw while leaning
		return;
	}
	if ( self->client->ps.forcePowerDebounce[FP_PUSH] > level.time )//self->client->ps.powerups[PW_FORCE_PUSH] > level.time )
	{//already pushing- now you can't haul someone across the room, sorry
		return;
	}
	if ( !self->s.number && (cg.zoomMode || in_camera) )
	{//can't force throw/pull when zoomed in or in cinematic
		return;
	}
	if ( self->client->ps.saberLockTime > level.time )
	{
		if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 )
		{//this can be a way to break out
			return;
		}
		//else, I'm breaking my half of the saberlock
		self->client->ps.saberLockTime = 0;
		self->client->ps.saberLockEnemy = ENTITYNUM_NONE;
	}

	if ( self->client->ps.legsAnim == BOTH_KNOCKDOWN3
		|| (self->client->ps.torsoAnim == BOTH_FORCE_GETUP_F1 && self->client->ps.torsoAnimTimer > 400)
		|| (self->client->ps.torsoAnim == BOTH_FORCE_GETUP_F2 && self->client->ps.torsoAnimTimer > 900)
		|| (self->client->ps.torsoAnim == BOTH_GETUP3 && self->client->ps.torsoAnimTimer > 500)
		|| (self->client->ps.torsoAnim == BOTH_GETUP4 && self->client->ps.torsoAnimTimer > 300)
		|| (self->client->ps.torsoAnim == BOTH_GETUP5 && self->client->ps.torsoAnimTimer > 500) )
	{//we're face-down, so we'd only be force-push/pulling the floor
		return;
	}
	if ( pull )
	{
		radius = forcePushPullRadius[self->client->ps.forcePowerLevel[FP_PULL]];
	}
	else
	{
		radius = forcePushPullRadius[self->client->ps.forcePowerLevel[FP_PUSH]];
	}

	if ( !radius )
	{//no ability to do this yet
		return;
	}

	if ( pull )
	{
		cost = forcePowerNeeded[FP_PULL];
		if ( !WP_ForcePowerUsable( self, FP_PULL, cost ) )
		{
			return;
		}
		//make sure this plays and that you cannot press fire for about 200ms after this
		anim = BOTH_FORCEPULL;
		soundIndex = G_SoundIndex( "sound/weapons/force/pull.wav" );
		hold = 200;
	}
	else
	{
		cost = forcePowerNeeded[FP_PUSH];
		if ( !WP_ForcePowerUsable( self, FP_PUSH, cost ) )
		{
			return;
		}
		//make sure this plays and that you cannot press fire for about 1 second after this
		anim = BOTH_FORCEPUSH;
		soundIndex = G_SoundIndex( "sound/weapons/force/push.wav" );
		hold = 650;
	}

	int parts = SETANIM_TORSO;
	if ( !PM_InKnockDown( &self->client->ps ) )
	{
		if ( self->client->ps.saberLockTime > level.time )
		{
			self->client->ps.saberLockTime = 0;
			self->painDebounceTime = level.time + 2000;
			hold += 1000;
			parts = SETANIM_BOTH;
		}
		else if ( !VectorLengthSquared( self->client->ps.velocity ) && !(self->client->ps.pm_flags&PMF_DUCKED))
		{
			parts = SETANIM_BOTH;
		}
	}
	NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART );
	self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
	self->client->ps.saberBlocked = BLOCKED_NONE;
	if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
	{
		hold = floor( hold*g_timescale->value );
	}
	self->client->ps.weaponTime = hold;//was 1000, but want to swing sooner
	//do effect... FIXME: build-up or delay this until in proper part of anim
	self->client->ps.powerups[PW_FORCE_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500;

	G_Sound( self, soundIndex );

	VectorCopy( self->client->ps.viewangles, fwdangles );
	//fwdangles[1] = self->client->ps.viewangles[1];
	AngleVectors( fwdangles, forward, right, NULL );
	VectorCopy( self->currentOrigin, center );

	for ( i = 0 ; i < 3 ; i++ )
	{
		mins[i] = center[i] - radius;
		maxs[i] = center[i] + radius;
	}

	numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );

	if ( pull )
	{
		cone = forcePullCone[self->client->ps.forcePowerLevel[FP_PULL]];
	}
	else
	{
		cone = forcePushCone[self->client->ps.forcePowerLevel[FP_PUSH]];
	}

	if ( cone >= 1.0f )
	{//must be pointing right at them
		VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
		gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_OPAQUE|CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE, G2_NOCOLLIDE, 0 );//was MASK_SHOT, changed to match crosshair trace
		/*
		//FIXME: can't just return, need to be able to push missiles
		if ( tr.entityNum >= ENTITYNUM_WORLD )
		{//no-one right in front of self, so short out
			return;
		}
		*/
		forwardEnt = &g_entities[tr.entityNum];
	}

	for ( e = 0 ; e < numListedEntities ; e++ )
	{
		ent = entityList[ e ];

		if (ent == self)
			continue;
		if ( ent->owner == self && ent->s.weapon != WP_THERMAL )//can push your own thermals
			continue;
		if ( !(ent->inuse) )
			continue;
		if ( ent->NPC && ent->NPC->scriptFlags & SCF_NO_FORCE )
		{
			if ( ent->s.weapon == WP_SABER )
			{//Hmm, should jedi do the resist behavior?  If this is on, perhaps it's because of a cinematic?
				WP_ResistForcePush( ent, self, qtrue );
			}
			continue;
		}
		if ( (ent->flags&FL_FORCE_PULLABLE_ONLY) && !pull )
		{//simple HACK: cannot force-push ammo rack items (because they may start in solid)
			continue;
		}
		//FIXME: don't push it if I already pushed it a little while ago
		if ( ent->s.eType != ET_MISSILE )
		{
			if ( cone >= 1.0f )
			{//must be pointing right at them
				if ( ent != forwardEnt )
				{//must be the person I'm looking right at
					if ( ent->client && !pull
						&& ent->client->ps.forceGripEntityNum == self->s.number
						&& (self->s.eFlags&EF_FORCE_GRIPPED) )
					{//this is the guy that's force-gripping me, use a wider cone regardless of force power level
					}
					else
					{
						continue;
					}
				}
			}
			if ( ent->s.eType != ET_ITEM && ent->e_ThinkFunc != thinkF_G_RunObject )//|| !(ent->flags&FL_DROPPED_ITEM) )//was only dropped items
			{
				//FIXME: need pushable objects
				if ( ent->s.eFlags & EF_NODRAW )
				{
					continue;
				}
				if ( !ent->client )
				{
					if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
					{//not a lightsaber
						if ( !(ent->svFlags&SVF_GLASS_BRUSH) )
						{//and not glass
							if ( Q_stricmp( "func_door", ent->classname ) != 0 || !(ent->spawnflags & 2/*MOVER_FORCE_ACTIVATE*/) )
							{//not a force-usable door
								if ( Q_stricmp( "func_static", ent->classname ) != 0 || (!(ent->spawnflags&1/*F_PUSH*/)&&!(ent->spawnflags&2/*F_PULL*/)) )
								{//not a force-usable func_static
									if ( Q_stricmp( "limb", ent->classname ) )
									{//not a limb
										if ( ent->s.weapon == WP_TURRET && !Q_stricmp( "PAS", ent->classname ) && ent->s.apos.trType == TR_STATIONARY )
										{//can knock over placed turrets
											if ( !self->s.number || self->enemy != ent )
											{//only NPCs who are actively mad at this turret can push it over
												continue;
											}
										}
										else
										{
											continue;
										}
									}
								}
							}
							else if ( ent->moverState != MOVER_POS1 && ent->moverState != MOVER_POS2 )
							{//not at rest
								continue;
							}
						}
					}
					//continue;
				}
				else if ( ent->client->NPC_class == CLASS_MARK1 )
				{//can't push Mark1 unless push 3
					if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 )
					{
						continue;
					}
				}
				else if ( ent->client->NPC_class == CLASS_GALAKMECH || ent->client->NPC_class == CLASS_ATST )
				{//can't push ATST or Galak
					continue;
				}
				else if ( ent->s.weapon == WP_EMPLACED_GUN )
				{//FIXME: maybe can pull them out?
					continue;
				}
				else if ( ent->client->playerTeam == self->client->playerTeam && self->enemy && self->enemy != ent )
				{//can't accidently push a teammate while in combat
					continue;
				}
			}
			else if ( ent->s.eType == ET_ITEM
				&& ent->item
				&& ent->item->giType == IT_HOLDABLE
				&& ent->item->giTag == INV_SECURITY_KEY )
				//&& (ent->flags&FL_DROPPED_ITEM) ???
			{//dropped security keys can't be pushed?  But placed ones can...?  does this make any sense?
				if ( !pull || self->s.number )
				{//can't push, NPC's can't do anything to it
					continue;
				}
				else
				{
					if ( g_crosshairEntNum != ent->s.number )
					{//player can pull it if looking *right* at it
						if ( cone >= 1.0f )
						{//we did a forwardEnt trace
							if ( forwardEnt != ent )
							{//must be pointing right at them
								continue;
							}
						}
						else
						{//do a forwardEnt trace
							VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
							gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_OPAQUE|CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE, G2_NOCOLLIDE, 0 );//was MASK_SHOT, changed to match crosshair trace
							if ( tr.entityNum != ent->s.number )
							{//last chance
								continue;
							}
						}
					}
				}
			}
		}
		else
		{
			if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) )
			{//can't force-push/pull stuck missiles (detpacks, tripmines)
				continue;
			}
			if ( ent->s.pos.trType == TR_STATIONARY && ent->s.weapon != WP_THERMAL )
			{//only thermal detonators can be pushed once stopped
				continue;
			}
		}

		//this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
		// find the distance from the edge of the bounding box
		for ( i = 0 ; i < 3 ; i++ )
		{
			if ( center[i] < ent->absmin[i] )
			{
				v[i] = ent->absmin[i] - center[i];
			} else if ( center[i] > ent->absmax[i] )
			{
				v[i] = center[i] - ent->absmax[i];
			} else
			{
				v[i] = 0;
			}
		}

		VectorSubtract( ent->absmax, ent->absmin, size );
		VectorMA( ent->absmin, 0.5, size, ent_org );

		//see if they're in front of me
		VectorSubtract( ent_org, center, dir );
		VectorNormalize( dir );
		if ( cone < 1.0f )
		{//must be within the forward cone
			if ( ent->client && !pull
				&& ent->client->ps.forceGripEntityNum == self->s.number
				&& self->s.eFlags&EF_FORCE_GRIPPED )
			{//this is the guy that's force-gripping me, use a wider cone regardless of force power level
				if ( (dot1 = DotProduct( dir, forward )) < cone-0.3f )
					continue;
			}
			else if ( ent->s.eType == ET_MISSILE )//&& ent->s.eType != ET_ITEM && ent->e_ThinkFunc != thinkF_G_RunObject )
			{//missiles are easier to force-push, never require direct trace (FIXME: maybe also items and general physics objects)
				if ( (dot1 = DotProduct( dir, forward )) < cone-0.3f )
					continue;
			}
			else if ( (dot1 = DotProduct( dir, forward )) < cone )
			{
				continue;
			}
		}
		else if ( ent->s.eType == ET_MISSILE )
		{//a missile and we're at force level 1... just use a small cone, but not ridiculously small
			if ( (dot1 = DotProduct( dir, forward )) < 0.75f )
			{
				continue;
			}
		}//else is an NPC or brush entity that our forward trace would have to hit

		dist = VectorLength( v );

		//Now check and see if we can actually deflect it
		//method1
		//if within a certain range, deflect it
		if ( ent->s.eType == ET_MISSILE && cone >= 1.0f )
		{//smaller radius on missile checks at force push 1
			if ( dist >= 192 )
			{
				continue;
			}
		}
		else if ( dist >= radius )
		{
			continue;
		}

		//in PVS?
		if ( !ent->bmodel && !gi.inPVS( ent_org, self->client->renderInfo.eyePoint ) )
		{//must be in PVS
			continue;
		}

		if ( ent != forwardEnt )
		{//don't need to trace against forwardEnt again
		//really should have a clear LOS to this thing...
			gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_OPAQUE|CONTENTS_SOLID, G2_NOCOLLIDE, 0 );//was MASK_SHOT, but changed to match above trace and crosshair trace
			if ( tr.fraction < 1.0f && tr.entityNum != ent->s.number )
			{//must have clear LOS
				continue;
			}
		}

		// ok, we are within the radius, add us to the incoming list
		push_list[ent_count] = ent;
		ent_count++;
	}

	if ( ent_count )
	{
		for ( int x = 0; x < ent_count; x++ )
		{
			if ( push_list[x]->client )
			{
				vec3_t	pushDir;
				float	knockback = pull?0:200;

//FIXMEFIXMEFIXMEFIXMEFIXME: extern a lot of this common code when I have the time!!!

				//First, if this is the player we're push/pulling, see if he can counter it
				if ( !push_list[x]->s.number )
				{//player
					if ( push_list[x]->health > 0 //alive
						&& push_list[x]->client //client
						&& push_list[x]->client->ps.torsoAnim != BOTH_FORCEGRIP_HOLD// BOTH_FORCEGRIP1//wasn't trying to grip anyone
						&& (self->client->NPC_class != CLASS_DESANN || !Q_irand( 0, 2 ) )//only 30% chance of resisting a Desann push
						&& push_list[x]->client->ps.groundEntityNum != ENTITYNUM_NONE//on the ground
						&& !PM_InKnockDown( &push_list[x]->client->ps )//not knocked down already
						&& push_list[x]->client->ps.saberLockTime < level.time//not involved in a saberLock
						&& push_list[x]->client->ps.weaponTime < level.time//not attacking or otherwise busy
						&& (push_list[x]->client->ps.weapon == WP_SABER||push_list[x]->client->ps.weapon == WP_MELEE) )//using saber or fists
					{//trying to push or pull the player!
						if ( push_list[x]->client->ps.powerups[PW_FORCE_PUSH] > level.time//player was pushing/pulling too
							||( pull && Q_irand( 0, (push_list[x]->client->ps.forcePowerLevel[FP_PULL] - self->client->ps.forcePowerLevel[FP_PULL])*2+1 ) > 0 )//player's pull is high enough
							||( !pull && Q_irand( 0, (push_list[x]->client->ps.forcePowerLevel[FP_PUSH] - self->client->ps.forcePowerLevel[FP_PUSH])*2+1 ) > 0 ) )//player's push is high enough
						{//player's force push/pull is high enough to try to stop me
							if ( InFront( self->currentOrigin, push_list[x]->client->renderInfo.eyePoint, push_list[x]->client->ps.viewangles, 0.3f ) )
							{//I'm in front of player
								WP_ResistForcePush( push_list[x], self, qfalse );
								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
								push_list[x]->client->ps.saberBlocked = BLOCKED_NONE;
								continue;
							}
						}
					}
				}
				else if ( push_list[x]->client && Jedi_WaitingAmbush( push_list[x] ) )
				{
					WP_ForceKnockdown( push_list[x], self, pull, qtrue, qfalse );
					continue;
				}


				//okay, everyone else (or player who couldn't resist it)...
				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
						&& push_list[x]->client->ps.weapon == WP_SABER //Jedi
						&& push_list[x]->health > 0 //alive
						&& (self->client->NPC_class != CLASS_DESANN || !Q_irand( 0, 2 ) )//only 30% chance of resisting a Desann push
						&& push_list[x]->client->ps.groundEntityNum != ENTITYNUM_NONE //on the ground
						&& InFront( self->currentOrigin, push_list[x]->currentOrigin, push_list[x]->client->ps.viewangles, 0.3f ) //I'm in front of him
						&& ( push_list[x]->client->ps.powerups[PW_FORCE_PUSH] > level.time ||//he's pushing too
								(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)
						   )
					)
				{//Jedi don't get pushed, they resist as long as they aren't already attacking and are on the ground
					if ( push_list[x]->client->ps.saberLockTime > level.time )
					{//they're in a lock
						if ( push_list[x]->client->ps.saberLockEnemy != self->s.number )
						{//they're not in a lock with me
							continue;
						}
						else if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 ||
							push_list[x]->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
						{//they're in a lock with me, but my push is too weak
							continue;
						}
						else
						{//we will knock them down
							self->painDebounceTime = 0;
							self->client->ps.weaponTime = 500;
							if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
							{
								self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
							}
						}
					}
					if ( !pull && self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 && !Q_irand(0,2) &&
							push_list[x]->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 )
					{//a level 3 push can even knock down a jedi
						if ( PM_InKnockDown( &push_list[x]->client->ps ) )
						{//can't knock them down again
							continue;
						}
						WP_ForceKnockdown( push_list[x], self, pull, qfalse, qtrue );
					}
					else
					{
						WP_ResistForcePush( push_list[x], self, qfalse );
					}
				}
				else
				{
					//UGH: FIXME: for enemy jedi, they should probably always do force pull 3, and not your weapon (if player?)!
					//shove them
					if ( push_list[x]->s.number && push_list[x]->message )
					{//an NPC who has a key
						//don't push me... FIXME: maybe can pull the key off me?
						WP_ForceKnockdown( push_list[x], self, pull, qfalse, qfalse );
						continue;
					}
					if ( pull )
					{
						VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, pushDir );
						if ( self->client->ps.forcePowerLevel[FP_PULL] > FORCE_LEVEL_1
							&& push_list[x]->s.weapon != WP_SABER
							&& push_list[x]->s.weapon != WP_MELEE
							&& push_list[x]->s.weapon != WP_THERMAL )
						{//yank the weapon - NOTE: level 1 just knocks them down, not take weapon
							//FIXME: weapon yank anim if not a knockdown?
							if ( InFront( self->currentOrigin, push_list[x]->currentOrigin, push_list[x]->client->ps.viewangles, 0.0f ) )
							{//enemy has to be facing me, too...
								WP_DropWeapon( push_list[x], pushDir );
							}
						}
						knockback += VectorNormalize( pushDir );
						if ( knockback > 200 )
						{
							knockback = 200;
						}
						if ( self->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_3 )
						{//maybe just knock them down
							knockback /= 3;
						}
					}
					else
					{
						VectorSubtract( push_list[x]->currentOrigin, self->currentOrigin, pushDir );
						knockback -= VectorNormalize( pushDir );
						if ( knockback < 100 )
						{
							knockback = 100;
						}
						//scale for push level
						if ( self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_2 )
						{//maybe just knock them down
							knockback /= 3;
						}
						else if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
						{//super-hard push
							//Hmm, maybe in this case can even nudge/knockdown a jedi?  Especially if close?
							//knockback *= 5;
						}
					}
					//actually push/pull the enemy
					G_Throw( push_list[x], pushDir, knockback );
					//make it so they don't actually hurt me when pulled at me...
					push_list[x]->forcePuller = self->s.number;

					if ( push_list[x]->client->ps.velocity[2] < knockback )
					{
						push_list[x]->client->ps.velocity[2] = knockback;
					}

					if ( push_list[x]->health > 0 )
					{//target is still alive
						if ( (push_list[x]->s.number||(cg.renderingThirdPerson&&!cg.zoomMode)) //NPC or 3rd person player
							&& ((!pull&&self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_2 && push_list[x]->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_1) //level 1 push
								|| (pull && self->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_2 && push_list[x]->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_1)) )//level 1 pull
						{//NPC or third person player (without force push/pull skill), and force push/pull level is at 1
							WP_ForceKnockdown(
								push_list[x], self, pull,
								(qboolean)(!pull && knockback > 150), qfalse );
						}
						else if ( !push_list[x]->s.number )
						{//player, have to force an anim on him
							WP_ForceKnockdown(
								push_list[x], self, pull,
								(qboolean)(!pull && knockback > 150), qfalse );
						}
						else
						{//NPC and force-push/pull at level 2 or higher
							WP_ForceKnockdown(
								push_list[x], self, pull,
								(qboolean)(!pull && knockback > 100), qfalse );
						}
					}
					push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
				}
			}
			else if ( push_list[x]->s.weapon == WP_SABER && (push_list[x]->contents&CONTENTS_LIGHTSABER) )
			{//a thrown saber, just send it back
				/*
				if ( pull )
				{//steal it?
				}
				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 )
				{//it's on and being controlled
					//FIXME: prevent it from damaging me?
					if ( self->s.number == 0 || Q_irand( 0, 2 ) )
					{//certain chance of throwing it aside and turning it off?
						//give it some velocity away from me
						//FIXME: maybe actually push or pull it?
						if ( Q_irand( 0, 1 ) )
						{
							VectorScale( right, -1, right );
						}
						G_ReflectMissile( self, push_list[x], right );
						//FIXME: isn't turning off!!!
						WP_SaberDrop( push_list[x]->owner, push_list[x] );
					}
					else
					{
						WP_SaberReturn( push_list[x]->owner, push_list[x] );
					}
					//different effect?
				}
			}
			else if ( push_list[x]->s.eType == ET_MISSILE
				&& push_list[x]->s.pos.trType != TR_STATIONARY
				&& (push_list[x]->s.pos.trType != TR_INTERPOLATE||push_list[x]->s.weapon != WP_THERMAL) )//rolling and stationary thermal detonators are dealt with below
			{
				vec3_t dir2Me;
				VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, dir2Me );
				float dot = DotProduct( push_list[x]->s.pos.trDelta, dir2Me );
				if ( pull )
				{//deflect rather than reflect?
				}
				else
				{
					if ( push_list[x]->s.eFlags&EF_MISSILE_STICK )
					{//caught a sticky in-air
						push_list[x]->s.eType = ET_MISSILE;
						push_list[x]->s.eFlags &= ~EF_MISSILE_STICK;
						push_list[x]->s.eFlags |= EF_BOUNCE_HALF;
						push_list[x]->splashDamage /= 3;
						push_list[x]->splashRadius /= 3;
						push_list[x]->e_ThinkFunc = thinkF_WP_Explode;
						push_list[x]->nextthink = level.time + Q_irand( 500, 3000 );
					}
					if ( dot >= 0 )
					{//it's heading towards me
						G_ReflectMissile( self, push_list[x], forward );
					}
					else
					{
						VectorScale( push_list[x]->s.pos.trDelta, 1.25f, push_list[x]->s.pos.trDelta );
					}
					//deflect sound
					//G_Sound( push_list[x], G_SoundIndex( va("sound/weapons/blaster/reflect%d.wav", Q_irand( 1, 3 ) ) ) );
					//push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
				}
			}
			else if ( push_list[x]->svFlags & SVF_GLASS_BRUSH )
			{//break the glass
				trace_t tr;
				vec3_t	pushDir;
				float	damage = 800;

				AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
				VectorNormalize( forward );
				VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
				gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
				if ( tr.entityNum != push_list[x]->s.number || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
				{//must be pointing right at it
					continue;
				}

				if ( pull )
				{
					VectorSubtract( self->client->renderInfo.eyePoint, tr.endpos, pushDir );
				}
				else
				{
					VectorSubtract( tr.endpos, self->client->renderInfo.eyePoint, pushDir );
				}
				/*
				VectorSubtract( push_list[x]->absmax, push_list[x]->absmin, size );
				VectorMA( push_list[x]->absmin, 0.5, size, center );
				if ( pull )
				{
					VectorSubtract( self->client->renderInfo.eyePoint, center, pushDir );
				}
				else
				{
					VectorSubtract( center, self->client->renderInfo.eyePoint, pushDir );
				}
				*/
				damage -= VectorNormalize( pushDir );
				if ( damage < 200 )
				{
					damage = 200;
				}
				VectorScale( pushDir, damage, pushDir );

				G_Damage( push_list[x], self, self, pushDir, tr.endpos, damage, 0, MOD_UNKNOWN );
			}
			else if ( !Q_stricmp( "func_static", push_list[x]->classname ) )
			{//force-usable func_static
				if ( !pull && (push_list[x]->spawnflags&1/*F_PUSH*/) )
				{
					GEntity_UseFunc( push_list[x], self, self );
				}
				else if ( pull && (push_list[x]->spawnflags&2/*F_PULL*/) )
				{
					GEntity_UseFunc( push_list[x], self, self );
				}
			}
			else if ( !Q_stricmp( "func_door", push_list[x]->classname ) && (push_list[x]->spawnflags&2/*MOVER_FORCE_ACTIVATE*/) )
			{//push/pull the door
				vec3_t	pos1, pos2;

				AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
				VectorNormalize( forward );
				VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
				gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
				if ( tr.entityNum != push_list[x]->s.number || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
				{//must be pointing right at it
					continue;
				}

				if ( VectorCompare( vec3_origin, push_list[x]->s.origin ) )
				{//does not have an origin brush, so pos1 & pos2 are relative to world origin, need to calc center
					VectorSubtract( push_list[x]->absmax, push_list[x]->absmin, size );
					VectorMA( push_list[x]->absmin, 0.5, size, center );
					if ( (push_list[x]->spawnflags&1) && push_list[x]->moverState == MOVER_POS1 )
					{//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
						VectorSubtract( center, push_list[x]->pos1, center );
					}
					else if ( !(push_list[x]->spawnflags&1) && push_list[x]->moverState == MOVER_POS2 )
					{//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
						VectorSubtract( center, push_list[x]->pos2, center );
					}
					VectorAdd( center, push_list[x]->pos1, pos1 );
					VectorAdd( center, push_list[x]->pos2, pos2 );
				}
				else
				{//actually has an origin, pos1 and pos2 are absolute
					VectorCopy( push_list[x]->currentOrigin, center );
					VectorCopy( push_list[x]->pos1, pos1 );
					VectorCopy( push_list[x]->pos2, pos2 );
				}

				if ( Distance( pos1, self->client->renderInfo.eyePoint ) < Distance( pos2, self->client->renderInfo.eyePoint ) )
				{//pos1 is closer
					if ( push_list[x]->moverState == MOVER_POS1 )
					{//at the closest pos
						if ( pull )
						{//trying to pull, but already at closest point, so screw it
							continue;
						}
					}
					else if ( push_list[x]->moverState == MOVER_POS2 )
					{//at farthest pos
						if ( !pull )
						{//trying to push, but already at farthest point, so screw it
							continue;
						}
					}
				}
				else
				{//pos2 is closer
					if ( push_list[x]->moverState == MOVER_POS1 )
					{//at the farthest pos
						if ( !pull )
						{//trying to push, but already at farthest point, so screw it
							continue;
						}
					}
					else if ( push_list[x]->moverState == MOVER_POS2 )
					{//at closest pos
						if ( pull )
						{//trying to pull, but already at closest point, so screw it
							continue;
						}
					}
				}
				GEntity_UseFunc( push_list[x], self, self );
			}
			else if ( push_list[x]->s.eType == ET_MISSILE/*thermal resting on ground*/
				|| push_list[x]->s.eType == ET_ITEM
				|| push_list[x]->e_ThinkFunc == thinkF_G_RunObject || Q_stricmp( "limb", push_list[x]->classname ) == 0 )
			{//general object, toss it
				vec3_t	pushDir, kvel;
				float	knockback = pull?0:200;
				float	mass = 200;
				if ( pull )
				{
					if ( push_list[x]->s.eType == ET_ITEM )
					{//pull it to a little higher point
						vec3_t	adjustedOrg;
						VectorCopy( self->currentOrigin, adjustedOrg );
						adjustedOrg[2] += self->maxs[2]/3;
						VectorSubtract( adjustedOrg, push_list[x]->currentOrigin, pushDir );
					}
					else
					{
						VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, pushDir );
					}
					knockback += VectorNormalize( pushDir );
					if ( knockback > 200 )
					{
						knockback = 200;
					}
					if ( push_list[x]->s.eType == ET_ITEM
						&& push_list[x]->item
						&& push_list[x]->item->giType == IT_HOLDABLE
						&& push_list[x]->item->giTag == INV_SECURITY_KEY )
					{//security keys are pulled with less enthusiasm
						if ( knockback > 100 )
						{
							knockback = 100;
						}
					}
					else if ( knockback > 200 )
					{
						knockback = 200;
					}
				}
				else
				{
					//HMM, if I have an auto-enemy & he's in front of me, push it toward him?
					VectorSubtract( push_list[x]->currentOrigin, self->currentOrigin, pushDir );
					knockback -= VectorNormalize( pushDir );
					if ( knockback < 100 )
					{
						knockback = 100;
					}
				}
				//FIXME: if pull a FL_FORCE_PULLABLE_ONLY, clear the flag, assuming it's no longer in solid?  or check?
				VectorCopy( push_list[x]->currentOrigin, push_list[x]->s.pos.trBase );
				push_list[x]->s.pos.trTime = level.time;								// move a bit on the very first frame
				if ( push_list[x]->s.pos.trType != TR_INTERPOLATE )
				{//don't do this to rolling missiles
					push_list[x]->s.pos.trType = TR_GRAVITY;
				}

				if ( push_list[x]->e_ThinkFunc == thinkF_G_RunObject && push_list[x]->physicsBounce )
				{
					mass = push_list[x]->physicsBounce;
				}
				if ( mass < 50 )
				{//???
					mass = 50;
				}
				if ( g_gravity->value > 0 )
				{
					VectorScale( pushDir, g_knockback->value * knockback / mass * 0.8, kvel );
					kvel[2] = pushDir[2] * g_knockback->value * knockback / mass * 1.5;
				}
				else
				{
					VectorScale( pushDir, g_knockback->value * knockback / mass, kvel );
				}
				VectorAdd( push_list[x]->s.pos.trDelta, kvel, push_list[x]->s.pos.trDelta );
				if ( g_gravity->value > 0 )
				{
					if ( push_list[x]->s.pos.trDelta[2] < knockback )
					{
						push_list[x]->s.pos.trDelta[2] = knockback;
					}
				}
				//no trDuration?
				if ( push_list[x]->e_ThinkFunc != thinkF_G_RunObject )
				{//objects spin themselves?
					//spin it
					//FIXME: messing with roll ruins the rotational center???
					push_list[x]->s.apos.trTime = level.time;
					push_list[x]->s.apos.trType = TR_LINEAR;
					VectorClear( push_list[x]->s.apos.trDelta );
					push_list[x]->s.apos.trDelta[1] = Q_irand( -800, 800 );
				}

				if ( Q_stricmp( "limb", push_list[x]->classname ) == 0 )
				{//make sure it runs it's physics
					push_list[x]->e_ThinkFunc = thinkF_LimbThink;
					push_list[x]->nextthink = level.time + FRAMETIME;
				}
				push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
				if ( push_list[x]->item && push_list[x]->item->giTag == INV_SECURITY_KEY )
				{
					AddSightEvent( player, push_list[x]->currentOrigin, 128, AEL_DISCOVERED );//security keys are more important
				}
				else
				{
					AddSightEvent( player, push_list[x]->currentOrigin, 128, AEL_SUSPICIOUS );//hmm... or should this always be discovered?
				}
			}
			else if ( push_list[x]->s.weapon == WP_TURRET
				&& !Q_stricmp( "PAS", push_list[x]->classname )
				&& push_list[x]->s.apos.trType == TR_STATIONARY )
			{//a portable turret
				WP_KnockdownTurret( self, push_list[x] );
			}
		}
		if ( pull )
		{
			if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
			{//at level 3, can pull multiple, so it costs more
				actualCost = forcePowerNeeded[FP_PUSH]*ent_count;
				if ( actualCost > 50 )
				{
					actualCost = 50;
				}
				else if ( actualCost < cost )
				{
					actualCost = cost;
				}
			}
			else
			{
				actualCost = cost;
			}
			WP_ForcePowerStart( self, FP_PULL, actualCost );
		}
		else
		{
			if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
			{//at level 3, can push multiple, so costs more
				actualCost = forcePowerNeeded[FP_PUSH]*ent_count;
				if ( actualCost > 50 )
				{
					actualCost = 50;
				}
				else if ( actualCost < cost )
				{
					actualCost = cost;
				}
			}
			else if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_1 )
			{//at level 3, can push multiple, so costs more
				actualCost = floor(forcePowerNeeded[FP_PUSH]*ent_count/1.5f);
				if ( actualCost > 50 )
				{
					actualCost = 50;
				}
				else if ( actualCost < cost )
				{
					actualCost = cost;
				}
			}
			else
			{
				actualCost = cost;
			}
			WP_ForcePowerStart( self, FP_PUSH, actualCost );
		}
	}
	else
	{//didn't push or pull anything?  don't penalize them too much
		if ( pull )
		{
			WP_ForcePowerStart( self, FP_PULL, 5 );
		}
		else
		{
			WP_ForcePowerStart( self, FP_PUSH, 5 );
		}
	}
	if ( self->NPC )
	{//NPCs can push more often
		//FIXME: vary by rank and game skill?
		self->client->ps.forcePowerDebounce[FP_PUSH] = level.time + 200;
	}
	else
	{
		self->client->ps.forcePowerDebounce[FP_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500;
	}
}

void ForceSpeed( gentity_t *self, int duration )
{
	if ( self->health <= 0 )
	{
		return;
	}
	if ( !WP_ForcePowerUsable( self, FP_SPEED, 0 ) )
	{
		return;
	}
	if ( self->client->ps.saberLockTime > level.time )
	{//FIXME: can this be a way to break out?
		return;
	}
	if ( !self->s.number && in_camera )
	{//player can't use force powers in cinematic
		return;
	}
	WP_ForcePowerStart( self, FP_SPEED, 0 );
	if ( duration )
	{
		self->client->ps.forcePowerDuration[FP_SPEED] = level.time + duration;
	}
	G_Sound( self, G_SoundIndex( "sound/weapons/force/speed.wav" ) );
}

void ForceHeal( gentity_t *self )
{
	if ( self->health <= 0 || self->client->ps.stats[STAT_MAX_HEALTH] <= self->health )
	{
		return;
	}

	if ( !WP_ForcePowerUsable( self, FP_HEAL, 20 ) )
	{//must have enough force power for at least 5 points of health
		return;
	}

	if ( self->painDebounceTime > level.time || (self->client->ps.weaponTime&&self->client->ps.weapon!=WP_NONE) )
	{//can't initiate a heal while taking pain or attacking
		return;
	}

	if ( self->client->ps.saberLockTime > level.time )
	{//FIXME: can this be a way to break out?
		return;
	}
	if ( !self->s.number && in_camera )
	{//player can't use force powers in cinematic
		return;
	}
	/*
	if ( self->client->ps.forcePowerLevel[FP_HEAL] > FORCE_LEVEL_2 )
	{//instant heal
		//no more than available force power
		int max = self->client->ps.forcePower;
		if ( max > MAX_FORCE_HEAL )
		{//no more than max allowed
			max = MAX_FORCE_HEAL;
		}
		if ( max > self->client->ps.stats[STAT_MAX_HEALTH] - self->health )
		{//no more than what's missing
			max = self->client->ps.stats[STAT_MAX_HEALTH] - self->health;
		}
		self->health += max;
		WP_ForcePowerDrain( self, FP_HEAL, max );
		G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d.mp3", Q_irand( 1, 4 ) ) );
	}
	else
	*/
	{
		//start health going up
		//NPC_SetAnim( self, SETANIM_TORSO, ?, SETANIM_FLAG_OVERRIDE );
		WP_ForcePowerStart( self, FP_HEAL, 0 );
		if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 )
		{//must meditate
			//FIXME: holster weapon (select WP_NONE?)
			//FIXME: BOTH_FORCEHEAL_START
			NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCEHEAL_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
			self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
			self->client->ps.saberBlocked = BLOCKED_NONE;
			self->client->ps.torsoAnimTimer = self->client->ps.legsAnimTimer = FORCE_HEAL_INTERVAL*MAX_FORCE_HEAL + 2000;//???
			if ( self->client->ps.saberActive )
			{
				self->client->ps.saberActive = qfalse;//turn off saber when meditating
				if ( self->client->playerTeam == TEAM_PLAYER )
				{
					G_SoundOnEnt( self, CHAN_WEAPON, "sound/weapons/saber/saberoff.wav" );
				}
				else
				{
					G_SoundOnEnt( self, CHAN_WEAPON, "sound/weapons/saber/enemy_saber_off.wav" );
				}
			}
		}
		else
		{//just a quick gesture
			/*
			//Can't get an anim that looks good...
			NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEHEAL_QUICK, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
			self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
			self->client->ps.saberBlocked = BLOCKED_NONE;
			*/
		}
	}

	//FIXME: always play healing effect
	G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/heal.mp3" );
}

extern void NPC_PlayConfusionSound( gentity_t *self );
extern void NPC_Jedi_PlayConfusionSound( gentity_t *self );
qboolean WP_CheckBreakControl( gentity_t *self )
{
	if ( !self )
	{
		return qfalse;
	}
	if ( !self->s.number )
	{//player
		if ( self->client && self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
		{//control-level
			if ( self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_WORLD )
			{//we are in a viewentity
				gentity_t *controlled = &g_entities[self->client->ps.viewEntity];
				if ( controlled->NPC && controlled->NPC->controlledTime > level.time )
				{//it is an NPC we controlled
					//clear it and return
					G_ClearViewEntity( self );
					return qtrue;
				}
			}
		}
	}
	else
	{//NPC
		if ( self->NPC && self->NPC->controlledTime > level.time )
		{//being controlled
			gentity_t *controller = &g_entities[0];
			if ( controller->client && controller->client->ps.viewEntity == self->s.number )
			{//we are being controlled by player
				if ( controller->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
				{//control-level mind trick
					//clear the control and return
					G_ClearViewEntity( controller );
					return qtrue;
				}
			}
		}
	}
	return qfalse;
}

void ForceTelepathy( gentity_t *self )
{
	trace_t	tr;
	vec3_t	end, forward;
	gentity_t	*traceEnt;
	qboolean	targetLive = qfalse;

	if ( WP_CheckBreakControl( self ) )
	{
		return;
	}
	if ( self->health <= 0 )
	{
		return;
	}
	//FIXME: if mind trick 3 and aiming at an enemy need more force power
	if ( !WP_ForcePowerUsable( self, FP_TELEPATHY, 0 ) )
	{
		return;
	}

	if ( self->client->ps.weaponTime >= 800 )
	{//just did one!
		return;
	}
	if ( self->client->ps.saberLockTime > level.time )
	{//FIXME: can this be a way to break out?
		return;
	}
	if ( !self->s.number && in_camera )
	{//player can't use force powers in cinematic
		return;
	}

	AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
	VectorNormalize( forward );
	VectorMA( self->client->renderInfo.eyePoint, 2048, forward, end );

	//Cause a distraction if enemy is not fighting
	gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_OPAQUE|CONTENTS_BODY, G2_NOCOLLIDE, 0 );
	if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
	{
		return;
	}

	traceEnt = &g_entities[tr.entityNum];

	if( traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE )
	{
		return;
	}

	if ( traceEnt && traceEnt->client  )
	{
		switch ( traceEnt->client->NPC_class )
		{
		case CLASS_GALAKMECH://cant grip him, he's in armor
		case CLASS_ATST://much too big to grip!
		//no droids either
		case CLASS_PROBE:
		case CLASS_GONK:
		case CLASS_R2D2:
		case CLASS_R5D2:
		case CLASS_MARK1:
		case CLASS_MARK2:
		case CLASS_MOUSE:
		case CLASS_SEEKER:
		case CLASS_REMOTE:
		case CLASS_PROTOCOL:
			break;
		default:
			targetLive = qtrue;
			break;
		}
	}
	if ( targetLive && traceEnt->NPC )
	{//hit an organic non-player
		if ( G_ActivateBehavior( traceEnt, BSET_MINDTRICK ) )
		{//activated a script on him
			//FIXME: do the visual sparkles effect on their heads, still?
			WP_ForcePowerStart( self, FP_TELEPATHY, 0 );
		}
		else if ( traceEnt->client->playerTeam != self->client->playerTeam )
		{//an enemy
			int override = 0;
			if ( (traceEnt->NPC->scriptFlags&SCF_NO_MIND_TRICK) )
			{
				if ( traceEnt->client->NPC_class == CLASS_GALAKMECH )
				{
					G_AddVoiceEvent( NPC, Q_irand( EV_CONFUSE1, EV_CONFUSE3 ), Q_irand( 3000, 5000 ) );
				}
			}
			else if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
			{//control them, even jedi
				G_SetViewEntity( self, traceEnt );
				traceEnt->NPC->controlledTime = level.time + 30000;
			}
			else if ( traceEnt->s.weapon != WP_SABER )
			{//haha!  Jedi aren't easily confused!
				if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_2 )
				{//turn them to our side
					//if mind trick 3 and aiming at an enemy need more force power
					override = 50;
					if ( self->client->ps.forcePower < 50 )
					{
						return;
					}
					if ( traceEnt->s.weapon != WP_NONE )
					{//don't charm people who aren't capable of fighting... like ugnaughts and droids
						if ( traceEnt->enemy )
						{
							G_ClearEnemy( traceEnt );
						}
						if ( traceEnt->NPC )
						{
							//traceEnt->NPC->tempBehavior = BS_FOLLOW_LEADER;
							traceEnt->client->leader = self;
						}
						//FIXME: maybe pick an enemy right here?
						team_t	saveTeam = traceEnt->client->enemyTeam;
						traceEnt->client->enemyTeam = traceEnt->client->playerTeam;
						traceEnt->client->playerTeam = saveTeam;
						//FIXME: need a *charmed* timer on this...?  Or do TEAM_PLAYERS assume that "confusion" means they should switch to team_enemy when done?
						traceEnt->NPC->charmedTime = level.time + mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]];
					}
				}
				else
				{//just confuse them
					//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?
					traceEnt->NPC->confusionTime = level.time + mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]];//confused for about 10 seconds
					NPC_PlayConfusionSound( traceEnt );
					if ( traceEnt->enemy )
					{
						G_ClearEnemy( traceEnt );
					}
				}
			}
			else
			{
				NPC_Jedi_PlayConfusionSound( traceEnt );
			}
			WP_ForcePowerStart( self, FP_TELEPATHY, override );
		}
		else if ( traceEnt->client->playerTeam == self->client->playerTeam )
		{//an ally
			//maybe just have him look at you?  Respond?  Take your enemy?
			if ( traceEnt->client->ps.pm_type < PM_DEAD && traceEnt->NPC!=NULL && !(traceEnt->NPC->scriptFlags&SCF_NO_RESPONSE) )
			{
				NPC_UseResponse( traceEnt, self, qfalse );
				WP_ForcePowerStart( self, FP_TELEPATHY, 1 );
			}
		}//NOTE: no effect on TEAM_NEUTRAL?
		vec3_t	eyeDir;
		AngleVectors( traceEnt->client->renderInfo.eyeAngles, eyeDir, NULL, NULL );
		VectorNormalize( eyeDir );
		G_PlayEffect( "force_touch", traceEnt->client->renderInfo.eyePoint, eyeDir );

		//make sure this plays and that you cannot press fire for about 1 second after this
		//FIXME: BOTH_FORCEMINDTRICK or BOTH_FORCEDISTRACT
		NPC_SetAnim( self, SETANIM_TORSO, BOTH_MINDTRICK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD );
		//FIXME: build-up or delay this until in proper part of anim
	}
	else
	{
		if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_1 && tr.fraction * 2048 > 64 )
		{//don't create a diversion less than 64 from you of if at power level 1
			//use distraction anim instead
			G_PlayEffect( G_EffectIndex( "force_touch" ), tr.endpos, tr.plane.normal );
			//FIXME: these events don't seem to always be picked up...?
			AddSoundEvent( self, tr.endpos, 512, AEL_SUSPICIOUS, qtrue );
			AddSightEvent( self, tr.endpos, 512, AEL_SUSPICIOUS, 50 );
			WP_ForcePowerStart( self, FP_TELEPATHY, 0 );
		}
		NPC_SetAnim( self, SETANIM_TORSO, BOTH_MINDTRICK2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD );
	}
	self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
	self->client->ps.saberBlocked = BLOCKED_NONE;
	self->client->ps.weaponTime = 1000;
	if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
	{
		self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
	}
}

void ForceGrip( gentity_t *self )
{//FIXME: make enemy Jedi able to use this
	trace_t	tr;
	vec3_t	end, forward;
	gentity_t	*traceEnt = NULL;

	if ( self->health <= 0 )
	{
		return;
	}
	if ( !self->s.number && (cg.zoomMode || in_camera) )
	{//can't force grip when zoomed in or in cinematic
		return;
	}
	if ( self->client->ps.leanofs )
	{//can't force-grip while leaning
		return;
	}

	if ( self->client->ps.forceGripEntityNum <= ENTITYNUM_WORLD )
	{//already gripping
		if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
		{
			self->client->ps.forcePowerDuration[FP_GRIP] = level.time + 100;
			self->client->ps.weaponTime = 1000;
			if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
			{
				self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
			}
		}
		return;
	}

	if ( !WP_ForcePowerUsable( self, FP_GRIP, 0 ) )
	{//can't use it right now
		return;
	}

	if ( self->client->ps.forcePower < 26 )
	{//need 20 to start, 6 to hold it for any decent amount of time...
		return;
	}

	if ( self->client->ps.weaponTime )
	{//busy
		return;
	}

	if ( self->client->ps.saberLockTime > level.time )
	{//FIXME: can this be a way to break out?
		return;
	}
	//Cause choking anim + health drain in ent in front of me
	NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEGRIP_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
	self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
	self->client->ps.saberBlocked = BLOCKED_NONE;

	self->client->ps.weaponTime = 1000;
	if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
	{
		self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
	}

	AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
	VectorNormalize( forward );
	VectorMA( self->client->renderInfo.handLPoint, FORCE_GRIP_DIST, forward, end );

	if ( self->enemy && (self->s.number || InFront( self->enemy->currentOrigin, self->client->renderInfo.eyePoint, self->client->ps.viewangles, 0.2f ) ) )
	{//NPCs can always lift enemy since we assume they're looking at them, players need to be facing the enemy
		if ( gi.inPVS( self->enemy->currentOrigin, self->client->renderInfo.eyePoint ) )
		{//must be in PVS
			gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, self->enemy->currentOrigin, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
			if ( tr.fraction == 1.0f || tr.entityNum == self->enemy->s.number )
			{//must have clear LOS
				traceEnt = self->enemy;
			}
		}
	}
	if ( !traceEnt )
	{//okay, trace straight ahead and see what's there
		gi.trace( &tr, self->client->renderInfo.handLPoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
		if ( tr.entityNum >= ENTITYNUM_WORLD || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
		{
			return;
		}

		traceEnt = &g_entities[tr.entityNum];
	}

	if ( !traceEnt || traceEnt == self/*???*/ || traceEnt->bmodel || (traceEnt->health <= 0 && traceEnt->takedamage) || (traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE) )
	{
		return;
	}

	if ( traceEnt->client )
	{
		if ( traceEnt->client->ps.forceJumpZStart )
		{//can't catch them in mid force jump - FIXME: maybe base it on velocity?
			return;
		}
		switch ( traceEnt->client->NPC_class )
		{
		case CLASS_GALAKMECH://cant grip him, he's in armor
			G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), Q_irand( 3000, 5000 ) );
			return;
			break;
		case CLASS_ATST://much too big to grip!
			return;
		//no droids either...?
		case CLASS_GONK:
		case CLASS_R2D2:
		case CLASS_R5D2:
		case CLASS_MARK1:
		case CLASS_MARK2:
		case CLASS_MOUSE://?
		case CLASS_PROTOCOL:
			//*sigh*... in JK3, you'll be able to grab and move *anything*...
			return;
			break;
		case CLASS_PROBE:
		case CLASS_SEEKER:
		case CLASS_REMOTE:
		case CLASS_SENTRY:
		case CLASS_INTERROGATOR:
			//*sigh*... in JK3, you'll be able to grab and move *anything*...
			return;
			break;
		case CLASS_DESANN://Desann cannot be gripped, he just pushes you back instantly
			Jedi_PlayDeflectSound( traceEnt );
			ForceThrow( traceEnt, qfalse );
			return;
			break;
		case CLASS_REBORN:
		case CLASS_SHADOWTROOPER:
		case CLASS_TAVION:
		case CLASS_JEDI:
		case CLASS_LUKE:
			if ( traceEnt->NPC && traceEnt->NPC->rank > RANK_CIVILIAN && self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
			{
				Jedi_PlayDeflectSound( traceEnt );
				ForceThrow( traceEnt, qfalse );
				return;
			}
			break;
		default:
			break;
		}
		if ( traceEnt->s.weapon == WP_EMPLACED_GUN )
		{//FIXME: maybe can pull them out?
			return;
		}
		if ( self->enemy && traceEnt != self->enemy && traceEnt->client->playerTeam == self->client->playerTeam )
		{//can't accidently grip your teammate in combat
			return;
		}
	}
	else
	{//can't grip non-clients... right?
		return;
	}
	WP_ForcePowerStart( self, FP_GRIP, 20 );
	//FIXME: rule out other things?
	//FIXME: Jedi resist, like the push and pull?
	self->client->ps.forceGripEntityNum = traceEnt->s.number;
	//if ( traceEnt->client )
	{
		G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
		if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 || traceEnt->s.weapon == WP_SABER )
		{//if we pick up & carry, drop their weap
			if ( traceEnt->s.weapon )
			{
				if ( traceEnt->s.weapon != WP_SABER )
				{
					WP_DropWeapon( traceEnt, NULL );
				}
				else
				{
					//turn it off?
					traceEnt->client->ps.saberActive = qfalse;
					G_SoundOnEnt( traceEnt, CHAN_WEAPON, "sound/weapons/saber/saberoffquick.wav" );
				}
			}
		}
		//else FIXME: need a one-armed choke if we're not on a high enough level to make them drop their gun
		VectorCopy( traceEnt->client->renderInfo.headPoint, self->client->ps.forceGripOrg );
	}
	/*
	else
	{
		VectorCopy( traceEnt->currentOrigin, self->client->ps.forceGripOrg );
	}
	*/
	self->client->ps.forceGripOrg[2] += 48;//FIXME: define?
	if ( self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
	{//just a duration
		self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 250;
		self->client->ps.forcePowerDuration[FP_GRIP] = level.time + 5000;
		traceEnt->s.loopSound = G_SoundIndex( "sound/weapons/force/grip.mp3" );
	}
	else
	{
		if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_2 )
		{//lifting sound?  or always?
		}
		//if ( traceEnt->s.number )
		{//picks them up for a second first
			self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 1000;
		}
		/*
		else
		{//player should take damage right away
			self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 250;
		}
		*/
		self->s.loopSound = G_SoundIndex( "sound/weapons/force/grip.mp3" );
	}
}

void ForceLightning( gentity_t *self )
{
	if ( self->health <= 0 )
	{
		return;
	}
	if ( !self->s.number && (cg.zoomMode || in_camera) )
	{//can't force lightning when zoomed in or in cinematic
		return;
	}
	if ( self->client->ps.leanofs )
	{//can't force-lightning while leaning
		return;
	}
	if ( self->client->ps.forcePower < 25 || !WP_ForcePowerUsable( self, FP_LIGHTNING, 0 ) )
	{
		return;
	}
	if ( self->client->ps.forcePowerDebounce[FP_LIGHTNING] > level.time )
	{//stops it while using it and also after using it, up to 3 second delay
		return;
	}
	if ( self->client->ps.saberLockTime > level.time )
	{//FIXME: can this be a way to break out?
		return;
	}
	//Shoot lightning from hand
	//make sure this plays and that you cannot press fire for about 1 second after this
	if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
	{
		NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
	}
	else
	{
		NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
	}
	self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
	self->client->ps.saberBlocked = BLOCKED_NONE;

	G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/lightning.wav" );
	if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
	{//short burst
		//G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/lightning.wav" );
	}
	else
	{//holding it
		self->s.loopSound = G_SoundIndex( "sound/weapons/force/lightning2.wav" );
	}

	//FIXME: build-up or delay this until in proper part of anim
	self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
	WP_ForcePowerStart( self, FP_LIGHTNING, self->client->ps.torsoAnimTimer );
}

void ForceLightningDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, float dist, float dot, vec3_t impactPoint )
{
	if( traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE )
	{
		return;
	}

	if ( traceEnt && traceEnt->takedamage )
	{
		if ( !traceEnt->client || traceEnt->client->playerTeam != self->client->playerTeam || self->enemy == traceEnt || traceEnt->enemy == self )
		{//an enemy or object
			int	dmg;
			if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 )
			{//more damage if closer and more in front
				dmg = 1;
				if ( dist < 100 )
				{
					dmg += 2;
				}
				else if ( dist < 200 )
				{
					dmg += 1;
				}
				if ( dot > 0.9f )
				{
					dmg += 2;
				}
				else if ( dot > 0.7f )
				{
					dmg += 1;
				}
			}
			else
			{
				dmg = Q_irand( 1, 3 );//*self->client->ps.forcePowerLevel[FP_LIGHTNING];
			}
			if ( traceEnt->client && traceEnt->health > 0 && ( traceEnt->client->NPC_class == CLASS_DESANN || traceEnt->client->NPC_class == CLASS_LUKE ) )
			{//Luke and Desann can shield themselves from the attack
				//FIXME: shield effect or something?
				int parts;
				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 ) )
				{//if on a surface and not in a spin or flip, play full body resist
					parts = SETANIM_BOTH;
				}
				else
				{//play resist just in torso
					parts = SETANIM_TORSO;
				}
				NPC_SetAnim( traceEnt, parts, BOTH_RESISTPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
				Jedi_PlayDeflectSound( traceEnt );
				dmg = 0;
			}
			else if ( traceEnt->s.weapon == WP_SABER )
			{
				if ( Q_irand( 0, 1 ) )
				{//jedi less likely to be damaged
					dmg = 0;
				}
				else
				{
					dmg = 1;
				}
			}
			if ( traceEnt && traceEnt->client && traceEnt->client->NPC_class == CLASS_GALAK )
			{
				if ( traceEnt->client->ps.powerups[PW_GALAK_SHIELD] )
				{//has shield up
					dmg = 0;
				}
			}
			G_Damage( traceEnt, self, self, dir, impactPoint, dmg, 0, MOD_ELECTROCUTE );
			if ( traceEnt->client )
			{
				if ( !Q_irand( 0, 2 ) )
				{
					G_Sound( traceEnt, G_SoundIndex( va( "sound/weapons/force/lightninghit%d.wav", Q_irand( 1, 3 ) ) ) );
				}
				traceEnt->s.powerups |= ( 1 << PW_SHOCKED );

				// If we are dead or we are a bot, we can do the full version
				class_t npc_class = traceEnt->client->NPC_class;
				if ( traceEnt->health <= 0 || ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE ||
					 npc_class == CLASS_MOUSE || npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_REMOTE ||
					 npc_class == CLASS_R5D2 || npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 ||
					 npc_class == CLASS_MARK2 || npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST ) ||
					 npc_class == CLASS_SENTRY )
				{
					traceEnt->client->ps.powerups[PW_SHOCKED] = level.time + 4000;
				}
				else //short version
				{
					traceEnt->client->ps.powerups[PW_SHOCKED] = level.time + 500;
				}
			}
		}
	}
}

void ForceShootLightning( gentity_t *self )
{
	trace_t	tr;
	vec3_t	end, forward;
	gentity_t	*traceEnt;

	if ( self->health <= 0 )
	{
		return;
	}
	if ( !self->s.number && cg.zoomMode )
	{//can't force lightning when zoomed in
		return;
	}

	AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
	VectorNormalize( forward );

	//FIXME: if lightning hits water, do water-only-flagged radius damage from that point
	if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 )
	{//arc
		vec3_t	center, mins, maxs, dir, ent_org, size, v;
		float	radius = 512, dot, dist;
		gentity_t	*entityList[MAX_GENTITIES];
		int		e, numListedEntities, i;

		VectorCopy( self->currentOrigin, center );
		for ( i = 0 ; i < 3 ; i++ )
		{
			mins[i] = center[i] - radius;
			maxs[i] = center[i] + radius;
		}
		numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );

		for ( e = 0 ; e < numListedEntities ; e++ )
		{
			traceEnt = entityList[e];

			if ( !traceEnt )
				continue;
			if ( traceEnt == self )
				continue;
			if ( traceEnt->owner == self && traceEnt->s.weapon != WP_THERMAL )//can push your own thermals
				continue;
			if ( !traceEnt->inuse )
				continue;
			if ( !traceEnt->takedamage )
				continue;
			if ( traceEnt->health <= 0 )//no torturing corpses
				continue;
			//this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
			// find the distance from the edge of the bounding box
			for ( i = 0 ; i < 3 ; i++ )
			{
				if ( center[i] < traceEnt->absmin[i] )
				{
					v[i] = traceEnt->absmin[i] - center[i];
				} else if ( center[i] > traceEnt->absmax[i] )
				{
					v[i] = center[i] - traceEnt->absmax[i];
				} else
				{
					v[i] = 0;
				}
			}

			VectorSubtract( traceEnt->absmax, traceEnt->absmin, size );
			VectorMA( traceEnt->absmin, 0.5, size, ent_org );

			//see if they're in front of me
			//must be within the forward cone
			VectorSubtract( ent_org, center, dir );
			VectorNormalize( dir );
			if ( (dot = DotProduct( dir, forward )) < 0.5 )
				continue;

			//must be close enough
			dist = VectorLength( v );
			if ( dist >= radius )
			{
				continue;
			}

			//in PVS?
			if ( !traceEnt->bmodel && !gi.inPVS( ent_org, self->client->renderInfo.handLPoint ) )
			{//must be in PVS
				continue;
			}

			//Now check and see if we can actually hit it
			gi.trace( &tr, self->client->renderInfo.handLPoint, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
			if ( tr.fraction < 1.0f && tr.entityNum != traceEnt->s.number )
			{//must have clear LOS
				continue;
			}

			// ok, we are within the radius, add us to the incoming list
			//FIXME: maybe add up the ents and do more damage the less ents there are
			//		as if we're spreading out the damage?
			ForceLightningDamage( self, traceEnt, dir, dist, dot, ent_org );
		}

	}
	else
	{//trace-line
		int ignore = self->s.number;
		int traces = 0;
		vec3_t	start;

		VectorCopy( self->client->renderInfo.handLPoint, start );
		VectorMA( self->client->renderInfo.handLPoint, 2048, forward, end );

		while ( traces < 10 )
		{//need to loop this in case we hit a Jedi who dodges the shot
			gi.trace( &tr, start, vec3_origin, vec3_origin, end, ignore, MASK_SHOT, G2_RETURNONHIT, 0 );
			if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
			{
				return;
			}

			traceEnt = &g_entities[tr.entityNum];
			//NOTE: only NPCs do this auto-dodge
			if ( traceEnt && traceEnt->s.number && traceEnt->s.weapon == WP_SABER )//&& traceEnt->NPC
			{//FIXME: need a more reliable way to know we hit a jedi?
				if ( !Jedi_DodgeEvasion( traceEnt, self, &tr, HL_NONE ) )
				{//act like we didn't even hit him
					VectorCopy( tr.endpos, start );
					ignore = tr.entityNum;
					traces++;
					continue;
				}
			}
			//a Jedi is not dodging this shot
			break;
		}

		traceEnt = &g_entities[tr.entityNum];
		ForceLightningDamage( self, traceEnt, forward, 0, 0, tr.endpos );
	}
}

void ForceJumpCharge( gentity_t *self, usercmd_t *ucmd )
{
	float forceJumpChargeInterval = forceJumpStrength[0] / (FORCE_JUMP_CHARGE_TIME/FRAMETIME);

	if ( self->health <= 0 )
	{
		return;
	}
	if ( !self->s.number && cg.zoomMode )
	{//can't force jump when zoomed in
		return;
	}

	//need to play sound
	if ( !self->client->ps.forceJumpCharge )
	{//FIXME: this should last only as long as the actual charge-up
		G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jumpbuild.wav" );
	}
	//Increment
	self->client->ps.forceJumpCharge += forceJumpChargeInterval;

	//clamp to max strength for current level
	if ( self->client->ps.forceJumpCharge > forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]] )
	{
		self->client->ps.forceJumpCharge = forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]];
	}

	//clamp to max available force power
	if ( self->client->ps.forceJumpCharge/forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME)*forcePowerNeeded[FP_LEVITATION] > self->client->ps.forcePower )
	{//can't use more than you have
		self->client->ps.forceJumpCharge = self->client->ps.forcePower*forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME);
	}
	//FIXME: a simple tap should always do at least a normal height's jump?
}

int WP_GetVelocityForForceJump( gentity_t *self, vec3_t jumpVel, usercmd_t *ucmd )
{
	float pushFwd = 0, pushRt = 0;
	vec3_t	view, forward, right;
	VectorCopy( self->client->ps.viewangles, view );
	view[0] = 0;
	AngleVectors( view, forward, right, NULL );
	if ( ucmd->forwardmove && ucmd->rightmove )
	{
		if ( ucmd->forwardmove > 0 )
		{
			pushFwd = 50;
		}
		else
		{
			pushFwd = -50;
		}
		if ( ucmd->rightmove > 0 )
		{
			pushRt = 50;
		}
		else
		{
			pushRt = -50;
		}
	}
	else if ( ucmd->forwardmove || ucmd->rightmove )
	{
		if ( ucmd->forwardmove > 0 )
		{
			pushFwd = 100;
		}
		else if ( ucmd->forwardmove < 0 )
		{
			pushFwd = -100;
		}
		else if ( ucmd->rightmove > 0 )
		{
			pushRt = 100;
		}
		else if ( ucmd->rightmove < 0 )
		{
			pushRt = -100;
		}
	}
	VectorMA( self->client->ps.velocity, pushFwd, forward, jumpVel );
	VectorMA( self->client->ps.velocity, pushRt, right, jumpVel );
	jumpVel[2] += self->client->ps.forceJumpCharge;//forceJumpStrength;
	if ( pushFwd > 0 && self->client->ps.forceJumpCharge > 200 )
	{
		return FJ_FORWARD;
	}
	else if ( pushFwd < 0 && self->client->ps.forceJumpCharge > 200 )
	{
		return FJ_BACKWARD;
	}
	else if ( pushRt > 0 && self->client->ps.forceJumpCharge > 200 )
	{
		return FJ_RIGHT;
	}
	else if ( pushRt < 0 && self->client->ps.forceJumpCharge > 200 )
	{
		return FJ_LEFT;
	}
	else
	{//FIXME: jump straight up anim
		return FJ_UP;
	}
}

void ForceJump( gentity_t *self, usercmd_t *ucmd )
{
	if ( self->client->ps.forcePowerDuration[FP_LEVITATION] > level.time )
	{
		return;
	}
	if ( !WP_ForcePowerUsable( self, FP_LEVITATION, 0 ) )
	{
		return;
	}
	if ( self->s.groundEntityNum == ENTITYNUM_NONE )
	{
		return;
	}
	if ( self->client->ps.pm_flags&PMF_JUMP_HELD )
	{
		return;
	}
	if ( self->health <= 0 )
	{
		return;
	}
	if ( !self->s.number && (cg.zoomMode || in_camera) )
	{//can't force jump when zoomed in or in cinematic
		return;
	}
	if ( self->client->ps.saberLockTime > level.time )
	{//FIXME: can this be a way to break out?
		return;
	}

	G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" );

	float forceJumpChargeInterval = forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]]/(FORCE_JUMP_CHARGE_TIME/FRAMETIME);

	int anim;
	vec3_t	jumpVel;

	switch( WP_GetVelocityForForceJump( self, jumpVel, ucmd ) )
	{
	case FJ_FORWARD:
		if ( self->NPC && self->NPC->rank != RANK_CREWMAN && self->NPC->rank <= RANK_LT_JG )
		{//can't do acrobatics
			anim = BOTH_FORCEJUMP1;
		}
		else
		{
			anim = BOTH_FLIP_F;
		}
		break;
	case FJ_BACKWARD:
		if ( self->NPC && self->NPC->rank != RANK_CREWMAN && self->NPC->rank <= RANK_LT_JG )
		{//can't do acrobatics
			anim = BOTH_FORCEJUMPBACK1;
		}
		else
		{
			anim = BOTH_FLIP_B;
		}
		break;
	case FJ_RIGHT:
		if ( self->NPC && self->NPC->rank != RANK_CREWMAN && self->NPC->rank <= RANK_LT_JG )
		{//can't do acrobatics
			anim = BOTH_FORCEJUMPRIGHT1;
		}
		else
		{
			anim = BOTH_FLIP_R;
		}
		break;
	case FJ_LEFT:
		if ( self->NPC && self->NPC->rank != RANK_CREWMAN && self->NPC->rank <= RANK_LT_JG )
		{//can't do acrobatics
			anim = BOTH_FORCEJUMPLEFT1;
		}
		else
		{
			anim = BOTH_FLIP_L;
		}
		break;
	default:
	case FJ_UP:
		anim = BOTH_JUMP1;
		break;
	}

	int	parts = SETANIM_BOTH;
	if ( self->client->ps.weaponTime )
	{//FIXME: really only care if we're in a saber attack anim.. maybe trail length?
		parts = SETANIM_LEGS;
	}

	NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );

	//FIXME: sound effect
	self->client->ps.forceJumpZStart = self->currentOrigin[2];//remember this for when we land
	VectorCopy( jumpVel, self->client->ps.velocity );
	//wasn't allowing them to attack when jumping, but that was annoying
	//self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;

	WP_ForcePowerStart( self, FP_LEVITATION, self->client->ps.forceJumpCharge/forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME)*forcePowerNeeded[FP_LEVITATION] );
	//self->client->ps.forcePowerDuration[FP_LEVITATION] = level.time + self->client->ps.weaponTime;
	self->client->ps.forceJumpCharge = 0;
}

void WP_ForcePowerRegenerate( gentity_t *self, int overrideAmt )
{
	if ( !self->client )
	{
		return;
	}

	if ( self->client->ps.forcePower < self->client->ps.forcePowerMax )
	{
		if ( overrideAmt )
		{
			self->client->ps.forcePower += overrideAmt;
		}
		else
		{
			self->client->ps.forcePower++;
		}
		if ( self->client->ps.forcePower > self->client->ps.forcePowerMax )
		{
			self->client->ps.forcePower = self->client->ps.forcePowerMax;
		}
	}
}

void WP_ForcePowerDrain( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
{
	if ( self->NPC )
	{//For now, NPCs have infinite force power
		return;
	}
	//take away the power
	int	drain = overrideAmt;
	if ( !drain )
	{
		drain = forcePowerNeeded[forcePower];
	}
	if ( !drain )
	{
		return;
	}
	self->client->ps.forcePower -= drain;
	if ( self->client->ps.forcePower < 0 )
	{
		self->client->ps.forcePower = 0;
	}
}

void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
{
	int	duration = 0;

	//FIXME: debounce some of these

	//and it in
	//set up duration time
	switch( (int)forcePower )
	{
	case FP_HEAL:
		self->client->ps.forcePowersActive |= ( 1 << forcePower );
		self->client->ps.forceHealCount = 0;
		break;
	case FP_LEVITATION:
		self->client->ps.forcePowersActive |= ( 1 << forcePower );
		break;
	case FP_SPEED:
		//duration is always 5 seconds, player time
		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...
		self->client->ps.forcePowersActive |= ( 1 << forcePower );
		self->s.loopSound = G_SoundIndex( "sound/weapons/force/speedloop.wav" );
		break;
	case FP_PUSH:
		break;
	case FP_PULL:
		break;
	case FP_TELEPATHY:
		break;
	case FP_GRIP:
		duration = 1000;
		self->client->ps.forcePowersActive |= ( 1 << forcePower );
		break;
	case FP_LIGHTNING:
		duration = overrideAmt;
		overrideAmt = 0;
		self->client->ps.forcePowersActive |= ( 1 << forcePower );
		break;
	default:
		break;
	}
	if ( duration )
	{
		self->client->ps.forcePowerDuration[forcePower] = level.time + duration;
	}
	else
	{
		self->client->ps.forcePowerDuration[forcePower] = 0;
	}
	self->client->ps.forcePowerDebounce[forcePower] = 0;

	WP_ForcePowerDrain( self, forcePower, overrideAmt );

	if ( !self->s.number )
	{
		self->client->sess.missionStats.forceUsed[(int)forcePower]++;
	}
}

qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
{
	if ( forcePower == FP_LEVITATION )
	{
		return qtrue;
	}
	int	drain = overrideAmt?overrideAmt:forcePowerNeeded[forcePower];
	if ( !drain )
	{
		return qtrue;
	}
	if ( self->client->ps.forcePower < drain )
	{
		//G_AddEvent( self, EV_NOAMMO, 0 );
		return qfalse;
	}
	return qtrue;
}

extern void CG_PlayerLockedWeaponSpeech( int jumping );
qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
{

	if ( !(self->client->ps.forcePowersKnown & ( 1 << forcePower )) )
	{//don't know this power
		return qfalse;
	}

	if ( self->client->ps.forcePowerLevel[forcePower] <= 0 )
	{//can't use this power
		return qfalse;
	}

	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
	{
		CG_PlayerLockedWeaponSpeech( qfalse );
		return qfalse;
	}

	if ( (self->client->ps.forcePowersActive & ( 1 << forcePower )) )
	{//already using this power
		return qfalse;
	}
	/*
	if ( !self->client->ps.forcePowerLevel[(int)(forcePower)] )
	{
		return qfalse;
	}
	*/
	if ( self->client->NPC_class == CLASS_ATST )
	{//Doh!  No force powers in an AT-ST!
		return qfalse;
	}
	if ( self->client->ps.vehicleModel != 0 )
	{//Doh!  No force powers when flying a vehicle!
		return qfalse;
	}
	if ( self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_WORLD )
	{//Doh!  No force powers when controlling an NPC
		return qfalse;
	}
	if ( self->client->ps.eFlags & EF_LOCKED_TO_WEAPON )
	{//Doh!  No force powers when in an emplaced gun!
		return qfalse;
	}

	return WP_ForcePowerAvailable( self, forcePower, overrideAmt );
}

void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower )
{
	gentity_t	*gripEnt;

	self->client->ps.forcePowersActive &= ~( 1 << forcePower );

	switch( (int)forcePower )
	{
	case FP_HEAL:
		//if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_3 )
		{//wasn't an instant heal and heal is now done
			if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 )
			{//if in meditation pose, must come out of it
				//FIXME: BOTH_FORCEHEAL_STOP
				if ( self->client->ps.legsAnim == BOTH_FORCEHEAL_START )
				{
					NPC_SetAnim( self, SETANIM_LEGS, BOTH_FORCEHEAL_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
				}
				if ( self->client->ps.torsoAnim == BOTH_FORCEHEAL_START )
				{
					NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEHEAL_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
				}
				self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
				self->client->ps.saberBlocked = BLOCKED_NONE;
			}
		}
		break;
	case FP_LEVITATION:
		self->client->ps.forcePowerDebounce[FP_LEVITATION] = 0;
		break;
	case FP_SPEED:
		if ( !self->s.number )
		{//player using force speed
			if ( g_timescale->value != 1.0 )
			{
				gi.cvar_set("timescale", "1");
			}
		}
		//FIXME: reset my current anim, keeping current frame, but with proper anim speed
		//		otherwise, the anim will continue playing at high speed
		self->s.loopSound = 0;
		break;
	case FP_PUSH:
		break;
	case FP_PULL:
		break;
	case FP_TELEPATHY:
		break;
	case FP_GRIP:
		if ( self->client->ps.forceGripEntityNum < ENTITYNUM_WORLD )
		{
			gripEnt = &g_entities[self->client->ps.forceGripEntityNum];
			if ( gripEnt )
			{
				gripEnt->s.loopSound = 0;
				if ( gripEnt->client )
				{
					gripEnt->client->ps.eFlags &= ~EF_FORCE_GRIPPED;
					if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
					{//sanity-cap the velocity
						float gripVel = VectorNormalize( gripEnt->client->ps.velocity );
						if ( gripVel > 500.0f )
						{
							gripVel = 500.0f;
						}
						VectorScale( gripEnt->client->ps.velocity, gripVel, gripEnt->client->ps.velocity );
					}

					//FIXME: they probably dropped their weapon, should we make them flee?  Or should AI handle no-weapon behavior?
					if ( gripEnt->health > 0 )
					{
						int	holdTime = 500;
						G_AddEvent( gripEnt, EV_WATER_CLEAR, 0 );
						if ( gripEnt->client->ps.forcePowerDebounce[FP_PUSH] > level.time )
						{//they probably pushed out of it
							holdTime = 0;
						}
						else if ( gripEnt->s.weapon == WP_SABER )
						{//jedi recover faster
							holdTime = self->client->ps.forcePowerLevel[FP_GRIP]*200;
						}
						else
						{
							holdTime = self->client->ps.forcePowerLevel[FP_GRIP]*500;
						}
						//stop the anims soon, keep them locked in place for a bit
						if ( gripEnt->client->ps.torsoAnim == BOTH_CHOKE1 || gripEnt->client->ps.torsoAnim == BOTH_CHOKE3 )
						{//stop choking anim on torso
							if ( gripEnt->client->ps.torsoAnimTimer > holdTime )
							{
								gripEnt->client->ps.torsoAnimTimer = holdTime;
							}
						}
						if ( gripEnt->client->ps.legsAnim == BOTH_CHOKE1 || gripEnt->client->ps.legsAnim == BOTH_CHOKE3 )
						{//stop choking anim on legs
							gripEnt->client->ps.legsAnimTimer = 0;
							if ( holdTime )
							{
								//lock them in place for a bit
								gripEnt->client->ps.pm_time = gripEnt->client->ps.torsoAnimTimer;
								gripEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
								if ( gripEnt->s.number )
								{//NPC
									gripEnt->painDebounceTime = level.time + gripEnt->client->ps.torsoAnimTimer;
								}
								else
								{//player
									gripEnt->aimDebounceTime = level.time + gripEnt->client->ps.torsoAnimTimer;
								}
							}
						}
						if ( gripEnt->NPC )
						{
							if ( !(gripEnt->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
							{//not falling to their death
								gripEnt->NPC->nextBStateThink = level.time + holdTime;
							}
							//if still alive after stopped gripping, let them wake others up
							G_AngerAlert( gripEnt );
						}
					}
				}
				else
				{
					gripEnt->s.eFlags &= ~EF_FORCE_GRIPPED;
					if ( gripEnt->s.eType == ET_MISSILE )
					{//continue normal movement
						if ( gripEnt->s.weapon == WP_THERMAL )
						{
							gripEnt->s.pos.trType = TR_INTERPOLATE;
						}
						else
						{
							gripEnt->s.pos.trType = TR_LINEAR;//FIXME: what about gravity-effected projectiles?
						}
						VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase );
						gripEnt->s.pos.trTime = level.time;
					}
					else
					{//drop it
						gripEnt->e_ThinkFunc = thinkF_G_RunObject;
						gripEnt->nextthink = level.time + FRAMETIME;
						gripEnt->s.pos.trType = TR_GRAVITY;
						VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase );
						gripEnt->s.pos.trTime = level.time;
					}
				}
			}
			self->s.loopSound = 0;
			self->client->ps.forceGripEntityNum = ENTITYNUM_NONE;
		}
		if ( self->client->ps.torsoAnim == BOTH_FORCEGRIP_HOLD )
		{
			NPC_SetAnim( self, BOTH_FORCEGRIP_RELEASE, BOTH_FORCELIGHTNING_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
		}
		break;
	case FP_LIGHTNING:
		if ( self->client->ps.torsoAnim == BOTH_FORCELIGHTNING_HOLD
			|| self->client->ps.torsoAnim == BOTH_FORCELIGHTNING_START )
		{
			NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
		}
		if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
		{//don't do it again for 3 seconds, minimum... FIXME: this should be automatic once regeneration is slower (normal)
			self->client->ps.forcePowerDebounce[FP_LIGHTNING] = level.time + 3000;//FIXME: define?
		}
		else
		{//stop the looping sound
			self->client->ps.forcePowerDebounce[FP_LIGHTNING] = level.time + 1000;//FIXME: define?
			self->s.loopSound = 0;
		}
		break;
	default:
		break;
	}
}

extern qboolean PM_ForceJumpingUp( gentity_t *gent );
static void WP_ForcePowerRun( gentity_t *self, forcePowers_t forcePower, usercmd_t *cmd )
{
	float				speed, newSpeed;
	gentity_t			*gripEnt;
	vec3_t				angles, dir, gripOrg, gripEntOrg;
	float				dist;
	extern usercmd_t	ucmd;

	switch( (int)forcePower )
	{
	case FP_HEAL:
		if ( self->client->ps.forceHealCount >= MAX_FORCE_HEAL || self->health >= self->client->ps.stats[STAT_MAX_HEALTH] )
		{//fully healed or used up all 25
			if ( !Q3_TaskIDPending( self, TID_CHAN_VOICE ) )
			{
				G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d.mp3", Q_irand( 1, 4 ) ) );
			}
			WP_ForcePowerStop( self, forcePower );
		}
		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) ) )
		{//attacked or was hit while healing...
			//stop healing
			WP_ForcePowerStop( self, forcePower );
		}
		else if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 && ( cmd->rightmove || cmd->forwardmove || cmd->upmove > 0 ) )
		{//moved while healing... FIXME: also, in WP_ForcePowerStart, stop healing if any other force power is used
			//stop healing
			WP_ForcePowerStop( self, forcePower );
		}
		else if ( self->client->ps.forcePowerDebounce[FP_HEAL] < level.time )
		{//time to heal again
			if ( WP_ForcePowerAvailable( self, forcePower, 4 ) )
			{//have available power
				self->health++;
				self->client->ps.forceHealCount++;
				if ( self->client->ps.forcePowerLevel[FP_HEAL] > FORCE_LEVEL_2 )
				{
					self->client->ps.forcePowerDebounce[FP_HEAL] = level.time + 50;
				}
				else
				{
					self->client->ps.forcePowerDebounce[FP_HEAL] = level.time + FORCE_HEAL_INTERVAL;
				}
				WP_ForcePowerDrain( self, forcePower, 4 );
			}
			else
			{//stop
				WP_ForcePowerStop( self, forcePower );
			}
		}
		break;
	case FP_LEVITATION:
		if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE && !self->client->ps.forceJumpZStart )
		{//done with jump
			WP_ForcePowerStop( self, forcePower );
		}
		else
		{
			if ( PM_ForceJumpingUp( self ) )
			{//holding jump in air
				if ( cmd->upmove > 10 )
				{//still trying to go up
					if ( WP_ForcePowerAvailable( self, FP_LEVITATION, 1 ) )
					{
						if ( self->client->ps.forcePowerDebounce[FP_LEVITATION] < level.time )
						{
							WP_ForcePowerDrain( self, FP_LEVITATION, 5 );
							self->client->ps.forcePowerDebounce[FP_LEVITATION] = level.time + 100;
						}
						self->client->ps.forcePowersActive |= ( 1 << FP_LEVITATION );
						self->client->ps.forceJumpCharge = 1;//just used as a flag for the player, cleared when he lands
					}
					else
					{//cut the jump short
						WP_ForcePowerStop( self, forcePower );
					}
				}
				else
				{//cut the jump short
					WP_ForcePowerStop( self, forcePower );
				}
			}
			else
			{
				WP_ForcePowerStop( self, forcePower );
			}
		}
		break;
	case FP_SPEED:
		speed = forceSpeedValue[self->client->ps.forcePowerLevel[FP_SPEED]];
		if ( !self->s.number )
		{//player using force speed
			gi.cvar_set("timescale", va("%4.2f", speed));
			if ( g_timescale->value > speed )
			{
				newSpeed = g_timescale->value - 0.05;
				if ( newSpeed < speed )
				{
					newSpeed = speed;
				}
				gi.cvar_set("timescale", va("%4.2f", newSpeed));
			}
		}
		break;
	case FP_PUSH:
		break;
	case FP_PULL:
		break;
	case FP_TELEPATHY:
		break;
	case FP_GRIP:
		if ( !WP_ForcePowerAvailable( self, FP_GRIP, 0 )
			|| (self->client->ps.forcePowerLevel[FP_GRIP]>FORCE_LEVEL_1&&!self->s.number&&!(cmd->buttons&BUTTON_FORCEGRIP)) )
		{
			WP_ForcePowerStop( self, FP_GRIP );
			return;
		}
		else if ( self->client->ps.forceGripEntityNum >= 0 && self->client->ps.forceGripEntityNum < ENTITYNUM_WORLD )
		{
			gripEnt = &g_entities[self->client->ps.forceGripEntityNum];

			if ( !gripEnt || (gripEnt->health <= 0&&gripEnt->takedamage) )//FIXME: what about things that never had health or lose takedamage when they die?
			{//either invalid ent, or dead ent
				WP_ForcePowerStop( self, FP_GRIP );
				return;
			}
			else if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_1
				&& gripEnt->client
				&& gripEnt->client->ps.groundEntityNum == ENTITYNUM_NONE )
			{
				WP_ForcePowerStop( self, FP_GRIP );
				return;
			}
			else if ( gripEnt->s.weapon == WP_SABER && gripEnt->NPC && gripEnt->client && gripEnt->client->ps.forcePowersKnown&(1<<FP_PUSH) && !Q_irand( 0, 100-(gripEnt->NPC->stats.evasion*10)-(g_spskill->integer*10) ) )
			{//a jedi who broke free FIXME: maybe have some minimum grip length- a reaction time?
				ForceThrow( gripEnt, qfalse );
				//FIXME: I need to go into some pushed back anim...
				WP_ForcePowerStop( self, FP_GRIP );
				return;
			}
			else
			{
				if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
				{//holding it
					NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEGRIP_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
				}
				//get their org
				VectorCopy( self->client->ps.viewangles, angles );
				angles[0] -= 10;
				AngleVectors( angles, dir, NULL, NULL );
				if ( gripEnt->client )
				{//move
					VectorCopy( gripEnt->client->renderInfo.headPoint, gripEntOrg );
				}
				else
				{
					VectorCopy( gripEnt->currentOrigin, gripEntOrg );
				}

				//how far are they
				dist = Distance( self->client->renderInfo.handLPoint, gripEntOrg );
				if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_2 &&
					(!InFront( gripEntOrg, self->client->renderInfo.handLPoint, self->client->ps.viewangles, 0.3f ) ||
						DistanceSquared( gripEntOrg, self->client->renderInfo.handLPoint ) > FORCE_GRIP_DIST_SQUARED))
				{//must face them
					WP_ForcePowerStop( self, FP_GRIP );
					return;
				}

				//check for lift or carry
				if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 )
				{//carry
					//cap dist
					if ( dist > 256 )
					{
						dist = 256;
					}
					else if ( dist < 128 )
					{
						dist = 128;
					}
					VectorMA( self->client->renderInfo.handLPoint, dist, dir, gripOrg );
				}
				else if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
				{//just lift
					VectorCopy( self->client->ps.forceGripOrg, gripOrg );
				}
				else
				{
					VectorCopy( gripEnt->currentOrigin, gripOrg );
				}
				//now move them
				if ( gripEnt->client )
				{
					if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
					{//level 1 just holds them
						VectorSubtract( gripOrg, gripEntOrg, gripEnt->client->ps.velocity );
						if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 )
						{//level 2 just lifts them
							float gripDist = VectorNormalize( gripEnt->client->ps.velocity )/3.0f;
							if ( gripDist < 5.0f )
							{
								gripDist = 5.0f;
							}
							VectorScale( gripEnt->client->ps.velocity, (gripDist*gripDist), gripEnt->client->ps.velocity );
						}
					}
					//stop them from thinking
					gripEnt->client->ps.pm_time = 2000;
					gripEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
					if ( gripEnt->NPC )
					{
						if ( !(gripEnt->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
						{//not falling to their death
							gripEnt->NPC->nextBStateThink = level.time + 2000;
						}
						if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
						{//level 1 just holds them
							vectoangles( dir, angles );
							gripEnt->NPC->desiredYaw = AngleNormalize180(angles[YAW]+180);
							gripEnt->NPC->desiredPitch = -angles[PITCH];
							SaveNPCGlobals();
							SetNPCGlobals( gripEnt );
							NPC_UpdateAngles( qtrue, qtrue );
							gripEnt->NPC->last_ucmd.angles[0] = ucmd.angles[0];
							gripEnt->NPC->last_ucmd.angles[1] = ucmd.angles[1];
							gripEnt->NPC->last_ucmd.angles[2] = ucmd.angles[2];
							RestoreNPCGlobals();
							//FIXME: why does he turn back to his original angles once he dies or is let go?
						}
					}
					else if ( !gripEnt->s.number )
					{
						//vectoangles( dir, angles );
						//gripEnt->client->ps.viewangles[0] = -angles[0];
						//gripEnt->client->ps.viewangles[1] = AngleNormalize180(angles[YAW]+180);
						gripEnt->enemy = self;
						NPC_SetLookTarget( gripEnt, self->s.number, level.time+1000 );
					}

					gripEnt->client->ps.eFlags |= EF_FORCE_GRIPPED;
					//dammit!  Make sure that saber stays off!
					gripEnt->client->ps.saberActive = qfalse;
				}
				else
				{//move
					if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
					{//level 1 just holds them
						VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase );
						VectorSubtract( gripOrg, gripEntOrg, gripEnt->s.pos.trDelta );
						if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 )
						{//level 2 just lifts them
							VectorScale( gripEnt->s.pos.trDelta, 10, gripEnt->s.pos.trDelta );
						}
						gripEnt->s.pos.trType = TR_LINEAR;
						gripEnt->s.pos.trTime = level.time;
					}

					gripEnt->s.eFlags |= EF_FORCE_GRIPPED;
				}

				//Shouldn't this be discovered?
				//AddSightEvent( self, gripOrg, 128, AEL_DANGER, 20 );
				AddSightEvent( self, gripOrg, 128, AEL_DISCOVERED, 20 );

				if ( self->client->ps.forcePowerDebounce[FP_GRIP] < level.time )
				{
					//GEntity_PainFunc( gripEnt, self, self, gripOrg, 0, MOD_CRUSH );
					gripEnt->painDebounceTime = 0;
					G_Damage( gripEnt, self, self, dir, gripOrg, forceGripDamage[self->client->ps.forcePowerLevel[FP_GRIP]], DAMAGE_NO_ARMOR, MOD_CRUSH );//MOD_???
					if ( gripEnt->s.number )
					{
						if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 )
						{//do damage faster at level 3
							self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 150, 750 );
						}
						else
						{
							self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 250, 1000 );
						}
					}
					else
					{//player takes damage faster
						self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 100, 600 );
					}
					if ( forceGripDamage[self->client->ps.forcePowerLevel[FP_GRIP]] > 0 )
					{//no damage at level 1
						WP_ForcePowerDrain( self, FP_GRIP, 3 );
					}
				}
				else
				{
					//WP_ForcePowerDrain( self, FP_GRIP, 0 );
					if ( !gripEnt->enemy )
					{
						G_SetEnemy( gripEnt, self );
					}
				}
				if ( gripEnt->client && gripEnt->health > 0 )
				{
					int anim = BOTH_CHOKE3; //left-handed choke
					if ( gripEnt->client->ps.weapon == WP_NONE || gripEnt->client->ps.weapon == WP_MELEE )
					{
						anim = BOTH_CHOKE1; //two-handed choke
					}
					if ( self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
					{//still on ground, only set anim on torso
						NPC_SetAnim( gripEnt, SETANIM_TORSO, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
					}
					else
					{//in air, set on whole body
						NPC_SetAnim( gripEnt, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
					}
					gripEnt->painDebounceTime = level.time + 2000;
				}
			}
		}
		break;
	case FP_LIGHTNING:
		if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 )
		{//higher than level 1
			if ( cmd->buttons & BUTTON_FORCE_LIGHTNING )
			{//holding it keeps it going
				self->client->ps.forcePowerDuration[FP_LIGHTNING] = level.time + 500;
				if ( self->client->ps.torsoAnim == BOTH_FORCELIGHTNING_START )
				{
					if ( !self->client->ps.torsoAnimTimer )
					{
						NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
					}
					else
					{
						NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
					}
				}
				else
				{
					NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
				}
			}
		}
		if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) )
		{
			WP_ForcePowerStop( self, forcePower );
		}
		else
		{
			ForceShootLightning( self );
			WP_ForcePowerDrain( self, forcePower, 0 );
		}
		break;
	default:
		break;
	}
}

void WP_ForcePowersUpdate( gentity_t *self, usercmd_t *ucmd )
{
	qboolean	usingForce = qfalse;
	int			i;
	//see if any force powers are running
	if ( !self )
	{
		return;
	}
	if ( !self->client )
	{
		return;
	}

	if ( self->health <= 0 )
	{//if dead, deactivate any active force powers
		for ( i = 0; i < NUM_FORCE_POWERS; i++ )
		{
			if ( self->client->ps.forcePowerDuration[i] || (self->client->ps.forcePowersActive&( 1 << i )) )
			{
				WP_ForcePowerStop( self, (forcePowers_t)i );
				self->client->ps.forcePowerDuration[i] = 0;
			}
		}
		return;
	}

	if ( !self->s.number )
	{//player uses different kind of force-jump
	}
	else
	{
		/*
		if ( ucmd->buttons & BUTTON_FORCEJUMP )
		{//just charging up
			ForceJumpCharge( self, ucmd );
		}
		else */
		if ( self->client->ps.forceJumpCharge )
		{//let go of charge button, have charge
			//if leave the ground by some other means, cancel the force jump so we don't suddenly jump when we land.
			if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE
				&& !PM_SwimmingAnim( self->client->ps.legsAnim ) )
			{//FIXME: stop sound?
				//self->client->ps.forceJumpCharge = 0;
				//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.
			}
			else
			{//still on ground, so jump
				ForceJump( self, ucmd );
				return;
			}
		}
	}

	if ( ucmd->buttons & BUTTON_FORCEGRIP )
	{
		ForceGrip( self );
	}

	if ( ucmd->buttons & BUTTON_FORCE_LIGHTNING )
	{
		ForceLightning( self );
	}

	for ( i = 0; i < NUM_FORCE_POWERS; i++ )
	{
		if ( self->client->ps.forcePowerDuration[i] )
		{
			if ( self->client->ps.forcePowerDuration[i] < level.time )
			{
				if ( (self->client->ps.forcePowersActive&( 1 << i )) )
				{//turn it off
					WP_ForcePowerStop( self, (forcePowers_t)i );
				}
				self->client->ps.forcePowerDuration[i] = 0;
			}
		}
		if ( (self->client->ps.forcePowersActive&( 1 << i )) )
		{
			usingForce = qtrue;
			WP_ForcePowerRun( self, (forcePowers_t)i, ucmd );
		}
	}
	if ( self->client->ps.saberInFlight )
	{//don't regen force power while throwing saber
		if ( self->client->ps.saberEntityNum < ENTITYNUM_NONE && self->client->ps.saberEntityNum > 0 )//player is 0
		{//
			if ( &g_entities[self->client->ps.saberEntityNum] != NULL && g_entities[self->client->ps.saberEntityNum].s.pos.trType == TR_LINEAR )
			{//fell to the ground and we're trying to pull it back
				usingForce = qtrue;
			}
		}
	}
	if ( !usingForce )
	{//when not using the force, regenerate at 10 points per second
		if ( self->client->ps.forcePowerRegenDebounceTime < level.time )
		{
			WP_ForcePowerRegenerate( self, 0 );
			self->client->ps.forcePowerRegenDebounceTime = level.time + 100;
		}
	}
}

void WP_InitForcePowers( gentity_t *ent )
{
	if ( !ent || !ent->client )
	{
		return;
	}

	if( ent->client->NPC_class == CLASS_TAVION ||
		ent->client->NPC_class == CLASS_REBORN ||
		ent->client->NPC_class == CLASS_DESANN ||
		ent->client->NPC_class == CLASS_SHADOWTROOPER ||
		ent->client->NPC_class == CLASS_JEDI ||
		ent->client->NPC_class == CLASS_LUKE )
	{//an NPC jedi
		ent->client->ps.forcePower = ent->client->ps.forcePowerMax = FORCE_POWER_MAX;
		ent->client->ps.forcePowerRegenDebounceTime = 0;
		ent->client->ps.forceGripEntityNum = ENTITYNUM_NONE;

		if ( ent->client->NPC_class == CLASS_DESANN )
		{
			ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_GRIP )|( 1 << FP_LIGHTNING)|( 1 << FP_SABERTHROW)|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
		}
		else if ( ent->client->NPC_class == CLASS_LUKE )
		{
			ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_SABERTHROW)|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
		}
		else if ( ent->client->NPC_class == CLASS_TAVION || ( ent->client->NPC_class == CLASS_JEDI && ent->NPC->rank == RANK_COMMANDER ) )
		{//Tavia or trainer Jedi
			ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_SABERTHROW)|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_2;
			ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
			if ( ent->client->NPC_class == CLASS_TAVION )
			{
				ent->client->ps.forcePowersKnown |= ( 1 << FP_LIGHTNING)|( 1 << FP_GRIP );
				ent->client->ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_2;
				ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_2;
			}
		}
		else if ( ent->client->NPC_class == CLASS_SHADOWTROOPER )
		{//Shadow Trooper
			ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_SABERTHROW)|( 1 << FP_GRIP )|( 1 << FP_LIGHTNING)|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
			ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_2;
			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_2;
			ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_2;
			ent->client->ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_1;
			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
		}
		else if ( ent->NPC->rank == RANK_LT || ent->client->NPC_class == CLASS_JEDI )
		{//Reborn Boss or ally Jedi
			ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_SABERTHROW)|( 1 << FP_GRIP )|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
			ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_2;
			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_2;
			ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_2;
			ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_1;
			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_2;
			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
			if ( ent->client->NPC_class != CLASS_JEDI )
			{
				ent->client->ps.forcePowersKnown |= ( 1 << FP_GRIP );
				ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_2;
			}
		}
		else if ( ent->NPC->rank == RANK_LT_JG )
		{//Reborn Fencer
			ent->client->ps.forcePowersKnown = ( 1 << FP_PUSH )|( 1 << FP_SABERTHROW )|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
			ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_2;//FIXME: maybe make him only use it in defense- to throw away grenades
			ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_2;
			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_1;
			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_2;
		}
		else if ( ent->NPC->rank == RANK_ENSIGN )
		{//Force User
			ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
			ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_2;
			ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_1;
			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_1;
			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_1;
			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_1;
			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_1;
		}
		else if ( ent->NPC->rank == RANK_CREWMAN )
		{//Acrobat
			ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_2;
			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_1;
			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_1;
			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_1;
			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_2;
		}
		else if ( ent->NPC->rank == RANK_CIVILIAN )
		{//Grunt (NOTE: grunt turns slower and has less health)
			ent->client->ps.forcePowersKnown = ( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
			ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_1;
			ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_1;
			ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_1;
			ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_1;
		}
	}
	else
	{//player
		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 );
		ent->client->ps.forcePower = ent->client->ps.forcePowerMax = FORCE_POWER_MAX;
		ent->client->ps.forcePowerRegenDebounceTime = 0;
		ent->client->ps.forcePowerLevel[FP_HEAL] = FORCE_LEVEL_2;
		ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_2;
		ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_1;
		ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_1;
		ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_2;
		ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_2;
		ent->client->ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_1;
		ent->client->ps.forcePowerLevel[FP_TELEPATHY] = FORCE_LEVEL_2;
		ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
		ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
		if ( ent->NPC )
		{//???
			ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_3;
		}
		else
		{
			ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_2;
		}
		ent->client->ps.forceGripEntityNum = ENTITYNUM_NONE;
	}
}
