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