// Copyright (C) 2001-2002 Raven Software, Inc.
//
// Main user interface 
//

#include "ui_local.h"
#include "../game/bg_public.h"

uiInfo_t uiInfo;

static const char *MonthAbbrev[] = 
{
	"Jan","Feb","Mar",
	"Apr","May","Jun",
	"Jul","Aug","Sep",
	"Oct","Nov","Dec"
};


static const char *skillLevels[] = 
{
	"Amature",
	"Gun For Hire",
	"Consultant",
	"Soldier of Fortune",
};

static const char *netSources[] = 
{
	"Local",
	"Internet",
	"Favorites"
};

static const serverFilter_t serverFilters[] = 
{
	{"All", "" },
	{"Soldier of Fortune 2", "" },
};

static char* netnames[] = 
{
	"???",
	"UDP",
	"IPX",
	NULL
};

static const int numNetSources				= sizeof(netSources) / sizeof(const char*);
static const int numSkillLevels				= sizeof(skillLevels) / sizeof(const char*);
static const int numServerFilters			= sizeof(serverFilters) / sizeof(serverFilter_t);

static int gamecodetoui[] = {4,2,3,0,5,1,6};
static int uitogamecode[] = {4,6,2,3,1,5,7};

static playerInfo_t		playerInfo = { 0 };


static void			UI_StartServerRefresh(qboolean full);
static void			UI_StopServerRefresh( void );
static void			UI_DoServerRefresh( void );
static void			UI_FeederSelection(float feederID, int index);
static void			UI_BuildServerDisplayList(qboolean force);
static void			UI_BuildServerStatus(qboolean force);
static void			UI_BuildFindPlayerList(qboolean force);
static int QDECL	UI_ServersQsortCompare( const void *arg1, const void *arg2 );
static int			UI_MapCountByGameType(void);
static void			UI_ParseTeamInfo(const char *teamFile);
static const char*	UI_SelectedMap(int index, int *actual);
static const char*	UI_SelectedHead(int index, int *actual);
static int			UI_GetIndexFromSelection(int actual);
static void			UI_DrawVersionDownloadProgress(rectDef_t *rect, qhandle_t font, float scale, vec4_t color );
static int			UI_MapCountForVote ( void );

/*
================
vmMain

This is the only way control passes into the module.
This must be the very first function compiled into the .qvm file
================
*/
vmCvar_t  ui_new;
vmCvar_t  ui_debug;
vmCvar_t  ui_initialized;
vmCvar_t  ui_sof2FirstRun;

void		_UI_Init		 ( qboolean );
void		_UI_Shutdown	 ( void );
qboolean	_UI_KeyEvent	 ( int key, qboolean down );
qboolean	_UI_MouseEvent	 ( int dx, int dy );
void		_UI_Refresh		 ( int realtime );

int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11  ) 
{
	switch ( command ) 
	{
		case UI_GETAPIVERSION:
			return UI_API_VERSION;

		case UI_INIT:
			_UI_Init(arg0);
			return 0;

		case UI_SHUTDOWN:
			_UI_Shutdown();
			return 0;

		case UI_KEY_EVENT:
			return _UI_KeyEvent( arg0, arg1 );

		case UI_MOUSE_EVENT:
			return _UI_MouseEvent( arg0, arg1 );

		case UI_REFRESH:
			_UI_Refresh( arg0 );
			return 0;

		case UI_IS_FULLSCREEN:
			return Menus_AnyFullScreenVisible();

		case UI_SET_ACTIVE_MENU:
			UI_SetActiveMenu( arg0 );
			return 0;

		case UI_CLOSEALL:
			Menus_CloseAll ( );
			return 0;

		case UI_CONSOLE_COMMAND:
			return UI_ConsoleCommand(arg0);

		case UI_DRAW_CONNECT_SCREEN:
			UI_DrawConnectScreen( arg0 );
			return 0;

		case UI_DRAW_LOADING_SCREEN:
			UI_DrawLoadingScreen();
			return 0;

		case UI_HASUNIQUECDKEY: 
			return qfalse; 
	}

	return -1;
}

/*
================
AssetCache

PreCaches the assets for the main user interface
=================
*/
void AssetCache ( void ) 
{
	int n;

	// Scrollbar and slider assets
	uiInfo.uiDC.Assets.scrollBar			= trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR );
	uiInfo.uiDC.Assets.scrollBarHoriz		= trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_HORIZ );
	uiInfo.uiDC.Assets.scrollBarArrowDown	= trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN );
	uiInfo.uiDC.Assets.scrollBarArrowUp		= trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP );
	uiInfo.uiDC.Assets.scrollBarArrowLeft	= trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT );
	uiInfo.uiDC.Assets.scrollBarArrowRight	= trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT );
	uiInfo.uiDC.Assets.scrollBarThumb		= trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB );
	uiInfo.uiDC.Assets.sliderBar			= trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR );
	uiInfo.uiDC.Assets.sliderThumb			= trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB );

	// Load the shaders for all of the supported crosshairs
	for( n = 0; n < NUM_CROSSHAIRS; n++ ) 
	{
		uiInfo.uiDC.Assets.crosshairShader[n] = trap_R_RegisterShaderNoMip( va("gfx/menus/crosshairs/ch%i", n) );
	}
}

/*
================
_UI_DrawRectLeftRight

Draws the left and right sides of the rectangle
=================
*/
void _UI_DrawRectLeftRight ( float x, float y, float w, float h, float size ) 
{
	UI_AdjustFrom640( &x, &y, &w, &h );
	size *= uiInfo.uiDC.xscale;
	trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, NULL, uiInfo.uiDC.whiteShader );
	trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, NULL, uiInfo.uiDC.whiteShader );
}

/*
================
_UI_DrawRectTopBottom

Draws the top and bottom sides of the rectangle
=================
*/
void _UI_DrawRectTopBottom ( float x, float y, float w, float h, float size ) 
{
	UI_AdjustFrom640( &x, &y, &w, &h );
	size *= uiInfo.uiDC.yscale;
	trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, NULL, uiInfo.uiDC.whiteShader );
	trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, NULL, uiInfo.uiDC.whiteShader );
}

/*
================
UI_DrawRect

Coordinates are 640*480 virtual values
=================
*/
void _UI_DrawRect( float x, float y, float width, float height, float size, const float *color ) 
{
	trap_R_SetColor( color );

	_UI_DrawRectTopBottom(x, y, width, height, size);
	_UI_DrawRectLeftRight(x, y, width, height, size);

	trap_R_SetColor( NULL );
}

/*
=================
UI_DrawCenteredPic
=================
*/
void UI_DrawCenteredPic(qhandle_t image, int w, int h) 
{
	int x;
	int	y;
  
	x = (SCREEN_WIDTH - w) / 2;
	y = (SCREEN_HEIGHT - h) / 2;

	UI_DrawHandlePic (x, y, w, h, image);
}


/*
=================
_UI_Refresh
=================
*/
#define	UI_FPS_FRAMES	4
void _UI_Refresh( int realtime )
{
	static int index;
	static int	previousTimes[UI_FPS_FRAMES];
	int			catcher;

	//if ( !( trap_Key_GetCatcher() & KEYCATCH_UI ) ) {
	//	return;
	//}

	uiInfo.uiDC.frameTime = realtime - uiInfo.uiDC.realTime;
	uiInfo.uiDC.realTime = realtime;

	previousTimes[index % UI_FPS_FRAMES] = uiInfo.uiDC.frameTime;
	index++;
	if ( index > UI_FPS_FRAMES ) 
	{
		int i, total;
		// average multiple frames together to smooth changes out a bit
		total = 0;
		for ( i = 0 ; i < UI_FPS_FRAMES ; i++ ) 
		{
			total += previousTimes[i];
		}
		if ( !total ) 
		{
			total = 1;
		}
		uiInfo.uiDC.FPS = 1000 * UI_FPS_FRAMES / total;
	}

	// Update all of the user interface cvars
	UI_UpdateCvars();

	// Render the menus if there are any
	if (Menu_Count() > 0) 
	{
		// paint all the menus
		Menu_PaintAll();

		// refresh server browser list
		UI_DoServerRefresh();

		// refresh server status
		UI_BuildServerStatus(qfalse);

		// refresh find player list
		UI_BuildFindPlayerList(qfalse);
	} 
	
	// draw cursor
	UI_SetColor( NULL );

	catcher = trap_Key_GetCatcher ( );
	if (Menu_Count() > 0 && (catcher&KEYCATCH_UI) && !(catcher & KEYCATCH_NUMBERSONLY) ) 
	{
		UI_DrawHandlePic( uiInfo.uiDC.cursorx - 16, uiInfo.uiDC.cursory - 16, 32, 32, uiInfo.uiDC.Assets.cursor);
	}
}

/*
=================
_UI_Shutdown
=================
*/
void _UI_Shutdown( void ) 
{
	if (playerInfo.playerG2Model)
	{
		trap_G2API_CleanGhoul2Models(&playerInfo.playerG2Model);
	}

	trap_LAN_SaveCachedServers();
}

char *defaultMenu = NULL;

char *GetMenuBuffer(const char *filename) {
	int	len;
	fileHandle_t	f;
	static char buf[MAX_MENUFILE];

	len = trap_FS_FOpenFile( filename, &f, FS_READ );
	if ( !f ) {
		trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) );
		return defaultMenu;
	}
	if ( len >= MAX_MENUFILE ) {
		trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", filename, len, MAX_MENUFILE ) );
		trap_FS_FCloseFile( f );
		return defaultMenu;
	}

	trap_FS_Read( buf, len, f );
	buf[len] = 0;
	trap_FS_FCloseFile( f );
	//COM_Compress(buf);
  return buf;

}

qboolean Asset_Parse(int handle) {
	pc_token_t token;
	const char *tempStr;

	if (!trap_PC_ReadToken(handle, &token))
		return qfalse;
	if (Q_stricmp(token.string, "{") != 0) {
		return qfalse;
	}
    
	while ( 1 ) {

		memset(&token, 0, sizeof(pc_token_t));

		if (!trap_PC_ReadToken(handle, &token))
			return qfalse;

		if (Q_stricmp(token.string, "}") == 0) {
			return qtrue;
		}

		// font
		if (Q_stricmp(token.string, "defaultFont") == 0) 
		{
			if (!PC_String_Parse(handle, &tempStr)) 
			{
				return qfalse;
			}
			uiInfo.uiDC.Assets.defaultFont = trap_R_RegisterFont(tempStr );
			uiInfo.uiDC.Assets.fontRegistered = qtrue;
			continue;
		}

		if (Q_stricmp(token.string, "cursor") == 0) 
		{
			if (!PC_String_Parse(handle, &uiInfo.uiDC.Assets.cursorStr))
			{
				Com_Printf(S_COLOR_YELLOW,"Bad 1st parameter for keyword 'cursor'");
				return qfalse;
			}
			uiInfo.uiDC.Assets.cursor = trap_R_RegisterShaderNoMip( uiInfo.uiDC.Assets.cursorStr);
			continue;
		}

		// itemFocusSound
		if (Q_stricmp(token.string, "itemFocusSound") == 0) {
			if (!PC_String_Parse(handle, &tempStr)) {
				return qfalse;
			}
			uiInfo.uiDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr );
			continue;
		}

		if (Q_stricmp(token.string, "fadeClamp") == 0) {
			if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.fadeClamp)) {
				return qfalse;
			}
			continue;
		}

		if (Q_stricmp(token.string, "fadeCycle") == 0) {
			if (!PC_Int_Parse(handle, &uiInfo.uiDC.Assets.fadeCycle)) {
				return qfalse;
			}
			continue;
		}

		if (Q_stricmp(token.string, "fadeAmount") == 0) {
			if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.fadeAmount)) {
				return qfalse;
			}
			continue;
		}

		if (Q_stricmp(token.string, "shadowX") == 0) {
			if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.shadowX)) {
				return qfalse;
			}
			continue;
		}

		if (Q_stricmp(token.string, "shadowY") == 0) {
			if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.shadowY)) {
				return qfalse;
			}
			continue;
		}

		if (Q_stricmp(token.string, "shadowColor") == 0) {
			if (!PC_Color_Parse(handle, &uiInfo.uiDC.Assets.shadowColor)) {
				return qfalse;
			}
			uiInfo.uiDC.Assets.shadowFadeClamp = uiInfo.uiDC.Assets.shadowColor[3];
			continue;
		}

		if (Q_stricmp(token.string, "shader") == 0) 
		{
			if (!PC_String_Parse(handle, &tempStr)) 
			{
				return qfalse;
			}
			trap_R_RegisterShaderNoMip ( tempStr );
			continue;
		}
	}
	return qfalse;
}

void UI_Report() 
{
	String_Report();
}

void UI_ParseMenu(const char *menuFile) 
{
	int			handle;
	pc_token_t	token;

	handle = trap_PC_LoadSource(menuFile);
	if (!handle) 
	{
		return;
	}

	while ( 1 ) 
	{
		memset(&token, 0, sizeof(pc_token_t));
		if (!trap_PC_ReadToken( handle, &token )) 
		{
			break;
		}

		if ( token.string[0] == '}' ) 
		{
			break;
		}

		if (Q_stricmp(token.string, "assetGlobalDef") == 0) 
		{
			if (Asset_Parse(handle)) 
			{
				continue;
			} 
			else 
			{
				break;
			}
		}

		if (Q_stricmp(token.string, "menudef") == 0) 
		{
			// start a new menu
			Menu_New(handle);
		}
	}

	trap_PC_FreeSource(handle);
}

qboolean Load_Menu(int handle) 
{
	pc_token_t token;

	if (!trap_PC_ReadToken(handle, &token))
	{
		return qfalse;
	}

	if (token.string[0] != '{') 
	{
		return qfalse;
	}

	while ( 1 ) 
	{
		if (!trap_PC_ReadToken(handle, &token))
		{
			return qfalse;
		}
    
		if ( token.string[0] == 0 ) 
		{
			return qfalse;
		}

		if ( token.string[0] == '}' ) 
		{
			return qtrue;
		}

		UI_ParseMenu(token.string); 
	}

	return qfalse;
}

extern menuDef_t Menus[MAX_MENUS];      // defined menus
extern int menuCount;               // how many

/*
=================
UI_LoadMenus

Loads all menus from the given text file and optionally resets the 
existing menus
=================
*/
void UI_LoadMenus ( const char *menuFile, qboolean reset ) 
{
	pc_token_t	token;
	int			handle;
	int			start;

	start = trap_Milliseconds();

	trap_PC_LoadGlobalDefines ( "ui/menudef.h" );

	handle = trap_PC_LoadSource( menuFile );
	if (!handle) 
	{
		trap_Error( va( S_COLOR_RED "menu file not found: %s, unable to continue!\n", menuFile ) );
	}

	ui_new.integer = 1;

	if (reset) 
	{
		Menu_Reset();
	}

	while ( 1 ) 
	{
		if (!trap_PC_ReadToken(handle, &token))
		{
			break;
		}

		if( token.string[0] == 0 || token.string[0] == '}') 
		{
			break;
		}

		if ( token.string[0] == '}' ) 
		{
			break;
		}

		if (Q_stricmp(token.string, "loadmenu") == 0) 
		{
			if (Load_Menu(handle)) 
			{
				if (menuCount == 1 && Menus_FindByName("Loading"))
				{
					// handle double / tripple buffering...
					UI_UpdateScreen(qtrue);
					UI_UpdateScreen(qtrue);
					UI_UpdateScreen(qtrue);
				}
				continue;
			} 
			else 
			{
				break;
			}
		}
	}

	Com_Printf("UI total menu load time = %d ms\n", trap_Milliseconds() - start);

	trap_PC_FreeSource( handle );

	trap_PC_RemoveAllGlobalDefines ( );
}

/*
=================
UI_Reload

Reloads the 
=================
*/
void UI_Reload ( void ) 
{
	char		lastName[1024];
	menuDef_t	*menu;
	int			actual;

	// Retail the last active menu so it 
	// can be opened again
	menu = Menu_GetFocused();
	if (menu && menu->window.name) 
	{
		strcpy(lastName, menu->window.name);
	}

	// Reinitialize the strings
	String_Init();

	// Read in the gametype list for the various user interface screens
	BG_BuildGametypeList ( );

	// Load the list of arenas available for play
	UI_LoadArenas();

	UI_SelectedMap ( 0, &actual );
	trap_Cvar_Set("ui_mapIndex", "0");
	ui_currentNetMap.integer = actual;
	trap_Cvar_Set("ui_currentNetMap", va("%d",actual));
	trap_Cvar_Set("ui_currentNetMapName", uiInfo.mapList[actual].mapLoadName );

	// Load all of the menus files now
	UI_LoadMenus( uiInfo.menusFile, qtrue);

	// Close any open menus and open the last menu that was open before reloading
	Menus_CloseAll();
	Menus_ActivateByName(lastName);
}

void UI_DrawObjectivePhotos ( rectDef_t *rect, qhandle_t font, float scale, vec4_t color )
{
	int   gametype = (int) trap_Cvar_VariableValue ( "ui_about_gametype" );
	int   count;
	int   index;
	float width;
	float x;
	float spacing;
	float height;
	float fontheight;

	// Figure out how many photos there are
	for ( count = 0; bg_gametypeData[gametype].photos[count].name && count < MAX_GAMETYPE_PHOTOS; count ++ );

	// Images should be saved as 256 x 192
	width  = (rect->w / count) - ((count-1) * 10);
	height = width / 256 * 192;

	// If height is too big for the given rect, scale them down again
	if ( height > rect->h )
	{
		width = rect->h / height * width;
		height = rect->h;
	}
	
	// How much space is there between each photo horizontally?
	spacing = (rect->w - (width * count)) / (count-1);

	fontheight = trap_R_GetTextHeight ( "W", font, scale, 0 );

	for ( x = rect->x, index = 0; index < count; index ++ )
	{
		qhandle_t pic;

		pic = trap_R_RegisterShaderNoMip ( va("gfx/menus/levelshots/%s_%s_%s",
									  UI_Cvar_VariableString ( "ui_about_mapname" ),
									  bg_gametypeData[gametype].name,
									  bg_gametypeData[gametype].photos[index].name ) );

		if ( pic )
		{
			UI_DrawText ( x, rect->y - fontheight - 2, font, scale, color, bg_gametypeData[gametype].photos[index].displayName, 0, 0 );
			UI_DrawHandlePic ( x, rect->y, width, height, pic );
		}

		x += spacing;
		x += width;
	}	
}

// ui_gameType assumes gametype 0 is -1 ALL and will not show
static void UI_DrawGameType(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) 
{
	UI_DrawText (rect->x, rect->y, font, scale, color, bg_gametypeData[(int)trap_Cvar_VariableValue ( "ui_about_gametype" )].displayName, 0, 0 );
}

static void UI_DrawNetGameType(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) 
{
	if ( ui_netGameType.integer < 0 || ui_netGameType.integer >= bg_gametypeCount ) 
	{
		trap_Cvar_Set("ui_netGameType", "0");
		trap_Cvar_Set("ui_actualNetGameType", "0");

		trap_Cvar_Update ( &ui_netGameType );
		trap_Cvar_Update ( &ui_actualNetGameType );
	}

	UI_DrawText (rect->x, rect->y, font, scale, color, bg_gametypeData[ ui_netGameType.integer ].displayName, 0, 0 );
}

static void UI_DrawJoinGameType(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) 
{
 	if ( ui_joinGameType.integer < 0 || ui_joinGameType.integer > bg_gametypeCount ) 
	{
		trap_Cvar_Set("ui_joinGameType", "0");
	}

	if ( ui_joinGameType.integer == 0 )
	{
		UI_DrawText(rect->x, rect->y, font, scale, color, "All", 0, 0 );
	}
	else
	{
		UI_DrawText(rect->x, rect->y, font, scale, color, bg_gametypeData[ui_joinGameType.integer-1].displayName, 0, 0 );
	}
}

static void UI_DrawPreviewCinematic(rectDef_t *rect, float scale, vec4_t color) {
	if (uiInfo.previewMovie > -2) {
		uiInfo.previewMovie = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.movieList[uiInfo.movieIndex]), 0, 0, 0, 0, (CIN_loop | CIN_silent) );
		if (uiInfo.previewMovie >= 0) {
		  trap_CIN_RunCinematic(uiInfo.previewMovie);
			trap_CIN_SetExtents(uiInfo.previewMovie, rect->x, rect->y, rect->w, rect->h);
 			trap_CIN_DrawCinematic(uiInfo.previewMovie);
		} else {
			uiInfo.previewMovie = -2;
		}
	} 

}

static void UI_DrawSkill(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) 
{
	int i;
	
	i = trap_Cvar_VariableValue( "g_botSkill" );
	
	if (i < 1 || i > numSkillLevels) 
	{
		i = 1;
	}

	UI_DrawText (rect->x, rect->y, font, scale, color, skillLevels[i-1],0, 0 );
}


static void UI_DrawTeamName(rectDef_t *rect, qhandle_t font, float scale, vec4_t color, qboolean blue ) 
{
	char name[MAX_QPATH];
	trap_Cvar_VariableStringBuffer ( va("ui_%steamname", blue?"blue":"red"), name, MAX_QPATH );

	UI_DrawText (rect->x, rect->y, font, scale, color, name,0, 0 );
}

static void UI_DrawTeamIdentity ( rectDef_t *rect, team_t team, int index )
{
	if ( uiInfo.identityTeams[team][index] && uiInfo.identityTeams[team][index]->mIcon )
	{
		UI_DrawHandlePic( rect->x + 2, rect->y + 2, rect->w - 4, rect->h - 4, uiInfo.identityTeams[team][index]->mIcon);
	}
	else
	{
		static vec4_t color = {0,0,0,0.25f};
		UI_DrawRect ( rect->x + 2, rect->y + 2, rect->w - 4, rect->h -4, color );
	}
}

static void UI_DrawMapPreview(rectDef_t *rect, float scale, vec4_t color, qboolean net) 
{
	int map = (net) ? ui_currentNetMap.integer : ui_currentMap.integer;
	if (map < 0 || map > uiInfo.mapCount) 
	{
		if (net) 
		{
			int actual;
			trap_Cvar_Set("ui_mapIndex", "0");
			UI_SelectedMap ( 0, &actual );
			ui_currentNetMap.integer = actual;
			trap_Cvar_Set("ui_currentNetMap", va("%d",actual));
			trap_Cvar_Set("ui_currentNetMapName", uiInfo.mapList[actual].mapLoadName );
		} 
		else 
		{
			ui_currentMap.integer = 0;
			trap_Cvar_Set("ui_currentMap", "0");
		}
		map = 0;
	}

	if (uiInfo.mapList[map].levelShot == -1) 
	{
		uiInfo.mapList[map].levelShot = trap_R_RegisterShaderNoMip(uiInfo.mapList[map].imageName);
	}

	if (uiInfo.mapList[map].levelShot > 0) {
		UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.mapList[map].levelShot);
	} else {
		UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, trap_R_RegisterShaderNoMip("gfx/menus/levelshots/unknownmap_mp"));
	}
}						 

static void UI_DrawMapCinematic(rectDef_t *rect, float scale, vec4_t color, qboolean net) 
{
	int map = (net) ? ui_currentNetMap.integer : ui_currentMap.integer; 
	if (map < 0 || map > uiInfo.mapCount) 
	{
		if (net) 
		{
			int actual;
			trap_Cvar_Set("ui_mapIndex", "0");
			UI_SelectedMap ( 0, &actual );
			ui_currentNetMap.integer = actual;
			trap_Cvar_Set("ui_currentNetMap", va("%d",actual));
			trap_Cvar_Set("ui_currentNetMapName", uiInfo.mapList[actual].mapLoadName );
		} 
		else 
		{
			ui_currentMap.integer = 0;
			trap_Cvar_Set("ui_currentMap", "0");
		}
		
		map = 0;
	}

	if (uiInfo.mapList[map].cinematic >= -1) 
	{
		if (uiInfo.mapList[map].cinematic == -1) 
		{
			uiInfo.mapList[map].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.mapList[map].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent) );
		}
		if (uiInfo.mapList[map].cinematic >= 0) 
		{
			trap_CIN_RunCinematic(uiInfo.mapList[map].cinematic);
			trap_CIN_SetExtents(uiInfo.mapList[map].cinematic, rect->x, rect->y, rect->w, rect->h);
			trap_CIN_DrawCinematic(uiInfo.mapList[map].cinematic);
		} 
		else 
		{
			uiInfo.mapList[map].cinematic = -2;
		}
	} 
	else 
	{
		UI_DrawMapPreview(rect, scale, color, net);
	}
}

static qboolean updateModel = qtrue;


/*
================
UI_DrawOutfittingBackground

Draws the slot background
================
*/
static void UI_DrawOutfittingBackground ( rectDef_t* rect, vec4_t color, int slot )
{
	vec4_t  col;

	VectorCopy4 ( color, col );

	if ( bg_outfittings[uiInfo.outfittingItemGroup].items[slot] == -1 )
	{
		col[3] *= 0.5f;
	}
	else
	{
		int item = bg_outfittingGroups[slot][bg_outfittings[uiInfo.outfittingItemGroup].items[slot]];

		if ( item <= 0 )
		{
			col[3] *= 0.5f;
		}
	}

	trap_R_SetColor ( col );
	UI_DrawHandlePic ( rect->x, rect->y, rect->w, rect->h, uiInfo.uiDC.whiteShader );
}

/*
================
UI_DrawOutfittingSlotName

Draw the name of the weapon
================
*/
static void UI_DrawOutfittingSlotName ( rectDef_t *rect, qhandle_t font, float scale, vec4_t color, int slot )
{
	const char* s;
	int			item;
	float		w;

	// Handle the case where there is nothing at all available for this group
	if ( bg_outfittings[uiInfo.outfittingItemGroup].items[slot] == -1 )
	{
		s = "NONE";
	}
	else
	{
		item = bg_outfittingGroups[slot][bg_outfittings[uiInfo.outfittingItemGroup].items[slot]];
		if ( item <= 0 )
		{
			s = "NONE";
		}
		else
		{
			s = bg_itemlist[item].pickup_name;
		}
	}

	w = trap_R_GetTextWidth ( s, font, scale, 0 );	

	UI_DrawText ( rect->x - w, rect->y, font, scale, color, s, 0, 0 );	
}

/*
================
UI_DrawOutfittingSlotRender

Draw the weapon render for the given outfitting slot
================
*/
static void UI_DrawOutfittingSlotRender ( rectDef_t *rect, int slot )
{
	qhandle_t	shader;
	const char*	name;

	// Nothing at all? Then draw a black square
	if ( bg_outfittings[uiInfo.outfittingItemGroup].items[slot] == -1 )
	{
		return;
	}
	
	name = bg_itemlist[bg_outfittingGroups[slot][bg_outfittings[uiInfo.outfittingItemGroup].items[slot]]].render;
	if ( !name )
	{
		return;
	}

	// This is a bit hacky, but any render with a * in the front of it
	// is only half size
	if ( *name == '*')
	{
		rect->x += rect->w / 4;
		rect->w /= 2;
		name ++;
	}

	shader = trap_R_RegisterShaderNoMip ( name );

	UI_DrawHandlePic ( rect->x, rect->y, rect->w, rect->h, shader );
}

static void UI_DrawPlayerModel(rectDef_t *rect) 
{
	vec3_t	viewangles;
	vec3_t	moveangles;
	char	identity[MAX_QPATH];

	if ( (int)trap_Cvar_VariableValue ( "ui_info_teamgame" ) )
	{
		strcpy(identity, UI_Cvar_VariableString("team_identity"));
	}
	else
	{
		strcpy(identity, UI_Cvar_VariableString("identity"));
	}

	if (updateModel) 
	{
		if (playerInfo.playerG2Model)
		{
			trap_G2API_CleanGhoul2Models(&playerInfo.playerG2Model);
		}

  		memset( &playerInfo, 0, sizeof(playerInfo_t) );
  		viewangles[YAW]   = 180 - 10;
  		viewangles[PITCH] = 0;
  		viewangles[ROLL]  = 0;
  		VectorClear( moveangles );
		UI_PlayerInfo_SetIdentity ( &playerInfo, identity );
		UI_PlayerInfo_SetInfo( &playerInfo, LEGS_WALK, TORSO_IDLE_PISTOL, viewangles, vec3_origin, WP_FIRST_RANGED_WEAPON, qfalse );
		updateModel = qfalse;
	}

	UI_DrawPlayer( rect->x, rect->y, rect->w, rect->h, &playerInfo, uiInfo.uiDC.realTime / 2);
}

static void UI_DrawNetSource(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) 
{
	if (ui_netSource.integer < 0 || ui_netSource.integer > numNetSources) 
	{
		ui_netSource.integer = 0;
	}

	UI_DrawText (rect->x, rect->y, font, scale, color, va("Source: %s", netSources[ui_netSource.integer]), 0, 0 );
}

static void UI_DrawNetMapPreview(rectDef_t *rect, float scale, vec4_t color) {

	if (uiInfo.serverStatus.currentServerPreview > 0) {
		UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.serverStatus.currentServerPreview);
	} else {
		UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, trap_R_RegisterShaderNoMip("gfx/menus/levelshots/unknownmap_mp"));
	}
}

static void UI_DrawNetMapCinematic(rectDef_t *rect, float scale, vec4_t color) 
{
	if (ui_currentNetMap.integer < 0 || ui_currentNetMap.integer > uiInfo.mapCount) 
	{
		int actual;
		trap_Cvar_Set("ui_mapIndex", "0");
		UI_SelectedMap ( 0, &actual );
		ui_currentNetMap.integer = actual;
		trap_Cvar_Set("ui_currentNetMap", va("%d",actual));
		trap_Cvar_Set("ui_currentNetMapName", uiInfo.mapList[actual].mapLoadName );
	}

	if (uiInfo.serverStatus.currentServerCinematic >= 0) 
	{
	  trap_CIN_RunCinematic(uiInfo.serverStatus.currentServerCinematic);
	  trap_CIN_SetExtents(uiInfo.serverStatus.currentServerCinematic, rect->x, rect->y, rect->w, rect->h);
 	  trap_CIN_DrawCinematic(uiInfo.serverStatus.currentServerCinematic);
	} 
	else 
	{
		UI_DrawNetMapPreview(rect, scale, color);
	}
}

static void UI_DrawNetFilter(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) 
{
	if (ui_serverFilterType.integer < 0 || ui_serverFilterType.integer > numServerFilters) 
	{
		ui_serverFilterType.integer = 0;
	}

	UI_DrawText (rect->x, rect->y, font, scale, color, va("Filter: %s", serverFilters[ui_serverFilterType.integer].description), 0, 0 );
}


static const char *UI_EnglishMapName(const char *map) {
	int i;
	for (i = 0; i < uiInfo.mapCount; i++) {
		if (Q_stricmp(map, uiInfo.mapList[i].mapLoadName) == 0) {
			return uiInfo.mapList[i].mapName;
		}
	}
	return "";
}

static void UI_DrawAllMapsSelection(rectDef_t *rect, qhandle_t font, float scale, vec4_t color, int textStyle, qboolean net) 
{
	int map = (net) ? ui_currentNetMap.integer : ui_currentMap.integer;
	
	if (map >= 0 && map < uiInfo.mapCount) 
	{
		UI_DrawText (rect->x, rect->y, font, scale, color, uiInfo.mapList[map].mapName, 0, 0 );
	}
}

static int UI_OwnerDrawWidth(int ownerDraw, qhandle_t font, float scale ) 
{
	const char	*s = NULL;

	switch (ownerDraw) 
	{
		case UI_GAMETYPE:
			s = bg_gametypeData[ui_gameType.integer].displayName;
			break;

		case UI_SKILL:
			{
				int	i;

				i = trap_Cvar_VariableValue( "g_botSkill" );
				if (i < 1 || i > numSkillLevels) 
				{
					i = 1;
				}
				s = skillLevels[i-1];
			}
			break;
   
		case UI_NETSOURCE:
			if (ui_netSource.integer < 0 || ui_netSource.integer >= bg_gametypeCount) 
			{
				ui_netSource.integer = 0;
			}
			s = va("Source: %s", netSources[ui_netSource.integer]);
			break;

		case UI_NETFILTER:
			if (ui_serverFilterType.integer < 0 || ui_serverFilterType.integer > numServerFilters) {
				ui_serverFilterType.integer = 0;
			}
			s = va("Filter: %s", serverFilters[ui_serverFilterType.integer].description );
			break;

		case UI_ALLMAPS_SELECTION:
			break;

		case UI_SERVERREFRESHDATE:
			s = UI_Cvar_VariableString(va("ui_lastServerRefresh_%i", ui_netSource.integer));
			break;
    
		default:
			break;
	}

	if (s) 
	{
		return trap_R_GetTextWidth (s, font, scale, 0 );
	}

	return 0;
}

static void UI_DrawBotName(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) 
{
	int			value = uiInfo.botIndex;
	const char	*text = "";

	if (value >= UI_GetNumBots()) 
	{
		value = 0;
	}

	text = UI_GetBotNameByNumber(value);

	UI_DrawText (rect->x, rect->y, font, scale, color, text, 0, 0 );
}

static void UI_DrawBotSkill(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) 
{
	if (uiInfo.skillIndex < 0 || uiInfo.skillIndex >= numSkillLevels) 
		return;

	UI_DrawText(rect->x, rect->y, font, scale, color, skillLevels[uiInfo.skillIndex], 0, 0 );
}

static void UI_DrawRedBlue(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) 
{
	UI_DrawText (rect->x, rect->y, font, scale, color, (uiInfo.redBlue == 0) ? "Red" : "Blue", 0, 0 );
}

static void UI_DrawCrosshair(rectDef_t *rect, float scale, vec4_t color) {
 	trap_R_SetColor( color );
	if (uiInfo.currentCrosshair < 0 || uiInfo.currentCrosshair >= NUM_CROSSHAIRS) {
		uiInfo.currentCrosshair = 0;
	}
	UI_DrawHandlePic( rect->x, rect->y, 24, 24, uiInfo.uiDC.Assets.crosshairShader[uiInfo.currentCrosshair]);
 	trap_R_SetColor( NULL );
}

/*
===============
UI_BuildPlayerList
===============
*/
static void UI_BuildPlayerList() 
{
	uiClientState_t	cs;
	int				n, count, team, team2, playerTeamNumber;
	char			info[MAX_INFO_STRING];

	trap_GetClientState( &cs );
	trap_GetConfigString( CS_PLAYERS + cs.clientNum, info, MAX_INFO_STRING );
	uiInfo.playerNumber = cs.clientNum;
	uiInfo.teamLeader = atoi(Info_ValueForKey(info, "tl"));
	team = atoi(Info_ValueForKey(info, "t"));
	trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) );
	count = atoi( Info_ValueForKey( info, "sv_maxclients" ) );
	uiInfo.playerCount = 0;
	uiInfo.myTeamCount = 0;
	playerTeamNumber = 0;
	
	for( n = 0; n < count; n++ ) 
	{
		trap_GetConfigString( CS_PLAYERS + n, info, MAX_INFO_STRING );

		if (info[0]) 
		{
			Com_sprintf ( uiInfo.playerNames[uiInfo.playerCount], 4, "%2d", n );
			Q_strncpyz( uiInfo.playerNames[uiInfo.playerCount] + 2, Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH );
			Q_CleanStr( uiInfo.playerNames[uiInfo.playerCount] + 2 );
			uiInfo.playerCount++;
			team2 = atoi(Info_ValueForKey(info, "t"));
			
			if (team2 == team) 
			{
				Q_strncpyz( uiInfo.teamNames[uiInfo.myTeamCount], Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH );
				Q_CleanStr( uiInfo.teamNames[uiInfo.myTeamCount] );
				uiInfo.teamClientNums[uiInfo.myTeamCount] = n;

				if (uiInfo.playerNumber == n) 
				{
					playerTeamNumber = uiInfo.myTeamCount;
				}

				uiInfo.myTeamCount++;
			}
		}
	}

	if (!uiInfo.teamLeader) {
		trap_Cvar_Set("cg_selectedPlayer", va("%d", playerTeamNumber));
	}

	n = trap_Cvar_VariableValue("cg_selectedPlayer");
	if (n < 0 || n > uiInfo.myTeamCount) {
		n = 0;
	}
	if (n < uiInfo.myTeamCount) {
		trap_Cvar_Set("cg_selectedPlayerName", uiInfo.teamNames[n]);
	}
}


static void UI_DrawSelectedPlayer(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) 
{
	if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) 
	{
		uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000;
		UI_BuildPlayerList();
	}

	UI_DrawText (rect->x, rect->y, font, scale, color, (uiInfo.teamLeader) ? UI_Cvar_VariableString("cg_selectedPlayerName") : UI_Cvar_VariableString("name") , 0, 0 );
}

static void UI_DrawServerRefreshDate(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) 
{
	if (uiInfo.serverStatus.refreshActive) 
	{
		vec4_t lowLight, newColor;
		lowLight[0] = 0.8 * color[0]; 
		lowLight[1] = 0.8 * color[1]; 
		lowLight[2] = 0.8 * color[2]; 
		lowLight[3] = 0.8 * color[3]; 
		LerpColor(color,lowLight,newColor,0.5+0.5*sin(uiInfo.uiDC.realTime / PULSE_DIVISOR));
		UI_DrawText (rect->x, rect->y, font, scale, newColor, va("Getting info for %d servers (ESC to cancel)", trap_LAN_GetServerCount(ui_netSource.integer)), 0, 0 );
	} 
	else 
	{
		char buff[64];
		Q_strncpyz(buff, UI_Cvar_VariableString(va("ui_lastServerRefresh_%i", ui_netSource.integer)), 64);
		UI_DrawText (rect->x, rect->y, font, scale, color, va("Refresh Time: %s", buff), 0, 0 );
	}
}

static void UI_DrawServerMOTD(rectDef_t *rect, float scale, vec4_t color) {
	if (uiInfo.serverStatus.motdLen) {
		float maxX;
	 
		if (uiInfo.serverStatus.motdWidth == -1) {
			uiInfo.serverStatus.motdWidth = 0;
			uiInfo.serverStatus.motdPaintX = rect->x + 1;
			uiInfo.serverStatus.motdPaintX2 = -1;
		}

		if (uiInfo.serverStatus.motdOffset > uiInfo.serverStatus.motdLen) {
			uiInfo.serverStatus.motdOffset = 0;
			uiInfo.serverStatus.motdPaintX = rect->x + 1;
			uiInfo.serverStatus.motdPaintX2 = -1;
		}

		if (uiInfo.uiDC.realTime > uiInfo.serverStatus.motdTime) 
		{
			uiInfo.serverStatus.motdTime = uiInfo.uiDC.realTime + 10;
			if (uiInfo.serverStatus.motdPaintX <= rect->x + 2) 
			{
				if (uiInfo.serverStatus.motdOffset < uiInfo.serverStatus.motdLen) 
				{
					uiInfo.serverStatus.motdPaintX += trap_R_GetTextWidth ( &uiInfo.serverStatus.motd[uiInfo.serverStatus.motdOffset], uiInfo.uiDC.Assets.defaultFont, scale, 1 ) - 1;
					uiInfo.serverStatus.motdOffset++;
				} 
				else 
				{
					uiInfo.serverStatus.motdOffset = 0;
					if (uiInfo.serverStatus.motdPaintX2 >= 0) 
					{
						uiInfo.serverStatus.motdPaintX = uiInfo.serverStatus.motdPaintX2;
					} 
					else 
					{
						uiInfo.serverStatus.motdPaintX = rect->x + rect->w - 2;
					}
					uiInfo.serverStatus.motdPaintX2 = -1;
				}
			} 
			else 
			{
				//serverStatus.motdPaintX--;
				uiInfo.serverStatus.motdPaintX -= 2;
				if (uiInfo.serverStatus.motdPaintX2 >= 0) {
					//serverStatus.motdPaintX2--;
					uiInfo.serverStatus.motdPaintX2 -= 2;
				}
			}
		}

		maxX = rect->x + rect->w - 2;
//		Text_Paint_Limit(&maxX, uiInfo.serverStatus.motdPaintX, rect->y + rect->h - 3, scale, color, &uiInfo.serverStatus.motd[uiInfo.serverStatus.motdOffset], 0, 0, NULL ); 
		if (uiInfo.serverStatus.motdPaintX2 >= 0) 
		{
//			float maxX2 = rect->x + rect->w - 2;
//			Text_Paint_Limit(&maxX2, uiInfo.serverStatus.motdPaintX2, rect->y + rect->h - 3, scale, color, uiInfo.serverStatus.motd, 0, uiInfo.serverStatus.motdOffset, NULL ); 
		}
		if (uiInfo.serverStatus.motdOffset && maxX > 0) {
			// if we have an offset ( we are skipping the first part of the string ) and we fit the string
			if (uiInfo.serverStatus.motdPaintX2 == -1) {
						uiInfo.serverStatus.motdPaintX2 = rect->x + rect->w - 2;
			}
		} else {
			uiInfo.serverStatus.motdPaintX2 = -1;
		}

	}
}

static void UI_DrawKeyBindStatus(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) 
{
	if (Display_KeyBindPending()) 
	{
		UI_DrawText (rect->x, rect->y, font, scale, color, "Waiting for new key... Press ESCAPE to cancel", 0, 0 );
	} 
	else 
	{
		UI_DrawText (rect->x, rect->y, font, scale, color, "Press ENTER or CLICK to change, Press BACKSPACE to clear", 0, 0 );
	}
}

static void UI_DrawGLInfo(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) 
{
	char * eptr;
	char buff[4096];
	const char *lines[64];
	int y, numLines, i;

	UI_DrawText (rect->x + 2, rect->y, font, scale, color, va("VENDOR: %s", uiInfo.uiDC.glconfig.vendor_string), 30, 0 );
	UI_DrawText (rect->x + 2, rect->y + 15, font, scale, color, va("VERSION: %s: %s", uiInfo.uiDC.glconfig.version_string,uiInfo.uiDC.glconfig.renderer_string), 30, 0 );
	UI_DrawText (rect->x + 2, rect->y + 30, font, scale, color, va ("PIXELFORMAT: color(%d-bits) Z(%d-bits) stencil(%d-bits)", uiInfo.uiDC.glconfig.colorBits, uiInfo.uiDC.glconfig.depthBits, uiInfo.uiDC.glconfig.stencilBits), 30, 0);

	// build null terminated extension strings
	Q_strncpyz(buff, uiInfo.uiDC.glconfig.extensions_string, 4096);
	eptr = buff;
	y = rect->y + 45;
	numLines = 0;
	while ( y < rect->y + rect->h && *eptr )
	{
		while ( *eptr && *eptr == ' ' )
			*eptr++ = '\0';

		// track start of valid string
		if (*eptr && *eptr != ' ') {
			lines[numLines++] = eptr;
		}

		while ( *eptr && *eptr != ' ' )
			eptr++;
	}

	i = 0;
	while (i < numLines) 
	{
		UI_DrawText (rect->x + 2, y, font, scale, color, lines[i++], 20, 0 );
		
		if (i < numLines) 
		{
			UI_DrawText(rect->x + rect->w / 2, y, font, scale, color, lines[i++], 20, 0 );
		
		}
		y += 10;

		if (y > rect->y + rect->h - 11) 
		{
			break;
		}
	}


}

/*
=================
UI_Version
=================
*/
static void UI_Version(rectDef_t *rect, float scale, vec4_t color) 
{
	int width;
	
	width = trap_R_GetTextWidth ( Q3_VERSION, uiInfo.uiDC.Assets.defaultFont, scale, 0 );

	UI_DrawText (rect->x - width, rect->y, uiInfo.uiDC.Assets.defaultFont, scale, color, Q3_VERSION, 0, 0 );
}

// FIXME: table drive
//
static void UI_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, qhandle_t font, float scale, vec4_t color, qhandle_t shader, int textStyle, const char* param ) 
{
	rectDef_t rect;

	rect.x = x + text_x;
	rect.y = y + text_y;
	rect.w = w;
	rect.h = h;

	switch (ownerDraw) 
	{
		case UI_OUTFITTING_SLOT_RENDER:
			UI_DrawOutfittingSlotRender ( &rect, atoi ( param ));
			break;

		case UI_OUTFITTING_SLOT_NAME:
			UI_DrawOutfittingSlotName ( &rect, font, scale, color, atoi ( param ) );
			break;		

		case UI_OUTFITTING_SLOT_BACKGROUND:
			UI_DrawOutfittingBackground ( &rect, color, atoi ( param ) );
			break;

		case UI_PLAYERMODEL:
			UI_DrawPlayerModel(&rect);
			break;

		case UI_PREVIEWCINEMATIC:
			UI_DrawPreviewCinematic(&rect, scale, color);
			break;
		
		case UI_GAMETYPE:
			UI_DrawGameType(&rect, font, scale, color );
			break;

		case UI_OBJECTIVE_PHOTOS:	
			UI_DrawObjectivePhotos (&rect, font, scale, color );
			break;

		case UI_NETGAMETYPE:
			UI_DrawNetGameType(&rect, font, scale, color );
			break;

		case UI_JOINGAMETYPE:
			UI_DrawJoinGameType(&rect, font, scale, color );
			break;

		case UI_MAPPREVIEW:
			UI_DrawMapPreview(&rect, scale, color, qtrue);
			break;
	
		case UI_MAPCINEMATIC:
      UI_DrawMapCinematic(&rect, scale, color, qfalse);
      break;
    case UI_STARTMAPCINEMATIC:
      UI_DrawMapCinematic(&rect, scale, color, qtrue);
      break;
		
		case UI_SKILL:
			UI_DrawSkill(&rect, font, scale, color );
			break;

		case UI_REDTEAM_IDENTITY:
			UI_DrawTeamIdentity ( &rect, TEAM_RED, atoi(param) );
			break;

		case UI_BLUETEAM_IDENTITY:
			UI_DrawTeamIdentity ( &rect, TEAM_BLUE, atoi(param) );
			break;
		
		case UI_NETSOURCE:
			UI_DrawNetSource(&rect, font, scale, color );
			break;
		
    case UI_NETMAPPREVIEW:
      UI_DrawNetMapPreview(&rect, scale, color);
      break;
    case UI_NETMAPCINEMATIC:
      UI_DrawNetMapCinematic(&rect, scale, color);
      break;
		
		case UI_NETFILTER:
			UI_DrawNetFilter(&rect, font, scale, color );
			break;
		case UI_ALLMAPS_SELECTION:
			UI_DrawAllMapsSelection(&rect, font, scale, color, textStyle, qtrue);
			break;
		case UI_MAPS_SELECTION:
			UI_DrawAllMapsSelection(&rect, font, scale, color, textStyle, qfalse);
			break;
		case UI_BOTNAME:
			UI_DrawBotName(&rect, font, scale, color );
			break;
		case UI_BOTSKILL:
			UI_DrawBotSkill(&rect, font, scale, color );
			break;
		case UI_REDBLUE:
			UI_DrawRedBlue(&rect, font, scale, color );
			break;
		case UI_CROSSHAIR:
			UI_DrawCrosshair(&rect, scale, color);
			break;
		case UI_SELECTEDPLAYER:
			UI_DrawSelectedPlayer(&rect, font, scale, color );
			break;
		case UI_SERVERREFRESHDATE:
			UI_DrawServerRefreshDate(&rect, font, scale, color );
			break;
		case UI_SERVERMOTD:
			UI_DrawServerMOTD(&rect, scale, color);
			break;
		case UI_GLINFO:
			UI_DrawGLInfo(&rect, font, scale, color );
			break;
		case UI_VERSIONDOWNLOAD_PROGRESS:
			UI_DrawVersionDownloadProgress(&rect, font, scale, color );
			break;
		default:
			break;
	}
}

static void UI_DrawVersionDownloadProgress(rectDef_t *rect, qhandle_t font, float scale, vec4_t color )
{
	// Add code to show loading progress
	UI_DrawText (rect->x, rect->y, font, scale, color, va("Progress: %d %",90),0,0);
}

static qboolean UI_OwnerDrawDisabled ( int flags, const char* param )
{	
	return qfalse;
}

static qboolean UI_OwnerDrawVisible ( int flags, const char* param ) 
{
	qboolean vis = qtrue;

	while (vis && flags) 
	{
		if (flags & UI_SHOW_LEADER) 
		{
			// these need to show when this client can give orders to a player or a group
			if (!uiInfo.teamLeader) 
			{
				vis = qfalse;
			} 
			else 
			{
				// if showing yourself
				if (ui_selectedPlayer.integer < uiInfo.myTeamCount && uiInfo.teamClientNums[ui_selectedPlayer.integer] == uiInfo.playerNumber) 
				{				 
					vis = qfalse;
				}
			}
			flags &= ~UI_SHOW_LEADER;
		} 

		if (flags & UI_SHOW_NOTLEADER) 
		{
			// these need to show when this client is assigning their own status or they are NOT the leader
			if (uiInfo.teamLeader) {
				// if not showing yourself
				if (!(ui_selectedPlayer.integer < uiInfo.myTeamCount && uiInfo.teamClientNums[ui_selectedPlayer.integer] == uiInfo.playerNumber)) { 
					vis = qfalse;
				}
				// these need to show when this client can give orders to a player or a group
			}
			flags &= ~UI_SHOW_NOTLEADER;
		} 
		if (flags & UI_SHOW_FAVORITESERVERS) {
			// this assumes you only put this type of display flag on something showing in the proper context
			if (ui_netSource.integer != AS_FAVORITES) {
				vis = qfalse;
			}
			flags &= ~UI_SHOW_FAVORITESERVERS;
		} 
		if (flags & UI_SHOW_NOTFAVORITESERVERS) {
			// this assumes you only put this type of display flag on something showing in the proper context
			if (ui_netSource.integer == AS_FAVORITES) {
				vis = qfalse;
			}
			flags &= ~UI_SHOW_NOTFAVORITESERVERS;
		} 
		if (flags & UI_SHOW_ANYTEAMGAME) 
		{
			if ( !bg_gametypeData[ui_gameType.integer].teams ) 
			{
				vis = qfalse;
			}
			flags &= ~UI_SHOW_ANYTEAMGAME;
		} 
		
		if (flags & UI_SHOW_ANYNONTEAMGAME) 
		{
			if ( bg_gametypeData[ui_gameType.integer].teams ) 
			{
				vis = qfalse;
			}
			
			flags &= ~UI_SHOW_ANYNONTEAMGAME;
		} 
		
		if (flags & UI_SHOW_NETANYTEAMGAME) 
		{
			if ( !bg_gametypeData[ui_netGameType.integer].teams ) 
			{
				vis = qfalse;
			}

			flags &= ~UI_SHOW_NETANYTEAMGAME;
		} 
		
		if (flags & UI_SHOW_NETANYNONTEAMGAME) 
		{
			if ( bg_gametypeData[ui_netGameType.integer].teams ) 
			{
				vis = qfalse;
			}
			flags &= ~UI_SHOW_NETANYNONTEAMGAME;
		} 
		if (flags & UI_SHOW_NEWHIGHSCORE) {
			if (uiInfo.newHighScoreTime < uiInfo.uiDC.realTime) {
				vis = qfalse;
			} else {
				if (uiInfo.soundHighScore) {
					if (trap_Cvar_VariableValue("sv_killserver") == 0) {
						// wait on server to go down before playing sound
						uiInfo.soundHighScore = qfalse;
					}
				}
			}
			flags &= ~UI_SHOW_NEWHIGHSCORE;
		} 
		if (flags & UI_SHOW_NEWBESTTIME) {
			if (uiInfo.newBestTime < uiInfo.uiDC.realTime) {
				vis = qfalse;
			}
			flags &= ~UI_SHOW_NEWBESTTIME;
		} 
		if (flags & UI_SHOW_DEMOAVAILABLE) {
			if (!uiInfo.demoAvailable) {
				vis = qfalse;
			}
			flags &= ~UI_SHOW_DEMOAVAILABLE;
		} else {
			flags = 0;
		}
	}
  return vis;
}

static qboolean UI_GameType_HandleKey(int flags, float *special, int key, qboolean resetMap) 
{
	if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) 
	{
		int oldCount = UI_MapCountByGameType();

		// hard coded mess here
		if (key == K_MOUSE2) 
		{
			ui_gameType.integer--;
			if (ui_gameType.integer == 2) 
			{
				ui_gameType.integer = 1;
			} 
			else if (ui_gameType.integer < 2) 
			{
				ui_gameType.integer = bg_gametypeCount - 1;
			}
		} 
		else 
		{
			ui_gameType.integer++;
			if (ui_gameType.integer >= bg_gametypeCount ) 
			{
				ui_gameType.integer = 1;
			} 
			else if (ui_gameType.integer == 2) 
			{
				ui_gameType.integer = 3;
			}
		}
    
		trap_Cvar_Set("ui_gameType", va("%d", ui_gameType.integer));
		
		if (resetMap && oldCount != UI_MapCountByGameType()) 
		{
	  		trap_Cvar_Set( "ui_currentMap", "0");
			Menu_SetFeederSelection(NULL, FEEDER_MAPS, 0, NULL);
		}

		return qtrue;
	}
	
	return qfalse;
}

static qboolean UI_NetGameType_HandleKey(int flags, float *special, int key) 
{
	if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) 
	{
		int oldMapIndex;
		int actual;

		if (key == K_MOUSE2) 
		{
			ui_netGameType.integer--;
		} 
		else 
		{
			ui_netGameType.integer++;
		}

		if (ui_netGameType.integer < 0) 
		{
			ui_netGameType.integer = bg_gametypeCount - 1;
		} 
		else if (ui_netGameType.integer >= bg_gametypeCount) 
		{
			ui_netGameType.integer = 0;
		} 

		if( uiInfo.mapCount )
		{
			oldMapIndex = ui_currentNetMap.integer;
		}
		else
		{
			oldMapIndex = 0;
		}

  		trap_Cvar_Set( "ui_netGameType", va("%d", ui_netGameType.integer));
  		trap_Cvar_Set( "ui_actualnetGameType", va("%d", ui_netGameType.integer));
		trap_Cvar_Set( "ui_gtRespawnType", va("%d", bg_gametypeData[ui_netGameType.integer].respawnType ) );
		trap_Cvar_Set( "ui_gtPickupsDisabled", va("%d", bg_gametypeData[ui_netGameType.integer].pickupsDisabled ) );

		UI_SelectedMap ( 0, &actual );
		ui_currentNetMap.integer = actual;
		trap_Cvar_Set("ui_currentNetMap", va("%d",actual));
		trap_Cvar_Set("ui_currentNetMapName", uiInfo.mapList[actual].mapLoadName );

		UI_MapCountByGameType();
		Menu_SetFeederSelection(NULL, FEEDER_ALLMAPS, 0, NULL);

		if ( oldMapIndex > 0)
		{
			if ( uiInfo.mapList[oldMapIndex].active )
			{
				Menu_SetFeederSelection ( NULL, FEEDER_ALLMAPS, UI_GetIndexFromSelection(oldMapIndex), NULL );
			}
		}		
		
		return qtrue;
	}

	return qfalse;
}

static qboolean UI_JoinGameType_HandleKey(int flags, float *special, int key) 
{
	if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) 
	{
		if (key == K_MOUSE2) 
		{
			ui_joinGameType.integer--;
		} 
		else 
		{
			ui_joinGameType.integer++;
		}

		if (ui_joinGameType.integer < 0) 
		{
			ui_joinGameType.integer = bg_gametypeCount;
		} 
		else if (ui_joinGameType.integer > bg_gametypeCount ) 
		{
			ui_joinGameType.integer = 0;
		}

		trap_Cvar_Set( "ui_joinGameType", va("%d", ui_joinGameType.integer));
		UI_BuildServerDisplayList(qtrue);
		return qtrue;
	}
	return qfalse;
}



static qboolean UI_Skill_HandleKey(int flags, float *special, int key) 
{
	if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) 
	{
		int i = trap_Cvar_VariableValue( "g_botSkill" );

		if (key == K_MOUSE2) 
		{
			i--;
		} 
		else 
		{
			i++;
		}

		if (i < 1) 
		{
			i = numSkillLevels;
		} 
		else if (i > numSkillLevels) 
		{
			i = 1;
		}

		trap_Cvar_Set("g_botSkill", va("%i", i));
		
		return qtrue;
	}
	
	return qfalse;
}

static qboolean UI_NetSource_HandleKey(int flags, float *special, int key) {
  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
		
		if (key == K_MOUSE2) {
			ui_netSource.integer--;
		} else {
			ui_netSource.integer++;
		}
    
		if (ui_netSource.integer >= numNetSources) {
      ui_netSource.integer = 0;
    } else if (ui_netSource.integer < 0) {
      ui_netSource.integer = numNetSources - 1;
		}

		UI_BuildServerDisplayList(qtrue);
		if (ui_netSource.integer != AS_GLOBAL) {
			UI_StartServerRefresh(qtrue);
		}
  	trap_Cvar_Set( "ui_netSource", va("%d", ui_netSource.integer));
    return qtrue;
  }
  return qfalse;
}

static qboolean UI_NetFilter_HandleKey(int flags, float *special, int key) {
  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {

		if (key == K_MOUSE2) {
			ui_serverFilterType.integer--;
		} else {
			ui_serverFilterType.integer++;
		}

    if (ui_serverFilterType.integer >= numServerFilters) {
      ui_serverFilterType.integer = 0;
    } else if (ui_serverFilterType.integer < 0) {
      ui_serverFilterType.integer = numServerFilters - 1;
		}
		UI_BuildServerDisplayList(qtrue);
    return qtrue;
  }
  return qfalse;
}

static qboolean UI_BotName_HandleKey(int flags, float *special, int key) 
{
	if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) 
	{
		int value = uiInfo.botIndex;

		if (key == K_MOUSE2) 
		{
			value--;
		} 
		else 
		{
			value++;
		}

		if (value >= UI_GetNumBots() ) 
		{
			value = 0;
		} 
		else if (value < 0) 
		{
			value = UI_GetNumBots() - 1;
		}
		
		uiInfo.botIndex = value;
		
		return qtrue;
	}
	
	return qfalse;
}

static qboolean UI_BotSkill_HandleKey(int flags, float *special, int key) {
  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
		if (key == K_MOUSE2) {
			uiInfo.skillIndex--;
		} else {
			uiInfo.skillIndex++;
		}
		if (uiInfo.skillIndex >= numSkillLevels) {
			uiInfo.skillIndex = 0;
		} else if (uiInfo.skillIndex < 0) {
			uiInfo.skillIndex = numSkillLevels-1;
		}
    return qtrue;
  }
	return qfalse;
}

static qboolean UI_RedBlue_HandleKey(int flags, float *special, int key) {
  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
		uiInfo.redBlue ^= 1;
		return qtrue;
	}
	return qfalse;
}

static qboolean UI_Crosshair_HandleKey(int flags, float *special, int key) {
  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
		if (key == K_MOUSE2) {
			uiInfo.currentCrosshair--;
		} else {
			uiInfo.currentCrosshair++;
		}

		if (uiInfo.currentCrosshair >= NUM_CROSSHAIRS) {
			uiInfo.currentCrosshair = 0;
		} else if (uiInfo.currentCrosshair < 0) {
			uiInfo.currentCrosshair = NUM_CROSSHAIRS - 1;
		}
		trap_Cvar_Set("cg_drawCrosshair", va("%d", uiInfo.currentCrosshair)); 
		return qtrue;
	}
	return qfalse;
}

static qboolean UI_OutfittingSlot_HandleKey ( int slot, int key )
{
	weapon_t weapon;

	switch ( key )
	{
		case K_MOUSE1:
		case K_ENTER:
		case K_RIGHTARROW:

			// Nothing to cycle?
			if ( bg_outfittings[uiInfo.outfittingItemGroup].items[slot] == -1 )
			{
				break;
			}

			do
			{
				if ( uiInfo.outfittingItemGroup != 0 )
				{
					memcpy ( &bg_outfittings[0].items, &bg_outfittings[uiInfo.outfittingItemGroup].items, sizeof(bg_outfittings[0].items) );
					Menu_SetFeederSelection(NULL, FEEDER_OUTFITTING_TEMPLATES, 0, "ingame_outfitting");
				}

				bg_outfittings[uiInfo.outfittingItemGroup].items[slot]++;
				if ( bg_outfittingGroups[slot][bg_outfittings[uiInfo.outfittingItemGroup].items[slot]] == -1 )
				{
					bg_outfittings[uiInfo.outfittingItemGroup].items[slot] = 0;
				}

				if ( bg_itemlist[bg_outfittingGroups[slot][bg_outfittings[uiInfo.outfittingItemGroup].items[slot]]].giType != IT_WEAPON )
				{
					break;
				}

				weapon = bg_itemlist[bg_outfittingGroups[slot][bg_outfittings[uiInfo.outfittingItemGroup].items[slot]]].giTag;
			}
			while ( !BG_IsWeaponAvailableForOutfitting ( weapon, 2 ) );

			return qtrue;

		case K_MOUSE2:
		case K_SPACE:
		case K_LEFTARROW:

			// Nothing to cycle?
			if ( bg_outfittings[uiInfo.outfittingItemGroup].items[slot] == -1 )
			{
				break;
			}

			do
			{
				if ( uiInfo.outfittingItemGroup != 0 )
				{
					memcpy ( &bg_outfittings[0].items, &bg_outfittings[uiInfo.outfittingItemGroup].items, sizeof(bg_outfittings[0].items) );
					Menu_SetFeederSelection(NULL, FEEDER_OUTFITTING_TEMPLATES, 0, "ingame_outfitting");
				}

				bg_outfittings[uiInfo.outfittingItemGroup].items[slot]--;
				if ( bg_outfittings[uiInfo.outfittingItemGroup].items[slot] < 0 )
				{
					bg_outfittings[uiInfo.outfittingItemGroup].items[slot] = MAX_OUTFITTING_GROUPITEM-1;
					// Find the first valid one starting from the back
					while ( bg_outfittingGroups[slot][bg_outfittings[uiInfo.outfittingItemGroup].items[slot]] == -1 )
					{
						bg_outfittings[uiInfo.outfittingItemGroup].items[slot]--;
					}
				}

				if ( bg_itemlist[bg_outfittingGroups[slot][bg_outfittings[uiInfo.outfittingItemGroup].items[slot]]].giType != IT_WEAPON )
				{
					break;
				}

				weapon = bg_itemlist[bg_outfittingGroups[slot][bg_outfittings[uiInfo.outfittingItemGroup].items[slot]]].giTag;
			}
			while ( !BG_IsWeaponAvailableForOutfitting ( weapon, 2 ) );

			return qtrue;
	}

	return qfalse;
}

static qboolean UI_SelectedPlayer_HandleKey(int flags, float *special, int key) {
  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
		int selected;

		UI_BuildPlayerList();
		if (!uiInfo.teamLeader) {
			return qfalse;
		}
		selected = trap_Cvar_VariableValue("cg_selectedPlayer");
		
		if (key == K_MOUSE2) {
			selected--;
		} else {
			selected++;
		}

		if (selected > uiInfo.myTeamCount) {
			selected = 0;
		} else if (selected < 0) {
			selected = uiInfo.myTeamCount;
		}

		if (selected == uiInfo.myTeamCount) {
		 	trap_Cvar_Set( "cg_selectedPlayerName", "Everyone");
		} else {
		 	trap_Cvar_Set( "cg_selectedPlayerName", uiInfo.teamNames[selected]);
		}
	 	trap_Cvar_Set( "cg_selectedPlayer", va("%d", selected));
	}
	return qfalse;
}


static qboolean UI_OwnerDrawHandleKey ( int ownerDraw, int flags, float *special, int key, const char* param ) 
{
	switch (ownerDraw) 
	{
		case UI_GAMETYPE:
			return UI_GameType_HandleKey(flags, special, key, qtrue);

		case UI_NETGAMETYPE:
			return UI_NetGameType_HandleKey(flags, special, key);

		case UI_JOINGAMETYPE:
			return UI_JoinGameType_HandleKey(flags, special, key);

		case UI_SKILL:
			return UI_Skill_HandleKey(flags, special, key);

		case UI_NETSOURCE:
			UI_NetSource_HandleKey(flags, special, key);
			break;
		
		case UI_NETFILTER:
			UI_NetFilter_HandleKey(flags, special, key);
			break;
		
	
		case UI_BOTNAME:
			return UI_BotName_HandleKey(flags, special, key);

		case UI_BOTSKILL:
			return UI_BotSkill_HandleKey(flags, special, key);

		case UI_REDBLUE:
			UI_RedBlue_HandleKey(flags, special, key);
			break;

		case UI_CROSSHAIR:
			UI_Crosshair_HandleKey(flags, special, key);
			break;

		case UI_SELECTEDPLAYER:
			UI_SelectedPlayer_HandleKey(flags, special, key);
			break;
		
		case UI_OUTFITTING_SLOT:
			UI_OutfittingSlot_HandleKey ( atoi(param), key );
			break;

		default:
			break;
	}

	return qfalse;
}


static float UI_GetValue(int ownerDraw) {
  return 0;
}

/*
=================
UI_ServersQsortCompare
=================
*/
static int QDECL UI_ServersQsortCompare( const void *arg1, const void *arg2 ) {
	return trap_LAN_CompareServers( ui_netSource.integer, uiInfo.serverStatus.sortKey, uiInfo.serverStatus.sortDir, *(int*)arg1, *(int*)arg2);
}


/*
=================
UI_ServersSort
=================
*/
void UI_ServersSort(int column, qboolean force) {

	if ( !force ) {
		if ( uiInfo.serverStatus.sortKey == column ) {
			return;
		}
	}

	uiInfo.serverStatus.sortKey = column;
	qsort( &uiInfo.serverStatus.displayServers[0], uiInfo.serverStatus.numDisplayServers, sizeof(int), UI_ServersQsortCompare);
}
/*
===============
UI_LoadMods
===============
*/
static void UI_LoadMods() {
	int		numdirs;
	char	dirlist[2048];
	char	*dirptr;
  char  *descptr;
	int		i;
	int		dirlen;

	uiInfo.modCount = 0;
	numdirs = trap_FS_GetFileList( "$modlist", "", dirlist, sizeof(dirlist) );
	dirptr  = dirlist;
	for( i = 0; i < numdirs; i++ ) {
		dirlen = strlen( dirptr ) + 1;
    descptr = dirptr + dirlen;
		uiInfo.modList[uiInfo.modCount].modName = String_Alloc(dirptr);
		uiInfo.modList[uiInfo.modCount].modDescr = String_Alloc(descptr);
    dirptr += dirlen + strlen(descptr) + 1;
		uiInfo.modCount++;
		if (uiInfo.modCount >= MAX_MODS) {
			break;
		}
	}

}

/*
===============
UI_LoadMovies
===============
*/
static void UI_LoadMovies() {
	char	movielist[4096];
	char	*moviename;
	int		i, len;

	uiInfo.movieCount = trap_FS_GetFileList( "video", "roq", movielist, 4096 );

	if (uiInfo.movieCount) {
		if (uiInfo.movieCount > MAX_MOVIES) {
			uiInfo.movieCount = MAX_MOVIES;
		}
		moviename = movielist;
		for ( i = 0; i < uiInfo.movieCount; i++ ) {
			len = strlen( moviename );
			if (!Q_stricmp(moviename +  len - 4,".roq")) {
				moviename[len-4] = '\0';
			}
			Q_strupr(moviename);
			uiInfo.movieList[i] = String_Alloc(moviename);
			moviename += len + 1;
		}
	}

}

/*
===============
UI_LoadDemos
===============
*/
static void UI_LoadDemos() 
{
	char	demolist[4096];
	char demoExt[32];
	char	*demoname;
	int		i, len;

	Com_sprintf(demoExt, sizeof(demoExt), "dm_%d", (int)trap_Cvar_VariableValue("protocol"));

	uiInfo.demoCount = trap_FS_GetFileList( "demos", demoExt, demolist, 4096 );

	Com_sprintf(demoExt, sizeof(demoExt), ".dm_%d", (int)trap_Cvar_VariableValue("protocol"));

	if (uiInfo.demoCount) {
		if (uiInfo.demoCount > MAX_DEMOS) {
			uiInfo.demoCount = MAX_DEMOS;
		}
		demoname = demolist;
		for ( i = 0; i < uiInfo.demoCount; i++ ) {
			len = strlen( demoname );
			if (!Q_stricmp(demoname +  len - strlen(demoExt), demoExt)) {
				demoname[len-strlen(demoExt)] = '\0';
			}
			Q_strupr(demoname);
			uiInfo.demoList[i] = String_Alloc(demoname);
			demoname += len + 1;
		}
	}

}


static qboolean UI_SetNextMap(int actual, int index) {
	int i;
	for (i = actual + 1; i < uiInfo.mapCount; i++) {
		if (uiInfo.mapList[i].active) {
			Menu_SetFeederSelection(NULL, FEEDER_MAPS, index + 1, "skirmish");
			return qtrue;
		}
	}
	return qfalse;
}

static void UI_Update ( const char *name ) 
{
	int	val = trap_Cvar_VariableValue(name);

 	if (Q_stricmp(name, "ui_SetName") == 0) 
	{
		trap_Cvar_Set( "name", UI_Cvar_VariableString("ui_Name"));
 	} 
	else if (Q_stricmp(name, "ui_setRate") == 0) 
	{
		float rate = trap_Cvar_VariableValue("rate");
		if (rate >= 5000) 
		{
			trap_Cvar_Set("cl_maxpackets", "30");
			trap_Cvar_Set("cl_packetdup", "1");
		} 
		else if (rate >= 4000) 
		{
			trap_Cvar_Set("cl_maxpackets", "15");
			trap_Cvar_Set("cl_packetdup", "2");		// favor less prediction errors when there's packet loss
		} 
		else 
		{
			trap_Cvar_Set("cl_maxpackets", "15");
			trap_Cvar_Set("cl_packetdup", "1");		// favor lower bandwidth
		}
 	} 
	else if (Q_stricmp(name, "ui_GetName") == 0) 
	{
		trap_Cvar_Set( "ui_Name", UI_Cvar_VariableString("name"));
 	} 
	else if (Q_stricmp(name, "r_colorbits") == 0) 
	{
		switch (val) 
		{
			case 0:
				trap_Cvar_SetValue( "r_depthbits", 0 );
				trap_Cvar_SetValue( "r_stencilbits", 0 );
			break;
			case 16:
				trap_Cvar_SetValue( "r_depthbits", 16 );
				trap_Cvar_SetValue( "r_stencilbits", 0 );
			break;
			case 32:
				trap_Cvar_SetValue( "r_depthbits", 24 );
			break;
		}
	} 
	else if (Q_stricmp(name, "r_lodbias") == 0) 
	{
		switch (val) 
		{
			case 0:
				trap_Cvar_SetValue( "r_subdivisions", 4 );
				break;
			
			case 1:
				trap_Cvar_SetValue( "r_subdivisions", 12 );
				break;
			
			case 2:
				trap_Cvar_SetValue( "r_subdivisions", 20 );
				break;
		}
	} 
	else if (Q_stricmp(name, "ui_glCustom") == 0) 
	{
		switch (val) 
		{
			case 0:	// high quality
				trap_Cvar_SetValue( "r_fullScreen", 1 );
				trap_Cvar_SetValue( "r_subdivisions", 4 );
				trap_Cvar_SetValue( "r_lodbias", 0 );
				trap_Cvar_SetValue( "r_colorbits", 32 );
				trap_Cvar_SetValue( "r_depthbits", 24 );
				trap_Cvar_SetValue( "r_picmip", 1 );
				trap_Cvar_SetValue( "r_mode", 4 );
				trap_Cvar_SetValue( "r_texturebits", 32 );
				trap_Cvar_SetValue( "r_fastSky", 0 );
				trap_Cvar_SetValue( "r_inGameVideo", 1 );
				trap_Cvar_SetValue( "cg_shadows", 1 );
				trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" );
				break;

			case 1: // normal 
				trap_Cvar_SetValue( "r_fullScreen", 1 );
				trap_Cvar_SetValue( "r_subdivisions", 12 );
				trap_Cvar_SetValue( "r_lodbias", 0 );
				trap_Cvar_SetValue( "r_colorbits", 0 );
				trap_Cvar_SetValue( "r_depthbits", 24 );
				trap_Cvar_SetValue( "r_picmip", 2 );
				trap_Cvar_SetValue( "r_mode", 3 );
				trap_Cvar_SetValue( "r_texturebits", 0 );
				trap_Cvar_SetValue( "r_fastSky", 0 );
				trap_Cvar_SetValue( "r_inGameVideo", 1 );
				trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" );
				trap_Cvar_SetValue( "cg_shadows", 0 );
				break;

			case 2: // fast
				trap_Cvar_SetValue( "r_fullScreen", 1 );
				trap_Cvar_SetValue( "r_subdivisions", 8 );
				trap_Cvar_SetValue( "r_lodbias", 1 );
				trap_Cvar_SetValue( "r_colorbits", 0 );
				trap_Cvar_SetValue( "r_depthbits", 0 );
				trap_Cvar_SetValue( "r_picmip", 3 );
				trap_Cvar_SetValue( "r_mode", 3 );
				trap_Cvar_SetValue( "r_texturebits", 0 );
				trap_Cvar_SetValue( "cg_shadows", 0 );
				trap_Cvar_SetValue( "r_fastSky", 1 );
				trap_Cvar_SetValue( "r_inGameVideo", 0 );
				trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" );
				break;

			case 3: // fastest
				trap_Cvar_SetValue( "r_fullScreen", 1 );
				trap_Cvar_SetValue( "r_subdivisions", 20 );
				trap_Cvar_SetValue( "r_lodbias", 2 );
				trap_Cvar_SetValue( "r_colorbits", 16 );
				trap_Cvar_SetValue( "r_depthbits", 16 );
				trap_Cvar_SetValue( "r_mode", 3 );
				trap_Cvar_SetValue( "r_picmip", 3 );
				trap_Cvar_SetValue( "r_texturebits", 16 );
				trap_Cvar_SetValue( "cg_shadows", 0 );
				trap_Cvar_SetValue( "r_fastSky", 1 );
				trap_Cvar_SetValue( "r_inGameVideo", 0 );
				trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" );
				break;
		}
	} 
	else if (Q_stricmp(name, "ui_mousePitch") == 0) 
	{
		if (val == 0) 
		{
			trap_Cvar_SetValue( "m_pitch", 0.022f );
		} 
		else 
		{
			trap_Cvar_SetValue( "m_pitch", -0.022f );
		}
	}
}

/*
===============
UI_VerifyNetwork

Verifies that there is a network connection and if not dumps a com_error
===============
*/
qboolean UI_VerifyNetwork ( void )
{
	if ( ui_noNetCheck.integer )
	{
		return qtrue;
	}

	if ( !trap_NET_Available ( ) )
	{
		if ( !(int)trap_Cvar_VariableValue ( "com_ignoreverifynetwork" ) )
		{
			Menus_ActivateByName("verifynetwork_popmenu");
			return qfalse;
		}
	}

	return qtrue;
}

/*
===============
UI_RunMenuScript

Runs scripts from the menu
===============
*/
static void UI_RunMenuScript(const char **args) 
{
	const char *name, *name2;
	char buff[1024];

	if (String_Parse(args, &name)) 
	{		
		if ( Q_stricmp ( name, "LoadOutfittings" ) == 0 )
		{
			BG_SetAvailableOutfitting ( UI_Cvar_VariableString ( "ui_info_availableweapons" ) );
			
			BG_ParseOutfittingTemplates ( qfalse );
		}
		else if ( Q_stricmp ( name, "DecompressOutfitting" ) == 0 )
		{
			int				index;
			goutfitting_t	outfitting;

			BG_DecompressOutfitting ( UI_Cvar_VariableString ( "outfitting" ), &outfitting );

			index = BG_FindOutfitting ( &outfitting );
			if ( index < 0 )
			{
				memcpy ( &bg_outfittings[0].items[0], &outfitting.items[0], sizeof(outfitting.items) );
				Menu_SetFeederSelection(NULL, FEEDER_OUTFITTING_TEMPLATES, 0, "main");
			}
			else
			{
				Menu_SetFeederSelection(NULL, FEEDER_OUTFITTING_TEMPLATES, index, "main");
			}
		}
		else if ( Q_stricmp ( name, "CompressOutfitting" ) == 0 )
		{
			char compressed[OUTFITTING_GROUP_MAX + 1];
			BG_CompressOutfitting ( &bg_outfittings[uiInfo.outfittingItemGroup], compressed, OUTFITTING_GROUP_MAX );
			trap_Cvar_Set ( "outfitting", compressed );
		}
		// Verify that the user is connected and if not through a com_error
		else if ( Q_stricmp ( name, "VerifyNet" ) == 0 )
		{
			UI_VerifyNetwork ( );
		}
		else if ( Q_stricmp ( name, "autoTeamJoin" ) == 0 )
		{
			qboolean joinRed = qfalse;

			if ( !String_Parse(args, &name) || !name )
			{
				return;
			}

			if ( !String_Parse(args, &name2) || !name2 )
			{
				return;
			}

			// If the teams have the same number of players then join the 
			// team that has less points
			if ( ui_info_bluecount.integer == ui_info_redcount.integer )
			{
				// If the blue team has more points then join red
				if ( ui_info_bluescore.integer > ui_info_redscore.integer )
				{
					joinRed = qtrue;
				}
			}
			// If the blue team has more players then join red
			else if ( ui_info_bluecount.integer > ui_info_redcount.integer )
			{
				joinRed = qtrue;
			}
					
			if ( joinRed )
			{
				Menus_OpenByName ( name );
			}
			else 
			{
				Menus_OpenByName ( name2 );
			}
		}
		else if ( Q_stricmp ( name, "JoinTeam" ) == 0 )
		{
			int		index;
			team_t	team;

			if ( !String_Parse(args, &name) || !name )
			{
				return;
			}

			if ( !Int_Parse ( args, &index ) )
			{
				return;
			}

			if ( Q_stricmp ( name, "red" ) == 0 )
			{
				team = TEAM_RED;
			}
			else if ( Q_stricmp ( name, "blue" ) == 0 )
			{
				team = TEAM_BLUE;
			}
			else
			{	
				return;
			}

			if ( index < 0 || index > 4 )
			{
				index = 0;
			}

			if ( !uiInfo.identityTeams[team][index] )
			{
				return;
			}

			trap_Cmd_ExecuteText( EXEC_APPEND, va("cmd team %s %s\n", name, uiInfo.identityTeams[team][index]->mName ));
		}
		else if ( Q_stricmp ( name, "UpdateModel" ) == 0 )
		{
			updateModel = qtrue;
		}
		else if ( Q_stricmp ( name, "UpdateParental" ) == 0 )
		{
			char password[MAX_QPATH];
			trap_Parental_GetPassword ( password, MAX_QPATH );

			// As long as the password is correct, let them by
			if ( Q_stricmp ( password, uiInfo.parentalPassword ) == 0 )
			{
				trap_Parental_Update ( );
			}
		}
		else if ( Q_stricmp ( name, "OpenParentalPasswordMenu" ) == 0 )
		{
				trap_Cvar_Set ( "ui_lock_password2", "" );				
		}
		else if ( Q_stricmp ( name, "UpdateParentalPassword" ) == 0 )
		{
			char password[MAX_QPATH],password2[MAX_QPATH];

			trap_Cvar_VariableStringBuffer ( "ui_lock_password", password, MAX_QPATH );
			trap_Cvar_VariableStringBuffer ( "ui_lock_password2", password2, MAX_QPATH );

			if ( !Q_stricmp ( password, password2  ) )
			{
				trap_Cvar_VariableStringBuffer ( "ui_lock_password", uiInfo.parentalPassword , MAX_QPATH );
				trap_Parental_SetPassword ( uiInfo.parentalPassword );	

				trap_Cvar_Set ( "ui_lock_password", "" );

				Menus_CloseByName("violence_setpassword_popmenu");
			}
			else
			{
				trap_Cvar_Set ( "setpassword_error", "Password and Re-Enter Password must match." );
			}
		}
		else if (Q_stricmp(name,"violence_options") == 0 ) 
		{
			char password[MAX_QPATH];
			trap_Parental_GetPassword ( password, MAX_QPATH );
			trap_Cvar_VariableStringBuffer ( "ui_lock_password", uiInfo.parentalPassword, MAX_QPATH );

			if ( !Q_stricmp ( password, uiInfo.parentalPassword  ) )
			{
				Menus_CloseByName ( "violence_menu" );
				Menus_OpenByName ( "violence_options_menu" );
				trap_Cvar_Set ( "parental_password_error", " " );
			}
			else
			{
				trap_Cvar_Set ( "parental_password_error", "Incorrect password." );
			}
			trap_Cvar_Set ( "ui_lock_password", "" );
		} 
		else if (Q_stricmp(name, "SetConnectPassword") == 0)
		{
			char password[MAX_QPATH];
			uiInfo.connectPasswordRequest = qfalse;
			trap_Cvar_VariableStringBuffer ( "ui_connect_password", password, MAX_QPATH );
			trap_Cvar_Set ( "password", password );
			trap_Cvar_Set ( "ui_connect_password", "" );
		}
		else if (Q_stricmp(name, "StartServer") == 0) 
		{
#ifdef _SOF2_BOTS
			int		clients;
			int		oldclients;
			float	skill;
#endif

			trap_Cvar_Set("cg_thirdPerson", "0");
			trap_Cvar_Set("cg_cameraOrbit", "0");
			trap_Cvar_SetValue( "dedicated", Com_Clamp( 0, 2, ui_dedicated.integer ) );
			trap_Cvar_Set ( "g_gametype", bg_gametypeData[ui_netGameType.integer].name );

			// Must be connected
			if ( ui_dedicated.integer > 0 )
			{
				if ( !UI_VerifyNetwork ( ) )
				{
					return;
				}
			}

			// Handle randomly generated maps specially
			if ( !Q_stricmp ( uiInfo.mapList[ui_currentNetMap.integer].mapLoadName, "*random" ) )
			{
				char	rmgConfig[MAX_QPATH];
				char	rmgSize[MAX_QPATH];
				char	rmgTime[MAX_QPATH];
				char	rmgSeed[MAX_QPATH];


				trap_Cvar_VariableStringBuffer ( "ui_rmg_size", rmgSize, MAX_QPATH );
				trap_Cvar_VariableStringBuffer ( "ui_rmg_config", rmgConfig, MAX_QPATH );
				trap_Cvar_VariableStringBuffer ( "ui_rmg_time", rmgTime, MAX_QPATH );
				trap_Cvar_VariableStringBuffer ( "ui_rmg_seed", rmgSeed, MAX_QPATH );

				trap_Cmd_ExecuteText ( EXEC_APPEND, va( "wait ; wait ; rmgmap 1 \"%s\" 2 \"%s\" 3 \"%s\" 4 \"%s\" 0\n", 
					rmgSize, rmgConfig, rmgTime, rmgSeed  ) );
			}
			else
			{
				trap_Cmd_ExecuteText ( EXEC_APPEND, va( "wait ; wait ; map %s\n", uiInfo.mapList[ui_currentNetMap.integer].mapLoadName ) );
			}
#ifdef _SOF2_BOTS

			skill = trap_Cvar_VariableValue( "g_botSkill" );	
	
			// set max clients based on spots
			oldclients = trap_Cvar_VariableValue( "sv_maxClients" );
			clients    = 0;

			if (clients == 0) 
			{
				clients = 8;
			}
			
			if (oldclients > clients) 
			{
				clients = oldclients;
			}

			trap_Cvar_Set("sv_maxClients", va("%d",clients));
#endif
		} 
		else if (Q_stricmp(name, "resetDefaults") == 0) 
		{
			trap_Cmd_ExecuteText( EXEC_APPEND, "exec sof2mp_default.cfg\n");
			trap_Cmd_ExecuteText( EXEC_APPEND, "cvar_restart\n");
			Controls_SetDefaults();
			trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart\n" );
		} 
		else if (Q_stricmp(name, "getCDKey") == 0) {
			char out[17];
			trap_GetCDKey(buff, 17);
			trap_Cvar_Set("cdkey1", "");
			trap_Cvar_Set("cdkey2", "");
			trap_Cvar_Set("cdkey3", "");
			trap_Cvar_Set("cdkey4", "");
			trap_Cvar_Set("cdkeychecksum", "");
			trap_Cvar_Set("cdkey_error", "");
			
			if (strlen(buff) == CDKEY_LEN) 
			{
				Q_strncpyz(out, buff, 5);
				trap_Cvar_Set("cdkey1", out);
				Q_strncpyz(out, buff + 4, 5);
				trap_Cvar_Set("cdkey2", out);
				Q_strncpyz(out, buff + 8, 5);
				trap_Cvar_Set("cdkey3", out);
				Q_strncpyz(out, buff + 12, 5);
				trap_Cvar_Set("cdkey4", out);
				Q_strncpyz(out, buff + 16, 2);
				trap_Cvar_Set("cdkeychecksum", out);
			}

		} else if (Q_stricmp(name, "verifyCDKey") == 0) {
			buff[0] = '\0';
			Q_strcat(buff, 1024, UI_Cvar_VariableString("cdkey1")); 
			Q_strcat(buff, 1024, UI_Cvar_VariableString("cdkey2")); 
			Q_strcat(buff, 1024, UI_Cvar_VariableString("cdkey3")); 
			Q_strcat(buff, 1024, UI_Cvar_VariableString("cdkey4")); 
			Q_strcat(buff, 1024, UI_Cvar_VariableString("cdkeychecksum")); 
			trap_Cvar_Set("cdkey", buff);

			if (trap_VerifyCDKey(buff)) 
			{
				trap_Cvar_Set("cdkey_error", "Valid CD Key.");
				trap_SetCDKey(buff);
				Menus_CloseByName ( "cdkey_popmenu" );
			} 
			else 
			{
				trap_Cvar_Set("cdkey_error", "Invalid CD Key.");
			}			
		} 
		else if (Q_stricmp(name, "loadArenas") == 0) 
		{
			int actual;

			UI_LoadArenas();
			UI_MapCountByGameType();
			Menu_SetFeederSelection(NULL, FEEDER_ALLMAPS, 0, "createserver_menu");

			trap_Cvar_Set("ui_mapIndex", "0");
			ui_mapIndex.integer = 0;
			UI_SelectedMap ( 0, &actual );
			ui_currentNetMap.integer = actual;
			trap_Cvar_Set("ui_currentNetMap", va("%d",actual));
			trap_Cvar_Set("ui_currentNetMapName", uiInfo.mapList[actual].mapLoadName );
		} 
		else if (Q_stricmp(name, "loadVoteArenas") == 0) 
		{
			int actual;

			UI_LoadArenas();
			UI_MapCountForVote();
			Menu_SetFeederSelection(NULL, FEEDER_VOTEMAPS, 0, "ingame_callvote");

			trap_Cvar_Set("ui_mapIndex", "0");
			ui_mapIndex.integer = 0;
			UI_SelectedMap ( 0, &actual );
			ui_currentNetMap.integer = actual;
			trap_Cvar_Set("ui_currentNetMap", va("%d",actual));
			trap_Cvar_Set("ui_currentNetMapName", uiInfo.mapList[actual].mapLoadName );
		} 
		else if (Q_stricmp(name, "saveControls") == 0) {
			Controls_SetConfig(qtrue);
		} else if (Q_stricmp(name, "loadControls") == 0) {
			Controls_GetConfig();
		} else if (Q_stricmp(name, "clearError") == 0) {
			trap_Cvar_Set("com_errorMessage", "");
		} 
		else if (Q_stricmp(name, "loadGameInfo") == 0) 
		{
			BG_BuildGametypeList ( );				
		} else if (Q_stricmp(name, "RefreshServers") == 0) {
			UI_StartServerRefresh(qtrue);
			UI_BuildServerDisplayList(qtrue);
		} else if (Q_stricmp(name, "RefreshFilter") == 0) {
			UI_StartServerRefresh(qfalse);
			UI_BuildServerDisplayList(qtrue);
		} else if (Q_stricmp(name, "RunSPDemo") == 0) {
			if (uiInfo.demoAvailable) {
			  trap_Cmd_ExecuteText( EXEC_APPEND, va("demo %s_%s\n", uiInfo.mapList[ui_currentMap.integer].mapLoadName, bg_gametypeData[ui_gameType.integer].name));
			}
		} else if (Q_stricmp(name, "LoadDemos") == 0) {
			UI_LoadDemos();
		} else if (Q_stricmp(name, "LoadMovies") == 0) {
			UI_LoadMovies();
		} else if (Q_stricmp(name, "LoadMods") == 0) {
			UI_LoadMods();
		} else if (Q_stricmp(name, "playMovie") == 0) {
			if (uiInfo.previewMovie >= 0) {
			  trap_CIN_StopCinematic(uiInfo.previewMovie);
			}
			trap_Cmd_ExecuteText( EXEC_APPEND, va("cinematic %s.roq 2\n", uiInfo.movieList[uiInfo.movieIndex]));
		} else if (Q_stricmp(name, "RunMod") == 0) {
			trap_Cvar_Set( "fs_game", uiInfo.modList[uiInfo.modIndex].modName);
			trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart;" );
		} else if (Q_stricmp(name, "RunDemo") == 0) {
			trap_Cmd_ExecuteText( EXEC_APPEND, va("demo \"%s\"\n", uiInfo.demoList[uiInfo.demoIndex]));
		} else if (Q_stricmp(name, "Quake3") == 0) {
			trap_Cvar_Set( "fs_game", "");
			trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart;" );
		} else if (Q_stricmp(name, "closeJoin") == 0) {
			if (uiInfo.serverStatus.refreshActive) {
				UI_StopServerRefresh();
				uiInfo.serverStatus.nextDisplayRefresh = 0;
				uiInfo.nextServerStatusRefresh = 0;
				uiInfo.nextFindPlayerRefresh = 0;
				UI_BuildServerDisplayList(qtrue);
			} else {
				Menus_CloseByName("joinserver");
				Menus_OpenByName("main");
			}
		} else if (Q_stricmp(name, "StopRefresh") == 0) {
			UI_StopServerRefresh();
			uiInfo.serverStatus.nextDisplayRefresh = 0;
			uiInfo.nextServerStatusRefresh = 0;
			uiInfo.nextFindPlayerRefresh = 0;
		} else if (Q_stricmp(name, "UpdateFilter") == 0) {
			if (ui_netSource.integer == AS_LOCAL) {
				UI_StartServerRefresh(qtrue);
			}
			UI_BuildServerDisplayList(qtrue);
			UI_FeederSelection(FEEDER_SERVERS, 0);
		} else if (Q_stricmp(name, "ServerStatus") == 0) {
			trap_LAN_GetServerAddressString(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], uiInfo.serverStatusAddress, sizeof(uiInfo.serverStatusAddress));
			UI_BuildServerStatus(qtrue);
		} else if (Q_stricmp(name, "FoundPlayerServerStatus") == 0) {
			Q_strncpyz(uiInfo.serverStatusAddress, uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer], sizeof(uiInfo.serverStatusAddress));
			UI_BuildServerStatus(qtrue);
			Menu_SetFeederSelection(NULL, FEEDER_FINDPLAYER, 0, NULL);
		} else if (Q_stricmp(name, "FindPlayer") == 0) {
			UI_BuildFindPlayerList(qtrue);
			// clear the displayed server status info
			uiInfo.serverStatusInfo.numLines = 0;
			Menu_SetFeederSelection(NULL, FEEDER_FINDPLAYER, 0, NULL);
		} else if (Q_stricmp(name, "JoinServer") == 0) {
			trap_Cvar_Set("cg_thirdPerson", "0");
			trap_Cvar_Set("cg_cameraOrbit", "0");
			if (uiInfo.serverStatus.currentServer >= 0 && uiInfo.serverStatus.currentServer < uiInfo.serverStatus.numDisplayServers) {
				trap_LAN_GetServerAddressString(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, 1024);
				trap_Cmd_ExecuteText( EXEC_APPEND, va( "connect %s\n", buff ) );
			}
		} else if (Q_stricmp(name, "FoundPlayerJoinServer") == 0) {
			if (uiInfo.currentFoundPlayerServer >= 0 && uiInfo.currentFoundPlayerServer < uiInfo.numFoundPlayerServers) {
				trap_Cmd_ExecuteText( EXEC_APPEND, va( "connect %s\n", uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer] ) );
			}
		} 
		else if (Q_stricmp(name, "Quit") == 0) {
			trap_Cmd_ExecuteText( EXEC_NOW, "quit");
		} else if (Q_stricmp(name, "Controls") == 0) {
		  trap_Cvar_Set( "cl_paused", "1" );
			trap_Key_SetCatcher( KEYCATCH_UI );
			Menus_CloseAll();
			Menus_ActivateByName("setup_menu2");
		} else if (Q_stricmp(name, "Leave") == 0) 
		{
			uiInfo.connectPasswordRequest = qfalse;
			trap_Cmd_ExecuteText( EXEC_APPEND, "disconnect\n" );
			trap_Key_SetCatcher( KEYCATCH_UI );
			Menus_CloseAll();
			Menus_ActivateByName("main");
		} else if (Q_stricmp(name, "ServerSort") == 0) {
			int sortColumn;
			if (Int_Parse(args, &sortColumn)) {
				// if same column we're already sorting on then flip the direction
				if (sortColumn == uiInfo.serverStatus.sortKey) {
					uiInfo.serverStatus.sortDir = !uiInfo.serverStatus.sortDir;
				}
				// make sure we sort again
				UI_ServersSort(sortColumn, qtrue);
			}
		} else if (Q_stricmp(name, "closeingame") == 0) {
			trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
			trap_Key_ClearStates();
			trap_Cvar_Set( "cl_paused", "0" );
			Menus_CloseAll();
		} 
		else if (Q_stricmp(name, "voteMap") == 0) 
		{
			if (ui_currentNetMap.integer >=0 && ui_currentNetMap.integer < uiInfo.mapCount) 
			{
				trap_Cmd_ExecuteText( EXEC_APPEND, va("callvote map %s\n",uiInfo.mapList[ui_currentNetMap.integer].mapLoadName) );
			}
		} 
		else if (Q_stricmp(name, "voteKick") == 0) 
		{
			if (uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount) 
			{
				char clientnum[3];
				
				// The client number is hidden in front of the string
				strncpy ( clientnum, uiInfo.playerNames[uiInfo.playerIndex], 2 );
				clientnum[2] = 0;

				trap_Cmd_ExecuteText( EXEC_APPEND, va("callvote clientkick \"%s\"\n",clientnum) );
			}
		} 
		else if (Q_stricmp(name, "voteGame") == 0) 
		{
			if (ui_netGameType.integer >= 0 && ui_netGameType.integer < bg_gametypeCount ) 
			{
				trap_Cmd_ExecuteText( EXEC_APPEND, va("callvote g_gametype %s\n",bg_gametypeData[ui_netGameType.integer].name) );
			}
		}
		else if ( Q_stricmp ( name, "voteRMG" ) == 0 )
		{
			char	rmgConfig[MAX_QPATH];
			char	rmgSize[MAX_QPATH];
			char	rmgTime[MAX_QPATH];
			char	rmgSeed[MAX_QPATH];

			trap_Cvar_VariableStringBuffer ( "ui_rmg_size", rmgSize, MAX_QPATH );
			trap_Cvar_VariableStringBuffer ( "ui_rmg_config", rmgConfig, MAX_QPATH );
			trap_Cvar_VariableStringBuffer ( "ui_rmg_time", rmgTime, MAX_QPATH );
			trap_Cvar_VariableStringBuffer ( "ui_rmg_seed", rmgSeed, MAX_QPATH );

			trap_Cmd_ExecuteText ( EXEC_APPEND, va( "callvote rmgmap \"%s\" \"%s\" \"%s\" \"%s\"\n", 
								   rmgSize, rmgConfig, rmgTime, rmgSeed  ) );			
		} 
		else if (Q_stricmp(name, "loadNewVersionFromBrowser") == 0) 
		{
			trap_Version_Download(VD_BROWSER_DOWNLOAD);
		} 
		else if (Q_stricmp(name, "ignoreCurrentVersion") == 0) 
		{
			Menus_CloseByName("invalid_version_ignore_menu");
			Menus_CloseByName("invalid_version_menu");
			trap_Version_Download(VD_IGNORE);
		}
		else if (Q_stricmp(name, "loadNewVersion") == 0) 
		{
			Menus_ActivateByName("patch_download");
			trap_Version_Download(uiInfo.versionIndex);
		} 
		else if (Q_stricmp(name, "cancelDownload") == 0) 
		{
			trap_Version_Download(VD_CANCEL);
		}
		else if (Q_stricmp(name, "addBot") == 0) 
		{
			trap_Cmd_ExecuteText( EXEC_APPEND, va("addbot %s %i %s\n", UI_GetBotNameByNumber(uiInfo.botIndex), uiInfo.skillIndex+1, ui_botteam.string ) );
		} 
		else if (Q_stricmp(name, "addFavorite") == 0) 
		{
			if (ui_netSource.integer != AS_FAVORITES) {
				char name[MAX_NAME_LENGTH];
				char addr[MAX_NAME_LENGTH];
				int res;

				trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, MAX_STRING_CHARS);
				name[0] = addr[0] = '\0';
				Q_strncpyz(name, 	Info_ValueForKey(buff, "hostname"), MAX_NAME_LENGTH);
				Q_strncpyz(addr, 	Info_ValueForKey(buff, "addr"), MAX_NAME_LENGTH);
				if (strlen(name) > 0 && strlen(addr) > 0) {
					res = trap_LAN_AddServer(AS_FAVORITES, name, addr);
					if (res == 0) {
						// server already in the list
						Com_Printf("Favorite already in list\n");
					}
					else if (res == -1) {
						// list full
						Com_Printf("Favorite list full\n");
					}
					else {
						// successfully added
						Com_Printf("Added favorite server %s\n", addr);
					}
				}
			}
		} 
		else if (Q_stricmp(name, "deleteFavorite") == 0) 
		{
			if (ui_netSource.integer == AS_FAVORITES) 
			{
				char addr[MAX_NAME_LENGTH];

				trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, MAX_STRING_CHARS);
				addr[0] = '\0';
				Q_strncpyz(addr, 	Info_ValueForKey(buff, "addr"), MAX_NAME_LENGTH);
				if (strlen(addr) > 0) {
					trap_LAN_RemoveServer(AS_FAVORITES, addr);
				}

				UI_BuildServerDisplayList(qtrue);
			}
		} 
		else if (Q_stricmp(name, "CancelCreateFavorite") == 0) 
		{
			trap_Cvar_Set( "fave_ip_error", " " );				
			trap_Cvar_Set( "ui_favoriteAddress", " " );				
		}
		else if (Q_stricmp(name, "createFavorite") == 0) 
		{
			char	name[MAX_NAME_LENGTH];
			char	addr[MAX_NAME_LENGTH];
			int		res;

			name[0] = addr[0] = '\0';
			Q_strncpyz(name, 	UI_Cvar_VariableString("ui_favoriteAddress"), MAX_NAME_LENGTH);
			Q_strncpyz(addr, 	UI_Cvar_VariableString("ui_favoriteAddress"), MAX_NAME_LENGTH);

			if ( strlen(name) > 0 && strlen(addr) > 0) 
			{
				res = trap_LAN_AddServer(AS_FAVORITES, name, addr);
				if (res == 0) 
				{
					// server already in the list
					Com_Printf("Favorite already in list\n");
				}
				else if (res == -1) 
				{
					// list full
					Com_Printf("Favorite list full\n");
				}
				else if (res == -2) 
				{
					trap_Cvar_Set( "fave_ip_error", "Invalid address." );				
				}
				else 
				{
					// successfully added
					Com_Printf("Added favorite server %s\n", addr);
					trap_Cvar_Set( "fave_ip_error", " " );				
					trap_Cvar_Set( "ui_favoriteAddress", " " );				
					Menus_CloseByName("createfavorite_popmenu");
				}

				UI_BuildServerDisplayList(qtrue);
			}
		} 
		else if (Q_stricmp(name, "display_settings_store") == 0)
		{	
			trap_Cvar_SetValue( "ui_glCustom_store", trap_Cvar_VariableValue("ui_glCustom") );
			trap_Cvar_SetValue( "r_fullscreen_store",trap_Cvar_VariableValue("r_fullscreen") );
			trap_Cvar_SetValue( "r_allowExtensions_store", trap_Cvar_VariableValue("r_allowExtensions") );
			trap_Cvar_SetValue( "r_mode_store", trap_Cvar_VariableValue("r_mode") );
			trap_Cvar_SetValue( "r_colorbits_store",trap_Cvar_VariableValue("r_colorbits") );
			trap_Cvar_SetValue( "r_lodbias_store", trap_Cvar_VariableValue("r_lodbias") );
			trap_Cvar_SetValue( "r_picmip_store", trap_Cvar_VariableValue("r_picmip") );
			trap_Cvar_SetValue( "r_texturebits_store", trap_Cvar_VariableValue("r_texturebits") );
			trap_Cvar_SetValue( "r_texturemode_store", trap_Cvar_VariableValue("r_texturemode") );
			trap_Cvar_SetValue( "r_ext_compressed_textures_store", trap_Cvar_VariableValue("r_ext_compressed_textures") ); 
		}
		else if (Q_stricmp(name, "display_settings_recall") == 0)
		{
			trap_Cvar_SetValue( "ui_glCustom", trap_Cvar_VariableValue("ui_glCustom_store") );
			trap_Cvar_SetValue( "r_fullscreen",trap_Cvar_VariableValue("r_fullscreen_store") );
			trap_Cvar_SetValue( "r_allowExtensions", trap_Cvar_VariableValue("r_allowExtensions_store") );
			trap_Cvar_SetValue( "r_mode", trap_Cvar_VariableValue("r_mode_store") );
			trap_Cvar_SetValue( "r_colorbits",trap_Cvar_VariableValue("r_colorbits_store") );
			trap_Cvar_SetValue( "r_lodbias", trap_Cvar_VariableValue("r_lodbias_store") );
			trap_Cvar_SetValue( "r_picmip", trap_Cvar_VariableValue("r_picmip_store") );
			trap_Cvar_SetValue( "r_texturebits", trap_Cvar_VariableValue("r_texturebits_store") );
			trap_Cvar_SetValue( "r_texturemode", trap_Cvar_VariableValue("r_texturemode_store") );
			trap_Cvar_SetValue( "r_ext_compressed_textures", trap_Cvar_VariableValue("r_ext_compressed_textures_store") ); 
		}
		else if (Q_stricmp(name, "orders") == 0) {
			const char *orders;
			if (String_Parse(args, &orders)) {
				int selectedPlayer = trap_Cvar_VariableValue("cg_selectedPlayer");
				if (selectedPlayer < uiInfo.myTeamCount) {
					strcpy(buff, orders);
					trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, uiInfo.teamClientNums[selectedPlayer]) );
					trap_Cmd_ExecuteText( EXEC_APPEND, "\n" );
				} else {
					int i;
					for (i = 0; i < uiInfo.myTeamCount; i++) {
						if (Q_stricmp(UI_Cvar_VariableString("name"), uiInfo.teamNames[i]) == 0) {
							continue;
						}
						strcpy(buff, orders);
						trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, uiInfo.teamNames[i]) );
						trap_Cmd_ExecuteText( EXEC_APPEND, "\n" );
					}
				}
				trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
				trap_Key_ClearStates();
				trap_Cvar_Set( "cl_paused", "0" );
				Menus_CloseAll();
			}
		}
		else if (Q_stricmp(name, "voiceOrdersTeam") == 0) {
			const char *orders;
			if (String_Parse(args, &orders)) {
				int selectedPlayer = trap_Cvar_VariableValue("cg_selectedPlayer");
				if (selectedPlayer == uiInfo.myTeamCount) {
					trap_Cmd_ExecuteText( EXEC_APPEND, orders );
					trap_Cmd_ExecuteText( EXEC_APPEND, "\n" );
				}
				trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
				trap_Key_ClearStates();
				trap_Cvar_Set( "cl_paused", "0" );
				Menus_CloseAll();
			}
		} else if (Q_stricmp(name, "voiceOrders") == 0) {
			const char *orders;
			if (String_Parse(args, &orders)) {
				int selectedPlayer = trap_Cvar_VariableValue("cg_selectedPlayer");
				if (selectedPlayer < uiInfo.myTeamCount) {
					strcpy(buff, orders);
					trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, uiInfo.teamClientNums[selectedPlayer]) );
					trap_Cmd_ExecuteText( EXEC_APPEND, "\n" );
				}
				trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
				trap_Key_ClearStates();
				trap_Cvar_Set( "cl_paused", "0" );
				Menus_CloseAll();
			}
		}
		else if (Q_stricmp(name, "RMGRandomSettings") == 0) 
		{
			trap_Cvar_Set( "ui_rmg_config", "?" );
			trap_Cvar_Set( "ui_rmg_time", "?" );				
			trap_Cvar_Set( "ui_rmg_seed", "?" );				
		} else if (Q_stricmp(name, "glCustom") == 0) {
			trap_Cvar_Set("ui_glCustom", "4");
		} else if (Q_stricmp(name, "update") == 0) {
			if (String_Parse(args, &name2)) {
				UI_Update(name2);
		}
		else {
			Com_Printf("unknown UI script %s\n", name);
			}
		}
	}
}

static void UI_GetTeamColor(vec4_t *color) {
}

/*
==================
UI_MapCountByGameType

returns the number of available maps for the netgametype as well as
marks the active maps active
==================
*/
static int UI_MapCountByGameType( void ) 
{
	int i, c, game;
	c = 0;
	game = ui_netGameType.integer;

	for (i = 0; i < uiInfo.mapCount; i++) 
	{
		uiInfo.mapList[i].active = qfalse;
		if ( uiInfo.mapList[i].typeBits & (1 << game)) 
		{
			c++;
			uiInfo.mapList[i].active = qtrue;
		}
	}
	return c;
}

/*
==================
UI_MapCountForVote

returns the number of maps available for a vote
==================
*/
static int UI_MapCountForVote ( void )
{
	int i;
	int	c;

	c = 0;

	for (i = 0; i < uiInfo.mapCount; i++) 
	{
		uiInfo.mapList[i].active = qfalse;

		// The random map is prefaced by a *
		if ( uiInfo.mapList[i].mapLoadName[0] != '*' ) 
		{
			c++;
			uiInfo.mapList[i].active = qtrue;
		}
	}

	return c;
}

/*
==================
UI_InsertServerIntoDisplayList
==================
*/
static void UI_InsertServerIntoDisplayList(int num, int position) 
{
	int i;

	if (position < 0 || position > uiInfo.serverStatus.numDisplayServers ) 
	{
		return;
	}
	
	uiInfo.serverStatus.numDisplayServers++;
	for (i = uiInfo.serverStatus.numDisplayServers; i > position; i--) 
	{
		uiInfo.serverStatus.displayServers[i] = uiInfo.serverStatus.displayServers[i-1];
	}

	uiInfo.serverStatus.displayServers[position] = num;
}

/*
==================
UI_RemoveServerFromDisplayList
==================
*/
static void UI_RemoveServerFromDisplayList(int num) {
	int i, j;

	for (i = 0; i < uiInfo.serverStatus.numDisplayServers; i++) {
		if (uiInfo.serverStatus.displayServers[i] == num) {
			uiInfo.serverStatus.numDisplayServers--;
			for (j = i; j < uiInfo.serverStatus.numDisplayServers; j++) {
				uiInfo.serverStatus.displayServers[j] = uiInfo.serverStatus.displayServers[j+1];
			}
			return;
		}
	}
}

/*
==================
UI_BinaryServerInsertion
==================
*/
static void UI_BinaryServerInsertion(int num) {
	int mid, offset, res, len;

	// use binary search to insert server
	len = uiInfo.serverStatus.numDisplayServers;
	mid = len;
	offset = 0;
	res = 0;
	while(mid > 0) {
		mid = len >> 1;
		//
		res = trap_LAN_CompareServers( ui_netSource.integer, uiInfo.serverStatus.sortKey,
					uiInfo.serverStatus.sortDir, num, uiInfo.serverStatus.displayServers[offset+mid]);
		// if equal
		if (res == 0) {
			UI_InsertServerIntoDisplayList(num, offset+mid);
			return;
		}
		// if larger
		else if (res == 1) {
			offset += mid;
			len -= mid;
		}
		// if smaller
		else {
			len -= mid;
		}
	}
	if (res == 1) {
		offset++;
	}
	UI_InsertServerIntoDisplayList(num, offset);
}

/*
==================
UI_BuildServerDisplayList
==================
*/
static void UI_BuildServerDisplayList(qboolean force) {
	int i, count, clients, maxClients, ping, game, len, visible;
	char info[MAX_STRING_CHARS];
//	qboolean startRefresh = qtrue; TTimo: unused
	static int numinvisible;

	if (!(force || uiInfo.uiDC.realTime > uiInfo.serverStatus.nextDisplayRefresh)) {
		return;
	}
	// if we shouldn't reset
	if ( force == 2 ) {
		force = 0;
	}

	// do motd updates here too
	trap_Cvar_VariableStringBuffer( "cl_motdString", uiInfo.serverStatus.motd, sizeof(uiInfo.serverStatus.motd) );
	len = strlen(uiInfo.serverStatus.motd);
	if (len == 0) {
		strcpy(uiInfo.serverStatus.motd, "Welcome to Team Arena!");
		len = strlen(uiInfo.serverStatus.motd);
	} 
	if (len != uiInfo.serverStatus.motdLen) {
		uiInfo.serverStatus.motdLen = len;
		uiInfo.serverStatus.motdWidth = -1;
	} 

	if (force) {
		numinvisible = 0;
		// clear number of displayed servers
		uiInfo.serverStatus.numDisplayServers = 0;
		uiInfo.serverStatus.numPlayersOnServers = 0;
		// set list box index to zero
		Menu_SetFeederSelection(NULL, FEEDER_SERVERS, 0, NULL);
		// mark all servers as visible so we store ping updates for them
		trap_LAN_MarkServerVisible(ui_netSource.integer, -1, qtrue);
	}

	// get the server count (comes from the master)
	count = trap_LAN_GetServerCount(ui_netSource.integer);
	if (count == -1 || (ui_netSource.integer == AS_LOCAL && count == 0) ) {
		// still waiting on a response from the master
		uiInfo.serverStatus.numDisplayServers = 0;
		uiInfo.serverStatus.numPlayersOnServers = 0;
		uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 500;
		return;
	}

	visible = qfalse;
	for (i = 0; i < count; i++) {
		// if we already got info for this server
		if (!trap_LAN_ServerIsVisible(ui_netSource.integer, i)) {
			continue;
		}
		visible = qtrue;
		// get the ping for this server
		ping = trap_LAN_GetServerPing(ui_netSource.integer, i);
		if (ping > 0 || ui_netSource.integer == AS_FAVORITES) {

			trap_LAN_GetServerInfo(ui_netSource.integer, i, info, MAX_STRING_CHARS);

			clients = atoi(Info_ValueForKey(info, "clients"));
			uiInfo.serverStatus.numPlayersOnServers += clients;

			if (ui_browserShowEmpty.integer == 0) {
				if (clients == 0) {
					trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse);
					continue;
				}
			}

			if (ui_browserShowFull.integer == 0) {
				maxClients = atoi(Info_ValueForKey(info, "sv_maxclients"));
				if (clients == maxClients) {
					trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse);
					continue;
				}
			}

			if ( ui_joinGameType.integer != 0) 
			{
				game = BG_FindGametype(Info_ValueForKey(info, "gametype"));
				
				if (game != ui_joinGameType.integer - 1 ) 
				{
					trap_LAN_MarkServerVisible( ui_netSource.integer, i, qfalse);
					continue;
				}
			}
				
			if (ui_serverFilterType.integer > 0) {
				if (Q_stricmp(Info_ValueForKey(info, "game"), serverFilters[ui_serverFilterType.integer].basedir) != 0) {
					trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse);
					continue;
				}
			}
			// make sure we never add a favorite server twice
			if (ui_netSource.integer == AS_FAVORITES) {
				UI_RemoveServerFromDisplayList(i);
			}
			// insert the server into the list
			UI_BinaryServerInsertion(i);
			// done with this server
			if (ping > 0) {
				trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse);
				numinvisible++;
			}
		}
	}

	uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime;

	// if there were no servers visible for ping updates
	if (!visible) {
//		UI_StopServerRefresh();
//		uiInfo.serverStatus.nextDisplayRefresh = 0;
	}
}

typedef struct
{
	char	*name;
	char	*altName;

} serverStatusCvar_t;

serverStatusCvar_t serverStatusCvars[] = 
{
	{"sv_hostname",		"Name"		},
	{"Address",			""			},
	{"gamename",		"Game name"	},
	{"g_gametype",		"Game type"	},
	{"mapname",			"Map"		},
	{"version",			""			},
	{"protocol",		""			},
	{"timelimit",		""			},
	{"scorelimit",		""			},
	{NULL, NULL}
};

/*
==================
UI_SortServerStatusInfo
==================
*/
static void UI_SortServerStatusInfo( serverStatusInfo_t *info ) 
{
	int i, j, index;
	char *tmp1, *tmp2;

	index = 0;
	for (i = 0; serverStatusCvars[i].name; i++) 
	{
		for (j = 0; j < info->numLines; j++) 
		{
			if ( !info->lines[j][1] || info->lines[j][1][0] ) 
			{
				continue;
			}

			if ( !Q_stricmp(serverStatusCvars[i].name, info->lines[j][0]) ) 
			{
				// swap lines
				tmp1 = info->lines[index][0];
				tmp2 = info->lines[index][3];
				info->lines[index][0] = info->lines[j][0];
				info->lines[index][3] = info->lines[j][3];
				info->lines[j][0] = tmp1;
				info->lines[j][3] = tmp2;
				
				//
				if ( strlen(serverStatusCvars[i].altName) ) 
				{
					info->lines[index][0] = serverStatusCvars[i].altName;
				}
				index++;
			}
		}
	}
}

/*
==================
UI_GetServerStatusInfo
==================
*/
static int UI_GetServerStatusInfo( const char *serverAddress, serverStatusInfo_t *info ) {
	char *p, *score, *ping, *name, *team;
	int i, len;

	if (!info) {
		trap_LAN_ServerStatus( serverAddress, NULL, 0);
		return qfalse;
	}
	memset(info, 0, sizeof(*info));
	if ( trap_LAN_ServerStatus( serverAddress, info->text, sizeof(info->text)) ) {
		Q_strncpyz(info->address, serverAddress, sizeof(info->address));
		p = info->text;
		info->numLines = 0;
		info->lines[info->numLines][0] = "Address";
		info->lines[info->numLines][1] = "";
		info->lines[info->numLines][2] = "";
		info->lines[info->numLines][3] = info->address;
		info->numLines++;
		// get the cvars
		while (p && *p) {
			p = strchr(p, '\\');
			if (!p) break;
			*p++ = '\0';
			if (*p == '\\')
				break;
			info->lines[info->numLines][0] = p;
			info->lines[info->numLines][1] = "";
			info->lines[info->numLines][2] = "";
			p = strchr(p, '\\');
			if (!p) break;
			*p++ = '\0';
			info->lines[info->numLines][3] = p;

			info->numLines++;
			if (info->numLines >= MAX_SERVERSTATUS_LINES)
				break;
		}
		// get the player list
		if (info->numLines < MAX_SERVERSTATUS_LINES-3) {
			// empty line
			info->lines[info->numLines][0] = "";
			info->lines[info->numLines][1] = "";
			info->lines[info->numLines][2] = "";
			info->lines[info->numLines][3] = "";
			info->numLines++;
			// header
			info->lines[info->numLines][0] = "num";
			info->lines[info->numLines][1] = "score";
			info->lines[info->numLines][2] = "ping";
			info->lines[info->numLines][3] = "name";
			info->numLines++;
			// parse players
			i = 0;
			len = 0;
			while (p && *p) {
				if (*p == '\\')
					*p++ = '\0';
				if (!p)
					break;
				score = p;
				p = strchr(p, ' ');
				if (!p)
					break;
				*p++ = '\0';
				ping = p;
				p = strchr(p, ' ');
				if (!p)
					break;
				*p++ = '\0';
				team = p;
				p = strchr(p, ' ');
				if (!p)
					break;
				*p++ = '\0';
				name = p;
				Com_sprintf(&info->pings[len], sizeof(info->pings)-len, "%d", i);
				info->lines[info->numLines][0] = &info->pings[len];
				len += strlen(&info->pings[len]) + 1;
				info->lines[info->numLines][1] = score;
				info->lines[info->numLines][2] = ping;
				info->lines[info->numLines][3] = name;
				info->numLines++;
				if (info->numLines >= MAX_SERVERSTATUS_LINES)
					break;
				p = strchr(p, '\\');
				if (!p)
					break;
				*p++ = '\0';
				//
				i++;
			}
		}
		UI_SortServerStatusInfo( info );
		return qtrue;
	}
	return qfalse;
}

/*
==================
stristr
==================
*/
static char *stristr(char *str, char *charset) {
	int i;

	while(*str) {
		for (i = 0; charset[i] && str[i]; i++) {
			if (toupper(charset[i]) != toupper(str[i])) break;
		}
		if (!charset[i]) return str;
		str++;
	}
	return NULL;
}

/*
==================
UI_BuildFindPlayerList
==================
*/
static void UI_BuildFindPlayerList(qboolean force) {
	static int numFound, numTimeOuts;
	int i, j, resend;
	serverStatusInfo_t info;
	char name[MAX_NAME_LENGTH+2];
	char infoString[MAX_STRING_CHARS];

	if (!force) {
		if (!uiInfo.nextFindPlayerRefresh || uiInfo.nextFindPlayerRefresh > uiInfo.uiDC.realTime) {
			return;
		}
	}
	else {
		memset(&uiInfo.pendingServerStatus, 0, sizeof(uiInfo.pendingServerStatus));
		uiInfo.numFoundPlayerServers = 0;
		uiInfo.currentFoundPlayerServer = 0;
		trap_Cvar_VariableStringBuffer( "ui_findPlayer", uiInfo.findPlayerName, sizeof(uiInfo.findPlayerName));
		Q_CleanStr(uiInfo.findPlayerName);
		// should have a string of some length
		if (!strlen(uiInfo.findPlayerName)) {
			uiInfo.nextFindPlayerRefresh = 0;
			return;
		}
		// set resend time
		resend = ui_serverStatusTimeOut.integer / 2 - 10;
		if (resend < 50) {
			resend = 50;
		}
		trap_Cvar_Set("cl_serverStatusResendTime", va("%d", resend));
		// reset all server status requests
		trap_LAN_ServerStatus( NULL, NULL, 0);
		//
		uiInfo.numFoundPlayerServers = 1;
		Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1],
						sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1]),
							"searching %d...", uiInfo.pendingServerStatus.num);
		numFound = 0;
		numTimeOuts++;
	}
	for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
		// if this pending server is valid
		if (uiInfo.pendingServerStatus.server[i].valid) {
			// try to get the server status for this server
			if (UI_GetServerStatusInfo( uiInfo.pendingServerStatus.server[i].adrstr, &info ) ) {
				//
				numFound++;
				// parse through the server status lines
				for (j = 0; j < info.numLines; j++) {
					// should have ping info
					if ( !info.lines[j][2] || !info.lines[j][2][0] ) {
						continue;
					}
					// clean string first
					Q_strncpyz(name, info.lines[j][3], sizeof(name));
					Q_CleanStr(name);
					// if the player name is a substring
					if (stristr(name, uiInfo.findPlayerName)) {
						// add to found server list if we have space (always leave space for a line with the number found)
						if (uiInfo.numFoundPlayerServers < MAX_FOUNDPLAYER_SERVERS-1) {
							//
							Q_strncpyz(uiInfo.foundPlayerServerAddresses[uiInfo.numFoundPlayerServers-1],
										uiInfo.pendingServerStatus.server[i].adrstr,
											sizeof(uiInfo.foundPlayerServerAddresses[0]));
							Q_strncpyz(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1],
										uiInfo.pendingServerStatus.server[i].name,
											sizeof(uiInfo.foundPlayerServerNames[0]));
							uiInfo.numFoundPlayerServers++;
						}
						else {
							// can't add any more so we're done
							uiInfo.pendingServerStatus.num = uiInfo.serverStatus.numDisplayServers;
						}
					}
				}
				Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1],
								sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1]),
									"searching %d/%d...", uiInfo.pendingServerStatus.num, numFound);
				// retrieved the server status so reuse this spot
				uiInfo.pendingServerStatus.server[i].valid = qfalse;
			}
		}
		// if empty pending slot or timed out
		if (!uiInfo.pendingServerStatus.server[i].valid ||
			uiInfo.pendingServerStatus.server[i].startTime < uiInfo.uiDC.realTime - ui_serverStatusTimeOut.integer) {
			if (uiInfo.pendingServerStatus.server[i].valid) {
				numTimeOuts++;
			}
			// reset server status request for this address
			UI_GetServerStatusInfo( uiInfo.pendingServerStatus.server[i].adrstr, NULL );
			// reuse pending slot
			uiInfo.pendingServerStatus.server[i].valid = qfalse;
			// if we didn't try to get the status of all servers in the main browser yet
			if (uiInfo.pendingServerStatus.num < uiInfo.serverStatus.numDisplayServers) {
				uiInfo.pendingServerStatus.server[i].startTime = uiInfo.uiDC.realTime;
				trap_LAN_GetServerAddressString(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.pendingServerStatus.num],
							uiInfo.pendingServerStatus.server[i].adrstr, sizeof(uiInfo.pendingServerStatus.server[i].adrstr));
				trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.pendingServerStatus.num], infoString, sizeof(infoString));
				Q_strncpyz(uiInfo.pendingServerStatus.server[i].name, Info_ValueForKey(infoString, "hostname"), sizeof(uiInfo.pendingServerStatus.server[0].name));
				uiInfo.pendingServerStatus.server[i].valid = qtrue;
				uiInfo.pendingServerStatus.num++;
				Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1],
								sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1]),
									"searching %d/%d...", uiInfo.pendingServerStatus.num, numFound);
			}
		}
	}
	for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
		if (uiInfo.pendingServerStatus.server[i].valid) {
			break;
		}
	}
	// if still trying to retrieve server status info
	if (i < MAX_SERVERSTATUSREQUESTS) {
		uiInfo.nextFindPlayerRefresh = uiInfo.uiDC.realTime + 25;
	}
	else {
		// add a line that shows the number of servers found
		if (!uiInfo.numFoundPlayerServers) {
			Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], sizeof(uiInfo.foundPlayerServerAddresses[0]), "no servers found");
		}
		else {
			Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], sizeof(uiInfo.foundPlayerServerAddresses[0]),
						"%d server%s found for player", uiInfo.numFoundPlayerServers-1,
						uiInfo.numFoundPlayerServers == 2 ? "":"s");
		}
		uiInfo.nextFindPlayerRefresh = 0;
		// show the server status info for the selected server
		UI_FeederSelection(FEEDER_FINDPLAYER, uiInfo.currentFoundPlayerServer);
	}
}

/*
==================
UI_BuildServerStatus
==================
*/
static void UI_BuildServerStatus(qboolean force) {

	if (uiInfo.nextFindPlayerRefresh) {
		return;
	}
	if (!force) {
		if (!uiInfo.nextServerStatusRefresh || uiInfo.nextServerStatusRefresh > uiInfo.uiDC.realTime) {
			return;
		}
	}
	else {
		Menu_SetFeederSelection(NULL, FEEDER_SERVERSTATUS, 0, NULL);
		uiInfo.serverStatusInfo.numLines = 0;
		// reset all server status requests
		trap_LAN_ServerStatus( NULL, NULL, 0);
	}
	if (uiInfo.serverStatus.currentServer < 0 || uiInfo.serverStatus.currentServer > uiInfo.serverStatus.numDisplayServers || uiInfo.serverStatus.numDisplayServers == 0) {
		return;
	}
	if (UI_GetServerStatusInfo( uiInfo.serverStatusAddress, &uiInfo.serverStatusInfo ) ) {
		uiInfo.nextServerStatusRefresh = 0;
		UI_GetServerStatusInfo( uiInfo.serverStatusAddress, NULL );
	}
	else {
		uiInfo.nextServerStatusRefresh = uiInfo.uiDC.realTime + 500;
	}
}

/*
==================
UI_FeederCount
==================
*/
static int UI_FeederCount(float feederID) 
{
	switch ( (int)feederID )
	{
		case FEEDER_IDENTITIES:
			return bg_identityCount;

		case FEEDER_TEAMIDENTITIES:
		{
			int		count;
			team_t	team;

			team = ui_info_team.integer;
			if ( team != TEAM_RED && team != TEAM_BLUE )
			{	
				return 0;
			}

			for ( count = 0; count < MAX_TEAMIDENTITIES; count ++ )
			{
				if ( !uiInfo.identityTeams[team][count] )
					break;
			}

			return count;
		}

		case FEEDER_CINEMATICS:
			return uiInfo.movieCount;

		case FEEDER_MAPS:
		case FEEDER_ALLMAPS:
			return UI_MapCountByGameType ();

		case FEEDER_VOTEMAPS:
			return UI_MapCountForVote ( );

		case FEEDER_OUTFITTING_TEMPLATES:
			return bg_outfittingCount;

		case FEEDER_SERVERS:
			return uiInfo.serverStatus.numDisplayServers;

		case FEEDER_SERVERSTATUS:
			return uiInfo.serverStatusInfo.numLines;

		case FEEDER_FINDPLAYER:
			return uiInfo.numFoundPlayerServers;

		case FEEDER_NEWVERSION_WEBSITES:
			return trap_Version_GetNumSites();

		case FEEDER_PLAYER_LIST:
			if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) 
			{
				uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000;
				UI_BuildPlayerList();
			}
			return uiInfo.playerCount;

		case FEEDER_MODS:
			return uiInfo.modCount;

		case FEEDER_DEMOS:
			return uiInfo.demoCount;
	}
	
	return 0;
}

static const char *UI_SelectedMap(int index, int *actual) 
{
	int i, c;
	c = 0;
	*actual = 0;

	for (i = 0; i < uiInfo.mapCount; i++) 
	{
		if (uiInfo.mapList[i].active) 
		{
			if (c == index) 
			{
				*actual = i;
				return uiInfo.mapList[i].mapName;
			} 
			else 
			{
				c++;
			}
		}
	}
	
	return "";
}

static int UI_GetIndexFromSelection ( int actual ) 
{
	int i;
	int	c;

	for ( c = 0, i = 0; i < uiInfo.mapCount; i++) 
	{
		if (uiInfo.mapList[i].active) 
		{
			if (i == actual) 
			{
				return c;
			}
			
			c++;
		}
	}

	return 0;
}

static void UI_UpdatePendingPings() { 
	trap_LAN_ResetPings(ui_netSource.integer);
	uiInfo.serverStatus.refreshActive = qtrue;
	uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000;

}


static const char *UI_FeederItemText(float feederID, int index, int column, qhandle_t *handle) 
{
	static char info[MAX_STRING_CHARS];
	static char info2[MAX_STRING_CHARS];
	static char hostname[MAX_STRING_CHARS];
	static char needpass[2];
	static char clientBuff[32];
	static int	lastColumn = -1;
	static int	lastTime = 0;

	*handle = -1;

	switch ( (int)feederID )
	{
		case FEEDER_IDENTITIES:
			if (index >= 0 && index < bg_identityCount) 
			{
				return bg_identities[index].mName;
			}
			break;

		case FEEDER_TEAMIDENTITIES:
			if ( index >= 0 && index < MAX_TEAMIDENTITIES )
			{
				return uiInfo.identityTeams[ui_info_team.integer][index]->mName;
			}
			break;

		case FEEDER_MAPS:
		case FEEDER_ALLMAPS:
		case FEEDER_VOTEMAPS:
		{
			int actual;
			return UI_SelectedMap(index, &actual);
		}

		case FEEDER_OUTFITTING_TEMPLATES:
			return bg_outfittings[index].name;

		case FEEDER_NEWVERSION_TEXT:
			trap_Version_GetDescription(info2, sizeof(info2));
			return info2;

		case FEEDER_NEWVERSION_WEBSITES:
			trap_Version_GetSite(index, info, sizeof(info));
			return info;

		case FEEDER_SERVERS:
			if (index >= 0 && index < uiInfo.serverStatus.numDisplayServers) 
			{
				int ping;
				int game;
				
				if (lastColumn != column || lastTime > uiInfo.uiDC.realTime + 5000) 
				{
					trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[index], info, MAX_STRING_CHARS);
					lastColumn = column;
					lastTime = uiInfo.uiDC.realTime;
				}
				
				ping = atoi(Info_ValueForKey(info, "ping"));
				if (ping == -1) 
				{
					// if we ever see a ping that is out of date, do a server refresh
					// UI_UpdatePendingPings();
				}
			
				switch (column) 
				{
					case SORT_NEEDPASS:
						needpass[0] = atoi(Info_ValueForKey(info, "needpass"))?'*':' ';
						needpass[1] = 0;
						return needpass;

					case SORT_HOST : 
						if (ping <= 0) 
						{
							return Info_ValueForKey(info, "addr");
						} 
						else 
						{
							if ( ui_netSource.integer == AS_LOCAL ) 
							{
								Com_sprintf( hostname, sizeof(hostname), "[%s] %s",
											 netnames[atoi(Info_ValueForKey(info, "nettype"))],
											 Info_ValueForKey(info, "hostname") );
								return hostname;
							}
							else
							{
								// anonymous server
								if (atoi(Info_ValueForKey(info, "sv_allowAnonymous")) != 0)
								{				
									Com_sprintf( hostname, sizeof(hostname), "(A) %s",
												 Info_ValueForKey(info, "hostname"));
								} 
								else 
								{
									Com_sprintf( hostname, sizeof(hostname), "%s",
												 Info_ValueForKey(info, "hostname"));
								}
							}
						
							return hostname;
						}

						break;

					case SORT_MAP : 
						return Info_ValueForKey(info, "mapname");

					case SORT_CLIENTS : 
						Com_sprintf( clientBuff, sizeof(clientBuff), "%s (%s)", Info_ValueForKey(info, "clients"), Info_ValueForKey(info, "sv_maxclients"));
						return clientBuff;
				
					case SORT_GAME :
						game = BG_FindGametype ( Info_ValueForKey(info, "gametype") );
						if (ping <= 0)
						{
							return "Inactive";	
						}
						else if (game < 0) 
						{
							return "Unknown";	
						} 

						return bg_gametypeData[game].name;

					case SORT_PING : 
						if (ping <= 0) 
						{
							return "...";
						} 

						return Info_ValueForKey(info, "ping");
				}
			}

			break;

		case FEEDER_SERVERSTATUS:

			if ( index >= 0 && index < uiInfo.serverStatusInfo.numLines ) 
			{
				if ( column >= 0 && column < 4 ) 
				{
					return uiInfo.serverStatusInfo.lines[index][column];
				}
			}
			break;

		case FEEDER_FINDPLAYER:
			if ( index >= 0 && index < uiInfo.numFoundPlayerServers ) 
			{
				return uiInfo.foundPlayerServerNames[index];
			}
			break;

		case FEEDER_PLAYER_LIST:
			if (index >= 0 && index < uiInfo.playerCount) 
			{
				return uiInfo.playerNames[index] + 2;
			}
			break;
	
		case FEEDER_MODS:
			if (index >= 0 && index < uiInfo.modCount) 
			{
				if (uiInfo.modList[index].modDescr && *uiInfo.modList[index].modDescr) 
				{
					return uiInfo.modList[index].modDescr;
				} 
				else 
				{
					return uiInfo.modList[index].modName;
				}
			}
			break;

		case FEEDER_CINEMATICS:
			if (index >= 0 && index < uiInfo.movieCount) 
			{
				return uiInfo.movieList[index];
			}
			break;
	
		case FEEDER_DEMOS:
			if (index >= 0 && index < uiInfo.demoCount) 
			{
				return uiInfo.demoList[index];
			}
			break;
	}
	
	return "";
}

static qhandle_t UI_FeederItemImage ( float feederID, int index ) 
{
	switch ( (int)feederID )
	{
		case FEEDER_IDENTITIES:
			if (index >= 0 && index < bg_identityCount) 
			{
				return bg_identities[index].mIcon;
			}
			break;

		case FEEDER_TEAMIDENTITIES:
			if (index >= 0 && index < MAX_TEAMIDENTITIES) 
			{
				return uiInfo.identityTeams[ui_info_team.integer][index]->mIcon;
			}
			break;

		case FEEDER_ALLMAPS:
		case FEEDER_MAPS:
		case FEEDER_VOTEMAPS:
		{
			int actual;
			
			UI_SelectedMap(index, &actual);
			index = actual;
			
			if (index >= 0 && index < uiInfo.mapCount) 
			{
				if (uiInfo.mapList[index].levelShot == -1) 
				{
					uiInfo.mapList[index].levelShot = trap_R_RegisterShaderNoMip(uiInfo.mapList[index].imageName);
				}
				return uiInfo.mapList[index].levelShot;
			}

			break;
		}
	}
  
	return 0;
}

static void UI_FeederSelection(float feederID, int index) 
{
	static char info[MAX_STRING_CHARS];
	
	switch ( (int)feederID )
	{
		case FEEDER_OUTFITTING_TEMPLATES:
			uiInfo.outfittingItemGroup = index;
			break;
	
		case FEEDER_IDENTITIES:
			if (index >= 0 && index < bg_identityCount) 
			{
				trap_Cvar_Set( "identity", bg_identities[index].mName);
				updateModel = qtrue;
			}
			break;

		case FEEDER_TEAMIDENTITIES:
			if (index >= 0 && index < MAX_TEAMIDENTITIES) 
			{
				trap_Cvar_Set( "team_identity", uiInfo.identityTeams[ui_info_team.integer][index]->mName );
				updateModel = qtrue;
			}
			break;

		case FEEDER_MAPS:
		case FEEDER_ALLMAPS:
		case FEEDER_VOTEMAPS:
		{
			int actual, map;
			map = (feederID == FEEDER_ALLMAPS) ? ui_currentNetMap.integer : ui_currentMap.integer;
			if (uiInfo.mapList[map].cinematic >= 0) 
			{
				trap_CIN_StopCinematic(uiInfo.mapList[map].cinematic);
				uiInfo.mapList[map].cinematic = -1;
			}
			
			if ( *UI_SelectedMap(index, &actual) )
			{
				trap_Cvar_Set("ui_mapIndex", va("%d", index));
				ui_mapIndex.integer = index;

				if (feederID == FEEDER_MAPS) 
				{
					ui_currentMap.integer = actual;
					trap_Cvar_Set("ui_currentMap", va("%d", actual));
	  				uiInfo.mapList[ui_currentMap.integer].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.mapList[ui_currentMap.integer].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent) );
					trap_Cvar_Set("ui_opponentModel", uiInfo.mapList[ui_currentMap.integer].opponentName);
				} 
				else 
				{
					ui_currentNetMap.integer = actual;
					trap_Cvar_Set("ui_currentNetMap", va("%d", actual));
					trap_Cvar_Set("ui_currentNetMapName", uiInfo.mapList[actual].mapLoadName );
	  				uiInfo.mapList[ui_currentNetMap.integer].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.mapList[ui_currentNetMap.integer].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent) );
				}
			}
			break;
		}

		case FEEDER_NEWVERSION_WEBSITES:
			uiInfo.versionIndex = index;
			break;

		case FEEDER_SERVERS:
		{
			const char *mapName = NULL;
			uiInfo.serverStatus.currentServer = index;
			trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[index], info, MAX_STRING_CHARS);
			uiInfo.serverStatus.currentServerPreview = trap_R_RegisterShaderNoMip(va("gfx/menus/levelshots/%s", Info_ValueForKey(info, "mapname")));
			if (uiInfo.serverStatus.currentServerCinematic >= 0) 
			{
				trap_CIN_StopCinematic( uiInfo.serverStatus.currentServerCinematic);
				uiInfo.serverStatus.currentServerCinematic = -1;
			}
			mapName = Info_ValueForKey(info, "mapname");
			if (mapName && *mapName) 
			{
				uiInfo.serverStatus.currentServerCinematic = trap_CIN_PlayCinematic(va("%s.roq", mapName), 0, 0, 0, 0, (CIN_loop | CIN_silent) );
			}
			break;
		}

		case FEEDER_FINDPLAYER:
			uiInfo.currentFoundPlayerServer = index;

			if ( index < uiInfo.numFoundPlayerServers-1) 
			{
				// build a new server status for this server
				Q_strncpyz(uiInfo.serverStatusAddress, uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer], sizeof(uiInfo.serverStatusAddress));
				Menu_SetFeederSelection(NULL, FEEDER_SERVERSTATUS, 0, NULL);
				UI_BuildServerStatus(qtrue);
			}
			break;

		case FEEDER_PLAYER_LIST:
			uiInfo.playerIndex = index;
			break;

		case FEEDER_MODS:
			uiInfo.modIndex = index;
			break;

		case FEEDER_CINEMATICS:
			uiInfo.movieIndex = index;
			if (uiInfo.previewMovie >= 0) 
			{
				trap_CIN_StopCinematic(uiInfo.previewMovie);
			}
			uiInfo.previewMovie = -1;
			break;

		case FEEDER_DEMOS:
			uiInfo.demoIndex = index;
			break;
	}
}

static qboolean Alias_Parse(const char **p) {
  char *token;

  token = COM_ParseExt(p, qtrue);

  if (token[0] != '{') {
    return qfalse;
  }

  while ( 1 ) {
    token = COM_ParseExt(p, qtrue);

    if (Q_stricmp(token, "}") == 0) {
      return qtrue;
    }

    if ( !token || token[0] == 0 ) {
      return qfalse;
    }

    if (token[0] == '{') {
      // three tokens per line, character name, bot alias, and preferred action a - all purpose, d - defense, o - offense
      if (!String_Parse(p, &uiInfo.aliasList[uiInfo.aliasCount].name) || !String_Parse(p, &uiInfo.aliasList[uiInfo.aliasCount].ai) || !String_Parse(p, &uiInfo.aliasList[uiInfo.aliasCount].action)) {
        return qfalse;
      }
    
      Com_Printf("Loaded character alias %s using character ai %s.\n", uiInfo.aliasList[uiInfo.aliasCount].name, uiInfo.aliasList[uiInfo.aliasCount].ai);
      if (uiInfo.aliasCount < MAX_ALIASES) {
        uiInfo.aliasCount++;
      } else {
        Com_Printf("Too many aliases, last alias replaced!\n");
      }
     
      token = COM_ParseExt(p, qtrue);
      if (token[0] != '}') {
        return qfalse;
      }
    }
  }

  return qfalse;
}

static void UI_Pause(qboolean b) 
{
	if (b) 
	{
		// pause the game and set the ui keycatcher
		trap_Cvar_Set( "cl_paused", "1" );
		trap_Key_SetCatcher( KEYCATCH_UI );
	} 
	else 
	{
		// unpause the game and clear the ui keycatcher
		trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
		trap_Key_ClearStates();
		trap_Cvar_Set( "cl_paused", "0" );
	}
}

static int UI_PlayCinematic(const char *name, float x, float y, float w, float h) 
{
	return trap_CIN_PlayCinematic(name, x, y, w, h, (CIN_loop | CIN_silent));
}

static void UI_StopCinematic(int handle) {
	if (handle >= 0) {
	  trap_CIN_StopCinematic(handle);
	} else {
		handle = abs(handle);
		if (handle == UI_MAPCINEMATIC) {
			if (uiInfo.mapList[ui_currentMap.integer].cinematic >= 0) {
			  trap_CIN_StopCinematic(uiInfo.mapList[ui_currentMap.integer].cinematic);
			  uiInfo.mapList[ui_currentMap.integer].cinematic = -1;
			}
		} else if (handle == UI_NETMAPCINEMATIC) {
			if (uiInfo.serverStatus.currentServerCinematic >= 0) {
			  trap_CIN_StopCinematic(uiInfo.serverStatus.currentServerCinematic);
				uiInfo.serverStatus.currentServerCinematic = -1;
			}
		} 
	}
}

static void UI_DrawCinematic(int handle, float x, float y, float w, float h) {
	trap_CIN_SetExtents(handle, x, y, w, h);
  trap_CIN_DrawCinematic(handle);
}

static void UI_RunCinematicFrame(int handle) {
  trap_CIN_RunCinematic(handle);
}

/*
=================
UI_Init
=================
*/
void _UI_Init( qboolean inGameLoad ) 
{
	int			start;
	int			i;
	char		identity[256];

	UI_RegisterCvars();

	// cache redundant calulations
	trap_GetGlconfig( &uiInfo.uiDC.glconfig );

	// for 640x480 virtualized screen
	uiInfo.uiDC.yscale = uiInfo.uiDC.glconfig.vidHeight * (1.0/480.0);
	uiInfo.uiDC.xscale = uiInfo.uiDC.glconfig.vidWidth * (1.0/640.0);
	if ( uiInfo.uiDC.glconfig.vidWidth * 480 > uiInfo.uiDC.glconfig.vidHeight * 640 ) 
	{
		// wide screen
		uiInfo.uiDC.bias = 0.5 * ( uiInfo.uiDC.glconfig.vidWidth - ( uiInfo.uiDC.glconfig.vidHeight * (640.0/480.0) ) );
	}
	else 
	{
		// no wide screen
		uiInfo.uiDC.bias = 0;
	}

	uiInfo.uiDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip;
	uiInfo.uiDC.setColor = &UI_SetColor;
	uiInfo.uiDC.drawHandlePic = &UI_DrawHandlePic;
	uiInfo.uiDC.drawStretchPic = &trap_R_DrawStretchPic;
	uiInfo.uiDC.drawText = &UI_DrawText;
	uiInfo.uiDC.drawTextWithCursor = &UI_DrawTextWithCursor;
	uiInfo.uiDC.getTextWidth = &trap_R_GetTextWidth;
	uiInfo.uiDC.getTextHeight = &trap_R_GetTextHeight;
	uiInfo.uiDC.registerModel = &trap_R_RegisterModel;
	uiInfo.uiDC.modelBounds = &trap_R_ModelBounds;
	uiInfo.uiDC.fillRect = &UI_FillRect;
	uiInfo.uiDC.drawRect = &_UI_DrawRect;
	uiInfo.uiDC.drawSides = &_UI_DrawRectLeftRight;
	uiInfo.uiDC.drawTopBottom = &_UI_DrawRectTopBottom;
	uiInfo.uiDC.clearScene = &trap_R_ClearScene;
	uiInfo.uiDC.addRefEntityToScene = &trap_R_AddRefEntityToScene;
	uiInfo.uiDC.renderScene = &trap_R_RenderScene;
	uiInfo.uiDC.registerFont = &trap_R_RegisterFont;
	uiInfo.uiDC.ownerDrawItem = &UI_OwnerDraw;
	uiInfo.uiDC.getValue = &UI_GetValue;
	uiInfo.uiDC.ownerDrawVisible = &UI_OwnerDrawVisible;
	uiInfo.uiDC.ownerDrawDisabled = &UI_OwnerDrawDisabled;
	uiInfo.uiDC.runScript = &UI_RunMenuScript;
	uiInfo.uiDC.getTeamColor = &UI_GetTeamColor;
	uiInfo.uiDC.setCVar = trap_Cvar_Set;
	uiInfo.uiDC.getCVarString = trap_Cvar_VariableStringBuffer;
	uiInfo.uiDC.getCVarValue = trap_Cvar_VariableValue;
	uiInfo.uiDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode;
	uiInfo.uiDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode;
	uiInfo.uiDC.startLocalSound = &trap_S_StartLocalSound;
	uiInfo.uiDC.ownerDrawHandleKey = &UI_OwnerDrawHandleKey;
	uiInfo.uiDC.feederCount = &UI_FeederCount;
	uiInfo.uiDC.feederItemImage = &UI_FeederItemImage;
	uiInfo.uiDC.feederItemText = &UI_FeederItemText;
	uiInfo.uiDC.feederSelection = &UI_FeederSelection;
	uiInfo.uiDC.setBinding = &trap_Key_SetBinding;
	uiInfo.uiDC.getBindingBuf = &trap_Key_GetBindingBuf;
	uiInfo.uiDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf;
	uiInfo.uiDC.executeText = &trap_Cmd_ExecuteText;
	uiInfo.uiDC.Error = &Com_Error; 
	uiInfo.uiDC.Print = &Com_Printf; 
	uiInfo.uiDC.Pause = &UI_Pause;
	uiInfo.uiDC.ownerDrawWidth = &UI_OwnerDrawWidth;
	uiInfo.uiDC.registerSound = &trap_S_RegisterSound;
	uiInfo.uiDC.startBackgroundTrack = &trap_S_StartBackgroundTrack;
	uiInfo.uiDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack;
	uiInfo.uiDC.playCinematic = &UI_PlayCinematic;
	uiInfo.uiDC.stopCinematic = &UI_StopCinematic;
	uiInfo.uiDC.drawCinematic = &UI_DrawCinematic;
	uiInfo.uiDC.runCinematicFrame = &UI_RunCinematicFrame;

	Init_Display(&uiInfo.uiDC);

	String_Init();
  
	uiInfo.uiDC.whiteShader = trap_R_RegisterShaderNoMip( "white" );

	AssetCache();

	start = trap_Milliseconds();

	uiInfo.aliasCount = 0;

	// Read in the gametype list for the various user interface screens
	BG_BuildGametypeList ( );
	BG_ParseNPCFiles ( );

	// Load all the menus
	if ( inGameLoad )
	{
		uiInfo.menusFile = "ui/ingame.txt";
		uiInfo.inGameLoad = qtrue;
	}
	else
	{
		uiInfo.menusFile = "ui/menus.txt";
	}

	UI_LoadMenus( uiInfo.menusFile, qtrue);
	
	Menus_CloseAll();

	trap_LAN_LoadCachedServers();

	UI_LoadIdentityIcons ( );

	// Initialize the team cvars
	trap_Cvar_Set ( "ui_info_redteam", "" );
	trap_Cvar_Set ( "ui_info_blueteam", "" );
	trap_Cvar_Set ( "ui_info_gametype", "0" );
	trap_Cvar_Set ( "ui_info_team", "0" );

#ifdef _SOF2_BOTS
	UI_LoadBots();
#endif

	// sets defaults for ui temp cvars
	uiInfo.currentCrosshair = (int)trap_Cvar_VariableValue("cg_drawCrosshair");
	trap_Cvar_Set("ui_mousePitch", (trap_Cvar_VariableValue("m_pitch") >= 0) ? "0" : "1");

	uiInfo.serverStatus.currentServerCinematic = -1;
	uiInfo.previewMovie = -1;

	if (trap_Cvar_VariableValue("ui_sof2FirstRun") == 0) 
	{
		trap_Cvar_Set("s_volume", "0.8");
		trap_Cvar_Set("s_musicvolume", "0.5");
		trap_Cvar_Set("s_attenuate", "0.00045" );
		trap_Cvar_Set("ui_sof2FirstRun", "1");
	}

	trap_Cvar_Register(NULL, "debug_protocol", "", 0, 0.0, 0.0 );

	trap_Cvar_Set("ui_actualNetGameType", va("%d", ui_netGameType.integer));
	trap_Cvar_Set("ui_gtRespawnType", va("%d", bg_gametypeData[ui_netGameType.integer].respawnType));
	trap_Cvar_Set("ui_gtPickupsDisabled", va("%d", bg_gametypeData[ui_netGameType.integer].pickupsDisabled));

	trap_Cvar_VariableStringBuffer("identity", identity, sizeof(identity));
	for(i=0;i<bg_identityCount;i++)
	{
		if (Q_stricmp(bg_identities[i].mName, identity) == 0)
		{
			Menu_SetFeederSelection(NULL, FEEDER_IDENTITIES, i, "player_menu");
			break;
		}
	}
}

/*
=================
UI_KeyEvent
=================
*/
qboolean _UI_KeyEvent( int key, qboolean down ) 
{
	menuDef_t *menu;

	if (Menu_Count() <= 0) 
	{
		return qfalse;
	}

    menu = Menu_GetFocused();
	
	if (menu) 
	{
		if (key == K_ESCAPE && down && !Menus_AnyFullScreenVisible()) 
		{
			Menus_CloseAll();
		} 
		else 
		{
			// Passthrough has no mouse input
			if ( (key < '0' || key > '9') && (trap_Key_GetCatcher ( ) & KEYCATCH_NUMBERSONLY) )
			{
				return qfalse;
			}

			return Menu_HandleKey(menu, key, down );
		}
	} 
	else 
	{
		int catcher = trap_Key_GetCatcher ( );

		if ( catcher & KEYCATCH_NUMBERSONLY )
		{
			trap_Key_SetCatcher( trap_Key_GetCatcher() & ~(KEYCATCH_UI|KEYCATCH_NUMBERSONLY) );
		}
		else
		{
			trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
			trap_Key_ClearStates();
			trap_Cvar_Set( "cl_paused", "0" );
		}
	}

	return qfalse;
}

/*
=================
UI_MouseEvent
=================
*/
qboolean _UI_MouseEvent( int dx, int dy )
{
	// update mouse screen position
	uiInfo.uiDC.cursorx += dx;
	if (uiInfo.uiDC.cursorx < 0)
		uiInfo.uiDC.cursorx = 0;
	else if (uiInfo.uiDC.cursorx > SCREEN_WIDTH)
		uiInfo.uiDC.cursorx = SCREEN_WIDTH;

	uiInfo.uiDC.cursory += dy;
	if (uiInfo.uiDC.cursory < 0)
		uiInfo.uiDC.cursory = 0;
	else if (uiInfo.uiDC.cursory > SCREEN_HEIGHT)
		uiInfo.uiDC.cursory = SCREEN_HEIGHT;

	// If the cursor has moved then reset the tooltip location and time.
	if ( uiInfo.uiDC.tooltipx != uiInfo.uiDC.cursorx ||
	     uiInfo.uiDC.tooltipy != uiInfo.uiDC.cursory    )
	{
		uiInfo.uiDC.tooltipx = uiInfo.uiDC.cursorx;
		uiInfo.uiDC.tooltipy = uiInfo.uiDC.cursory;
		uiInfo.uiDC.tooltiptime = uiInfo.uiDC.realTime;
	}

	if (Menu_Count() > 0) 
	{
		//menuDef_t *menu = Menu_GetFocused();
		//Menu_HandleMouseMove(menu, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory);
		Display_MouseMove(NULL, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory);
	}

	return qtrue;
}

void UI_LoadNonIngame() 
{
	UI_LoadMenus("ui/menus.txt", qtrue );
	uiInfo.inGameLoad = qfalse;
}

void UI_SetActiveMenu( uiMenuCommand_t menu ) 
{
	char	buf[256];

	// this should be the ONLY way the menu system is brought up
	// enusure minumum menu data is cached
	if (Menu_Count() <= 0) 
	{	
		return;
	}
	
	switch ( menu ) 
	{
		case UIMENU_NONE:
			trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
			trap_Key_ClearStates();
			trap_Cvar_Set( "cl_paused", "0" );
			Menus_CloseAll();
			return;

		case UIMENU_MAIN:
		{
			qboolean   active = qfalse;
			menuDef_t* menudef;
			itemDef_t* item;

			// Make sure the player setup alwasy shows non team game models
			trap_Cvar_Set ( "ui_info_teamgame", "0" );
	
			trap_Key_SetCatcher( KEYCATCH_UI );

			Menus_CloseAll();

			if (uiInfo.inGameLoad) 
			{
				uiInfo.inGameLoad = qfalse;
				UI_LoadNonIngame();
			}
			
			menudef = Menus_ActivateByName("main");

			// the ui_joinserver cvar tells us whether or not we should jump
			// straight to the join server menu
			if ( ui_joinserver.integer )
			{
				item = Menu_GetItemByName ( menudef, "play_button" );
			}
			else
			{
				item = NULL;
			}

			trap_Cvar_Set ( "ui_joinserver", "0" );

			if ( item )
			{
				Item_Action ( item );

				trap_Cvar_VariableStringBuffer("com_errorMessage", buf, sizeof(buf));
				if (strlen(buf)) 
				{
					Menus_ActivateByName("error_popmenu");
					active = qtrue;
				}
			}
			else
			{
				trap_Cvar_VariableStringBuffer("com_errorMessage", buf, sizeof(buf));
				if (strlen(buf)) 
				{
					Menus_ActivateByName("error_popmenu");
					active = qtrue;
				}
				
				if (!active && ui_invalidversion.integer) 
				{
					Menus_ActivateByName("patch_info");
					active = qtrue;
				}
				
				if ( !active && !ui_cdkeychecked.integer )
				{
					char key[20];

					trap_GetCDKey( key, sizeof(key) );
					if( !trap_VerifyCDKey( key ) ) 
					{
						Menus_ActivateByName("cdkey_popmenu");
						active = qtrue;
					}
				}
				
				if ( !active && (int)trap_Cvar_VariableValue ( "com_othertasks" ) )
				{
					trap_Cvar_Set("com_othertasks", "0");
					if ( !(int)trap_Cvar_VariableValue ( "com_ignoreothertasks" ) )
					{
						Menus_ActivateByName("backgroundtask_popmenu");
						active = qtrue;
					}
				}
			}

			return;
		}

		case UIMENU_VERSION:
			trap_Key_SetCatcher( KEYCATCH_UI );
			Menus_ActivateByName("patch_info");
			return;

		case UIMENU_BAD_CD_KEY:
			// no cd check in TA
			//trap_Key_SetCatcher( KEYCATCH_UI );
			//Menus_ActivateByName("badcd");
			//UI_ConfirmMenu( "Bad CD Key", NULL, NeedCDKeyAction );
			return;

		 case UIMENU_INGAME:
			trap_Cvar_Set( "cl_paused", "1" );
			trap_Key_SetCatcher( KEYCATCH_UI );
			UI_BuildPlayerList();
			Menus_CloseAll();
			Menus_ActivateByName("ingame");
			return;

		case UIMENU_OBJECTIVES:
			trap_Key_SetCatcher( KEYCATCH_UI );
			Menus_CloseAll();
			Menus_ActivateByName("ingame_objectives");				
			return;

		case UIMENU_RADIO:
			trap_Key_SetCatcher( KEYCATCH_UI|KEYCATCH_NUMBERSONLY );
			Menus_CloseAll();
			Menus_ActivateByName("ingame_radio");				
			return;

		case UIMENU_OUTFITTING:
//			trap_Cvar_Set( "cl_paused", "1" );
			trap_Key_SetCatcher( KEYCATCH_UI );
			Menus_CloseAll();
			Menus_ActivateByName("ingame_outfitting");
			return;

		case UIMENU_TEAM:
//			trap_Cvar_Set( "cl_paused", "1" );
			trap_Key_SetCatcher( KEYCATCH_UI );
			Menus_CloseAll();
			Menus_ActivateByName("ingame_team");
			return;
	}
}

static connstate_t	lastConnState;
static char			lastLoadingText[MAX_INFO_VALUE];

static void UI_ReadableSize ( char *buf, int bufsize, int value )
{
	if (value > 1024*1024*1024 ) { // gigs
		Com_sprintf( buf, bufsize, "%d", value / (1024*1024*1024) );
		Com_sprintf( buf+strlen(buf), bufsize-strlen(buf), ".%02d GB", 
			(value % (1024*1024*1024))*100 / (1024*1024*1024) );
	} else if (value > 1024*1024 ) { // megs
		Com_sprintf( buf, bufsize, "%d", value / (1024*1024) );
		Com_sprintf( buf+strlen(buf), bufsize-strlen(buf), ".%02d MB", 
			(value % (1024*1024))*100 / (1024*1024) );
	} else if (value > 1024 ) { // kilos
		Com_sprintf( buf, bufsize, "%d KB", value / 1024 );
	} else { // bytes
		Com_sprintf( buf, bufsize, "%d bytes", value );
	}
}

// Assumes time is in msec
static void UI_PrintTime ( char *buf, int bufsize, int time ) {
	time /= 1000;  // change to seconds

	if (time > 3600) { // in the hours range
		Com_sprintf( buf, bufsize, "%d hr %d min", time / 3600, (time % 3600) / 60 );
	} else if (time > 60) { // mins
		Com_sprintf( buf, bufsize, "%d min %d sec", time / 60, time % 60 );
	} else  { // secs
		Com_sprintf( buf, bufsize, "%d sec", time );
	}
}

void Text_PaintCenter(float x, float y, qhandle_t font, float scale, vec4_t color, const char *text, float adjust ) 
{
	int len = trap_R_GetTextWidth (text, font, scale, 0 );
	
	UI_DrawText ( x - len / 2, y, font, scale, color, text, 0, 0 );
}


static void UI_DisplayDownloadInfo( const char *downloadName, float centerPoint, float yStart, float scale ) 
{
	static char dlText[]	= "Downloading:";
	static char etaText[]	= "Estimated time left:";
	static char xferText[]	= "Transfer rate:";

	int downloadSize, downloadCount, downloadTime;
	char dlSizeBuf[64], totalSizeBuf[64], xferRateBuf[64], dlTimeBuf[64];
	int xferRate;
	int leftWidth;
	const char *s;

	downloadSize = trap_Cvar_VariableValue( "cl_downloadSize" );
	downloadCount = trap_Cvar_VariableValue( "cl_downloadCount" );
	downloadTime = trap_Cvar_VariableValue( "cl_downloadTime" );

	leftWidth = 320;

	UI_SetColor(colorWhite);
	Text_PaintCenter(centerPoint, yStart, uiInfo.uiDC.Assets.defaultFont, scale, colorWhite, dlText, 0 );
	Text_PaintCenter(centerPoint, yStart + 90, uiInfo.uiDC.Assets.defaultFont, scale, colorWhite, etaText, 0 );
	Text_PaintCenter(centerPoint, yStart + 50, uiInfo.uiDC.Assets.defaultFont, scale, colorWhite, xferText, 0 );

	if (downloadSize > 0) {
		s = va( "%s (%d%%)", downloadName, (int)((float)downloadCount * (100.0f / (float)downloadSize)) );
	} else {
		s = downloadName;
	}

	Text_PaintCenter(centerPoint, yStart+15, uiInfo.uiDC.Assets.defaultFont, scale, colorMdGrey, s, 0 );

	UI_ReadableSize( dlSizeBuf,		sizeof dlSizeBuf,		downloadCount );
	UI_ReadableSize( totalSizeBuf,	sizeof totalSizeBuf,	downloadSize );

	if (downloadCount < 4096 || !downloadTime) {
		Text_PaintCenter(leftWidth, yStart+105, uiInfo.uiDC.Assets.defaultFont, scale, colorMdGrey, "estimating", 0 );
		Text_PaintCenter(leftWidth, yStart+30, uiInfo.uiDC.Assets.defaultFont, scale, colorMdGrey, va("(%s of %s copied)", dlSizeBuf, totalSizeBuf), 0 );
	} else {
		if ((uiInfo.uiDC.realTime - downloadTime) / 1000) {
			xferRate = downloadCount / ((uiInfo.uiDC.realTime - downloadTime) / 1000);
		} else {
			xferRate = 0;
		}
		UI_ReadableSize( xferRateBuf, sizeof xferRateBuf, xferRate );

		// Extrapolate estimated completion time
		if (downloadSize && xferRate) {
			int n = downloadSize / xferRate; // estimated time for entire d/l in secs

			// We do it in K (/1024) because we'd overflow around 4MB
			UI_PrintTime ( dlTimeBuf, sizeof dlTimeBuf, 
				(n - (((downloadCount/1024) * n) / (downloadSize/1024))) * 1000);

			Text_PaintCenter(leftWidth, yStart+105, uiInfo.uiDC.Assets.defaultFont, scale, colorMdGrey, dlTimeBuf, 0 );
			Text_PaintCenter(leftWidth, yStart+30, uiInfo.uiDC.Assets.defaultFont, scale, colorMdGrey, va("(%s of %s copied)", dlSizeBuf, totalSizeBuf), 0 );
		} else {
			Text_PaintCenter(leftWidth, yStart+105, uiInfo.uiDC.Assets.defaultFont, scale, colorMdGrey, "estimating", 0 );
			if (downloadSize) {
				Text_PaintCenter(leftWidth, yStart+30, uiInfo.uiDC.Assets.defaultFont, scale, colorMdGrey, va("(%s of %s copied)", dlSizeBuf, totalSizeBuf), 0 );
			} else {
				Text_PaintCenter(leftWidth, yStart+30, uiInfo.uiDC.Assets.defaultFont, scale, colorMdGrey, va("(%s copied)", dlSizeBuf), 0 );
			}
		}

		if (xferRate) {
			Text_PaintCenter(leftWidth, yStart+65, uiInfo.uiDC.Assets.defaultFont, scale, colorMdGrey, va("%s/Sec", xferRateBuf), 0 );
		}
	}
}

/*
========================
UI_DrawConnectScreen

This will also be overlaid on the cgame info screen during loading
to prevent it from blinking away too rapidly on local or lan games.
========================
*/
void UI_DrawConnectScreen( qboolean overlay ) {
	char			*s;
	uiClientState_t	cstate;
	char			info[MAX_INFO_VALUE];
	char text[256];
	float centerPoint, yStart, scale;
	
	menuDef_t *menu = Menus_FindByName("Connect");

	if ( !ui_joinserver.integer )
	{
		trap_Cvar_Set ( "ui_joinserver", "1" );
	}

	if ( !overlay && menu ) {
		Menu_Paint(menu, qtrue);
	}

	if (!overlay) {
		centerPoint = 320;
		yStart = 340;
		scale = 0.53f;
	} else {
		centerPoint = 340;
		yStart = 380;
		scale = 0.53f;
		return;
	}

	// see what information we should display
	trap_GetClientState( &cstate );

	if ( cstate.connState == CA_CONNECTED )
	{
		char downloadName[MAX_INFO_VALUE];

		trap_Cvar_VariableStringBuffer( "cl_downloadName", downloadName, sizeof(downloadName) );
		if (*downloadName) 
		{
			if (!Q_stricmp(cstate.servername,"localhost")) 
			{
				Text_PaintCenter(centerPoint, 50, uiInfo.uiDC.Assets.defaultFont, scale, colorWhite, va("Loading..."), 0 );
			} 
			else 
			{
				strcpy(text, va("Connecting to %s", cstate.servername));
				Text_PaintCenter(centerPoint, 50, uiInfo.uiDC.Assets.defaultFont, scale, colorWhite,text , 0 );
			}

			UI_DisplayDownloadInfo( downloadName, centerPoint, yStart, 0.43f );
			return;
		}
	}
					
	info[0] = '\0';
	if( trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) ) ) 
	{
		Text_PaintCenter(centerPoint, yStart, uiInfo.uiDC.Assets.defaultFont, scale, colorWhite, va( "Loading %s", Info_ValueForKey( info, "mapname" )), 0 );
	}

	if (!Q_stricmp(cstate.servername,"localhost")) 
	{
		Text_PaintCenter(centerPoint, yStart + 48, uiInfo.uiDC.Assets.defaultFont, scale, colorWhite, va("Loading..."), 0 );
	} 
	else 
	{
		strcpy(text, va("Connecting to %s", cstate.servername));
		Text_PaintCenter(centerPoint, yStart + 48, uiInfo.uiDC.Assets.defaultFont, scale, colorWhite,text , 0 );
	}

	// display global MOTD at bottom
	Text_PaintCenter(centerPoint, 600, uiInfo.uiDC.Assets.defaultFont, scale, colorWhite, Info_ValueForKey( cstate.updateInfoString, "motd" ), 0 );
	
	// print any server info (server full, bad version, etc)
	if ( cstate.connState < CA_CONNECTED ) 
	{
		// Display the password dialog
		if ( strstr ( cstate.messageString, "password" ) )
		{
			if ( !uiInfo.connectPasswordRequest )
			{
				char password[MAX_QPATH];
				trap_Cvar_VariableStringBuffer ( "password", password, MAX_QPATH );

				// Dont bring it up again until the server says that password was bad too
				if ( strstr ( cstate.messageString, password ) )
				{
					trap_Key_SetCatcher( KEYCATCH_UI );
					Menus_CloseAll ( );
					Menus_ActivateByName("password_popmenu");
					uiInfo.connectPasswordRequest = qtrue;
				}
			}
		}
		
		Text_PaintCenter(centerPoint, yStart + 112, uiInfo.uiDC.Assets.defaultFont, scale, colorRed, cstate.messageString, 0 );
	}

	if ( lastConnState > cstate.connState ) {
		lastLoadingText[0] = '\0';
	}
	lastConnState = cstate.connState;

	switch ( cstate.connState ) {
	case CA_CONNECTING:
		s = va("Awaiting connection...%i", cstate.connectPacketCount);
		break;
	case CA_CHALLENGING:
		s = va("Awaiting challenge...%i", cstate.connectPacketCount);
		break;
	case CA_CONNECTED:
		s = "Awaiting gamestate";
		break;
	case CA_LOADING:
		return;
	case CA_PRIMED:
		return;
	default:
		return;
	}


	if (Q_stricmp(cstate.servername,"localhost")) {
		Text_PaintCenter(centerPoint, yStart + 80, uiInfo.uiDC.Assets.defaultFont, scale, colorWhite, s, 0 );
	}

	// password required / connection rejected information goes here
}



/*
========================
UI_DrawConnectScreen

This will also be overlaid on the cgame info screen during loading
to prevent it from blinking away too rapidly on local or lan games.
========================
*/
void UI_DrawLoadingScreen( void ) 
{
	float centerPoint, yStart, scale;
	
	menuDef_t *menu = Menus_FindByName("Loading");

	if ( menu ) 
	{
		Menu_Paint(menu, qtrue);
	}

	centerPoint = 320;
	yStart = 388;
	scale = 0.53f;

	Text_PaintCenter(centerPoint, yStart, uiInfo.uiDC.Assets.defaultFont, scale, colorWhite, "Loading...", 0 );
}


/*
================
cvars
================
*/

typedef struct 
{
	vmCvar_t	*vmCvar;
	char		*cvarName;
	char		*defaultString;
	int			cvarFlags;
	float		mMinValue, mMaxValue;
} cvarTable_t;

vmCvar_t	ui_arenasFile;
vmCvar_t	ui_botsFile;
vmCvar_t	ui_botSkill;

vmCvar_t	ui_browserMaster;
vmCvar_t	ui_browserGameType;
vmCvar_t	ui_browserSortKey;
vmCvar_t	ui_browserShowFull;
vmCvar_t	ui_browserShowEmpty;

vmCvar_t	ui_drawCrosshair;
vmCvar_t	ui_drawCrosshairNames;

vmCvar_t	ui_server1;
vmCvar_t	ui_server2;
vmCvar_t	ui_server3;
vmCvar_t	ui_server4;
vmCvar_t	ui_server5;
vmCvar_t	ui_server6;
vmCvar_t	ui_server7;
vmCvar_t	ui_server8;
vmCvar_t	ui_server9;
vmCvar_t	ui_server10;
vmCvar_t	ui_server11;
vmCvar_t	ui_server12;
vmCvar_t	ui_server13;
vmCvar_t	ui_server14;
vmCvar_t	ui_server15;
vmCvar_t	ui_server16;

vmCvar_t	ui_cdkeychecked;
vmCvar_t	ui_invalidversion;
vmCvar_t	ui_downloadsize;
vmCvar_t	ui_downloadsite;
vmCvar_t	ui_downloadstatus;

vmCvar_t	ui_dedicated;
vmCvar_t	ui_gameType;
vmCvar_t	ui_netGameType;
vmCvar_t	ui_actualNetGameType;
vmCvar_t	ui_gtRespawnType;
vmCvar_t	ui_gtPickupsDisabled;
vmCvar_t	ui_joinGameType;
vmCvar_t	ui_netSource;
vmCvar_t	ui_serverFilterType;
vmCvar_t	ui_currentMap;
vmCvar_t	ui_currentNetMap;
vmCvar_t	ui_mapIndex;
vmCvar_t	ui_selectedPlayer;
vmCvar_t	ui_selectedPlayerName;
vmCvar_t	ui_lastServerRefresh_0;
vmCvar_t	ui_lastServerRefresh_1;
vmCvar_t	ui_lastServerRefresh_2;
vmCvar_t	ui_lastServerRefresh_3;
vmCvar_t	ui_scoreLimit;
vmCvar_t	ui_findPlayer;
vmCvar_t	ui_hudFiles;
vmCvar_t	ui_realWarmUp;
vmCvar_t	ui_serverStatusTimeOut;
vmCvar_t	ui_glCustom;
vmCvar_t	ui_botteam;

vmCvar_t	ui_rmg_config;
vmCvar_t	ui_rmg_size;
vmCvar_t	ui_rmg_time;
vmCvar_t	ui_rmg_seed;

// Readonly cvars used to transfer information from cgame to ui
vmCvar_t	ui_info_redteam;
vmCvar_t	ui_info_blueteam;
vmCvar_t	ui_info_redcount;
vmCvar_t	ui_info_bluecount;
vmCvar_t	ui_info_objectives;
vmCvar_t	ui_info_gametype;
vmCvar_t	ui_info_team;
vmCvar_t	ui_info_teamgame;
vmCvar_t	ui_info_redscore;
vmCvar_t	ui_info_bluescore;
vmCvar_t	ui_joinserver;

vmCvar_t	ui_allowparental;

vmCvar_t	ui_noNetCheck;

static cvarTable_t cvarTable[] = 
{
	{ &ui_botteam, "ui_botteam", "auto", 0 },
	{ &ui_glCustom, "ui_glCustom", "0", CVAR_ARCHIVE },

	{ &ui_botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM },
	{ &ui_botSkill, "g_botSkill", "2", CVAR_ARCHIVE },

	{ &ui_browserMaster, "ui_browserMaster", "0", CVAR_ARCHIVE },
	{ &ui_browserGameType, "ui_browserGameType", "0", CVAR_ARCHIVE },
	{ &ui_browserSortKey, "ui_browserSortKey", "4", CVAR_ARCHIVE },
	{ &ui_browserShowFull, "ui_browserShowFull", "1", CVAR_ARCHIVE },
	{ &ui_browserShowEmpty, "ui_browserShowEmpty", "1", CVAR_ARCHIVE },

	{ &ui_drawCrosshair, "cg_drawCrosshair", "4", CVAR_ARCHIVE },
	{ &ui_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE },

	{ &ui_server1, "server1", "", CVAR_ARCHIVE },
	{ &ui_server2, "server2", "", CVAR_ARCHIVE },
	{ &ui_server3, "server3", "", CVAR_ARCHIVE },
	{ &ui_server4, "server4", "", CVAR_ARCHIVE },
	{ &ui_server5, "server5", "", CVAR_ARCHIVE },
	{ &ui_server6, "server6", "", CVAR_ARCHIVE },
	{ &ui_server7, "server7", "", CVAR_ARCHIVE },
	{ &ui_server8, "server8", "", CVAR_ARCHIVE },
	{ &ui_server9, "server9", "", CVAR_ARCHIVE },
	{ &ui_server10, "server10", "", CVAR_ARCHIVE },
	{ &ui_server11, "server11", "", CVAR_ARCHIVE },
	{ &ui_server12, "server12", "", CVAR_ARCHIVE },
	{ &ui_server13, "server13", "", CVAR_ARCHIVE },
	{ &ui_server14, "server14", "", CVAR_ARCHIVE },
	{ &ui_server15, "server15", "", CVAR_ARCHIVE },
	{ &ui_server16, "server16", "", CVAR_ARCHIVE },
	{ &ui_cdkeychecked, "ui_cdkeychecked", "0", CVAR_ROM },
	{ &ui_invalidversion, "ui_invalidversion", "0", CVAR_TEMP },
	{ &ui_downloadsize, "ui_downloadsize", "0", CVAR_TEMP },
	{ &ui_downloadsite, "ui_downloadsite", "0", CVAR_TEMP },
	{ &ui_downloadstatus, "ui_downloadstatus", "0", CVAR_TEMP },
	{ &ui_new, "ui_new", "0", CVAR_TEMP },
	{ &ui_debug, "ui_debug", "0", CVAR_TEMP },
	{ &ui_initialized, "ui_initialized", "0", CVAR_TEMP },
	{ &ui_dedicated, "ui_dedicated", "0", CVAR_ARCHIVE },
	{ &ui_gameType, "ui_gametype", "3", CVAR_ARCHIVE },
	{ &ui_joinGameType, "ui_joinGametype", "0", CVAR_ARCHIVE },
	{ &ui_netGameType, "ui_netGametype", "3", CVAR_ARCHIVE | CVAR_INTERNAL | CVAR_ROM },
	{ &ui_actualNetGameType, "ui_actualNetGametype", "3", CVAR_ARCHIVE | CVAR_INTERNAL | CVAR_ROM },
	{ &ui_gtRespawnType, "ui_gtRespawnType", "0", CVAR_ARCHIVE | CVAR_INTERNAL | CVAR_ROM },
	{ &ui_gtPickupsDisabled, "ui_gtPickupsDisabled", "0", CVAR_ARCHIVE | CVAR_INTERNAL | CVAR_ROM }, 
	{ &ui_netSource, "ui_netSource", "0", CVAR_ARCHIVE },
	{ &ui_currentMap, "ui_currentMap", "0", CVAR_ARCHIVE },
	{ &ui_currentNetMap, "ui_currentNetMap", "0", CVAR_ARCHIVE },
	{ &ui_mapIndex, "ui_mapIndex", "0", CVAR_ARCHIVE },
	{ &ui_selectedPlayer, "cg_selectedPlayer", "0", CVAR_ARCHIVE},
	{ &ui_selectedPlayerName, "cg_selectedPlayerName", "", CVAR_ARCHIVE},
	{ &ui_lastServerRefresh_0, "ui_lastServerRefresh_0", "", CVAR_ARCHIVE},
	{ &ui_lastServerRefresh_1, "ui_lastServerRefresh_1", "", CVAR_ARCHIVE},
	{ &ui_lastServerRefresh_2, "ui_lastServerRefresh_2", "", CVAR_ARCHIVE},
	{ &ui_lastServerRefresh_3, "ui_lastServerRefresh_3", "", CVAR_ARCHIVE},
	{ &ui_scoreLimit, "ui_scoreLimit", "10", 0},
	{ &ui_findPlayer, "ui_findPlayer", "unknown", CVAR_ARCHIVE},
	{ &ui_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE},
	{ &ui_sof2FirstRun, "ui_sof2FirstRun", "0", CVAR_ARCHIVE},
	{ &ui_realWarmUp, "g_warmup", "20", CVAR_ARCHIVE},
	{ &ui_serverStatusTimeOut, "ui_serverStatusTimeOut", "7000", CVAR_ARCHIVE},

	{ &ui_rmg_config,		"ui_rmg_config",		"desert",						CVAR_ARCHIVE },
	{ &ui_rmg_size,			"ui_rmg_size",			"small",						CVAR_ARCHIVE },
	{ &ui_rmg_time,			"ui_rmg_time",			"day",							CVAR_ARCHIVE },
	{ &ui_rmg_seed,			"ui_rmg_seed",			"0",							CVAR_ARCHIVE },

	// Readonly cvars used to transfer information from cgame to ui
	{ &ui_info_redteam,		"ui_info_redteam",		"0", CVAR_ROM|CVAR_INTERNAL },
	{ &ui_info_blueteam,	"ui_info_blueteam",		"0", CVAR_ROM|CVAR_INTERNAL },
	{ &ui_info_redcount,	"ui_info_redcount",		"0", CVAR_ROM|CVAR_INTERNAL },
	{ &ui_info_bluecount,	"ui_info_bluecount",	"0", CVAR_ROM|CVAR_INTERNAL },
	{ &ui_info_redscore,	"ui_info_redscore",		"0", CVAR_ROM|CVAR_INTERNAL },
	{ &ui_info_bluescore,	"ui_info_bluescore",	"0", CVAR_ROM|CVAR_INTERNAL },
	{ &ui_info_objectives,	"ui_info_objectives",	"0", CVAR_ROM|CVAR_INTERNAL },
	{ &ui_info_gametype,	"ui_info_gametype",		"0", CVAR_ROM|CVAR_INTERNAL },
	{ &ui_info_team,		"ui_info_team",			"0", CVAR_ROM|CVAR_INTERNAL },
	{ &ui_info_teamgame,	"ui_info_teamgame",		"0", CVAR_ROM|CVAR_INTERNAL },

	{ &ui_joinserver,		"ui_joinserver",		"0", CVAR_ROM|CVAR_INTERNAL },

	{ &ui_allowparental,	"ui_allowparental",		"1", CVAR_ROM | CVAR_INTERNAL | CVAR_PARENTAL },

	{ &ui_noNetCheck,		"ui_noNetCheck",		"0", CVAR_ARCHIVE },
};

static int	cvarTableSize = sizeof(cvarTable) / sizeof(cvarTable[0]);

/*
=================
UI_RegisterCvars
=================
*/
void UI_RegisterCvars( void ) 
{
	int			i;
	cvarTable_t	*cv;

	for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) 
	{
		trap_Cvar_Register( cv->vmCvar, cv->cvarName, cv->defaultString, cv->cvarFlags, cv->mMinValue, cv->mMaxValue );
	}

	trap_Cvar_Register(NULL, "lock_password",	"", CVAR_INTERNAL, 0.0, 0.0 );
	trap_Cvar_Register(NULL, "lock_blood",		"0", CVAR_INTERNAL, 0.0, 0.0 );
	trap_Cvar_Register(NULL, "lock_deaths",		"0", CVAR_INTERNAL, 0.0, 0.0 );
	trap_Cvar_Register(NULL, "lock_sever",		"0", CVAR_INTERNAL, 0.0, 0.0 );
}

/*
=================
UI_UpdateCvars
=================
*/
void UI_UpdateCvars( void ) 
{
	int			i;
	cvarTable_t	*cv;

	for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) 
	{
		int modificationCount = cv->vmCvar->modificationCount;

		trap_Cvar_Update( cv->vmCvar );

		if ( cv->vmCvar->modificationCount != modificationCount )
		{
			if ( Q_stricmp ( cv->cvarName, "ui_info_redteam" ) == 0 )
			{
				int	 count;
				int	 i;

				memset ( &uiInfo.identityTeams[TEAM_RED], 0, sizeof(TIdentity*) * MAX_TEAMIDENTITIES );

				if ( cv->vmCvar->string[0] )
				{
					for ( i = 0, count = 0; i < bg_identityCount && count < MAX_TEAMIDENTITIES; i ++ )
					{
						if ( Q_stricmp ( bg_identities[i].mTeam, cv->vmCvar->string ) == 0 )
						{
							uiInfo.identityTeams[TEAM_RED][count++] = &bg_identities[i];
						}
					}
				}
			}
			else if ( Q_stricmp ( cv->cvarName, "ui_info_blueteam" ) == 0 )
			{
				int	 count;
				int	 i;

				memset ( &uiInfo.identityTeams[TEAM_BLUE], 0, sizeof(TIdentity*) * MAX_TEAMIDENTITIES );

				if ( cv->vmCvar->string[0] )
				{
					for ( i = 0, count = 0; i < bg_identityCount && count < MAX_TEAMIDENTITIES; i ++ )
					{
						if ( Q_stricmp ( bg_identities[i].mTeam, cv->vmCvar->string ) == 0 )
						{
							uiInfo.identityTeams[TEAM_BLUE][count++] = &bg_identities[i];
						}
					}
				}
			}
		}
	}
}


/*
=================
ArenaServers_StopRefresh
=================
*/
static void UI_StopServerRefresh( void )
{
	int count;

	if (!uiInfo.serverStatus.refreshActive) {
		// not currently refreshing
		return;
	}
	uiInfo.serverStatus.refreshActive = qfalse;
	Com_Printf("%d servers listed in browser with %d players.\n",
					uiInfo.serverStatus.numDisplayServers,
					uiInfo.serverStatus.numPlayersOnServers);
	count = trap_LAN_GetServerCount(ui_netSource.integer);
	if (count - uiInfo.serverStatus.numDisplayServers > 0) {
		Com_Printf("%d servers not listed due to packet loss or pings higher than %d\n",
						count - uiInfo.serverStatus.numDisplayServers,
						(int) trap_Cvar_VariableValue("cl_maxPing"));
	}

}

/*
=================
ArenaServers_MaxPing
=================
*/
#ifndef MISSIONPACK // bk001206
static int ArenaServers_MaxPing( void ) {
	int		maxPing;

	maxPing = (int)trap_Cvar_VariableValue( "cl_maxPing" );
	if( maxPing < 100 ) {
		maxPing = 100;
	}
	return maxPing;
}
#endif

/*
=================
UI_DoServerRefresh
=================
*/
static void UI_DoServerRefresh( void )
{
	qboolean wait = qfalse;

	if (!uiInfo.serverStatus.refreshActive) {
		return;
	}
	if (ui_netSource.integer != AS_FAVORITES) {
		if (ui_netSource.integer == AS_LOCAL) {
			if (!trap_LAN_GetServerCount(ui_netSource.integer)) {
				wait = qtrue;
			}
		} else {
			if (trap_LAN_GetServerCount(ui_netSource.integer) < 0) {
				wait = qtrue;
			}
		}
	}

	if (uiInfo.uiDC.realTime < uiInfo.serverStatus.refreshtime) {
		if (wait) {
			return;
		}
	}

	// if still trying to retrieve pings
	if (trap_LAN_UpdateVisiblePings(ui_netSource.integer)) {
		uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000;
	} else if (!wait) {
		// get the last servers in the list
		UI_BuildServerDisplayList(2);
		// stop the refresh
		UI_StopServerRefresh();
	}
	//
	UI_BuildServerDisplayList(qfalse);
}

/*
=================
UI_StartServerRefresh
=================
*/
static void UI_StartServerRefresh ( qboolean full )
{
	char*	ptr;
	qtime_t q;

	trap_RealTime(&q);
 	trap_Cvar_Set( va("ui_lastServerRefresh_%i", ui_netSource.integer), va("%s-%i, %i at %i:%i", MonthAbbrev[q.tm_mon],q.tm_mday, 1900+q.tm_year,q.tm_hour,q.tm_min));

	if (!full) 
	{
		UI_UpdatePendingPings();
		return;
	}

	uiInfo.serverStatus.refreshActive = qtrue;
	uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 1000;

	// clear number of displayed servers
	uiInfo.serverStatus.numDisplayServers = 0;
	uiInfo.serverStatus.numPlayersOnServers = 0;

	// mark all servers as visible so we store ping updates for them
	trap_LAN_MarkServerVisible(ui_netSource.integer, -1, qtrue);
	
	// reset all the pings
	trap_LAN_ResetPings(ui_netSource.integer);

	if( ui_netSource.integer == AS_LOCAL ) 
	{
		trap_Cmd_ExecuteText( EXEC_NOW, "localservers\n" );
		uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000;
		return;
	}

	uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 5000;
	if( ui_netSource.integer == AS_GLOBAL  ) 
	{
		ptr = UI_Cvar_VariableString("debug_protocol");
		if (strlen(ptr)) 
		{
			trap_Cmd_ExecuteText( EXEC_NOW, va( "globalservers 0 %s\n", ptr));
		}
		else 
		{
			trap_Cmd_ExecuteText( EXEC_NOW, va( "globalservers 0 %d\n", (int)trap_Cvar_VariableValue( "protocol" ) ) );
		}
	}
}

