/*
   Copyright (C) 1997-2001 Id Software, Inc.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   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, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

 */
// g_combat.c

#include "g_local.h"
#include "g_gametypes.h"

//============
//G_ModToAmmo
//Helper function for weapon stat system
//============
int G_ModToAmmo( int mod )
{
	if( mod == MOD_GUNBLADE_W )
		return AMMO_WEAK_GUNBLADE;
	else if( mod == MOD_GUNBLADE_S )
		return AMMO_CELLS;
	//else if( mod == MOD_SHOCKWAVE_W )
	//	return AMMO_WEAK_WAVES;
	//else if( mod == MOD_SHOCKWAVE_S )
	//	return AMMO_WAVES;
	else if( mod == MOD_RIOTGUN_W )
		return AMMO_WEAK_SHELLS;
	else if( mod == MOD_RIOTGUN_S )
		return AMMO_SHELLS;
	else if( mod == MOD_GRENADE_W || mod == MOD_GRENADE_SPLASH_W )
		return AMMO_WEAK_GRENADES;
	else if( mod == MOD_GRENADE_S || mod == MOD_GRENADE_SPLASH_S )
		return AMMO_GRENADES;
	else if( mod == MOD_ROCKET_W || mod == MOD_ROCKET_SPLASH_W )
		return AMMO_WEAK_ROCKETS;
	else if( mod == MOD_ROCKET_S || mod == MOD_ROCKET_SPLASH_S )
		return AMMO_ROCKETS;
	else if( mod == MOD_PLASMA_W || mod == MOD_PLASMA_SPLASH_W )
		return AMMO_WEAK_PLASMA;
	else if( mod == MOD_PLASMA_S || mod == MOD_PLASMA_SPLASH_S )
		return AMMO_PLASMA;
	else if( mod == MOD_ELECTROBOLT_W )
		return AMMO_WEAK_BOLTS;
	else if( mod == MOD_ELECTROBOLT_S )
		return AMMO_BOLTS;
	else if( mod == MOD_INSTAGUN_W )
		return AMMO_WEAK_INSTAS;
	else if( mod == MOD_INSTAGUN_S )
		return AMMO_INSTAS;
	else if( mod == MOD_LASERGUN_W )
		return AMMO_WEAK_LASERS;
	else if( mod == MOD_LASERGUN_S )
		return AMMO_LASERS;
	else
		return AMMO_NONE;
}

//============
//CanDamage
//
//Returns true if the inflictor can directly damage the target.  Used for
//explosions and melee attacks.
//============
qboolean CanDamage( edict_t *targ, edict_t *inflictor )
{
	vec3_t dest;
	trace_t	trace;

	// bmodels need special checking because their origin is 0,0,0
	if( targ->movetype == MOVETYPE_PUSH )
	{
		// NOT FOR PLAYERS only for entities that can push the players
		VectorAdd( targ->r.absmin, targ->r.absmax, dest );
		VectorScale( dest, 0.5, dest );
		G_Trace( &trace, inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID );
		if( trace.fraction == 1.0 )
			return qtrue;
		if( &game.edicts[trace.ent] == targ )
			return qtrue;
		return qfalse;
	}

	// This is for players
	G_Trace( &trace, inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID );
	if( trace.fraction == 1.0 )
		return qtrue;

	VectorCopy( targ->s.origin, dest );
	dest[0] += 15.0;
	dest[1] += 15.0;
	G_Trace( &trace, inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID );
	if( trace.fraction == 1.0 )
		return qtrue;

	VectorCopy( targ->s.origin, dest );
	dest[0] += 15.0;
	dest[1] -= 15.0;
	G_Trace( &trace, inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID );
	if( trace.fraction == 1.0 )
		return qtrue;

	VectorCopy( targ->s.origin, dest );
	dest[0] -= 15.0;
	dest[1] += 15.0;
	G_Trace( &trace, inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID );
	if( trace.fraction == 1.0 )
		return qtrue;

	VectorCopy( targ->s.origin, dest );
	dest[0] -= 15.0;
	dest[1] -= 15.0;
	G_Trace( &trace, inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID );
	if( trace.fraction == 1.0 )
		return qtrue;

	return qfalse;
}

static qboolean CanSplashDamage( edict_t *targ, edict_t *inflictor, cplane_t *plane )
{
	vec3_t dest, origin;
	trace_t	trace;
	int solidmask = MASK_SOLID;

	if( !targ ) return qfalse;

	if( plane == NULL )
	{
		VectorCopy( inflictor->s.origin, origin );
	}
	else
	{
		VectorMA( inflictor->s.origin, 3, plane->normal, origin );
	}

	// bmodels need special checking because their origin is 0,0,0
	if( targ->movetype == MOVETYPE_PUSH )
	{
		// NOT FOR PLAYERS only for entities that can push the players
		VectorAdd( targ->r.absmin, targ->r.absmax, dest );
		VectorScale( dest, 0.5, dest );
		G_Trace4D( &trace, origin, vec3_origin, vec3_origin, dest, inflictor, solidmask, inflictor->timeDelta );
		if( trace.fraction == 1.0 || trace.ent == ENTNUM( targ ) )
			return qtrue;

		return qfalse;
	}

	// This is for players
	G_Trace4D( &trace, origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, solidmask, inflictor->timeDelta );
	if( trace.fraction == 1.0 || trace.ent == ENTNUM( targ ) )
		return qtrue;

	VectorCopy( targ->s.origin, dest );
	dest[0] += 15.0;
	dest[1] += 15.0;
	G_Trace4D( &trace, origin, vec3_origin, vec3_origin, dest, inflictor, solidmask, inflictor->timeDelta );
	if( trace.fraction == 1.0 || trace.ent == ENTNUM( targ ) )
		return qtrue;

	VectorCopy( targ->s.origin, dest );
	dest[0] += 15.0;
	dest[1] -= 15.0;
	G_Trace4D( &trace, origin, vec3_origin, vec3_origin, dest, inflictor, solidmask, inflictor->timeDelta );
	if( trace.fraction == 1.0 || trace.ent == ENTNUM( targ ) )
		return qtrue;

	VectorCopy( targ->s.origin, dest );
	dest[0] -= 15.0;
	dest[1] += 15.0;
	G_Trace4D( &trace, origin, vec3_origin, vec3_origin, dest, inflictor, solidmask, inflictor->timeDelta );
	if( trace.fraction == 1.0 || trace.ent == ENTNUM( targ ) )
		return qtrue;

	VectorCopy( targ->s.origin, dest );
	dest[0] -= 15.0;
	dest[1] -= 15.0;
	G_Trace4D( &trace, origin, vec3_origin, vec3_origin, dest, inflictor, solidmask, inflictor->timeDelta );
	if( trace.fraction == 1.0 || trace.ent == ENTNUM( targ ) )
		return qtrue;

	return qfalse;
}

//============
//Killed
//============
void Killed( edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point, int mod )
{
	if( targ->health < -999 )
		targ->health = -999;

	targ->enemy = attacker;

	if( targ != attacker && targ->r.client && !targ->deadflag )
	{
		if( G_IsTeamDamage( targ, attacker ) )
			attacker->snap.teamkill = qtrue;
		else
			attacker->snap.kill = qtrue;
	}

	if( targ->r.client && attacker->r.client )
		G_AwardPlayerKilled( targ, inflictor, attacker, mod );

	//newgametypes[start]
	if( G_Gametype_Killed( targ, inflictor, attacker, damage, point, mod ) )
		return;
	//newgametypes[end]

	if( ( targ->r.svflags & SVF_MONSTER ) && ( targ->deadflag != DEAD_DEAD ) )
	{
		//		targ->r.svflags |= SVF_CORPSE;		// now treat as a different content type
	}

	if( targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE )
	{ // doors, triggers, etc
		targ->die( targ, inflictor, attacker, damage, point );
		return;
	}

	targ->die( targ, inflictor, attacker, damage, point );
}

static float CheckArmor( edict_t *ent, float damage, int dflags )
{
	gclient_t *client;
	gitem_t	*armor;
	float save;

	if( !damage )
		return 0;

	client = ent->r.client;
	if( !client )
		return 0;

	if( dflags & DAMAGE_NO_ARMOR )
		return 0;

	if( client->armortag == ARMOR_NONE )
		return 0;

	armor = game.items[client->armortag];

	save = min( damage, client->armor / ARMOR_DEGRADATION ) * ( (gitem_armor_t *)armor->info )->protection;
	client->armor -= min( ARMOR_DEGRADATION * damage, client->armor );

	if( client->armor == 0 )
		client->armortag = ARMOR_NONE;
	// if using the constant protection/degradation settings, convert the armortag
	// according to armor count, so the more representative color is shown at HUD
	else
	{
		/*	gitem_armor_t *armorinfo;
		    client->armortag = ARMOR_GA;
		    armorinfo = (gitem_armor_t *)game.items[ARMOR_YA]->info;
		    if( client->armor > armorinfo->base_count )
		   	client->armortag = ARMOR_YA;
		    if( client->armor > armorinfo->max_count )
		   	client->armortag = ARMOR_RA;
		   }*/
		gitem_armor_t *armorinfo;
		armorinfo = (gitem_armor_t *)game.items[ARMOR_YA]->info;
		// nip: Handle RA differently
		if( client->armortag == ARMOR_RA )
		{
			if( client->armor < armorinfo->base_count )
				client->armortag = ARMOR_YA;
			else
				client->armortag = ARMOR_RA;
		}
		else
		{
			if( client->armor < armorinfo->base_count )
				client->armortag = ARMOR_GA;
			if( client->armor > armorinfo->base_count )
				client->armortag = ARMOR_YA;
			if( client->armor > armorinfo->max_count )
				client->armortag = ARMOR_RA;
		}
	}

	return save;
}

//================
//G_IsTeamDamage - moveme to g_gameteams?
//================
qboolean G_IsTeamDamage( edict_t *targ, edict_t *attacker )
{
	if( !GS_Gametype_IsTeamBased( game.gametype ) )
		return qfalse;

	assert( targ && attacker );

	if( targ->s.team && attacker->s.team &&
	    targ->s.team == attacker->s.team &&
	    targ != attacker )
		return qtrue;

	return qfalse;
}

//================
//G_BlendFrameDamage
//================
static void G_BlendFrameDamage( edict_t *ent, float damage, float *old_damage, vec3_t point, vec3_t dir, vec3_t old_point, vec3_t old_dir )
{
	vec3_t offset;
	float frac;
	int i;

	if( !dir )
	{

	}

	if( !point )
		VectorSet( offset, 0, 0, ent->viewheight );
	else
		VectorSubtract( point, ent->s.origin, offset );

	VectorNormalize( dir );

	if( *old_damage == 0 )
	{
		VectorCopy( offset, old_point );
		VectorCopy( dir, old_dir );
		*old_damage = damage;
		return;
	}

	frac = damage / ( damage + *old_damage );
	for( i = 0; i < 3; i++ )
	{
		old_point[i] = ( old_point[i] * ( 1.0f - frac ) ) + offset[i] * frac;
		old_dir[i] = ( old_dir[i] * ( 1.0f - frac ) ) + dir[i] * frac;
	}
	*old_damage += damage;
}

static void T_KnockBackPush( edict_t *targ, vec3_t dir, int knockback )
{
	float mass = 50.0;
	float push;

	if( targ->flags & FL_NO_KNOCKBACK )
		knockback = 0;

	if( knockback <= 0 )
		return;

	if( ( targ->movetype == MOVETYPE_NONE ) ||
	   ( targ->movetype == MOVETYPE_PUSH ) ||
	   ( targ->movetype == MOVETYPE_STOP ) ||
	   ( targ->movetype == MOVETYPE_BOUNCE ) )
		return;

	if( targ->r.client )
	{
		targ->r.client->ps.pmove.stats[PM_STAT_KNOCKBACK] = 2 * knockback;
		//G_Printf( "KNOCK TIME:%i\n", targ->r.client->ps.pmove.stats[PM_STAT_KNOCKBACK] );
		clamp( targ->r.client->ps.pmove.stats[PM_STAT_KNOCKBACK], 50, 200 );
	}

	if( targ->mass > 50 )
		mass = targ->mass;

	push = 1000.0f * ( (float)knockback / mass );

	VectorNormalizeFast( dir );
	VectorMA( targ->velocity, push, dir, targ->velocity );
}

//============
//T_Damage
//
//targ		entity that is being damaged
//inflictor	entity that is causing the damage
//attacker	entity that caused the inflictor to damage targ
//	example: targ=monster, inflictor=rocket, attacker=player
//
//dir			direction of the attack
//point		point at which the damage is being inflicted
//normal		normal vector from that point
//damage		amount of damage being inflicted
//knockback	force to be applied against targ as a result of the damage
//
//dflags		these flags are used to control how T_Damage works
//	DAMAGE_RADIUS			damage was indirect (from a nearby explosion)
//	DAMAGE_NO_ARMOR			armor does not protect from this damage
//	DAMAGE_ENERGY			damage is from an energy based weapon
//	DAMAGE_NO_KNOCKBACK		do not affect velocity, just view angles
//	DAMAGE_BULLET			damage is from a bullet (used for ricochets)
//	DAMAGE_NO_PROTECTION	kills godmode, armor, everything
//============
void T_Damage( edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, float damage, float knockback, int dflags, int mod )
{
	gclient_t *client;
	float take;
	float save;
	float asave;

	if( !targ->takedamage )
		return;

	if( !attacker )
	{
		attacker = world;
		mod = MOD_TRIGGER_HURT;
	}

	meansOfDeath = mod;

	client = targ->r.client;

	// Cgg - race mode: players don't interact with one another
	if( game.gametype == GAMETYPE_RACE )
	{
		if( attacker->r.client && targ->r.client && attacker != targ )
			return;
	}

	// push
	if( !( dflags & DAMAGE_NO_KNOCKBACK ) )
		T_KnockBackPush( targ, dir, knockback );

	take = damage;
	save = 0;

	// check for cases where damage is protected
	if( !( dflags & DAMAGE_NO_PROTECTION ) )
	{
		// check for godmode
		if( targ->flags & FL_GODMODE )
		{
			take = 0;
			save = damage;
		}
		// never damage in timeout
		else if( gtimeout.active )
		{
			take = save = 0;
		} // ca has self splash damage disabled
		else if( game.gametype == GAMETYPE_CA && ( dflags & DAMAGE_RADIUS ) && attacker == targ && g_ca_allow_selfdamage->integer == 0 )
		{
			take = save = 0;
#ifdef DUEL_ARENA
		} // da has self splash damage disabled
		  // FIXME: use g_ca_allow_selfdamage?
		else if( game.gametype == GAMETYPE_DA && ( dflags & DAMAGE_RADIUS ) && attacker == targ && g_ca_allow_selfdamage->integer == 0 )
		{
			take = save = 0;
#endif
		} // don't get damage from players in race
		else if( ( game.gametype == GAMETYPE_RACE ) && attacker->r.client )
		{
			take = save = 0;
		}
		// team damage avoidance
		else if( G_IsTeamDamage( targ, attacker ) && !G_Gametype_CanTeamDamage( dflags ) )
		{
			take = save = 0;
		}
		// apply warShell powerup protection
		else if( targ->r.client && targ->r.client->shell_timeout > level.time )
		{
			take = ( damage * 0.25f );
			save = damage - take;
			// todo : add protection sound
		}
	}

	asave = CheckArmor( targ, take, dflags );
	take -= asave;

	//treat cheat/powerup savings the same as armor
	asave += save;

	// ca teamdamage is a exception. If its cvar equals 2 it only damages armor
	if( game.gametype == GAMETYPE_CA && G_IsTeamDamage( targ, attacker ) && !( dflags & DAMAGE_NO_PROTECTION ) && g_ca_allow_teamdamage->integer == 2 )
	{
		take = 0;
	}

#ifdef DUEL_ARENA
	// da teamdamage is a exception. If its cvar equals 2 it only damages armor
	// FIXME: use g_ca_allow_teamdamage?
	if( game.gametype == GAMETYPE_DA && G_IsTeamDamage( targ, attacker ) && !( dflags & DAMAGE_NO_PROTECTION ) && g_ca_allow_teamdamage->integer == 2 )
	{
		take = 0;
	}
#endif

	// ca selfdamage is a exception. If its cvar equals 2 it only damages armor
	if( game.gametype == GAMETYPE_CA && !( dflags & DAMAGE_NO_PROTECTION ) && ( dflags & DAMAGE_RADIUS ) && attacker == targ && g_ca_allow_selfdamage->integer == 2 )
	{
		take = 0;
	}

#ifdef DUEL_ARENA
	// da selfdamage is a exception. If its cvar equals 2 it only damages armor
	// FIXME: use g_ca_allow_teamdamage?
	// FIXME: use this to get a cleander code?
	if( game.gametype == GAMETYPE_CA && !( dflags & DAMAGE_NO_PROTECTION ) && ( dflags & DAMAGE_RADIUS ) && attacker == targ && g_ca_allow_selfdamage->integer == 2 )
	{
		take = 0;
	}
#endif

	// APPLY THE DAMAGES

	if( !take && !asave )
		return;

	G_Gametype_CTF_CheckHurtCarrier( targ, attacker );

	// do the damage
	if( take > 0 )
	{
		// adding damage given/received to stats
		if( attacker != targ ) // dont count self-damage cause it just adds the same to both stats
		{
			if( mod != MOD_TELEFRAG )
			{
				if( attacker->r.client )
					attacker->r.client->resp.total_damage_given += take + asave;
				if( client )
					client->resp.total_damage_received += take + asave;
			}
		}

		//G_SIMPLE_DAMAGE_FEEDBACK [start]
		if( targ->movetype != MOVETYPE_PUSH )
		{                               // doors don't bleed
			vec3_t dorigin, ddir;

			if( inflictor == world && mod == MOD_FALLING )
			{                                    // it's fall damage
				targ->snap.damage_fall += take + save;
			}

			if( attacker )
				VectorSubtract( targ->s.origin, attacker->s.origin, ddir );
			else if( inflictor )
				VectorSubtract( targ->s.origin, inflictor->s.origin, ddir );
			else
				VectorCopy( normal, ddir );

			if( point[0] != 0.0f || point[1] != 0.0f || point[2] != 0.0f )
				VectorCopy( point, dorigin );
			else
				VectorSet( dorigin,
				           targ->s.origin[0],
				           targ->s.origin[1],
				           targ->s.origin[2] + targ->viewheight );

			G_BlendFrameDamage( targ, take, &targ->snap.damage_taken, dorigin, ddir, targ->snap.damage_at, targ->snap.damage_dir );
			G_BlendFrameDamage( targ, save, &targ->snap.damage_saved, dorigin, ddir, targ->snap.damage_at, targ->snap.damage_dir );
		}
		//[end] of simple damage feedback

		targ->health = targ->health - take;

		// add damage done to stats
		if( !G_IsTeamDamage( targ, attacker ) && attacker != targ && G_ModToAmmo( mod ) != AMMO_NONE && targ->r.client )
		{
			attacker->r.client->resp.accuracy_hits[G_ModToAmmo( mod )-AMMO_CELLS]++;
			attacker->r.client->resp.accuracy_damage[G_ModToAmmo( mod )-AMMO_CELLS] += damage;
			G_AwardPlayerHit( targ, attacker, mod );
		}


		// wsw : jal : accumulate given damage for hit sounds
		if( ( take || asave ) && targ != attacker && targ->r.client && !targ->deadflag )
		{
			if( attacker )
			{
				if( G_IsTeamDamage( targ, attacker ) )
					attacker->snap.damageteam_given += take + asave; // we want to know how good our hit was, so saved also matters
				else
					attacker->snap.damage_given += take + asave;
			}
		}

		if( G_IsDead( targ ) )
		{
			if( ( targ->r.svflags & SVF_MONSTER ) || ( client ) )
				targ->flags |= FL_NO_KNOCKBACK;
			Killed( targ, inflictor, attacker, HEALTH_TO_INT( take ), point, mod );
			return;
		}
	}

	if( client )
	{
		if( !( targ->flags & FL_GODMODE ) && ( take ) )
			targ->pain( targ, attacker, knockback, take );
	}
	else if( take )
	{
		if( targ->pain )
			targ->pain( targ, attacker, knockback, take );
	}
}

float G_KnockbackPushFrac( vec3_t pushorigin, vec3_t origin, vec3_t mins, vec3_t maxs, vec3_t pushdir, float pushradius )
{
	vec3_t boxcenter = { 0, 0, 0 };
	float distance = 0.0f;
	int i;
	//	float	innerradius;
	//	float	outerradius;
	float pushFrac;

	float cyl_radius;
	float g_distance;
	float h_distance;
	float cyl_h;

	if( !pushradius )
		return 0;

	//cyl_radius = 16.0f ;	// cylinder radius
	cyl_radius = ( maxs[0] + maxs[1] - mins[0] - mins[1] ) * 0.25;
	cyl_h =	( maxs[2] - mins[2] ); //cylinder height

	//	innerradius = (maxs[0] + maxs[1] - mins[0] - mins[1]) * 0.25;
	//	outerradius = (sqrt( maxs[0]*maxs[0] + maxs[1]*maxs[1] ) + sqrt( mins[0]*mins[0] + mins[1]*mins[1] )) * 0.5;

	// find center of the box/cylinder
	for( i = 0; i < 3; i++ )
		boxcenter[i] = origin[i] + maxs[i] + mins[i];


	// find box radius to explosion origin direction
	VectorSubtract( boxcenter, pushorigin, pushdir );

	g_distance = sqrt( pushdir[0]*pushdir[0] + pushdir[1]*pushdir[1] ); // distance on the virtual ground
	h_distance = fabs( pushdir[2] );                                // corrected distance in height

	if( ( h_distance <= cyl_h/2 ) || ( g_distance > cyl_radius ) )
	{
		distance = g_distance - cyl_radius;
	}
	if( ( h_distance > cyl_h/2 ) || ( g_distance <= cyl_radius ) )
	{
		distance = h_distance - cyl_h/2;
	}
	if( ( h_distance > cyl_h/2 ) || ( g_distance > cyl_radius ) )
	{
		distance = sqrt( ( g_distance - cyl_radius )*( g_distance - cyl_radius ) + ( h_distance - cyl_h/2 )*( h_distance - cyl_h/2 ) );
	}

	//distance = VectorNormalize( pushdir );
	//distance -= ((innerradius + outerradius)*0.5f);
	pushFrac = 1.0 - fabs( distance / pushradius );
	clamp( pushFrac, 0.0f, 1.0f );

	return pushFrac;
}

//============
//T_RadiusDamage
//============
void T_RadiusDamage( edict_t *inflictor, edict_t *attacker, cplane_t *plane, float maxdamage, float maxknockback, float mindamage, edict_t *ignore, float radius, int mod )
{
	float points, points_old, points_new;
	edict_t	*ent = NULL;
	vec3_t dir;
	float knockback, knockback_old, knockback_new, minknockback;
	float pushFrac;
	float temp, tlog;

	if( radius <= 0 )
		return;

	tlog = mindamage / maxdamage;
	minknockback = maxknockback * tlog;
	tlog = log( tlog );

	while( ( ent = GClip_FindBoxInRadius4D( ent, inflictor->s.origin, radius, inflictor->timeDelta ) ) != NULL )
	{
		if( ent == ignore )
			continue;
		if( !ent->takedamage )
			continue;
		// in race, don't get splash damage from others
		if( game.gametype == GAMETYPE_RACE && ent != attacker )
			continue;

		if( g_instagib->integer && ent != attacker )
			continue;

		pushFrac = G_KnockbackPushFrac4D( inflictor->s.origin, ENTNUM( ent ), dir, radius, inflictor->timeDelta );
		temp = exp( ( 1.0f - pushFrac )*( 1.0f - pushFrac ) ) * tlog;

		knockback_old = minknockback + ( maxknockback - minknockback ) * pushFrac;
		points_old = mindamage + ( maxdamage - mindamage ) * pushFrac;

		// new kb and dmg calculations
		knockback_new = maxknockback * temp;
		points_new = maxdamage * temp;

		points = ( points_old > points_new ) ? points_old : points_new;
		knockback = ( knockback_old > knockback_new ) ? knockback_old : knockback_new;

		//G_Printf( "New D:%f K:%f F:%f\n Old: D:%f K:%f\n", points, knockback, (pow(1.0f-pushFrac, 2)), points_old, knockback_old );
		//ent->dmg_x = dir[0];
		//ent->dmg_y = dir[1];

		if( maxdamage == 0.0f || points > 0 )
		{
			if( CanSplashDamage( ent, inflictor, plane ) )
			{
				if( ent == attacker && ent->r.client )
				{                        // ROCKET JUMP HACK!!!
					firedef_t *firedef = NULL;
					// when doing weapon jumps, we always get the push from the strong fire definitions and ignore quad
					if( inflictor->s.type == ET_ROCKET )
						firedef = gs_weaponInfos[WEAP_ROCKETLAUNCHER].firedef;
					else if( inflictor->s.type == ET_GRENADE )
						firedef = gs_weaponInfos[WEAP_GRENADELAUNCHER].firedef;
					else if( inflictor->s.type == ET_PLASMA )
						firedef = gs_weaponInfos[WEAP_PLASMAGUN].firedef;

					if( firedef )
					{
						pushFrac = G_KnockbackPushFrac4D( inflictor->s.origin, ENTNUM( ent ), dir, firedef->splash_radius, ( inflictor->timeDelta*.05f ) );
						knockback = (float)firedef->knockback * pushFrac  * g_self_knockback->value;
						// selfdamage ratio
						points *= firedef->selfdamage;
					}
				}
				else dir[2] += 150; // vertical knockback hack
				T_Damage( ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, points, knockback, DAMAGE_RADIUS, mod );
			}
		}
	}
}
