diff options
author | Mikko Tiusanen <ams@daug.net> | 2014-05-04 01:18:52 +0300 |
---|---|---|
committer | Mikko Tiusanen <ams@daug.net> | 2014-05-04 01:18:52 +0300 |
commit | 01beb9919b95479d8be040bec74abc5cc67a5e43 (patch) | |
tree | 65f0b79e793848491832756a4c3a32b23668fab3 /src/ui | |
parent | 191d731da136b7ee959a17e63111c9146219a768 (diff) |
Initial import.
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/ui_atoms.c | 295 | ||||
-rw-r--r-- | src/ui/ui_gameinfo.c | 377 | ||||
-rw-r--r-- | src/ui/ui_local.h | 400 | ||||
-rw-r--r-- | src/ui/ui_main.c | 4646 | ||||
-rw-r--r-- | src/ui/ui_public.h | 205 | ||||
-rw-r--r-- | src/ui/ui_shared.c | 8081 | ||||
-rw-r--r-- | src/ui/ui_shared.h | 559 | ||||
-rw-r--r-- | src/ui/ui_syscalls.asm | 102 | ||||
-rw-r--r-- | src/ui/ui_syscalls.c | 483 |
9 files changed, 15148 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..0e1ebe3 --- /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[512]; + char dirlist[4048]; + 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, 4048 ); + 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..6487fae --- /dev/null +++ b/src/ui/ui_main.c @@ -0,0 +1,4646 @@ +/* +=========================================================================== +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 *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( "%04i-%02i-%02i %02i:%02i:%02i", + q.tm_year+1900, q.tm_mon+1, q.tm_mday, + q.tm_hour, q.tm_min, q.tm_sec ) ); + + 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[ 64 ], buffer[ 2048 ]; + + 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[2048]; + 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 )->buildPoints; + + switch( BG_Buildable( item->v.buildable )->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 )->humanName, + BG_Buildable( item->v.buildable )->info ); + } + else + { + s = va( "%s\n\n%s\n\n%s: %d", + BG_Buildable( item->v.buildable )->humanName, + BG_Buildable( item->v.buildable )->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 )->team == TEAM_ALIENS && + BG_Buildable( i )->buildWeapon & uiInfo.weapons && + BG_BuildableAllowedInStage( i, stage ) && + BG_BuildableIsAllowed( i ) ) + { + uiInfo.alienBuildList[ j ].text = BG_Buildable( i )->humanName; + uiInfo.alienBuildList[ j ].cmd = + String_Alloc( va( "cmd build %s\n", BG_Buildable( i )->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 )->team == TEAM_HUMANS && + BG_Buildable( i )->buildWeapon & uiInfo.weapons && + BG_BuildableAllowedInStage( i, stage ) && + BG_BuildableIsAllowed( i ) ) + { + uiInfo.humanBuildList[ j ].text = BG_Buildable( i )->humanName; + uiInfo.humanBuildList[ j ].cmd = + String_Alloc( va( "cmd build %s\n", BG_Buildable( i )->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 \"^3%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, + " ", 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..7a47413 --- /dev/null +++ b/src/ui/ui_shared.c @@ -0,0 +1,8081 @@ +/* +=========================================================================== +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 + 0.15 ); + emoticonW = UI_EmoticonWidth( font, scale + 0.15 ); + + 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 ▭ +} + +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 } + }; + + +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 ); +} |