/*
===========================================================================
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;
}