q3rally/engine/code/client/cl_keys.c

1585 lines
34 KiB
C
Raw Normal View History

2011-02-18 14:31:32 +00:00
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena 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 2 of the License,
or (at your option) any later version.
Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "client.h"
/*
key up events are sent even if in console mode
*/
field_t 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
field_t g_consoleField;
field_t chatField;
qboolean chat_team;
int chat_playerNum;
qboolean key_overstrikeMode;
int anykeydown;
qkey_t keys[MAX_KEYS];
typedef struct {
char *name;
int keynum;
} keyname_t;
// names not in this list can either be lowercase ascii, or '0xnn' hex sequences
keyname_t keynames[] =
{
{"TAB", K_TAB},
{"ENTER", K_ENTER},
{"ESCAPE", K_ESCAPE},
{"SPACE", K_SPACE},
{"BACKSPACE", K_BACKSPACE},
{"UPARROW", K_UPARROW},
{"DOWNARROW", K_DOWNARROW},
{"LEFTARROW", K_LEFTARROW},
{"RIGHTARROW", K_RIGHTARROW},
{"ALT", K_ALT},
{"CTRL", K_CTRL},
{"SHIFT", K_SHIFT},
{"COMMAND", K_COMMAND},
{"CAPSLOCK", K_CAPSLOCK},
{"F1", K_F1},
{"F2", K_F2},
{"F3", K_F3},
{"F4", K_F4},
{"F5", K_F5},
{"F6", K_F6},
{"F7", K_F7},
{"F8", K_F8},
{"F9", K_F9},
{"F10", K_F10},
{"F11", K_F11},
{"F12", K_F12},
{"F13", K_F13},
{"F14", K_F14},
{"F15", K_F15},
{"INS", K_INS},
{"DEL", K_DEL},
{"PGDN", K_PGDN},
{"PGUP", K_PGUP},
{"HOME", K_HOME},
{"END", K_END},
{"MOUSE1", K_MOUSE1},
{"MOUSE2", K_MOUSE2},
{"MOUSE3", K_MOUSE3},
{"MOUSE4", K_MOUSE4},
{"MOUSE5", K_MOUSE5},
{"MWHEELUP", K_MWHEELUP },
{"MWHEELDOWN", K_MWHEELDOWN },
{"JOY1", K_JOY1},
{"JOY2", K_JOY2},
{"JOY3", K_JOY3},
{"JOY4", K_JOY4},
{"JOY5", K_JOY5},
{"JOY6", K_JOY6},
{"JOY7", K_JOY7},
{"JOY8", K_JOY8},
{"JOY9", K_JOY9},
{"JOY10", K_JOY10},
{"JOY11", K_JOY11},
{"JOY12", K_JOY12},
{"JOY13", K_JOY13},
{"JOY14", K_JOY14},
{"JOY15", K_JOY15},
{"JOY16", K_JOY16},
{"JOY17", K_JOY17},
{"JOY18", K_JOY18},
{"JOY19", K_JOY19},
{"JOY20", K_JOY20},
{"JOY21", K_JOY21},
{"JOY22", K_JOY22},
{"JOY23", K_JOY23},
{"JOY24", K_JOY24},
{"JOY25", K_JOY25},
{"JOY26", K_JOY26},
{"JOY27", K_JOY27},
{"JOY28", K_JOY28},
{"JOY29", K_JOY29},
{"JOY30", K_JOY30},
{"JOY31", K_JOY31},
{"JOY32", K_JOY32},
{"AUX1", K_AUX1},
{"AUX2", K_AUX2},
{"AUX3", K_AUX3},
{"AUX4", K_AUX4},
{"AUX5", K_AUX5},
{"AUX6", K_AUX6},
{"AUX7", K_AUX7},
{"AUX8", K_AUX8},
{"AUX9", K_AUX9},
{"AUX10", K_AUX10},
{"AUX11", K_AUX11},
{"AUX12", K_AUX12},
{"AUX13", K_AUX13},
{"AUX14", K_AUX14},
{"AUX15", K_AUX15},
{"AUX16", K_AUX16},
{"KP_HOME", K_KP_HOME },
{"KP_UPARROW", K_KP_UPARROW },
{"KP_PGUP", K_KP_PGUP },
{"KP_LEFTARROW", K_KP_LEFTARROW },
{"KP_5", K_KP_5 },
{"KP_RIGHTARROW", K_KP_RIGHTARROW },
{"KP_END", K_KP_END },
{"KP_DOWNARROW", K_KP_DOWNARROW },
{"KP_PGDN", K_KP_PGDN },
{"KP_ENTER", K_KP_ENTER },
{"KP_INS", K_KP_INS },
{"KP_DEL", K_KP_DEL },
{"KP_SLASH", K_KP_SLASH },
{"KP_MINUS", K_KP_MINUS },
{"KP_PLUS", K_KP_PLUS },
{"KP_NUMLOCK", K_KP_NUMLOCK },
{"KP_STAR", K_KP_STAR },
{"KP_EQUALS", K_KP_EQUALS },
{"PAUSE", K_PAUSE},
{"SEMICOLON", ';'}, // because a raw semicolon separates commands
2011-02-18 14:31:32 +00:00
{"WORLD_0", K_WORLD_0},
{"WORLD_1", K_WORLD_1},
{"WORLD_2", K_WORLD_2},
{"WORLD_3", K_WORLD_3},
{"WORLD_4", K_WORLD_4},
{"WORLD_5", K_WORLD_5},
{"WORLD_6", K_WORLD_6},
{"WORLD_7", K_WORLD_7},
{"WORLD_8", K_WORLD_8},
{"WORLD_9", K_WORLD_9},
{"WORLD_10", K_WORLD_10},
{"WORLD_11", K_WORLD_11},
{"WORLD_12", K_WORLD_12},
{"WORLD_13", K_WORLD_13},
{"WORLD_14", K_WORLD_14},
{"WORLD_15", K_WORLD_15},
{"WORLD_16", K_WORLD_16},
{"WORLD_17", K_WORLD_17},
{"WORLD_18", K_WORLD_18},
{"WORLD_19", K_WORLD_19},
{"WORLD_20", K_WORLD_20},
{"WORLD_21", K_WORLD_21},
{"WORLD_22", K_WORLD_22},
{"WORLD_23", K_WORLD_23},
{"WORLD_24", K_WORLD_24},
{"WORLD_25", K_WORLD_25},
{"WORLD_26", K_WORLD_26},
{"WORLD_27", K_WORLD_27},
{"WORLD_28", K_WORLD_28},
{"WORLD_29", K_WORLD_29},
{"WORLD_30", K_WORLD_30},
{"WORLD_31", K_WORLD_31},
{"WORLD_32", K_WORLD_32},
{"WORLD_33", K_WORLD_33},
{"WORLD_34", K_WORLD_34},
{"WORLD_35", K_WORLD_35},
{"WORLD_36", K_WORLD_36},
{"WORLD_37", K_WORLD_37},
{"WORLD_38", K_WORLD_38},
{"WORLD_39", K_WORLD_39},
{"WORLD_40", K_WORLD_40},
{"WORLD_41", K_WORLD_41},
{"WORLD_42", K_WORLD_42},
{"WORLD_43", K_WORLD_43},
{"WORLD_44", K_WORLD_44},
{"WORLD_45", K_WORLD_45},
{"WORLD_46", K_WORLD_46},
{"WORLD_47", K_WORLD_47},
{"WORLD_48", K_WORLD_48},
{"WORLD_49", K_WORLD_49},
{"WORLD_50", K_WORLD_50},
{"WORLD_51", K_WORLD_51},
{"WORLD_52", K_WORLD_52},
{"WORLD_53", K_WORLD_53},
{"WORLD_54", K_WORLD_54},
{"WORLD_55", K_WORLD_55},
{"WORLD_56", K_WORLD_56},
{"WORLD_57", K_WORLD_57},
{"WORLD_58", K_WORLD_58},
{"WORLD_59", K_WORLD_59},
{"WORLD_60", K_WORLD_60},
{"WORLD_61", K_WORLD_61},
{"WORLD_62", K_WORLD_62},
{"WORLD_63", K_WORLD_63},
{"WORLD_64", K_WORLD_64},
{"WORLD_65", K_WORLD_65},
{"WORLD_66", K_WORLD_66},
{"WORLD_67", K_WORLD_67},
{"WORLD_68", K_WORLD_68},
{"WORLD_69", K_WORLD_69},
{"WORLD_70", K_WORLD_70},
{"WORLD_71", K_WORLD_71},
{"WORLD_72", K_WORLD_72},
{"WORLD_73", K_WORLD_73},
{"WORLD_74", K_WORLD_74},
{"WORLD_75", K_WORLD_75},
{"WORLD_76", K_WORLD_76},
{"WORLD_77", K_WORLD_77},
{"WORLD_78", K_WORLD_78},
{"WORLD_79", K_WORLD_79},
{"WORLD_80", K_WORLD_80},
{"WORLD_81", K_WORLD_81},
{"WORLD_82", K_WORLD_82},
{"WORLD_83", K_WORLD_83},
{"WORLD_84", K_WORLD_84},
{"WORLD_85", K_WORLD_85},
{"WORLD_86", K_WORLD_86},
{"WORLD_87", K_WORLD_87},
{"WORLD_88", K_WORLD_88},
{"WORLD_89", K_WORLD_89},
{"WORLD_90", K_WORLD_90},
{"WORLD_91", K_WORLD_91},
{"WORLD_92", K_WORLD_92},
{"WORLD_93", K_WORLD_93},
{"WORLD_94", K_WORLD_94},
{"WORLD_95", K_WORLD_95},
{"WINDOWS", K_SUPER},
{"COMPOSE", K_COMPOSE},
{"MODE", K_MODE},
{"HELP", K_HELP},
{"PRINT", K_PRINT},
{"SYSREQ", K_SYSREQ},
{"SCROLLOCK", K_SCROLLOCK },
{"BREAK", K_BREAK},
{"MENU", K_MENU},
{"POWER", K_POWER},
{"EURO", K_EURO},
{"UNDO", K_UNDO},
{"PAD0_A", K_PAD0_A },
{"PAD0_B", K_PAD0_B },
{"PAD0_X", K_PAD0_X },
{"PAD0_Y", K_PAD0_Y },
{"PAD0_BACK", K_PAD0_BACK },
{"PAD0_GUIDE", K_PAD0_GUIDE },
{"PAD0_START", K_PAD0_START },
{"PAD0_LEFTSTICK_CLICK", K_PAD0_LEFTSTICK_CLICK },
{"PAD0_RIGHTSTICK_CLICK", K_PAD0_RIGHTSTICK_CLICK },
{"PAD0_LEFTSHOULDER", K_PAD0_LEFTSHOULDER },
{"PAD0_RIGHTSHOULDER", K_PAD0_RIGHTSHOULDER },
{"PAD0_DPAD_UP", K_PAD0_DPAD_UP },
{"PAD0_DPAD_DOWN", K_PAD0_DPAD_DOWN },
{"PAD0_DPAD_LEFT", K_PAD0_DPAD_LEFT },
{"PAD0_DPAD_RIGHT", K_PAD0_DPAD_RIGHT },
{"PAD0_LEFTSTICK_LEFT", K_PAD0_LEFTSTICK_LEFT },
{"PAD0_LEFTSTICK_RIGHT", K_PAD0_LEFTSTICK_RIGHT },
{"PAD0_LEFTSTICK_UP", K_PAD0_LEFTSTICK_UP },
{"PAD0_LEFTSTICK_DOWN", K_PAD0_LEFTSTICK_DOWN },
{"PAD0_RIGHTSTICK_LEFT", K_PAD0_RIGHTSTICK_LEFT },
{"PAD0_RIGHTSTICK_RIGHT", K_PAD0_RIGHTSTICK_RIGHT },
{"PAD0_RIGHTSTICK_UP", K_PAD0_RIGHTSTICK_UP },
{"PAD0_RIGHTSTICK_DOWN", K_PAD0_RIGHTSTICK_DOWN },
{"PAD0_LEFTTRIGGER", K_PAD0_LEFTTRIGGER },
{"PAD0_RIGHTTRIGGER", K_PAD0_RIGHTTRIGGER },
2011-02-18 14:31:32 +00:00
{NULL,0}
};
/*
=============================================================================
EDIT FIELDS
=============================================================================
*/
/*
===================
Field_Draw
Handles horizontal scrolling and cursor blinking
x, y, and width are in pixels
===================
*/
void Field_VariableSizeDraw( field_t *edit, int x, int y, int width, int size, qboolean showCursor,
qboolean noColorEscape ) {
int len;
int drawLen;
int prestep;
int cursorChar;
char str[MAX_STRING_CHARS];
int i;
drawLen = edit->widthInChars - 1; // - 1 so there is always a space for the cursor
len = strlen( edit->buffer );
// guarantee that cursor will be visible
if ( len <= drawLen ) {
prestep = 0;
} else {
if ( edit->scroll + drawLen > len ) {
edit->scroll = len - drawLen;
if ( edit->scroll < 0 ) {
edit->scroll = 0;
}
}
prestep = edit->scroll;
}
if ( prestep + drawLen > len ) {
drawLen = len - prestep;
}
// extract <drawLen> characters from the field at <prestep>
if ( drawLen >= MAX_STRING_CHARS ) {
Com_Error( ERR_DROP, "drawLen >= MAX_STRING_CHARS" );
}
Com_Memcpy( str, edit->buffer + prestep, drawLen );
str[ drawLen ] = 0;
// draw it
if ( size == SMALLCHAR_WIDTH ) {
float color[4];
color[0] = color[1] = color[2] = color[3] = 1.0;
SCR_DrawSmallStringExt( x, y, str, color, qfalse, noColorEscape );
} else {
// draw big string with drop shadow
SCR_DrawBigString( x, y, str, 1.0, noColorEscape );
}
// draw the cursor
if ( showCursor ) {
if ( (int)( cls.realtime >> 8 ) & 1 ) {
return; // off blink
}
if ( key_overstrikeMode ) {
cursorChar = 11;
} else {
cursorChar = 10;
}
i = drawLen - strlen( str );
if ( size == SMALLCHAR_WIDTH ) {
SCR_DrawSmallChar( x + ( edit->cursor - prestep - i ) * size, y, cursorChar );
} else {
str[0] = cursorChar;
str[1] = 0;
SCR_DrawBigString( x + ( edit->cursor - prestep - i ) * size, y, str, 1.0, qfalse );
}
}
}
void Field_Draw( field_t *edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape )
{
Field_VariableSizeDraw( edit, x, y, width, SMALLCHAR_WIDTH, showCursor, noColorEscape );
}
void Field_BigDraw( field_t *edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape )
{
Field_VariableSizeDraw( edit, x, y, width, BIGCHAR_WIDTH, showCursor, noColorEscape );
}
/*
================
Field_Paste
================
*/
void Field_Paste( field_t *edit ) {
char *cbd;
int pasteLen, i;
cbd = Sys_GetClipboardData();
if ( !cbd ) {
return;
}
// send as if typed, so insert / overstrike works properly
pasteLen = strlen( cbd );
for ( i = 0 ; i < pasteLen ; i++ ) {
Field_CharEvent( edit, cbd[i] );
}
Z_Free( cbd );
}
/*
=================
Field_KeyDownEvent
Performs the basic line editing functions for the console,
in-game talk, and menu fields
Key events are used for non-printable characters, others are gotten from char events.
=================
*/
void Field_KeyDownEvent( field_t *edit, int key ) {
int len;
// shift-insert is paste
if ( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && keys[K_SHIFT].down ) {
Field_Paste( edit );
return;
}
key = tolower( key );
len = strlen( edit->buffer );
switch ( key ) {
case K_DEL:
if ( edit->cursor < len ) {
memmove( edit->buffer + edit->cursor,
edit->buffer + edit->cursor + 1, len - edit->cursor );
}
break;
case K_RIGHTARROW:
if ( edit->cursor < len ) {
edit->cursor++;
}
break;
case K_LEFTARROW:
if ( edit->cursor > 0 ) {
edit->cursor--;
}
break;
case K_HOME:
edit->cursor = 0;
break;
case K_END:
edit->cursor = len;
break;
case K_INS:
key_overstrikeMode = !key_overstrikeMode;
break;
default:
break;
}
// Change scroll if cursor is no longer visible
if ( edit->cursor < edit->scroll ) {
edit->scroll = edit->cursor;
} else if ( edit->cursor >= edit->scroll + edit->widthInChars && edit->cursor <= len ) {
edit->scroll = edit->cursor - edit->widthInChars + 1;
}
}
/*
==================
Field_CharEvent
==================
*/
void Field_CharEvent( field_t *edit, int ch ) {
int len;
if ( ch == 'v' - 'a' + 1 ) { // ctrl-v is paste
Field_Paste( edit );
return;
}
if ( ch == 'c' - 'a' + 1 ) { // ctrl-c clears the field
Field_Clear( edit );
return;
}
len = strlen( edit->buffer );
if ( ch == 'h' - 'a' + 1 ) { // ctrl-h is backspace
if ( edit->cursor > 0 ) {
memmove( edit->buffer + edit->cursor - 1,
edit->buffer + edit->cursor, len + 1 - edit->cursor );
edit->cursor--;
if ( edit->cursor < edit->scroll )
{
edit->scroll--;
}
}
return;
}
if ( ch == 'a' - 'a' + 1 ) { // ctrl-a is home
edit->cursor = 0;
edit->scroll = 0;
return;
}
if ( ch == 'e' - 'a' + 1 ) { // ctrl-e is end
edit->cursor = len;
edit->scroll = edit->cursor - edit->widthInChars;
return;
}
//
// ignore any other non printable chars
//
if ( ch < 32 ) {
return;
}
if ( key_overstrikeMode ) {
// - 2 to leave room for the leading slash and trailing \0
if ( edit->cursor == MAX_EDIT_LINE - 2 )
return;
edit->buffer[edit->cursor] = ch;
edit->cursor++;
} else { // insert mode
// - 2 to leave room for the leading slash and trailing \0
if ( len == MAX_EDIT_LINE - 2 ) {
return; // all full
}
memmove( edit->buffer + edit->cursor + 1,
edit->buffer + edit->cursor, len + 1 - edit->cursor );
edit->buffer[edit->cursor] = ch;
edit->cursor++;
}
if ( edit->cursor >= edit->widthInChars ) {
edit->scroll++;
}
if ( edit->cursor == len + 1) {
edit->buffer[edit->cursor] = 0;
}
}
/*
=============================================================================
CONSOLE LINE EDITING
==============================================================================
*/
/*
====================
Console_Key
Handles history and console scrollback
====================
*/
void Console_Key (int key) {
// ctrl-L clears screen
if ( key == 'l' && keys[K_CTRL].down ) {
Cbuf_AddText ("clear\n");
return;
}
// enter finishes the line
if ( key == K_ENTER || key == K_KP_ENTER ) {
// if not in the game explicitly prepend a slash if needed
if ( clc.state != CA_ACTIVE && con_autochat->integer &&
2011-02-18 14:31:32 +00:00
g_consoleField.buffer[0] &&
g_consoleField.buffer[0] != '\\' &&
g_consoleField.buffer[0] != '/' ) {
char temp[MAX_EDIT_LINE-1];
Q_strncpyz( temp, g_consoleField.buffer, sizeof( temp ) );
Com_sprintf( g_consoleField.buffer, sizeof( g_consoleField.buffer ), "\\%s", temp );
g_consoleField.cursor++;
}
Com_Printf ( "]%s\n", g_consoleField.buffer );
// leading slash is an explicit command
if ( g_consoleField.buffer[0] == '\\' || g_consoleField.buffer[0] == '/' ) {
Cbuf_AddText( g_consoleField.buffer+1 ); // valid command
Cbuf_AddText ("\n");
} else {
// other text will be chat messages
if ( !g_consoleField.buffer[0] ) {
return; // empty lines just scroll the console without adding to history
} else {
if ( con_autochat->integer ) {
Cbuf_AddText ("cmd say ");
}
2011-02-18 14:31:32 +00:00
Cbuf_AddText( g_consoleField.buffer );
Cbuf_AddText ("\n");
}
}
// copy line to history buffer
historyEditLines[nextHistoryLine % COMMAND_HISTORY] = g_consoleField;
nextHistoryLine++;
historyLine = nextHistoryLine;
Field_Clear( &g_consoleField );
g_consoleField.widthInChars = g_console_field_width;
CL_SaveConsoleHistory( );
if ( clc.state == CA_DISCONNECTED ) {
2011-02-18 14:31:32 +00:00
SCR_UpdateScreen (); // force an update, because the command
} // may take some time
return;
}
// command completion
if (key == K_TAB) {
Field_AutoComplete(&g_consoleField);
return;
}
// command history (ctrl-p ctrl-n for unix style)
if ( (key == K_MWHEELUP && keys[K_SHIFT].down) || ( key == K_UPARROW ) || ( key == K_KP_UPARROW ) ||
( ( tolower(key) == 'p' ) && keys[K_CTRL].down ) ) {
if ( nextHistoryLine - historyLine < COMMAND_HISTORY
&& historyLine > 0 ) {
historyLine--;
}
g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ];
return;
}
if ( (key == K_MWHEELDOWN && keys[K_SHIFT].down) || ( key == K_DOWNARROW ) || ( key == K_KP_DOWNARROW ) ||
( ( tolower(key) == 'n' ) && keys[K_CTRL].down ) ) {
historyLine++;
if (historyLine >= nextHistoryLine) {
historyLine = nextHistoryLine;
Field_Clear( &g_consoleField );
g_consoleField.widthInChars = g_console_field_width;
return;
}
g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ];
return;
}
// console scrolling
if ( key == K_PGUP ) {
Con_PageUp();
return;
}
if ( key == K_PGDN) {
Con_PageDown();
return;
}
if ( key == K_MWHEELUP) { //----(SA) added some mousewheel functionality to the console
Con_PageUp();
if(keys[K_CTRL].down) { // hold <ctrl> to accelerate scrolling
Con_PageUp();
Con_PageUp();
}
return;
}
if ( key == K_MWHEELDOWN) { //----(SA) added some mousewheel functionality to the console
Con_PageDown();
if(keys[K_CTRL].down) { // hold <ctrl> to accelerate scrolling
Con_PageDown();
Con_PageDown();
}
return;
}
// ctrl-home = top of console
if ( key == K_HOME && keys[K_CTRL].down ) {
Con_Top();
return;
}
// ctrl-end = bottom of console
if ( key == K_END && keys[K_CTRL].down ) {
Con_Bottom();
return;
}
// pass to the normal editline routine
Field_KeyDownEvent( &g_consoleField, key );
}
//============================================================================
/*
================
Message_Key
In game talk message
================
*/
void Message_Key( int key ) {
char buffer[MAX_STRING_CHARS];
if (key == K_ESCAPE) {
Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_MESSAGE );
Field_Clear( &chatField );
return;
}
if ( key == K_ENTER || key == K_KP_ENTER )
{
if ( chatField.buffer[0] && clc.state == CA_ACTIVE ) {
2011-02-18 14:31:32 +00:00
if (chat_playerNum != -1 )
Com_sprintf( buffer, sizeof( buffer ), "tell %i \"%s\"\n", chat_playerNum, chatField.buffer );
else if (chat_team)
Com_sprintf( buffer, sizeof( buffer ), "say_team \"%s\"\n", chatField.buffer );
else
Com_sprintf( buffer, sizeof( buffer ), "say \"%s\"\n", chatField.buffer );
CL_AddReliableCommand(buffer, qfalse);
}
Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_MESSAGE );
Field_Clear( &chatField );
return;
}
Field_KeyDownEvent( &chatField, key );
}
//============================================================================
qboolean Key_GetOverstrikeMode( void ) {
return key_overstrikeMode;
}
void Key_SetOverstrikeMode( qboolean state ) {
key_overstrikeMode = state;
}
/*
===================
Key_IsDown
===================
*/
qboolean Key_IsDown( int keynum ) {
if ( keynum < 0 || keynum >= MAX_KEYS ) {
return qfalse;
}
return keys[keynum].down;
}
/*
===================
Key_StringToKeynum
Returns a key number to be used to index keys[] by looking at
the given string. Single ascii characters return themselves, while
the K_* names are matched up.
0x11 will be interpreted as raw hex, which will allow new controlers
to be configured even if they don't have defined names.
===================
*/
int Key_StringToKeynum( char *str ) {
keyname_t *kn;
int n;
2011-02-18 14:31:32 +00:00
if ( !str || !str[0] ) {
return -1;
}
if ( !str[1] ) {
return tolower( str[0] );
2011-02-18 14:31:32 +00:00
}
// check for hex code
n = Com_HexStrToInt( str );
if ( n >= 0 && n < MAX_KEYS ) {
return n;
2011-02-18 14:31:32 +00:00
}
// scan for a text match
for ( kn=keynames ; kn->name ; kn++ ) {
if ( !Q_stricmp( str,kn->name ) )
return kn->keynum;
}
return -1;
}
/*
===================
Key_KeynumToString
Returns a string (either a single ascii char, a K_* name, or a 0x11 hex string) for the
given keynum.
===================
*/
char *Key_KeynumToString( int keynum ) {
keyname_t *kn;
static char tinystr[5];
int i, j;
if ( keynum == -1 ) {
return "<KEY NOT FOUND>";
}
if ( keynum < 0 || keynum >= MAX_KEYS ) {
return "<OUT OF RANGE>";
}
// check for printable ascii (don't use quote)
if ( keynum > 32 && keynum < 127 && keynum != '"' && keynum != ';' ) {
tinystr[0] = keynum;
tinystr[1] = 0;
return tinystr;
}
// check for a key string
for ( kn=keynames ; kn->name ; kn++ ) {
if (keynum == kn->keynum) {
return kn->name;
}
}
// make a hex string
i = keynum >> 4;
j = keynum & 15;
tinystr[0] = '0';
tinystr[1] = 'x';
tinystr[2] = i > 9 ? i - 10 + 'a' : i + '0';
tinystr[3] = j > 9 ? j - 10 + 'a' : j + '0';
tinystr[4] = 0;
return tinystr;
}
/*
===================
Key_SetBinding
===================
*/
void Key_SetBinding( int keynum, const char *binding ) {
if ( keynum < 0 || keynum >= MAX_KEYS ) {
return;
}
// free old bindings
if ( keys[ keynum ].binding ) {
Z_Free( keys[ keynum ].binding );
}
// allocate memory for new binding
keys[keynum].binding = CopyString( binding );
// consider this like modifying an archived cvar, so the
// file write will be triggered at the next opportunity
2011-02-18 14:31:32 +00:00
cvar_modifiedFlags |= CVAR_ARCHIVE;
}
/*
===================
Key_GetBinding
===================
*/
char *Key_GetBinding( int keynum ) {
if ( keynum < 0 || keynum >= MAX_KEYS ) {
return "";
}
return keys[ keynum ].binding;
}
/*
===================
Key_GetKey
===================
*/
int Key_GetKey(const char *binding) {
int i;
if (binding) {
for (i=0 ; i < MAX_KEYS ; i++) {
if (keys[i].binding && Q_stricmp(binding, keys[i].binding) == 0) {
return i;
}
}
}
return -1;
}
/*
===================
Key_Unbind_f
===================
*/
void Key_Unbind_f (void)
{
int b;
if (Cmd_Argc() != 2)
{
Com_Printf ("unbind <key> : remove commands from a key\n");
return;
}
b = Key_StringToKeynum (Cmd_Argv(1));
if (b==-1)
{
Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1));
return;
}
Key_SetBinding (b, "");
}
/*
===================
Key_Unbindall_f
===================
*/
void Key_Unbindall_f (void)
{
int i;
for (i=0 ; i < MAX_KEYS; i++)
if (keys[i].binding)
Key_SetBinding (i, "");
}
/*
===================
Key_Bind_f
===================
*/
void Key_Bind_f (void)
{
int i, c, b;
char cmd[1024];
c = Cmd_Argc();
if (c < 2)
{
Com_Printf ("bind <key> [command] : attach a command to a key\n");
return;
}
b = Key_StringToKeynum (Cmd_Argv(1));
if (b==-1)
{
Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1));
return;
}
if (c == 2)
{
if (keys[b].binding && keys[b].binding[0])
Com_Printf ("\"%s\" = \"%s\"\n", Key_KeynumToString(b), keys[b].binding );
2011-02-18 14:31:32 +00:00
else
Com_Printf ("\"%s\" is not bound\n", Key_KeynumToString(b) );
2011-02-18 14:31:32 +00:00
return;
}
// copy the rest of the command line
cmd[0] = 0; // start out with a null string
for (i=2 ; i< c ; i++)
{
strcat (cmd, Cmd_Argv(i));
if (i != (c-1))
strcat (cmd, " ");
}
Key_SetBinding (b, cmd);
}
/*
============
Key_WriteBindings
Writes lines containing "bind key value"
============
*/
void Key_WriteBindings( fileHandle_t f ) {
int i;
FS_Printf (f, "unbindall\n" );
for (i=0 ; i<MAX_KEYS ; i++) {
if (keys[i].binding && keys[i].binding[0] ) {
FS_Printf (f, "bind %s \"%s\"\n", Key_KeynumToString(i), keys[i].binding);
}
}
}
/*
============
Key_Bindlist_f
============
*/
void Key_Bindlist_f( void ) {
int i;
for ( i = 0 ; i < MAX_KEYS ; i++ ) {
if ( keys[i].binding && keys[i].binding[0] ) {
Com_Printf( "%s \"%s\"\n", Key_KeynumToString(i), keys[i].binding );
}
}
}
/*
============
Key_KeynameCompletion
============
*/
void Key_KeynameCompletion( void(*callback)(const char *s) ) {
int i;
for( i = 0; keynames[ i ].name != NULL; i++ )
callback( keynames[ i ].name );
}
/*
====================
Key_CompleteUnbind
====================
*/
static void Key_CompleteUnbind( char *args, int argNum )
{
if( argNum == 2 )
{
// Skip "unbind "
char *p = Com_SkipTokens( args, 1, " " );
if( p > args )
Field_CompleteKeyname( );
}
}
/*
====================
Key_CompleteBind
====================
*/
static void Key_CompleteBind( char *args, int argNum )
{
char *p;
if( argNum == 2 )
{
// Skip "bind "
p = Com_SkipTokens( args, 1, " " );
if( p > args )
Field_CompleteKeyname( );
}
else if( argNum >= 3 )
{
// Skip "bind <key> "
p = Com_SkipTokens( args, 2, " " );
if( p > args )
Field_CompleteCommand( p, qtrue, qtrue );
}
}
/*
===================
CL_InitKeyCommands
===================
*/
void CL_InitKeyCommands( void ) {
// register our functions
Cmd_AddCommand ("bind",Key_Bind_f);
Cmd_SetCommandCompletionFunc( "bind", Key_CompleteBind );
Cmd_AddCommand ("unbind",Key_Unbind_f);
Cmd_SetCommandCompletionFunc( "unbind", Key_CompleteUnbind );
Cmd_AddCommand ("unbindall",Key_Unbindall_f);
Cmd_AddCommand ("bindlist",Key_Bindlist_f);
}
/*
===================
CL_BindUICommand
Returns qtrue if bind command should be executed while user interface is shown
===================
*/
static qboolean CL_BindUICommand( const char *cmd ) {
if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE )
return qfalse;
if ( !Q_stricmp( cmd, "toggleconsole" ) )
return qtrue;
if ( !Q_stricmp( cmd, "togglemenu" ) )
return qtrue;
return qfalse;
}
2011-02-18 14:31:32 +00:00
/*
===================
CL_ParseBinding
Execute the commands in the bind string
===================
*/
void CL_ParseBinding( int key, qboolean down, unsigned time )
{
char buf[ MAX_STRING_CHARS ], *p = buf, *end;
qboolean allCommands, allowUpCmds;
2011-02-18 14:31:32 +00:00
if( clc.state == CA_DISCONNECTED && Key_GetCatcher( ) == 0 )
return;
2011-02-18 14:31:32 +00:00
if( !keys[key].binding || !keys[key].binding[0] )
return;
Q_strncpyz( buf, keys[key].binding, sizeof( buf ) );
// run all bind commands if console, ui, etc aren't reading keys
allCommands = ( Key_GetCatcher( ) == 0 );
// allow button up commands if in game even if key catcher is set
allowUpCmds = ( clc.state != CA_DISCONNECTED );
2011-02-18 14:31:32 +00:00
while( 1 )
{
while( isspace( *p ) )
p++;
end = strchr( p, ';' );
if( end )
*end = '\0';
if( *p == '+' )
{
// button commands add keynum and time as parameters
// so that multiple sources can be discriminated and
// subframe corrected
if ( allCommands || ( allowUpCmds && !down ) ) {
char cmd[1024];
Com_sprintf( cmd, sizeof( cmd ), "%c%s %d %d\n",
( down ) ? '+' : '-', p + 1, key, time );
Cbuf_AddText( cmd );
}
2011-02-18 14:31:32 +00:00
}
else if( down )
{
// normal commands only execute on key press
if ( allCommands || CL_BindUICommand( p ) ) {
Cbuf_AddText( p );
Cbuf_AddText( "\n" );
}
2011-02-18 14:31:32 +00:00
}
if( !end )
break;
p = end + 1;
}
}
/*
===================
CL_KeyDownEvent
Called by CL_KeyEvent to handle a keypress
===================
*/
void CL_KeyDownEvent( int key, unsigned time )
{
keys[key].down = qtrue;
keys[key].repeats++;
if( keys[key].repeats == 1 )
2011-02-18 14:31:32 +00:00
anykeydown++;
if( keys[K_ALT].down && key == K_ENTER )
{
// don't repeat fullscreen toggle when keys are held down
if ( keys[K_ENTER].repeats > 1 ) {
return;
}
2011-02-18 14:31:32 +00:00
Cvar_SetValue( "r_fullscreen",
!Cvar_VariableIntegerValue( "r_fullscreen" ) );
return;
}
// console key is hardcoded, so the user can never unbind it
if( key == K_CONSOLE || ( keys[K_SHIFT].down && key == K_ESCAPE ) )
{
Con_ToggleConsole_f ();
Key_ClearStates ();
return;
}
// keys can still be used for bound actions
if ( ( key < 128 || key == K_MOUSE1 ) &&
( clc.demoplaying || clc.state == CA_CINEMATIC ) && Key_GetCatcher( ) == 0 ) {
2011-02-18 14:31:32 +00:00
if (Cvar_VariableValue ("com_cameraMode") == 0) {
Cvar_Set ("nextdemo","");
key = K_ESCAPE;
}
}
// escape is always handled special
if ( key == K_ESCAPE ) {
if ( Key_GetCatcher( ) & KEYCATCH_MESSAGE ) {
// clear message mode
Message_Key( key );
return;
}
// escape always gets out of CGAME stuff
if (Key_GetCatcher( ) & KEYCATCH_CGAME) {
Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CGAME );
VM_Call (cgvm, CG_EVENT_HANDLING, CGAME_EVENT_NONE);
return;
}
if ( !( Key_GetCatcher( ) & KEYCATCH_UI ) ) {
if ( clc.state == CA_ACTIVE && !clc.demoplaying ) {
2011-02-18 14:31:32 +00:00
VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_INGAME );
}
else if ( clc.state != CA_DISCONNECTED ) {
2011-02-18 14:31:32 +00:00
CL_Disconnect_f();
S_StopAllSounds();
VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN );
}
return;
}
VM_Call( uivm, UI_KEY_EVENT, key, qtrue );
return;
}
// send the bound action
CL_ParseBinding( key, qtrue, time );
// distribute the key down event to the appropriate handler
2011-02-18 14:31:32 +00:00
if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) {
Console_Key( key );
} else if ( Key_GetCatcher( ) & KEYCATCH_UI ) {
if ( uivm ) {
VM_Call( uivm, UI_KEY_EVENT, key, qtrue );
}
} else if ( Key_GetCatcher( ) & KEYCATCH_CGAME ) {
if ( cgvm ) {
VM_Call( cgvm, CG_KEY_EVENT, key, qtrue );
}
} else if ( Key_GetCatcher( ) & KEYCATCH_MESSAGE ) {
Message_Key( key );
} else if ( clc.state == CA_DISCONNECTED ) {
2011-02-18 14:31:32 +00:00
Console_Key( key );
}
}
/*
===================
CL_KeyUpEvent
Called by CL_KeyEvent to handle a keyrelease
===================
*/
void CL_KeyUpEvent( int key, unsigned time )
{
keys[key].repeats = 0;
keys[key].down = qfalse;
anykeydown--;
2011-02-18 14:31:32 +00:00
if (anykeydown < 0) {
anykeydown = 0;
}
// don't process key-up events for the console key
if ( key == K_CONSOLE || ( key == K_ESCAPE && keys[K_SHIFT].down ) )
return;
//
// key up events only perform actions if the game key binding is
// a button command (leading + sign). These will be processed even in
// console mode and menu mode, to keep the character from continuing
// an action started before a mode switch.
//
CL_ParseBinding( key, qfalse, time );
2011-02-18 14:31:32 +00:00
if ( Key_GetCatcher( ) & KEYCATCH_UI && uivm ) {
VM_Call( uivm, UI_KEY_EVENT, key, qfalse );
} else if ( Key_GetCatcher( ) & KEYCATCH_CGAME && cgvm ) {
VM_Call( cgvm, CG_KEY_EVENT, key, qfalse );
}
}
/*
===================
CL_KeyEvent
Called by the system for both key up and key down events
===================
*/
void CL_KeyEvent (int key, qboolean down, unsigned time) {
if( down )
CL_KeyDownEvent( key, time );
else
CL_KeyUpEvent( key, time );
}
/*
===================
CL_CharEvent
Normal keyboard characters, already shifted / capslocked / etc
===================
*/
void CL_CharEvent( int key ) {
// delete is not a printable character and is
// otherwise handled by Field_KeyDownEvent
if ( key == 127 ) {
return;
}
// distribute the key down event to the appropriate handler
2011-02-18 14:31:32 +00:00
if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE )
{
Field_CharEvent( &g_consoleField, key );
}
else if ( Key_GetCatcher( ) & KEYCATCH_UI )
{
VM_Call( uivm, UI_KEY_EVENT, key | K_CHAR_FLAG, qtrue );
}
else if ( Key_GetCatcher( ) & KEYCATCH_MESSAGE )
{
Field_CharEvent( &chatField, key );
}
else if ( clc.state == CA_DISCONNECTED )
2011-02-18 14:31:32 +00:00
{
Field_CharEvent( &g_consoleField, key );
}
}
/*
===================
Key_ClearStates
===================
*/
void Key_ClearStates (void)
{
int i;
anykeydown = 0;
for ( i=0 ; i < MAX_KEYS ; i++ ) {
if ( keys[i].down ) {
CL_KeyEvent( i, qfalse, 0 );
}
keys[i].down = 0;
keys[i].repeats = 0;
}
}
static int keyCatchers = 0;
/*
====================
Key_GetCatcher
====================
*/
int Key_GetCatcher( void ) {
return keyCatchers;
}
/*
====================
Key_SetCatcher
====================
*/
void Key_SetCatcher( int catcher ) {
// If the catcher state is changing, clear all key states
if( catcher != keyCatchers )
Key_ClearStates( );
keyCatchers = catcher;
}
// This must not exceed MAX_CMD_LINE
#define MAX_CONSOLE_SAVE_BUFFER 1024
#define CONSOLE_HISTORY_FILE "q3history"
static char consoleSaveBuffer[ MAX_CONSOLE_SAVE_BUFFER ];
static int consoleSaveBufferSize = 0;
/*
================
CL_LoadConsoleHistory
Load the console history from cl_consoleHistory
================
*/
void CL_LoadConsoleHistory( void )
{
char *token, *text_p;
int i, numChars, numLines = 0;
fileHandle_t f;
consoleSaveBufferSize = FS_FOpenFileRead( CONSOLE_HISTORY_FILE, &f, qfalse );
if( !f )
{
Com_Printf( "Couldn't read %s.\n", CONSOLE_HISTORY_FILE );
return;
}
ioquake3 resync to revision 3511 from 3444. This updates from SDL 2.0.4 to SDL 2.0.8. Fix nullptr dereference in front of nullptr check in FS_CheckPak0 Fix undefined behaviour due to shifting signed in snd_mem.c Fix shifting bits out of byte in tr_font.c Fix shift into sign in cl_cin.c Fix signed bit operations in MSG_ReadBits Add missing address operator in cm_polylib.c OpenGL1: Decay float[8] to float * in tr_marks.c Avoid srcList[-1] in snd_openal.c Fix the behaviour of CVAR_LATCH|CVAR_CHEAT cvars Maximize cURL buffer size Fix mouse grab after toggling fullscreen Fix q3history buffer not cleared between mods and OOB-access Revert "Removed "Color Depth" from q3_ui system settings, it didn't control anything." Fix displayed color/depth/stencil bits values Restore setting r_colorbits in q3_ui Make setting r_stencilbits more consistent in Team Arena UI Fix map list in Team Arena start server menu after entering SP menu Support SDL audio devices that require float32 samples. sdl_snd.c should just initialize SDL audio without checking SDL_WasInit(). There's no need to SDL_PauseAudio(1) before calling SDL_CloseAudio(). Added audio capture support to SDL backend. Use the SDL2 audio device interface instead of the legacy 1.2 API. Disable SDL audio capture until prebuilt SDL libraries are updated to 2.0.8. Update SDL2 to 2.0.8 Add SDL 2.0.1 headers for macOS PPC Make macOS Universal Bundle target 10.6 for x86 and x86_64 Fix possible bot goal state NULL pointer dereference Fix uninitialized bot_goal_t fields Remove unnecessary NULL pointer check in Cmd_RemoveCommand Make UI_DrawProportionalString handle NULL string Fix compiling against macOS system OpenAL and SDL2 frameworks Fix array index in CanDamage() function - discovered by MARTY Fix compiling Makefile (broke in macOS frameworks commit) Fix clearing keys for control in Team Arena UI Make s_useOpenAL be CVAR_LATCH Improvements for dedicated camera followers (team follow1/2) Fix not closing description.txt and fix path seperator Fix duplicate bots displayed in Team Arena ingame add bot menu OpenGL2: Fix parsing specularScale in shaders Don't allow SDL audio capture using pulseaudio Isolate the Altivec code so non-Altivec PPC targets can use the same binary. Limit -maltivec to specific source files on OpenBSD too (untested) Use SDL 2.0.1 headers for macOS ppc64 Fix console offset while Team Arena voiceMenu is open OpenGL2: Readd r_deluxeSpecular. Fix client kicked as unpure when missing the latest cgame/ui pk3s Don't create multiple windows when GL context creation fails Require OpenGL 1.2 for GL_CLAMP_TO_EDGE Fix Linux uninstaller requiring Bash Fix Linux uninstaller redirecting stderr to stdout in preuninstall.sh Reported by @illwieckz. Fix in_restart causing fatal error while video is shutdown Allow pkg-config binary to be overridden with PKG_CONFIG Make testgun command without argument disable test gun model Remove unused renderer_buffer variable Don't upload 8 bit grayscale images as 16 bit luminance OpenGL1: Use RE_UploadCinematic() instead of duplicate code Don't load non-core GL functions for OpenGL 3.2 core context Load OpenGL ES 2.0 function procs Don't check fixed function GL extensions when using shader pipeline OpenGL2: Fix world VAO cache drawing when glIndex_t is unsigned short OpenGL2: Misc fixes and cleanup Fix IQM root joint backlerp when joint number is more than 0 Improve IQM loading Improve IQM CPU vertex skinning performance OpenGL2: Add GPU vertex skinning for IQM models
2018-07-30 11:35:12 +00:00
if( consoleSaveBufferSize < MAX_CONSOLE_SAVE_BUFFER &&
2011-02-18 14:31:32 +00:00
FS_Read( consoleSaveBuffer, consoleSaveBufferSize, f ) == consoleSaveBufferSize )
{
ioquake3 resync to revision 3511 from 3444. This updates from SDL 2.0.4 to SDL 2.0.8. Fix nullptr dereference in front of nullptr check in FS_CheckPak0 Fix undefined behaviour due to shifting signed in snd_mem.c Fix shifting bits out of byte in tr_font.c Fix shift into sign in cl_cin.c Fix signed bit operations in MSG_ReadBits Add missing address operator in cm_polylib.c OpenGL1: Decay float[8] to float * in tr_marks.c Avoid srcList[-1] in snd_openal.c Fix the behaviour of CVAR_LATCH|CVAR_CHEAT cvars Maximize cURL buffer size Fix mouse grab after toggling fullscreen Fix q3history buffer not cleared between mods and OOB-access Revert "Removed "Color Depth" from q3_ui system settings, it didn't control anything." Fix displayed color/depth/stencil bits values Restore setting r_colorbits in q3_ui Make setting r_stencilbits more consistent in Team Arena UI Fix map list in Team Arena start server menu after entering SP menu Support SDL audio devices that require float32 samples. sdl_snd.c should just initialize SDL audio without checking SDL_WasInit(). There's no need to SDL_PauseAudio(1) before calling SDL_CloseAudio(). Added audio capture support to SDL backend. Use the SDL2 audio device interface instead of the legacy 1.2 API. Disable SDL audio capture until prebuilt SDL libraries are updated to 2.0.8. Update SDL2 to 2.0.8 Add SDL 2.0.1 headers for macOS PPC Make macOS Universal Bundle target 10.6 for x86 and x86_64 Fix possible bot goal state NULL pointer dereference Fix uninitialized bot_goal_t fields Remove unnecessary NULL pointer check in Cmd_RemoveCommand Make UI_DrawProportionalString handle NULL string Fix compiling against macOS system OpenAL and SDL2 frameworks Fix array index in CanDamage() function - discovered by MARTY Fix compiling Makefile (broke in macOS frameworks commit) Fix clearing keys for control in Team Arena UI Make s_useOpenAL be CVAR_LATCH Improvements for dedicated camera followers (team follow1/2) Fix not closing description.txt and fix path seperator Fix duplicate bots displayed in Team Arena ingame add bot menu OpenGL2: Fix parsing specularScale in shaders Don't allow SDL audio capture using pulseaudio Isolate the Altivec code so non-Altivec PPC targets can use the same binary. Limit -maltivec to specific source files on OpenBSD too (untested) Use SDL 2.0.1 headers for macOS ppc64 Fix console offset while Team Arena voiceMenu is open OpenGL2: Readd r_deluxeSpecular. Fix client kicked as unpure when missing the latest cgame/ui pk3s Don't create multiple windows when GL context creation fails Require OpenGL 1.2 for GL_CLAMP_TO_EDGE Fix Linux uninstaller requiring Bash Fix Linux uninstaller redirecting stderr to stdout in preuninstall.sh Reported by @illwieckz. Fix in_restart causing fatal error while video is shutdown Allow pkg-config binary to be overridden with PKG_CONFIG Make testgun command without argument disable test gun model Remove unused renderer_buffer variable Don't upload 8 bit grayscale images as 16 bit luminance OpenGL1: Use RE_UploadCinematic() instead of duplicate code Don't load non-core GL functions for OpenGL 3.2 core context Load OpenGL ES 2.0 function procs Don't check fixed function GL extensions when using shader pipeline OpenGL2: Fix world VAO cache drawing when glIndex_t is unsigned short OpenGL2: Misc fixes and cleanup Fix IQM root joint backlerp when joint number is more than 0 Improve IQM loading Improve IQM CPU vertex skinning performance OpenGL2: Add GPU vertex skinning for IQM models
2018-07-30 11:35:12 +00:00
consoleSaveBuffer[consoleSaveBufferSize] = '\0';
2011-02-18 14:31:32 +00:00
text_p = consoleSaveBuffer;
for( i = COMMAND_HISTORY - 1; i >= 0; i-- )
{
if( !*( token = COM_Parse( &text_p ) ) )
break;
historyEditLines[ i ].cursor = atoi( token );
if( !*( token = COM_Parse( &text_p ) ) )
break;
historyEditLines[ i ].scroll = atoi( token );
if( !*( token = COM_Parse( &text_p ) ) )
break;
numChars = atoi( token );
text_p++;
if( numChars > ( strlen( consoleSaveBuffer ) - ( text_p - consoleSaveBuffer ) ) )
{
Com_DPrintf( S_COLOR_YELLOW "WARNING: probable corrupt history\n" );
break;
}
Com_Memcpy( historyEditLines[ i ].buffer,
text_p, numChars );
historyEditLines[ i ].buffer[ numChars ] = '\0';
text_p += numChars;
numLines++;
}
memmove( &historyEditLines[ 0 ], &historyEditLines[ i + 1 ],
numLines * sizeof( field_t ) );
for( i = numLines; i < COMMAND_HISTORY; i++ )
Field_Clear( &historyEditLines[ i ] );
historyLine = nextHistoryLine = numLines;
}
else
Com_Printf( "Couldn't read %s.\n", CONSOLE_HISTORY_FILE );
FS_FCloseFile( f );
}
/*
================
CL_SaveConsoleHistory
Save the console history into the cvar cl_consoleHistory
so that it persists across invocations of q3
================
*/
void CL_SaveConsoleHistory( void )
{
int i;
int lineLength, saveBufferLength, additionalLength;
fileHandle_t f;
consoleSaveBuffer[ 0 ] = '\0';
i = ( nextHistoryLine - 1 ) % COMMAND_HISTORY;
do
{
if( historyEditLines[ i ].buffer[ 0 ] )
{
lineLength = strlen( historyEditLines[ i ].buffer );
saveBufferLength = strlen( consoleSaveBuffer );
//ICK
additionalLength = lineLength + strlen( "999 999 999 " );
if( saveBufferLength + additionalLength < MAX_CONSOLE_SAVE_BUFFER )
{
Q_strcat( consoleSaveBuffer, MAX_CONSOLE_SAVE_BUFFER,
va( "%d %d %d %s ",
historyEditLines[ i ].cursor,
historyEditLines[ i ].scroll,
lineLength,
historyEditLines[ i ].buffer ) );
}
else
break;
}
i = ( i - 1 + COMMAND_HISTORY ) % COMMAND_HISTORY;
}
while( i != ( nextHistoryLine - 1 ) % COMMAND_HISTORY );
consoleSaveBufferSize = strlen( consoleSaveBuffer );
f = FS_FOpenFileWrite( CONSOLE_HISTORY_FILE );
if( !f )
{
Com_Printf( "Couldn't write %s.\n", CONSOLE_HISTORY_FILE );
return;
}
if( FS_Write( consoleSaveBuffer, consoleSaveBufferSize, f ) < consoleSaveBufferSize )
Com_Printf( "Couldn't write %s.\n", CONSOLE_HISTORY_FILE );
FS_FCloseFile( f );
}