diff options
Diffstat (limited to 'src/cl_scrn.c')
-rw-r--r-- | src/cl_scrn.c | 1804 |
1 files changed, 1804 insertions, 0 deletions
diff --git a/src/cl_scrn.c b/src/cl_scrn.c new file mode 100644 index 0000000..a14909c --- /dev/null +++ b/src/cl_scrn.c @@ -0,0 +1,1804 @@ +/* +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; + color_t crosshair_color; + + 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; + +static cvar_t *ch_red; +static cvar_t *ch_green; +static cvar_t *ch_blue; +static cvar_t *ch_alpha; + +#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 ; i<cls.netchan->dropped ; i++) + SCR_DebugGraph (30, 0x40); + + //for (i=0 ; i<cl.surpressCount ; i++) + // SCR_DebugGraph (30, 0xdf); + + // see what the latency was on this packet + in = cls.netchan->incoming_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 ; a<w ; a++) + { + i = (current-1-a+1024) & 1023; + v = values[i].value; + color = values[i].color; + v = v*scr_graphscale->value + 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 <name> <x> <y> [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 <name>\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 <basename> [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( scr_crosshair->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 ); + + scr.crosshair_color[0] = (byte)(ch_red->value * 255); + scr.crosshair_color[1] = (byte)(ch_green->value * 255); + scr.crosshair_color[2] = (byte)(ch_blue->value * 255); + scr.crosshair_color[3] = (byte)(ch_alpha->value * 255); + } 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; + + ch_red = Cvar_Get ("ch_red", "1", 0); + ch_red->changed = scr_crosshair_changed; + ch_green = Cvar_Get ("ch_green", "1", 0); + ch_green->changed = scr_crosshair_changed; + ch_blue = Cvar_Get ("ch_blue", "1", 0); + ch_blue->changed = scr_crosshair_changed; + ch_alpha = Cvar_Get ("ch_alpha", "1", 0); + ch_alpha->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_SetColor( DRAW_COLOR_RGBA, scr.crosshair_color ); + R_DrawPic( x, y, scr.crosshair_pic ); + R_SetColor( DRAW_COLOR_CLEAR, NULL ); +} + +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--; +} + + |