2019-11-05 18:57:48 +00:00
/*
* * c_console . cpp
* * Implements the console itself
* *
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* * Copyright 1998 - 2006 Randy Heit
* * All rights reserved .
* *
* * Redistribution and use in source and binary forms , with or without
* * modification , are permitted provided that the following conditions
* * are met :
* *
* * 1. Redistributions of source code must retain the above copyright
* * notice , this list of conditions and the following disclaimer .
* * 2. Redistributions in binary form must reproduce the above copyright
* * notice , this list of conditions and the following disclaimer in the
* * documentation and / or other materials provided with the distribution .
* * 3. The name of the author may not be used to endorse or promote products
* * derived from this software without specific prior written permission .
* *
* * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ` ` AS IS ' ' AND ANY EXPRESS OR
* * IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES
* * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED .
* * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT , INDIRECT ,
* * INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT
* * NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE ,
* * DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY
* * THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF
* * THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* *
*/
# include <string>
# include "templates.h"
# include "version.h"
# include "c_bind.h"
# include "c_console.h"
# include "c_cvars.h"
# include "c_dispatch.h"
2020-04-11 22:04:02 +00:00
# include "gamestate.h"
2019-11-05 18:57:48 +00:00
# include "v_text.h"
# include "filesystem.h"
# include "d_gui.h"
# include "cmdlib.h"
# include "d_event.h"
# include "c_consolebuffer.h"
# include "utf8.h"
# include "v_2ddrawer.h"
# include "v_draw.h"
# include "v_font.h"
# include "printf.h"
2019-11-06 22:40:10 +00:00
# include "inputstate.h"
2019-12-04 22:07:02 +00:00
# include "i_time.h"
# include "gamecvars.h"
2019-12-23 18:37:40 +00:00
# include "i_system.h"
2020-02-16 19:08:04 +00:00
# include "s_soundinternal.h"
2020-04-11 21:50:43 +00:00
# include "engineerrors.h"
2020-04-12 06:09:38 +00:00
# include "gamecontrol.h"
2020-05-25 15:11:32 +00:00
# include "v_video.h"
# include "v_draw.h"
2020-04-23 20:58:02 +00:00
# include "g_input.h"
2020-07-14 11:09:34 +00:00
# include "timer.h"
2020-07-21 22:42:50 +00:00
# include "menu.h"
# include "raze_music.h"
2019-11-05 18:57:48 +00:00
# define LEFTMARGIN 8
# define RIGHTMARGIN 8
# define BOTTOMARGIN 12
extern bool hud_toggled ;
void D_ToggleHud ( ) ;
CUSTOM_CVAR ( Int , con_buffersize , - 1 , CVAR_ARCHIVE | CVAR_GLOBALCONFIG )
{
// ensure a minimum size
if ( self > = 0 & & self < 128 ) self = 128 ;
}
FConsoleBuffer * conbuffer ;
static void C_TabComplete ( bool goForward ) ;
static bool C_TabCompleteList ( ) ;
static bool TabbedLast ; // True if last key pressed was tab
static bool TabbedList ; // True if tab list was shown
CVAR ( Bool , con_notablist , false , CVAR_ARCHIVE )
2020-07-21 22:42:50 +00:00
static FGameTexture * conback ;
2019-11-05 18:57:48 +00:00
static uint32_t conshade ;
static bool conline ;
extern int gametic ;
extern bool advancedemo ;
extern FBaseCVar * CVars ;
extern FConsoleCommand * Commands [ FConsoleCommand : : HASH_SIZE ] ;
unsigned ConCols ;
int ConWidth ;
bool vidactive = false ;
bool cursoron = false ;
int ConBottom , ConScroll , RowAdjust ;
2020-04-11 22:04:02 +00:00
uint64_t CursorTicker ;
2019-11-05 18:57:48 +00:00
constate_e ConsoleState = c_up ;
2020-08-19 14:40:54 +00:00
int NotifyFontScale = 1 ;
void C_SetNotifyFontScale ( int scale )
{
NotifyFontScale = scale ;
}
2019-11-05 18:57:48 +00:00
static int TopLine , InsertLine ;
static void ClearConsole ( ) ;
struct GameAtExit
{
GameAtExit ( FString str ) : Command ( str ) { }
GameAtExit * Next ;
FString Command ;
} ;
static GameAtExit * ExitCmdList ;
# define SCROLLUP 1
# define SCROLLDN 2
# define SCROLLNO 0
// Buffer for AddToConsole()
static char * work = NULL ;
static int worklen = 0 ;
CVAR ( Float , con_notifytime , 3.f , CVAR_ARCHIVE )
2020-08-25 16:03:15 +00:00
CUSTOM_CVAR ( Float , con_notifyscale , 1 , CVAR_ARCHIVE )
2019-11-05 18:57:48 +00:00
{
2020-08-25 16:03:15 +00:00
if ( self < 0.36f ) self = 0.36f ;
if ( self > 1 ) self = 1 ;
2019-11-05 18:57:48 +00:00
}
2020-08-25 16:03:15 +00:00
CVAR ( Bool , con_centernotify , false , CVAR_ARCHIVE )
CVAR ( Bool , con_notify_advanced , false , CVAR_ARCHIVE )
2020-08-23 20:39:53 +00:00
CVAR ( Bool , con_pulsetext , false , CVAR_ARCHIVE )
2019-11-05 18:57:48 +00:00
CUSTOM_CVAR ( Int , con_scale , 0 , CVAR_ARCHIVE )
{
if ( self < 0 ) self = 0 ;
}
CUSTOM_CVAR ( Float , con_alpha , 0.75f , CVAR_ARCHIVE )
{
if ( self < 0.f ) self = 0.f ;
if ( self > 1.f ) self = 1.f ;
}
// Command to run when Ctrl-D is pressed at start of line
CVAR ( String , con_ctrl_d , " " , CVAR_ARCHIVE | CVAR_GLOBALCONFIG )
CVAR ( Int , developer , 0 , CVAR_ARCHIVE | CVAR_GLOBALCONFIG )
EXTERN_CVAR ( Int , uiscale ) ;
2020-06-30 20:01:43 +00:00
CVAR ( Bool , generic_ui , false , CVAR_ARCHIVE )
2019-11-05 18:57:48 +00:00
struct History
{
struct History * Older ;
struct History * Newer ;
FString String ;
} ;
struct FCommandBuffer
{
private :
std : : u32string Text ;
unsigned CursorPos = 0 ;
unsigned StartPos = 0 ; // First character to display
unsigned CursorPosCells = 0 ;
unsigned StartPosCells = 0 ;
std : : u32string YankBuffer ; // Deleted text buffer
public :
bool AppendToYankBuffer = false ; // Append consecutive deletes to buffer
FCommandBuffer ( ) = default ;
FCommandBuffer ( const FCommandBuffer & o )
{
Text = o . Text ;
CursorPos = o . CursorPos ;
StartPos = o . StartPos ;
}
FString GetText ( ) const
{
FString build ;
for ( auto chr : Text ) build . AppendCharacter ( chr ) ;
return build ;
}
size_t TextLength ( ) const
{
return Text . length ( ) ;
}
void Draw ( int x , int y , int scale , bool cursor )
{
if ( scale = = 1 )
{
2019-12-30 18:29:32 +00:00
DrawChar ( twod , CurrentConsoleFont , CR_ORANGE , x , y , ' \x1c ' , TAG_DONE ) ;
2020-04-05 20:51:53 +00:00
DrawText ( twod , CurrentConsoleFont , CR_ORANGE , x + CurrentConsoleFont - > GetCharWidth ( 0x1c ) , y ,
2019-11-05 18:57:48 +00:00
& Text [ StartPos ] , TAG_DONE ) ;
if ( cursor )
{
2019-12-30 18:29:32 +00:00
DrawChar ( twod , CurrentConsoleFont , CR_YELLOW ,
2020-04-05 20:51:53 +00:00
x + CurrentConsoleFont - > GetCharWidth ( 0x1c ) + ( CursorPosCells - StartPosCells ) * CurrentConsoleFont - > GetCharWidth ( 0xb ) ,
2019-11-05 18:57:48 +00:00
y , ' \xb ' , TAG_DONE ) ;
}
}
else
{
2019-12-30 18:29:32 +00:00
DrawChar ( twod , CurrentConsoleFont , CR_ORANGE , x , y , ' \x1c ' ,
2020-04-11 21:27:37 +00:00
DTA_VirtualWidth , twod - > GetWidth ( ) / scale ,
DTA_VirtualHeight , twod - > GetHeight ( ) / scale ,
2019-11-05 18:57:48 +00:00
DTA_KeepRatio , true , TAG_DONE ) ;
2020-04-05 20:51:53 +00:00
DrawText ( twod , CurrentConsoleFont , CR_ORANGE , x + CurrentConsoleFont - > GetCharWidth ( 0x1c ) , y ,
2019-11-05 18:57:48 +00:00
& Text [ StartPos ] ,
2020-04-11 21:27:37 +00:00
DTA_VirtualWidth , twod - > GetWidth ( ) / scale ,
DTA_VirtualHeight , twod - > GetHeight ( ) / scale ,
2019-11-05 18:57:48 +00:00
DTA_KeepRatio , true , TAG_DONE ) ;
if ( cursor )
{
2019-12-30 18:29:32 +00:00
DrawChar ( twod , CurrentConsoleFont , CR_YELLOW ,
2020-04-05 20:51:53 +00:00
x + CurrentConsoleFont - > GetCharWidth ( 0x1c ) + ( CursorPosCells - StartPosCells ) * CurrentConsoleFont - > GetCharWidth ( 0xb ) ,
2019-11-05 18:57:48 +00:00
y , ' \xb ' ,
2020-04-11 21:27:37 +00:00
DTA_VirtualWidth , twod - > GetWidth ( ) / scale ,
DTA_VirtualHeight , twod - > GetHeight ( ) / scale ,
2019-11-05 18:57:48 +00:00
DTA_KeepRatio , true , TAG_DONE ) ;
}
}
}
unsigned CalcCellSize ( unsigned length )
{
unsigned cellcount = 0 ;
for ( unsigned i = 0 ; i < length ; i + + )
{
int w ;
NewConsoleFont - > GetChar ( Text [ i ] , CR_UNTRANSLATED , & w ) ;
cellcount + = w / 9 ;
}
return cellcount ;
}
unsigned CharsForCells ( unsigned cellin , bool * overflow )
{
unsigned chars = 0 ;
int cells = cellin ;
while ( cells > 0 )
{
int w ;
NewConsoleFont - > GetChar ( Text [ chars + + ] , CR_UNTRANSLATED , & w ) ;
cells - = w / 9 ;
}
* overflow = ( cells < 0 ) ;
return chars ;
}
void MakeStartPosGood ( )
{
// Make sure both values point to something valid.
if ( CursorPos > Text . length ( ) ) CursorPos = ( unsigned ) Text . length ( ) ;
if ( StartPos > Text . length ( ) ) StartPos = ( unsigned ) Text . length ( ) ;
CursorPosCells = CalcCellSize ( CursorPos ) ;
StartPosCells = CalcCellSize ( StartPos ) ;
unsigned LengthCells = CalcCellSize ( ( unsigned ) Text . length ( ) ) ;
int n = StartPosCells ;
2020-05-25 15:11:32 +00:00
unsigned cols = ConCols / active_con_scale ( twod ) ;
2019-11-05 18:57:48 +00:00
if ( StartPosCells > = LengthCells )
{ // Start of visible line is beyond end of line
n = CursorPosCells - cols + 2 ;
}
if ( ( CursorPosCells - StartPosCells ) > = cols - 2 )
{ // The cursor is beyond the visible part of the line
n = CursorPosCells - cols + 2 ;
}
if ( StartPosCells > CursorPosCells )
{ // The cursor is in front of the visible part of the line
n = CursorPosCells ;
}
StartPosCells = std : : max ( 0 , n ) ;
bool overflow ;
StartPos = CharsForCells ( StartPosCells , & overflow ) ;
if ( overflow )
{
// We ended up in the middle of a double cell character, so set the start to the following character.
StartPosCells + + ;
StartPos = CharsForCells ( StartPosCells , & overflow ) ;
}
}
void CursorStart ( )
{
CursorPos = 0 ;
StartPos = 0 ;
CursorPosCells = 0 ;
StartPosCells = 0 ;
}
void CursorEnd ( )
{
CursorPos = ( unsigned ) Text . length ( ) ;
MakeStartPosGood ( ) ;
}
private :
void MoveCursorLeft ( )
{
CursorPos - - ;
}
void MoveCursorRight ( )
{
CursorPos + + ;
}
public :
void CursorLeft ( )
{
if ( CursorPos > 0 )
{
MoveCursorLeft ( ) ;
MakeStartPosGood ( ) ;
}
}
void CursorRight ( )
{
if ( CursorPos < Text . length ( ) )
{
MoveCursorRight ( ) ;
MakeStartPosGood ( ) ;
}
}
void CursorWordLeft ( )
{
if ( CursorPos > 0 )
{
do MoveCursorLeft ( ) ;
while ( CursorPos > 0 & & Text [ CursorPos - 1 ] ! = ' ' ) ;
MakeStartPosGood ( ) ;
}
}
void CursorWordRight ( )
{
if ( CursorPos < Text . length ( ) )
{
do MoveCursorRight ( ) ;
while ( CursorPos < Text . length ( ) & & Text [ CursorPos ] ! = ' ' ) ;
MakeStartPosGood ( ) ;
}
}
void DeleteLeft ( )
{
if ( CursorPos > 0 )
{
MoveCursorLeft ( ) ;
Text . erase ( CursorPos , 1 ) ;
MakeStartPosGood ( ) ;
}
}
void DeleteRight ( )
{
if ( CursorPos < Text . length ( ) )
{
Text . erase ( CursorPos , 1 ) ;
MakeStartPosGood ( ) ;
}
}
void DeleteWordLeft ( )
{
if ( CursorPos > 0 )
{
auto now = CursorPos ;
CursorWordLeft ( ) ;
if ( AppendToYankBuffer ) {
YankBuffer = Text . substr ( CursorPos , now - CursorPos ) + YankBuffer ;
} else {
YankBuffer = Text . substr ( CursorPos , now - CursorPos ) ;
}
Text . erase ( CursorPos , now - CursorPos ) ;
MakeStartPosGood ( ) ;
}
}
void DeleteLineLeft ( )
{
if ( CursorPos > 0 )
{
if ( AppendToYankBuffer ) {
YankBuffer = Text . substr ( 0 , CursorPos ) + YankBuffer ;
} else {
YankBuffer = Text . substr ( 0 , CursorPos ) ;
}
Text . erase ( 0 , CursorPos ) ;
CursorStart ( ) ;
}
}
void DeleteLineRight ( )
{
if ( CursorPos < Text . length ( ) )
{
if ( AppendToYankBuffer ) {
YankBuffer + = Text . substr ( CursorPos , Text . length ( ) - CursorPos ) ;
} else {
YankBuffer = Text . substr ( CursorPos , Text . length ( ) - CursorPos ) ;
}
Text . resize ( CursorPos ) ;
CursorEnd ( ) ;
}
}
void AddChar ( int character )
{
if ( Text . length ( ) = = 0 )
{
Text + = character ;
}
else
{
Text . insert ( CursorPos , 1 , character ) ;
}
CursorPos + + ;
MakeStartPosGood ( ) ;
}
void AddString ( FString clip )
{
if ( clip . IsNotEmpty ( ) )
{
// Only paste the first line.
long brk = clip . IndexOfAny ( " \r \n \b " ) ;
std : : u32string build ;
if ( brk > = 0 )
{
clip . Truncate ( brk ) ;
}
auto strp = ( const uint8_t * ) clip . GetChars ( ) ;
while ( auto chr = GetCharFromString ( strp ) ) build + = chr ;
if ( Text . length ( ) = = 0 )
{
Text = build ;
}
else
{
Text . insert ( CursorPos , build ) ;
}
CursorPos + = ( unsigned ) build . length ( ) ;
MakeStartPosGood ( ) ;
}
}
void SetString ( const FString & str )
{
Text . clear ( ) ;
auto strp = ( const uint8_t * ) str . GetChars ( ) ;
while ( auto chr = GetCharFromString ( strp ) ) Text + = chr ;
CursorEnd ( ) ;
MakeStartPosGood ( ) ;
}
void AddYankBuffer ( )
{
if ( YankBuffer . length ( ) > 0 )
{
if ( Text . length ( ) = = 0 )
{
Text = YankBuffer ;
}
else
{
Text . insert ( CursorPos , YankBuffer ) ;
}
CursorPos + = ( unsigned ) YankBuffer . length ( ) ;
MakeStartPosGood ( ) ;
}
}
} ;
static FCommandBuffer CmdLine ;
# define MAXHISTSIZE 50
static struct History * HistHead = NULL , * HistTail = NULL , * HistPos = NULL ;
static int HistSize ;
# define NUMNOTIFIES 4
# define NOTIFYFADETIME 6
struct FNotifyText
{
int TimeOut ;
2020-04-11 22:04:02 +00:00
int Ticker ;
2019-11-05 18:57:48 +00:00
int PrintLevel ;
FString Text ;
} ;
struct FNotifyBuffer
{
public :
FNotifyBuffer ( ) ;
void AddString ( int printlevel , FString source ) ;
void Shift ( int maxlines ) ;
void Clear ( ) { Text . Clear ( ) ; }
void Tick ( ) ;
void Draw ( ) ;
2020-08-23 20:39:53 +00:00
void DrawNative ( ) ;
2019-11-05 18:57:48 +00:00
private :
TArray < FNotifyText > Text ;
int Top ;
int TopGoal ;
enum { NEWLINE , APPENDLINE , REPLACELINE } AddType ;
} ;
static FNotifyBuffer NotifyStrings ;
CUSTOM_CVAR ( Int , con_notifylines , NUMNOTIFIES , CVAR_GLOBALCONFIG | CVAR_ARCHIVE )
{
NotifyStrings . Shift ( self ) ;
}
2020-08-23 20:39:53 +00:00
int PrintColors [ PRINTLEVELS + 2 ] = { CR_UNTRANSLATED , CR_GOLD , CR_GRAY , CR_GREEN , CR_GREEN , CR_UNTRANSLATED } ;
2019-11-05 18:57:48 +00:00
static void setmsgcolor ( int index , int color ) ;
FILE * Logfile = NULL ;
FIntCVar msglevel ( " msg " , 0 , CVAR_ARCHIVE ) ;
2020-08-23 20:39:53 +00:00
CUSTOM_CVAR ( Int , msg0color , CR_UNTRANSLATED , CVAR_ARCHIVE )
2019-11-05 18:57:48 +00:00
{
setmsgcolor ( 0 , self ) ;
}
2020-08-23 20:39:53 +00:00
CUSTOM_CVAR ( Int , msg1color , CR_GOLD , CVAR_ARCHIVE )
2019-11-05 18:57:48 +00:00
{
setmsgcolor ( 1 , self ) ;
}
2020-08-23 20:39:53 +00:00
CUSTOM_CVAR ( Int , msg2color , CR_GRAY , CVAR_ARCHIVE )
2019-11-05 18:57:48 +00:00
{
setmsgcolor ( 2 , self ) ;
}
2020-08-23 20:39:53 +00:00
CUSTOM_CVAR ( Int , msg3color , CR_GREEN , CVAR_ARCHIVE )
2019-11-05 18:57:48 +00:00
{
setmsgcolor ( 3 , self ) ;
}
2020-08-23 20:39:53 +00:00
CUSTOM_CVAR ( Int , msg4color , CR_GREEN , CVAR_ARCHIVE )
2019-11-05 18:57:48 +00:00
{
setmsgcolor ( 4 , self ) ;
}
2020-08-23 20:39:53 +00:00
CUSTOM_CVAR ( Int , msgmidcolor , CR_UNTRANSLATED , CVAR_ARCHIVE )
2019-11-05 18:57:48 +00:00
{
setmsgcolor ( PRINTLEVELS , self ) ;
}
2020-08-23 20:39:53 +00:00
CUSTOM_CVAR ( Int , msgmidcolor2 , CR_BROWN , CVAR_ARCHIVE )
2019-11-05 18:57:48 +00:00
{
setmsgcolor ( PRINTLEVELS + 1 , self ) ;
}
void C_InitConback ( )
{
#if 0
conback = TexMan . CheckForTexture ( " CONBACK " , ETextureType : : MiscPatch ) ;
if ( ! conback . isValid ( ) )
{
conback = TexMan . GetTextureID ( gameinfo . TitlePage , ETextureType : : MiscPatch ) ;
conshade = MAKEARGB ( 175 , 0 , 0 , 0 ) ;
conline = true ;
}
else
{
conshade = 0 ;
conline = false ;
}
# else
conshade = MAKEARGB ( 175 , 0 , 0 , 0 ) ;
conline = true ;
# endif
}
void C_InitConsole ( int width , int height , bool ingame )
{
int cwidth , cheight ;
vidactive = ingame ;
if ( CurrentConsoleFont ! = NULL )
{
2020-04-05 20:51:53 +00:00
cwidth = CurrentConsoleFont - > GetCharWidth ( ' M ' ) ;
2019-11-05 18:57:48 +00:00
cheight = CurrentConsoleFont - > GetHeight ( ) ;
}
else
{
cwidth = cheight = 8 ;
}
ConWidth = ( width - LEFTMARGIN - RIGHTMARGIN ) ;
ConCols = ConWidth / cwidth ;
if ( conbuffer = = NULL ) conbuffer = new FConsoleBuffer ;
2019-11-06 22:40:10 +00:00
timerSetCallback ( C_Ticker ) ;
2019-11-05 18:57:48 +00:00
}
//==========================================================================
//
// CCMD atexit
//
//==========================================================================
2020-04-11 22:04:02 +00:00
UNSAFE_CCMD ( atexit )
2019-11-05 18:57:48 +00:00
{
if ( argv . argc ( ) = = 1 )
{
Printf ( " Registered atexit commands: \n " ) ;
GameAtExit * record = ExitCmdList ;
while ( record ! = NULL )
{
Printf ( " %s \n " , record - > Command . GetChars ( ) ) ;
record = record - > Next ;
}
return ;
}
for ( int i = 1 ; i < argv . argc ( ) ; + + i )
{
GameAtExit * record = new GameAtExit ( argv [ i ] ) ;
record - > Next = ExitCmdList ;
ExitCmdList = record ;
}
}
//==========================================================================
//
// C_DeinitConsole
//
// Executes the contents of the atexit cvar, if any, at quit time.
// Then releases all of the console's memory.
//
//==========================================================================
void C_DeinitConsole ( )
{
GameAtExit * cmd = ExitCmdList ;
while ( cmd ! = NULL )
{
GameAtExit * next = cmd - > Next ;
AddCommandString ( cmd - > Command ) ;
delete cmd ;
cmd = next ;
}
// Free command history
History * hist = HistTail ;
while ( hist ! = NULL )
{
History * next = hist - > Newer ;
delete hist ;
hist = next ;
}
HistTail = HistHead = HistPos = NULL ;
// Free cvars allocated at runtime
FBaseCVar * var , * next , * * nextp ;
for ( var = CVars , nextp = & CVars ; var ! = NULL ; var = next )
{
next = var - > m_Next ;
if ( var - > GetFlags ( ) & CVAR_UNSETTABLE )
{
delete var ;
* nextp = next ;
}
else
{
nextp = & var - > m_Next ;
}
}
// Free alias commands. (i.e. The "commands" that can be allocated
// at runtime.)
for ( size_t i = 0 ; i < countof ( Commands ) ; + + i )
{
FConsoleCommand * cmd = Commands [ i ] ;
while ( cmd ! = NULL )
{
FConsoleCommand * next = cmd - > m_Next ;
if ( cmd - > IsAlias ( ) )
{
delete cmd ;
}
cmd = next ;
}
}
// Make sure all tab commands are cleared before the memory for
// their names is deallocated.
C_ClearTabCommands ( ) ;
2019-12-24 17:53:29 +00:00
C_ClearDynCCmds ( ) ;
2019-11-05 18:57:48 +00:00
// Free AddToConsole()'s work buffer
if ( work ! = NULL )
{
free ( work ) ;
work = NULL ;
worklen = 0 ;
}
if ( conbuffer ! = NULL )
{
delete conbuffer ;
conbuffer = NULL ;
}
}
static void ClearConsole ( )
{
if ( conbuffer ! = NULL )
{
conbuffer - > Clear ( ) ;
}
TopLine = InsertLine = 0 ;
}
static void setmsgcolor ( int index , int color )
{
if ( ( unsigned ) color > = ( unsigned ) NUM_TEXT_COLORS )
color = 0 ;
PrintColors [ index ] = color ;
}
FNotifyBuffer : : FNotifyBuffer ( )
{
Top = TopGoal = 0 ;
AddType = NEWLINE ;
}
void FNotifyBuffer : : Shift ( int maxlines )
{
if ( maxlines > = 0 & & Text . Size ( ) > ( unsigned ) maxlines )
{
Text . Delete ( 0 , Text . Size ( ) - maxlines ) ;
}
}
void FNotifyBuffer : : AddString ( int printlevel , FString source )
{
TArray < FBrokenLines > lines ;
int width ;
2020-07-26 08:31:12 +00:00
if ( hud_messages = = 0 | |
2019-11-05 18:57:48 +00:00
source . IsEmpty ( ) | |
2020-07-21 22:42:50 +00:00
gamestate = = GS_FULLCONSOLE | |
2020-08-10 22:46:27 +00:00
gamestate = = GS_MENUSCREEN | |
2019-11-05 18:57:48 +00:00
con_notifylines = = 0 )
return ;
2020-08-25 16:03:15 +00:00
auto screenratio = ActiveRatio ( screen - > GetWidth ( ) , screen - > GetHeight ( ) ) ;
2019-11-05 18:57:48 +00:00
2020-06-30 20:01:43 +00:00
FFont * font = generic_ui ? NewSmallFont : SmallFont ? SmallFont : AlternativeSmallFont ;
2019-11-05 18:57:48 +00:00
if ( font = = nullptr ) return ; // Without an initialized font we cannot handle the message (this is for those which come here before the font system is ready.)
2020-08-25 16:03:15 +00:00
double fontscale = ( generic_ui ? 0.7 : NotifyFontScale ) * con_notifyscale ;
width = int ( 320 * ( screenratio / 1.333 ) / fontscale ) ;
2019-11-05 18:57:48 +00:00
if ( AddType = = APPENDLINE & & Text . Size ( ) > 0 & & Text [ Text . Size ( ) - 1 ] . PrintLevel = = printlevel )
{
FString str = Text [ Text . Size ( ) - 1 ] . Text + source ;
2020-08-25 16:03:15 +00:00
lines = V_BreakLines ( font , width , str ) ;
2019-11-05 18:57:48 +00:00
}
else
{
2020-08-25 16:03:15 +00:00
lines = V_BreakLines ( font , width , source ) ;
2019-11-05 18:57:48 +00:00
if ( AddType = = APPENDLINE )
{
AddType = NEWLINE ;
}
}
if ( lines . Size ( ) = = 0 )
return ;
for ( auto & line : lines )
{
FNotifyText newline ;
newline . Text = line . Text ;
2020-04-11 22:04:02 +00:00
newline . TimeOut = int ( con_notifytime * GameTicRate ) ;
newline . Ticker = 0 ;
2019-11-05 18:57:48 +00:00
newline . PrintLevel = printlevel ;
if ( AddType = = NEWLINE | | Text . Size ( ) = = 0 )
{
if ( con_notifylines > 0 )
{
Shift ( con_notifylines - 1 ) ;
}
Text . Push ( newline ) ;
}
else
{
Text [ Text . Size ( ) - 1 ] = newline ;
}
AddType = NEWLINE ;
}
switch ( source [ source . Len ( ) - 1 ] )
{
case ' \r ' : AddType = REPLACELINE ; break ;
case ' \n ' : AddType = NEWLINE ; break ;
default : AddType = APPENDLINE ; break ;
}
TopGoal = 0 ;
}
void AddToConsole ( int printlevel , const char * text )
{
conbuffer - > AddText ( printlevel , MakeUTF8 ( text ) ) ;
}
//==========================================================================
//
//
//
//==========================================================================
void WriteLineToLog ( FILE * LogFile , const char * outline )
{
// Strip out any color escape sequences before writing to the log file
TArray < char > copy ( strlen ( outline ) + 1 ) ;
const char * srcp = outline ;
char * dstp = copy . Data ( ) ;
while ( * srcp ! = 0 )
{
if ( * srcp ! = TEXTCOLOR_ESCAPE )
{
* dstp + + = * srcp + + ;
}
else if ( srcp [ 1 ] = = ' [ ' )
{
srcp + = 2 ;
while ( * srcp ! = ' ] ' & & * srcp ! = 0 ) srcp + + ;
if ( * srcp = = ' ] ' ) srcp + + ;
}
else
{
if ( srcp [ 1 ] ! = 0 ) srcp + = 2 ;
else break ;
}
}
* dstp = 0 ;
fputs ( copy . Data ( ) , LogFile ) ;
fflush ( LogFile ) ;
}
2020-04-11 21:50:43 +00:00
extern bool gameisdead ;
2019-11-05 18:57:48 +00:00
int PrintString ( int iprintlevel , const char * outline )
{
2020-04-11 21:50:43 +00:00
if ( gameisdead )
return 0 ;
2019-11-05 18:57:48 +00:00
if ( ! conbuffer ) return 0 ; // when called too early
int printlevel = iprintlevel & PRINT_TYPES ;
if ( printlevel < msglevel | | * outline = = ' \0 ' )
{
return 0 ;
}
if ( printlevel ! = PRINT_LOG | | Logfile ! = nullptr )
{
// Convert everything coming through here to UTF-8 so that all console text is in a consistent format
int count ;
outline = MakeUTF8 ( outline , & count ) ;
if ( printlevel ! = PRINT_LOG )
{
I_PrintStr ( outline ) ;
conbuffer - > AddText ( printlevel , outline ) ;
2020-08-23 20:39:53 +00:00
if ( ! ( iprintlevel & PRINT_NONOTIFY ) )
2019-11-05 18:57:48 +00:00
{
2020-08-23 20:39:53 +00:00
if ( vidactive & & ( ( iprintlevel & PRINT_NOTIFY ) | | con_notify_advanced ) )
{
NotifyStrings . AddString ( printlevel , outline ) ;
}
2019-11-05 18:57:48 +00:00
}
}
if ( Logfile ! = nullptr & & ! ( iprintlevel & PRINT_NOLOG ) )
{
WriteLineToLog ( Logfile , outline ) ;
}
return count ;
}
return 0 ; // Don't waste time on calculating this if nothing at all was printed...
}
int VPrintf ( int printlevel , const char * format , va_list parms )
{
FString outline ;
outline . VFormat ( format , parms ) ;
return PrintString ( printlevel , outline . GetChars ( ) ) ;
}
int Printf ( int printlevel , const char * format , . . . )
{
va_list argptr ;
int count ;
va_start ( argptr , format ) ;
count = VPrintf ( printlevel , format , argptr ) ;
va_end ( argptr ) ;
return count ;
}
int Printf ( const char * format , . . . )
{
va_list argptr ;
int count ;
va_start ( argptr , format ) ;
count = VPrintf ( PRINT_HIGH , format , argptr ) ;
va_end ( argptr ) ;
return count ;
}
int DPrintf ( int level , const char * format , . . . )
{
va_list argptr ;
int count ;
if ( developer > = level )
{
va_start ( argptr , format ) ;
count = VPrintf ( PRINT_HIGH , format , argptr ) ;
va_end ( argptr ) ;
return count ;
}
else
{
return 0 ;
}
}
void C_FlushDisplay ( )
{
NotifyStrings . Clear ( ) ;
}
void C_AdjustBottom ( )
{
2020-07-21 22:42:50 +00:00
if ( gamestate = = GS_FULLCONSOLE | | gamestate = = GS_STARTUP )
ConBottom = twod - > GetHeight ( ) ;
else if ( ConBottom > twod - > GetHeight ( ) / 2 | | ConsoleState = = c_down )
2020-04-11 21:27:37 +00:00
ConBottom = twod - > GetHeight ( ) / 2 ;
2019-11-05 18:57:48 +00:00
}
void C_NewModeAdjust ( )
{
2020-04-30 10:28:10 +00:00
C_InitConsole ( screen - > GetWidth ( ) , screen - > GetHeight ( ) , true ) ;
2019-11-05 18:57:48 +00:00
C_FlushDisplay ( ) ;
C_AdjustBottom ( ) ;
}
int consoletic = 0 ;
void C_Ticker ( )
{
2019-11-06 22:40:10 +00:00
// The engine timer ticks at 120 fps which is 4x too fast for this.
static int delay = 0 ;
if ( + + delay < 4 ) return ;
delay = 0 ;
2019-11-05 18:57:48 +00:00
static int lasttic = 0 ;
consoletic + + ;
if ( lasttic = = 0 )
lasttic = consoletic - 1 ;
if ( con_buffersize > 0 )
{
conbuffer - > ResizeBuffer ( con_buffersize ) ;
}
if ( ConsoleState ! = c_up )
{
if ( ConsoleState = = c_falling )
{
2020-04-11 21:27:37 +00:00
ConBottom + = ( consoletic - lasttic ) * ( twod - > GetHeight ( ) * 2 / 25 ) ;
if ( ConBottom > = twod - > GetHeight ( ) / 2 )
2019-11-05 18:57:48 +00:00
{
2020-04-11 21:27:37 +00:00
ConBottom = twod - > GetHeight ( ) / 2 ;
2019-11-05 18:57:48 +00:00
ConsoleState = c_down ;
}
}
else if ( ConsoleState = = c_rising )
{
2020-04-11 21:27:37 +00:00
ConBottom - = ( consoletic - lasttic ) * ( twod - > GetHeight ( ) * 2 / 25 ) ;
2019-11-05 18:57:48 +00:00
if ( ConBottom < = 0 )
{
ConsoleState = c_up ;
ConBottom = 0 ;
}
}
}
lasttic = consoletic ;
NotifyStrings . Tick ( ) ;
}
void FNotifyBuffer : : Tick ( )
{
if ( TopGoal > Top )
{
Top + + ;
}
else if ( TopGoal < Top )
{
Top - - ;
}
// Remove lines from the beginning that have expired.
unsigned i ;
for ( i = 0 ; i < Text . Size ( ) ; + + i )
{
2020-04-11 22:04:02 +00:00
Text [ i ] . Ticker + + ;
}
for ( i = 0 ; i < Text . Size ( ) ; + + i )
{
if ( Text [ i ] . TimeOut ! = 0 & & Text [ i ] . TimeOut > Text [ i ] . Ticker )
2019-11-05 18:57:48 +00:00
break ;
}
2020-04-11 22:04:02 +00:00
if ( i > 0 )
2019-11-05 18:57:48 +00:00
{
Text . Delete ( 0 , i ) ;
2020-06-30 20:01:43 +00:00
FFont * font = generic_ui ? NewSmallFont : SmallFont ? SmallFont : AlternativeSmallFont ;
2020-08-19 14:40:54 +00:00
Top + = font - > GetHeight ( ) / NotifyFontScale ;
2019-11-05 18:57:48 +00:00
}
}
2020-08-23 20:39:53 +00:00
void FNotifyBuffer : : DrawNative ( )
{
// Native display is:
// * centered at the top and pulsing for Duke
// * centered shifted down and not pulsing for Shadow Warrior
// * top left for Exhumed
// * 4 lines with the tiny font for Blood. (same mechanic as the regular one, just a different font and scale.)
bool center = g_gameType & ( GAMEFLAG_DUKE | GAMEFLAG_NAM | GAMEFLAG_WW2GI | GAMEFLAG_RR | GAMEFLAG_SW ) ;
bool pulse = g_gameType & ( GAMEFLAG_DUKE | GAMEFLAG_NAM | GAMEFLAG_WW2GI | GAMEFLAG_RR ) ;
unsigned topline = g_gameType & GAMEFLAG_BLOOD ? 0 : Text . Size ( ) - 1 ;
FFont * font = g_gameType & GAMEFLAG_BLOOD ? SmallFont2 : SmallFont ;
int line = ( g_gameType & GAMEFLAG_BLOOD ) ? Top : ( g_gameType & GAMEFLAG_SW ) ? 40 : font - > GetDisplacement ( ) ;
bool canskip = ( g_gameType & GAMEFLAG_BLOOD ) ;
2020-08-25 16:03:15 +00:00
double scale = 1 / ( NotifyFontScale * con_notifyscale ) ;
int lineadv = font - > GetHeight ( ) / NotifyFontScale ;
2020-08-23 20:39:53 +00:00
for ( unsigned i = topline ; i < Text . Size ( ) ; + + i )
{
FNotifyText & notify = Text [ i ] ;
if ( notify . TimeOut = = 0 )
continue ;
int j = notify . TimeOut - notify . Ticker ;
if ( j > 0 )
{
double alpha = g_gameType & GAMEFLAG_BLOOD ? ( ( j < NOTIFYFADETIME ) ? 1. * j / NOTIFYFADETIME : 1 ) : 1 ;
if ( pulse )
{
alpha * = 0.7 + 0.3 * sin ( I_msTime ( ) / 100. ) ;
}
if ( ! center )
{
DrawText ( twod , font , CR_UNTRANSLATED , 0 , line , notify . Text ,
DTA_FullscreenScale , FSMode_ScaleToHeight ,
2020-08-25 16:03:15 +00:00
DTA_VirtualWidthF , 320 * scale , DTA_VirtualHeightF , 200 * scale , DTA_KeepRatio , true ,
2020-08-23 20:39:53 +00:00
DTA_Alpha , alpha , TAG_DONE ) ;
}
else
{
2020-08-25 16:03:15 +00:00
DrawText ( twod , font , CR_UNTRANSLATED , 160 * scale - font - > StringWidth ( notify . Text ) / 2 , line , notify . Text ,
2020-08-23 20:39:53 +00:00
DTA_FullscreenScale , FSMode_ScaleToHeight ,
2020-08-25 16:03:15 +00:00
DTA_VirtualWidthF , 320 * scale , DTA_VirtualHeightF , 200 * scale ,
2020-08-23 20:39:53 +00:00
DTA_Alpha , alpha , TAG_DONE ) ;
}
line + = lineadv ;
canskip = false ;
}
else
{
notify . TimeOut = 0 ;
}
}
if ( canskip )
{
Top = TopGoal ;
}
}
2019-11-05 18:57:48 +00:00
void FNotifyBuffer : : Draw ( )
{
2020-08-10 22:46:27 +00:00
if ( gamestate = = GS_FULLCONSOLE | | gamestate = = GS_MENUSCREEN )
2020-07-21 22:42:50 +00:00
return ;
2019-11-05 18:57:48 +00:00
2020-08-23 20:39:53 +00:00
if ( ! con_notify_advanced )
{
DrawNative ( ) ;
return ;
}
bool center = ( con_centernotify ! = 0.f ) ;
int color ;
2020-08-25 16:03:15 +00:00
bool canskip = true ;
2020-08-23 20:39:53 +00:00
2019-11-05 18:57:48 +00:00
2020-08-25 16:03:15 +00:00
FFont * font = generic_ui ? NewSmallFont : SmallFont ? SmallFont : AlternativeSmallFont ;
double nfscale = ( generic_ui ? 0.7 : NotifyFontScale ) ;
double scale = 1 / ( * con_notifyscale ) ;
2019-11-05 18:57:48 +00:00
2020-08-25 16:03:15 +00:00
int line = Top + font - > GetDisplacement ( ) / nfscale ;
int lineadv = font - > GetHeight ( ) / nfscale ;
2019-11-05 18:57:48 +00:00
for ( unsigned i = 0 ; i < Text . Size ( ) ; + + i )
{
FNotifyText & notify = Text [ i ] ;
if ( notify . TimeOut = = 0 )
continue ;
2020-08-23 20:39:53 +00:00
int j = notify . TimeOut - notify . Ticker ;
2019-11-05 18:57:48 +00:00
if ( j > 0 )
{
double alpha = ( j < NOTIFYFADETIME ) ? 1. * j / NOTIFYFADETIME : 1 ;
2020-08-23 20:39:53 +00:00
if ( con_pulsetext )
2020-06-30 20:53:15 +00:00
{
alpha * = 0.7 + 0.3 * sin ( I_msTime ( ) / 100. ) ;
}
2019-11-05 18:57:48 +00:00
if ( notify . PrintLevel > = PRINTLEVELS )
color = CR_UNTRANSLATED ;
else
color = PrintColors [ notify . PrintLevel ] ;
if ( ! center )
2020-08-19 14:40:54 +00:00
DrawText ( twod , font , color , 0 , line * NotifyFontScale , notify . Text ,
2020-08-25 16:03:15 +00:00
DTA_FullscreenScale , FSMode_ScaleToHeight ,
DTA_VirtualWidthF , 320. * scale ,
DTA_VirtualHeightF , 200. * scale ,
2019-11-05 18:57:48 +00:00
DTA_KeepRatio , true ,
DTA_Alpha , alpha , TAG_DONE ) ;
else
2020-08-25 16:03:15 +00:00
DrawText ( twod , font , color , 160 * scale - font - > StringWidth ( notify . Text ) / 2. ,
2019-11-05 18:57:48 +00:00
line , notify . Text ,
2020-08-25 16:03:15 +00:00
DTA_FullscreenScale , FSMode_ScaleToHeight ,
DTA_VirtualWidthF , 320. * scale ,
DTA_VirtualHeightF , 200. * scale ,
2019-11-05 18:57:48 +00:00
DTA_Alpha , alpha , TAG_DONE ) ;
line + = lineadv ;
canskip = false ;
}
else
{
notify . TimeOut = 0 ;
}
}
if ( canskip )
{
Top = TopGoal ;
}
}
void C_DrawConsole ( )
{
static int oldbottom = 0 ;
int lines , left , offset ;
2020-05-25 15:11:32 +00:00
int textScale = active_con_scale ( twod ) ;
2019-11-05 18:57:48 +00:00
left = LEFTMARGIN ;
lines = ( ConBottom / textScale - CurrentConsoleFont - > GetHeight ( ) * 2 ) / CurrentConsoleFont - > GetHeight ( ) ;
if ( - CurrentConsoleFont - > GetHeight ( ) + lines * CurrentConsoleFont - > GetHeight ( ) > ConBottom / textScale - CurrentConsoleFont - > GetHeight ( ) * 7 / 2 )
{
offset = - CurrentConsoleFont - > GetHeight ( ) / 2 ;
lines - - ;
}
else
{
offset = - CurrentConsoleFont - > GetHeight ( ) ;
}
oldbottom = ConBottom ;
if ( ConsoleState = = c_up )
{
NotifyStrings . Draw ( ) ;
return ;
}
else if ( ConBottom )
{
int visheight ;
visheight = ConBottom ;
if ( conback )
{
2019-12-30 18:29:32 +00:00
DrawTexture ( twod , conback , 0 , visheight - screen - > GetHeight ( ) ,
2019-11-05 18:57:48 +00:00
DTA_DestWidth , screen - > GetWidth ( ) ,
DTA_DestHeight , screen - > GetHeight ( ) ,
DTA_ColorOverlay , conshade ,
DTA_Alpha , ( /*gamestate != GS_FULLCONSOLE*/ true ) ? ( double ) con_alpha : 1. ,
DTA_Masked , false ,
TAG_DONE ) ;
}
else
{
PalEntry pe ( ( uint8_t ) ( con_alpha * 255 ) , 0 , 0 , 0 ) ;
2019-12-30 18:29:32 +00:00
twod - > AddColorOnlyQuad ( 0 , 0 , screen - > GetWidth ( ) , visheight , pe ) ;
2019-11-05 18:57:48 +00:00
}
if ( conline & & visheight < screen - > GetHeight ( ) )
{
2019-12-30 18:29:32 +00:00
twod - > AddColorOnlyQuad ( 0 , visheight , screen - > GetWidth ( ) , visheight + 1 , 0xff000000 ) ;
2019-11-05 18:57:48 +00:00
}
if ( ConBottom > = 12 )
{
if ( textScale = = 1 )
2020-04-11 21:27:37 +00:00
DrawText ( twod , CurrentConsoleFont , CR_ORANGE , twod - > GetWidth ( ) - 8 -
2019-11-05 18:57:48 +00:00
CurrentConsoleFont - > StringWidth ( GetVersionString ( ) ) ,
ConBottom / textScale - CurrentConsoleFont - > GetHeight ( ) - 4 ,
GetVersionString ( ) , TAG_DONE ) ;
else
2020-04-11 21:27:37 +00:00
DrawText ( twod , CurrentConsoleFont , CR_ORANGE , twod - > GetWidth ( ) / textScale - 8 -
2019-11-05 18:57:48 +00:00
CurrentConsoleFont - > StringWidth ( GetVersionString ( ) ) ,
ConBottom / textScale - CurrentConsoleFont - > GetHeight ( ) - 4 ,
GetVersionString ( ) ,
2020-04-11 21:27:37 +00:00
DTA_VirtualWidth , twod - > GetWidth ( ) / textScale ,
DTA_VirtualHeight , twod - > GetHeight ( ) / textScale ,
2019-11-05 18:57:48 +00:00
DTA_KeepRatio , true , TAG_DONE ) ;
}
}
if ( menuactive ! = MENU_Off )
{
return ;
}
if ( lines > 0 )
{
// No more enqueuing because adding new text to the console won't touch the actual print data.
conbuffer - > FormatText ( CurrentConsoleFont , ConWidth / textScale ) ;
unsigned int consolelines = conbuffer - > GetFormattedLineCount ( ) ;
FBrokenLines * blines = conbuffer - > GetLines ( ) ;
FBrokenLines * printline = blines + consolelines - 1 - RowAdjust ;
int bottomline = ConBottom / textScale - CurrentConsoleFont - > GetHeight ( ) * 2 - 4 ;
for ( FBrokenLines * p = printline ; p > = blines & & lines > 0 ; p - - , lines - - )
{
if ( textScale = = 1 )
{
2019-12-30 18:29:32 +00:00
DrawText ( twod , CurrentConsoleFont , CR_TAN , LEFTMARGIN , offset + lines * CurrentConsoleFont - > GetHeight ( ) , p - > Text , TAG_DONE ) ;
2019-11-05 18:57:48 +00:00
}
else
{
2019-12-30 18:29:32 +00:00
DrawText ( twod , CurrentConsoleFont , CR_TAN , LEFTMARGIN , offset + lines * CurrentConsoleFont - > GetHeight ( ) , p - > Text ,
2020-04-11 21:27:37 +00:00
DTA_VirtualWidth , twod - > GetWidth ( ) / textScale ,
DTA_VirtualHeight , twod - > GetHeight ( ) / textScale ,
2019-11-05 18:57:48 +00:00
DTA_KeepRatio , true , TAG_DONE ) ;
}
}
if ( ConBottom > = 20 )
{
2020-04-11 22:04:02 +00:00
if ( gamestate ! = GS_STARTUP )
2019-11-05 18:57:48 +00:00
{
2020-04-11 22:04:02 +00:00
auto now = I_msTime ( ) ;
if ( now > CursorTicker )
{
CursorTicker = now + 500 ;
cursoron = ! cursoron ;
}
2019-11-05 18:57:48 +00:00
CmdLine . Draw ( left , bottomline , textScale , cursoron ) ;
}
if ( RowAdjust & & ConBottom > = CurrentConsoleFont - > GetHeight ( ) * 7 / 2 )
{
// Indicate that the view has been scrolled up (10)
// and if we can scroll no further (12)
if ( textScale = = 1 )
2020-04-11 21:27:37 +00:00
DrawChar ( twod , CurrentConsoleFont , CR_GREEN , 0 , bottomline , RowAdjust = = conbuffer - > GetFormattedLineCount ( ) ? 12 : 10 , TAG_DONE ) ;
2019-11-05 18:57:48 +00:00
else
2019-12-30 18:29:32 +00:00
DrawChar ( twod , CurrentConsoleFont , CR_GREEN , 0 , bottomline , RowAdjust = = conbuffer - > GetFormattedLineCount ( ) ? 12 : 10 ,
2020-04-11 21:27:37 +00:00
DTA_VirtualWidth , twod - > GetWidth ( ) / textScale ,
DTA_VirtualHeight , twod - > GetHeight ( ) / textScale ,
2019-11-05 18:57:48 +00:00
DTA_KeepRatio , true , TAG_DONE ) ;
}
}
}
}
void C_FullConsole ( )
{
2020-07-21 22:42:50 +00:00
/*
2019-11-05 18:57:48 +00:00
if ( hud_toggled )
D_ToggleHud ( ) ;
if ( demoplayback )
G_CheckDemoStatus ( ) ;
D_QuitNetGame ( ) ;
advancedemo = false ;
2020-07-21 22:42:50 +00:00
*/
2019-11-05 18:57:48 +00:00
ConsoleState = c_down ;
HistPos = NULL ;
TabbedLast = false ;
TabbedList = false ;
if ( gamestate ! = GS_STARTUP )
{
gamestate = GS_FULLCONSOLE ;
2020-07-21 22:42:50 +00:00
Mus_Stop ( ) ;
2019-11-05 18:57:48 +00:00
}
2020-07-21 22:42:50 +00:00
C_AdjustBottom ( ) ;
2019-11-05 18:57:48 +00:00
}
void C_ToggleConsole ( )
{
2020-07-21 22:42:50 +00:00
if ( gamestate = = GS_INTRO ) // blocked
2019-11-05 18:57:48 +00:00
{
2020-07-21 22:42:50 +00:00
return ;
2019-11-05 18:57:48 +00:00
}
2020-08-10 22:46:27 +00:00
if ( gamestate = = GS_MENUSCREEN )
2020-07-21 22:42:50 +00:00
{
gamestate = GS_FULLCONSOLE ;
C_FullConsole ( ) ;
}
else if ( ! chatmodeon & & ( ConsoleState = = c_up | | ConsoleState = = c_rising ) & & menuactive = = MENU_Off )
2019-11-05 18:57:48 +00:00
{
ConsoleState = c_falling ;
HistPos = NULL ;
TabbedLast = false ;
TabbedList = false ;
2020-04-23 19:18:40 +00:00
2019-11-05 18:57:48 +00:00
}
2020-07-21 22:42:50 +00:00
else if ( gamestate ! = GS_FULLCONSOLE & & gamestate ! = GS_STARTUP )
2019-11-05 18:57:48 +00:00
{
ConsoleState = c_rising ;
C_FlushDisplay ( ) ;
}
}
void C_HideConsole ( )
{
2020-04-11 22:04:02 +00:00
if ( gamestate ! = GS_FULLCONSOLE )
2019-11-05 18:57:48 +00:00
{
ConsoleState = c_up ;
ConBottom = 0 ;
HistPos = NULL ;
}
}
static bool C_HandleKey ( event_t * ev , FCommandBuffer & buffer )
{
int data1 = ev - > data1 ;
bool keepappending = false ;
switch ( ev - > subtype )
{
default :
return false ;
case EV_GUI_Char :
if ( ev - > data2 )
{
// Bash-style shortcuts
if ( data1 = = ' b ' )
{
buffer . CursorWordLeft ( ) ;
break ;
}
else if ( data1 = = ' f ' )
{
buffer . CursorWordRight ( ) ;
break ;
}
}
// Add keypress to command line
buffer . AddChar ( data1 ) ;
HistPos = NULL ;
TabbedLast = false ;
TabbedList = false ;
break ;
case EV_GUI_WheelUp :
case EV_GUI_WheelDown :
if ( ! ( ev - > data3 & GKM_SHIFT ) )
{
data1 = GK_PGDN + EV_GUI_WheelDown - ev - > subtype ;
}
else
{
data1 = GK_DOWN + EV_GUI_WheelDown - ev - > subtype ;
}
// Intentional fallthrough
case EV_GUI_KeyDown :
case EV_GUI_KeyRepeat :
switch ( data1 )
{
case ' \t ' :
// Try to do tab-completion
C_TabComplete ( ( ev - > data3 & GKM_SHIFT ) ? false : true ) ;
break ;
case GK_PGUP :
if ( ev - > data3 & ( GKM_SHIFT | GKM_CTRL ) )
{ // Scroll console buffer up one page
2020-05-25 15:11:32 +00:00
RowAdjust + = ( screen - > GetHeight ( ) - 4 ) / active_con_scale ( twod ) /
2020-07-21 22:42:50 +00:00
( ( gamestate = = GS_FULLCONSOLE | | gamestate = = GS_STARTUP ) ? CurrentConsoleFont - > GetHeight ( ) : CurrentConsoleFont - > GetHeight ( ) * 2 ) - 3 ;
2019-11-05 18:57:48 +00:00
}
else if ( RowAdjust < conbuffer - > GetFormattedLineCount ( ) )
{ // Scroll console buffer up
if ( ev - > subtype = = EV_GUI_WheelUp )
{
RowAdjust + = 3 ;
}
else
{
RowAdjust + + ;
}
if ( RowAdjust > conbuffer - > GetFormattedLineCount ( ) )
{
RowAdjust = conbuffer - > GetFormattedLineCount ( ) ;
}
}
break ;
case GK_PGDN :
if ( ev - > data3 & ( GKM_SHIFT | GKM_CTRL ) )
{ // Scroll console buffer down one page
2020-05-25 15:11:32 +00:00
const int scrollamt = ( screen - > GetHeight ( ) - 4 ) / active_con_scale ( twod ) /
2020-07-21 22:42:50 +00:00
( ( gamestate = = GS_FULLCONSOLE | | gamestate = = GS_STARTUP ) ? CurrentConsoleFont - > GetHeight ( ) : CurrentConsoleFont - > GetHeight ( ) * 2 ) - 3 ;
2019-11-05 18:57:48 +00:00
if ( RowAdjust < scrollamt )
{
RowAdjust = 0 ;
}
else
{
RowAdjust - = scrollamt ;
}
}
else if ( RowAdjust > 0 )
{ // Scroll console buffer down
if ( ev - > subtype = = EV_GUI_WheelDown )
{
RowAdjust = std : : max ( 0 , RowAdjust - 3 ) ;
}
else
{
RowAdjust - - ;
}
}
break ;
case GK_HOME :
if ( ev - > data3 & GKM_CTRL )
{ // Move to top of console buffer
RowAdjust = conbuffer - > GetFormattedLineCount ( ) ;
}
else
{ // Move cursor to start of line
buffer . CursorStart ( ) ;
}
break ;
case GK_END :
if ( ev - > data3 & GKM_CTRL )
{ // Move to bottom of console buffer
RowAdjust = 0 ;
}
else
{ // Move cursor to end of line
buffer . CursorEnd ( ) ;
}
break ;
case GK_LEFT :
// Move cursor left one character
buffer . CursorLeft ( ) ;
break ;
case GK_RIGHT :
// Move cursor right one character
buffer . CursorRight ( ) ;
break ;
case ' \b ' :
// Erase character to left of cursor
buffer . DeleteLeft ( ) ;
TabbedLast = false ;
TabbedList = false ;
break ;
case GK_DEL :
// Erase character under cursor
buffer . DeleteRight ( ) ;
TabbedLast = false ;
TabbedList = false ;
break ;
case GK_UP :
// Move to previous entry in the command history
if ( HistPos = = NULL )
{
HistPos = HistHead ;
}
else if ( HistPos - > Older )
{
HistPos = HistPos - > Older ;
}
if ( HistPos )
{
buffer . SetString ( HistPos - > String ) ;
}
TabbedLast = false ;
TabbedList = false ;
break ;
case GK_DOWN :
// Move to next entry in the command history
if ( HistPos & & HistPos - > Newer )
{
HistPos = HistPos - > Newer ;
buffer . SetString ( HistPos - > String ) ;
}
else
{
HistPos = NULL ;
buffer . SetString ( " " ) ;
}
TabbedLast = false ;
TabbedList = false ;
break ;
case ' X ' :
if ( ev - > data3 & GKM_CTRL )
{
buffer . SetString ( " " ) ;
TabbedLast = TabbedList = false ;
}
break ;
case ' D ' :
if ( ev - > data3 & GKM_CTRL & & buffer . TextLength ( ) = = 0 )
{ // Control-D pressed on an empty line
if ( strlen ( con_ctrl_d ) = = 0 )
{
break ; // Replacement is empty, so do nothing
}
buffer . SetString ( * con_ctrl_d ) ;
}
else
{
break ;
}
// Intentional fall-through for command(s) added with Ctrl-D
case ' \r ' :
{
// Execute command line (ENTER)
FString bufferText = buffer . GetText ( ) ;
bufferText . StripLeftRight ( ) ;
Printf ( 127 , TEXTCOLOR_WHITE " ]%s \n " , bufferText . GetChars ( ) ) ;
if ( bufferText . Len ( ) = = 0 )
{
// Command line is empty, so do nothing to the history
}
else if ( HistHead & & HistHead - > String . CompareNoCase ( bufferText ) = = 0 )
{
// Command line was the same as the previous one,
// so leave the history list alone
}
else
{
// Command line is different from last command line,
// or there is nothing in the history list,
// so add it to the history list.
History * temp = new History ;
temp - > String = bufferText ;
temp - > Older = HistHead ;
if ( HistHead )
{
HistHead - > Newer = temp ;
}
temp - > Newer = NULL ;
HistHead = temp ;
if ( ! HistTail )
{
HistTail = temp ;
}
if ( HistSize = = MAXHISTSIZE )
{
HistTail = HistTail - > Newer ;
delete HistTail - > Older ;
HistTail - > Older = NULL ;
}
else
{
HistSize + + ;
}
}
HistPos = NULL ;
buffer . SetString ( " " ) ;
AddCommandString ( bufferText ) ;
TabbedLast = false ;
TabbedList = false ;
break ;
}
case ' ` ' :
// Check to see if we have ` bound to the console before accepting
// it as a way to close the console.
if ( Bindings . GetBinding ( KEY_GRAVE ) . CompareNoCase ( " toggleconsole " ) )
{
break ;
}
case GK_ESCAPE :
// Close console and clear command line. But if we're in the
// fullscreen console mode, there's nothing to fall back on
// if it's closed, so open the main menu instead.
if ( gamestate = = GS_STARTUP )
{
return false ;
}
else if ( gamestate = = GS_FULLCONSOLE )
{
2020-08-10 22:46:27 +00:00
gamestate = GS_MENUSCREEN ;
2019-11-05 18:57:48 +00:00
C_DoCommand ( " menu_main " ) ;
}
else
{
buffer . SetString ( " " ) ;
HistPos = NULL ;
C_ToggleConsole ( ) ;
}
break ;
case ' C ' :
case ' V ' :
TabbedLast = false ;
TabbedList = false ;
# ifdef __APPLE__
if ( ev - > data3 & GKM_META )
# else // !__APPLE__
if ( ev - > data3 & GKM_CTRL )
# endif // __APPLE__
{
if ( data1 = = ' C ' )
{ // copy to clipboard
if ( buffer . TextLength ( ) > 0 )
{
I_PutInClipboard ( buffer . GetText ( ) ) ;
}
}
else
{ // paste from clipboard
buffer . AddString ( I_GetFromClipboard ( false ) ) ;
HistPos = NULL ;
}
break ;
}
break ;
// Bash-style shortcuts
case ' A ' :
if ( ev - > data3 & GKM_CTRL )
{
buffer . CursorStart ( ) ;
}
break ;
case ' E ' :
if ( ev - > data3 & GKM_CTRL )
{
buffer . CursorEnd ( ) ;
}
break ;
case ' W ' :
if ( ev - > data3 & GKM_CTRL )
{
buffer . DeleteWordLeft ( ) ;
keepappending = true ;
TabbedLast = false ;
TabbedList = false ;
}
break ;
case ' U ' :
if ( ev - > data3 & GKM_CTRL )
{
buffer . DeleteLineLeft ( ) ;
keepappending = true ;
TabbedLast = false ;
TabbedList = false ;
}
break ;
case ' K ' :
if ( ev - > data3 & GKM_CTRL )
{
buffer . DeleteLineRight ( ) ;
keepappending = true ;
TabbedLast = false ;
TabbedList = false ;
}
break ;
case ' Y ' :
if ( ev - > data3 & GKM_CTRL )
{
buffer . AddYankBuffer ( ) ;
TabbedLast = false ;
TabbedList = false ;
HistPos = NULL ;
}
break ;
}
break ;
# ifdef __unix__
case EV_GUI_MButtonDown :
buffer . AddString ( I_GetFromClipboard ( true ) ) ;
HistPos = NULL ;
break ;
# endif
}
buffer . AppendToYankBuffer = keepappending ;
// Ensure that the cursor is always visible while typing
2020-04-11 22:04:02 +00:00
CursorTicker = I_msTime ( ) + 500 ;
2019-11-05 18:57:48 +00:00
cursoron = 1 ;
return true ;
}
bool C_Responder ( event_t * ev )
{
if ( ev - > type ! = EV_GUI_Event | |
ConsoleState = = c_up | |
2020-07-21 22:42:50 +00:00
ConsoleState = = c_rising | |
menuactive ! = MENU_Off )
2019-11-05 18:57:48 +00:00
{
return false ;
}
return C_HandleKey ( ev , CmdLine ) ;
}
CCMD ( history )
{
struct History * hist = HistTail ;
while ( hist )
{
Printf ( " %s \n " , hist - > String . GetChars ( ) ) ;
hist = hist - > Newer ;
}
}
CCMD ( clear )
{
C_FlushDisplay ( ) ;
ClearConsole ( ) ;
}
CCMD ( echo )
{
int last = argv . argc ( ) - 1 ;
for ( int i = 1 ; i < = last ; + + i )
{
FString formatted = strbin1 ( argv [ i ] ) ;
Printf ( " %s%s " , formatted . GetChars ( ) , i ! = last ? " " : " \n " ) ;
}
}
2019-11-06 22:40:10 +00:00
CCMD ( toggleconsole )
{
C_ToggleConsole ( ) ;
}
2019-11-05 18:57:48 +00:00
/* Printing in the middle of the screen */
CVAR ( Float , con_midtime , 3.f , CVAR_ARCHIVE )
const char * console_bar = " ---------------------------------------- " ;
void C_MidPrint ( FFont * font , const char * msg , bool bold )
{
2020-06-14 16:57:55 +00:00
#if 0 // The Build engine cannot do this at the moment. Q: Implement and redirect some messages here?
2019-11-05 18:57:48 +00:00
if ( StatusBar = = nullptr | | screen = = nullptr )
return ;
// [MK] allow the status bar to take over MidPrint
IFVIRTUALPTR ( StatusBar , DBaseStatusBar , ProcessMidPrint )
{
FString msgstr = msg ;
VMValue params [ ] = { ( DObject * ) StatusBar , font , & msgstr , bold } ;
int rv ;
VMReturn ret ( & rv ) ;
VMCall ( func , params , countof ( params ) , & ret , 1 ) ;
if ( ! ! rv ) return ;
}
if ( msg ! = nullptr )
{
auto color = ( EColorRange ) PrintColors [ bold ? PRINTLEVELS + 1 : PRINTLEVELS ] ;
2019-12-04 20:35:35 +00:00
Printf ( PRINT_HIGH , TEXTCOLOR_ESCAPESTR " %c%s \n %s \n %s \n " , color , console_bar , msg , console_bar ) ;
2019-11-05 18:57:48 +00:00
StatusBar - > AttachMessage ( Create < DHUDMessage > ( font , msg , 1.5f , 0.375f , 0 , 0 , color , con_midtime ) , MAKE_ID ( ' C ' , ' N ' , ' T ' , ' R ' ) ) ;
}
else
{
StatusBar - > DetachMessage ( MAKE_ID ( ' C ' , ' N ' , ' T ' , ' R ' ) ) ;
}
# endif
2020-06-14 16:57:55 +00:00
}
2019-11-05 18:57:48 +00:00
/****** Tab completion code ******/
struct TabData
{
int UseCount ;
FName TabName ;
TabData ( )
: UseCount ( 0 ) , TabName ( NAME_None )
{
}
TabData ( const char * name )
: UseCount ( 1 ) , TabName ( name )
{
}
TabData ( const TabData & other ) = default ;
} ;
static TArray < TabData > TabCommands ( TArray < TabData > : : NoInit ) ;
static int TabPos ; // Last TabCommand tabbed to
static int TabStart ; // First char in CmdLine to use for tab completion
static int TabSize ; // Size of tab string
static bool FindTabCommand ( const char * name , int * stoppos , int len )
{
FName aname ( name ) ;
unsigned int i ;
int cval = 1 ;
for ( i = 0 ; i < TabCommands . Size ( ) ; i + + )
{
if ( TabCommands [ i ] . TabName = = aname )
{
* stoppos = i ;
return true ;
}
cval = strnicmp ( TabCommands [ i ] . TabName . GetChars ( ) , name , len ) ;
if ( cval > = 0 )
break ;
}
* stoppos = i ;
return ( cval = = 0 ) ;
}
void C_AddTabCommand ( const char * name )
{
int pos ;
if ( FindTabCommand ( name , & pos , INT_MAX ) )
{
TabCommands [ pos ] . UseCount + + ;
}
else
{
TabData tab ( name ) ;
TabCommands . Insert ( pos , tab ) ;
}
}
void C_RemoveTabCommand ( const char * name )
{
if ( TabCommands . Size ( ) = = 0 )
{
// There are no tab commands that can be removed.
// This is important to skip construction of aname
// in case the NameManager has already been destroyed.
return ;
}
FName aname ( name , true ) ;
if ( aname = = NAME_None )
{
return ;
}
for ( unsigned int i = 0 ; i < TabCommands . Size ( ) ; + + i )
{
if ( TabCommands [ i ] . TabName = = aname )
{
if ( - - TabCommands [ i ] . UseCount = = 0 )
{
TabCommands . Delete ( i ) ;
}
break ;
}
}
}
void C_ClearTabCommands ( )
{
TabCommands . Clear ( ) ;
}
static int FindDiffPoint ( FName name1 , const char * str2 )
{
const char * str1 = name1 . GetChars ( ) ;
int i ;
for ( i = 0 ; tolower ( str1 [ i ] ) = = tolower ( str2 [ i ] ) ; i + + )
if ( str1 [ i ] = = 0 | | str2 [ i ] = = 0 )
break ;
return i ;
}
static void C_TabComplete ( bool goForward )
{
unsigned i ;
int diffpoint ;
auto CmdLineText = CmdLine . GetText ( ) ;
if ( ! TabbedLast )
{
bool cancomplete ;
// Skip any spaces at beginning of command line
for ( i = 0 ; i < CmdLineText . Len ( ) ; + + i )
{
if ( CmdLineText [ i ] ! = ' ' )
break ;
}
if ( i = = CmdLineText . Len ( ) )
{ // Line was nothing but spaces
return ;
}
TabStart = i ;
TabSize = ( int ) CmdLineText . Len ( ) - TabStart ;
if ( ! FindTabCommand ( & CmdLineText [ TabStart ] , & TabPos , TabSize ) )
return ; // No initial matches
// Show a list of possible completions, if more than one.
if ( TabbedList | | con_notablist )
{
cancomplete = true ;
}
else
{
cancomplete = C_TabCompleteList ( ) ;
TabbedList = true ;
}
if ( goForward )
{ // Position just before the list of completions so that when TabPos
// gets advanced below, it will be at the first one.
- - TabPos ;
}
else
{ // Find the last matching tab, then go one past it.
while ( + + TabPos < ( int ) TabCommands . Size ( ) )
{
if ( FindDiffPoint ( TabCommands [ TabPos ] . TabName , & CmdLineText [ TabStart ] ) < TabSize )
{
break ;
}
}
}
TabbedLast = true ;
if ( ! cancomplete )
{
return ;
}
}
if ( ( goForward & & + + TabPos = = ( int ) TabCommands . Size ( ) ) | |
( ! goForward & & - - TabPos < 0 ) )
{
TabbedLast = false ;
CmdLineText . Truncate ( TabSize ) ;
}
else
{
diffpoint = FindDiffPoint ( TabCommands [ TabPos ] . TabName , & CmdLineText [ TabStart ] ) ;
if ( diffpoint < TabSize )
{
// No more matches
TabbedLast = false ;
CmdLineText . Truncate ( TabSize - TabStart ) ;
}
else
{
CmdLineText . Truncate ( TabStart ) ;
2020-04-11 22:04:02 +00:00
CmdLineText < < TabCommands [ TabPos ] . TabName . GetChars ( ) < < ' ' ;
2019-11-05 18:57:48 +00:00
}
}
CmdLine . SetString ( CmdLineText ) ;
CmdLine . MakeStartPosGood ( ) ;
}
static bool C_TabCompleteList ( )
{
int nummatches , i ;
size_t maxwidth ;
int commonsize = INT_MAX ;
nummatches = 0 ;
maxwidth = 0 ;
auto CmdLineText = CmdLine . GetText ( ) ;
for ( i = TabPos ; i < ( int ) TabCommands . Size ( ) ; + + i )
{
if ( FindDiffPoint ( TabCommands [ i ] . TabName , & CmdLineText [ TabStart ] ) < TabSize )
{
break ;
}
else
{
if ( i > TabPos )
{
// This keeps track of the longest common prefix for all the possible
// completions, so we can fill in part of the command for the user if
// the longest common prefix is longer than what the user already typed.
int diffpt = FindDiffPoint ( TabCommands [ i - 1 ] . TabName , TabCommands [ i ] . TabName . GetChars ( ) ) ;
if ( diffpt < commonsize )
{
commonsize = diffpt ;
}
}
nummatches + + ;
maxwidth = std : : max ( maxwidth , strlen ( TabCommands [ i ] . TabName . GetChars ( ) ) ) ;
}
}
if ( nummatches > 1 )
{
size_t x = 0 ;
maxwidth + = 3 ;
Printf ( TEXTCOLOR_BLUE " Completions for %s: \n " , CmdLineText . GetChars ( ) ) ;
for ( i = TabPos ; nummatches > 0 ; + + i , - - nummatches )
{
// [Dusk] Print console commands blue, CVars green, aliases red.
const char * colorcode = " " ;
FConsoleCommand * ccmd ;
2020-04-11 21:50:43 +00:00
if ( FindCVar ( TabCommands [ i ] . TabName . GetChars ( ) , NULL ) )
2019-11-05 18:57:48 +00:00
colorcode = TEXTCOLOR_GREEN ;
2020-04-11 21:50:43 +00:00
else if ( ( ccmd = FConsoleCommand : : FindByName ( TabCommands [ i ] . TabName . GetChars ( ) ) ) ! = NULL )
2019-11-05 18:57:48 +00:00
{
if ( ccmd - > IsAlias ( ) )
colorcode = TEXTCOLOR_RED ;
else
colorcode = TEXTCOLOR_LIGHTBLUE ;
}
Printf ( " %s%-*s " , colorcode , int ( maxwidth ) , TabCommands [ i ] . TabName . GetChars ( ) ) ;
x + = maxwidth ;
2020-05-25 15:11:32 +00:00
if ( x > ConCols / active_con_scale ( twod ) - maxwidth )
2019-11-05 18:57:48 +00:00
{
x = 0 ;
Printf ( " \n " ) ;
}
}
if ( x ! = 0 )
{
Printf ( " \n " ) ;
}
// Fill in the longest common prefix, if it's longer than what was typed.
if ( TabSize ! = commonsize )
{
TabSize = commonsize ;
CmdLineText . Truncate ( TabStart ) ;
CmdLineText . AppendCStrPart ( TabCommands [ TabPos ] . TabName . GetChars ( ) , commonsize ) ;
CmdLine . SetString ( CmdLineText ) ;
}
return false ;
}
return true ;
}