2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 #if !defined(WIN32) || defined(__MINGW32__)
31 float con_cursorspeed = 4;
33 #define CON_TEXTSIZE 131072
35 // total lines in console scrollback
37 // lines up from bottom to display
39 // where next message will be printed
41 // offset in current line for next print
43 char con_text[CON_TEXTSIZE];
45 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
46 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show (0-32)"};
47 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
50 cvar_t sys_specialcharactertranslation = {0, "sys_specialcharactertranslation", "1", "terminal console conchars to ASCII translation (set to 0 if your conchars.tga is for an 8bit character set or if you want raw output)"};
52 cvar_t sys_colortranslation = {0, "sys_colortranslation", "0", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
54 cvar_t sys_colortranslation = {0, "sys_colortranslation", "1", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
58 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
59 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
60 "0: add nothing after completion. "
61 "1: add the last color after completion. "
62 "2: add a quote when starting a quote instead of the color. "
63 "4: will replace 1, will force color, even after a quote. "
64 "8: ignore non-alphanumerics. "
65 "16: ignore spaces. "};
66 #define NICKS_ADD_COLOR 1
67 #define NICKS_ADD_QUOTE 2
68 #define NICKS_FORCE_COLOR 4
69 #define NICKS_ALPHANUMERICS_ONLY 8
70 #define NICKS_NO_SPACES 16
72 #define MAX_NOTIFYLINES 32
73 // cl.time time the line was generated for transparent notify lines
74 float con_times[MAX_NOTIFYLINES];
78 qboolean con_initialized;
80 // used for server replies to rcon command
81 qboolean rcon_redirect = false;
82 int rcon_redirect_bufferpos = 0;
83 char rcon_redirect_buffer[1400];
87 ==============================================================================
91 ==============================================================================
94 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
95 cvar_t log_dest_udp = {0, "log_dest_udp","", "UDP address to log messages to (in QW rcon compatible format); multiple destinations can be separated by spaces; DO NOT SPECIFY DNS NAMES HERE"};
96 char log_dest_buffer[1400]; // UDP packet
97 size_t log_dest_buffer_pos;
98 qboolean log_dest_buffer_appending;
99 char crt_log_file [MAX_OSPATH] = "";
100 qfile_t* logfile = NULL;
102 unsigned char* logqueue = NULL;
104 size_t logq_size = 0;
106 void Log_ConPrint (const char *msg);
113 static void Log_DestBuffer_Init()
115 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
116 log_dest_buffer_pos = 5;
124 void Log_DestBuffer_Flush()
126 lhnetaddress_t log_dest_addr;
127 lhnetsocket_t *log_dest_socket;
128 const char *s = log_dest_udp.string;
129 qboolean have_opened_temp_sockets = false;
130 if(s) if(log_dest_buffer_pos > 5)
132 ++log_dest_buffer_appending;
133 log_dest_buffer[log_dest_buffer_pos++] = 0;
135 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
137 have_opened_temp_sockets = true;
138 NetConn_OpenServerPorts(true);
141 while(COM_ParseToken_Console(&s))
142 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
144 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
146 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
148 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
151 if(have_opened_temp_sockets)
152 NetConn_CloseServerPorts();
153 --log_dest_buffer_appending;
155 log_dest_buffer_pos = 0;
163 const char* Log_Timestamp (const char *desc)
165 static char timestamp [128];
167 const struct tm *crt_tm;
168 char timestring [64];
170 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
172 crt_tm = localtime (&crt_time);
173 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
176 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
178 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
191 if (logfile != NULL || log_file.string[0] == '\0')
194 logfile = FS_Open (log_file.string, "ab", false, false);
197 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
198 FS_Print (logfile, Log_Timestamp ("Log started"));
208 void Log_Close (void)
213 FS_Print (logfile, Log_Timestamp ("Log stopped"));
214 FS_Print (logfile, "\n");
218 crt_log_file[0] = '\0';
227 void Log_Start (void)
233 // Dump the contents of the log queue into the log file and free it
234 if (logqueue != NULL)
236 unsigned char *temp = logqueue;
241 FS_Write (logfile, temp, logq_ind);
242 if(*log_dest_udp.string)
244 for(pos = 0; pos < logq_ind; )
246 if(log_dest_buffer_pos == 0)
247 Log_DestBuffer_Init();
248 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
249 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
250 log_dest_buffer_pos += n;
251 Log_DestBuffer_Flush();
268 void Log_ConPrint (const char *msg)
270 static qboolean inprogress = false;
272 // don't allow feedback loops with memory error reports
277 // Until the host is completely initialized, we maintain a log queue
278 // to store the messages, since the log can't be started before
279 if (logqueue != NULL)
281 size_t remain = logq_size - logq_ind;
282 size_t len = strlen (msg);
284 // If we need to enlarge the log queue
287 size_t factor = ((logq_ind + len) / logq_size) + 1;
288 unsigned char* newqueue;
291 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
292 memcpy (newqueue, logqueue, logq_ind);
295 remain = logq_size - logq_ind;
297 memcpy (&logqueue[logq_ind], msg, len);
304 // Check if log_file has changed
305 if (strcmp (crt_log_file, log_file.string) != 0)
311 // If a log file is available
313 FS_Print (logfile, msg);
324 void Log_Printf (const char *logfilename, const char *fmt, ...)
328 file = FS_Open (logfilename, "ab", true, false);
333 va_start (argptr, fmt);
334 FS_VPrintf (file, fmt, argptr);
343 ==============================================================================
347 ==============================================================================
355 void Con_ToggleConsole_f (void)
357 // toggle the 'user wants console' bit
358 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
359 memset (con_times, 0, sizeof(con_times));
367 void Con_Clear_f (void)
370 memset (con_text, ' ', CON_TEXTSIZE);
379 void Con_ClearNotify (void)
383 for (i=0 ; i<MAX_NOTIFYLINES ; i++)
393 void Con_MessageMode_f (void)
395 key_dest = key_message;
405 void Con_MessageMode2_f (void)
407 key_dest = key_message;
416 If the line width has changed, reformat the buffer.
419 void Con_CheckResize (void)
421 int i, j, width, oldwidth, oldtotallines, numlines, numchars;
423 char tbuf[CON_TEXTSIZE];
425 f = bound(1, con_textsize.value, 128);
426 if(f != con_textsize.value)
427 Cvar_SetValueQuick(&con_textsize, f);
428 width = (int)floor(vid_conwidth.value / con_textsize.value);
429 width = bound(1, width, CON_TEXTSIZE/4);
431 if (width == con_linewidth)
434 oldwidth = con_linewidth;
435 con_linewidth = width;
436 oldtotallines = con_totallines;
437 con_totallines = CON_TEXTSIZE / con_linewidth;
438 numlines = oldtotallines;
440 if (con_totallines < numlines)
441 numlines = con_totallines;
445 if (con_linewidth < numchars)
446 numchars = con_linewidth;
448 memcpy (tbuf, con_text, CON_TEXTSIZE);
449 memset (con_text, ' ', CON_TEXTSIZE);
451 for (i=0 ; i<numlines ; i++)
453 for (j=0 ; j<numchars ; j++)
455 con_text[(con_totallines - 1 - i) * con_linewidth + j] =
456 tbuf[((con_current - i + oldtotallines) %
457 oldtotallines) * oldwidth + j];
464 con_current = con_totallines - 1;
467 //[515]: the simplest command ever
468 //LordHavoc: not so simple after I made it print usage...
469 static void Con_Maps_f (void)
473 Con_Printf("usage: maps [mapnameprefix]\n");
476 else if (Cmd_Argc() == 2)
477 GetMapList(Cmd_Argv(1), NULL, 0);
479 GetMapList("", NULL, 0);
482 void Con_ConDump_f (void)
485 qboolean allblankssofar;
488 char temp[MAX_INPUTLINE+2];
491 Con_Printf("usage: condump <filename>\n");
494 file = FS_Open(Cmd_Argv(1), "wb", false, false);
497 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
500 // iterate over the entire console history buffer line by line
501 allblankssofar = true;
502 for (i = 0;i < con_totallines;i++)
504 text = con_text + ((con_current + 1 + i) % con_totallines)*con_linewidth;
505 // count the used characters on this line
506 for (l = min(con_linewidth, (int)sizeof(temp));l > 0 && text[l-1] == ' ';l--);
507 // if not a blank line, begin output
509 allblankssofar = false;
510 // output the current line to the file
514 memcpy(temp, text, l);
517 FS_Print(file, temp);
530 memset (con_text, ' ', CON_TEXTSIZE);
532 con_totallines = CON_TEXTSIZE / con_linewidth;
534 // Allocate a log queue, this will be freed after configs are parsed
535 logq_size = MAX_INPUTLINE;
536 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
539 Cvar_RegisterVariable (&sys_colortranslation);
540 Cvar_RegisterVariable (&sys_specialcharactertranslation);
542 Cvar_RegisterVariable (&log_file);
543 Cvar_RegisterVariable (&log_dest_udp);
545 // support for the classic Quake option
546 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
547 if (COM_CheckParm ("-condebug") != 0)
548 Cvar_SetQuick (&log_file, "qconsole.log");
550 // register our cvars
551 Cvar_RegisterVariable (&con_notifytime);
552 Cvar_RegisterVariable (&con_notify);
553 Cvar_RegisterVariable (&con_textsize);
556 Cvar_RegisterVariable (&con_nickcompletion);
557 Cvar_RegisterVariable (&con_nickcompletion_flags);
559 // register our commands
560 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
561 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
562 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
563 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
564 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
565 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
567 con_initialized = true;
568 Con_Print("Console initialized.\n");
577 void Con_Linefeed (void)
584 memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth);
591 Handles cursor positioning, line wrapping, etc
592 All console printing must go through this in order to be displayed
593 If no console is visible, the notify window will pop up.
596 void Con_PrintToHistory(const char *txt, int mask)
604 for (l=0 ; l< con_linewidth ; l++)
609 if (l != con_linewidth && (con_x + l > con_linewidth) )
624 // mark time for transparent overlay
625 if (con_current >= 0)
627 if (con_notify.integer < 0)
628 Cvar_SetValueQuick(&con_notify, 0);
629 if (con_notify.integer > MAX_NOTIFYLINES)
630 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
631 if (con_notify.integer > 0)
632 con_times[con_current % con_notify.integer] = cl.time;
647 default: // display character and advance
648 y = con_current % con_totallines;
649 con_text[y*con_linewidth+con_x] = c | mask;
651 if (con_x >= con_linewidth)
659 /* The translation table between the graphical font and plain ASCII --KB */
660 static char qfont_table[256] = {
661 '\0', '#', '#', '#', '#', '.', '#', '#',
662 '#', 9, 10, '#', ' ', 13, '.', '.',
663 '[', ']', '0', '1', '2', '3', '4', '5',
664 '6', '7', '8', '9', '.', '<', '=', '>',
665 ' ', '!', '"', '#', '$', '%', '&', '\'',
666 '(', ')', '*', '+', ',', '-', '.', '/',
667 '0', '1', '2', '3', '4', '5', '6', '7',
668 '8', '9', ':', ';', '<', '=', '>', '?',
669 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
670 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
671 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
672 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
673 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
674 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
675 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
676 'x', 'y', 'z', '{', '|', '}', '~', '<',
678 '<', '=', '>', '#', '#', '.', '#', '#',
679 '#', '#', ' ', '#', ' ', '>', '.', '.',
680 '[', ']', '0', '1', '2', '3', '4', '5',
681 '6', '7', '8', '9', '.', '<', '=', '>',
682 ' ', '!', '"', '#', '$', '%', '&', '\'',
683 '(', ')', '*', '+', ',', '-', '.', '/',
684 '0', '1', '2', '3', '4', '5', '6', '7',
685 '8', '9', ':', ';', '<', '=', '>', '?',
686 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
687 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
688 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
689 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
690 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
691 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
692 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
693 'x', 'y', 'z', '{', '|', '}', '~', '<'
700 Adds a character to the rcon buffer
703 void Con_Rcon_AddChar(char c)
705 if(log_dest_buffer_appending)
707 ++log_dest_buffer_appending;
709 // if this print is in response to an rcon command, add the character
710 // to the rcon redirect buffer
712 if (rcon_redirect && rcon_redirect_bufferpos < (int)sizeof(rcon_redirect_buffer) - 1)
713 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
714 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
716 if(log_dest_buffer_pos == 0)
717 Log_DestBuffer_Init();
718 log_dest_buffer[log_dest_buffer_pos++] = c;
719 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
720 Log_DestBuffer_Flush();
723 log_dest_buffer_pos = 0;
725 --log_dest_buffer_appending;
732 Prints to all appropriate console targets, and adds timestamps
735 extern cvar_t timestamps;
736 extern cvar_t timeformat;
737 extern qboolean sys_nostdout;
738 void Con_Print(const char *msg)
741 static int index = 0;
742 static char line[MAX_INPUTLINE];
746 Con_Rcon_AddChar(*msg);
747 // if this is the beginning of a new line, print timestamp
750 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
752 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
753 line[index++] = STRING_COLOR_TAG;
754 // assert( STRING_COLOR_DEFAULT < 10 )
755 line[index++] = STRING_COLOR_DEFAULT + '0';
756 // special color codes for chat messages must always come first
757 // for Con_PrintToHistory to work properly
758 if (*msg == 1 || *msg == 2)
763 if (msg[1] == '(' && cl.foundtalk2wav)
764 S_LocalSound ("sound/misc/talk2.wav");
766 S_LocalSound ("sound/misc/talk.wav");
768 line[index++] = STRING_COLOR_TAG;
771 Con_Rcon_AddChar(*msg);
774 for (;*timestamp;index++, timestamp++)
775 if (index < (int)sizeof(line) - 2)
776 line[index] = *timestamp;
778 // append the character
779 line[index++] = *msg;
780 // if this is a newline character, we have a complete line to print
781 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
783 // terminate the line
787 // send to scrollable buffer
788 if (con_initialized && cls.state != ca_dedicated)
789 Con_PrintToHistory(line, mask);
790 // send to terminal or dedicated server window
794 if(sys_specialcharactertranslation.integer)
796 for (p = (unsigned char *) line;*p; p++)
797 *p = qfont_table[*p];
800 if(sys_colortranslation.integer == 1) // ANSI
802 static char printline[MAX_INPUTLINE * 4 + 3];
803 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
804 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
808 for(in = line, out = printline; *in; ++in)
812 case STRING_COLOR_TAG:
815 case STRING_COLOR_TAG:
817 *out++ = STRING_COLOR_TAG;
823 if(lastcolor == 0) break; else lastcolor = 0;
824 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
829 if(lastcolor == 1) break; else lastcolor = 1;
830 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
835 if(lastcolor == 2) break; else lastcolor = 2;
836 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
841 if(lastcolor == 3) break; else lastcolor = 3;
842 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
847 if(lastcolor == 4) break; else lastcolor = 4;
848 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
853 if(lastcolor == 5) break; else lastcolor = 5;
854 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
859 if(lastcolor == 6) break; else lastcolor = 6;
860 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
867 if(lastcolor == 8) break; else lastcolor = 8;
868 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
871 *out++ = STRING_COLOR_TAG;
878 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
895 Sys_PrintToTerminal(printline);
897 else if(sys_colortranslation.integer == 2) // Quake
899 Sys_PrintToTerminal(line);
903 static char printline[MAX_INPUTLINE]; // it can only get shorter here
906 for(in = line, out = printline; *in; ++in)
910 case STRING_COLOR_TAG:
913 case STRING_COLOR_TAG:
915 *out++ = STRING_COLOR_TAG;
930 *out++ = STRING_COLOR_TAG;
940 Sys_PrintToTerminal(printline);
943 // empty the line buffer
954 Prints to all appropriate console targets
957 void Con_Printf(const char *fmt, ...)
960 char msg[MAX_INPUTLINE];
962 va_start(argptr,fmt);
963 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
973 A Con_Print that only shows up if the "developer" cvar is set
976 void Con_DPrint(const char *msg)
978 if (!developer.integer)
979 return; // don't confuse non-developers with techie stuff...
987 A Con_Printf that only shows up if the "developer" cvar is set
990 void Con_DPrintf(const char *fmt, ...)
993 char msg[MAX_INPUTLINE];
995 if (!developer.integer)
996 return; // don't confuse non-developers with techie stuff...
998 va_start(argptr,fmt);
999 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1007 ==============================================================================
1011 ==============================================================================
1018 The input line scrolls horizontally if typing goes beyond the right edge
1020 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1023 void Con_DrawInput (void)
1027 char editlinecopy[MAX_INPUTLINE+1], *text;
1029 if (!key_consoleactive)
1030 return; // don't draw anything
1032 strlcpy(editlinecopy, key_lines[edit_line], sizeof(editlinecopy));
1033 text = editlinecopy;
1035 // Advanced Console Editing by Radix radix@planetquake.com
1036 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1037 // use strlen of edit_line instead of key_linepos to allow editing
1038 // of early characters w/o erasing
1040 y = (int)strlen(text);
1042 // fill out remainder with spaces
1043 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1046 // add the cursor frame
1047 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1048 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1050 // text[key_linepos + 1] = 0;
1052 // prestep if horizontally scrolling
1053 if (key_linepos >= con_linewidth)
1054 text += 1 + key_linepos - con_linewidth;
1057 DrawQ_String(0, con_vislines - con_textsize.value*2, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false );
1060 // key_lines[edit_line][key_linepos] = 0;
1068 Draws the last few lines of output transparently over the game top
1071 void Con_DrawNotify (void)
1077 char temptext[MAX_INPUTLINE];
1078 int colorindex = -1; //-1 for default
1080 if (con_notify.integer < 0)
1081 Cvar_SetValueQuick(&con_notify, 0);
1082 if (con_notify.integer > MAX_NOTIFYLINES)
1083 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
1084 if (gamemode == GAME_TRANSFUSION)
1088 // make a copy of con_current here so that we can't get in a runaway loop printing new messages while drawing the notify text
1090 for (i= stop-con_notify.integer+1 ; i<=stop ; i++)
1095 time = con_times[i % con_notify.integer];
1098 time = cl.time - time;
1099 if (time > con_notifytime.value)
1101 text = con_text + (i % con_totallines)*con_linewidth;
1103 if (gamemode == GAME_NEXUIZ) {
1108 // count up to the last non-whitespace, and ignore color codes
1109 for (j = 0;j < con_linewidth && text[j];j++)
1111 if (text[j] == STRING_COLOR_TAG && (text[j+1] >= '0' && text[j+1] <= '9'))
1121 // center the line using the calculated width
1122 x = (vid_conwidth.integer - finalchars * con_textsize.value) * 0.5;
1126 DrawQ_String( x, v, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1128 v += con_textsize.value;
1132 if (key_dest == key_message)
1134 int colorindex = -1;
1138 // LordHavoc: speedup, and other improvements
1140 sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1142 sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1143 while ((int)strlen(temptext) >= con_linewidth)
1145 DrawQ_String( 0, v, temptext, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1146 strlcpy(temptext, &temptext[con_linewidth], sizeof(temptext));
1147 v += con_textsize.value;
1149 if (strlen(temptext) > 0)
1151 DrawQ_String( 0, v, temptext, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1152 v += con_textsize.value;
1161 Draws the console with the solid background
1162 The typing input line at the bottom should only be drawn if typing is allowed
1165 void Con_DrawConsole (int lines)
1167 int i, rows, j, stop;
1170 int colorindex = -1;
1175 // draw the background
1176 DrawQ_Pic(0, lines - vid_conheight.integer, scr_conbrightness.value >= 0.01f ? Draw_CachePic("gfx/conback", true) : NULL, vid_conwidth.integer, vid_conheight.integer, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, scr_conalpha.value, 0);
1177 DrawQ_String(vid_conwidth.integer - strlen(engineversion) * con_textsize.value - con_textsize.value, lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0, NULL, true);
1180 con_vislines = lines;
1182 rows = (int)ceil((lines/con_textsize.value)-2); // rows of text to draw
1183 y = lines - (rows+2)*con_textsize.value; // may start slightly negative
1185 // make a copy of con_current here so that we can't get in a runaway loop printing new messages while drawing the notify text
1187 for (i = stop - rows + 1;i <= stop;i++, y += con_textsize.value)
1189 j = max(i - con_backscroll, 0);
1190 text = con_text + (j % con_totallines)*con_linewidth;
1192 DrawQ_String( 0, y, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1195 // draw the input prompt, user text, and cursor if desired
1203 Prints not only map filename, but also
1204 its format (q1/q2/q3/hl) and even its message
1206 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1207 //LordHavoc: rewrote bsp type detection, added mcbsp support and rewrote message extraction to do proper worldspawn parsing
1208 //LordHavoc: added .ent file loading, and redesigned error handling to still try the .ent file even if the map format is not recognized, this also eliminated one goto
1209 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1210 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1214 int i, k, max, p, o, min;
1217 unsigned char buf[1024];
1219 sprintf(message, "maps/%s*.bsp", s);
1220 t = FS_Search(message, 1, true);
1223 if (t->numfilenames > 1)
1224 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1225 len = (unsigned char *)Z_Malloc(t->numfilenames);
1227 for(max=i=0;i<t->numfilenames;i++)
1229 k = (int)strlen(t->filenames[i]);
1239 for(i=0;i<t->numfilenames;i++)
1241 int lumpofs = 0, lumplen = 0;
1242 char *entities = NULL;
1243 const char *data = NULL;
1245 char entfilename[MAX_QPATH];
1246 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1248 f = FS_Open(t->filenames[i], "rb", true, false);
1251 memset(buf, 0, 1024);
1252 FS_Read(f, buf, 1024);
1253 if (!memcmp(buf, "IBSP", 4))
1255 p = LittleLong(((int *)buf)[1]);
1256 if (p == Q3BSPVERSION)
1258 q3dheader_t *header = (q3dheader_t *)buf;
1259 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1260 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1262 else if (p == Q2BSPVERSION)
1264 q2dheader_t *header = (q2dheader_t *)buf;
1265 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1266 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1269 else if (!memcmp(buf, "MCBSPpad", 8))
1271 p = LittleLong(((int *)buf)[2]);
1272 if (p == MCBSPVERSION)
1274 int numhulls = LittleLong(((int *)buf)[3]);
1275 lumpofs = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+0]);
1276 lumplen = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+1]);
1279 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1281 dheader_t *header = (dheader_t *)buf;
1282 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1283 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1287 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1288 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1289 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1290 if (!entities && lumplen >= 10)
1292 FS_Seek(f, lumpofs, SEEK_SET);
1293 entities = (char *)Z_Malloc(lumplen + 1);
1294 FS_Read(f, entities, lumplen);
1298 // if there are entities to parse, a missing message key just
1299 // means there is no title, so clear the message string now
1305 if (!COM_ParseToken_Simple(&data, false, false))
1307 if (com_token[0] == '{')
1309 if (com_token[0] == '}')
1311 // skip leading whitespace
1312 for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
1313 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
1314 keyname[l] = com_token[k+l];
1316 if (!COM_ParseToken_Simple(&data, false, false))
1318 if (developer.integer >= 100)
1319 Con_Printf("key: %s %s\n", keyname, com_token);
1320 if (!strcmp(keyname, "message"))
1322 // get the message contents
1323 strlcpy(message, com_token, sizeof(message));
1333 *(t->filenames[i]+len[i]+5) = 0;
1336 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1337 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1338 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1339 case MCBSPVERSION: strlcpy((char *)buf, "MC", sizeof(buf));break;
1340 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1341 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1343 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1348 k = *(t->filenames[0]+5+p);
1351 for(i=1;i<t->numfilenames;i++)
1352 if(*(t->filenames[i]+5+p) != k)
1356 if(p > o && completedname && completednamebufferlength > 0)
1358 memset(completedname, 0, completednamebufferlength);
1359 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1369 New function for tab-completion system
1370 Added by EvilTypeGuy
1371 MEGA Thanks to Taniwha
1374 void Con_DisplayList(const char **list)
1376 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1377 const char **walk = list;
1380 len = (int)strlen(*walk);
1388 len = (int)strlen(*list);
1389 if (pos + maxlen >= width) {
1395 for (i = 0; i < (maxlen - len); i++)
1406 /* Nicks_CompleteCountPossible
1408 Count the number of possible nicks to complete
1410 //qboolean COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets);
1411 void SanitizeString(char *in, char *out)
1415 if(*in == STRING_COLOR_TAG)
1420 out[0] = STRING_COLOR_TAG;
1423 } else if(*in >= '0' && *in <= '9')
1430 } else if (*in == STRING_COLOR_TAG)
1432 } else if (*in != STRING_COLOR_TAG) {
1436 *out = qfont_table[*(unsigned char*)in];
1442 int Sbar_GetPlayer (int index); // <- safety?
1444 // Now it becomes TRICKY :D --blub
1445 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
1446 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
1447 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
1448 static int Nicks_offset[MAX_SCOREBOARD]; // when nicks use a space, we need this to move the completion list string starts to avoid invalid memcpys
1449 static int Nicks_matchpos;
1451 // co against <<:BLASTER:>> is true!?
1452 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
1456 if(tolower(*a) == tolower(*b))
1470 return (*a < *b) ? -1 : 1;
1474 return (*a < *b) ? -1 : 1;
1478 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
1481 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
1483 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
1484 return Nicks_strncasecmp_nospaces(a, b, a_len);
1485 return strncasecmp(a, b, a_len);
1488 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
1490 // ignore non alphanumerics of B
1491 // if A contains a non-alphanumeric, B must contain it as well though!
1494 qboolean alnum_a, alnum_b;
1496 if(tolower(*a) == tolower(*b))
1498 if(*a == 0) // end of both strings, they're equal
1505 // not equal, end of one string?
1510 // ignore non alphanumerics
1511 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
1512 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
1513 if(!alnum_a) // b must contain this
1514 return (*a < *b) ? -1 : 1;
1517 // otherwise, both are alnum, they're just not equal, return the appropriate number
1519 return (*a < *b) ? -1 : 1;
1524 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
1533 if(!con_nickcompletion.integer)
1536 // changed that to 1
1537 if(!line[0])// || !line[1]) // we want at least... 2 written characters
1540 for(i = 0; i < cl.maxclients; ++i)
1542 /*p = Sbar_GetPlayer(i);
1546 if(!cl.scores[p].name[0])
1549 SanitizeString(cl.scores[p].name, name);
1550 //Con_Printf("Sanitized: %s^7 -> %s", cl.scores[p].name, name);
1555 length = strlen(name);
1557 spos = pos - 1; // no need for a minimum of characters :)
1559 while(spos >= 0 && (spos - pos) < length) // search-string-length < name length
1561 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
1563 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
1564 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
1570 if(isCon && spos == 0)
1572 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
1578 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
1579 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
1581 // the sanitized list
1582 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
1585 Nicks_matchpos = match;
1588 Nicks_offset[count] = s - (&line[match]);
1589 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
1596 void Cmd_CompleteNicksPrint(int count)
1599 for(i = 0; i < count; ++i)
1600 Con_Printf("%s\n", Nicks_list[i]);
1603 void Nicks_CutMatchesNormal(int count)
1605 // cut match 0 down to the longest possible completion
1608 c = strlen(Nicks_sanlist[0]) - 1;
1609 for(i = 1; i < count; ++i)
1611 l = strlen(Nicks_sanlist[i]) - 1;
1615 for(l = 0; l <= c; ++l)
1616 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
1622 Nicks_sanlist[0][c+1] = 0;
1623 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
1626 unsigned int Nicks_strcleanlen(const char *s)
1631 if( (*s >= 'a' && *s <= 'z') ||
1632 (*s >= 'A' && *s <= 'Z') ||
1633 (*s >= '0' && *s <= '9') ||
1641 void Nicks_CutMatchesAlphaNumeric(int count)
1643 // cut match 0 down to the longest possible completion
1646 char tempstr[sizeof(Nicks_sanlist[0])];
1648 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
1650 c = strlen(Nicks_sanlist[0]);
1651 for(i = 0, l = 0; i < (int)c; ++i)
1653 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
1654 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
1655 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
1657 tempstr[l++] = Nicks_sanlist[0][i];
1662 for(i = 1; i < count; ++i)
1665 b = Nicks_sanlist[i];
1675 if(tolower(*a) == tolower(*b))
1681 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
1683 // b is alnum, so cut
1690 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
1691 Nicks_CutMatchesNormal(count);
1692 //if(!Nicks_sanlist[0][0])
1693 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
1695 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
1696 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
1700 void Nicks_CutMatchesNoSpaces(int count)
1702 // cut match 0 down to the longest possible completion
1705 char tempstr[sizeof(Nicks_sanlist[0])];
1708 c = strlen(Nicks_sanlist[0]);
1709 for(i = 0, l = 0; i < (int)c; ++i)
1711 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
1713 tempstr[l++] = Nicks_sanlist[0][i];
1718 for(i = 1; i < count; ++i)
1721 b = Nicks_sanlist[i];
1731 if(tolower(*a) == tolower(*b))
1745 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
1746 Nicks_CutMatchesNormal(count);
1747 //if(!Nicks_sanlist[0][0])
1748 //Con_Printf("TS: %s\n", tempstr);
1749 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
1751 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
1752 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
1756 void Nicks_CutMatches(int count)
1758 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
1759 Nicks_CutMatchesAlphaNumeric(count);
1760 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
1761 Nicks_CutMatchesNoSpaces(count);
1763 Nicks_CutMatchesNormal(count);
1766 const char **Nicks_CompleteBuildList(int count)
1770 // the list is freed by Con_CompleteCommandLine, so create a char**
1771 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
1773 for(; bpos < count; ++bpos)
1774 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
1776 Nicks_CutMatches(count);
1782 int Nicks_AddLastColor(char *buffer, int pos)
1784 qboolean quote_added = false;
1788 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
1790 // we'll have to add a quote :)
1791 buffer[pos++] = '\"';
1795 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
1797 // add color when no quote was added, or when flags &4?
1799 for(match = Nicks_matchpos-1; match >= 0; --match)
1801 if(buffer[match] == STRING_COLOR_TAG && buffer[match+1] >= '0' && buffer[match+1] <= '9')
1803 color = buffer[match+1];
1807 if(!quote_added && buffer[pos-2] == STRING_COLOR_TAG && buffer[pos-1] >= '0' && buffer[pos-1] <= '9') // when thes use &4
1809 buffer[pos++] = STRING_COLOR_TAG;
1810 buffer[pos++] = color;
1815 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
1818 /*if(!con_nickcompletion.integer)
1819 return; is tested in Nicks_CompletionCountPossible */
1820 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
1826 msg = Nicks_list[0];
1827 len = min(size - Nicks_matchpos - 3, strlen(msg));
1828 memcpy(&buffer[Nicks_matchpos], msg, len);
1829 if( len < (size - 4) ) // space for color and space and \0
1830 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
1831 buffer[len++] = ' ';
1838 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
1839 Cmd_CompleteNicksPrint(n);
1841 Nicks_CutMatches(n);
1843 msg = Nicks_sanlist[0];
1844 len = min(size - Nicks_matchpos, strlen(msg));
1845 memcpy(&buffer[Nicks_matchpos], msg, len);
1846 buffer[Nicks_matchpos + len] = 0;
1848 return Nicks_matchpos + len;
1855 Con_CompleteCommandLine
1857 New function for tab-completion system
1858 Added by EvilTypeGuy
1859 Thanks to Fett erich@heintz.com
1861 Enhanced to tab-complete map names by [515]
1864 void Con_CompleteCommandLine (void)
1866 const char *cmd = "";
1868 const char **list[4] = {0, 0, 0, 0};
1870 int c, v, a, i, cmd_len, pos, k;
1871 int n; // nicks --blub
1873 //find what we want to complete
1877 k = key_lines[edit_line][pos];
1878 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
1883 s = key_lines[edit_line] + pos;
1884 strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2)); //save chars after cursor
1885 key_lines[edit_line][key_linepos] = 0; //hide them
1888 for(k=pos-1;k>2;k--)
1889 if(key_lines[edit_line][k] != ' ')
1891 if(key_lines[edit_line][k] == '\"' || key_lines[edit_line][k] == ';' || key_lines[edit_line][k] == '\'')
1893 if ((pos+k > 2 && !strncmp(key_lines[edit_line]+k-2, "map", 3))
1894 || (pos+k > 10 && !strncmp(key_lines[edit_line]+k-10, "changelevel", 11)))
1897 if (GetMapList(s, t, sizeof(t)))
1899 // first move the cursor
1900 key_linepos += (int)strlen(t) - (int)strlen(s);
1902 // and now do the actual work
1904 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
1905 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
1907 // and fix the cursor
1908 if(key_linepos > (int) strlen(key_lines[edit_line]))
1909 key_linepos = (int) strlen(key_lines[edit_line]);
1915 // Count number of possible matches and print them
1916 c = Cmd_CompleteCountPossible(s);
1919 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
1920 Cmd_CompleteCommandPrint(s);
1922 v = Cvar_CompleteCountPossible(s);
1925 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
1926 Cvar_CompleteCvarPrint(s);
1928 a = Cmd_CompleteAliasCountPossible(s);
1931 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
1932 Cmd_CompleteAliasPrint(s);
1934 n = Nicks_CompleteCountPossible(key_lines[edit_line], key_linepos, s, true);
1937 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
1938 Cmd_CompleteNicksPrint(n);
1941 if (!(c + v + a + n)) // No possible matches
1944 strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
1949 cmd = *(list[0] = Cmd_CompleteBuildList(s));
1951 cmd = *(list[1] = Cvar_CompleteBuildList(s));
1953 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
1955 cmd = *(list[3] = Nicks_CompleteBuildList(n));
1957 for (cmd_len = (int)strlen(s);;cmd_len++)
1960 for (i = 0; i < 3; i++)
1962 for (l = list[i];*l;l++)
1963 if ((*l)[cmd_len] != cmd[cmd_len])
1965 // all possible matches share this character, so we continue...
1968 // if all matches ended at the same position, stop
1969 // (this means there is only one match)
1975 // prevent a buffer overrun by limiting cmd_len according to remaining space
1976 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
1980 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
1981 key_linepos += cmd_len;
1982 // if there is only one match, add a space after it
1983 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
1986 { // was a nick, might have an offset, and needs colors ;) --blub
1987 key_linepos = pos - Nicks_offset[0];
1988 cmd_len = strlen(Nicks_list[0]);
1989 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 3 - pos);
1991 memcpy(&key_lines[edit_line][key_linepos] , Nicks_list[0], cmd_len);
1992 key_linepos += cmd_len;
1993 if(key_linepos < (int)(sizeof(key_lines[edit_line])-4)) // space for ^, X and space and \0
1994 key_linepos = Nicks_AddLastColor(key_lines[edit_line], key_linepos);
1996 key_lines[edit_line][key_linepos++] = ' ';
2000 // use strlcat to avoid a buffer overrun
2001 key_lines[edit_line][key_linepos] = 0;
2002 strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
2004 // free the command, cvar, and alias lists
2005 for (i = 0; i < 4; i++)
2007 Mem_Free((void *)list[i]);