//-----------------------------------------------------------------------------
//
//  $Logfile:: /Source/cl_ents.c                                              $
// $Revision:: 4                                                              $
//   $Author:: Burgermac                                                      $
//     $Date:: 2/5/02 7:36 PM                                                 $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
// entity parsing and management
//

#include <float.h>
#include "client.h"
#include "sinmdl.h"

void CL_CreateViewWeapon( entity_t * gun );
static int view_weapon_effects=0;
static int view_weapon_lastcmdtime=0;
// 2015 code
void CL_CreateSecondaryViewModel( entity_t * gun, int num );
static int lastflametime = 0; // last flame effect time
#define MAX_VIEWMODELS 8 // max number of secondary view models

/*
=========================================================================

FRAME PARSING

=========================================================================
*/

/*
=================
CL_ParseEntityBits

Returns the entity number and the header bits
=================
*/
int	bitcounts[32];	/// just for protocol profiling
int CL_ParseEntityBits (unsigned *bits)
{
	unsigned	b, total;
	int			i;
	int			number;

	total = MSG_ReadByte (&net_message);
	if (total & U_MOREBITS1)
	{
		b = MSG_ReadByte (&net_message);
		total |= b<<8;
	}
	if (total & U_MOREBITS2)
	{
		b = MSG_ReadByte (&net_message);
		total |= b<<16;
	}
	if (total & U_MOREBITS3)
	{
		b = MSG_ReadByte (&net_message);
		total |= b<<24;
	}

	// count the bits for net profiling
	for (i=0 ; i<32 ; i++)
		if (total&(1<<i))
			bitcounts[i]++;

	if (total & U_NUMBER16)
		number = MSG_ReadShort (&net_message);
	else
		number = MSG_ReadByte (&net_message);

	*bits = total;

	return number;
}

/*
==================
CL_ParseDelta

Can go from either a baseline or a previous packet_entity
==================
*/
void CL_ParseDelta (entity_state_t *from, entity_state_t *to, int number, int bits)
{
	// set everything to the state we are delta'ing from
	*to = *from;

	// VectorCopy (from->origin, to->old_origin);
	to->number = number;
	to->prevframe = -1;

	if (bits & U_MODEL)
	{
		int b;
		b = MSG_ReadByte (&net_message);
		to->modelindex = (to->modelindex & 0xff00) + b;
	}
	if (bits & U_GUNMODEL)
	{
		int b;
		b = MSG_ReadByte (&net_message);
		to->gunmodelindex = (to->gunmodelindex & 0xff00) + b;
	}
	if (bits & U_MODEL_16)
	{
		int b;
		b = MSG_ReadByte (&net_message);
		to->modelindex = (to->modelindex & 0xff) + (b<<8);
	}
	if (bits & U_GUNMODEL_16)
	{
		int b;
		b = MSG_ReadByte (&net_message);
		to->gunmodelindex = (to->gunmodelindex & 0xff) + (b<<8);
	}

	if (bits & U_FRAME8)
		to->frame = MSG_ReadByte (&net_message);
	if (bits & U_FRAME16)
	{
		unsigned short combo;
		combo = MSG_ReadShort (&net_message);
		to->frame = combo & 0xff;
		to->anim = combo>>8;
	}
	if (bits & U_SCALE)
	{
		unsigned short pktscale;
		pktscale = MSG_ReadShort (&net_message);
		to->scale = PKTSCALE_TO_SCALE( pktscale );
	}
	if (bits & U_ALPHA)
	{
		byte pktalpha;
		pktalpha = MSG_ReadByte(&net_message);
		to->alpha = PKTALPHA_TO_ALPHA( pktalpha );
	}
	if (bits & U_COLOR)
	{
		byte pktcolor;

		pktcolor = MSG_ReadByte(&net_message);
		to->color_r = PKTCOLOR_TO_COLOR( pktcolor );

		pktcolor = MSG_ReadByte(&net_message);
		to->color_g = PKTCOLOR_TO_COLOR( pktcolor );

		pktcolor = MSG_ReadByte(&net_message);
		to->color_b = PKTCOLOR_TO_COLOR( pktcolor );

		to->radius = MSG_ReadShort(&net_message);
	}
	if (bits & U_BONE)
	{
		unsigned short num_and_group;
		to->parent = MSG_ReadShort (&net_message);
		num_and_group = MSG_ReadShort (&net_message);
		to->bone.group_num = num_and_group>>12;
		to->bone.tri_num = num_and_group & 0xfff;
		to->bone.orientation[0] = MSG_ReadAngle (&net_message);
		to->bone.orientation[1] = MSG_ReadAngle (&net_message);
		to->bone.orientation[2] = MSG_ReadAngle (&net_message);
	}
	if (bits & U_MODELGROUPS)
	{
		int i;
		to->numgroups = MSG_ReadByte (&net_message);
		for (i=0;i<to->numgroups;i++)
		{
			to->groups[i] = MSG_ReadByte (&net_message);
		}
	}
	if (bits & U_GUNANIMFRAME)
	{
		to->gunanim = MSG_ReadByte (&net_message);
		to->gunframe = MSG_ReadByte (&net_message);
	}

	if ((bits & U_SKIN8) && (bits & U_SKIN16))		//used for laser colors
		to->skinnum = MSG_ReadLong(&net_message);
	else if (bits & U_SKIN8)
		to->skinnum = MSG_ReadByte(&net_message);
	else if (bits & U_SKIN16)
		to->skinnum = MSG_ReadShort(&net_message);

	if (bits & U_EFFECTS)
	{
		to->effects = MSG_ReadByte(&net_message);
		if ( to->effects & EF_EFFECTS_16 )
		{
			to->effects |= MSG_ReadByte(&net_message) << 8;
		}
		if ( to->effects & EF_EFFECTS_32 )
		{
			to->effects |= ( int )MSG_ReadShort(&net_message) << 16;
		}
	}

	if (bits & U_RENDERFX16)
		to->renderfx = MSG_ReadShort(&net_message);

	if (bits & U_ORIGIN1)
		to->origin[0] = MSG_ReadCoord (&net_message);
	if (bits & U_ORIGIN2)
		to->origin[1] = MSG_ReadCoord (&net_message);
	if (bits & U_ORIGIN3)
		to->origin[2] = MSG_ReadCoord (&net_message);

	if ( to->effects & EF_SMOOTHANGLES )
	{
		if (bits & U_ANGLE1)
			to->angles[0] = MSG_ReadAngle16(&net_message);
		if (bits & U_ANGLE2)
			to->angles[1] = MSG_ReadAngle16(&net_message);
		if (bits & U_ANGLE3)
			to->angles[2] = MSG_ReadAngle16(&net_message);
	}
	else
	{
		if (bits & U_ANGLE1)
			to->angles[0] = MSG_ReadAngle(&net_message);
		if (bits & U_ANGLE2)
			to->angles[1] = MSG_ReadAngle(&net_message);
		if (bits & U_ANGLE3)
			to->angles[2] = MSG_ReadAngle(&net_message);
	}

	if (bits & (U_ANGLE1|U_ANGLE2|U_ANGLE3))
	{
		EulerToQuat( to->angles, to->quat );
	}

	if (bits & U_OLDORIGIN)
		MSG_ReadPos (&net_message, to->old_origin);

	if (bits & U_SOUND)
		to->sound = MSG_ReadShort (&net_message);

	if (bits & U_SOLID)
		to->solid = MSG_ReadLong (&net_message);
	if (bits & U_LIGHTOFS)
	{
		to->lightofs = (signed char)MSG_ReadByte (&net_message);
	}
	if (bits & U_PREVFRAME)
	{
		to->prevframe = MSG_ReadByte (&net_message);
	}
}

/*
==================
CL_DeltaEntity

Parses deltas from the given base and adds the resulting entity
to the current frame
==================
*/
static void CL_DeltaEntity (frame_t *frame, int newnum, entity_state_t *old, int bits)
{
	centity_t	*ent;
	entity_state_t	*state;

	ent = &cl_entities[newnum];

	state = &cl_parse_entities[cl.parse_entities & (MAX_PARSE_ENTITIES-1)];
	cl.parse_entities++;
	frame->num_entities++;

	CL_ParseDelta (old, state, newnum, bits);

	// some data changes will force no lerping
	if (state->modelindex != ent->current.modelindex
		|| state->parent != ent->current.parent
		|| abs(state->origin[0] - ent->current.origin[0]) > 512
		|| abs(state->origin[1] - ent->current.origin[1]) > 512
		|| abs(state->origin[2] - ent->current.origin[2]) > 512
		)
		ent->serverframe = -99;

	if (ent->serverframe != cl.frame.serverframe - 1)
	{	// wasn't in last update, so initialize some things
		ent->trailcount = 1024;		// for diminishing rocket / grenade trails
		// duplicate the current state so lerping doesn't hurt anything
		ent->prev = *state;
		VectorCopy (state->old_origin, ent->prev.origin);
		VectorCopy (state->old_origin, ent->lerp_origin);

		if (state->parent != ent->current.parent)
		{
			VectorCopy (state->origin, ent->current.origin);
			VectorCopy (state->origin, ent->prev.origin);
		}
		ent->last_frame = 0;
		ent->last_anim = 0;
	}
	else
	{	// shuffle the last state to previous
		ent->prev = ent->current;
	}

	if ( state->prevframe >= 0 )
	{
		ent->prev.frame = state->prevframe;
		ent->last_frame = state->prevframe+1;
	}

	ent->serverframe = cl.frame.serverframe;
	ent->current = *state;
}

/*
==================
CL_ParsePacketEntities

An svc_packetentities has just been parsed, deal with the
rest of the data stream.
==================
*/
static void CL_ParsePacketEntities (frame_t *oldframe, frame_t *newframe)
{
	int			newnum;
	int			bits;
	entity_state_t	*oldstate;
	int			oldindex, oldnum;

	newframe->parse_entities = cl.parse_entities;
	newframe->num_entities = 0;

	/* silence compiler:
	 * 99999 is greater than MAX_EDICTS, so safe.. */
	oldstate = NULL;

	// delta from the entities present in oldframe
	oldindex = 0;
	if (!oldframe)
		oldnum = 99999;
	else
	{
		if (oldindex >= oldframe->num_entities)
			oldnum = 99999;
		else
		{
			oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
			oldnum = oldstate->number;
		}
	}

	while (1)
	{
		newnum = CL_ParseEntityBits ((unsigned int *)&bits);
		if (newnum >= MAX_EDICTS)
			Com_Error (ERR_DROP,"CL_ParsePacketEntities: bad number:%i", newnum);

		if (net_message.readcount > net_message.cursize)
			Com_Error (ERR_DROP,"CL_ParsePacketEntities: end of message");

		if (!newnum)
			break;

		while (oldnum < newnum)
		{	// one or more entities from the old packet are unchanged
			if (cl_shownet->value == 3)
				Com_Printf ("   unchanged: %i\n", oldnum);
			CL_DeltaEntity (newframe, oldnum, oldstate, 0);

			oldindex++;

			if (oldindex >= oldframe->num_entities)
				oldnum = 99999;
			else
			{
				oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
				oldnum = oldstate->number;
			}
		}

		if (bits & U_REMOVE)
		{	// the entity present in oldframe is not in the current frame
			if (cl_shownet->value == 3)
				Com_Printf ("   remove: %i\n", newnum);
			if (oldnum != newnum)
				Com_Printf ("U_REMOVE: oldnum != newnum\n");

			oldindex++;

			if (oldindex >= oldframe->num_entities)
				oldnum = 99999;
			else
			{
				oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
				oldnum = oldstate->number;
			}
			continue;
		}

		if (oldnum == newnum)
		{	// delta from previous state
			if (cl_shownet->value == 3)
				Com_Printf ("   delta: %i\n", newnum);
			CL_DeltaEntity (newframe, newnum, oldstate, bits);

			oldindex++;

			if (oldindex >= oldframe->num_entities)
				oldnum = 99999;
			else
			{
				oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
				oldnum = oldstate->number;
			}
			continue;
		}

		if (oldnum > newnum)
		{	// delta from baseline
			if (cl_shownet->value == 3)
				Com_Printf ("   baseline: %i\n", newnum);
			CL_DeltaEntity (newframe, newnum, &cl_entities[newnum].baseline, bits);
			continue;
		}
	}

	// any remaining entities in the old frame are copied over
	while (oldnum != 99999)
	{	// one or more entities from the old packet are unchanged
		if (cl_shownet->value == 3)
			Com_Printf ("   unchanged: %i\n", oldnum);
		CL_DeltaEntity (newframe, oldnum, oldstate, 0);

		oldindex++;

		if (oldindex >= oldframe->num_entities)
			oldnum = 99999;
		else
		{
			oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
			oldnum = oldstate->number;
		}
	}
}

/*
=====================
CL_ParseSurfaceStates
=====================
*/
void CL_ParseSurfaceStates ( qboolean single )
{
	byte flags;
	surface_state_t state;
	int surface_number;

	while (1)
	{
		surface_number  = MSG_ReadShort( &net_message );

		memset( &state, 0, sizeof( surface_state_t ) );

		// Check for end of surfaces
		if (surface_number == -1)
			break;

		flags = MSG_ReadByte( &net_message );

		if ( flags & S_TRANS_MAG )
		{
			state.trans_mag = MSG_ReadFloat( &net_message );
		}

		if ( flags & S_TRANS_ANGLE )
		{
			state.trans_angle = MSG_ReadFloat( &net_message );
		}

		if ( flags & S_TRANS_STATE )
		{
			state.trans_state = MSG_ReadByte( &net_message );
		}

		if ( flags & S_TRANSLUCENCE )
		{
			state.translucence = MSG_ReadFloat( &net_message );
		}

		if ( flags & S_MAGNITUDE )
		{
			state.magnitude = MSG_ReadFloat( &net_message );
		}

		if ( flags & S_FREQUENCY )
		{
			state.frequency = MSG_ReadFloat( &net_message );
		}

		if ( flags & S_DAMAGE_FRAME )
		{
			state.damage_frame = MSG_ReadShort( &net_message );
		}

		if ( flags )
		{
			CL_ProcessTexinfoCommand( surface_number, &state, flags );
		}

		if ( single )
			break;
	}
}

/*
=====================
CL_ParseConsoleStates
=====================
*/
void CL_ParseConsoleStates ( void )
{
	console3d_t *p;
	long flags;
	int num;
	byte pktcolor;
	int num_bars;
	int i;

	while (1)
	{
		num = MSG_ReadShort( &net_message );

		if (!num)
			break;

		flags = MSG_ReadLong( &net_message );

		if (flags & C_CREATECONSOLE)
			CONSOLE_CreateBase( num );

		p = CONSOLE_FindConsoleByNumber( num );

		if (!p)
		{
			Com_Error ( ERR_DROP, "CL_ParseConsoleStates: Console %d does not exist\n", num );
			break;
		}

		if (flags & C_CLEARCONSOLE)
			CONSOLE_ConClear( p );

		if (flags & C_LAYOUT)
			strcpy( p->LayoutPtr, MSG_ReadString( &net_message ));

		if (flags & C_VIRTUAL_WIDTH)
		{
			p->virtual_width = MSG_ReadFloat( &net_message );
			p->oovw              = 1.0f / p->virtual_width;
		}

		if (flags & C_VIRTUAL_HEIGHT)
		{
			p->virtual_height = MSG_ReadFloat( &net_message );
			p->oovh              = 1.0f / p->virtual_height;
		}

		if (flags & C_FRACTION)
			p->fraction = MSG_ReadFloat( &net_message );

		if (flags & C_MENUFILE)
		{
			const char *path;
			path = MSG_ReadString( &net_message ); // Load Menu File
			CONSOLE_LoadMenuFromFile( p, path );
		}

		if (flags & C_LAYOUTFILE)
		{
			const char *path;
			path = MSG_ReadString( &net_message ); // Load Layout File
			CONSOLE_LoadLayoutFromFile( p, path );
		}

		if (flags & C_ROWS)
			p->rows = MSG_ReadShort( &net_message );

		if (flags & C_COLUMNS)
			p->columns = MSG_ReadShort( &net_message );

		if (flags & C_CONPRINT)
		{
			const char *txt;
			txt = MSG_ReadString( &net_message );
			CONSOLE_ConPrint( p, txt );
		}

		if (flags & C_ACTIVE)
		{
			p->active = MSG_ReadByte( &net_message );
		}

		if (flags & C_MENUACTIVE)
		{
			p->drawmenu = MSG_ReadByte( &net_message );
		}

		if (flags & C_COLOR)
		{
			pktcolor = MSG_ReadByte( &net_message );
			p->r = PKTCOLOR_TO_COLOR( pktcolor );
			pktcolor = MSG_ReadByte( &net_message );
			p->g = PKTCOLOR_TO_COLOR( pktcolor );
			pktcolor = MSG_ReadByte( &net_message );
			p->b = PKTCOLOR_TO_COLOR( pktcolor );
			pktcolor = MSG_ReadByte( &net_message );
			p->a = PKTCOLOR_TO_COLOR( pktcolor );
		}

		if (flags & C_TYPE)
			CONSOLE_SetType( p,MSG_ReadByte( &net_message ));

		if (flags & C_FOCUS)
			CONSOLE_SetFocusByType( p,(consolefocus_t)MSG_ReadByte( &net_message ));

		if (flags & C_NAME)
			strcpy( p->console_name,MSG_ReadString( &net_message ));

		if (flags & C_STATUSBAR_INIT) {
			num_bars = MSG_ReadByte( &net_message );
			for (i=0; i<num_bars; i++) {
				sbar_t *BarPtr;
				num = MSG_ReadByte( &net_message );
				BarPtr = &p->SBarPtr[num];
				BarPtr->w       = MSG_ReadFloat( &net_message );
				BarPtr->h       = MSG_ReadFloat( &net_message );
				BarPtr->min     = MSG_ReadFloat( &net_message );
				BarPtr->max     = MSG_ReadFloat( &net_message );
				BarPtr->value   = MSG_ReadFloat( &net_message );
				pktcolor             = MSG_ReadByte( &net_message );
				BarPtr->red     = PKTCOLOR_TO_COLOR( pktcolor );
				pktcolor             = MSG_ReadByte( &net_message );
				BarPtr->green   = PKTCOLOR_TO_COLOR( pktcolor );
				pktcolor             = MSG_ReadByte( &net_message );
				BarPtr->blue    = PKTCOLOR_TO_COLOR( pktcolor );
				pktcolor             = MSG_ReadByte( &net_message );
				BarPtr->alpha   = PKTCOLOR_TO_COLOR( pktcolor );
			}
		}

		if (flags & C_STATUSBAR_VALUE)
		{
			num_bars = MSG_ReadByte( &net_message );
			for (i=0; i<num_bars; i++)
			{
				num = MSG_ReadByte( &net_message );
				p->SBarPtr[num].value = MSG_ReadFloat( &net_message );
			}
		}

		if (flags & C_LINERET)
		{
			if (p == cls.currentconsole)
				CONSOLE_ProcessInputLine( p );

			p->KeyLinesPtr[p->edit_line][0] = ']';
		}

		if (flags & C_LINEPOS)
		{
			const char *add_string = MSG_ReadString( &net_message );
			strcat( p->KeyLinesPtr[p->edit_line], add_string );
			p->key_linepos += (int)strlen( add_string );
			p->KeyLinesPtr[p->edit_line][p->key_linepos] = 0;
		}

		if (flags & C_LINENEG)
		{
			int newpos = MSG_ReadShort( &net_message );
			p->key_linepos = newpos;
			p->KeyLinesPtr[p->edit_line][p->key_linepos] = 0;
		}

		if (flags & C_MENUINFO)
		{
			p->current_menu_level = MSG_ReadShort( &net_message );
			p->sel_menu_item      = MSG_ReadShort( &net_message );
		}
	}
}

/*
=====================
CL_ParseConsoleSingle
=====================
*/
void CL_ParseConsoleSingleState ( void )
{
	console3d_t *p;
	int flags;
	int num;
	int num_bars;
	int i;
	byte pktcolor;

	num = MSG_ReadShort( &net_message );
	flags = MSG_ReadLong( &net_message );

	if (flags & C_CREATECONSOLE)
		CONSOLE_CreateBase( num );

	p = CONSOLE_FindConsoleByNumber( num );

	if (!p)
	{
		Com_Error ( ERR_DROP, "CL_ParseConsoleSingleState: Console %d does not exist\n", num );
	}

	if (flags & C_LAYOUT)
		strcpy( p->LayoutPtr, MSG_ReadString( &net_message ));

	if (flags & C_VIRTUAL_WIDTH)
	{
		p->virtual_width = MSG_ReadFloat( &net_message );
		p->oovw              = 1.0f / p->virtual_width;
	}

	if (flags & C_VIRTUAL_HEIGHT)
	{
		p->virtual_height = MSG_ReadFloat( &net_message );
		p->oovh              = 1.0f / p->virtual_height;
	}

	if (flags & C_FRACTION)
		p->fraction = MSG_ReadFloat( &net_message );

	if (flags & C_MENUFILE)
	{
		const char *path;
		path = MSG_ReadString( &net_message ); // Load Menu File
		CONSOLE_LoadMenuFromFile( p, path );
	}

	if (flags & C_LAYOUTFILE)
	{
		const char *path;
		path = MSG_ReadString( &net_message ); // Load Layout File
		CONSOLE_LoadLayoutFromFile( p, path );
	}

	if (flags & C_ROWS)
		p->rows = MSG_ReadShort( &net_message );

	if (flags & C_COLUMNS)
		p->columns = MSG_ReadShort( &net_message );

	if (flags & C_CONPRINT)
	{
		const char *txt;
		txt = MSG_ReadString( &net_message );
		CONSOLE_ConPrint( p, txt );
	}

	if (flags & C_ACTIVE)
	{
		p->active = MSG_ReadByte( &net_message );
	}

	if (flags & C_MENUACTIVE)
	{
		p->drawmenu = MSG_ReadByte( &net_message );
	}

	if (flags & C_COLOR)
	{
		pktcolor = MSG_ReadByte( &net_message );
		p->r = PKTCOLOR_TO_COLOR( pktcolor );
		pktcolor = MSG_ReadByte( &net_message );
		p->g = PKTCOLOR_TO_COLOR( pktcolor );
		pktcolor = MSG_ReadByte( &net_message );
		p->b = PKTCOLOR_TO_COLOR( pktcolor );
		pktcolor = MSG_ReadByte( &net_message );
		p->a = PKTCOLOR_TO_COLOR( pktcolor );
	}

	if (flags & C_CLEARCONSOLE)
		CONSOLE_ConClear( p );

	if (flags & C_TYPE)
		CONSOLE_SetType( p,MSG_ReadByte( &net_message ));

	if (flags & C_FOCUS)
		CONSOLE_SetFocusByType( p,(consolefocus_t)MSG_ReadByte( &net_message ));

	if (flags & C_NAME)
		strcpy( p->console_name,MSG_ReadString( &net_message ));

	if (flags & C_STATUSBAR_INIT)
	{
		num_bars = MSG_ReadByte( &net_message );
		for (i=0; i<num_bars; i++)
		{
			sbar_t *BarPtr;
			num = MSG_ReadByte( &net_message );
			BarPtr = &p->SBarPtr[num];
			BarPtr->w = MSG_ReadFloat( &net_message );
			BarPtr->h = MSG_ReadFloat( &net_message );
			BarPtr->min = MSG_ReadFloat( &net_message );
			BarPtr->max = MSG_ReadFloat( &net_message );
			BarPtr->value = MSG_ReadFloat( &net_message );
			pktcolor = MSG_ReadByte( &net_message );
			BarPtr->red = PKTCOLOR_TO_COLOR( pktcolor );
			pktcolor = MSG_ReadByte( &net_message );
			BarPtr->green = PKTCOLOR_TO_COLOR( pktcolor );
			pktcolor = MSG_ReadByte( &net_message );
			BarPtr->blue = PKTCOLOR_TO_COLOR( pktcolor );
			pktcolor = MSG_ReadByte( &net_message );
			BarPtr->alpha = PKTCOLOR_TO_COLOR( pktcolor );
		}
	}

	if (flags & C_STATUSBAR_VALUE)
	{
		num_bars = MSG_ReadByte( &net_message );
		for (i=0; i<num_bars; i++)
		{
			num = MSG_ReadByte( &net_message );
			p->SBarPtr[num].value = MSG_ReadFloat( &net_message );
		}
	}

	if (flags & C_LINERET)
	{
		if (p == cls.currentconsole)
			CONSOLE_ProcessInputLine( p );

		p->KeyLinesPtr[p->edit_line][0] = ']';
	}

	if (flags & C_LINEPOS)
	{
		const char *add_string = MSG_ReadString( &net_message );
		strcat( p->KeyLinesPtr[p->edit_line], add_string );
		p->key_linepos += (int)strlen( add_string );
		p->KeyLinesPtr[p->edit_line][p->key_linepos] = 0;
	}

	if (flags & C_LINENEG)
	{
		int newpos = MSG_ReadShort( &net_message );
		p->key_linepos = newpos;
		p->KeyLinesPtr[p->edit_line][p->key_linepos] = 0;
	}

	if (flags & C_MENUINFO)
	{
		p->current_menu_level = MSG_ReadShort( &net_message );
		p->sel_menu_item      = MSG_ReadShort( &net_message );
	}
}

/*
===================
CL_ParsePlayerstate
===================
*/
static void CL_ParsePlayerstate (frame_t *oldframe, frame_t *newframe)
{
	unsigned int flags;
	gravityaxis_t grav;
	player_state_t	*state;
	int i;
	int statbits;

	state = &newframe->playerstate;

	// clear to old value before delta parsing
	if (oldframe)
		*state = oldframe->playerstate;
	else
		memset (state, 0, sizeof(*state));

	flags = MSG_ReadShort (&net_message) & 0xffff;

#if 0
	{
		static cvar_t *cl_showbits = NULL;

		if ( !cl_showbits )
		{
			cl_showbits = Cvar_Get ("cl_showbits", "0", 0 );
		}

		if ( cl_showbits->value )
		{
			int f;
			Com_DPrintf( "Player: " );
			for( f = 31; f >= 0; f-- )
			{
				Com_DPrintf( flags&(1<<f) ? "1":"0" );
				if ( !( f & 0x7 ) )
				{
					Com_DPrintf( ":" );
				}
			}
			Com_DPrintf( "\n" );
		}
	}
#endif

	//
	// parse the pmove_state_t
	//
	if (flags & PS_M_TYPE)
		state->pmove.pm_type = (pmtype_t)MSG_ReadByte (&net_message);

	if (flags & PS_M_ORIGIN)
	{
		state->pmove.origin[0] = MSG_ReadShort (&net_message);
		state->pmove.origin[1] = MSG_ReadShort (&net_message);
		state->pmove.origin[2] = MSG_ReadShort (&net_message);
	}

	if (flags & PS_M_VELOCITY)
	{
		state->pmove.velocity[0] = MSG_ReadShort (&net_message);
		state->pmove.velocity[1] = MSG_ReadShort (&net_message);
		state->pmove.velocity[2] = MSG_ReadShort (&net_message);
	}

	if (flags & PS_M_TIME)
		state->pmove.pm_time = MSG_ReadByte (&net_message);

	if (flags & PS_M_FLAGS)
		state->pmove.pm_flags = MSG_ReadByte (&net_message);
	if ( state->pmove.pm_flags & PMF_MOREBITS )
	{
		byte temp;
		temp = MSG_ReadByte( &net_message );
		state->pmove.pm_flags |= temp<<8;
	}

	grav = gravity_axis[ state->pmove.gravity_axis ];

	if (flags & PS_M_GRAVITY)
	{
		short gravity;
		vec3_t temp;

		temp[ 0 ] = state->viewoffset[ grav.x ];
		temp[ 1 ] = state->viewoffset[ grav.y ] * grav.sign;
		temp[ 2 ] = state->viewoffset[ grav.z ] * grav.sign;

		gravity = MSG_ReadShort (&net_message);
		state->pmove.gravity = gravity & GRAVITY_MASK;
		state->pmove.gravity_axis = ( gravity & GRAVITY_AXIS_MASK ) >> GRAVITY_AXIS_SHIFT;

		grav = gravity_axis[ state->pmove.gravity_axis ];

		state->viewoffset[ grav.x ] = temp[ 0 ];
		state->viewoffset[ grav.y ] = temp[ 1 ] * grav.sign;
		state->viewoffset[ grav.z ] = temp[ 2 ] * grav.sign;
	}

	if (flags & PS_M_DELTA_ANGLES)
	{
		state->pmove.delta_angles[0] = MSG_ReadShort (&net_message);
		state->pmove.delta_angles[1] = MSG_ReadShort (&net_message);
		state->pmove.delta_angles[2] = MSG_ReadShort (&net_message);
	}

	if (cl.attractloop)
		state->pmove.pm_type = PM_FREEZE;		// demo playback

	//
	// parse the rest of the player_state_t
	//
	if (flags & PS_VIEWOFFSET)
	{
		state->viewoffset[ grav.x ] = MSG_ReadChar (&net_message) * 0.25;
		state->viewoffset[ grav.y ] = MSG_ReadChar (&net_message) * 0.25 * grav.sign;
		state->viewoffset[ grav.z ] = MSG_ReadShort (&net_message) * 0.25 * grav.sign;
	}

	if (flags & PS_VIEWANGLES)
	{
		state->viewangles[0] = MSG_ReadAngle16 (&net_message);
		state->viewangles[1] = MSG_ReadAngle16 (&net_message);
		state->viewangles[2] = MSG_ReadAngle16 (&net_message);
	}

	if (flags & PS_KICKANGLES)
	{
		state->kick_angles[0] = MSG_ReadChar (&net_message) * 0.25;
		state->kick_angles[1] = MSG_ReadChar (&net_message) * 0.25;
		state->kick_angles[2] = MSG_ReadChar (&net_message) * 0.25;
	}

	if ( flags & PS_MUSICMOOD )
	{
		int b;
		int current_mood, fallback_mood;

		b = MSG_ReadByte (&net_message);
		current_mood = b & 0xf;
		fallback_mood = b >> 4;
		state->current_music_mood = current_mood;
		state->fallback_music_mood = fallback_mood;
		CL_UpdateMood( current_mood, fallback_mood );
	}
	if (flags & PS_GUNOFFSET)
	{
		state->gunoffset[0] = MSG_ReadChar (&net_message)*0.25;
		state->gunoffset[1] = MSG_ReadChar (&net_message)*0.25;
		state->gunoffset[2] = MSG_ReadChar (&net_message)*0.25;
		state->gunangles[0] = MSG_ReadChar (&net_message)*0.25;
		state->gunangles[1] = MSG_ReadChar (&net_message)*0.25;
		state->gunangles[2] = MSG_ReadChar (&net_message)*0.25;
	}

	if (flags & PS_BLEND)
	{
		state->blend[0] = MSG_ReadByte (&net_message)/255.0;
		state->blend[1] = MSG_ReadByte (&net_message)/255.0;
		state->blend[2] = MSG_ReadByte (&net_message)/255.0;
		state->blend[3] = MSG_ReadByte (&net_message)/255.0;
	}

	if (flags & PS_FOV)
		state->fov = MSG_ReadByte (&net_message);

	if (flags & PS_RDFLAGS)
		state->rdflags = MSG_ReadByte (&net_message);

	// parse stats
	statbits = MSG_ReadLong (&net_message);
	for (i=0 ; i<MAX_STATS ; i++)
		if (statbits & (1<<i) )
			state->stats[i] = MSG_ReadShort(&net_message);

}

/*
==================
CL_FireEntityEvents

==================
*/
#if 0
static void CL_FireEntityEvents (frame_t *frame)
{
}
#endif

/*
================
CL_ParseFrame
================
*/
void CL_ParseFrame (void)
{
	int			cmd;
	int			len;
	frame_t		*old;

	memset(&cl.frame, 0, sizeof(cl.frame));

	cl.frame.serverframe = MSG_ReadLong (&net_message);
	cl.frame.deltaframe = MSG_ReadLong (&net_message);
	if(cls.state != ca_active)
	{
		cl.frame.servertime = 0;
	}
	else
	{
		cl.frame.servertime = (cl.frame.serverframe - cl.initial_server_frame) * (100); /* FS */
	}

	// BIG HACK to let old demos continue to work
	if (cls.serverProtocol != 26)
		cl.surpressCount = MSG_ReadByte (&net_message);

	if (cl_shownet->value == 3)
		Com_Printf ("   frame:%i  delta:%i\n", cl.frame.serverframe, cl.frame.deltaframe);

	// If the frame is delta compressed from data that we
	// no longer have available, we must suck up the rest of
	// the frame, but not use it, then ask for a non-compressed
	// message
	if (cl.frame.deltaframe <= 0)
	{
		cl.frame.valid = true;		// uncompressed frame
		old = NULL;
		cls.demowaiting = false;	// we can start recording now
	}
	else
	{
		old = &cl.frames[cl.frame.deltaframe & UPDATE_MASK];
		if (!old->valid)
		{	// should never happen
			Com_Printf ("Delta from invalid frame (not supposed to happen!).\n");
		}
		if (old->serverframe != cl.frame.deltaframe)
		{	// The frame that the server did the delta from
			// is too old, so we can't reconstruct it properly.
			Com_Printf ("Delta frame too old.\n");
		}
		else if (cl.parse_entities - old->parse_entities > MAX_PARSE_ENTITIES-128)
		{
			Com_Printf ("Delta parse_entities too old.\n");
		}
		else
			cl.frame.valid = true;	// valid delta parse
	}

	// clamp time
	if (cl.time > cl.frame.servertime)
		cl.time = cl.frame.servertime;
	else if (cl.time < cl.frame.servertime - 100)
		cl.time = cl.frame.servertime - 100;

	// read areabits
	len = MSG_ReadByte (&net_message);
	MSG_ReadData (&net_message, &cl.frame.areabits, len);

	// read playerinfo
	cmd = MSG_ReadByte (&net_message);
	SHOWNET(svc_strings[cmd]);
	if (cmd != svc_playerinfo)
		Com_Error (ERR_DROP, "CL_ParseFrame: not playerinfo");
	CL_ParsePlayerstate (old, &cl.frame);

	// Check to see if the player is zoomed in and scale his sensitivity
	if ((cl.frame.playerstate.pmove.pm_type == PM_ZOOM) || (cl.frame.playerstate.pmove.pm_type == PM_INVEHICLE_ZOOM))
	{
		cls.scaleSensitivity = zoomscale->value;
	}
	else
	{
		cls.scaleSensitivity = 1.0f;
	}

#ifndef SEPERATE_SURFSANDCONSOLE
	// read console states
	cmd = MSG_ReadByte (&net_message);
	SHOWNET(svc_strings[cmd]);
	if (cmd != svc_consolestates)
		Com_Error (ERR_DROP, "CL_ParseFrame: not consolestates");
	CL_ParseConsoleStates();
	// read surface states
	cmd = MSG_ReadByte (&net_message);
	SHOWNET(svc_strings[cmd]);
	if (cmd != svc_surfacestates)
		Com_Error (ERR_DROP, "CL_ParseFrame: not surfacestates");
	CL_ParseSurfaceStates(false);
#endif

	// read packet entities
	cmd = MSG_ReadByte (&net_message);
	SHOWNET(svc_strings[cmd]);
	if (cmd != svc_packetentities)
		Com_Error (ERR_DROP, "CL_ParseFrame: not packetentities");
	CL_ParsePacketEntities (old, &cl.frame);

	// save the frame off in the backup array for later delta comparisons
	cl.frames[cl.frame.serverframe & UPDATE_MASK] = cl.frame;

	if (cl.frame.valid)
	{
		// getting a valid frame message ends the connection process
		if (cls.state != ca_active)
		{
			cls.state = ca_active;

			cl.initial_server_frame = cl.frame.serverframe; /* FS */
			cl.frame.servertime = (cl.frame.serverframe - cl.initial_server_frame) * (100); /* FS */

			cl.force_refdef = true;

			cl.predicted_origin[0] = cl.frame.playerstate.pmove.origin[0]*0.125;
			cl.predicted_origin[1] = cl.frame.playerstate.pmove.origin[1]*0.125;
			cl.predicted_origin[2] = cl.frame.playerstate.pmove.origin[2]*0.125;
			VectorCopy (cl.frame.playerstate.viewangles, cl.predicted_angles);

			CONSOLE_FadeIn( "1 0 0 0" );

			if (cls.disable_servercount != cl.servercount
				&& cl.refresh_prepped)
				SCR_EndLoadingPlaque ();	// get rid of loading plaque
		}
		cl.sound_prepped = true;	// can start mixing ambient sounds
		CL_CheckPredictionError ();
	}
}

/*
================
CL_ParseExtraData
================
*/
void CL_ParseExtraData (void)
{
	int cmd;

	// read console states
	cmd = MSG_ReadByte (&net_message);
	SHOWNET(svc_strings[cmd]);
	if (cmd != svc_consolestates)
		Com_Error (ERR_DROP, "CL_ParseFrame: not consolestates");
	CL_ParseConsoleStates();
	// read surface states
	cmd = MSG_ReadByte (&net_message);
	SHOWNET(svc_strings[cmd]);
	if (cmd != svc_surfacestates)
		Com_Error (ERR_DROP, "CL_ParseFrame: not surfacestates");
	CL_ParseSurfaceStates(false);
}

#define FADE_OUT_SIZE_MIN  0
#define FADE_OUT_SIZE_MAX  9999.0f
#define FADE_OUT_RATE      1.0f
#define FADE_ANGLE         0.0f
#define FADE_ALPHA_MAX     1.0f

static void LensFlareEffects(entity_t *ent, int renderfx, centity_t *cent)
{
	// Check to see if the player is visible to this flare.
	vec3_t start,end;
	trace_t trace;
	int my_z, sign;

	my_z = gravity_axis[ cl.frame.playerstate.pmove.gravity_axis ].z;
	sign = gravity_axis[ cl.frame.playerstate.pmove.gravity_axis ].sign;

	ent->flags = renderfx | RF_TRANSLUCENT;

	VectorCopy(cl.frame.playerstate.pmove.origin, start);
	VectorScale(start, 0.125, start);

	// Adjust for viewheight;
	if (cl.frame.playerstate.pmove.pm_flags & PMF_DUCKED)
	{
		start[ my_z ] += CROUCH_EYE_HEIGHT * sign;
	}
	else if (!(cl.frame.playerstate.pmove.pm_flags & PMF_NO_PREDICTION))
	{
		start[ my_z ] += STAND_EYE_HEIGHT * sign;
	}

	VectorCopy(ent->origin, end);
	trace = CL_GenericTrace (start, vec3_origin, vec3_origin, end, MASK_OPAQUE, true, -1);

	if (trace.fraction == 1.0)
	{
		ent->alpha = 1.0f;
	}
	else
	{
		ent->alpha = 0;
	}

	cent->current.alpha = ent->alpha;
}


/*
==========================================================================

INTERPOLATE BETWEEN FRAMES TO GET RENDERING PARMS

==========================================================================
*/

/*
===============
CL_AddPacketEntities

===============
*/
static void CL_AddPacketEntities (frame_t *frame)
{
	entity_t			ent;
	entity_state_t		*s1;
	float				autorotate;
	int					i;
	int					pnum;
	centity_t			*cent;
	//int					autoanim;
	clientinfo_t		*ci;
	int					effects, renderfx;
	int					modelframe, modelanim;
	void				*model;
	vec3_t				saveangles={0,0,0};
	bikeinfo_t			*bi;
	qboolean			goggleson; // true if player is using therm-optic goggles

	// bonus items rotate at a fixed rate
	autorotate = anglemod(cl.time/10);

	// brush models can auto animate their frames
	//autoanim = 2*cl.time/1000;

	// set state of therm-optic goggles
	if(cl.frame.playerstate.stats[STAT_NIGHTVISION])
		goggleson = true;
	else
		goggleson = false;

	memset(&ent, 0, sizeof(ent));

	for (pnum = 0 ; pnum<frame->num_entities ; pnum++)
	{
		s1 = &cl_parse_entities[(frame->parse_entities+pnum)&(MAX_PARSE_ENTITIES-1)];

		cent = &cl_entities[s1->number];

		effects  = s1->effects;
		renderfx = s1->renderfx;

		// set frame
		ent.frame = s1->frame;
		ent.oldframe = cent->prev.frame;
		ent.backlerp = 1.0 - cl.lerpfrac;
		ent.anim     = s1->anim;
		ent.scale    = cent->prev.scale + cl.lerpfrac * ( cent->current.scale - cent->prev.scale );
		ent.oldanim  = cent->prev.anim;
		ent.color_r  = cent->prev.color_r + cl.lerpfrac * (cent->current.color_r - cent->prev.color_r);
		ent.color_g  = cent->prev.color_g + cl.lerpfrac * (cent->current.color_g - cent->prev.color_g);
		ent.color_b  = cent->prev.color_b + cl.lerpfrac * (cent->current.color_b - cent->prev.color_b);
		ent.radius   = cent->prev.radius  + cl.lerpfrac * (cent->current.radius  - cent->prev.radius);
		ent.parent   = s1->parent;
		ent.bone     = s1->bone;
		ent.lightofs = s1->lightofs;
		ent.real_entity_number = s1->number;
		memcpy( ent.groups, s1->groups, s1->numgroups );

		ent.gravaxis = EFFECTS_TO_GRAVITYAXIS( effects );

		if (renderfx & RF_FRAMELERP)
		{   // step origin discretely, because the frames
			// do the animation properly
			VectorCopy ( cent->current.origin, ent.origin );
			VectorCopy ( cent->current.old_origin, ent.oldorigin );
		}
		else if ( (renderfx & (RF_BEAM)) || ( effects & (EF_BEAMEFFECT)) )
		{       // lerp both the origin and the old_origin
			VectorCopy ( cent->current.origin, ent.origin );
			VectorCopy ( cent->current.old_origin, ent.oldorigin );
			for (i=0; i<3; i++)
			{
				ent.origin[i] = cent->prev.origin[i] + cl.lerpfrac *
				                (cent->current.origin[i] - cent->prev.origin[i]);
				ent.oldorigin[i] = cent->prev.old_origin[i] + cl.lerpfrac *
				                   (cent->current.old_origin[i] - cent->prev.old_origin[i]);
			}
		}
		else
		{       // interpolate origin
			for (i=0; i<3; i++)
			{
				ent.origin[i] = ent.oldorigin[i] = cent->prev.origin[i] + cl.lerpfrac *
				                                   (cent->current.origin[i] - cent->prev.origin[i]);
			}
		}

		// create a new entity

		if (renderfx & (RF_LENSFLARE))
			ent.alpha = cent->prev.alpha;
		else
			ent.alpha = cent->prev.alpha + cl.lerpfrac * (cent->current.alpha - cent->prev.alpha);

		// adjust alpha for therm-optic goggles
		if(goggleson && (effects & EF_WARM))
		{
			if(ent.alpha < 0.5)
				ent.alpha = 0.5;
		}

		if ( ( renderfx & RF_BEAM ) || ( effects & EF_BEAMEFFECT ) )
		{
			ent.model = NULL;
			ent.skinnum = s1->skinnum;
		}
		else
		{
			// set skin
			if ( renderfx & RF_CUSTOMSKIN )
			{
				// added custom skins for hoverbikes
				if(effects & EF_HOVER) //if EF_HOVER is set, then it's a hoverbike
				{
					// it's a weapon model
					if(s1->gunmodelindex)
					{
						ent.skinnum = s1->skinnum;
						ent.skin = NULL;
						ent.model = cl.model_draw[s1->modelindex];
					}
					else
					{
						// skinnum is the player number
						bi = &cl.bikeinfo[s1->skinnum];
						ent.skinnum = 0;
						if(bi->skin && bi->model)
						{
							ent.skinnum    = 0;
							ent.model      = bi->model;
							ent.skin       = bi->skin;
						}
						else
						{
							ent.skinnum = s1->skinnum;
							ent.skin    = NULL;
							ent.model   = cl.model_draw[s1->modelindex];
						}
					}
				}
				else
				{
					// skinnum is the player number
					ci = &cl.clientinfo[s1->skinnum];

					// Zero out skinnum, so we don't get bogus skins
					ent.skinnum = 0;

					if (ci->skin && ci->model && ci->damageskin)
					{
						ent.model      = ci->model;
						ent.skin       = ci->skin;
						ent.damageskin = ci->damageskin;
					}
					else
					{
						// Skin, model, or damageskin not found, default to NULL
						// which will use the base skin of the player model in the renderer
						ent.skin    = NULL;
						ent.model   = cl.model_draw[s1->modelindex];
					}
				}
			}
			else
			{
				ent.skinnum = s1->skinnum;
				ent.skin = NULL;
				ent.model = cl.model_draw[s1->modelindex];
			}
		}

		ent.flags = renderfx;

		// 2015 code
		// added some rendering effects for therm-optic goggles
		if(goggleson && (effects & EF_WARM))
		{
			ent.flags |= RF_ENVMAPPED;
			ent.flags |= RF_FULLBRIGHT;

			// hack to be able to use different environment maps
			ent.flags &= ~RF_LIGHTOFFSET;
			ent.lightofs = 1;
		}

		if ( ( s1->gunmodelindex ) && !( (s1->parent == cl.playernum+1) && (ent.flags & RF_VIEWERMODEL) ) )
		{
			VectorCopy( cent->current.angles, cent->prev.angles );
		}

		// calculate angles
		if (effects & EF_ROTATE)
		{   // some bonus items auto-rotate
			vec3_t vec;

			vec[ 0 ] = 0;
			vec[ 1 ] = autorotate;
			vec[ 2 ] = 0;

			AnglesToMat( vec, ent.mat );
			VectorCopy( vec, ent.angles );
		}
		else
		{
			//
			// slerp quaternions
			// quaternion to matrix
			//
			float quat[ 4 ];

			if ( !memcmp( cent->current.angles, cent->prev.angles, sizeof( vec3_t ) ) )
			{
				VectorCopy( cent->current.angles, ent.angles );
				AnglesToMat( ent.angles, ent.mat );
			}
			else
			{
				SlerpQuaternion( cent->prev.quat, cent->current.quat, cl.lerpfrac, quat );
				QuatToMat( quat, ent.mat );
				MatrixToEulerAngles( ent.mat, ent.angles );
			}
		}
		//
		// see if we are auto animating
		//
		if ( effects & EF_AUTO_ANIMATE )
		{
			int numframes;

			numframes = re.GetNumFrames( &ent );
			if ( numframes )
				ent.frame = ( cl.time / 100 ) % numframes;
			else
				ent.frame = 0;
		}

		//
		// initialize these variables
		//
		model = ent.model;
		modelframe = ent.frame;
		modelanim = ent.anim;

		if (renderfx & RF_DLIGHT)
			V_AddLight ( ent.origin, ent.radius, ent.color_r, ent.color_g, ent.color_b );

		// 2015 code
		// added secondary view models
		//if((s1->gunmodelindex) && (effects & EF_VIEWMODEL))
		if(effects & EF_VIEWMODEL)
		{
			int tmpgunmodelindex;
			qboolean gunmodelisowner = false;

			//Com_DPrintf("gunmodelindex is : %i\n", s1->gunmodelindex);
			if(s1->gunmodelindex > MAX_VIEWMODELS)
			{
				tmpgunmodelindex = s1->gunmodelindex - MAX_VIEWMODELS;
				if(tmpgunmodelindex == cl.playernum+1)
					gunmodelisowner = true;
			}
			else
				tmpgunmodelindex = s1->gunmodelindex;

			//if (s1->parent == cl.playernum+1 && (ent.flags & RF_VIEWERMODEL))
			if ((s1->parent == cl.playernum+1 || gunmodelisowner) && (ent.flags & RF_VIEWERMODEL))
			{
				//Com_DPrintf("making secondary view model\n");
				if (cent->prev.modelindex != cent->current.modelindex)
				{
					ent.oldanim = ent.anim;
					ent.oldframe = ent.frame;
				}

				if(gunmodelisowner) // automatically use the last slot
					CL_CreateSecondaryViewModel( &ent, MAX_VIEWMODELS );
				else
					CL_CreateSecondaryViewModel( &ent, tmpgunmodelindex - 1 );
			}

			continue;
		}

		//
		// see if it is a weapon and the viewer model
		//
		if ( s1->gunmodelindex )
		{
			if ( (s1->parent == cl.playernum+1) && (ent.flags & RF_VIEWERMODEL) )
			{
				//
				// it is the viewmodel
				//
				if (cent->prev.modelindex != cent->current.modelindex)
				{
					ent.oldanim = ent.anim;
					ent.oldframe = ent.frame;
				}
				CL_CreateViewWeapon( &ent );
				view_weapon_effects = effects;
				continue;
			}
			else
			{
				//
				// This is just a world model weapon
				// we need to attach it normally and swap the modelindex, frame and anim
				//
				// need to check for a hoverbike's custom skin & model
				//ent.model = cl.model_draw[s1->gunmodelindex];
				if((effects & EF_HOVER) && (renderfx & RF_CUSTOMSKIN))
				{
					// skinnum is the player number
					bi = &cl.bikeinfo[s1->skinnum];
					if(bi->skin && bi->model)
					{
						ent.skinnum    = 0;
						ent.model      = bi->model;
						ent.skin       = bi->skin;
					}
					else
					{
						ent.model = cl.model_draw[s1->gunmodelindex];
					}
				}
				else
					ent.model = cl.model_draw[s1->gunmodelindex];
				ent.anim = s1->gunanim;
				ent.oldanim = ent.anim;
				ent.frame = s1->gunframe;
				ent.oldframe = ent.frame;
				ent.scale = 1.0f;

				// Save the angles
				VectorCopy( ent.angles, saveangles );
				VectorClear( ent.angles );
				AnglesToMat( ent.angles, ent.mat );

				// if EF_HOVER is set, then it's a hoverbike, so
				// don't make it detail
				//ent.flags |= RF_DETAIL;
				if(effects & EF_HOVER)
					ent.flags &= ~RF_DETAIL;
				else
					ent.flags |= RF_DETAIL;
			}
		}
		if ( renderfx & (RF_LENSFLARE) )
		{
			LensFlareEffects( &ent,renderfx,cent );
		}

		if ( effects & (EF_BEAMEFFECT) )
		{
			vec3_t vec_zero = {0,0,0};

			CL_CreateBeam( ent.origin,
			               vec_zero,
			               cent,
			               cl.model_draw[s1->modelindex],
			               ent.alpha,
			               ent.scale,
			               ent.skinnum,
			               0,
			               100,
			               false,
			               true,
			               ent.oldorigin,
			               ent.radius,
			               ent.frame,
			               ent.lightofs );
			continue;
		}

		if (ent.model && ((!(effects & EF_BEAMEFFECT) && !((renderfx & RF_LENSFLARE) && !cl_add_glows->value) && !(s1->number == cl.playernum+1 && (ent.flags & RF_VIEWERMODEL))) || (renderfx & RF_BEAM)))
		{
			// add to refresh list
			V_AddEntity ( &ent );
		}

		if ( ( s1->gunmodelindex ) && !( (s1->parent == cl.playernum+1) && (ent.flags & RF_VIEWERMODEL) ) )
		{
			VectorCopy( saveangles, ent.angles );
			VectorCopy( saveangles, ent.angles );
		}

		//
		// execute client commands if necessary
		//
		if ((model) && (((effects & EF_EVERYFRAME) && ( ( cl.time - cent->last_cmdtime ) > 20 ) ) ||
			((modelframe+1) != cent->last_frame) ||
			((modelanim+1) != cent->last_anim) ))
		{
			int handle;
			int entnum;

			if (ent.parent)
				entnum = ent.parent;
			else
				entnum = s1->number;
			cent->last_frame = modelframe+1;
			cent->last_anim = modelanim+1;
			cent->last_cmdtime = cl.time;
			handle = re.SINMDL_GetDefHandle((struct model_t *)model );
			if ( handle >= 0 )
			{
				sinmdl_cmd_t * cmds;

				cmds = SINMDL_Frame_Commands_Client( handle, modelanim, modelframe );
				if ( cmds )
				{
					int j;
					for ( j = 0; j < cmds->num_cmds; j++ )
					{
						current_entity = &ent;
						CL_CMDS_ProcessCommands( entnum, handle, cmds->cmds[j].num_args, (const char **)cmds->cmds[j].args );
						current_entity = NULL;
					}
				}
			}
		}
		// if set to invisible, skip
// fix to allow effects from other people's viewer models
//		if (!ent.model || (ent.flags & RF_VIEWERMODEL))
		if (!ent.model || ((s1->parent == cl.playernum+1) && (ent.flags & RF_VIEWERMODEL)))
			continue;
		//
		// clear out the real_entity_number
		//
		ent.real_entity_number = MAGIC_UNUSED_NUMBER;

		// altered to allow use of several at once
		if (effects & EF_GIB)
		{
			CL_DiminishingTrail ( cent->lerp_origin, ent.origin, cent, effects );
		}
		/*else */if (effects & EF_ROCKET)
		{
			CL_RocketTrail ( cent->lerp_origin, ent.origin, cent );
		}
		/*else */if (effects & EF_PULSE)
		{
			CL_LensFlareParticles ( ent.origin );
		}
// added effect for anime rockets
		if(effects & EF_ANIMEROCKET)
		{
			CL_AnimeRocketTrail( cent->lerp_origin, ent.origin );
		}
// added effect for flame trails
		if(effects & EF_FLAMES)
		{
			// only make flames every 0.1 sec
			if((cl.time - lastflametime) > 10)
				CL_FlameTrail(cent->current.origin, cent->prev.origin);
//				CL_FlameTrail(cent->current.origin, ent.origin);
		}
// added effect for death by flames
		if(effects & EF_DEATHFLAMES)
		{
			// only make flames every 0.1 sec
			if((cl.time - lastflametime) > 10)
				CL_DeathFlames(cent->current.origin, cent->prev.origin, cent);
//				CL_FlameTrail(cent->current.origin, ent.origin);
		}
// hoverbike effects
		if((effects & EF_HOVER) && (ent.parent)) // only do if attached to something
		{
			// only do every 0.1 sec to keep the number of particles under control
//			CL_HoverEffect(ent.origin, cent->current.angles);
			if((cl.time - lastflametime) > 10)
				CL_HoverEffect( &ent );
		}
		if(effects & EF_HOVERTHRUST)
		{
//			CL_HoverThrust(cent->current.origin, cent->lerp_origin, ent.origin, cent->current.angles);
//			CL_HoverThrust(cent->current.origin, &ent);
			CL_HoverThrust( cent->lerp_origin, &ent );
		}
		if(effects & EF_HOVERTURBO)
		{
//			CL_HoverTurbo(cent->current.origin, cent->lerp_origin, ent.origin, cent->current.angles);
//			CL_HoverTurbo(cent->current.origin, &ent);
			CL_HoverTurbo( cent->lerp_origin, &ent );
		}
		// beam trail for plasma bow
		if(effects & EF_PLASMATRAIL1)
		{
			CL_PlasmaTrail( cent->lerp_origin, ent.origin, cent );
		}
		// flame trail for secondary plasma bow
		if(effects & EF_PLASMATRAIL2)
		{
			CL_PlasmaTrail2( cent->lerp_origin, ent.origin );
		}
		if(effects & EF_NUKETRAIL)
		{
			CL_NukeTrailParticles( ent.origin );
		}
		VectorCopy ( ent.origin, cent->lerp_origin );
	}
	// timming valiable for flame effects
	if((cl.time - lastflametime) > 10)
		lastflametime = cl.time;
}

/*
==============
CL_CreateViewWeapon
==============
*/
static entity_t view_weapon;
static void * old_view_model = NULL;
void CL_CreateViewWeapon( entity_t * gun )
{
	memcpy( &view_weapon, gun, sizeof( entity_t ) );
}

qboolean CL_AddSecondaryViewWeapons(player_state_t *ps, player_state_t *ops); 

/*
==============
CL_AddViewWeapon
==============
*/
static void CL_AddViewWeapon (player_state_t *ps, player_state_t *ops)
{
	entity_t	gun;		// view model
	int			i;
	qboolean xflipped = false;
	qboolean secondarys_used;

	// allow the gun to be completely removed
	if (!cl_gun->value)
		return;

	secondarys_used = CL_AddSecondaryViewWeapons(ps, ops);

	memset(&gun, 0, sizeof(gun));

	//
	// make sure their is a valid model index
	//
	if (!view_weapon.model)
		return;

	gun.model = view_weapon.model;

	// set up gun position
	for (i=0; i<3; i++)
	{
		gun.origin[i] = cl.refdef.vieworg[i] + ops->gunoffset[i]
		                + cl.lerpfrac * (ps->gunoffset[i] - ops->gunoffset[i]);
		gun.angles[i] = cl.refdef.viewangles[i] + LerpAngle ( ops->gunangles[i],
		                                                      ps->gunangles[i], cl.lerpfrac );
	}

	AnglesToMat( gun.angles, gun.mat );

	gun.gravaxis = ps->pmove.gravity_axis;
	gun.frame   = view_weapon.frame;
	gun.anim        = view_weapon.anim;
	gun.oldframe    = view_weapon.oldframe;
	gun.oldanim     = view_weapon.oldanim&0x7f;
	memcpy( gun.groups, view_weapon.groups, MAX_MODEL_GROUPS );
	if (gun.anim & 0x80)
	{
		xflipped = true;
		gun.anim &= 0x7f;
	}
	gun.scale = view_weapon.scale;
	gun.flags = view_weapon.flags | RF_MINLIGHT | RF_WEAPONMODEL;
	gun.alpha = view_weapon.alpha;
	gun.real_entity_number = view_weapon.real_entity_number;

	// added to allow for goggles heat signature
	gun.lightofs = view_weapon.lightofs;

//   if (xflipped)
//      gun.flags |= RF_XFLIP;
	// don't allow the view model to be flipped if
	// also using secondary view models
	if(secondarys_used)
	{
		if(xflipped)
		{
			if(hand->value != 1)
				gun.flags |= RF_XFLIP;
		}
		else if(hand->value == 1)
			gun.flags |= RF_XFLIP;
	}
	else if(xflipped)
		gun.flags |= RF_XFLIP;

	gun.backlerp = 1.0 - cl.lerpfrac;
	VectorCopy ( gun.origin, gun.oldorigin ); // don't lerp at all

	if ( !( view_weapon.flags & RF_DONTDRAW ) )
		V_AddEntity ( &gun );

	memset( &view_weapon, 0, sizeof( entity_t ) );

	// So client side effects work
	gun.angles[PITCH] = -gun.angles[PITCH];

	//
	// execute client commands if necessary
	//
	if (gun.model && (gun.frame+1 != ps->last_gunframe || gun.anim+1 != ps->last_gunanim ||
		 ((view_weapon_effects & EF_EVERYFRAME) && cl.time - view_weapon_lastcmdtime > 20)) )
	{
		int handle;

		ps->last_gunframe = gun.frame+1;
		ps->last_gunanim = gun.anim+1;
		view_weapon_lastcmdtime = cl.time;
		handle = re.SINMDL_GetDefHandle( gun.model );
		if ( handle >= 0 )
		{
			sinmdl_cmd_t * cmds;

			cmds = SINMDL_Frame_Commands_Client( handle, gun.anim, gun.frame );
			if ( cmds )
			{
				int j;
				for ( j = 0; j < cmds->num_cmds; j++ )
				{
					current_entity = &gun;
					CL_CMDS_ProcessCommands( cl.playernum+1, handle, cmds->cmds[j].num_args, (const char **)cmds->cmds[j].args );
					current_entity = NULL;
				}
			}
		}
	}
	if ( (void *)gun.model != old_view_model)
	{
		old_view_model = (void *)gun.model;
		CL_UpdateBarrelPosition( &gun );
	}
}

static entity_t view_models[MAX_VIEWMODELS];

void CL_CreateSecondaryViewModel(entity_t *gun, int num)
{
	if(num >= MAX_VIEWMODELS || num < 0)
		return;
	memcpy(&view_models[num], gun, sizeof(entity_t));
}

qboolean CL_AddSecondaryViewWeapons(player_state_t *ps, player_state_t *ops)
{
	entity_t	gun;		// view model
	int			i, vm;
	qboolean	xflipped = false;
	qboolean	secondarys_used = false;

	// allow the gun to be completely removed
	if (!cl_gun->value)
		return false;

	memset(&gun, 0, sizeof(gun));

	for(vm = 0; vm < MAX_VIEWMODELS; vm++)
	{
		//
		// make sure their is a valid model index
		//
		if (!view_models[vm].model)
		{
			memset(&view_models[vm], 0, sizeof(entity_t));
			continue;
		}

		gun.model = view_models[vm].model;

		// set up gun position
		for (i=0 ; i<3 ; i++)
		{
			gun.origin[i] = cl.refdef.vieworg[i] + ops->gunoffset[i]
				+ cl.lerpfrac * (ps->gunoffset[i] - ops->gunoffset[i]);
			gun.angles[i] = cl.refdef.viewangles[i] + LerpAngle (ops->gunangles[i],
				ps->gunangles[i], cl.lerpfrac);
		}

		AnglesToMat( gun.angles, gun.mat );

		gun.frame	= view_models[vm].frame;
		gun.anim		= view_models[vm].anim;
		gun.oldframe	= view_models[vm].oldframe;
		gun.oldanim		= view_models[vm].oldanim&0x7f;
		memcpy( gun.groups, view_models[vm].groups, MAX_MODEL_GROUPS );
		if (gun.anim & 0x80)
		{
			xflipped = true;
			gun.anim &= 0x7f;
		}
		gun.scale = view_models[vm].scale;
		gun.flags = view_models[vm].flags | RF_MINLIGHT | RF_WEAPONMODEL;
		gun.real_entity_number = view_models[vm].real_entity_number;

		// added to allow for goggles heat signature
		gun.lightofs = view_weapon.lightofs;

		gun.alpha = view_models[vm].alpha;

		// flip them for left handed
		if(xflipped)
		{
			if(hand->value != 1)
				gun.flags |= RF_XFLIP;
		}
		else if(hand->value == 1)
			gun.flags |= RF_XFLIP;

		gun.backlerp = 1.0 - cl.lerpfrac;
		VectorCopy (gun.origin, gun.oldorigin);	// don't lerp at all

		V_AddEntity (&gun);

		memset(&view_models[vm], 0, sizeof(entity_t));

		secondarys_used = true;
	}

	return secondarys_used;
}

static void MyVectorsToEulerAngles(vec3_t forward, vec3_t right, vec3_t up,vec3_t ang)
{
	double theta;
	double cp;
	double sp;

	sp = forward[ 2 ];

	// cap off our sin value so that we don't get any NANs
	if ( sp > 1.0 )
		sp = 1.0;
	if ( sp < -1.0 )
		sp = -1.0;

	theta = -asin( sp );
	cp = cos( theta );

	if ( cp > 8192 * FLT_EPSILON )
	{
		ang[ 0 ] = theta * 180 / M_PI;
		ang[ 1 ] = atan2( forward[ 1 ], forward[ 0 ] ) * 180 / M_PI;
		ang[ 2 ] = atan2( -right[ 2 ], up[ 2 ] ) * 180 / M_PI;
	}
	else
	{
		ang[ 0 ] = theta * 180 / M_PI;
		ang[ 1 ] = -atan2( right[ 0 ], -right[ 1 ] ) * 180 / M_PI;
		ang[ 2 ] = 0;
	}
}

static void MyVectorsToEulerAngles2(vec3_t forward, vec3_t right, vec3_t up, vec3_t ang)
{
	double theta;
	double cp;
	double sp;

	sp = forward[ 2 ];

	// cap off our sin value so that we don't get any NANs
	if ( sp > 1.0 )
		sp = 1.0;
	if ( sp < -1.0 )
		sp = -1.0;

	theta = -asin( sp );
	cp = cos( theta );

	if ( cp > 8192 * FLT_EPSILON )
	{
		ang[ 0 ] = theta * 180 / M_PI;
		ang[ 1 ] = atan2( forward[ 1 ], forward[ 0 ] ) * 180 / M_PI;
		ang[ 2 ] = atan2( -right[ 2 ], up[ 2 ] ) * 180 / M_PI;
	}
	else
	{
		ang[ 0 ] = theta * 180 / M_PI;
		if ( theta > 0 )
		{
			ang[ 1 ] = 180;
			ang[ 2 ] = atan2( right[ 0 ], -right[ 1 ] ) * 180 / M_PI;
		}
		else
		{
			ang[ 1 ] = 0;
			ang[ 2 ] = atan2( right[ 0 ], -right[ 1 ] ) * 180 / M_PI;
		}
	}
}

/*
===============
CL_CalcViewValues

Sets cl.refdef view values
===============
*/
static void CL_CalcViewValues (void)
{
	int			i;
	float		lerp, backlerp;
	//centity_t	*ent;
	frame_t		*oldframe;
	player_state_t	*ps, *ops;
	vec3_t		t[ 3 ];
	gravityaxis_t grav;
	float		*oldviewoffset;

	// find the previous frame to interpolate from
	ps = &cl.frame.playerstate;
	i = (cl.frame.serverframe - 1) & UPDATE_MASK;
	oldframe = &cl.frames[i];
	if (oldframe->serverframe != cl.frame.serverframe-1 || !oldframe->valid)
	{
		oldframe = &cl.frame;		// previous frame was dropped or involid
	}
	ops = &oldframe->playerstate;

	// see if the player entity was teleported this frame
	if ( (game2015->value &&
		(cl.frame.playerstate.pmove.pm_type == PM_LOCKVIEW ||
		 cl.frame.playerstate.pmove.pm_type == PM_FREEZE) &&
		(abs(ops->pmove.origin[0] - ps->pmove.origin[0]) >  64*8 ||
		 abs(ops->pmove.origin[1] - ps->pmove.origin[1]) >  64*8 ||
		 abs(ops->pmove.origin[2] - ps->pmove.origin[2]) >  64*8)
	     ) ||
		(abs(ops->pmove.origin[0] - ps->pmove.origin[0]) > 512*8 ||
		 abs(ops->pmove.origin[1] - ps->pmove.origin[1]) > 512*8 ||
		 abs(ops->pmove.origin[2] - ps->pmove.origin[2]) > 512*8) )
	{
		ops = ps;		// don't interpolate
	}

	//ent = &cl_entities[cl.playernum+1];
	lerp = cl.lerpfrac;

	// don't interpolate viewoffset when gravity axis has changed
	if ( ops->pmove.gravity_axis != ps->pmove.gravity_axis )
	{
		oldviewoffset = ps->viewoffset;
	}
	else
	{
		oldviewoffset = ops->viewoffset;
	}

	// calculate the origin
	if ((cl_predict->value) && !(cl.frame.playerstate.pmove.pm_flags & PMF_NO_PREDICTION))
	{	// use predicted values
		unsigned delta;

		backlerp = 1.0 - lerp;
		for (i=0 ; i<3 ; i++)
		{
			cl.refdef.vieworg[i] = cl.predicted_origin[i] + oldviewoffset[i]
				+ cl.lerpfrac * (ps->viewoffset[i] - oldviewoffset[i])
				- backlerp * cl.prediction_error[i];
		}
		// smooth out stair climbing
		delta = cls.realtime - cl.predicted_step_time;
		if (delta < 100)
		{
			cl.refdef.vieworg[2] -= cl.predicted_step * (100 - delta) * 0.01;
		}
	}
	else
	{	// just use interpolated values
		for (i=0 ; i<3 ; i++)
		{
			cl.refdef.vieworg[i] = ops->pmove.origin[i]*0.125 + oldviewoffset[i]
				+ lerp * (ps->pmove.origin[i]*0.125 + ps->viewoffset[i]
				- (ops->pmove.origin[i]*0.125 + oldviewoffset[i]) );
		}
	}

	// if not running a demo or on a locked frame, add the local angle movement
	if((cl.frame.playerstate.pmove.pm_type < PM_LOCKVIEW) || (cl.frame.playerstate.pmove.pm_type > PM_MOVECAPTURED)
		|| ((cl.frame.playerstate.pmove.pm_type == PM_FREEZE) && (cl.frame.playerstate.rdflags & RDF_CINEMATIC_NO_LERP))) /* FS: Don't lerp for in-game cinematics */
	{	// use predicted values
		for (i=0 ; i<3 ; i++)
		{
			cl.refdef.viewangles[i] = cl.predicted_angles[i];
		}
	}
	else
	{	// just use interpolated values
		for (i=0 ; i<3 ; i++)
		{
			cl.refdef.viewangles[i] = LerpAngle (ops->viewangles[i], ps->viewangles[i], lerp);
		}
	}

	for (i=0 ; i<3 ; i++)
	{
		cl.refdef.viewangles[i] += LerpAngle (ops->kick_angles[i], ps->kick_angles[i], lerp);
	}

	if ( ps->pmove.gravity_axis )
	{
		grav = gravity_axis[ ps->pmove.gravity_axis ];

		AngleVectors (cl.refdef.viewangles, t[0], t[1], t[2] );
		cl.v_forward[ grav.x ] = t[ 0 ][ 0 ];
		cl.v_forward[ grav.y ] = t[ 0 ][ 1 ] * grav.sign;
		cl.v_forward[ grav.z ] = t[ 0 ][ 2 ] * grav.sign;
		cl.v_right[ grav.x ] = t[ 1 ][ 0 ];
		cl.v_right[ grav.y ] = t[ 1 ][ 1 ] * grav.sign;
		cl.v_right[ grav.z ] = t[ 1 ][ 2 ] * grav.sign;
		cl.v_up[ grav.x ] = t[ 2 ][ 0 ];
		cl.v_up[ grav.y ] = t[ 2 ][ 1 ] * grav.sign;
		cl.v_up[ grav.z ] = t[ 2 ][ 2 ] * grav.sign;
		switch( ps->pmove.gravity_axis )
		{
		case 1:
		case 4:
			MyVectorsToEulerAngles( cl.v_forward, cl.v_right, cl.v_up, cl.refdef.viewangles);
			break;
		case 2:
		case 5:
			MyVectorsToEulerAngles2( cl.v_forward, cl.v_right, cl.v_up, cl.refdef.viewangles);
			break;
		default:
			VectorsToEulerAngles( cl.v_forward, cl.v_right, cl.v_up, cl.refdef.viewangles);
			break;
		}
	}
	else
	{
		AngleVectors (cl.refdef.viewangles, cl.v_forward, cl.v_right, cl.v_up);
	}

	// interpolate field of view
	cl.refdef.fov_x = ops->fov + lerp * (ps->fov - ops->fov);

	// don't interpolate blend color
	for (i=0 ; i<4 ; i++)
	{
		cl.refdef.blend[i] = ps->blend[i];
	}

	// add the weapon
	CL_AddViewWeapon (ps, ops);
}

/*
===============
CL_AddEntities

Emits all entities, particles, and lights to the refresh
===============
*/
void CL_AddEntities (void)
{
	if (cls.state != ca_active)
		return;

	if (cl.time > cl.frame.servertime)
	{
		if (cl_showclamp->value)
			Com_Printf ("high clamp %i\n", cl.time - cl.frame.servertime);
		cl.time = cl.frame.servertime;
		cl.lerpfrac = 1.0;
	}
	else if (cl.time < cl.frame.servertime - 100)
	{
		if (cl_showclamp->value)
			Com_Printf ("low clamp %i\n", cl.frame.servertime-100 - cl.time);
		cl.time = cl.frame.servertime - 100;
		cl.lerpfrac = 0;
	}
	else
		cl.lerpfrac = 1.0 - (cl.frame.servertime - cl.time) * 0.01;

	if (CL_IsADemoWithTimeDemo() || cl_dumpdemo->value) /* FS: timedemo only in demos, please */
		cl.lerpfrac = 1.0;

	CL_AddPacketEntities (&cl.frame);
	CL_AddTEnts ();
	CL_AddParticles ();
	CL_AddTriangles ();
	// advance local effects for this frame
	CL_RunDLights ();
	CL_RunLightStyles ();
	CL_AddDLights ();
	CL_AddLightStyles ();

	CL_CalcViewValues ();
	CL_AddBeams();
	CL_AddTempmodels ();
	CL_AddTempdlights ();
}


/*
===============
CL_GetEntitySoundOrigin

Called to get the sound spatialization origin
===============
*/
void CL_GetEntitySoundOrigin (int ent, vec3_t org)
{
	centity_t	*old;

	if (ent < 0 || ent >= MAX_EDICTS)
	{
		Com_DPrintf(DEVELOPER_MSG_CLIENT, "CL_GetEntitySoundOrigin: bad ent");
		return;
	}

	old = &cl_entities[ent];
	VectorCopy (old->lerp_origin, org);

	// FIXME: bmodel issues...
}

/*
===============
CL_GetEntityScale

Called to get the scale of an entity
===============
*/
float CL_GetEntityScale( int ent )
{
	centity_t	*old;

	if (ent < 0 || ent >= MAX_EDICTS)
	{
		Com_DPrintf(DEVELOPER_MSG_CLIENT, "CL_GetEntityScale: bad ent");
		return 1.0f;
	}

	old = &cl_entities[ent];
	if ( !old->current.scale )
	{
		return 1;
	}
	else
	{
		return old->current.scale;
	}
}

/*
===============
CL_CalculateViewVolume

Calculates an approximation of the current volume the viewer is in
returns true if approximation is valid
===============
*/
qboolean CL_CalculateViewVolume( vec3_t mins, vec3_t maxs )
{
	trace_t	trace;
	vec3_t	start;
	vec3_t	end;
	vec3_t	pmins, pmaxs;
	vec3_t	v_forward = {1,0,0};
	vec3_t	v_right = {0,1,0};
	vec3_t	v_up = {0,0,1};

	if (cls.state != ca_active)
	{
		return false;
	}

	if (cl_paused->value)
	{
		return false;
	}

	mins[ 0 ] = mins[ 1 ] = mins[ 2 ] = 99999;
	maxs[ 0 ] = maxs[ 1 ] = maxs[ 2 ] = -99999;
	pmins[ 0 ] = pmins[ 1 ] = -16;
	pmaxs[ 0 ] = pmaxs[ 1 ] = 16;
	pmins[ 2 ] = 0;
	pmaxs[ 2 ] = 32;

	VectorCopy( cl.refdef.vieworg, start );

	// north 
	VectorCopy( start, end );
	VectorMA( end, 4096, v_forward, end );
	trace = CL_WorldTrace ( start, pmins, pmaxs, end, MASK_PLAYERSOLID );
	VectorSubtract( trace.endpos, start, end );
	AddPointToBounds( end, mins, maxs );

	// north east
	VectorCopy( start, end );
	VectorMA( end, 4096, v_forward, end );
	VectorMA( end, 4096, v_right, end );
	trace = CL_WorldTrace ( start, pmins, pmaxs, end, MASK_PLAYERSOLID );
	VectorSubtract( trace.endpos, start, end );
	AddPointToBounds( end, mins, maxs );

	// east 
	VectorCopy( start, end );
	VectorMA( end, 4096, v_right, end );
	trace = CL_WorldTrace ( start, pmins, pmaxs, end, MASK_PLAYERSOLID );
	VectorSubtract( trace.endpos, start, end );
	AddPointToBounds( end, mins, maxs );

	// south east
	VectorCopy( start, end );
	VectorMA( end, -4096, v_forward, end );
	VectorMA( end, 4096, v_right, end );
	trace = CL_WorldTrace ( start, pmins, pmaxs, end, MASK_PLAYERSOLID );
	VectorSubtract( trace.endpos, start, end );
	AddPointToBounds( end, mins, maxs );

	// south 
	VectorCopy( start, end );
	VectorMA( end, -4096, v_forward, end );
	trace = CL_WorldTrace ( start, pmins, pmaxs, end, MASK_PLAYERSOLID );
	VectorSubtract( trace.endpos, start, end );
	AddPointToBounds( end, mins, maxs );

	// south west
	VectorCopy( start, end );
	VectorMA( end, -4096, v_forward, end );
	VectorMA( end, -4096, v_right, end );
	trace = CL_WorldTrace ( start, pmins, pmaxs, end, MASK_PLAYERSOLID );
	VectorSubtract( trace.endpos, start, end );
	AddPointToBounds( end, mins, maxs );

	// west
	VectorCopy( start, end );
	VectorMA( end, -4096, v_right, end );
	trace = CL_WorldTrace ( start, pmins, pmaxs, end, MASK_PLAYERSOLID );
	VectorSubtract( trace.endpos, start, end );
	AddPointToBounds( end, mins, maxs );

	// north west
	VectorCopy( start, end );
	VectorMA( end, 4096, v_forward, end );
	VectorMA( end, -4096, v_right, end );
	trace = CL_WorldTrace ( start, pmins, pmaxs, end, MASK_PLAYERSOLID );
	VectorSubtract( trace.endpos, start, end );
	AddPointToBounds( end, mins, maxs );

	// up
	VectorCopy( start, end );
	VectorMA( end, 4096, v_up, end );
	trace = CL_WorldTrace ( start, pmins, pmaxs, end, MASK_PLAYERSOLID );
	VectorSubtract( trace.endpos, start, end );
	AddPointToBounds( end, mins, maxs );

	// down
	VectorCopy( start, end );
	VectorMA( end, -4096, v_up, end );
	trace = CL_WorldTrace ( start, pmins, pmaxs, end, MASK_PLAYERSOLID );
	VectorSubtract( trace.endpos, start, end );
	AddPointToBounds( end, mins, maxs );

	// up north east
	VectorCopy( start, end );
	VectorMA( end, 4096, v_forward, end );
	VectorMA( end, 4096, v_right, end );
	VectorMA( end, 4096, v_up, end );
	trace = CL_WorldTrace ( start, pmins, pmaxs, end, MASK_PLAYERSOLID );
	VectorSubtract( trace.endpos, start, end );
	AddPointToBounds( end, mins, maxs );

	// up south east
	VectorCopy( start, end );
	VectorMA( end, -4096, v_forward, end );
	VectorMA( end, 4096, v_right, end );
	VectorMA( end, 4096, v_up, end );
	trace = CL_WorldTrace ( start, pmins, pmaxs, end, MASK_PLAYERSOLID );
	VectorSubtract( trace.endpos, start, end );
	AddPointToBounds( end, mins, maxs );

	// up south west
	VectorCopy( start, end );
	VectorMA( end, -4096, v_forward, end );
	VectorMA( end, -4096, v_right, end );
	VectorMA( end, 4096, v_up, end );
	trace = CL_WorldTrace ( start, pmins, pmaxs, end, MASK_PLAYERSOLID );
	VectorSubtract( trace.endpos, start, end );
	AddPointToBounds( end, mins, maxs );

	// up north west
	VectorCopy( start, end );
	VectorMA( end, 4096, v_forward, end );
	VectorMA( end, -4096, v_right, end );
	VectorMA( end, 4096, v_up, end );
	trace = CL_WorldTrace ( start, pmins, pmaxs, end, MASK_PLAYERSOLID );
	VectorSubtract( trace.endpos, start, end );
	AddPointToBounds( end, mins, maxs );

	return true;
}

/*
===============
CL_ClassifyViewerPosition

Figures out which surface we are standing on and whether we are underwater
===============
*/
qboolean CL_ClassifyViewerPosition( int * surfacetype, qboolean * inwater, qboolean * inmutantmode )
{
	int contents;
	trace_t	trace;
	csurface_t *s;
	vec3_t start;
	vec3_t end;
	vec3_t vec_zero = {0,0,0};

	if (cls.state != ca_active)
	{
		return false;
	}

	if (cl_paused->value)
	{
		return false;
	}

	if (cl.map_sequence_number != map_sequence_number)
	{
		return false;
	}

	*surfacetype = 0;
	*inwater = false;

	if ( cl.frame.playerstate.pmove.pm_flags & PMF_MUTANT )
	{
		*inmutantmode = true;
	}
	else
	{
		*inmutantmode = false;
	}

	VectorCopy( cl.refdef.vieworg, start );
	VectorCopy( start, end );
	end[ 2 ] -= 2048;
	trace = CL_GenericTrace ( start, vec_zero, vec_zero, end, MASK_PLAYERSOLID, false, 1 );
	s = trace.surface;
	if (s)
	{
		*surfacetype = s->flags & MASK_SURF_TYPE;
		contents = CL_PMpointcontents ( trace.endpos );
		if ( contents & MASK_WATER )
		{
			start[ 2 ] += 48;
			contents = CL_PMpointcontents ( start );
			if ( contents & MASK_WATER )
			{
				*inwater = true;
			}
			*surfacetype = SURF_TYPE_WATER;
		}
	}
	return true;
}
