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