//-----------------------------------------------------------------------------
//
// $Id$
//
//-----------------------------------------------------------------------------
//
// $Log$
// Revision 1.35  2005/02/15 16:33:39  makro
// Tons of updates (entity tree attachment system, UI vectors)
//
// Revision 1.34  2003/04/26 22:33:06  jbravo
// Wratted all calls to G_FreeEnt() to avoid crashing and provide debugging
//
// Revision 1.33  2003/03/22 20:29:26  jbravo
// wrapping linkent and unlinkent calls
//
// Revision 1.32  2003/03/09 21:30:38  jbravo
// Adding unlagged.   Still needs work.
//
// Revision 1.31  2002/09/29 16:06:45  jbravo
// Work done at the HPWorld expo
//
// Revision 1.30  2002/08/28 23:10:06  jbravo
// Added cg_RQ3_SuicideLikeARealMan, timestamping to server logs and
// fixed stats for non-TP modes.
//
// Revision 1.29  2002/06/16 20:06:14  jbravo
// Reindented all the source files with "indent -kr -ut -i8 -l120 -lc120 -sob -bad -bap"
//
// Revision 1.28  2002/06/16 17:38:00  jbravo
// Removed the MISSIONPACK ifdefs and missionpack only code.
//
// Revision 1.27  2002/06/11 15:52:21  makro
// Fixed knife + unbreakable breakables/breakables with high health bug
//
// Revision 1.26  2002/04/29 06:16:27  niceass
// small change to pressure system
//
// Revision 1.25  2002/04/06 21:42:20  makro
// Changes to bot code. New surfaceparm system.
//
// Revision 1.24  2002/04/05 18:53:26  jbravo
// Warning fixes
//
// Revision 1.23  2002/03/18 19:18:39  slicer
// Fixed bandage bugs ( i hope )
//
// Revision 1.22  2002/03/12 04:55:31  blaze
// stats should only be recored when the round is in progress
//
// Revision 1.21  2002/01/14 01:20:45  niceass
// No more default 800 gravity on items
// Thrown knife+Glass fix - NiceAss
//
// Revision 1.20  2002/01/11 19:48:30  jbravo
// Formatted the source in non DOS format.
//
// Revision 1.19  2001/12/31 16:28:42  jbravo
// I made a Booboo with the Log tag.
//
//
//-----------------------------------------------------------------------------
// Copyright (C) 1999-2000 Id Software, Inc.
//
#include "g_local.h"

#define	MISSILE_PRESTEP_TIME	50

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

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

	// reflect the velocity on the trace plane
	hitTime = level.previousTime + (level.time - level.previousTime) * trace->fraction;
	G_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);
			ent->s.time = level.time / 4;
			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_ExplodeMissile

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

	G_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));

	ent->freeAfterEvent = qtrue;

	// splash damage
	if (ent->splashDamage) {
		if (G_RadiusDamage
		    (ent->r.currentOrigin, ent->parent, ent->splashDamage, ent->splashRadius, ent,
		     ent->splashMethodOfDeath)) {
			g_entities[ent->r.ownerNum].client->accuracy_hits++;
			// Elder: Statistics tracking
			if (ent->s.weapon == WP_KNIFE && 
					((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY))
				g_entities[ent->r.ownerNum].client->pers.records[REC_KNIFETHROWHITS]++;
			if (ent->s.weapon == WP_GRENADE &&
					((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY))
				g_entities[ent->r.ownerNum].client->pers.records[REC_GRENADEHITS]++;
		}
	}
	//Elder: huhh?
	//G_Printf("exploding knife\n");
	trap_LinkEntity(ent);
}

/*
================
G_MissileImpact
================
*/
void G_MissileImpact(gentity_t * ent, trace_t * trace)
{
	gentity_t *other;
	qboolean hitClient = qfalse;
	vec3_t velocity;

	//Elder: added for thrown knife condition
	gitem_t *xr_item;
	gentity_t *xr_drop;

	other = &g_entities[trace->entityNum];

	//Elder: no grenade explosion on impact
	if (ent->s.weapon == WP_GRENADE && (ent->s.eFlags & (EF_BOUNCE | EF_BOUNCE_HALF))) {
		G_BounceMissile(ent, trace);
		G_AddEvent(ent, EV_GRENADE_BOUNCE, 0);
		return;
	}

	// impact damage
	if (other->takedamage) {
		// FIXME: wrong damage direction?
		if (ent->damage) {
			if (LogAccuracyHit(other, &g_entities[ent->r.ownerNum])) {
				g_entities[ent->r.ownerNum].client->accuracy_hits++;
				// Elder: Statistics tracking
				if (ent->s.weapon == WP_KNIFE &&
						((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY))
					g_entities[ent->r.ownerNum].client->pers.records[REC_KNIFETHROWHITS]++;
				//g_entities[ent->r.ownerNum].client->knifeHits++;
				if (ent->s.weapon == WP_GRENADE &&
						((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY))
					g_entities[ent->r.ownerNum].client->pers.records[REC_GRENADEHITS]++;
				//g_entities[ent->r.ownerNum].client->grenHits++;
				hitClient = qtrue;
			}
			G_EvaluateTrajectoryDelta(&ent->s.pos, level.time, velocity);
			if (VectorLength(velocity) == 0) {
				velocity[2] = 1;	// stepped on a grenade
			}
		}
	}

	if (!strcmp(ent->classname, "hook")) {
		gentity_t *nent;
		vec3_t v;

		nent = G_Spawn();
		if (other->takedamage && other->client) {

			G_AddEvent(nent, EV_MISSILE_HIT, DirToByte(trace->plane.normal));
			nent->s.otherEntityNum = other->s.number;

			ent->enemy = other;

			v[0] = other->r.currentOrigin[0] + (other->r.mins[0] + other->r.maxs[0]) * 0.5;
			v[1] = other->r.currentOrigin[1] + (other->r.mins[1] + other->r.maxs[1]) * 0.5;
			v[2] = other->r.currentOrigin[2] + (other->r.mins[2] + other->r.maxs[2]) * 0.5;

			SnapVectorTowards(v, ent->s.pos.trBase);	// save net bandwidth
		} else {
			VectorCopy(trace->endpos, v);
			G_AddEvent(nent, EV_MISSILE_MISS, DirToByte(trace->plane.normal));
			ent->enemy = NULL;
		}

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

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

		G_SetOrigin(ent, v);
		G_SetOrigin(nent, v);

		//Blaze: Hook dosent exist any more
		//ent->think = Weapon_HookThink;
		ent->nextthink = level.time + FRAMETIME;

		ent->parent->client->ps.pm_flags |= PMF_GRAPPLE_PULL;
		VectorCopy(ent->r.currentOrigin, ent->parent->client->ps.grapplePoint);

		trap_LinkEntity(ent);
		trap_LinkEntity(nent);

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

	//Elder: I don't know but that's what we did for the knife
	if (other->takedamage && other->client) {
		G_AddEvent(ent, EV_MISSILE_HIT, DirToByte(trace->plane.normal));
		ent->s.otherEntityNum = other->s.number;
		//Makro - new surfaceparm system
		//} else if( trace->surfaceFlags & SURF_METALSTEPS ) {
	} else if (IsMetalFlag(trace->surfaceFlags)) {
		G_AddEvent(ent, EV_MISSILE_MISS_METAL, DirToByte(trace->plane.normal));
	} else {
		G_AddEvent(ent, EV_MISSILE_MISS, DirToByte(trace->plane.normal));
	}

	//Elder: knife handling routines go HERE, not in g_main.c !!!
	if (ent->s.weapon == WP_KNIFE) {
		if (other->takedamage && other->client) {
			// Elder: must add it in here if we remove it from above
			// NiceAss: Fixed knife-leg damage bug here. Passed it the wrong vector I guess...
			G_Damage(other, ent, &g_entities[ent->r.ownerNum], velocity, ent->r.currentOrigin, THROW_DAMAGE,
				 0, MOD_KNIFE_THROWN);
			//hit a player - send the gurgle or embedding sound event
		} else {
			vec3_t temp;
			vec3_t knifeOffset;
			vec3_t knifeVelocity;
			vec3_t origin;

			//missed throw or hit func_breakable;
			//spawn a knife at its trajectory end-point
			xr_item = BG_FindItemForWeapon(WP_KNIFE);

			G_EvaluateTrajectoryDelta(&ent->s.pos, level.time, knifeVelocity);

			//Makro - added check for unbreakable breakables/breakables with high health
			if (other->s.eType == ET_BREAKABLE && !other->unbreakable && other->health <= THROW_DAMAGE) {
				VectorCopy(trace->endpos, origin);
				G_Damage(other, ent, &g_entities[ent->r.ownerNum], velocity, origin, THROW_DAMAGE, 0,
					 MOD_KNIFE_THROWN);

				// NiceAss: Find current position and set it. 
				VectorCopy(trace->endpos, ent->s.pos.trBase);
				ent->s.pos.trTime = level.time;
				// And cut the velocity down.
				VectorScale(ent->s.pos.trDelta, .2, ent->s.pos.trDelta);
				return;
			} else {
				//leave embedded in the wall
				xr_drop = LaunchItem(xr_item, trace->endpos, 0, FL_THROWN_KNIFE | FL_DROPPED_ITEM);
				VectorClear(xr_drop->s.pos.trDelta);

				//Elder: make the knife stick out a bit more
				//and transfer into shared entityState
				VectorCopy(knifeVelocity, temp);
				VectorNormalize(temp);
				VectorScale(temp, -4, temp);
				VectorAdd(trace->endpos, temp, knifeOffset);

				//VectorCopy(xr_drop->s.origin, temp);
				VectorAdd(xr_drop->s.origin, knifeOffset, xr_drop->s.origin);

				//Makro - "hurt" the breakable if needed
				if (other->s.eType == ET_BREAKABLE) {
					VectorCopy(trace->endpos, origin);
					G_Damage(other, ent, &g_entities[ent->r.ownerNum], velocity, origin,
						 THROW_DAMAGE, 0, MOD_KNIFE_THROWN);
				}

				if (other->s.eType == ET_PRESSURE)
					G_CreatePressure(xr_drop->s.origin, trace->plane.normal,
							 &g_entities[trace->entityNum]);
			}

			//Elder: transfer entity data into the shared entityState
			//They are rotated on the client side in cg_ents.c

			//G_Printf("movedir: %s\n", vtos(ent->s.pos.trDelta));
			xr_drop->s.eFlags = xr_drop->flags;

			//vectoangles( trace->plane.normal, xr_drop->s.angles );

			vectoangles(knifeVelocity, xr_drop->s.angles);
			xr_drop->s.angles[0] += 90;
		}
	}

	ent->freeAfterEvent = qtrue;

	// 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 (G_RadiusDamage(trace->endpos, ent->parent, ent->splashDamage, ent->splashRadius,
				   other, ent->splashMethodOfDeath)) {
			if (!hitClient) {
				g_entities[ent->r.ownerNum].client->accuracy_hits++;
				// Elder: Statistics tracking
				if (ent->s.weapon == WP_KNIFE &&
						((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY))
					g_entities[ent->r.ownerNum].client->pers.records[REC_KNIFETHROWHITS]++;
				//g_entities[ent->r.ownerNum].client->knifeHits++;
				if (ent->s.weapon == WP_GRENADE &&
						((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY))
					g_entities[ent->r.ownerNum].client->pers.records[REC_GRENADEHITS]++;
				//g_entities[ent->r.ownerNum].client->grenHits++;
			}
		}
	}

	trap_LinkEntity(ent);
}

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

	// get current position
	G_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;
	}
	// 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 (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) {
		// never explode or bounce on sky
		if (tr.surfaceFlags & SURF_NOIMPACT) {
			// If grapple, reset owner
			if (ent->parent && ent->parent->client && ent->parent->client->hook == ent) {
				ent->parent->client->hook = NULL;
			}
			G_FreeEntity(ent);
			return;
		}
		G_MissileImpact(ent, &tr);
		if (ent->s.eType != ET_MISSILE) {
			return;	// exploded
		}
	}

	// check think function after bouncing
	G_RunThink(ent);
}

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

/*
=================
fire_grenade
=================
*/
gentity_t *fire_grenade(gentity_t * self, vec3_t start, vec3_t dir)
{
	gentity_t *bolt;
	int speed = 0;
	vec3_t up, right;

	if (self->client)
		AngleVectors(self->client->ps.viewangles, NULL, right, up);
	else {
		// just in case we put those shooters back
		up[0] = up[1] = up[2] = 0;
		right[0] = right[1] = right[2] = 0;
	}

	VectorNormalize(dir);

	bolt = G_Spawn();
	bolt->classname = "grenade";
	bolt->nextthink = level.time + 2000;	// Action had 2 seconds
	bolt->think = G_ExplodeMissile;
	bolt->s.eType = ET_MISSILE;
	bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
	bolt->s.weapon = WP_GRENADE;
	bolt->s.eFlags = EF_BOUNCE_HALF;
	bolt->r.ownerNum = self->s.number;
	bolt->s.otherEntityNum = self->s.number;
	bolt->parent = self;
	bolt->damage = GRENADE_DAMAGE;	//probably only used on a direct hit
	bolt->splashDamage = GRENADE_SPLASH_DAMAGE;	//Blaze: Reaction Grenade Damage
	bolt->splashRadius = GRENADE_SPLASH_RADIUS;	//Blaze: changed from 150 was 170, but I uped it some more
	bolt->methodOfDeath = MOD_GRENADE;
	bolt->splashMethodOfDeath = MOD_GRENADE_SPLASH;
	bolt->clipmask = MASK_SHOT;
	bolt->target_ent = NULL;

	bolt->s.pos.trType = TR_GRAVITY;
	bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;	// move a bit on the very first frame
	VectorCopy(start, bolt->s.pos.trBase);

	//Elder: grenade toggle distances/speeds
	if (self->client) {
		// Elder: Statistics tracking
		if ((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY)
			self->client->pers.records[REC_GRENADESHOTS]++;
		if (self->client->ps.stats[STAT_HEALTH] <= 0 ||
		    self->client->ps.weaponstate == WEAPON_BANDAGING ||
		    // NiceAss: Should catch any case of switching weapons with a grenade "cocked"
		    self->client->ps.weaponstate == WEAPON_DROPPING) {
			//Always drop close range if dead or about to bandage
			speed = GRENADE_SHORT_SPEED / 2;
		} else if ((self->client->ps.persistant[PERS_WEAPONMODES] & RQ3_GRENSHORT) == RQ3_GRENSHORT &&
			   (self->client->ps.persistant[PERS_WEAPONMODES] & RQ3_GRENMED) == RQ3_GRENMED) {
			//Long range throw
			speed = GRENADE_LONG_SPEED;
		} else if ((self->client->ps.persistant[PERS_WEAPONMODES] & RQ3_GRENSHORT) == RQ3_GRENSHORT) {
			//Short range throw
			speed = GRENADE_SHORT_SPEED;
		} else if ((self->client->ps.persistant[PERS_WEAPONMODES] & RQ3_GRENMED) == RQ3_GRENMED) {
			//Medium range throw
			speed = GRENADE_MEDIUM_SPEED;
		} else {
			//Elder: shouldn't be here
			G_Printf("fire_grenade: Did not receive a grenade parm\n");
			speed = GRENADE_MEDIUM_SPEED;
		}
	}

	VectorScale(dir, speed, bolt->s.pos.trDelta);
	VectorMA(bolt->s.pos.trDelta, 200 + crandom() * 10.0f, up, bolt->s.pos.trDelta);
	VectorMA(bolt->s.pos.trDelta, crandom() * 10.0f, right, bolt->s.pos.trDelta);
	SnapVector(bolt->s.pos.trDelta);	// save net bandwidth

	VectorCopy(start, bolt->r.currentOrigin);

	return bolt;
}

gentity_t *fire_knife(gentity_t * self, vec3_t start, vec3_t dir)
{

	gentity_t *bolt;

	VectorNormalize(dir);

	bolt = G_Spawn();
	bolt->classname = "weapon_knife";
	bolt->nextthink = level.time + 10000;
	bolt->think = G_FreeEntity;
	bolt->s.eType = ET_MISSILE;
	bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
	bolt->s.weapon = WP_KNIFE;
	bolt->r.ownerNum = self->s.number;
	bolt->s.otherEntityNum = self->s.number;
	bolt->parent = self;
	bolt->damage = THROW_DAMAGE;
	bolt->splashDamage = 0;
	bolt->splashRadius = 0;
	bolt->methodOfDeath = MOD_KNIFE_THROWN;
	bolt->clipmask = MASK_SHOT;
	bolt->target_ent = NULL;
	bolt->count = 1;
	bolt->s.pos.trType = TR_GRAVITY;
	bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;	// move a bit on the very first frame
	VectorCopy(start, bolt->s.pos.trBase);
	VectorScale(dir, THROW_SPEED, bolt->s.pos.trDelta);
	SnapVector(bolt->s.pos.trDelta);	// save net bandwidth
	VectorCopy(start, bolt->r.currentOrigin);

	VectorCopy(dir, bolt->s.apos.trBase);
	VectorCopy(dir, bolt->r.currentAngles);

	if (self->client && ((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY)) {
		// Elder: Statistics tracking
		self->client->pers.records[REC_KNIFETHROWSHOTS]++;
	}

	return bolt;
}
