// Copyright (C) 2001-2002 Raven Software
//
#include "g_local.h"

int G_MultipleDamageLocations(int hitLocation);

#define	MISSILE_PRESTEP_TIME	50

/*
================
G_BounceMissile

================
*/
void G_BounceMissile( gentity_t *ent, trace_t *trace ) 
{
	vec3_t	velocity;
	float	dot;
	int		hitTime;

	// nothing to do if already stationary
	if ( ent->s.pos.trType == TR_STATIONARY )
	{
		return;
	}

	// reflect the velocity on the trace plane
	hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
	BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
	dot = DotProduct( velocity, trace->plane.normal );
	VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta );

	if ( ent->s.eFlags & EF_BOUNCE_HALF ) 
	{
		VectorScale( ent->s.pos.trDelta, 0.65, ent->s.pos.trDelta );
		// check for stop
		if ( trace->plane.normal[2] > 0.2 && VectorLength( ent->s.pos.trDelta ) < 40 ) 
		{
			G_SetOrigin( ent, trace->endpos );
			return;
		}
	}
	else if ( ent->s.eFlags & EF_BOUNCE_SCALE ) 
	{
		// IF it hit a client then barely bounce off of them since they are "soft"
		if ( trace->entityNum < MAX_CLIENTS )
		{
			VectorScale( ent->s.pos.trDelta, 0.04f, ent->s.pos.trDelta );

			// Make sure the grenade doesnt continuously collide with teh player it hit
			ent->target_ent = &g_entities[trace->entityNum];
		}
		else
		{
			VectorScale( ent->s.pos.trDelta, ent->bounceScale, ent->s.pos.trDelta );
		}

		// check for stop
		if ( trace->plane.normal[2] > 0.2 && VectorLength( ent->s.pos.trDelta ) < 40 ) 
		{
			G_SetOrigin( ent, trace->endpos );

			if ( ent->parent && ent->parent->client )
			{
				gentity_t* nearby;
				
				// Find someone on the opposite team near wher ethe grenade landed
				nearby = G_FindNearbyClient ( trace->endpos, ent->parent->client->sess.team==TEAM_RED?TEAM_BLUE:TEAM_RED, 800, NULL );				

				if ( nearby )
				{
					// Make sure there is someone around to hear them scream
					nearby = G_FindNearbyClient ( trace->endpos, nearby->client->sess.team, 800, nearby );				
					G_VoiceGlobal ( nearby, "grenade", qtrue );
				}
			}

			return;
		}
 	}

	VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin);
	VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
	ent->s.pos.trTime = level.time;

	G_AddEvent( ent, EV_GRENADE_BOUNCE, trace->surfaceFlags& MATERIAL_MASK );
}

/*
================
G_ExplodeMissile

Explode a missile without an impact
================
*/
void G_ExplodeMissile( gentity_t *ent ) {
	vec3_t		dir;
	vec3_t		origin;

	BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
	SnapVector( origin );
	G_SetOrigin( ent, origin );

	// we don't have a valid direction, so just point straight up
	dir[0] = dir[1] = 0;
	dir[2] = 1;

	ent->s.eType = ET_GENERAL;
	G_AddEvent( ent, EV_MISSILE_MISS, (DirToByte( dir ) << MATERIAL_BITS) | MATERIAL_NONE);

	ent->freeAfterEvent = qtrue;

	// All grenade explosions are now broadcast to ensure that fire and smoke is always seen
	ent->r.svFlags |= SVF_BROADCAST;

	// splash damage
	if ( ent->splashDamage ) 
	{
		if (ent->dflags & DAMAGE_AREA_DAMAGE)
		{	
			// do damage over time rather than instantly
			G_CreateDamageArea ( ent->r.currentOrigin, ent->parent, ent->splashDamage*0.06f,ent->splashRadius, 8000,ent->methodOfDeath );

			// do some instant damage
			G_RadiusDamage( ent->r.currentOrigin, ent->parent, ent->damage, ent->splashRadius, ent, 
							1, ent->dflags, ent->splashMethodOfDeath );
		}
		else
		{	// normal radius of effect damage
			G_RadiusDamage( ent->r.currentOrigin, ent->parent, ent->splashDamage, ent->splashRadius, ent, 
							1, ent->dflags, ent->splashMethodOfDeath );
		}
	}

	trap_LinkEntity( ent );
}

/*
================
G_GrenadeThink

Marks the grenade ready to explode
================
*/
void G_GrenadeThink ( gentity_t* ent )
{
	ent->s.eFlags |= EF_EXPLODE;
}

void G_RunStuckMissile( gentity_t *ent )
{
	if ( ent->takedamage )
	{
		if ( ent->s.groundEntityNum >= 0 && ent->s.groundEntityNum < ENTITYNUM_WORLD )
		{
			gentity_t *other = &g_entities[ent->s.groundEntityNum];

			if ( (!VectorCompare( vec3_origin, other->s.pos.trDelta ) && other->s.pos.trType != TR_STATIONARY) || 
				(!VectorCompare( vec3_origin, other->s.apos.trDelta ) && other->s.apos.trType != TR_STATIONARY) )
			{//thing I stuck to is moving or rotating now, kill me
				G_Damage( ent, other, other, NULL, NULL, 99999, 0, MOD_CRUSH, HL_NONE );
				return;
			}
		}
	}
	// check think function
	G_RunThink( ent );
}

/*
================
G_BounceProjectile
================
*/
void G_BounceProjectile( vec3_t start, vec3_t impact, vec3_t dir, vec3_t endout ) {
	vec3_t v, newv;
	float dot;

	VectorSubtract( impact, start, v );
	dot = DotProduct( v, dir );
	VectorMA( v, -2*dot, dir, newv );

	VectorNormalize(newv);
	VectorMA(impact, 8192, newv, endout);
}


/*
================
G_CreateMissile
================
*/
gentity_t* G_CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, attackType_t attack  )
{
	gentity_t	*missile;

	missile = G_Spawn();
	
	missile->nextthink = level.time + life;
	missile->think = G_FreeEntity;
	missile->s.eType = ET_MISSILE;
	missile->r.svFlags = SVF_USE_CURRENT_ORIGIN;
	missile->parent = owner;
	missile->r.ownerNum = owner->s.number;

	if ( attack == ATTACK_ALTERNATE )
	{
		missile->s.eFlags |= EF_ALT_FIRING;
	}

	missile->s.pos.trType = TR_LINEAR;
	missile->s.pos.trTime = level.time;// - MISSILE_PRESTEP_TIME;	// NOTENOTE This is a Quake 3 addition over JK2
	missile->target_ent = NULL;

	SnapVector(org);
	VectorCopy( org, missile->s.pos.trBase );
	VectorScale( dir, vel, missile->s.pos.trDelta );
	VectorCopy( org, missile->r.currentOrigin);
	SnapVector(missile->s.pos.trDelta);

	return missile;
}

/*
================
G_CauseAreaDamage
================
*/
void G_CauseAreaDamage( gentity_t *ent ) 
{
	G_RadiusDamage ( ent->r.currentOrigin, ent->parent, ent->splashDamage, ent->splashRadius, ent, 3, DAMAGE_NO_TEAMKILL, ent->methodOfDeath );

	ent->s.time2--;

	if ( ent->s.time2 <= 0 )
	{	
		G_FreeEntity ( ent );
		return;
	}

	ent->nextthink = level.time + 350;
	trap_LinkEntity( ent );
}

/*
================
G_CreateDamageArea
================
*/
gentity_t* G_CreateDamageArea ( vec3_t origin, gentity_t* attacker, float damage, float radius, int duration, int mod )
{
	gentity_t	*damageArea;

	damageArea = G_Spawn();
	
	damageArea->nextthink = level.time + 350;
	damageArea->think = G_CauseAreaDamage;
	damageArea->s.eType = ET_DAMAGEAREA;
	damageArea->r.svFlags = SVF_USE_CURRENT_ORIGIN;
	damageArea->parent = attacker;
	damageArea->r.ownerNum = attacker->s.number;

	damageArea->s.pos.trType = TR_STATIONARY;
	damageArea->s.pos.trTime = level.time;
	damageArea->s.time2 = duration / 350;
	damageArea->target_ent = NULL;

	damageArea->classname = "DamageArea";

	VectorSet( damageArea->r.maxs, 1, 1, 1 );
	VectorScale( damageArea->r.maxs, -1, damageArea->r.mins );

	damageArea->splashDamage = damage;
	damageArea->splashRadius = radius;
	damageArea->methodOfDeath = mod;

	damageArea->dflags = DAMAGE_RADIUS;
	damageArea->clipmask = MASK_SHOT;

	VectorCopy( origin, damageArea->s.pos.trBase );
	VectorCopy( origin, damageArea->r.currentOrigin);
	SnapVector( damageArea->r.currentOrigin  );

	return damageArea;
}

/*
================
G_MissileImpact
================
*/
extern gentity_t *CreateWeaponPickup(vec3_t pos,weapon_t weapon);
extern int G_GetHitLocation(gentity_t *target, vec3_t ppoint, vec3_t dir );
void G_MissileImpact( gentity_t *ent, trace_t *trace ) 
{
	gentity_t		*other;
	vec3_t	velocity;
	int d;
	other = &g_entities[trace->entityNum];

	d = 0;

	// check for bounce
	if ( ( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF | EF_BOUNCE_SCALE ) ) ) 
	{
		G_BounceMissile( ent, trace );
		return;
	}
	
	// impact damage
	if (other->takedamage)
	{
		// FIXME: wrong damage direction?
		BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, velocity );
		if ( VectorLength( velocity ) == 0 )
		{
			velocity[2] = 1;	// stepped on a grenade
		}
		if ( ent->damage )
		{
			int location;

			location = HL_NONE;
			if ( other->client )
			{
				VectorNormalize ( velocity );
				location = G_GetHitLocation ( other, ent->r.currentOrigin, velocity );
				if ( ent->splashDamage ) 
				{
					location = G_MultipleDamageLocations(location);
				}
			}
		
			d = G_Damage(other, ent, &g_entities[ent->r.ownerNum], velocity, 
					 ent->s.origin, ent->damage, ent->dflags, 
					 ent->methodOfDeath, location );

			if ( d && other->client )
			{
				gentity_t *tent;
				vec3_t hitdir;

				// Put some procedural gore on the target.
				tent = G_TempEntity( ent->r.currentOrigin, EV_EXPLOSION_HIT_FLESH );
				
				// send entity and direction
				VectorSubtract(other->r.currentOrigin, ent->r.currentOrigin, hitdir);
				VectorNormalize(hitdir);
				tent->s.eventParm = DirToByte( hitdir );
				tent->s.otherEntityNum2 = other->s.number;			// Victim entity number

				// Pack the shot info into the temp end for gore
				tent->s.time  = ent->s.weapon + ((((int)other->s.apos.trBase[YAW]&0x7FFF) % 360) << 16);		
				if ( ent->s.eFlags & EF_ALT_FIRING )
				{
					tent->s.time += (ATTACK_ALTERNATE<<8);
				}

				VectorCopy ( other->r.currentOrigin, tent->s.angles );
				SnapVector ( tent->s.angles );
			}
		}
	}

	// is it cheaper in bandwidth to just remove this ent and create a new
	// one, rather than changing the missile into the explosion?

	if ( d && other->client ) 
	{
		G_AddEvent( ent, EV_MISSILE_HIT, 
					(DirToByte( trace->plane.normal ) << MATERIAL_BITS) | (trace->surfaceFlags & MATERIAL_MASK));
		ent->s.otherEntityNum = other->s.number;
		if( ent->damage )
		{
			// FIXME: might be able to use the value from inside G_Damage to avoid recalc???
			ent->s.otherEntityNum2 = G_GetHitLocation ( other, g_entities[ent->r.ownerNum].r.currentOrigin, velocity );
		}
	} 
	else 
	{
		G_AddEvent( ent, EV_MISSILE_MISS, 
					(DirToByte( trace->plane.normal ) << MATERIAL_BITS) | (trace->surfaceFlags & MATERIAL_MASK));

		// If missile should stick into impact point (e.g. a thrown knife).
		if(!Q_stricmp(ent->classname,"Knife"))
		{
			// Create a pickup where we impacted.
			vec3_t		pickupPos;
			gentity_t	*pickupEnt;

			VectorMA(trace->endpos,1,trace->plane.normal,pickupPos);

			pickupEnt=CreateWeaponPickup(pickupPos,WP_KNIFE);
			if(pickupEnt)
			{
				vec3_t knifeDir,knifeAngles;

				BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, knifeDir );

				//FIXME: needs work to set model angles!
				VectorNormalize ( knifeDir );
				vectoangles(knifeDir,knifeAngles);
				knifeAngles[YAW] += 90;
				knifeAngles[ROLL] = knifeAngles[PITCH];
				knifeAngles[PITCH] = 0;

				pickupEnt->s.angles[0]=knifeAngles[0];
				pickupEnt->s.angles[1]=knifeAngles[1];
				pickupEnt->s.angles[2]=knifeAngles[2];

				pickupEnt->think = G_FreeEntity;
				pickupEnt->nextthink = level.time + 30000;  // Stick around for 30 seconds

				pickupEnt->count = 1;
	
				pickupEnt->s.eFlags |= EF_ANGLE_OVERRIDE;
				VectorCopy(pickupEnt->s.angles,pickupEnt->r.currentAngles);
				VectorCopy(pickupEnt->s.angles,pickupEnt->s.apos.trBase);
				pickupEnt->s.pos.trType=TR_STATIONARY;
				pickupEnt->s.apos.trTime=level.time;
				pickupEnt->clipmask = ent->clipmask;
				pickupEnt->s.groundEntityNum = trace->entityNum;
				trap_LinkEntity(pickupEnt);			
			}
		}
	}

	ent->freeAfterEvent = qtrue;

	// All grenade explosions are now broadcast to ensure that fire and smoke is always seen
	ent->r.svFlags |= SVF_BROADCAST;

	// change over to a normal entity right at the point of impact
	ent->s.eType = ET_GENERAL;

	SnapVectorTowards( trace->endpos, ent->s.pos.trBase );	// save net bandwidth

	G_SetOrigin( ent, trace->endpos );

	// splash damage (doesn't apply to person directly hit)
	if ( ent->splashDamage ) 
	{
		if (ent->dflags & DAMAGE_AREA_DAMAGE)
		{	
			// do damage over time rather than instantly
			G_CreateDamageArea ( trace->endpos, ent->parent, ent->splashDamage*0.06f,ent->splashRadius*2, 8000,ent->methodOfDeath );

			// do some instant damage
			G_RadiusDamage( trace->endpos, ent->parent, ent->damage, ent->splashRadius, other, 
							1, ent->dflags, ent->splashMethodOfDeath );
		}
		else
		{	// normal radius of effect damage
			G_RadiusDamage( trace->endpos, ent->parent, ent->splashDamage, ent->splashRadius, 
							other, 1, ent->dflags, ent->splashMethodOfDeath );
		}
	}

	trap_LinkEntity( ent );
}

/*
================
G_RunMissile
================
*/
void G_RunMissile( gentity_t *ent ) 
{
	vec3_t		origin;
	trace_t		tr;
	int			passent;

	// get current position
	BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );

	// if this missile bounced off an invulnerability sphere
	if ( ent->target_ent ) 
	{
		passent = ent->target_ent->s.number;
	}
	else 
	{
		// ignore interactions with the missile owner
		passent = ent->r.ownerNum;
	}

	// Special case where the grenade has gone up into the sky
	if ( ent->s.eFlags & EF_INSKY )
	{
		// Check to see if its out of the world on the X,Y plane
		// or below it (above is a special case)
		if ( origin[0] < level.worldMins[0] ||
			 origin[1] < level.worldMins[1] ||
			 origin[0] > level.worldMaxs[0] ||
			 origin[1] > level.worldMaxs[1] ||
			 origin[2] < level.worldMins[2]    )
		{
			G_FreeEntity( ent );
			return;
		}

		// Above it only kills it if the item has no gravity
		if ( origin[2] > level.worldMaxs[2] && ent->s.pos.trType != TR_GRAVITY && ent->s.pos.trType != TR_LIGHTGRAVITY)
		{
			G_FreeEntity( ent );
			return;				
		}

		trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, passent, ent->clipmask );

		// Hit another sky, must be reentering
		if ( tr.fraction == 1.0f )
		{
			ent->s.eFlags &= ~EF_INSKY;
			VectorCopy ( origin, ent->r.currentOrigin );
		}

		VectorCopy ( origin, ent->r.currentOrigin );
		trap_LinkEntity ( ent );
	}
	else
	{
		// Run the same test again because the condition may have changed as a result
		// of the greande falling below the sky again
		// Loop this trace so we can break windows
		while ( 1 )
		{
			// trace a line from the previous position to the current position
			trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, passent, ent->clipmask );

			// If its glass then redo the trace after breaking the glass
			if ( tr.fraction != 1 && !Q_stricmp ( g_entities[tr.entityNum].classname, "func_glass" ) )
			{
				g_entities[tr.entityNum].use ( &g_entities[tr.entityNum], ent, ent );
				continue;
			}

			break;
		}
		
		if ( tr.startsolid || tr.allsolid ) 
		{
			// make sure the tr.entityNum is set to the entity we're stuck in
			trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, passent, ent->clipmask );
			tr.fraction = 0;
		}
		else 
		{
			VectorCopy( tr.endpos, ent->r.currentOrigin );
		}

		trap_LinkEntity( ent );

		if ( tr.fraction != 1 ) 
		{
			// Hit the sky or moving through something
			if ( (tr.surfaceFlags & SURF_NOIMPACT) && (tr.surfaceFlags & SURF_SKY) ) 
			{
				// Dont kill a missle that hits the sky and has gravity
				ent->s.eFlags |= EF_INSKY;
				ent->r.svFlags |= SVF_BROADCAST;
				VectorCopy ( origin, ent->r.currentOrigin );
				trap_LinkEntity( ent );
			}
			else
			{
				G_MissileImpact( ent, &tr );

				// Is it time to explode
				if ( ent->s.eFlags & EF_EXPLODE )
				{
					ent->s.eFlags &= (~EF_EXPLODE);
					G_ExplodeMissile ( ent );
					return;
				}

				// Exploded
				if ( ent->s.eType != ET_MISSILE ) 
				{
					return;
				}
			}
		}

		// Is it time to explode
		else if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags & EF_EXPLODE) )
		{
			ent->s.eFlags &= (~EF_EXPLODE);
			G_ExplodeMissile ( ent );
			return;
		}
	}

	// If this is a knife then reorient its angles
	if ( ent->s.weapon == WP_KNIFE )
	{
		vec3_t vel;

		BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, vel );
		vectoangles( vel, ent->s.angles );

		ent->s.angles[YAW] += 90;
//		ent->s.angles[ROLL] = ent->s.angles[PITCH];
		ent->s.angles[ROLL] = 0;
		ent->s.angles[PITCH] = 0;
	}
	
	// check think function after bouncing
	G_RunThink( ent );
}


//=============================================================================




