/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 Source Code 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. Doom 3 Source Code 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 Doom 3 Source Code. If not, see . In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "sys/platform.h" #include "idlib/math/Vector.h" #include "framework/async/AsyncNetwork.h" #include "framework/BuildVersion.h" #include "framework/CVarSystem.h" #include "framework/Session.h" #include "framework/EditField.h" #include "framework/KeyInput.h" #include "framework/EventLoop.h" #include "renderer/RenderSystem.h" #include "sound/sound.h" #include "framework/Console.h" #include "tools/edit_public.h" void SCR_DrawTextLeftAlign( float &y, const char *text, ... ) id_attribute((format(printf,2,3))); void SCR_DrawTextRightAlign( float &y, const char *text, ... ) id_attribute((format(printf,2,3))); #define LINE_WIDTH 78 #define NUM_CON_TIMES 4 #define CON_TEXTSIZE 0x30000 #define TOTAL_LINES (CON_TEXTSIZE / LINE_WIDTH) #define CONSOLE_FIRSTREPEAT 200 #define CONSOLE_REPEAT 100 #define COMMAND_HISTORY 64 // the console will query the cvar and command systems for // command completion information class idConsoleLocal : public idConsole { public: virtual void Init( void ); virtual void Shutdown( void ); virtual void LoadGraphics( void ); virtual bool ProcessEvent( const sysEvent_t *event, bool forceAccept ); virtual bool Active( void ); virtual void ClearNotifyLines( void ); virtual void Close( void ); virtual void Print( const char *text ); virtual void Draw( bool forceFullScreen ); void Dump( const char *toFile ); void Clear(); virtual void SaveHistory(); virtual void LoadHistory(); //============================ const idMaterial * charSetShader; private: void KeyDownEvent( int key ); void Linefeed(); void PageUp(); void PageDown(); void Top(); void Bottom(); void DrawInput(); void DrawNotify(); void DrawSolidConsole( float frac ); void Scroll(); void SetDisplayFraction( float frac ); void UpdateDisplayFraction( void ); //============================ bool keyCatching; 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 lastKeyEvent; // time of last key event for scroll delay int nextKeyEvent; // keyboard repeat rate float displayFrac; // approaches finalFrac at scr_conspeed float finalFrac; // 0.0 to 1.0 lines of console to display int fracTime; // time of last displayFrac update int vislines; // in scanlines int times[NUM_CON_TIMES]; // cls.realtime time the line was generated // for transparent notify lines idVec4 color; idEditField historyEditLines[COMMAND_HISTORY]; int nextHistoryLine;// the last line in the history buffer, not masked int historyLine; // the line being displayed from history buffer // will be <= nextHistoryLine idEditField consoleField; static idCVar con_speed; static idCVar con_notifyTime; static idCVar con_noPrint; const idMaterial * whiteShader; const idMaterial * consoleShader; }; static idConsoleLocal localConsole; idConsole *console = &localConsole; idCVar idConsoleLocal::con_speed( "con_speed", "3", CVAR_SYSTEM, "speed at which the console moves up and down" ); idCVar idConsoleLocal::con_notifyTime( "con_notifyTime", "3", CVAR_SYSTEM, "time messages are displayed onscreen when console is pulled up" ); #ifdef DEBUG idCVar idConsoleLocal::con_noPrint( "con_noPrint", "0", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "print on the console but not onscreen when console is pulled up" ); #else idCVar idConsoleLocal::con_noPrint( "con_noPrint", "1", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "print on the console but not onscreen when console is pulled up" ); #endif /* ============================================================================= Misc stats ============================================================================= */ /* ================== SCR_DrawTextLeftAlign ================== */ void SCR_DrawTextLeftAlign( float &y, const char *text, ... ) { char string[MAX_STRING_CHARS]; va_list argptr; va_start( argptr, text ); idStr::vsnPrintf( string, sizeof( string ), text, argptr ); va_end( argptr ); renderSystem->DrawSmallStringExt( 0, y + 2, string, colorWhite, true, localConsole.charSetShader ); y += SMALLCHAR_HEIGHT + 4; } /* ================== SCR_DrawTextRightAlign ================== */ void SCR_DrawTextRightAlign( float &y, const char *text, ... ) { char string[MAX_STRING_CHARS]; va_list argptr; va_start( argptr, text ); int i = idStr::vsnPrintf( string, sizeof( string ), text, argptr ); va_end( argptr ); renderSystem->DrawSmallStringExt( 635 - i * SMALLCHAR_WIDTH, y + 2, string, colorWhite, true, localConsole.charSetShader ); y += SMALLCHAR_HEIGHT + 4; } /* ================== SCR_DrawFPS ================== */ #define FPS_FRAMES 4 float SCR_DrawFPS( float y ) { char *s; int w; static int previousTimes[FPS_FRAMES]; static int index; int i, total; int fps; static int previous; int t, frameTime; // don't use serverTime, because that will be drifting to // correct for internet lag changes, timescales, timedemos, etc t = Sys_Milliseconds(); frameTime = t - previous; previous = t; previousTimes[index % FPS_FRAMES] = frameTime; index++; if ( index > FPS_FRAMES ) { // average multiple frames together to smooth changes out a bit total = 0; for ( i = 0 ; i < FPS_FRAMES ; i++ ) { total += previousTimes[i]; } if ( !total ) { total = 1; } fps = 10000 * FPS_FRAMES / total; fps = (fps + 5)/10; s = va( "%ifps", fps ); w = strlen( s ) * BIGCHAR_WIDTH; renderSystem->DrawBigStringExt( 635 - w, idMath::FtoiFast( y ) + 2, s, colorWhite, true, localConsole.charSetShader); } return y + BIGCHAR_HEIGHT + 4; } /* ================== SCR_DrawMemoryUsage ================== */ float SCR_DrawMemoryUsage( float y ) { memoryStats_t allocs, frees; Mem_GetStats( allocs ); SCR_DrawTextRightAlign( y, "total allocated memory: %4d, %4dkB", allocs.num, allocs.totalSize>>10 ); Mem_GetFrameStats( allocs, frees ); SCR_DrawTextRightAlign( y, "frame alloc: %4d, %4dkB frame free: %4d, %4dkB", allocs.num, allocs.totalSize>>10, frees.num, frees.totalSize>>10 ); Mem_ClearFrameStats(); return y; } /* ================== SCR_DrawAsyncStats ================== */ float SCR_DrawAsyncStats( float y ) { int i, outgoingRate, incomingRate; float outgoingCompression, incomingCompression; if ( idAsyncNetwork::server.IsActive() ) { SCR_DrawTextRightAlign( y, "server delay = %d msec", idAsyncNetwork::server.GetDelay() ); SCR_DrawTextRightAlign( y, "total outgoing rate = %d KB/s", idAsyncNetwork::server.GetOutgoingRate() >> 10 ); SCR_DrawTextRightAlign( y, "total incoming rate = %d KB/s", idAsyncNetwork::server.GetIncomingRate() >> 10 ); for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { outgoingRate = idAsyncNetwork::server.GetClientOutgoingRate( i ); incomingRate = idAsyncNetwork::server.GetClientIncomingRate( i ); outgoingCompression = idAsyncNetwork::server.GetClientOutgoingCompression( i ); incomingCompression = idAsyncNetwork::server.GetClientIncomingCompression( i ); if ( outgoingRate != -1 && incomingRate != -1 ) { SCR_DrawTextRightAlign( y, "client %d: out rate = %d B/s (% -2.1f%%), in rate = %d B/s (% -2.1f%%)", i, outgoingRate, outgoingCompression, incomingRate, incomingCompression ); } } idStr msg; idAsyncNetwork::server.GetAsyncStatsAvgMsg( msg ); SCR_DrawTextRightAlign( y, "%s", msg.c_str() ); } else if ( idAsyncNetwork::client.IsActive() ) { outgoingRate = idAsyncNetwork::client.GetOutgoingRate(); incomingRate = idAsyncNetwork::client.GetIncomingRate(); outgoingCompression = idAsyncNetwork::client.GetOutgoingCompression(); incomingCompression = idAsyncNetwork::client.GetIncomingCompression(); if ( outgoingRate != -1 && incomingRate != -1 ) { SCR_DrawTextRightAlign( y, "out rate = %d B/s (% -2.1f%%), in rate = %d B/s (% -2.1f%%)", outgoingRate, outgoingCompression, incomingRate, incomingCompression ); } SCR_DrawTextRightAlign( y, "packet loss = %d%%, client prediction = %d", (int)idAsyncNetwork::client.GetIncomingPacketLoss(), idAsyncNetwork::client.GetPrediction() ); SCR_DrawTextRightAlign( y, "predicted frames: %d", idAsyncNetwork::client.GetPredictedFrames() ); } return y; } /* ================== SCR_DrawSoundDecoders ================== */ float SCR_DrawSoundDecoders( float y ) { int index, numActiveDecoders; soundDecoderInfo_t decoderInfo; index = -1; numActiveDecoders = 0; while( ( index = soundSystem->GetSoundDecoderInfo( index, decoderInfo ) ) != -1 ) { int localTime = decoderInfo.current44kHzTime - decoderInfo.start44kHzTime; int sampleTime = decoderInfo.num44kHzSamples / decoderInfo.numChannels; int percent; if ( localTime > sampleTime ) { if ( decoderInfo.looping ) { percent = ( localTime % sampleTime ) * 100 / sampleTime; } else { percent = 100; } } else { percent = localTime * 100 / sampleTime; } SCR_DrawTextLeftAlign( y, "%3d: %3d%% (%1.2f) %s: %s (%dkB)", numActiveDecoders, percent, decoderInfo.lastVolume, decoderInfo.format.c_str(), decoderInfo.name.c_str(), decoderInfo.numBytes >> 10 ); numActiveDecoders++; } return y; } //========================================================================= /* ============== Con_Clear_f ============== */ static void Con_Clear_f( const idCmdArgs &args ) { localConsole.Clear(); } /* ============== Con_Dump_f ============== */ static void Con_Dump_f( const idCmdArgs &args ) { if ( args.Argc() != 2 ) { common->Printf( "usage: conDump \n" ); return; } idStr fileName = args.Argv(1); fileName.DefaultFileExtension(".txt"); common->Printf( "Dumped console text to %s.\n", fileName.c_str() ); localConsole.Dump( fileName.c_str() ); } /* ============== idConsoleLocal::Init ============== */ void idConsoleLocal::Init( void ) { int i; keyCatching = false; lastKeyEvent = -1; nextKeyEvent = CONSOLE_FIRSTREPEAT; consoleField.Clear(); consoleField.SetWidthInChars( LINE_WIDTH ); for ( i = 0 ; i < COMMAND_HISTORY ; i++ ) { historyEditLines[i].Clear(); historyEditLines[i].SetWidthInChars( LINE_WIDTH ); } cmdSystem->AddCommand( "clear", Con_Clear_f, CMD_FL_SYSTEM, "clears the console" ); cmdSystem->AddCommand( "conDump", Con_Dump_f, CMD_FL_SYSTEM, "dumps the console text to a file" ); } /* ============== idConsoleLocal::Shutdown ============== */ void idConsoleLocal::Shutdown( void ) { cmdSystem->RemoveCommand( "clear" ); cmdSystem->RemoveCommand( "conDump" ); } /* ============== LoadGraphics Can't be combined with init, because init happens before the renderSystem is initialized ============== */ void idConsoleLocal::LoadGraphics() { charSetShader = declManager->FindMaterial( "textures/bigchars" ); whiteShader = declManager->FindMaterial( "_white" ); consoleShader = declManager->FindMaterial( "console" ); } /* ================ idConsoleLocal::Active ================ */ bool idConsoleLocal::Active( void ) { return keyCatching; } /* ================ idConsoleLocal::ClearNotifyLines ================ */ void idConsoleLocal::ClearNotifyLines() { int i; for ( i = 0 ; i < NUM_CON_TIMES ; i++ ) { times[i] = 0; } } /* ================ idConsoleLocal::Close ================ */ void idConsoleLocal::Close() { keyCatching = false; SetDisplayFraction( 0 ); displayFrac = 0; // don't scroll to that point, go immediately ClearNotifyLines(); } /* ================ idConsoleLocal::Clear ================ */ void idConsoleLocal::Clear() { int i; for ( i = 0 ; i < CON_TEXTSIZE ; i++ ) { text[i] = (idStr::ColorIndex(C_COLOR_CYAN)<<8) | ' '; } Bottom(); // go to end } /* ================ idConsoleLocal::Dump Save the console contents out to a file ================ */ void idConsoleLocal::Dump( const char *fileName ) { int l, x, i; short * line; idFile *f; char buffer[LINE_WIDTH + 3]; f = fileSystem->OpenFileWrite( fileName ); if ( !f ) { common->Warning( "couldn't open %s", fileName ); return; } // skip empty lines l = current - TOTAL_LINES + 1; if ( l < 0 ) { l = 0; } for ( ; l <= current ; l++ ) { line = text + ( l % TOTAL_LINES ) * LINE_WIDTH; for ( x = 0; x < LINE_WIDTH; x++ ) if ( ( line[x] & 0xff ) > ' ' ) break; if ( x != LINE_WIDTH ) break; } // write the remaining lines for ( ; l <= current; l++ ) { line = text + ( l % TOTAL_LINES ) * LINE_WIDTH; for( i = 0; i < LINE_WIDTH; i++ ) { buffer[i] = line[i] & 0xff; } for ( x = LINE_WIDTH-1; x >= 0; x-- ) { if ( buffer[x] <= ' ' ) { buffer[x] = 0; } else { break; } } buffer[x+1] = '\r'; buffer[x+2] = '\n'; buffer[x+3] = 0; f->Write( buffer, strlen( buffer ) ); } fileSystem->CloseFile( f ); } void idConsoleLocal::SaveHistory() { idFile *f = fileSystem->OpenFileWrite( "consolehistory.dat" ); for ( int i=0; i < COMMAND_HISTORY; ++i ) { // make sure the history is in the right order int line = (nextHistoryLine + i) % COMMAND_HISTORY; const char *s = historyEditLines[line].GetBuffer(); if ( s && s[0] ) { f->WriteString(s); } } fileSystem->CloseFile(f); } void idConsoleLocal::LoadHistory() { idFile *f = fileSystem->OpenFileRead( "consolehistory.dat" ); if ( f == NULL ) // file doesn't exist return; historyLine = 0; idStr tmp; for ( int i=0; i < COMMAND_HISTORY; ++i ) { if ( f->Tell() >= f->Length() ) { break; // EOF is reached } f->ReadString(tmp); historyEditLines[i].SetBuffer(tmp.c_str()); ++historyLine; } nextHistoryLine = historyLine; fileSystem->CloseFile(f); } /* ================ idConsoleLocal::PageUp ================ */ void idConsoleLocal::PageUp( void ) { display -= 2; if ( current - display >= TOTAL_LINES ) { display = current - TOTAL_LINES + 1; } } /* ================ idConsoleLocal::PageDown ================ */ void idConsoleLocal::PageDown( void ) { display += 2; if ( display > current ) { display = current; } } /* ================ idConsoleLocal::Top ================ */ void idConsoleLocal::Top( void ) { display = 0; } /* ================ idConsoleLocal::Bottom ================ */ void idConsoleLocal::Bottom( void ) { display = current; } /* ============================================================================= CONSOLE LINE EDITING ============================================================================== */ /* ==================== KeyDownEvent Handles history and console scrollback ==================== */ void idConsoleLocal::KeyDownEvent( int key ) { // Execute F key bindings if ( key >= K_F1 && key <= K_F12 ) { idKeyInput::ExecKeyBinding( key ); return; } // ctrl-L clears screen if ( key == 'l' && idKeyInput::IsDown( K_CTRL ) ) { Clear(); return; } // enter finishes the line if ( key == K_ENTER || key == K_KP_ENTER ) { common->Printf ( "]%s\n", consoleField.GetBuffer() ); cmdSystem->BufferCommandText( CMD_EXEC_APPEND, consoleField.GetBuffer() ); // valid command cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "\n" ); // copy line to history buffer, if it isn't the same as the last command if ( idStr::Cmp( consoleField.GetBuffer(), historyEditLines[(nextHistoryLine + COMMAND_HISTORY - 1) % COMMAND_HISTORY].GetBuffer()) != 0 ) { historyEditLines[nextHistoryLine % COMMAND_HISTORY] = consoleField; nextHistoryLine++; } historyLine = nextHistoryLine; // clear the next line from old garbage, else the oldest history entry turns up when pressing DOWN historyEditLines[nextHistoryLine % COMMAND_HISTORY].Clear(); consoleField.Clear(); consoleField.SetWidthInChars( LINE_WIDTH ); session->UpdateScreen();// force an update, because the command // may take some time return; } // command completion if ( key == K_TAB ) { consoleField.AutoComplete(); return; } // command history (ctrl-p ctrl-n for unix style) if ( ( key == K_UPARROW ) || ( ( tolower(key) == 'p' ) && idKeyInput::IsDown( K_CTRL ) ) ) { if ( nextHistoryLine - historyLine < COMMAND_HISTORY && historyLine > 0 ) { historyLine--; } consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ]; return; } if ( ( key == K_DOWNARROW ) || ( ( tolower( key ) == 'n' ) && idKeyInput::IsDown( K_CTRL ) ) ) { if ( historyLine == nextHistoryLine ) { return; } historyLine++; consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ]; return; } // console scrolling if ( key == K_PGUP ) { PageUp(); lastKeyEvent = eventLoop->Milliseconds(); nextKeyEvent = CONSOLE_FIRSTREPEAT; return; } if ( key == K_PGDN ) { PageDown(); lastKeyEvent = eventLoop->Milliseconds(); nextKeyEvent = CONSOLE_FIRSTREPEAT; return; } if ( key == K_MWHEELUP ) { PageUp(); return; } if ( key == K_MWHEELDOWN ) { PageDown(); return; } // ctrl-home = top of console if ( key == K_HOME && idKeyInput::IsDown( K_CTRL ) ) { Top(); return; } // ctrl-end = bottom of console if ( key == K_END && idKeyInput::IsDown( K_CTRL ) ) { Bottom(); return; } // pass to the normal editline routine consoleField.KeyDownEvent( key ); } /* ============== Scroll deals with scrolling text because we don't have key repeat ============== */ void idConsoleLocal::Scroll( ) { if (lastKeyEvent == -1 || (lastKeyEvent+200) > eventLoop->Milliseconds()) { return; } // console scrolling if ( idKeyInput::IsDown( K_PGUP ) ) { PageUp(); nextKeyEvent = CONSOLE_REPEAT; return; } if ( idKeyInput::IsDown( K_PGDN ) ) { PageDown(); nextKeyEvent = CONSOLE_REPEAT; return; } } /* ============== SetDisplayFraction Causes the console to start opening the desired amount. ============== */ void idConsoleLocal::SetDisplayFraction( float frac ) { finalFrac = frac; fracTime = com_frameTime; } /* ============== UpdateDisplayFraction Scrolls the console up or down based on conspeed ============== */ void idConsoleLocal::UpdateDisplayFraction( void ) { if ( con_speed.GetFloat() <= 0.1f ) { fracTime = com_frameTime; displayFrac = finalFrac; return; } // scroll towards the destination height if ( finalFrac < displayFrac ) { displayFrac -= con_speed.GetFloat() * ( com_frameTime - fracTime ) * 0.001f; if ( finalFrac > displayFrac ) { displayFrac = finalFrac; } fracTime = com_frameTime; } else if ( finalFrac > displayFrac ) { displayFrac += con_speed.GetFloat() * ( com_frameTime - fracTime ) * 0.001f; if ( finalFrac < displayFrac ) { displayFrac = finalFrac; } fracTime = com_frameTime; } } /* ============== ProcessEvent ============== */ bool idConsoleLocal::ProcessEvent( const sysEvent_t *event, bool forceAccept ) { bool consoleKey = false; if(event->evType == SE_KEY) { bool shiftPressed = idKeyInput::IsDown( K_SHIFT ); if( event->evValue == K_CONSOLE || event->evValue == Sys_GetConsoleKey( shiftPressed ) || (event->evValue == K_ESCAPE && shiftPressed) ) // shift+esc should also open console { consoleKey = true; } } #if ID_CONSOLE_LOCK // If the console's not already down, and we have it turned off, check for ctrl+alt if ( !keyCatching && !com_allowConsole.GetBool() ) { if ( !idKeyInput::IsDown( K_CTRL ) || !idKeyInput::IsDown( K_ALT ) ) { consoleKey = false; } } #endif // we always catch the console key event if ( !forceAccept && consoleKey ) { // ignore up events if ( event->evValue2 == 0 ) { return true; } consoleField.ClearAutoComplete(); // a down event will toggle the destination lines if ( keyCatching ) { Close(); cvarSystem->SetCVarBool( "ui_chat", false ); } else { consoleField.Clear(); keyCatching = true; if ( idKeyInput::IsDown( K_SHIFT ) && event->evValue != K_ESCAPE ) { // if the shift key is down, don't open the console as much // except we used shift+esc. SetDisplayFraction( 0.2f ); } else { SetDisplayFraction( 0.5f ); } cvarSystem->SetCVarBool( "ui_chat", true ); } return true; } // if we aren't key catching, dump all the other events if ( !forceAccept && !keyCatching ) { return false; } // handle key and character events if ( event->evType == SE_CHAR ) { // never send the console key as a character if ( event->evValue != Sys_GetConsoleKey( idKeyInput::IsDown( K_SHIFT ) ) ) { consoleField.CharEvent( event->evValue ); } return true; } if ( event->evType == SE_KEY ) { // ignore up key events if ( event->evValue2 == 0 ) { return true; } KeyDownEvent( event->evValue ); return true; } // we don't handle things like mouse, joystick, and network packets return false; } /* ============================================================================== PRINTING ============================================================================== */ /* =============== Linefeed =============== */ void idConsoleLocal::Linefeed() { int i; // mark time for transparent overlay if ( current >= 0 ) { times[current % NUM_CON_TIMES] = com_frameTime; } x = 0; if ( display == current ) { display++; } current++; for ( i = 0; i < LINE_WIDTH; i++ ) { text[(current%TOTAL_LINES)*LINE_WIDTH+i] = (idStr::ColorIndex(C_COLOR_CYAN)<<8) | ' '; } } /* ================ Print Handles cursor positioning, line wrapping, etc ================ */ void idConsoleLocal::Print( const char *txt ) { int y; int c, l; int color; #ifdef ID_ALLOW_TOOLS RadiantPrint( txt ); if( com_editors & EDITOR_MATERIAL ) { MaterialEditorPrintConsole(txt); } #endif color = idStr::ColorIndex( C_COLOR_CYAN ); while ( (c = *(const unsigned char*)txt) != 0 ) { if ( idStr::IsColor( txt ) ) { if ( *(txt+1) == C_COLOR_DEFAULT ) { color = idStr::ColorIndex( C_COLOR_CYAN ); } else { color = idStr::ColorIndex( *(txt+1) ); } txt += 2; continue; } y = current % TOTAL_LINES; // if we are about to print a new word, check to see // if we should wrap to the new line if ( c > ' ' && ( x == 0 || text[y*LINE_WIDTH+x-1] <= ' ' ) ) { // count word length for (l=0 ; l< LINE_WIDTH ; l++) { if ( txt[l] <= ' ') { break; } } // word wrap if (l != LINE_WIDTH && (x + l >= LINE_WIDTH) ) { Linefeed(); } } txt++; switch( c ) { case '\n': Linefeed (); break; case '\t': do { text[y*LINE_WIDTH+x] = (color << 8) | ' '; x++; if ( x >= LINE_WIDTH ) { Linefeed(); x = 0; } } while ( x & 3 ); break; case '\r': x = 0; break; default: // display character and advance text[y*LINE_WIDTH+x] = (color << 8) | c; x++; if ( x >= LINE_WIDTH ) { Linefeed(); x = 0; } break; } } // mark time for transparent overlay if ( current >= 0 ) { times[current % NUM_CON_TIMES] = com_frameTime; } } /* ============================================================================== DRAWING ============================================================================== */ /* ================ DrawInput Draw the editline after a ] prompt ================ */ void idConsoleLocal::DrawInput() { int y, autoCompleteLength; y = vislines - ( SMALLCHAR_HEIGHT * 2 ); if ( consoleField.GetAutoCompleteLength() != 0 ) { autoCompleteLength = strlen( consoleField.GetBuffer() ) - consoleField.GetAutoCompleteLength(); if ( autoCompleteLength > 0 ) { renderSystem->SetColor4( .8f, .2f, .2f, .45f ); renderSystem->DrawStretchPic( 2 * SMALLCHAR_WIDTH + consoleField.GetAutoCompleteLength() * SMALLCHAR_WIDTH, y + 2, autoCompleteLength * SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT - 2, 0, 0, 0, 0, whiteShader ); } } renderSystem->SetColor( idStr::ColorForIndex( C_COLOR_CYAN ) ); renderSystem->DrawSmallChar( 1 * SMALLCHAR_WIDTH, y, ']', localConsole.charSetShader ); consoleField.Draw(2 * SMALLCHAR_WIDTH, y, SCREEN_WIDTH - 3 * SMALLCHAR_WIDTH, true, charSetShader ); } /* ================ DrawNotify Draws the last few lines of output transparently over the game top ================ */ void idConsoleLocal::DrawNotify() { int x, v; short *text_p; int i; int time; int currentColor; if ( con_noPrint.GetBool() ) { return; } currentColor = idStr::ColorIndex( C_COLOR_WHITE ); renderSystem->SetColor( idStr::ColorForIndex( currentColor ) ); v = 0; for ( i = current-NUM_CON_TIMES+1; i <= current; i++ ) { if ( i < 0 ) { continue; } time = times[i % NUM_CON_TIMES]; if ( time == 0 ) { continue; } time = com_frameTime - time; if ( time > con_notifyTime.GetFloat() * 1000 ) { continue; } text_p = text + (i % TOTAL_LINES)*LINE_WIDTH; for ( x = 0; x < LINE_WIDTH; x++ ) { if ( ( text_p[x] & 0xff ) == ' ' ) { continue; } if ( idStr::ColorIndex(text_p[x]>>8) != currentColor ) { currentColor = idStr::ColorIndex(text_p[x]>>8); renderSystem->SetColor( idStr::ColorForIndex( currentColor ) ); } renderSystem->DrawSmallChar( (x+1)*SMALLCHAR_WIDTH, v, text_p[x] & 0xff, localConsole.charSetShader ); } v += SMALLCHAR_HEIGHT; } renderSystem->SetColor( colorCyan ); } /* ================ DrawSolidConsole Draws the console with the solid background ================ */ void idConsoleLocal::DrawSolidConsole( float frac ) { int i, x; float y; int rows; short *text_p; int row; int lines; int currentColor; lines = idMath::FtoiFast( SCREEN_HEIGHT * frac ); if ( lines <= 0 ) { return; } if ( lines > SCREEN_HEIGHT ) { lines = SCREEN_HEIGHT; } // draw the background y = frac * SCREEN_HEIGHT - 2; if ( y < 1.0f ) { y = 0.0f; } else { renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, y, 0, 1.0f - displayFrac, 1, 1, consoleShader ); } renderSystem->SetColor( colorCyan ); renderSystem->DrawStretchPic( 0, y, SCREEN_WIDTH, 2, 0, 0, 0, 0, whiteShader ); renderSystem->SetColor( colorWhite ); // draw the version number renderSystem->SetColor( idStr::ColorForIndex( C_COLOR_CYAN ) ); idStr version = va("%s.%i", ENGINE_VERSION, BUILD_NUMBER); i = version.Length(); for ( x = 0; x < i; x++ ) { renderSystem->DrawSmallChar( SCREEN_WIDTH - ( i - x ) * SMALLCHAR_WIDTH, (lines-(SMALLCHAR_HEIGHT+SMALLCHAR_HEIGHT/2)), version[x], localConsole.charSetShader ); } // draw the text vislines = lines; rows = (lines-SMALLCHAR_WIDTH)/SMALLCHAR_WIDTH; // rows of text to draw y = lines - (SMALLCHAR_HEIGHT*3); // draw from the bottom up if ( display != current ) { // draw arrows to show the buffer is backscrolled renderSystem->SetColor( idStr::ColorForIndex( C_COLOR_CYAN ) ); for ( x = 0; x < LINE_WIDTH; x += 4 ) { renderSystem->DrawSmallChar( (x+1)*SMALLCHAR_WIDTH, idMath::FtoiFast( y ), '^', localConsole.charSetShader ); } y -= SMALLCHAR_HEIGHT; rows--; } row = display; if ( x == 0 ) { row--; } currentColor = idStr::ColorIndex( C_COLOR_WHITE ); renderSystem->SetColor( idStr::ColorForIndex( currentColor ) ); for ( i = 0; i < rows; i++, y -= SMALLCHAR_HEIGHT, row-- ) { if ( row < 0 ) { break; } if ( current - row >= TOTAL_LINES ) { // past scrollback wrap point continue; } text_p = text + (row % TOTAL_LINES)*LINE_WIDTH; for ( x = 0; x < LINE_WIDTH; x++ ) { if ( ( text_p[x] & 0xff ) == ' ' ) { continue; } if ( idStr::ColorIndex(text_p[x]>>8) != currentColor ) { currentColor = idStr::ColorIndex(text_p[x]>>8); renderSystem->SetColor( idStr::ColorForIndex( currentColor ) ); } renderSystem->DrawSmallChar( (x+1)*SMALLCHAR_WIDTH, idMath::FtoiFast( y ), text_p[x] & 0xff, localConsole.charSetShader ); } } // draw the input prompt, user text, and cursor if desired DrawInput(); renderSystem->SetColor( colorCyan ); } /* ============== Draw ForceFullScreen is used by the editor ============== */ void idConsoleLocal::Draw( bool forceFullScreen ) { float y = 0.0f; if ( !charSetShader ) { return; } if ( forceFullScreen ) { // if we are forced full screen because of a disconnect, // we want the console closed when we go back to a session state Close(); // we are however catching keyboard input keyCatching = true; } Scroll(); UpdateDisplayFraction(); if ( forceFullScreen ) { DrawSolidConsole( 1.0f ); } else if ( displayFrac ) { DrawSolidConsole( displayFrac ); } else { // only draw the notify lines if the developer cvar is set, // or we are a debug build if ( !con_noPrint.GetBool() ) { DrawNotify(); } } if ( com_showFPS.GetBool() ) { y = SCR_DrawFPS( 0 ); } if ( com_showMemoryUsage.GetBool() ) { y = SCR_DrawMemoryUsage( y ); } if ( com_showAsyncStats.GetBool() ) { y = SCR_DrawAsyncStats( y ); } if ( com_showSoundDecoders.GetBool() ) { y = SCR_DrawSoundDecoders( y ); } }