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

static	edict_t		*current_player;
static	gclient_t	*current_client;

trace_t pmtrace;

static	vec3_t	forward, right, up;
float	xyspeed;

/*
===============
P_DamageFeedback

Handles color blends and view kicks
===============
*/
void P_DamageFeedback( edict_t *ent )
{
	gclient_t	*client;
	client = ent->r.client;

	//jalfixme: STAT_FLASHES is never used anywhere in client side

	// flash the backgrounds behind the status numbers
//	client->ps.stats[STAT_FLASHES] = 0;
//	if (client->damage_taken)
//		client->ps.stats[STAT_FLASHES] |= 1;
	//if (client->damage_armor && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum))
	//	client->ps.stats[STAT_FLASHES] |= 2;

	if( client->damage_taken )
	{
		// play an apropriate pain sound
		if ((level.time > ent->pain_debounce_time) && !(ent->flags & FL_GODMODE) )
		{
			ent->pain_debounce_time = level.time + 0.7;
			
			if (!G_IsDead(ent))
			{
				if (ent->health < 25)
					G_AddEvent (ent, EV_PAIN, PAIN_25, qtrue);
				else if (ent->health < 50)
					G_AddEvent (ent, EV_PAIN, PAIN_50, qtrue);
				else if (ent->health < 75)
					G_AddEvent (ent, EV_PAIN, PAIN_75, qtrue);
				else
					G_AddEvent (ent, EV_PAIN, PAIN_100, qtrue);
			}
		}
	}
}

//===============
//SV_CalcViewOffset
//===============
void SV_CalcViewOffset( edict_t *ent )
{
	ent->r.client->ps.viewheight = ent->viewheight;
}

/*
=================
P_FallingDamage
=================
*/
// wsw : jal : brand new version
void P_FallingDamage( edict_t *ent )
{
//#define FALL_DAMAGE_PRINTS
	float	delta;
	float	damage;
	int		dflags;
	vec3_t	dir = { 0.0f, 0.0f, 1.0f };

	if (ent->movetype == MOVETYPE_NOCLIP)
		return;
	
	if( ent->groundentity )
	{
		delta = ent->r.client->fall_value;
		
	} else {
		// bounced ?
		if( ent->r.client->fall_value < 0.0f &&
			ent->velocity[2] >= 0.0f ) {
#ifdef FALL_DAMAGE_PRINTS
			G_Printf( "Bounced\n" );
#endif
			delta = ent->r.client->fall_value;

		// bounced and continued falling
		} else if ( ent->r.client->fall_value < 0.0f &&
			ent->velocity[2] > ent->r.client->fall_value ) {
#ifdef FALL_DAMAGE_PRINTS
			G_Printf( "Bounced and continued\n" );
#endif
			delta = ent->r.client->fall_value - ent->velocity[2];

		} else
			delta = 0.0f;
	}

	ent->r.client->fall_value = ent->velocity[2];
	if( delta  >= 0.0f )
		return;
	
	delta = delta * delta * 0.0001;	// make positive and reescale
	//adjust
	delta *= 0.80f;
#ifdef FALL_DAMAGE_PRINTS
	G_Printf( "FALL DELTA:%f\n", delta );
#endif
	// scale delta if was pushed by jump pad
	if ( ent->r.client->jumppad_time && ent->r.client->jumppad_time < level.time ) {
		delta /= (1 + level.time - ent->r.client->jumppad_time) * 0.5;
		ent->r.client->jumppad_time = 0;
	}

	// never take falling damage if completely underwater
	if (ent->waterlevel == 3)
		return;
	if (ent->waterlevel == 2)
		delta *= 0.25;
	if (ent->waterlevel == 1)
		delta *= 0.5;

	if (delta < 15)
		return;

	// never take damage
	if ( !ent->r.client->fall_fatal && (delta < 35 || pmtrace.surfFlags & SURF_NODAMAGE || !G_Gametype_CanFallDamage()) ) {
		G_AddEvent ( ent, EV_FALL, FALL_SHORT, qfalse );
		return;
	}

	// find and apply damage
	ent->pain_debounce_time = level.time;	// no normal pain sound

	damage = (delta-35)*0.60f;
	if (damage < 1.0f)
		damage = 1.0f;

	// limit max fall damage in multiplayer
	if (damage > 30.0f)
		damage = 30.0f;

	if (ent->r.client->fall_fatal) {
		damage = ceil(ent->health) - GIB_HEALTH + 1;
		dflags = DAMAGE_NO_PROTECTION;
	} else {
		dflags = 0;
	}

#ifdef FALL_DAMAGE_PRINTS
	G_Printf( "FALL DAMAGE:%f\n", damage );
#endif

	T_Damage (ent, world, world, dir, ent->s.origin, vec3_origin, damage, 0, dflags, MOD_FALLING);
	if (!G_IsDead(ent))
	{
		if( damage > 15.0f )
			G_AddEvent (ent, EV_FALL, FALL_FAR, qtrue);
		else
			G_AddEvent (ent, EV_FALL, FALL_MEDIUM, qtrue);
	} else
		G_AddEvent (ent, EV_FALL, FALL_SHORT, qfalse);
}

/*
=============
P_WorldEffects
=============
*/
void P_WorldEffects( void )
{
	int waterlevel, old_waterlevel;
	int watertype, old_watertype;

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

	waterlevel = current_player->waterlevel;
	watertype = current_player->watertype;
	old_waterlevel = current_client->old_waterlevel;
	old_watertype = current_client->old_watertype;
	current_client->old_waterlevel = waterlevel;
	current_client->old_watertype = watertype;

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

		// clear damage_debounce, so the pain sound will play immediately
		current_player->damage_debounce_time = level.time - 1;
	}

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

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

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

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

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

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

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

		if ( current_player->watertype & CONTENTS_SLIME )
		{
			T_Damage( current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, (10*waterlevel)*FRAMETIME, 0, 0, MOD_SLIME );
		}
	}
}


/*
===============
G_SetClientEffects
===============
*/
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

	if (client->quad_timeout > level.timemsec)
	{
		remaining = (client->quad_timeout - level.timemsec)/game.framemsec;
		if( remaining > 30 || (remaining & 4) )
			G_Gametype_CTF_SetPowerUpEffect( ent );
	}
	// show cheaters!!!
	if (ent->flags & FL_GODMODE)
	{
		ent->s.renderfx |= (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE);
	}

	// wsw : jal : powering up the weapon
	// jalfixme: find a better way of sending this one?
	// mdr: added hax to disable powering effect in timeout and countdown
	if( client->weapon_powered && !gtimeout.active && match.state != MATCH_STATE_COUNTDOWN )
		ent->s.effects |= EF_WEAPON_POWERING; 

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


/*
===============
G_SetClientEvent
===============
*/
void G_SetClientEvent( edict_t *ent )
{
	if ( G_IsDead(ent) ) {
		return;
	}

	// wsw : jal : was cyclic footstep play
}

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

/*
===============
G_SetClientFrame - SPLITMODELS
===============
*/
void G_SetClientFrame( edict_t *ent );

//===============
//G_CLUpdateLaserGunTrail
//===============
void G_CLUpdateLaserGunTrail( edict_t *ent )
{
	gclient_t	*client;
	firedef_t	*firedef = g_weaponInfos[WEAP_LASERGUN].firedef_weak;
	vec3_t	start;
	vec3_t	dir;

	client = ent->r.client;
	AngleVectors( client->v_angle, dir, NULL, NULL );
	VectorSet( start, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] + ent->viewheight );
	VectorMA( start, firedef->timeout, dir, client->lasergunTrail[(level.framenum+1) & LASERGUN_WEAK_TRAIL_MASK ] );
}


//===============
//G_SetClientPSEvent - wsw : jal
//===============
void G_SetClientPSEvent( edict_t *ent )
{
	int i;

	ent->r.client->ps.event = ent->r.client->events[0];
	if( ent->r.client->events[0] == PSEV_NONE )
		return;

	// Pick the first event in the queue. 
	// Move all them one position down. 
	// Put a no-event on top.
	for( i = 0; i < 15; i++ )
	{
		ent->r.client->events[i] = ent->r.client->events[i+1];
		if( ent->r.client->events[i] == PSEV_NONE )
			break;
	}
	ent->r.client->events[15] = PSEV_NONE;
}

/*
=================
ClientEndServerFrame

Called for each player at the end of the server frame
and right after spawning
=================
*/
void ClientEndServerFrame( edict_t *ent )
{
	int		i;
	vec3_t dir;

	current_player = ent;
	current_client = ent->r.client;

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

	//
	// If the origin or velocity have changed since ClientThink(),
	// update the pmove values.  This will happen when the client
	// is pushed by a bmodel or kicked by an explosion.
	// 
	// If it wasn't updated here, the view position would lag a frame
	// behind the body position when pushed -- "sinking into plats"
	//
	for( i=0; i<3; i++ )
	{
		current_client->ps.pmove.origin[i] = ent->s.origin[i]*16;
		current_client->ps.pmove.velocity[i] = ent->velocity[i]*16;
	}

	VectorSet ( dir, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 0.25 );

	if( !G_IsDead(ent) ) {
		if( ent->groundentity ) {
			trap_Trace( &pmtrace, ent->s.origin, ent->r.mins, ent->r.maxs, dir, ent, MASK_PLAYERSOLID );
		} else {
			trap_Trace( &pmtrace, ent->s.origin, ent->r.mins, ent->r.maxs, ent->s.origin, ent, MASK_PLAYERSOLID );
		}
	} else {
		trap_Trace( &pmtrace, ent->s.origin, ent->r.mins, ent->r.maxs, ent->s.origin, ent, MASK_DEADSOLID );
	}

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

	current_client->ps.fov = current_client->pers.fov;

	AngleVectors( current_client->v_angle, forward, right, up );

	// burn from lava, etc
	P_WorldEffects();

	//
	// set model angles from view angles so other things in
	// the world can tell which direction you are looking
	//
	//splitmodels: jal[start]: send the total angles value
	ent->s.angles[YAW] = current_client->v_angle[YAW];
	if( ent->deadflag ) {
		ent->s.angles[PITCH] = 0;
		ent->s.angles[ROLL] = 0;
	} else {
		ent->s.angles[PITCH] = current_client->v_angle[PITCH];
		ent->s.angles[ROLL] = 0;	//jal: roll is ignored clientside
	}
	//jal[end]

	// detect hitting the floor
	P_FallingDamage( ent );

	// apply all the damage taken this frame
	P_DamageFeedback( ent );

	// determine the view offsets
	SV_CalcViewOffset( ent );

// #ifdef PROTOCOL_NOKICKS_NOBLENDS
	// add colorblend from accumulated damage
	if( ent->r.client->damage_taken ) 
	{
		//clamp
		if( ent->r.client->damage_taken < 10 )
			ent->r.client->damage_taken = 10;
		if( ent->r.client->damage_taken > 80 )
			ent->r.client->damage_taken = 80;

		G_AddPlayerStateEvent( ent->r.client, PSEV_DAMAGE_BLEND, HEALTH_TO_INT(ent->r.client->damage_taken) );
		ent->r.client->damage_taken = 0;
	}

	// does nothing to armor saved damage (add some effect? A sound maybe?)
	if( ent->r.client->damage_saved )
		ent->r.client->damage_saved = 0;

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

		ent->r.client->damage_given = 0;
		ent->r.client->damageteam_given = 0;
	}
// #endif // PROTOCOL_NOKICKS_NOBLENDS

	G_SetStats( ent );

	G_SetClientEvent( ent );

	G_SetClientEffects( ent );

	G_SetClientSound( ent );

	G_SetClientFrame( ent );

	VectorCopy( ent->velocity, ent->r.client->oldvelocity);
	VectorCopy( ent->r.client->ps.viewangles, ent->r.client->oldviewangles);

	G_SetClientPSEvent( ent );

	G_CLUpdateLaserGunTrail( ent );
}

