/* 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 #include "cl_local.h" #define STAT_PICS 11 #define STAT_MINUS (STAT_PICS-1) // num frame for '-' stats digit static struct { qboolean initialized; // ready to draw qhandle_t crosshair_pic; int crosshair_width, crosshair_height; qhandle_t pause_pic; int pause_width, pause_height; qhandle_t loading_pic; int loading_width, loading_height; qboolean draw_loading; qhandle_t sb_pics[2][STAT_PICS]; qhandle_t inven_pic; qhandle_t field_pic; qhandle_t backtile_pic; qhandle_t net_pic; qhandle_t font_pic; int hud_width, hud_height; } scr; static cvar_t *scr_viewsize; static cvar_t *scr_centertime; static cvar_t *scr_showpause; #ifdef _DEBUG static cvar_t *scr_showstats; static cvar_t *scr_showpmove; #endif static cvar_t *scr_showturtle; static cvar_t *scr_draw2d; static cvar_t *scr_lag_x; static cvar_t *scr_lag_y; static cvar_t *scr_lag_draw; static cvar_t *scr_lag_max; static cvar_t *scr_alpha; static cvar_t *scr_demobar; static cvar_t *scr_font; static cvar_t *scr_scale; static cvar_t *scr_crosshair; #ifdef _DEBUG cvar_t *scr_netgraph; cvar_t *scr_timegraph; cvar_t *scr_debuggraph; static cvar_t *scr_graphheight; static cvar_t *scr_graphscale; static cvar_t *scr_graphshift; #endif vrect_t scr_vrect; // position of render window on screen glconfig_t scr_glconfig; static const char *const sb_nums[2][STAT_PICS] = { { "num_0", "num_1", "num_2", "num_3", "num_4", "num_5", "num_6", "num_7", "num_8", "num_9", "num_minus" }, { "anum_0", "anum_1", "anum_2", "anum_3", "anum_4", "anum_5", "anum_6", "anum_7", "anum_8", "anum_9", "anum_minus" } }; const color_t colorTable[8] = { { 0, 0, 0, 255 }, { 255, 0, 0, 255 }, { 0, 255, 0, 255 }, { 255, 255, 0, 255 }, { 0, 0, 255, 255 }, { 0, 255, 255, 255 }, { 255, 0, 255, 255 }, { 255, 255, 255, 255 } }; /* =============================================================================== UTILS =============================================================================== */ #define SCR_DrawString( x, y, flags, string ) \ SCR_DrawStringEx( x, y, flags, MAX_STRING_CHARS, string, scr.font_pic ) /* ============== SCR_DrawStringEx ============== */ int SCR_DrawStringEx( int x, int y, int flags, size_t maxlen, const char *s, qhandle_t font ) { size_t len = strlen( s ); if( len > maxlen ) { len = maxlen; } if( ( flags & UI_CENTER ) == UI_CENTER ) { x -= len * CHAR_WIDTH / 2; } else if( flags & UI_RIGHT ) { x -= len * CHAR_WIDTH; } return R_DrawString( x, y, flags, maxlen, s, font ); } /* ============== SCR_DrawStringMulti ============== */ void SCR_DrawStringMulti( int x, int y, int flags, size_t maxlen, const char *s, qhandle_t font ) { char *p; size_t len; while( *s ) { p = strchr( s, '\n' ); if( !p ) { SCR_DrawStringEx( x, y, flags, maxlen, s, font ); break; } len = p - s; if( len > maxlen ) { len = maxlen; } SCR_DrawStringEx( x, y, flags, len, s, font ); y += CHAR_HEIGHT; s = p + 1; } } /* ================= SCR_FadeAlpha ================= */ float SCR_FadeAlpha( unsigned startTime, unsigned visTime, unsigned fadeTime ) { float alpha; unsigned timeLeft, delta = cls.realtime - startTime; if( delta >= visTime ) { return 0; } if( fadeTime > visTime ) { fadeTime = visTime; } alpha = 1; timeLeft = visTime - delta; if( timeLeft < fadeTime ) { alpha = ( float )timeLeft / fadeTime; } return alpha; } qboolean SCR_ParseColor( const char *s, color_t color ) { int i; int c[8]; if( *s == '#' ) { s++; for( i = 0; s[i]; i++ ) { c[i] = Q_charhex( s[i] ); if( c[i] == -1 ) { return qfalse; } } switch( i ) { case 3: color[0] = c[0] | ( c[0] << 4 ); color[1] = c[1] | ( c[1] << 4 ); color[2] = c[2] | ( c[2] << 4 ); color[3] = 255; break; case 6: color[0] = c[1] | ( c[0] << 4 ); color[1] = c[3] | ( c[2] << 4 ); color[2] = c[5] | ( c[4] << 4 ); color[3] = 255; break; case 8: color[0] = c[1] | ( c[0] << 4 ); color[1] = c[3] | ( c[2] << 4 ); color[2] = c[5] | ( c[4] << 4 ); color[3] = c[7] | ( c[6] << 4 ); break; default: return qfalse; } return qtrue; } else { i = Com_ParseColor( s, COLOR_WHITE ); if( i == COLOR_NONE ) { return qfalse; } FastColorCopy( colorTable[i], color ); return qtrue; } } /* =============================================================================== BAR GRAPHS =============================================================================== */ #ifdef _DEBUG /* ============== CL_AddNetgraph A new packet was just parsed ============== */ void CL_AddNetgraph (void) { int i; int in; int ping; if (!scr.initialized) return; // if using the debuggraph for something else, don't // add the net lines if (scr_debuggraph->integer || scr_timegraph->integer) return; for (i=0 ; idropped ; i++) SCR_DebugGraph (30, 0x40); //for (i=0 ; iincoming_acknowledged & CMD_MASK; ping = cls.realtime - cl.history[in].sent; ping /= 30; if (ping > 30) ping = 30; SCR_DebugGraph (ping, 0xd0); } typedef struct { float value; int color; } graphsamp_t; static int current; static graphsamp_t values[1024]; /* ============== SCR_DebugGraph ============== */ void SCR_DebugGraph (float value, int color) { values[current&1023].value = value; values[current&1023].color = color; current++; } /* ============== SCR_DrawDebugGraph ============== */ void SCR_DrawDebugGraph (void) { int a, x, y, w, i, h; float v; int color; // // draw the graph // w = scr_glconfig.vidWidth; x = w-1; y = scr_glconfig.vidHeight; R_DrawFill (x, y-scr_graphheight->value, w, scr_graphheight->value, 8); for (a=0 ; avalue + scr_graphshift->value; if (v < 0) v += scr_graphheight->value * (1+(int)(-v/scr_graphheight->value)); h = (int)v % (int)scr_graphheight->value; R_DrawFill (x, y - h, 1, h, color); x--; } } #endif static void draw_percent_bar( int percent ) { char buffer[16]; int x, w; size_t len; scr.hud_height -= CHAR_HEIGHT; w = scr.hud_width * percent / 100; R_DrawFill( 0, scr.hud_height, w, CHAR_HEIGHT, 4 ); R_DrawFill( w, scr.hud_height, scr.hud_width - w, CHAR_HEIGHT, 0 ); len = Q_scnprintf( buffer, sizeof( buffer ), "%d%%", percent ); x = ( scr.hud_width - len * CHAR_WIDTH ) / 2; R_DrawString( x, scr.hud_height, 0, MAX_STRING_CHARS, buffer, scr.font_pic ); } static void draw_demo_bar( void ) { #if USE_MVD_CLIENT int percent; #endif if( !scr_demobar->integer ) { return; } if( cls.demo.playback ) { if( cls.demo.file_size ) { draw_percent_bar( cls.demo.file_percent ); } return; } #if USE_MVD_CLIENT if( sv_running->integer != ss_broadcast ) { return; } if( ( percent = MVD_GetDemoPercent() ) == -1 ) { return; } draw_percent_bar( percent ); #endif } /* =============================================================================== CENTER PRINTING =============================================================================== */ static char scr_centerstring[MAX_STRING_CHARS]; static unsigned scr_centertime_start; // for slow victory printing static int scr_center_lines; /* ============== SCR_CenterPrint Called for important messages that should stay in the center of the screen for a few moments ============== */ void SCR_CenterPrint( const char *str ) { const char *s; scr_centertime_start = cls.realtime; if( !strcmp( scr_centerstring, str ) ) { return; } Q_strlcpy( scr_centerstring, str, sizeof( scr_centerstring ) ); // count the number of lines for centering scr_center_lines = 1; s = str; while( *s ) { if( *s == '\n' ) scr_center_lines++; s++; } // echo it to the console Com_Printf( "%s\n", scr_centerstring ); Con_ClearNotify_f(); } static void draw_center_string( void ) { int y; float alpha; Cvar_ClampValue( scr_centertime, 0.3f, 10.0f ); alpha = SCR_FadeAlpha( scr_centertime_start, scr_centertime->value * 1000, 300 ); if( !alpha ) { return; } R_SetColor( DRAW_COLOR_ALPHA, ( byte * )&alpha ); y = scr.hud_height / 4 - scr_center_lines * 8 / 2; SCR_DrawStringMulti( scr.hud_width / 2, y, UI_CENTER, MAX_STRING_CHARS, scr_centerstring, scr.font_pic ); R_SetColor( DRAW_COLOR_CLEAR, NULL ); } /* =============================================================================== LAGOMETER =============================================================================== */ #define LAG_WIDTH 48 #define LAG_HEIGHT 48 #define LAG_CRIT_BIT ( 1 << 31 ) #define LAG_WARN_BIT ( 1 << 30 ) #define LAG_BASE 0xD5 #define LAG_WARN 0xDC #define LAG_CRIT 0xF2 static struct { unsigned samples[LAG_WIDTH]; unsigned head; } lag; void SCR_LagClear( void ) { lag.head = 0; } void SCR_LagSample( void ) { int i = cls.netchan->incoming_acknowledged & CMD_MASK; client_history_t *h = &cl.history[i]; unsigned ping; h->rcvd = cls.realtime; if( !h->cmdNumber || h->rcvd < h->sent ) { return; } ping = h->rcvd - h->sent; for( i = 0; i < cls.netchan->dropped; i++ ) { lag.samples[lag.head % LAG_WIDTH] = ping | LAG_CRIT_BIT; lag.head++; } if( cl.frameflags & FF_SURPRESSED ) { ping |= LAG_WARN_BIT; } lag.samples[lag.head % LAG_WIDTH] = ping; lag.head++; } static void draw_ping_graph( int x, int y ) { int i, j, v, c, max = Cvar_ClampInteger( scr_lag_max, 16, 480 ); for( i = 0; i < LAG_WIDTH; i++ ) { j = lag.head - i - 1; if( j < 0 ) { break; } v = lag.samples[j % LAG_WIDTH]; if( v & LAG_CRIT_BIT ) { c = LAG_CRIT; } else if( v & LAG_WARN_BIT ) { c = LAG_WARN; } else { c = LAG_BASE; } v &= ~(LAG_WARN_BIT|LAG_CRIT_BIT); v = v * LAG_HEIGHT / max; if( v > LAG_HEIGHT ) { v = LAG_HEIGHT; } R_DrawFill( x + LAG_WIDTH - i - 1, y + LAG_HEIGHT - v, 1, v, c ); } } static void draw_lagometer( void ) { int x = scr_lag_x->integer; int y = scr_lag_y->integer; if( x < 0 ) { x += scr.hud_width - LAG_WIDTH + 1; } if( y < 0 ) { y += scr.hud_height - LAG_HEIGHT + 1; } // draw ping graph if( scr_lag_draw->integer ) { if( scr_lag_draw->integer > 1 ) { R_DrawFill( x, y, LAG_WIDTH, LAG_HEIGHT, 4 ); } draw_ping_graph( x, y ); } // draw phone jack if( cls.netchan && cls.netchan->outgoing_sequence - cls.netchan->incoming_acknowledged >= CMD_BACKUP ) { if( ( cls.realtime >> 8 ) & 3 ) { R_DrawStretchPic( x, y, LAG_WIDTH, LAG_HEIGHT, scr.net_pic ); } } } /* =============================================================================== DRAW OBJECTS =============================================================================== */ typedef struct { list_t entry; int x, y; cvar_t *cvar; cmd_macro_t *macro; int flags; color_t color; } drawobj_t; #define FOR_EACH_DRAWOBJ( obj ) \ LIST_FOR_EACH( drawobj_t, obj, &scr_objects, entry ) #define FOR_EACH_DRAWOBJ_SAFE( obj, next ) \ LIST_FOR_EACH_SAFE( drawobj_t, obj, next, &scr_objects, entry ) static LIST_DECL( scr_objects ); static void SCR_Color_g( genctx_t *ctx ) { int color; for( color = 0; color < 10; color++ ) { if( !Prompt_AddMatch( ctx, colorNames[color] ) ) { break; } } } static void SCR_Draw_c( genctx_t *ctx, int argnum ) { if( argnum == 1 ) { Cvar_Variable_g( ctx ); Cmd_Macro_g( ctx ); } else if( argnum == 4 ) { SCR_Color_g( ctx ); } } // draw cl_fps -1 80 static void SCR_Draw_f( void ) { int x, y; const char *s, *c; drawobj_t *obj; cmd_macro_t *macro; // int stat; color_t color = { 0, 0, 0, 0 }; int flags = UI_IGNORECOLOR; int argc = Cmd_Argc(); if( argc == 1 ) { if( LIST_EMPTY( &scr_objects ) ) { Com_Printf( "No draw strings registered.\n" ); return; } Com_Printf( "Name X Y\n" "--------------- ---- ----\n" ); FOR_EACH_DRAWOBJ( obj ) { s = obj->macro ? obj->macro->name : obj->cvar->name; Com_Printf( "%-15s %4d %4d\n", s, obj->x, obj->y ); } return; } if( argc < 4 ) { Com_Printf( "Usage: %s [color]\n", Cmd_Argv( 0 ) ); return; } s = Cmd_Argv( 1 ); x = atoi( Cmd_Argv( 2 ) ); if( x < 0 ) { flags |= UI_RIGHT; } y = atoi( Cmd_Argv( 3 ) ); if( argc > 4 ) { c = Cmd_Argv( 4 ); if( !strcmp( c, "alt" ) ) { flags |= UI_ALTCOLOR; } else { if( !SCR_ParseColor( c, color ) ) { Com_Printf( "Unknown color '%s'\n", c ); return; } flags &= ~UI_IGNORECOLOR; } } obj = Z_Malloc( sizeof( *obj ) ); obj->x = x; obj->y = y; obj->flags = flags; *( uint32_t * )obj->color = *( uint32_t * )color; macro = Cmd_FindMacro( s ); if( macro ) { obj->cvar = NULL; obj->macro = macro; } else { obj->cvar = Cvar_Ref( s ); obj->macro = NULL; } List_Append( &scr_objects, &obj->entry ); } static void SCR_Draw_g( genctx_t *ctx ) { drawobj_t *obj; const char *s; if( LIST_EMPTY( &scr_objects ) ) { return; } Prompt_AddMatch( ctx, "all" ); FOR_EACH_DRAWOBJ( obj ) { s = obj->macro ? obj->macro->name : obj->cvar->name; if( !Prompt_AddMatch( ctx, s ) ) { break; } } } static void SCR_UnDraw_c( genctx_t *ctx, int argnum ) { if( argnum == 1 ) { SCR_Draw_g( ctx ); } } static void SCR_UnDraw_f( void ) { char *s; drawobj_t *obj, *next; cmd_macro_t *macro; cvar_t *cvar; qboolean deleted; if( Cmd_Argc() != 2 ) { Com_Printf( "Usage: %s \n", Cmd_Argv( 0 ) ); return; } if( LIST_EMPTY( &scr_objects ) ) { Com_Printf( "No draw strings registered.\n" ); return; } s = Cmd_Argv( 1 ); if( !strcmp( s, "all" ) ) { FOR_EACH_DRAWOBJ_SAFE( obj, next ) { Z_Free( obj ); } List_Init( &scr_objects ); Com_Printf( "Deleted all draw strings.\n" ); return; } cvar = NULL; macro = Cmd_FindMacro( s ); if( !macro ) { cvar = Cvar_Ref( s ); } deleted = qfalse; FOR_EACH_DRAWOBJ_SAFE( obj, next ) { if( obj->macro == macro && obj->cvar == cvar ) { List_Remove( &obj->entry ); Z_Free( obj ); deleted = qtrue; } } if( !deleted ) { Com_Printf( "Draw string '%s' not found.\n", s ); } } static void draw_objects( void ) { char buffer[MAX_QPATH]; int x, y; drawobj_t *obj; FOR_EACH_DRAWOBJ( obj ) { x = obj->x; y = obj->y; if( x < 0 ) { x += scr.hud_width + 1; } if( y < 0 ) { y += scr.hud_height - CHAR_HEIGHT + 1; } if( !( obj->flags & UI_IGNORECOLOR ) ) { R_SetColor( DRAW_COLOR_RGBA, obj->color ); } if( obj->macro ) { obj->macro->function( buffer, sizeof( buffer ) ); SCR_DrawString( x, y, obj->flags, buffer ); } else { SCR_DrawString( x, y, obj->flags, obj->cvar->string ); } R_SetColor( DRAW_COLOR_CLEAR, NULL ); } } /* =============================================================================== DEBUG STUFF =============================================================================== */ static void draw_turtle( void ) { int x = 8; int y = scr.hud_height - 88; #define DF( f ) if( cl.frameflags & FF_ ## f ) { \ SCR_DrawString( x, y, UI_ALTCOLOR, #f ); \ y += 8; \ } DF( SURPRESSED ) DF( CLIENTPRED ) else DF( CLIENTDROP ) DF( SERVERDROP ) DF( BADFRAME ) DF( OLDFRAME ) DF( OLDENT ) DF( NODELTA ) #undef DF } #ifdef _DEBUG static void draw_stats( void ) { char buffer[MAX_QPATH]; int i, j; int x, y; j = scr_showstats->integer; if( j > MAX_STATS ) { j = MAX_STATS; } x = CHAR_WIDTH; y = ( scr.hud_height - j * CHAR_HEIGHT ) / 2; for( i = 0; i < j; i++ ) { Q_snprintf( buffer, sizeof( buffer ), "%2d: %d", i, cl.frame.ps.stats[i] ); if( cl.oldframe.ps.stats[i] != cl.frame.ps.stats[i] ) { R_SetColor( DRAW_COLOR_RGBA, colorRed ); } R_DrawString( x, y, 0, MAX_STRING_CHARS, buffer, scr.font_pic ); R_SetColor( DRAW_COLOR_CLEAR, NULL ); y += CHAR_HEIGHT; } } static void draw_pmove( void ) { static const char * const types[] = { "NORMAL", "SPECTATOR", "DEAD", "GIB", "FREEZE" }; static const char * const flags[] = { "DUCKED", "JUMP_HELD", "ON_GROUND", "TIME_WATERJUMP", "TIME_LAND", "TIME_TELEPORT", "NO_PREDICTION", "TELEPORT_BIT" }; int x = CHAR_WIDTH; int y = ( scr.hud_height - 2 * CHAR_HEIGHT ) / 2; unsigned i, j; i = cl.frame.ps.pmove.pm_type; if( i > PM_FREEZE ) { i = PM_FREEZE; } R_DrawString( x, y, 0, MAX_STRING_CHARS, types[i], scr.font_pic ); y += CHAR_HEIGHT; j = cl.frame.ps.pmove.pm_flags; for( i = 0; i < 8; i++ ) { if( j & ( 1 << i ) ) { x = R_DrawString( x, y, 0, MAX_STRING_CHARS, flags[i], scr.font_pic ); x += CHAR_WIDTH; } } } #endif //============================================================================ // Sets scr_vrect, the coordinates of the rendered window static void calc_vrect( void ) { int size; // bound viewsize size = Cvar_ClampInteger( scr_viewsize, 40, 100 ); scr_viewsize->modified = qfalse; scr_vrect.width = scr.hud_width * size / 100; scr_vrect.width &= ~7; scr_vrect.height = scr.hud_height * size / 100; scr_vrect.height &= ~1; scr_vrect.x = ( scr.hud_width - scr_vrect.width ) / 2; scr_vrect.y = ( scr.hud_height - scr_vrect.height ) / 2; } /* ================= SCR_SizeUp_f Keybinding command ================= */ static void SCR_SizeUp_f( void ) { Cvar_SetInteger( scr_viewsize, scr_viewsize->integer + 10, FROM_CONSOLE ); } /* ================= SCR_SizeDown_f Keybinding command ================= */ static void SCR_SizeDown_f( void ) { Cvar_SetInteger( scr_viewsize, scr_viewsize->integer - 10, FROM_CONSOLE ); } /* ================= SCR_Sky_f Set a specific sky and rotation speed ================= */ static void SCR_Sky_f( void ) { float rotate = 0; vec3_t axis = { 0, 0, 1 }; int argc = Cmd_Argc(); if( argc < 2 ) { Com_Printf ("Usage: sky [rotate] [axis x y z]\n"); return; } if( argc > 2 ) rotate = atof(Cmd_Argv(2)); if( argc == 6 ) { axis[0] = atof(Cmd_Argv(3)); axis[1] = atof(Cmd_Argv(4)); axis[2] = atof(Cmd_Argv(5)); } R_SetSky (Cmd_Argv(1), rotate, axis); } /* ================ SCR_TimeRefresh_f ================ */ static void SCR_TimeRefresh_f (void) { int i; unsigned start, stop; float time; if( cls.state != ca_active ) { Com_Printf( "No map loaded.\n" ); return; } start = Sys_Milliseconds (); if (Cmd_Argc() == 2) { // run without page flipping R_BeginFrame(); for (i=0 ; i<128 ; i++) { cl.refdef.viewangles[1] = i/128.0f*360.0f; R_RenderFrame (&cl.refdef); } R_EndFrame(); } else { for (i=0 ; i<128 ; i++) { cl.refdef.viewangles[1] = i/128.0f*360.0f; R_BeginFrame(); R_RenderFrame (&cl.refdef); R_EndFrame(); } } stop = Sys_Milliseconds(); time = (stop-start)*0.001f; Com_Printf ("%f seconds (%f fps)\n", time, 128.0f/time); } //============================================================================ static void scr_crosshair_changed( cvar_t *self ) { char buffer[16]; if( self->integer > 0 ) { Q_snprintf( buffer, sizeof( buffer ), "ch%i", scr_crosshair->integer ); scr.crosshair_pic = R_RegisterPic( buffer ); R_GetPicSize( &scr.crosshair_width, &scr.crosshair_height, scr.crosshair_pic ); } else { scr.crosshair_pic = 0; } } void SCR_ModeChanged( void ) { R_GetConfig( &scr_glconfig ); IN_Activate(); #if USE_UI UI_ModeChanged(); #endif } /* ================== SCR_RegisterMedia ================== */ void SCR_RegisterMedia( void ) { int i, j; R_GetConfig( &scr_glconfig ); for( i = 0; i < 2; i++ ) for( j = 0; j < STAT_PICS; j++ ) scr.sb_pics[i][j] = R_RegisterPic( sb_nums[i][j] ); scr.inven_pic = R_RegisterPic( "inventory" ); scr.field_pic = R_RegisterPic( "field_3" ); scr.backtile_pic = R_RegisterPic( "backtile" ); scr.pause_pic = R_RegisterPic( "pause" ); R_GetPicSize( &scr.pause_width, &scr.pause_height, scr.pause_pic ); scr.loading_pic = R_RegisterPic( "loading" ); R_GetPicSize( &scr.loading_width, &scr.loading_height, scr.loading_pic ); scr.net_pic = R_RegisterPic( "net" ); scr.font_pic = R_RegisterFont( scr_font->string ); scr_crosshair_changed( scr_crosshair ); } static void scr_font_changed( cvar_t *self ) { scr.font_pic = R_RegisterFont( self->string ); } static const cmdreg_t scr_cmds[] = { { "timerefresh", SCR_TimeRefresh_f }, { "sizeup", SCR_SizeUp_f }, { "sizedown", SCR_SizeDown_f }, { "sky", SCR_Sky_f }, { "draw", SCR_Draw_f, SCR_Draw_c }, { "undraw", SCR_UnDraw_f, SCR_UnDraw_c }, { NULL } }; /* ================== SCR_Init ================== */ void SCR_Init( void ) { scr_viewsize = Cvar_Get ("viewsize", "100", CVAR_ARCHIVE); scr_showpause = Cvar_Get ("scr_showpause", "1", 0); scr_centertime = Cvar_Get ("scr_centertime", "2.5", 0); #ifdef _DEBUG 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); #endif scr_demobar = Cvar_Get( "scr_demobar", "1", CVAR_ARCHIVE ); scr_font = Cvar_Get( "scr_font", "conchars", CVAR_ARCHIVE ); scr_font->changed = scr_font_changed; scr_scale = Cvar_Get( "scr_scale", "1", CVAR_ARCHIVE ); scr_crosshair = Cvar_Get ("crosshair", "0", CVAR_ARCHIVE); scr_crosshair->changed = scr_crosshair_changed; scr_draw2d = Cvar_Get( "scr_draw2d", "2", 0 ); scr_showturtle = Cvar_Get( "scr_showturtle", "1", 0 ); scr_lag_x = Cvar_Get( "scr_lag_x", "-1", 0 ); scr_lag_y = Cvar_Get( "scr_lag_y", "-1", 0 ); scr_lag_draw = Cvar_Get( "scr_lag_draw", "0", 0 ); scr_lag_max = Cvar_Get( "scr_lag_max", "200", 0 ); scr_alpha = Cvar_Get( "scr_alpha", "1", 0 ); #ifdef _DEBUG scr_showstats = Cvar_Get( "scr_showstats", "0", 0 ); scr_showpmove = Cvar_Get( "scr_showpmove", "0", 0 ); #endif Cmd_Register( scr_cmds ); scr_glconfig.vidWidth = 640; scr_glconfig.vidHeight = 480; scr.initialized = qtrue; } void SCR_Shutdown( void ) { Cmd_Deregister( scr_cmds ); scr.initialized = qfalse; } //============================================================================= /* ================ SCR_BeginLoadingPlaque ================ */ void SCR_BeginLoadingPlaque( void ) { if( cls.disable_screen ) { return; } #ifdef _DEBUG if( developer->integer ) { return; } #endif // if at console or menu, don't bring up the plaque if( cls.key_dest & (KEY_CONSOLE|KEY_MENU) ) { return; } scr.draw_loading = qtrue; SCR_UpdateScreen(); cls.disable_screen = Sys_Milliseconds(); } /* ================ SCR_EndLoadingPlaque ================ */ void SCR_EndLoadingPlaque( void ) { cls.disable_screen = 0; Con_ClearNotify_f(); #if USE_CHATHUD SCR_ClearChatHUD_f(); #endif } // Clear any parts of the tiled background that were drawn on last frame static void tile_clear( void ) { int top, bottom, left, right; //if( con.currentHeight == 1 ) // return; // full screen console if( scr_viewsize->integer == 100 ) return; // full screen rendering top = scr_vrect.y; bottom = top + scr_vrect.height - 1; left = scr_vrect.x; right = left + scr_vrect.width - 1; // clear above view screen R_TileClear( 0, 0, scr_glconfig.vidWidth, top, scr.backtile_pic ); // clear below view screen R_TileClear( 0, bottom, scr_glconfig.vidWidth, scr_glconfig.vidHeight - bottom, scr.backtile_pic ); // clear left of view screen R_TileClear( 0, top, left, scr_vrect.height, scr.backtile_pic ); // clear right of view screen R_TileClear( right, top, scr_glconfig.vidWidth - right, scr_vrect.height, scr.backtile_pic ); } /* =============================================================================== STAT PROGRAMS =============================================================================== */ #define ICON_WIDTH 24 #define ICON_HEIGHT 24 #define DIGIT_WIDTH 16 #define ICON_SPACE 8 #define HUD_DrawString( x, y, string ) \ R_DrawString( x, y, 0, MAX_STRING_CHARS, string, scr.font_pic ) #define HUD_DrawAltString( x, y, string ) \ R_DrawString( x, y, UI_ALTCOLOR, MAX_STRING_CHARS, string, scr.font_pic ) #define HUD_DrawCenterString( x, y, string ) \ SCR_DrawStringMulti( x, y, UI_CENTER, MAX_STRING_CHARS, string, scr.font_pic ) #define HUD_DrawAltCenterString( x, y, string ) \ SCR_DrawStringMulti( x, y, UI_CENTER|UI_ALTCOLOR, MAX_STRING_CHARS, string, scr.font_pic ) static void HUD_DrawNumber( int x, int y, int color, int width, int value ) { char num[16], *ptr; int l; int frame; if( width < 1 ) return; // draw number string if( width > 5 ) width = 5; color &= 1; l = Q_scnprintf( num, sizeof( num ), "%i", value ); if( l > width ) l = width; x += 2 + DIGIT_WIDTH * ( width - l ); ptr = num; while( *ptr && l ) { if( *ptr == '-' ) frame = STAT_MINUS; else frame = *ptr - '0'; R_DrawPic( x, y, scr.sb_pics[color][frame] ); x += DIGIT_WIDTH; ptr++; l--; } } #define DISPLAY_ITEMS 17 static void draw_inventory( void ) { int i; int num, selected_num, item; int index[MAX_ITEMS]; char string[MAX_STRING_CHARS]; int x, y; char *bind; int selected; int top; selected = cl.frame.ps.stats[STAT_SELECTED_ITEM]; num = 0; selected_num = 0; for( i = 0; i < MAX_ITEMS; i++ ) { if( i == selected ) { selected_num = num; } if( cl.inventory[i] ) { index[num++] = i; } } // determine scroll point top = selected_num - DISPLAY_ITEMS / 2; if( top > num - DISPLAY_ITEMS ) { top = num - DISPLAY_ITEMS; } if( top < 0 ) { top = 0; } x = ( scr.hud_width - 256 ) / 2; y = ( scr.hud_height - 240 ) / 2; R_DrawPic( x, y + 8, scr.inven_pic ); y += 24; x += 24; HUD_DrawString( x, y, "hotkey ### item" ); y += CHAR_HEIGHT; HUD_DrawString( x, y, "------ --- ----" ); y += CHAR_HEIGHT; for( i = top; i < num && i < top + DISPLAY_ITEMS; i++ ) { item = index[i]; // search for a binding Q_concat( string, sizeof( string ), "use ", cl.configstrings[CS_ITEMS + item], NULL ); bind = Key_GetBinding( string ); Q_snprintf( string, sizeof( string ), "%6s %3i %s", bind, cl.inventory[item], cl.configstrings[CS_ITEMS + item] ); if( item != selected ) { HUD_DrawAltString( x, y, string ); } else { // draw a blinky cursor by the selected item HUD_DrawString( x, y, string ); if( ( cls.realtime >> 8 ) & 1 ) { R_DrawChar( x - CHAR_WIDTH, y, 0, 15, scr.font_pic ); } } y += CHAR_HEIGHT; } } static void draw_layout_string( const char *s ) { char buffer[MAX_QPATH]; int x, y; int value; char *token; int width; int index; clientinfo_t *ci; if( !s[0] ) return; x = 0; y = 0; width = 3; while( s ) { token = COM_Parse( &s ); if( token[2] == 0 ) { if( token[0] == 'x' ) { if( token[1] == 'l' ) { token = COM_Parse( &s ); x = atoi( token ); continue; } if( token[1] == 'r' ) { token = COM_Parse( &s ); x = scr.hud_width + atoi( token ); continue; } if( token[1] == 'v' ) { token = COM_Parse( &s ); x = scr.hud_width / 2 - 160 + atoi( token ); continue; } } if( token[0] == 'y' ) { if( token[1] == 't' ) { token = COM_Parse( &s ); y = atoi( token ); continue; } if( token[1] == 'b' ) { token = COM_Parse( &s ); y = scr.hud_height + atoi( token ); continue; } if( token[1] == 'v' ) { token = COM_Parse( &s ); y = scr.hud_height / 2 - 120 + atoi( token ); continue; } } } if( !strcmp( token, "pic" ) ) { // draw a pic from a stat number token = COM_Parse( &s ); value = atoi( token ); if( value < 0 || value >= MAX_STATS ) { Com_Error( ERR_DROP, "%s: invalid stat index", __func__ ); } value = cl.frame.ps.stats[value]; if( value < 0 || value >= MAX_IMAGES ) { Com_Error( ERR_DROP, "%s: invalid pic index", __func__ ); } token = cl.configstrings[CS_IMAGES + value]; if( token[0] ) { R_DrawPic( x, y, R_RegisterPic( token ) ); } continue; } if( !strcmp( token, "client" ) ) { // draw a deathmatch client block int score, ping, time; token = COM_Parse( &s ); x = scr.hud_width / 2 - 160 + atoi( token ); token = COM_Parse( &s ); y = scr.hud_height / 2 - 120 + atoi( token ); token = COM_Parse( &s ); value = atoi( token ); if( value < 0 || value >= MAX_CLIENTS ) { Com_Error( ERR_DROP, "%s: invalid client index", __func__ ); } ci = &cl.clientinfo[value]; token = COM_Parse( &s ); score = atoi( token ); token = COM_Parse( &s ); ping = atoi( token ); token = COM_Parse( &s ); time = atoi( token ); HUD_DrawString( x + 32, y, ci->name ); Q_snprintf( buffer, sizeof( buffer ), "Score: %i", score ); HUD_DrawString( x + 32, y + CHAR_HEIGHT, buffer ); Q_snprintf( buffer, sizeof( buffer ), "Ping: %i", ping ); HUD_DrawString( x + 32, y + 2 * CHAR_HEIGHT, buffer ); Q_snprintf( buffer, sizeof( buffer ), "Time: %i", time ); HUD_DrawString( x + 32, y + 3 * CHAR_HEIGHT, buffer ); if( !ci->icon ) { ci = &cl.baseclientinfo; } R_DrawPic( x, y, ci->icon ); continue; } if( !strcmp( token, "ctf" ) ) { // draw a ctf client block int score, ping; token = COM_Parse( &s ); x = scr.hud_width / 2 - 160 + atoi( token ); token = COM_Parse( &s ); y = scr.hud_height / 2 - 120 + atoi( token ); token = COM_Parse( &s ); value = atoi( token ); if( value < 0 || value >= MAX_CLIENTS ) { Com_Error( ERR_DROP, "%s: invalid client index", __func__ ); } ci = &cl.clientinfo[value]; token = COM_Parse( &s ); score = atoi( token ); token = COM_Parse( &s ); ping = atoi( token ); if( ping > 999 ) ping = 999; Q_snprintf( buffer, sizeof( buffer ), "%3d %3d %-12.12s", score, ping, ci->name ); if( value == cl.frame.clientNum ) { HUD_DrawAltString( x, y, buffer ); } else { HUD_DrawString( x, y, buffer ); } continue; } if( !strcmp( token, "picn" ) ) { // draw a pic from a name token = COM_Parse( &s ); R_DrawPic( x, y, R_RegisterPic( token ) ); continue; } if( !strcmp( token, "num" ) ) { // draw a number token = COM_Parse( &s ); width = atoi( token ); token = COM_Parse( &s ); value = atoi( token ); if( value < 0 || value >= MAX_STATS ) { Com_Error( ERR_DROP, "%s: invalid stat index", __func__ ); } value = cl.frame.ps.stats[value]; HUD_DrawNumber( x, y, 0, width, value ); continue; } if( !strcmp( token, "hnum" ) ) { // health number int color; width = 3; value = cl.frame.ps.stats[STAT_HEALTH]; if( value > 25 ) color = 0; // green else if( value > 0 ) color = ( cl.frame.number >> 2 ) & 1; // flash else color = 1; if( cl.frame.ps.stats[STAT_FLASHES] & 1 ) R_DrawPic( x, y, scr.field_pic ); HUD_DrawNumber( x, y, color, width, value ); continue; } if( !strcmp( token, "anum" ) ) { // ammo number int color; width = 3; value = cl.frame.ps.stats[STAT_AMMO]; if( value > 5 ) color = 0; // green else if( value >= 0 ) color = ( cl.frame.number >> 2 ) & 1; // flash else continue; // negative number = don't show if( cl.frame.ps.stats[STAT_FLASHES] & 4 ) R_DrawPic( x, y, scr.field_pic ); HUD_DrawNumber( x, y, color, width, value ); continue; } if( !strcmp( token, "rnum" ) ) { // armor number int color; width = 3; value = cl.frame.ps.stats[STAT_ARMOR]; if( value < 1 ) continue; color = 0; // green if( cl.frame.ps.stats[STAT_FLASHES] & 2 ) R_DrawPic( x, y, scr.field_pic ); HUD_DrawNumber( x, y, color, width, value ); continue; } if( !strcmp( token, "stat_string" ) ) { token = COM_Parse( &s ); index = atoi( token ); if( index < 0 || index >= MAX_STATS ) { Com_Error( ERR_DROP, "%s: invalid stat index", __func__ ); } index = cl.frame.ps.stats[index]; if( index < 0 || index >= MAX_CONFIGSTRINGS ) { Com_Error( ERR_DROP, "%s: invalid string index", __func__ ); } HUD_DrawString( x, y, cl.configstrings[index] ); continue; } if( !strcmp( token, "cstring" ) ) { token = COM_Parse( &s ); HUD_DrawCenterString( x + 320 / 2, y, token ); continue; } if( !strcmp( token, "cstring2" ) ) { token = COM_Parse( &s ); HUD_DrawAltCenterString( x + 320 / 2, y, token ); continue; } if( !strcmp( token, "string" ) ) { token = COM_Parse( &s ); HUD_DrawString( x, y, token ); continue; } if( !strcmp( token, "string2" ) ) { token = COM_Parse( &s ); HUD_DrawAltString( x, y, token ); continue; } if( !strcmp( token, "if" ) ) { token = COM_Parse( &s ); value = atoi( token ); if( value < 0 || value >= MAX_STATS ) { Com_Error( ERR_DROP, "%s: invalid stat index", __func__ ); } value = cl.frame.ps.stats[value]; if( !value ) { // skip to endif while( strcmp( token, "endif" ) ) { token = COM_Parse( &s ); if( !s ) { break; } } } continue; } } } static void draw_pause( void ) { int x, y; if( !sv_paused->integer ) { return; } if( !cl_paused->integer ) { return; } if( !scr_showpause->integer ) { // turn off for screenshots return; } x = ( scr.hud_width - scr.pause_width ) / 2; y = ( scr.hud_height - scr.pause_height ) / 2; R_DrawPic( x, y, scr.pause_pic ); } static void draw_loading( void ) { int x = ( scr_glconfig.vidWidth - scr.loading_width ) / 2; int y = ( scr_glconfig.vidHeight - scr.loading_height ) / 2; R_DrawPic( x, y, scr.loading_pic ); } static void draw_crosshair( void ) { int x = ( scr.hud_width - scr.crosshair_width ) / 2; int y = ( scr.hud_height - scr.crosshair_height ) / 2; R_DrawPic( x, y, scr.crosshair_pic ); } static void draw_2d( void ) { #if USE_REF == REF_SOFT clipRect_t rc; // avoid DoS by making sure nothing is drawn out of bounds rc.left = 0; rc.top = 0; rc.right = scr.hud_width; rc.bottom = scr.hud_height; R_SetClipRect( DRAW_CLIP_MASK, &rc ); #endif R_SetColor( DRAW_COLOR_CLEAR, NULL ); if( scr_crosshair->integer ) { draw_crosshair(); } Cvar_ClampValue( scr_alpha, 0, 1 ); R_SetColor( DRAW_COLOR_ALPHA, ( byte * )&scr_alpha->value ); if( scr_draw2d->integer > 1 ) { draw_layout_string( cl.configstrings[CS_STATUSBAR] ); } if( ( cl.frame.ps.stats[STAT_LAYOUTS] & 1 ) || ( cls.demo.playback && Key_IsDown( K_F1 ) ) ) { draw_layout_string( cl.layout ); } if( cl.frame.ps.stats[STAT_LAYOUTS] & 2 ) { draw_inventory(); } draw_center_string(); draw_objects(); draw_lagometer(); R_SetColor( DRAW_COLOR_CLEAR, NULL ); if( scr_showturtle->integer && cl.frameflags ) { draw_turtle(); } #ifdef _DEBUG if( scr_showstats->integer ) { draw_stats(); } if( scr_showpmove->integer ) { draw_pmove(); } #endif draw_pause(); #if USE_REF == REF_SOFT R_SetClipRect( DRAW_CLIP_DISABLED, NULL ); #endif } static void draw_active_frame( void ) { float scale; if( cls.state < ca_active ) { // draw black background if not active R_DrawFill( 0, 0, scr_glconfig.vidWidth, scr_glconfig.vidHeight, 0 ); return; } scr.hud_height = scr_glconfig.vidHeight; scr.hud_width = scr_glconfig.vidWidth; draw_demo_bar(); calc_vrect(); // clear any dirty part of the background tile_clear(); // draw 3D game view V_RenderView(); if( scr_scale->value != 1 ) { scale = 1.0f / Cvar_ClampValue( scr_scale, 1, 9 ); R_SetScale( &scale ); scr.hud_height *= scale; scr.hud_width *= scale; } // draw all 2D elements if( scr_draw2d->integer && !( cls.key_dest & KEY_MENU ) ) { draw_2d(); } R_SetScale( NULL ); } //======================================================= /* ================== SCR_UpdateScreen This is called every frame, and can also be called explicitly to flush text to the screen. ================== */ void SCR_UpdateScreen( void ) { static int recursive; if( !scr.initialized ) { return; // not initialized yet } // if the screen is disabled (loading plaque is up), do nothing at all if( cls.disable_screen ) { unsigned delta = Sys_Milliseconds() - cls.disable_screen; if( delta < 120*1000 ) { return; } cls.disable_screen = 0; Com_Printf( "Loading plaque timed out.\n" ); } if( recursive > 1 ) { Com_Error( ERR_FATAL, "%s: recursively called", __func__ ); } recursive++; R_BeginFrame(); #if USE_UI if( UI_IsTransparent() ) { // do 3D refresh drawing draw_active_frame(); } // draw main menu UI_Draw( cls.realtime ); #else // do 3D refresh drawing draw_active_frame(); #endif // draw console Con_DrawConsole(); // draw loading plaque if( scr.draw_loading ) { draw_loading(); scr.draw_loading = qfalse; } #ifdef _DEBUG // draw debug graphs if( scr_timegraph->integer ) SCR_DebugGraph( cls.frametime*300, 0 ); if( scr_debuggraph->integer || scr_timegraph->integer || scr_netgraph->integer ) { SCR_DrawDebugGraph(); } #endif R_EndFrame(); recursive--; }