/* Copyright (C) 1997-2001 Id Software, Inc. This program 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. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "ui_local.h" char *menu_in_sound = S_UI_MENU_IN_SOUND; char *menu_move_sound = S_UI_MENU_MOVE_SOUND; char *menu_out_sound = S_UI_MENU_OUT_SOUND; void M_Menu_Main_f( void ); void M_Menu_JoinServer_f( void ); void M_Menu_Setup_f( void ); void M_Menu_PlayerConfig_f( void ); void M_Menu_TeamConfig_f( void ); void M_Menu_StartServer_f( void ); void M_Menu_Options_f( void ); void M_Menu_Keys_f( void ); void M_Menu_Vsays_f( void ); void M_Menu_Sound_f( void ); void M_Menu_Gfx_f( void ); void M_Menu_Video_f( void ); void M_Menu_GLExt_f( void ); void M_Menu_Quit_f( void ); void M_Menu_Demos_f( void ); void M_Menu_Mods_f( void ); void M_Menu_Game_f( void ); void M_Menu_Failed_f( void ); void M_Menu_MsgBox_f( void ); ui_local_t uis; qboolean m_entersound; // play after drawing a frame, so caching // won't disrupt the sound struct mempool_s *uipool; menuframework_s *m_active; void *m_cursoritem; void (*m_drawfunc) (void); const char *(*m_keyfunc) (int key); const char *(*m_chareventfunc) (int key); //====================================================================== char *gametype_names[] = { "dm", "duel", "tdm", "ctf", "race", "midair", 0 }; char *noyes_names[] = { "no", "yes", 0 }; char *offon_names[] = { "off", "on", 0 }; //====================================================================== /* ============ UI_API ============ */ int UI_API (void) { return UI_API_VERSION; } /* ============ UI_Error ============ */ void UI_Error ( char *fmt, ... ) { char msg[1024]; va_list argptr; va_start ( argptr, fmt ); if ( vsprintf (msg, fmt, argptr) > sizeof(msg) ) { trap_Error ( "CG_Error: Buffer overflow" ); } va_end ( argptr ); trap_Error ( msg ); } /* ============ UI_Printf ============ */ void UI_Printf ( char *fmt, ... ) { char msg[1024]; va_list argptr; va_start ( argptr, fmt ); if ( vsprintf (msg, fmt, argptr) > sizeof(msg) ) { trap_Error ( "CG_Print: Buffer overflow" ); } va_end ( argptr ); trap_Print ( msg ); } //============================================================================= /* Support Routines */ #define MAX_MENU_DEPTH 8 typedef struct { menuframework_s *m; void (*draw) (void); const char *(*key) (int k); const char *(*charevent) (int k); } menulayer_t; menulayer_t m_layers[MAX_MENU_DEPTH]; int m_menudepth; void UI_UpdateMousePosition (void); //================= //UI_RegisterFonts //================= void UI_RegisterFonts( void ) { cvar_t *con_fontSystemSmall = trap_Cvar_Get( "con_fontSystemSmall", DEFAULT_FONT_SMALL, CVAR_ARCHIVE ); cvar_t *con_fontSystemMedium = trap_Cvar_Get( "con_fontSystemMedium", DEFAULT_FONT_MEDIUM, CVAR_ARCHIVE ); cvar_t *con_fontSystemBig = trap_Cvar_Get( "con_fontSystemBig", DEFAULT_FONT_BIG, CVAR_ARCHIVE ); uis.fontSystemSmall = trap_SCR_RegisterFont( con_fontSystemSmall->string ); if( !uis.fontSystemSmall ) { uis.fontSystemSmall = trap_SCR_RegisterFont( DEFAULT_FONT_SMALL ); if( !uis.fontSystemSmall ) UI_Error( "Couldn't load default font \"%s\"", DEFAULT_FONT_SMALL ); } uis.fontSystemMedium = trap_SCR_RegisterFont( con_fontSystemMedium->string ); if( !uis.fontSystemMedium ) uis.fontSystemMedium = trap_SCR_RegisterFont( DEFAULT_FONT_MEDIUM ); uis.fontSystemBig = trap_SCR_RegisterFont( con_fontSystemBig->string ); if( !uis.fontSystemBig ) uis.fontSystemBig = trap_SCR_RegisterFont( DEFAULT_FONT_BIG ); } void M_Cache( void ) { // precache sounds trap_S_RegisterSound( S_UI_MENU_IN_SOUND ); trap_S_RegisterSound( S_UI_MENU_MOVE_SOUND ); trap_S_RegisterSound( S_UI_MENU_OUT_SOUND ); uis.whiteShader = trap_R_RegisterPic( "gfx/ui/white" ); trap_R_RegisterPic( UI_SHADER_FXBACK ); trap_R_RegisterPic( UI_SHADER_BIGLOGO ); trap_R_RegisterPic( UI_SHADER_CURSOR ); UI_RegisterFonts(); } void M_PushMenu( menuframework_s *m, void (*draw) (void), const char *(*key) (int k), const char *(*charevent) (int k) ) { int i; // if this menu is already present, drop back to that level // to avoid stacking menus by hotkeys for( i=0 ; i= MAX_MENU_DEPTH ) { UI_Error ("M_PushMenu: MAX_MENU_DEPTH"); return; } m_layers[m_menudepth].m = m_active; m_layers[m_menudepth].draw = m_drawfunc; m_layers[m_menudepth].key = m_keyfunc; m_layers[m_menudepth].charevent = m_chareventfunc; m_menudepth++; M_Cache(); } m_drawfunc = draw; m_keyfunc = key; m_chareventfunc = charevent; m_active = m; m_entersound = qtrue; UI_UpdateMousePosition(); trap_CL_SetKeyDest( key_menu ); } void M_ForceMenuOff( void ) { m_active = 0; m_drawfunc = 0; m_keyfunc = 0; trap_CL_SetKeyDest( key_game ); m_menudepth = 0; trap_Key_ClearStates(); } void M_PopMenu( void ) { if( m_menudepth == 1 ) { // start the demo loop again if( uis.clientState < CA_CONNECTING ) // trap_Cmd_ExecuteText( EXEC_APPEND, "d1\n" ); return; M_ForceMenuOff(); return; } trap_S_StartLocalSound( menu_out_sound ); if( m_menudepth < 1 ) { UI_Error( "M_PopMenu: depth < 1" ); return; } m_menudepth--; m_drawfunc = m_layers[m_menudepth].draw; m_keyfunc = m_layers[m_menudepth].key; m_active = m_layers[m_menudepth].m; M_Cache(); UI_UpdateMousePosition(); } void M_genericBackFunc( menucommon_t *menuitem ) { M_PopMenu(); } const char *Default_MenuKey( menuframework_s *m, int key ) { const char *sound = NULL; menucommon_t *item = NULL; if( m ) { item = Menu_ItemAtCursor( m ); if( item != NULL ) { if( item->type == MTYPE_FIELD ) { if( Field_Key( item, key ) ) return NULL; } } } switch( key ) { case K_ESCAPE: M_PopMenu(); return menu_out_sound; case K_MOUSE1: if( m && (m_cursoritem == item) && Menu_SlideItem( m, 1, key ) ) { sound = menu_move_sound; } else { if( m ) Menu_SelectItem( m ); sound = menu_move_sound; } break; case K_MOUSE2: // wsw : pb : fix ui slider bug if( m && (m_cursoritem == item) && Menu_SlideItem( m, -1, key ) ) { sound = menu_move_sound; } else { M_PopMenu (); sound = menu_out_sound; } break; case K_MWHEELUP: case KP_UPARROW: case K_UPARROW: if ( m ) { m->cursor--; Menu_AdjustCursor( m, -1 ); sound = menu_move_sound; } break; case K_TAB: if ( m ) { m->cursor++; Menu_AdjustCursor( m, 1 ); sound = menu_move_sound; } break; case K_MWHEELDOWN: case KP_DOWNARROW: case K_DOWNARROW: if ( m ) { m->cursor++; Menu_AdjustCursor( m, 1 ); sound = menu_move_sound; } break; case KP_LEFTARROW: case K_LEFTARROW: if ( m ) { Menu_SlideItem( m, -1, key ); sound = menu_move_sound; } break; case KP_RIGHTARROW: case K_RIGHTARROW: if ( m ) { Menu_SlideItem( m, 1, key ); sound = menu_move_sound; } break; case K_MOUSE3: 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: case K_AUX17: case K_AUX18: case K_AUX19: case K_AUX20: case K_AUX21: case K_AUX22: case K_AUX23: case K_AUX24: case K_AUX25: case K_AUX26: case K_AUX27: case K_AUX28: case K_AUX29: case K_AUX30: case K_AUX31: case K_AUX32: case KP_ENTER: case K_ENTER: if( m ) Menu_SelectItem( m ); sound = menu_move_sound; break; } return sound; } const char *Default_MenuCharEvent( menuframework_s *m, int key ) { menucommon_t *item = NULL; if( m ) { item = Menu_ItemAtCursor( m ); if( item != NULL ) { if( item->type == MTYPE_FIELD ) { if( Field_CharEvent( item, key ) ) return NULL; } } } return NULL; } float M_ClampCvar( float min, float max, float value ) { if( value < min ) return min; if( value > max ) return max; return value; } //============================================================================= /* ================= UI_CopyString ================= */ char *UI_CopyString( const char *in ) { char *out; out = UI_Malloc(strlen(in)+1); strcpy (out, in); return out; } //============================================================================= //============================================================================= /* User Interface Subsystem */ /* ================= UI_Init ================= */ void UI_Init ( int vidWidth, int vidHeight ) { uipool = UI_MemAllocPool ( "UI" ); m_active = NULL; m_cursoritem = NULL; m_drawfunc = NULL; m_keyfunc = NULL; m_entersound = qfalse; memset( &uis, 0, sizeof( uis ) ); uis.vidWidth = vidWidth; uis.vidHeight = vidHeight; #if 0 uis.scaleX = UI_WIDTHSCALE; uis.scaleY = UI_HEIGHTSCALE; #else uis.scaleX = 1; uis.scaleY = 1; #endif uis.cursorX = uis.vidWidth / 2; uis.cursorY = uis.vidHeight / 2; trap_Cmd_AddCommand( "menu_main", M_Menu_Main_f ); trap_Cmd_AddCommand( "menu_setup", M_Menu_Setup_f ); trap_Cmd_AddCommand( "menu_joinserver", M_Menu_JoinServer_f ); trap_Cmd_AddCommand( "menu_playerconfig", M_Menu_PlayerConfig_f ); trap_Cmd_AddCommand( "menu_startserver", M_Menu_StartServer_f ); trap_Cmd_AddCommand( "menu_sound", M_Menu_Sound_f ); trap_Cmd_AddCommand( "menu_gfx", M_Menu_Gfx_f ); trap_Cmd_AddCommand( "menu_video", M_Menu_Video_f ); trap_Cmd_AddCommand( "menu_glext", M_Menu_GLExt_f ); trap_Cmd_AddCommand( "menu_options", M_Menu_Options_f ); trap_Cmd_AddCommand( "menu_keys", M_Menu_Keys_f ); trap_Cmd_AddCommand( "menu_vsays", M_Menu_Vsays_f ); trap_Cmd_AddCommand( "menu_quit", M_Menu_Quit_f ); trap_Cmd_AddCommand( "menu_demos", M_Menu_Demos_f ); trap_Cmd_AddCommand( "menu_mods", M_Menu_Mods_f ); trap_Cmd_AddCommand( "menu_game", M_Menu_Game_f ); trap_Cmd_AddCommand( "menu_failed", M_Menu_Failed_f ); trap_Cmd_AddCommand( "menu_msgbox", M_Menu_MsgBox_f ); trap_Cmd_AddCommand( "menu_teamconfig", M_Menu_TeamConfig_f ); M_Cache(); UI_Playermodel_Init(); // create a list with the available player models UI_InitTemporaryBoneposesCache(); // skelmod trap_S_StartBackgroundTrack( S_MUSIC_MENU, S_MUSIC_MENU ); } /* ================= UI_Shutdown ================= */ void UI_Shutdown( void ) { trap_S_StopBackgroundTrack(); trap_Cmd_RemoveCommand( "menu_main" ); trap_Cmd_RemoveCommand( "menu_setup" ); trap_Cmd_RemoveCommand( "menu_joinserver" ); trap_Cmd_RemoveCommand( "menu_playerconfig" ); trap_Cmd_RemoveCommand( "menu_startserver" ); trap_Cmd_RemoveCommand( "menu_gfx" ); trap_Cmd_RemoveCommand( "menu_sound" ); trap_Cmd_RemoveCommand( "menu_video" ); trap_Cmd_RemoveCommand( "menu_glext" ); trap_Cmd_RemoveCommand( "menu_options" ); trap_Cmd_RemoveCommand( "menu_keys" ); trap_Cmd_RemoveCommand( "menu_vsays" ); trap_Cmd_RemoveCommand( "menu_quit" ); trap_Cmd_RemoveCommand( "menu_demos" ); trap_Cmd_RemoveCommand( "menu_mods" ); trap_Cmd_RemoveCommand( "menu_game" ); trap_Cmd_RemoveCommand( "menu_failed" ); trap_Cmd_RemoveCommand( "menu_msgbox" ); trap_Cmd_RemoveCommand( "menu_teamconfig" ); UI_MemFreePool( &uipool ); } /* ================= UI_UpdateMousePosition ================= */ void UI_UpdateMousePosition (void) { int i; menucommon_t *menuitem; if ( !m_active || !m_active->nitems ) { return; } /* ** check items */ m_cursoritem = NULL; for( i = 0; i < m_active->nitems; i++ ) { menuitem = m_active->items[i]; if ( uis.cursorX > menuitem->maxs[0] || uis.cursorY > menuitem->maxs[1] || uis.cursorX < menuitem->mins[0] || uis.cursorY < menuitem->mins[1] ) continue; m_cursoritem = m_active->items[i]; if( m_active->cursor == i ) { break; } Menu_AdjustCursor( m_active, i - m_active->cursor ); m_active->cursor = i; //trap_S_StartLocalSound( ( char * )menu_move_sound ); break; } } /* ================= UI_MouseMove ================= */ void UI_MouseMove( int dx, int dy ) { uis.cursorX += dx; uis.cursorY += dy; clamp( uis.cursorX, 0, uis.vidWidth ); clamp( uis.cursorY, 0, uis.vidHeight ); if( dx || dy ) { UI_UpdateMousePosition(); } } /* ================= UI_DrawConnectScreen ================= */ void UI_DrawConnectScreen( char *serverName, char *rejectmessage, char *downloadfilename, int connectCount, qboolean backGround ) { qboolean localhost; char str[MAX_QPATH], levelshot[MAX_QPATH]; char mapname[MAX_QPATH], message[MAX_QPATH]; trap_S_StopBackgroundTrack(); localhost = !serverName || !serverName[0] || !Q_stricmp ( serverName, "localhost" ); M_Cache(); trap_GetConfigString ( CS_MAPNAME, mapname, sizeof(mapname) ); if ( backGround ) { if ( mapname[0] ) { Q_snprintfz ( levelshot, sizeof(levelshot), "levelshots/%s.jpg", mapname ); if ( trap_FS_FOpenFile( levelshot, NULL, FS_READ ) == -1 ) Q_snprintfz ( levelshot, sizeof(levelshot), "levelshots/%s.tga", mapname ); if ( trap_FS_FOpenFile( levelshot, NULL, FS_READ ) == -1 ) Q_snprintfz ( levelshot, sizeof(levelshot), "gfx/ui/unknownmap" ); trap_R_DrawStretchPic ( 0, 0, uis.vidWidth, uis.vidHeight, 0, 0, 1, 1, colorWhite, trap_R_RegisterPic ( levelshot ) ); trap_R_DrawStretchPic ( 0, 0, uis.vidWidth, uis.vidHeight, 0, 0, 2.5, 2, colorWhite, trap_R_RegisterPic ( "levelShotDetail" ) ); } else { UI_FillRect ( 0, 0, uis.vidWidth, uis.vidHeight, colorBlack ); } } // draw server name if not local host if ( !localhost ) { Q_snprintfz ( str, sizeof(str), "Connecting to %s", serverName ); trap_SCR_DrawString( uis.vidWidth/2, 64, ALIGN_CENTER_TOP, str, uis.fontSystemBig, colorWhite ); } if( rejectmessage ) { Q_snprintfz ( str, sizeof(str), "Refused: %s", rejectmessage ); trap_SCR_DrawString( uis.vidWidth/2, 86, ALIGN_CENTER_TOP, str, uis.fontSystemMedium, colorWhite ); } if( downloadfilename ) { Q_snprintfz ( str, sizeof(str), "Downloading %s", downloadfilename ); trap_SCR_DrawString( uis.vidWidth/2, 86, ALIGN_CENTER_TOP, str, uis.fontSystemMedium, colorWhite ); } if( mapname[0] ) { // draw Warsow logo at bottom trap_R_DrawStretchPic( 0, uis.vidHeight - UI_SCALED_HEIGHT(64), UI_SCALED_WIDTH(640), UI_SCALED_HEIGHT(64), 0, 0, 1, 1, colorWhite, trap_R_RegisterPic( "gfx/ui/loadscreen_logo" ) ); trap_GetConfigString( CS_MESSAGE, message, sizeof(message) ); if( message[0] ) // level name ("message") trap_SCR_DrawString( uis.vidWidth/2, 150, ALIGN_CENTER_TOP, message, uis.fontSystemBig, colorWhite ); } else { if( !localhost ) { Q_snprintfz ( message, sizeof(message), "Awaiting connection... %i", connectCount ); trap_SCR_DrawString( uis.vidWidth/2, 150, ALIGN_CENTER_TOP, message, uis.fontSystemBig, colorWhite ); } else { Q_strncpyz ( message, "Loading...", sizeof(message) ); trap_SCR_DrawString( uis.vidWidth/2, 150, ALIGN_CENTER_TOP, message, uis.fontSystemBig, colorWhite ); } } } /* ================= UI_Refresh ================= */ void UI_Refresh( unsigned int time, int clientState, int serverState, qboolean backGround ) { uis.time = time; uis.clientState = clientState; uis.serverState = serverState; uis.backGround = backGround; if( !m_drawfunc ) // ui is inactive return; // draw background if( uis.backGround ) { trap_R_DrawStretchPic( 0, 0, uis.vidWidth, uis.vidHeight, 0, 0, 1, 1, colorWhite, trap_R_RegisterPic( UI_SHADER_VIDEOBACK ) ); trap_R_DrawStretchPic ( 0, 0, uis.vidWidth, uis.vidHeight, 0, 0, 1, 1, colorWhite, trap_R_RegisterPic( UI_SHADER_FXBACK ) ); trap_R_DrawStretchPic ( 0, 0, uis.vidWidth, uis.vidHeight, 0, 0, 1, 1, colorWhite, trap_R_RegisterPic( UI_SHADER_BIGLOGO ) ); } else { //trap_R_DrawStretchPic( 0, 0, uis.vidWidth, uis.vidHeight, // 0, 0, 1, 1, colorDkGrey, trap_R_RegisterPic( "gfx/ui/novideoback" ) ); //trap_R_DrawStretchPic ( 0, 0, uis.vidWidth, uis.vidHeight, // 0, 0, 1, 1, colorWhite, trap_R_RegisterPic( UI_SHADER_BIGLOGO ) ); } m_drawfunc(); // draw cursor trap_R_DrawStretchPic( uis.cursorX - 16, uis.cursorY - 16, 32, 32, 0, 0, 1, 1, colorWhite, trap_R_RegisterPic( UI_SHADER_CURSOR ) ); // delay playing the enter sound until after the // menu has been drawn, to avoid delay while // caching images if (m_entersound) { trap_S_StartLocalSound( menu_in_sound ); m_entersound = qfalse; } } /* ================= UI_Keydown ================= */ void UI_Keydown (int key) { const char *s; if( m_keyfunc ) if( ( s = m_keyfunc( key ) ) != 0 ) trap_S_StartLocalSound( ( char * ) s ); } /* ================= UI_CharEvent ================= */ void UI_CharEvent (int key) { const char *s; if( m_chareventfunc ) if( ( s = m_chareventfunc( key ) ) != 0 ) trap_S_StartLocalSound( ( char * ) s ); } //====================================================================== #ifndef UI_HARD_LINKED // this is only here so the functions in q_shared.c and q_math.c can link void Sys_Error (char *error, ...) { va_list argptr; char text[1024]; va_start (argptr, error); vsnprintf (text, sizeof(text), error, argptr); va_end (argptr); text[sizeof(text)-1] = 0; trap_Error (text); } void Com_Printf (char *fmt, ...) { va_list argptr; char text[1024]; va_start (argptr, fmt); vsnprintf (text, sizeof(text), fmt, argptr); va_end (argptr); text[sizeof(text)-1] = 0; trap_Print (text); } #endif