]> icculus.org git repositories - divverent/darkplaces.git/blob - console.c
442
[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                                         S_LocalSound ("sound/misc/talk.wav");
640                                 line[index++] = STRING_COLOR_TAG;
641                                 line[index++] = '3';
642                                 msg++;
643                         }
644                         // store timestamp
645                         for (;*timestamp;index++, timestamp++)
646                                 if (index < (int)sizeof(line) - 2)
647                                         line[index] = *timestamp;
648                 }
649                 // append the character
650                 line[index++] = *msg;
651                 // if this is a newline character, we have a complete line to print
652                 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
653                 {
654                         // terminate the line
655                         line[index] = 0;
656                         // send to log file
657                         Log_ConPrint(line);
658                         // send to scrollable buffer
659                         if (con_initialized && cls.state != ca_dedicated)
660                                 Con_PrintToHistory(line, mask);
661                         // send to terminal or dedicated server window
662                         if (!sys_nostdout)
663                         {
664                                 unsigned char *p;
665                                 if(sys_specialcharactertranslation.integer)
666                                 {
667                                         for (p = (unsigned char *) line;*p; p++)
668                                                 *p = qfont_table[*p];
669                                 }
670
671                                 if(sys_colortranslation.integer == 1) // ANSI
672                                 {
673                                         static char printline[MAX_INPUTLINE * 4 + 3];
674                                                 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
675                                                 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
676                                         int lastcolor = 0;
677                                         const char *in;
678                                         char *out;
679                                         for(in = line, out = printline; *in; ++in)
680                                         {
681                                                 switch(*in)
682                                                 {
683                                                         case STRING_COLOR_TAG:
684                                                                 switch(in[1])
685                                                                 {
686                                                                         case STRING_COLOR_TAG:
687                                                                                 ++in;
688                                                                                 *out++ = STRING_COLOR_TAG;
689                                                                                 break;
690                                                                         case '0':
691                                                                         case '7':
692                                                                                 // normal color
693                                                                                 ++in;
694                                                                                 if(lastcolor == 0) break; else lastcolor = 0;
695                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
696                                                                                 break;
697                                                                         case '1':
698                                                                                 // light red
699                                                                                 ++in;
700                                                                                 if(lastcolor == 1) break; else lastcolor = 1;
701                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
702                                                                                 break;
703                                                                         case '2':
704                                                                                 // light green
705                                                                                 ++in;
706                                                                                 if(lastcolor == 2) break; else lastcolor = 2;
707                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
708                                                                                 break;
709                                                                         case '3':
710                                                                                 // yellow
711                                                                                 ++in;
712                                                                                 if(lastcolor == 3) break; else lastcolor = 3;
713                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
714                                                                                 break;
715                                                                         case '4':
716                                                                                 // light blue
717                                                                                 ++in;
718                                                                                 if(lastcolor == 4) break; else lastcolor = 4;
719                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
720                                                                                 break;
721                                                                         case '5':
722                                                                                 // light cyan
723                                                                                 ++in;
724                                                                                 if(lastcolor == 5) break; else lastcolor = 5;
725                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
726                                                                                 break;
727                                                                         case '6':
728                                                                                 // light magenta
729                                                                                 ++in;
730                                                                                 if(lastcolor == 6) break; else lastcolor = 6;
731                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
732                                                                                 break;
733                                                                         // 7 handled above
734                                                                         case '8':
735                                                                         case '9':
736                                                                                 // bold normal color
737                                                                                 ++in;
738                                                                                 if(lastcolor == 8) break; else lastcolor = 8;
739                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
740                                                                                 break;
741                                                                         default:
742                                                                                 *out++ = STRING_COLOR_TAG;
743                                                                                 break;
744                                                                 }
745                                                                 break;
746                                                         case '\n':
747                                                                 if(lastcolor != 0)
748                                                                 {
749                                                                         *out++ = 0x1B; *out++ = '['; *out++ = 'm';
750                                                                         lastcolor = 0;
751                                                                 }
752                                                                 *out++ = *in;
753                                                                 break;
754                                                         default:
755                                                                 *out++ = *in;
756                                                                 break;
757                                                 }
758                                         }
759                                         if(lastcolor != 0)
760                                         {
761                                                 *out++ = 0x1B;
762                                                 *out++ = '[';
763                                                 *out++ = 'm';
764                                         }
765                                         *out++ = 0;
766                                         Sys_PrintToTerminal(printline);
767                                 }
768                                 else if(sys_colortranslation.integer == 2) // Quake
769                                 {
770                                         Sys_PrintToTerminal(line);
771                                 }
772                                 else // strip
773                                 {
774                                         static char printline[MAX_INPUTLINE]; // it can only get shorter here
775                                         const char *in;
776                                         char *out;
777                                         for(in = line, out = printline; *in; ++in)
778                                         {
779                                                 switch(*in)
780                                                 {
781                                                         case STRING_COLOR_TAG:
782                                                                 switch(in[1])
783                                                                 {
784                                                                         case STRING_COLOR_TAG:
785                                                                                 ++in;
786                                                                                 *out++ = STRING_COLOR_TAG;
787                                                                                 break;
788                                                                         case '0':
789                                                                         case '1':
790                                                                         case '2':
791                                                                         case '3':
792                                                                         case '4':
793                                                                         case '5':
794                                                                         case '6':
795                                                                         case '7':
796                                                                         case '8':
797                                                                         case '9':
798                                                                                 ++in;
799                                                                                 break;
800                                                                         default:
801                                                                                 *out++ = STRING_COLOR_TAG;
802                                                                                 break;
803                                                                 }
804                                                                 break;
805                                                         default:
806                                                                 *out++ = *in;
807                                                                 break;
808                                                 }
809                                         }
810                                         *out++ = 0;
811                                         Sys_PrintToTerminal(printline);
812                                 }
813                         }
814                         // empty the line buffer
815                         index = 0;
816                 }
817         }
818 }
819
820
821 /*
822 ================
823 Con_Printf
824
825 Prints to all appropriate console targets
826 ================
827 */
828 void Con_Printf(const char *fmt, ...)
829 {
830         va_list argptr;
831         char msg[MAX_INPUTLINE];
832
833         va_start(argptr,fmt);
834         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
835         va_end(argptr);
836
837         Con_Print(msg);
838 }
839
840 /*
841 ================
842 Con_DPrint
843
844 A Con_Print that only shows up if the "developer" cvar is set
845 ================
846 */
847 void Con_DPrint(const char *msg)
848 {
849         if (!developer.integer)
850                 return;                 // don't confuse non-developers with techie stuff...
851         Con_Print(msg);
852 }
853
854 /*
855 ================
856 Con_DPrintf
857
858 A Con_Printf that only shows up if the "developer" cvar is set
859 ================
860 */
861 void Con_DPrintf(const char *fmt, ...)
862 {
863         va_list argptr;
864         char msg[MAX_INPUTLINE];
865
866         if (!developer.integer)
867                 return;                 // don't confuse non-developers with techie stuff...
868
869         va_start(argptr,fmt);
870         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
871         va_end(argptr);
872
873         Con_Print(msg);
874 }
875
876
877 /*
878 ==============================================================================
879
880 DRAWING
881
882 ==============================================================================
883 */
884
885 /*
886 ================
887 Con_DrawInput
888
889 The input line scrolls horizontally if typing goes beyond the right edge
890
891 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
892 ================
893 */
894 void Con_DrawInput (void)
895 {
896         int             y;
897         int             i;
898         char editlinecopy[MAX_INPUTLINE+1], *text;
899
900         if (!key_consoleactive)
901                 return;         // don't draw anything
902
903         strlcpy(editlinecopy, key_lines[edit_line], sizeof(editlinecopy));
904         text = editlinecopy;
905
906         // Advanced Console Editing by Radix radix@planetquake.com
907         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
908         // use strlen of edit_line instead of key_linepos to allow editing
909         // of early characters w/o erasing
910
911         y = (int)strlen(text);
912
913 // fill out remainder with spaces
914         for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
915                 text[i] = ' ';
916
917         // add the cursor frame
918         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
919                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
920
921 //      text[key_linepos + 1] = 0;
922
923         // prestep if horizontally scrolling
924         if (key_linepos >= con_linewidth)
925                 text += 1 + key_linepos - con_linewidth;
926
927         // draw it
928         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 );
929
930         // remove cursor
931 //      key_lines[edit_line][key_linepos] = 0;
932 }
933
934
935 /*
936 ================
937 Con_DrawNotify
938
939 Draws the last few lines of output transparently over the game top
940 ================
941 */
942 void Con_DrawNotify (void)
943 {
944         float   x, v;
945         char    *text;
946         int             i, stop;
947         float   time;
948         char    temptext[MAX_INPUTLINE];
949         int colorindex = -1; //-1 for default
950
951         if (con_notify.integer < 0)
952                 Cvar_SetValueQuick(&con_notify, 0);
953         if (con_notify.integer > MAX_NOTIFYLINES)
954                 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
955         if (gamemode == GAME_TRANSFUSION)
956                 v = 8;
957         else
958                 v = 0;
959         // 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
960         stop = con_current;
961         for (i= stop-con_notify.integer+1 ; i<=stop ; i++)
962         {
963
964                 if (i < 0)
965                         continue;
966                 time = con_times[i % con_notify.integer];
967                 if (time == 0)
968                         continue;
969                 time = cl.time - time;
970                 if (time > con_notifytime.value)
971                         continue;
972                 text = con_text + (i % con_totallines)*con_linewidth;
973
974                 if (gamemode == GAME_NEXUIZ) {
975                         int chars = 0;
976                         int finalchars = 0;
977                         int j;
978
979                         // count up to the last non-whitespace, and ignore color codes
980                         for (j = 0;j < con_linewidth && text[j];j++)
981                         {
982                                 if (text[j] == STRING_COLOR_TAG && (text[j+1] >= '0' && text[j+1] <= '9'))
983                                 {
984                                         j++;
985                                         continue;
986                                 }
987                                 chars++;
988                                 if (text[j] == ' ')
989                                         continue;
990                                 finalchars = chars;
991                         }
992                         // center the line using the calculated width
993                         x = (vid_conwidth.integer - finalchars * con_textsize.value) * 0.5;
994                 } else
995                         x = 0;
996
997                 DrawQ_ColoredString( x, v, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
998
999                 v += con_textsize.value;
1000         }
1001
1002
1003         if (key_dest == key_message)
1004         {
1005                 int colorindex = -1;
1006
1007                 x = 0;
1008
1009                 // LordHavoc: speedup, and other improvements
1010                 if (chat_team)
1011                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1012                 else
1013                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1014                 while ((int)strlen(temptext) >= con_linewidth)
1015                 {
1016                         DrawQ_ColoredString( 0, v, temptext, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
1017                         strlcpy(temptext, &temptext[con_linewidth], sizeof(temptext));
1018                         v += con_textsize.value;
1019                 }
1020                 if (strlen(temptext) > 0)
1021                 {
1022                         DrawQ_ColoredString( 0, v, temptext, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
1023                         v += con_textsize.value;
1024                 }
1025         }
1026 }
1027
1028 /*
1029 ================
1030 Con_DrawConsole
1031
1032 Draws the console with the solid background
1033 The typing input line at the bottom should only be drawn if typing is allowed
1034 ================
1035 */
1036 void Con_DrawConsole (int lines)
1037 {
1038         int i, rows, j, stop;
1039         float y;
1040         char *text;
1041         int colorindex = -1;
1042
1043         if (lines <= 0)
1044                 return;
1045
1046 // draw the background
1047         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);
1048         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);
1049
1050 // draw the text
1051         con_vislines = lines;
1052
1053         rows = (int)ceil((lines/con_textsize.value)-2);         // rows of text to draw
1054         y = lines - (rows+2)*con_textsize.value;        // may start slightly negative
1055
1056         // 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
1057         stop = con_current;
1058         for (i = stop - rows + 1;i <= stop;i++, y += con_textsize.value)
1059         {
1060                 j = max(i - con_backscroll, 0);
1061                 text = con_text + (j % con_totallines)*con_linewidth;
1062
1063                 DrawQ_ColoredString( 0, y, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
1064         }
1065
1066 // draw the input prompt, user text, and cursor if desired
1067         Con_DrawInput ();
1068 }
1069
1070 /*
1071 GetMapList
1072
1073 Made by [515]
1074 Prints not only map filename, but also
1075 its format (q1/q2/q3/hl) and even its message
1076 */
1077 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1078 //LordHavoc: rewrote bsp type detection, added mcbsp support and rewrote message extraction to do proper worldspawn parsing
1079 //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
1080 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1081 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1082 {
1083         fssearch_t      *t;
1084         char            message[64];
1085         int                     i, k, max, p, o, min;
1086         unsigned char *len;
1087         qfile_t         *f;
1088         unsigned char buf[1024];
1089
1090         sprintf(message, "maps/%s*.bsp", s);
1091         t = FS_Search(message, 1, true);
1092         if(!t)
1093                 return false;
1094         if (t->numfilenames > 1)
1095                 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1096         len = (unsigned char *)Z_Malloc(t->numfilenames);
1097         min = 666;
1098         for(max=i=0;i<t->numfilenames;i++)
1099         {
1100                 k = (int)strlen(t->filenames[i]);
1101                 k -= 9;
1102                 if(max < k)
1103                         max = k;
1104                 else
1105                 if(min > k)
1106                         min = k;
1107                 len[i] = k;
1108         }
1109         o = (int)strlen(s);
1110         for(i=0;i<t->numfilenames;i++)
1111         {
1112                 int lumpofs = 0, lumplen = 0;
1113                 char *entities = NULL;
1114                 const char *data = NULL;
1115                 char keyname[64];
1116                 char entfilename[MAX_QPATH];
1117                 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1118                 p = 0;
1119                 f = FS_Open(t->filenames[i], "rb", true, false);
1120                 if(f)
1121                 {
1122                         memset(buf, 0, 1024);
1123                         FS_Read(f, buf, 1024);
1124                         if (!memcmp(buf, "IBSP", 4))
1125                         {
1126                                 p = LittleLong(((int *)buf)[1]);
1127                                 if (p == Q3BSPVERSION)
1128                                 {
1129                                         q3dheader_t *header = (q3dheader_t *)buf;
1130                                         lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1131                                         lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1132                                 }
1133                                 else if (p == Q2BSPVERSION)
1134                                 {
1135                                         q2dheader_t *header = (q2dheader_t *)buf;
1136                                         lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1137                                         lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1138                                 }
1139                         }
1140                         else if (!memcmp(buf, "MCBSPpad", 8))
1141                         {
1142                                 p = LittleLong(((int *)buf)[2]);
1143                                 if (p == MCBSPVERSION)
1144                                 {
1145                                         int numhulls = LittleLong(((int *)buf)[3]);
1146                                         lumpofs = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+0]);
1147                                         lumplen = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+1]);
1148                                 }
1149                         }
1150                         else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1151                         {
1152                                 dheader_t *header = (dheader_t *)buf;
1153                                 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1154                                 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1155                         }
1156                         else
1157                                 p = 0;
1158                         strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1159                         memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1160                         entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1161                         if (!entities && lumplen >= 10)
1162                         {
1163                                 FS_Seek(f, lumpofs, SEEK_SET);
1164                                 entities = (char *)Z_Malloc(lumplen + 1);
1165                                 FS_Read(f, entities, lumplen);
1166                         }
1167                         if (entities)
1168                         {
1169                                 // if there are entities to parse, a missing message key just
1170                                 // means there is no title, so clear the message string now
1171                                 message[0] = 0;
1172                                 data = entities;
1173                                 for (;;)
1174                                 {
1175                                         int l;
1176                                         if (!COM_ParseTokenConsole(&data))
1177                                                 break;
1178                                         if (com_token[0] == '{')
1179                                                 continue;
1180                                         if (com_token[0] == '}')
1181                                                 break;
1182                                         // skip leading whitespace
1183                                         for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
1184                                         for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
1185                                                 keyname[l] = com_token[k+l];
1186                                         keyname[l] = 0;
1187                                         if (!COM_ParseTokenConsole(&data))
1188                                                 break;
1189                                         if (developer.integer >= 100)
1190                                                 Con_Printf("key: %s %s\n", keyname, com_token);
1191                                         if (!strcmp(keyname, "message"))
1192                                         {
1193                                                 // get the message contents
1194                                                 strlcpy(message, com_token, sizeof(message));
1195                                                 break;
1196                                         }
1197                                 }
1198                         }
1199                 }
1200                 if (entities)
1201                         Z_Free(entities);
1202                 if(f)
1203                         FS_Close(f);
1204                 *(t->filenames[i]+len[i]+5) = 0;
1205                 switch(p)
1206                 {
1207                 case Q3BSPVERSION:      strlcpy((char *)buf, "Q3", sizeof(buf));break;
1208                 case Q2BSPVERSION:      strlcpy((char *)buf, "Q2", sizeof(buf));break;
1209                 case BSPVERSION:        strlcpy((char *)buf, "Q1", sizeof(buf));break;
1210                 case MCBSPVERSION:      strlcpy((char *)buf, "MC", sizeof(buf));break;
1211                 case 30:                        strlcpy((char *)buf, "HL", sizeof(buf));break;
1212                 default:                        strlcpy((char *)buf, "??", sizeof(buf));break;
1213                 }
1214                 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1215         }
1216         Con_Print("\n");
1217         for(p=o;p<min;p++)
1218         {
1219                 k = *(t->filenames[0]+5+p);
1220                 if(k == 0)
1221                         goto endcomplete;
1222                 for(i=1;i<t->numfilenames;i++)
1223                         if(*(t->filenames[i]+5+p) != k)
1224                                 goto endcomplete;
1225         }
1226 endcomplete:
1227         if(p > o)
1228         {
1229                 memset(completedname, 0, completednamebufferlength);
1230                 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1231         }
1232         Z_Free(len);
1233         FS_FreeSearch(t);
1234         return p > o;
1235 }
1236
1237 /*
1238         Con_DisplayList
1239
1240         New function for tab-completion system
1241         Added by EvilTypeGuy
1242         MEGA Thanks to Taniwha
1243
1244 */
1245 void Con_DisplayList(const char **list)
1246 {
1247         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1248         const char **walk = list;
1249
1250         while (*walk) {
1251                 len = (int)strlen(*walk);
1252                 if (len > maxlen)
1253                         maxlen = len;
1254                 walk++;
1255         }
1256         maxlen += 1;
1257
1258         while (*list) {
1259                 len = (int)strlen(*list);
1260                 if (pos + maxlen >= width) {
1261                         Con_Print("\n");
1262                         pos = 0;
1263                 }
1264
1265                 Con_Print(*list);
1266                 for (i = 0; i < (maxlen - len); i++)
1267                         Con_Print(" ");
1268
1269                 pos += maxlen;
1270                 list++;
1271         }
1272
1273         if (pos)
1274                 Con_Print("\n\n");
1275 }
1276
1277 /*
1278         Con_CompleteCommandLine
1279
1280         New function for tab-completion system
1281         Added by EvilTypeGuy
1282         Thanks to Fett erich@heintz.com
1283         Thanks to taniwha
1284         Enhanced to tab-complete map names by [515]
1285
1286 */
1287 void Con_CompleteCommandLine (void)
1288 {
1289         const char *cmd = "";
1290         char *s;
1291         const char **list[3] = {0, 0, 0};
1292         char s2[512];
1293         int c, v, a, i, cmd_len, pos, k;
1294
1295         //find what we want to complete
1296         pos = key_linepos;
1297         while(--pos)
1298         {
1299                 k = key_lines[edit_line][pos];
1300                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
1301                         break;
1302         }
1303         pos++;
1304
1305         s = key_lines[edit_line] + pos;
1306         strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2));    //save chars after cursor
1307         key_lines[edit_line][key_linepos] = 0;                                  //hide them
1308
1309         //maps search
1310         for(k=pos-1;k>2;k--)
1311                 if(key_lines[edit_line][k] != ' ')
1312                 {
1313                         if(key_lines[edit_line][k] == '\"' || key_lines[edit_line][k] == ';' || key_lines[edit_line][k] == '\'')
1314                                 break;
1315                         if      ((pos+k > 2 && !strncmp(key_lines[edit_line]+k-2, "map", 3))
1316                                 || (pos+k > 10 && !strncmp(key_lines[edit_line]+k-10, "changelevel", 11)))
1317                         {
1318                                 char t[MAX_QPATH];
1319                                 if (GetMapList(s, t, sizeof(t)))
1320                                 {
1321                                         // first move the cursor
1322                                         key_linepos += (int)strlen(t) - (int)strlen(s);
1323
1324                                         // and now do the actual work
1325                                         *s = 0;
1326                                         strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
1327                                         strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
1328
1329                                         // and fix the cursor
1330                                         if(key_linepos > (int) strlen(key_lines[edit_line]))
1331                                                 key_linepos = (int) strlen(key_lines[edit_line]);
1332                                 }
1333                                 return;
1334                         }
1335                 }
1336
1337         // Count number of possible matches and print them
1338         c = Cmd_CompleteCountPossible(s);
1339         if (c)
1340         {
1341                 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
1342                 Cmd_CompleteCommandPrint(s);
1343         }
1344         v = Cvar_CompleteCountPossible(s);
1345         if (v)
1346         {
1347                 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
1348                 Cvar_CompleteCvarPrint(s);
1349         }
1350         a = Cmd_CompleteAliasCountPossible(s);
1351         if (a)
1352         {
1353                 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
1354                 Cmd_CompleteAliasPrint(s);
1355         }
1356
1357         if (!(c + v + a))       // No possible matches
1358         {
1359                 if(s2[0])
1360                         strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
1361                 return;
1362         }
1363
1364         if (c)
1365                 cmd = *(list[0] = Cmd_CompleteBuildList(s));
1366         if (v)
1367                 cmd = *(list[1] = Cvar_CompleteBuildList(s));
1368         if (a)
1369                 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
1370
1371         for (cmd_len = (int)strlen(s);;cmd_len++)
1372         {
1373                 const char **l;
1374                 for (i = 0; i < 3; i++)
1375                         if (list[i])
1376                                 for (l = list[i];*l;l++)
1377                                         if ((*l)[cmd_len] != cmd[cmd_len])
1378                                                 goto done;
1379                 // all possible matches share this character, so we continue...
1380                 if (!cmd[cmd_len])
1381                 {
1382                         // if all matches ended at the same position, stop
1383                         // (this means there is only one match)
1384                         break;
1385                 }
1386         }
1387 done:
1388
1389         // prevent a buffer overrun by limiting cmd_len according to remaining space
1390         cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
1391         if (cmd)
1392         {
1393                 key_linepos = pos;
1394                 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
1395                 key_linepos += cmd_len;
1396                 // if there is only one match, add a space after it
1397                 if (c + v + a == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
1398                         key_lines[edit_line][key_linepos++] = ' ';
1399         }
1400
1401         // use strlcat to avoid a buffer overrun
1402         key_lines[edit_line][key_linepos] = 0;
1403         strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
1404
1405         // free the command, cvar, and alias lists
1406         for (i = 0; i < 3; i++)
1407                 if (list[i])
1408                         Mem_Free((void *)list[i]);
1409 }
1410