/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2006 Pat Raynor <raynorpat@sbcglobal.net>

This file is part of the Fortress Evolution source code.

Fortress Evolution is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

The Fortress Evolution source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Fortress Evolution; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

// cg_draw.c - draw all of the graphical elements during active (after loading) gameplay
#include "cg_local.h"

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

extern displayContextDef_t cgDC;
extern int		HudGroupFlag;
menuDef_t      *menuScoreboard = NULL; // used for scoreboard

int             sortedTeamPlayers[TEAM_MAXOVERLAY];
int             numSortedTeamPlayers;

char            systemChat[256];
char            teamChat1[256];
char            teamChat2[256];

int CG_Text_Width(const char *text, float scale, int limit)
{
	int             count, len;
	float           out;
	glyphInfo_t    *glyph;
	float           useScale;

// FIXME: see ui_main.c, same problem
//  const unsigned char *s = text;
	const char     *s = text;
	fontInfo_t     *font = &cgDC.Assets.textFont;

	if(scale <= cg_smallFont.value)
	{
		font = &cgDC.Assets.smallFont;
	}
	else if(scale > cg_bigFont.value)
	{
		font = &cgDC.Assets.bigFont;
	}
	useScale = scale * font->glyphScale;
	out = 0;
	if(text)
	{
		len = strlen(text);
		if(limit > 0 && len > limit)
		{
			len = limit;
		}
		count = 0;
		while(s && *s && count < len)
		{
			if(Q_IsColorString(s))
			{
				s += 2;
				continue;
			}
			else
			{
				glyph = &font->glyphs[(int)*s];	// TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build
				out += glyph->xSkip;
				s++;
				count++;
			}
		}
	}
	return out * useScale;
}

int CG_Text_Height(const char *text, float scale, int limit)
{
	int             len, count;
	float           max;
	glyphInfo_t    *glyph;
	float           useScale;

// TTimo: FIXME
//  const unsigned char *s = text;
	const char     *s = text;
	fontInfo_t     *font = &cgDC.Assets.textFont;

	if(scale <= cg_smallFont.value)
	{
		font = &cgDC.Assets.smallFont;
	}
	else if(scale > cg_bigFont.value)
	{
		font = &cgDC.Assets.bigFont;
	}
	useScale = scale * font->glyphScale;
	max = 0;
	if(text)
	{
		len = strlen(text);
		if(limit > 0 && len > limit)
		{
			len = limit;
		}
		count = 0;
		while(s && *s && count < len)
		{
			if(Q_IsColorString(s))
			{
				s += 2;
				continue;
			}
			else
			{
				glyph = &font->glyphs[(int)*s];	// TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build
				if(max < glyph->height)
				{
					max = glyph->height;
				}
				s++;
				count++;
			}
		}
	}
	return max * useScale;
}

void CG_Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader)
{
	float           w, h;

	w = width * scale;
	h = height * scale;
	CG_AdjustFrom640(&x, &y, &w, &h);
	trap_R_DrawStretchPic(x, y, w, h, s, t, s2, t2, hShader);
}

void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style)
{
	int             len, count;
	vec4_t          newColor;
	glyphInfo_t    *glyph;
	float           useScale;
	fontInfo_t     *font = &cgDC.Assets.textFont;

	if(scale <= cg_smallFont.value)
	{
		font = &cgDC.Assets.smallFont;
	}
	else if(scale > cg_bigFont.value)
	{
		font = &cgDC.Assets.bigFont;
	}

	useScale = scale * font->glyphScale;

	if(text)
	{
// TTimo: FIXME
//      const unsigned char *s = text;
		const char     *s = text;

		trap_R_SetColor(color);
		memcpy(&newColor[0], &color[0], sizeof(vec4_t));
		len = strlen(text);
		if(limit > 0 && len > limit)
		{
			len = limit;
		}
		count = 0;
		while(s && *s && count < len)
		{
			glyph = &font->glyphs[(int)*s];	// TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build
			//int yadj = Assets.textFont.glyphs[text[i]].bottom + Assets.textFont.glyphs[text[i]].top;
			//float yadj = scale * (Assets.textFont.glyphs[text[i]].imageHeight - Assets.textFont.glyphs[text[i]].height);
			if(Q_IsColorString(s))
			{
				memcpy(newColor, g_color_table[ColorIndex(*(s + 1))], sizeof(newColor));
				newColor[3] = color[3];
				trap_R_SetColor(newColor);
				s += 2;
				continue;
			}
			else
			{
				float           yadj = useScale * glyph->top;

				if(style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE)
				{
					int             ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2;

					colorBlack[3] = newColor[3];
					trap_R_SetColor(colorBlack);
					CG_Text_PaintChar(x + ofs, y - yadj + ofs,
									  glyph->imageWidth,
									  glyph->imageHeight, useScale, glyph->s, glyph->t, glyph->s2, glyph->t2, glyph->glyph);
					colorBlack[3] = 1.0;
					trap_R_SetColor(newColor);
				}
				else if(style == ITEM_TEXTSTYLE_NEON)
				{
					vec4_t glow, outer, inner, white;
					
					glow[0] = newColor[0] * 0.5;
					glow[1] = newColor[1] * 0.5;
					glow[2] = newColor[2] * 0.5;
					glow[3] = newColor[3] * 0.2;
					
					outer[0] = newColor[0];
					outer[1] = newColor[1];
					outer[2] = newColor[2];
					outer[3] = newColor[3];
					
					inner[0] = newColor[0] * 1.5 > 1.0f ? 1.0f : newColor[0] * 1.5;
					inner[1] = newColor[1] * 1.5 > 1.0f ? 1.0f : newColor[1] * 1.5;
					inner[2] = newColor[2] * 1.5 > 1.0f ? 1.0f : newColor[2] * 1.5;
					inner[3] = newColor[3];
					
					white[0] = white[1] = white[2] = white[3] = 1.0f;
					
					trap_R_SetColor(glow);
					CG_Text_PaintChar(x - 3, y - yadj - 3,
						glyph->imageWidth + 6,
						glyph->imageHeight + 6,
						useScale,
						glyph->s,
						glyph->t,
						glyph->s2,
						glyph->t2,
						glyph->glyph);
					
					trap_R_SetColor(outer);
					CG_Text_PaintChar(x - 1, y - yadj - 1,
						glyph->imageWidth + 2,
						glyph->imageHeight + 2,
						useScale,
						glyph->s,
						glyph->t,
						glyph->s2,
						glyph->t2,
						glyph->glyph);
					
					trap_R_SetColor(inner);
					CG_Text_PaintChar(x - 0.5, y - yadj - 0.5,
						glyph->imageWidth + 1,
						glyph->imageHeight + 1,
						useScale,
						glyph->s,
						glyph->t,
						glyph->s2,
						glyph->t2,
						glyph->glyph);
					
					trap_R_SetColor(white);
				}

				CG_Text_PaintChar(x, y - yadj,
								  glyph->imageWidth,
								  glyph->imageHeight, useScale, glyph->s, glyph->t, glyph->s2, glyph->t2, glyph->glyph);
				x += (glyph->xSkip * useScale) + adjust;
				s++;
				count++;
			}
		}
		trap_R_SetColor(NULL);
	}
}

/*
================
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;
	vec3_t          lightOrigin;

	if(!cg_draw3dIcons.integer || !cg_drawIcons.integer)
	{
		return;
	}

	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);
	
	VectorCopy(origin, lightOrigin);
	lightOrigin[0] -= 90;
	lightOrigin[1] += 10;
	lightOrigin[2] += 30;
	trap_R_AddLightToScene(lightOrigin, 200, 1.0, 1.0, 1.0);
	
	trap_R_RenderScene(&refdef);
}

/*
================
CG_Draw3DWeaponModel
================
*/
void CG_Draw3DWeaponModel(float x, float y, float w, float h, qhandle_t weaponModel, qhandle_t barrelModel, qhandle_t skin, vec3_t origin, vec3_t angles)
{
	refdef_t        refdef;
	refEntity_t     ent;
	vec3_t          lightOrigin;

	if(!cg_draw3dIcons.integer || !cg_drawIcons.integer)
	{
		return;
	}

	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 = weaponModel;
	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);
	if(barrelModel)
	{
		refEntity_t     barrel;

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

		barrel.hModel = barrelModel;

		VectorCopy(ent.lightingOrigin, barrel.lightingOrigin);
		barrel.shadowPlane = ent.shadowPlane;
		barrel.renderfx = ent.renderfx;

		CG_PositionRotatedEntityOnTag(&barrel, &ent, weaponModel, "tag_barrel");

		AxisCopy(ent.axis, barrel.axis);
		barrel.nonNormalizedAxes = ent.nonNormalizedAxes;

		trap_R_AddRefEntityToScene(&barrel);
	}
	
	VectorCopy(origin, lightOrigin);
	lightOrigin[0] -= 90;
	lightOrigin[1] += 10;
	lightOrigin[2] += 30;
	trap_R_AddLightToScene(lightOrigin, 200, 1.0, 1.0, 1.0);

	trap_R_RenderScene(&refdef);
}

/*
================
CG_DrawHead

Used for both the status bar and the scoreboard
================
*/
void CG_DrawHead(float x, float y, float w, float h, int clientNum, vec3_t headAngles)
{
	clipHandle_t    cm;
	clientInfo_t   *ci;
	float           len;
	vec3_t          origin;
	vec3_t          mins, maxs;

	ci = &cgs.clientinfo[clientNum];

	if(cg_draw3dIcons.integer)
	{
		cm = ci->headModel;
		if(!cm)
		{
			return;
		}

		// offset the origin y and z to center the head
		trap_R_ModelBounds(cm, mins, maxs);

		origin[2] = -0.5 * (mins[2] + maxs[2]);
		origin[1] = 0.5 * (mins[1] + maxs[1]);

		// calculate distance so the head nearly fills the box
		// assume heads are taller than wide
		len = 0.7 * (maxs[2] - mins[2]);
		origin[0] = len / 0.268;	// len / tan( fov/2 )

		// allow per-model tweaking
		VectorAdd(origin, ci->headOffset, origin);

		CG_Draw3DModel(x, y, w, h, ci->headModel, ci->headSkin, origin, headAngles);
	}
	else if(cg_drawIcons.integer)
	{
		CG_DrawPic(x, y, w, h, ci->modelIcon);
	}

	// if they are deferred, draw a cross out
	if(ci->deferred)
	{
		CG_DrawPic(x, y, w, h, cgs.media.deferShader);
	}
}

/*
================
CG_DrawFlagModel

Used for both the status bar and the scoreboard
================
*/
void CG_DrawFlagModel(float x, float y, float w, float h, int team, qboolean force2D)
{
	qhandle_t       cm;
	float           len;
	vec3_t          origin, angles;
	vec3_t          mins, maxs;
	qhandle_t       handle;

	if(!force2D && cg_draw3dIcons.integer)
	{

		VectorClear(angles);

		cm = cgs.media.redFlagModel;

		// offset the origin y and z to center the flag
		trap_R_ModelBounds(cm, mins, maxs);

		origin[2] = -0.5 * (mins[2] + maxs[2]);
		origin[1] = 0.5 * (mins[1] + maxs[1]);

		// calculate distance so the flag nearly fills the box
		// assume heads are taller than wide
		len = 0.5 * (maxs[2] - mins[2]);
		origin[0] = len / 0.268;	// len / tan( fov/2 )

		angles[YAW] = 60 * sin(cg.time / 2000.0);;

		if(team == TEAM_RED)
		{
			handle = cgs.media.redFlagModel;
		}
		else if(team == TEAM_BLUE)
		{
			handle = cgs.media.blueFlagModel;
		}
		else if(team == TEAM_FREE)
		{
			handle = cgs.media.neutralFlagModel;
		}
		else
		{
			return;
		}
		CG_Draw3DModel(x, y, w, h, handle, 0, origin, angles);
	}
	else if(cg_drawIcons.integer)
	{
		gitem_t        *item;

		if(team == TEAM_RED)
		{
			item = BG_FindItemForPowerup(PW_REDFLAG);
		}
		else if(team == TEAM_BLUE)
		{
			item = BG_FindItemForPowerup(PW_BLUEFLAG);
		}
		else if(team == TEAM_FREE)
		{
			item = BG_FindItemForPowerup(PW_NEUTRALFLAG);
		}
		else
		{
			return;
		}
		if(item)
		{
			CG_DrawPic(x, y, w, h, cg_items[ITEM_INDEX(item)].icon);
		}
	}
}

/*
================
CG_DrawTeamBackground
================
*/
void CG_DrawTeamBackground(int x, int y, int w, int h, float alpha, int team)
{
	vec4_t          hcolor;

	hcolor[3] = alpha;
	if(team == TEAM_RED)
	{
		hcolor[0] = 1;
		hcolor[1] = 0;
		hcolor[2] = 0;
	}
	else if(team == TEAM_BLUE)
	{
		hcolor[0] = 0;
		hcolor[1] = 0;
		hcolor[2] = 1;
	}
	else
	{
		return;
	}
	trap_R_SetColor(hcolor);
	CG_DrawPic(x, y, w, h, cgs.media.teamStatusBar);
	trap_R_SetColor(NULL);
}

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

  UPPER RIGHT CORNER

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

/*
================
CG_DrawAttacker
================
*/
static float CG_DrawAttacker(float y)
{
	int             t;
	float           size;
	vec3_t          angles;
	const char     *info;
	const char     *name;
	int             clientNum;

	if(cg.predictedPlayerState.stats[STAT_HEALTH] <= 0)
	{
		return y;
	}

	if(!cg.attackerTime)
	{
		return y;
	}

	clientNum = cg.predictedPlayerState.persistant[PERS_ATTACKER];
	if(clientNum < 0 || clientNum >= MAX_CLIENTS || clientNum == cg.snap->ps.clientNum)
	{
		return y;
	}

	t = cg.time - cg.attackerTime;
	if(t > ATTACKER_HEAD_TIME)
	{
		cg.attackerTime = 0;
		return y;
	}

	size = ICON_SIZE * 1.25;

	angles[PITCH] = 0;
	angles[YAW] = 180;
	angles[ROLL] = 0;
	CG_DrawHead(640 - size, y, size, size, clientNum, angles);

	info = CG_ConfigString(CS_PLAYERS + clientNum);
	name = Info_ValueForKey(info, "n");
	y += size;
	CG_DrawBigString(640 - (Q_PrintStrlen(name) * BIGCHAR_WIDTH), y, name, 0.5);

	return y + BIGCHAR_HEIGHT + 2;
}

/*
==================
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 = CG_DrawStrlen(s) * BIGCHAR_WIDTH;

	CG_DrawBigString(635 - w, y + 2, s, 1.0F);

	return y + BIGCHAR_HEIGHT + 4;
}

/*
==================
CG_DrawFPS
==================
*/
#define	FPS_FRAMES	40
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("%ifps", fps);
		w = CG_DrawStrlen(s) * BIGCHAR_WIDTH;

		CG_DrawBigString(635 - w, y + 2, s, 1.0F);
	}

	return y + BIGCHAR_HEIGHT + 4;
}

/*
=================
CG_DrawTimer
=================
*/
static float CG_DrawTimer(float y)
{
	char           *s;
	int             w;
	int             mins, seconds, tens;
	int             msec;

	msec = cg.time - cgs.levelStartTime;

	seconds = msec / 1000;
	mins = seconds / 60;
	seconds -= mins * 60;
	tens = seconds / 10;
	seconds -= tens * 10;

	s = va("%i:%i%i", mins, tens, seconds);
	w = CG_DrawStrlen(s) * BIGCHAR_WIDTH;

	CG_DrawBigString(635 - w, y + 2, s, 1.0F);

	return y + BIGCHAR_HEIGHT + 4;
}

/*
=================
CG_DrawTeamOverlay
=================
*/
static float CG_DrawTeamOverlay(float y, qboolean right, qboolean upper)
{
	int             x, w, h, xx;
	int             i, j, len;
	const char     *p;
	vec4_t          hcolor;
	int             pwidth, lwidth;
	int             plyrs;
	char            st[16];
	clientInfo_t   *ci;
	gitem_t        *item;
	int             ret_y, count;

	if(!cg_drawTeamOverlay.integer)
	{
		return y;
	}

	if(cg.snap->ps.persistant[PERS_TEAM] != TEAM_RED && cg.snap->ps.persistant[PERS_TEAM] != TEAM_BLUE)
	{
		return y;				// Not on any team
	}

	plyrs = 0;

	// max player name width
	pwidth = 0;
	count = (numSortedTeamPlayers > 8) ? 8 : numSortedTeamPlayers;
	for(i = 0; i < count; i++)
	{
		ci = cgs.clientinfo + sortedTeamPlayers[i];
		if(ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM])
		{
			plyrs++;
			len = CG_DrawStrlen(ci->name);
			if(len > pwidth)
				pwidth = len;
		}
	}

	if(!plyrs)
		return y;

	if(pwidth > TEAM_OVERLAY_MAXNAME_WIDTH)
		pwidth = TEAM_OVERLAY_MAXNAME_WIDTH;

	// max location name width
	lwidth = 0;
	for(i = 1; i < MAX_LOCATIONS; i++)
	{
		p = CG_ConfigString(CS_LOCATIONS + i);
		if(p && *p)
		{
			len = CG_DrawStrlen(p);
			if(len > lwidth)
				lwidth = len;
		}
	}

	if(lwidth > TEAM_OVERLAY_MAXLOCATION_WIDTH)
		lwidth = TEAM_OVERLAY_MAXLOCATION_WIDTH;

	w = (pwidth + lwidth + 4 + 7) * TINYCHAR_WIDTH;

	if(right)
		x = 640 - w;
	else
		x = 0;

	h = plyrs * TINYCHAR_HEIGHT;

	if(upper)
	{
		ret_y = y + h;
	}
	else
	{
		y -= h;
		ret_y = y;
	}

	if(cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED)
	{
		hcolor[0] = 1.0f;
		hcolor[1] = 0.0f;
		hcolor[2] = 0.0f;
		hcolor[3] = 0.33f;
	}
	else
	{							// if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE )
		hcolor[0] = 0.0f;
		hcolor[1] = 0.0f;
		hcolor[2] = 1.0f;
		hcolor[3] = 0.33f;
	}
	trap_R_SetColor(hcolor);
	CG_DrawPic(x, y, w, h, cgs.media.teamStatusBar);
	trap_R_SetColor(NULL);

	for(i = 0; i < count; i++)
	{
		ci = cgs.clientinfo + sortedTeamPlayers[i];
		if(ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM])
		{

			hcolor[0] = hcolor[1] = hcolor[2] = hcolor[3] = 1.0;

			xx = x + TINYCHAR_WIDTH;

			CG_DrawStringExt(xx, y,
							 ci->name, hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, TEAM_OVERLAY_MAXNAME_WIDTH);

			if(lwidth)
			{
				p = CG_ConfigString(CS_LOCATIONS + ci->location);
				if(!p || !*p)
					p = "unknown";
				len = CG_DrawStrlen(p);
				if(len > lwidth)
					len = lwidth;

//              xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth + 
//                  ((lwidth/2 - len/2) * TINYCHAR_WIDTH);
				xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth;
				CG_DrawStringExt(xx, y,
								 p, hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, TEAM_OVERLAY_MAXLOCATION_WIDTH);
			}

			CG_GetColorForHealth(ci->health, ci->armor, hcolor);

			Com_sprintf(st, sizeof(st), "%3i %3i", ci->health, ci->armor);

			xx = x + TINYCHAR_WIDTH * 3 + TINYCHAR_WIDTH * pwidth + TINYCHAR_WIDTH * lwidth;

			CG_DrawStringExt(xx, y, st, hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0);

			// draw weapon icon
			xx += TINYCHAR_WIDTH * 3;

			if(cg_weapons[ci->curWeapon].weaponIcon)
			{
				CG_DrawPic(xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, cg_weapons[ci->curWeapon].weaponIcon);
			}
			else
			{
				CG_DrawPic(xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, cgs.media.deferShader);
			}

			// Draw powerup icons
			if(right)
			{
				xx = x;
			}
			else
			{
				xx = x + w - TINYCHAR_WIDTH;
			}
			for(j = 0; j <= PW_NUM_POWERUPS; j++)
			{
				if(ci->powerups & (1 << j))
				{

					item = BG_FindItemForPowerup(j);

					if(item)
					{
						CG_DrawPic(xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, trap_R_RegisterShader(item->icon));
						if(right)
						{
							xx -= TINYCHAR_WIDTH;
						}
						else
						{
							xx += TINYCHAR_WIDTH;
						}
					}
				}
			}

			y += TINYCHAR_HEIGHT;
		}
	}

	return ret_y;
}

/*
=====================
CG_DrawUpperRight

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

	y = 0;

	if(cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 1)
	{
		y = CG_DrawTeamOverlay(y, qtrue, qtrue);
	}
	if(cg_drawSnapshot.integer)
	{
		y = CG_DrawSnapshot(y);
	}
	if(cg_drawFPS.integer)
	{
		y = CG_DrawFPS(y);
	}
	if(cg_drawTimer.integer)
	{
		y = CG_DrawTimer(y);
	}
	if(cg_drawAttacker.integer)
	{
		y = CG_DrawAttacker(y);
	}

}

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

  LOWER RIGHT CORNER

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

/*
===================
CG_DrawReward
===================
*/
static void CG_DrawReward(void)
{
	float          *color;
	int             i, count;
	float           x, y;
	char            buf[32];

	if(!cg_drawRewards.integer)
	{
		return;
	}

	color = CG_FadeColor(cg.rewardTime, REWARD_TIME);
	if(!color)
	{
		if(cg.rewardStack > 0)
		{
			for(i = 0; i < cg.rewardStack; i++)
			{
				cg.rewardSound[i] = cg.rewardSound[i + 1];
				cg.rewardShader[i] = cg.rewardShader[i + 1];
				cg.rewardCount[i] = cg.rewardCount[i + 1];
			}
			cg.rewardTime = cg.time;
			cg.rewardStack--;
			color = CG_FadeColor(cg.rewardTime, REWARD_TIME);
			trap_S_StartLocalSound(cg.rewardSound[0], CHAN_ANNOUNCER);
		}
		else
		{
			return;
		}
	}

	trap_R_SetColor(color);

	/*
	   count = cg.rewardCount[0]/10;                // number of big rewards to draw

	   if (count) {
	   y = 4;
	   x = 320 - count * ICON_SIZE;
	   for ( i = 0 ; i < count ; i++ ) {
	   CG_DrawPic( x, y, (ICON_SIZE*2)-4, (ICON_SIZE*2)-4, cg.rewardShader[0] );
	   x += (ICON_SIZE*2);
	   }
	   }

	   count = cg.rewardCount[0] - count*10;        // number of small rewards to draw
	 */

	if(cg.rewardCount[0] >= 10)
	{
		y = 56;
		x = 320 - ICON_SIZE / 2;
		CG_DrawPic(x, y, ICON_SIZE - 4, ICON_SIZE - 4, cg.rewardShader[0]);
		Com_sprintf(buf, sizeof(buf), "%d", cg.rewardCount[0]);
		x = (SCREEN_WIDTH - SMALLCHAR_WIDTH * CG_DrawStrlen(buf)) / 2;
		CG_DrawStringExt(x, y + ICON_SIZE, buf, color, qfalse, qtrue, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0);
	}
	else
	{

		count = cg.rewardCount[0];

		y = 56;
		x = 320 - count * ICON_SIZE / 2;
		for(i = 0; i < count; i++)
		{
			CG_DrawPic(x, y, ICON_SIZE - 4, ICON_SIZE - 4, cg.rewardShader[0]);
			x += ICON_SIZE;
		}
	}
	trap_R_SetColor(NULL);
}

/*
==============
CG_DrawBloom
==============
*/
static void CG_DrawBloom(void)
{
	if(cg_drawBloom.integer == 1)
	{
		CG_DrawPic(0, 0, 640, 480, cgs.media.bloomShader);
	}
	else if(cg_drawBloom.integer == 2)
	{
		CG_DrawPic(0, 0, 640, 480, cgs.media.bloom2Shader);
	}
}

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

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

Should we draw something differnet for long lag vs no packets?
==============
*/
static void CG_DrawDisconnect(void)
{
	float           x, y;
	int             cmdNum;
	usercmd_t       cmd;
	const char     *s;
	int             w;			// bk010215 - FIXME char message[1024];

	// draw the phone jack if we are completely past our buffers
	cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1;
	trap_GetUserCmd(cmdNum, &cmd);
	if(cmd.serverTime <= cg.snap->ps.commandTime || cmd.serverTime > cg.time)
	{							// special check for map_restart // bk 0102165 - FIXME
		return;
	}

	// also add text in center of screen
	s = "Connection Interrupted";	// bk 010215 - FIXME
	w = CG_DrawStrlen(s) * BIGCHAR_WIDTH;
	CG_DrawBigString(320 - w / 2, 100, s, 1.0F);

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

	x = 640 - 48;
	y = 480 - 48;

	CG_DrawPic(x, y, 48, 48, trap_R_RegisterShader("gfx/2d/net.tga"));
}

#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;

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

	//
	// draw the graph
	//
	x = 640 - 48;
	y = 480 - 144;

	trap_R_SetColor(NULL);
	CG_DrawPic(x, y, 48, 48, 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, 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, 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, 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, cgs.media.whiteShader);
		}
	}

	trap_R_SetColor(NULL);

	if(cg_nopredict.integer || cg_synchronousClients.integer)
	{
		CG_DrawBigString(ax, ay, "snc", 1.0);
	}

	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, int y, int charWidth)
{
	char           *s;

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

	cg.centerPrintTime = cg.time;
	cg.centerPrintY = y;
	cg.centerPrintCharWidth = charWidth;

	// 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.centerPrintTime)
	{
		return;
	}

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

	trap_R_SetColor(color);

	start = cg.centerPrint;

	y = cg.centerPrintY - cg.centerPrintLines * BIGCHAR_HEIGHT / 2;

	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 = CG_Text_Width(linebuffer, 0.5, 0);
		h = CG_Text_Height(linebuffer, 0.5, 0);
		x = (SCREEN_WIDTH - w) / 2;
		CG_Text_Paint(x, y + h, 0.5, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE);
		y += h + 6;

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

	trap_R_SetColor(NULL);
}

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

CROSSHAIR

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

/*
=================
CG_DrawCrosshair
=================
*/
static void CG_DrawCrosshair(void)
{
	float           w, h;
	qhandle_t       hShader;
	float           f;
	float           x, y;
	int             ca;

	if(cg_drawCrosshair.integer < 0)
		return;

	if(cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR)
		return;

	if(cg.renderingThirdPerson)
		return;

	// set color based on health
	if(cg_crosshairHealth.integer)
	{
		vec4_t          hcolor;

		CG_ColorForHealth(hcolor);
		trap_R_SetColor(hcolor);
	}
	else
	{
		trap_R_SetColor(NULL);
	}

	w = h = cg_crosshairSize.value;

	// pulse the size of the crosshair when picking up items
	f = cg.time - cg.itemPickupBlendTime;
	if(f > 0 && f < ITEM_BLOB_TIME)
	{
		f /= ITEM_BLOB_TIME;
		w *= (1 + f);
		h *= (1 + f);
	}

	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, hShader);
}

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

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

	CG_Trace(&trace, start, vec3_origin, vec3_origin, end, cg.snap->ps.clientNum, CONTENTS_SOLID | CONTENTS_BODY);
	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;
	}

	// if the player is invisible, don't show it
	if(cg_entities[trace.entityNum].currentState.powerups & (1 << PW_INVIS))
	{
		return;
	}

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


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

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

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

	// 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;

	color[3] *= 0.5f;
	w = CG_Text_Width(name, 0.3f, 0);
	CG_Text_Paint(320 - w / 2, 190, 0.3f, color, name, 0, 0, ITEM_TEXTSTYLE_SHADOWED);

	trap_R_SetColor(NULL);
}


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

/*
=================
CG_DrawSpectator
=================
*/
static void CG_DrawSpectator(void)
{
	CG_DrawBigString(320 - 9 * 8, 440, "SPECTATOR", 1.0F);
	if(cgs.gametype == GT_TOURNAMENT)
	{
		CG_DrawBigString(320 - 15 * 8, 460, "waiting to play", 1.0F);
	}
	else if(cgs.gametype >= GT_TEAM)
	{
		CG_DrawBigString(320 - 39 * 8, 460, "press ESC and use the JOIN menu to play", 1.0F);
	}
}

/*
=================
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 = (VOTE_TIME - (cg.time - cgs.voteTime)) / 1000;
	if(sec < 0)
	{
		sec = 0;
	}

	s = va("VOTE(%i):%s yes:%i no:%i", sec, cgs.voteString, cgs.voteYes, cgs.voteNo);
	CG_DrawSmallString(0, 58, s, 1.0F);
	s = "or press ESC then click Vote";
	CG_DrawSmallString(0, 58 + SMALLCHAR_HEIGHT + 2, s, 1.0F);
}

/*
=================
CG_DrawTeamVote
=================
*/
static void CG_DrawTeamVote(void)
{
	char           *s;
	int             sec, cs_offset;

	if(cgs.clientinfo->team == TEAM_RED)
		cs_offset = 0;
	else if(cgs.clientinfo->team == TEAM_BLUE)
		cs_offset = 1;
	else
		return;

	if(!cgs.teamVoteTime[cs_offset])
	{
		return;
	}

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

	sec = (VOTE_TIME - (cg.time - cgs.teamVoteTime[cs_offset])) / 1000;
	if(sec < 0)
	{
		sec = 0;
	}
	s = va("TEAMVOTE(%i):%s yes:%i no:%i", sec, cgs.teamVoteString[cs_offset],
		   cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset]);
	CG_DrawSmallString(0, 90, s, 1.0F);
}


static qboolean CG_DrawScoreboard()
{
	static qboolean firstTime = qtrue;
	float           fade, *fadeColor;

	if(menuScoreboard)
	{
		menuScoreboard->window.flags &= ~WINDOW_FORCED;
	}
	if(cg_paused.integer)
	{
		cg.deferredPlayerLoading = 0;
		firstTime = qtrue;
		return qfalse;
	}

	// should never happen in Team Arena
	if(cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION)
	{
		cg.deferredPlayerLoading = 0;
		firstTime = qtrue;
		return qfalse;
	}

	// don't draw scoreboard during death while warmup up
	if(cg.warmup && !cg.showScores)
	{
		return qfalse;
	}

	if(cg.showScores || cg.predictedPlayerState.pm_type == PM_DEAD || cg.predictedPlayerState.pm_type == PM_INTERMISSION)
	{
		fade = 1.0;
		fadeColor = colorWhite;
	}
	else
	{
		fadeColor = CG_FadeColor(cg.scoreFadeTime, FADE_TIME);
		if(!fadeColor)
		{
			// next time scoreboard comes up, don't print killer
			cg.deferredPlayerLoading = 0;
			cg.killerName[0] = 0;
			firstTime = qtrue;
			return qfalse;
		}
		fade = *fadeColor;
	}


	if(menuScoreboard == NULL)
	{
		if(cgs.gametype >= GT_TEAM)
		{
			menuScoreboard = Menus_FindByName("teamscore_menu");
		}
		else
		{
			menuScoreboard = Menus_FindByName("score_menu");
		}
	}

	if(menuScoreboard)
	{
		if(firstTime)
		{
			CG_SetScoreSelection(menuScoreboard);
			firstTime = qfalse;
		}
		Menu_Paint(menuScoreboard, qtrue);
	}

	// load any models that have been deferred
	if(++cg.deferredPlayerLoading > 10)
	{
		CG_LoadDeferredPlayers();
	}

	return qtrue;
}

/*
=================
CG_DrawIntermission
=================
*/
static void CG_DrawIntermission(void)
{
	if(cgs.gametype == GT_SINGLE_PLAYER)
	{
		CG_DrawCenterString();
		return;
	}

	cg.scoreFadeTime = cg.time;
	cg.scoreBoardShowing = CG_DrawScoreboard();
}

/*
=================
CG_DrawFollow
=================
*/
static qboolean CG_DrawFollow(void)
{
	float           x;
	vec4_t          color;
	const char     *name;

	if(!(cg.snap->ps.pm_flags & PMF_FOLLOW))
	{
		return qfalse;
	}
	color[0] = 1;
	color[1] = 1;
	color[2] = 1;
	color[3] = 1;


	CG_DrawBigString(320 - 9 * 8, 24, "following", 1.0F);

	name = cgs.clientinfo[cg.snap->ps.clientNum].name;

	x = 0.5 * (640 - GIANT_WIDTH * CG_DrawStrlen(name));

	CG_DrawStringExt(x, 40, name, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0);

	return qtrue;
}



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

	if(cg_drawAmmoWarning.integer == 0)
	{
		return;
	}

	if(!cg.lowAmmoWarning)
	{
		return;
	}

	if(cg.lowAmmoWarning == 2)
	{
		s = "OUT OF AMMO";
	}
	else
	{
		s = "LOW AMMO WARNING";
	}
	w = CG_DrawStrlen(s) * BIGCHAR_WIDTH;
	CG_DrawBigString(320 - w / 2, 64, s, 1.0F);
}


#ifdef MISSIONPACK
/*
=================
CG_DrawProxWarning
=================
*/
static void CG_DrawProxWarning(void)
{
	char            s[32];
	int             w;
	static int      proxTime;
	static int      proxCounter;
	static int      proxTick;

	if(!(cg.snap->ps.eFlags & EF_TICKING))
	{
		proxTime = 0;
		return;
	}

	if(proxTime == 0)
	{
		proxTime = cg.time + 5000;
		proxCounter = 5;
		proxTick = 0;
	}

	if(cg.time > proxTime)
	{
		proxTick = proxCounter--;
		proxTime = cg.time + 1000;
	}

	if(proxTick != 0)
	{
		Com_sprintf(s, sizeof(s), "INTERNAL COMBUSTION IN: %i", proxTick);
	}
	else
	{
		Com_sprintf(s, sizeof(s), "YOU HAVE BEEN MINED");
	}

	w = CG_DrawStrlen(s) * BIGCHAR_WIDTH;
	CG_DrawBigStringColor(320 - w / 2, 64 + BIGCHAR_HEIGHT, s, g_color_table[ColorIndex(COLOR_RED)]);
}
#endif


/*
=================
CG_DrawWarmup
=================
*/
static void CG_DrawWarmup(void)
{
	int             w;
	int             sec;
	int             i;
	float           scale;
	clientInfo_t   *ci1, *ci2;
	int             cw;
	const char     *s;

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

	if(sec < 0)
	{
		s = "Waiting for players";
		w = CG_DrawStrlen(s) * BIGCHAR_WIDTH;
		CG_DrawBigString(320 - w / 2, 24, s, 1.0F);
		cg.warmupCount = 0;
		return;
	}

	if(cgs.gametype == GT_TOURNAMENT)
	{
		// find the two active players
		ci1 = NULL;
		ci2 = NULL;
		for(i = 0; i < cgs.maxclients; i++)
		{
			if(cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_FREE)
			{
				if(!ci1)
				{
					ci1 = &cgs.clientinfo[i];
				}
				else
				{
					ci2 = &cgs.clientinfo[i];
				}
			}
		}

		if(ci1 && ci2)
		{
			s = va("%s vs %s", ci1->name, ci2->name);
			w = CG_Text_Width(s, 0.6f, 0);
			CG_Text_Paint(320 - w / 2, 60, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE);
		}
	}
	else
	{
		if(cgs.gametype == GT_FFA)
		{
			s = "Free For All";
		}
		else if(cgs.gametype == GT_TEAM)
		{
			s = "Team Deathmatch";
		}
		else if(cgs.gametype == GT_CTF)
		{
			s = "Capture the Flag";
		}
		else
		{
			s = "";
		}

		w = CG_Text_Width(s, 0.6f, 0);
		CG_Text_Paint(320 - w / 2, 90, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE);

		w = CG_DrawStrlen(s);
		if(w > 640 / GIANT_WIDTH)
		{
			cw = 640 / w;
		}
		else
		{
			cw = GIANT_WIDTH;
		}
		CG_DrawStringExt(320 - w * cw / 2, 25, s, colorWhite, qfalse, qtrue, cw, (int)(cw * 1.1f), 0);
	}

	sec = (sec - cg.time) / 1000;
	if(sec < 0)
	{
		cg.warmup = 0;
		sec = 0;
	}
	s = va("Starts in: %i", sec + 1);
	if(sec != cg.warmupCount)
	{
		cg.warmupCount = sec;
		switch (sec)
		{
			case 0:
				trap_S_StartLocalSound(cgs.media.count1Sound, CHAN_ANNOUNCER);
				break;
			case 1:
				trap_S_StartLocalSound(cgs.media.count2Sound, CHAN_ANNOUNCER);
				break;
			case 2:
				trap_S_StartLocalSound(cgs.media.count3Sound, CHAN_ANNOUNCER);
				break;
			default:
				break;
		}
	}
	scale = 0.45f;
	switch (cg.warmupCount)
	{
		case 0:
			cw = 28;
			scale = 0.54f;
			break;
		case 1:
			cw = 24;
			scale = 0.51f;
			break;
		case 2:
			cw = 20;
			scale = 0.48f;
			break;
		default:
			cw = 16;
			scale = 0.45f;
			break;
	}

	w = CG_Text_Width(s, scale, 0);
	CG_Text_Paint(320 - w / 2, 125, scale, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE);
}

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

/* 
=================
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_Draw2D
=================
*/
static void CG_Draw2D(void)
{
	if(cgs.orderPending && cg.time > cgs.orderTime)
		CG_CheckOrderPending();

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

	if(cg_draw2D.integer == 0)
		return;

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

	if (cg.cameraMode)
	{
		CG_DrawFlashFade();
		return;
	}

	if(cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR)
	{
		CG_DrawSpectator();
		CG_DrawCrosshair();

		if(!(HudGroupFlag & XHAIRNAME))
			CG_DrawCrosshairNames();
	}
	else
	{
		// don't draw any status if dead or the scoreboard is being explicitly shown
		if(!cg.showScores && cg.snap->ps.stats[STAT_HEALTH] > 0)
		{
			if(cg_drawStatus.integer)
			{
				Menu_PaintAll();
				CG_DrawTimedMenus();
			}

			CG_DrawAmmoWarning();

			CG_DrawCrosshair();

			if(!(HudGroupFlag & XHAIRNAME))
				CG_DrawCrosshairNames();

			if(!(HudGroupFlag & XWEAPONSELECT))
				CG_DrawWeaponSelect();

			CG_DrawReward();
		}
	}

	CG_DrawVote();
	CG_DrawTeamVote();

	if(!(HudGroupFlag & UPPERIGHT))
		CG_DrawLagometer();

	if(!cg_paused.integer)
	{
		if(!(HudGroupFlag & UPPERIGHT))
			CG_DrawUpperRight();
	}

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

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

	CG_DrawFlashFade();
}

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

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

	// optionally draw the info screen instead
	if(!cg.snap)
	{
		menuDef_t *loading = Menus_FindByName("Loading");

		if(loading==NULL)
			CG_DrawInformation();

		return;
	}

	// optionally draw the tournement scoreboard instead
	if(cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR && (cg.snap->ps.pm_flags & PMF_SCOREBOARD))
	{
		CG_DrawTourneyScoreboard();
		return;
	}

	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;
			CG_Error("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);
	}

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

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

	// draw bloom post process effect
	CG_DrawBloom();

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

/*
=================
CG_Fade
=================
*/
void CG_Fade(int a, int time, int duration)
{
	cgs.scrFadeAlpha = (float)a / 255.0f;
	cgs.scrFadeStartTime = time;
	cgs.scrFadeDuration = duration;

	if (cgs.scrFadeStartTime + cgs.scrFadeDuration <= cg.time)
		cgs.scrFadeAlphaCurrent = cgs.scrFadeAlpha;

	return;
}

/*
=================
CG_DrawFlashFade
=================
*/
void CG_DrawFlashFade(void)
{
	static int lastTime;
	int elapsed, time;
	vec4_t col;

	if (cgs.scrFadeStartTime + cgs.scrFadeDuration < cg.time)
	{
		cgs.scrFadeAlphaCurrent = cgs.scrFadeAlpha;
	}
	else if (cgs.scrFadeAlphaCurrent != cgs.scrFadeAlpha)
	{
		elapsed = (time = trap_Milliseconds()) - lastTime;
		lastTime = time;
		if (elapsed < 500 && elapsed > 0)
		{
			if (cgs.scrFadeAlphaCurrent > cgs.scrFadeAlpha)
			{
				cgs.scrFadeAlphaCurrent -= ((float)elapsed/(float)cgs.scrFadeDuration);
				if (cgs.scrFadeAlphaCurrent < cgs.scrFadeAlpha)
					cgs.scrFadeAlphaCurrent = cgs.scrFadeAlpha;
			}
			else
			{
				cgs.scrFadeAlphaCurrent += ((float)elapsed/(float)cgs.scrFadeDuration);
				if (cgs.scrFadeAlphaCurrent > cgs.scrFadeAlpha)
					cgs.scrFadeAlphaCurrent = cgs.scrFadeAlpha;
			}
		}
	}

	// now draw the fade
	if (cgs.scrFadeAlphaCurrent > 0.0)
	{
		VectorClear( col );
		col[3] = cgs.scrFadeAlphaCurrent;
		CG_FillRect( 0, 0, 640, 480, col );
	}
}

/*
======================
CG_UpdateMediaFraction
======================
*/
void CG_UpdateMediaFraction(float newFract)
{
	cg.mediaFraction = newFract;
	trap_UpdateScreen();
}

/*
======================
CG_UpdateSoundFraction
======================
*/
void CG_UpdateSoundFraction(float newFract)
{
	cg.soundFraction = newFract;
	trap_UpdateScreen();
}

/*
======================
CG_UpdateGraphicFraction
======================
*/
void CG_UpdateGraphicFraction(float newFract)
{
	cg.graphicFraction = newFract;
	trap_UpdateScreen();
}

/*
====================
CG_DrawLoadingScreen
====================
*/
void CG_DrawLoadingScreen(void)
{
	menuDef_t *loading = Menus_FindByName("Loading");
	if(loading!=NULL)
	{
		Menu_Paint(loading, qtrue);
	}
	else
	{
		CG_DrawInformation();
	}
}