summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
authorPaweł Redman <trem.redman@gmail.com>2013-09-06 22:40:51 +0200
committerPaweł Redman <trem.redman@gmail.com>2013-09-06 22:40:51 +0200
commit5a85e81685300e2299dabfeb25d513b99df471be (patch)
tree45c3e342a9af062528c6c32b695629a65eede91b /src/ui
Initial commit
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/ui_atoms.c295
-rw-r--r--src/ui/ui_gameinfo.c377
-rw-r--r--src/ui/ui_local.h400
-rw-r--r--src/ui/ui_main.c4653
-rw-r--r--src/ui/ui_public.h205
-rw-r--r--src/ui/ui_shared.c8083
-rw-r--r--src/ui/ui_shared.h559
-rw-r--r--src/ui/ui_syscalls.asm102
-rw-r--r--src/ui/ui_syscalls.c483
9 files changed, 15157 insertions, 0 deletions
diff --git a/src/ui/ui_atoms.c b/src/ui/ui_atoms.c
new file mode 100644
index 0000000..af3a3ef
--- /dev/null
+++ b/src/ui/ui_atoms.c
@@ -0,0 +1,295 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/**********************************************************************
+ UI_ATOMS.C
+
+ User interface building blocks and support functions.
+**********************************************************************/
+#include "ui_local.h"
+
+qboolean m_entersound; // after a frame, so caching won't disrupt the sound
+
+void QDECL Com_Error( int level, const char *error, ... )
+{
+ va_list argptr;
+ char text[1024];
+
+ va_start( argptr, error );
+ Q_vsnprintf( text, sizeof( text ), error, argptr );
+ va_end( argptr );
+
+ trap_Error( text );
+}
+
+void QDECL Com_Printf( const char *msg, ... )
+{
+ va_list argptr;
+ char text[1024];
+
+ va_start( argptr, msg );
+ Q_vsnprintf( text, sizeof( text ), msg, argptr );
+ va_end( argptr );
+
+ trap_Print( text );
+}
+
+
+/*
+=================
+UI_ClampCvar
+=================
+*/
+float UI_ClampCvar( float min, float max, float value )
+{
+ if( value < min ) return min;
+
+ if( value > max ) return max;
+
+ return value;
+}
+
+/*
+=================
+UI_StartDemoLoop
+=================
+*/
+void UI_StartDemoLoop( void )
+{
+ trap_Cmd_ExecuteText( EXEC_APPEND, "d1\n" );
+}
+
+char *UI_Argv( int arg )
+{
+ static char buffer[MAX_STRING_CHARS];
+
+ trap_Argv( arg, buffer, sizeof( buffer ) );
+
+ return buffer;
+}
+
+char *UI_ConcatArgs( int arg, char *buf, int len )
+{
+ char *p;
+ int c;
+
+ if( len <= 0 )
+ return buf;
+
+ p = buf;
+ c = trap_Argc();
+
+ for( ; arg < c; arg++ )
+ {
+ char *argp = UI_Argv( arg );
+
+ while( *argp && p < &buf[ len - 1 ] )
+ *p++ = *argp++;
+
+ if( p < &buf[ len - 2 ] )
+ *p++ = ' ';
+ else
+ break;
+ }
+
+ *p = '\0';
+
+ return buf;
+}
+
+char *UI_Cvar_VariableString( const char *var_name )
+{
+ static char buffer[MAX_STRING_CHARS];
+
+ trap_Cvar_VariableStringBuffer( var_name, buffer, sizeof( buffer ) );
+
+ return buffer;
+}
+
+static void UI_Cache_f( void )
+{
+ Display_CacheAll();
+}
+
+static void UI_Menu_f( void )
+{
+ if( Menu_Count( ) > 0 )
+ {
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ Menus_ActivateByName( UI_Argv( 1 ) );
+ }
+}
+
+static void UI_CloseMenus_f( void )
+{
+ if( Menu_Count( ) > 0 )
+ {
+ trap_Key_SetCatcher( trap_Key_GetCatcher( ) & ~KEYCATCH_UI );
+ trap_Key_ClearStates( );
+ trap_Cvar_Set( "cl_paused", "0" );
+ Menus_CloseAll( );
+ }
+}
+
+static void UI_MessageMode_f( void )
+{
+ char *arg = UI_Argv( 0 );
+
+ trap_Cvar_Set( "ui_sayBuffer", "" );
+
+ switch( arg[ 11 ] )
+ {
+ default:
+ case '\0':
+ // Global
+ uiInfo.chatTeam = qfalse;
+ break;
+
+ case '2':
+ // Team
+ uiInfo.chatTeam = qtrue;
+ break;
+ }
+
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ Menus_CloseByName( "say" );
+ Menus_CloseByName( "say_team" );
+
+ if( uiInfo.chatTeam )
+ Menus_ActivateByName( "say_team" );
+ else
+ Menus_ActivateByName( "say" );
+}
+
+static void UI_Me_f( void )
+{
+ char buf[ MAX_SAY_TEXT - 4 ];
+
+ UI_ConcatArgs( 1, buf, sizeof( buf ) );
+
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "say \"/me %s\"", buf ) );
+}
+
+struct uicmd
+{
+ char *cmd;
+ void ( *function )( void );
+} commands[ ] = {
+ { "closemenus", UI_CloseMenus_f },
+ { "me", UI_Me_f },
+ { "menu", UI_Menu_f },
+ { "messagemode", UI_MessageMode_f },
+ { "messagemode2", UI_MessageMode_f },
+ { "ui_cache", UI_Cache_f },
+ { "ui_load", UI_Load },
+ { "ui_report", UI_Report }
+};
+
+/*
+=================
+UI_ConsoleCommand
+=================
+*/
+qboolean UI_ConsoleCommand( int realTime )
+{
+ struct uicmd *cmd = bsearch( UI_Argv( 0 ), commands,
+ sizeof( commands ) / sizeof( commands[ 0 ] ), sizeof( commands[ 0 ] ),
+ cmdcmp );
+
+ uiInfo.uiDC.frameTime = realTime - uiInfo.uiDC.realTime;
+ uiInfo.uiDC.realTime = realTime;
+
+ if( cmd )
+ {
+ cmd->function( );
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+void UI_DrawNamedPic( float x, float y, float width, float height, const char *picname )
+{
+ qhandle_t hShader;
+
+ hShader = trap_R_RegisterShaderNoMip( picname );
+ UI_AdjustFrom640( &x, &y, &width, &height );
+ trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader );
+}
+
+void UI_DrawHandlePic( float x, float y, float w, float h, qhandle_t hShader )
+{
+ float s0;
+ float s1;
+ float t0;
+ float t1;
+
+ if( w < 0 )
+ { // flip about vertical
+ w = -w;
+ s0 = 1;
+ s1 = 0;
+ }
+ else
+ {
+ s0 = 0;
+ s1 = 1;
+ }
+
+ if( h < 0 )
+ { // flip about horizontal
+ h = -h;
+ t0 = 1;
+ t1 = 0;
+ }
+ else
+ {
+ t0 = 0;
+ t1 = 1;
+ }
+
+ UI_AdjustFrom640( &x, &y, &w, &h );
+ trap_R_DrawStretchPic( x, y, w, h, s0, t0, s1, t1, hShader );
+}
+
+/*
+================
+UI_FillRect
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void UI_FillRect( float x, float y, float width, float height, const float *color )
+{
+ trap_R_SetColor( color );
+
+ UI_AdjustFrom640( &x, &y, &width, &height );
+ trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+
+ trap_R_SetColor( NULL );
+}
+
+void UI_SetColor( const float *rgba )
+{
+ trap_R_SetColor( rgba );
+}
diff --git a/src/ui/ui_gameinfo.c b/src/ui/ui_gameinfo.c
new file mode 100644
index 0000000..18a604b
--- /dev/null
+++ b/src/ui/ui_gameinfo.c
@@ -0,0 +1,377 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+//
+// gameinfo.c
+//
+
+#include "ui_local.h"
+
+
+//
+// arena and bot info
+//
+
+
+int ui_numBots;
+static char *ui_botInfos[MAX_BOTS];
+
+static int ui_numArenas;
+static char *ui_arenaInfos[MAX_ARENAS];
+
+/*
+===============
+UI_ParseInfos
+===============
+*/
+int UI_ParseInfos( char *buf, int max, char *infos[] )
+{
+ char * token;
+ int count;
+ char key[MAX_TOKEN_CHARS];
+ char info[MAX_INFO_STRING];
+
+ count = 0;
+
+ while( 1 )
+ {
+ token = COM_Parse( &buf );
+
+ if( !token[0] )
+ break;
+
+ if( strcmp( token, "{" ) )
+ {
+ Com_Printf( "Missing { in info file\n" );
+ break;
+ }
+
+ if( count == max )
+ {
+ Com_Printf( "Max infos exceeded\n" );
+ break;
+ }
+
+ info[0] = '\0';
+
+ while( 1 )
+ {
+ token = COM_ParseExt( &buf, qtrue );
+
+ if( !token[0] )
+ {
+ Com_Printf( "Unexpected end of info file\n" );
+ break;
+ }
+
+ if( !strcmp( token, "}" ) )
+ break;
+
+ Q_strncpyz( key, token, sizeof( key ) );
+
+ token = COM_ParseExt( &buf, qfalse );
+
+ if( !token[0] )
+ strcpy( token, "<NULL>" );
+
+ Info_SetValueForKey( info, key, token );
+ }
+
+ //NOTE: extra space for arena number
+ infos[count] = UI_Alloc( strlen( info ) + strlen( "\\num\\" ) + strlen( va( "%d", MAX_ARENAS ) ) + 1 );
+
+ if( infos[count] )
+ {
+ strcpy( infos[count], info );
+ count++;
+ }
+ }
+
+ return count;
+}
+
+/*
+===============
+UI_LoadArenasFromFile
+===============
+*/
+static void UI_LoadArenasFromFile( char *filename )
+{
+ int len;
+ fileHandle_t f;
+ char buf[MAX_ARENAS_TEXT];
+
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+
+ if( !f )
+ {
+ trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) );
+ return;
+ }
+
+ if( len >= MAX_ARENAS_TEXT )
+ {
+ trap_Print( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) );
+ trap_FS_FCloseFile( f );
+ return;
+ }
+
+ trap_FS_Read( buf, len, f );
+ buf[len] = 0;
+ trap_FS_FCloseFile( f );
+
+ ui_numArenas += UI_ParseInfos( buf, MAX_ARENAS - ui_numArenas, &ui_arenaInfos[ui_numArenas] );
+}
+
+/*
+=================
+UI_MapNameCompare
+=================
+*/
+static int UI_MapNameCompare( const void *a, const void *b )
+{
+ mapInfo * A = ( mapInfo * )a;
+ mapInfo *B = ( mapInfo * )b;
+
+ return Q_stricmp( A->mapName, B->mapName );
+}
+
+/*
+===============
+UI_LoadArenas
+===============
+*/
+void UI_LoadArenas( void )
+{
+ int numdirs;
+ char filename[128];
+ char dirlist[1024];
+ char* dirptr;
+ int i, n;
+ int dirlen;
+ char *type;
+
+ ui_numArenas = 0;
+ uiInfo.mapCount = 0;
+
+ // get all arenas from .arena files
+ numdirs = trap_FS_GetFileList( "scripts", ".arena", dirlist, 1024 );
+ dirptr = dirlist;
+
+ for( i = 0; i < numdirs; i++, dirptr += dirlen + 1 )
+ {
+ dirlen = strlen( dirptr );
+ strcpy( filename, "scripts/" );
+ strcat( filename, dirptr );
+ UI_LoadArenasFromFile( filename );
+ }
+
+ trap_Print( va( "[skipnotify]%i arenas parsed\n", ui_numArenas ) );
+
+ if( UI_OutOfMemory() )
+ trap_Print( S_COLOR_YELLOW"WARNING: not anough memory in pool to load all arenas\n" );
+
+ for( n = 0; n < ui_numArenas; n++ )
+ {
+ // determine type
+ type = Info_ValueForKey( ui_arenaInfos[ n ], "type" );
+ // if no type specified, it will be treated as "ffa"
+
+ uiInfo.mapList[uiInfo.mapCount].cinematic = -1;
+ uiInfo.mapList[uiInfo.mapCount].mapLoadName = String_Alloc( Info_ValueForKey( ui_arenaInfos[n], "map" ) );
+ uiInfo.mapList[uiInfo.mapCount].mapName = String_Alloc( Info_ValueForKey( ui_arenaInfos[n], "longname" ) );
+ uiInfo.mapList[uiInfo.mapCount].levelShot = -1;
+ uiInfo.mapList[uiInfo.mapCount].imageName = String_Alloc( va( "levelshots/%s", uiInfo.mapList[uiInfo.mapCount].mapLoadName ) );
+
+ uiInfo.mapCount++;
+
+ if( uiInfo.mapCount >= MAX_MAPS )
+ break;
+ }
+
+ qsort( uiInfo.mapList, uiInfo.mapCount, sizeof( mapInfo ), UI_MapNameCompare );
+}
+
+
+/*
+===============
+UI_LoadBotsFromFile
+===============
+*/
+static void UI_LoadBotsFromFile( char *filename )
+{
+ int len;
+ fileHandle_t f;
+ char buf[MAX_BOTS_TEXT];
+
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+
+ if( !f )
+ {
+ trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) );
+ return;
+ }
+
+ if( len >= MAX_BOTS_TEXT )
+ {
+ trap_Print( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) );
+ trap_FS_FCloseFile( f );
+ return;
+ }
+
+ trap_FS_Read( buf, len, f );
+ buf[len] = 0;
+ trap_FS_FCloseFile( f );
+
+ COM_Compress( buf );
+
+ ui_numBots += UI_ParseInfos( buf, MAX_BOTS - ui_numBots, &ui_botInfos[ui_numBots] );
+}
+
+/*
+===============
+UI_LoadBots
+===============
+*/
+void UI_LoadBots( void )
+{
+ vmCvar_t botsFile;
+ int numdirs;
+ char filename[128];
+ char dirlist[1024];
+ char* dirptr;
+ int i;
+ int dirlen;
+
+ ui_numBots = 0;
+
+ trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT | CVAR_ROM );
+
+ if( *botsFile.string )
+ UI_LoadBotsFromFile( botsFile.string );
+ else
+ UI_LoadBotsFromFile( "scripts/bots.txt" );
+
+ // get all bots from .bot files
+ numdirs = trap_FS_GetFileList( "scripts", ".bot", dirlist, 1024 );
+
+ dirptr = dirlist;
+
+ for( i = 0; i < numdirs; i++, dirptr += dirlen + 1 )
+ {
+ dirlen = strlen( dirptr );
+ strcpy( filename, "scripts/" );
+ strcat( filename, dirptr );
+ UI_LoadBotsFromFile( filename );
+ }
+
+ trap_Print( va( "%i bots parsed\n", ui_numBots ) );
+}
+
+
+/*
+===============
+UI_GetBotInfoByNumber
+===============
+*/
+char *UI_GetBotInfoByNumber( int num )
+{
+ if( num < 0 || num >= ui_numBots )
+ {
+ trap_Print( va( S_COLOR_RED "Invalid bot number: %i\n", num ) );
+ return NULL;
+ }
+
+ return ui_botInfos[num];
+}
+
+
+/*
+===============
+UI_GetBotInfoByName
+===============
+*/
+char *UI_GetBotInfoByName( const char *name )
+{
+ int n;
+ char *value;
+
+ for( n = 0; n < ui_numBots ; n++ )
+ {
+ value = Info_ValueForKey( ui_botInfos[n], "name" );
+
+ if( !Q_stricmp( value, name ) )
+ return ui_botInfos[n];
+ }
+
+ return NULL;
+}
+
+int UI_GetNumBots( void )
+{
+ return ui_numBots;
+}
+
+
+char *UI_GetBotNameByNumber( int num )
+{
+ char * info = UI_GetBotInfoByNumber( num );
+
+ if( info )
+ return Info_ValueForKey( info, "name" );
+
+ return "";
+}
+
+void UI_ServerInfo( void )
+{
+ char info[ MAX_INFO_VALUE ];
+ char hostname[MAX_HOSTNAME_LENGTH];
+
+ info[0] = '\0';
+
+ if( trap_GetConfigString( CS_SERVERINFO, info, sizeof( info ) ) )
+ {
+ trap_Cvar_Set( "ui_serverinfo_mapname",
+ Info_ValueForKey( info, "mapname" ) );
+ trap_Cvar_Set( "ui_serverinfo_timelimit",
+ Info_ValueForKey( info, "timelimit" ) );
+ trap_Cvar_Set( "ui_serverinfo_sd",
+ Info_ValueForKey( info, "g_suddenDeathTime" ) );
+ UI_EscapeEmoticons( hostname, Info_ValueForKey( info, "sv_hostname" ),
+ sizeof( hostname ) );
+ trap_Cvar_Set( "ui_serverinfo_hostname", hostname );
+ trap_Cvar_Set( "ui_serverinfo_maxclients",
+ Info_ValueForKey( info, "sv_maxclients" ) );
+ trap_Cvar_Set( "ui_serverinfo_version",
+ Info_ValueForKey( info, "version" ) );
+ trap_Cvar_Set( "ui_serverinfo_unlagged",
+ Info_ValueForKey( info, "g_unlagged" ) );
+ trap_Cvar_Set( "ui_serverinfo_friendlyFire",
+ Info_ValueForKey( info, "g_friendlyFire" ) );
+ trap_Cvar_Set( "ui_serverinfo_friendlyBuildableFire",
+ Info_ValueForKey( info, "g_friendlyBuildableFire" ) );
+ trap_Cvar_Set( "ui_serverinfo_allowdl",
+ Info_ValueForKey( info, "sv_allowdownload" ) );
+ }
+}
diff --git a/src/ui/ui_local.h b/src/ui/ui_local.h
new file mode 100644
index 0000000..2c78e26
--- /dev/null
+++ b/src/ui/ui_local.h
@@ -0,0 +1,400 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#ifndef UI_LOCAL_H
+#define UI_LOCAL_H
+
+#include "../qcommon/q_shared.h"
+#include "../renderer/tr_types.h"
+#include "ui_public.h"
+#include "../client/keycodes.h"
+#include "../game/bg_public.h"
+#include "ui_shared.h"
+
+//
+// ui_main.c
+//
+void UI_Report( void );
+void UI_Load( void );
+void UI_LoadMenus( const char *menuFile, qboolean reset );
+int UI_AdjustTimeByGame( int time );
+void UI_ClearScores( void );
+void UI_LoadArenas( void );
+void UI_ServerInfo( void );
+void UI_UpdateNews( qboolean );
+
+void UI_RegisterCvars( void );
+void UI_UpdateCvars( void );
+void UI_DrawConnectScreen( qboolean overlay );
+
+// new ui stuff
+#define MAX_MAPS 128
+#define MAX_PINGREQUESTS 32
+#define MAX_ADDRESSLENGTH 64
+#define MAX_DISPLAY_SERVERS 2048
+#define MAX_SERVERSTATUS_LINES 128
+#define MAX_SERVERSTATUS_TEXT 1024
+#define MAX_NEWS_LINES 50
+#define MAX_NEWS_LINEWIDTH 85
+#define MAX_FOUNDPLAYER_SERVERS 16
+#define MAX_MODS 64
+#define MAX_DEMOS 256
+#define MAX_MOVIES 256
+#define MAX_HELP_INFOPANES 32
+#define MAX_RESOLUTIONS 32
+
+typedef struct
+{
+ const char *mapName;
+ const char *mapLoadName;
+ const char *imageName;
+ int cinematic;
+ qhandle_t levelShot;
+}
+mapInfo;
+
+typedef struct serverFilter_s
+{
+ const char *description;
+ const char *basedir;
+}
+serverFilter_t;
+
+typedef struct
+{
+ char adrstr[MAX_ADDRESSLENGTH];
+ int start;
+}
+pinglist_t;
+
+
+typedef struct serverStatus_s
+{
+ pinglist_t pingList[MAX_PINGREQUESTS];
+ int numqueriedservers;
+ int currentping;
+ int nextpingtime;
+ int maxservers;
+ int refreshtime;
+ int numServers;
+ int sortKey;
+ int sortDir;
+ qboolean sorted;
+ int lastCount;
+ qboolean refreshActive;
+ int currentServer;
+ int displayServers[MAX_DISPLAY_SERVERS];
+ int numDisplayServers;
+ int numPlayersOnServers;
+ int nextDisplayRefresh;
+ int nextSortTime;
+ qhandle_t currentServerPreview;
+ int currentServerCinematic;
+ int motdLen;
+ int motdWidth;
+ int motdPaintX;
+ int motdPaintX2;
+ int motdOffset;
+ int motdTime;
+ char motd[MAX_STRING_CHARS];
+}
+serverStatus_t;
+
+
+typedef struct
+{
+ char adrstr[MAX_ADDRESSLENGTH];
+ char name[MAX_ADDRESSLENGTH];
+ int startTime;
+ int serverNum;
+ qboolean valid;
+}
+pendingServer_t;
+
+typedef struct
+{
+ int num;
+ pendingServer_t server[MAX_SERVERSTATUSREQUESTS];
+}
+pendingServerStatus_t;
+
+typedef struct
+{
+ char address[MAX_ADDRESSLENGTH];
+ char *lines[MAX_SERVERSTATUS_LINES][4];
+ char text[MAX_SERVERSTATUS_TEXT];
+ char pings[MAX_CLIENTS * 3];
+ int numLines;
+}
+serverStatusInfo_t;
+
+typedef struct
+{
+ char text[MAX_NEWS_LINES][MAX_NEWS_LINEWIDTH];
+ int numLines;
+ qboolean refreshActive;
+ int refreshtime;
+}
+newsInfo_t;
+
+typedef struct
+{
+ const char *modName;
+ const char *modDescr;
+}
+modInfo_t;
+
+typedef enum
+{
+ INFOTYPE_TEXT,
+ INFOTYPE_BUILDABLE,
+ INFOTYPE_CLASS,
+ INFOTYPE_WEAPON,
+ INFOTYPE_UPGRADE
+} infoType_t;
+
+typedef struct
+{
+ const char *text;
+ const char *cmd;
+ infoType_t type;
+ union
+ {
+ const char *text;
+ buildable_t buildable;
+ class_t pclass;
+ weapon_t weapon;
+ upgrade_t upgrade;
+ } v;
+}
+menuItem_t;
+
+typedef struct
+{
+ int w;
+ int h;
+}
+resolution_t;
+
+typedef struct
+{
+ displayContextDef_t uiDC;
+
+ int playerCount;
+ int myTeamCount;
+ int teamPlayerIndex;
+ int playerRefresh;
+ int playerIndex;
+ int playerNumber;
+ int myPlayerIndex;
+ int ignoreIndex;
+ char playerNames[MAX_CLIENTS][MAX_NAME_LENGTH];
+ char rawPlayerNames[MAX_CLIENTS][MAX_NAME_LENGTH];
+ char teamNames[MAX_CLIENTS][MAX_NAME_LENGTH];
+ char rawTeamNames[MAX_CLIENTS][MAX_NAME_LENGTH];
+ int clientNums[MAX_CLIENTS];
+ int teamClientNums[MAX_CLIENTS];
+ clientList_t ignoreList[MAX_CLIENTS];
+
+ int mapCount;
+ mapInfo mapList[MAX_MAPS];
+
+ modInfo_t modList[MAX_MODS];
+ int modCount;
+ int modIndex;
+
+ const char *demoList[MAX_DEMOS];
+ int demoCount;
+ int demoIndex;
+
+ const char *movieList[MAX_MOVIES];
+ int movieCount;
+ int movieIndex;
+ int previewMovie;
+
+ menuItem_t teamList[ 4 ];
+ int teamCount;
+ int teamIndex;
+
+ menuItem_t alienClassList[ 3 ];
+ int alienClassCount;
+ int alienClassIndex;
+
+ menuItem_t humanItemList[ 3 ];
+ int humanItemCount;
+ int humanItemIndex;
+
+ menuItem_t humanArmouryBuyList[ 32 ];
+ int humanArmouryBuyCount;
+ int humanArmouryBuyIndex;
+
+ menuItem_t humanArmourySellList[ 32 ];
+ int humanArmourySellCount;
+ int humanArmourySellIndex;
+
+ menuItem_t alienUpgradeList[ 16 ];
+ int alienUpgradeCount;
+ int alienUpgradeIndex;
+
+ menuItem_t alienBuildList[ 32 ];
+ int alienBuildCount;
+ int alienBuildIndex;
+
+ menuItem_t humanBuildList[ 32 ];
+ int humanBuildCount;
+ int humanBuildIndex;
+
+ menuItem_t helpList[ MAX_HELP_INFOPANES ];
+ int helpCount;
+ int helpIndex;
+
+ int weapons;
+ int upgrades;
+
+ serverStatus_t serverStatus;
+
+ // for showing the game news window
+ newsInfo_t newsInfo;
+
+ // for the showing the status of a server
+ char serverStatusAddress[MAX_ADDRESSLENGTH];
+ serverStatusInfo_t serverStatusInfo;
+ int nextServerStatusRefresh;
+
+ // to retrieve the status of server to find a player
+ pendingServerStatus_t pendingServerStatus;
+ char findPlayerName[MAX_STRING_CHARS];
+ char foundPlayerServerAddresses[MAX_FOUNDPLAYER_SERVERS][MAX_ADDRESSLENGTH];
+ char foundPlayerServerNames[MAX_FOUNDPLAYER_SERVERS][MAX_ADDRESSLENGTH];
+ int currentFoundPlayerServer;
+ int numFoundPlayerServers;
+ int nextFindPlayerRefresh;
+
+ resolution_t resolutions[ MAX_RESOLUTIONS ];
+ int numResolutions;
+ int resolutionIndex;
+
+ qboolean inGameLoad;
+
+ qboolean chatTeam;
+}
+uiInfo_t;
+
+extern uiInfo_t uiInfo;
+
+
+qboolean UI_ConsoleCommand( int realTime );
+char *UI_Cvar_VariableString( const char *var_name );
+void UI_SetColor( const float *rgba );
+void UI_AdjustFrom640( float *x, float *y, float *w, float *h );
+void UI_Refresh( int time );
+void UI_DrawHandlePic( float x, float y, float w, float h, qhandle_t hShader );
+void UI_FillRect( float x, float y, float width, float height, const float *color );
+
+//
+// ui_syscalls.c
+//
+void trap_Print( const char *string );
+void trap_Error( const char *string );
+int trap_Milliseconds( void );
+void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags );
+void trap_Cvar_Update( vmCvar_t *vmCvar );
+void trap_Cvar_Set( const char *var_name, const char *value );
+float trap_Cvar_VariableValue( const char *var_name );
+void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize );
+void trap_Cvar_SetValue( const char *var_name, float value );
+void trap_Cvar_Reset( const char *name );
+void trap_Cvar_Create( const char *var_name, const char *var_value, int flags );
+void trap_Cvar_InfoStringBuffer( int bit, char *buffer, int bufsize );
+int trap_Argc( void );
+void trap_Argv( int n, char *buffer, int bufferLength );
+void trap_Cmd_ExecuteText( int exec_when, const char *text ); // don't use EXEC_NOW!
+int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode );
+void trap_FS_Read( void *buffer, int len, fileHandle_t f );
+void trap_FS_Write( const void *buffer, int len, fileHandle_t f );
+void trap_FS_FCloseFile( fileHandle_t f );
+int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize );
+int trap_FS_Seek( fileHandle_t f, long offset, int origin ); // fsOrigin_t
+qhandle_t trap_R_RegisterModel( const char *name );
+qhandle_t trap_R_RegisterSkin( const char *name );
+qhandle_t trap_R_RegisterShaderNoMip( const char *name );
+void trap_R_ClearScene( void );
+void trap_R_AddRefEntityToScene( const refEntity_t *re );
+void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts );
+void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b );
+void trap_R_RenderScene( const refdef_t *fd );
+void trap_R_SetColor( const float *rgba );
+void trap_R_SetClipRegion( const float *region );
+void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader );
+void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs );
+void trap_UpdateScreen( void );
+int trap_CM_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName );
+void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum );
+sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed );
+void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen );
+void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen );
+void trap_Key_SetBinding( int keynum, const char *binding );
+qboolean trap_Key_IsDown( int keynum );
+qboolean trap_Key_GetOverstrikeMode( void );
+void trap_Key_SetOverstrikeMode( qboolean state );
+void trap_Key_ClearStates( void );
+int trap_Key_GetCatcher( void );
+void trap_Key_SetCatcher( int catcher );
+void trap_GetClipboardData( char *buf, int bufsize );
+void trap_GetClientState( uiClientState_t *state );
+void trap_GetGlconfig( glconfig_t *glconfig );
+int trap_GetConfigString( int index, char* buff, int buffsize );
+int trap_LAN_GetServerCount( int source );
+void trap_LAN_GetServerAddressString( int source, int n, char *buf, int buflen );
+void trap_LAN_GetServerInfo( int source, int n, char *buf, int buflen );
+int trap_LAN_GetServerPing( int source, int n );
+int trap_LAN_GetPingQueueCount( void );
+void trap_LAN_ClearPing( int n );
+void trap_LAN_GetPing( int n, char *buf, int buflen, int *pingtime );
+void trap_LAN_GetPingInfo( int n, char *buf, int buflen );
+void trap_LAN_LoadCachedServers( void );
+void trap_LAN_SaveCachedServers( void );
+void trap_LAN_MarkServerVisible( int source, int n, qboolean visible );
+int trap_LAN_ServerIsVisible( int source, int n );
+qboolean trap_LAN_UpdateVisiblePings( int source );
+int trap_LAN_AddServer( int source, const char *name, const char *addr );
+void trap_LAN_RemoveServer( int source, const char *addr );
+void trap_LAN_ResetPings( int n );
+int trap_LAN_ServerStatus( const char *serverAddress, char *serverStatus, int maxLen );
+qboolean trap_GetNews( qboolean force );
+int trap_LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 );
+int trap_MemoryRemaining( void );
+void trap_R_RegisterFont( const char *pFontname, int pointSize, fontInfo_t *font );
+void trap_S_StopBackgroundTrack( void );
+void trap_S_StartBackgroundTrack( const char *intro, const char *loop );
+int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits );
+e_status trap_CIN_StopCinematic( int handle );
+e_status trap_CIN_RunCinematic ( int handle );
+void trap_CIN_DrawCinematic ( int handle );
+void trap_CIN_SetExtents ( int handle, int x, int y, int w, int h );
+int trap_RealTime( qtime_t *qtime );
+void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset );
+
+void trap_SetPbClStatus( int status );
+
+#endif
diff --git a/src/ui/ui_main.c b/src/ui/ui_main.c
new file mode 100644
index 0000000..6566d5e
--- /dev/null
+++ b/src/ui/ui_main.c
@@ -0,0 +1,4653 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*
+=======================================================================
+
+USER INTERFACE MAIN
+
+=======================================================================
+*/
+
+#include "ui_local.h"
+
+uiInfo_t uiInfo;
+
+static const char *MonthAbbrev[ ] =
+{
+ "Jan", "Feb", "Mar",
+ "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep",
+ "Oct", "Nov", "Dec"
+};
+
+static const char *netSources[ ] =
+{
+ "Internet",
+ "Mplayer",
+ "LAN",
+ "Favorites"
+};
+
+static const int numNetSources = sizeof( netSources ) / sizeof( const char* );
+
+static const char *netnames[ ] =
+{
+ "???",
+ "UDP",
+ "IPX",
+ NULL
+};
+
+/*
+================
+cvars
+================
+*/
+
+typedef struct
+{
+ vmCvar_t *vmCvar;
+ char *cvarName;
+ char *defaultString;
+ int cvarFlags;
+}
+
+cvarTable_t;
+
+vmCvar_t ui_browserShowFull;
+vmCvar_t ui_browserShowEmpty;
+
+vmCvar_t ui_dedicated;
+vmCvar_t ui_netSource;
+vmCvar_t ui_selectedMap;
+vmCvar_t ui_lastServerRefresh_0;
+vmCvar_t ui_lastServerRefresh_1;
+vmCvar_t ui_lastServerRefresh_2;
+vmCvar_t ui_lastServerRefresh_3;
+vmCvar_t ui_lastServerRefresh_0_time;
+vmCvar_t ui_lastServerRefresh_1_time;
+vmCvar_t ui_lastServerRefresh_2_time;
+vmCvar_t ui_lastServerRefresh_3_time;
+vmCvar_t ui_smallFont;
+vmCvar_t ui_bigFont;
+vmCvar_t ui_findPlayer;
+vmCvar_t ui_serverStatusTimeOut;
+vmCvar_t ui_textWrapCache;
+vmCvar_t ui_developer;
+
+vmCvar_t ui_emoticons;
+vmCvar_t ui_winner;
+vmCvar_t ui_chatCommands;
+
+static cvarTable_t cvarTable[ ] =
+{
+ { &ui_browserShowFull, "ui_browserShowFull", "1", CVAR_ARCHIVE },
+ { &ui_browserShowEmpty, "ui_browserShowEmpty", "1", CVAR_ARCHIVE },
+
+ { &ui_dedicated, "ui_dedicated", "0", CVAR_ARCHIVE },
+ { &ui_netSource, "ui_netSource", "0", CVAR_ARCHIVE },
+ { &ui_selectedMap, "ui_selectedMap", "0", 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_lastServerRefresh_0, "ui_lastServerRefresh_0_time", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_1, "ui_lastServerRefresh_1_time", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_2, "ui_lastServerRefresh_2_time", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_3, "ui_lastServerRefresh_3_time", "", CVAR_ARCHIVE},
+ { &ui_smallFont, "ui_smallFont", "0.2", CVAR_ARCHIVE | CVAR_LATCH },
+ { &ui_bigFont, "ui_bigFont", "0.5", CVAR_ARCHIVE | CVAR_LATCH },
+ { &ui_findPlayer, "ui_findPlayer", "", CVAR_ARCHIVE},
+ { &ui_serverStatusTimeOut, "ui_serverStatusTimeOut", "7000", CVAR_ARCHIVE},
+ { &ui_textWrapCache, "ui_textWrapCache", "1", CVAR_ARCHIVE },
+ { &ui_developer, "ui_developer", "0", CVAR_ARCHIVE | CVAR_CHEAT },
+ { &ui_emoticons, "cg_emoticons", "1", CVAR_LATCH | CVAR_ARCHIVE },
+ { &ui_winner, "ui_winner", "", CVAR_ROM },
+ { &ui_chatCommands, "ui_chatCommands", "1", CVAR_ARCHIVE }
+};
+
+static int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] );
+
+/*
+================
+vmMain
+
+This is the only way control passes into the module.
+This must be the very first function compiled into the .qvm file
+================
+*/
+void UI_Init( qboolean );
+void UI_Shutdown( void );
+void UI_KeyEvent( int key, qboolean down );
+void UI_MouseEvent( int dx, int dy );
+int UI_MousePosition( void );
+void UI_SetMousePosition( int x, int y );
+void UI_Refresh( int realtime );
+qboolean UI_IsFullscreen( void );
+void UI_SetActiveMenu( uiMenuCommand_t menu );
+Q_EXPORT intptr_t 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:
+ UI_KeyEvent( arg0, arg1 );
+ return 0;
+
+ case UI_MOUSE_EVENT:
+ UI_MouseEvent( arg0, arg1 );
+ return 0;
+
+ case UI_MOUSE_POSITION:
+ return UI_MousePosition( );
+
+ case UI_SET_MOUSE_POSITION:
+ UI_SetMousePosition( arg0, arg1 );
+ return 0;
+
+ case UI_REFRESH:
+ UI_Refresh( arg0 );
+ return 0;
+
+ case UI_IS_FULLSCREEN:
+ return UI_IsFullscreen( );
+
+ case UI_SET_ACTIVE_MENU:
+ UI_SetActiveMenu( arg0 );
+ return 0;
+
+ case UI_CONSOLE_COMMAND:
+ return UI_ConsoleCommand( arg0 );
+
+ case UI_DRAW_CONNECT_SCREEN:
+ UI_DrawConnectScreen( arg0 );
+ return 0;
+ }
+
+ return -1;
+}
+
+
+
+void AssetCache( void )
+{
+ int i;
+
+ uiInfo.uiDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR );
+ uiInfo.uiDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR );
+ 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 );
+
+ if( ui_emoticons.integer )
+ {
+ uiInfo.uiDC.Assets.emoticonCount = BG_LoadEmoticons(
+ uiInfo.uiDC.Assets.emoticons, MAX_EMOTICONS );
+ }
+ else
+ uiInfo.uiDC.Assets.emoticonCount = 0;
+
+ for( i = 0; i < uiInfo.uiDC.Assets.emoticonCount; i++ )
+ {
+ uiInfo.uiDC.Assets.emoticons[ i ].shader = trap_R_RegisterShaderNoMip(
+ va( "emoticons/%s_%dx1.tga", uiInfo.uiDC.Assets.emoticons[ i ].name,
+ uiInfo.uiDC.Assets.emoticons[ i ].width ) );
+ }
+}
+
+void UI_DrawSides( float x, float y, float w, float h, float size )
+{
+ float sizeY;
+
+ UI_AdjustFrom640( &x, &y, &w, &h );
+ sizeY = size * uiInfo.uiDC.yscale;
+ size *= uiInfo.uiDC.xscale;
+
+ trap_R_DrawStretchPic( x, y + sizeY, size, h - ( sizeY * 2.0f ), 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+ trap_R_DrawStretchPic( x + w - size, y + sizeY, size, h - ( sizeY * 2.0f ), 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+}
+
+void UI_DrawTopBottom( 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, uiInfo.uiDC.whiteShader );
+ trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, 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_DrawTopBottom( x, y, width, height, size );
+ UI_DrawSides( x, y, width, height, size );
+
+ trap_R_SetColor( NULL );
+}
+
+/*
+==================
+UI_ServerInfoIsValid
+
+Return false if the infostring contains nonprinting characters,
+ or if the hostname is blank/undefined
+==================
+*/
+static qboolean UI_ServerInfoIsValid( char *info )
+{
+ char *c;
+ int len = 0;
+
+ for( c = info; *c; c++ )
+ {
+ if( !isprint( *c ) )
+ return qfalse;
+ }
+
+ for( c = Info_ValueForKey( info, "hostname" ); *c; c++ )
+ {
+ if( isgraph( *c ) )
+ len++;
+ }
+
+ if( len )
+ return qtrue;
+ else
+ return qfalse;
+}
+
+/*
+==================
+UI_InsertServerIntoDisplayList
+==================
+*/
+static void UI_InsertServerIntoDisplayList( int num, int position )
+{
+ int i;
+ static char info[MAX_STRING_CHARS];
+
+ if( position < 0 || position > uiInfo.serverStatus.numDisplayServers )
+ return;
+
+ trap_LAN_GetServerInfo( ui_netSource.integer, num, info, MAX_STRING_CHARS );
+
+ if( !UI_ServerInfoIsValid( info ) ) // don't list servers with invalid info
+ 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;
+ static char info[MAX_STRING_CHARS];
+
+ for( i = 0; i < uiInfo.serverStatus.numDisplayServers; i++ )
+ {
+ if( uiInfo.serverStatus.displayServers[i] == num )
+ {
+ uiInfo.serverStatus.numDisplayServers--;
+
+ trap_LAN_GetServerInfo( ui_netSource.integer, num, info, MAX_STRING_CHARS );
+
+ 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 );
+}
+
+typedef struct
+{
+ char *name, *altName;
+}
+
+serverStatusCvar_t;
+
+serverStatusCvar_t serverStatusCvars[] = {
+ {"sv_hostname", "Name"},
+ {"Address", ""},
+ {"gamename", "Game name"},
+ {"mapname", "Map"},
+ {"version", ""},
+ {"protocol", ""},
+ {"timelimit", ""},
+ {NULL, NULL}
+ };
+
+/*
+==================
+UI_SortServerStatusInfo
+==================
+*/
+
+static int UI_SortServerStatusCompare( const void *a, const void *b )
+{
+ const char **la = (const char **)a;
+ const char **lb = (const char **)b;
+
+ return strcmp( la[0], lb[0] );
+}
+
+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++;
+ }
+ }
+ }
+
+ // sort remaining cvars
+ qsort( info->lines + index,
+ info->numLines - index, sizeof( info->lines[ 0 ] ),
+ UI_SortServerStatusCompare );
+}
+
+/*
+==================
+UI_GetServerStatusInfo
+==================
+*/
+static int UI_GetServerStatusInfo( const char *serverAddress, serverStatusInfo_t *info )
+{
+ char *p, *score, *ping, *name;
+ 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;
+ }
+
+ UI_SortServerStatusInfo( info );
+
+ // 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';
+
+ 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++;
+ }
+ }
+
+ 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_FeederSelection( int feederID, int index );
+
+static void UI_BuildFindPlayerList( qboolean force )
+{
+ static int numFound, numTimeOuts;
+ int i, j, k, resend;
+ serverStatusInfo_t info;
+ char name[MAX_NAME_LENGTH+2];
+ char infoString[MAX_STRING_CHARS];
+ qboolean duplicate;
+
+ 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 );
+
+ duplicate = qfalse;
+
+ for( k = 0; k < uiInfo.numFoundPlayerServers - 1; k++ )
+ {
+ if( Q_strncmp( uiInfo.foundPlayerServerAddresses[ k ],
+ uiInfo.pendingServerStatus.server[ i ].adrstr,
+ MAX_ADDRESSLENGTH ) == 0 )
+ duplicate = qtrue;
+ }
+
+ // if the player name is a substring
+ if( stristr( name, uiInfo.findPlayerName ) && !duplicate )
+ {
+ // 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...", numFound, uiInfo.pendingServerStatus.num );
+ // 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...", numFound, uiInfo.pendingServerStatus.num );
+ }
+ }
+ }
+
+ 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 with player %s", uiInfo.numFoundPlayerServers - 1,
+ uiInfo.numFoundPlayerServers == 2 ? "" : "s", uiInfo.findPlayerName );
+ }
+
+ 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_BuildServerDisplayList
+==================
+*/
+static void UI_BuildServerDisplayList( qboolean force )
+{
+ int i, count, clients, maxClients, ping, len, visible;
+ char info[MAX_STRING_CHARS];
+ 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 != 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;
+ }
+ }
+
+ // 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;
+ }
+}
+
+
+/*
+=================
+UI_StopServerRefresh
+=================
+*/
+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, invalid info,"
+ " or pings higher than %d\n",
+ count - uiInfo.serverStatus.numDisplayServers,
+ ( int ) trap_Cvar_VariableValue( "cl_maxPing" ) );
+ }
+
+}
+
+/*
+=================
+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_UpdatePendingPings
+=================
+*/
+static void UI_UpdatePendingPings( void )
+{
+ trap_LAN_ResetPings( ui_netSource.integer );
+ uiInfo.serverStatus.refreshActive = qtrue;
+ uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000;
+
+}
+
+/*
+=================
+UI_StartServerRefresh
+=================
+*/
+static void UI_StartServerRefresh( qboolean full )
+{
+ int time;
+ qtime_t q;
+
+ time = trap_RealTime( &q );
+ trap_Cvar_Set( va( "ui_lastServerRefresh_%i_time", ui_netSource.integer ),
+ va( "%i", time ) );
+ trap_Cvar_Set( va( "ui_lastServerRefresh_%i", ui_netSource.integer ),
+ va( "%s-%i, %i at %i:%02i", 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_APPEND, "localservers\n" );
+ uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000;
+ return;
+ }
+
+ uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 5000;
+
+ if( ui_netSource.integer == AS_GLOBAL || ui_netSource.integer == AS_MPLAYER )
+ {
+ qboolean global = ui_netSource.integer == AS_GLOBAL;
+
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "globalservers %d 70 full empty\n",
+ global ? 0 : 1 ) );
+ }
+}
+
+int frameCount = 0;
+int startTime;
+
+#define UI_FPS_FRAMES 4
+void UI_Refresh( int realtime )
+{
+ static int index;
+ static int previousTimes[UI_FPS_FRAMES];
+
+ //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;
+ }
+
+ UI_UpdateCvars();
+
+ if( Menu_Count() > 0 )
+ {
+ Menu_UpdateAll( );
+ Menu_PaintAll( );
+ UI_DoServerRefresh( );
+ UI_BuildServerStatus( qfalse );
+ UI_BuildFindPlayerList( qfalse );
+ UI_UpdateNews( qfalse );
+ }
+
+ // draw cursor
+ UI_SetColor( NULL );
+
+ if( trap_Key_GetCatcher( ) == KEYCATCH_UI && !trap_Cvar_VariableValue( "ui_hideCursor" ) )
+ {
+ UI_DrawHandlePic( uiInfo.uiDC.cursorx - ( 16.0f * uiInfo.uiDC.aspectScale ), uiInfo.uiDC.cursory - 16.0f,
+ 32.0f * uiInfo.uiDC.aspectScale, 32.0f, uiInfo.uiDC.Assets.cursor );
+ }
+}
+
+/*
+=================
+UI_Shutdown
+=================
+*/
+void UI_Shutdown( void )
+{
+ trap_LAN_SaveCachedServers();
+}
+
+qboolean Asset_Parse( int handle )
+{
+ pc_token_t token;
+ const char *tempStr;
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ if( Q_stricmp( token.string, "{" ) != 0 )
+ return qfalse;
+
+ while( 1 )
+ {
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ if( Q_stricmp( token.string, "}" ) == 0 )
+ return qtrue;
+
+ // font
+ if( Q_stricmp( token.string, "font" ) == 0 )
+ {
+ int pointSize;
+
+ if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) )
+ return qfalse;
+
+ trap_R_RegisterFont( tempStr, pointSize, &uiInfo.uiDC.Assets.textFont );
+ uiInfo.uiDC.Assets.fontRegistered = qtrue;
+ continue;
+ }
+
+ if( Q_stricmp( token.string, "smallFont" ) == 0 )
+ {
+ int pointSize;
+
+ if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) )
+ return qfalse;
+
+ trap_R_RegisterFont( tempStr, pointSize, &uiInfo.uiDC.Assets.smallFont );
+ continue;
+ }
+
+ if( Q_stricmp( token.string, "bigFont" ) == 0 )
+ {
+ int pointSize;
+
+ if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) )
+ return qfalse;
+
+ trap_R_RegisterFont( tempStr, pointSize, &uiInfo.uiDC.Assets.bigFont );
+ continue;
+ }
+
+
+ // gradientbar
+ if( Q_stricmp( token.string, "gradientbar" ) == 0 )
+ {
+ if( !PC_String_Parse( handle, &tempStr ) )
+ return qfalse;
+
+ uiInfo.uiDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( tempStr );
+ continue;
+ }
+
+ // enterMenuSound
+ if( Q_stricmp( token.string, "menuEnterSound" ) == 0 )
+ {
+ if( !PC_String_Parse( handle, &tempStr ) )
+ return qfalse;
+
+ uiInfo.uiDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr, qfalse );
+ continue;
+ }
+
+ // exitMenuSound
+ if( Q_stricmp( token.string, "menuExitSound" ) == 0 )
+ {
+ if( !PC_String_Parse( handle, &tempStr ) )
+ return qfalse;
+
+ uiInfo.uiDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr, qfalse );
+ 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, qfalse );
+ continue;
+ }
+
+ // menuBuzzSound
+ if( Q_stricmp( token.string, "menuBuzzSound" ) == 0 )
+ {
+ if( !PC_String_Parse( handle, &tempStr ) )
+ return qfalse;
+
+ uiInfo.uiDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr, qfalse );
+ continue;
+ }
+
+ if( Q_stricmp( token.string, "cursor" ) == 0 )
+ {
+ if( !PC_String_Parse( handle, &uiInfo.uiDC.Assets.cursorStr ) )
+ return qfalse;
+
+ uiInfo.uiDC.Assets.cursor = trap_R_RegisterShaderNoMip( uiInfo.uiDC.Assets.cursorStr );
+ 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;
+ }
+
+ }
+
+ return qfalse;
+}
+
+void UI_Report( void )
+{
+ String_Report();
+}
+
+void UI_ParseMenu( const char *menuFile )
+{
+ int handle;
+ pc_token_t token;
+
+ handle = trap_Parse_LoadSource( menuFile );
+
+ if( !handle )
+ {
+ Com_Printf( S_COLOR_YELLOW "WARNING: Menu file %s not found\n",
+ menuFile );
+ return;
+ }
+
+ while( 1 )
+ {
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ //if( Q_stricmp( token, "{" ) ) {
+ // Com_Printf( "Missing { in menu file\n" );
+ // break;
+ //}
+
+ //if( menuCount == MAX_MENUS ) {
+ // Com_Printf( "Too many menus!\n" );
+ // 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_Parse_FreeSource( handle );
+}
+
+qboolean Load_Menu( int handle )
+{
+ pc_token_t token;
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ if( token.string[0] != '{' )
+ return qfalse;
+
+ while( 1 )
+ {
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ if( token.string[0] == 0 )
+ return qfalse;
+
+ if( token.string[0] == '}' )
+ return qtrue;
+
+ UI_ParseMenu( token.string );
+ }
+
+ return qfalse;
+}
+
+void UI_LoadMenus( const char *menuFile, qboolean reset )
+{
+ pc_token_t token;
+ int handle;
+ int start;
+
+ start = trap_Milliseconds();
+
+ handle = trap_Parse_LoadSource( menuFile );
+
+ if( !handle )
+ trap_Error( va( S_COLOR_RED "menu list '%s' not found, unable to continue!\n", menuFile ) );
+
+ if( reset )
+ Menu_Reset();
+
+ while( 1 )
+ {
+ if( !trap_Parse_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 ) )
+ continue;
+ else
+ break;
+ }
+ }
+
+ Com_Printf( "UI menu file '%s' loaded in %d msec\n", menuFile, trap_Milliseconds() - start );
+
+ trap_Parse_FreeSource( handle );
+}
+
+void UI_LoadHelp( const char *helpFile )
+{
+ pc_token_t token;
+ int handle, start;
+ char title[ 32 ], buffer[ 1024 ];
+
+ start = trap_Milliseconds();
+
+ handle = trap_Parse_LoadSource( helpFile );
+ if( !handle )
+ {
+ Com_Printf( S_COLOR_YELLOW "WARNING: help file '%s' not found!\n",
+ helpFile );
+ return;
+ }
+
+ if( !trap_Parse_ReadToken( handle, &token ) ||
+ token.string[0] == 0 || token.string[0] != '{' )
+ {
+ Com_Printf( S_COLOR_YELLOW "WARNING: help file '%s' does not start with "
+ "'{'\n", helpFile );
+ return;
+ }
+
+ uiInfo.helpCount = 0;
+ title[ 0 ] = 0;
+ while( 1 )
+ {
+ if( !trap_Parse_ReadToken( handle, &token ) ||
+ token.string[0] == 0 || token.string[0] == '}' )
+ break;
+
+ if( token.string[0] == '{' )
+ {
+ buffer[ 0 ] = 0;
+ Q_strcat( buffer, sizeof( buffer ), title );
+ Q_strcat( buffer, sizeof( buffer ), "\n\n" );
+ while( trap_Parse_ReadToken( handle, &token ) &&
+ token.string[0] != 0 && token.string[0] != '}' )
+ {
+ Q_strcat( buffer, sizeof( buffer ), token.string );
+ }
+
+ uiInfo.helpList[ uiInfo.helpCount ].text = String_Alloc( title );
+ uiInfo.helpList[ uiInfo.helpCount ].v.text = String_Alloc( buffer );
+ uiInfo.helpList[ uiInfo.helpCount ].type = INFOTYPE_TEXT;
+ uiInfo.helpCount++;
+ title[ 0 ] = 0;
+ }
+ else
+ Q_strcat( title, sizeof( title ), token.string );
+ }
+
+ trap_Parse_FreeSource( handle );
+
+ Com_Printf( "UI help file '%s' loaded in %d msec (%d infopanes)\n",
+ helpFile, trap_Milliseconds() - start, uiInfo.helpCount );
+}
+
+void UI_Load( void )
+{
+ char lastName[1024];
+ menuDef_t *menu = Menu_GetFocused();
+
+ if( menu && menu->window.name )
+ strcpy( lastName, menu->window.name );
+
+ String_Init();
+
+ UI_LoadMenus( "ui/menus.txt", qtrue );
+ UI_LoadMenus( "ui/ingame.txt", qfalse );
+ UI_LoadMenus( "ui/tremulous.txt", qfalse );
+ UI_LoadHelp( "ui/help.txt" );
+ Menus_CloseAll( );
+ Menus_ActivateByName( lastName );
+
+}
+
+/*
+===============
+UI_GetCurrentAlienStage
+===============
+*/
+static stage_t UI_GetCurrentAlienStage( void )
+{
+ char buffer[ MAX_TOKEN_CHARS ];
+ stage_t stage, dummy;
+
+ trap_Cvar_VariableStringBuffer( "ui_stages", buffer, sizeof( buffer ) );
+ sscanf( buffer, "%d %d", ( int * ) & stage , ( int * ) & dummy );
+
+ return stage;
+}
+
+/*
+===============
+UI_GetCurrentHumanStage
+===============
+*/
+static stage_t UI_GetCurrentHumanStage( void )
+{
+ char buffer[ MAX_TOKEN_CHARS ];
+ stage_t stage, dummy;
+
+ trap_Cvar_VariableStringBuffer( "ui_stages", buffer, sizeof( buffer ) );
+ sscanf( buffer, "%d %d", ( int * ) & dummy, ( int * ) & stage );
+
+ return stage;
+}
+
+/*
+===============
+UI_DrawInfoPane
+===============
+*/
+static void UI_DrawInfoPane( menuItem_t *item, rectDef_t *rect, float text_x, float text_y,
+ float scale, int textalign, int textvalign, vec4_t color, int textStyle )
+{
+ int value = 0;
+ const char *s = "";
+ char *string = "";
+
+ int class, credits;
+ char ui_currentClass[ MAX_STRING_CHARS ];
+
+ trap_Cvar_VariableStringBuffer( "ui_currentClass", ui_currentClass, MAX_STRING_CHARS );
+
+ sscanf( ui_currentClass, "%d %d", &class, &credits );
+
+ switch( item->type )
+ {
+ case INFOTYPE_TEXT:
+ s = item->v.text;
+ break;
+
+ case INFOTYPE_CLASS:
+ value = ( BG_ClassCanEvolveFromTo( class, item->v.pclass, credits,
+ UI_GetCurrentAlienStage(), 0 ) +
+ ALIEN_CREDITS_PER_KILL - 1 ) / ALIEN_CREDITS_PER_KILL;
+
+ if( value < 1 )
+ {
+ s = va( "%s\n\n%s",
+ BG_ClassConfig( item->v.pclass )->humanName,
+ BG_Class( item->v.pclass )->info );
+ }
+ else
+ {
+ s = va( "%s\n\n%s\n\nFrags: %d",
+ BG_ClassConfig( item->v.pclass )->humanName,
+ BG_Class( item->v.pclass )->info,
+ value );
+ }
+
+ break;
+
+ case INFOTYPE_WEAPON:
+ value = BG_Weapon( item->v.weapon )->price;
+
+ if( value == 0 )
+ {
+ s = va( "%s\n\n%s\n\nCredits: Free",
+ BG_Weapon( item->v.weapon )->humanName,
+ BG_Weapon( item->v.weapon )->info );
+ }
+ else
+ {
+ s = va( "%s\n\n%s\n\nCredits: %d",
+ BG_Weapon( item->v.weapon )->humanName,
+ BG_Weapon( item->v.weapon )->info,
+ value );
+ }
+
+ break;
+
+ case INFOTYPE_UPGRADE:
+ value = BG_Upgrade( item->v.upgrade )->price;
+
+ if( value == 0 )
+ {
+ s = va( "%s\n\n%s\n\nCredits: Free",
+ BG_Upgrade( item->v.upgrade )->humanName,
+ BG_Upgrade( item->v.upgrade )->info );
+ }
+ else
+ {
+ s = va( "%s\n\n%s\n\nCredits: %d",
+ BG_Upgrade( item->v.upgrade )->humanName,
+ BG_Upgrade( item->v.upgrade )->info,
+ value );
+ }
+
+ break;
+
+ case INFOTYPE_BUILDABLE:
+ value = BG_Buildable( item->v.buildable, NULL )->buildPoints;
+
+ switch( BG_Buildable( item->v.buildable, NULL )->team )
+ {
+ case TEAM_ALIENS:
+ string = "Sentience";
+ break;
+
+ case TEAM_HUMANS:
+ string = "Power";
+ break;
+
+ default:
+ break;
+ }
+
+ if( value == 0 )
+ {
+ s = va( "%s\n\n%s",
+ BG_Buildable( item->v.buildable, NULL )->humanName,
+ BG_Buildable( item->v.buildable, NULL )->info );
+ }
+ else
+ {
+ s = va( "%s\n\n%s\n\n%s: %d",
+ BG_Buildable( item->v.buildable, NULL )->humanName,
+ BG_Buildable( item->v.buildable, NULL )->info,
+ string, value );
+ }
+
+ break;
+ }
+
+ UI_DrawTextBlock( rect, text_x, text_y, color, scale,
+ textalign, textvalign, textStyle, s );
+}
+
+
+static void UI_DrawServerMapPreview( rectDef_t *rect, float scale, vec4_t color )
+{
+ 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 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/2d/load_screen" ) );
+}
+
+static void UI_DrawSelectedMapPreview( rectDef_t *rect, float scale, vec4_t color )
+{
+ int map = ui_selectedMap.integer;
+
+ if( map < 0 || map > uiInfo.mapCount )
+ {
+ ui_selectedMap.integer = 0;
+ trap_Cvar_Set( "ui_selectedMap", "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
+ {
+ 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/2d/load_screen" ) );
+ }
+}
+
+static void UI_DrawSelectedMapName( rectDef_t *rect, float scale, vec4_t color, int textStyle )
+{
+ int map = ui_selectedMap.integer;
+
+ if( map >= 0 && map < uiInfo.mapCount )
+ UI_Text_Paint( rect->x, rect->y, scale, color, uiInfo.mapList[map].mapName, 0, 0, textStyle );
+}
+
+static const char *UI_OwnerDrawText( int ownerDraw )
+{
+ const char *s = NULL;
+
+ switch( ownerDraw )
+ {
+ case UI_NETSOURCE:
+ if( ui_netSource.integer < 0 || ui_netSource.integer >= numNetSources )
+ ui_netSource.integer = 0;
+
+ s = netSources[ui_netSource.integer];
+ break;
+
+ case UI_KEYBINDSTATUS:
+ if( Display_KeyBindPending() )
+ s = "Waiting for new key... Press ESCAPE to cancel";
+ else
+ s = "Press ENTER or CLICK to change, Press BACKSPACE to clear";
+
+ break;
+
+ case UI_SERVERREFRESHDATE:
+ if( uiInfo.serverStatus.refreshActive )
+ {
+#define MAX_DOTS 5
+ int numServers = trap_LAN_GetServerCount( ui_netSource.integer );
+ int numDots = ( uiInfo.uiDC.realTime / 500 ) % ( MAX_DOTS + 1 );
+ char dots[ MAX_DOTS + 1 ];
+ int i;
+
+ for( i = 0; i < numDots; i++ )
+ dots[ i ] = '.';
+
+ dots[ i ] = '\0';
+
+ s = numServers < 0 ? va( "Waiting for response%s", dots ) :
+ va( "Getting info for %d servers (ESC to cancel)%s", numServers, dots );
+ }
+ else
+ s = va( "Refresh Time: %s", UI_Cvar_VariableString( va( "ui_lastServerRefresh_%i", ui_netSource.integer ) ) );
+
+ break;
+
+ case UI_SERVERMOTD:
+ s = uiInfo.serverStatus.motd;
+ break;
+
+ default:
+ break;
+ }
+
+ return s;
+}
+
+static int UI_OwnerDrawWidth( int ownerDraw, float scale )
+{
+ const char *s = NULL;
+
+ switch( ownerDraw )
+ {
+ case UI_NETSOURCE:
+ case UI_KEYBINDSTATUS:
+ case UI_SERVERREFRESHDATE:
+ case UI_SERVERMOTD:
+ s = UI_OwnerDrawText( ownerDraw );
+ break;
+
+ default:
+ break;
+ }
+
+ if( s )
+ return UI_Text_Width( s, scale );
+
+ return 0;
+}
+
+/*
+===============
+UI_BuildPlayerList
+===============
+*/
+static void UI_BuildPlayerList( void )
+{
+ 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;
+ 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;
+ uiInfo.myPlayerIndex = 0;
+ playerTeamNumber = 0;
+
+ for( n = 0; n < count; n++ )
+ {
+ trap_GetConfigString( CS_PLAYERS + n, info, MAX_INFO_STRING );
+
+ if( info[0] )
+ {
+ Com_ClientListParse( &uiInfo.ignoreList[ uiInfo.playerCount ],
+ Info_ValueForKey( info, "ig" ) );
+ Q_strncpyz( uiInfo.rawPlayerNames[uiInfo.playerCount],
+ Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH );
+ Q_strncpyz( uiInfo.playerNames[uiInfo.playerCount],
+ Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH );
+ Q_CleanStr( uiInfo.playerNames[uiInfo.playerCount] );
+ uiInfo.clientNums[uiInfo.playerCount] = n;
+
+ if( n == uiInfo.playerNumber )
+ uiInfo.myPlayerIndex = uiInfo.playerCount;
+
+ uiInfo.playerCount++;
+
+ team2 = atoi( Info_ValueForKey( info, "t" ) );
+
+ if( team2 == team )
+ {
+ Q_strncpyz( uiInfo.rawTeamNames[uiInfo.myTeamCount],
+ Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH );
+ 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++;
+ }
+ }
+ }
+}
+
+static void UI_DrawGLInfo( rectDef_t *rect, float scale, int textalign, int textvalign,
+ vec4_t color, int textStyle, float text_x, float text_y )
+{
+ char buffer[ 4096 ];
+
+ Com_sprintf( buffer, sizeof( buffer ), "VENDOR: %s\nVERSION: %s\n"
+ "PIXELFORMAT: color(%d-bits) Z(%d-bits) stencil(%d-bits)\n%s",
+ uiInfo.uiDC.glconfig.vendor_string, uiInfo.uiDC.glconfig.renderer_string,
+ uiInfo.uiDC.glconfig.colorBits, uiInfo.uiDC.glconfig.depthBits,
+ uiInfo.uiDC.glconfig.stencilBits, uiInfo.uiDC.glconfig.extensions_string );
+
+ UI_DrawTextBlock( rect, text_x, text_y, color, scale,
+ textalign, textvalign, textStyle, buffer );
+}
+
+// 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,
+ int textalign, int textvalign, float borderSize,
+ float scale, vec4_t foreColor, vec4_t backColor,
+ qhandle_t shader,
+ int textStyle )
+{
+ rectDef_t rect;
+
+ rect.x = x;
+ rect.y = y;
+ rect.w = w;
+ rect.h = h;
+
+ switch( ownerDraw )
+ {
+ case UI_TEAMINFOPANE:
+ UI_DrawInfoPane( &uiInfo.teamList[ uiInfo.teamIndex ],
+ &rect, text_x, text_y, scale, textalign, textvalign, foreColor, textStyle );
+ break;
+
+ case UI_ACLASSINFOPANE:
+ UI_DrawInfoPane( &uiInfo.alienClassList[ uiInfo.alienClassIndex ],
+ &rect, text_x, text_y, scale, textalign, textvalign, foreColor, textStyle );
+ break;
+
+ case UI_AUPGRADEINFOPANE:
+ UI_DrawInfoPane( &uiInfo.alienUpgradeList[ uiInfo.alienUpgradeIndex ],
+ &rect, text_x, text_y, scale, textalign, textvalign, foreColor, textStyle );
+ break;
+
+ case UI_HITEMINFOPANE:
+ UI_DrawInfoPane( &uiInfo.humanItemList[ uiInfo.humanItemIndex ],
+ &rect, text_x, text_y, scale, textalign, textvalign, foreColor, textStyle );
+ break;
+
+ case UI_HBUYINFOPANE:
+ UI_DrawInfoPane( &uiInfo.humanArmouryBuyList[ uiInfo.humanArmouryBuyIndex ],
+ &rect, text_x, text_y, scale, textalign, textvalign, foreColor, textStyle );
+ break;
+
+ case UI_HSELLINFOPANE:
+ UI_DrawInfoPane( &uiInfo.humanArmourySellList[ uiInfo.humanArmourySellIndex ],
+ &rect, text_x, text_y, scale, textalign, textvalign, foreColor, textStyle );
+ break;
+
+ case UI_ABUILDINFOPANE:
+ UI_DrawInfoPane( &uiInfo.alienBuildList[ uiInfo.alienBuildIndex ],
+ &rect, text_x, text_y, scale, textalign, textvalign, foreColor, textStyle );
+ break;
+
+ case UI_HBUILDINFOPANE:
+ UI_DrawInfoPane( &uiInfo.humanBuildList[ uiInfo.humanBuildIndex ],
+ &rect, text_x, text_y, scale, textalign, textvalign, foreColor, textStyle );
+ break;
+
+ case UI_HELPINFOPANE:
+ UI_DrawInfoPane( &uiInfo.helpList[ uiInfo.helpIndex ],
+ &rect, text_x, text_y, scale, textalign, textvalign, foreColor, textStyle );
+ break;
+
+ case UI_NETMAPPREVIEW:
+ UI_DrawServerMapPreview( &rect, scale, foreColor );
+ break;
+
+ case UI_SELECTEDMAPPREVIEW:
+ UI_DrawSelectedMapPreview( &rect, scale, foreColor );
+ break;
+
+ case UI_SELECTEDMAPNAME:
+ UI_DrawSelectedMapName( &rect, scale, foreColor, textStyle );
+ break;
+
+ case UI_GLINFO:
+ UI_DrawGLInfo( &rect, scale, textalign, textvalign, foreColor, textStyle, text_x, text_y );
+ break;
+
+ default:
+ break;
+ }
+
+}
+
+static qboolean UI_OwnerDrawVisible( int flags )
+{
+ qboolean vis = qtrue;
+ uiClientState_t cs;
+ team_t team;
+ char info[ MAX_INFO_STRING ];
+
+ trap_GetClientState( &cs );
+ trap_GetConfigString( CS_PLAYERS + cs.clientNum, info, MAX_INFO_STRING );
+ team = atoi( Info_ValueForKey( info, "t" ) );
+
+
+ while( flags )
+ {
+ if( flags & UI_SHOW_NOTSPECTATING )
+ {
+ if( team == TEAM_NONE )
+ vis = qfalse;
+
+ flags &= ~UI_SHOW_NOTSPECTATING;
+ }
+
+ if( flags & UI_SHOW_VOTEACTIVE )
+ {
+ if( !trap_Cvar_VariableValue( "ui_voteActive" ) )
+ vis = qfalse;
+
+ flags &= ~UI_SHOW_VOTEACTIVE;
+ }
+
+ if( flags & UI_SHOW_CANVOTE )
+ {
+ if( trap_Cvar_VariableValue( "ui_voteActive" ) )
+ vis = qfalse;
+
+ flags &= ~UI_SHOW_CANVOTE;
+ }
+
+ if( flags & UI_SHOW_TEAMVOTEACTIVE )
+ {
+ if( team == TEAM_ALIENS )
+ {
+ if( !trap_Cvar_VariableValue( "ui_alienTeamVoteActive" ) )
+ vis = qfalse;
+ }
+ else if( team == TEAM_HUMANS )
+ {
+ if( !trap_Cvar_VariableValue( "ui_humanTeamVoteActive" ) )
+ vis = qfalse;
+ }
+
+ flags &= ~UI_SHOW_TEAMVOTEACTIVE;
+ }
+
+ if( flags & UI_SHOW_CANTEAMVOTE )
+ {
+ if( team == TEAM_ALIENS )
+ {
+ if( trap_Cvar_VariableValue( "ui_alienTeamVoteActive" ) )
+ vis = qfalse;
+ }
+ else if( team == TEAM_HUMANS )
+ {
+ if( trap_Cvar_VariableValue( "ui_humanTeamVoteActive" ) )
+ vis = qfalse;
+ }
+
+ flags &= ~UI_SHOW_CANTEAMVOTE;
+ }
+
+ 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;
+ }
+ else
+ flags = 0;
+ }
+
+ return vis;
+}
+
+static qboolean UI_NetSource_HandleKey( int key )
+{
+ if( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER )
+ {
+ if( key == K_MOUSE2 )
+ {
+ ui_netSource.integer--;
+
+ if( ui_netSource.integer == AS_MPLAYER )
+ ui_netSource.integer--;
+ }
+ else
+ {
+ ui_netSource.integer++;
+
+ if( ui_netSource.integer == AS_MPLAYER )
+ 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_OwnerDrawHandleKey( int ownerDraw, int key )
+{
+ switch( ownerDraw )
+ {
+ case UI_NETSOURCE:
+ UI_NetSource_HandleKey( key );
+ break;
+
+ default:
+ break;
+ }
+
+ return qfalse;
+}
+
+
+/*
+=================
+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_LoadTeams
+===============
+*/
+static void UI_LoadTeams( void )
+{
+ uiInfo.teamCount = 4;
+
+ uiInfo.teamList[ 0 ].text = "Aliens";
+ uiInfo.teamList[ 0 ].cmd = "cmd team aliens\n";
+ uiInfo.teamList[ 0 ].type = INFOTYPE_TEXT;
+ uiInfo.teamList[ 0 ].v.text =
+ "The Alien Team\n\n"
+ "The Aliens' strengths are in movement and the ability to "
+ "quickly construct new bases quickly. They possess a range "
+ "of abilities including basic melee attacks, movement-"
+ "crippling poisons and more.";
+
+ uiInfo.teamList[ 1 ].text = "Humans";
+ uiInfo.teamList[ 1 ].cmd = "cmd team humans\n";
+ uiInfo.teamList[ 1 ].type = INFOTYPE_TEXT;
+ uiInfo.teamList[ 1 ].v.text =
+ "The Human Team\n\n"
+ "The humans are the masters of technology. Although their "
+ "bases take long to construct, their automated defense "
+ "ensures they stay built. A wide range of upgrades and "
+ "weapons are available to the humans, each contributing "
+ "to eradicate the alien threat.";
+
+ uiInfo.teamList[ 2 ].text = "Spectate";
+ uiInfo.teamList[ 2 ].cmd = "cmd team spectate\n";
+ uiInfo.teamList[ 2 ].type = INFOTYPE_TEXT;
+ uiInfo.teamList[ 2 ].v.text = "Watch the game without playing.";
+
+ uiInfo.teamList[ 3 ].text = "Auto select";
+ uiInfo.teamList[ 3 ].cmd = "cmd team auto\n";
+ uiInfo.teamList[ 3 ].type = INFOTYPE_TEXT;
+ uiInfo.teamList[ 3 ].v.text = "Join the team with the least players.";
+}
+
+/*
+===============
+UI_AddClass
+===============
+*/
+
+static void UI_AddClass( class_t class )
+{
+ uiInfo.alienClassList[ uiInfo.alienClassCount ].text =
+ BG_ClassConfig( class )->humanName;
+ uiInfo.alienClassList[ uiInfo.alienClassCount ].cmd =
+ String_Alloc( va( "cmd class %s\n", BG_Class( class )->name ) );
+ uiInfo.alienClassList[ uiInfo.alienClassCount ].type = INFOTYPE_CLASS;
+
+ uiInfo.alienClassList[ uiInfo.alienClassCount ].v.pclass = class;
+
+ uiInfo.alienClassCount++;
+}
+
+/*
+===============
+UI_LoadAlienClasses
+===============
+*/
+static void UI_LoadAlienClasses( void )
+{
+ uiInfo.alienClassCount = 0;
+
+ if( BG_ClassIsAllowed( PCL_ALIEN_LEVEL0 ) )
+ UI_AddClass( PCL_ALIEN_LEVEL0 );
+
+ if( BG_ClassIsAllowed( PCL_ALIEN_BUILDER0_UPG ) &&
+ BG_ClassAllowedInStage( PCL_ALIEN_BUILDER0_UPG, UI_GetCurrentAlienStage( ) ) )
+ UI_AddClass( PCL_ALIEN_BUILDER0_UPG );
+ else if( BG_ClassIsAllowed( PCL_ALIEN_BUILDER0 ) )
+ UI_AddClass( PCL_ALIEN_BUILDER0 );
+}
+
+/*
+===============
+UI_AddItem
+===============
+*/
+static void UI_AddItem( weapon_t weapon )
+{
+ uiInfo.humanItemList[ uiInfo.humanItemCount ].text =
+ BG_Weapon( weapon )->humanName;
+ uiInfo.humanItemList[ uiInfo.humanItemCount ].cmd =
+ String_Alloc( va( "cmd class %s\n", BG_Weapon( weapon )->name ) );
+ uiInfo.humanItemList[ uiInfo.humanItemCount ].type = INFOTYPE_WEAPON;
+ uiInfo.humanItemList[ uiInfo.humanItemCount ].v.weapon = weapon;
+
+ uiInfo.humanItemCount++;
+}
+
+/*
+===============
+UI_LoadHumanItems
+===============
+*/
+static void UI_LoadHumanItems( void )
+{
+ uiInfo.humanItemCount = 0;
+
+ if( BG_WeaponIsAllowed( WP_MACHINEGUN ) )
+ UI_AddItem( WP_MACHINEGUN );
+
+ if( BG_WeaponIsAllowed( WP_HBUILD ) )
+ UI_AddItem( WP_HBUILD );
+}
+
+/*
+===============
+UI_ParseCarriageList
+===============
+*/
+static void UI_ParseCarriageList( void )
+{
+ int i;
+ char carriageCvar[ MAX_TOKEN_CHARS ];
+ char *iterator;
+ char buffer[ MAX_TOKEN_CHARS ];
+ char *bufPointer;
+
+ trap_Cvar_VariableStringBuffer( "ui_carriage", carriageCvar, sizeof( carriageCvar ) );
+ iterator = carriageCvar;
+
+ uiInfo.weapons = 0;
+ uiInfo.upgrades = 0;
+
+ //simple parser to give rise to weapon/upgrade list
+
+ while( iterator && iterator[ 0 ] != '$' )
+ {
+ bufPointer = buffer;
+
+ if( iterator[ 0 ] == 'W' )
+ {
+ iterator++;
+
+ while( iterator[ 0 ] != ' ' )
+ *bufPointer++ = *iterator++;
+
+ *bufPointer++ = '\n';
+
+ i = atoi( buffer );
+
+ uiInfo.weapons |= ( 1 << i );
+ }
+ else if( iterator[ 0 ] == 'U' )
+ {
+ iterator++;
+
+ while( iterator[ 0 ] != ' ' )
+ *bufPointer++ = *iterator++;
+
+ *bufPointer++ = '\n';
+
+ i = atoi( buffer );
+
+ uiInfo.upgrades |= ( 1 << i );
+ }
+
+ iterator++;
+ }
+}
+
+/*
+===============
+UI_LoadHumanArmouryBuys
+===============
+*/
+static void UI_LoadHumanArmouryBuys( void )
+{
+ int i, j = 0;
+ stage_t stage = UI_GetCurrentHumanStage( );
+ int slots = 0;
+
+ UI_ParseCarriageList( );
+
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( uiInfo.weapons & ( 1 << i ) )
+ slots |= BG_Weapon( i )->slots;
+ }
+
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( uiInfo.upgrades & ( 1 << i ) )
+ slots |= BG_Upgrade( i )->slots;
+ }
+
+ uiInfo.humanArmouryBuyCount = 0;
+
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( BG_Weapon( i )->team == TEAM_HUMANS &&
+ BG_Weapon( i )->purchasable &&
+ BG_WeaponAllowedInStage( i, stage ) &&
+ BG_WeaponIsAllowed( i ) &&
+ !( BG_Weapon( i )->slots & slots ) &&
+ !( uiInfo.weapons & ( 1 << i ) ) )
+ {
+ uiInfo.humanArmouryBuyList[ j ].text = BG_Weapon( i )->humanName;
+ uiInfo.humanArmouryBuyList[ j ].cmd =
+ String_Alloc( va( "cmd buy %s\n", BG_Weapon( i )->name ) );
+ uiInfo.humanArmouryBuyList[ j ].type = INFOTYPE_WEAPON;
+ uiInfo.humanArmouryBuyList[ j ].v.weapon = i;
+
+ j++;
+
+ uiInfo.humanArmouryBuyCount++;
+ }
+ }
+
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( BG_Upgrade( i )->team == TEAM_HUMANS &&
+ BG_Upgrade( i )->purchasable &&
+ BG_UpgradeAllowedInStage( i, stage ) &&
+ BG_UpgradeIsAllowed( i ) &&
+ !( BG_Upgrade( i )->slots & slots ) &&
+ !( uiInfo.upgrades & ( 1 << i ) ) )
+ {
+ uiInfo.humanArmouryBuyList[ j ].text = BG_Upgrade( i )->humanName;
+ uiInfo.humanArmouryBuyList[ j ].cmd =
+ String_Alloc( va( "cmd buy %s\n", BG_Upgrade( i )->name ) );
+ uiInfo.humanArmouryBuyList[ j ].type = INFOTYPE_UPGRADE;
+ uiInfo.humanArmouryBuyList[ j ].v.upgrade = i;
+
+ j++;
+
+ uiInfo.humanArmouryBuyCount++;
+ }
+ }
+}
+
+/*
+===============
+UI_LoadHumanArmourySells
+===============
+*/
+static void UI_LoadHumanArmourySells( void )
+{
+ int i, j = 0;
+
+ uiInfo.humanArmourySellCount = 0;
+ UI_ParseCarriageList( );
+
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( uiInfo.weapons & ( 1 << i ) )
+ {
+ uiInfo.humanArmourySellList[ j ].text = BG_Weapon( i )->humanName;
+ uiInfo.humanArmourySellList[ j ].cmd =
+ String_Alloc( va( "cmd sell %s\n", BG_Weapon( i )->name ) );
+ uiInfo.humanArmourySellList[ j ].type = INFOTYPE_WEAPON;
+ uiInfo.humanArmourySellList[ j ].v.weapon = i;
+
+ j++;
+
+ uiInfo.humanArmourySellCount++;
+ }
+ }
+
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( uiInfo.upgrades & ( 1 << i ) )
+ {
+ uiInfo.humanArmourySellList[ j ].text = BG_Upgrade( i )->humanName;
+ uiInfo.humanArmourySellList[ j ].cmd =
+ String_Alloc( va( "cmd sell %s\n", BG_Upgrade( i )->name ) );
+ uiInfo.humanArmourySellList[ j ].type = INFOTYPE_UPGRADE;
+ uiInfo.humanArmourySellList[ j ].v.upgrade = i;
+
+ j++;
+
+ uiInfo.humanArmourySellCount++;
+ }
+ }
+}
+
+/*
+===============
+UI_ArmouryRefreshCb
+===============
+*/
+static void UI_ArmouryRefreshCb( void *data )
+{
+ int oldWeapons = uiInfo.weapons;
+ int oldUpgrades = uiInfo.upgrades;
+
+ UI_ParseCarriageList( );
+
+ if( uiInfo.weapons != oldWeapons || uiInfo.upgrades != oldUpgrades )
+ {
+ UI_LoadHumanArmouryBuys( );
+ UI_LoadHumanArmourySells( );
+ UI_RemoveCaptureFunc( );
+ }
+}
+
+/*
+===============
+UI_LoadAlienUpgrades
+===============
+*/
+static void UI_LoadAlienUpgrades( void )
+{
+ int i, j = 0;
+
+ int class, credits;
+ char ui_currentClass[ MAX_STRING_CHARS ];
+ stage_t stage = UI_GetCurrentAlienStage( );
+
+ trap_Cvar_VariableStringBuffer( "ui_currentClass", ui_currentClass, MAX_STRING_CHARS );
+
+ sscanf( ui_currentClass, "%d %d", &class, &credits );
+
+ uiInfo.alienUpgradeCount = 0;
+
+ for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ )
+ {
+ if( BG_ClassCanEvolveFromTo( class, i, credits, stage, 0 ) >= 0 )
+ {
+ uiInfo.alienUpgradeList[ j ].text = BG_ClassConfig( i )->humanName;
+ uiInfo.alienUpgradeList[ j ].cmd =
+ String_Alloc( va( "cmd class %s\n", BG_Class( i )->name ) );
+ uiInfo.alienUpgradeList[ j ].type = INFOTYPE_CLASS;
+ uiInfo.alienUpgradeList[ j ].v.pclass = i;
+
+ j++;
+
+ uiInfo.alienUpgradeCount++;
+ }
+ }
+}
+
+/*
+===============
+UI_LoadAlienBuilds
+===============
+*/
+static void UI_LoadAlienBuilds( void )
+{
+ int i, j = 0;
+ stage_t stage;
+
+ UI_ParseCarriageList( );
+ stage = UI_GetCurrentAlienStage( );
+
+ uiInfo.alienBuildCount = 0;
+
+ for( i = BA_NONE + 1; i < BA_NUM_BUILDABLES; i++ )
+ {
+ if( BG_Buildable( i, NULL )->team == TEAM_ALIENS &&
+ BG_Buildable( i, NULL )->buildWeapon & uiInfo.weapons &&
+ BG_BuildableAllowedInStage( i, stage ) &&
+ BG_BuildableIsAllowed( i ) )
+ {
+ uiInfo.alienBuildList[ j ].text = BG_Buildable( i, NULL )->humanName;
+ uiInfo.alienBuildList[ j ].cmd =
+ String_Alloc( va( "cmd build %s\n", BG_Buildable( i, NULL )->name ) );
+ uiInfo.alienBuildList[ j ].type = INFOTYPE_BUILDABLE;
+ uiInfo.alienBuildList[ j ].v.buildable = i;
+
+ j++;
+
+ uiInfo.alienBuildCount++;
+ }
+ }
+}
+
+/*
+===============
+UI_LoadHumanBuilds
+===============
+*/
+static void UI_LoadHumanBuilds( void )
+{
+ int i, j = 0;
+ stage_t stage;
+
+ UI_ParseCarriageList( );
+ stage = UI_GetCurrentHumanStage( );
+
+ uiInfo.humanBuildCount = 0;
+
+ for( i = BA_NONE + 1; i < BA_NUM_BUILDABLES; i++ )
+ {
+ if( BG_Buildable( i, NULL )->team == TEAM_HUMANS &&
+ BG_Buildable( i, NULL )->buildWeapon & uiInfo.weapons &&
+ BG_BuildableAllowedInStage( i, stage ) &&
+ BG_BuildableIsAllowed( i ) )
+ {
+ uiInfo.humanBuildList[ j ].text = BG_Buildable( i, NULL )->humanName;
+ uiInfo.humanBuildList[ j ].cmd =
+ String_Alloc( va( "cmd build %s\n", BG_Buildable( i, NULL )->name ) );
+ uiInfo.humanBuildList[ j ].type = INFOTYPE_BUILDABLE;
+ uiInfo.humanBuildList[ j ].v.buildable = i;
+
+ j++;
+
+ uiInfo.humanBuildCount++;
+ }
+ }
+}
+
+/*
+===============
+UI_LoadMods
+===============
+*/
+static void UI_LoadMods( void )
+{
+ 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( void )
+{
+ 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( void )
+{
+ 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';
+
+ uiInfo.demoList[i] = String_Alloc( demoname );
+ demoname += len + 1;
+ }
+ }
+
+}
+
+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_subdivisions", 4 );
+ trap_Cvar_SetValue( "r_vertexlight", 0 );
+ trap_Cvar_SetValue( "r_lodbias", 0 );
+ trap_Cvar_SetValue( "r_colorbits", 32 );
+ trap_Cvar_SetValue( "r_depthbits", 24 );
+ trap_Cvar_SetValue( "r_picmip", 0 );
+ 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_SetValue( "cg_bounceParticles", 1 );
+ trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" );
+ break;
+
+ case 1: // normal
+ trap_Cvar_SetValue( "r_subdivisions", 12 );
+ trap_Cvar_SetValue( "r_vertexlight", 0 );
+ trap_Cvar_SetValue( "r_lodbias", 0 );
+ trap_Cvar_SetValue( "r_colorbits", 0 );
+ trap_Cvar_SetValue( "r_depthbits", 24 );
+ trap_Cvar_SetValue( "r_picmip", 1 );
+ 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 );
+ trap_Cvar_SetValue( "cg_bounceParticles", 0 );
+ break;
+
+ case 2: // fast
+ trap_Cvar_SetValue( "r_subdivisions", 8 );
+ trap_Cvar_SetValue( "r_vertexlight", 0 );
+ trap_Cvar_SetValue( "r_lodbias", 1 );
+ trap_Cvar_SetValue( "r_colorbits", 0 );
+ trap_Cvar_SetValue( "r_depthbits", 0 );
+ trap_Cvar_SetValue( "r_picmip", 1 );
+ 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_SetValue( "cg_bounceParticles", 0 );
+ trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" );
+ break;
+
+ case 3: // fastest
+ trap_Cvar_SetValue( "r_subdivisions", 20 );
+ trap_Cvar_SetValue( "r_vertexlight", 1 );
+ trap_Cvar_SetValue( "r_lodbias", 2 );
+ trap_Cvar_SetValue( "r_colorbits", 16 );
+ trap_Cvar_SetValue( "r_depthbits", 16 );
+ trap_Cvar_SetValue( "r_picmip", 2 );
+ 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_SetValue( "cg_bounceParticles", 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 );
+ }
+}
+
+//FIXME: lookup table
+static void UI_RunMenuScript( char **args )
+{
+ const char *name, *name2;
+ char buff[1024];
+ const char *cmd;
+
+ if( String_Parse( args, &name ) )
+ {
+ if( Q_stricmp( name, "StartServer" ) == 0 )
+ {
+ trap_Cvar_SetValue( "dedicated", Com_Clamp( 0, 2, ui_dedicated.integer ) );
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "wait ; wait ; map %s\n",
+ uiInfo.mapList[ui_selectedMap.integer].mapLoadName ) );
+ }
+ else if( Q_stricmp( name, "resetDefaults" ) == 0 )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, "exec default.cfg\n" );
+ trap_Cmd_ExecuteText( EXEC_APPEND, "cvar_restart\n" );
+ Controls_SetDefaults();
+ trap_Cvar_Set( "com_introPlayed", "1" );
+ trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart\n" );
+ }
+ else if( Q_stricmp( name, "loadArenas" ) == 0 )
+ {
+ UI_LoadArenas();
+ Menu_SetFeederSelection( NULL, FEEDER_MAPS, 0, "createserver" );
+ }
+ else if( Q_stricmp( name, "loadServerInfo" ) == 0 )
+ UI_ServerInfo();
+ else if( Q_stricmp( name, "getNews" ) == 0 )
+ UI_UpdateNews( qtrue );
+ 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, "downloadIgnore") == 0) {
+ trap_Cvar_Set("com_downloadPrompt", va("%d", DLP_IGNORE));
+ } else if (Q_stricmp(name, "downloadCURL") == 0) {
+ trap_Cvar_Set("com_downloadPrompt", va("%d", DLP_CURL));
+ } else if (Q_stricmp(name, "downloadUDP") == 0) {
+ trap_Cvar_Set("com_downloadPrompt", va("%d", DLP_UDP));
+ } else if (Q_stricmp(name, "RefreshServers") == 0) {
+ UI_StartServerRefresh(qtrue);
+ UI_BuildServerDisplayList(qtrue);
+ } else if (Q_stricmp(name, "InitServerList") == 0) {
+ int time = trap_RealTime( NULL );
+ int last;
+ int sortColumn;
+
+ // set up default sorting
+
+ if( !uiInfo.serverStatus.sorted && Int_Parse( args, &sortColumn ) )
+ {
+ uiInfo.serverStatus.sortKey = sortColumn;
+ uiInfo.serverStatus.sortDir = 0;
+ }
+
+ // refresh if older than 3 days or if list is empty
+ last = atoi( UI_Cvar_VariableString( va( "ui_lastServerRefresh_%i_time",
+ ui_netSource.integer ) ) );
+
+ if( trap_LAN_GetServerCount( ui_netSource.integer ) < 1 ||
+ ( time - last ) > 3600 )
+ {
+ UI_StartServerRefresh( qtrue );
+ UI_BuildServerDisplayList( qtrue );
+ }
+ }
+ else if( Q_stricmp( name, "RefreshFilter" ) == 0 )
+ {
+ UI_StartServerRefresh( qfalse );
+ UI_BuildServerDisplayList( qtrue );
+ }
+ 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, "LoadTeams" ) == 0 )
+ UI_LoadTeams( );
+ else if( Q_stricmp( name, "JoinTeam" ) == 0 )
+ {
+ if( ( cmd = uiInfo.teamList[ uiInfo.teamIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadHumanItems" ) == 0 )
+ UI_LoadHumanItems( );
+ else if( Q_stricmp( name, "SpawnWithHumanItem" ) == 0 )
+ {
+ if( ( cmd = uiInfo.humanItemList[ uiInfo.humanItemIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadAlienClasses" ) == 0 )
+ UI_LoadAlienClasses( );
+ else if( Q_stricmp( name, "SpawnAsAlienClass" ) == 0 )
+ {
+ if( ( cmd = uiInfo.alienClassList[ uiInfo.alienClassIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadHumanArmouryBuys" ) == 0 )
+ UI_LoadHumanArmouryBuys( );
+ else if( Q_stricmp( name, "BuyFromArmoury" ) == 0 )
+ {
+ if( ( cmd = uiInfo.humanArmouryBuyList[ uiInfo.humanArmouryBuyIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+
+ UI_InstallCaptureFunc( UI_ArmouryRefreshCb, NULL, 1000 );
+ }
+ else if( Q_stricmp( name, "LoadHumanArmourySells" ) == 0 )
+ UI_LoadHumanArmourySells( );
+ else if( Q_stricmp( name, "SellToArmoury" ) == 0 )
+ {
+ if( ( cmd = uiInfo.humanArmourySellList[ uiInfo.humanArmourySellIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+
+ UI_InstallCaptureFunc( UI_ArmouryRefreshCb, NULL, 1000 );
+ }
+ else if( Q_stricmp( name, "LoadAlienUpgrades" ) == 0 )
+ {
+ UI_LoadAlienUpgrades( );
+
+ //disallow the menu if it would be empty
+
+ if( uiInfo.alienUpgradeCount <= 0 )
+ Menus_CloseAll( );
+ }
+ else if( Q_stricmp( name, "UpgradeToNewClass" ) == 0 )
+ {
+ if( ( cmd = uiInfo.alienUpgradeList[ uiInfo.alienUpgradeIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadAlienBuilds" ) == 0 )
+ UI_LoadAlienBuilds( );
+ else if( Q_stricmp( name, "BuildAlienBuildable" ) == 0 )
+ {
+ if( ( cmd = uiInfo.alienBuildList[ uiInfo.alienBuildIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadHumanBuilds" ) == 0 )
+ UI_LoadHumanBuilds( );
+ else if( Q_stricmp( name, "BuildHumanBuildable" ) == 0 )
+ {
+ if( ( cmd = uiInfo.humanBuildList[ uiInfo.humanBuildIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "Say" ) == 0 )
+ {
+ char buffer[ MAX_CVAR_VALUE_STRING ];
+ trap_Cvar_VariableStringBuffer( "ui_sayBuffer", buffer, sizeof( buffer ) );
+
+ if( !buffer[ 0 ] )
+ ;
+ else if( ui_chatCommands.integer && ( buffer[ 0 ] == '/' ||
+ buffer[ 0 ] == '\\' ) )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "%s\n", buffer + 1 ) );
+ }
+ else if( uiInfo.chatTeam )
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "say_team \"%s\"\n", buffer ) );
+ else
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "say \"%s\"\n", buffer ) );
+ }
+ else if( Q_stricmp( name, "SayKeydown" ) == 0 )
+ {
+ if( ui_chatCommands.integer )
+ {
+ char buffer[ MAX_CVAR_VALUE_STRING ];
+ trap_Cvar_VariableStringBuffer( "ui_sayBuffer", buffer, sizeof( buffer ) );
+
+ if( buffer[ 0 ] == '/' || buffer[ 0 ] == '\\' )
+ Menus_ReplaceActiveByName( "say_command" );
+ else if( uiInfo.chatTeam )
+ Menus_ReplaceActiveByName( "say_team" );
+ else
+ Menus_ReplaceActiveByName( "say" );
+ }
+ }
+ 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, "Tremulous" ) == 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_ActivateByName( "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 )
+ {
+ 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_APPEND, "quit" );
+ else if( Q_stricmp( name, "Leave" ) == 0 )
+ {
+ 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 );
+
+ uiInfo.serverStatus.sorted = 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_selectedMap.integer >= 0 && ui_selectedMap.integer < uiInfo.mapCount )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote map %s\n",
+ uiInfo.mapList[ui_selectedMap.integer].mapLoadName ) );
+ }
+ }
+ else if( Q_stricmp( name, "voteNextMap" ) == 0 )
+ {
+ if( ui_selectedMap.integer >= 0 && ui_selectedMap.integer < uiInfo.mapCount )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote nextmap %s\n",
+ uiInfo.mapList[ui_selectedMap.integer].mapLoadName ) );
+ }
+ }
+ else if( Q_stricmp( name, "voteKick" ) == 0 )
+ {
+ if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount )
+ {
+ char buffer[ MAX_CVAR_VALUE_STRING ];
+ trap_Cvar_VariableStringBuffer( "ui_reason", buffer, sizeof( buffer ) );
+
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote kick %d %s\n",
+ uiInfo.clientNums[ uiInfo.playerIndex ],
+ buffer ) );
+ trap_Cvar_Set( "ui_reason", "" );
+ }
+ }
+ else if( Q_stricmp( name, "voteMute" ) == 0 )
+ {
+ if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount )
+ {
+ char buffer[ MAX_CVAR_VALUE_STRING ];
+ trap_Cvar_VariableStringBuffer( "ui_reason", buffer, sizeof( buffer ) );
+
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote mute %d %s\n",
+ uiInfo.clientNums[ uiInfo.playerIndex ],
+ buffer ) );
+ trap_Cvar_Set( "ui_reason", "" );
+ }
+ }
+ else if( Q_stricmp( name, "voteUnMute" ) == 0 )
+ {
+ if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote unmute %d\n",
+ uiInfo.clientNums[ uiInfo.playerIndex ] ) );
+ }
+ }
+ else if( Q_stricmp( name, "voteTeamKick" ) == 0 )
+ {
+ if( uiInfo.teamPlayerIndex >= 0 && uiInfo.teamPlayerIndex < uiInfo.myTeamCount )
+ {
+ char buffer[ MAX_CVAR_VALUE_STRING ];
+ trap_Cvar_VariableStringBuffer( "ui_reason", buffer, sizeof( buffer ) );
+
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote kick %d %s\n",
+ uiInfo.teamClientNums[ uiInfo.teamPlayerIndex ],
+ buffer ) );
+ trap_Cvar_Set( "ui_reason", "" );
+ }
+ }
+ else if( Q_stricmp( name, "voteTeamDenyBuild" ) == 0 )
+ {
+ if( uiInfo.teamPlayerIndex >= 0 && uiInfo.teamPlayerIndex < uiInfo.myTeamCount )
+ {
+ char buffer[ MAX_CVAR_VALUE_STRING ];
+ trap_Cvar_VariableStringBuffer( "ui_reason", buffer, sizeof( buffer ) );
+
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote denybuild %d %s\n",
+ uiInfo.teamClientNums[ uiInfo.teamPlayerIndex ],
+ buffer ) );
+ trap_Cvar_Set( "ui_reason", "" );
+ }
+ }
+ else if( Q_stricmp( name, "voteTeamAllowBuild" ) == 0 )
+ {
+ if( uiInfo.teamPlayerIndex >= 0 && uiInfo.teamPlayerIndex < uiInfo.myTeamCount )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote allowbuild %d\n",
+ uiInfo.teamClientNums[ uiInfo.teamPlayerIndex ] ) );
+ }
+ }
+ 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 );
+ }
+ }
+ else if( Q_stricmp( name, "createFavorite" ) == 0 )
+ {
+ if( ui_netSource.integer == AS_FAVORITES )
+ {
+ char name[MAX_NAME_LENGTH];
+ char addr[MAX_NAME_LENGTH];
+ int res;
+
+ name[0] = addr[0] = '\0';
+ Q_strncpyz( name, UI_Cvar_VariableString( "ui_favoriteName" ), 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
+ {
+ // successfully added
+ Com_Printf( "Added favorite server %s\n", addr );
+ }
+ }
+ }
+ }
+ 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 if( Q_stricmp( name, "InitIgnoreList" ) == 0 )
+ UI_BuildPlayerList();
+ else if( Q_stricmp( name, "ToggleIgnore" ) == 0 )
+ {
+ if( uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount )
+ {
+ if( Com_ClientListContains( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) )
+ {
+ Com_ClientListRemove( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] );
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "unignore %i\n",
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) );
+ }
+ else
+ {
+ Com_ClientListAdd( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] );
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "ignore %i\n",
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) );
+ }
+ }
+ }
+ else if( Q_stricmp( name, "IgnorePlayer" ) == 0 )
+ {
+ if( uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount )
+ {
+ if( !Com_ClientListContains( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) )
+ {
+ Com_ClientListAdd( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] );
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "ignore %i\n",
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) );
+ }
+ }
+ }
+ else if( Q_stricmp( name, "UnIgnorePlayer" ) == 0 )
+ {
+ if( uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount )
+ {
+ if( Com_ClientListContains( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) )
+ {
+ Com_ClientListRemove( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] );
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "unignore %i\n",
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) );
+ }
+ }
+ }
+ else
+ Com_Printf( "unknown UI script %s\n", name );
+ }
+}
+
+static int UI_FeederInitialise( int feederID );
+
+/*
+==================
+UI_FeederCount
+==================
+*/
+static int UI_FeederCount( int feederID )
+{
+ if( feederID == FEEDER_CINEMATICS )
+ return uiInfo.movieCount;
+ else if( feederID == FEEDER_MAPS )
+ return uiInfo.mapCount;
+ else if( feederID == FEEDER_SERVERS )
+ return uiInfo.serverStatus.numDisplayServers;
+ else if( feederID == FEEDER_SERVERSTATUS )
+ return uiInfo.serverStatusInfo.numLines;
+ else if( feederID == FEEDER_NEWS )
+ return uiInfo.newsInfo.numLines;
+ else if( feederID == FEEDER_FINDPLAYER )
+ return uiInfo.numFoundPlayerServers;
+ else if( feederID == FEEDER_PLAYER_LIST )
+ {
+ if( uiInfo.uiDC.realTime > uiInfo.playerRefresh )
+ {
+ uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000;
+ UI_BuildPlayerList();
+ }
+
+ return uiInfo.playerCount;
+ }
+ else if( feederID == FEEDER_TEAM_LIST )
+ {
+ if( uiInfo.uiDC.realTime > uiInfo.playerRefresh )
+ {
+ uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000;
+ UI_BuildPlayerList();
+ }
+
+ return uiInfo.myTeamCount;
+ }
+ else if( feederID == FEEDER_IGNORE_LIST )
+ return uiInfo.playerCount;
+ else if( feederID == FEEDER_HELP_LIST )
+ return uiInfo.helpCount;
+ else if( feederID == FEEDER_MODS )
+ return uiInfo.modCount;
+ else if( feederID == FEEDER_DEMOS )
+ return uiInfo.demoCount;
+ else if( feederID == FEEDER_TREMTEAMS )
+ return uiInfo.teamCount;
+ else if( feederID == FEEDER_TREMHUMANITEMS )
+ return uiInfo.humanItemCount;
+ else if( feederID == FEEDER_TREMALIENCLASSES )
+ return uiInfo.alienClassCount;
+ else if( feederID == FEEDER_TREMHUMANARMOURYBUY )
+ return uiInfo.humanArmouryBuyCount;
+ else if( feederID == FEEDER_TREMHUMANARMOURYSELL )
+ return uiInfo.humanArmourySellCount;
+ else if( feederID == FEEDER_TREMALIENUPGRADE )
+ return uiInfo.alienUpgradeCount;
+ else if( feederID == FEEDER_TREMALIENBUILD )
+ return uiInfo.alienBuildCount;
+ else if( feederID == FEEDER_TREMHUMANBUILD )
+ return uiInfo.humanBuildCount;
+ else if( feederID == FEEDER_RESOLUTIONS )
+ {
+ if( UI_FeederInitialise( feederID ) == uiInfo.numResolutions )
+ return uiInfo.numResolutions + 1;
+ else
+ return uiInfo.numResolutions;
+ }
+
+ 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( c == index )
+ {
+ *actual = i;
+ return uiInfo.mapList[i].mapName;
+ }
+ else
+ c++;
+ }
+
+ return "";
+}
+
+static int GCD( int a, int b )
+{
+ int c;
+
+ while( b != 0 )
+ {
+ c = a % b;
+ a = b;
+ b = c;
+ }
+
+ return a;
+}
+
+static const char *UI_DisplayAspectString( int w, int h )
+{
+ int gcd = GCD( w, h );
+
+ w /= gcd;
+ h /= gcd;
+
+ // For some reason 8:5 is usually referred to as 16:10
+ if( w == 8 && h == 5 )
+ {
+ w = 16;
+ h = 10;
+ }
+
+ return va( "%d:%d", w, h );
+}
+
+static const char *UI_FeederItemText( int feederID, int index, int column, qhandle_t *handle )
+{
+ if( handle )
+ *handle = -1;
+
+ if( feederID == FEEDER_MAPS )
+ {
+ int actual;
+ return UI_SelectedMap( index, &actual );
+ }
+ else if( feederID == FEEDER_SERVERS )
+ {
+ if( index >= 0 && index < UI_FeederCount( feederID ) )
+ {
+ static char info[MAX_STRING_CHARS];
+ static char clientBuff[ 32 ];
+ static char cleaned[ MAX_STRING_CHARS ];
+ static int lastColumn = -1;
+ static int lastTime = 0;
+ int ping;
+
+ 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" ) );
+
+ UI_EscapeEmoticons( cleaned, Info_ValueForKey( info, "hostname" ), sizeof( cleaned ) );
+
+ switch( column )
+ {
+ case SORT_HOST:
+ if( ping <= 0 )
+ return Info_ValueForKey( info, "addr" );
+ else
+ {
+ static char hostname[1024];
+
+ if( ui_netSource.integer == AS_LOCAL )
+ {
+ Com_sprintf( hostname, sizeof( hostname ), "%s [%s]", cleaned,
+ netnames[atoi( Info_ValueForKey( info, "nettype" ) )] );
+ return hostname;
+ }
+ else
+ {
+ char *text;
+ char *label;
+
+ label = Info_ValueForKey( info, "label" );
+ if( label[0] )
+ {
+ // First char of the label response is a sorting tag. Skip it.
+ label+= 1;
+
+ Com_sprintf( hostname, sizeof( hostname ), "%s %s",
+ label, cleaned );
+ }
+ else
+ {
+ Com_sprintf( hostname, sizeof( hostname ), "%s",
+ cleaned );
+ }
+
+ // Strip leading whitespace
+ text = hostname;
+
+ while( *text != '\0' && *text == ' ' )
+ text++;
+
+ return text;
+ }
+ }
+
+ case SORT_GAME:
+ return Info_ValueForKey( info, "game" );
+
+ 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_PING:
+ if( ping <= 0 )
+ return "...";
+ else
+ return Info_ValueForKey( info, "ping" );
+ }
+ }
+ }
+ else if( feederID == FEEDER_SERVERSTATUS )
+ {
+ if( index >= 0 && index < uiInfo.serverStatusInfo.numLines )
+ {
+ if( column >= 0 && column < 4 )
+ return uiInfo.serverStatusInfo.lines[index][column];
+ }
+ }
+ else if( feederID == FEEDER_NEWS )
+ {
+ if( index >= 0 && index < uiInfo.newsInfo.numLines )
+ return uiInfo.newsInfo.text[index];
+ }
+ else if( feederID == FEEDER_FINDPLAYER )
+ {
+ if( index >= 0 && index < uiInfo.numFoundPlayerServers )
+ return uiInfo.foundPlayerServerNames[index];
+ }
+ else if( feederID == FEEDER_PLAYER_LIST )
+ {
+ if( index >= 0 && index < uiInfo.playerCount )
+ return uiInfo.playerNames[index];
+ }
+ else if( feederID == FEEDER_TEAM_LIST )
+ {
+ if( index >= 0 && index < uiInfo.myTeamCount )
+ return uiInfo.teamNames[index];
+ }
+ else if( feederID == FEEDER_IGNORE_LIST )
+ {
+ if( index >= 0 && index < uiInfo.playerCount )
+ {
+ switch( column )
+ {
+ case 1:
+ // am I ignoring him
+ return Com_ClientListContains( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ index ] ) ? "X" : "";
+
+ case 2:
+ // is he ignoring me
+ return Com_ClientListContains( &uiInfo.ignoreList[ index ],
+ uiInfo.playerNumber ) ? "X" : "";
+
+ default:
+ return uiInfo.playerNames[index];
+ }
+ }
+ }
+ else if( feederID == FEEDER_HELP_LIST )
+ {
+ if( index >= 0 && index < uiInfo.helpCount )
+ return uiInfo.helpList[ index ].text;
+ }
+ else if( feederID == 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;
+ }
+ }
+ else if( feederID == FEEDER_CINEMATICS )
+ {
+ if( index >= 0 && index < uiInfo.movieCount )
+ return uiInfo.movieList[index];
+ }
+ else if( feederID == FEEDER_DEMOS )
+ {
+ if( index >= 0 && index < uiInfo.demoCount )
+ return uiInfo.demoList[index];
+ }
+ else if( feederID == FEEDER_TREMTEAMS )
+ {
+ if( index >= 0 && index < uiInfo.teamCount )
+ return uiInfo.teamList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMHUMANITEMS )
+ {
+ if( index >= 0 && index < uiInfo.humanItemCount )
+ return uiInfo.humanItemList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMALIENCLASSES )
+ {
+ if( index >= 0 && index < uiInfo.alienClassCount )
+ return uiInfo.alienClassList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMHUMANARMOURYBUY )
+ {
+ if( index >= 0 && index < uiInfo.humanArmouryBuyCount )
+ return uiInfo.humanArmouryBuyList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMHUMANARMOURYSELL )
+ {
+ if( index >= 0 && index < uiInfo.humanArmourySellCount )
+ return uiInfo.humanArmourySellList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMALIENUPGRADE )
+ {
+ if( index >= 0 && index < uiInfo.alienUpgradeCount )
+ return uiInfo.alienUpgradeList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMALIENBUILD )
+ {
+ if( index >= 0 && index < uiInfo.alienBuildCount )
+ return uiInfo.alienBuildList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMHUMANBUILD )
+ {
+ if( index >= 0 && index < uiInfo.humanBuildCount )
+ return uiInfo.humanBuildList[ index ].text;
+ }
+ else if( feederID == FEEDER_RESOLUTIONS )
+ {
+ static char resolution[MAX_STRING_CHARS];
+ int w, h;
+
+ if( index >= 0 && index < uiInfo.numResolutions )
+ {
+ w = uiInfo.resolutions[ index ].w;
+ h = uiInfo.resolutions[ index ].h;
+
+ Com_sprintf( resolution, sizeof( resolution ), "%dx%d (%s)", w, h,
+ UI_DisplayAspectString( w, h ) );
+
+ return resolution;
+ }
+
+ w = (int)trap_Cvar_VariableValue( "r_width" );
+ h = (int)trap_Cvar_VariableValue( "r_height" );
+ Com_sprintf( resolution, sizeof( resolution ), "Custom (%dx%d)", w, h );
+
+ return resolution;
+ }
+
+ return "";
+}
+
+
+static qhandle_t UI_FeederItemImage( int feederID, int index )
+{
+ if( feederID == FEEDER_MAPS )
+ {
+ 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;
+ }
+ }
+
+ return 0;
+}
+
+static void UI_FeederSelection( int feederID, int index )
+{
+ static char info[MAX_STRING_CHARS];
+
+ if( feederID == FEEDER_MAPS )
+ {
+ int actual, map;
+ map = ui_selectedMap.integer;
+
+ if( uiInfo.mapList[map].cinematic >= 0 )
+ {
+ trap_CIN_StopCinematic( uiInfo.mapList[map].cinematic );
+ uiInfo.mapList[map].cinematic = -1;
+ }
+
+ UI_SelectedMap( index, &actual );
+
+ ui_selectedMap.integer = actual;
+ trap_Cvar_Set( "ui_selectedMap", va( "%d", actual ) );
+ uiInfo.mapList[ui_selectedMap.integer].cinematic =
+ trap_CIN_PlayCinematic( va( "%s.roq", uiInfo.mapList[ui_selectedMap.integer].mapLoadName ),
+ 0, 0, 0, 0, ( CIN_loop | CIN_silent ) );
+ }
+ else if( feederID == 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( "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 ) );
+ }
+ }
+ else if( feederID == FEEDER_SERVERSTATUS )
+ {
+ }
+ else if( feederID == 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 );
+ }
+ }
+ else if( feederID == FEEDER_PLAYER_LIST )
+ uiInfo.playerIndex = index;
+ else if( feederID == FEEDER_TEAM_LIST )
+ uiInfo.teamPlayerIndex = index;
+ else if( feederID == FEEDER_IGNORE_LIST )
+ uiInfo.ignoreIndex = index;
+ else if( feederID == FEEDER_HELP_LIST )
+ uiInfo.helpIndex = index;
+ else if( feederID == FEEDER_MODS )
+ uiInfo.modIndex = index;
+ else if( feederID == FEEDER_CINEMATICS )
+ {
+ uiInfo.movieIndex = index;
+
+ if( uiInfo.previewMovie >= 0 )
+ trap_CIN_StopCinematic( uiInfo.previewMovie );
+
+ uiInfo.previewMovie = -1;
+ }
+ else if( feederID == FEEDER_DEMOS )
+ uiInfo.demoIndex = index;
+ else if( feederID == FEEDER_TREMTEAMS )
+ uiInfo.teamIndex = index;
+ else if( feederID == FEEDER_TREMHUMANITEMS )
+ uiInfo.humanItemIndex = index;
+ else if( feederID == FEEDER_TREMALIENCLASSES )
+ uiInfo.alienClassIndex = index;
+ else if( feederID == FEEDER_TREMHUMANARMOURYBUY )
+ uiInfo.humanArmouryBuyIndex = index;
+ else if( feederID == FEEDER_TREMHUMANARMOURYSELL )
+ uiInfo.humanArmourySellIndex = index;
+ else if( feederID == FEEDER_TREMALIENUPGRADE )
+ uiInfo.alienUpgradeIndex = index;
+ else if( feederID == FEEDER_TREMALIENBUILD )
+ uiInfo.alienBuildIndex = index;
+ else if( feederID == FEEDER_TREMHUMANBUILD )
+ uiInfo.humanBuildIndex = index;
+ else if( feederID == FEEDER_RESOLUTIONS )
+ {
+ if( index >= 0 && index < uiInfo.numResolutions )
+ {
+ trap_Cvar_Set( "r_width", va( "%d", uiInfo.resolutions[ index ].w ) );
+ trap_Cvar_Set( "r_height", va( "%d", uiInfo.resolutions[ index ].h ) );
+ }
+
+ uiInfo.resolutionIndex = index;
+ }
+}
+
+static int UI_FeederInitialise( int feederID )
+{
+ if( feederID == FEEDER_RESOLUTIONS )
+ {
+ int i;
+ int w = trap_Cvar_VariableValue( "r_width" );
+ int h = trap_Cvar_VariableValue( "r_height" );
+
+ for( i = 0; i < uiInfo.numResolutions; i++ )
+ {
+ if( w == uiInfo.resolutions[ i ].w && h == uiInfo.resolutions[ i ].h )
+ return i;
+ }
+
+ return uiInfo.numResolutions;
+ }
+
+ return 0;
+}
+
+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_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 );
+}
+
+static float UI_GetValue( int ownerDraw )
+{
+ return 0.0f;
+}
+
+/*
+=================
+UI_ParseResolutions
+=================
+*/
+void UI_ParseResolutions( void )
+{
+ char buf[ MAX_STRING_CHARS ];
+ char w[ 16 ], h[ 16 ];
+ char *p;
+ const char *out;
+ char *s = NULL;
+
+ trap_Cvar_VariableStringBuffer( "r_availableModes", buf, sizeof( buf ) );
+ p = buf;
+ uiInfo.numResolutions = 0;
+
+ while( String_Parse( &p, &out ) )
+ {
+ Q_strncpyz( w, out, sizeof( w ) );
+ s = strchr( w, 'x' );
+ if( !s )
+ return;
+
+ *s++ = '\0';
+ Q_strncpyz( h, s, sizeof( h ) );
+
+ uiInfo.resolutions[ uiInfo.numResolutions ].w = atoi( w );
+ uiInfo.resolutions[ uiInfo.numResolutions ].h = atoi( h );
+ uiInfo.numResolutions++;
+ }
+}
+
+/*
+=================
+UI_Init
+=================
+*/
+void UI_Init( qboolean inGameLoad )
+{
+ int start;
+
+ BG_InitClassConfigs( );
+ BG_InitAllowedGameElements( );
+
+ uiInfo.inGameLoad = inGameLoad;
+
+ UI_RegisterCvars();
+ UI_InitMemory();
+
+ // cache redundant calulations
+ trap_GetGlconfig( &uiInfo.uiDC.glconfig );
+
+ // for 640x480 virtualized screen
+ uiInfo.uiDC.yscale = uiInfo.uiDC.glconfig.vidHeight * ( 1.0f / 480.0f );
+ uiInfo.uiDC.xscale = uiInfo.uiDC.glconfig.vidWidth * ( 1.0f / 640.0f );
+
+ // wide screen
+ uiInfo.uiDC.aspectScale = ( ( 640.0f * uiInfo.uiDC.glconfig.vidHeight ) /
+ ( 480.0f * uiInfo.uiDC.glconfig.vidWidth ) );
+
+ uiInfo.uiDC.smallFontScale = trap_Cvar_VariableValue( "ui_smallFont" );
+ uiInfo.uiDC.bigFontScale = trap_Cvar_VariableValue( "ui_bigFont" );
+
+ uiInfo.uiDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip;
+ uiInfo.uiDC.setColor = &UI_SetColor;
+ uiInfo.uiDC.drawHandlePic = &UI_DrawHandlePic;
+ uiInfo.uiDC.drawStretchPic = &trap_R_DrawStretchPic;
+ 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_DrawSides;
+ uiInfo.uiDC.drawTopBottom = &UI_DrawTopBottom;
+ uiInfo.uiDC.clearScene = &trap_R_ClearScene;
+ uiInfo.uiDC.drawSides = &UI_DrawSides;
+ 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.runScript = &UI_RunMenuScript;
+ 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.feederInitialise = &UI_FeederInitialise;
+ 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.ownerDrawText = &UI_OwnerDrawText;
+ 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();
+
+ UI_LoadMenus( "ui/menus.txt", qtrue );
+ UI_LoadMenus( "ui/ingame.txt", qfalse );
+ UI_LoadMenus( "ui/tremulous.txt", qfalse );
+ UI_LoadHelp( "ui/help.txt" );
+
+ Menus_CloseAll( );
+
+ trap_LAN_LoadCachedServers();
+
+ // sets defaults for ui temp cvars
+ trap_Cvar_Set( "ui_mousePitch", ( trap_Cvar_VariableValue( "m_pitch" ) >= 0 ) ? "0" : "1" );
+
+ uiInfo.serverStatus.currentServerCinematic = -1;
+ uiInfo.previewMovie = -1;
+
+ UI_ParseResolutions( );
+}
+
+
+/*
+=================
+UI_KeyEvent
+=================
+*/
+void UI_KeyEvent( int key, qboolean down )
+{
+ if( Menu_Count() > 0 )
+ {
+ menuDef_t *menu = Menu_GetFocused();
+
+ if( menu )
+ {
+ if( key == K_ESCAPE && down && !Menus_AnyFullScreenVisible() )
+ Menus_CloseAll( );
+ else
+ Menu_HandleKey( menu, key, down );
+ }
+ else
+ {
+ trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
+ trap_Key_ClearStates();
+ trap_Cvar_Set( "cl_paused", "0" );
+ }
+ }
+}
+
+/*
+=================
+UI_MouseEvent
+=================
+*/
+void UI_MouseEvent( int dx, int dy )
+{
+ // update mouse screen position
+ uiInfo.uiDC.cursorx += ( dx * uiInfo.uiDC.aspectScale );
+
+ 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( Menu_Count( ) > 0 )
+ Display_MouseMove( NULL, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory );
+}
+
+/*
+=================
+UI_MousePosition
+=================
+*/
+int UI_MousePosition( void )
+{
+ return (int)rint( uiInfo.uiDC.cursorx ) |
+ (int)rint( uiInfo.uiDC.cursory ) << 16;
+}
+
+/*
+=================
+UI_SetMousePosition
+=================
+*/
+void UI_SetMousePosition( int x, int y )
+{
+ uiInfo.uiDC.cursorx = x;
+ uiInfo.uiDC.cursory = y;
+
+ if( Menu_Count( ) > 0 )
+ Display_MouseMove( NULL, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory );
+}
+
+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 )
+ {
+ vec3_t v;
+ v[0] = v[1] = v[2] = 0;
+
+ 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:
+ trap_Cvar_Set( "sv_killserver", "1" );
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ Menus_CloseAll( );
+ Menus_ActivateByName( "main" );
+ trap_Cvar_VariableStringBuffer( "com_errorMessage", buf, sizeof( buf ) );
+
+ if( strlen( buf ) )
+ {
+ if( trap_Cvar_VariableValue( "com_errorCode" ) == ERR_SERVERDISCONNECT )
+ Menus_ActivateByName( "drop_popmenu" );
+ else
+ Menus_ActivateByName( "error_popmenu" );
+ }
+
+ return;
+
+ case UIMENU_INGAME:
+ trap_Cvar_Set( "cl_paused", "1" );
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ UI_BuildPlayerList();
+ Menus_CloseAll( );
+ Menus_ActivateByName( "ingame" );
+ return;
+ }
+ }
+}
+
+qboolean UI_IsFullscreen( void )
+{
+ return Menus_AnyFullScreenVisible( );
+}
+
+
+
+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 );
+ }
+}
+
+// FIXME: move to ui_shared.c?
+void Text_PaintCenter( float x, float y, float scale, vec4_t color, const char *text, float adjust )
+{
+ int len = UI_Text_Width( text, scale );
+ UI_Text_Paint( x - len / 2, y, scale, color, text, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE );
+}
+
+void Text_PaintCenter_AutoWrapped( float x, float y, float xmax, float ystep, float scale, vec4_t color, const char *str, float adjust )
+{
+ int width;
+ char *s1, *s2, *s3;
+ char c_bcp;
+ char buf[1024];
+
+ if( !str || str[0] == '\0' )
+ return;
+
+ Q_strncpyz( buf, str, sizeof( buf ) );
+
+ s1 = s2 = s3 = buf;
+
+ while( 1 )
+ {
+ do
+ s3++;
+ while( *s3 != ' ' && *s3 != '\0' );
+
+ c_bcp = *s3;
+
+ *s3 = '\0';
+
+ width = UI_Text_Width( s1, scale );
+
+ *s3 = c_bcp;
+
+ if( width > xmax )
+ {
+ if( s1 == s2 )
+ {
+ // fuck, don't have a clean cut, we'll overflow
+ s2 = s3;
+ }
+
+ *s2 = '\0';
+ Text_PaintCenter( x, y, scale, color, s1, adjust );
+ y += ystep;
+
+ if( c_bcp == '\0' )
+ {
+ // that was the last word
+ // we could start a new loop, but that wouldn't be much use
+ // even if the word is too long, we would overflow it (see above)
+ // so just print it now if needed
+ s2++;
+
+ if( *s2 != '\0' ) // if we are printing an overflowing line we have s2 == s3
+ Text_PaintCenter( x, y, scale, color, s2, adjust );
+
+ break;
+ }
+
+ s2++;
+ s1 = s2;
+ s3 = s2;
+ }
+ else
+ {
+ s2 = s3;
+
+ if( c_bcp == '\0' ) // we reached the end
+ {
+ Text_PaintCenter( x, y, scale, color, s1, adjust );
+ break;
+ }
+ }
+ }
+}
+
+
+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 + 112, scale, colorWhite, dlText, 0 );
+ Text_PaintCenter( centerPoint, yStart + 192, scale, colorWhite, etaText, 0 );
+ Text_PaintCenter( centerPoint, yStart + 248, scale, colorWhite, xferText, 0 );
+
+ if( downloadSize > 0 )
+ s = va( "%s (%d%%)", downloadName, ( int )( ( float )downloadCount * 100.0f / downloadSize ) );
+ else
+ s = downloadName;
+
+ Text_PaintCenter( centerPoint, yStart + 136, scale, colorWhite, s, 0 );
+
+ UI_ReadableSize( dlSizeBuf, sizeof dlSizeBuf, downloadCount );
+ UI_ReadableSize( totalSizeBuf, sizeof totalSizeBuf, downloadSize );
+
+ if( downloadCount < 4096 || !downloadTime )
+ {
+ Text_PaintCenter( leftWidth, yStart + 216, scale, colorWhite, "estimating", 0 );
+ Text_PaintCenter( leftWidth, yStart + 160, scale, colorWhite, 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 + 216, scale, colorWhite, dlTimeBuf, 0 );
+ Text_PaintCenter( leftWidth, yStart + 160, scale, colorWhite, va( "(%s of %s copied)", dlSizeBuf, totalSizeBuf ), 0 );
+ }
+ else
+ {
+ Text_PaintCenter( leftWidth, yStart + 216, scale, colorWhite, "estimating", 0 );
+
+ if( downloadSize )
+ Text_PaintCenter( leftWidth, yStart + 160, scale, colorWhite, va( "(%s of %s copied)", dlSizeBuf, totalSizeBuf ), 0 );
+ else
+ Text_PaintCenter( leftWidth, yStart + 160, scale, colorWhite, va( "(%s copied)", dlSizeBuf ), 0 );
+ }
+
+ if( xferRate )
+ Text_PaintCenter( leftWidth, yStart + 272, scale, colorWhite, va( "%s/Sec", xferRateBuf ), 0 );
+ }
+}
+
+/*
+========================
+UI_DrawConnectScreen
+========================
+*/
+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( !overlay && menu )
+ Menu_Paint( menu, qtrue );
+
+ if( !overlay )
+ {
+ centerPoint = 320;
+ yStart = 130;
+ scale = 0.5f;
+ }
+ else
+ {
+ centerPoint = 320;
+ yStart = 32;
+ scale = 0.6f;
+ return;
+ }
+
+ // see what information we should display
+ trap_GetClientState( &cstate );
+
+ info[0] = '\0';
+
+ if( trap_GetConfigString( CS_SERVERINFO, info, sizeof( info ) ) )
+ Text_PaintCenter( centerPoint, yStart, scale, colorWhite, va( "Loading %s", Info_ValueForKey( info, "mapname" ) ), 0 );
+
+ if( !Q_stricmp( cstate.servername, "localhost" ) )
+ Text_PaintCenter( centerPoint, yStart + 48, scale, colorWhite,
+ "Starting up...", ITEM_TEXTSTYLE_SHADOWEDMORE );
+ else
+ {
+ Com_sprintf( text, sizeof( text ), "Connecting to %s", cstate.servername );
+ Text_PaintCenter( centerPoint, yStart + 48, scale, colorWhite, text , ITEM_TEXTSTYLE_SHADOWEDMORE );
+ }
+
+
+ // display global MOTD at bottom
+ Text_PaintCenter( centerPoint, 600, scale, colorWhite, Info_ValueForKey( cstate.updateInfoString, "motd" ), 0 );
+
+ // print any server info (server full, bad version, etc)
+ if( cstate.connState < CA_CONNECTED )
+ Text_PaintCenter( centerPoint, yStart + 176, scale, colorWhite, 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:
+ {
+ char downloadName[MAX_INFO_VALUE];
+ int prompt = trap_Cvar_VariableValue( "com_downloadPrompt" );
+
+ if( prompt & DLP_SHOW ) {
+ Com_Printf("Opening download prompt...\n");
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ Menus_ActivateByName("download_popmenu");
+ trap_Cvar_Set( "com_downloadPrompt", "0" );
+ }
+
+ trap_Cvar_VariableStringBuffer( "cl_downloadName", downloadName, sizeof( downloadName ) );
+
+ if( *downloadName )
+ {
+ UI_DisplayDownloadInfo( downloadName, centerPoint, yStart, scale );
+ return;
+ }
+ }
+
+ 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, scale, colorWhite, s, 0 );
+
+ // password required / connection rejected information goes here
+}
+
+/*
+=================
+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 );
+}
+
+/*
+=================
+UI_UpdateCvars
+=================
+*/
+void UI_UpdateCvars( void )
+{
+ int i;
+ cvarTable_t *cv;
+
+ for( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ )
+ trap_Cvar_Update( cv->vmCvar );
+}
+
+/*
+=================
+UI_UpdateNews
+=================
+*/
+void UI_UpdateNews( qboolean begin )
+{
+ char newsString[ MAX_NEWS_STRING ];
+ const char *c;
+ const char *wrapped;
+ int line = 0;
+ int linePos = 0;
+ qboolean finished;
+
+ if( begin && !uiInfo.newsInfo.refreshActive ) {
+ uiInfo.newsInfo.refreshtime = uiInfo.uiDC.realTime + 10000;
+ uiInfo.newsInfo.refreshActive = qtrue;
+ }
+ else if( !uiInfo.newsInfo.refreshActive ) // do nothing
+ return;
+ else if( uiInfo.uiDC.realTime > uiInfo.newsInfo.refreshtime ) {
+ strcpy( uiInfo.newsInfo.text[ 0 ],
+ "^1Error: Timed out while contacting the server.");
+ uiInfo.newsInfo.numLines = 1;
+ return;
+ }
+
+ // start the news fetching
+ finished = trap_GetNews( begin );
+
+ // parse what comes back. Parse newlines and otherwise chop when necessary
+ trap_Cvar_VariableStringBuffer( "cl_newsString", newsString,
+ sizeof( newsString ) );
+
+ // FIXME remove magic width constant
+ wrapped = Item_Text_Wrap( newsString, 0.25f, 325 * uiInfo.uiDC.aspectScale );
+
+ for( c = wrapped; *c != '\0'; ++c ) {
+ if( linePos == (MAX_NEWS_LINEWIDTH - 1) || *c == '\n' ) {
+ uiInfo.newsInfo.text[ line ][ linePos ] = '\0';
+
+ if( line == ( MAX_NEWS_LINES - 1 ) )
+ break;
+
+ linePos = 0;
+ line++;
+
+ if( *c != '\n' ) {
+ uiInfo.newsInfo.text[ line ][ linePos ] = *c;
+ linePos++;
+ }
+ } else if( isprint( *c ) ) {
+ uiInfo.newsInfo.text[ line ][ linePos ] = *c;
+ linePos++;
+ }
+ }
+
+ uiInfo.newsInfo.text[ line ] [linePos ] = '\0';
+ uiInfo.newsInfo.numLines = line + 1;
+
+ if( finished )
+ uiInfo.newsInfo.refreshActive = qfalse;
+}
+
diff --git a/src/ui/ui_public.h b/src/ui/ui_public.h
new file mode 100644
index 0000000..0fb2292
--- /dev/null
+++ b/src/ui/ui_public.h
@@ -0,0 +1,205 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#ifndef UI_PUBLIC_H
+#define UI_PUBLIC_H
+
+#define UI_API_VERSION 6
+
+typedef struct
+{
+ connstate_t connState;
+ int connectPacketCount;
+ int clientNum;
+ char servername[MAX_STRING_CHARS];
+ char updateInfoString[MAX_STRING_CHARS];
+ char messageString[MAX_STRING_CHARS];
+}
+uiClientState_t;
+
+typedef enum
+{
+ UI_ERROR,
+ UI_PRINT,
+ UI_MILLISECONDS,
+ UI_CVAR_SET,
+ UI_CVAR_VARIABLEVALUE,
+ UI_CVAR_VARIABLESTRINGBUFFER,
+ UI_CVAR_SETVALUE,
+ UI_CVAR_RESET,
+ UI_CVAR_CREATE,
+ UI_CVAR_INFOSTRINGBUFFER,
+ UI_ARGC,
+ UI_ARGV,
+ UI_CMD_EXECUTETEXT,
+ UI_FS_FOPENFILE,
+ UI_FS_READ,
+ UI_FS_WRITE,
+ UI_FS_FCLOSEFILE,
+ UI_FS_GETFILELIST,
+ UI_R_REGISTERMODEL,
+ UI_R_REGISTERSKIN,
+ UI_R_REGISTERSHADERNOMIP,
+ UI_R_CLEARSCENE,
+ UI_R_ADDREFENTITYTOSCENE,
+ UI_R_ADDPOLYTOSCENE,
+ UI_R_ADDLIGHTTOSCENE,
+ UI_R_RENDERSCENE,
+ UI_R_SETCOLOR,
+ UI_R_SETCLIPREGION,
+ UI_R_DRAWSTRETCHPIC,
+ UI_UPDATESCREEN,
+ UI_CM_LERPTAG,
+ UI_CM_LOADMODEL,
+ UI_S_REGISTERSOUND,
+ UI_S_STARTLOCALSOUND,
+ UI_KEY_KEYNUMTOSTRINGBUF,
+ UI_KEY_GETBINDINGBUF,
+ UI_KEY_SETBINDING,
+ UI_KEY_ISDOWN,
+ UI_KEY_GETOVERSTRIKEMODE,
+ UI_KEY_SETOVERSTRIKEMODE,
+ UI_KEY_CLEARSTATES,
+ UI_KEY_GETCATCHER,
+ UI_KEY_SETCATCHER,
+ UI_GETCLIPBOARDDATA,
+ UI_GETGLCONFIG,
+ UI_GETCLIENTSTATE,
+ UI_GETCONFIGSTRING,
+ UI_LAN_GETPINGQUEUECOUNT,
+ UI_LAN_CLEARPING,
+ UI_LAN_GETPING,
+ UI_LAN_GETPINGINFO,
+ UI_CVAR_REGISTER,
+ UI_CVAR_UPDATE,
+ UI_MEMORY_REMAINING,
+ UI_R_REGISTERFONT,
+ UI_R_MODELBOUNDS,
+ UI_S_STOPBACKGROUNDTRACK,
+ UI_S_STARTBACKGROUNDTRACK,
+ UI_REAL_TIME,
+ UI_LAN_GETSERVERCOUNT,
+ UI_LAN_GETSERVERADDRESSSTRING,
+ UI_LAN_GETSERVERINFO,
+ UI_LAN_MARKSERVERVISIBLE,
+ UI_LAN_UPDATEVISIBLEPINGS,
+ UI_LAN_RESETPINGS,
+ UI_LAN_LOADCACHEDSERVERS,
+ UI_LAN_SAVECACHEDSERVERS,
+ UI_LAN_ADDSERVER,
+ UI_LAN_REMOVESERVER,
+ UI_CIN_PLAYCINEMATIC,
+ UI_CIN_STOPCINEMATIC,
+ UI_CIN_RUNCINEMATIC,
+ UI_CIN_DRAWCINEMATIC,
+ UI_CIN_SETEXTENTS,
+ UI_R_REMAP_SHADER,
+ UI_LAN_SERVERSTATUS,
+ UI_LAN_GETSERVERPING,
+ UI_LAN_SERVERISVISIBLE,
+ UI_LAN_COMPARESERVERS,
+ // 1.32
+ UI_FS_SEEK,
+ UI_SET_PBCLSTATUS,
+
+ UI_PARSE_ADD_GLOBAL_DEFINE,
+ UI_PARSE_LOAD_SOURCE,
+ UI_PARSE_FREE_SOURCE,
+ UI_PARSE_READ_TOKEN,
+ UI_PARSE_SOURCE_FILE_AND_LINE,
+ UI_GETNEWS,
+
+ UI_MEMSET = 100,
+ UI_MEMCPY,
+ UI_STRNCPY,
+ UI_SIN,
+ UI_COS,
+ UI_ATAN2,
+ UI_SQRT,
+ UI_FLOOR,
+ UI_CEIL
+}
+uiImport_t;
+
+typedef enum
+{
+ UIMENU_NONE,
+ UIMENU_MAIN,
+ UIMENU_INGAME
+}
+uiMenuCommand_t;
+
+typedef enum
+{
+ SORT_HOST,
+ SORT_MAP,
+ SORT_CLIENTS,
+ SORT_PING,
+ SORT_GAME
+}
+serverSortField_t;
+
+typedef enum
+{
+ UI_GETAPIVERSION = 0, // system reserved
+
+ UI_INIT,
+ // void UI_Init( void );
+
+ UI_SHUTDOWN,
+ // void UI_Shutdown( void );
+
+ UI_KEY_EVENT,
+ // void UI_KeyEvent( int key );
+
+ UI_MOUSE_EVENT,
+ // void UI_MouseEvent( int dx, int dy );
+
+ UI_MOUSE_POSITION,
+ // int UI_MousePosition( void );
+
+ UI_SET_MOUSE_POSITION,
+ // void UI_SetMousePosition( int x, int y );
+
+ UI_REFRESH,
+ // void UI_Refresh( int time );
+
+ UI_IS_FULLSCREEN,
+ // qboolean UI_IsFullscreen( void );
+
+ UI_SET_ACTIVE_MENU,
+ // void UI_SetActiveMenu( uiMenuCommand_t menu );
+
+ UI_CONSOLE_COMMAND,
+ // qboolean UI_ConsoleCommand( int realTime );
+
+ UI_DRAW_CONNECT_SCREEN
+ // void UI_DrawConnectScreen( qboolean overlay );
+
+ // if !overlay, the background will be drawn, otherwise it will be
+ // overlayed over whatever the cgame has drawn.
+ // a GetClientState syscall will be made to get the current strings
+}
+uiExport_t;
+
+#endif
diff --git a/src/ui/ui_shared.c b/src/ui/ui_shared.c
new file mode 100644
index 0000000..7822f93
--- /dev/null
+++ b/src/ui/ui_shared.c
@@ -0,0 +1,8083 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "ui_shared.h"
+
+#define SCROLL_TIME_START 500
+#define SCROLL_TIME_ADJUST 150
+#define SCROLL_TIME_ADJUSTOFFSET 40
+#define SCROLL_TIME_FLOOR 20
+
+typedef struct scrollInfo_s
+{
+ int nextScrollTime;
+ int nextAdjustTime;
+ int adjustValue;
+ int scrollKey;
+ float xStart;
+ float yStart;
+ itemDef_t *item;
+ qboolean scrollDir;
+}
+scrollInfo_t;
+
+static scrollInfo_t scrollInfo;
+
+// prevent compiler warnings
+void voidFunction( void *var )
+{
+ return;
+}
+
+qboolean voidFunction2( itemDef_t *var1, int var2 )
+{
+ return qfalse;
+}
+
+static CaptureFunc *captureFunc = voidFunction;
+static int captureFuncExpiry = 0;
+static void *captureData = NULL;
+static itemDef_t *itemCapture = NULL; // item that has the mouse captured ( if any )
+
+displayContextDef_t *DC = NULL;
+
+static qboolean g_waitingForKey = qfalse;
+static qboolean g_editingField = qfalse;
+
+static itemDef_t *g_bindItem = NULL;
+static itemDef_t *g_editItem = NULL;
+static itemDef_t *g_comboBoxItem = NULL;
+
+menuDef_t Menus[MAX_MENUS]; // defined menus
+int menuCount = 0; // how many
+
+menuDef_t *menuStack[MAX_OPEN_MENUS];
+int openMenuCount = 0;
+
+#define DOUBLE_CLICK_DELAY 300
+static int lastListBoxClickTime = 0;
+
+itemDataType_t Item_DataType( itemDef_t *item );
+void Item_RunScript( itemDef_t *item, const char *s );
+void Item_SetupKeywordHash( void );
+static ID_INLINE qboolean Item_IsEditField( itemDef_t *item );
+static ID_INLINE qboolean Item_IsListBox( itemDef_t *item );
+static void Item_ListBox_SetStartPos( itemDef_t *item, int startPos );
+void Menu_SetupKeywordHash( void );
+int BindingIDFromName( const char *name );
+qboolean Item_Bind_HandleKey( itemDef_t *item, int key, qboolean down );
+itemDef_t *Menu_SetPrevCursorItem( menuDef_t *menu );
+itemDef_t *Menu_SetNextCursorItem( menuDef_t *menu );
+static qboolean Menu_OverActiveItem( menuDef_t *menu, float x, float y );
+
+/*
+===============
+UI_InstallCaptureFunc
+===============
+*/
+void UI_InstallCaptureFunc( CaptureFunc *f, void *data, int timeout )
+{
+ captureFunc = f;
+ captureData = data;
+
+ if( timeout > 0 )
+ captureFuncExpiry = DC->realTime + timeout;
+ else
+ captureFuncExpiry = 0;
+}
+
+/*
+===============
+UI_RemoveCaptureFunc
+===============
+*/
+void UI_RemoveCaptureFunc( void )
+{
+ captureFunc = voidFunction;
+ captureData = NULL;
+ captureFuncExpiry = 0;
+}
+
+#ifdef CGAME
+#define MEM_POOL_SIZE 128 * 1024
+#else
+#define MEM_POOL_SIZE 1024 * 1024
+#endif
+
+static char UI_memoryPool[MEM_POOL_SIZE];
+static int allocPoint, outOfMemory;
+
+/*
+===============
+UI_Alloc
+===============
+*/
+void *UI_Alloc( int size )
+{
+ char *p;
+
+ if( allocPoint + size > MEM_POOL_SIZE )
+ {
+ outOfMemory = qtrue;
+
+ if( DC->Print )
+ DC->Print( "UI_Alloc: Failure. Out of memory!\n" );
+
+ //DC->trap_Print(S_COLOR_YELLOW"WARNING: UI Out of Memory!\n");
+ return NULL;
+ }
+
+ p = &UI_memoryPool[ allocPoint ];
+
+ allocPoint += ( size + 15 ) & ~15;
+
+ return p;
+}
+
+/*
+===============
+UI_InitMemory
+===============
+*/
+void UI_InitMemory( void )
+{
+ allocPoint = 0;
+ outOfMemory = qfalse;
+}
+
+qboolean UI_OutOfMemory( )
+{
+ return outOfMemory;
+}
+
+
+
+
+
+#define HASH_TABLE_SIZE 2048
+/*
+================
+return a hash value for the string
+================
+*/
+static long hashForString( const char *str )
+{
+ int i;
+ long hash;
+ char letter;
+
+ hash = 0;
+ i = 0;
+
+ while( str[i] != '\0' )
+ {
+ letter = tolower( str[i] );
+ hash += ( long )( letter ) * ( i + 119 );
+ i++;
+ }
+
+ hash &= ( HASH_TABLE_SIZE - 1 );
+ return hash;
+}
+
+typedef struct stringDef_s
+{
+ struct stringDef_s *next;
+ const char *str;
+}
+
+stringDef_t;
+
+static int strPoolIndex = 0;
+static char strPool[STRING_POOL_SIZE];
+
+static int strHandleCount = 0;
+static stringDef_t *strHandle[HASH_TABLE_SIZE];
+
+// Make a copy of a string for later use. Can safely be called on the
+// same string repeatedly. Redundant on string literals or global
+// constants.
+const char *String_Alloc( const char *p )
+{
+ int len;
+ long hash;
+ stringDef_t *str, *last;
+
+ if( p == NULL )
+ return NULL;
+
+ if( *p == 0 )
+ return "";
+
+ hash = hashForString( p );
+
+ str = strHandle[hash];
+
+ while( str )
+ {
+ if( strcmp( p, str->str ) == 0 )
+ return str->str;
+
+ str = str->next;
+ }
+
+ len = strlen( p );
+
+ if( len + strPoolIndex + 1 < STRING_POOL_SIZE )
+ {
+ int ph = strPoolIndex;
+ strcpy( &strPool[strPoolIndex], p );
+ strPoolIndex += len + 1;
+
+ str = strHandle[hash];
+ last = str;
+
+ while( str && str->next )
+ {
+ last = str;
+ str = str->next;
+ }
+
+ str = UI_Alloc( sizeof( stringDef_t ) );
+ str->next = NULL;
+ str->str = &strPool[ph];
+
+ if( last )
+ last->next = str;
+ else
+ strHandle[hash] = str;
+
+ return &strPool[ph];
+ }
+ else
+ {
+ Com_Error( ERR_DROP, "String_Alloc( %s ): string pool full!", p );
+ return NULL; // not that we return at all
+ }
+}
+
+void String_Report( void )
+{
+ float f;
+ Com_Printf( "Memory/String Pool Info\n" );
+ Com_Printf( "----------------\n" );
+ f = strPoolIndex;
+ f /= STRING_POOL_SIZE;
+ f *= 100;
+ Com_Printf( "String Pool is %.1f%% full, %i bytes out of %i used.\n", f, strPoolIndex, STRING_POOL_SIZE );
+ f = allocPoint;
+ f /= MEM_POOL_SIZE;
+ f *= 100;
+ Com_Printf( "Memory Pool is %.1f%% full, %i bytes out of %i used.\n", f, allocPoint, MEM_POOL_SIZE );
+}
+
+/*
+=================
+String_Init
+=================
+*/
+void String_Init( void )
+{
+ int i;
+
+ for( i = 0; i < HASH_TABLE_SIZE; i++ )
+ strHandle[ i ] = 0;
+
+ strHandleCount = 0;
+
+ strPoolIndex = 0;
+
+ menuCount = 0;
+
+ openMenuCount = 0;
+
+ UI_InitMemory( );
+
+ Item_SetupKeywordHash( );
+
+ Menu_SetupKeywordHash( );
+
+ if( DC && DC->getBindingBuf )
+ Controls_GetConfig( );
+}
+
+/*
+=================
+PC_SourceWarning
+=================
+*/
+void PC_SourceWarning( int handle, char *format, ... )
+{
+ int line;
+ char filename[128];
+ va_list argptr;
+ static char string[4096];
+
+ va_start( argptr, format );
+ Q_vsnprintf( string, sizeof( string ), format, argptr );
+ va_end( argptr );
+
+ filename[0] = '\0';
+ line = 0;
+ trap_Parse_SourceFileAndLine( handle, filename, &line );
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: %s, line %d: %s\n", filename, line, string );
+}
+
+/*
+=================
+PC_SourceError
+=================
+*/
+void PC_SourceError( int handle, char *format, ... )
+{
+ int line;
+ char filename[128];
+ va_list argptr;
+ static char string[4096];
+
+ va_start( argptr, format );
+ Q_vsnprintf( string, sizeof( string ), format, argptr );
+ va_end( argptr );
+
+ filename[0] = '\0';
+ line = 0;
+ trap_Parse_SourceFileAndLine( handle, filename, &line );
+
+ Com_Printf( S_COLOR_RED "ERROR: %s, line %d: %s\n", filename, line, string );
+}
+
+/*
+=================
+LerpColor
+=================
+*/
+void LerpColor( vec4_t a, vec4_t b, vec4_t c, float t )
+{
+ int i;
+
+ // lerp and clamp each component
+
+ for( i = 0; i < 4; i++ )
+ {
+ c[i] = a[i] + t * ( b[i] - a[i] );
+
+ if( c[i] < 0 )
+ c[i] = 0;
+ else if( c[i] > 1.0 )
+ c[i] = 1.0;
+ }
+}
+
+/*
+=================
+Float_Parse
+=================
+*/
+qboolean Float_Parse( char **p, float *f )
+{
+ char *token;
+ token = COM_ParseExt( p, qfalse );
+
+ if( token && token[0] != 0 )
+ {
+ *f = atof( token );
+ return qtrue;
+ }
+ else
+ return qfalse;
+}
+
+#define MAX_EXPR_ELEMENTS 32
+
+typedef enum
+{
+ EXPR_OPERATOR,
+ EXPR_VALUE
+}
+exprType_t;
+
+typedef struct exprToken_s
+{
+ exprType_t type;
+ union
+ {
+ char op;
+ float val;
+ } u;
+}
+exprToken_t;
+
+typedef struct exprList_s
+{
+ exprToken_t l[ MAX_EXPR_ELEMENTS ];
+ int f, b;
+}
+exprList_t;
+
+/*
+=================
+OpPrec
+
+Return a value reflecting operator precedence
+=================
+*/
+static ID_INLINE int OpPrec( char op )
+{
+ switch( op )
+ {
+ case '*':
+ return 4;
+
+ case '/':
+ return 3;
+
+ case '+':
+ return 2;
+
+ case '-':
+ return 1;
+
+ case '(':
+ return 0;
+
+ default:
+ return -1;
+ }
+}
+
+/*
+=================
+PC_Expression_Parse
+=================
+*/
+static qboolean PC_Expression_Parse( int handle, float *f )
+{
+ pc_token_t token;
+ int unmatchedParentheses = 0;
+ exprList_t stack, fifo;
+ exprToken_t value;
+ qboolean expectingNumber = qtrue;
+
+#define FULL( a ) ( a.b >= ( MAX_EXPR_ELEMENTS - 1 ) )
+#define EMPTY( a ) ( a.f > a.b )
+
+#define PUSH_VAL( a, v ) \
+ { \
+ if( FULL( a ) ) \
+ return qfalse; \
+ a.b++; \
+ a.l[ a.b ].type = EXPR_VALUE; \
+ a.l[ a.b ].u.val = v; \
+ }
+
+#define PUSH_OP( a, o ) \
+ { \
+ if( FULL( a ) ) \
+ return qfalse; \
+ a.b++; \
+ a.l[ a.b ].type = EXPR_OPERATOR; \
+ a.l[ a.b ].u.op = o; \
+ }
+
+#define POP_STACK( a ) \
+ { \
+ if( EMPTY( a ) ) \
+ return qfalse; \
+ value = a.l[ a.b ]; \
+ a.b--; \
+ }
+
+#define PEEK_STACK_OP( a ) ( a.l[ a.b ].u.op )
+#define PEEK_STACK_VAL( a ) ( a.l[ a.b ].u.val )
+
+#define POP_FIFO( a ) \
+ { \
+ if( EMPTY( a ) ) \
+ return qfalse; \
+ value = a.l[ a.f ]; \
+ a.f++; \
+ }
+
+ stack.f = fifo.f = 0;
+ stack.b = fifo.b = -1;
+
+ while( trap_Parse_ReadToken( handle, &token ) )
+ {
+ if( !unmatchedParentheses && token.string[ 0 ] == ')' )
+ break;
+
+ // Special case to catch negative numbers
+ if( expectingNumber && token.string[ 0 ] == '-' )
+ {
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ token.floatvalue = -token.floatvalue;
+ }
+
+ if( token.type == TT_NUMBER )
+ {
+ if( !expectingNumber )
+ return qfalse;
+
+ expectingNumber = !expectingNumber;
+
+ PUSH_VAL( fifo, token.floatvalue );
+ }
+ else
+ {
+ switch( token.string[ 0 ] )
+ {
+ case '(':
+ unmatchedParentheses++;
+ PUSH_OP( stack, '(' );
+ break;
+
+ case ')':
+ unmatchedParentheses--;
+
+ if( unmatchedParentheses < 0 )
+ return qfalse;
+
+ while( !EMPTY( stack ) && PEEK_STACK_OP( stack ) != '(' )
+ {
+ POP_STACK( stack );
+ PUSH_OP( fifo, value.u.op );
+ }
+
+ // Pop the '('
+ POP_STACK( stack );
+
+ break;
+
+ case '*':
+ case '/':
+ case '+':
+ case '-':
+ if( expectingNumber )
+ return qfalse;
+
+ expectingNumber = !expectingNumber;
+
+ if( EMPTY( stack ) )
+ {
+ PUSH_OP( stack, token.string[ 0 ] );
+ }
+ else
+ {
+ while( !EMPTY( stack ) && OpPrec( token.string[ 0 ] ) < OpPrec( PEEK_STACK_OP( stack ) ) )
+ {
+ POP_STACK( stack );
+ PUSH_OP( fifo, value.u.op );
+ }
+
+ PUSH_OP( stack, token.string[ 0 ] );
+ }
+
+ break;
+
+ default:
+ // Unknown token
+ return qfalse;
+ }
+ }
+ }
+
+ while( !EMPTY( stack ) )
+ {
+ POP_STACK( stack );
+ PUSH_OP( fifo, value.u.op );
+ }
+
+ while( !EMPTY( fifo ) )
+ {
+ POP_FIFO( fifo );
+
+ if( value.type == EXPR_VALUE )
+ {
+ PUSH_VAL( stack, value.u.val );
+ }
+ else if( value.type == EXPR_OPERATOR )
+ {
+ char op = value.u.op;
+ float operand1, operand2, result;
+
+ POP_STACK( stack );
+ operand2 = value.u.val;
+ POP_STACK( stack );
+ operand1 = value.u.val;
+
+ switch( op )
+ {
+ case '*':
+ result = operand1 * operand2;
+ break;
+
+ case '/':
+ result = operand1 / operand2;
+ break;
+
+ case '+':
+ result = operand1 + operand2;
+ break;
+
+ case '-':
+ result = operand1 - operand2;
+ break;
+
+ default:
+ Com_Error( ERR_FATAL, "Unknown operator '%c' in postfix string", op );
+ return qfalse;
+ }
+
+ PUSH_VAL( stack, result );
+ }
+ }
+
+ POP_STACK( stack );
+
+ *f = value.u.val;
+
+ return qtrue;
+
+#undef FULL
+#undef EMPTY
+#undef PUSH_VAL
+#undef PUSH_OP
+#undef POP_STACK
+#undef PEEK_STACK_OP
+#undef PEEK_STACK_VAL
+#undef POP_FIFO
+}
+
+/*
+=================
+PC_Float_Parse
+=================
+*/
+qboolean PC_Float_Parse( int handle, float *f )
+{
+ pc_token_t token;
+ int negative = qfalse;
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ if( token.string[ 0 ] == '(' )
+ return PC_Expression_Parse( handle, f );
+
+ if( token.string[0] == '-' )
+ {
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ negative = qtrue;
+ }
+
+ if( token.type != TT_NUMBER )
+ {
+ PC_SourceError( handle, "expected float but found %s\n", token.string );
+ return qfalse;
+ }
+
+ if( negative )
+ * f = -token.floatvalue;
+ else
+ *f = token.floatvalue;
+
+ return qtrue;
+}
+
+/*
+=================
+Color_Parse
+=================
+*/
+qboolean Color_Parse( char **p, vec4_t *c )
+{
+ int i;
+ float f;
+
+ for( i = 0; i < 4; i++ )
+ {
+ if( !Float_Parse( p, &f ) )
+ return qfalse;
+
+ ( *c )[i] = f;
+ }
+
+ return qtrue;
+}
+
+/*
+=================
+PC_Color_Parse
+=================
+*/
+qboolean PC_Color_Parse( int handle, vec4_t *c )
+{
+ int i;
+ float f;
+
+ for( i = 0; i < 4; i++ )
+ {
+ if( !PC_Float_Parse( handle, &f ) )
+ return qfalse;
+
+ ( *c )[i] = f;
+ }
+
+ return qtrue;
+}
+
+/*
+=================
+Int_Parse
+=================
+*/
+qboolean Int_Parse( char **p, int *i )
+{
+ char *token;
+ token = COM_ParseExt( p, qfalse );
+
+ if( token && token[0] != 0 )
+ {
+ *i = atoi( token );
+ return qtrue;
+ }
+ else
+ return qfalse;
+}
+
+/*
+=================
+PC_Int_Parse
+=================
+*/
+qboolean PC_Int_Parse( int handle, int *i )
+{
+ pc_token_t token;
+ int negative = qfalse;
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ if( token.string[ 0 ] == '(' )
+ {
+ float f;
+
+ if( PC_Expression_Parse( handle, &f ) )
+ {
+ *i = ( int )f;
+ return qtrue;
+ }
+ else
+ return qfalse;
+ }
+
+ if( token.string[0] == '-' )
+ {
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ negative = qtrue;
+ }
+
+ if( token.type != TT_NUMBER )
+ {
+ PC_SourceError( handle, "expected integer but found %s\n", token.string );
+ return qfalse;
+ }
+
+ *i = token.intvalue;
+
+ if( negative )
+ * i = - *i;
+
+ return qtrue;
+}
+
+/*
+=================
+Rect_Parse
+=================
+*/
+qboolean Rect_Parse( char **p, rectDef_t *r )
+{
+ if( Float_Parse( p, &r->x ) )
+ {
+ if( Float_Parse( p, &r->y ) )
+ {
+ if( Float_Parse( p, &r->w ) )
+ {
+ if( Float_Parse( p, &r->h ) )
+ return qtrue;
+ }
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+=================
+PC_Rect_Parse
+=================
+*/
+qboolean PC_Rect_Parse( int handle, rectDef_t *r )
+{
+ if( PC_Float_Parse( handle, &r->x ) )
+ {
+ if( PC_Float_Parse( handle, &r->y ) )
+ {
+ if( PC_Float_Parse( handle, &r->w ) )
+ {
+ if( PC_Float_Parse( handle, &r->h ) )
+ return qtrue;
+ }
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+=================
+String_Parse
+=================
+*/
+qboolean String_Parse( char **p, const char **out )
+{
+ char *token;
+
+ token = COM_ParseExt( p, qfalse );
+
+ if( token && token[0] != 0 )
+ {
+ *( out ) = String_Alloc( token );
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+/*
+=================
+PC_String_Parse
+=================
+*/
+qboolean PC_String_Parse( int handle, const char **out )
+{
+ pc_token_t token;
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ *( out ) = String_Alloc( token.string );
+
+ return qtrue;
+}
+
+/*
+=================
+PC_Script_Parse
+=================
+*/
+qboolean PC_Script_Parse( int handle, const char **out )
+{
+ char script[1024];
+ pc_token_t token;
+
+ memset( script, 0, sizeof( script ) );
+ // scripts start with { and have ; separated command lists.. commands are command, arg..
+ // basically we want everything between the { } as it will be interpreted at run time
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ if( Q_stricmp( token.string, "{" ) != 0 )
+ return qfalse;
+
+ while( 1 )
+ {
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ if( Q_stricmp( token.string, "}" ) == 0 )
+ {
+ *out = String_Alloc( script );
+ return qtrue;
+ }
+
+ if( token.string[1] != '\0' )
+ Q_strcat( script, 1024, va( "\"%s\"", token.string ) );
+ else
+ Q_strcat( script, 1024, token.string );
+
+ Q_strcat( script, 1024, " " );
+ }
+
+ return qfalse;
+}
+
+// display, window, menu, item code
+//
+
+/*
+==================
+Init_Display
+
+Initializes the display with a structure to all the drawing routines
+==================
+*/
+void Init_Display( displayContextDef_t *dc )
+{
+ DC = dc;
+}
+
+
+
+// type and style painting
+
+void GradientBar_Paint( rectDef_t *rect, vec4_t color )
+{
+ // gradient bar takes two paints
+ DC->setColor( color );
+ DC->drawHandlePic( rect->x, rect->y, rect->w, rect->h, DC->Assets.gradientBar );
+ DC->setColor( NULL );
+}
+
+
+/*
+==================
+Window_Init
+
+Initializes a window structure ( windowDef_t ) with defaults
+==================
+*/
+void Window_Init( Window *w )
+{
+ memset( w, 0, sizeof( windowDef_t ) );
+ w->borderSize = 1;
+ w->foreColor[0] = w->foreColor[1] = w->foreColor[2] = w->foreColor[3] = 1.0;
+ w->cinematic = -1;
+}
+
+void Fade( int *flags, float *f, float clamp, int *nextTime, int offsetTime, qboolean bFlags, float fadeAmount )
+{
+ if( *flags & ( WINDOW_FADINGOUT | WINDOW_FADINGIN ) )
+ {
+ if( DC->realTime > *nextTime )
+ {
+ *nextTime = DC->realTime + offsetTime;
+
+ if( *flags & WINDOW_FADINGOUT )
+ {
+ *f -= fadeAmount;
+
+ if( bFlags && *f <= 0.0 )
+ *flags &= ~( WINDOW_FADINGOUT | WINDOW_VISIBLE );
+ }
+ else
+ {
+ *f += fadeAmount;
+
+ if( *f >= clamp )
+ {
+ *f = clamp;
+
+ if( bFlags )
+ *flags &= ~WINDOW_FADINGIN;
+ }
+ }
+ }
+ }
+}
+
+
+
+static void Window_Paint( Window *w, float fadeAmount, float fadeClamp, float fadeCycle )
+{
+ vec4_t color;
+ rectDef_t fillRect = w->rect;
+
+
+ if( DC->getCVarValue( "ui_developer" ) )
+ {
+ color[0] = color[1] = color[2] = color[3] = 1;
+ DC->drawRect( w->rect.x, w->rect.y, w->rect.w, w->rect.h, 1, color );
+ }
+
+ if( w == NULL || ( w->style == 0 && w->border == 0 ) )
+ return;
+
+ if( w->border != 0 )
+ {
+ fillRect.x += w->borderSize;
+ fillRect.y += w->borderSize;
+ fillRect.w -= w->borderSize + 1;
+ fillRect.h -= w->borderSize + 1;
+ }
+
+ if( w->style == WINDOW_STYLE_FILLED )
+ {
+ // box, but possible a shader that needs filled
+
+ if( w->background )
+ {
+ Fade( &w->flags, &w->backColor[3], fadeClamp, &w->nextTime, fadeCycle, qtrue, fadeAmount );
+ DC->setColor( w->backColor );
+ DC->drawHandlePic( fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background );
+ DC->setColor( NULL );
+ }
+ else
+ DC->fillRect( fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->backColor );
+ }
+ else if( w->style == WINDOW_STYLE_GRADIENT )
+ {
+ GradientBar_Paint( &fillRect, w->backColor );
+ // gradient bar
+ }
+ else if( w->style == WINDOW_STYLE_SHADER )
+ {
+ if( w->flags & WINDOW_FORECOLORSET )
+ DC->setColor( w->foreColor );
+
+ DC->drawHandlePic( fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background );
+ DC->setColor( NULL );
+ }
+ else if( w->style == WINDOW_STYLE_CINEMATIC )
+ {
+ if( w->cinematic == -1 )
+ {
+ w->cinematic = DC->playCinematic( w->cinematicName, fillRect.x, fillRect.y, fillRect.w, fillRect.h );
+
+ if( w->cinematic == -1 )
+ w->cinematic = -2;
+ }
+
+ if( w->cinematic >= 0 )
+ {
+ DC->runCinematicFrame( w->cinematic );
+ DC->drawCinematic( w->cinematic, fillRect.x, fillRect.y, fillRect.w, fillRect.h );
+ }
+ }
+}
+
+static void Border_Paint( Window *w )
+{
+ if( w == NULL || ( w->style == 0 && w->border == 0 ) )
+ return;
+
+ if( w->border == WINDOW_BORDER_FULL )
+ {
+ // full
+ DC->drawRect( w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize, w->borderColor );
+ }
+ else if( w->border == WINDOW_BORDER_HORZ )
+ {
+ // top/bottom
+ DC->setColor( w->borderColor );
+ DC->drawTopBottom( w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize );
+ DC->setColor( NULL );
+ }
+ else if( w->border == WINDOW_BORDER_VERT )
+ {
+ // left right
+ DC->setColor( w->borderColor );
+ DC->drawSides( w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize );
+ DC->setColor( NULL );
+ }
+ else if( w->border == WINDOW_BORDER_KCGRADIENT )
+ {
+ // this is just two gradient bars along each horz edge
+ rectDef_t r = w->rect;
+ r.h = w->borderSize;
+ GradientBar_Paint( &r, w->borderColor );
+ r.y = w->rect.y + w->rect.h - 1;
+ GradientBar_Paint( &r, w->borderColor );
+ }
+}
+
+
+void Item_SetScreenCoords( itemDef_t *item, float x, float y )
+{
+ if( item == NULL )
+ return;
+
+ if( item->window.border != 0 )
+ {
+ x += item->window.borderSize;
+ y += item->window.borderSize;
+ }
+
+ item->window.rect.x = x + item->window.rectClient.x;
+ item->window.rect.y = y + item->window.rectClient.y;
+ item->window.rect.w = item->window.rectClient.w;
+ item->window.rect.h = item->window.rectClient.h;
+
+ // force the text rects to recompute
+ item->textRect.w = 0;
+ item->textRect.h = 0;
+}
+
+// FIXME: consolidate this with nearby stuff
+void Item_UpdatePosition( itemDef_t *item )
+{
+ float x, y;
+ menuDef_t *menu;
+
+ if( item == NULL || item->parent == NULL )
+ return;
+
+ menu = item->parent;
+
+ x = menu->window.rect.x;
+ y = menu->window.rect.y;
+
+ if( menu->window.border != 0 )
+ {
+ x += menu->window.borderSize;
+ y += menu->window.borderSize;
+ }
+
+ Item_SetScreenCoords( item, x, y );
+
+}
+
+// menus
+void Menu_UpdatePosition( menuDef_t *menu )
+{
+ int i;
+ float x, y;
+
+ if( menu == NULL )
+ return;
+
+ x = menu->window.rect.x;
+ y = menu->window.rect.y;
+
+ if( menu->window.border != 0 )
+ {
+ x += menu->window.borderSize;
+ y += menu->window.borderSize;
+ }
+
+ for( i = 0; i < menu->itemCount; i++ )
+ Item_SetScreenCoords( menu->items[i], x, y );
+}
+
+static void Menu_AspectiseRect( int bias, Rectangle *rect )
+{
+ switch( bias )
+ {
+ case ALIGN_LEFT:
+ rect->x *= DC->aspectScale;
+ rect->w *= DC->aspectScale;
+ break;
+
+ case ALIGN_CENTER:
+ rect->x = ( rect->x * DC->aspectScale ) +
+ ( 320.0f - ( 320.0f * DC->aspectScale ) );
+ rect->w *= DC->aspectScale;
+ break;
+
+ case ALIGN_RIGHT:
+ rect->x = 640.0f - ( ( 640.0f - rect->x ) * DC->aspectScale );
+ rect->w *= DC->aspectScale;
+ break;
+
+ default:
+
+ case ASPECT_NONE:
+ break;
+ }
+}
+
+void Menu_AspectCompensate( menuDef_t *menu )
+{
+ int i;
+
+ if( menu->window.aspectBias != ASPECT_NONE )
+ {
+ Menu_AspectiseRect( menu->window.aspectBias, &menu->window.rect );
+
+ for( i = 0; i < menu->itemCount; i++ )
+ {
+ menu->items[ i ]->window.rectClient.x *= DC->aspectScale;
+ menu->items[ i ]->window.rectClient.w *= DC->aspectScale;
+ menu->items[ i ]->textalignx *= DC->aspectScale;
+ }
+ }
+ else
+ {
+ for( i = 0; i < menu->itemCount; i++ )
+ {
+ Menu_AspectiseRect( menu->items[ i ]->window.aspectBias,
+ &menu->items[ i ]->window.rectClient );
+
+ if( menu->items[ i ]->window.aspectBias != ASPECT_NONE )
+ menu->items[ i ]->textalignx *= DC->aspectScale;
+ }
+ }
+}
+
+void Menu_PostParse( menuDef_t *menu )
+{
+ int i, j;
+
+ if( menu == NULL )
+ return;
+
+ if( menu->fullScreen )
+ {
+ menu->window.rect.x = 0;
+ menu->window.rect.y = 0;
+ menu->window.rect.w = 640;
+ menu->window.rect.h = 480;
+ }
+
+ Menu_AspectCompensate( menu );
+ Menu_UpdatePosition( menu );
+
+ // Push lists to the end of the array as they can potentially be drawn on top
+ // of other elements
+ for( i = 0; i < menu->itemCount; i++ )
+ {
+ itemDef_t *item = menu->items[ i ];
+
+ if( Item_IsListBox( item ) )
+ {
+ for( j = i; j < menu->itemCount - 1; j++ )
+ menu->items[ j ] = menu->items[ j + 1 ];
+
+ menu->items[ j ] = item;
+ }
+ }
+}
+
+itemDef_t *Menu_ClearFocus( menuDef_t *menu )
+{
+ int i;
+ itemDef_t *ret = NULL;
+
+ if( menu == NULL )
+ return NULL;
+
+ for( i = 0; i < menu->itemCount; i++ )
+ {
+ if( menu->items[i]->window.flags & WINDOW_HASFOCUS )
+ ret = menu->items[i];
+
+ menu->items[i]->window.flags &= ~WINDOW_HASFOCUS;
+
+ if( menu->items[i]->leaveFocus )
+ Item_RunScript( menu->items[i], menu->items[i]->leaveFocus );
+ }
+
+ return ret;
+}
+
+qboolean IsVisible( int flags )
+{
+ return ( flags & WINDOW_VISIBLE && !( flags & WINDOW_FADINGOUT ) );
+}
+
+qboolean Rect_ContainsPoint( rectDef_t *rect, float x, float y )
+{
+ if( rect )
+ {
+ if( x > rect->x && x < rect->x + rect->w && y > rect->y && y < rect->y + rect->h )
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+int Menu_ItemsMatchingGroup( menuDef_t *menu, const char *name )
+{
+ int i;
+ int count = 0;
+
+ for( i = 0; i < menu->itemCount; i++ )
+ {
+ if( Q_stricmp( menu->items[i]->window.name, name ) == 0 ||
+ ( menu->items[i]->window.group && Q_stricmp( menu->items[i]->window.group, name ) == 0 ) )
+ {
+ count++;
+ }
+ }
+
+ return count;
+}
+
+itemDef_t *Menu_GetMatchingItemByNumber( menuDef_t *menu, int index, const char *name )
+{
+ int i;
+ int count = 0;
+
+ for( i = 0; i < menu->itemCount; i++ )
+ {
+ if( Q_stricmp( menu->items[i]->window.name, name ) == 0 ||
+ ( menu->items[i]->window.group && Q_stricmp( menu->items[i]->window.group, name ) == 0 ) )
+ {
+ if( count == index )
+ return menu->items[i];
+
+ count++;
+ }
+ }
+
+ return NULL;
+}
+
+
+
+void Script_SetColor( itemDef_t *item, char **args )
+{
+ const char *name;
+ int i;
+ float f;
+ vec4_t *out;
+ // expecting type of color to set and 4 args for the color
+
+ if( String_Parse( args, &name ) )
+ {
+ out = NULL;
+
+ if( Q_stricmp( name, "backcolor" ) == 0 )
+ {
+ out = &item->window.backColor;
+ item->window.flags |= WINDOW_BACKCOLORSET;
+ }
+ else if( Q_stricmp( name, "forecolor" ) == 0 )
+ {
+ out = &item->window.foreColor;
+ item->window.flags |= WINDOW_FORECOLORSET;
+ }
+ else if( Q_stricmp( name, "bordercolor" ) == 0 )
+ out = &item->window.borderColor;
+
+ if( out )
+ {
+ for( i = 0; i < 4; i++ )
+ {
+ if( !Float_Parse( args, &f ) )
+ return;
+
+ ( *out )[i] = f;
+ }
+ }
+ }
+}
+
+void Script_SetAsset( itemDef_t *item, char **args )
+{
+ const char *name;
+ // expecting name to set asset to
+
+ if( String_Parse( args, &name ) )
+ {
+ // check for a model
+
+ if( item->type == ITEM_TYPE_MODEL )
+ {}
+
+ }
+
+}
+
+void Script_SetBackground( itemDef_t *item, char **args )
+{
+ const char *name;
+ // expecting name to set asset to
+
+ if( String_Parse( args, &name ) )
+ item->window.background = DC->registerShaderNoMip( name );
+}
+
+
+
+
+itemDef_t *Menu_FindItemByName( menuDef_t *menu, const char *p )
+{
+ int i;
+
+ if( menu == NULL || p == NULL )
+ return NULL;
+
+ for( i = 0; i < menu->itemCount; i++ )
+ {
+ if( Q_stricmp( p, menu->items[i]->window.name ) == 0 )
+ return menu->items[i];
+ }
+
+ return NULL;
+}
+
+void Script_SetItemColor( itemDef_t *item, char **args )
+{
+ const char *itemname;
+ const char *name;
+ vec4_t color;
+ int i;
+ vec4_t *out;
+ // expecting type of color to set and 4 args for the color
+
+ if( String_Parse( args, &itemname ) && String_Parse( args, &name ) )
+ {
+ itemDef_t * item2;
+ int j;
+ int count = Menu_ItemsMatchingGroup( item->parent, itemname );
+
+ if( !Color_Parse( args, &color ) )
+ return;
+
+ for( j = 0; j < count; j++ )
+ {
+ item2 = Menu_GetMatchingItemByNumber( item->parent, j, itemname );
+
+ if( item2 != NULL )
+ {
+ out = NULL;
+
+ if( Q_stricmp( name, "backcolor" ) == 0 )
+ out = &item2->window.backColor;
+ else if( Q_stricmp( name, "forecolor" ) == 0 )
+ {
+ out = &item2->window.foreColor;
+ item2->window.flags |= WINDOW_FORECOLORSET;
+ }
+ else if( Q_stricmp( name, "bordercolor" ) == 0 )
+ out = &item2->window.borderColor;
+
+ if( out )
+ {
+ for( i = 0; i < 4; i++ )
+ ( *out )[i] = color[i];
+ }
+ }
+ }
+ }
+}
+
+
+void Menu_ShowItemByName( menuDef_t *menu, const char *p, qboolean bShow )
+{
+ itemDef_t * item;
+ int i;
+ int count = Menu_ItemsMatchingGroup( menu, p );
+
+ for( i = 0; i < count; i++ )
+ {
+ item = Menu_GetMatchingItemByNumber( menu, i, p );
+
+ if( item != NULL )
+ {
+ if( bShow )
+ item->window.flags |= WINDOW_VISIBLE;
+ else
+ {
+ item->window.flags &= ~WINDOW_VISIBLE;
+ // stop cinematics playing in the window
+
+ if( item->window.cinematic >= 0 )
+ {
+ DC->stopCinematic( item->window.cinematic );
+ item->window.cinematic = -1;
+ }
+ }
+ }
+ }
+}
+
+void Menu_FadeItemByName( menuDef_t *menu, const char *p, qboolean fadeOut )
+{
+ itemDef_t * item;
+ int i;
+ int count = Menu_ItemsMatchingGroup( menu, p );
+
+ for( i = 0; i < count; i++ )
+ {
+ item = Menu_GetMatchingItemByNumber( menu, i, p );
+
+ if( item != NULL )
+ {
+ if( fadeOut )
+ {
+ item->window.flags |= ( WINDOW_FADINGOUT | WINDOW_VISIBLE );
+ item->window.flags &= ~WINDOW_FADINGIN;
+ }
+ else
+ {
+ item->window.flags |= ( WINDOW_VISIBLE | WINDOW_FADINGIN );
+ item->window.flags &= ~WINDOW_FADINGOUT;
+ }
+ }
+ }
+}
+
+menuDef_t *Menus_FindByName( const char *p )
+{
+ int i;
+
+ for( i = 0; i < menuCount; i++ )
+ {
+ if( Q_stricmp( Menus[i].window.name, p ) == 0 )
+ return & Menus[i];
+ }
+
+ return NULL;
+}
+
+static void Menu_RunCloseScript( menuDef_t *menu )
+{
+ if( menu && menu->window.flags & WINDOW_VISIBLE && menu->onClose )
+ {
+ itemDef_t item;
+ item.parent = menu;
+ Item_RunScript( &item, menu->onClose );
+ }
+}
+
+static void Menus_Close( menuDef_t *menu )
+{
+ if( menu != NULL )
+ {
+ Menu_RunCloseScript( menu );
+ menu->window.flags &= ~( WINDOW_VISIBLE | WINDOW_HASFOCUS );
+
+ if( openMenuCount > 0 )
+ openMenuCount--;
+
+ if( openMenuCount > 0 )
+ Menus_Activate( menuStack[ openMenuCount - 1 ] );
+ }
+}
+
+void Menus_CloseByName( const char *p )
+{
+ Menus_Close( Menus_FindByName( p ) );
+}
+
+void Menus_CloseAll( void )
+{
+ int i;
+
+ // Close any menus on the stack first
+ if( openMenuCount > 0 )
+ {
+ for( i = openMenuCount; i > 0; i-- )
+ Menus_Close( menuStack[ i ] );
+
+ openMenuCount = 0;
+ }
+
+ // Close all other menus
+ for( i = 0; i < menuCount; i++ )
+ Menus_Close( &Menus[ i ] );
+
+ g_editingField = qfalse;
+ g_waitingForKey = qfalse;
+ g_comboBoxItem = NULL;
+}
+
+
+void Script_Show( itemDef_t *item, char **args )
+{
+ const char *name;
+
+ if( String_Parse( args, &name ) )
+ Menu_ShowItemByName( item->parent, name, qtrue );
+}
+
+void Script_Hide( itemDef_t *item, char **args )
+{
+ const char *name;
+
+ if( String_Parse( args, &name ) )
+ Menu_ShowItemByName( item->parent, name, qfalse );
+}
+
+void Script_FadeIn( itemDef_t *item, char **args )
+{
+ const char *name;
+
+ if( String_Parse( args, &name ) )
+ Menu_FadeItemByName( item->parent, name, qfalse );
+}
+
+void Script_FadeOut( itemDef_t *item, char **args )
+{
+ const char *name;
+
+ if( String_Parse( args, &name ) )
+ Menu_FadeItemByName( item->parent, name, qtrue );
+}
+
+
+
+void Script_Open( itemDef_t *item, char **args )
+{
+ const char *name;
+
+ if( String_Parse( args, &name ) )
+ Menus_ActivateByName( name );
+}
+
+void Script_ConditionalOpen( itemDef_t *item, char **args )
+{
+ const char *cvar;
+ const char *name1;
+ const char *name2;
+ float val;
+
+ if( String_Parse( args, &cvar ) && String_Parse( args, &name1 ) && String_Parse( args, &name2 ) )
+ {
+ val = DC->getCVarValue( cvar );
+
+ if( val == 0.0f )
+ Menus_ActivateByName( name2 );
+ else
+ Menus_ActivateByName( name1 );
+ }
+}
+
+void Script_Close( itemDef_t *item, char **args )
+{
+ const char *name;
+
+ if( String_Parse( args, &name ) )
+ Menus_CloseByName( name );
+}
+
+void Menu_TransitionItemByName( menuDef_t *menu, const char *p, rectDef_t rectFrom, rectDef_t rectTo,
+ int time, float amt )
+{
+ itemDef_t *item;
+ int i;
+ int count = Menu_ItemsMatchingGroup( menu, p );
+
+ for( i = 0; i < count; i++ )
+ {
+ item = Menu_GetMatchingItemByNumber( menu, i, p );
+
+ if( item != NULL )
+ {
+ item->window.flags |= ( WINDOW_INTRANSITION | WINDOW_VISIBLE );
+ item->window.offsetTime = time;
+ memcpy( &item->window.rectClient, &rectFrom, sizeof( rectDef_t ) );
+ memcpy( &item->window.rectEffects, &rectTo, sizeof( rectDef_t ) );
+ item->window.rectEffects2.x = abs( rectTo.x - rectFrom.x ) / amt;
+ item->window.rectEffects2.y = abs( rectTo.y - rectFrom.y ) / amt;
+ item->window.rectEffects2.w = abs( rectTo.w - rectFrom.w ) / amt;
+ item->window.rectEffects2.h = abs( rectTo.h - rectFrom.h ) / amt;
+ Item_UpdatePosition( item );
+ }
+ }
+}
+
+
+void Script_Transition( itemDef_t *item, char **args )
+{
+ const char *name;
+ rectDef_t rectFrom, rectTo;
+ int time;
+ float amt;
+
+ if( String_Parse( args, &name ) )
+ {
+ if( Rect_Parse( args, &rectFrom ) && Rect_Parse( args, &rectTo ) &&
+ Int_Parse( args, &time ) && Float_Parse( args, &amt ) )
+ {
+ Menu_TransitionItemByName( item->parent, name, rectFrom, rectTo, time, amt );
+ }
+ }
+}
+
+
+void Menu_OrbitItemByName( menuDef_t *menu, const char *p, float x, float y, float cx, float cy, int time )
+{
+ itemDef_t *item;
+ int i;
+ int count = Menu_ItemsMatchingGroup( menu, p );
+
+ for( i = 0; i < count; i++ )
+ {
+ item = Menu_GetMatchingItemByNumber( menu, i, p );
+
+ if( item != NULL )
+ {
+ item->window.flags |= ( WINDOW_ORBITING | WINDOW_VISIBLE );
+ item->window.offsetTime = time;
+ item->window.rectEffects.x = cx;
+ item->window.rectEffects.y = cy;
+ item->window.rectClient.x = x;
+ item->window.rectClient.y = y;
+ Item_UpdatePosition( item );
+ }
+ }
+}
+
+
+void Script_Orbit( itemDef_t *item, char **args )
+{
+ const char *name;
+ float cx, cy, x, y;
+ int time;
+
+ if( String_Parse( args, &name ) )
+ {
+ if( Float_Parse( args, &x ) && Float_Parse( args, &y ) &&
+ Float_Parse( args, &cx ) && Float_Parse( args, &cy ) && Int_Parse( args, &time ) )
+ {
+ Menu_OrbitItemByName( item->parent, name, x, y, cx, cy, time );
+ }
+ }
+}
+
+
+
+void Script_SetFocus( itemDef_t *item, char **args )
+{
+ const char *name;
+ itemDef_t *focusItem;
+
+ if( String_Parse( args, &name ) )
+ {
+ focusItem = Menu_FindItemByName( item->parent, name );
+
+ if( focusItem && !( focusItem->window.flags & WINDOW_DECORATION ) )
+ {
+ Menu_ClearFocus( item->parent );
+ focusItem->window.flags |= WINDOW_HASFOCUS;
+
+ if( focusItem->onFocus )
+ Item_RunScript( focusItem, focusItem->onFocus );
+
+ // Edit fields get activated too
+ if( Item_IsEditField( focusItem ) )
+ {
+ g_editingField = qtrue;
+ g_editItem = focusItem;
+ }
+
+ if( DC->Assets.itemFocusSound )
+ DC->startLocalSound( DC->Assets.itemFocusSound, CHAN_LOCAL_SOUND );
+ }
+ }
+}
+
+void Script_Reset( itemDef_t *item, char **args )
+{
+ const char *name;
+ itemDef_t *resetItem;
+
+ if( String_Parse( args, &name ) )
+ {
+ resetItem = Menu_FindItemByName( item->parent, name );
+
+ if( resetItem )
+ {
+ if( Item_IsListBox( resetItem ) )
+ {
+ resetItem->cursorPos = DC->feederInitialise( resetItem->feederID );
+ Item_ListBox_SetStartPos( resetItem, 0 );
+ DC->feederSelection( resetItem->feederID, resetItem->cursorPos );
+ }
+ }
+ }
+}
+
+void Script_SetPlayerModel( itemDef_t *item, char **args )
+{
+ const char *name;
+
+ if( String_Parse( args, &name ) )
+ DC->setCVar( "team_model", name );
+}
+
+void Script_SetPlayerHead( itemDef_t *item, char **args )
+{
+ const char *name;
+
+ if( String_Parse( args, &name ) )
+ DC->setCVar( "team_headmodel", name );
+}
+
+void Script_SetCvar( itemDef_t *item, char **args )
+{
+ const char *cvar, *val;
+
+ if( String_Parse( args, &cvar ) && String_Parse( args, &val ) )
+ DC->setCVar( cvar, val );
+
+}
+
+void Script_Exec( itemDef_t *item, char **args )
+{
+ const char *val;
+
+ if( String_Parse( args, &val ) )
+ DC->executeText( EXEC_APPEND, va( "%s ; ", val ) );
+}
+
+void Script_Play( itemDef_t *item, char **args )
+{
+ const char *val;
+
+ if( String_Parse( args, &val ) )
+ DC->startLocalSound( DC->registerSound( val, qfalse ), CHAN_LOCAL_SOUND );
+}
+
+void Script_playLooped( itemDef_t *item, char **args )
+{
+ const char *val;
+
+ if( String_Parse( args, &val ) )
+ {
+ DC->stopBackgroundTrack();
+ DC->startBackgroundTrack( val, val );
+ }
+}
+
+static ID_INLINE float UI_EmoticonHeight( fontInfo_t *font, float scale )
+{
+ return font->glyphs[ (int)'[' ].height * scale * font->glyphScale;
+}
+
+static ID_INLINE float UI_EmoticonWidth( fontInfo_t *font, float scale )
+{
+ return UI_EmoticonHeight( font, scale ) * DC->aspectScale;
+}
+
+void UI_EscapeEmoticons( char *dest, const char *src, int destsize )
+{
+ int len;
+ qboolean escaped;
+
+ for( ; *src && destsize > 1; src++, destsize-- )
+ {
+ if( UI_Text_IsEmoticon( src, &escaped, &len, NULL, NULL ) && !escaped )
+ {
+ *dest++ = '[';
+ destsize--;
+ }
+
+ *dest++ = *src;
+ }
+
+ *dest++ = '\0';
+}
+
+qboolean UI_Text_IsEmoticon( const char *s, qboolean *escaped,
+ int *length, qhandle_t *h, int *width )
+{
+ const char *p = s;
+ char emoticon[ MAX_EMOTICON_NAME_LEN ];
+ int i;
+
+ if( *p != '[' )
+ return qfalse;
+ p++;
+
+ if( *p == '[' )
+ {
+ *escaped = qtrue;
+ p++;
+ }
+ else
+ *escaped = qfalse;
+
+ for( *length = 0; p[ *length ] != ']'; ( *length )++ )
+ {
+ if( !p[ *length ] || *length == MAX_EMOTICON_NAME_LEN - 1 )
+ return qfalse;
+
+ emoticon[ *length ] = p[ *length ];
+ }
+ emoticon[ *length ] = '\0';
+
+ for( i = 0; i < DC->Assets.emoticonCount; i++ )
+ if( !Q_stricmp( DC->Assets.emoticons[ i ].name, emoticon ) )
+ break;
+
+ if( i == DC->Assets.emoticonCount )
+ return qfalse;
+
+ if( h )
+ *h = DC->Assets.emoticons[ i ].shader;
+ if( width )
+ *width = DC->Assets.emoticons[ i ].width;
+
+ ( *length ) += 2;
+
+ if( *escaped )
+ ( *length )++;
+
+ return qtrue;
+}
+
+static float UI_Parse_Indent( const char **text )
+{
+ char indentWidth[ 32 ];
+ char *indentWidthPtr;
+ const char *p = *text;
+ int numDigits;
+ float pixels;
+
+ while( isdigit( *p ) || *p == '.' )
+ p++;
+
+ if( *p != INDENT_MARKER )
+ return 0.0f;
+
+ numDigits = ( p - *text );
+
+ if( numDigits > sizeof( indentWidth ) - 1 )
+ return 0.0f;
+
+ strncpy( indentWidth, *text, numDigits );
+
+ indentWidth[ numDigits ] = '\0';
+ indentWidthPtr = indentWidth;
+
+ if( !Float_Parse( &indentWidthPtr, &pixels ) )
+ return 0.0f;
+
+ (*text) += ( numDigits + 1 );
+
+ return pixels;
+}
+
+static ID_INLINE fontInfo_t *UI_FontForScale( float scale )
+{
+ if( scale <= DC->smallFontScale )
+ return &DC->Assets.smallFont;
+ else if( scale >= DC->bigFontScale )
+ return &DC->Assets.bigFont;
+ else
+ return &DC->Assets.textFont;
+}
+
+float UI_Char_Width( const char **text, float scale )
+{
+ glyphInfo_t *glyph;
+ fontInfo_t *font;
+ int emoticonLen;
+ qboolean emoticonEscaped;
+ int emoticonWidth;
+
+ if( text && *text )
+ {
+ if( Q_IsColorString( *text ) )
+ {
+ *text += 2;
+ return 0.0f;
+ }
+
+ if( **text == INDENT_MARKER )
+ {
+ (*text)++;
+ return 0.0f;
+ }
+
+ font = UI_FontForScale( scale );
+
+ if( UI_Text_IsEmoticon( *text, &emoticonEscaped, &emoticonLen,
+ NULL, &emoticonWidth ) )
+ {
+ if( emoticonEscaped )
+ (*text)++;
+ else
+ {
+ *text += emoticonLen;
+ return emoticonWidth * UI_EmoticonWidth( font, scale );
+ }
+ }
+
+ (*text)++;
+
+ glyph = &font->glyphs[ (int)**text ];
+ return glyph->xSkip * DC->aspectScale * scale * font->glyphScale;
+ }
+
+ return 0.0f;
+}
+
+float UI_Text_Width( const char *text, float scale )
+{
+ float out;
+ const char *s = text;
+ float indentWidth = 0.0f;
+
+ out = 0.0f;
+
+ if( text )
+ {
+ indentWidth = UI_Parse_Indent( &s );
+
+ while( *s )
+ out += UI_Char_Width( &s, scale );
+ }
+
+ return out + indentWidth;
+}
+
+float UI_Text_Height( const char *text, float scale )
+{
+ float max;
+ glyphInfo_t *glyph;
+ float useScale;
+ const char *s = text;
+ fontInfo_t *font = UI_FontForScale( scale );
+
+ useScale = scale * font->glyphScale;
+ max = 0;
+
+ if( text )
+ {
+ while( s && *s )
+ {
+ if( Q_IsColorString( s ) )
+ {
+ s += 2;
+ continue;
+ }
+ else
+ {
+ glyph = &font->glyphs[( int )*s];
+
+ if( max < glyph->height )
+ max = glyph->height;
+
+ s++;
+ }
+ }
+ }
+
+ return max * useScale;
+}
+
+float UI_Text_EmWidth( float scale )
+{
+ return UI_Text_Width( "M", scale );
+}
+
+float UI_Text_EmHeight( float scale )
+{
+ return UI_Text_Height( "M", scale );
+}
+
+
+/*
+================
+UI_AdjustFrom640
+
+Adjusted for resolution and screen aspect ratio
+================
+*/
+void UI_AdjustFrom640( float *x, float *y, float *w, float *h )
+{
+ *x *= DC->xscale;
+ *y *= DC->yscale;
+ *w *= DC->xscale;
+ *h *= DC->yscale;
+}
+
+/*
+================
+UI_SetClipRegion
+=================
+*/
+void UI_SetClipRegion( float x, float y, float w, float h )
+{
+ vec4_t clip;
+
+ UI_AdjustFrom640( &x, &y, &w, &h );
+
+ clip[ 0 ] = x;
+ clip[ 1 ] = y;
+ clip[ 2 ] = x + w;
+ clip[ 3 ] = y + h;
+
+ trap_R_SetClipRegion( clip );
+}
+
+/*
+================
+UI_ClearClipRegion
+=================
+*/
+void UI_ClearClipRegion( void )
+{
+ trap_R_SetClipRegion( NULL );
+}
+
+static void UI_Text_PaintChar( float x, float y, float scale,
+ glyphInfo_t *glyph, float size )
+{
+ float w, h;
+
+ w = glyph->imageWidth;
+ h = glyph->imageHeight;
+
+ if( size > 0.0f )
+ {
+ float half = size * 0.5f * scale;
+ x -= half;
+ y -= half;
+ w += size;
+ h += size;
+ }
+
+ w *= ( DC->aspectScale * scale );
+ h *= scale;
+ y -= ( glyph->top * scale );
+ UI_AdjustFrom640( &x, &y, &w, &h );
+
+ DC->drawStretchPic( x, y, w, h,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+}
+
+static void UI_Text_Paint_Generic( float x, float y, float scale, float gapAdjust,
+ const char *text, vec4_t color, int style,
+ int limit, float *maxX,
+ int cursorPos, char cursor )
+{
+ const char *s = text;
+ int len;
+ int count = 0;
+ vec4_t newColor;
+ fontInfo_t *font = UI_FontForScale( scale );
+ glyphInfo_t *glyph;
+ float useScale;
+ qhandle_t emoticonHandle = 0;
+ float emoticonH, emoticonW;
+ qboolean emoticonEscaped;
+ int emoticonLen = 0;
+ int emoticonWidth;
+ int cursorX = -1;
+
+ if( !text )
+ return;
+
+ useScale = scale * font->glyphScale;
+
+ emoticonH = UI_EmoticonHeight( font, scale );
+ emoticonW = UI_EmoticonWidth( font, scale );
+
+ len = strlen( text );
+ if( limit > 0 && len > limit )
+ len = limit;
+
+ DC->setColor( color );
+ memcpy( &newColor[0], &color[0], sizeof( vec4_t ) );
+
+ x += UI_Parse_Indent( &s );
+
+ while( s && *s && count < len )
+ {
+ const char *t = s;
+ float charWidth = UI_Char_Width( &t, scale );
+ glyph = &font->glyphs[ (int)*s ];
+
+ if( maxX && charWidth + x > *maxX )
+ {
+ *maxX = 0;
+ break;
+ }
+
+ if( cursorPos < 0 )
+ {
+ if( Q_IsColorString( s ) )
+ {
+ memcpy( newColor, g_color_table[ColorIndex( *( s+1 ) )],
+ sizeof( newColor ) );
+ newColor[3] = color[3];
+ DC->setColor( newColor );
+ s += 2;
+ continue;
+ }
+
+ if( *s == INDENT_MARKER )
+ {
+ s++;
+ continue;
+ }
+
+ if( UI_Text_IsEmoticon( s, &emoticonEscaped, &emoticonLen,
+ &emoticonHandle, &emoticonWidth ) )
+ {
+ if( emoticonEscaped )
+ s++;
+ else
+ {
+ float yadj = useScale * glyph->top;
+
+ DC->setColor( NULL );
+ DC->drawHandlePic( x, y - yadj,
+ ( emoticonW * emoticonWidth ),
+ emoticonH,
+ emoticonHandle );
+ DC->setColor( newColor );
+ x += ( emoticonW * emoticonWidth ) + gapAdjust;
+ s += emoticonLen;
+ count += emoticonWidth;
+ continue;
+ }
+ }
+ }
+
+ if( style == ITEM_TEXTSTYLE_SHADOWED ||
+ style == ITEM_TEXTSTYLE_SHADOWEDMORE )
+ {
+ int ofs;
+
+ if( style == ITEM_TEXTSTYLE_SHADOWED )
+ ofs = 1;
+ else
+ ofs = 2;
+ colorBlack[3] = newColor[3];
+ DC->setColor( colorBlack );
+ UI_Text_PaintChar( x + ofs, y + ofs, useScale, glyph, 0.0f );
+ DC->setColor( newColor );
+ colorBlack[3] = 1.0f;
+ }
+ else if( style == ITEM_TEXTSTYLE_NEON )
+ {
+ vec4_t glow;
+
+ memcpy( &glow[0], &newColor[0], sizeof( vec4_t ) );
+ glow[ 3 ] *= 0.2f;
+
+ DC->setColor( glow );
+ UI_Text_PaintChar( x, y, useScale, glyph, 6.0f );
+ UI_Text_PaintChar( x, y, useScale, glyph, 4.0f );
+ DC->setColor( newColor );
+ UI_Text_PaintChar( x, y, useScale, glyph, 2.0f );
+
+ DC->setColor( colorWhite );
+ }
+
+ UI_Text_PaintChar( x, y, useScale, glyph, 0.0f );
+
+ if( count == cursorPos )
+ cursorX = x;
+
+ x += ( glyph->xSkip * DC->aspectScale * useScale ) + gapAdjust;
+ s++;
+ count++;
+
+ }
+
+ if( maxX )
+ *maxX = x;
+
+ // paint cursor
+ if( cursorPos >= 0 )
+ {
+ if( cursorPos == len )
+ cursorX = x;
+
+ if( cursorX >= 0 && !( ( DC->realTime / BLINK_DIVISOR ) & 1 ) )
+ {
+ glyph = &font->glyphs[ (int)cursor ];
+ UI_Text_PaintChar( cursorX, y, useScale, glyph, 0.0f );
+ }
+ }
+
+ DC->setColor( NULL );
+}
+
+void UI_Text_Paint_Limit( float *maxX, float x, float y, float scale,
+ vec4_t color, const char *text, float adjust, int limit )
+{
+ UI_Text_Paint_Generic( x, y, scale, adjust,
+ text, color, ITEM_TEXTSTYLE_NORMAL,
+ limit, maxX, -1, 0 );
+}
+
+void UI_Text_Paint( float x, float y, float scale, vec4_t color, const char *text,
+ float adjust, int limit, int style )
+{
+ UI_Text_Paint_Generic( x, y, scale, adjust,
+ text, color, style,
+ limit, NULL, -1, 0 );
+}
+
+void UI_Text_PaintWithCursor( float x, float y, float scale, vec4_t color, const char *text,
+ int cursorPos, char cursor, int limit, int style )
+{
+ UI_Text_Paint_Generic( x, y, scale, 0.0,
+ text, color, style,
+ limit, NULL, cursorPos, cursor );
+}
+
+commandDef_t commandList[] =
+ {
+ {"close", &Script_Close}, // menu
+ {"conditionalopen", &Script_ConditionalOpen}, // menu
+ {"exec", &Script_Exec}, // group/name
+ {"fadein", &Script_FadeIn}, // group/name
+ {"fadeout", &Script_FadeOut}, // group/name
+ {"hide", &Script_Hide}, // group/name
+ {"open", &Script_Open}, // menu
+ {"orbit", &Script_Orbit}, // group/name
+ {"play", &Script_Play}, // group/name
+ {"playlooped", &Script_playLooped}, // group/name
+ {"reset", &Script_Reset}, // resets the state of the item argument
+ {"setasset", &Script_SetAsset}, // works on this
+ {"setbackground", &Script_SetBackground}, // works on this
+ {"setcolor", &Script_SetColor}, // works on this
+ {"setcvar", &Script_SetCvar}, // group/name
+ {"setfocus", &Script_SetFocus}, // sets this background color to team color
+ {"setitemcolor", &Script_SetItemColor}, // group/name
+ {"setplayerhead", &Script_SetPlayerHead}, // sets this background color to team color
+ {"setplayermodel", &Script_SetPlayerModel}, // sets this background color to team color
+ {"show", &Script_Show}, // group/name
+ {"transition", &Script_Transition}, // group/name
+ };
+
+static size_t scriptCommandCount = sizeof( commandList ) / sizeof( commandDef_t );
+
+// despite what lcc thinks, we do not get cmdcmp here
+static int commandComp( const void *a, const void *b )
+{
+ return Q_stricmp( (const char *)a, ((commandDef_t *)b)->name );
+}
+
+void Item_RunScript( itemDef_t *item, const char *s )
+{
+ char script[1024], *p;
+ commandDef_t *cmd;
+ memset( script, 0, sizeof( script ) );
+
+ if( item && s && s[0] )
+ {
+ Q_strcat( script, 1024, s );
+ p = script;
+
+ while( 1 )
+ {
+ const char *command;
+ // expect command then arguments, ; ends command, NULL ends script
+
+ if( !String_Parse( &p, &command ) )
+ return;
+
+ if( command[0] == ';' && command[1] == '\0' )
+ continue;
+
+ cmd = bsearch( command, commandList, scriptCommandCount,
+ sizeof( commandDef_t ), commandComp );
+ if( cmd )
+ cmd->handler( item, &p );
+ else
+ // not in our auto list, pass to handler
+ DC->runScript( &p );
+ }
+ }
+}
+
+
+qboolean Item_EnableShowViaCvar( itemDef_t *item, int flag )
+{
+ char script[1024], *p;
+ memset( script, 0, sizeof( script ) );
+
+ if( item && item->enableCvar && *item->enableCvar && item->cvarTest && *item->cvarTest )
+ {
+ char buff[1024];
+ DC->getCVarString( item->cvarTest, buff, sizeof( buff ) );
+
+ Q_strcat( script, 1024, item->enableCvar );
+ p = script;
+
+ while( 1 )
+ {
+ const char *val;
+ // expect value then ; or NULL, NULL ends list
+
+ if( !String_Parse( &p, &val ) )
+ return ( item->cvarFlags & flag ) ? qfalse : qtrue;
+
+ if( val[0] == ';' && val[1] == '\0' )
+ continue;
+
+ // enable it if any of the values are true
+ if( item->cvarFlags & flag )
+ {
+ if( Q_stricmp( buff, val ) == 0 )
+ return qtrue;
+ }
+ else
+ {
+ // disable it if any of the values are true
+
+ if( Q_stricmp( buff, val ) == 0 )
+ return qfalse;
+ }
+
+ }
+
+ return ( item->cvarFlags & flag ) ? qfalse : qtrue;
+ }
+
+ return qtrue;
+}
+
+
+// will optionaly set focus to this item
+qboolean Item_SetFocus( itemDef_t *item, float x, float y )
+{
+ int i;
+ itemDef_t *oldFocus;
+ sfxHandle_t *sfx = &DC->Assets.itemFocusSound;
+ qboolean playSound = qfalse;
+ menuDef_t *parent;
+ // sanity check, non-null, not a decoration and does not already have the focus
+
+ if( item == NULL || item->window.flags & WINDOW_DECORATION ||
+ item->window.flags & WINDOW_HASFOCUS || !( item->window.flags & WINDOW_VISIBLE ) )
+ {
+ return qfalse;
+ }
+
+ parent = ( menuDef_t* )item->parent;
+
+ // items can be enabled and disabled based on cvars
+
+ if( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) && !Item_EnableShowViaCvar( item, CVAR_ENABLE ) )
+ return qfalse;
+
+ if( item->cvarFlags & ( CVAR_SHOW | CVAR_HIDE ) && !Item_EnableShowViaCvar( item, CVAR_SHOW ) )
+ return qfalse;
+
+ oldFocus = Menu_ClearFocus( item->parent );
+
+ if( item->type == ITEM_TYPE_TEXT )
+ {
+ rectDef_t r;
+ r = item->textRect;
+ r.y -= r.h;
+
+ if( Rect_ContainsPoint( &r, x, y ) )
+ {
+ item->window.flags |= WINDOW_HASFOCUS;
+
+ if( item->focusSound )
+ sfx = &item->focusSound;
+
+ playSound = qtrue;
+ }
+ else
+ {
+ if( oldFocus )
+ {
+ oldFocus->window.flags |= WINDOW_HASFOCUS;
+
+ if( oldFocus->onFocus )
+ Item_RunScript( oldFocus, oldFocus->onFocus );
+ }
+ }
+ }
+ else
+ {
+ item->window.flags |= WINDOW_HASFOCUS;
+
+ if( item->onFocus )
+ Item_RunScript( item, item->onFocus );
+
+ if( item->focusSound )
+ sfx = &item->focusSound;
+
+ playSound = qtrue;
+ }
+
+ if( playSound && sfx )
+ DC->startLocalSound( *sfx, CHAN_LOCAL_SOUND );
+
+ for( i = 0; i < parent->itemCount; i++ )
+ {
+ if( parent->items[i] == item )
+ {
+ parent->cursorItem = i;
+ break;
+ }
+ }
+
+ return qtrue;
+}
+
+static float Item_ListBox_HeightForNumItems( itemDef_t *item, int numItems )
+{
+ listBoxDef_t *listPtr = item->typeData.list;
+
+ return ( listPtr->elementHeight * numItems ) + 2.0f;
+}
+
+static int Item_ListBox_NumItemsForItemHeight( itemDef_t *item )
+{
+ listBoxDef_t *listPtr = item->typeData.list;
+
+ if( item->type == ITEM_TYPE_COMBOBOX )
+ return listPtr->dropItems;
+ else
+ return ( ( item->window.rect.h - 2.0f ) / listPtr->elementHeight );
+}
+
+int Item_ListBox_MaxScroll( itemDef_t *item )
+{
+ int total = DC->feederCount( item->feederID );
+ int max = total - Item_ListBox_NumItemsForItemHeight( item );
+
+ if( max < 0 )
+ return 0;
+
+ return max;
+}
+
+static float oldComboBoxY;
+static float oldComboBoxH;
+
+static qboolean Item_ComboBox_MaybeCastToListBox( itemDef_t *item )
+{
+ listBoxDef_t *listPtr = item->typeData.list;
+ qboolean cast = g_comboBoxItem != NULL &&
+ ( item->type == ITEM_TYPE_COMBOBOX );
+
+ if( cast )
+ {
+ oldComboBoxY = item->window.rect.y;
+ oldComboBoxH = item->window.rect.h;
+
+ item->window.rect.y += item->window.rect.h;
+ item->window.rect.h = Item_ListBox_HeightForNumItems( item, listPtr->dropItems );
+ item->type = ITEM_TYPE_LISTBOX;
+ }
+
+ return cast;
+}
+
+static void Item_ComboBox_MaybeUnCastFromListBox( itemDef_t *item, qboolean unCast )
+{
+ if( unCast )
+ {
+ item->window.rect.y = oldComboBoxY;
+ item->window.rect.h = oldComboBoxH;
+ item->type = ITEM_TYPE_COMBOBOX;
+ }
+}
+
+static void Item_ListBox_SetStartPos( itemDef_t *item, int startPos )
+{
+ listBoxDef_t *listPtr = item->typeData.list;
+ int total = DC->feederCount( item->feederID );
+ int max = Item_ListBox_MaxScroll( item );
+
+ if( startPos < 0 )
+ listPtr->startPos = 0;
+ else if( startPos > max )
+ listPtr->startPos = max;
+ else
+ listPtr->startPos = startPos;
+
+ listPtr->endPos = listPtr->startPos + MIN( ( total - listPtr->startPos ),
+ Item_ListBox_NumItemsForItemHeight( item ) );
+}
+
+float Item_ListBox_ThumbPosition( itemDef_t *item )
+{
+ float max, pos, size;
+ float startPos = (float)item->typeData.list->startPos;
+
+ max = Item_ListBox_MaxScroll( item );
+ size = SCROLLBAR_SLIDER_HEIGHT( item );
+
+ if( max > 0.0f )
+ pos = ( size - SCROLLBAR_ARROW_HEIGHT ) / max;
+ else
+ pos = 0.0f;
+
+ pos *= startPos;
+
+ return SCROLLBAR_SLIDER_Y( item ) + pos;
+}
+
+float Item_ListBox_ThumbDrawPosition( itemDef_t *item )
+{
+ if( itemCapture == item )
+ {
+ float min = SCROLLBAR_SLIDER_Y( item );
+ float max = min + SCROLLBAR_SLIDER_HEIGHT( item ) - SCROLLBAR_ARROW_HEIGHT;
+ float halfThumbSize = SCROLLBAR_ARROW_HEIGHT / 2.0f;
+
+ if( DC->cursory >= min + halfThumbSize && DC->cursory <= max + halfThumbSize )
+ return DC->cursory - halfThumbSize;
+ }
+
+ return Item_ListBox_ThumbPosition( item );
+}
+
+float Item_Slider_ThumbPosition( itemDef_t *item )
+{
+ float value, range, x;
+ editFieldDef_t *editDef = item->typeData.edit;
+
+ if( item->text )
+ x = item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET;
+ else
+ x = item->window.rect.x;
+
+ if( editDef == NULL && item->cvar )
+ return x;
+
+ value = DC->getCVarValue( item->cvar );
+
+ if( value < editDef->minVal )
+ value = editDef->minVal;
+ else if( value > editDef->maxVal )
+ value = editDef->maxVal;
+
+ range = editDef->maxVal - editDef->minVal;
+ value -= editDef->minVal;
+ value /= range;
+ value *= SLIDER_WIDTH;
+ x += value;
+
+ return x;
+}
+
+static float Item_Slider_VScale( itemDef_t *item )
+{
+ if( SLIDER_THUMB_HEIGHT > item->window.rect.h )
+ return item->window.rect.h / SLIDER_THUMB_HEIGHT;
+ else
+ return 1.0f;
+}
+
+int Item_Slider_OverSlider( itemDef_t *item, float x, float y )
+{
+ rectDef_t r;
+ float vScale = Item_Slider_VScale( item );
+
+ r.x = Item_Slider_ThumbPosition( item ) - ( SLIDER_THUMB_WIDTH / 2 );
+ r.y = item->textRect.y - item->textRect.h +
+ ( ( item->textRect.h - ( SLIDER_THUMB_HEIGHT * vScale ) ) / 2.0f );
+ r.w = SLIDER_THUMB_WIDTH;
+ r.h = SLIDER_THUMB_HEIGHT * vScale;
+
+ if( Rect_ContainsPoint( &r, x, y ) )
+ return WINDOW_LB_THUMB;
+
+ return 0;
+}
+
+int Item_ListBox_OverLB( itemDef_t *item, float x, float y )
+{
+ rectDef_t r;
+ int thumbstart;
+ int count;
+
+ count = DC->feederCount( item->feederID );
+
+ r.x = SCROLLBAR_SLIDER_X( item );
+ r.y = SCROLLBAR_Y( item );
+ r.w = SCROLLBAR_ARROW_WIDTH;
+ r.h = SCROLLBAR_ARROW_HEIGHT;
+
+ if( Rect_ContainsPoint( &r, x, y ) )
+ return WINDOW_LB_UPARROW;
+
+ r.y = SCROLLBAR_SLIDER_Y( item ) + SCROLLBAR_SLIDER_HEIGHT( item );
+
+ if( Rect_ContainsPoint( &r, x, y ) )
+ return WINDOW_LB_DOWNARROW;
+
+ thumbstart = Item_ListBox_ThumbPosition( item );
+ r.y = thumbstart;
+
+ if( Rect_ContainsPoint( &r, x, y ) )
+ return WINDOW_LB_THUMB;
+
+ r.y = SCROLLBAR_SLIDER_Y( item );
+ r.h = thumbstart - r.y;
+
+ if( Rect_ContainsPoint( &r, x, y ) )
+ return WINDOW_LB_PGUP;
+
+ r.y = thumbstart + SCROLLBAR_ARROW_HEIGHT;
+ r.h = ( SCROLLBAR_SLIDER_Y( item ) + SCROLLBAR_SLIDER_HEIGHT( item ) ) - r.y;
+
+ if( Rect_ContainsPoint( &r, x, y ) )
+ return WINDOW_LB_PGDN;
+
+ return 0;
+}
+
+
+void Item_ListBox_MouseEnter( itemDef_t *item, float x, float y )
+{
+ rectDef_t r;
+ listBoxDef_t *listPtr = item->typeData.list;
+ int listBoxFlags = ( WINDOW_LB_UPARROW | WINDOW_LB_DOWNARROW | WINDOW_LB_THUMB |
+ WINDOW_LB_PGUP | WINDOW_LB_PGDN );
+ int total = DC->feederCount( item->feederID );
+
+ item->window.flags &= ~listBoxFlags;
+ item->window.flags |= Item_ListBox_OverLB( item, x, y );
+
+ if( !( item->window.flags & listBoxFlags ) )
+ {
+ r.x = SCROLLBAR_X( item );
+ r.y = SCROLLBAR_Y( item );
+ r.w = SCROLLBAR_W( item );
+ r.h = listPtr->elementHeight *
+ MIN( Item_ListBox_NumItemsForItemHeight( item ), total );
+
+ if( Rect_ContainsPoint( &r, x, y ) )
+ {
+ listPtr->cursorPos = (int)( ( y - r.y ) / listPtr->elementHeight ) + listPtr->startPos;
+
+ if( listPtr->cursorPos >= listPtr->endPos )
+ listPtr->cursorPos = listPtr->endPos - 1;
+ }
+ else
+ listPtr->cursorPos = -1;
+ }
+}
+
+void Item_MouseEnter( itemDef_t *item, float x, float y )
+{
+ rectDef_t r;
+
+ if( item )
+ {
+ r = item->textRect;
+ r.y -= r.h;
+ // in the text rect?
+
+ // items can be enabled and disabled based on cvars
+
+ if( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) && !Item_EnableShowViaCvar( item, CVAR_ENABLE ) )
+ return;
+
+ if( item->cvarFlags & ( CVAR_SHOW | CVAR_HIDE ) && !Item_EnableShowViaCvar( item, CVAR_SHOW ) )
+ return;
+
+ if( Rect_ContainsPoint( &r, x, y ) )
+ {
+ if( !( item->window.flags & WINDOW_MOUSEOVERTEXT ) )
+ {
+ Item_RunScript( item, item->mouseEnterText );
+ item->window.flags |= WINDOW_MOUSEOVERTEXT;
+ }
+
+ if( !( item->window.flags & WINDOW_MOUSEOVER ) )
+ {
+ Item_RunScript( item, item->mouseEnter );
+ item->window.flags |= WINDOW_MOUSEOVER;
+ }
+
+ }
+ else
+ {
+ // not in the text rect
+
+ if( item->window.flags & WINDOW_MOUSEOVERTEXT )
+ {
+ // if we were
+ Item_RunScript( item, item->mouseExitText );
+ item->window.flags &= ~WINDOW_MOUSEOVERTEXT;
+ }
+
+ if( !( item->window.flags & WINDOW_MOUSEOVER ) )
+ {
+ Item_RunScript( item, item->mouseEnter );
+ item->window.flags |= WINDOW_MOUSEOVER;
+ }
+
+ if( item->type == ITEM_TYPE_LISTBOX )
+ Item_ListBox_MouseEnter( item, x, y );
+ }
+ }
+}
+
+void Item_MouseLeave( itemDef_t *item )
+{
+ if( item )
+ {
+ if( item->window.flags & WINDOW_MOUSEOVERTEXT )
+ {
+ Item_RunScript( item, item->mouseExitText );
+ item->window.flags &= ~WINDOW_MOUSEOVERTEXT;
+ }
+
+ Item_RunScript( item, item->mouseExit );
+ item->window.flags &= ~( WINDOW_LB_DOWNARROW | WINDOW_LB_UPARROW );
+ }
+}
+
+itemDef_t *Menu_HitTest( menuDef_t *menu, float x, float y )
+{
+ int i;
+
+ for( i = 0; i < menu->itemCount; i++ )
+ {
+ if( Rect_ContainsPoint( &menu->items[i]->window.rect, x, y ) )
+ return menu->items[i];
+ }
+
+ return NULL;
+}
+
+void Item_SetMouseOver( itemDef_t *item, qboolean focus )
+{
+ if( item )
+ {
+ if( focus )
+ item->window.flags |= WINDOW_MOUSEOVER;
+ else
+ item->window.flags &= ~WINDOW_MOUSEOVER;
+ }
+}
+
+
+qboolean Item_OwnerDraw_HandleKey( itemDef_t *item, int key )
+{
+ if( item && DC->ownerDrawHandleKey )
+ return DC->ownerDrawHandleKey( item->window.ownerDraw, key );
+
+ return qfalse;
+}
+
+qboolean Item_ListBox_HandleKey( itemDef_t *item, int key, qboolean down, qboolean force )
+{
+ listBoxDef_t *listPtr = item->typeData.list;
+ int count = DC->feederCount( item->feederID );
+ int viewmax;
+
+ if( force || ( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) &&
+ item->window.flags & WINDOW_HASFOCUS ) )
+ {
+ viewmax = Item_ListBox_NumItemsForItemHeight( item );
+
+ switch( key )
+ {
+ case K_MOUSE1:
+ case K_MOUSE2:
+ if( item->window.flags & WINDOW_LB_UPARROW )
+ Item_ListBox_SetStartPos( item, listPtr->startPos - 1 );
+ else if( item->window.flags & WINDOW_LB_DOWNARROW )
+ Item_ListBox_SetStartPos( item, listPtr->startPos + 1 );
+ else if( item->window.flags & WINDOW_LB_PGUP )
+ Item_ListBox_SetStartPos( item, listPtr->startPos - viewmax );
+ else if( item->window.flags & WINDOW_LB_PGDN )
+ Item_ListBox_SetStartPos( item, listPtr->startPos + viewmax );
+ else if( item->window.flags & WINDOW_LB_THUMB )
+ break; // Handled by capture function
+ else
+ {
+ // Select an item
+ qboolean runDoubleClick = qfalse;
+
+ // Mouse isn't over an item
+ if( listPtr->cursorPos < 0 )
+ break;
+
+ if( item->cursorPos != listPtr->cursorPos )
+ {
+ item->cursorPos = listPtr->cursorPos;
+ DC->feederSelection( item->feederID, item->cursorPos );
+ }
+
+ runDoubleClick = DC->realTime < lastListBoxClickTime && listPtr->doubleClick;
+ lastListBoxClickTime = DC->realTime + DOUBLE_CLICK_DELAY;
+
+ // Made a selection, so close combobox
+ if( g_comboBoxItem != NULL )
+ {
+ if( listPtr->doubleClick )
+ runDoubleClick = qtrue;
+
+ g_comboBoxItem = NULL;
+ }
+
+ if( runDoubleClick )
+ Item_RunScript( item, listPtr->doubleClick );
+ }
+
+ break;
+
+ case K_MWHEELUP:
+ Item_ListBox_SetStartPos( item, listPtr->startPos - 1 );
+ break;
+
+ case K_MWHEELDOWN:
+ Item_ListBox_SetStartPos( item, listPtr->startPos + 1 );
+ break;
+
+ case K_ENTER:
+ // Invoke the doubleClick handler when enter is pressed
+ if( listPtr->doubleClick )
+ Item_RunScript( item, listPtr->doubleClick );
+
+ break;
+
+ case K_PGUP:
+ case K_KP_PGUP:
+ if( !listPtr->notselectable )
+ {
+ listPtr->cursorPos -= viewmax;
+
+ if( listPtr->cursorPos < 0 )
+ listPtr->cursorPos = 0;
+
+ if( listPtr->cursorPos < listPtr->startPos )
+ Item_ListBox_SetStartPos( item, listPtr->cursorPos );
+
+ if( listPtr->cursorPos >= listPtr->startPos + viewmax )
+ Item_ListBox_SetStartPos( item, listPtr->cursorPos - viewmax + 1 );
+
+ item->cursorPos = listPtr->cursorPos;
+ DC->feederSelection( item->feederID, item->cursorPos );
+ }
+ else
+ Item_ListBox_SetStartPos( item, listPtr->startPos - viewmax );
+
+ break;
+
+ case K_PGDN:
+ case K_KP_PGDN:
+ if( !listPtr->notselectable )
+ {
+ listPtr->cursorPos += viewmax;
+
+ if( listPtr->cursorPos < listPtr->startPos )
+ Item_ListBox_SetStartPos( item, listPtr->cursorPos );
+
+ if( listPtr->cursorPos >= count )
+ listPtr->cursorPos = count - 1;
+
+ if( listPtr->cursorPos >= listPtr->startPos + viewmax )
+ Item_ListBox_SetStartPos( item, listPtr->cursorPos - viewmax + 1 );
+
+ item->cursorPos = listPtr->cursorPos;
+ DC->feederSelection( item->feederID, item->cursorPos );
+ }
+ else
+ Item_ListBox_SetStartPos( item, listPtr->startPos + viewmax );
+
+ break;
+
+ default:
+ // Not handled
+ return qfalse;
+ }
+
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+qboolean Item_ComboBox_HandleKey( itemDef_t *item, int key, qboolean down, qboolean force )
+{
+ if( g_comboBoxItem != NULL )
+ {
+ qboolean result;
+
+ qboolean cast = Item_ComboBox_MaybeCastToListBox( item );
+ result = Item_ListBox_HandleKey( item, key, down, force );
+ Item_ComboBox_MaybeUnCastFromListBox( item, cast );
+
+ if( !result )
+ g_comboBoxItem = NULL;
+
+ return result;
+ }
+ else
+ {
+ if( force || ( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) &&
+ item->window.flags & WINDOW_HASFOCUS ) )
+ {
+ if( key == K_MOUSE1 || key == K_MOUSE2 )
+ {
+ g_comboBoxItem = item;
+
+ return qtrue;
+ }
+ }
+ }
+
+ return qfalse;
+}
+
+qboolean Item_YesNo_HandleKey( itemDef_t *item, int key )
+{
+ if( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) &&
+ item->window.flags & WINDOW_HASFOCUS && item->cvar )
+ {
+ if( key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3 )
+ {
+ DC->setCVar( item->cvar, va( "%i", !DC->getCVarValue( item->cvar ) ) );
+ return qtrue;
+ }
+ }
+
+ return qfalse;
+
+}
+
+int Item_Multi_CountSettings( itemDef_t *item )
+{
+ if( item->typeData.multi == NULL )
+ return 0;
+
+ return item->typeData.multi->count;
+}
+
+int Item_Multi_FindCvarByValue( itemDef_t *item )
+{
+ char buff[1024];
+ float value = 0;
+ int i;
+ multiDef_t *multiPtr = item->typeData.multi;
+
+ if( multiPtr )
+ {
+ if( multiPtr->strDef )
+ DC->getCVarString( item->cvar, buff, sizeof( buff ) );
+ else
+ value = DC->getCVarValue( item->cvar );
+
+ for( i = 0; i < multiPtr->count; i++ )
+ {
+ if( multiPtr->strDef )
+ {
+ if( Q_stricmp( buff, multiPtr->cvarStr[i] ) == 0 )
+ return i;
+ }
+ else
+ {
+ if( multiPtr->cvarValue[i] == value )
+ return i;
+ }
+ }
+ }
+
+ return 0;
+}
+
+const char *Item_Multi_Setting( itemDef_t *item )
+{
+ char buff[1024];
+ float value = 0;
+ int i;
+ multiDef_t *multiPtr = item->typeData.multi;
+
+ if( multiPtr )
+ {
+ if( multiPtr->strDef )
+ DC->getCVarString( item->cvar, buff, sizeof( buff ) );
+ else
+ value = DC->getCVarValue( item->cvar );
+
+ for( i = 0; i < multiPtr->count; i++ )
+ {
+ if( multiPtr->strDef )
+ {
+ if( Q_stricmp( buff, multiPtr->cvarStr[i] ) == 0 )
+ return multiPtr->cvarList[i];
+ }
+ else
+ {
+ if( multiPtr->cvarValue[i] == value )
+ return multiPtr->cvarList[i];
+ }
+ }
+ }
+
+ return "";
+}
+
+qboolean Item_Cycle_HandleKey( itemDef_t *item, int key )
+{
+ cycleDef_t *cyclePtr = item->typeData.cycle;
+ qboolean mouseOver = Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory );
+ int count = DC->feederCount( item->feederID );
+
+ if( cyclePtr )
+ {
+ if( item->window.flags & WINDOW_HASFOCUS )
+ {
+ if( ( mouseOver && key == K_MOUSE1 ) ||
+ key == K_ENTER || key == K_RIGHTARROW || key == K_DOWNARROW )
+ {
+ if( count > 0 )
+ cyclePtr->cursorPos = ( cyclePtr->cursorPos + 1 ) % count;
+
+ DC->feederSelection( item->feederID, cyclePtr->cursorPos );
+
+ return qtrue;
+ }
+ else if( ( mouseOver && key == K_MOUSE2 ) ||
+ key == K_LEFTARROW || key == K_UPARROW )
+ {
+ if( count > 0 )
+ cyclePtr->cursorPos = ( count + cyclePtr->cursorPos - 1 ) % count;
+
+ DC->feederSelection( item->feederID, cyclePtr->cursorPos );
+
+ return qtrue;
+ }
+ }
+ }
+
+ return qfalse;
+}
+
+qboolean Item_Multi_HandleKey( itemDef_t *item, int key )
+{
+ qboolean mouseOver = Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory );
+ int max = Item_Multi_CountSettings( item );
+ qboolean changed = qfalse;
+
+ if( item->typeData.multi )
+ {
+ if( item->window.flags & WINDOW_HASFOCUS && item->cvar && max > 0 )
+ {
+ int current;
+
+ if( ( mouseOver && key == K_MOUSE1 ) ||
+ key == K_ENTER || key == K_RIGHTARROW || key == K_DOWNARROW )
+ {
+ current = ( Item_Multi_FindCvarByValue( item ) + 1 ) % max;
+ changed = qtrue;
+ }
+ else if( ( mouseOver && key == K_MOUSE2 ) ||
+ key == K_LEFTARROW || key == K_UPARROW )
+ {
+ current = ( Item_Multi_FindCvarByValue( item ) + max - 1 ) % max;
+ changed = qtrue;
+ }
+
+ if( changed )
+ {
+ if( item->typeData.multi->strDef )
+ DC->setCVar( item->cvar, item->typeData.multi->cvarStr[current] );
+ else
+ {
+ float value = item->typeData.multi->cvarValue[current];
+
+ if( ( ( float )( ( int ) value ) ) == value )
+ DC->setCVar( item->cvar, va( "%i", ( int ) value ) );
+ else
+ DC->setCVar( item->cvar, va( "%f", value ) );
+ }
+
+ return qtrue;
+ }
+ }
+ }
+
+ return qfalse;
+}
+
+#define MIN_FIELD_WIDTH 10
+#define EDIT_CURSOR_WIDTH 10
+
+static void Item_TextField_CalcPaintOffset( itemDef_t *item, char *buff )
+{
+ editFieldDef_t *editPtr = item->typeData.edit;
+
+ if( item->cursorPos < editPtr->paintOffset )
+ editPtr->paintOffset = item->cursorPos;
+ else
+ {
+ // If there is a maximum field width
+
+ if( editPtr->maxFieldWidth > 0 )
+ {
+ // If the cursor is at the end of the string, maximise the amount of the
+ // string that's visible
+
+ if( buff[ item->cursorPos + 1 ] == '\0' )
+ {
+ while( UI_Text_Width( &buff[ editPtr->paintOffset ], item->textscale ) <=
+ ( editPtr->maxFieldWidth - EDIT_CURSOR_WIDTH ) && editPtr->paintOffset > 0 )
+ editPtr->paintOffset--;
+ }
+
+ buff[ item->cursorPos + 1 ] = '\0';
+
+ // Shift paintOffset so that the cursor is visible
+
+ while( UI_Text_Width( &buff[ editPtr->paintOffset ], item->textscale ) >
+ ( editPtr->maxFieldWidth - EDIT_CURSOR_WIDTH ) )
+ editPtr->paintOffset++;
+ }
+ }
+}
+
+qboolean Item_TextField_HandleKey( itemDef_t *item, int key )
+{
+ char buff[1024];
+ int len;
+ itemDef_t *newItem = NULL;
+ editFieldDef_t *editPtr = item->typeData.edit;
+ qboolean releaseFocus = qtrue;
+
+ if( item->cvar )
+ {
+ Com_Memset( buff, 0, sizeof( buff ) );
+ DC->getCVarString( item->cvar, buff, sizeof( buff ) );
+ len = strlen( buff );
+
+ if( len < item->cursorPos )
+ item->cursorPos = len;
+
+ if( editPtr->maxChars && len > editPtr->maxChars )
+ len = editPtr->maxChars;
+
+ if( key & K_CHAR_FLAG )
+ {
+ key &= ~K_CHAR_FLAG;
+
+ if( key == 'h' - 'a' + 1 )
+ {
+ // ctrl-h is backspace
+
+ if( item->cursorPos > 0 )
+ {
+ memmove( &buff[item->cursorPos - 1], &buff[item->cursorPos], len + 1 - item->cursorPos );
+ item->cursorPos--;
+ }
+
+ DC->setCVar( item->cvar, buff );
+ }
+ else if( key < 32 || !item->cvar )
+ {
+ // Ignore any non printable chars
+ releaseFocus = qfalse;
+ goto exit;
+ }
+ else if( item->type == ITEM_TYPE_NUMERICFIELD && ( key < '0' || key > '9' ) )
+ {
+ // Ignore non-numeric characters
+ releaseFocus = qfalse;
+ goto exit;
+ }
+ else
+ {
+ if( !DC->getOverstrikeMode() )
+ {
+ if( ( len == MAX_EDITFIELD - 1 ) || ( editPtr->maxChars && len >= editPtr->maxChars ) )
+ {
+ // Reached maximum field length
+ releaseFocus = qfalse;
+ goto exit;
+ }
+
+ memmove( &buff[item->cursorPos + 1], &buff[item->cursorPos], len + 1 - item->cursorPos );
+ }
+ else
+ {
+ // Reached maximum field length
+ if( editPtr->maxChars && item->cursorPos >= editPtr->maxChars )
+ {
+ releaseFocus = qfalse;
+ goto exit;
+ }
+ }
+
+ buff[ item->cursorPos ] = key;
+
+ DC->setCVar( item->cvar, buff );
+
+ if( item->cursorPos < len + 1 )
+ item->cursorPos++;
+ }
+ }
+ else
+ {
+ switch( key )
+ {
+ case K_DEL:
+ case K_KP_DEL:
+ if( item->cursorPos < len )
+ {
+ memmove( buff + item->cursorPos, buff + item->cursorPos + 1, len - item->cursorPos );
+ DC->setCVar( item->cvar, buff );
+ }
+
+ break;
+
+ case K_RIGHTARROW:
+ case K_KP_RIGHTARROW:
+ if( item->cursorPos < len )
+ item->cursorPos++;
+
+ break;
+
+ case K_LEFTARROW:
+ case K_KP_LEFTARROW:
+ if( item->cursorPos > 0 )
+ item->cursorPos--;
+
+ break;
+
+ case K_HOME:
+ case K_KP_HOME:
+ item->cursorPos = 0;
+
+ break;
+
+ case K_END:
+ case K_KP_END:
+ item->cursorPos = len;
+
+ break;
+
+ case K_INS:
+ case K_KP_INS:
+ DC->setOverstrikeMode( !DC->getOverstrikeMode() );
+
+ break;
+
+ case K_TAB:
+ case K_DOWNARROW:
+ case K_KP_DOWNARROW:
+ case K_UPARROW:
+ case K_KP_UPARROW:
+ // Ignore these keys from the say field
+ if( item->type == ITEM_TYPE_SAYFIELD )
+ break;
+
+ newItem = Menu_SetNextCursorItem( item->parent );
+
+ if( newItem && Item_IsEditField( newItem ) )
+ {
+ g_editItem = newItem;
+ }
+ else
+ {
+ releaseFocus = qtrue;
+ goto exit;
+ }
+
+ break;
+
+ case K_MOUSE1:
+ case K_MOUSE2:
+ case K_MOUSE3:
+ case K_MOUSE4:
+ // Ignore these buttons from the say field
+ if( item->type == ITEM_TYPE_SAYFIELD )
+ break;
+ // FALLTHROUGH
+ case K_ENTER:
+ case K_KP_ENTER:
+ case K_ESCAPE:
+ releaseFocus = qtrue;
+ goto exit;
+
+ default:
+ break;
+ }
+ }
+
+ releaseFocus = qfalse;
+ }
+
+exit:
+ Item_TextField_CalcPaintOffset( item, buff );
+
+ return !releaseFocus;
+}
+
+static void _Scroll_ListBox_AutoFunc( scrollInfo_t *si )
+{
+ if( DC->realTime > si->nextScrollTime )
+ {
+ // need to scroll which is done by simulating a click to the item
+ // this is done a bit sideways as the autoscroll "knows" that the item is a listbox
+ // so it calls it directly
+ Item_ListBox_HandleKey( si->item, si->scrollKey, qtrue, qfalse );
+
+ si->nextScrollTime = DC->realTime + si->adjustValue;
+ }
+
+ if( DC->realTime > si->nextAdjustTime )
+ {
+ si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST;
+
+ if( si->adjustValue > SCROLL_TIME_FLOOR )
+ si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET;
+ }
+}
+
+static void Scroll_ListBox_AutoFunc( void *p )
+{
+ scrollInfo_t *si = ( scrollInfo_t* )p;
+
+ qboolean cast = Item_ComboBox_MaybeCastToListBox( si->item );
+ _Scroll_ListBox_AutoFunc( si );
+ Item_ComboBox_MaybeUnCastFromListBox( si->item, cast );
+}
+
+static void _Scroll_ListBox_ThumbFunc( scrollInfo_t *si )
+{
+ rectDef_t r;
+ int pos, max;
+
+ if( DC->cursory != si->yStart )
+ {
+ r.x = si->item->window.rect.x + si->item->window.rect.w - SCROLLBAR_ARROW_WIDTH - 1;
+ r.y = si->item->window.rect.y + SCROLLBAR_ARROW_HEIGHT + 1;
+ r.w = SCROLLBAR_ARROW_WIDTH;
+ r.h = si->item->window.rect.h - ( SCROLLBAR_ARROW_HEIGHT * 2 ) - 2;
+ max = Item_ListBox_MaxScroll( si->item );
+ //
+ pos = ( DC->cursory - r.y - SCROLLBAR_ARROW_HEIGHT / 2 ) * max / ( r.h - SCROLLBAR_ARROW_HEIGHT );
+
+ if( pos < 0 )
+ pos = 0;
+ else if( pos > max )
+ pos = max;
+
+ Item_ListBox_SetStartPos( si->item, pos );
+ si->yStart = DC->cursory;
+ }
+
+ if( DC->realTime > si->nextScrollTime )
+ {
+ // need to scroll which is done by simulating a click to the item
+ // this is done a bit sideways as the autoscroll "knows" that the item is a listbox
+ // so it calls it directly
+ Item_ListBox_HandleKey( si->item, si->scrollKey, qtrue, qfalse );
+
+ si->nextScrollTime = DC->realTime + si->adjustValue;
+ }
+
+ if( DC->realTime > si->nextAdjustTime )
+ {
+ si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST;
+
+ if( si->adjustValue > SCROLL_TIME_FLOOR )
+ si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET;
+ }
+}
+
+static void Scroll_ListBox_ThumbFunc( void *p )
+{
+ scrollInfo_t *si = ( scrollInfo_t* )p;
+
+ qboolean cast = Item_ComboBox_MaybeCastToListBox( si->item );
+ _Scroll_ListBox_ThumbFunc( si );
+ Item_ComboBox_MaybeUnCastFromListBox( si->item, cast );
+}
+
+static void Scroll_Slider_ThumbFunc( void *p )
+{
+ float x, value, cursorx;
+ scrollInfo_t *si = ( scrollInfo_t* )p;
+
+ if( si->item->text )
+ x = si->item->textRect.x + si->item->textRect.w + ITEM_VALUE_OFFSET;
+ else
+ x = si->item->window.rect.x;
+
+ cursorx = DC->cursorx;
+
+ if( cursorx < x )
+ cursorx = x;
+ else if( cursorx > x + SLIDER_WIDTH )
+ cursorx = x + SLIDER_WIDTH;
+
+ value = cursorx - x;
+ value /= SLIDER_WIDTH;
+ value *= si->item->typeData.edit->maxVal - si->item->typeData.edit->minVal;
+ value += si->item->typeData.edit->minVal;
+ DC->setCVar( si->item->cvar, va( "%f", value ) );
+}
+
+void Item_StartCapture( itemDef_t *item, int key )
+{
+ int flags;
+
+ // Don't allow captureFunc to be overridden
+
+ if( captureFunc != voidFunction )
+ return;
+
+ switch( item->type )
+ {
+ case ITEM_TYPE_LISTBOX:
+ case ITEM_TYPE_COMBOBOX:
+ {
+ qboolean cast = Item_ComboBox_MaybeCastToListBox( item );
+ flags = Item_ListBox_OverLB( item, DC->cursorx, DC->cursory );
+ Item_ComboBox_MaybeUnCastFromListBox( item, cast );
+
+ if( flags & ( WINDOW_LB_UPARROW | WINDOW_LB_DOWNARROW ) )
+ {
+ scrollInfo.nextScrollTime = DC->realTime + SCROLL_TIME_START;
+ scrollInfo.nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST;
+ scrollInfo.adjustValue = SCROLL_TIME_START;
+ scrollInfo.scrollKey = key;
+ scrollInfo.scrollDir = ( flags & WINDOW_LB_UPARROW ) ? qtrue : qfalse;
+ scrollInfo.item = item;
+ UI_InstallCaptureFunc( Scroll_ListBox_AutoFunc, &scrollInfo, 0 );
+ itemCapture = item;
+ }
+ else if( flags & WINDOW_LB_THUMB )
+ {
+ scrollInfo.scrollKey = key;
+ scrollInfo.item = item;
+ scrollInfo.xStart = DC->cursorx;
+ scrollInfo.yStart = DC->cursory;
+ UI_InstallCaptureFunc( Scroll_ListBox_ThumbFunc, &scrollInfo, 0 );
+ itemCapture = item;
+ }
+
+ break;
+ }
+
+ case ITEM_TYPE_SLIDER:
+ {
+ flags = Item_Slider_OverSlider( item, DC->cursorx, DC->cursory );
+
+ if( flags & WINDOW_LB_THUMB )
+ {
+ scrollInfo.scrollKey = key;
+ scrollInfo.item = item;
+ scrollInfo.xStart = DC->cursorx;
+ scrollInfo.yStart = DC->cursory;
+ UI_InstallCaptureFunc( Scroll_Slider_ThumbFunc, &scrollInfo, 0 );
+ itemCapture = item;
+ }
+
+ break;
+ }
+ }
+}
+
+void Item_StopCapture( itemDef_t *item )
+{
+}
+
+qboolean Item_Slider_HandleKey( itemDef_t *item, int key, qboolean down )
+{
+ float x, value, width;
+
+ if( item->window.flags & WINDOW_HASFOCUS && item->cvar &&
+ Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) )
+ {
+ if( item->typeData.edit && ( key == K_ENTER ||
+ key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) )
+ {
+ rectDef_t testRect;
+ width = SLIDER_WIDTH;
+
+ if( item->text )
+ x = item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET;
+ else
+ x = item->window.rect.x;
+
+ testRect = item->window.rect;
+ value = ( float )SLIDER_THUMB_WIDTH / 2;
+ testRect.x = x - value;
+ testRect.w = SLIDER_WIDTH + value;
+
+ if( Rect_ContainsPoint( &testRect, DC->cursorx, DC->cursory ) )
+ {
+ value = ( float )( DC->cursorx - x ) / width;
+ value *= ( item->typeData.edit->maxVal - item->typeData.edit->minVal );
+ value += item->typeData.edit->minVal;
+ DC->setCVar( item->cvar, va( "%f", value ) );
+ return qtrue;
+ }
+ }
+ }
+
+ return qfalse;
+}
+
+
+qboolean Item_HandleKey( itemDef_t *item, int key, qboolean down )
+{
+ if( itemCapture )
+ {
+ Item_StopCapture( itemCapture );
+ itemCapture = NULL;
+ UI_RemoveCaptureFunc( );
+ }
+ else
+ {
+ if( down && ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) )
+ Item_StartCapture( item, key );
+ }
+
+ if( !down )
+ return qfalse;
+
+ // Edit fields are handled specially
+ if( Item_IsEditField( item ) )
+ return qfalse;
+
+ switch( item->type )
+ {
+ case ITEM_TYPE_BUTTON:
+ return qfalse;
+
+ case ITEM_TYPE_RADIOBUTTON:
+ return qfalse;
+
+ case ITEM_TYPE_CHECKBOX:
+ return qfalse;
+
+ case ITEM_TYPE_CYCLE:
+ return Item_Cycle_HandleKey( item, key );
+
+ case ITEM_TYPE_LISTBOX:
+ return Item_ListBox_HandleKey( item, key, down, qfalse );
+
+ case ITEM_TYPE_COMBOBOX:
+ return Item_ComboBox_HandleKey( item, key, down, qfalse );
+
+ case ITEM_TYPE_YESNO:
+ return Item_YesNo_HandleKey( item, key );
+
+ case ITEM_TYPE_MULTI:
+ return Item_Multi_HandleKey( item, key );
+
+ case ITEM_TYPE_OWNERDRAW:
+ return Item_OwnerDraw_HandleKey( item, key );
+
+ case ITEM_TYPE_BIND:
+ return Item_Bind_HandleKey( item, key, down );
+
+ case ITEM_TYPE_SLIDER:
+ return Item_Slider_HandleKey( item, key, down );
+
+ default:
+ return qfalse;
+ }
+}
+
+void Item_Action( itemDef_t *item )
+{
+ if( item )
+ Item_RunScript( item, item->action );
+}
+
+itemDef_t *Menu_SetPrevCursorItem( menuDef_t *menu )
+{
+ qboolean wrapped = qfalse;
+ int oldCursor = menu->cursorItem;
+
+ if( menu->cursorItem < 0 )
+ {
+ menu->cursorItem = menu->itemCount - 1;
+ wrapped = qtrue;
+ }
+
+ while( menu->cursorItem > -1 )
+ {
+ menu->cursorItem--;
+
+ if( menu->cursorItem < 0 && !wrapped )
+ {
+ wrapped = qtrue;
+ menu->cursorItem = menu->itemCount - 1;
+ }
+
+ if( Item_SetFocus( menu->items[menu->cursorItem], DC->cursorx, DC->cursory ) )
+ {
+ Menu_HandleMouseMove( menu, menu->items[menu->cursorItem]->window.rect.x + 1,
+ menu->items[menu->cursorItem]->window.rect.y + 1 );
+ return menu->items[menu->cursorItem];
+ }
+ }
+
+ menu->cursorItem = oldCursor;
+ return NULL;
+
+}
+
+itemDef_t *Menu_SetNextCursorItem( menuDef_t *menu )
+{
+ qboolean wrapped = qfalse;
+ int oldCursor = menu->cursorItem;
+
+
+ if( menu->cursorItem == -1 )
+ {
+ menu->cursorItem = 0;
+ wrapped = qtrue;
+ }
+
+ while( menu->cursorItem < menu->itemCount )
+ {
+ menu->cursorItem++;
+
+ if( menu->cursorItem >= menu->itemCount && !wrapped )
+ {
+ wrapped = qtrue;
+ menu->cursorItem = 0;
+ }
+
+ if( Item_SetFocus( menu->items[menu->cursorItem], DC->cursorx, DC->cursory ) )
+ {
+ Menu_HandleMouseMove( menu, menu->items[menu->cursorItem]->window.rect.x + 1,
+ menu->items[menu->cursorItem]->window.rect.y + 1 );
+ return menu->items[menu->cursorItem];
+ }
+
+ }
+
+ menu->cursorItem = oldCursor;
+ return NULL;
+}
+
+static void Window_CloseCinematic( windowDef_t *window )
+{
+ if( window->style == WINDOW_STYLE_CINEMATIC && window->cinematic >= 0 )
+ {
+ DC->stopCinematic( window->cinematic );
+ window->cinematic = -1;
+ }
+}
+
+static void Menu_CloseCinematics( menuDef_t *menu )
+{
+ if( menu )
+ {
+ int i;
+ Window_CloseCinematic( &menu->window );
+
+ for( i = 0; i < menu->itemCount; i++ )
+ {
+ Window_CloseCinematic( &menu->items[i]->window );
+
+ if( menu->items[i]->type == ITEM_TYPE_OWNERDRAW )
+ DC->stopCinematic( 0 - menu->items[i]->window.ownerDraw );
+ }
+ }
+}
+
+static void Display_CloseCinematics( void )
+{
+ int i;
+
+ for( i = 0; i < menuCount; i++ )
+ Menu_CloseCinematics( &Menus[i] );
+}
+
+void Menus_Activate( menuDef_t *menu )
+{
+ int i;
+ qboolean onTopOfMenuStack = qfalse;
+
+ if( openMenuCount > 0 && menuStack[ openMenuCount - 1 ] == menu )
+ onTopOfMenuStack = qtrue;
+
+ menu->window.flags |= ( WINDOW_HASFOCUS | WINDOW_VISIBLE );
+
+ // If being opened for the first time
+ if( !onTopOfMenuStack )
+ {
+ if( menu->onOpen )
+ {
+ itemDef_t item;
+ item.parent = menu;
+ Item_RunScript( &item, menu->onOpen );
+ }
+
+ if( menu->soundName && *menu->soundName )
+ DC->startBackgroundTrack( menu->soundName, menu->soundName );
+
+ Display_CloseCinematics( );
+
+ Menu_HandleMouseMove( menu, DC->cursorx, DC->cursory ); // force the item under the cursor to focus
+
+ for( i = 0; i < menu->itemCount; i++ ) // reset selection in listboxes when opened
+ {
+ if( Item_IsListBox( menu->items[ i ] ) )
+ {
+ menu->items[ i ]->cursorPos = DC->feederInitialise( menu->items[ i ]->feederID );
+ Item_ListBox_SetStartPos( menu->items[ i ], 0 );
+ DC->feederSelection( menu->items[ i ]->feederID, menu->items[ i ]->cursorPos );
+ }
+ else if( menu->items[ i ]->type == ITEM_TYPE_CYCLE )
+ {
+ menu->items[ i ]->typeData.cycle->cursorPos =
+ DC->feederInitialise( menu->items[ i ]->feederID );
+ }
+
+ }
+
+ if( openMenuCount < MAX_OPEN_MENUS )
+ menuStack[ openMenuCount++ ] = menu;
+ }
+}
+
+qboolean Menus_ReplaceActive( menuDef_t *menu )
+{
+ int i;
+ menuDef_t *active;
+
+ if( openMenuCount < 1 )
+ return qfalse;
+
+ active = menuStack[ openMenuCount - 1 ];
+
+ if( !( active->window.flags & WINDOW_HASFOCUS ) ||
+ !( active->window.flags & WINDOW_VISIBLE ) )
+ {
+ return qfalse;
+ }
+
+ if( menu == active )
+ return qfalse;
+
+ if( menu->itemCount != active->itemCount )
+ {
+ Com_Printf( S_COLOR_YELLOW
+ "WARNING: Menus_ReplaceActive: expecting %i menu items, found %i\n",
+ menu->itemCount, active->itemCount);
+ return qfalse;
+ }
+
+ for( i = 0; i < menu->itemCount; i++ )
+ {
+ if( menu->items[ i ]->type != active->items[ i ]->type )
+ {
+ Com_Printf( S_COLOR_YELLOW
+ "WARNING: Menus_ReplaceActive: type mismatch on item %i\n", i + 1 );
+ return qfalse;
+ }
+ }
+
+ active->window.flags &= ~( WINDOW_FADINGOUT | WINDOW_VISIBLE );
+ menu->window.flags |= ( WINDOW_HASFOCUS | WINDOW_VISIBLE );
+
+ menuStack[ openMenuCount - 1 ] = menu;
+ if( menu->onOpen )
+ {
+ itemDef_t item;
+ item.parent = menu;
+ Item_RunScript( &item, menu->onOpen );
+ }
+
+ // set the cursor position on the new menu to match the active one
+ for( i = 0; i < menu->itemCount; i++ )
+ {
+ menu->items[ i ]->cursorPos = active->items[ i ]->cursorPos;
+ menu->items[ i ]->feederID = active->items[ i ]->feederID;
+ switch( Item_DataType( menu->items[ i ] ) )
+ {
+ case TYPE_LIST:
+ menu->items[ i ]->typeData.list->startPos =
+ active->items[ i ]->typeData.list->startPos;
+ menu->items[ i ]->typeData.list->cursorPos =
+ active->items[ i ]->typeData.list->cursorPos;
+ break;
+ case TYPE_COMBO:
+ menu->items[ i ]->typeData.cycle->cursorPos =
+ active->items[ i ]->typeData.cycle->cursorPos;
+ break;
+ default:
+ break;
+ }
+ }
+ return qtrue;
+}
+
+int Display_VisibleMenuCount( void )
+{
+ int i, count;
+ count = 0;
+
+ for( i = 0; i < menuCount; i++ )
+ {
+ if( Menus[i].window.flags & ( WINDOW_FORCED | WINDOW_VISIBLE ) )
+ count++;
+ }
+
+ return count;
+}
+
+void Menus_HandleOOBClick( menuDef_t *menu, int key, qboolean down )
+{
+ if( menu )
+ {
+ int i;
+ // basically the behaviour we are looking for is if there are windows in the stack.. see if
+ // the cursor is within any of them.. if not close them otherwise activate them and pass the
+ // key on.. force a mouse move to activate focus and script stuff
+
+ if( down && menu->window.flags & WINDOW_OOB_CLICK )
+ Menus_Close( menu );
+
+ for( i = 0; i < menuCount; i++ )
+ {
+ if( Menu_OverActiveItem( &Menus[i], DC->cursorx, DC->cursory ) )
+ {
+ Menus_Close( menu );
+ Menus_Activate( &Menus[i] );
+ Menu_HandleMouseMove( &Menus[i], DC->cursorx, DC->cursory );
+ Menu_HandleKey( &Menus[i], key, down );
+ }
+ }
+
+ if( Display_VisibleMenuCount() == 0 )
+ {
+ if( DC->Pause )
+ DC->Pause( qfalse );
+ }
+
+ Display_CloseCinematics();
+ }
+}
+
+static rectDef_t *Item_CorrectedTextRect( itemDef_t *item )
+{
+ static rectDef_t rect;
+ memset( &rect, 0, sizeof( rectDef_t ) );
+
+ if( item )
+ {
+ rect = item->textRect;
+
+ if( rect.w )
+ rect.y -= rect.h;
+ }
+
+ return &rect;
+}
+
+void Menu_HandleKey( menuDef_t *menu, int key, qboolean down )
+{
+ int i;
+ itemDef_t *item = NULL;
+ qboolean inHandler = qfalse;
+
+ inHandler = qtrue;
+
+ if( g_waitingForKey && down )
+ {
+ Item_Bind_HandleKey( g_bindItem, key, down );
+ inHandler = qfalse;
+ return;
+ }
+
+ if( g_editingField && down )
+ {
+ if( !Item_TextField_HandleKey( g_editItem, key ) )
+ {
+ g_editingField = qfalse;
+ Item_RunScript( g_editItem, g_editItem->onTextEntry );
+ g_editItem = NULL;
+ inHandler = qfalse;
+ return;
+ }
+ else
+ {
+ Item_RunScript( g_editItem, g_editItem->onCharEntry );
+ }
+ }
+
+ if( menu == NULL )
+ {
+ inHandler = qfalse;
+ return;
+ }
+
+ // see if the mouse is within the window bounds and if so is this a mouse click
+ if( down && !( menu->window.flags & WINDOW_POPUP ) &&
+ !Rect_ContainsPoint( &menu->window.rect, DC->cursorx, DC->cursory ) )
+ {
+ static qboolean inHandleKey = qfalse;
+
+ if( !inHandleKey && ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) )
+ {
+ inHandleKey = qtrue;
+ Menus_HandleOOBClick( menu, key, down );
+ inHandleKey = qfalse;
+ inHandler = qfalse;
+ return;
+ }
+ }
+
+ if( g_comboBoxItem == NULL )
+ {
+ // get the item with focus
+ for( i = 0; i < menu->itemCount; i++ )
+ {
+ if( menu->items[i]->window.flags & WINDOW_HASFOCUS )
+ item = menu->items[i];
+ }
+ }
+ else
+ item = g_comboBoxItem;
+
+ if( item != NULL )
+ {
+ if( Item_HandleKey( item, key, down ) )
+ {
+ Item_Action( item );
+ inHandler = qfalse;
+ return;
+ }
+ }
+
+ if( !down )
+ {
+ inHandler = qfalse;
+ return;
+ }
+
+ // default handling
+ switch( key )
+ {
+ case K_F12:
+ if( DC->getCVarValue( "developer" ) )
+ DC->executeText( EXEC_APPEND, "screenshot\n" );
+
+ break;
+
+ case K_KP_UPARROW:
+ case K_UPARROW:
+ Menu_SetPrevCursorItem( menu );
+ break;
+
+ case K_ESCAPE:
+ if( !g_waitingForKey && menu->onESC )
+ {
+ itemDef_t it;
+ it.parent = menu;
+ Item_RunScript( &it, menu->onESC );
+ }
+
+ break;
+
+ case K_TAB:
+ case K_KP_DOWNARROW:
+ case K_DOWNARROW:
+ Menu_SetNextCursorItem( menu );
+ break;
+
+ case K_MOUSE1:
+ case K_MOUSE2:
+ if( item )
+ {
+ if( item->type == ITEM_TYPE_TEXT )
+ {
+ if( Rect_ContainsPoint( Item_CorrectedTextRect( item ), DC->cursorx, DC->cursory ) )
+ Item_Action( item );
+ }
+ else if( Item_IsEditField( item ) )
+ {
+ if( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) )
+ {
+ char buffer[ MAX_STRING_CHARS ] = { 0 };
+
+ if( item->cvar )
+ DC->getCVarString( item->cvar, buffer, sizeof( buffer ) );
+
+ item->cursorPos = strlen( buffer );
+
+ Item_TextField_CalcPaintOffset( item, buffer );
+
+ g_editingField = qtrue;
+
+ g_editItem = item;
+ }
+ }
+ else
+ {
+ if( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) )
+ Item_Action( item );
+ }
+ }
+
+ break;
+
+ case K_JOY1:
+ case K_JOY2:
+ case K_JOY3:
+ case K_JOY4:
+ case K_AUX1:
+ case K_AUX2:
+ case K_AUX3:
+ case K_AUX4:
+ case K_AUX5:
+ case K_AUX6:
+ case K_AUX7:
+ case K_AUX8:
+ case K_AUX9:
+ case K_AUX10:
+ case K_AUX11:
+ case K_AUX12:
+ case K_AUX13:
+ case K_AUX14:
+ case K_AUX15:
+ case K_AUX16:
+ break;
+
+ case K_KP_ENTER:
+ case K_ENTER:
+ if( item )
+ {
+ if( Item_IsEditField( item ) )
+ {
+ char buffer[ MAX_STRING_CHARS ] = { 0 };
+
+ if( item->cvar )
+ DC->getCVarString( item->cvar, buffer, sizeof( buffer ) );
+
+ item->cursorPos = strlen( buffer );
+
+ Item_TextField_CalcPaintOffset( item, buffer );
+
+ g_editingField = qtrue;
+
+ g_editItem = item;
+ }
+ else
+ Item_Action( item );
+ }
+
+ break;
+ }
+
+ inHandler = qfalse;
+}
+
+void ToWindowCoords( float *x, float *y, windowDef_t *window )
+{
+ if( window->border != 0 )
+ {
+ *x += window->borderSize;
+ *y += window->borderSize;
+ }
+
+ *x += window->rect.x;
+ *y += window->rect.y;
+}
+
+void Rect_ToWindowCoords( rectDef_t *rect, windowDef_t *window )
+{
+ ToWindowCoords( &rect->x, &rect->y, window );
+}
+
+void Item_SetTextExtents( itemDef_t *item, const char *text )
+{
+ const char *textPtr = ( text ) ? text : item->text;
+ qboolean cvarContent;
+
+ // It's hard to make a policy on what should be aligned statically and what
+ // should be aligned dynamically; there are reasonable cases for both. If
+ // it continues to be a problem then there should probably be an item keyword
+ // for it; but for the moment only adjusting the alignment of ITEM_TYPE_TEXT
+ // seems to suffice.
+ cvarContent = ( item->cvar && item->textalignment != ALIGN_LEFT &&
+ item->type == ITEM_TYPE_TEXT );
+
+ if( textPtr == NULL )
+ return;
+
+ // as long as the item isn't dynamic content (ownerdraw or cvar), this
+ // keeps us from computing the widths and heights more than once
+ if( item->textRect.w == 0.0f || cvarContent || ( item->type == ITEM_TYPE_OWNERDRAW &&
+ item->textalignment != ALIGN_LEFT ) )
+ {
+ float originalWidth = 0.0f;
+
+ if( item->textalignment == ALIGN_CENTER || item->textalignment == ALIGN_RIGHT )
+ {
+ if( cvarContent )
+ {
+ char buff[ MAX_CVAR_VALUE_STRING ];
+ DC->getCVarString( item->cvar, buff, sizeof( buff ) );
+ originalWidth = UI_Text_Width( item->text, item->textscale ) +
+ UI_Text_Width( buff, item->textscale );
+ }
+ else
+ originalWidth = UI_Text_Width( item->text, item->textscale );
+ }
+
+ item->textRect.w = UI_Text_Width( textPtr, item->textscale );
+ item->textRect.h = UI_Text_Height( textPtr, item->textscale );
+
+ if( item->textvalignment == VALIGN_BOTTOM )
+ item->textRect.y = item->textaligny + item->window.rect.h;
+ else if( item->textvalignment == VALIGN_CENTER )
+ item->textRect.y = item->textaligny + ( ( item->textRect.h + item->window.rect.h ) / 2.0f );
+ else if( item->textvalignment == VALIGN_TOP )
+ item->textRect.y = item->textaligny + item->textRect.h;
+
+ if( item->textalignment == ALIGN_LEFT )
+ item->textRect.x = item->textalignx;
+ else if( item->textalignment == ALIGN_CENTER )
+ item->textRect.x = item->textalignx + ( ( item->window.rect.w - originalWidth ) / 2.0f );
+ else if( item->textalignment == ALIGN_RIGHT )
+ item->textRect.x = item->textalignx + item->window.rect.w - originalWidth;
+
+ ToWindowCoords( &item->textRect.x, &item->textRect.y, &item->window );
+ }
+}
+
+void Item_TextColor( itemDef_t *item, vec4_t *newColor )
+{
+ vec4_t lowLight;
+ menuDef_t *parent = ( menuDef_t* )item->parent;
+
+ Fade( &item->window.flags, &item->window.foreColor[3], parent->fadeClamp,
+ &item->window.nextTime, parent->fadeCycle, qtrue, parent->fadeAmount );
+
+ if( item->window.flags & WINDOW_HASFOCUS )
+ memcpy( newColor, &parent->focusColor, sizeof( vec4_t ) );
+ else if( item->textStyle == ITEM_TEXTSTYLE_BLINK && !( ( DC->realTime / BLINK_DIVISOR ) & 1 ) )
+ {
+ lowLight[0] = 0.8 * item->window.foreColor[0];
+ lowLight[1] = 0.8 * item->window.foreColor[1];
+ lowLight[2] = 0.8 * item->window.foreColor[2];
+ lowLight[3] = 0.8 * item->window.foreColor[3];
+ LerpColor( item->window.foreColor, lowLight, *newColor, 0.5 + 0.5 * sin( DC->realTime / PULSE_DIVISOR ) );
+ }
+ else
+ {
+ memcpy( newColor, &item->window.foreColor, sizeof( vec4_t ) );
+ // items can be enabled and disabled based on cvars
+ }
+
+ if( item->enableCvar != NULL && *item->enableCvar && item->cvarTest != NULL && *item->cvarTest )
+ {
+ if( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) && !Item_EnableShowViaCvar( item, CVAR_ENABLE ) )
+ memcpy( newColor, &parent->disableColor, sizeof( vec4_t ) );
+ }
+}
+
+static void SkipColorCodes( const char **text, char *lastColor )
+{
+ while( Q_IsColorString( *text ) )
+ {
+ lastColor[ 0 ] = (*text)[ 0 ];
+ lastColor[ 1 ] = (*text)[ 1 ];
+ (*text) += 2;
+ }
+}
+
+static void SkipWhiteSpace( const char **text, char *lastColor )
+{
+ while( **text )
+ {
+ SkipColorCodes( text, lastColor );
+
+ if( **text != '\n' && isspace( **text ) )
+ (*text)++;
+ else
+ break;
+ }
+}
+
+const char *Item_Text_Wrap( const char *text, float scale, float width )
+{
+ static char out[ 8192 ] = "";
+ char *paint = out;
+ char c[ 3 ] = "";
+ const char *p;
+ const char *eos;
+ float indentWidth = 0.0f;
+
+ if( !text )
+ return NULL;
+
+ p = text;
+ eos = p + strlen( p );
+
+ if( ( eos - p ) >= sizeof( out ) )
+ return NULL;
+
+ *paint = '\0';
+
+ while( *p )
+ {
+ float textWidth = 0.0f;
+ const char *eol = p;
+ const char *q = p;
+ float testWidth = width - indentWidth;
+
+ SkipColorCodes( &q, c );
+
+ while( q && textWidth < testWidth )
+ {
+ qboolean previousCharIsSpace = qfalse;
+
+ // Remaining string is too short to wrap
+ if( q >= eos )
+ {
+ eol = eos;
+ break;
+ }
+
+ if( q > p && *q == INDENT_MARKER )
+ {
+ indentWidth = textWidth;
+ eol = p;
+ }
+
+ // Some color escapes might still be present
+ SkipColorCodes( &q, c );
+
+ // Manual line break
+ if( *q == '\n' )
+ {
+ eol = q + 1;
+ break;
+ }
+
+ if( !previousCharIsSpace && isspace( *q ) )
+ eol = q;
+
+ textWidth += UI_Char_Width( &q, scale );
+ }
+
+ // No split has taken place, so just split mid-word
+ if( eol == p )
+ eol = q;
+
+ // Note that usage of strcat and strlen is deliberately being
+ // avoided here as it becomes surprisingly expensive on larger
+ // blocks of text
+
+ // Copy text
+ strncpy( paint, p, eol - p );
+ paint += ( eol - p );
+ *paint = '\0';
+
+ p = eol;
+
+ if( paint - out > 0 && *( paint - 1 ) == '\n' )
+ {
+ // The line is deliberately broken, clear the color and
+ // any current indent
+ c[ 0 ] = '\0';
+ indentWidth = 0.0f;
+ }
+ else
+ {
+ // Add a \n if it's not there already
+ *paint++ = '\n';
+ *paint = '\0';
+
+ // Insert a pixel indent on the next line
+ if( indentWidth > 0.0f )
+ {
+ char *indentMarkerText = va( "%f%c", indentWidth, INDENT_MARKER );
+ int indentMarkerTextLength = strlen( indentMarkerText );
+
+ strncpy( paint, indentMarkerText, indentMarkerTextLength );
+ paint += indentMarkerTextLength;
+ *paint = '\0';
+ }
+
+ // Skip leading whitespace on next line and save the
+ // last color code
+ SkipWhiteSpace( &p, c );
+ }
+
+ if( c[ 0 ] )
+ {
+ *paint++ = c[ 0 ];
+ *paint++ = c[ 1 ];
+ *paint = '\0';
+ }
+ }
+
+ return out;
+}
+
+#define MAX_WRAP_CACHE 16
+#define MAX_WRAP_LINES 32
+#define MAX_WRAP_TEXT 512
+
+typedef struct
+{
+ char text[ MAX_WRAP_TEXT * MAX_WRAP_LINES ]; //FIXME: augment with hash?
+ rectDef_t rect;
+ float scale;
+ char lines[ MAX_WRAP_LINES ][ MAX_WRAP_TEXT ];
+ float lineCoords[ MAX_WRAP_LINES ][ 2 ];
+ int numLines;
+}
+
+wrapCache_t;
+
+static wrapCache_t wrapCache[ MAX_WRAP_CACHE ];
+static int cacheWriteIndex = 0;
+static int cacheReadIndex = 0;
+static int cacheReadLineNum = 0;
+
+static void UI_CreateCacheEntry( const char *text, rectDef_t *rect, float scale )
+{
+ wrapCache_t *cacheEntry = &wrapCache[ cacheWriteIndex ];
+
+ Q_strncpyz( cacheEntry->text, text, sizeof( cacheEntry->text ) );
+ cacheEntry->rect.x = rect->x;
+ cacheEntry->rect.y = rect->y;
+ cacheEntry->rect.w = rect->w;
+ cacheEntry->rect.h = rect->h;
+ cacheEntry->scale = scale;
+ cacheEntry->numLines = 0;
+}
+
+static void UI_AddCacheEntryLine( const char *text, float x, float y )
+{
+ wrapCache_t *cacheEntry = &wrapCache[ cacheWriteIndex ];
+
+ if( cacheEntry->numLines >= MAX_WRAP_LINES )
+ return;
+
+ Q_strncpyz( cacheEntry->lines[ cacheEntry->numLines ], text,
+ sizeof( cacheEntry->lines[ 0 ] ) );
+
+ cacheEntry->lineCoords[ cacheEntry->numLines ][ 0 ] = x;
+
+ cacheEntry->lineCoords[ cacheEntry->numLines ][ 1 ] = y;
+
+ cacheEntry->numLines++;
+}
+
+static void UI_FinishCacheEntry( void )
+{
+ cacheWriteIndex = ( cacheWriteIndex + 1 ) % MAX_WRAP_CACHE;
+}
+
+static qboolean UI_CheckWrapCache( const char *text, rectDef_t *rect, float scale )
+{
+ int i;
+
+ for( i = 0; i < MAX_WRAP_CACHE; i++ )
+ {
+ wrapCache_t *cacheEntry = &wrapCache[ i ];
+
+ if( strcmp( text, cacheEntry->text ) )
+ continue;
+
+ if( rect->x != cacheEntry->rect.x ||
+ rect->y != cacheEntry->rect.y ||
+ rect->w != cacheEntry->rect.w ||
+ rect->h != cacheEntry->rect.h )
+ continue;
+
+ if( cacheEntry->scale != scale )
+ continue;
+
+ // This is a match
+ cacheReadIndex = i;
+
+ cacheReadLineNum = 0;
+
+ return qtrue;
+ }
+
+ // No match - wrap isn't cached
+ return qfalse;
+}
+
+static qboolean UI_NextWrapLine( const char **text, float *x, float *y )
+{
+ wrapCache_t *cacheEntry = &wrapCache[ cacheReadIndex ];
+
+ if( cacheReadLineNum >= cacheEntry->numLines )
+ return qfalse;
+
+ *text = cacheEntry->lines[ cacheReadLineNum ];
+
+ *x = cacheEntry->lineCoords[ cacheReadLineNum ][ 0 ];
+
+ *y = cacheEntry->lineCoords[ cacheReadLineNum ][ 1 ];
+
+ cacheReadLineNum++;
+
+ return qtrue;
+}
+
+void Item_Text_Wrapped_Paint( itemDef_t *item )
+{
+ char text[ 1024 ];
+ const char *p, *textPtr;
+ float x, y, w, h;
+ vec4_t color;
+ qboolean useWrapCache = (qboolean)DC->getCVarValue( "ui_textWrapCache" );
+
+ if( item->text == NULL )
+ {
+ if( item->cvar == NULL )
+ return;
+ else
+ {
+ DC->getCVarString( item->cvar, text, sizeof( text ) );
+ textPtr = text;
+ }
+ }
+ else
+ textPtr = item->text;
+
+ if( *textPtr == '\0' )
+ return;
+
+ Item_TextColor( item, &color );
+
+ // Check if this block is cached
+ if( useWrapCache && UI_CheckWrapCache( textPtr, &item->window.rect, item->textscale ) )
+ {
+ while( UI_NextWrapLine( &p, &x, &y ) )
+ {
+ UI_Text_Paint( x, y, item->textscale, color,
+ p, 0, 0, item->textStyle );
+ }
+ }
+ else
+ {
+ char buff[ 1024 ];
+ float fontHeight = UI_Text_EmHeight( item->textscale );
+ const float lineSpacing = fontHeight * 0.4f;
+ float lineHeight = fontHeight + lineSpacing;
+ float textHeight;
+ int textLength;
+ int paintLines, totalLines, lineNum = 0;
+ float paintY;
+ int i;
+
+ if( useWrapCache )
+ UI_CreateCacheEntry( textPtr, &item->window.rect, item->textscale );
+
+ x = item->window.rect.x + item->textalignx;
+ y = item->window.rect.y + item->textaligny;
+ w = item->window.rect.w - ( 2.0f * item->textalignx );
+ h = item->window.rect.h - ( 2.0f * item->textaligny );
+
+ textPtr = Item_Text_Wrap( textPtr, item->textscale, w );
+ textLength = strlen( textPtr );
+
+ // Count lines
+ totalLines = 0;
+
+ for( i = 0; i < textLength; i++ )
+ {
+ if( textPtr[ i ] == '\n' )
+ totalLines++;
+ }
+
+ paintLines = ( int )floor( ( h + lineSpacing ) / lineHeight );
+
+ if( paintLines > totalLines )
+ paintLines = totalLines;
+
+ textHeight = ( paintLines * lineHeight ) - lineSpacing;
+
+ switch( item->textvalignment )
+ {
+ default:
+
+ case VALIGN_BOTTOM:
+ paintY = y + ( h - textHeight );
+ break;
+
+ case VALIGN_CENTER:
+ paintY = y + ( ( h - textHeight ) / 2.0f );
+ break;
+
+ case VALIGN_TOP:
+ paintY = y;
+ break;
+ }
+
+ p = textPtr;
+
+ for( i = 0, lineNum = 0; i < textLength && lineNum < paintLines; i++ )
+ {
+ int lineLength = &textPtr[ i ] - p;
+
+ if( lineLength >= sizeof( buff ) - 1 )
+ break;
+
+ if( textPtr[ i ] == '\n' || textPtr[ i ] == '\0' )
+ {
+ itemDef_t lineItem;
+
+ memset( &lineItem, 0, sizeof( itemDef_t ) );
+ strncpy( buff, p, lineLength );
+ buff[ lineLength ] = '\0';
+ p = &textPtr[ i + 1 ];
+
+ lineItem.type = ITEM_TYPE_TEXT;
+ lineItem.textscale = item->textscale;
+ lineItem.textStyle = item->textStyle;
+ lineItem.text = buff;
+ lineItem.textalignment = item->textalignment;
+ lineItem.textvalignment = VALIGN_TOP;
+ lineItem.textalignx = 0.0f;
+ lineItem.textaligny = 0.0f;
+
+ lineItem.textRect.w = 0.0f;
+ lineItem.textRect.h = 0.0f;
+ lineItem.window.rect.x = x;
+ lineItem.window.rect.y = paintY + ( lineNum * lineHeight );
+ lineItem.window.rect.w = w;
+ lineItem.window.rect.h = lineHeight;
+ lineItem.window.border = item->window.border;
+ lineItem.window.borderSize = item->window.borderSize;
+
+ if( DC->getCVarValue( "ui_developer" ) )
+ {
+ vec4_t color;
+ color[ 0 ] = color[ 2 ] = color[ 3 ] = 1.0f;
+ color[ 1 ] = 0.0f;
+ DC->drawRect( lineItem.window.rect.x, lineItem.window.rect.y,
+ lineItem.window.rect.w, lineItem.window.rect.h, 1, color );
+ }
+
+ Item_SetTextExtents( &lineItem, buff );
+ UI_Text_Paint( lineItem.textRect.x, lineItem.textRect.y,
+ lineItem.textscale, color, buff, 0, 0,
+ lineItem.textStyle );
+
+ if( useWrapCache )
+ UI_AddCacheEntryLine( buff, lineItem.textRect.x, lineItem.textRect.y );
+
+ lineNum++;
+ }
+ }
+
+ if( useWrapCache )
+ UI_FinishCacheEntry( );
+ }
+}
+
+/*
+==============
+UI_DrawTextBlock
+==============
+*/
+void UI_DrawTextBlock( rectDef_t *rect, float text_x, float text_y, vec4_t color,
+ float scale, int textalign, int textvalign,
+ int textStyle, const char *text )
+{
+ static menuDef_t dummyParent;
+ static itemDef_t textItem;
+
+ textItem.text = text;
+
+ textItem.parent = &dummyParent;
+ memcpy( textItem.window.foreColor, color, sizeof( vec4_t ) );
+ textItem.window.flags = 0;
+
+ textItem.window.rect.x = rect->x;
+ textItem.window.rect.y = rect->y;
+ textItem.window.rect.w = rect->w;
+ textItem.window.rect.h = rect->h;
+ textItem.window.border = 0;
+ textItem.window.borderSize = 0.0f;
+ textItem.textRect.x = 0.0f;
+ textItem.textRect.y = 0.0f;
+ textItem.textRect.w = 0.0f;
+ textItem.textRect.h = 0.0f;
+ textItem.textalignment = textalign;
+ textItem.textvalignment = textvalign;
+ textItem.textalignx = text_x;
+ textItem.textaligny = text_y;
+ textItem.textscale = scale;
+ textItem.textStyle = textStyle;
+
+ // Utilise existing wrap code
+ Item_Text_Wrapped_Paint( &textItem );
+}
+
+void Item_Text_Paint( itemDef_t *item )
+{
+ char text[1024];
+ const char *textPtr;
+ vec4_t color;
+
+ if( item->window.flags & WINDOW_WRAPPED )
+ {
+ Item_Text_Wrapped_Paint( item );
+ return;
+ }
+
+ if( item->text == NULL )
+ {
+ if( item->cvar == NULL )
+ return;
+ else
+ {
+ DC->getCVarString( item->cvar, text, sizeof( text ) );
+ textPtr = text;
+ }
+ }
+ else
+ textPtr = item->text;
+
+ // this needs to go here as it sets extents for cvar types as well
+ Item_SetTextExtents( item, textPtr );
+
+ if( *textPtr == '\0' )
+ return;
+
+ Item_TextColor( item, &color );
+
+ UI_Text_Paint( item->textRect.x, item->textRect.y, item->textscale, color, textPtr, 0, 0, item->textStyle );
+}
+
+
+
+void Item_TextField_Paint( itemDef_t *item )
+{
+ char buff[1024];
+ vec4_t newColor;
+ int offset = ( item->text && *item->text ) ? ITEM_VALUE_OFFSET : 0;
+ menuDef_t *parent = ( menuDef_t* )item->parent;
+ editFieldDef_t *editPtr = item->typeData.edit;
+ char cursor = DC->getOverstrikeMode() ? '|' : '_';
+ qboolean editing = ( item->window.flags & WINDOW_HASFOCUS && g_editingField );
+ const int cursorWidth = editing ? EDIT_CURSOR_WIDTH : 0;
+
+ //FIXME: causes duplicate printing if item->text is not set (NULL)
+ Item_Text_Paint( item );
+
+ buff[0] = '\0';
+
+ if( item->cvar )
+ DC->getCVarString( item->cvar, buff, sizeof( buff ) );
+
+ // maxFieldWidth hasn't been set, so use the item's rect
+ if( editPtr->maxFieldWidth == 0 )
+ {
+ editPtr->maxFieldWidth = item->window.rect.w -
+ ( item->textRect.w + offset + ( item->textRect.x - item->window.rect.x ) );
+
+ if( editPtr->maxFieldWidth < MIN_FIELD_WIDTH )
+ editPtr->maxFieldWidth = MIN_FIELD_WIDTH;
+ }
+
+ if( !editing )
+ editPtr->paintOffset = 0;
+
+ // Shorten string to max viewable
+ while( UI_Text_Width( buff + editPtr->paintOffset, item->textscale ) >
+ ( editPtr->maxFieldWidth - cursorWidth ) && strlen( buff ) > 0 )
+ buff[ strlen( buff ) - 1 ] = '\0';
+
+ parent = ( menuDef_t* )item->parent;
+
+ if( item->window.flags & WINDOW_HASFOCUS )
+ memcpy( newColor, &parent->focusColor, sizeof( vec4_t ) );
+ else
+ memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) );
+
+ if( editing )
+ {
+ UI_Text_PaintWithCursor( item->textRect.x + item->textRect.w + offset,
+ item->textRect.y, item->textscale, newColor,
+ buff + editPtr->paintOffset,
+ item->cursorPos - editPtr->paintOffset,
+ cursor, editPtr->maxPaintChars, item->textStyle );
+ }
+ else
+ {
+ UI_Text_Paint( item->textRect.x + item->textRect.w + offset,
+ item->textRect.y, item->textscale, newColor,
+ buff + editPtr->paintOffset, 0,
+ editPtr->maxPaintChars, item->textStyle );
+ }
+
+}
+
+void Item_YesNo_Paint( itemDef_t *item )
+{
+ vec4_t newColor;
+ float value;
+ int offset;
+ menuDef_t *parent = ( menuDef_t* )item->parent;
+
+ value = ( item->cvar ) ? DC->getCVarValue( item->cvar ) : 0;
+
+ if( item->window.flags & WINDOW_HASFOCUS )
+ memcpy( newColor, &parent->focusColor, sizeof( vec4_t ) );
+ else
+ memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) );
+
+ offset = ( item->text && *item->text ) ? ITEM_VALUE_OFFSET : 0;
+
+ if( item->text )
+ {
+ Item_Text_Paint( item );
+ UI_Text_Paint( item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale,
+ newColor, ( value != 0 ) ? "Yes" : "No", 0, 0, item->textStyle );
+ }
+ else
+ UI_Text_Paint( item->textRect.x, item->textRect.y, item->textscale, newColor, ( value != 0 ) ? "Yes" : "No", 0, 0, item->textStyle );
+}
+
+void Item_Multi_Paint( itemDef_t *item )
+{
+ vec4_t newColor;
+ const char *text = "";
+ menuDef_t *parent = ( menuDef_t* )item->parent;
+
+ if( item->window.flags & WINDOW_HASFOCUS )
+ memcpy( newColor, &parent->focusColor, sizeof( vec4_t ) );
+ else
+ memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) );
+
+ text = Item_Multi_Setting( item );
+
+ if( item->text )
+ {
+ Item_Text_Paint( item );
+ UI_Text_Paint( item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y,
+ item->textscale, newColor, text, 0, 0, item->textStyle );
+ }
+ else
+ UI_Text_Paint( item->textRect.x, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle );
+}
+
+void Item_Cycle_Paint( itemDef_t *item )
+{
+ vec4_t newColor;
+ const char *text = "";
+ menuDef_t *parent = (menuDef_t *)item->parent;
+
+ if( item->window.flags & WINDOW_HASFOCUS )
+ memcpy( newColor, &parent->focusColor, sizeof( vec4_t ) );
+ else
+ memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) );
+
+ if( item->typeData.cycle )
+ text = DC->feederItemText( item->feederID, item->typeData.cycle->cursorPos,
+ 0, NULL );
+
+ if( item->text )
+ {
+ Item_Text_Paint( item );
+ UI_Text_Paint( item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y,
+ item->textscale, newColor, text, 0, 0, item->textStyle );
+ }
+ else
+ UI_Text_Paint( item->textRect.x, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle );
+}
+
+
+typedef struct
+{
+ char *command;
+ int id;
+ int defaultbind1;
+ int defaultbind2;
+ int bind1;
+ int bind2;
+}
+bind_t;
+
+typedef struct
+{
+ char *name;
+ float defaultvalue;
+ float value;
+}
+configcvar_t;
+
+
+static bind_t g_bindings[] =
+ {
+ { "+scores", K_TAB, -1, -1, -1 },
+ { "+button2", K_ENTER, -1, -1, -1 },
+ { "+speed", K_SHIFT, -1, -1, -1 },
+ { "+button6", 'z', -1, -1, -1 }, // human dodging
+ { "+button8", 'x', -1, -1, -1 },
+ { "+forward", K_UPARROW, -1, -1, -1 },
+ { "+back", K_DOWNARROW, -1, -1, -1 },
+ { "+moveleft", ',', -1, -1, -1 },
+ { "+moveright", '.', -1, -1, -1 },
+ { "+moveup", K_SPACE, -1, -1, -1 },
+ { "+movedown", 'c', -1, -1, -1 },
+ { "+left", K_LEFTARROW, -1, -1, -1 },
+ { "+right", K_RIGHTARROW, -1, -1, -1 },
+ { "+strafe", K_ALT, -1, -1, -1 },
+ { "+lookup", K_PGDN, -1, -1, -1 },
+ { "+lookdown", K_DEL, -1, -1, -1 },
+ { "+mlook", '/', -1, -1, -1 },
+ { "centerview", K_END, -1, -1, -1 },
+ { "+zoom", -1, -1, -1, -1 },
+ { "weapon 1", '1', -1, -1, -1 },
+ { "weapon 2", '2', -1, -1, -1 },
+ { "weapon 3", '3', -1, -1, -1 },
+ { "weapon 4", '4', -1, -1, -1 },
+ { "weapon 5", '5', -1, -1, -1 },
+ { "weapon 6", '6', -1, -1, -1 },
+ { "weapon 7", '7', -1, -1, -1 },
+ { "weapon 8", '8', -1, -1, -1 },
+ { "weapon 9", '9', -1, -1, -1 },
+ { "weapon 10", '0', -1, -1, -1 },
+ { "weapon 11", -1, -1, -1, -1 },
+ { "weapon 12", -1, -1, -1, -1 },
+ { "weapon 13", -1, -1, -1, -1 },
+ { "+attack", K_MOUSE1, -1, -1, -1 },
+ { "+button5", K_MOUSE2, -1, -1, -1 }, // secondary attack
+ { "reload", 'r', -1, -1, -1 }, // reload
+ { "buy ammo", 'b', -1, -1, -1 }, // buy ammo
+ { "itemact medkit", 'm', -1, -1, -1 }, // use medkit
+ { "+button7", 'q', -1, -1, -1 }, // buildable use
+ { "deconstruct", 'e', -1, -1, -1 }, // buildable destroy
+ { "weapprev", '[', -1, -1, -1 },
+ { "weapnext", ']', -1, -1, -1 },
+ { "+button3", K_MOUSE3, -1, -1, -1 },
+ { "+button4", K_MOUSE4, -1, -1, -1 },
+ { "vote yes", K_F1, -1, -1, -1 },
+ { "vote no", K_F2, -1, -1, -1 },
+ { "teamvote yes", K_F3, -1, -1, -1 },
+ { "teamvote no", K_F4, -1, -1, -1 },
+ { "scoresUp", K_KP_PGUP, -1, -1, -1 },
+ { "scoresDown", K_KP_PGDN, -1, -1, -1 },
+ { "screenshotJPEG",-1, -1, -1, -1 },
+ { "messagemode", -1, -1, -1, -1 },
+ { "messagemode2", -1, -1, -1, -1 },
+ { "cuboidAxis next", 'h', -1, -1, -1 },
+ { "cuboidRotate", 'j', -1, -1, -1 }
+ };
+
+
+static const int g_bindCount = sizeof( g_bindings ) / sizeof( bind_t );
+
+/*
+=================
+Controls_GetKeyAssignment
+=================
+*/
+static void Controls_GetKeyAssignment ( char *command, int *twokeys )
+{
+ int count;
+ int j;
+ char b[256];
+
+ twokeys[0] = twokeys[1] = -1;
+ count = 0;
+
+ for( j = 0; j < 256; j++ )
+ {
+ DC->getBindingBuf( j, b, 256 );
+
+ if( *b == 0 )
+ continue;
+
+ if( !Q_stricmp( b, command ) )
+ {
+ twokeys[count] = j;
+ count++;
+
+ if( count == 2 )
+ break;
+ }
+ }
+}
+
+/*
+=================
+Controls_GetConfig
+=================
+*/
+void Controls_GetConfig( void )
+{
+ int i;
+ int twokeys[ 2 ];
+
+ // iterate each command, get its numeric binding
+
+ for( i = 0; i < g_bindCount; i++ )
+ {
+ Controls_GetKeyAssignment( g_bindings[ i ].command, twokeys );
+
+ g_bindings[ i ].bind1 = twokeys[ 0 ];
+ g_bindings[ i ].bind2 = twokeys[ 1 ];
+ }
+}
+
+/*
+=================
+Controls_SetConfig
+=================
+*/
+void Controls_SetConfig( qboolean restart )
+{
+ int i;
+
+ // iterate each command, get its numeric binding
+
+ for( i = 0; i < g_bindCount; i++ )
+ {
+ if( g_bindings[i].bind1 != -1 )
+ {
+ DC->setBinding( g_bindings[i].bind1, g_bindings[i].command );
+
+ if( g_bindings[i].bind2 != -1 )
+ DC->setBinding( g_bindings[i].bind2, g_bindings[i].command );
+ }
+ }
+
+ DC->executeText( EXEC_APPEND, "in_restart\n" );
+}
+
+/*
+=================
+Controls_SetDefaults
+=================
+*/
+void Controls_SetDefaults( void )
+{
+ int i;
+
+ // iterate each command, set its default binding
+
+ for( i = 0; i < g_bindCount; i++ )
+ {
+ g_bindings[i].bind1 = g_bindings[i].defaultbind1;
+ g_bindings[i].bind2 = g_bindings[i].defaultbind2;
+ }
+}
+
+int BindingIDFromName( const char *name )
+{
+ int i;
+
+ for( i = 0; i < g_bindCount; i++ )
+ {
+ if( Q_stricmp( name, g_bindings[i].command ) == 0 )
+ return i;
+ }
+
+ return -1;
+}
+
+char g_nameBind1[32];
+char g_nameBind2[32];
+
+void BindingFromName( const char *cvar )
+{
+ int i, b1, b2;
+
+ // iterate each command, set its default binding
+
+ for( i = 0; i < g_bindCount; i++ )
+ {
+ if( Q_stricmp( cvar, g_bindings[i].command ) == 0 )
+ {
+ b1 = g_bindings[i].bind1;
+
+ if( b1 == -1 )
+ break;
+
+ DC->keynumToStringBuf( b1, g_nameBind1, 32 );
+ Q_strupr( g_nameBind1 );
+
+ b2 = g_bindings[i].bind2;
+
+ if( b2 != -1 )
+ {
+ DC->keynumToStringBuf( b2, g_nameBind2, 32 );
+ Q_strupr( g_nameBind2 );
+ strcat( g_nameBind1, " or " );
+ strcat( g_nameBind1, g_nameBind2 );
+ }
+
+ return;
+ }
+ }
+
+ strcpy( g_nameBind1, "???" );
+}
+
+void Item_Slider_Paint( itemDef_t *item )
+{
+ vec4_t newColor;
+ float x, y, value;
+ menuDef_t *parent = ( menuDef_t* )item->parent;
+ float vScale = Item_Slider_VScale( item );
+
+ value = ( item->cvar ) ? DC->getCVarValue( item->cvar ) : 0;
+
+ if( item->window.flags & WINDOW_HASFOCUS )
+ memcpy( newColor, &parent->focusColor, sizeof( vec4_t ) );
+ else
+ memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) );
+
+ if( item->text )
+ {
+ Item_Text_Paint( item );
+ x = item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET;
+ y = item->textRect.y - item->textRect.h +
+ ( ( item->textRect.h - ( SLIDER_HEIGHT * vScale ) ) / 2.0f );
+ }
+ else
+ {
+ x = item->window.rect.x;
+ y = item->window.rect.y;
+ }
+
+ DC->setColor( newColor );
+ DC->drawHandlePic( x, y, SLIDER_WIDTH, SLIDER_HEIGHT * vScale, DC->Assets.sliderBar );
+
+ y = item->textRect.y - item->textRect.h +
+ ( ( item->textRect.h - ( SLIDER_THUMB_HEIGHT * vScale ) ) / 2.0f );
+
+ x = Item_Slider_ThumbPosition( item );
+ DC->drawHandlePic( x - ( SLIDER_THUMB_WIDTH / 2 ), y,
+ SLIDER_THUMB_WIDTH, SLIDER_THUMB_HEIGHT * vScale, DC->Assets.sliderThumb );
+
+}
+
+void Item_Bind_Paint( itemDef_t *item )
+{
+ vec4_t newColor, lowLight;
+ float value;
+ int maxChars = 0;
+ menuDef_t *parent = ( menuDef_t* )item->parent;
+
+ if( item->typeData.edit )
+ maxChars = item->typeData.edit->maxPaintChars;
+
+ value = ( item->cvar ) ? DC->getCVarValue( item->cvar ) : 0;
+
+ if( item->window.flags & WINDOW_HASFOCUS )
+ {
+ if( g_bindItem == item )
+ {
+ lowLight[0] = 0.8f * parent->focusColor[0];
+ lowLight[1] = 0.8f * parent->focusColor[1];
+ lowLight[2] = 0.8f * parent->focusColor[2];
+ lowLight[3] = 0.8f * parent->focusColor[3];
+
+ LerpColor( parent->focusColor, lowLight, newColor,
+ 0.5 + 0.5 * sin( DC->realTime / PULSE_DIVISOR ) );
+ }
+ else
+ memcpy( &newColor, &parent->focusColor, sizeof( vec4_t ) );
+ }
+ else
+ memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) );
+
+ if( item->text )
+ {
+ Item_Text_Paint( item );
+
+ if( g_bindItem == item && g_waitingForKey )
+ {
+ UI_Text_Paint( item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y,
+ item->textscale, newColor, "Press key", 0, maxChars, item->textStyle );
+ }
+ else
+ {
+ BindingFromName( item->cvar );
+ UI_Text_Paint( item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y,
+ item->textscale, newColor, g_nameBind1, 0, maxChars, item->textStyle );
+ }
+ }
+ else
+ UI_Text_Paint( item->textRect.x, item->textRect.y, item->textscale, newColor,
+ ( value != 0 ) ? "FIXME" : "FIXME", 0, maxChars, item->textStyle );
+}
+
+qboolean Display_KeyBindPending( void )
+{
+ return g_waitingForKey;
+}
+
+qboolean Item_Bind_HandleKey( itemDef_t *item, int key, qboolean down )
+{
+ int id;
+ int i;
+
+ if( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) && !g_waitingForKey )
+ {
+ if( down && ( key == K_MOUSE1 || key == K_ENTER ) )
+ {
+ g_waitingForKey = qtrue;
+ g_bindItem = item;
+ }
+
+ return qtrue;
+ }
+ else
+ {
+ if( !g_waitingForKey || g_bindItem == NULL )
+ return qtrue;
+
+ if( key & K_CHAR_FLAG )
+ return qtrue;
+
+ switch( key )
+ {
+ case K_ESCAPE:
+ g_waitingForKey = qfalse;
+ return qtrue;
+
+ case K_BACKSPACE:
+ id = BindingIDFromName( item->cvar );
+
+ if( id != -1 )
+ {
+ g_bindings[id].bind1 = -1;
+ g_bindings[id].bind2 = -1;
+ }
+
+ Controls_SetConfig( qtrue );
+ g_waitingForKey = qfalse;
+ g_bindItem = NULL;
+ return qtrue;
+
+ case '`':
+ return qtrue;
+ }
+ }
+
+ if( key != -1 )
+ {
+ for( i = 0; i < g_bindCount; i++ )
+ {
+ if( g_bindings[i].bind2 == key )
+ g_bindings[i].bind2 = -1;
+
+ if( g_bindings[i].bind1 == key )
+ {
+ g_bindings[i].bind1 = g_bindings[i].bind2;
+ g_bindings[i].bind2 = -1;
+ }
+ }
+ }
+
+
+ id = BindingIDFromName( item->cvar );
+
+ if( id != -1 )
+ {
+ if( key == -1 )
+ {
+ if( g_bindings[id].bind1 != -1 )
+ {
+ DC->setBinding( g_bindings[id].bind1, "" );
+ g_bindings[id].bind1 = -1;
+ }
+
+ if( g_bindings[id].bind2 != -1 )
+ {
+ DC->setBinding( g_bindings[id].bind2, "" );
+ g_bindings[id].bind2 = -1;
+ }
+ }
+ else if( g_bindings[id].bind1 == -1 )
+ g_bindings[id].bind1 = key;
+ else if( g_bindings[id].bind1 != key && g_bindings[id].bind2 == -1 )
+ g_bindings[id].bind2 = key;
+ else
+ {
+ DC->setBinding( g_bindings[id].bind1, "" );
+ DC->setBinding( g_bindings[id].bind2, "" );
+ g_bindings[id].bind1 = key;
+ g_bindings[id].bind2 = -1;
+ }
+ }
+
+ Controls_SetConfig( qtrue );
+ g_waitingForKey = qfalse;
+
+ return qtrue;
+}
+
+
+void Item_Model_Paint( itemDef_t *item )
+{
+ float x, y, w, h;
+ refdef_t refdef;
+ refEntity_t ent;
+ vec3_t mins, maxs, origin;
+ vec3_t angles;
+ modelDef_t *modelPtr = item->typeData.model;
+
+ if( modelPtr == NULL )
+ return;
+
+ // setup the refdef
+ memset( &refdef, 0, sizeof( refdef ) );
+
+ refdef.rdflags = RDF_NOWORLDMODEL;
+
+ AxisClear( refdef.viewaxis );
+
+ x = item->window.rect.x + 1;
+ y = item->window.rect.y + 1;
+ w = item->window.rect.w - 2;
+ h = item->window.rect.h - 2;
+
+ UI_AdjustFrom640( &x, &y, &w, &h );
+
+ refdef.x = x;
+ refdef.y = y;
+ refdef.width = w;
+ refdef.height = h;
+
+ DC->modelBounds( item->asset, mins, maxs );
+
+ origin[2] = -0.5 * ( mins[2] + maxs[2] );
+ origin[1] = 0.5 * ( mins[1] + maxs[1] );
+
+ // calculate distance so the model nearly fills the box
+ if( qtrue )
+ {
+ float len = 0.5 * ( maxs[2] - mins[2] );
+ origin[0] = len / 0.268; // len / tan( fov/2 )
+ //origin[0] = len / tan(w/2);
+ }
+ else
+ origin[0] = item->textscale;
+
+ refdef.fov_x = ( modelPtr->fov_x ) ? modelPtr->fov_x : w;
+ refdef.fov_y = ( modelPtr->fov_y ) ? modelPtr->fov_y : h;
+
+ //refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f);
+ //xx = refdef.width / tan( refdef.fov_x / 360 * M_PI );
+ //refdef.fov_y = atan2( refdef.height, xx );
+ //refdef.fov_y *= ( 360 / M_PI );
+
+ DC->clearScene();
+
+ refdef.time = DC->realTime;
+
+ // add the model
+
+ memset( &ent, 0, sizeof( ent ) );
+
+ //adjust = 5.0 * sin( (float)uis.realtime / 500 );
+ //adjust = 360 % (int)((float)uis.realtime / 1000);
+ //VectorSet( angles, 0, 0, 1 );
+
+ // use item storage to track
+
+ if( modelPtr->rotationSpeed )
+ {
+ if( DC->realTime > item->window.nextTime )
+ {
+ item->window.nextTime = DC->realTime + modelPtr->rotationSpeed;
+ modelPtr->angle = ( int )( modelPtr->angle + 1 ) % 360;
+ }
+ }
+
+ VectorSet( angles, 0, modelPtr->angle, 0 );
+ AnglesToAxis( angles, ent.axis );
+
+ ent.hModel = item->asset;
+ VectorCopy( origin, ent.origin );
+ VectorCopy( origin, ent.lightingOrigin );
+ ent.renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW;
+ VectorCopy( ent.origin, ent.oldorigin );
+
+ DC->addRefEntityToScene( &ent );
+ DC->renderScene( &refdef );
+
+}
+
+
+void Item_ListBoxRow_Paint( itemDef_t *item, int row, int renderPos, qboolean highlight, qboolean scrollbar )
+{
+ float x, y, w;
+ listBoxDef_t *listPtr = item->typeData.list;
+ menuDef_t *menu = ( menuDef_t * )item->parent;
+ float one, two;
+
+ one = 1.0f * DC->aspectScale;
+ two = 2.0f * DC->aspectScale;
+
+ x = SCROLLBAR_X( item );
+ y = SCROLLBAR_Y( item ) + ( listPtr->elementHeight * renderPos );
+ w = item->window.rect.w - ( two * item->window.borderSize );
+
+ if( scrollbar )
+ w -= SCROLLBAR_ARROW_WIDTH;
+
+ if( listPtr->elementStyle == LISTBOX_IMAGE )
+ {
+ qhandle_t image = DC->feederItemImage( item->feederID, row );
+
+ UI_SetClipRegion( x, y, listPtr->elementWidth, listPtr->elementHeight );
+
+ if( image )
+ DC->drawHandlePic( x + one, y + 1.0f, listPtr->elementWidth - two, listPtr->elementHeight - 2.0f, image );
+
+ if( highlight && row == item->cursorPos )
+ {
+ DC->drawRect( x, y, listPtr->elementWidth, listPtr->elementHeight,
+ item->window.borderSize, item->window.borderColor );
+ }
+
+ UI_ClearClipRegion( );
+ }
+ else
+ {
+ const float m = UI_Text_EmHeight( item->textscale );
+ char text[ MAX_STRING_CHARS ];
+ qhandle_t optionalImage;
+
+ if( listPtr->numColumns > 0 )
+ {
+ int j;
+
+ for( j = 0; j < listPtr->numColumns; j++ )
+ {
+ float columnPos;
+ float width, height, yOffset;
+
+ if( menu->window.aspectBias != ASPECT_NONE || item->window.aspectBias != ASPECT_NONE )
+ {
+ columnPos = ( listPtr->columnInfo[ j ].pos + 4.0f ) * DC->aspectScale;
+ width = listPtr->columnInfo[ j ].width * DC->aspectScale;
+ }
+ else
+ {
+ columnPos = ( listPtr->columnInfo[ j ].pos + 4.0f );
+ width = listPtr->columnInfo[ j ].width;
+ }
+
+ height = listPtr->columnInfo[ j ].width;
+ yOffset = y + ( ( listPtr->elementHeight - height ) / 2.0f );
+
+ Q_strncpyz( text, DC->feederItemText( item->feederID, row, j, &optionalImage ), sizeof( text ) );
+
+ UI_SetClipRegion( x + columnPos, yOffset, width, height );
+
+ if( optionalImage >= 0 )
+ DC->drawHandlePic( x + columnPos, yOffset, width, height, optionalImage );
+ else if( text[ 0 ] )
+ {
+ float alignOffset = 0.0f, tw;
+
+ tw = UI_Text_Width( text, item->textscale );
+
+ switch( listPtr->columnInfo[ j ].align )
+ {
+ case ALIGN_LEFT:
+ alignOffset = 0.0f;
+ break;
+
+ case ALIGN_RIGHT:
+ alignOffset = width - tw;
+ break;
+
+ case ALIGN_CENTER:
+ alignOffset = ( width / 2.0f ) - ( tw / 2.0f );
+ break;
+
+ default:
+ alignOffset = 0.0f;
+ }
+
+ UI_Text_Paint( x + columnPos + alignOffset,
+ y + m + ( ( listPtr->elementHeight - m ) / 2.0f ),
+ item->textscale, item->window.foreColor, text, 0,
+ 0, item->textStyle );
+ }
+
+ UI_ClearClipRegion( );
+ }
+ }
+ else
+ {
+ float offset;
+
+ if( menu->window.aspectBias != ASPECT_NONE || item->window.aspectBias != ASPECT_NONE )
+ offset = 4.0f * DC->aspectScale;
+ else
+ offset = 4.0f;
+
+ Q_strncpyz( text, DC->feederItemText( item->feederID, row, 0, &optionalImage ), sizeof( text ) );
+
+ UI_SetClipRegion( x, y, w, listPtr->elementHeight );
+
+ if( optionalImage >= 0 )
+ DC->drawHandlePic( x + offset, y, listPtr->elementHeight, listPtr->elementHeight, optionalImage );
+ else if( text[ 0 ] )
+ {
+ UI_Text_Paint( x + offset, y + m + ( ( listPtr->elementHeight - m ) / 2.0f ),
+ item->textscale, item->window.foreColor, text, 0,
+ 0, item->textStyle );
+ }
+
+ UI_ClearClipRegion( );
+ }
+
+ if( highlight && row == item->cursorPos )
+ DC->fillRect( x, y, w, listPtr->elementHeight, item->window.outlineColor );
+ }
+}
+
+void Item_ListBox_Paint( itemDef_t *item )
+{
+ float size;
+ int i;
+ listBoxDef_t *listPtr = item->typeData.list;
+ int count = DC->feederCount( item->feederID );
+ qboolean scrollbar = !listPtr->noscrollbar &&
+ count > Item_ListBox_NumItemsForItemHeight( item );
+
+ if( scrollbar )
+ {
+ float x = SCROLLBAR_SLIDER_X( item );
+ float y = SCROLLBAR_Y( item );
+ float thumbY = Item_ListBox_ThumbDrawPosition( item );
+
+ // Up arrow
+ DC->drawHandlePic( x, y, SCROLLBAR_ARROW_WIDTH, SCROLLBAR_ARROW_HEIGHT, DC->Assets.scrollBarArrowUp );
+ y = SCROLLBAR_SLIDER_Y( item );
+
+ // Scroll bar
+ size = SCROLLBAR_SLIDER_HEIGHT( item );
+ DC->drawHandlePic( x, y, SCROLLBAR_ARROW_WIDTH, size, DC->Assets.scrollBar );
+ y = SCROLLBAR_SLIDER_Y( item ) + size;
+
+ // Down arrow
+ DC->drawHandlePic( x, y, SCROLLBAR_ARROW_WIDTH, SCROLLBAR_ARROW_HEIGHT, DC->Assets.scrollBarArrowDown );
+
+ // Thumb
+ DC->drawHandlePic( x, thumbY, SCROLLBAR_ARROW_WIDTH, SCROLLBAR_ARROW_HEIGHT, DC->Assets.scrollBarThumb );
+ }
+
+ // Paint rows
+ for( i = listPtr->startPos; i < listPtr->endPos; i++ )
+ Item_ListBoxRow_Paint( item, i, i - listPtr->startPos, qtrue, scrollbar );
+}
+
+void Item_Paint( itemDef_t *item );
+
+void Item_ComboBox_Paint( itemDef_t *item )
+{
+ float x, y, h;
+
+ x = SCROLLBAR_SLIDER_X( item );
+ y = SCROLLBAR_Y( item );
+ h = item->window.rect.h - 2.0f;
+
+ // Down arrow
+ DC->drawHandlePic( x, y, SCROLLBAR_ARROW_WIDTH, h, DC->Assets.scrollBarArrowDown );
+
+ Item_ListBoxRow_Paint( item, item->cursorPos, 0, qfalse, qtrue );
+
+ if( g_comboBoxItem != NULL )
+ {
+ qboolean cast = Item_ComboBox_MaybeCastToListBox( item );
+ Item_Paint( item );
+ Item_ComboBox_MaybeUnCastFromListBox( item, cast );
+ }
+}
+
+void Item_ListBox_Update( itemDef_t *item )
+{
+ listBoxDef_t *listPtr = item->typeData.list;
+ int feederCount = DC->feederCount( item->feederID );
+
+ if( listPtr->lastFeederCount != feederCount )
+ {
+ if( listPtr->resetonfeederchange )
+ {
+ item->cursorPos = DC->feederInitialise( item->feederID );
+ Item_ListBox_SetStartPos( item, 0 );
+ DC->feederSelection( item->feederID, item->cursorPos );
+ }
+ else
+ {
+ // Make sure endPos is up-to-date
+ Item_ListBox_SetStartPos( item, listPtr->startPos );
+
+ // If the selection is off the end now, select the last element
+ if( item->cursorPos >= feederCount )
+ item->cursorPos = feederCount - 1;
+ }
+ }
+
+ listPtr->lastFeederCount = feederCount;
+}
+
+void Item_OwnerDraw_Paint( itemDef_t *item )
+{
+ menuDef_t *parent;
+ const char *text;
+
+ if( item == NULL )
+ return;
+
+ parent = ( menuDef_t* )item->parent;
+
+ if( DC->ownerDrawItem )
+ {
+ vec4_t color, lowLight;
+ menuDef_t *parent = ( menuDef_t* )item->parent;
+ Fade( &item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime,
+ parent->fadeCycle, qtrue, parent->fadeAmount );
+ memcpy( &color, &item->window.foreColor, sizeof( color ) );
+
+ if( item->numColors > 0 && DC->getValue )
+ {
+ // if the value is within one of the ranges then set color to that, otherwise leave at default
+ int i;
+ float f = DC->getValue( item->window.ownerDraw );
+
+ for( i = 0; i < item->numColors; i++ )
+ {
+ if( f >= item->colorRanges[i].low && f <= item->colorRanges[i].high )
+ {
+ memcpy( &color, &item->colorRanges[i].color, sizeof( color ) );
+ break;
+ }
+ }
+ }
+
+ if( item->window.flags & WINDOW_HASFOCUS )
+ memcpy( color, &parent->focusColor, sizeof( vec4_t ) );
+ else if( item->textStyle == ITEM_TEXTSTYLE_BLINK && !( ( DC->realTime / BLINK_DIVISOR ) & 1 ) )
+ {
+ lowLight[0] = 0.8 * item->window.foreColor[0];
+ lowLight[1] = 0.8 * item->window.foreColor[1];
+ lowLight[2] = 0.8 * item->window.foreColor[2];
+ lowLight[3] = 0.8 * item->window.foreColor[3];
+ LerpColor( item->window.foreColor, lowLight, color, 0.5 + 0.5 * sin( DC->realTime / PULSE_DIVISOR ) );
+ }
+
+ if( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) && !Item_EnableShowViaCvar( item, CVAR_ENABLE ) )
+ Com_Memcpy( color, parent->disableColor, sizeof( vec4_t ) );
+
+ if( DC->ownerDrawText && ( text = DC->ownerDrawText( item->window.ownerDraw ) ) )
+ {
+ if( item->text && *item->text )
+ {
+ Item_Text_Paint( item );
+
+ UI_Text_Paint( item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET,
+ item->textRect.y, item->textscale,
+ color, text, 0, 0, item->textStyle );
+ }
+ else
+ {
+ item->text = text;
+ Item_Text_Paint( item );
+ item->text = NULL;
+ }
+ }
+ else
+ {
+ DC->ownerDrawItem( item->window.rect.x, item->window.rect.y,
+ item->window.rect.w, item->window.rect.h,
+ item->textalignx, item->textaligny,
+ item->window.ownerDraw, item->window.ownerDrawFlags,
+ item->alignment, item->textalignment, item->textvalignment,
+ item->window.borderSize, item->textscale, color, item->window.backColor,
+ item->window.background, item->textStyle );
+ }
+ }
+}
+
+
+void Item_Update( itemDef_t *item )
+{
+ if( item == NULL )
+ return;
+
+ if( Item_IsListBox( item ) )
+ Item_ListBox_Update( item );
+}
+
+void Item_Paint( itemDef_t *item )
+{
+ vec4_t red;
+ menuDef_t *parent = ( menuDef_t* )item->parent;
+ red[0] = red[3] = 1;
+ red[1] = red[2] = 0;
+
+ if( item == NULL )
+ return;
+
+ if( item->window.flags & WINDOW_ORBITING )
+ {
+ if( DC->realTime > item->window.nextTime )
+ {
+ float rx, ry, a, c, s, w, h;
+
+ item->window.nextTime = DC->realTime + item->window.offsetTime;
+ // translate
+ w = item->window.rectClient.w / 2;
+ h = item->window.rectClient.h / 2;
+ rx = item->window.rectClient.x + w - item->window.rectEffects.x;
+ ry = item->window.rectClient.y + h - item->window.rectEffects.y;
+ a = 3 * M_PI / 180;
+ c = cos( a );
+ s = sin( a );
+ item->window.rectClient.x = ( rx * c - ry * s ) + item->window.rectEffects.x - w;
+ item->window.rectClient.y = ( rx * s + ry * c ) + item->window.rectEffects.y - h;
+ Item_UpdatePosition( item );
+
+ }
+ }
+
+
+ if( item->window.flags & WINDOW_INTRANSITION )
+ {
+ if( DC->realTime > item->window.nextTime )
+ {
+ int done = 0;
+ item->window.nextTime = DC->realTime + item->window.offsetTime;
+ // transition the x,y
+
+ if( item->window.rectClient.x == item->window.rectEffects.x )
+ done++;
+ else
+ {
+ if( item->window.rectClient.x < item->window.rectEffects.x )
+ {
+ item->window.rectClient.x += item->window.rectEffects2.x;
+
+ if( item->window.rectClient.x > item->window.rectEffects.x )
+ {
+ item->window.rectClient.x = item->window.rectEffects.x;
+ done++;
+ }
+ }
+ else
+ {
+ item->window.rectClient.x -= item->window.rectEffects2.x;
+
+ if( item->window.rectClient.x < item->window.rectEffects.x )
+ {
+ item->window.rectClient.x = item->window.rectEffects.x;
+ done++;
+ }
+ }
+ }
+
+ if( item->window.rectClient.y == item->window.rectEffects.y )
+ done++;
+ else
+ {
+ if( item->window.rectClient.y < item->window.rectEffects.y )
+ {
+ item->window.rectClient.y += item->window.rectEffects2.y;
+
+ if( item->window.rectClient.y > item->window.rectEffects.y )
+ {
+ item->window.rectClient.y = item->window.rectEffects.y;
+ done++;
+ }
+ }
+ else
+ {
+ item->window.rectClient.y -= item->window.rectEffects2.y;
+
+ if( item->window.rectClient.y < item->window.rectEffects.y )
+ {
+ item->window.rectClient.y = item->window.rectEffects.y;
+ done++;
+ }
+ }
+ }
+
+ if( item->window.rectClient.w == item->window.rectEffects.w )
+ done++;
+ else
+ {
+ if( item->window.rectClient.w < item->window.rectEffects.w )
+ {
+ item->window.rectClient.w += item->window.rectEffects2.w;
+
+ if( item->window.rectClient.w > item->window.rectEffects.w )
+ {
+ item->window.rectClient.w = item->window.rectEffects.w;
+ done++;
+ }
+ }
+ else
+ {
+ item->window.rectClient.w -= item->window.rectEffects2.w;
+
+ if( item->window.rectClient.w < item->window.rectEffects.w )
+ {
+ item->window.rectClient.w = item->window.rectEffects.w;
+ done++;
+ }
+ }
+ }
+
+ if( item->window.rectClient.h == item->window.rectEffects.h )
+ done++;
+ else
+ {
+ if( item->window.rectClient.h < item->window.rectEffects.h )
+ {
+ item->window.rectClient.h += item->window.rectEffects2.h;
+
+ if( item->window.rectClient.h > item->window.rectEffects.h )
+ {
+ item->window.rectClient.h = item->window.rectEffects.h;
+ done++;
+ }
+ }
+ else
+ {
+ item->window.rectClient.h -= item->window.rectEffects2.h;
+
+ if( item->window.rectClient.h < item->window.rectEffects.h )
+ {
+ item->window.rectClient.h = item->window.rectEffects.h;
+ done++;
+ }
+ }
+ }
+
+ Item_UpdatePosition( item );
+
+ if( done == 4 )
+ item->window.flags &= ~WINDOW_INTRANSITION;
+
+ }
+ }
+
+ if( item->window.ownerDrawFlags && DC->ownerDrawVisible )
+ {
+ if( !DC->ownerDrawVisible( item->window.ownerDrawFlags ) )
+ item->window.flags &= ~WINDOW_VISIBLE;
+ else
+ item->window.flags |= WINDOW_VISIBLE;
+ }
+
+ if( item->cvarFlags & ( CVAR_SHOW | CVAR_HIDE ) )
+ {
+ if( !Item_EnableShowViaCvar( item, CVAR_SHOW ) )
+ return;
+ }
+
+ if( item->window.flags & WINDOW_TIMEDVISIBLE )
+ {
+ }
+
+ if( !( item->window.flags & WINDOW_VISIBLE ) )
+ return;
+
+ Window_Paint( &item->window, parent->fadeAmount, parent->fadeClamp, parent->fadeCycle );
+
+ if( DC->getCVarValue( "ui_developer" ) )
+ {
+ vec4_t color;
+ rectDef_t *r = Item_CorrectedTextRect( item );
+ color[1] = color[3] = 1;
+ color[0] = color[2] = 0;
+ DC->drawRect( r->x, r->y, r->w, r->h, 1, color );
+ }
+
+ switch( item->type )
+ {
+ case ITEM_TYPE_OWNERDRAW:
+ Item_OwnerDraw_Paint( item );
+ break;
+
+ case ITEM_TYPE_TEXT:
+ case ITEM_TYPE_BUTTON:
+ Item_Text_Paint( item );
+ break;
+
+ case ITEM_TYPE_RADIOBUTTON:
+ break;
+
+ case ITEM_TYPE_CHECKBOX:
+ break;
+
+ case ITEM_TYPE_CYCLE:
+ Item_Cycle_Paint( item );
+ break;
+
+ case ITEM_TYPE_LISTBOX:
+ Item_ListBox_Paint( item );
+ break;
+
+ case ITEM_TYPE_COMBOBOX:
+ Item_ComboBox_Paint( item );
+ break;
+
+ case ITEM_TYPE_MODEL:
+ Item_Model_Paint( item );
+ break;
+
+ case ITEM_TYPE_YESNO:
+ Item_YesNo_Paint( item );
+ break;
+
+ case ITEM_TYPE_MULTI:
+ Item_Multi_Paint( item );
+ break;
+
+ case ITEM_TYPE_BIND:
+ Item_Bind_Paint( item );
+ break;
+
+ case ITEM_TYPE_SLIDER:
+ Item_Slider_Paint( item );
+ break;
+
+ default:
+ if( Item_IsEditField( item ) )
+ Item_TextField_Paint( item );
+
+ break;
+ }
+
+ Border_Paint( &item->window );
+}
+
+void Menu_Init( menuDef_t *menu )
+{
+ memset( menu, 0, sizeof( menuDef_t ) );
+ menu->cursorItem = -1;
+ menu->fadeAmount = DC->Assets.fadeAmount;
+ menu->fadeClamp = DC->Assets.fadeClamp;
+ menu->fadeCycle = DC->Assets.fadeCycle;
+ Window_Init( &menu->window );
+ menu->window.aspectBias = ALIGN_CENTER;
+}
+
+itemDef_t *Menu_GetFocusedItem( menuDef_t *menu )
+{
+ int i;
+
+ if( menu )
+ {
+ for( i = 0; i < menu->itemCount; i++ )
+ {
+ if( menu->items[i]->window.flags & WINDOW_HASFOCUS )
+ return menu->items[i];
+ }
+ }
+
+ return NULL;
+}
+
+menuDef_t *Menu_GetFocused( void )
+{
+ int i;
+
+ for( i = 0; i < menuCount; i++ )
+ {
+ if( Menus[i].window.flags & WINDOW_HASFOCUS && Menus[i].window.flags & WINDOW_VISIBLE )
+ return & Menus[i];
+ }
+
+ return NULL;
+}
+
+void Menu_ScrollFeeder( menuDef_t *menu, int feeder, qboolean down )
+{
+ if( menu )
+ {
+ int i;
+
+ for( i = 0; i < menu->itemCount; i++ )
+ {
+ itemDef_t *item = menu->items[ i ];
+
+ if( item->feederID == feeder )
+ {
+ qboolean cast = Item_ComboBox_MaybeCastToListBox( item );
+ Item_ListBox_HandleKey( item, down ? K_DOWNARROW : K_UPARROW, qtrue, qtrue );
+ Item_ComboBox_MaybeUnCastFromListBox( item, cast );
+
+ return;
+ }
+ }
+ }
+}
+
+
+
+void Menu_SetFeederSelection( menuDef_t *menu, int feeder, int index, const char *name )
+{
+ if( menu == NULL )
+ {
+ if( name == NULL )
+ menu = Menu_GetFocused();
+ else
+ menu = Menus_FindByName( name );
+ }
+
+ if( menu )
+ {
+ int i;
+
+ for( i = 0; i < menu->itemCount; i++ )
+ {
+ if( menu->items[i]->feederID == feeder )
+ {
+ if( Item_IsListBox( menu->items[i] ) && index == 0 )
+ {
+ menu->items[ i ]->typeData.list->cursorPos = 0;
+ Item_ListBox_SetStartPos( menu->items[ i ], 0 );
+ }
+
+ menu->items[i]->cursorPos = index;
+ DC->feederSelection( menu->items[i]->feederID, menu->items[i]->cursorPos );
+ return;
+ }
+ }
+ }
+}
+
+qboolean Menus_AnyFullScreenVisible( void )
+{
+ int i;
+
+ for( i = 0; i < menuCount; i++ )
+ {
+ if( Menus[i].window.flags & WINDOW_VISIBLE && Menus[i].fullScreen )
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+menuDef_t *Menus_ActivateByName( const char *p )
+{
+ int i;
+ menuDef_t *m = NULL;
+
+ // Activate one menu
+
+ for( i = 0; i < menuCount; i++ )
+ {
+ if( Q_stricmp( Menus[i].window.name, p ) == 0 )
+ {
+ m = &Menus[i];
+ Menus_Activate( m );
+ break;
+ }
+ }
+
+ // Defocus the others
+ for( i = 0; i < menuCount; i++ )
+ {
+ if( Q_stricmp( Menus[i].window.name, p ) != 0 )
+ Menus[i].window.flags &= ~WINDOW_HASFOCUS;
+ }
+
+ return m;
+}
+
+menuDef_t *Menus_ReplaceActiveByName( const char *p )
+{
+ int i;
+ menuDef_t *m = NULL;
+
+ // Activate one menu
+
+ for( i = 0; i < menuCount; i++ )
+ {
+ if( Q_stricmp( Menus[i].window.name, p ) == 0 )
+ {
+ m = &Menus[i];
+ if(! Menus_ReplaceActive( m ) )
+ return NULL;
+ break;
+ }
+ }
+ return m;
+}
+
+
+void Item_Init( itemDef_t *item )
+{
+ memset( item, 0, sizeof( itemDef_t ) );
+ item->textscale = 0.55f;
+ Window_Init( &item->window );
+ item->window.aspectBias = ASPECT_NONE;
+}
+
+static qboolean Item_HandleMouseMove( itemDef_t *item, float x, float y, int pass, qboolean focusSet )
+{
+ if( Rect_ContainsPoint( &item->window.rect, x, y ) )
+ {
+ if( pass == 1 )
+ {
+ if( item->type == ITEM_TYPE_TEXT && item->text )
+ {
+ if( !Rect_ContainsPoint( Item_CorrectedTextRect( item ), x, y ) )
+ return qtrue;
+ }
+
+ // if we are over an item
+ if( IsVisible( item->window.flags ) )
+ {
+ // different one
+ Item_MouseEnter( item, x, y );
+
+ if( !focusSet )
+ focusSet = Item_SetFocus( item, x, y );
+ }
+ }
+
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+void Menu_HandleMouseMove( menuDef_t *menu, float x, float y )
+{
+ int i, pass;
+ qboolean focusSet = qfalse;
+ qboolean result;
+ qboolean cast;
+
+ if( menu == NULL )
+ return;
+
+ if( !( menu->window.flags & ( WINDOW_VISIBLE | WINDOW_FORCED ) ) )
+ return;
+
+ if( itemCapture )
+ {
+ //Item_MouseMove(itemCapture, x, y);
+ return;
+ }
+
+ if( g_waitingForKey || g_editingField )
+ return;
+
+ if( g_comboBoxItem != NULL )
+ {
+ Item_SetFocus( g_comboBoxItem, x, y );
+ focusSet = qtrue;
+ }
+
+ // FIXME: this is the whole issue of focus vs. mouse over..
+ // need a better overall solution as i don't like going through everything twice
+ for( pass = 0; pass < 2; pass++ )
+ {
+ for( i = 0; i < menu->itemCount; i++ )
+ {
+ itemDef_t *item = menu->items[ i ];
+
+ // turn off focus each item
+ // menu->items[i].window.flags &= ~WINDOW_HASFOCUS;
+
+ if( !( item->window.flags & ( WINDOW_VISIBLE | WINDOW_FORCED ) ) )
+ continue;
+
+ // items can be enabled and disabled based on cvars
+ if( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) &&
+ !Item_EnableShowViaCvar( item, CVAR_ENABLE ) )
+ continue;
+
+ if( item->cvarFlags & ( CVAR_SHOW | CVAR_HIDE ) &&
+ !Item_EnableShowViaCvar( item, CVAR_SHOW ) )
+ continue;
+
+ cast = Item_ComboBox_MaybeCastToListBox( item );
+ result = Item_HandleMouseMove( item, x, y, pass, focusSet );
+ Item_ComboBox_MaybeUnCastFromListBox( item, cast );
+
+ if( !result && item->window.flags & WINDOW_MOUSEOVER )
+ {
+ Item_MouseLeave( item );
+ Item_SetMouseOver( item, qfalse );
+ }
+ }
+ }
+
+}
+
+void Menu_Update( menuDef_t *menu )
+{
+ int i;
+
+ if( menu == NULL )
+ return;
+
+ for( i = 0; i < menu->itemCount; i++ )
+ Item_Update( menu->items[ i ] );
+}
+
+void Menu_Paint( menuDef_t *menu, qboolean forcePaint )
+{
+ int i;
+
+ if( menu == NULL )
+ return;
+
+ if( !( menu->window.flags & WINDOW_VISIBLE ) && !forcePaint )
+ return;
+
+ if( menu->window.ownerDrawFlags && DC->ownerDrawVisible && !DC->ownerDrawVisible( menu->window.ownerDrawFlags ) )
+ return;
+
+ if( forcePaint )
+ menu->window.flags |= WINDOW_FORCED;
+
+ // draw the background if necessary
+ if( menu->fullScreen )
+ {
+ // implies a background shader
+ // FIXME: make sure we have a default shader if fullscreen is set with no background
+ DC->drawHandlePic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, menu->window.background );
+ }
+
+ // paint the background and or border
+ Window_Paint( &menu->window, menu->fadeAmount, menu->fadeClamp, menu->fadeCycle );
+
+ Border_Paint( &menu->window );
+
+ for( i = 0; i < menu->itemCount; i++ )
+ Item_Paint( menu->items[i] );
+
+ if( DC->getCVarValue( "ui_developer" ) )
+ {
+ vec4_t color;
+ color[0] = color[2] = color[3] = 1;
+ color[1] = 0;
+ DC->drawRect( menu->window.rect.x, menu->window.rect.y, menu->window.rect.w, menu->window.rect.h, 1, color );
+ }
+}
+
+/*
+===============
+Keyword Hash
+===============
+*/
+
+#define KEYWORDHASH_SIZE 512
+
+typedef struct keywordHash_s
+{
+ char *keyword;
+ qboolean ( *func )( itemDef_t *item, int handle );
+
+ int param;
+
+ struct keywordHash_s *next;
+}
+
+keywordHash_t;
+
+int KeywordHash_Key( char *keyword )
+{
+ int register hash, i;
+
+ hash = 0;
+
+ for( i = 0; keyword[i] != '\0'; i++ )
+ {
+ if( keyword[i] >= 'A' && keyword[i] <= 'Z' )
+ hash += ( keyword[i] + ( 'a' - 'A' ) ) * ( 119 + i );
+ else
+ hash += keyword[i] * ( 119 + i );
+ }
+
+ hash = ( hash ^ ( hash >> 10 ) ^ ( hash >> 20 ) ) & ( KEYWORDHASH_SIZE - 1 );
+ return hash;
+}
+
+void KeywordHash_Add( keywordHash_t *table[], keywordHash_t *key )
+{
+ int hash;
+
+ hash = KeywordHash_Key( key->keyword );
+ /*
+ if(table[hash]) int collision = qtrue;
+ */
+ key->next = table[hash];
+ table[hash] = key;
+}
+
+keywordHash_t *KeywordHash_Find( keywordHash_t *table[], char *keyword )
+{
+ keywordHash_t *key;
+ int hash;
+
+ hash = KeywordHash_Key( keyword );
+
+ for( key = table[hash]; key; key = key->next )
+ {
+ if( !Q_stricmp( key->keyword, keyword ) )
+ return key;
+ }
+
+ return NULL;
+}
+
+/*
+===============
+Item_DataType
+
+Give a numeric representation of which typeData union element this item uses
+===============
+*/
+itemDataType_t Item_DataType( itemDef_t *item )
+{
+ switch( item->type )
+ {
+ default:
+ case ITEM_TYPE_NONE:
+ return TYPE_NONE;
+
+ case ITEM_TYPE_LISTBOX:
+ case ITEM_TYPE_COMBOBOX:
+ return TYPE_LIST;
+
+ case ITEM_TYPE_CYCLE:
+ return TYPE_COMBO;
+
+ case ITEM_TYPE_EDITFIELD:
+ case ITEM_TYPE_NUMERICFIELD:
+ case ITEM_TYPE_SAYFIELD:
+ case ITEM_TYPE_YESNO:
+ case ITEM_TYPE_BIND:
+ case ITEM_TYPE_SLIDER:
+ case ITEM_TYPE_TEXT:
+ return TYPE_EDIT;
+
+ case ITEM_TYPE_MULTI:
+ return TYPE_MULTI;
+
+ case ITEM_TYPE_MODEL:
+ return TYPE_MODEL;
+ }
+}
+
+/*
+===============
+Item_IsEditField
+===============
+*/
+static ID_INLINE qboolean Item_IsEditField( itemDef_t *item )
+{
+ switch( item->type )
+ {
+ case ITEM_TYPE_EDITFIELD:
+ case ITEM_TYPE_NUMERICFIELD:
+ case ITEM_TYPE_SAYFIELD:
+ return qtrue;
+
+ default:
+ return qfalse;
+ }
+}
+
+/*
+===============
+Item_IsListBox
+===============
+*/
+static ID_INLINE qboolean Item_IsListBox( itemDef_t *item )
+{
+ switch( item->type )
+ {
+ case ITEM_TYPE_LISTBOX:
+ case ITEM_TYPE_COMBOBOX:
+ return qtrue;
+
+ default:
+ return qfalse;
+ }
+}
+
+/*
+===============
+Item Keyword Parse functions
+===============
+*/
+
+// name <string>
+qboolean ItemParse_name( itemDef_t *item, int handle )
+{
+ if( !PC_String_Parse( handle, &item->window.name ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+// name <string>
+qboolean ItemParse_focusSound( itemDef_t *item, int handle )
+{
+ const char *temp;
+
+ if( !PC_String_Parse( handle, &temp ) )
+ return qfalse;
+
+ item->focusSound = DC->registerSound( temp, qfalse );
+ return qtrue;
+}
+
+
+// text <string>
+qboolean ItemParse_text( itemDef_t *item, int handle )
+{
+ if( !PC_String_Parse( handle, &item->text ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+// group <string>
+qboolean ItemParse_group( itemDef_t *item, int handle )
+{
+ if( !PC_String_Parse( handle, &item->window.group ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+// asset_model <string>
+qboolean ItemParse_asset_model( itemDef_t *item, int handle )
+{
+ const char *temp;
+
+ if( !PC_String_Parse( handle, &temp ) )
+ return qfalse;
+
+ item->asset = DC->registerModel( temp );
+ item->typeData.model->angle = rand() % 360;
+ return qtrue;
+}
+
+// asset_shader <string>
+qboolean ItemParse_asset_shader( itemDef_t *item, int handle )
+{
+ const char *temp;
+
+ if( !PC_String_Parse( handle, &temp ) )
+ return qfalse;
+
+ item->asset = DC->registerShaderNoMip( temp );
+ return qtrue;
+}
+
+// model_origin <number> <number> <number>
+qboolean ItemParse_model_origin( itemDef_t *item, int handle )
+{
+ return ( PC_Float_Parse( handle, &item->typeData.model->origin[0] ) &&
+ PC_Float_Parse( handle, &item->typeData.model->origin[1] ) &&
+ PC_Float_Parse( handle, &item->typeData.model->origin[2] ) );
+}
+
+// model_fovx <number>
+qboolean ItemParse_model_fovx( itemDef_t *item, int handle )
+{
+ return PC_Float_Parse( handle, &item->typeData.model->fov_x );
+}
+
+// model_fovy <number>
+qboolean ItemParse_model_fovy( itemDef_t *item, int handle )
+{
+ return PC_Float_Parse( handle, &item->typeData.model->fov_y );
+}
+
+// model_rotation <integer>
+qboolean ItemParse_model_rotation( itemDef_t *item, int handle )
+{
+ return PC_Int_Parse( handle, &item->typeData.model->rotationSpeed );
+}
+
+// model_angle <integer>
+qboolean ItemParse_model_angle( itemDef_t *item, int handle )
+{
+ return PC_Int_Parse( handle, &item->typeData.model->angle );
+}
+
+// rect <rectangle>
+qboolean ItemParse_rect( itemDef_t *item, int handle )
+{
+ if( !PC_Rect_Parse( handle, &item->window.rectClient ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+// aspectBias <bias>
+qboolean ItemParse_aspectBias( itemDef_t *item, int handle )
+{
+ if( !PC_Int_Parse( handle, &item->window.aspectBias ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+// style <integer>
+qboolean ItemParse_style( itemDef_t *item, int handle )
+{
+ if( !PC_Int_Parse( handle, &item->window.style ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+// decoration
+qboolean ItemParse_decoration( itemDef_t *item, int handle )
+{
+ item->window.flags |= WINDOW_DECORATION;
+ return qtrue;
+}
+
+// notselectable
+qboolean ItemParse_notselectable( itemDef_t *item, int handle )
+{
+ item->typeData.list->notselectable = qtrue;
+ return qtrue;
+}
+
+// noscrollbar
+qboolean ItemParse_noscrollbar( itemDef_t *item, int handle )
+{
+ item->typeData.list->noscrollbar = qtrue;
+ return qtrue;
+}
+
+// resetonfeederchange
+qboolean ItemParse_resetonfeederchange( itemDef_t *item, int handle )
+{
+ item->typeData.list->resetonfeederchange = qtrue;
+ return qtrue;
+}
+
+// auto wrapped
+qboolean ItemParse_wrapped( itemDef_t *item, int handle )
+{
+ item->window.flags |= WINDOW_WRAPPED;
+ return qtrue;
+}
+
+
+// type <integer>
+qboolean ItemParse_type( itemDef_t *item, int handle )
+{
+ if( item->type != ITEM_TYPE_NONE )
+ {
+ PC_SourceError( handle, "item already has a type" );
+ return qfalse;
+ }
+
+ if( !PC_Int_Parse( handle, &item->type ) )
+ return qfalse;
+
+ if( item->type == ITEM_TYPE_NONE )
+ {
+ PC_SourceError( handle, "type must not be none" );
+ return qfalse;
+ }
+
+ // allocate the relevant type data
+ switch( item->type )
+ {
+ case ITEM_TYPE_LISTBOX:
+ case ITEM_TYPE_COMBOBOX:
+ item->typeData.list = UI_Alloc( sizeof( listBoxDef_t ) );
+ memset( item->typeData.list, 0, sizeof( listBoxDef_t ) );
+ break;
+
+ case ITEM_TYPE_CYCLE:
+ item->typeData.cycle = UI_Alloc( sizeof( cycleDef_t ) );
+ memset( item->typeData.cycle, 0, sizeof( cycleDef_t ) );
+ break;
+
+ case ITEM_TYPE_EDITFIELD:
+ case ITEM_TYPE_SAYFIELD:
+ case ITEM_TYPE_NUMERICFIELD:
+ case ITEM_TYPE_YESNO:
+ case ITEM_TYPE_BIND:
+ case ITEM_TYPE_SLIDER:
+ case ITEM_TYPE_TEXT:
+ item->typeData.edit = UI_Alloc( sizeof( editFieldDef_t ) );
+ memset( item->typeData.edit, 0, sizeof( editFieldDef_t ) );
+
+ if( item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_SAYFIELD )
+ item->typeData.edit->maxPaintChars = MAX_EDITFIELD;
+ break;
+
+ case ITEM_TYPE_MULTI:
+ item->typeData.multi = UI_Alloc( sizeof( multiDef_t ) );
+ memset( item->typeData.multi, 0, sizeof( multiDef_t ) );
+ break;
+
+ case ITEM_TYPE_MODEL:
+ item->typeData.model = UI_Alloc( sizeof( modelDef_t ) );
+ memset( item->typeData.model, 0, sizeof( modelDef_t ) );
+ break;
+
+ default:
+ break;
+ }
+
+ return qtrue;
+}
+
+// elementwidth, used for listbox image elements
+qboolean ItemParse_elementwidth( itemDef_t *item, int handle )
+{
+ return PC_Float_Parse( handle, &item->typeData.list->elementWidth );
+}
+
+// elementheight, used for listbox image elements
+qboolean ItemParse_elementheight( itemDef_t *item, int handle )
+{
+ return PC_Float_Parse( handle, &item->typeData.list->elementHeight );
+}
+
+// dropitems, number of items to drop from a combobox
+qboolean ItemParse_dropitems( itemDef_t *item, int handle )
+{
+ return PC_Int_Parse( handle, &item->typeData.list->dropItems );
+}
+
+// feeder <int>
+qboolean ItemParse_feeder( itemDef_t *item, int handle )
+{
+ if( !PC_Int_Parse( handle, &item->feederID ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+// elementtype, used to specify what type of elements a listbox contains
+// uses textstyle for storage
+qboolean ItemParse_elementtype( itemDef_t *item, int handle )
+{
+ return PC_Int_Parse( handle, &item->typeData.list->elementStyle );
+}
+
+// columns sets a number of columns and an x pos and width per..
+qboolean ItemParse_columns( itemDef_t *item, int handle )
+{
+ int i;
+
+ if( !PC_Int_Parse( handle, &item->typeData.list->numColumns ) )
+ return qfalse;
+
+ if( item->typeData.list->numColumns > MAX_LB_COLUMNS )
+ {
+ PC_SourceError( handle, "exceeded maximum allowed columns (%d)",
+ MAX_LB_COLUMNS );
+ return qfalse;
+ }
+
+ for( i = 0; i < item->typeData.list->numColumns; i++ )
+ {
+ int pos, width, align;
+
+ if( !PC_Int_Parse( handle, &pos ) ||
+ !PC_Int_Parse( handle, &width ) ||
+ !PC_Int_Parse( handle, &align ) )
+ return qfalse;
+
+ item->typeData.list->columnInfo[i].pos = pos;
+ item->typeData.list->columnInfo[i].width = width;
+ item->typeData.list->columnInfo[i].align = align;
+ }
+
+ return qtrue;
+}
+
+qboolean ItemParse_border( itemDef_t *item, int handle )
+{
+ if( !PC_Int_Parse( handle, &item->window.border ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_bordersize( itemDef_t *item, int handle )
+{
+ if( !PC_Float_Parse( handle, &item->window.borderSize ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+// FIXME: why does this require a parameter? visible MENU_FALSE does nothing
+qboolean ItemParse_visible( itemDef_t *item, int handle )
+{
+ int i;
+
+ if( !PC_Int_Parse( handle, &i ) )
+ return qfalse;
+
+ if( i )
+ item->window.flags |= WINDOW_VISIBLE;
+
+ return qtrue;
+}
+
+// ownerdraw <number>, implies ITEM_TYPE_OWNERDRAW
+qboolean ItemParse_ownerdraw( itemDef_t *item, int handle )
+{
+ if( !PC_Int_Parse( handle, &item->window.ownerDraw ) )
+ return qfalse;
+
+ if( item->type != ITEM_TYPE_NONE && item->type != ITEM_TYPE_OWNERDRAW )
+ {
+ PC_SourceError( handle, "ownerdraws cannot have an item type" );
+ return qfalse;
+ }
+
+ item->type = ITEM_TYPE_OWNERDRAW;
+
+ return qtrue;
+}
+
+qboolean ItemParse_align( itemDef_t *item, int handle )
+{
+ if( !PC_Int_Parse( handle, &item->alignment ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_textalign( itemDef_t *item, int handle )
+{
+ if( !PC_Int_Parse( handle, &item->textalignment ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_textvalign( itemDef_t *item, int handle )
+{
+ if( !PC_Int_Parse( handle, &item->textvalignment ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_textalignx( itemDef_t *item, int handle )
+{
+ if( !PC_Float_Parse( handle, &item->textalignx ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_textaligny( itemDef_t *item, int handle )
+{
+ if( !PC_Float_Parse( handle, &item->textaligny ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_textscale( itemDef_t *item, int handle )
+{
+ if( !PC_Float_Parse( handle, &item->textscale ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_textstyle( itemDef_t *item, int handle )
+{
+ if( !PC_Int_Parse( handle, &item->textStyle ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_backcolor( itemDef_t *item, int handle )
+{
+ int i;
+ float f;
+
+ for( i = 0; i < 4; i++ )
+ {
+ if( !PC_Float_Parse( handle, &f ) )
+ return qfalse;
+
+ item->window.backColor[i] = f;
+ }
+
+ return qtrue;
+}
+
+qboolean ItemParse_forecolor( itemDef_t *item, int handle )
+{
+ int i;
+ float f;
+
+ for( i = 0; i < 4; i++ )
+ {
+ if( !PC_Float_Parse( handle, &f ) )
+ return qfalse;
+
+ item->window.foreColor[i] = f;
+ item->window.flags |= WINDOW_FORECOLORSET;
+ }
+
+ return qtrue;
+}
+
+qboolean ItemParse_bordercolor( itemDef_t *item, int handle )
+{
+ int i;
+ float f;
+
+ for( i = 0; i < 4; i++ )
+ {
+ if( !PC_Float_Parse( handle, &f ) )
+ return qfalse;
+
+ item->window.borderColor[i] = f;
+ }
+
+ return qtrue;
+}
+
+qboolean ItemParse_outlinecolor( itemDef_t *item, int handle )
+{
+ if( !PC_Color_Parse( handle, &item->window.outlineColor ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_background( itemDef_t *item, int handle )
+{
+ const char *temp;
+
+ if( !PC_String_Parse( handle, &temp ) )
+ return qfalse;
+
+ item->window.background = DC->registerShaderNoMip( temp );
+ return qtrue;
+}
+
+qboolean ItemParse_cinematic( itemDef_t *item, int handle )
+{
+ if( !PC_String_Parse( handle, &item->window.cinematicName ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_doubleClick( itemDef_t *item, int handle )
+{
+ return ( item->typeData.list &&
+ PC_Script_Parse( handle, &item->typeData.list->doubleClick ) );
+}
+
+qboolean ItemParse_onFocus( itemDef_t *item, int handle )
+{
+ if( !PC_Script_Parse( handle, &item->onFocus ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_leaveFocus( itemDef_t *item, int handle )
+{
+ if( !PC_Script_Parse( handle, &item->leaveFocus ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_mouseEnter( itemDef_t *item, int handle )
+{
+ if( !PC_Script_Parse( handle, &item->mouseEnter ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_mouseExit( itemDef_t *item, int handle )
+{
+ if( !PC_Script_Parse( handle, &item->mouseExit ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_mouseEnterText( itemDef_t *item, int handle )
+{
+ if( !PC_Script_Parse( handle, &item->mouseEnterText ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_mouseExitText( itemDef_t *item, int handle )
+{
+ if( !PC_Script_Parse( handle, &item->mouseExitText ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_onTextEntry( itemDef_t *item, int handle )
+{
+ if( !PC_Script_Parse( handle, &item->onTextEntry ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+
+qboolean ItemParse_onCharEntry( itemDef_t *item, int handle )
+{
+ if( !PC_Script_Parse( handle, &item->onCharEntry ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_action( itemDef_t *item, int handle )
+{
+ if( !PC_Script_Parse( handle, &item->action ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_cvarTest( itemDef_t *item, int handle )
+{
+ if( !PC_String_Parse( handle, &item->cvarTest ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean ItemParse_cvar( itemDef_t *item, int handle )
+{
+ if( !PC_String_Parse( handle, &item->cvar ) )
+ return qfalse;
+
+ if( Item_DataType( item ) == TYPE_EDIT )
+ {
+ item->typeData.edit->minVal = -1;
+ item->typeData.edit->maxVal = -1;
+ item->typeData.edit->defVal = -1;
+ }
+
+ return qtrue;
+}
+
+qboolean ItemParse_maxChars( itemDef_t *item, int handle )
+{
+ return PC_Int_Parse( handle, &item->typeData.edit->maxChars );
+}
+
+qboolean ItemParse_maxPaintChars( itemDef_t *item, int handle )
+{
+ return PC_Int_Parse( handle, &item->typeData.edit->maxPaintChars );
+}
+
+qboolean ItemParse_maxFieldWidth( itemDef_t *item, int handle )
+{
+ if( !PC_Int_Parse( handle, &item->typeData.edit->maxFieldWidth ) )
+ return qfalse;
+
+ if( item->typeData.edit->maxFieldWidth < MIN_FIELD_WIDTH )
+ {
+ PC_SourceError( handle, "max field width must be at least %d",
+ MIN_FIELD_WIDTH );
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+
+
+qboolean ItemParse_cvarFloat( itemDef_t *item, int handle )
+{
+ return ( PC_String_Parse( handle, &item->cvar ) &&
+ PC_Float_Parse( handle, &item->typeData.edit->defVal ) &&
+ PC_Float_Parse( handle, &item->typeData.edit->minVal ) &&
+ PC_Float_Parse( handle, &item->typeData.edit->maxVal ) );
+}
+
+qboolean ItemParse_cvarStrList( itemDef_t *item, int handle )
+{
+ pc_token_t token;
+ multiDef_t *multiPtr;
+ int pass;
+
+ multiPtr = item->typeData.multi;
+ multiPtr->count = 0;
+ multiPtr->strDef = qtrue;
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ if( *token.string != '{' )
+ return qfalse;
+
+ pass = 0;
+
+ while( 1 )
+ {
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ {
+ PC_SourceError( handle, "end of file inside menu item\n" );
+ return qfalse;
+ }
+
+ if( *token.string == '}' )
+ return qtrue;
+
+ if( *token.string == ',' || *token.string == ';' )
+ continue;
+
+ if( pass == 0 )
+ {
+ multiPtr->cvarList[multiPtr->count] = String_Alloc( token.string );
+ pass = 1;
+ }
+ else
+ {
+ multiPtr->cvarStr[multiPtr->count] = String_Alloc( token.string );
+ pass = 0;
+ multiPtr->count++;
+
+ if( multiPtr->count >= MAX_MULTI_CVARS )
+ {
+ PC_SourceError( handle, "cvar string list may not exceed %d cvars",
+ MAX_MULTI_CVARS );
+ return qfalse;
+ }
+ }
+
+ }
+
+ return qfalse;
+}
+
+qboolean ItemParse_cvarFloatList( itemDef_t *item, int handle )
+{
+ pc_token_t token;
+ multiDef_t *multiPtr;
+
+ multiPtr = item->typeData.multi;
+ multiPtr->count = 0;
+ multiPtr->strDef = qfalse;
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ if( *token.string != '{' )
+ return qfalse;
+
+ while( 1 )
+ {
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ {
+ PC_SourceError( handle, "end of file inside menu item\n" );
+ return qfalse;
+ }
+
+ if( *token.string == '}' )
+ return qtrue;
+
+ if( *token.string == ',' || *token.string == ';' )
+ continue;
+
+ multiPtr->cvarList[multiPtr->count] = String_Alloc( token.string );
+
+ if( !PC_Float_Parse( handle, &multiPtr->cvarValue[multiPtr->count] ) )
+ return qfalse;
+
+ multiPtr->count++;
+
+ if( multiPtr->count >= MAX_MULTI_CVARS )
+ {
+ PC_SourceError( handle, "cvar string list may not exceed %d cvars",
+ MAX_MULTI_CVARS );
+ return qfalse;
+ }
+ }
+
+ return qfalse;
+}
+
+
+
+qboolean ItemParse_addColorRange( itemDef_t *item, int handle )
+{
+ colorRangeDef_t color;
+
+ if( PC_Float_Parse( handle, &color.low ) &&
+ PC_Float_Parse( handle, &color.high ) &&
+ PC_Color_Parse( handle, &color.color ) )
+ {
+ if( item->numColors < MAX_COLOR_RANGES )
+ {
+ memcpy( &item->colorRanges[item->numColors], &color, sizeof( color ) );
+ item->numColors++;
+ }
+ else
+ {
+ PC_SourceError( handle, "may not exceed %d color ranges",
+ MAX_COLOR_RANGES );
+ return qfalse;
+ }
+
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+qboolean ItemParse_ownerdrawFlag( itemDef_t *item, int handle )
+{
+ int i;
+
+ if( !PC_Int_Parse( handle, &i ) )
+ return qfalse;
+
+ item->window.ownerDrawFlags |= i;
+ return qtrue;
+}
+
+qboolean ItemParse_enableCvar( itemDef_t *item, int handle )
+{
+ if( PC_Script_Parse( handle, &item->enableCvar ) )
+ {
+ item->cvarFlags = CVAR_ENABLE;
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+qboolean ItemParse_disableCvar( itemDef_t *item, int handle )
+{
+ if( PC_Script_Parse( handle, &item->enableCvar ) )
+ {
+ item->cvarFlags = CVAR_DISABLE;
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+qboolean ItemParse_showCvar( itemDef_t *item, int handle )
+{
+ if( PC_Script_Parse( handle, &item->enableCvar ) )
+ {
+ item->cvarFlags = CVAR_SHOW;
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+qboolean ItemParse_hideCvar( itemDef_t *item, int handle )
+{
+ if( PC_Script_Parse( handle, &item->enableCvar ) )
+ {
+ item->cvarFlags = CVAR_HIDE;
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+
+keywordHash_t itemParseKeywords[] = {
+ {"name", ItemParse_name, TYPE_ANY},
+ {"type", ItemParse_type, TYPE_ANY},
+ {"text", ItemParse_text, TYPE_ANY},
+ {"group", ItemParse_group, TYPE_ANY},
+ {"asset_model", ItemParse_asset_model, TYPE_MODEL},
+ {"asset_shader", ItemParse_asset_shader, TYPE_ANY}, // ?
+ {"model_origin", ItemParse_model_origin, TYPE_MODEL},
+ {"model_fovx", ItemParse_model_fovx, TYPE_MODEL},
+ {"model_fovy", ItemParse_model_fovy, TYPE_MODEL},
+ {"model_rotation", ItemParse_model_rotation, TYPE_MODEL},
+ {"model_angle", ItemParse_model_angle, TYPE_MODEL},
+ {"rect", ItemParse_rect, TYPE_ANY},
+ {"aspectBias", ItemParse_aspectBias, TYPE_ANY},
+ {"style", ItemParse_style, TYPE_ANY},
+ {"decoration", ItemParse_decoration, TYPE_ANY},
+ {"notselectable", ItemParse_notselectable, TYPE_LIST},
+ {"noscrollbar", ItemParse_noscrollbar, TYPE_LIST},
+ {"resetonfeederchange", ItemParse_resetonfeederchange, TYPE_LIST},
+ {"wrapped", ItemParse_wrapped, TYPE_ANY},
+ {"elementwidth", ItemParse_elementwidth, TYPE_LIST},
+ {"elementheight", ItemParse_elementheight, TYPE_LIST},
+ {"dropitems", ItemParse_dropitems, TYPE_LIST},
+ {"feeder", ItemParse_feeder, TYPE_ANY},
+ {"elementtype", ItemParse_elementtype, TYPE_LIST},
+ {"columns", ItemParse_columns, TYPE_LIST},
+ {"border", ItemParse_border, TYPE_ANY},
+ {"bordersize", ItemParse_bordersize, TYPE_ANY},
+ {"visible", ItemParse_visible, TYPE_ANY},
+ {"ownerdraw", ItemParse_ownerdraw, TYPE_ANY},
+ {"align", ItemParse_align, TYPE_ANY},
+ {"textalign", ItemParse_textalign, TYPE_ANY},
+ {"textvalign", ItemParse_textvalign, TYPE_ANY},
+ {"textalignx", ItemParse_textalignx, TYPE_ANY},
+ {"textaligny", ItemParse_textaligny, TYPE_ANY},
+ {"textscale", ItemParse_textscale, TYPE_ANY},
+ {"textstyle", ItemParse_textstyle, TYPE_ANY},
+ {"backcolor", ItemParse_backcolor, TYPE_ANY},
+ {"forecolor", ItemParse_forecolor, TYPE_ANY},
+ {"bordercolor", ItemParse_bordercolor, TYPE_ANY},
+ {"outlinecolor", ItemParse_outlinecolor, TYPE_ANY},
+ {"background", ItemParse_background, TYPE_ANY},
+ {"onFocus", ItemParse_onFocus, TYPE_ANY},
+ {"leaveFocus", ItemParse_leaveFocus, TYPE_ANY},
+ {"mouseEnter", ItemParse_mouseEnter, TYPE_ANY},
+ {"mouseExit", ItemParse_mouseExit, TYPE_ANY},
+ {"mouseEnterText", ItemParse_mouseEnterText, TYPE_ANY},
+ {"mouseExitText", ItemParse_mouseExitText, TYPE_ANY},
+ {"onTextEntry", ItemParse_onTextEntry, TYPE_ANY},
+ {"onCharEntry", ItemParse_onCharEntry, TYPE_ANY},
+ {"action", ItemParse_action, TYPE_ANY},
+ {"cvar", ItemParse_cvar, TYPE_ANY},
+ {"maxChars", ItemParse_maxChars, TYPE_EDIT},
+ {"maxPaintChars", ItemParse_maxPaintChars, TYPE_EDIT},
+ {"maxFieldWidth", ItemParse_maxFieldWidth, TYPE_EDIT},
+ {"focusSound", ItemParse_focusSound, TYPE_ANY},
+ {"cvarFloat", ItemParse_cvarFloat, TYPE_EDIT},
+ {"cvarStrList", ItemParse_cvarStrList, TYPE_MULTI},
+ {"cvarFloatList", ItemParse_cvarFloatList, TYPE_MULTI},
+ {"addColorRange", ItemParse_addColorRange, TYPE_ANY},
+ {"ownerdrawFlag", ItemParse_ownerdrawFlag, TYPE_ANY}, // hm.
+ {"enableCvar", ItemParse_enableCvar, TYPE_ANY},
+ {"cvarTest", ItemParse_cvarTest, TYPE_ANY},
+ {"disableCvar", ItemParse_disableCvar, TYPE_ANY},
+ {"showCvar", ItemParse_showCvar, TYPE_ANY},
+ {"hideCvar", ItemParse_hideCvar, TYPE_ANY},
+ {"cinematic", ItemParse_cinematic, TYPE_ANY},
+ {"doubleclick", ItemParse_doubleClick, TYPE_LIST},
+ {NULL, voidFunction2}
+};
+
+keywordHash_t *itemParseKeywordHash[KEYWORDHASH_SIZE];
+
+/*
+===============
+Item_SetupKeywordHash
+===============
+*/
+void Item_SetupKeywordHash( void )
+{
+ int i;
+
+ memset( itemParseKeywordHash, 0, sizeof( itemParseKeywordHash ) );
+
+ for( i = 0; itemParseKeywords[ i ].keyword; i++ )
+ KeywordHash_Add( itemParseKeywordHash, &itemParseKeywords[ i ] );
+}
+
+/*
+===============
+Item_Parse
+===============
+*/
+qboolean Item_Parse( int handle, itemDef_t *item )
+{
+ pc_token_t token;
+ keywordHash_t *key;
+
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ if( *token.string != '{' )
+ return qfalse;
+
+ while( 1 )
+ {
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ {
+ PC_SourceError( handle, "end of file inside menu item\n" );
+ return qfalse;
+ }
+
+ if( *token.string == '}' )
+ return qtrue;
+
+ key = KeywordHash_Find( itemParseKeywordHash, token.string );
+
+ if( !key )
+ {
+ PC_SourceError( handle, "unknown menu item keyword %s", token.string );
+ continue;
+ }
+
+ // do type-checks
+ if( key->param != TYPE_ANY )
+ {
+ itemDataType_t test = Item_DataType( item );
+
+ if( test != key->param )
+ {
+ if( test == TYPE_NONE )
+ PC_SourceError( handle, "menu item keyword %s requires "
+ "type specification", token.string );
+ else
+ PC_SourceError( handle, "menu item keyword %s is incompatible with "
+ "specified item type", token.string );
+ continue;
+ }
+ }
+
+ if( !key->func( item, handle ) )
+ {
+ PC_SourceError( handle, "couldn't parse menu item keyword %s", token.string );
+ return qfalse;
+ }
+ }
+
+ return qfalse;
+}
+
+
+// Item_InitControls
+// init's special control types
+void Item_InitControls( itemDef_t *item )
+{
+ if( item == NULL )
+ return;
+
+ if( Item_IsListBox( item ) )
+ {
+ item->cursorPos = 0;
+
+ if( item->typeData.list )
+ {
+ item->typeData.list->cursorPos = 0;
+ Item_ListBox_SetStartPos( item, 0 );
+ item->typeData.list->cursorPos = 0;
+ }
+ }
+}
+
+/*
+===============
+Menu Keyword Parse functions
+===============
+*/
+
+qboolean MenuParse_font( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_String_Parse( handle, &menu->font ) )
+ return qfalse;
+
+ if( !DC->Assets.fontRegistered )
+ {
+ DC->registerFont( menu->font, 48, &DC->Assets.textFont );
+ DC->Assets.fontRegistered = qtrue;
+ }
+
+ return qtrue;
+}
+
+qboolean MenuParse_name( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_String_Parse( handle, &menu->window.name ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean MenuParse_fullscreen( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_Int_Parse( handle, ( int* ) & menu->fullScreen ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean MenuParse_rect( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_Rect_Parse( handle, &menu->window.rect ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean MenuParse_aspectBias( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_Int_Parse( handle, &menu->window.aspectBias ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean MenuParse_style( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_Int_Parse( handle, &menu->window.style ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean MenuParse_visible( itemDef_t *item, int handle )
+{
+ int i;
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_Int_Parse( handle, &i ) )
+ return qfalse;
+
+ if( i )
+ menu->window.flags |= WINDOW_VISIBLE;
+
+ return qtrue;
+}
+
+qboolean MenuParse_onOpen( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_Script_Parse( handle, &menu->onOpen ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean MenuParse_onClose( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_Script_Parse( handle, &menu->onClose ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean MenuParse_onESC( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_Script_Parse( handle, &menu->onESC ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+
+
+qboolean MenuParse_border( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_Int_Parse( handle, &menu->window.border ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean MenuParse_borderSize( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_Float_Parse( handle, &menu->window.borderSize ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean MenuParse_backcolor( itemDef_t *item, int handle )
+{
+ int i;
+ float f;
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ for( i = 0; i < 4; i++ )
+ {
+ if( !PC_Float_Parse( handle, &f ) )
+ return qfalse;
+
+ menu->window.backColor[i] = f;
+ }
+
+ return qtrue;
+}
+
+qboolean MenuParse_forecolor( itemDef_t *item, int handle )
+{
+ int i;
+ float f;
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ for( i = 0; i < 4; i++ )
+ {
+ if( !PC_Float_Parse( handle, &f ) )
+ return qfalse;
+
+ menu->window.foreColor[i] = f;
+ menu->window.flags |= WINDOW_FORECOLORSET;
+ }
+
+ return qtrue;
+}
+
+qboolean MenuParse_bordercolor( itemDef_t *item, int handle )
+{
+ int i;
+ float f;
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ for( i = 0; i < 4; i++ )
+ {
+ if( !PC_Float_Parse( handle, &f ) )
+ return qfalse;
+
+ menu->window.borderColor[i] = f;
+ }
+
+ return qtrue;
+}
+
+qboolean MenuParse_focuscolor( itemDef_t *item, int handle )
+{
+ int i;
+ float f;
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ for( i = 0; i < 4; i++ )
+ {
+ if( !PC_Float_Parse( handle, &f ) )
+ return qfalse;
+
+ menu->focusColor[i] = f;
+ }
+
+ return qtrue;
+}
+
+qboolean MenuParse_disablecolor( itemDef_t *item, int handle )
+{
+ int i;
+ float f;
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ for( i = 0; i < 4; i++ )
+ {
+ if( !PC_Float_Parse( handle, &f ) )
+ return qfalse;
+
+ menu->disableColor[i] = f;
+ }
+
+ return qtrue;
+}
+
+
+qboolean MenuParse_outlinecolor( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_Color_Parse( handle, &menu->window.outlineColor ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean MenuParse_background( itemDef_t *item, int handle )
+{
+ const char *buff;
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_String_Parse( handle, &buff ) )
+ return qfalse;
+
+ menu->window.background = DC->registerShaderNoMip( buff );
+ return qtrue;
+}
+
+qboolean MenuParse_cinematic( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_String_Parse( handle, &menu->window.cinematicName ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean MenuParse_ownerdrawFlag( itemDef_t *item, int handle )
+{
+ int i;
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_Int_Parse( handle, &i ) )
+ return qfalse;
+
+ menu->window.ownerDrawFlags |= i;
+ return qtrue;
+}
+
+qboolean MenuParse_ownerdraw( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_Int_Parse( handle, &menu->window.ownerDraw ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+
+// decoration
+qboolean MenuParse_popup( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+ menu->window.flags |= WINDOW_POPUP;
+ return qtrue;
+}
+
+
+qboolean MenuParse_outOfBounds( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ menu->window.flags |= WINDOW_OOB_CLICK;
+ return qtrue;
+}
+
+qboolean MenuParse_soundLoop( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_String_Parse( handle, &menu->soundName ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean MenuParse_fadeClamp( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_Float_Parse( handle, &menu->fadeClamp ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+qboolean MenuParse_fadeAmount( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_Float_Parse( handle, &menu->fadeAmount ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+
+qboolean MenuParse_fadeCycle( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( !PC_Int_Parse( handle, &menu->fadeCycle ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+
+qboolean MenuParse_itemDef( itemDef_t *item, int handle )
+{
+ menuDef_t *menu = ( menuDef_t* )item;
+
+ if( menu->itemCount < MAX_MENUITEMS )
+ {
+ menu->items[menu->itemCount] = UI_Alloc( sizeof( itemDef_t ) );
+ Item_Init( menu->items[menu->itemCount] );
+
+ if( !Item_Parse( handle, menu->items[menu->itemCount] ) )
+ return qfalse;
+
+ Item_InitControls( menu->items[menu->itemCount] );
+ menu->items[menu->itemCount++]->parent = menu;
+ }
+ else
+ {
+ PC_SourceError( handle, "itemDefs per menu may not exceed %d",
+ MAX_MENUITEMS );
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+keywordHash_t menuParseKeywords[] = {
+ {"font", MenuParse_font},
+ {"name", MenuParse_name},
+ {"fullscreen", MenuParse_fullscreen},
+ {"rect", MenuParse_rect},
+ {"aspectBias", MenuParse_aspectBias},
+ {"style", MenuParse_style},
+ {"visible", MenuParse_visible},
+ {"onOpen", MenuParse_onOpen},
+ {"onClose", MenuParse_onClose},
+ {"onESC", MenuParse_onESC},
+ {"border", MenuParse_border},
+ {"borderSize", MenuParse_borderSize},
+ {"backcolor", MenuParse_backcolor},
+ {"forecolor", MenuParse_forecolor},
+ {"bordercolor", MenuParse_bordercolor},
+ {"focuscolor", MenuParse_focuscolor},
+ {"disablecolor", MenuParse_disablecolor},
+ {"outlinecolor", MenuParse_outlinecolor},
+ {"background", MenuParse_background},
+ {"ownerdraw", MenuParse_ownerdraw},
+ {"ownerdrawFlag", MenuParse_ownerdrawFlag},
+ {"outOfBoundsClick", MenuParse_outOfBounds},
+ {"soundLoop", MenuParse_soundLoop},
+ {"itemDef", MenuParse_itemDef},
+ {"cinematic", MenuParse_cinematic},
+ {"popup", MenuParse_popup},
+ {"fadeClamp", MenuParse_fadeClamp},
+ {"fadeCycle", MenuParse_fadeCycle},
+ {"fadeAmount", MenuParse_fadeAmount},
+ {NULL, voidFunction2}
+};
+
+keywordHash_t *menuParseKeywordHash[KEYWORDHASH_SIZE];
+
+/*
+===============
+Menu_SetupKeywordHash
+===============
+*/
+void Menu_SetupKeywordHash( void )
+{
+ int i;
+
+ memset( menuParseKeywordHash, 0, sizeof( menuParseKeywordHash ) );
+
+ for( i = 0; menuParseKeywords[ i ].keyword; i++ )
+ KeywordHash_Add( menuParseKeywordHash, &menuParseKeywords[ i ] );
+}
+
+/*
+===============
+Menu_Parse
+===============
+*/
+qboolean Menu_Parse( int handle, menuDef_t *menu )
+{
+ pc_token_t token;
+ keywordHash_t *key;
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ if( *token.string != '{' )
+ return qfalse;
+
+ while( 1 )
+ {
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ {
+ PC_SourceError( handle, "end of file inside menu\n" );
+ return qfalse;
+ }
+
+ if( *token.string == '}' )
+ return qtrue;
+
+ key = KeywordHash_Find( menuParseKeywordHash, token.string );
+
+ if( !key )
+ {
+ PC_SourceError( handle, "unknown menu keyword %s", token.string );
+ continue;
+ }
+
+ if( !key->func( ( itemDef_t* )menu, handle ) )
+ {
+ PC_SourceError( handle, "couldn't parse menu keyword %s", token.string );
+ return qfalse;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+Menu_New
+===============
+*/
+void Menu_New( int handle )
+{
+ menuDef_t *menu = &Menus[menuCount];
+
+ if( menuCount < MAX_MENUS )
+ {
+ Menu_Init( menu );
+
+ if( Menu_Parse( handle, menu ) )
+ {
+ Menu_PostParse( menu );
+ menuCount++;
+ }
+ }
+}
+
+int Menu_Count( void )
+{
+ return menuCount;
+}
+
+void Menu_UpdateAll( void )
+{
+ int i;
+
+ for( i = 0; i < openMenuCount; i++ )
+ Menu_Update( menuStack[ i ] );
+}
+
+void Menu_PaintAll( void )
+{
+ int i;
+
+ if( g_editingField || g_waitingForKey )
+ DC->setCVar( "ui_hideCursor", "1" );
+ else
+ DC->setCVar( "ui_hideCursor", "0" );
+
+ if( captureFunc != voidFunction )
+ {
+ if( captureFuncExpiry > 0 && DC->realTime > captureFuncExpiry )
+ UI_RemoveCaptureFunc( );
+ else
+ captureFunc( captureData );
+ }
+
+ for( i = 0; i < openMenuCount; i++ )
+ Menu_Paint( menuStack[ i ], qfalse );
+
+ if( DC->getCVarValue( "ui_developer" ) )
+ {
+ vec4_t v = {1, 1, 1, 1};
+ UI_Text_Paint( 5, 25, .5, v, va( "fps: %f", DC->FPS ), 0, 0, 0 );
+ }
+}
+
+void Menu_Reset( void )
+{
+ menuCount = 0;
+}
+
+displayContextDef_t *Display_GetContext( void )
+{
+ return DC;
+}
+
+void *Display_CaptureItem( int x, int y )
+{
+ int i;
+
+ for( i = 0; i < menuCount; i++ )
+ {
+ if( Rect_ContainsPoint( &Menus[i].window.rect, x, y ) )
+ return & Menus[i];
+ }
+
+ return NULL;
+}
+
+
+// FIXME:
+qboolean Display_MouseMove( void *p, float x, float y )
+{
+ int i;
+ menuDef_t *menu = p;
+
+ if( menu == NULL )
+ {
+ menu = Menu_GetFocused();
+
+ if( menu )
+ {
+ if( menu->window.flags & WINDOW_POPUP )
+ {
+ Menu_HandleMouseMove( menu, x, y );
+ return qtrue;
+ }
+ }
+
+ for( i = 0; i < menuCount; i++ )
+ Menu_HandleMouseMove( &Menus[i], x, y );
+ }
+ else
+ {
+ menu->window.rect.x += x;
+ menu->window.rect.y += y;
+ Menu_UpdatePosition( menu );
+ }
+
+ return qtrue;
+
+}
+
+int Display_CursorType( int x, int y )
+{
+ int i;
+
+ for( i = 0; i < menuCount; i++ )
+ {
+ rectDef_t r2;
+ r2.x = Menus[i].window.rect.x - 3;
+ r2.y = Menus[i].window.rect.y - 3;
+ r2.w = r2.h = 7;
+
+ if( Rect_ContainsPoint( &r2, x, y ) )
+ return CURSOR_SIZER;
+ }
+
+ return CURSOR_ARROW;
+}
+
+
+void Display_HandleKey( int key, qboolean down, int x, int y )
+{
+ menuDef_t *menu = Display_CaptureItem( x, y );
+
+ if( menu == NULL )
+ menu = Menu_GetFocused();
+
+ if( menu )
+ Menu_HandleKey( menu, key, down );
+}
+
+static void Window_CacheContents( windowDef_t *window )
+{
+ if( window )
+ {
+ if( window->cinematicName )
+ {
+ int cin = DC->playCinematic( window->cinematicName, 0, 0, 0, 0 );
+ DC->stopCinematic( cin );
+ }
+ }
+}
+
+
+static void Item_CacheContents( itemDef_t *item )
+{
+ if( item )
+ Window_CacheContents( &item->window );
+
+}
+
+static void Menu_CacheContents( menuDef_t *menu )
+{
+ if( menu )
+ {
+ int i;
+ Window_CacheContents( &menu->window );
+
+ for( i = 0; i < menu->itemCount; i++ )
+ Item_CacheContents( menu->items[i] );
+
+ if( menu->soundName && *menu->soundName )
+ DC->registerSound( menu->soundName, qfalse );
+ }
+
+}
+
+void Display_CacheAll( void )
+{
+ int i;
+
+ for( i = 0; i < menuCount; i++ )
+ Menu_CacheContents( &Menus[i] );
+}
+
+
+static qboolean Menu_OverActiveItem( menuDef_t *menu, float x, float y )
+{
+ if( menu && menu->window.flags & ( WINDOW_VISIBLE | WINDOW_FORCED ) )
+ {
+ if( Rect_ContainsPoint( &menu->window.rect, x, y ) )
+ {
+ int i;
+
+ for( i = 0; i < menu->itemCount; i++ )
+ {
+ if( !( menu->items[i]->window.flags & ( WINDOW_VISIBLE | WINDOW_FORCED ) ) )
+ continue;
+
+ if( menu->items[i]->window.flags & WINDOW_DECORATION )
+ continue;
+
+ if( Rect_ContainsPoint( &menu->items[i]->window.rect, x, y ) )
+ {
+ itemDef_t * overItem = menu->items[i];
+
+ if( overItem->type == ITEM_TYPE_TEXT && overItem->text )
+ {
+ if( Rect_ContainsPoint( Item_CorrectedTextRect( overItem ), x, y ) )
+ return qtrue;
+ else
+ continue;
+ }
+ else
+ return qtrue;
+ }
+ }
+
+ }
+ }
+
+ return qfalse;
+}
diff --git a/src/ui/ui_shared.h b/src/ui/ui_shared.h
new file mode 100644
index 0000000..649d51f
--- /dev/null
+++ b/src/ui/ui_shared.h
@@ -0,0 +1,559 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#ifndef __UI_SHARED_H
+#define __UI_SHARED_H
+
+
+#include "../qcommon/q_shared.h"
+#include "../renderer/tr_types.h"
+#include "../client/keycodes.h"
+
+#include "../../assets/ui/menudef.h"
+
+#define MAX_MENUNAME 32
+#define MAX_ITEMTEXT 64
+#define MAX_ITEMACTION 64
+#define MAX_MENUDEFFILE 4096
+#define MAX_MENUFILE 32768
+#define MAX_MENUS 256
+#define MAX_MENUITEMS 128
+#define MAX_COLOR_RANGES 10
+#define MAX_OPEN_MENUS 16
+
+#define WINDOW_MOUSEOVER 0x00000001 // mouse is over it, non exclusive
+#define WINDOW_HASFOCUS 0x00000002 // has cursor focus, exclusive
+#define WINDOW_VISIBLE 0x00000004 // is visible
+#define WINDOW_GREY 0x00000008 // is visible but grey ( non-active )
+#define WINDOW_DECORATION 0x00000010 // for decoration only, no mouse, keyboard, etc..
+#define WINDOW_FADINGOUT 0x00000020 // fading out, non-active
+#define WINDOW_FADINGIN 0x00000040 // fading in
+#define WINDOW_MOUSEOVERTEXT 0x00000080 // mouse is over it, non exclusive
+#define WINDOW_INTRANSITION 0x00000100 // window is in transition
+#define WINDOW_FORECOLORSET 0x00000200 // forecolor was explicitly set ( used to color alpha images or not )
+#define WINDOW_HORIZONTAL 0x00000400 // for list boxes and sliders, vertical is default this is set of horizontal
+#define WINDOW_LB_UPARROW 0x00000800 // mouse is over up arrow
+#define WINDOW_LB_DOWNARROW 0x00001000 // mouse is over down arrow
+#define WINDOW_LB_THUMB 0x00002000 // mouse is over thumb
+#define WINDOW_LB_PGUP 0x00004000 // mouse is over page up
+#define WINDOW_LB_PGDN 0x00008000 // mouse is over page down
+#define WINDOW_ORBITING 0x00010000 // item is in orbit
+#define WINDOW_OOB_CLICK 0x00020000 // close on out of bounds click
+#define WINDOW_WRAPPED 0x00080000 // wrap text
+#define WINDOW_FORCED 0x00100000 // forced open
+#define WINDOW_POPUP 0x00200000 // popup
+#define WINDOW_BACKCOLORSET 0x00400000 // backcolor was explicitly set
+#define WINDOW_TIMEDVISIBLE 0x00800000 // visibility timing ( NOT implemented )
+
+
+// CGAME cursor type bits
+#define CURSOR_NONE 0x00000001
+#define CURSOR_ARROW 0x00000002
+#define CURSOR_SIZER 0x00000004
+
+#ifdef CGAME
+#define STRING_POOL_SIZE 128*1024
+#else
+#define STRING_POOL_SIZE 384*1024
+#endif
+#define MAX_STRING_HANDLES 4096
+
+#define MAX_SCRIPT_ARGS 12
+#define MAX_EDITFIELD 256
+#define ITEM_VALUE_OFFSET 8
+
+#define ART_FX_BASE "menu/art/fx_base"
+#define ART_FX_BLUE "menu/art/fx_blue"
+#define ART_FX_CYAN "menu/art/fx_cyan"
+#define ART_FX_GREEN "menu/art/fx_grn"
+#define ART_FX_RED "menu/art/fx_red"
+#define ART_FX_TEAL "menu/art/fx_teal"
+#define ART_FX_WHITE "menu/art/fx_white"
+#define ART_FX_YELLOW "menu/art/fx_yel"
+
+#define ASSET_GRADIENTBAR "ui/assets/gradientbar2.tga"
+#define ASSET_SCROLLBAR "ui/assets/scrollbar.tga"
+#define ASSET_SCROLLBAR_ARROWDOWN "ui/assets/scrollbar_arrow_dwn_a.tga"
+#define ASSET_SCROLLBAR_ARROWUP "ui/assets/scrollbar_arrow_up_a.tga"
+#define ASSET_SCROLLBAR_ARROWLEFT "ui/assets/scrollbar_arrow_left.tga"
+#define ASSET_SCROLLBAR_ARROWRIGHT "ui/assets/scrollbar_arrow_right.tga"
+#define ASSET_SCROLL_THUMB "ui/assets/scrollbar_thumb.tga"
+#define ASSET_SLIDER_BAR "ui/assets/slider2.tga"
+#define ASSET_SLIDER_THUMB "ui/assets/sliderbutt_1.tga"
+
+#define SCROLLBAR_ARROW_SIZE 16.0f
+#define SCROLLBAR_ARROW_WIDTH (SCROLLBAR_ARROW_SIZE*DC->aspectScale)
+#define SCROLLBAR_ARROW_HEIGHT SCROLLBAR_ARROW_SIZE
+#define SCROLLBAR_SLIDER_X(_item) (_item->window.rect.x+_item->window.rect.w- \
+ SCROLLBAR_ARROW_WIDTH-DC->aspectScale)
+#define SCROLLBAR_SLIDER_Y(_item) (SCROLLBAR_Y(_item)+SCROLLBAR_ARROW_HEIGHT)
+#define SCROLLBAR_SLIDER_HEIGHT(_item) (_item->window.rect.h-(SCROLLBAR_ARROW_HEIGHT*2.0f)-2.0f)
+#define SCROLLBAR_X(_item) (_item->window.rect.x+DC->aspectScale)
+#define SCROLLBAR_Y(_item) (_item->window.rect.y+1.0f)
+#define SCROLLBAR_W(_item) (SCROLLBAR_SLIDER_X(_item)-SCROLLBAR_X(_item))
+#define SCROLLBAR_H(_item) (_item->window.rect.h-2.0f)
+
+#define SLIDER_WIDTH (96.0f*DC->aspectScale)
+#define SLIDER_HEIGHT 16.0f
+#define SLIDER_THUMB_WIDTH (12.0f*DC->aspectScale)
+#define SLIDER_THUMB_HEIGHT 20.0f
+#define NUM_CROSSHAIRS 10
+
+typedef struct
+{
+ const char *command;
+ const char *args[MAX_SCRIPT_ARGS];
+}
+scriptDef_t;
+
+
+typedef struct
+{
+ float x; // horiz position
+ float y; // vert position
+ float w; // width
+ float h; // height;
+}
+rectDef_t;
+
+typedef rectDef_t Rectangle;
+
+// FIXME: do something to separate text vs window stuff
+
+typedef struct
+{
+ Rectangle rect; // client coord rectangle
+ int aspectBias; // direction in which to aspect compensate
+ Rectangle rectClient; // screen coord rectangle
+ const char *name; //
+ const char *group; // if it belongs to a group
+ const char *cinematicName; // cinematic name
+ int cinematic; // cinematic handle
+ int style; //
+ int border; //
+ int ownerDraw; // ownerDraw style
+ int ownerDrawFlags; // show flags for ownerdraw items
+ float borderSize; //
+ int flags; // visible, focus, mouseover, cursor
+ Rectangle rectEffects; // for various effects
+ Rectangle rectEffects2; // for various effects
+ int offsetTime; // time based value for various effects
+ int nextTime; // time next effect should cycle
+ vec4_t foreColor; // text color
+ vec4_t backColor; // border color
+ vec4_t borderColor; // border color
+ vec4_t outlineColor; // border color
+ qhandle_t background; // background asset
+}
+windowDef_t;
+
+typedef windowDef_t Window;
+
+typedef struct
+{
+ vec4_t color;
+ float low;
+ float high;
+}
+colorRangeDef_t;
+
+// FIXME: combine flags into bitfields to save space
+// FIXME: consolidate all of the common stuff in one structure for menus and items
+// THINKABOUTME: is there any compelling reason not to have items contain items
+// and do away with a menu per say.. major issue is not being able to dynamically allocate
+// and destroy stuff.. Another point to consider is adding an alloc free call for vm's and have
+// the engine just allocate the pool for it based on a cvar
+// many of the vars are re-used for different item types, as such they are not always named appropriately
+// the benefits of c++ in DOOM will greatly help crap like this
+// FIXME: need to put a type ptr that points to specific type info per type
+//
+#define MAX_LB_COLUMNS 16
+
+typedef struct columnInfo_s
+{
+ int pos;
+ int width;
+ int align;
+}
+columnInfo_t;
+
+typedef struct listBoxDef_s
+{
+ int startPos;
+ int endPos;
+ int cursorPos;
+
+ float elementWidth;
+ float elementHeight;
+ int elementStyle;
+ int dropItems;
+
+ int numColumns;
+ columnInfo_t columnInfo[MAX_LB_COLUMNS];
+
+ const char *doubleClick;
+
+ qboolean notselectable;
+ qboolean noscrollbar;
+
+ qboolean resetonfeederchange;
+ int lastFeederCount;
+}
+listBoxDef_t;
+
+typedef struct cycleDef_s
+{
+ int cursorPos;
+}
+cycleDef_t;
+
+typedef struct editFieldDef_s
+{
+ float minVal; // edit field limits
+ float maxVal; //
+ float defVal; //
+ float range; //
+ int maxChars; // for edit fields
+ int maxPaintChars; // for edit fields
+ int maxFieldWidth; // for edit fields
+ int paintOffset; //
+}
+editFieldDef_t;
+
+#define MAX_MULTI_CVARS 32
+
+typedef struct multiDef_s
+{
+ const char *cvarList[MAX_MULTI_CVARS];
+ const char *cvarStr[MAX_MULTI_CVARS];
+ float cvarValue[MAX_MULTI_CVARS];
+ int count;
+ qboolean strDef;
+}
+multiDef_t;
+
+typedef struct modelDef_s
+{
+ int angle;
+ vec3_t origin;
+ float fov_x;
+ float fov_y;
+ int rotationSpeed;
+}
+modelDef_t;
+
+#define CVAR_ENABLE 0x00000001
+#define CVAR_DISABLE 0x00000002
+#define CVAR_SHOW 0x00000004
+#define CVAR_HIDE 0x00000008
+
+typedef enum
+{
+ TYPE_ANY = -1,
+ TYPE_NONE,
+ TYPE_LIST,
+ TYPE_EDIT,
+ TYPE_MULTI,
+ TYPE_COMBO,
+ TYPE_MODEL
+} itemDataType_t;
+
+typedef struct itemDef_s
+{
+ Window window; // common positional, border, style, layout info
+ Rectangle textRect; // rectangle the text ( if any ) consumes
+ int type; // text, button, radiobutton, checkbox, textfield, listbox, combo
+ int alignment; // left center right
+ int textalignment; // ( optional ) alignment for text within rect based on text width
+ int textvalignment; // ( optional ) alignment for text within rect based on text width
+ float textalignx; // ( optional ) text alignment x coord
+ float textaligny; // ( optional ) text alignment x coord
+ float textscale; // scale percentage from 72pts
+ int textStyle; // ( optional ) style, normal and shadowed are it for now
+ const char *text; // display text
+ void *parent; // menu owner
+ qhandle_t asset; // handle to asset
+ const char *mouseEnterText; // mouse enter script
+ const char *mouseExitText; // mouse exit script
+ const char *mouseEnter; // mouse enter script
+ const char *mouseExit; // mouse exit script
+ const char *action; // select script
+ const char *onFocus; // select script
+ const char *leaveFocus; // select script
+ const char *onTextEntry; // called when text entered
+ const char *onCharEntry; // called when text entered
+ const char *cvar; // associated cvar
+ const char *cvarTest; // associated cvar for enable actions
+ const char *enableCvar; // enable, disable, show, or hide based on value, this can contain a list
+ int cvarFlags; // what type of action to take on cvarenables
+ sfxHandle_t focusSound;
+ int numColors; // number of color ranges
+ colorRangeDef_t colorRanges[MAX_COLOR_RANGES];
+ int feederID; // where to get data for this item
+ int cursorPos; // cursor position in characters
+ union
+ {
+ void *data;
+ listBoxDef_t *list;
+ editFieldDef_t *edit;
+ multiDef_t *multi;
+ cycleDef_t *cycle;
+ modelDef_t *model;
+ } typeData; // type specific data pointers
+}
+itemDef_t;
+
+typedef struct
+{
+ Window window;
+ const char *font; // font
+ qboolean fullScreen; // covers entire screen
+ int itemCount; // number of items;
+ int fontIndex; //
+ int cursorItem; // which item as the cursor
+ int fadeCycle; //
+ float fadeClamp; //
+ float fadeAmount; //
+ const char *onOpen; // run when the menu is first opened
+ const char *onClose; // run when the menu is closed
+ const char *onESC; // run when the menu is closed
+ const char *soundName; // background loop sound for menu
+
+ vec4_t focusColor; // focus color for items
+ vec4_t disableColor; // focus color for items
+ itemDef_t *items[MAX_MENUITEMS]; // items this menu contains
+}
+menuDef_t;
+
+typedef struct
+{
+ const char *fontStr;
+ const char *cursorStr;
+ const char *gradientStr;
+ fontInfo_t textFont;
+ fontInfo_t smallFont;
+ fontInfo_t bigFont;
+ qhandle_t cursor;
+ qhandle_t gradientBar;
+ qhandle_t scrollBarArrowUp;
+ qhandle_t scrollBarArrowDown;
+ qhandle_t scrollBarArrowLeft;
+ qhandle_t scrollBarArrowRight;
+ qhandle_t scrollBar;
+ qhandle_t scrollBarThumb;
+ qhandle_t buttonMiddle;
+ qhandle_t buttonInside;
+ qhandle_t solidBox;
+ qhandle_t sliderBar;
+ qhandle_t sliderThumb;
+ sfxHandle_t menuEnterSound;
+ sfxHandle_t menuExitSound;
+ sfxHandle_t menuBuzzSound;
+ sfxHandle_t itemFocusSound;
+ float fadeClamp;
+ int fadeCycle;
+ float fadeAmount;
+ float shadowX;
+ float shadowY;
+ vec4_t shadowColor;
+ float shadowFadeClamp;
+ qboolean fontRegistered;
+ emoticon_t emoticons[ MAX_EMOTICONS ];
+ int emoticonCount;
+}
+cachedAssets_t;
+
+typedef struct
+{
+ const char *name;
+ void ( *handler ) ( itemDef_t *item, char** args );
+}
+commandDef_t;
+
+typedef struct
+{
+ qhandle_t ( *registerShaderNoMip ) ( const char *p );
+ void ( *setColor ) ( const vec4_t v );
+ void ( *drawHandlePic ) ( float x, float y, float w, float h, qhandle_t asset );
+ void ( *drawStretchPic ) ( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader );
+ qhandle_t ( *registerModel ) ( const char *p );
+ void ( *modelBounds ) ( qhandle_t model, vec3_t min, vec3_t max );
+ void ( *fillRect ) ( float x, float y, float w, float h, const vec4_t color );
+ void ( *drawRect ) ( float x, float y, float w, float h, float size, const vec4_t color );
+ void ( *drawSides ) ( float x, float y, float w, float h, float size );
+ void ( *drawTopBottom ) ( float x, float y, float w, float h, float size );
+ void ( *clearScene ) ( void );
+ void ( *addRefEntityToScene ) ( const refEntity_t *re );
+ void ( *renderScene ) ( const refdef_t *fd );
+ void ( *registerFont ) ( const char *pFontname, int pointSize, fontInfo_t *font );
+ void ( *ownerDrawItem ) ( float x, float y, float w, float h, float text_x,
+ float text_y, int ownerDraw, int ownerDrawFlags,
+ int align, int textalign, int textvalign,
+ float borderSize, float scale, vec4_t foreColor,
+ vec4_t backColor, qhandle_t shader, int textStyle );
+ float ( *getValue ) ( int ownerDraw );
+ qboolean ( *ownerDrawVisible ) ( int flags );
+ void ( *runScript )( char **p );
+ void ( *getCVarString )( const char *cvar, char *buffer, int bufsize );
+ float ( *getCVarValue )( const char *cvar );
+ void ( *setCVar )( const char *cvar, const char *value );
+ void ( *drawTextWithCursor )( float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style );
+ void ( *setOverstrikeMode )( qboolean b );
+ qboolean ( *getOverstrikeMode )( void );
+ void ( *startLocalSound )( sfxHandle_t sfx, int channelNum );
+ qboolean ( *ownerDrawHandleKey )( int ownerDraw, int key );
+ int ( *feederCount )( int feederID );
+ const char *( *feederItemText )( int feederID, int index, int column, qhandle_t *handle );
+ qhandle_t ( *feederItemImage )( int feederID, int index );
+ void ( *feederSelection )( int feederID, int index );
+ int ( *feederInitialise )( int feederID );
+ void ( *keynumToStringBuf )( int keynum, char *buf, int buflen );
+ void ( *getBindingBuf )( int keynum, char *buf, int buflen );
+ void ( *setBinding )( int keynum, const char *binding );
+ void ( *executeText )( int exec_when, const char *text );
+ void ( *Error )( int level, const char *error, ... );
+ void ( *Print )( const char *msg, ... );
+ void ( *Pause )( qboolean b );
+ int ( *ownerDrawWidth )( int ownerDraw, float scale );
+ const char *( *ownerDrawText )( int ownerDraw );
+ sfxHandle_t ( *registerSound )( const char *name, qboolean compressed );
+ void ( *startBackgroundTrack )( const char *intro, const char *loop );
+ void ( *stopBackgroundTrack )( void );
+ int ( *playCinematic )( const char *name, float x, float y, float w, float h );
+ void ( *stopCinematic )( int handle );
+ void ( *drawCinematic )( int handle, float x, float y, float w, float h );
+ void ( *runCinematicFrame )( int handle );
+
+ float yscale;
+ float xscale;
+ float aspectScale;
+ int realTime;
+ int frameTime;
+ float cursorx;
+ float cursory;
+ float smallFontScale;
+ float bigFontScale;
+ qboolean debug;
+
+ cachedAssets_t Assets;
+
+ glconfig_t glconfig;
+ qhandle_t whiteShader;
+ qhandle_t gradientImage;
+ qhandle_t cursor;
+ float FPS;
+
+}
+displayContextDef_t;
+
+const char *String_Alloc( const char *p );
+void String_Init( void );
+void String_Report( void );
+void Init_Display( displayContextDef_t *dc );
+void Display_ExpandMacros( char * buff );
+void Menu_Init( menuDef_t *menu );
+void Item_Init( itemDef_t *item );
+void Menu_PostParse( menuDef_t *menu );
+menuDef_t *Menu_GetFocused( void );
+void Menu_HandleKey( menuDef_t *menu, int key, qboolean down );
+void Menu_HandleMouseMove( menuDef_t *menu, float x, float y );
+void Menu_ScrollFeeder( menuDef_t *menu, int feeder, qboolean down );
+qboolean Float_Parse( char **p, float *f );
+qboolean Color_Parse( char **p, vec4_t *c );
+qboolean Int_Parse( char **p, int *i );
+qboolean Rect_Parse( char **p, rectDef_t *r );
+qboolean String_Parse( char **p, const char **out );
+qboolean Script_Parse( char **p, const char **out );
+qboolean PC_Float_Parse( int handle, float *f );
+qboolean PC_Color_Parse( int handle, vec4_t *c );
+qboolean PC_Int_Parse( int handle, int *i );
+qboolean PC_Rect_Parse( int handle, rectDef_t *r );
+qboolean PC_String_Parse( int handle, const char **out );
+qboolean PC_Script_Parse( int handle, const char **out );
+int Menu_Count( void );
+void Menu_New( int handle );
+void Menu_UpdateAll( void );
+void Menu_PaintAll( void );
+menuDef_t *Menus_ActivateByName( const char *p );
+menuDef_t *Menus_ReplaceActiveByName( const char *p );
+void Menu_Reset( void );
+qboolean Menus_AnyFullScreenVisible( void );
+void Menus_Activate( menuDef_t *menu );
+qboolean Menus_ReplaceActive( menuDef_t *menu );
+
+displayContextDef_t *Display_GetContext( void );
+void *Display_CaptureItem( int x, int y );
+qboolean Display_MouseMove( void *p, float x, float y );
+int Display_CursorType( int x, int y );
+qboolean Display_KeyBindPending( void );
+menuDef_t *Menus_FindByName( const char *p );
+void Menus_CloseByName( const char *p );
+void Display_HandleKey( int key, qboolean down, int x, int y );
+void LerpColor( vec4_t a, vec4_t b, vec4_t c, float t );
+void Menus_CloseAll( void );
+void Menu_Update( menuDef_t *menu );
+void Menu_Paint( menuDef_t *menu, qboolean forcePaint );
+void Menu_SetFeederSelection( menuDef_t *menu, int feeder, int index, const char *name );
+void Display_CacheAll( void );
+
+typedef void ( CaptureFunc ) ( void *p );
+
+void UI_InstallCaptureFunc( CaptureFunc *f, void *data, int timeout );
+void UI_RemoveCaptureFunc( void );
+
+void *UI_Alloc( int size );
+void UI_InitMemory( void );
+qboolean UI_OutOfMemory( void );
+
+void Controls_GetConfig( void );
+void Controls_SetConfig( qboolean restart );
+void Controls_SetDefaults( void );
+
+void trap_R_SetClipRegion( const float *region );
+
+//for cg_draw.c
+void Item_Text_Wrapped_Paint( itemDef_t *item );
+const char *Item_Text_Wrap( const char *text, float scale, float width );
+void UI_DrawTextBlock( rectDef_t *rect, float text_x, float text_y, vec4_t color,
+ float scale, int textalign, int textvalign,
+ int textStyle, const char *text );
+void UI_Text_Paint( float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style );
+void UI_Text_Paint_Limit( float *maxX, float x, float y, float scale,
+ vec4_t color, const char* text, float adjust, int limit );
+float UI_Text_Width( const char *text, float scale );
+float UI_Text_Height( const char *text, float scale );
+float UI_Text_EmWidth( float scale );
+float UI_Text_EmHeight( float scale );
+qboolean UI_Text_IsEmoticon( const char *s, qboolean *escaped, int *length, qhandle_t *h, int *width );
+void UI_EscapeEmoticons( char *dest, const char *src, int destsize );
+
+int trap_Parse_AddGlobalDefine( char *define );
+int trap_Parse_LoadSource( const char *filename );
+int trap_Parse_FreeSource( int handle );
+int trap_Parse_ReadToken( int handle, pc_token_t *pc_token );
+int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line );
+
+void BindingFromName( const char *cvar );
+extern char g_nameBind1[ 32 ];
+extern char g_nameBind2[ 32 ];
+#endif
diff --git a/src/ui/ui_syscalls.asm b/src/ui/ui_syscalls.asm
new file mode 100644
index 0000000..1e797a9
--- /dev/null
+++ b/src/ui/ui_syscalls.asm
@@ -0,0 +1,102 @@
+code
+
+equ trap_Error -1
+equ trap_Print -2
+equ trap_Milliseconds -3
+equ trap_Cvar_Set -4
+equ trap_Cvar_VariableValue -5
+equ trap_Cvar_VariableStringBuffer -6
+equ trap_Cvar_SetValue -7
+equ trap_Cvar_Reset -8
+equ trap_Cvar_Create -9
+equ trap_Cvar_InfoStringBuffer -10
+equ trap_Argc -11
+equ trap_Argv -12
+equ trap_Cmd_ExecuteText -13
+equ trap_FS_FOpenFile -14
+equ trap_FS_Read -15
+equ trap_FS_Write -16
+equ trap_FS_FCloseFile -17
+equ trap_FS_GetFileList -18
+equ trap_R_RegisterModel -19
+equ trap_R_RegisterSkin -20
+equ trap_R_RegisterShaderNoMip -21
+equ trap_R_ClearScene -22
+equ trap_R_AddRefEntityToScene -23
+equ trap_R_AddPolyToScene -24
+equ trap_R_AddLightToScene -25
+equ trap_R_RenderScene -26
+equ trap_R_SetColor -27
+equ trap_R_SetClipRegion -28
+equ trap_R_DrawStretchPic -29
+equ trap_UpdateScreen -30
+equ trap_CM_LerpTag -31
+equ trap_CM_LoadModel -32
+equ trap_S_RegisterSound -33
+equ trap_S_StartLocalSound -34
+equ trap_Key_KeynumToStringBuf -35
+equ trap_Key_GetBindingBuf -36
+equ trap_Key_SetBinding -37
+equ trap_Key_IsDown -38
+equ trap_Key_GetOverstrikeMode -39
+equ trap_Key_SetOverstrikeMode -40
+equ trap_Key_ClearStates -41
+equ trap_Key_GetCatcher -42
+equ trap_Key_SetCatcher -43
+equ trap_GetClipboardData -44
+equ trap_GetGlconfig -45
+equ trap_GetClientState -46
+equ trap_GetConfigString -47
+equ trap_LAN_GetPingQueueCount -48
+equ trap_LAN_ClearPing -49
+equ trap_LAN_GetPing -50
+equ trap_LAN_GetPingInfo -51
+equ trap_Cvar_Register -52
+equ trap_Cvar_Update -53
+equ trap_MemoryRemaining -54
+equ trap_R_RegisterFont -55
+equ trap_R_ModelBounds -56
+equ trap_S_StopBackgroundTrack -57
+equ trap_S_StartBackgroundTrack -58
+equ trap_RealTime -59
+equ trap_LAN_GetServerCount -60
+equ trap_LAN_GetServerAddressString -61
+equ trap_LAN_GetServerInfo -62
+equ trap_LAN_MarkServerVisible -63
+equ trap_LAN_UpdateVisiblePings -64
+equ trap_LAN_ResetPings -65
+equ trap_LAN_LoadCachedServers -66
+equ trap_LAN_SaveCachedServers -67
+equ trap_LAN_AddServer -68
+equ trap_LAN_RemoveServer -69
+equ trap_CIN_PlayCinematic -70
+equ trap_CIN_StopCinematic -71
+equ trap_CIN_RunCinematic -72
+equ trap_CIN_DrawCinematic -73
+equ trap_CIN_SetExtents -74
+equ trap_R_RemapShader -75
+equ trap_LAN_ServerStatus -76
+equ trap_LAN_GetServerPing -77
+equ trap_LAN_ServerIsVisible -78
+equ trap_LAN_CompareServers -79
+equ trap_FS_Seek -80
+equ trap_SetPbClStatus -81
+
+equ trap_Parse_AddGlobalDefine -82
+equ trap_Parse_LoadSource -83
+equ trap_Parse_FreeSource -84
+equ trap_Parse_ReadToken -85
+equ trap_Parse_SourceFileAndLine -86
+
+equ trap_GetNews -87
+
+equ memset -101
+equ memcpy -102
+equ strncpy -103
+equ sin -104
+equ cos -105
+equ atan2 -106
+equ sqrt -107
+equ floor -108
+equ ceil -109
+
diff --git a/src/ui/ui_syscalls.c b/src/ui/ui_syscalls.c
new file mode 100644
index 0000000..2187f18
--- /dev/null
+++ b/src/ui/ui_syscalls.c
@@ -0,0 +1,483 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "ui_local.h"
+
+// this file is only included when building a dll
+// syscalls.asm is included instead when building a qvm
+
+static intptr_t ( QDECL *syscall )( intptr_t arg, ... ) = ( intptr_t ( QDECL * )( intptr_t, ... ) ) - 1;
+
+Q_EXPORT void dllEntry( intptr_t ( QDECL *syscallptr )( intptr_t arg, ... ) )
+{
+ syscall = syscallptr;
+}
+
+int PASSFLOAT( float x )
+{
+ float floatTemp;
+ floatTemp = x;
+ return *( int * ) & floatTemp;
+}
+
+void trap_Print( const char *string )
+{
+ syscall( UI_PRINT, string );
+}
+
+void trap_Error( const char *string )
+{
+ syscall( UI_ERROR, string );
+}
+
+int trap_Milliseconds( void )
+{
+ return syscall( UI_MILLISECONDS );
+}
+
+void trap_Cvar_Register( vmCvar_t *cvar, const char *var_name, const char *value, int flags )
+{
+ syscall( UI_CVAR_REGISTER, cvar, var_name, value, flags );
+}
+
+void trap_Cvar_Update( vmCvar_t *cvar )
+{
+ syscall( UI_CVAR_UPDATE, cvar );
+}
+
+void trap_Cvar_Set( const char *var_name, const char *value )
+{
+ syscall( UI_CVAR_SET, var_name, value );
+}
+
+float trap_Cvar_VariableValue( const char *var_name )
+{
+ int temp;
+ temp = syscall( UI_CVAR_VARIABLEVALUE, var_name );
+ return ( *( float* ) & temp );
+}
+
+void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize )
+{
+ syscall( UI_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize );
+}
+
+void trap_Cvar_SetValue( const char *var_name, float value )
+{
+ syscall( UI_CVAR_SETVALUE, var_name, PASSFLOAT( value ) );
+}
+
+void trap_Cvar_Reset( const char *name )
+{
+ syscall( UI_CVAR_RESET, name );
+}
+
+void trap_Cvar_Create( const char *var_name, const char *var_value, int flags )
+{
+ syscall( UI_CVAR_CREATE, var_name, var_value, flags );
+}
+
+void trap_Cvar_InfoStringBuffer( int bit, char *buffer, int bufsize )
+{
+ syscall( UI_CVAR_INFOSTRINGBUFFER, bit, buffer, bufsize );
+}
+
+int trap_Argc( void )
+{
+ return syscall( UI_ARGC );
+}
+
+void trap_Argv( int n, char *buffer, int bufferLength )
+{
+ syscall( UI_ARGV, n, buffer, bufferLength );
+}
+
+void trap_Cmd_ExecuteText( int exec_when, const char *text )
+{
+ syscall( UI_CMD_EXECUTETEXT, exec_when, text );
+}
+
+int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode )
+{
+ return syscall( UI_FS_FOPENFILE, qpath, f, mode );
+}
+
+void trap_FS_Read( void *buffer, int len, fileHandle_t f )
+{
+ syscall( UI_FS_READ, buffer, len, f );
+}
+
+void trap_FS_Write( const void *buffer, int len, fileHandle_t f )
+{
+ syscall( UI_FS_WRITE, buffer, len, f );
+}
+
+void trap_FS_FCloseFile( fileHandle_t f )
+{
+ syscall( UI_FS_FCLOSEFILE, f );
+}
+
+int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize )
+{
+ return syscall( UI_FS_GETFILELIST, path, extension, listbuf, bufsize );
+}
+
+int trap_FS_Seek( fileHandle_t f, long offset, int origin )
+{
+ return syscall( UI_FS_SEEK, f, offset, origin );
+}
+
+qhandle_t trap_R_RegisterModel( const char *name )
+{
+ return syscall( UI_R_REGISTERMODEL, name );
+}
+
+qhandle_t trap_R_RegisterSkin( const char *name )
+{
+ return syscall( UI_R_REGISTERSKIN, name );
+}
+
+void trap_R_RegisterFont( const char *fontName, int pointSize, fontInfo_t *font )
+{
+ syscall( UI_R_REGISTERFONT, fontName, pointSize, font );
+}
+
+qhandle_t trap_R_RegisterShaderNoMip( const char *name )
+{
+ return syscall( UI_R_REGISTERSHADERNOMIP, name );
+}
+
+void trap_R_ClearScene( void )
+{
+ syscall( UI_R_CLEARSCENE );
+}
+
+void trap_R_AddRefEntityToScene( const refEntity_t *re )
+{
+ syscall( UI_R_ADDREFENTITYTOSCENE, re );
+}
+
+void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts )
+{
+ syscall( UI_R_ADDPOLYTOSCENE, hShader, numVerts, verts );
+}
+
+void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b )
+{
+ syscall( UI_R_ADDLIGHTTOSCENE, org, PASSFLOAT( intensity ), PASSFLOAT( r ), PASSFLOAT( g ), PASSFLOAT( b ) );
+}
+
+void trap_R_RenderScene( const refdef_t *fd )
+{
+ syscall( UI_R_RENDERSCENE, fd );
+}
+
+void trap_R_SetColor( const float *rgba )
+{
+ syscall( UI_R_SETCOLOR, rgba );
+}
+
+void trap_R_SetClipRegion( const float *region )
+{
+ syscall( UI_R_SETCLIPREGION, region );
+}
+
+void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader )
+{
+ syscall( UI_R_DRAWSTRETCHPIC, PASSFLOAT( x ), PASSFLOAT( y ), PASSFLOAT( w ), PASSFLOAT( h ), PASSFLOAT( s1 ), PASSFLOAT( t1 ), PASSFLOAT( s2 ), PASSFLOAT( t2 ), hShader );
+}
+
+void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs )
+{
+ syscall( UI_R_MODELBOUNDS, model, mins, maxs );
+}
+
+void trap_UpdateScreen( void )
+{
+ syscall( UI_UPDATESCREEN );
+}
+
+int trap_CM_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName )
+{
+ return syscall( UI_CM_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT( frac ), tagName );
+}
+
+void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum )
+{
+ syscall( UI_S_STARTLOCALSOUND, sfx, channelNum );
+}
+
+sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed )
+{
+ return syscall( UI_S_REGISTERSOUND, sample, compressed );
+}
+
+void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen )
+{
+ syscall( UI_KEY_KEYNUMTOSTRINGBUF, keynum, buf, buflen );
+}
+
+void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen )
+{
+ syscall( UI_KEY_GETBINDINGBUF, keynum, buf, buflen );
+}
+
+void trap_Key_SetBinding( int keynum, const char *binding )
+{
+ syscall( UI_KEY_SETBINDING, keynum, binding );
+}
+
+qboolean trap_Key_IsDown( int keynum )
+{
+ return syscall( UI_KEY_ISDOWN, keynum );
+}
+
+qboolean trap_Key_GetOverstrikeMode( void )
+{
+ return syscall( UI_KEY_GETOVERSTRIKEMODE );
+}
+
+void trap_Key_SetOverstrikeMode( qboolean state )
+{
+ syscall( UI_KEY_SETOVERSTRIKEMODE, state );
+}
+
+void trap_Key_ClearStates( void )
+{
+ syscall( UI_KEY_CLEARSTATES );
+}
+
+int trap_Key_GetCatcher( void )
+{
+ return syscall( UI_KEY_GETCATCHER );
+}
+
+void trap_Key_SetCatcher( int catcher )
+{
+ syscall( UI_KEY_SETCATCHER, catcher );
+}
+
+void trap_GetClipboardData( char *buf, int bufsize )
+{
+ syscall( UI_GETCLIPBOARDDATA, buf, bufsize );
+}
+
+void trap_GetClientState( uiClientState_t *state )
+{
+ syscall( UI_GETCLIENTSTATE, state );
+}
+
+void trap_GetGlconfig( glconfig_t *glconfig )
+{
+ syscall( UI_GETGLCONFIG, glconfig );
+}
+
+int trap_GetConfigString( int index, char* buff, int buffsize )
+{
+ return syscall( UI_GETCONFIGSTRING, index, buff, buffsize );
+}
+
+int trap_LAN_GetServerCount( int source )
+{
+ return syscall( UI_LAN_GETSERVERCOUNT, source );
+}
+
+void trap_LAN_GetServerAddressString( int source, int n, char *buf, int buflen )
+{
+ syscall( UI_LAN_GETSERVERADDRESSSTRING, source, n, buf, buflen );
+}
+
+void trap_LAN_GetServerInfo( int source, int n, char *buf, int buflen )
+{
+ syscall( UI_LAN_GETSERVERINFO, source, n, buf, buflen );
+}
+
+int trap_LAN_GetServerPing( int source, int n )
+{
+ return syscall( UI_LAN_GETSERVERPING, source, n );
+}
+
+int trap_LAN_GetPingQueueCount( void )
+{
+ return syscall( UI_LAN_GETPINGQUEUECOUNT );
+}
+
+int trap_LAN_ServerStatus( const char *serverAddress, char *serverStatus, int maxLen )
+{
+ return syscall( UI_LAN_SERVERSTATUS, serverAddress, serverStatus, maxLen );
+}
+
+qboolean trap_GetNews( qboolean force )
+{
+ return syscall( UI_GETNEWS, force );
+}
+
+void trap_LAN_SaveCachedServers( void )
+{
+ syscall( UI_LAN_SAVECACHEDSERVERS );
+}
+
+void trap_LAN_LoadCachedServers( void )
+{
+ syscall( UI_LAN_LOADCACHEDSERVERS );
+}
+
+void trap_LAN_ResetPings( int n )
+{
+ syscall( UI_LAN_RESETPINGS, n );
+}
+
+void trap_LAN_ClearPing( int n )
+{
+ syscall( UI_LAN_CLEARPING, n );
+}
+
+void trap_LAN_GetPing( int n, char *buf, int buflen, int *pingtime )
+{
+ syscall( UI_LAN_GETPING, n, buf, buflen, pingtime );
+}
+
+void trap_LAN_GetPingInfo( int n, char *buf, int buflen )
+{
+ syscall( UI_LAN_GETPINGINFO, n, buf, buflen );
+}
+
+void trap_LAN_MarkServerVisible( int source, int n, qboolean visible )
+{
+ syscall( UI_LAN_MARKSERVERVISIBLE, source, n, visible );
+}
+
+int trap_LAN_ServerIsVisible( int source, int n )
+{
+ return syscall( UI_LAN_SERVERISVISIBLE, source, n );
+}
+
+qboolean trap_LAN_UpdateVisiblePings( int source )
+{
+ return syscall( UI_LAN_UPDATEVISIBLEPINGS, source );
+}
+
+int trap_LAN_AddServer( int source, const char *name, const char *addr )
+{
+ return syscall( UI_LAN_ADDSERVER, source, name, addr );
+}
+
+void trap_LAN_RemoveServer( int source, const char *addr )
+{
+ syscall( UI_LAN_REMOVESERVER, source, addr );
+}
+
+int trap_LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 )
+{
+ return syscall( UI_LAN_COMPARESERVERS, source, sortKey, sortDir, s1, s2 );
+}
+
+int trap_MemoryRemaining( void )
+{
+ return syscall( UI_MEMORY_REMAINING );
+}
+
+int trap_Parse_AddGlobalDefine( char *define )
+{
+ return syscall( UI_PARSE_ADD_GLOBAL_DEFINE, define );
+}
+
+int trap_Parse_LoadSource( const char *filename )
+{
+ return syscall( UI_PARSE_LOAD_SOURCE, filename );
+}
+
+int trap_Parse_FreeSource( int handle )
+{
+ return syscall( UI_PARSE_FREE_SOURCE, handle );
+}
+
+int trap_Parse_ReadToken( int handle, pc_token_t *pc_token )
+{
+ return syscall( UI_PARSE_READ_TOKEN, handle, pc_token );
+}
+
+int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line )
+{
+ return syscall( UI_PARSE_SOURCE_FILE_AND_LINE, handle, filename, line );
+}
+
+void trap_S_StopBackgroundTrack( void )
+{
+ syscall( UI_S_STOPBACKGROUNDTRACK );
+}
+
+void trap_S_StartBackgroundTrack( const char *intro, const char *loop )
+{
+ syscall( UI_S_STARTBACKGROUNDTRACK, intro, loop );
+}
+
+int trap_RealTime( qtime_t *qtime )
+{
+ return syscall( UI_REAL_TIME, qtime );
+}
+
+// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate)
+int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits )
+{
+ return syscall( UI_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits );
+}
+
+// stops playing the cinematic and ends it. should always return FMV_EOF
+// cinematics must be stopped in reverse order of when they are started
+e_status trap_CIN_StopCinematic( int handle )
+{
+ return syscall( UI_CIN_STOPCINEMATIC, handle );
+}
+
+
+// will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been reached.
+e_status trap_CIN_RunCinematic ( int handle )
+{
+ return syscall( UI_CIN_RUNCINEMATIC, handle );
+}
+
+
+// draws the current frame
+void trap_CIN_DrawCinematic ( int handle )
+{
+ syscall( UI_CIN_DRAWCINEMATIC, handle );
+}
+
+
+// allows you to resize the animation dynamically
+void trap_CIN_SetExtents ( int handle, int x, int y, int w, int h )
+{
+ syscall( UI_CIN_SETEXTENTS, handle, x, y, w, h );
+}
+
+
+void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset )
+{
+ syscall( UI_R_REMAP_SHADER, oldShader, newShader, timeOffset );
+}
+
+void trap_SetPbClStatus( int status )
+{
+ syscall( UI_SET_PBCLSTATUS, status );
+}