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.
22 #if !defined(WIN32) || defined(__MINGW32__)
30 float con_cursorspeed = 4;
32 #define CON_TEXTSIZE 131072
34 // total lines in console scrollback
36 // lines up from bottom to display
38 // where next message will be printed
40 // offset in current line for next print
42 char con_text[CON_TEXTSIZE];
44 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
45 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show (0-32)"};
46 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
49 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)"};
51 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)"};
53 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)"};
57 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
58 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
59 "0: add nothing after completion. "
60 "1: add the last color after completion. "
61 "2: add a quote when starting a quote instead of the color. "
62 "4: will replace 1, will force color, even after a quote. "
63 "8: ignore non-alphanumerics. "
64 "16: ignore spaces. "};
65 #define NICKS_ADD_COLOR 1
66 #define NICKS_ADD_QUOTE 2
67 #define NICKS_FORCE_COLOR 4
68 #define NICKS_ALPHANUMERICS_ONLY 8
69 #define NICKS_NO_SPACES 16
71 #define MAX_NOTIFYLINES 32
72 // cl.time time the line was generated for transparent notify lines
73 float con_times[MAX_NOTIFYLINES];
77 qboolean con_initialized;
79 // used for server replies to rcon command
80 qboolean rcon_redirect = false;
81 int rcon_redirect_bufferpos = 0;
82 char rcon_redirect_buffer[1400];
86 ==============================================================================
90 ==============================================================================
93 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
94 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"};
95 char log_dest_buffer[1400]; // UDP packet
96 size_t log_dest_buffer_pos;
97 qboolean log_dest_buffer_appending;
98 char crt_log_file [MAX_OSPATH] = "";
99 qfile_t* logfile = NULL;
101 unsigned char* logqueue = NULL;
103 size_t logq_size = 0;
105 void Log_ConPrint (const char *msg);
112 static void Log_DestBuffer_Init()
114 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
115 log_dest_buffer_pos = 5;
123 void Log_DestBuffer_Flush()
125 lhnetaddress_t log_dest_addr;
126 lhnetsocket_t *log_dest_socket;
127 const char *s = log_dest_udp.string;
128 qboolean have_opened_temp_sockets = false;
129 if(s) if(log_dest_buffer_pos > 5)
131 ++log_dest_buffer_appending;
132 log_dest_buffer[log_dest_buffer_pos++] = 0;
134 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
136 have_opened_temp_sockets = true;
137 NetConn_OpenServerPorts(true);
140 while(COM_ParseToken_Console(&s))
141 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
143 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
145 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
147 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
150 if(have_opened_temp_sockets)
151 NetConn_CloseServerPorts();
152 --log_dest_buffer_appending;
154 log_dest_buffer_pos = 0;
162 const char* Log_Timestamp (const char *desc)
164 static char timestamp [128];
166 const struct tm *crt_tm;
167 char timestring [64];
169 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
171 crt_tm = localtime (&crt_time);
172 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
175 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
177 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
190 if (logfile != NULL || log_file.string[0] == '\0')
193 logfile = FS_Open (log_file.string, "ab", false, false);
196 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
197 FS_Print (logfile, Log_Timestamp ("Log started"));
207 void Log_Close (void)
212 FS_Print (logfile, Log_Timestamp ("Log stopped"));
213 FS_Print (logfile, "\n");
217 crt_log_file[0] = '\0';
226 void Log_Start (void)
232 // Dump the contents of the log queue into the log file and free it
233 if (logqueue != NULL)
235 unsigned char *temp = logqueue;
240 FS_Write (logfile, temp, logq_ind);
241 if(*log_dest_udp.string)
243 for(pos = 0; pos < logq_ind; )
245 if(log_dest_buffer_pos == 0)
246 Log_DestBuffer_Init();
247 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
248 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
249 log_dest_buffer_pos += n;
250 Log_DestBuffer_Flush();
267 void Log_ConPrint (const char *msg)
269 static qboolean inprogress = false;
271 // don't allow feedback loops with memory error reports
276 // Until the host is completely initialized, we maintain a log queue
277 // to store the messages, since the log can't be started before
278 if (logqueue != NULL)
280 size_t remain = logq_size - logq_ind;
281 size_t len = strlen (msg);
283 // If we need to enlarge the log queue
286 size_t factor = ((logq_ind + len) / logq_size) + 1;
287 unsigned char* newqueue;
290 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
291 memcpy (newqueue, logqueue, logq_ind);
294 remain = logq_size - logq_ind;
296 memcpy (&logqueue[logq_ind], msg, len);
303 // Check if log_file has changed
304 if (strcmp (crt_log_file, log_file.string) != 0)
310 // If a log file is available
312 FS_Print (logfile, msg);
323 void Log_Printf (const char *logfilename, const char *fmt, ...)
327 file = FS_Open (logfilename, "ab", true, false);
332 va_start (argptr, fmt);
333 FS_VPrintf (file, fmt, argptr);
342 ==============================================================================
346 ==============================================================================
354 void Con_ToggleConsole_f (void)
356 // toggle the 'user wants console' bit
357 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
358 memset (con_times, 0, sizeof(con_times));
366 void Con_Clear_f (void)
369 memset (con_text, ' ', CON_TEXTSIZE);
378 void Con_ClearNotify (void)
382 for (i=0 ; i<MAX_NOTIFYLINES ; i++)
392 void Con_MessageMode_f (void)
394 key_dest = key_message;
404 void Con_MessageMode2_f (void)
406 key_dest = key_message;
415 If the line width has changed, reformat the buffer.
418 void Con_CheckResize (void)
420 int i, j, width, oldwidth, oldtotallines, numlines, numchars;
422 char tbuf[CON_TEXTSIZE];
424 f = bound(1, con_textsize.value, 128);
425 if(f != con_textsize.value)
426 Cvar_SetValueQuick(&con_textsize, f);
427 width = (int)floor(vid_conwidth.value / con_textsize.value);
428 width = bound(1, width, CON_TEXTSIZE/4);
430 if (width == con_linewidth)
433 oldwidth = con_linewidth;
434 con_linewidth = width;
435 oldtotallines = con_totallines;
436 con_totallines = CON_TEXTSIZE / con_linewidth;
437 numlines = oldtotallines;
439 if (con_totallines < numlines)
440 numlines = con_totallines;
444 if (con_linewidth < numchars)
445 numchars = con_linewidth;
447 memcpy (tbuf, con_text, CON_TEXTSIZE);
448 memset (con_text, ' ', CON_TEXTSIZE);
450 for (i=0 ; i<numlines ; i++)
452 for (j=0 ; j<numchars ; j++)
454 con_text[(con_totallines - 1 - i) * con_linewidth + j] =
455 tbuf[((con_current - i + oldtotallines) %
456 oldtotallines) * oldwidth + j];
463 con_current = con_totallines - 1;
466 //[515]: the simplest command ever
467 //LordHavoc: not so simple after I made it print usage...
468 static void Con_Maps_f (void)
472 Con_Printf("usage: maps [mapnameprefix]\n");
475 else if (Cmd_Argc() == 2)
476 GetMapList(Cmd_Argv(1), NULL, 0);
478 GetMapList("", NULL, 0);
481 void Con_ConDump_f (void)
484 qboolean allblankssofar;
487 char temp[MAX_INPUTLINE+2];
490 Con_Printf("usage: condump <filename>\n");
493 file = FS_Open(Cmd_Argv(1), "wb", false, false);
496 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
499 // iterate over the entire console history buffer line by line
500 allblankssofar = true;
501 for (i = 0;i < con_totallines;i++)
503 text = con_text + ((con_current + 1 + i) % con_totallines)*con_linewidth;
504 // count the used characters on this line
505 for (l = min(con_linewidth, (int)sizeof(temp));l > 0 && text[l-1] == ' ';l--);
506 // if not a blank line, begin output
508 allblankssofar = false;
509 // output the current line to the file
513 memcpy(temp, text, l);
516 FS_Print(file, temp);
529 memset (con_text, ' ', CON_TEXTSIZE);
531 con_totallines = CON_TEXTSIZE / con_linewidth;
533 // Allocate a log queue, this will be freed after configs are parsed
534 logq_size = MAX_INPUTLINE;
535 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
538 Cvar_RegisterVariable (&sys_colortranslation);
539 Cvar_RegisterVariable (&sys_specialcharactertranslation);
541 Cvar_RegisterVariable (&log_file);
542 Cvar_RegisterVariable (&log_dest_udp);
544 // support for the classic Quake option
545 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
546 if (COM_CheckParm ("-condebug") != 0)
547 Cvar_SetQuick (&log_file, "qconsole.log");
549 // register our cvars
550 Cvar_RegisterVariable (&con_notifytime);
551 Cvar_RegisterVariable (&con_notify);
552 Cvar_RegisterVariable (&con_textsize);
555 Cvar_RegisterVariable (&con_nickcompletion);
556 Cvar_RegisterVariable (&con_nickcompletion_flags);
558 // register our commands
559 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
560 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
561 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
562 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
563 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
564 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
566 con_initialized = true;
567 Con_Print("Console initialized.\n");
576 void Con_Linefeed (void)
583 memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth);
590 Handles cursor positioning, line wrapping, etc
591 All console printing must go through this in order to be displayed
592 If no console is visible, the notify window will pop up.
595 void Con_PrintToHistory(const char *txt, int mask)
603 for (l=0 ; l< con_linewidth ; l++)
608 if (l != con_linewidth && (con_x + l > con_linewidth) )
623 // mark time for transparent overlay
624 if (con_current >= 0)
626 if (con_notify.integer < 0)
627 Cvar_SetValueQuick(&con_notify, 0);
628 if (con_notify.integer > MAX_NOTIFYLINES)
629 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
630 if (con_notify.integer > 0)
631 con_times[con_current % con_notify.integer] = cl.time;
646 default: // display character and advance
647 y = con_current % con_totallines;
648 con_text[y*con_linewidth+con_x] = c | mask;
650 if (con_x >= con_linewidth)
658 /* The translation table between the graphical font and plain ASCII --KB */
659 static char qfont_table[256] = {
660 '\0', '#', '#', '#', '#', '.', '#', '#',
661 '#', 9, 10, '#', ' ', 13, '.', '.',
662 '[', ']', '0', '1', '2', '3', '4', '5',
663 '6', '7', '8', '9', '.', '<', '=', '>',
664 ' ', '!', '"', '#', '$', '%', '&', '\'',
665 '(', ')', '*', '+', ',', '-', '.', '/',
666 '0', '1', '2', '3', '4', '5', '6', '7',
667 '8', '9', ':', ';', '<', '=', '>', '?',
668 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
669 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
670 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
671 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
672 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
673 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
674 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
675 'x', 'y', 'z', '{', '|', '}', '~', '<',
677 '<', '=', '>', '#', '#', '.', '#', '#',
678 '#', '#', ' ', '#', ' ', '>', '.', '.',
679 '[', ']', '0', '1', '2', '3', '4', '5',
680 '6', '7', '8', '9', '.', '<', '=', '>',
681 ' ', '!', '"', '#', '$', '%', '&', '\'',
682 '(', ')', '*', '+', ',', '-', '.', '/',
683 '0', '1', '2', '3', '4', '5', '6', '7',
684 '8', '9', ':', ';', '<', '=', '>', '?',
685 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
686 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
687 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
688 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
689 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
690 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
691 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
692 'x', 'y', 'z', '{', '|', '}', '~', '<'
699 Adds a character to the rcon buffer
702 void Con_Rcon_AddChar(char c)
704 if(log_dest_buffer_appending)
706 ++log_dest_buffer_appending;
708 // if this print is in response to an rcon command, add the character
709 // to the rcon redirect buffer
711 if (rcon_redirect && rcon_redirect_bufferpos < (int)sizeof(rcon_redirect_buffer) - 1)
712 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
713 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
715 if(log_dest_buffer_pos == 0)
716 Log_DestBuffer_Init();
717 log_dest_buffer[log_dest_buffer_pos++] = c;
718 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
719 Log_DestBuffer_Flush();
722 log_dest_buffer_pos = 0;
724 --log_dest_buffer_appending;
731 Prints to all appropriate console targets, and adds timestamps
734 extern cvar_t timestamps;
735 extern cvar_t timeformat;
736 extern qboolean sys_nostdout;
737 void Con_Print(const char *msg)
740 static int index = 0;
741 static char line[MAX_INPUTLINE];
745 Con_Rcon_AddChar(*msg);
746 // if this is the beginning of a new line, print timestamp
749 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
751 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
752 line[index++] = STRING_COLOR_TAG;
753 // assert( STRING_COLOR_DEFAULT < 10 )
754 line[index++] = STRING_COLOR_DEFAULT + '0';
755 // special color codes for chat messages must always come first
756 // for Con_PrintToHistory to work properly
757 if (*msg == 1 || *msg == 2)
762 if (msg[1] == '(' && cl.foundtalk2wav)
763 S_LocalSound ("sound/misc/talk2.wav");
765 S_LocalSound ("sound/misc/talk.wav");
767 line[index++] = STRING_COLOR_TAG;
770 Con_Rcon_AddChar(*msg);
773 for (;*timestamp;index++, timestamp++)
774 if (index < (int)sizeof(line) - 2)
775 line[index] = *timestamp;
777 // append the character
778 line[index++] = *msg;
779 // if this is a newline character, we have a complete line to print
780 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
782 // terminate the line
786 // send to scrollable buffer
787 if (con_initialized && cls.state != ca_dedicated)
788 Con_PrintToHistory(line, mask);
789 // send to terminal or dedicated server window
793 if(sys_specialcharactertranslation.integer)
795 for (p = (unsigned char *) line;*p; p++)
796 *p = qfont_table[*p];
799 if(sys_colortranslation.integer == 1) // ANSI
801 static char printline[MAX_INPUTLINE * 4 + 3];
802 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
803 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
807 for(in = line, out = printline; *in; ++in)
811 case STRING_COLOR_TAG:
814 case STRING_COLOR_TAG:
816 *out++ = STRING_COLOR_TAG;
822 if(lastcolor == 0) break; else lastcolor = 0;
823 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
828 if(lastcolor == 1) break; else lastcolor = 1;
829 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
834 if(lastcolor == 2) break; else lastcolor = 2;
835 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
840 if(lastcolor == 3) break; else lastcolor = 3;
841 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
846 if(lastcolor == 4) break; else lastcolor = 4;
847 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
852 if(lastcolor == 5) break; else lastcolor = 5;
853 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
858 if(lastcolor == 6) break; else lastcolor = 6;
859 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
866 if(lastcolor == 8) break; else lastcolor = 8;
867 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
870 *out++ = STRING_COLOR_TAG;
877 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
894 Sys_PrintToTerminal(printline);
896 else if(sys_colortranslation.integer == 2) // Quake
898 Sys_PrintToTerminal(line);
902 static char printline[MAX_INPUTLINE]; // it can only get shorter here
905 for(in = line, out = printline; *in; ++in)
909 case STRING_COLOR_TAG:
912 case STRING_COLOR_TAG:
914 *out++ = STRING_COLOR_TAG;
929 *out++ = STRING_COLOR_TAG;
939 Sys_PrintToTerminal(printline);
942 // empty the line buffer
953 Prints to all appropriate console targets
956 void Con_Printf(const char *fmt, ...)
959 char msg[MAX_INPUTLINE];
961 va_start(argptr,fmt);
962 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
972 A Con_Print that only shows up if the "developer" cvar is set
975 void Con_DPrint(const char *msg)
977 if (!developer.integer)
978 return; // don't confuse non-developers with techie stuff...
986 A Con_Printf that only shows up if the "developer" cvar is set
989 void Con_DPrintf(const char *fmt, ...)
992 char msg[MAX_INPUTLINE];
994 if (!developer.integer)
995 return; // don't confuse non-developers with techie stuff...
997 va_start(argptr,fmt);
998 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1006 ==============================================================================
1010 ==============================================================================
1017 The input line scrolls horizontally if typing goes beyond the right edge
1019 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1022 void Con_DrawInput (void)
1026 char editlinecopy[MAX_INPUTLINE+1], *text;
1028 if (!key_consoleactive)
1029 return; // don't draw anything
1031 strlcpy(editlinecopy, key_lines[edit_line], sizeof(editlinecopy));
1032 text = editlinecopy;
1034 // Advanced Console Editing by Radix radix@planetquake.com
1035 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1036 // use strlen of edit_line instead of key_linepos to allow editing
1037 // of early characters w/o erasing
1039 y = (int)strlen(text);
1041 // fill out remainder with spaces
1042 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1045 // add the cursor frame
1046 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1047 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1049 // text[key_linepos + 1] = 0;
1051 // prestep if horizontally scrolling
1052 if (key_linepos >= con_linewidth)
1053 text += 1 + key_linepos - con_linewidth;
1056 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 );
1059 // key_lines[edit_line][key_linepos] = 0;
1067 Draws the last few lines of output transparently over the game top
1070 void Con_DrawNotify (void)
1076 char temptext[MAX_INPUTLINE];
1077 int colorindex = -1; //-1 for default
1079 if (con_notify.integer < 0)
1080 Cvar_SetValueQuick(&con_notify, 0);
1081 if (con_notify.integer > MAX_NOTIFYLINES)
1082 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
1083 if (gamemode == GAME_TRANSFUSION)
1087 // 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
1089 for (i= stop-con_notify.integer+1 ; i<=stop ; i++)
1094 time = con_times[i % con_notify.integer];
1097 time = cl.time - time;
1098 if (time > con_notifytime.value)
1100 text = con_text + (i % con_totallines)*con_linewidth;
1102 if (gamemode == GAME_NEXUIZ) {
1107 // count up to the last non-whitespace, and ignore color codes
1108 for (j = 0;j < con_linewidth && text[j];j++)
1110 if (text[j] == STRING_COLOR_TAG && (text[j+1] >= '0' && text[j+1] <= '9'))
1120 // center the line using the calculated width
1121 x = (vid_conwidth.integer - finalchars * con_textsize.value) * 0.5;
1125 DrawQ_String( x, v, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1127 v += con_textsize.value;
1131 if (key_dest == key_message)
1133 int colorindex = -1;
1137 // LordHavoc: speedup, and other improvements
1139 sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1141 sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1142 while ((int)strlen(temptext) >= con_linewidth)
1144 DrawQ_String( 0, v, temptext, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1145 strlcpy(temptext, &temptext[con_linewidth], sizeof(temptext));
1146 v += con_textsize.value;
1148 if (strlen(temptext) > 0)
1150 DrawQ_String( 0, v, temptext, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1151 v += con_textsize.value;
1160 Draws the console with the solid background
1161 The typing input line at the bottom should only be drawn if typing is allowed
1164 void Con_DrawConsole (int lines)
1166 int i, rows, j, stop;
1169 int colorindex = -1;
1174 // draw the background
1175 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);
1176 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);
1179 con_vislines = lines;
1181 rows = (int)ceil((lines/con_textsize.value)-2); // rows of text to draw
1182 y = lines - (rows+2)*con_textsize.value; // may start slightly negative
1184 // 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
1186 for (i = stop - rows + 1;i <= stop;i++, y += con_textsize.value)
1188 j = max(i - con_backscroll, 0);
1189 text = con_text + (j % con_totallines)*con_linewidth;
1191 DrawQ_String( 0, y, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1194 // draw the input prompt, user text, and cursor if desired
1202 Prints not only map filename, but also
1203 its format (q1/q2/q3/hl) and even its message
1205 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1206 //LordHavoc: rewrote bsp type detection, added mcbsp support and rewrote message extraction to do proper worldspawn parsing
1207 //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
1208 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1209 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1213 int i, k, max, p, o, min;
1216 unsigned char buf[1024];
1218 sprintf(message, "maps/%s*.bsp", s);
1219 t = FS_Search(message, 1, true);
1222 if (t->numfilenames > 1)
1223 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1224 len = (unsigned char *)Z_Malloc(t->numfilenames);
1226 for(max=i=0;i<t->numfilenames;i++)
1228 k = (int)strlen(t->filenames[i]);
1238 for(i=0;i<t->numfilenames;i++)
1240 int lumpofs = 0, lumplen = 0;
1241 char *entities = NULL;
1242 const char *data = NULL;
1244 char entfilename[MAX_QPATH];
1245 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1247 f = FS_Open(t->filenames[i], "rb", true, false);
1250 memset(buf, 0, 1024);
1251 FS_Read(f, buf, 1024);
1252 if (!memcmp(buf, "IBSP", 4))
1254 p = LittleLong(((int *)buf)[1]);
1255 if (p == Q3BSPVERSION)
1257 q3dheader_t *header = (q3dheader_t *)buf;
1258 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1259 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1261 else if (p == Q2BSPVERSION)
1263 q2dheader_t *header = (q2dheader_t *)buf;
1264 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1265 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1268 else if (!memcmp(buf, "MCBSPpad", 8))
1270 p = LittleLong(((int *)buf)[2]);
1271 if (p == MCBSPVERSION)
1273 int numhulls = LittleLong(((int *)buf)[3]);
1274 lumpofs = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+0]);
1275 lumplen = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+1]);
1278 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1280 dheader_t *header = (dheader_t *)buf;
1281 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1282 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1286 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1287 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1288 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1289 if (!entities && lumplen >= 10)
1291 FS_Seek(f, lumpofs, SEEK_SET);
1292 entities = (char *)Z_Malloc(lumplen + 1);
1293 FS_Read(f, entities, lumplen);
1297 // if there are entities to parse, a missing message key just
1298 // means there is no title, so clear the message string now
1304 if (!COM_ParseToken_Simple(&data, false, false))
1306 if (com_token[0] == '{')
1308 if (com_token[0] == '}')
1310 // skip leading whitespace
1311 for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
1312 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
1313 keyname[l] = com_token[k+l];
1315 if (!COM_ParseToken_Simple(&data, false, false))
1317 if (developer.integer >= 100)
1318 Con_Printf("key: %s %s\n", keyname, com_token);
1319 if (!strcmp(keyname, "message"))
1321 // get the message contents
1322 strlcpy(message, com_token, sizeof(message));
1332 *(t->filenames[i]+len[i]+5) = 0;
1335 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1336 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1337 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1338 case MCBSPVERSION: strlcpy((char *)buf, "MC", sizeof(buf));break;
1339 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1340 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1342 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1347 k = *(t->filenames[0]+5+p);
1350 for(i=1;i<t->numfilenames;i++)
1351 if(*(t->filenames[i]+5+p) != k)
1355 if(p > o && completedname && completednamebufferlength > 0)
1357 memset(completedname, 0, completednamebufferlength);
1358 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1368 New function for tab-completion system
1369 Added by EvilTypeGuy
1370 MEGA Thanks to Taniwha
1373 void Con_DisplayList(const char **list)
1375 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1376 const char **walk = list;
1379 len = (int)strlen(*walk);
1387 len = (int)strlen(*list);
1388 if (pos + maxlen >= width) {
1394 for (i = 0; i < (maxlen - len); i++)
1405 /* Nicks_CompleteCountPossible
1407 Count the number of possible nicks to complete
1409 //qboolean COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets);
1410 void SanitizeString(char *in, char *out)
1414 if(*in == STRING_COLOR_TAG)
1419 out[0] = STRING_COLOR_TAG;
1422 } else if(*in >= '0' && *in <= '9')
1429 } else if (*in == STRING_COLOR_TAG)
1431 } else if (*in != STRING_COLOR_TAG) {
1435 *out = qfont_table[*(unsigned char*)in];
1441 int Sbar_GetPlayer (int index); // <- safety?
1443 // Now it becomes TRICKY :D --blub
1444 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
1445 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
1446 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
1447 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
1448 static int Nicks_matchpos;
1450 // co against <<:BLASTER:>> is true!?
1451 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
1455 if(tolower(*a) == tolower(*b))
1469 return (*a < *b) ? -1 : 1;
1473 return (*a < *b) ? -1 : 1;
1477 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
1480 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
1482 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
1483 return Nicks_strncasecmp_nospaces(a, b, a_len);
1484 return strncasecmp(a, b, a_len);
1487 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
1489 // ignore non alphanumerics of B
1490 // if A contains a non-alphanumeric, B must contain it as well though!
1493 qboolean alnum_a, alnum_b;
1495 if(tolower(*a) == tolower(*b))
1497 if(*a == 0) // end of both strings, they're equal
1504 // not equal, end of one string?
1509 // ignore non alphanumerics
1510 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
1511 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
1512 if(!alnum_a) // b must contain this
1513 return (*a < *b) ? -1 : 1;
1516 // otherwise, both are alnum, they're just not equal, return the appropriate number
1518 return (*a < *b) ? -1 : 1;
1523 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
1532 if(!con_nickcompletion.integer)
1535 // changed that to 1
1536 if(!line[0])// || !line[1]) // we want at least... 2 written characters
1539 for(i = 0; i < cl.maxclients; ++i)
1541 /*p = Sbar_GetPlayer(i);
1545 if(!cl.scores[p].name[0])
1548 SanitizeString(cl.scores[p].name, name);
1549 //Con_Printf("Sanitized: %s^7 -> %s", cl.scores[p].name, name);
1554 length = strlen(name);
1556 spos = pos - 1; // no need for a minimum of characters :)
1558 while(spos >= 0 && (spos - pos) < length) // search-string-length < name length
1560 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
1562 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
1563 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
1569 if(isCon && spos == 0)
1571 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
1577 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
1578 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
1580 // the sanitized list
1581 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
1584 Nicks_matchpos = match;
1587 Nicks_offset[count] = s - (&line[match]);
1588 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
1595 void Cmd_CompleteNicksPrint(int count)
1598 for(i = 0; i < count; ++i)
1599 Con_Printf("%s\n", Nicks_list[i]);
1602 void Nicks_CutMatchesNormal(int count)
1604 // cut match 0 down to the longest possible completion
1607 c = strlen(Nicks_sanlist[0]) - 1;
1608 for(i = 1; i < count; ++i)
1610 l = strlen(Nicks_sanlist[i]) - 1;
1614 for(l = 0; l <= c; ++l)
1615 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
1621 Nicks_sanlist[0][c+1] = 0;
1622 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
1625 unsigned int Nicks_strcleanlen(const char *s)
1630 if( (*s >= 'a' && *s <= 'z') ||
1631 (*s >= 'A' && *s <= 'Z') ||
1632 (*s >= '0' && *s <= '9') ||
1640 void Nicks_CutMatchesAlphaNumeric(int count)
1642 // cut match 0 down to the longest possible completion
1645 char tempstr[sizeof(Nicks_sanlist[0])];
1647 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
1649 c = strlen(Nicks_sanlist[0]);
1650 for(i = 0, l = 0; i < (int)c; ++i)
1652 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
1653 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
1654 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
1656 tempstr[l++] = Nicks_sanlist[0][i];
1661 for(i = 1; i < count; ++i)
1664 b = Nicks_sanlist[i];
1674 if(tolower(*a) == tolower(*b))
1680 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
1682 // b is alnum, so cut
1689 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
1690 Nicks_CutMatchesNormal(count);
1691 //if(!Nicks_sanlist[0][0])
1692 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
1694 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
1695 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
1699 void Nicks_CutMatchesNoSpaces(int count)
1701 // cut match 0 down to the longest possible completion
1704 char tempstr[sizeof(Nicks_sanlist[0])];
1707 c = strlen(Nicks_sanlist[0]);
1708 for(i = 0, l = 0; i < (int)c; ++i)
1710 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
1712 tempstr[l++] = Nicks_sanlist[0][i];
1717 for(i = 1; i < count; ++i)
1720 b = Nicks_sanlist[i];
1730 if(tolower(*a) == tolower(*b))
1744 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
1745 Nicks_CutMatchesNormal(count);
1746 //if(!Nicks_sanlist[0][0])
1747 //Con_Printf("TS: %s\n", tempstr);
1748 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
1750 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
1751 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
1755 void Nicks_CutMatches(int count)
1757 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
1758 Nicks_CutMatchesAlphaNumeric(count);
1759 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
1760 Nicks_CutMatchesNoSpaces(count);
1762 Nicks_CutMatchesNormal(count);
1765 const char **Nicks_CompleteBuildList(int count)
1769 // the list is freed by Con_CompleteCommandLine, so create a char**
1770 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
1772 for(; bpos < count; ++bpos)
1773 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
1775 Nicks_CutMatches(count);
1781 int Nicks_AddLastColor(char *buffer, int pos)
1783 qboolean quote_added = false;
1787 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
1789 // we'll have to add a quote :)
1790 buffer[pos++] = '\"';
1794 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
1796 // add color when no quote was added, or when flags &4?
1798 for(match = Nicks_matchpos-1; match >= 0; --match)
1800 if(buffer[match] == STRING_COLOR_TAG && buffer[match+1] >= '0' && buffer[match+1] <= '9')
1802 color = buffer[match+1];
1806 if(!quote_added && buffer[pos-2] == STRING_COLOR_TAG && buffer[pos-1] >= '0' && buffer[pos-1] <= '9') // when thes use &4
1808 buffer[pos++] = STRING_COLOR_TAG;
1809 buffer[pos++] = color;
1814 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
1817 /*if(!con_nickcompletion.integer)
1818 return; is tested in Nicks_CompletionCountPossible */
1819 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
1825 msg = Nicks_list[0];
1826 len = min(size - Nicks_matchpos - 3, strlen(msg));
1827 memcpy(&buffer[Nicks_matchpos], msg, len);
1828 if( len < (size - 4) ) // space for color and space and \0
1829 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
1830 buffer[len++] = ' ';
1837 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
1838 Cmd_CompleteNicksPrint(n);
1840 Nicks_CutMatches(n);
1842 msg = Nicks_sanlist[0];
1843 len = min(size - Nicks_matchpos, strlen(msg));
1844 memcpy(&buffer[Nicks_matchpos], msg, len);
1845 buffer[Nicks_matchpos + len] = 0;
1847 return Nicks_matchpos + len;
1854 Con_CompleteCommandLine
1856 New function for tab-completion system
1857 Added by EvilTypeGuy
1858 Thanks to Fett erich@heintz.com
1860 Enhanced to tab-complete map names by [515]
1863 void Con_CompleteCommandLine (void)
1865 const char *cmd = "";
1867 const char **list[4] = {0, 0, 0, 0};
1869 int c, v, a, i, cmd_len, pos, k;
1870 int n; // nicks --blub
1872 //find what we want to complete
1876 k = key_lines[edit_line][pos];
1877 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
1882 s = key_lines[edit_line] + pos;
1883 strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2)); //save chars after cursor
1884 key_lines[edit_line][key_linepos] = 0; //hide them
1887 for(k=pos-1;k>2;k--)
1888 if(key_lines[edit_line][k] != ' ')
1890 if(key_lines[edit_line][k] == '\"' || key_lines[edit_line][k] == ';' || key_lines[edit_line][k] == '\'')
1892 if ((pos+k > 2 && !strncmp(key_lines[edit_line]+k-2, "map", 3))
1893 || (pos+k > 10 && !strncmp(key_lines[edit_line]+k-10, "changelevel", 11)))
1896 if (GetMapList(s, t, sizeof(t)))
1898 // first move the cursor
1899 key_linepos += (int)strlen(t) - (int)strlen(s);
1901 // and now do the actual work
1903 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
1904 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
1906 // and fix the cursor
1907 if(key_linepos > (int) strlen(key_lines[edit_line]))
1908 key_linepos = (int) strlen(key_lines[edit_line]);
1914 // Count number of possible matches and print them
1915 c = Cmd_CompleteCountPossible(s);
1918 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
1919 Cmd_CompleteCommandPrint(s);
1921 v = Cvar_CompleteCountPossible(s);
1924 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
1925 Cvar_CompleteCvarPrint(s);
1927 a = Cmd_CompleteAliasCountPossible(s);
1930 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
1931 Cmd_CompleteAliasPrint(s);
1933 n = Nicks_CompleteCountPossible(key_lines[edit_line], key_linepos, s, true);
1936 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
1937 Cmd_CompleteNicksPrint(n);
1940 if (!(c + v + a + n)) // No possible matches
1943 strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
1948 cmd = *(list[0] = Cmd_CompleteBuildList(s));
1950 cmd = *(list[1] = Cvar_CompleteBuildList(s));
1952 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
1954 cmd = *(list[3] = Nicks_CompleteBuildList(n));
1956 for (cmd_len = (int)strlen(s);;cmd_len++)
1959 for (i = 0; i < 3; i++)
1961 for (l = list[i];*l;l++)
1962 if ((*l)[cmd_len] != cmd[cmd_len])
1964 // all possible matches share this character, so we continue...
1967 // if all matches ended at the same position, stop
1968 // (this means there is only one match)
1974 // prevent a buffer overrun by limiting cmd_len according to remaining space
1975 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
1979 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
1980 key_linepos += cmd_len;
1981 // if there is only one match, add a space after it
1982 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
1985 { // was a nick, might have an offset, and needs colors ;) --blub
1986 key_linepos = pos - Nicks_offset[0];
1987 cmd_len = strlen(Nicks_list[0]);
1988 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 3 - pos);
1990 memcpy(&key_lines[edit_line][key_linepos] , Nicks_list[0], cmd_len);
1991 key_linepos += cmd_len;
1992 if(key_linepos < (int)(sizeof(key_lines[edit_line])-4)) // space for ^, X and space and \0
1993 key_linepos = Nicks_AddLastColor(key_lines[edit_line], key_linepos);
1995 key_lines[edit_line][key_linepos++] = ' ';
1999 // use strlcat to avoid a buffer overrun
2000 key_lines[edit_line][key_linepos] = 0;
2001 strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
2003 // free the command, cvar, and alias lists
2004 for (i = 0; i < 4; i++)
2006 Mem_Free((void *)list[i]);