641bc965c2e7cd0fe08baf3f9ee055eaf7bb898f
[xonotic/darkplaces.git] / console.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20 // console.c
21
22 #if !defined(WIN32) || defined(__MINGW32__)
23 # include <unistd.h>
24 #endif
25 #include <time.h>
26 #include "quakedef.h"
27
28 int con_linewidth;
29
30 float con_cursorspeed = 4;
31
32 #define         CON_TEXTSIZE    131072
33
34 // total lines in console scrollback
35 int con_totallines;
36 // lines up from bottom to display
37 int con_backscroll;
38 // where next message will be printed
39 int con_current;
40 // offset in current line for next print
41 int con_x;
42 char con_text[CON_TEXTSIZE];
43
44 //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 = 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 = 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                 for(i=1;i<t->numfilenames;i++)
1010                         if(*(t->filenames[i]+5+p) != k)
1011                                 goto endcomplete;
1012         }
1013 endcomplete:
1014         if(p > o)
1015         {
1016                 memset(completedname, 0, completednamebufferlength);
1017                 memcpy(completedname, (t->filenames[0]+5), p);
1018         }
1019         Z_Free(len);
1020         FS_FreeSearch(t);
1021         return p > o;
1022 }
1023
1024 /*
1025         Con_DisplayList
1026
1027         New function for tab-completion system
1028         Added by EvilTypeGuy
1029         MEGA Thanks to Taniwha
1030
1031 */
1032 void Con_DisplayList(const char **list)
1033 {
1034         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1035         const char **walk = list;
1036
1037         while (*walk) {
1038                 len = (int)strlen(*walk);
1039                 if (len > maxlen)
1040                         maxlen = len;
1041                 walk++;
1042         }
1043         maxlen += 1;
1044
1045         while (*list) {
1046                 len = (int)strlen(*list);
1047                 if (pos + maxlen >= width) {
1048                         Con_Print("\n");
1049                         pos = 0;
1050                 }
1051
1052                 Con_Print(*list);
1053                 for (i = 0; i < (maxlen - len); i++)
1054                         Con_Print(" ");
1055
1056                 pos += maxlen;
1057                 list++;
1058         }
1059
1060         if (pos)
1061                 Con_Print("\n\n");
1062 }
1063
1064 /*
1065         Con_CompleteCommandLine
1066
1067         New function for tab-completion system
1068         Added by EvilTypeGuy
1069         Thanks to Fett erich@heintz.com
1070         Thanks to taniwha
1071         Enhanced to tab-complete map names by [515]
1072
1073 */
1074 void Con_CompleteCommandLine (void)
1075 {
1076         const char *cmd = "", *s;
1077         const char **list[3] = {0, 0, 0};
1078         char s2[512];
1079         int c, v, a, i, cmd_len, pos, k;
1080
1081         //find what we want to complete
1082         pos = key_linepos;
1083         while(--pos)
1084         {
1085                 k = key_lines[edit_line][pos];
1086                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
1087                         break;
1088         }
1089         pos++;
1090
1091         s = key_lines[edit_line] + pos;
1092         strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2));    //save chars after cursor
1093         key_lines[edit_line][key_linepos] = 0;                                  //hide them
1094
1095         //maps search
1096         for(k=pos-1;k>2;k--)
1097                 if(key_lines[edit_line][k] != ' ')
1098                 {
1099                         if(key_lines[edit_line][k] == '\"' || key_lines[edit_line][k] == ';' || key_lines[edit_line][k] == '\'')
1100                                 break;
1101                         if      ((pos+k > 2 && !strncmp(key_lines[edit_line]+k-2, "map", 3))
1102                                 || (pos+k > 10 && !strncmp(key_lines[edit_line]+k-10, "changelevel", 11)))
1103                         {
1104                                 char t[MAX_QPATH];
1105                                 if (GetMapList(s, t, sizeof(t)))
1106                                 {
1107                                         i = strlen(t) - strlen(s);
1108                                         strcpy((char*)s, t);
1109                                         if(s2[0])       //add back chars after cursor
1110                                                 strcpy(&key_lines[edit_line][key_linepos], s2);
1111                                         key_linepos += i;
1112                                 }
1113                                 return;
1114                         }
1115                 }
1116
1117         // Count number of possible matches
1118         c = Cmd_CompleteCountPossible(s);
1119         v = Cvar_CompleteCountPossible(s);
1120         a = Cmd_CompleteAliasCountPossible(s);
1121
1122         if (!(c + v + a))       // No possible matches
1123         {
1124                 if(s2[0])
1125                         strcpy(&key_lines[edit_line][key_linepos], s2);
1126                 return;
1127         }
1128
1129         if (c + v + a == 1) {
1130                 if (c)
1131                         list[0] = Cmd_CompleteBuildList(s);
1132                 else if (v)
1133                         list[0] = Cvar_CompleteBuildList(s);
1134                 else
1135                         list[0] = Cmd_CompleteAliasBuildList(s);
1136                 cmd = *list[0];
1137                 cmd_len = (int)strlen (cmd);
1138         } else {
1139                 if (c)
1140                         cmd = *(list[0] = Cmd_CompleteBuildList(s));
1141                 if (v)
1142                         cmd = *(list[1] = Cvar_CompleteBuildList(s));
1143                 if (a)
1144                         cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
1145
1146                 cmd_len = (int)strlen (s);
1147                 do {
1148                         for (i = 0; i < 3; i++) {
1149                                 char ch = cmd[cmd_len];
1150                                 const char **l = list[i];
1151                                 if (l) {
1152                                         while (*l && (*l)[cmd_len] == ch)
1153                                                 l++;
1154                                         if (*l)
1155                                                 break;
1156                                 }
1157                         }
1158                         if (i == 3)
1159                                 cmd_len++;
1160                 } while (i == 3);
1161                 // 'quakebar'
1162                 Con_Print("\n\35");
1163                 for (i = 0; i < con_linewidth - 4; i++)
1164                         Con_Print("\36");
1165                 Con_Print("\37\n");
1166
1167                 // Print Possible Commands
1168                 if (c) {
1169                         Con_Printf("%i possible command%s\n", c, (c > 1) ? "s: " : ":");
1170                         Con_DisplayList(list[0]);
1171                 }
1172
1173                 if (v) {
1174                         Con_Printf("%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
1175                         Con_DisplayList(list[1]);
1176                 }
1177
1178                 if (a) {
1179                         Con_Printf("%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
1180                         Con_DisplayList(list[2]);
1181                 }
1182         }
1183
1184         if (cmd) {
1185                 strncpy(key_lines[edit_line] + pos, cmd, cmd_len);
1186                 key_linepos = cmd_len + pos;
1187                 if (c + v + a == 1) {
1188                         key_lines[edit_line][key_linepos] = ' ';
1189                         key_linepos++;
1190                 }
1191                 if(s2[0])
1192                         strcpy(&key_lines[edit_line][key_linepos], s2);
1193                 else
1194                         key_lines[edit_line][key_linepos] = 0;
1195         }
1196         else
1197                 if(s2[0])
1198                         strcpy(&key_lines[edit_line][key_linepos], s2);
1199         for (i = 0; i < 3; i++)
1200                 if (list[i])
1201                         Mem_Free((void *)list[i]);
1202 }
1203