/* 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. */ // console.c #include "client.h" console_t con; cvar_t *con_notifytime; cvar_t *con_drawNotify; cvar_t *con_printText; #define MAXCMDLINE 256 extern char key_lines[32][MAXCMDLINE]; extern int edit_line; extern int key_linepos; void Key_ClearTyping (void) { key_lines[edit_line][1] = 0; // clear any typing key_linepos = 1; } //================ //Con_Close //================ void Con_Close( void ) { scr_con_current = 0; Key_ClearTyping(); Con_ClearNotify(); Key_ClearStates(); if( !Cvar_VariableValue( "vid_fullscreen" ) && !Cvar_VariableValue( "in_grabinconsole" ) ) IN_Activate( qtrue ); } //================ //Con_ToggleConsole_f //================ void Con_ToggleConsole_f( void ) { SCR_EndLoadingPlaque(); // get rid of loading plaque if( cls.state == CA_CONNECTING || cls.state == CA_CONNECTED ) return; Key_ClearTyping(); Con_ClearNotify(); if( cls.key_dest == key_console ) { // close console CL_SetKeyDest( cls.old_key_dest ); if( !Cvar_VariableValue( "vid_fullscreen" ) && !Cvar_VariableValue( "in_grabinconsole" ) ) IN_Activate( qtrue ); } else { // open console CL_SetOldKeyDest( cls.key_dest ); CL_SetKeyDest( key_console ); if( !Cvar_VariableValue( "vid_fullscreen" ) && !Cvar_VariableValue( "in_grabinconsole" ) ) IN_Activate( qfalse ); } } //================ //Con_Clear_f //================ void Con_Clear_f( void ) { memset( con.text, ' ', CON_TEXTSIZE ); con.display = con.current; } //================ //Con_Dump_f // //Save the console contents out to a file //================ void Con_Dump_f( void ) { int l, x; char *line; int file; char buffer[MAX_PRINTMSG]; char name[MAX_OSPATH]; if( Cmd_Argc() != 2 ) { Com_Printf( "usage: condump \n" ); return; } Q_snprintfz( name, sizeof(name), "%s.txt", Cmd_Argv(1) ); Com_Printf( "Dumped console text to %s.\n", name ); if( FS_FOpenFile( name, &file, FS_WRITE ) == -1 ) { Com_Printf("ERROR: couldn't open.\n"); return; } // skip empty lines for( l = con.current - con.totallines + 1; l <= con.current; l++ ) { line = con.text + (l%con.totallines)*con.linewidth; for( x = 0; x < con.linewidth; x++ ) if( line[x] != ' ' ) break; if( x != con.linewidth ) break; } // write the remaining lines buffer[con.linewidth] = 0; for( ; l <= con.current; l++ ) { line = con.text + (l%con.totallines)*con.linewidth; strncpy( buffer, line, con.linewidth ); for( x = con.linewidth-1; x >= 0; x-- ) { if( buffer[x] == ' ' ) buffer[x] = 0; else break; } for( x = 0; buffer[x]; x++ ) buffer[x] &= 0x7f; FS_Printf( file, "%s\n", buffer ); } FS_FCloseFile( file ); } //================ //Con_ClearNotify //================ void Con_ClearNotify( void ) { int i; for( i = 0; i < NUM_CON_TIMES; i++ ) con.times[i] = 0; } //================ //Con_MessageMode_f //================ void Con_MessageMode_f( void ) { chat_team = qfalse; if( cls.state == CA_ACTIVE ) CL_SetKeyDest( key_message ); } //================ //Con_MessageMode2_f //================ void Con_MessageMode2_f( void ) { chat_team = qtrue; if( cls.state == CA_ACTIVE ) CL_SetKeyDest( key_message ); } //================ //Con_CheckResize // //If the line width has changed, reformat the buffer. //================ void Con_CheckResize( void ) { int i, j, width, oldwidth, oldtotallines, numlines, numchars; char tbuf[CON_TEXTSIZE]; width = viddef.width / SMALL_CHAR_WIDTH - 2; if( width == con.linewidth ) return; if( width < 1 ) // video hasn't been initialized yet { width = 78; con.linewidth = width; con.totallines = CON_TEXTSIZE / con.linewidth; memset( con.text, ' ', CON_TEXTSIZE ); } else { oldwidth = con.linewidth; con.linewidth = width; oldtotallines = con.totallines; con.totallines = CON_TEXTSIZE / con.linewidth; numlines = oldtotallines; if( con.totallines < numlines ) numlines = con.totallines; numchars = oldwidth; if( con.linewidth < numchars ) numchars = con.linewidth; memcpy( tbuf, con.text, CON_TEXTSIZE ); memset( con.text, ' ', CON_TEXTSIZE ); for( i = 0; i < numlines; i++ ) { for( j = 0; j < numchars; j++ ) { con.text[(con.totallines - 1 - i) * con.linewidth + j] = tbuf[((con.current - i + oldtotallines) % oldtotallines) * oldwidth + j]; } } Con_ClearNotify(); } con.current = con.totallines - 1; con.display = con.current; } //================ //Con_Init //================ void Con_Init( void ) { con.linewidth = -1; Con_CheckResize(); Com_Printf("Console initialized.\n"); // // register our commands // con_notifytime = Cvar_Get( "con_notifytime", "3", CVAR_ARCHIVE ); con_drawNotify = Cvar_Get( "con_drawNotify", "1", CVAR_ARCHIVE ); con_printText = Cvar_Get( "con_printText", "1", CVAR_ARCHIVE ); Cmd_AddCommand( "toggleconsole", Con_ToggleConsole_f ); Cmd_AddCommand( "messagemode", Con_MessageMode_f ); Cmd_AddCommand( "messagemode2", Con_MessageMode2_f ); Cmd_AddCommand( "clear", Con_Clear_f ); Cmd_AddCommand( "condump", Con_Dump_f ); con.initialized = qtrue; } //=============== //Con_Linefeed //=============== void Con_Linefeed( void ) { con.x = 0; if( con.display/* == con.current*/ ) con.display++; con.current++; memset( &con.text[(con.current%con.totallines)*con.linewidth], ' ', con.linewidth ); } //================ //Con_Print // //Handles cursor positioning, line wrapping, etc //All console printing must go through this in order to be logged to disk //If no console is visible, the text will appear at the top of the game window //================ void Con_Print( char *txt ) { int y; int c, l; static int cr; int color; if( !con.initialized ) return; if(con_printText->integer==0) return; color = ColorIndex( COLOR_WHITE ); while( (c = *txt) ) { // count word length for( l = 0; l < con.linewidth; l++ ) if( txt[l] <= ' ' ) break; // word wrap if( l != con.linewidth && (con.x + l > con.linewidth) ) con.x = 0; if( cr ) { con.current--; cr = qfalse; } if( !con.x ) { Con_Linefeed(); // mark time for transparent overlay if( con.current >= 0 ) con.times[con.current % NUM_CON_TIMES] = cls.realtime; y = con.current % con.totallines; if( color != ColorIndex( COLOR_WHITE ) ) { con.text[y*con.linewidth] = '^'; con.text[y*con.linewidth+1] = '0' + color; con.x += 2; } } switch( c ) { case '\n': color = ColorIndex( COLOR_WHITE ); con.x = 0; break; case '\r': color = ColorIndex( COLOR_WHITE ); con.x = 0; cr = 1; break; default: // display character and advance y = con.current % con.totallines; con.text[y*con.linewidth+con.x] = c; con.x++; if( con.x >= con.linewidth ) con.x = 0; if( Q_IsColorString( txt ) ) { color = ColorIndex( *(txt+1) ); } break; } txt++; } } /* ============================================================================== DRAWING ============================================================================== */ //================ //Con_DrawConsoleLine //Console lines need special handling cause they aren't zero terminated //================ void Con_DrawConsoleLine( int x, int y, int align, const char *str, size_t len, struct mufont_s *font, vec4_t color ) { static char consoleString[MAX_STRING_CHARS]; Q_strncpyz( consoleString, str, len + 1 ); SCR_DrawStringLen( x, y, align, consoleString, len, font, color ); } int Q_ColorCharCount( char *s, int byteofs ) { int c; char *end; end = s + byteofs; for( c = 0; *s && s < end; s++, c++ ) if( Q_IsColorString(s) ) s++, c--; return c; } int Q_ColorCharOffset( char *s, int charcount ) { char *start = s; for( ; *s && charcount; s++ ) { if( Q_IsColorString(s) ) s++; else charcount--; } return s - start; } int Q_ColorStrLastColor( char *s, int byteofs ) { int c; char *end; int lastcolor = ColorIndex(COLOR_WHITE); end = s + byteofs - 1; // don't check last byte for( c = 0; s < end && *s; s++, c++ ) if( Q_IsColorString(s) ) { lastcolor = ColorIndex(s[1]); s++, c--; } return lastcolor; } //================ //Con_DrawInput // //The input line scrolls horizontally if typing goes beyond the right edge //================ void Con_DrawInput( void ) { char *text; extern qboolean key_insert; int colorlinepos; int startcolor = ColorIndex(COLOR_WHITE); int byteofs; int bytelen; int smallCharHeight = SCR_strHeight( cls.fontSystemSmall ); if( cls.key_dest != key_console ) return; text = key_lines[edit_line]; // convert byte offset to visible character count colorlinepos = Q_ColorCharCount( text, key_linepos ); // prestep if horizontally scrolling if( colorlinepos >= con.linewidth + 1 ) { byteofs = Q_ColorCharOffset( text, colorlinepos - con.linewidth ); startcolor = Q_ColorStrLastColor( text, byteofs ); text += byteofs; colorlinepos = con.linewidth; } // draw it bytelen = Q_ColorCharOffset( text, con.linewidth ); SCR_DrawStringLen( 8, con.vislines-smallCharHeight-14, ALIGN_LEFT_TOP, text, bytelen, cls.fontSystemSmall, color_table[startcolor] ); // add the cursor frame if( (int)(cls.realtime>>8)&1 ) { int widthoffset; if( key_insert ) { char keychar; keychar = text[key_linepos]; text[key_linepos] = 0; widthoffset = 8+SCR_strWidth( text, cls.fontSystemSmall, 0 ); text[key_linepos] = keychar; } else { widthoffset = 8+SCR_strWidth( text, cls.fontSystemSmall, 0 ); } SCR_DrawRawChar( widthoffset, con.vislines-smallCharHeight-14, key_insert ? '_' : 11, cls.fontSystemSmall, colorWhite ); } } //================ //Con_DrawNotify // //Draws the last few lines of output transparently over the game top //================ void Con_DrawNotify( void ) { int v; char *text; int i; int time; char *s; int skip; unsigned int charbuffer_width; v = 0; if( con_drawNotify->integer ) { for( i= con.current-NUM_CON_TIMES+1 ; i<=con.current ; i++ ) { if( i < 0 ) continue; time = con.times[i % NUM_CON_TIMES]; if( time == 0 ) continue; time = cls.realtime - time; if( time > con_notifytime->value*1000 ) continue; text = con.text + (i % con.totallines)*con.linewidth; Con_DrawConsoleLine( 8, v, ALIGN_LEFT_TOP, text, con.linewidth, cls.fontSystemSmall, colorWhite ); v += SCR_strHeight( cls.fontSystemSmall ); } } if( cls.key_dest == key_message ) { if( chat_team ) { SCR_DrawString( 8, v, ALIGN_LEFT_TOP, "say_team:", cls.fontSystemSmall, colorWhite ); skip = 8 + SCR_strWidth( "say_team: ", cls.fontSystemSmall, 0 ); } else { SCR_DrawString( 8, v, ALIGN_LEFT_TOP, "say:", cls.fontSystemSmall, colorWhite ); skip = 8 + SCR_strWidth( "say: ", cls.fontSystemSmall, 0 ); } s = chat_buffer; charbuffer_width = SCR_strWidth( s, cls.fontSystemSmall, chat_bufferlen+1 ); while( charbuffer_width > viddef.width - (skip+72) ) { // 72 is an arbitrary offset for not overlapping the FPS and clock prints s++; charbuffer_width = SCR_strWidth( s, cls.fontSystemSmall, chat_bufferlen+1 ); } SCR_DrawString( skip, v, ALIGN_LEFT_TOP, s, cls.fontSystemSmall, colorWhite ); skip += SCR_strWidth( s, cls.fontSystemSmall, 0 ); SCR_DrawRawChar( skip, v, ((cls.realtime>>8)&1)?'_':' ', cls.fontSystemSmall, colorWhite ); v += SCR_strHeight( cls.fontSystemSmall ); } } //================ //Con_DrawConsole // //Draws the console with the solid background //================ void Con_DrawConsole( float frac ) { int i, x, y; int rows; char *text; int row; unsigned int lines; char version[256]; time_t long_time; struct tm *newtime; int smallCharHeight = SCR_strHeight( cls.fontSystemSmall ); lines = viddef.height * frac; if (lines <= 0) return; if( lines > viddef.height ) lines = viddef.height; // draw the background R_DrawStretchPic( 0, 0, viddef.width, lines, 0, 0, 1, 1, colorWhite, cls.consoleShader ); SCR_DrawFillRect( 0, lines - 2, viddef.width, 2, colorRed ); // get date from system time( &long_time ); newtime = localtime( &long_time ); Q_snprintfz( version, sizeof(version), "%02d:%02d %s v%4.2f", newtime->tm_hour,newtime->tm_min, APPLICATION,VERSION ); SCR_DrawString( viddef.width-SCR_strWidth(version,cls.fontSystemSmall, 0)-4, lines-20, ALIGN_LEFT_TOP, version, cls.fontSystemSmall, colorRed ); // draw the text con.vislines = lines; rows = (lines-smallCharHeight-14) / smallCharHeight; // rows of text to draw y = lines - smallCharHeight-14-smallCharHeight; // draw from the bottom up if( con.display != con.current ) { // draw arrows to show the buffer is backscrolled for( x = 0; x < con.linewidth; x += 4 ) SCR_DrawRawChar( (x+1)*SCR_strWidth("^",cls.fontSystemSmall, 0), y, '^', cls.fontSystemSmall, colorRed ); y -= smallCharHeight; rows--; } row = con.display; for( i=0 ; i= con.totallines ) break; // past scrollback wrap point text = con.text + (row % con.totallines)*con.linewidth; Con_DrawConsoleLine( 8, y, ALIGN_LEFT_TOP, text, con.linewidth, cls.fontSystemSmall, colorWhite ); } // draw the input prompt, user text, and cursor if desired Con_DrawInput(); } //================ //Con_DisplayList //New function for tab-completion system //Added by EvilTypeGuy //MEGA Thanks to Taniwha //================ void Con_DisplayList( char **list ) { int i = 0; int pos = 0; int len = 0; int maxlen = 0; int width = (con.linewidth - 4); char **walk = list; while( *walk ) { len = (int)strlen( *walk ); if( len > maxlen ) maxlen = len; walk++; } maxlen += 1; while( *list ) { len = (int)strlen(*list); if( pos + maxlen >= width ) { Com_Printf( "\n" ); pos = 0; } Com_Printf( "%s", *list ); for( i = 0; i < (maxlen - len); i++ ) Com_Printf( " " ); pos += maxlen; list++; } if( pos ) Com_Printf( "\n\n" ); } //================ //Con_CompleteCommandLine //New function for tab-completion system //Added by EvilTypeGuy //Thanks to Fett erich@heintz.com //Thanks to taniwha //================ void Con_CompleteCommandLine( void ) { char *cmd = ""; char *s; int c, v, a, i; int cmd_len; char **list[3] = { 0, 0, 0 }; s = key_lines[edit_line] + 1; if( *s == '\\' || *s == '/' ) s++; if( !*s ) return; // Count number of possible matches c = Cmd_CompleteCountPossible(s); v = Cvar_CompleteCountPossible(s); a = Cmd_CompleteAliasCountPossible(s); if( !(c + v + a) ) { // No possible matches, let the user know they're insane Com_Printf("\nNo matching aliases, commands, or cvars were found.\n\n"); return; } if( c + v + a == 1 ) { if( c ) list[0] = Cmd_CompleteBuildList(s); else if (v) list[0] = Cvar_CompleteBuildList(s); else list[0] = Cmd_CompleteAliasBuildList(s); cmd = *list[0]; cmd_len = (int)strlen (cmd); } else { if( c ) cmd = *( list[0] = Cmd_CompleteBuildList(s) ); if( v ) cmd = *( list[1] = Cvar_CompleteBuildList(s) ); if( a ) cmd = *( list[2] = Cmd_CompleteAliasBuildList(s) ); cmd_len = (int)strlen( s ); do { for( i = 0; i < 3; i++ ) { char ch = cmd[cmd_len]; char **l = list[i]; if( l ) { while( *l && (*l)[cmd_len] == ch ) l++; if( *l ) break; } } if( i == 3 ) cmd_len++; } while( i == 3 ); // Print Possible Commands if( c ) { Com_Printf( S_COLOR_RED "%i possible command%s%s\n", c, (c > 1) ? "s: " : ":", S_COLOR_WHITE ); Con_DisplayList( list[0] ); } if( v ) { Com_Printf( S_COLOR_RED "%i possible variable%s%s\n", v, (v > 1) ? "s: " : ":", S_COLOR_WHITE ); Con_DisplayList( list[1] ); } if( a ) { Com_Printf( S_COLOR_RED "%i possible alias%s%s\n", a, (a > 1) ? "es: " : ":", S_COLOR_WHITE ); Con_DisplayList( list[2] ); } } if( cmd ) { Q_strncpyz( key_lines[edit_line] + 1, cmd, sizeof(key_lines[edit_line]) ); key_linepos = cmd_len + 1; if( c + v + a == 1 ) { key_lines[edit_line][key_linepos] = ' '; key_linepos++; } key_lines[edit_line][key_linepos] = 0; } for( i = 0; i < 3; i++ ) { if( list[i] ) Mem_TempFree( list[i] ); } }