/*
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_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 );
		trap_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
	trap_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;
	trap_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;
	trap_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;
	trap_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;
	trap_Trace( &trace, inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID );
	if( trace.fraction == 1.0 )
		return qtrue;

	return qfalse;
}

qboolean CanSplashDamage( edict_t *targ, edict_t *inflictor, cplane_t *plane )
{
	vec3_t	dest, origin;
	trace_t	trace;

	if( plane == NULL ) {
		VectorCopy( inflictor->s.origin, origin );
	} else {
		VectorMA( inflictor->s.origin, 4, 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 );
		trap_Trace( &trace, 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
	trap_Trace( &trace, 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;
	trap_Trace( &trace, 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;
	trap_Trace( &trace, 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;
	trap_Trace( &trace, 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;
	trap_Trace( &trace, origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID );
	if( trace.fraction == 1.0 )
		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;

	//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, int 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];

#ifdef UNBALANCED_ARMORS
	save = min(damage, client->armor) * ((gitem_armor_t *)armor->info)->protection;
	client->armor -= min(ARMOR_DEGRADATION * (float)damage, client->armor);
#else
	save = ((gitem_armor_t *)armor->info)->protection * (float)damage;
	if( save > client->armor )
		save = client->armor;

	if( !save )
		return 0;

	client->armor -= save;
#endif

	if( client->armor == 0 )
		client->armortag = ARMOR_NONE;
#ifdef ARMOR_SYSTEM_OF_THE_DAY
	// 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;
	}
#endif

	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;
	
	if( targ->s.team && attacker->s.team &&
		targ->s.team == attacker->s.team &&
		targ != attacker )
		return qtrue;
	
	return qfalse;
}

//================
//G_BlendFrameDamage
//================
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;
}

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 );
	
	// wsw: pb midair knock back hack
	// vel=vel+push*dir;
	// we want to scale Z part by a factor
	if( game.gametype == GAMETYPE_MIDAIR )
		targ->velocity[2]+=1.75f*push*dir[2]; // add 175%
}

//============
//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;

	meansOfDeath = mod;

	client = targ->r.client;

	// push
	if( !(dflags & DAMAGE_NO_KNOCKBACK) ) 
	{

		// dont apply pushback in those case
		// race: only allow rockets jump and damage by world
		if( (game.gametype == GAMETYPE_RACE && (attacker!=targ && attacker!=world)) )
			return;

		T_KnockBackPush( targ, dir, knockback );
	}

	// don't apply damage in this cases...
	// race: only accept damage by world (for void/kill)
	if( (game.gametype == GAMETYPE_RACE && attacker!=world) 
		|| (game.gametype == GAMETYPE_MIDAIR && (dflags & DAMAGE_RADIUS) ) )
		return;

	// pb: midair hack
	// world is there for lava
	if( game.gametype == GAMETYPE_MIDAIR && inflictor != world)
	{
		trace_t trace;
		float		height;

		trap_Trace( &trace, targ->s.origin, targ->r.mins, targ->r.maxs,
			tv(targ->s.origin[0], targ->s.origin[1], targ->s.origin[2] - 16000),
			targ, MASK_PLAYERSOLID );

		if( trace.fraction == 1 || trace.allsolid )
			return; // nothing touched

		height=targ->s.origin[2] - trace.endpos[2];
		//G_Printf("Height: %f\n",height);

		if( height<32 // only damage if the target is at least 32 units from the ground
			/*targ->groundentity != NULL // only apply damage if not on ground */
			)
			return;
	}
	// pb: end of midair hack


	take = damage;
	save = 0;

	// check for godmode
	if( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) )
	{
		take = 0;
		save = damage;
	}
	
	asave = CheckArmor( targ, take, dflags);
	take -= asave;

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

	// team damage avoidance
	if( G_IsTeamDamage(targ, attacker) && !G_Gametype_CanTeamDamage(dflags) )
		return;

	// 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;
	}

	//newgametypes
	G_Gametype_CTF_CheckHurtCarrier( targ, attacker );

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

			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->frame_damage_taken, dorigin, ddir, targ->frame_damage_at, targ->frame_damage_dir );
			G_BlendFrameDamage( targ, save, &targ->frame_damage_saved, dorigin, ddir, targ->frame_damage_at, targ->frame_damage_dir );
		}
		//[end] of simple damage feedback

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

		// wsw : jal : accumulate taken damage for color blends
		if( targ->r.client ) {
			targ->r.client->damage_taken += take;
			targ->r.client->damage_saved += save;
		}

		// wsw : jal : accumulate given damage for hit sounds
		if( (take) && targ != attacker && targ->r.client && !targ->deadflag )
		{
			if( attacker && attacker->r.client ) {
				if( G_IsTeamDamage(targ, attacker) )
					attacker->r.client->damageteam_given += take;
				else
					attacker->r.client->damage_given += take;
			}
		}

		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;
	int		i;
#define BOXRADIUS
#ifdef BOXRADIUS
	float	boxradius = 0.0f;

	if(1) {
		// inner radius
		boxradius = (maxs[0] + maxs[1] - mins[0] - mins[1]) * 0.25;
	} else {
		// outer radius
		boxradius = (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
	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 );
	distance = VectorNormalize( pushdir );
	if( distance - boxradius > pushradius )
		return 0;

	return 1.0 - ((distance - boxradius) / pushradius);
#else 
	// find center of the box
	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 );
	distance = VectorNormalize( pushdir );
	if( distance > pushradius )
		return 0;

	return 1.0 - (distance / pushradius);
#endif
}

/*
============
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;
	edict_t	*ent = NULL;
	vec3_t	dir;
	float	knockback;
	float	pushFrac;

	if( radius <= 0 )
		return;

	while( (ent = G_FindBoxInRadius(ent, inflictor->s.origin, radius)) != NULL )
	{
		if( ent == ignore )
			continue;
		if( !ent->takedamage )
			continue;

		pushFrac = G_KnockbackPushFrac( inflictor->s.origin, ent->s.origin, ent->r.mins, ent->r.maxs, dir, radius );
		knockback = (float)maxknockback * pushFrac;
		points = maxdamage * pushFrac;
		//G_Printf( "DAMAGE:%f MINDAMAGE:%f KNOCKBACK:%f\n", points, mindamage, knockback );
		if( points < mindamage ) 
			points = mindamage;

		if( 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 = g_weaponInfos[WEAP_ROCKETLAUNCHER].firedef;
					else if( inflictor->s.type == ET_GRENADE )
						firedef = g_weaponInfos[WEAP_GRENADELAUNCHER].firedef;
					else if( inflictor->s.type == ET_PLASMA )
						firedef = g_weaponInfos[WEAP_PLASMAGUN].firedef;

					if( firedef ) {
						pushFrac = G_KnockbackPushFrac( inflictor->s.origin, ent->s.origin, ent->r.mins, ent->r.maxs, dir, firedef->splash_radius );
						knockback = (float)firedef->knockback * pushFrac  * 1.35;
					}
					
					points = points * 0.5; // half the damage on self
				}
				T_Damage( ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, points, knockback, DAMAGE_RADIUS, mod );
			}
		}
	}
}
