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