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