/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2000-2013 Darklegion Development Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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 3 of the License, or (at your option) any later version. Tremulous 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 Tremulous; if not, see =========================================================================== */ // console.c #include "client.h" #include "qcommon/cdefs.h" int g_console_field_width = 78; #define NUM_CON_TIMES 4 #define CON_TEXTSIZE 163840 typedef struct { bool initialized; short text[CON_TEXTSIZE]; int current; // line where next message will be printed int x; // offset in current line for next print int display; // bottom of console displays this line int linewidth; // characters across screen int totallines; // total lines in console scrollback float xadjust; // for wide aspect screens float displayFrac; // aproaches finalFrac at scr_conspeed float finalFrac; // 0.0 to 1.0 lines of console to display int vislines; // in scanlines vec4_t color; } console_t; console_t con; cvar_t *con_conspeed; cvar_t *con_height; cvar_t *con_useShader; cvar_t *con_colorRed; cvar_t *con_colorGreen; cvar_t *con_colorBlue; cvar_t *con_colorAlpha; cvar_t *con_versionStr; #define DEFAULT_CONSOLE_WIDTH 78 /* ================ Con_ToggleConsole_f ================ */ void Con_ToggleConsole_f (void) { // Can't toggle the console when it's the only thing available if ( clc.state == CA_DISCONNECTED && Key_GetCatcher( ) == KEYCATCH_CONSOLE ) { return; } Field_Clear( &g_consoleField ); g_consoleField.widthInChars = g_console_field_width; Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_CONSOLE ); } /* =================== Con_ToggleMenu_f =================== */ void Con_ToggleMenu_f( void ) { CL_KeyEvent( K_ESCAPE, true, Sys_Milliseconds() ); CL_KeyEvent( K_ESCAPE, false, Sys_Milliseconds() ); } /* =================== Con_MessageMode_f =================== -*/ void Con_MessageMode_f (void) { chat_playerNum = -1; chat_team = false; chat_admins = false; chat_clans = false; Field_Clear( &chatField ); chatField.widthInChars = 30; Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE ); } /* ==================== Con_MessageMode2_f ==================== */ void Con_MessageMode2_f (void) { chat_playerNum = -1; chat_team = true; chat_admins = false; chat_clans = false; Field_Clear( &chatField ); chatField.widthInChars = 25; Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE ); } /* =================== Con_MessageMode3_f =================== */ void Con_MessageMode3_f (void) { chat_playerNum = VM_Call( cls.cgame, CG_CROSSHAIR_PLAYER ); if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) { chat_playerNum = -1; return; } chat_team = false; chat_admins = false; chat_clans = false; Field_Clear( &chatField ); chatField.widthInChars = 30; Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE ); } /* ===================== Con_MessageMode4_f ===================== */ void Con_MessageMode4_f (void) { chat_playerNum = VM_Call( cls.cgame, CG_LAST_ATTACKER ); if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) { chat_playerNum = -1; return; } chat_team = false; chat_admins = false; chat_clans = false; Field_Clear( &chatField ); chatField.widthInChars = 30; Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE ); } /* ================ Con_MessageMode5_f ================ */ void Con_MessageMode5_f (void) { int i; chat_playerNum = -1; chat_team = false; chat_admins = true; chat_clans = false; Field_Clear( &chatField ); chatField.widthInChars = 25; Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE ); } /* ================ Con_MessageMode6_f ================ */ void Con_MessageMode6_f (void) { int i; chat_playerNum = -1; chat_team = false; chat_admins = false; chat_clans = true; Field_Clear( &chatField ); chatField.widthInChars = 25; Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE ); } /* ================ Con_Clear_f ================ */ void Con_Clear_f (void) { int i; for ( i = 0 ; i < CON_TEXTSIZE ; i++ ) { con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' '; } Con_Bottom(); // go to end } /* ================ Con_Dump_f Save the console contents out to a file ================ */ void Con_Dump_f (void) { int l, x, i; short *line; fileHandle_t f; int bufferlen; char *buffer; char filename[MAX_QPATH]; if (Cmd_Argc() != 2) { Com_Printf ("usage: condump \n"); return; } Q_strncpyz( filename, Cmd_Argv( 1 ), sizeof( filename ) ); COM_DefaultExtension( filename, sizeof( filename ), ".txt" ); if (!COM_CompareExtension(filename, ".txt")) { Com_Printf("Con_Dump_f: Only the \".txt\" extension is supported by this command!\n"); return; } f = FS_FOpenFileWrite( filename ); if (!f) { Com_Printf("ERROR: couldn't open %s.\n", filename); return; } Com_Printf("Dumped console text to %s.\n", filename ); // 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=0 ; x--) { if (buffer[x] == ' ') buffer[x] = 0; else break; } #ifdef _WIN32 Q_strcat(buffer, bufferlen, "\r\n"); #else Q_strcat(buffer, bufferlen, "\n"); #endif FS_Write(buffer, strlen(buffer), f); } Hunk_FreeTempMemory( buffer ); FS_FCloseFile( f ); } /* ================ Con_ClearNotify ================ */ void Con_ClearNotify( void ) { Cmd_TokenizeString( NULL ); CL_GameConsoleText( ); } /* ================ Con_CheckResize If the line width has changed, reformat the buffer. ================ */ void Con_CheckResize (void) { int i, j, width, oldwidth, oldtotallines, numlines, numchars; short tbuf[CON_TEXTSIZE]; width = (SCREEN_WIDTH / SMALLCHAR_WIDTH) - 2; if (width == con.linewidth) return; if (width < 1) // video hasn't been initialized yet { width = DEFAULT_CONSOLE_WIDTH; con.linewidth = width; con.totallines = CON_TEXTSIZE / con.linewidth; for(i=0; iinteger ) return; if (!con.initialized) { con.color[0] = con.color[1] = con.color[2] = con.color[3] = 1.0f; con.linewidth = -1; Con_CheckResize (); con.initialized = true; } if( !skipnotify && !(Key_GetCatcher( ) & KEYCATCH_CONSOLE) ) { Cmd_SaveCmdContext( ); // feed the text to cgame Cmd_TokenizeString( txt ); CL_GameConsoleText( ); Cmd_RestoreCmdContext( ); } color = ColorIndex(COLOR_WHITE); while ( (c = *((unsigned char *)txt)) != 0 ) { if ( Q_IsColorString( txt ) ) { color = ColorIndex( *(txt+1) ); txt += 2; continue; } // 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_Linefeed(); txt++; switch (c) { case INDENT_MARKER: break; case '\n': Con_Linefeed(); break; case '\r': con.x = 0; break; default: // display character and advance y = con.current % con.totallines; con.text[y*con.linewidth+con.x] = (color << 8) | c; con.x++; if(con.x >= con.linewidth) Con_Linefeed(); break; } } } /* ============================================================================== DRAWING ============================================================================== */ /* ================ Con_DrawInput Draw the editline after a ] prompt ================ */ static void Con_DrawInput (void) { int y; if ( clc.state != CA_DISCONNECTED && !(Key_GetCatcher( ) & KEYCATCH_CONSOLE ) ) { return; } y = con.vislines - ( SMALLCHAR_HEIGHT * 2 ); re.SetColor( con.color ); SCR_DrawSmallChar( con.xadjust + 1 * SMALLCHAR_WIDTH, y, ']' ); Field_Draw( &g_consoleField, con.xadjust + 2 * SMALLCHAR_WIDTH, y, SCREEN_WIDTH - 3 * SMALLCHAR_WIDTH, true, true ); } /* ================ Con_DrawSolidConsole Draws the console with the solid background ================ */ static void Con_DrawSolidConsole( float frac ) { int i, x, y; int rows; short *text; int row; int lines; int currentColor; vec4_t color; lines = cls.glconfig.vidHeight * frac; if (lines <= 0) return; if (lines > cls.glconfig.vidHeight ) lines = cls.glconfig.vidHeight; // on wide screens, we will center the text con.xadjust = 0; SCR_AdjustFrom640( &con.xadjust, NULL, NULL, NULL ); // draw the background y = frac * SCREEN_HEIGHT; if ( y < 1 ) { y = 0; } else if (con_useShader->integer) { SCR_DrawPic(0, 0, SCREEN_WIDTH, y, cls.consoleShader); } else { color[0] = con_colorRed->value; color[1] = con_colorGreen->value; color[2] = con_colorBlue->value; color[3] = con_colorAlpha->value; SCR_FillRect(0, 0, SCREEN_WIDTH, y, color); } color[0] = 1; color[1] = 0; color[2] = 0; color[3] = 1; SCR_FillRect(0, y, SCREEN_WIDTH, 2, color); // draw the version number re.SetColor( g_color_table[ColorIndex(COLOR_RED)] ); i = strlen( Q3_VERSION ); for (x=0 ; x= con.totallines) { // past scrollback wrap point continue; } text = con.text + (row % con.totallines)*con.linewidth; for (x=0 ; x>8 ) != currentColor ) { currentColor = ColorIndexForNumber( text[x]>>8 ); re.SetColor( g_color_table[currentColor] ); } SCR_DrawSmallChar( con.xadjust + (x+1)*SMALLCHAR_WIDTH, y, text[x] & 0xff ); } } // draw the input prompt, user text, and cursor if desired Con_DrawInput (); re.SetColor( NULL ); } /* ================== Con_DrawConsole ================== */ void Con_DrawConsole( void ) { // check for console width changes from a vid mode change Con_CheckResize (); // if disconnected, render console full screen if ( clc.state == CA_DISCONNECTED ) { if ( !( Key_GetCatcher( ) & (KEYCATCH_UI | KEYCATCH_CGAME)) ) { Con_DrawSolidConsole( 1.0 ); return; } } // draw the chat line if( clc.netchan.alternateProtocol == 2 && ( Key_GetCatcher( ) & KEYCATCH_MESSAGE ) ) { int skip; if( chatField.buffer[0] == '/' || chatField.buffer[0] == '\\' ) { SCR_DrawBigString( 8, 232, "Command:", 1.0f, qfalse ); skip = 10; } else if( chat_team ) { SCR_DrawBigString( 8, 232, "Team Say:", 1.0f, qfalse ); skip = 11; } else if( chat_admins ) { SCR_DrawBigString( 8, 232, "Admin Say:", 1.0f, qfalse ); skip = 11; } else if( chat_clans ) { SCR_DrawBigString( 8, 232, "Clan Say:", 1.0f, qfalse ); skip = 11; } else { SCR_DrawBigString( 8, 232, "Say:", 1.0f, qfalse ); skip = 5; } Field_BigDraw( &chatField, skip * BIGCHAR_WIDTH, 232, SCREEN_WIDTH - ( skip + 1 ) * BIGCHAR_WIDTH, qtrue, qtrue ); } if ( con.displayFrac ) { Con_DrawSolidConsole( con.displayFrac ); } if( Key_GetCatcher( ) & ( KEYCATCH_UI | KEYCATCH_CGAME ) ) return; } //================================================================ /* ================== Con_RunConsole Scroll it up or down ================== */ void Con_RunConsole (void) { // decide on the destination height of the console if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) con.finalFrac = MAX(0.10, 0.01 * con_height->integer); else con.finalFrac = 0; // none visible // scroll towards the destination height if (con.finalFrac < con.displayFrac) { con.displayFrac -= con_conspeed->value*cls.realFrametime*0.001; if (con.finalFrac > con.displayFrac) con.displayFrac = con.finalFrac; } else if (con.finalFrac > con.displayFrac) { con.displayFrac += con_conspeed->value*cls.realFrametime*0.001; if (con.finalFrac < con.displayFrac) con.displayFrac = con.finalFrac; } } void Con_PageUp( void ) { con.display -= 2; if ( con.current - con.display >= con.totallines ) { con.display = con.current - con.totallines + 1; } } void Con_PageDown( void ) { con.display += 2; if (con.display > con.current) { con.display = con.current; } } void Con_Top( void ) { con.display = con.totallines; if ( con.current - con.display >= con.totallines ) { con.display = con.current - con.totallines + 1; } } void Con_Bottom( void ) { con.display = con.current; } void Con_Close( void ) { if ( !com_cl_running->integer ) { return; } Field_Clear( &g_consoleField ); Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CONSOLE ); con.finalFrac = 0; // none visible con.displayFrac = 0; }