/* 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. */ // cl_scrn.c -- master for refresh, status bar, console, chat, notify, etc /* full screen console put up loading plaque blanked background with loading plaque blanked background with menu cinematics full screen image for quit and victory end of unit intermissions */ #include "client.h" float scr_con_current; // aproaches scr_conlines at scr_conspeed float scr_conlines; // 0.0 to 1.0 lines of console to display qboolean scr_initialized; // ready to draw int scr_draw_loading; cvar_t *scr_conspeed; cvar_t *scr_netgraph; cvar_t *scr_timegraph; cvar_t *scr_debuggraph; cvar_t *scr_graphheight; cvar_t *scr_graphscale; cvar_t *scr_graphshift; /* =============================================================================== MUFONT STRINGS =============================================================================== */ // // Variable width (proportional) fonts // //=============================================================================== //FONT LOADING //=============================================================================== mempool_t *fonts_mempool; #define Font_Alloc(size) Mem_Alloc( fonts_mempool, size ) typedef struct { qbyte x, y, width, height; float s1, t1, s2, t2; } muchar_t; typedef struct mufont_s { char name[MAX_QPATH]; muchar_t chars[256]; int fontheight; float imagewidth, imageheight; struct shader_s *shader; struct mufont_s *next; } mufont_t; static mufont_t *SCR_LoadMUFont( char *name ) { char filename[MAX_QPATH]; qbyte *buf; char *ptr, *token; int filenum; int length; mufont_t *font; struct shader_s *shader; int numchar; // load the shader Q_snprintfz( filename, sizeof(filename), "fonts/%s.tga", name ); shader = R_RegisterPic( filename ); if( !shader ) return NULL; // load the font description Q_snprintfz( filename, sizeof(filename), "fonts/%s.wfd", name ); // load the file length = FS_FOpenFile( filename, &filenum, FS_READ ); if( length == -1 ) { return NULL; } buf = Mem_TempMalloc( length + 1 ); length = FS_Read( buf, length, filenum ); FS_FCloseFile( filenum ); if( !length ) { Mem_TempFree( buf ); return NULL; } // seems to be valid. Allocate it font = (mufont_t *)Font_Alloc( sizeof(mufont_t) ); font->shader = shader; Q_strncpyz( font->name, name, sizeof(font->name) ); //proceed ptr = ( char * )buf; // get texture width and height token = COM_Parse( &ptr ); if( !token[0] ) { Mem_TempFree( buf ); return NULL; } font->imagewidth = atoi( token ); token = COM_Parse( &ptr ); if( !token[0] ) { Mem_TempFree( buf ); return NULL; } font->imageheight = atoi( token ); //get the chars while( ptr ) { // "" "" "" "" "" token = COM_Parse( &ptr ); if( !token[0] ) break; numchar = atoi(token); if( numchar < 32 || numchar >= 256 ) break; font->chars[numchar].x = atoi( COM_Parse( &ptr ) ); font->chars[numchar].y = atoi( COM_Parse( &ptr ) ); font->chars[numchar].width = atoi( COM_Parse( &ptr ) ); font->chars[numchar].height = atoi( COM_Parse( &ptr ) ); // create the texture coordinates font->chars[numchar].s1 = ((float)font->chars[numchar].x)/(float)font->imagewidth; font->chars[numchar].s2 = ((float)(font->chars[numchar].x + font->chars[numchar].width))/(float)font->imagewidth; font->chars[numchar].t1 = ((float)font->chars[numchar].y)/(float)font->imageheight; font->chars[numchar].t2 = ((float)(font->chars[numchar].y + font->chars[numchar].height))/(float)font->imageheight; } //mudFont is not always giving a proper size to the space character font->chars[' '].width = font->chars['a'].height / 2; // height is the same for every character font->fontheight = font->chars['a'].height; Mem_TempFree( buf ); return font; } mufont_t *gs_muFonts; //================== // SCR_RegisterFont //================== struct mufont_s *SCR_RegisterFont( char *name ) { mufont_t *font; COM_StripExtension( name ); for( font = gs_muFonts; font; font = font->next ) { if( !Q_stricmp( font->name, name ) ) return font; } font = SCR_LoadMUFont( name ); if( !font ) { return NULL; } font->next = gs_muFonts; gs_muFonts = font; return font; } void SCR_InitFonts( void ) { cvar_t *con_fontSystemSmall = Cvar_Get( "con_fontSystemSmall", DEFAULT_FONT_SMALL, CVAR_ARCHIVE ); cvar_t *con_fontSystemMedium = Cvar_Get( "con_fontSystemMedium", DEFAULT_FONT_MEDIUM, CVAR_ARCHIVE ); cvar_t *con_fontSystemBig = Cvar_Get( "con_fontSystemBig", DEFAULT_FONT_BIG, CVAR_ARCHIVE ); fonts_mempool = Mem_AllocPool( NULL, "Fonts" ); // register system fonts cls.fontSystemSmall = SCR_RegisterFont( con_fontSystemSmall->string ); if( !cls.fontSystemSmall ) { cls.fontSystemSmall = SCR_RegisterFont( DEFAULT_FONT_SMALL ); if( !cls.fontSystemSmall ) Com_Error( ERR_FATAL, "Couldn't load default font \"%s\"", DEFAULT_FONT_SMALL ); } cls.fontSystemMedium = SCR_RegisterFont( con_fontSystemMedium->string ); if( !cls.fontSystemMedium ) cls.fontSystemMedium = SCR_RegisterFont( DEFAULT_FONT_MEDIUM ); cls.fontSystemBig = SCR_RegisterFont( con_fontSystemBig->string ); if( !cls.fontSystemBig ) cls.fontSystemBig = SCR_RegisterFont( DEFAULT_FONT_BIG ); } void SCR_ShutdownFonts( void ) { Mem_FreePool( &fonts_mempool ); gs_muFonts = NULL; cls.fontSystemSmall = NULL; cls.fontSystemMedium = NULL; } //=============================================================================== //STRINGS HELPERS //=============================================================================== int SCR_HorizontalAlignForString( const int x, int align, int width ) { int nx = x; if( align % 3 == 0 ) // left nx = x; if( align % 3 == 1 ) // center nx = x - width / 2; if( align % 3 == 2 ) // right nx = x - width; return nx; } int SCR_VerticalAlignForString( const int y, int align, int height ) { int ny = y; if( align / 3 == 0 ) // top ny = y; else if( align / 3 == 1 ) // middle ny = y - height / 2; else if( align / 3 == 2 ) // bottom ny = y - height; return ny; } /* //================== // SCR_Strlen // doesn't count invisible characters //================== static size_t SCR_Strlen( const char *str ) { const char *s = str; size_t count = 0; if( s ) { while( *s ) { if( ((*s)&255) < 32 ) { s++; } else if( Q_IsColorString( s ) ) { s+=2; } else { count++; s++; } } } return count; } */ //================== // SCR_strHeight // it's font height in fact, but for preserving simetry I call it str //================== size_t SCR_strHeight( struct mufont_s *font ) { if( !font ) font = cls.fontSystemSmall; return font->fontheight; } //================== // SCR_strWidth // doesn't count invisible characters. Counts up to given length, if any. //================== size_t SCR_strWidth( const char *str, struct mufont_s *font, int maxlen ) { const char *s = str; size_t width = 0; int num; if( str ) { if( !font ) font = cls.fontSystemSmall; while( *s && *s != '\n' ) { if( maxlen && (s - str) >= maxlen ) // stop counting at desired len return width; num = (*s)&255; if( num < 32 ) { s++; } else if( Q_IsColorString( s ) ) { s+=2; } else { width += font->chars[num].width; s++; } } } return width; } //================== // SCR_StrlenForWidth // returns the len allowed for the string to fit inside a given width when using a given font. //================== size_t SCR_StrlenForWidth( const char *str, struct mufont_s *font, size_t maxwidth ) { const char *s = str; size_t width = 0; int num; if( !str ) return 0; if( !font ) font = cls.fontSystemSmall; while( *s && *s != '\n' ) { if( width >= maxwidth ) { return (unsigned)(s - str); } num = (*s)&255; if( num < 32 ) { s++; } else if( Q_IsColorString( s ) ) { s+=2; } else { width += font->chars[num].width; s++; } } return (unsigned)(s - str); } //=============================================================================== //STRINGS DRAWING //=============================================================================== #define MAX_DRAWSTRINGLEN_CHARS 2048 static char stringLenBuffer[ MAX_DRAWSTRINGLEN_CHARS ]; //================ //SCR_DrawRawChar // //Draws one graphics character with 0 being transparent. //It can be clipped to the top of the screen to allow the console to be //smoothly scrolled off. //================ void SCR_DrawRawChar( int x, int y, int num, struct mufont_s *font, vec4_t color ) { if( !font ) font = cls.fontSystemSmall; num &= 255; if ( (num&127) <= 32 ) return; // space if( y <= -font->fontheight ) return; // totally off screen R_DrawStretchPic( x, y, font->chars[num].width, font->fontheight, font->chars[num].s1, font->chars[num].t1, font->chars[num].s2, font->chars[num].t2, color, font->shader ); } //================== // SCR_DrawRawString : does not align nor does check off screen //================== void SCR_DrawRawString( int x, int y, const char *str, struct mufont_s *font, vec4_t color ) { int num; int xoffset = 0, yoffset = 0; vec4_t scolor; if( !str ) return; if( !font ) font = cls.fontSystemSmall; Vector4Copy( color, scolor ); while( *str ) { if( Q_IsColorString( str ) ) { VectorCopy( color_table[ColorIndex(str[1])], scolor ); str += 2; continue; } num = (*str)&255; str++; if( num < 32 ) continue; if( (num&127) != 32 ) { // not a space R_DrawStretchPic( x+xoffset, y+yoffset, font->chars[num].width, font->chars[num].height, font->chars[num].s1, font->chars[num].t1, font->chars[num].s2, font->chars[num].t2, scolor, font->shader ); } xoffset += font->chars[num].width; } } //================== // SCR_DrawString //================== void SCR_DrawString( int x, int y, int align, const char *str, struct mufont_s *font, vec4_t color ) { int width; if( !str ) return; if( !font ) font = cls.fontSystemSmall; width = SCR_strWidth( str, font, 0 ); if( width ) { x = SCR_HorizontalAlignForString( x, align, width ); y = SCR_VerticalAlignForString( y, align, font->fontheight ); if( y <= -font->fontheight ) return; // totally off screen if( x <= -width ) return; // totally off screen SCR_DrawRawString( x, y, str, font, color ); } } //================== // SCR_DrawString - ignores invisible characters at counting len //================== void SCR_DrawStringLen( int x, int y, int align, const char *str, size_t len, struct mufont_s *font, vec4_t color ) { const char *s = str; size_t screenlen = 0; if( !str ) return; if( !font ) font = cls.fontSystemSmall; if( len >= MAX_DRAWSTRINGLEN_CHARS ) len = MAX_DRAWSTRINGLEN_CHARS - 1; while( *s && screenlen < len && *s != '\n' ) { if( ((*s)&255) < 32 ) { s++; } else if( Q_IsColorString( s ) ) { s+=2; } else { screenlen++; s++; } } // it fits inside desired len if( screenlen ) { int reallen = s - str + 1; Q_strncpyz( stringLenBuffer, str, reallen ); SCR_DrawString( x, y, align, stringLenBuffer, font, color ); } } //============= //SCR_DrawFillRect // //Fills a box of pixels with a single color //============= void SCR_DrawFillRect( int x, int y, int w, int h, vec4_t color ) { R_DrawStretchPic( x, y, w, h, 0, 0, 1, 1, color, cls.whiteShader ); } /* =============================================================================== BAR GRAPHS =============================================================================== */ //============== //CL_AddNetgraph // //A new packet was just parsed //============== void CL_AddNetgraph( void ) { int i; int in; int ping; // if using the debuggraph for something else, don't // add the net lines if( scr_timegraph->integer ) return; for( i = 0; i < cls.netchan.dropped; i++ ) SCR_DebugGraph( 30.0f, 0.655f, 0.231f, 0.169f ); for( i = 0; i < cl.suppressCount; i++ ) SCR_DebugGraph( 30.0f, 0.0f, 1.0f, 0.0f ); // see what the latency was on this packet in = cls.netchan.incoming_acknowledged & CMD_MASK; ping = cls.realtime - cl.cmd_time[in]; ping /= 30; if( ping > 30 ) ping = 30; SCR_DebugGraph( ping, 1.0f, 0.75f, 0.06f ); } typedef struct { float value; vec4_t color; } graphsamp_t; static int current; static graphsamp_t values[1024]; /* ============== SCR_DebugGraph ============== */ void SCR_DebugGraph( float value, float r, float g, float b ) { values[current].value = value; values[current].color[0] = r; values[current].color[1] = g; values[current].color[2] = b; values[current].color[3] = 1.0f; current++; current &= 1023; } /* ============== SCR_DrawDebugGraph ============== */ void SCR_DrawDebugGraph( void ) { int a, x, y, w, i, h; float v; // // draw the graph // w = viddef.width; x = 0; y = 0+viddef.height; SCR_DrawFillRect (x, y-scr_graphheight->integer, w, scr_graphheight->integer, colorBlack); for (a=0 ; ainteger + scr_graphshift->integer; if (v < 0) v += scr_graphheight->integer * (1+(int)(-v/scr_graphheight->integer)); h = (int)v % scr_graphheight->integer; SCR_DrawFillRect (x+w-1-a, y - h, 1, h, values[i].color); } } //============================================================================ /* ================== SCR_InitScreen ================== */ void SCR_InitScreen( void ) { scr_conspeed = Cvar_Get( "scr_conspeed", "3", CVAR_ARCHIVE ); scr_netgraph = Cvar_Get( "netgraph", "0", 0 ); scr_timegraph = Cvar_Get( "timegraph", "0", 0 ); scr_debuggraph = Cvar_Get( "debuggraph", "0", 0 ); scr_graphheight = Cvar_Get( "graphheight", "32", 0 ); scr_graphscale = Cvar_Get( "graphscale", "1", 0 ); scr_graphshift = Cvar_Get( "graphshift", "0", 0 ); scr_initialized = qtrue; } //============================================================================= /* ================== SCR_RunConsole Scroll it up or down ================== */ void SCR_RunConsole( void ) { // decide on the height of the console if( cls.key_dest == key_console ) scr_conlines = 0.5; // half screen else scr_conlines = 0; // none visible if( scr_conlines < scr_con_current ) { scr_con_current -= scr_conspeed->value*cls.frametime; if( scr_conlines > scr_con_current ) scr_con_current = scr_conlines; } else if( scr_conlines > scr_con_current ) { scr_con_current += scr_conspeed->value*cls.frametime; if( scr_conlines < scr_con_current ) scr_con_current = scr_conlines; } } /* ================== SCR_DrawConsole ================== */ void SCR_DrawConsole( void ) { Con_CheckResize(); if( scr_con_current ) { Con_DrawConsole( scr_con_current ); return; } if( cls.state == CA_ACTIVE && (cls.key_dest == key_game || cls.key_dest == key_message) ) { Con_DrawNotify(); // only draw notify in game } } /* ================ SCR_BeginLoadingPlaque ================ */ void SCR_BeginLoadingPlaque( void ) { S_StopAllSounds(); cl.soundPrepped = qfalse; // don't play ambients memset( cl.configstrings, 0, sizeof(cl.configstrings) ); scr_conlines = 0; // none visible scr_draw_loading = 2; // clear to black first SCR_UpdateScreen(); CL_ShutdownMedia(); #if 0 if( cls.disable_screen ) return; if( developer->integer ) return; if( cls.state == ca_disconnected ) return; // if at console, don't bring up the plaque if( cls.key_dest == key_console ) return; if( cl.cin.time > 0 ) scr_draw_loading = 2; // clear to black first else scr_draw_loading = 1; SCR_UpdateScreen(); cls.disable_screen = Sys_Milliseconds(); cls.disable_servercount = cl.servercount; #endif } /* ================ SCR_EndLoadingPlaque ================ */ void SCR_EndLoadingPlaque( void ) { cls.disable_screen = 0; Con_ClearNotify(); CL_InitMedia(); } //======================================================= //================= //SCR_RegisterConsoleMedia //================= void SCR_RegisterConsoleMedia( void ) { cls.whiteShader = R_RegisterPic( "gfx/ui/white" ); cls.consoleShader = R_RegisterPic( "gfx/ui/console" ); SCR_InitFonts(); } //================= //SCR_ShutDownConsoleMedia //================= void SCR_ShutDownConsoleMedia( void ) { SCR_ShutdownFonts(); } //============================================================================ /* ================== SCR_RenderView ================== */ void SCR_RenderView( float stereo_separation ) { if( cls.demoplaying ) { if( cl_timedemo->integer ) { if( !cl.timedemo_start ) cl.timedemo_start = Sys_Milliseconds (); cl.timedemo_frames++; } } // frame is not valid until we load the CM data if( CM_ClientLoad() ) CL_GameModule_RenderView( stereo_separation ); } //============================================================================ /* ================== SCR_UpdateScreen This is called every frame, and can also be called explicitly to flush text to the screen. ================== */ void SCR_UpdateScreen( void ) { int numframes; int i; float separation[2]; // if the screen is disabled (loading plaque is up, or vid mode changing) // do nothing at all if( cls.disable_screen ) { if( Sys_Milliseconds() - cls.disable_screen > 120000 ) { cls.disable_screen = 0; Com_Printf( "Loading plaque timed out.\n" ); } return; } if( !scr_initialized || !con.initialized || !cls.mediaInitialized ) return; // not initialized yet /* ** range check cl_camera_separation so we don't inadvertently fry someone's ** brain */ if( cl_stereo_separation->value > 1.0 ) Cvar_SetValue( "cl_stereo_separation", 1.0 ); else if( cl_stereo_separation->value < 0 ) Cvar_SetValue( "cl_stereo_separation", 0.0 ); if( cl_stereo->integer ) { numframes = 2; separation[0] = -cl_stereo_separation->value / 2; separation[1] = cl_stereo_separation->value / 2; } else { separation[0] = 0; separation[1] = 0; numframes = 1; } for( i = 0; i < numframes; i++ ) { R_BeginFrame( separation[i] ); if( scr_draw_loading == 2 ) { // loading plaque over black screen scr_draw_loading = 0; CL_UIModule_DrawConnectScreen( qtrue ); } // if a cinematic is supposed to be running, handle menus // and console specially else if( cl.cin.time > 0 ) { SCR_DrawCinematic(); } else if( cls.state == CA_DISCONNECTED ) { CL_UIModule_Refresh( qtrue ); SCR_DrawConsole(); } else if( cls.state == CA_CONNECTING || cls.state == CA_CONNECTED ) { CL_UIModule_DrawConnectScreen( qtrue ); } else if( cls.state == CA_LOADING ) { SCR_RenderView( separation[i] ); CL_UIModule_DrawConnectScreen( qfalse ); } else if( cls.state == CA_ACTIVE ) { SCR_RenderView( separation[i] ); CL_UIModule_Refresh( qfalse ); if( scr_timegraph->integer ) SCR_DebugGraph( cls.frametime*300, 1, 1, 1 ); if( scr_debuggraph->integer || scr_timegraph->integer || scr_netgraph->integer ) SCR_DrawDebugGraph (); SCR_DrawConsole(); } R_EndFrame(); } }