// Copyright (C) 2001-2002 Raven Software
//
// cg_draw.c -- draw all of the graphical elements during
// active (after loading) gameplay

#include "cg_local.h"

#include "../ui/ui_shared.h"

// used for scoreboard
extern displayContextDef_t cgDC;

void CG_DrawRadar ( void );
void CG_DrawAutomap ( void );
void CG_DrawTimers ( void );

/*
================
CG_Draw3DModel
================
*/
void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ) 
{
	refdef_t		refdef;
	refEntity_t		ent;

	CG_AdjustFrom640( &x, &y, &w, &h );

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

	memset( &ent, 0, sizeof( ent ) );
	AnglesToAxis( angles, ent.axis );
	VectorCopy( origin, ent.origin );
	ent.hModel = model;
	ent.customSkin = skin;
	ent.renderfx = RF_NOSHADOW;		// no stencil shadows

	refdef.rdflags = RDF_NOWORLDMODEL;

	AxisClear( refdef.viewaxis );

	refdef.fov_x = 30;
	refdef.fov_y = 30;

	refdef.x = x;
	refdef.y = y;
	refdef.width = w;
	refdef.height = h;

	refdef.time = cg.time;

	trap_R_ClearScene();
	trap_R_AddRefEntityToScene( &ent );
	trap_R_RenderScene( &refdef );
}

void CG_Draw3DG2Model( float x, float y, float w, float h, void *ghoul2, qhandle_t skin, vec3_t origin, vec3_t angles ) 
{
	refdef_t		refdef;
	refEntity_t		ent;

	CG_AdjustFrom640( &x, &y, &w, &h );

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

	memset( &ent, 0, sizeof( ent ) );
	AnglesToAxis( angles, ent.axis );
	VectorCopy( origin, ent.origin );
	ent.ghoul2 = ghoul2;
	ent.customSkin = skin;
	ent.renderfx = RF_NOSHADOW;		// no stencil shadows

	refdef.rdflags = RDF_NOWORLDMODEL;

	AxisClear( refdef.viewaxis );

	refdef.fov_x = 30;
	refdef.fov_y = 30;

	refdef.x = x;
	refdef.y = y;
	refdef.width = w;
	refdef.height = h;

	refdef.time = cg.time;

	trap_R_ClearScene();
	trap_R_AddRefEntityToScene( &ent );
	trap_R_RenderScene( &refdef );
}

/*
==================
CG_DrawSnapshot
==================
*/
static float CG_DrawSnapshot( float y ) 
{
	char		*s;
	int			w;

	s = va( "time:%i snap:%i cmd:%i", cg.snap->serverTime, 
		cg.latestSnapshotNum, cgs.serverCommandSequence );

	w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.53f, 0 );

	CG_DrawText ( 635 - w, y - 6, cgs.media.hudFont, 0.53f, colorWhite, s, 0, 0 );

	return 20;
}

/*
==================
CG_DrawFPS
==================
*/
#define	FPS_FRAMES	4
static float CG_DrawFPS( float y ) 
{
	char		*s;
	int			w;
	static int	previousTimes[FPS_FRAMES];
	static int	index;
	int		i, total;
	int		fps;
	static	int	previous;
	int		t, frameTime;

	// don't use serverTime, because that will be drifting to
	// correct for internet lag changes, timescales, timedemos, etc
	t = trap_Milliseconds();
	frameTime = t - previous;
	previous = t;

	previousTimes[index % FPS_FRAMES] = frameTime;
	index++;
	
	if ( index > FPS_FRAMES ) 
	{
		// average multiple frames together to smooth changes out a bit
		total = 0;
		for ( i = 0 ; i < FPS_FRAMES ; i++ ) {
			total += previousTimes[i];
		}
		if ( !total ) {
			total = 1;
		}
		fps = 1000 * FPS_FRAMES / total;

		s = va( "%i fps", fps );

		w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.53f, 0 );

		CG_DrawText ( 635 - w, y, cgs.media.hudFont, 0.53f, g_color_table[ColorIndex(COLOR_GREEN)], s, 0,0 );
	}

	return y + 20;
}

/*
=====================
CG_DrawRadar
=====================
*/
#define RADAR_RANGE				2500
#define RADAR_RADIUS			60
#define RADAR_X					(580 - RADAR_RADIUS)
#define RADAR_Y					10
#define RADAR_CHAT_DURATION		6000

void CG_DrawRadar ( void )
{
	vec4_t			color;
	vec4_t			teamColor;
	float			arrow_w;
	float			arrow_h;
	clientInfo_t	*cl;
	clientInfo_t	*local;
	int				i;
	float			scale;

	// Make sure the radar should be showing
	if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 || cg.weaponMenuUp )
	{
		return;
	}

	if ( (cg.predictedPlayerState.pm_flags & PMF_GHOST) || cgs.clientinfo[cg.predictedPlayerState.clientNum].team == TEAM_SPECTATOR )
	{
		return;
	}

	local = &cgs.clientinfo[ cg.snap->ps.clientNum ];
	if ( !local->infoValid )
	{
		return;
	}

	// Draw the radar background image
	color[0] = color[1] = color[2] = 1.0f;
	color[3] = 0.6f;
	trap_R_SetColor ( color );
	CG_DrawPic( RADAR_X, RADAR_Y, RADAR_RADIUS*2, RADAR_RADIUS*2, cgs.media.radarShader );

	arrow_w = 16 * RADAR_RADIUS / 128;
	arrow_h = 16 * RADAR_RADIUS / 128;

	// determine the color of the arrows to draw
	switch(local->team)
	{
		default:
		case TEAM_RED:
			VectorCopy ( g_color_table[ColorIndex(COLOR_RED)], teamColor );
			break;

		case TEAM_BLUE:
			VectorCopy ( g_color_table[ColorIndex(COLOR_BLUE)], teamColor );
			break;
	}

	// Darken the color a tad
	VectorScale ( teamColor, 0.5f, teamColor );
	teamColor[3] = 1.0f;

	// Draw all of the radar entities.  Draw them backwards so players are drawn last
	for ( i = cg.radarEntityCount -1 ; i >= 0 ; i-- ) 
	{	
		vec3_t		dirLook;	
		vec3_t		dirPlayer;
		float		angleLook;
		float		anglePlayer;
		float		angle;
		float		distance;
		centity_t*	cent;

		cent = cg.radarEntities[i];

		// Get the distances first
		VectorSubtract ( cg.predictedPlayerState.origin, cent->lerpOrigin, dirPlayer );		
		dirPlayer[2] = 0;
		distance = VectorNormalize ( dirPlayer );

		if ( distance > RADAR_RANGE * 0.8f) 
		{
			continue;
		}

		distance  = distance / RADAR_RANGE;
		distance *= RADAR_RADIUS;

		AngleVectors ( cg.predictedPlayerState.viewangles, dirLook, NULL, NULL );

		dirLook[2] = 0;
		anglePlayer = atan2(dirPlayer[0],dirPlayer[1]);		
		VectorNormalize ( dirLook );
		angleLook = atan2(dirLook[0],dirLook[1]);
		angle = angleLook - anglePlayer;

		switch ( cent->currentState.eType )
		{
			case ET_ITEM:
				if ( cg_items[cent->currentState.modelindex].registered )
				{
					float  x;
					float  y;
		
					x = (float)RADAR_X + (float)RADAR_RADIUS + (float)sin (angle) * distance;
					y = (float)RADAR_Y + (float)RADAR_RADIUS + (float)cos (angle) * distance;

					trap_R_SetColor ( NULL );
					CG_DrawPic ( x - 4, y - 4, 9, 9, cg_items[cent->currentState.modelindex].icon );
				}
				break;

			case ET_PLAYER:
			{
				vec4_t color;

				cl = &cgs.clientinfo[ cent->currentState.number ];

				// not valid then dont draw it
				if ( !cl->infoValid ) 
				{	
					continue;
				}

				if ( cent->currentState.gametypeitems )
				{
					VectorCopy4 ( g_color_table[ColorIndex(COLOR_YELLOW)], color );
				}
				else
				{
					VectorCopy4 ( teamColor, color );
				}

				if (cl->mLastChatTime+RADAR_CHAT_DURATION > cg.time)
				{
					vec3_t finalColor;

					scale = ((cg.time - cl->mLastChatTime) / (float)RADAR_CHAT_DURATION);
					scale *= scale;

					finalColor[0] = (color[0] * (scale)) + (colorWhite[0] * (1.0-scale));
					finalColor[1] = (color[1] * (scale)) + (colorWhite[1] * (1.0-scale));
					finalColor[2] = (color[2] * (scale)) + (colorWhite[2] * (1.0-scale));
					finalColor[3] = color[3];
					trap_R_SetColor ( finalColor );
					scale += 1.0;
				}
				else
				{
					trap_R_SetColor ( color );
					scale = 1.0;
				}

				CG_DrawRotatePic2( RADAR_X + RADAR_RADIUS + sin (angle) * distance,
								   RADAR_Y + RADAR_RADIUS + cos (angle) * distance, 
								   arrow_w, arrow_h, 
								   (360 - cent->lerpAngles[YAW]) + cg.predictedPlayerState.viewangles[YAW], cgs.media.mAutomapPlayerIcon );
				break;
			}
		}
	}

	trap_R_SetColor ( colorWhite );
	CG_DrawRotatePic2( RADAR_X + RADAR_RADIUS, RADAR_Y + RADAR_RADIUS, arrow_w, arrow_h, 
					   0, cgs.media.mAutomapPlayerIcon );
}

/*
=====================
CG_DrawTeamScores
=====================
*/
static void CG_DrawTeamScores ( float y )
{
	char		scores[2][16];
	float		w;
	const char* s;

	if ( cgs.scores1 == SCORE_NOT_PRESENT ) 
	{
		Com_sprintf (scores[0], sizeof(scores[0]), "-");
	}
	else 
	{
		Com_sprintf (scores[0], sizeof(scores[0]), "%i", cgs.scores1);
	}

	if ( cgs.scores2 == SCORE_NOT_PRESENT ) 
	{
		Com_sprintf (scores[1], sizeof(scores[1]), "-");
	}
	else 
	{
		Com_sprintf (scores[1], sizeof(scores[1]), "%i", cgs.scores2);
	}

	s = va ( "Red: %s  Blue: %s", scores[0], scores[1] );

	w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.35f, 0 );

	CG_DrawText ( RADAR_X + RADAR_RADIUS - w / 2, y, cgs.media.hudFont, 0.35f, g_color_table[ColorIndex(COLOR_GREEN)], s, 0, DT_OUTLINE );
}

/*
=====================
CG_DrawUpperRight
=====================
*/
static void CG_DrawUpperRight( void ) 
{
	float	y;

	y = 0;

	if ( cg.scoreBoardShowing )
	{
		return;
	}

	if ( cg_drawSnapshot.integer ) 
	{
		y = CG_DrawSnapshot( y );
	}
	
	switch ( cg_drawRadar.integer  )
	{
		// Off unless the key is pressed
		case 0:
			if ( cg.showAutomap )
			{
				// If in rmg default to the auto map and if in a non team game
				// the radar is useless so default to automap as well.
				if ( cg.mInRMG || !cgs.gametypeData->teams )
				{
					CG_DrawAutomap ( );
				}
				else
				{
					CG_DrawRadar ( );
				}
			}
			break;
		
		// Draw the radar unless the automap key is down
		case 1:
			if ( cg.showAutomap && cg.mInRMG )
			{
				CG_DrawAutomap ( );
			}
			// If its a team game allow radar
			else if ( cgs.gametypeData->teams )
			{
				CG_DrawRadar ( );
			}
			break;
		
		// Draw the automap only, but if the key is pressed show the radar
		case 2:
			if ( cgs.gametypeData->teams && (cg.showAutomap || !cg.mInRMG) )
			{
				CG_DrawRadar ( );
			}
			else
			{			
				CG_DrawAutomap ( );
			}
			break;
	}	

	if ( cg_drawFPS.integer ) 
	{
		y = CG_DrawFPS( y );
		y = 18;
	}
	else
	{
		y = 10;
	}

	if ( cg_drawTeamScores.integer && cgs.gametypeData->teams )
	{
		CG_DrawTeamScores ( y );		
	}

	CG_DrawTimers ( );
}

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

LAGOMETER

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

#define	LAG_SAMPLES		128


typedef struct {
	int		frameSamples[LAG_SAMPLES];
	int		frameCount;
	int		snapshotFlags[LAG_SAMPLES];
	int		snapshotSamples[LAG_SAMPLES];
	int		snapshotCount;
} lagometer_t;

lagometer_t		lagometer;

/*
==============
CG_AddLagometerFrameInfo

Adds the current interpolate / extrapolate bar for this frame
==============
*/
void CG_AddLagometerFrameInfo( void ) 
{
	int			offset;

	offset = cg.time - cg.latestSnapshotTime;
	lagometer.frameSamples[ lagometer.frameCount & ( LAG_SAMPLES - 1) ] = offset;
	lagometer.frameCount++;
}

/*
==============
CG_AddLagometerSnapshotInfo

Each time a snapshot is received, log its ping time and
the number of snapshots that were dropped before it.

Pass NULL for a dropped packet.
==============
*/
void CG_AddLagometerSnapshotInfo( snapshot_t *snap ) 
{
	// dropped packet
	if ( !snap ) 
	{
		lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = -1;
		lagometer.snapshotCount++;
		return;
	}

	// add this snapshot's info
	lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->ping;
	lagometer.snapshotFlags[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->snapFlags;
	lagometer.snapshotCount++;
}

/*
==============
CG_DrawDisconnect

Draws a little connection icon on the screen when connection to the 
server is lost
==============
*/
static void CG_DrawDisconnect ( void ) 
{
	float		x;
	float		y;
	int			cmdNum;
	usercmd_t	cmd;

	// draw the phone jack if we are completely past our buffers
	cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1;
	trap_GetUserCmd( cmdNum, &cmd );

	// special check for map_restart
	if ( cmd.serverTime <= cg.snap->ps.commandTime || cmd.serverTime > cg.time ) 
	{	
		return;
	}

	// blink the icon
	if ( ( cg.time >> 9 ) & 1 ) 
	{
		return;
	}

	x = 640 - 76;
	y = 480 - 165;

	CG_DrawPic( x - 3, y - 3, 56, 56, cgs.media.disconnectShader );
}


static vec4_t ChatColor =
{	// green
	0.0, 1.0, 0.0, 1.0
};

/*
==============
CG_DrawAutomap

Draws the rmg map with any radar items placed on it.
==============
*/
void CG_DrawAutomap ( void ) 
{
	TCGConvertPos	*pos = (TCGConvertPos *)cg.sharedBuffer;
	clientInfo_t	*cl, *local;
	int				i;
	qhandle_t		teamIcon;
	vec4_t			teamColor, finalColor;
	int				arrow_w;
	int				arrow_h;
	int				item_w;
	int				item_h;
	float			scale;

	// Only enabled in the RMG
	if (!cg.mInRMG || cg.weaponMenuUp )
	{
		return;
	}

	// invalid parms, so don't draw!
	if (cg_automap_x.integer < 0 || cg_automap_x.integer > 640-32 ||
		cg_automap_y.integer < 0 || cg_automap_y.integer > 480-32 ||
		cg_automap_w.integer < 32 || cg_automap_w.integer > 640 ||
		cg_automap_h.integer < 32 || cg_automap_h.integer > 480 ||
		cg_automap_x.integer + cg_automap_w.integer > 640 ||
		cg_automap_y.integer + cg_automap_h.integer > 480)
	{	
		Com_Printf("Automap drawing coordinates out of range!\n");
		return;
	}

	// Register the automap image if not already registerd
	if ( !cgs.media.mAutomap )
	{
		trap_CM_TM_Upload(0, 0);		
		cgs.media.mAutomap = trap_R_RegisterShader( "gfx/menus/rmg/automap" );
	}

	finalColor[0] = finalColor[1] = finalColor[2] = 1.0;
	finalColor[3] = cg_automap_a.value;
	if (finalColor[3] > 1.0)
	{
		finalColor[3] = 1.0;
	}

	if ( cgs.media.mAutomap)
	{
		trap_R_SetColor (finalColor);
		CG_DrawPic( cg_automap_x.integer, cg_automap_y.integer, cg_automap_w.integer, cg_automap_h.integer, cgs.media.mAutomap );
	}

	local = &cgs.clientinfo[ cg.snap->ps.clientNum ];
	if ( !local->infoValid)
	{
		return;
	}

	arrow_w = 16 * cg_automap_w.integer / 512;
	arrow_h = 16 * cg_automap_h.integer / 512;
	item_w  = 24 * cg_automap_w.integer / 512;
	item_h  = 24 * cg_automap_h.integer / 512;

	switch(local->team)
	{
		case TEAM_RED:
			teamColor[0] = 1.0;
			teamColor[1] = 0.0;
			teamColor[2] = 0.0;
			break;
		case TEAM_BLUE:
			teamColor[0] = 0.0;
			teamColor[1] = 0.0;
			teamColor[2] = 1.0;
			break;
		case TEAM_SPECTATOR:
		default:
			teamColor[0] = 1.0;
			teamColor[1] = 1.0;
			teamColor[2] = 1.0;
			break;
	}
	teamIcon = cgs.media.mAutomapPlayerIcon;
	teamColor[3] = cg_automap_a.value + 0.1;
	if (teamColor[3] > 1.0)
	{
		teamColor[3] = 1.0;
	}

	// Only team games show other people on the automap
	if ( cgs.gametypeData->teams )
	{
		// Draw all of the radar entities.  Draw them backwards so players are drawn last
		for ( i = cg.radarEntityCount -1 ; i >= 0 ; i-- ) 
		{	
			centity_t*	cent;

			cent = cg.radarEntities[i];

			switch ( cent->currentState.eType )
			{
				case ET_ITEM:
					if ( cg_items[cent->currentState.modelindex].registered )
					{
						VectorCopy( cent->lerpOrigin, pos->mOrigin);
						pos->mWidth = cg_automap_w.integer;
						pos->mHeight = cg_automap_h.integer;
						trap_CM_TM_ConvertPosition();
			
						trap_R_SetColor ( NULL );
						CG_DrawPic ( pos->mX - item_w / 2 + cg_automap_x.integer, 
									 pos->mY - item_h / 2 + cg_automap_y.integer, 
									 item_w, item_h, cg_items[cent->currentState.modelindex].icon );
					}
					break;

				case ET_PLAYER:

					cl = &cgs.clientinfo[ cent->currentState.number ];

					// not valid then dont draw it
					if ( !cl->infoValid ) 
					{	
						continue;
					}

					if (cl->mLastChatTime+RADAR_CHAT_DURATION > cg.time)
					{
						vec3_t finalColor;

						scale = ((cg.time - cl->mLastChatTime) / (float)RADAR_CHAT_DURATION);
						scale *= scale;

						finalColor[0] = (teamColor[0] * (scale)) + (colorWhite[0] * (1.0-scale));
						finalColor[1] = (teamColor[1] * (scale)) + (colorWhite[1] * (1.0-scale));
						finalColor[2] = (teamColor[2] * (scale)) + (colorWhite[2] * (1.0-scale));
						finalColor[3] = teamColor[3];
						trap_R_SetColor ( finalColor );
						scale += 1.0;
					}
					else
					{
						trap_R_SetColor ( teamColor );
						scale = 1.0;
					}

					VectorCopy( cent->lerpOrigin, pos->mOrigin);
					pos->mWidth = cg_automap_w.integer;
					pos->mHeight = cg_automap_h.integer;
					trap_CM_TM_ConvertPosition();

					CG_DrawRotatePic2( pos->mX + cg_automap_x.integer, 
									   pos->mY + cg_automap_y.integer, 
									   arrow_w*scale, arrow_h*scale, 
									   (360 - cent->lerpAngles[1]) - 90.0, teamIcon );
					break;
				}
		}
	}

	VectorCopy(cg.refdef.vieworg, pos->mOrigin);
	pos->mWidth = cg_automap_w.integer;
	pos->mHeight = cg_automap_h.integer;
	trap_CM_TM_ConvertPosition();

	teamColor[0] = teamColor[1] = teamColor[2] = 1.0;
	teamColor[3] = cg_automap_a.value + 0.2;
	if (teamColor[3] > 1.0)
	{
		teamColor[3] = 1.0;
	}
	trap_R_SetColor ( teamColor );
	scale = 1.0;

	CG_DrawRotatePic2( pos->mX + cg_automap_x.integer, pos->mY + cg_automap_y.integer, arrow_w*scale, arrow_h*scale, 
		(360 - cg.refdef.viewangles[1]) - 90.0, cgs.media.mAutomapPlayerIcon );
}

#define	MAX_LAGOMETER_PING	900
#define	MAX_LAGOMETER_RANGE	300

/*
==============
CG_DrawLagometer
==============
*/
static void CG_DrawLagometer( void ) 
{
	int		a, x, y, i;
	float	v;
	float	ax, ay, aw, ah, mid, range;
	int		color;
	float	vscale;

	trap_R_SetColor( NULL );

	if ( !cg_lagometer.integer || cgs.localServer ) 
	{
		CG_DrawDisconnect();
		return;
	}

	// draw the graph
	x = 640 - 76;
	y = 480 - 165;

	CG_DrawPic( x - 3, y - 3, 56, 56, cgs.media.lagometerShader );

	ax = x;
	ay = y;
	aw = 48;
	ah = 48;
	CG_AdjustFrom640( &ax, &ay, &aw, &ah );

	color = -1;
	range = ah / 3;
	mid = ay + range;

	vscale = range / MAX_LAGOMETER_RANGE;

	// draw the frame interpoalte / extrapolate graph
	for ( a = 0 ; a < aw ; a++ ) {
		i = ( lagometer.frameCount - 1 - a ) & (LAG_SAMPLES - 1);
		v = lagometer.frameSamples[i];
		v *= vscale;
		if ( v > 0 ) {
			if ( color != 1 ) {
				color = 1;
				trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] );
			}
			if ( v > range ) {
				v = range;
			}
			trap_R_DrawStretchPic ( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, NULL, cgs.media.whiteShader );
		} else if ( v < 0 ) {
			if ( color != 2 ) {
				color = 2;
				trap_R_SetColor( g_color_table[ColorIndex(COLOR_BLUE)] );
			}
			v = -v;
			if ( v > range ) {
				v = range;
			}
			trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, NULL, cgs.media.whiteShader );
		}
	}

	// draw the snapshot latency / drop graph
	range = ah / 2;
	vscale = range / MAX_LAGOMETER_PING;

	for ( a = 0 ; a < aw ; a++ ) {
		i = ( lagometer.snapshotCount - 1 - a ) & (LAG_SAMPLES - 1);
		v = lagometer.snapshotSamples[i];
		if ( v > 0 ) {
			if ( lagometer.snapshotFlags[i] & SNAPFLAG_RATE_DELAYED ) {
				if ( color != 5 ) {
					color = 5;	// YELLOW for rate delay
					trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] );
				}
			} else {
				if ( color != 3 ) {
					color = 3;
					trap_R_SetColor( g_color_table[ColorIndex(COLOR_GREEN)] );
				}
			}
			v = v * vscale;
			if ( v > range ) {
				v = range;
			}
			trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, NULL, cgs.media.whiteShader );
		} else if ( v < 0 ) {
			if ( color != 4 ) {
				color = 4;		// RED for dropped snapshots
				trap_R_SetColor( g_color_table[ColorIndex(COLOR_RED)] );
			}
			trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, NULL, cgs.media.whiteShader );
		}
	}

	trap_R_SetColor( NULL );

	CG_DrawDisconnect();
}

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

CENTER PRINTING

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


/*
==============
CG_CenterPrint

Called for important messages that should stay in the center of the screen
for a few moments
==============
*/
void CG_CenterPrint( const char *str, float scale ) 
{
	char	*s;

	Q_strncpyz( cg.centerPrint, str, sizeof(cg.centerPrint) );

	cg.centerPrintTime = cg.time;
	cg.centerPrintScale = scale;

	// count the number of lines for centering
	cg.centerPrintLines = 1;
	s = cg.centerPrint;
	while( *s ) 
	{
		if (*s == '\n')
		{
			cg.centerPrintLines++;
		}

		s++;
	}
}

/*
===================
CG_DrawCenterString
===================
*/
static void CG_DrawCenterString( void ) 
{
	char	*start;
	int		l;
	int		x, y, w;
	int		h;
	float	*color;

	if ( !cg_centertime.value )
	{
		return;
	}

	if ( !cg.centerPrintTime ) 
	{
		return;
	}

	color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value );
	if ( !color ) 
	{
		return;
	}

	trap_R_SetColor( color );

	start = cg.centerPrint;

	y = cg_centerY.integer;

	while ( 1 ) 
	{
		char linebuffer[1024];

		for ( l = 0; l < 50; l++ ) 
		{
			if ( !start[l] || start[l] == '\n' ) 
			{
				break;
			}
			
			linebuffer[l] = start[l];
		}
		linebuffer[l] = 0;

		w = trap_R_GetTextWidth(linebuffer, cgs.media.hudFont, cg.centerPrintScale, 0 );
		h = trap_R_GetTextHeight(linebuffer, cgs.media.hudFont, cg.centerPrintScale, 0 );
		x = (SCREEN_WIDTH - w) / 2;
		CG_DrawText (x, y + h, cgs.media.hudFont, cg.centerPrintScale, color, linebuffer, 0, DT_OUTLINE );
		y += h + 6;

		while ( *start && ( *start != '\n' ) ) 
		{
			start++;
		}
		
		if ( !*start ) 
		{
			break;
		}
		start++;
	}

	trap_R_SetColor( NULL );
}

/*
=================
CG_DrawCenterText
=================
*/
static void CG_DrawCenterText ( void )
{
	int w;
	int h;

	if ( cgs.gametypeMessageTime < cg.time )
	{
		CG_DrawCenterString ( );
		return;
	}

	w = trap_R_GetTextWidth( cgs.gametypeMessage, cgs.media.hudFont, 0.43f, 0 );
	h = trap_R_GetTextHeight( cgs.gametypeMessage, cgs.media.hudFont, 0.43f, 0 );
	CG_DrawText ( (SCREEN_WIDTH - w) / 2, cg_centerY.integer + h, cgs.media.hudFont, 0.43f, colorWhite, cgs.gametypeMessage, 0, DT_OUTLINE );
}


/*
=================
CG_DrawCrosshair
=================
*/
static void CG_DrawCrosshair(void) 
{
	float		w;
	float		h;
	qhandle_t	hShader;
	float		x;
	float		y;
	float		scale;
	int			ca;
	qboolean	zoomed;

	if ( !cg_drawCrosshair.integer ) 
	{
		return;
	}

	if ( cg.predictedPlayerState.stats[STAT_USEWEAPONDROP] )
	{
		return;
	}

	// If zoomed or unzoomed with the sniper rifle dont draw the standard crosshair
	zoomed = (cg.predictedPlayerState.pm_flags&PMF_ZOOMED);
	if ( zoomed || (cg.predictedPlayerState.weapon==WP_MSG90A1 && !zoomed) )
	{
		return;
	}

	if ( cg.snap->ps.pm_type == PM_SPECTATOR ) 
	{
		return;
	}

	if ( cg.renderingThirdPerson ) 
	{
		return;
	}

	// Default crosshair color
	trap_R_SetColor( cg.crosshairRGBA );

	// Change the crosshair color when its on friendly targets
	if ( cg.crosshairColorClientNum != -1 && cgs.gametypeData->teams )	
	{
		// Is the crosshair over a friendly target?
		if ( cgs.clientinfo[ cg.crosshairColorClientNum ].team == cgs.clientinfo[ cg.snap->ps.clientNum ].team )
		{
			trap_R_SetColor( cg.crosshairFriendRGBA );
		}
	}

	w = h = cg_crosshairSize.value;

	// Determine the 
	if ( cg_crosshairGrow.integer )
	{
		scale = ((float)cg.predictedPlayerState.inaccuracy / ((float)weaponData[cg.predictedPlayerState.weapon].attack[ATTACK_NORMAL].maxInaccuracy+1));
		scale = 1 + scale * 1; 
	}
	else
	{
		scale = 1;
	}

	w = w * scale;
	h = h * scale;

	{
		x = cg_crosshairX.integer;
		y = cg_crosshairY.integer;
	}

	CG_AdjustFrom640( &x, &y, &w, &h );

	ca = cg_drawCrosshair.integer;
	if (ca < 0) 
	{
		ca = 0;
	}

	hShader = cgs.media.crosshairShader[ ca % NUM_CROSSHAIRS ];

	trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * (cg.refdef.width - w), 
		y + cg.refdef.y + 0.5 * (cg.refdef.height - h), 
		w, h, 0, 0, 1, 1, NULL, hShader );
}

qboolean CG_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y)
{
	int	xcenter, ycenter;
	vec3_t	local, transformed;
	vec3_t	vfwd;
	vec3_t	vright;
	vec3_t	vup;
	float xzi;
	float yzi;

//	xcenter = cg.refdef.width / 2;//gives screen coords adjusted for resolution
//	ycenter = cg.refdef.height / 2;//gives screen coords adjusted for resolution
	
	//NOTE: did it this way because most draw functions expect virtual 640x480 coords
	//	and adjust them for current resolution
	xcenter = 640 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn
	ycenter = 480 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn

	AngleVectors (cg.refdef.viewangles, vfwd, vright, vup);

	VectorSubtract (worldCoord, cg.refdef.vieworg, local);

	transformed[0] = DotProduct(local,vright);
	transformed[1] = DotProduct(local,vup);
	transformed[2] = DotProduct(local,vfwd);		

	// Make sure Z is not negative.
	if(transformed[2] < 0.01)
	{
		return qfalse;
	}

	xzi = xcenter / transformed[2] * (90.0/cg.refdef.fov_x);
	yzi = ycenter / transformed[2] * (90.0/cg.refdef.fov_y);

	*x = xcenter + xzi * transformed[0];
	*y = ycenter - yzi * transformed[1];

	return qtrue;
}

qboolean CG_WorldCoordToScreenCoord( vec3_t worldCoord, int *x, int *y )
{
	float	xF, yF;
	qboolean retVal = CG_WorldCoordToScreenCoordFloat( worldCoord, &xF, &yF );
	*x = (int)xF;
	*y = (int)yF;
	return retVal;
}

/*
=================
CG_ScanForCrosshairEntity
=================
*/
static void CG_ScanForCrosshairEntity( void ) 
{
	trace_t		trace;
	vec3_t		start;
	vec3_t		end;
	int			content;

	VectorCopy( cg.refdef.vieworg, start );
	VectorMA( start, 131072, cg.refdef.viewaxis[0], end );

	cg.crosshairColorClientNum = -1;

	CG_Trace( &trace, start, vec3_origin, vec3_origin, end, 
		cg.snap->ps.clientNum, MASK_SHOT );
	if ( trace.entityNum >= MAX_CLIENTS ) {
		return;
	}

	// if the player is in fog, don't show it
	content = trap_CM_PointContents( trace.endpos, 0 );
	if ( content & CONTENTS_FOG ) {
		return;
	}

	// People playing a team game cant see the name of people not on their team, unless
	// they are spectating.
	if ( cgs.gametypeData->teams )
	{
		if ( cg.predictedPlayerState.pm_type != PM_SPECTATOR && !(cg.predictedPlayerState.pm_flags&PMF_GHOST) && !(cg.predictedPlayerState.pm_flags&PMF_FOLLOW) )
		{
			if ( cgs.clientinfo[ trace.entityNum ].team != cg.predictedPlayerState.persistant[PERS_TEAM] )
			{
				return;
			}
		}
	}

	// update the fade timer
	cg.crosshairClientNum = trace.entityNum;
	cg.crosshairClientTime = cg.time;
	cg.crosshairColorClientNum = trace.entityNum;
}

/*
=====================
CG_DrawCrosshairNames
=====================
*/
static void CG_DrawCrosshairNames( void ) 
{
	float		*color;
	char		*name;
	float		w;
	int			y;

	if ( !cg_drawCrosshair.integer ) 
	{
		return;
	}
	
	if ( !cg_drawCrosshairNames.integer ) 
	{
		return;
	}
	
	if ( cg.renderingThirdPerson ) 
	{
		return;
	}

	// If the player isnt spectating then make sure he cant see enemies names
	if ( cgs.gametypeData->teams )
	{
		if ( cg.predictedPlayerState.pm_type != PM_SPECTATOR && !(cg.predictedPlayerState.pm_flags&PMF_GHOST) && !(cg.predictedPlayerState.pm_flags&PMF_FOLLOW) )
		{
			if ( cgs.clientinfo[ cg.crosshairClientNum ].team != cg.predictedPlayerState.persistant[PERS_TEAM] )
			{
				return;
			}
		}
	}

	// draw the name of the player being looked at
	color = CG_FadeColor( cg.crosshairClientTime, 1000 );
	if ( !color ) {
		trap_R_SetColor( NULL );
		return;
	}

	name = cgs.clientinfo[ cg.crosshairClientNum ].name;

	if ( cg_drawCrosshairNames.integer == 2 )  // Just below the crosshair
	{
		y = (SCREEN_HEIGHT / 2) + 20 ;
	}
	else
	{
		y = 465 ;
	}


	color[3] *= 0.5f;
	w = trap_R_GetTextWidth(name, cgs.media.hudFont, 0.43f, 0);
	CG_DrawText( 320 - w / 2, y, cgs.media.hudFont, 0.43f, color, name, 0, 0 );

	trap_R_SetColor( NULL );
}


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

/*
=================
CG_DrawSpectator
=================
*/
static void CG_DrawSpectator(void) 
{
	const char* s;
	float		y;

	if ( cg.scoreBoardShowing )
	{
		return;
	}

	y = 415;

	// Need to be a ghost or someone following who isnt a spectator to see the respawn time
	if ( (cg.predictedPlayerState.pm_flags & PMF_GHOST) ||
	     ((cg.snap->ps.pm_flags & PMF_FOLLOW) && cgs.clientinfo[cg.clientNum].team != TEAM_SPECTATOR) )
	{
		if ( cg.predictedPlayerState.respawnTimer )
		{
			int time;
			time = cg.predictedPlayerState.respawnTimer - cg.time;
			time /= 1000;
			if ( time < 1 )
			{
				time = 1;
			}
			s = va("RESPAWN IN %i SECONDS", time );
		}
		else
		{
			s = "GHOST";
		}
	}
	else
		s = "SPECTATOR";

	if ( (cg.snap->ps.pm_flags & PMF_FOLLOW) )
	{
		y = 65;
	}

	CG_DrawText ( 320 - trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.45f, 0 ) / 2,
					y, cgs.media.hudFont, 0.45f, colorWhite, s, 0, DT_OUTLINE );
	
	// Draw some instructions on the screen for joining the game when the client is a spectator
	if ( cgs.clientinfo[cg.clientNum].team == TEAM_SPECTATOR )
	{
		// Make sure they arent following someone and arent a ghost 
		if ( !(cg.predictedPlayerState.pm_flags & (PMF_GHOST|PMF_FOLLOW)) ) 
		{
			s = "press ESC and use the PLAYER menu to play";
			CG_DrawText( 320 - trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.43f, 0 ) / 2,
							y + 15, cgs.media.hudFont, 0.43f, colorWhite, s, 0, DT_OUTLINE );
		}
	}
}

/*
=================
CG_DrawVote
=================
*/
static void CG_DrawVote(void) 
{
	char	*s;
	int		sec;

	if ( !cgs.voteTime ) 
	{
		return;
	}

	// play a talk beep whenever it is modified
	if ( cgs.voteModified ) 
	{
		cgs.voteModified = qfalse;
		trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
	}

	sec = ( cgs.voteDuration - ( cg.time - cgs.voteTime ) ) / 1000;
	
	if ( sec < 0 ) 
	{
		sec = 0;
	}

	s = va("VOTE(%i):%s", sec, cgs.voteString );
	CG_DrawText ( 10, 58, cgs.media.hudFont, 0.40f, colorLtGrey, s, 0, DT_OUTLINE );

	s = va("needed:%i yes:%i no:%i", cgs.voteNeeded, cgs.voteYes, cgs.voteNo);
	CG_DrawText ( 10, 70, cgs.media.hudFont, 0.40f, colorLtGrey, s, 0, DT_OUTLINE );
}

/*
=================
CG_DrawIntermission
=================
*/
static void CG_DrawIntermission( void ) 
{
	cg.scoreFadeTime = cg.time;
	cg.scoreBoardShowing = CG_DrawScoreboard();
}

/*
=================
CG_DrawFollow
=================
*/
static qboolean CG_DrawFollow( void ) 
{
	const char *s;

	if ( cg.scoreBoardShowing )
	{
		return qfalse;
	}

	if ( !(cg.snap->ps.pm_flags & PMF_FOLLOW) ) 
	{
		return qfalse;
	}

	s = va("following %s", cgs.clientinfo[ cg.snap->ps.clientNum ].name );
	CG_DrawText ( 320 - trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.63f, 0 ) / 2,
					40, cgs.media.hudFont, 0.63f, colorWhite, s, 0, DT_OUTLINE );

	CG_DrawSpectator ( );

	return qtrue;
}

/*
=================
CG_DrawWarmup
=================
*/
static void CG_DrawWarmup( void ) 
{
	int				w;
	int				sec;
	const char		*s;

	sec = cg.warmup;
	if ( !sec ) 
	{
		return;
	}

	if ( sec < 0 ) 
	{
		s = "Waiting for players";		

		w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.53f, 0 );
		CG_DrawText ( 320 - w / 2, 24, cgs.media.hudFont, 0.53f, colorWhite, s, 0, DT_OUTLINE );

		cg.warmupCount = 0;
		return;
	}

	sec = ( sec - cg.time ) / 1000;
	if ( sec < 0 ) 
	{
		cg.warmup = 0;
		sec = 0;
	}

	s = va( "map restart in: %i", sec + 1 );
	if ( sec != cg.warmupCount ) 
	{
		cg.warmupCount = sec;
	}

	w = trap_R_GetTextWidth(s, cgs.media.hudFont, 0.53f, 0 );
	CG_DrawText (320 - w / 2, 155, cgs.media.hudFont, 0.53f, colorWhite, s, 0, DT_OUTLINE );
}

/* 
=================
CG_DrawChat
=================
*/
static void CG_DrawChat ( void ) 
{
	float	w;
	int		h;
	int		i;
	int		chatHeight;
	float	y;
	float	x;
	
	// Grab the users text height but dont let it get bigger than the define
	chatHeight = cg_chatHeight.integer;
	if (chatHeight > CHAT_HEIGHT )
	{
		chatHeight = CHAT_HEIGHT;
	}

	// Is the chat enabled right now?
	if ( chatHeight <= 0 )
	{
		return; 
	}

	// Nothing to draw 
	if (cgs.chatLastPos == cgs.chatPos ) 
	{
		return;
	}

	// Is it time to stop drawing the current chat message?
	if ( cg.time - cgs.chatTime[cgs.chatLastPos % chatHeight] > cg_chatTime.integer) 
	{
		cgs.chatLastPos++;
	}

	// Determine how tall the entire chat block is
	h = (cgs.chatPos - cgs.chatLastPos) * 15;

	if ( cg.scoreBoardShowing )
	{
		y = 395;
		x = 50;

		if ( cg.scoreBoardBottom + 10 > y - h )
		{
			y = cg.scoreBoardBottom + 10 + h;
		}

		if ( y > 480 )
		{
			y = 475;
		}
	}
	else
	{
		y = 380;
		x = 35;
	}

	// Find the greatest width out of the strings rendered
	for (w = 0, i = cgs.chatLastPos; i < cgs.chatPos; i++) 
	{
		float tw = trap_R_GetTextWidth ( cgs.chatText[i % chatHeight], cgs.media.hudFont, 0.43f, 0 );

		if (tw > w)
		{
			w = tw;
		}
	}

	for (i = cgs.chatPos - 1; i >= cgs.chatLastPos ; i--) 
	{
		qhandle_t font = cgs.media.hudFont;
		float     scale = 0.38f;

		CG_DrawText ( x, y - (cgs.chatPos - i - 1) * 15,
					  font, scale, colorWhite, cgs.chatText[i % chatHeight], 0, DT_OUTLINE );
	}
}

/* 
=================
CG_DrawTimedMenus
=================
*/
void CG_DrawTimedMenus() 
{
	if (cg.voiceTime) 
	{
		int t = cg.time - cg.voiceTime;
		if ( t > 2500 ) 
		{
			Menus_CloseByName("voiceMenu");
			trap_Cvar_Set("cl_conXOffset", "0");
			cg.voiceTime = 0;
		}
	}
}

/*
=================
CG_DrawMapChange
=================
*/
void CG_DrawMapChange ( void )
{
	const char	*s;
	int			w;
	float		x;

	// Draw a nice background image
	CG_DrawStretchPic ( 0, 0, 640, 480, 0, 0, 1, 1, colorWhite, 
						trap_R_RegisterShaderNoMip ( "gfx/menus/backdrop/pra1_sof2_logo" ) );

	s = "Server Changing Maps";
	w = trap_R_GetTextWidth(s, cgs.media.hudFont, 0.53f, 0 );
	x = (SCREEN_WIDTH - w) / 2;
	CG_DrawText (x, 360, cgs.media.hudFont, 0.53f, colorWhite, s, 0, 0 );

	s = "Please wait";
	w = trap_R_GetTextWidth(s, cgs.media.hudFont, 0.53f, 0 );
	x = (SCREEN_WIDTH - w) / 2;
	CG_DrawText (x, 400, cgs.media.hudFont, 0.53f, colorWhite, s, 0, 0 );
}

/*
==================
CG_DrawTimers

Draws the round, total, and frozer timers
==================
*/
void CG_DrawTimers ( void )
{
	int y;
	int x;

	if ( !cg_drawTimer.integer )
	{
		return;
	}

	y = 435;
	x = 120;

	if ( cg.predictedPlayerState.stats[STAT_FROZEN] )
	{
		CG_DrawTimer ( x, y, cgs.media.hudFont, 0.53f, colorGreen, DT_OUTLINE, -cg.predictedPlayerState.stats[STAT_FROZEN] );
	}
	else if ( cgs.gametypeTimerTime != 0  )
	{		
		if ( cgs.gametypeTimerTime < cg.time )
		{
			return;
		}	

		CG_DrawTimer ( x, y, cgs.media.hudFont, 0.53f, colorGreen, DT_OUTLINE, cgs.gametypeTimerTime - cg.time );
	}
}

/*
==================
CG_DrawFlashBang

Renders and handles the progression of the flashgrenade.  The flash grenade is just a white 
overlay on the whole screen which fades out over time
==================
*/
static void CG_DrawFlashBang ( void )
{
	vec4_t color;

	// Is there an active flash bang?
	if ( cg.flashbangTime + cg.flashbangFadeTime <= cg.time )
	{
		return;
	}

	// Spectators and dead people dont need to see a flash so it can stop here
	if ( (cg.predictedPlayerState.pm_flags & (PMF_GHOST|PMF_FOLLOW)) || cg.predictedPlayerState.pm_type != PM_NORMAL )
	{
		cg.flashbangTime = 0;
		return;
	}
	
	VectorCopy ( colorWhite, color );
	color[3] = cg.flashbangAlpha * (1.0f - ((float)(cg.time - cg.flashbangTime) / (float)cg.flashbangFadeTime));

	color[3] *=  2.0;
	if (color[3]<0)
	{
		color[3]=0;
	}
	
	if (color[3]>1) 
	{
		color[3]=1;
	}

	CG_FillRect ( 0, 0, 640, 480, color );
}

/*
=================
CG_DrawHUDIcons

draws the currnet list of hud icons in the bottom left corner
=================
*/
static void CG_DrawHUDIcons ( void )
{
	int	  i;
	float x;

	// User turn off hud icons?
	if ( !cg_drawHUDIcons.integer )
	{
		return;
	}

	x = 25;

	for ( i = 0; i < MAX_HUDICONS; i ++ )
	{
		// No hud icon? skip it
		if ( !cgs.hudIcons[i] )
		{
			continue;
		}

		CG_DrawPic ( x, 425, 32, 32, cgs.gameIcons[ cgs.hudIcons[i] ] );

		x += 40;
	}
}

/*
=================
CG_Draw2D
=================
*/
static void CG_Draw2D( void ) 
{
	static qboolean oldScoreBoardShowing = qfalse;

	// if we are taking a levelshot for the menu, don't draw anything
	if ( cg.levelShot ) 
	{
		return;
	}

	if (cg.mMapChange)
	{
		CG_DrawMapChange ( );
		return;
	}

	if ( cg_draw2D.integer == 0 ) 
	{
		CG_DrawFlashBang ( );
		return;
	}

	// Handle the diabling of the console messages when in the scoreboard.  enough
	// clutter already on the scoreboard without seeing those too.
	if ( oldScoreBoardShowing != cg.scoreBoardShowing )
	{
		trap_Cvar_Set ( "con_draw", cg.scoreBoardShowing?"0":"1" );
		oldScoreBoardShowing = cg.scoreBoardShowing;
	}

	if ( cg.snap->ps.pm_type == PM_INTERMISSION ) 
	{
		CG_DrawIntermission();
		CG_DrawChat ( );
		return;
	}

	CG_DrawFlashBang ( );

	// scan the known entities to see if the crosshair is sighted on one
	CG_ScanForCrosshairEntity();

	if ( cg.snap->ps.pm_type == PM_SPECTATOR || cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) 
	{
		CG_DrawSpectator();
		CG_DrawCrosshair();
		CG_DrawCrosshairNames();
		CG_DrawHUDIcons();
	} 
	else 
	{
		// don't draw any status if dead or the scoreboard is being explicitly shown
		if ( cg.snap->ps.stats[STAT_HEALTH] > 0 ) 
		{
			Menu_PaintAll();
			
			if ( !cg.showScores )
			{
				CG_DrawHUDIcons();
				CG_DrawTimedMenus();   
				CG_DrawCrosshair();
				CG_DrawCrosshairNames();
			}
		} 
	}

	CG_DrawVote();

	CG_DrawLagometer();

	if (!cg_paused.integer) 
	{
		CG_DrawUpperRight();
	}

	if ( !CG_DrawFollow() ) 
	{
		CG_DrawWarmup();
	}

	// don't draw center string if scoreboard is up
	cg.scoreBoardShowing = CG_DrawScoreboard();
	if ( !cg.scoreBoardShowing) 
	{
		CG_DrawCenterText();
	}

	// Always Draw chat
	CG_DrawChat ( );
}

/*
=====================
CG_DrawActive

Perform all drawing needed to completely fill the screen
=====================
*/
void CG_DrawActive( stereoFrame_t stereoView ) 
{
	float		separation;
	vec3_t		baseOrg;
	float		parm1, parm2;

	// optionally draw the info screen instead
	if ( !cg.snap ) 
	{
		CG_DrawInformation();
		return;
	}

	if (cg.mMapChange)
	{
		CG_DrawMapChange ( );
		return;
	}

	// Handle the start of an inf match
	if ( cg.predictedPlayerState.persistant[PERS_TEAM] != TEAM_SPECTATOR &&
		 !(cg.predictedPlayerState.pm_flags & PMF_GHOST) && 
	     !(cg.predictedPlayerState.pm_flags & PMF_FOLLOW) )
	{
		if ( cgs.gametypeTimerTime >= cg.time && !cg.predictedPlayerState.stats[STAT_FROZEN] && cgs.gametypeTimerTime != 0 && !cg.gametypeStarted )
		{
			CG_CenterPrint( "GO!", 1.1f );
			cg.gametypeStarted = qtrue;

			trap_S_StartLocalSound ( cgs.media.goSound, CHAN_AUTO );
		}
	}

	// Popup the objectives scren if we need to
	if( cg.popupObjectives && !cg.demoPlayback)
	{
		char temp[MAX_INFO_STRING];
		char lastobjectives[MAX_INFO_STRING];

		Com_sprintf ( lastobjectives, MAX_INFO_STRING, "%s_%s_%d", cgs.mapname, cgs.gametypeData->name, cgs.gameID );
		trap_Cvar_VariableStringBuffer ( "cg_lastobjectives", temp, MAX_INFO_STRING );

		if ( Q_stricmp ( temp, lastobjectives ) )
		{
			if ( !cgs.gametypeData->description )
			{
				// If the client isnt on a team yet and this is a team game, bring up the team dialog
				if ( cgs.gametypeData->teams && cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_SPECTATOR )
				{
					trap_SendConsoleCommand ( "ui_team;" );
				}
				// Providing outfitting is available bring up the outfitting dialog
				else if ( cgs.pickupsDisabled )
				{
					trap_SendConsoleCommand ( "ui_outfitting;" );
				}
			}
			else
			{
				// If already on a team then no need to choose a team
				if ( cgs.clientinfo[cg.clientNum].team != TEAM_SPECTATOR )
				{
					trap_Cvar_Set ( "ui_info_seenobjectives", "1" );
				}
		
				trap_SendConsoleCommand ( "ui_objectives;" );
			}

			trap_Cvar_Set ( "cg_lastobjectives", lastobjectives );
		}

		cg.popupObjectives = qfalse;
	}

	switch ( stereoView ) 
	{
		case STEREO_CENTER:
			separation = 0;
			break;
		case STEREO_LEFT:
			separation = -cg_stereoSeparation.value / 2;
			break;
		case STEREO_RIGHT:
			separation = cg_stereoSeparation.value / 2;
			break;
		default:
			separation = 0;
			Com_Error( ERR_FATAL, "CG_DrawActive: Undefined stereoView" );
	}

	// clear around the rendered view if sized down
	CG_TileClear();

	// offset vieworg appropriately if we're doing stereo separation
	VectorCopy( cg.refdef.vieworg, baseOrg );
	if ( separation != 0 ) {
		VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[1], cg.refdef.vieworg );
	}

	// When goggles are on there are a few passes to generate the effect
	if ( cg.predictedPlayerState.pm_flags & PMF_GOGGLES_ON )
	{
		switch(cg.predictedPlayerState.stats[STAT_GOGGLES])
		{
			case GOGGLES_NIGHTVISION:
				if (cg.mInRMG)
				{
					parm1 = RMG_distancecull.value;
					parm2 = 0.0;
				}
				else
				{
					parm1 = 2500;
					parm2 = 0.0;
				}
				break;
			case GOGGLES_INFRARED:
				parm1 = cgs.mIRSeeThrough;
				parm2 = cgs.mIRDist;
				break;
			default:
				parm1 = parm2 = 0.0;
				break;
		}

		trap_R_DrawVisualOverlay ( cg.predictedPlayerState.stats[STAT_GOGGLES], qtrue, parm1, parm2);

		// draw 3D view
		trap_R_RenderScene( &cg.refdef );

		trap_R_DrawVisualOverlay( cg.predictedPlayerState.stats[STAT_GOGGLES], qfalse, parm1, parm2);
	}
	// Normal rendering
	else
	{
		trap_R_RenderScene( &cg.refdef );
	}

	// restore original viewpoint if running stereo
	if ( separation != 0 ) 
	{
		VectorCopy( baseOrg, cg.refdef.vieworg );
	}

	// draw status bar and other floating elements
 	CG_Draw2D();
}

