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