/*
   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.

 */

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

float xyspeed;

//====================================================================
// DEAD VIEW
//====================================================================

//==================
//G_ProjectThirdPersonView
//==================
static void G_ProjectThirdPersonView( vec3_t vieworg, vec3_t viewangles, edict_t *passent )
{
	float thirdPersonRange = 60;
	float thirdPersonAngle = 0;
	float dist, f, r;
	vec3_t dest, stop;
	vec3_t chase_dest;
	trace_t	trace;
	vec3_t mins = { -4, -4, -4 };
	vec3_t maxs = { 4, 4, 4 };
	vec3_t v_forward, v_right, v_up;

	AngleVectors( viewangles, v_forward, v_right, v_up );

	// calc exact destination
	VectorCopy( vieworg, chase_dest );
	r = DEG2RAD( thirdPersonAngle );
	f = -cos( r );
	r = -sin( r );
	VectorMA( chase_dest, thirdPersonRange * f, v_forward, chase_dest );
	VectorMA( chase_dest, thirdPersonRange * r, v_right, chase_dest );
	chase_dest[2] += 8;

	// find the spot the player is looking at
	VectorMA( vieworg, 512, v_forward, dest );
	G_Trace( &trace, vieworg, mins, maxs, dest, passent, MASK_SOLID );

	// calculate pitch to look at the same spot from camera
	VectorSubtract( trace.endpos, vieworg, stop );
	dist = sqrt( stop[0] * stop[0] + stop[1] * stop[1] );
	if( dist < 1 )
		dist = 1;
	viewangles[PITCH] = RAD2DEG( -atan2( stop[2], dist ) );
	viewangles[YAW] -= thirdPersonAngle;
	AngleVectors( viewangles, v_forward, v_right, v_up );

	// move towards destination
	G_Trace( &trace, vieworg, mins, maxs, chase_dest, passent, MASK_SOLID );

	if( trace.fraction != 1.0 )
	{
		VectorCopy( trace.endpos, stop );
		stop[2] += ( 1.0 - trace.fraction ) * 32;
		G_Trace( &trace, vieworg, mins, maxs, stop, passent, MASK_SOLID );
		VectorCopy( trace.endpos, chase_dest );
	}

	VectorCopy( chase_dest, vieworg );
}

//=============
//G_Client_DeadView
//=============
static void G_Client_DeadView( edict_t *ent )
{
	edict_t	*body;
	trace_t	trace;

	// find the body
	for( body = game.edicts + game.maxclients; ENTNUM( body ) < game.maxclients + BODY_QUEUE_SIZE + 1; body++ )
	{
		if( !body->r.inuse || body->r.svflags & SVF_NOCLIENT )
			continue;
		if( body->activator == ent )  // this is our body
			break;
	}

	if( body->activator != ent )
	{                          // ran all the list and didn't find our body
		return;
	}

	// move us to body position
	VectorCopy( body->s.origin, ent->s.origin );
	ent->r.client->ps.viewangles[ROLL] = 0;
	ent->r.client->ps.viewangles[PITCH] = 0;

	// see if our killer is still in view
	if( body->enemy && ( body->enemy != ent ) )
	{
		G_Trace( &trace, ent->s.origin, vec3_origin, vec3_origin, body->enemy->s.origin, body, MASK_OPAQUE );
		if( trace.fraction != 1.0f )
		{
			body->enemy = NULL;
		}
		else
		{
			ent->r.client->ps.viewangles[YAW] = LookAtKillerYAW( ent, NULL, body->enemy );
		}
	}
	else
	{    // nobody killed us, so just circle around the body ?

	}

	G_ProjectThirdPersonView( ent->s.origin, ent->r.client->ps.viewangles, body );
	VectorCopy( ent->r.client->ps.viewangles, ent->s.angles );
	VectorCopy( ent->s.origin, ent->r.client->ps.pmove.origin );
}

//====================================================================
// WEAPONLIST
//====================================================================

//==================
//G_ClientUpdateWeaponListStats
//==================
static void G_ClientUpdateWeaponListStats( gclient_t *client )
{
	int i;
	gclient_t *source;

	if( !client )
		return;

	// when chasing generate from target
	if( client->chase.active && game.edicts[client->chase.target].r.client )
		source = game.edicts[client->chase.target].r.client;
	else
		source = client;

	memset( &client->ps.weaponlist, 0, sizeof( client->ps.weaponlist ) );

	// note that weaponlist only supports up to 32 weapons
	for( i = 0; ( i < WEAP_TOTAL-1 ) && ( i < MAX_WEAPLIST_STATS ); i++ )
	{
		client->ps.weaponlist[i][0] = min( source->inventory[WEAP_GUNBLADE + i], 255 ); // weapon
		client->ps.weaponlist[i][1] = min( source->inventory[AMMO_CELLS + i], 255 );    // strong ammo
		client->ps.weaponlist[i][2] = min( source->inventory[AMMO_WEAK_GUNBLADE + i], 255 ); // weak ammo
	}
}

//====================================================================
// EFFECTS
//====================================================================

//===============
//G_ClientDamageFeedback
//
//Adds color blends, hitsounds, etc
//===============
void G_ClientDamageFeedback( edict_t *ent )
{
	// add color blends from accumulated damage. Does nothing with armor saved damage (add some effect?)
	if( ent->snap.damage_taken )
	{
		int blendsDamage;
		blendsDamage = HEALTH_TO_INT( ent->snap.damage_taken );
		clamp( blendsDamage, 10, 80 );
		G_AddPlayerStateEvent( ent->r.client, PSEV_DAMAGE_BLEND, blendsDamage );
	}

	// add hitsounds from given damage
	if( ent->snap.damage_given || ent->snap.damageteam_given || ent->snap.kill || ent->snap.teamkill )
	{
		// we can't make team damage hit sound at the same time as we do damage hit sound
		// let's determine what's more relevant
		if( ent->snap.teamkill || ent->snap.damageteam_given > 50 ||
		   ( ent->snap.damageteam_given > 2 * ent->snap.damage_given && !ent->snap.kill ) )
		{
			G_AddPlayerStateEvent( ent->r.client, PSEV_HIT, 5 );
		}
		else
		{
			if( ent->snap.kill )
				G_AddPlayerStateEvent( ent->r.client, PSEV_HIT, 4 );
			else if( ent->snap.damage_given > 75 )
				G_AddPlayerStateEvent( ent->r.client, PSEV_HIT, 0 );
			else if( ent->snap.damage_given > 50 )
				G_AddPlayerStateEvent( ent->r.client, PSEV_HIT, 1 );
			else if( ent->snap.damage_given > 25 )
				G_AddPlayerStateEvent( ent->r.client, PSEV_HIT, 2 );
			else
				G_AddPlayerStateEvent( ent->r.client, PSEV_HIT, 3 );
		}
	}
}

//=============
//G_PlayerWorldEffects
//=============
static void G_PlayerWorldEffects( edict_t *ent )
{
	int waterlevel, old_waterlevel;
	int watertype, old_watertype;

	if( ent->movetype == MOVETYPE_NOCLIP )
	{
		ent->air_finished = level.time + ( 12*1000 ); // don't need air
		return;
	}

	waterlevel = ent->waterlevel;
	watertype = ent->watertype;
	old_waterlevel = ent->r.client->old_waterlevel;
	old_watertype = ent->r.client->old_watertype;
	ent->r.client->old_waterlevel = waterlevel;
	ent->r.client->old_watertype = watertype;

	//
	// if just entered a water volume, play a sound
	//
	if( !old_waterlevel && waterlevel )
	{
		if( ent->watertype & CONTENTS_LAVA )
			G_Sound( ent, CHAN_BODY, trap_SoundIndex( S_WORLD_LAVA_IN ), 1, ATTN_NORM );
		else if( ent->watertype & CONTENTS_SLIME )
			G_Sound( ent, CHAN_BODY, trap_SoundIndex( S_WORLD_SLIME_IN ), 1, ATTN_NORM );
		else if( ent->watertype & CONTENTS_WATER )
			G_Sound( ent, CHAN_BODY, trap_SoundIndex( S_WORLD_WATER_IN ), 1, ATTN_NORM );
		ent->flags |= FL_INWATER;
	}

	//
	// if just completely exited a water volume, play a sound
	//
	if( old_waterlevel && !waterlevel )
	{
		if( old_watertype & CONTENTS_LAVA )
			G_Sound( ent, CHAN_BODY, trap_SoundIndex( S_WORLD_LAVA_OUT ), 1, ATTN_NORM );
		else if( old_watertype & CONTENTS_SLIME )
			G_Sound( ent, CHAN_BODY, trap_SoundIndex( S_WORLD_SLIME_OUT ), 1, ATTN_NORM );
		else if( old_watertype & CONTENTS_WATER )
			G_Sound( ent, CHAN_BODY, trap_SoundIndex( S_WORLD_WATER_OUT ), 1, ATTN_NORM );
		ent->flags &= ~FL_INWATER;
	}

	//
	// check for head just coming out of water
	//
	if( old_waterlevel == 3 && waterlevel != 3 )
	{
		if( ent->air_finished < level.time )
		{ // gasp for air
			// wsw : jal : todo : better variations of gasp sounds
			G_AddEvent( ent, EV_SEXEDSOUND, 1, qtrue );
		}
		else if( ent->air_finished < level.time + 11000 )
		{ // just break surface
			// wsw : jal : todo : better variations of gasp sounds
			G_AddEvent( ent, EV_SEXEDSOUND, 2, qtrue );
		}
	}

	//
	// check for drowning
	//
	if( waterlevel == 3 )
	{
		// if out of air, start drowning
		if( ent->air_finished < level.time )
		{ // drown!
			if( ent->r.client->next_drown_time < level.time && !G_IsDead( ent ) )
			{
				ent->r.client->next_drown_time = level.time + 1000;

				// take more damage the longer underwater
				ent->dmg += 2;
				if( ent->dmg > 15 )
					ent->dmg = 15;

				// wsw : jal : todo : better variations of gasp sounds
				// play a gurp sound instead of a normal pain sound
				if( HEALTH_TO_INT( ent->health - ent->dmg ) <= 0 )
					G_AddEvent( ent, EV_SEXEDSOUND, 2, qtrue );
				else
					G_AddEvent( ent, EV_SEXEDSOUND, 1, qtrue );
				ent->pain_debounce_time = level.time;

				T_Damage( ent, world, world, vec3_origin, ent->s.origin, vec3_origin, ent->dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER );
			}
		}
	}
	else
	{
		ent->air_finished = level.time + 12000;
		ent->dmg = 2;
	}

	//
	// check for sizzle damage
	//
	if( waterlevel && ( ent->watertype & ( CONTENTS_LAVA|CONTENTS_SLIME ) ) )
	{
		if( ent->watertype & CONTENTS_LAVA )
		{
			// wsw: Medar: We don't have the sounds yet and this seems to overwrite the normal pain sounds
			//if( !G_IsDead(ent) && ent->pain_debounce_time <= level.time )
			//{
			//	G_Sound( ent, CHAN_VOICE, trap_SoundIndex(va(S_PLAYER_BURN_1_to_2, (rand()&1)+1)), 1, ATTN_NORM );
			//	ent->pain_debounce_time = level.time + 1000;
			//}
			T_Damage( ent, world, world, vec3_origin, ent->s.origin, vec3_origin,
			          ( 30 * waterlevel ) * game.snapFrameTime / 1000.0f, 0, 0, MOD_LAVA );
		}

		if( ent->watertype & CONTENTS_SLIME )
		{
			T_Damage( ent, world, world, vec3_origin, ent->s.origin, vec3_origin,
			          ( 10 * waterlevel ) * game.snapFrameTime / 1000.0f, 0, 0, MOD_SLIME );
		}
	}
}

//===============
//G_SetClientEffects
//===============
static void G_SetClientEffects( edict_t *ent )
{
	int remaining;
	gclient_t *client = ent->r.client;

	ent->s.effects = 0;
	ent->s.renderfx = 0;

	if( G_IsDead( ent ) || match.state >= MATCH_STATE_POSTMATCH )
		return;

	//ZOID
	//newgametypes
	G_Gametype_CTF_Effects( ent );
	//ZOID

	ent->s.effects &= ~EF_QUAD;
	if( client->quad_timeout > level.time )
	{
		remaining = ( client->quad_timeout - level.time )/game.snapFrameTime;
		if( remaining > 30 || ( remaining & 4 ) )
			ent->s.effects |= EF_QUAD;
	}

	ent->s.effects &= ~EF_SHELL;
	if( client->shell_timeout > level.time )
	{
		remaining = ( client->shell_timeout - level.time )/game.snapFrameTime;
		if( remaining > 30 || ( remaining & 4 ) )
			ent->s.effects |= EF_SHELL;
	}

	ent->s.effects &= ~EF_STRONG_WEAPON;
	if( ent->s.weapon )
	{
		firedef_t *firedef = Player_GetCurrentWeaponFiredef( ent );
		if( firedef && firedef->fire_mode == FIRE_MODE_STRONG )
			ent->s.effects |= EF_STRONG_WEAPON;
	}

	// show cheaters!!!
	ent->s.renderfx &= ~( RF_COLORSHELL_RED|RF_COLORSHELL_GREEN|RF_COLORSHELL_BLUE );
	if( ent->flags & FL_GODMODE )
	{
		ent->s.renderfx |= ( RF_COLORSHELL_RED|RF_COLORSHELL_GREEN|RF_COLORSHELL_BLUE );
	}

	// jal : add chating icon effect
	ent->s.effects &= ~EF_BUSYICON;
	if( ent->snap.buttons & BUTTON_BUSYICON )
		ent->s.effects |= EF_BUSYICON;

}

//===============
//G_SetClientSound
//===============
static void G_SetClientSound( edict_t *ent )
{
	gclient_t *client = ent->r.client;

	if( ent->waterlevel == 3 )
	{
		if( ent->watertype & CONTENTS_LAVA )
			ent->s.sound = trap_SoundIndex( S_WORLD_UNDERLAVA );
		else if( ent->watertype & CONTENTS_SLIME )
			ent->s.sound = trap_SoundIndex( S_WORLD_UNDERSLIME );
		else if( ent->watertype & CONTENTS_WATER )
			ent->s.sound = trap_SoundIndex( S_WORLD_UNDERWATER );
	}
	else if( client->weapon_sound )
		ent->s.sound = client->weapon_sound;
	else
		ent->s.sound = 0;
}

qboolean G_ClientIsZoom( edict_t *ent )
{
	if( ent->s.team < TEAM_PLAYERS )
		return qfalse;
	if( G_IsDead( ent ) )
		return qfalse;
	if( ent->r.client->chase.active )
		return qfalse;
	if( ent->snap.buttons & BUTTON_ZOOM )
		return qtrue;

	return qfalse;
}

//=================
//G_SetClientFrame
//=================
void G_SetClientFrame( edict_t *ent )
{
	if( ent->s.type != ET_PLAYER )
		return;

	if( !G_IsDead( ent ) )
	{
		ent->s.frame = 0;
	}
}

//=================
//G_ClientEndSnapFrame
//
//Called for each player at the end of the server frame
//and right after spawning
//=================
void G_ClientEndSnapFrame( edict_t *ent )
{
	edict_t *chase;

	if( trap_GetClientState( PLAYERNUM( ent ) ) != CS_SPAWNED )
		return;

	// the view POV id
	ent->r.client->ps.POVnum = ENTNUM( ent );

	G_ClientUpdateWeaponListStats( ent->r.client );

	// If the end of unit layout is displayed, don't give
	// the player any normal movement attributes
	if( match.state >= MATCH_STATE_POSTMATCH )
	{
		ent->r.client->ps.fov = 90;
		G_SetClientStats( ent );
		G_ReleaseClientPSEvent( ent->r.client );
		return;
	}

	if( G_ClientIsZoom( ent ) )
	{
		ent->r.client->ps.fov = ent->r.client->pers.zoomfov;
	}
	else
	{
		ent->r.client->ps.fov = ent->r.client->pers.fov;
	}
	ent->r.client->ps.viewheight = ent->viewheight;

	if( G_IsDead( ent ) )
	{
		G_Client_DeadView( ent );
	}

	G_PlayerWorldEffects( ent ); // burn from lava, etc
	G_ClientDamageFeedback( ent ); // show damage taken along the snap
	G_SetClientStats( ent );
	G_SetClientEffects( ent );
	G_SetClientSound( ent );
	G_SetClientFrame( ent );

	// updating plrkey in player_state_t
	if( ent->r.client->chase.active && ent->r.client->chase.target )
	{
		chase = game.edicts + ent->r.client->chase.target;
		ent->r.client->ps.plrkeys = chase->r.client->plrkeys;
	}
	else
		ent->r.client->ps.plrkeys = ent->r.client->plrkeys;

	G_ReleaseClientPSEvent( ent->r.client );
}
