//-----------------------------------------------------------------------------
//
// $Id$
//
//-----------------------------------------------------------------------------
//
// $Log$
// Revision 1.28  2003/09/01 15:09:48  jbravo
// Cleanups, crashbug fix and version bumped to 3.2
//
// Revision 1.27  2003/07/30 16:05:46  makro
// no message
//
// Revision 1.26  2002/06/17 15:27:41  slicer
// Added the fix for the FOV bug when selecting the ssg at spawn
//
// Revision 1.25  2002/06/16 20:06:13  jbravo
// Reindented all the source files with "indent -kr -ut -i8 -l120 -lc120 -sob -bad -bap"
//
// Revision 1.24  2002/06/16 19:12:52  jbravo
// Removed the MISSIONPACK ifdefs and missionpack only code.
//
// Revision 1.23  2002/06/12 19:59:39  jbravo
// Removed unused variables
//
// Revision 1.22  2002/06/05 20:09:17  jbravo
// Never commit uncompiled code! :)
//
// Revision 1.21  2002/06/05 20:06:07  jbravo
// no more Q3 X minutes left or X frags left sounds
//
// Revision 1.20  2002/05/31 00:17:06  jbravo
// Slicers fix for the weaponswitching issue
//
// Revision 1.19  2002/03/31 03:31:24  jbravo
// Compiler warning cleanups
//
// Revision 1.18  2002/03/26 10:32:52  jbravo
// Bye bye LCA lag
//
// Revision 1.17  2002/03/13 18:41:18  slicer
// Adjusted some of elder's unzoom code for the new sniper system ( server side )
//
// Revision 1.16  2002/01/11 19:48:29  jbravo
// Formatted the source in non DOS format.
//
// Revision 1.15  2001/12/31 16:28:41  jbravo
// I made a Booboo with the Log tag.
//
//
//-----------------------------------------------------------------------------
// Copyright (C) 1999-2000 Id Software, Inc.
//
// cg_playerstate.c -- this file acts on changes in a new playerState_t
// With normal play, this will be done after local prediction, but when
// following another player or playing back a demo, it will be checked
// when the snapshot transitions like all the other entities

#include "cg_local.h"

/*
==============
CG_CheckAmmo

If the ammo has gone low enough to generate the warning, play a sound
Elder: changed the purpose of the function
It now generates a warning ONLY if the *current* weapon is low on/out of ammo
==============
*/
void CG_CheckAmmo(void)
{
	//int           i;
	int total;
//	int previous;

	//int           weapons;

	// see about how many seconds of ammo we have remaining
	//weapons = cg.snap->ps.stats[ STAT_WEAPONS ];
	total = 0;
	//Blaze: ammo check
	//for ( i = WP_KNIFE ; i < WP_NUM_WEAPONS ; i++ ) {
	//if ( ! ( weapons & ( 1 << i ) ) ) {
	//continue;
	//}
//Blaze: Dont need this
/*
		switch ( i ) {
		case WP_ROCKET_LAUNCHER:
		case WP_GRENADE_LAUNCHER:
		case WP_RAILGUN:
		case WP_SHOTGUN:
			total += cg.snap->ps.ammo[i] * 1000;
			break;
		default:
			total += cg.snap->ps.ammo[i] * 200;
			break;
		}
*/
	//Elder: how it should be handled - but we're not using it
	//for this purpose
	/*
	   switch (i) {
	   case WP_PISTOL:
	   total += cg.snap->ps.ammo[i] * RQ3_PISTOL_DELAY;
	   break;
	   case WP_M3:
	   total += cg.snap->ps.ammo[i] * RQ3_M3_DELAY;
	   break;
	   case WP_M4:
	   total += cg.snap->ps.ammo[i] * RQ3_M4_DELAY;
	   break;
	   case WP_MP5:
	   total += cg.snap->ps.ammo[i] * RQ3_MP5_DELAY;
	   break;
	   case WP_M3:
	   total += cg.snap->ps.ammo[i] * RQ3_M3_DELAY;
	   break;
	   case WP_SSG3000:
	   total += cg.snap->ps.ammo[i] * RQ3_SSG3000_DELAY;
	   break;
	   case WP_GRENADE:
	   total += cg.snap->ps.ammo[i] * RQ3_GRENADE_DELAY;
	   break;
	   case WP_KNIFE:
	   total += cg.snap->ps.ammo[i] * RQ3_KNIFE_DELAY;
	   break;
	   default:
	   total += cg.snap->ps.ammo[i] * 2000;
	   break;
	   }
	 */

	//total += cg.snap->ps.ammo[i] * 2000;//Blaze: Make this big so you dont his the amo check off the bat
	/*
	   if ( total >= 5000 ) {
	   cg.lowAmmoWarning = 0;
	   return;
	   }
	   } */

	total = cg.snap->ps.ammo[cg.snap->ps.weapon];

	if (total > 0) {
		cg.lowAmmoWarning = 0;
		if (cg.snap->ps.weapon == WP_SSG3000 && cg.zoomFirstReturn == -1)
			cg.zoomFirstReturn = 0;
		return;
	}

//	previous = cg.lowAmmoWarning;

	if (total == 0) {
		cg.lowAmmoWarning = 2;
	}
	//else {
	//Elder: only allow completely empty ammo warning sounds
	//cg.lowAmmoWarning = 1;
	//}

	// play a sound on transitions
	//if ( cg.lowAmmoWarning != previous ) {
	//trap_S_StartLocalSound( cgs.media.noAmmoSound, CHAN_LOCAL_SOUND );
	//}
}

/*
==============
CG_DamageFeedback
==============
*/
void CG_DamageFeedback(int yawByte, int pitchByte, int damage)
{
	float left, front, up;
	float kick;
	int health;
	float scale;
	vec3_t dir;
	vec3_t angles;
	float dist;
	float yaw, pitch;

	//Elder: removed
	// show the attacking player's head and name in corner
	//cg.attackerTime = cg.time;

	//Elder: added
	cg.rq3_trueDamage = damage;
	//Elder: memset in cg_main.c should let us increment safely
	if (damage * 10 < 100)
		cg.rq3_blendTime += 100;
	else
		cg.rq3_blendTime += damage * 10;

	// the lower on health you are, the greater the view kick will be
	health = cg.snap->ps.stats[STAT_HEALTH];
	if (health < 40) {
		scale = 1;
	} else {
		scale = 40.0 / health;
	}
	kick = damage * scale;

	if (kick < 5)
		kick = 5;
	if (kick > 10)
		kick = 10;

	// if yaw and pitch are both 255, make the damage always centered (falling, etc)
	if (yawByte == 255 && pitchByte == 255) {
		cg.damageX = 0;
		cg.damageY = 0;
		cg.v_dmg_roll = 0;
		cg.v_dmg_pitch = -kick;
	} else {
		// positional
		pitch = pitchByte / 255.0 * 360;
		yaw = yawByte / 255.0 * 360;

		angles[PITCH] = pitch;
		angles[YAW] = yaw;
		angles[ROLL] = 0;

		AngleVectors(angles, dir, NULL, NULL);
		VectorSubtract(vec3_origin, dir, dir);

		front = DotProduct(dir, cg.refdef.viewaxis[0]);
		left = DotProduct(dir, cg.refdef.viewaxis[1]);
		up = DotProduct(dir, cg.refdef.viewaxis[2]);

		dir[0] = front;
		dir[1] = left;
		dir[2] = 0;
		dist = VectorLength(dir);
		if (dist < 0.1) {
			dist = 0.1f;
		}

		cg.v_dmg_roll = kick * left;

		cg.v_dmg_pitch = -kick * front;

		if (front <= 0.1) {
			front = 0.1f;
		}
		cg.damageX = -left / front;
		cg.damageY = up / dist;
	}

	// clamp the position
	if (cg.damageX > 1.0) {
		cg.damageX = 1.0;
	}
	if (cg.damageX < -1.0) {
		cg.damageX = -1.0;
	}

	if (cg.damageY > 1.0) {
		cg.damageY = 1.0;
	}
	if (cg.damageY < -1.0) {
		cg.damageY = -1.0;
	}
	// don't let the screen flashes vary as much
	if (kick > 10) {
		kick = 10;
	}
	cg.damageValue = kick;
	cg.v_dmg_time = cg.time + DAMAGE_TIME;
	cg.damageTime = cg.snap->serverTime;
}

/*
================
CG_Respawn

A respawn happened this snapshot
================
*/
void CG_Respawn(void)
{

	//Slicer: Force Reset the FOV to 90

	cg.zoomLevel = 0;
	cg.lastZoomLevel = 0;

	//int i=0;
	//float cvar_val;
	// no error decay on player movement
	cg.thisFrameTeleport = qtrue;

	// display weapons available
	cg.weaponSelectTime = cg.time;

	// select the weapon the server says we are using
	cg.weaponSelect = cg.snap->ps.weapon;

//Slicer: Done Server Side
	//Elder: added to reset zoom stuff LOCALLY
	//CG_RQ3_Zoom1x();

}

extern char *eventnames[];

/*
==============
CG_CheckPlayerstateEvents
==============
*/
void CG_CheckPlayerstateEvents(playerState_t * ps, playerState_t * ops)
{
	int i;
	int event;
	centity_t *cent;

	if (ps->externalEvent && ps->externalEvent != ops->externalEvent) {
		cent = &cg_entities[ps->clientNum];
		cent->currentState.event = ps->externalEvent;
		cent->currentState.eventParm = ps->externalEventParm;
		CG_EntityEvent(cent, cent->lerpOrigin);
	}

	cent = &cg.predictedPlayerEntity;	// cg_entities[ ps->clientNum ];
	// go through the predictable events buffer
	for (i = ps->eventSequence - MAX_PS_EVENTS; i < ps->eventSequence; i++) {
		// if we have a new predictable event
		if (i >= ops->eventSequence
		    // or the server told us to play another event instead of a predicted event we already issued
		    // or something the server told us changed our prediction causing a different event
		    || (i > ops->eventSequence - MAX_PS_EVENTS
			&& ps->events[i & (MAX_PS_EVENTS - 1)] != ops->events[i & (MAX_PS_EVENTS - 1)])) {

			event = ps->events[i & (MAX_PS_EVENTS - 1)];
			cent->currentState.event = event;
			cent->currentState.eventParm = ps->eventParms[i & (MAX_PS_EVENTS - 1)];
			CG_EntityEvent(cent, cent->lerpOrigin);

			cg.predictableEvents[i & (MAX_PREDICTED_EVENTS - 1)] = event;

			cg.eventSequence++;
		}
	}
}

/*
==================
CG_CheckChangedPredictableEvents
==================
*/
void CG_CheckChangedPredictableEvents(playerState_t * ps)
{
	int i;
	int event;
	centity_t *cent;

	cent = &cg.predictedPlayerEntity;
	for (i = ps->eventSequence - MAX_PS_EVENTS; i < ps->eventSequence; i++) {
		//
		if (i >= cg.eventSequence) {
			continue;
		}
		// if this event is not further back in than the maximum predictable events we remember
		if (i > cg.eventSequence - MAX_PREDICTED_EVENTS) {
			// if the new playerstate event is different from a previously predicted one
			if (ps->events[i & (MAX_PS_EVENTS - 1)] != cg.predictableEvents[i & (MAX_PREDICTED_EVENTS - 1)]) {

				event = ps->events[i & (MAX_PS_EVENTS - 1)];
				cent->currentState.event = event;
				cent->currentState.eventParm = ps->eventParms[i & (MAX_PS_EVENTS - 1)];
				CG_EntityEvent(cent, cent->lerpOrigin);

				cg.predictableEvents[i & (MAX_PREDICTED_EVENTS - 1)] = event;

				if (cg_showmiss.integer) {
					CG_Printf("WARNING: changed predicted event\n");
				}
			}
		}
	}
}

/*
==================
pushReward
==================
// JBravo: apparently not used.
*/
/*
static void pushReward(sfxHandle_t sfx, qhandle_t shader, int rewardCount) {
	if (cg.rewardStack < (MAX_REWARDSTACK-1)) {
		cg.rewardStack++;
		cg.rewardSound[cg.rewardStack] = sfx;
		cg.rewardShader[cg.rewardStack] = shader;
		cg.rewardCount[cg.rewardStack] = rewardCount;
	}
}
*/

/*
==================
CG_CheckLocalSounds
==================
*/
void CG_CheckLocalSounds(playerState_t * ps, playerState_t * ops)
{
	//int health, armor, reward;
	int reward;

	// don't play the sounds if the player just changed teams
	if (ps->persistant[PERS_TEAM] != ops->persistant[PERS_TEAM]) {
		return;
	}
	// hit changes
	if (ps->persistant[PERS_HITS] > ops->persistant[PERS_HITS]) {
//		armor = ps->persistant[PERS_ATTACKEE_ARMOR] & 0xff;
//		health = ps->persistant[PERS_ATTACKEE_ARMOR] >> 8;
	} else if (ps->persistant[PERS_HITS] < ops->persistant[PERS_HITS]) {
		if (cg_RQ3_anouncer.integer == 1)
			trap_S_StartLocalSound(cgs.media.hitTeamSound, CHAN_LOCAL_SOUND);
	}
	// health changes of more than -1 should make pain sounds
	if (ps->stats[STAT_HEALTH] < ops->stats[STAT_HEALTH] - 1) {
		if (ps->stats[STAT_HEALTH] > 0) {
			CG_PainEvent(&cg.predictedPlayerEntity, ps->stats[STAT_HEALTH]);
		}
	}

	// if we are going into the intermission, don't start any voices
	if (cg.intermissionStarted) {
		return;
	}
	// reward sounds
	reward = qfalse;

	// if any of the player event bits changed
	if (ps->persistant[PERS_PLAYEREVENTS] != ops->persistant[PERS_PLAYEREVENTS]) {
		if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD) !=
		    (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD)) {
			if (cg_RQ3_anouncer.integer == 1)
				trap_S_StartLocalSound(cgs.media.deniedSound, CHAN_ANNOUNCER);
		} else if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD) !=
			   (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD)) {
			if (cg_RQ3_anouncer.integer == 1)
				trap_S_StartLocalSound(cgs.media.humiliationSound, CHAN_ANNOUNCER);
		} else if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_HOLYSHIT) !=
			   (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_HOLYSHIT)) {
			if (cg_RQ3_anouncer.integer == 1)
				trap_S_StartLocalSound(cgs.media.holyShitSound, CHAN_ANNOUNCER);
		}
		reward = qtrue;
	}
	// check for flag pickup
	if (cgs.gametype > GT_TEAMPLAY) {
		if ((ps->powerups[PW_REDFLAG] != ops->powerups[PW_REDFLAG] && ps->powerups[PW_REDFLAG]) ||
		    (ps->powerups[PW_BLUEFLAG] != ops->powerups[PW_BLUEFLAG] && ps->powerups[PW_BLUEFLAG]) ||
		    (ps->powerups[PW_NEUTRALFLAG] != ops->powerups[PW_NEUTRALFLAG] && ps->powerups[PW_NEUTRALFLAG])) {
			trap_S_StartLocalSound(cgs.media.youHaveFlagSound, CHAN_ANNOUNCER);
		}
	}
	// lead changes
	if (!reward) {
		//
		if (!cg.warmup) {
			// never play lead changes during warmup
			if (ps->persistant[PERS_RANK] != ops->persistant[PERS_RANK]) {
				if (cgs.gametype < GT_TEAM) {
					if (ps->persistant[PERS_RANK] == 0) {
						if (cg_RQ3_anouncer.integer == 1)
							CG_AddBufferedSound(cgs.media.takenLeadSound);
					} else if (ps->persistant[PERS_RANK] == RANK_TIED_FLAG) {
						if (cg_RQ3_anouncer.integer == 1)
							CG_AddBufferedSound(cgs.media.tiedLeadSound);
					} else if ((ops->persistant[PERS_RANK] & ~RANK_TIED_FLAG) == 0) {
						if (cg_RQ3_anouncer.integer == 1)
							CG_AddBufferedSound(cgs.media.lostLeadSound);
					}
				}
			}
		}
	}
	// timelimit warnings
	// JBravo: this does nothing
/*
	if (cgs.timelimit > 0) {
		int msec;
		msec = cg.time - cgs.levelStartTime;
	}
*/
}

/*
===============
CG_TransitionPlayerState

===============
*/
void CG_TransitionPlayerState(playerState_t * ps, playerState_t * ops)
{
	// check for changing follow mode
	if (ps->clientNum != ops->clientNum) {
		cg.thisFrameTeleport = qtrue;
		// Slicer
		if (cgs.gametype == GT_TEAMPLAY) {
			if (ps->persistant[PERS_SPAWN_COUNT] != ops->persistant[PERS_SPAWN_COUNT])
				CG_Respawn();
		}
		// make sure we don't get any unwanted transition effects
		*ops = *ps;
	}
	// damage events (player is getting wounded)
	//Makro - not if wearing kevlar
	if (ps->damageEvent != ops->damageEvent && ps->damageCount && !(cg.snap->ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_KEVLAR))) {
		CG_DamageFeedback(ps->damageYaw, ps->damagePitch, ps->damageCount);
	}
	// respawning
	if (ps->persistant[PERS_SPAWN_COUNT] != ops->persistant[PERS_SPAWN_COUNT]) {
		CG_Respawn();
	}

	if (cg.mapRestart) {
		CG_Respawn();
		cg.mapRestart = qfalse;
	}

	if (cg.snap->ps.pm_type != PM_INTERMISSION && ps->persistant[PERS_TEAM] != TEAM_SPECTATOR) {
		CG_CheckLocalSounds(ps, ops);
	}
	// check for going low on ammo
	CG_CheckAmmo();

	// run events
	CG_CheckPlayerstateEvents(ps, ops);

	// smooth the ducking viewheight change
	if (ps->viewheight != ops->viewheight) {
		cg.duckChange = ps->viewheight - ops->viewheight;
		cg.duckTime = cg.time;
	}
	// Elder: reset sync sounds
	/*
	   if ( ps->weaponstate != ops->weaponstate )
	   {
	   cg.curSyncSound = 0;
	   }
	 */

	//Elder: grenade message
	if (ps->weapon == WP_GRENADE && ps->weaponstate == WEAPON_COCKED && ops->weaponstate != WEAPON_COCKED) {
		switch (CG_RQ3_GetGrenadeMode()) {
		case RQ3_GRENSHORT | RQ3_GRENMED:
			CG_AddMessage("Pin pulled. Ready to make a long range throw.\n");
			break;
		case RQ3_GRENMED:
			CG_AddMessage("Pin pulled. Ready to make a medium range throw.\n");
			break;
		case RQ3_GRENSHORT:
			CG_AddMessage("Pin pulled. Ready to make a short range throw.\n");
			break;
		}
	}
}
