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