renamed COM_ParseTokenConsole to COM_ParseToken_Console
[divverent/darkplaces.git] / console.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
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.
8
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.
12
13 See the GNU General Public License for more details.
14
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.
18
19 */
20 // console.c
21
22 #if !defined(WIN32) || defined(__MINGW32__)
23 # include <unistd.h>
24 #endif
25 #include <time.h>
26 #include "quakedef.h"
27
28 int con_linewidth;
29
30 float con_cursorspeed = 4;
31
32 #define         CON_TEXTSIZE    131072
33
34 // total lines in console scrollback
35 int con_totallines;
36 // lines up from bottom to display
37 int con_backscroll;
38 // where next message will be printed
39 int con_current;
40 // offset in current line for next print
41 int con_x;
42 char con_text[CON_TEXTSIZE];
43
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"};
47
48
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)"};
50 #ifdef WIN32
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)"};
52 #else
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)"};
54 #endif
55
56 #define MAX_NOTIFYLINES 32
57 // cl.time time the line was generated for transparent notify lines
58 float con_times[MAX_NOTIFYLINES];
59
60 int con_vislines;
61
62 qboolean con_initialized;
63
64 // used for server replies to rcon command
65 qboolean rcon_redirect = false;
66 int rcon_redirect_bufferpos = 0;
67 char rcon_redirect_buffer[1400];
68
69
70 /*
71 ==============================================================================
72
73 LOGGING
74
75 ==============================================================================
76 */
77
78 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
79 char crt_log_file [MAX_OSPATH] = "";
80 qfile_t* logfile = NULL;
81
82 unsigned char* logqueue = NULL;
83 size_t logq_ind = 0;
84 size_t logq_size = 0;
85
86 void Log_ConPrint (const char *msg);
87
88 /*
89 ====================
90 Log_Timestamp
91 ====================
92 */
93 const char* Log_Timestamp (const char *desc)
94 {
95         static char timestamp [128];
96         time_t crt_time;
97         const struct tm *crt_tm;
98         char timestring [64];
99
100         // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
101         time (&crt_time);
102         crt_tm = localtime (&crt_time);
103         strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
104
105         if (desc != NULL)
106                 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
107         else
108                 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
109
110         return timestamp;
111 }
112
113
114 /*
115 ====================
116 Log_Open
117 ====================
118 */
119 void Log_Open (void)
120 {
121         if (logfile != NULL || log_file.string[0] == '\0')
122                 return;
123
124         logfile = FS_Open (log_file.string, "ab", false, false);
125         if (logfile != NULL)
126         {
127                 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
128                 FS_Print (logfile, Log_Timestamp ("Log started"));
129         }
130 }
131
132
133 /*
134 ====================
135 Log_Close
136 ====================
137 */
138 void Log_Close (void)
139 {
140         if (logfile == NULL)
141                 return;
142
143         FS_Print (logfile, Log_Timestamp ("Log stopped"));
144         FS_Print (logfile, "\n");
145         FS_Close (logfile);
146
147         logfile = NULL;
148         crt_log_file[0] = '\0';
149 }
150
151
152 /*
153 ====================
154 Log_Start
155 ====================
156 */
157 void Log_Start (void)
158 {
159         Log_Open ();
160
161         // Dump the contents of the log queue into the log file and free it
162         if (logqueue != NULL)
163         {
164                 unsigned char *temp = logqueue;
165                 logqueue = NULL;
166                 if (logfile != NULL && logq_ind != 0)
167                         FS_Write (logfile, temp, logq_ind);
168                 Mem_Free (temp);
169                 logq_ind = 0;
170                 logq_size = 0;
171         }
172 }
173
174
175 /*
176 ================
177 Log_ConPrint
178 ================
179 */
180 void Log_ConPrint (const char *msg)
181 {
182         static qboolean inprogress = false;
183
184         // don't allow feedback loops with memory error reports
185         if (inprogress)
186                 return;
187         inprogress = true;
188
189         // Until the host is completely initialized, we maintain a log queue
190         // to store the messages, since the log can't be started before
191         if (logqueue != NULL)
192         {
193                 size_t remain = logq_size - logq_ind;
194                 size_t len = strlen (msg);
195
196                 // If we need to enlarge the log queue
197                 if (len > remain)
198                 {
199                         size_t factor = ((logq_ind + len) / logq_size) + 1;
200                         unsigned char* newqueue;
201
202                         logq_size *= factor;
203                         newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
204                         memcpy (newqueue, logqueue, logq_ind);
205                         Mem_Free (logqueue);
206                         logqueue = newqueue;
207                         remain = logq_size - logq_ind;
208                 }
209                 memcpy (&logqueue[logq_ind], msg, len);
210                 logq_ind += len;
211
212                 inprogress = false;
213                 return;
214         }
215
216         // Check if log_file has changed
217         if (strcmp (crt_log_file, log_file.string) != 0)
218         {
219                 Log_Close ();
220                 Log_Open ();
221         }
222
223         // If a log file is available
224         if (logfile != NULL)
225                 FS_Print (logfile, msg);
226         inprogress = false;
227 }
228
229
230 /*
231 ================
232 Log_Printf
233 ================
234 */
235 void Log_Printf (const char *logfilename, const char *fmt, ...)
236 {
237         qfile_t *file;
238
239         file = FS_Open (logfilename, "ab", true, false);
240         if (file != NULL)
241         {
242                 va_list argptr;
243
244                 va_start (argptr, fmt);
245                 FS_VPrintf (file, fmt, argptr);
246                 va_end (argptr);
247
248                 FS_Close (file);
249         }
250 }
251
252
253 /*
254 ==============================================================================
255
256 CONSOLE
257
258 ==============================================================================
259 */
260
261 /*
262 ================
263 Con_ToggleConsole_f
264 ================
265 */
266 void Con_ToggleConsole_f (void)
267 {
268         // toggle the 'user wants console' bit
269         key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
270         memset (con_times, 0, sizeof(con_times));
271 }
272
273 /*
274 ================
275 Con_Clear_f
276 ================
277 */
278 void Con_Clear_f (void)
279 {
280         if (con_text)
281                 memset (con_text, ' ', CON_TEXTSIZE);
282 }
283
284
285 /*
286 ================
287 Con_ClearNotify
288 ================
289 */
290 void Con_ClearNotify (void)
291 {
292         int i;
293
294         for (i=0 ; i<MAX_NOTIFYLINES ; i++)
295                 con_times[i] = 0;
296 }
297
298
299 /*
300 ================
301 Con_MessageMode_f
302 ================
303 */
304 void Con_MessageMode_f (void)
305 {
306         key_dest = key_message;
307         chat_team = false;
308 }
309
310
311 /*
312 ================
313 Con_MessageMode2_f
314 ================
315 */
316 void Con_MessageMode2_f (void)
317 {
318         key_dest = key_message;
319         chat_team = true;
320 }
321
322
323 /*
324 ================
325 Con_CheckResize
326
327 If the line width has changed, reformat the buffer.
328 ================
329 */
330 void Con_CheckResize (void)
331 {
332         int i, j, width, oldwidth, oldtotallines, numlines, numchars;
333         float f;
334         char tbuf[CON_TEXTSIZE];
335
336         f = bound(1, con_textsize.value, 128);
337         if(f != con_textsize.value)
338                 Cvar_SetValueQuick(&con_textsize, f);
339         width = (int)floor(vid_conwidth.value / con_textsize.value);
340         width = bound(1, width, CON_TEXTSIZE/4);
341
342         if (width == con_linewidth)
343                 return;
344
345         oldwidth = con_linewidth;
346         con_linewidth = width;
347         oldtotallines = con_totallines;
348         con_totallines = CON_TEXTSIZE / con_linewidth;
349         numlines = oldtotallines;
350
351         if (con_totallines < numlines)
352                 numlines = con_totallines;
353
354         numchars = oldwidth;
355
356         if (con_linewidth < numchars)
357                 numchars = con_linewidth;
358
359         memcpy (tbuf, con_text, CON_TEXTSIZE);
360         memset (con_text, ' ', CON_TEXTSIZE);
361
362         for (i=0 ; i<numlines ; i++)
363         {
364                 for (j=0 ; j<numchars ; j++)
365                 {
366                         con_text[(con_totallines - 1 - i) * con_linewidth + j] =
367                                         tbuf[((con_current - i + oldtotallines) %
368                                                   oldtotallines) * oldwidth + j];
369                 }
370         }
371
372         Con_ClearNotify ();
373
374         con_backscroll = 0;
375         con_current = con_totallines - 1;
376 }
377
378 //[515]: the simplest command ever
379 //LordHavoc: not so simple after I made it print usage...
380 static void Con_Maps_f (void)
381 {
382         if (Cmd_Argc() > 2)
383         {
384                 Con_Printf("usage: maps [mapnameprefix]\n");
385                 return;
386         }
387         else if (Cmd_Argc() == 2)
388                 GetMapList(Cmd_Argv(1), NULL, 0);
389         else
390                 GetMapList("", NULL, 0);
391 }
392
393 void Con_ConDump_f (void)
394 {
395         int i, l;
396         qboolean allblankssofar;
397         const char *text;
398         qfile_t *file;
399         char temp[MAX_INPUTLINE+2];
400         if (Cmd_Argc() != 2)
401         {
402                 Con_Printf("usage: condump <filename>\n");
403                 return;
404         }
405         file = FS_Open(Cmd_Argv(1), "wb", false, false);
406         if (!file)
407         {
408                 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
409                 return;
410         }
411         // iterate over the entire console history buffer line by line
412         allblankssofar = true;
413         for (i = 0;i < con_totallines;i++)
414         {
415                 text = con_text + ((con_current + 1 + i) % con_totallines)*con_linewidth;
416                 // count the used characters on this line
417                 for (l = min(con_linewidth, (int)sizeof(temp));l > 0 && text[l-1] == ' ';l--);
418                 // if not a blank line, begin output
419                 if (l)
420                         allblankssofar = false;
421                 // output the current line to the file
422                 if (!allblankssofar)
423                 {
424                         if (l)
425                                 memcpy(temp, text, l);
426                         temp[l] = '\n';
427                         temp[l+1] = 0;
428                         FS_Print(file, temp);
429                 }
430         }
431         FS_Close(file);
432 }
433
434 /*
435 ================
436 Con_Init
437 ================
438 */
439 void Con_Init (void)
440 {
441         memset (con_text, ' ', CON_TEXTSIZE);
442         con_linewidth = 80;
443         con_totallines = CON_TEXTSIZE / con_linewidth;
444
445         // Allocate a log queue, this will be freed after configs are parsed
446         logq_size = MAX_INPUTLINE;
447         logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
448         logq_ind = 0;
449
450         Cvar_RegisterVariable (&sys_colortranslation);
451         Cvar_RegisterVariable (&sys_specialcharactertranslation);
452
453         Cvar_RegisterVariable (&log_file);
454
455         // support for the classic Quake option
456 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
457         if (COM_CheckParm ("-condebug") != 0)
458                 Cvar_SetQuick (&log_file, "qconsole.log");
459
460         // register our cvars
461         Cvar_RegisterVariable (&con_notifytime);
462         Cvar_RegisterVariable (&con_notify);
463         Cvar_RegisterVariable (&con_textsize);
464
465         // register our commands
466         Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
467         Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
468         Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
469         Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
470         Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
471         Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
472
473         con_initialized = true;
474         Con_Print("Console initialized.\n");
475 }
476
477
478 /*
479 ===============
480 Con_Linefeed
481 ===============
482 */
483 void Con_Linefeed (void)
484 {
485         if (con_backscroll)
486                 con_backscroll++;
487
488         con_x = 0;
489         con_current++;
490         memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth);
491 }
492
493 /*
494 ================
495 Con_PrintToHistory
496
497 Handles cursor positioning, line wrapping, etc
498 All console printing must go through this in order to be displayed
499 If no console is visible, the notify window will pop up.
500 ================
501 */
502 void Con_PrintToHistory(const char *txt, int mask)
503 {
504         int y, c, l;
505         static int cr;
506
507         while ( (c = *txt) )
508         {
509         // count word length
510                 for (l=0 ; l< con_linewidth ; l++)
511                         if ( txt[l] <= ' ')
512                                 break;
513
514         // word wrap
515                 if (l != con_linewidth && (con_x + l > con_linewidth) )
516                         con_x = 0;
517
518                 txt++;
519
520                 if (cr)
521                 {
522                         con_current--;
523                         cr = false;
524                 }
525
526
527                 if (!con_x)
528                 {
529                         Con_Linefeed ();
530                 // mark time for transparent overlay
531                         if (con_current >= 0)
532                         {
533                                 if (con_notify.integer < 0)
534                                         Cvar_SetValueQuick(&con_notify, 0);
535                                 if (con_notify.integer > MAX_NOTIFYLINES)
536                                         Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
537                                 if (con_notify.integer > 0)
538                                         con_times[con_current % con_notify.integer] = cl.time;
539                         }
540                 }
541
542                 switch (c)
543                 {
544                 case '\n':
545                         con_x = 0;
546                         break;
547
548                 case '\r':
549                         con_x = 0;
550                         cr = 1;
551                         break;
552
553                 default:        // display character and advance
554                         y = con_current % con_totallines;
555                         con_text[y*con_linewidth+con_x] = c | mask;
556                         con_x++;
557                         if (con_x >= con_linewidth)
558                                 con_x = 0;
559                         break;
560                 }
561
562         }
563 }
564
565 /* The translation table between the graphical font and plain ASCII  --KB */
566 static char qfont_table[256] = {
567         '\0', '#',  '#',  '#',  '#',  '.',  '#',  '#',
568         '#',  9,    10,   '#',  ' ',  13,   '.',  '.',
569         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
570         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
571         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
572         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
573         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
574         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
575         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
576         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
577         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
578         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
579         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
580         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
581         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
582         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<',
583
584         '<',  '=',  '>',  '#',  '#',  '.',  '#',  '#',
585         '#',  '#',  ' ',  '#',  ' ',  '>',  '.',  '.',
586         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
587         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
588         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
589         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
590         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
591         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
592         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
593         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
594         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
595         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
596         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
597         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
598         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
599         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<'
600 };
601
602 /*
603 ================
604 Con_Print
605
606 Prints to all appropriate console targets, and adds timestamps
607 ================
608 */
609 extern cvar_t timestamps;
610 extern cvar_t timeformat;
611 extern qboolean sys_nostdout;
612 void Con_Print(const char *msg)
613 {
614         int mask = 0;
615         static int index = 0;
616         static char line[MAX_INPUTLINE];
617
618         for (;*msg;msg++)
619         {
620                 // if this print is in response to an rcon command, add the character
621                 // to the rcon redirect buffer
622                 if (rcon_redirect && rcon_redirect_bufferpos < (int)sizeof(rcon_redirect_buffer) - 1)
623                         rcon_redirect_buffer[rcon_redirect_bufferpos++] = *msg;
624                 // if this is the beginning of a new line, print timestamp
625                 if (index == 0)
626                 {
627                         const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
628                         // reset the color
629                         // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
630                         line[index++] = STRING_COLOR_TAG;
631                         // assert( STRING_COLOR_DEFAULT < 10 )
632                         line[index++] = STRING_COLOR_DEFAULT + '0';
633                         // special color codes for chat messages must always come first
634                         // for Con_PrintToHistory to work properly
635                         if (*msg == 1 || *msg == 2)
636                         {
637                                 // play talk wav
638                                 if (*msg == 1)
639                                 {
640                                         if (msg[1] == '(' && cl.foundtalk2wav)
641                                                 S_LocalSound ("sound/misc/talk2.wav");
642                                         else
643                                                 S_LocalSound ("sound/misc/talk.wav");
644                                 }
645                                 line[index++] = STRING_COLOR_TAG;
646                                 line[index++] = '3';
647                                 msg++;
648                         }
649                         // store timestamp
650                         for (;*timestamp;index++, timestamp++)
651                                 if (index < (int)sizeof(line) - 2)
652                                         line[index] = *timestamp;
653                 }
654                 // append the character
655                 line[index++] = *msg;
656                 // if this is a newline character, we have a complete line to print
657                 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
658                 {
659                         // terminate the line
660                         line[index] = 0;
661                         // send to log file
662                         Log_ConPrint(line);
663                         // send to scrollable buffer
664                         if (con_initialized && cls.state != ca_dedicated)
665                                 Con_PrintToHistory(line, mask);
666                         // send to terminal or dedicated server window
667                         if (!sys_nostdout)
668                         {
669                                 unsigned char *p;
670                                 if(sys_specialcharactertranslation.integer)
671                                 {
672                                         for (p = (unsigned char *) line;*p; p++)
673                                                 *p = qfont_table[*p];
674                                 }
675
676                                 if(sys_colortranslation.integer == 1) // ANSI
677                                 {
678                                         static char printline[MAX_INPUTLINE * 4 + 3];
679                                                 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
680                                                 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
681                                         int lastcolor = 0;
682                                         const char *in;
683                                         char *out;
684                                         for(in = line, out = printline; *in; ++in)
685                                         {
686                                                 switch(*in)
687                                                 {
688                                                         case STRING_COLOR_TAG:
689                                                                 switch(in[1])
690                                                                 {
691                                                                         case STRING_COLOR_TAG:
692                                                                                 ++in;
693                                                                                 *out++ = STRING_COLOR_TAG;
694                                                                                 break;
695                                                                         case '0':
696                                                                         case '7':
697                                                                                 // normal color
698                                                                                 ++in;
699                                                                                 if(lastcolor == 0) break; else lastcolor = 0;
700                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
701                                                                                 break;
702                                                                         case '1':
703                                                                                 // light red
704                                                                                 ++in;
705                                                                                 if(lastcolor == 1) break; else lastcolor = 1;
706                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
707                                                                                 break;
708                                                                         case '2':
709                                                                                 // light green
710                                                                                 ++in;
711                                                                                 if(lastcolor == 2) break; else lastcolor = 2;
712                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
713                                                                                 break;
714                                                                         case '3':
715                                                                                 // yellow
716                                                                                 ++in;
717                                                                                 if(lastcolor == 3) break; else lastcolor = 3;
718                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
719                                                                                 break;
720                                                                         case '4':
721                                                                                 // light blue
722                                                                                 ++in;
723                                                                                 if(lastcolor == 4) break; else lastcolor = 4;
724                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
725                                                                                 break;
726                                                                         case '5':
727                                                                                 // light cyan
728                                                                                 ++in;
729                                                                                 if(lastcolor == 5) break; else lastcolor = 5;
730                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
731                                                                                 break;
732                                                                         case '6':
733                                                                                 // light magenta
734                                                                                 ++in;
735                                                                                 if(lastcolor == 6) break; else lastcolor = 6;
736                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
737                                                                                 break;
738                                                                         // 7 handled above
739                                                                         case '8':
740                                                                         case '9':
741                                                                                 // bold normal color
742                                                                                 ++in;
743                                                                                 if(lastcolor == 8) break; else lastcolor = 8;
744                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
745                                                                                 break;
746                                                                         default:
747                                                                                 *out++ = STRING_COLOR_TAG;
748                                                                                 break;
749                                                                 }
750                                                                 break;
751                                                         case '\n':
752                                                                 if(lastcolor != 0)
753                                                                 {
754                                                                         *out++ = 0x1B; *out++ = '['; *out++ = 'm';
755                                                                         lastcolor = 0;
756                                                                 }
757                                                                 *out++ = *in;
758                                                                 break;
759                                                         default:
760                                                                 *out++ = *in;
761                                                                 break;
762                                                 }
763                                         }
764                                         if(lastcolor != 0)
765                                         {
766                                                 *out++ = 0x1B;
767                                                 *out++ = '[';
768                                                 *out++ = 'm';
769                                         }
770                                         *out++ = 0;
771                                         Sys_PrintToTerminal(printline);
772                                 }
773                                 else if(sys_colortranslation.integer == 2) // Quake
774                                 {
775                                         Sys_PrintToTerminal(line);
776                                 }
777                                 else // strip
778                                 {
779                                         static char printline[MAX_INPUTLINE]; // it can only get shorter here
780                                         const char *in;
781                                         char *out;
782                                         for(in = line, out = printline; *in; ++in)
783                                         {
784                                                 switch(*in)
785                                                 {
786                                                         case STRING_COLOR_TAG:
787                                                                 switch(in[1])
788                                                                 {
789                                                                         case STRING_COLOR_TAG:
790                                                                                 ++in;
791                                                                                 *out++ = STRING_COLOR_TAG;
792                                                                                 break;
793                                                                         case '0':
794                                                                         case '1':
795                                                                         case '2':
796                                                                         case '3':
797                                                                         case '4':
798                                                                         case '5':
799                                                                         case '6':
800                                                                         case '7':
801                                                                         case '8':
802                                                                         case '9':
803                                                                                 ++in;
804                                                                                 break;
805                                                                         default:
806                                                                                 *out++ = STRING_COLOR_TAG;
807                                                                                 break;
808                                                                 }
809                                                                 break;
810                                                         default:
811                                                                 *out++ = *in;
812                                                                 break;
813                                                 }
814                                         }
815                                         *out++ = 0;
816                                         Sys_PrintToTerminal(printline);
817                                 }
818                         }
819                         // empty the line buffer
820                         index = 0;
821                 }
822         }
823 }
824
825
826 /*
827 ================
828 Con_Printf
829
830 Prints to all appropriate console targets
831 ================
832 */
833 void Con_Printf(const char *fmt, ...)
834 {
835         va_list argptr;
836         char msg[MAX_INPUTLINE];
837
838         va_start(argptr,fmt);
839         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
840         va_end(argptr);
841
842         Con_Print(msg);
843 }
844
845 /*
846 ================
847 Con_DPrint
848
849 A Con_Print that only shows up if the "developer" cvar is set
850 ================
851 */
852 void Con_DPrint(const char *msg)
853 {
854         if (!developer.integer)
855                 return;                 // don't confuse non-developers with techie stuff...
856         Con_Print(msg);
857 }
858
859 /*
860 ================
861 Con_DPrintf
862
863 A Con_Printf that only shows up if the "developer" cvar is set
864 ================
865 */
866 void Con_DPrintf(const char *fmt, ...)
867 {
868         va_list argptr;
869         char msg[MAX_INPUTLINE];
870
871         if (!developer.integer)
872                 return;                 // don't confuse non-developers with techie stuff...
873
874         va_start(argptr,fmt);
875         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
876         va_end(argptr);
877
878         Con_Print(msg);
879 }
880
881
882 /*
883 ==============================================================================
884
885 DRAWING
886
887 ==============================================================================
888 */
889
890 /*
891 ================
892 Con_DrawInput
893
894 The input line scrolls horizontally if typing goes beyond the right edge
895
896 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
897 ================
898 */
899 void Con_DrawInput (void)
900 {
901         int             y;
902         int             i;
903         char editlinecopy[MAX_INPUTLINE+1], *text;
904
905         if (!key_consoleactive)
906                 return;         // don't draw anything
907
908         strlcpy(editlinecopy, key_lines[edit_line], sizeof(editlinecopy));
909         text = editlinecopy;
910
911         // Advanced Console Editing by Radix radix@planetquake.com
912         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
913         // use strlen of edit_line instead of key_linepos to allow editing
914         // of early characters w/o erasing
915
916         y = (int)strlen(text);
917
918 // fill out remainder with spaces
919         for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
920                 text[i] = ' ';
921
922         // add the cursor frame
923         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
924                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
925
926 //      text[key_linepos + 1] = 0;
927
928         // prestep if horizontally scrolling
929         if (key_linepos >= con_linewidth)
930                 text += 1 + key_linepos - con_linewidth;
931
932         // draw it
933         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 );
934
935         // remove cursor
936 //      key_lines[edit_line][key_linepos] = 0;
937 }
938
939
940 /*
941 ================
942 Con_DrawNotify
943
944 Draws the last few lines of output transparently over the game top
945 ================
946 */
947 void Con_DrawNotify (void)
948 {
949         float   x, v;
950         char    *text;
951         int             i, stop;
952         float   time;
953         char    temptext[MAX_INPUTLINE];
954         int colorindex = -1; //-1 for default
955
956         if (con_notify.integer < 0)
957                 Cvar_SetValueQuick(&con_notify, 0);
958         if (con_notify.integer > MAX_NOTIFYLINES)
959                 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
960         if (gamemode == GAME_TRANSFUSION)
961                 v = 8;
962         else
963                 v = 0;
964         // 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
965         stop = con_current;
966         for (i= stop-con_notify.integer+1 ; i<=stop ; i++)
967         {
968
969                 if (i < 0)
970                         continue;
971                 time = con_times[i % con_notify.integer];
972                 if (time == 0)
973                         continue;
974                 time = cl.time - time;
975                 if (time > con_notifytime.value)
976                         continue;
977                 text = con_text + (i % con_totallines)*con_linewidth;
978
979                 if (gamemode == GAME_NEXUIZ) {
980                         int chars = 0;
981                         int finalchars = 0;
982                         int j;
983
984                         // count up to the last non-whitespace, and ignore color codes
985                         for (j = 0;j < con_linewidth && text[j];j++)
986                         {
987                                 if (text[j] == STRING_COLOR_TAG && (text[j+1] >= '0' && text[j+1] <= '9'))
988                                 {
989                                         j++;
990                                         continue;
991                                 }
992                                 chars++;
993                                 if (text[j] == ' ')
994                                         continue;
995                                 finalchars = chars;
996                         }
997                         // center the line using the calculated width
998                         x = (vid_conwidth.integer - finalchars * con_textsize.value) * 0.5;
999                 } else
1000                         x = 0;
1001
1002                 DrawQ_String( x, v, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1003
1004                 v += con_textsize.value;
1005         }
1006
1007
1008         if (key_dest == key_message)
1009         {
1010                 int colorindex = -1;
1011
1012                 x = 0;
1013
1014                 // LordHavoc: speedup, and other improvements
1015                 if (chat_team)
1016                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1017                 else
1018                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1019                 while ((int)strlen(temptext) >= con_linewidth)
1020                 {
1021                         DrawQ_String( 0, v, temptext, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1022                         strlcpy(temptext, &temptext[con_linewidth], sizeof(temptext));
1023                         v += con_textsize.value;
1024                 }
1025                 if (strlen(temptext) > 0)
1026                 {
1027                         DrawQ_String( 0, v, temptext, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1028                         v += con_textsize.value;
1029                 }
1030         }
1031 }
1032
1033 /*
1034 ================
1035 Con_DrawConsole
1036
1037 Draws the console with the solid background
1038 The typing input line at the bottom should only be drawn if typing is allowed
1039 ================
1040 */
1041 void Con_DrawConsole (int lines)
1042 {
1043         int i, rows, j, stop;
1044         float y;
1045         char *text;
1046         int colorindex = -1;
1047
1048         if (lines <= 0)
1049                 return;
1050
1051 // draw the background
1052         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);
1053         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);
1054
1055 // draw the text
1056         con_vislines = lines;
1057
1058         rows = (int)ceil((lines/con_textsize.value)-2);         // rows of text to draw
1059         y = lines - (rows+2)*con_textsize.value;        // may start slightly negative
1060
1061         // 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
1062         stop = con_current;
1063         for (i = stop - rows + 1;i <= stop;i++, y += con_textsize.value)
1064         {
1065                 j = max(i - con_backscroll, 0);
1066                 text = con_text + (j % con_totallines)*con_linewidth;
1067
1068                 DrawQ_String( 0, y, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1069         }
1070
1071 // draw the input prompt, user text, and cursor if desired
1072         Con_DrawInput ();
1073 }
1074
1075 /*
1076 GetMapList
1077
1078 Made by [515]
1079 Prints not only map filename, but also
1080 its format (q1/q2/q3/hl) and even its message
1081 */
1082 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1083 //LordHavoc: rewrote bsp type detection, added mcbsp support and rewrote message extraction to do proper worldspawn parsing
1084 //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
1085 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1086 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1087 {
1088         fssearch_t      *t;
1089         char            message[64];
1090         int                     i, k, max, p, o, min;
1091         unsigned char *len;
1092         qfile_t         *f;
1093         unsigned char buf[1024];
1094
1095         sprintf(message, "maps/%s*.bsp", s);
1096         t = FS_Search(message, 1, true);
1097         if(!t)
1098                 return false;
1099         if (t->numfilenames > 1)
1100                 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1101         len = (unsigned char *)Z_Malloc(t->numfilenames);
1102         min = 666;
1103         for(max=i=0;i<t->numfilenames;i++)
1104         {
1105                 k = (int)strlen(t->filenames[i]);
1106                 k -= 9;
1107                 if(max < k)
1108                         max = k;
1109                 else
1110                 if(min > k)
1111                         min = k;
1112                 len[i] = k;
1113         }
1114         o = (int)strlen(s);
1115         for(i=0;i<t->numfilenames;i++)
1116         {
1117                 int lumpofs = 0, lumplen = 0;
1118                 char *entities = NULL;
1119                 const char *data = NULL;
1120                 char keyname[64];
1121                 char entfilename[MAX_QPATH];
1122                 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1123                 p = 0;
1124                 f = FS_Open(t->filenames[i], "rb", true, false);
1125                 if(f)
1126                 {
1127                         memset(buf, 0, 1024);
1128                         FS_Read(f, buf, 1024);
1129                         if (!memcmp(buf, "IBSP", 4))
1130                         {
1131                                 p = LittleLong(((int *)buf)[1]);
1132                                 if (p == Q3BSPVERSION)
1133                                 {
1134                                         q3dheader_t *header = (q3dheader_t *)buf;
1135                                         lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1136                                         lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1137                                 }
1138                                 else if (p == Q2BSPVERSION)
1139                                 {
1140                                         q2dheader_t *header = (q2dheader_t *)buf;
1141                                         lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1142                                         lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1143                                 }
1144                         }
1145                         else if (!memcmp(buf, "MCBSPpad", 8))
1146                         {
1147                                 p = LittleLong(((int *)buf)[2]);
1148                                 if (p == MCBSPVERSION)
1149                                 {
1150                                         int numhulls = LittleLong(((int *)buf)[3]);
1151                                         lumpofs = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+0]);
1152                                         lumplen = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+1]);
1153                                 }
1154                         }
1155                         else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1156                         {
1157                                 dheader_t *header = (dheader_t *)buf;
1158                                 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1159                                 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1160                         }
1161                         else
1162                                 p = 0;
1163                         strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1164                         memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1165                         entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1166                         if (!entities && lumplen >= 10)
1167                         {
1168                                 FS_Seek(f, lumpofs, SEEK_SET);
1169                                 entities = (char *)Z_Malloc(lumplen + 1);
1170                                 FS_Read(f, entities, lumplen);
1171                         }
1172                         if (entities)
1173                         {
1174                                 // if there are entities to parse, a missing message key just
1175                                 // means there is no title, so clear the message string now
1176                                 message[0] = 0;
1177                                 data = entities;
1178                                 for (;;)
1179                                 {
1180                                         int l;
1181                                         if (!COM_ParseToken_Simple(&data, false))
1182                                                 break;
1183                                         if (com_token[0] == '{')
1184                                                 continue;
1185                                         if (com_token[0] == '}')
1186                                                 break;
1187                                         // skip leading whitespace
1188                                         for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
1189                                         for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
1190                                                 keyname[l] = com_token[k+l];
1191                                         keyname[l] = 0;
1192                                         if (!COM_ParseToken_Simple(&data, false))
1193                                                 break;
1194                                         if (developer.integer >= 100)
1195                                                 Con_Printf("key: %s %s\n", keyname, com_token);
1196                                         if (!strcmp(keyname, "message"))
1197                                         {
1198                                                 // get the message contents
1199                                                 strlcpy(message, com_token, sizeof(message));
1200                                                 break;
1201                                         }
1202                                 }
1203                         }
1204                 }
1205                 if (entities)
1206                         Z_Free(entities);
1207                 if(f)
1208                         FS_Close(f);
1209                 *(t->filenames[i]+len[i]+5) = 0;
1210                 switch(p)
1211                 {
1212                 case Q3BSPVERSION:      strlcpy((char *)buf, "Q3", sizeof(buf));break;
1213                 case Q2BSPVERSION:      strlcpy((char *)buf, "Q2", sizeof(buf));break;
1214                 case BSPVERSION:        strlcpy((char *)buf, "Q1", sizeof(buf));break;
1215                 case MCBSPVERSION:      strlcpy((char *)buf, "MC", sizeof(buf));break;
1216                 case 30:                        strlcpy((char *)buf, "HL", sizeof(buf));break;
1217                 default:                        strlcpy((char *)buf, "??", sizeof(buf));break;
1218                 }
1219                 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1220         }
1221         Con_Print("\n");
1222         for(p=o;p<min;p++)
1223         {
1224                 k = *(t->filenames[0]+5+p);
1225                 if(k == 0)
1226                         goto endcomplete;
1227                 for(i=1;i<t->numfilenames;i++)
1228                         if(*(t->filenames[i]+5+p) != k)
1229                                 goto endcomplete;
1230         }
1231 endcomplete:
1232         if(p > o && completedname && completednamebufferlength > 0)
1233         {
1234                 memset(completedname, 0, completednamebufferlength);
1235                 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1236         }
1237         Z_Free(len);
1238         FS_FreeSearch(t);
1239         return p > o;
1240 }
1241
1242 /*
1243         Con_DisplayList
1244
1245         New function for tab-completion system
1246         Added by EvilTypeGuy
1247         MEGA Thanks to Taniwha
1248
1249 */
1250 void Con_DisplayList(const char **list)
1251 {
1252         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1253         const char **walk = list;
1254
1255         while (*walk) {
1256                 len = (int)strlen(*walk);
1257                 if (len > maxlen)
1258                         maxlen = len;
1259                 walk++;
1260         }
1261         maxlen += 1;
1262
1263         while (*list) {
1264                 len = (int)strlen(*list);
1265                 if (pos + maxlen >= width) {
1266                         Con_Print("\n");
1267                         pos = 0;
1268                 }
1269
1270                 Con_Print(*list);
1271                 for (i = 0; i < (maxlen - len); i++)
1272                         Con_Print(" ");
1273
1274                 pos += maxlen;
1275                 list++;
1276         }
1277
1278         if (pos)
1279                 Con_Print("\n\n");
1280 }
1281
1282 /*
1283         Con_CompleteCommandLine
1284
1285         New function for tab-completion system
1286         Added by EvilTypeGuy
1287         Thanks to Fett erich@heintz.com
1288         Thanks to taniwha
1289         Enhanced to tab-complete map names by [515]
1290
1291 */
1292 void Con_CompleteCommandLine (void)
1293 {
1294         const char *cmd = "";
1295         char *s;
1296         const char **list[3] = {0, 0, 0};
1297         char s2[512];
1298         int c, v, a, i, cmd_len, pos, k;
1299
1300         //find what we want to complete
1301         pos = key_linepos;
1302         while(--pos)
1303         {
1304                 k = key_lines[edit_line][pos];
1305                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
1306                         break;
1307         }
1308         pos++;
1309
1310         s = key_lines[edit_line] + pos;
1311         strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2));    //save chars after cursor
1312         key_lines[edit_line][key_linepos] = 0;                                  //hide them
1313
1314         //maps search
1315         for(k=pos-1;k>2;k--)
1316                 if(key_lines[edit_line][k] != ' ')
1317                 {
1318                         if(key_lines[edit_line][k] == '\"' || key_lines[edit_line][k] == ';' || key_lines[edit_line][k] == '\'')
1319                                 break;
1320                         if      ((pos+k > 2 && !strncmp(key_lines[edit_line]+k-2, "map", 3))
1321                                 || (pos+k > 10 && !strncmp(key_lines[edit_line]+k-10, "changelevel", 11)))
1322                         {
1323                                 char t[MAX_QPATH];
1324                                 if (GetMapList(s, t, sizeof(t)))
1325                                 {
1326                                         // first move the cursor
1327                                         key_linepos += (int)strlen(t) - (int)strlen(s);
1328
1329                                         // and now do the actual work
1330                                         *s = 0;
1331                                         strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
1332                                         strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
1333
1334                                         // and fix the cursor
1335                                         if(key_linepos > (int) strlen(key_lines[edit_line]))
1336                                                 key_linepos = (int) strlen(key_lines[edit_line]);
1337                                 }
1338                                 return;
1339                         }
1340                 }
1341
1342         // Count number of possible matches and print them
1343         c = Cmd_CompleteCountPossible(s);
1344         if (c)
1345         {
1346                 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
1347                 Cmd_CompleteCommandPrint(s);
1348         }
1349         v = Cvar_CompleteCountPossible(s);
1350         if (v)
1351         {
1352                 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
1353                 Cvar_CompleteCvarPrint(s);
1354         }
1355         a = Cmd_CompleteAliasCountPossible(s);
1356         if (a)
1357         {
1358                 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
1359                 Cmd_CompleteAliasPrint(s);
1360         }
1361
1362         if (!(c + v + a))       // No possible matches
1363         {
1364                 if(s2[0])
1365                         strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
1366                 return;
1367         }
1368
1369         if (c)
1370                 cmd = *(list[0] = Cmd_CompleteBuildList(s));
1371         if (v)
1372                 cmd = *(list[1] = Cvar_CompleteBuildList(s));
1373         if (a)
1374                 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
1375
1376         for (cmd_len = (int)strlen(s);;cmd_len++)
1377         {
1378                 const char **l;
1379                 for (i = 0; i < 3; i++)
1380                         if (list[i])
1381                                 for (l = list[i];*l;l++)
1382                                         if ((*l)[cmd_len] != cmd[cmd_len])
1383                                                 goto done;
1384                 // all possible matches share this character, so we continue...
1385                 if (!cmd[cmd_len])
1386                 {
1387                         // if all matches ended at the same position, stop
1388                         // (this means there is only one match)
1389                         break;
1390                 }
1391         }
1392 done:
1393
1394         // prevent a buffer overrun by limiting cmd_len according to remaining space
1395         cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
1396         if (cmd)
1397         {
1398                 key_linepos = pos;
1399                 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
1400                 key_linepos += cmd_len;
1401                 // if there is only one match, add a space after it
1402                 if (c + v + a == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
1403                         key_lines[edit_line][key_linepos++] = ' ';
1404         }
1405
1406         // use strlcat to avoid a buffer overrun
1407         key_lines[edit_line][key_linepos] = 0;
1408         strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
1409
1410         // free the command, cvar, and alias lists
1411         for (i = 0; i < 3; i++)
1412                 if (list[i])
1413                         Mem_Free((void *)list[i]);
1414 }
1415