]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - console.c
0ba430c3e42ecf432b5e43571d727f1a81671982
[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                 f = FS_Open(t->filenames[i], "rb", true, false);
908                 if(f)
909                 {
910                         memset(buf, 0, 1024);
911                         FS_Read(f, buf, 1024);
912                         if (!memcmp(buf, "IBSP", 4))
913                         {
914                                 p = LittleLong(((int *)buf)[1]);
915                                 if (p == Q3BSPVERSION)
916                                 {
917                                         q3dheader_t *header = (q3dheader_t *)buf;
918                                         lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
919                                         lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
920                                 }
921                                 else if (p == Q2BSPVERSION)
922                                 {
923                                         q2dheader_t *header = (q2dheader_t *)buf;
924                                         lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
925                                         lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
926                                 }
927                         }
928                         else if (!memcmp(buf, "MCBSPpad", 8))
929                         {
930                                 p = LittleLong(((int *)buf)[2]);
931                                 if (p == MCBSPVERSION)
932                                 {
933                                         int numhulls = LittleLong(((int *)buf)[3]);
934                                         lumpofs = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+0]);
935                                         lumplen = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+1]);
936                                 }
937                         }
938                         else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
939                         {
940                                 dheader_t *header = (dheader_t *)buf;
941                                 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
942                                 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
943                         }
944                         else
945                                 p = 0;
946                         strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
947                         strcpy(entfilename + strlen(entfilename) - 4, ".ent");
948                         entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
949                         if (!entities && lumplen >= 10)
950                         {
951                                 FS_Seek(f, lumpofs, SEEK_SET);
952                                 entities = Z_Malloc(lumplen + 1);
953                                 FS_Read(f, entities, lumplen);
954                         }
955                         if (entities)
956                         {
957                                 // if there are entities to parse, a missing message key just
958                                 // means there is no title, so clear the message string now
959                                 message[0] = 0;
960                                 data = entities;
961                                 for (;;)
962                                 {
963                                         int l;
964                                         if (!COM_ParseToken(&data, false))
965                                                 break;
966                                         if (com_token[0] == '{')
967                                                 continue;
968                                         if (com_token[0] == '}')
969                                                 break;
970                                         // skip leading whitespace
971                                         for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
972                                         for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
973                                                 keyname[l] = com_token[k+l];
974                                         keyname[l] = 0;
975                                         if (!COM_ParseToken(&data, false))
976                                                 break;
977                                         if (developer.integer >= 2)
978                                                 Con_Printf("key: %s %s\n", keyname, com_token);
979                                         if (!strcmp(keyname, "message"))
980                                         {
981                                                 // get the message contents
982                                                 strlcpy(message, com_token, sizeof(message));
983                                                 break;
984                                         }
985                                 }
986                         }
987                 }
988                 if (entities)
989                         Z_Free(entities);
990                 if(f)
991                         FS_Close(f);
992                 *(t->filenames[i]+len[i]+5) = 0;
993                 switch(p)
994                 {
995                 case Q3BSPVERSION:      strcpy((char *)buf, "Q3");break;
996                 case Q2BSPVERSION:      strcpy((char *)buf, "Q2");break;
997                 case BSPVERSION:        strcpy((char *)buf, "Q1");break;
998                 case MCBSPVERSION:      strcpy((char *)buf, "MC");break;
999                 case 30:                        strcpy((char *)buf, "HL");break;
1000                 default:                        strcpy((char *)buf, "??");break;
1001                 }
1002                 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1003         }
1004         Con_Print("\n");
1005         for(p=o;p<min;p++)
1006         {
1007                 k = *(t->filenames[0]+5+p);
1008                 for(i=1;i<t->numfilenames;i++)
1009                         if(*(t->filenames[i]+5+p) != k)
1010                                 goto endcomplete;
1011         }
1012 endcomplete:
1013         if(p > o)
1014         {
1015                 memset(completedname, 0, completednamebufferlength);
1016                 memcpy(completedname, (t->filenames[0]+5), p);
1017         }
1018         Z_Free(len);
1019         FS_FreeSearch(t);
1020         return p > o;
1021 }
1022
1023 /*
1024         Con_DisplayList
1025
1026         New function for tab-completion system
1027         Added by EvilTypeGuy
1028         MEGA Thanks to Taniwha
1029
1030 */
1031 void Con_DisplayList(const char **list)
1032 {
1033         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1034         const char **walk = list;
1035
1036         while (*walk) {
1037                 len = (int)strlen(*walk);
1038                 if (len > maxlen)
1039                         maxlen = len;
1040                 walk++;
1041         }
1042         maxlen += 1;
1043
1044         while (*list) {
1045                 len = (int)strlen(*list);
1046                 if (pos + maxlen >= width) {
1047                         Con_Print("\n");
1048                         pos = 0;
1049                 }
1050
1051                 Con_Print(*list);
1052                 for (i = 0; i < (maxlen - len); i++)
1053                         Con_Print(" ");
1054
1055                 pos += maxlen;
1056                 list++;
1057         }
1058
1059         if (pos)
1060                 Con_Print("\n\n");
1061 }
1062
1063 /*
1064         Con_CompleteCommandLine
1065
1066         New function for tab-completion system
1067         Added by EvilTypeGuy
1068         Thanks to Fett erich@heintz.com
1069         Thanks to taniwha
1070         Enhanced to tab-complete map names by [515]
1071
1072 */
1073 void Con_CompleteCommandLine (void)
1074 {
1075         const char *cmd = "", *s;
1076         const char **list[3] = {0, 0, 0};
1077         char s2[512];
1078         int c, v, a, i, cmd_len, pos, k;
1079
1080         //find what we want to complete
1081         pos = key_linepos;
1082         while(--pos)
1083         {
1084                 k = key_lines[edit_line][pos];
1085                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
1086                         break;
1087         }
1088         pos++;
1089
1090         s = key_lines[edit_line] + pos;
1091         strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2));    //save chars after cursor
1092         key_lines[edit_line][key_linepos] = 0;                                  //hide them
1093
1094         //maps search
1095         for(k=pos-1;k>2;k--)
1096                 if(key_lines[edit_line][k] != ' ')
1097                 {
1098                         if(key_lines[edit_line][k] == '\"' || key_lines[edit_line][k] == ';' || key_lines[edit_line][k] == '\'')
1099                                 break;
1100                         if      ((pos+k > 2 && !strncmp(key_lines[edit_line]+k-2, "map", 3))
1101                                 || (pos+k > 10 && !strncmp(key_lines[edit_line]+k-10, "changelevel", 11)))
1102                         {
1103                                 char t[MAX_QPATH];
1104                                 if (GetMapList(s, t, sizeof(t)))
1105                                 {
1106                                         i = strlen(t) - strlen(s);
1107                                         strcpy((char*)s, t);
1108                                         if(s2[0])       //add back chars after cursor
1109                                                 strcpy(&key_lines[edit_line][key_linepos], s2);
1110                                         key_linepos += i;
1111                                 }
1112                                 return;
1113                         }
1114                 }
1115
1116         // Count number of possible matches
1117         c = Cmd_CompleteCountPossible(s);
1118         v = Cvar_CompleteCountPossible(s);
1119         a = Cmd_CompleteAliasCountPossible(s);
1120
1121         if (!(c + v + a))       // No possible matches
1122         {
1123                 if(s2[0])
1124                         strcpy(&key_lines[edit_line][key_linepos], s2);
1125                 return;
1126         }
1127
1128         if (c + v + a == 1) {
1129                 if (c)
1130                         list[0] = Cmd_CompleteBuildList(s);
1131                 else if (v)
1132                         list[0] = Cvar_CompleteBuildList(s);
1133                 else
1134                         list[0] = Cmd_CompleteAliasBuildList(s);
1135                 cmd = *list[0];
1136                 cmd_len = (int)strlen (cmd);
1137         } else {
1138                 if (c)
1139                         cmd = *(list[0] = Cmd_CompleteBuildList(s));
1140                 if (v)
1141                         cmd = *(list[1] = Cvar_CompleteBuildList(s));
1142                 if (a)
1143                         cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
1144
1145                 cmd_len = (int)strlen (s);
1146                 do {
1147                         for (i = 0; i < 3; i++) {
1148                                 char ch = cmd[cmd_len];
1149                                 const char **l = list[i];
1150                                 if (l) {
1151                                         while (*l && (*l)[cmd_len] == ch)
1152                                                 l++;
1153                                         if (*l)
1154                                                 break;
1155                                 }
1156                         }
1157                         if (i == 3)
1158                                 cmd_len++;
1159                 } while (i == 3);
1160                 // 'quakebar'
1161                 Con_Print("\n\35");
1162                 for (i = 0; i < con_linewidth - 4; i++)
1163                         Con_Print("\36");
1164                 Con_Print("\37\n");
1165
1166                 // Print Possible Commands
1167                 if (c) {
1168                         Con_Printf("%i possible command%s\n", c, (c > 1) ? "s: " : ":");
1169                         Con_DisplayList(list[0]);
1170                 }
1171
1172                 if (v) {
1173                         Con_Printf("%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
1174                         Con_DisplayList(list[1]);
1175                 }
1176
1177                 if (a) {
1178                         Con_Printf("%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
1179                         Con_DisplayList(list[2]);
1180                 }
1181         }
1182
1183         if (cmd) {
1184                 strncpy(key_lines[edit_line] + pos, cmd, cmd_len);
1185                 key_linepos = cmd_len + pos;
1186                 if (c + v + a == 1) {
1187                         key_lines[edit_line][key_linepos] = ' ';
1188                         key_linepos++;
1189                 }
1190                 if(s2[0])
1191                         strcpy(&key_lines[edit_line][key_linepos], s2);
1192                 else
1193                         key_lines[edit_line][key_linepos] = 0;
1194         }
1195         else
1196                 if(s2[0])
1197                         strcpy(&key_lines[edit_line][key_linepos], s2);
1198         for (i = 0; i < 3; i++)
1199                 if (list[i])
1200                         Mem_Free((void *)list[i]);
1201 }
1202