- Removed Con_SafePrint and Con_SafePrintf since they now does the same things as...
[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 = 0;
43
44 //seconds
45 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3"};
46 cvar_t con_notify = {CVAR_SAVE, "con_notify","4"};
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 #define MAXCMDLINE      256
55 extern char key_lines[32][MAXCMDLINE];
56 extern int edit_line;
57 extern int key_linepos;
58 extern int key_insert;
59
60
61 qboolean con_initialized;
62
63 mempool_t *console_mempool;
64
65
66 /*
67 ==============================================================================
68
69 LOGGING
70
71 ==============================================================================
72 */
73
74 cvar_t log_file = {0, "log_file",""};
75 cvar_t log_sync = {0, "log_sync","0"};
76 char crt_log_file [MAX_OSPATH] = "";
77 qfile_t* logfile = NULL;
78
79 qbyte* logqueue = NULL;
80 size_t logq_ind = 0;
81 size_t logq_size = 0;
82
83 void Log_ConPrint (const char *msg);
84
85 /*
86 ====================
87 Log_Timestamp
88 ====================
89 */
90 const char* Log_Timestamp (const char *desc)
91 {
92         static char timestamp [128];
93         time_t crt_time;
94         const struct tm *crt_tm;
95         char timestring [64];
96
97         // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
98         time (&crt_time);
99         crt_tm = localtime (&crt_time);
100         strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
101
102         if (desc != NULL)
103                 snprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
104         else
105                 snprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
106
107         return timestamp;
108 }
109
110
111 /*
112 ====================
113 Log_Init
114 ====================
115 */
116 void Log_Init (void)
117 {
118         // Allocate a log queue
119         logq_size = 512;
120         logqueue = Mem_Alloc (tempmempool, logq_size);
121         logq_ind = 0;
122
123         Cvar_RegisterVariable (&log_file);
124         Cvar_RegisterVariable (&log_sync);
125
126         // support for the classic Quake option
127 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log with sync on (so it keeps every message up to a crash), see also log_file and log_sync
128         if (COM_CheckParm ("-condebug") != 0)
129         {
130                 Cvar_SetQuick (&log_file, "qconsole.log");
131                 Cvar_SetValueQuick (&log_sync, 1);
132         }
133 }
134
135
136 /*
137 ====================
138 Log_Open
139 ====================
140 */
141 void Log_Open (void)
142 {
143         if (logfile != NULL || log_file.string[0] == '\0')
144                 return;
145
146         logfile = FS_Open (log_file.string, "at", false);
147         if (logfile != NULL)
148         {
149                 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
150                 FS_Print (logfile, Log_Timestamp ("Log started"));
151         }
152 }
153
154
155 /*
156 ====================
157 Log_Close
158 ====================
159 */
160 void Log_Close (void)
161 {
162         if (logfile == NULL)
163                 return;
164
165         FS_Print (logfile, Log_Timestamp ("Log stopped"));
166         FS_Print (logfile, "\n");
167         FS_Close (logfile);
168
169         logfile = NULL;
170         crt_log_file[0] = '\0';
171 }
172
173
174 /*
175 ====================
176 Log_Start
177 ====================
178 */
179 void Log_Start (void)
180 {
181         Log_Open ();
182
183         // Dump the contents of the log queue into the log file and free it
184         if (logqueue != NULL)
185         {
186                 if (logfile != NULL && logq_ind != 0)
187                         FS_Write (logfile, logqueue, logq_ind);
188                 Mem_Free (logqueue);
189                 logqueue = NULL;
190                 logq_ind = 0;
191                 logq_size = 0;
192         }
193 }
194
195
196 /*
197 ================
198 Log_ConPrint
199 ================
200 */
201 void Log_ConPrint (const char *msg)
202 {
203         static qboolean inprogress = false;
204         // don't allow feedback loops with memory error reports
205         if (inprogress)
206                 return;
207         inprogress = true;
208         // Until the host is completely initialized, we maintain a log queue
209         // to store the messages, since the log can't be started before
210         if (logqueue != NULL)
211         {
212                 size_t remain = logq_size - logq_ind;
213                 size_t len = strlen (msg);
214
215                 // If we need to enlarge the log queue
216                 if (len > remain)
217                 {
218                         unsigned int factor = ((logq_ind + len) / logq_size) + 1;
219                         qbyte* newqueue;
220
221                         logq_size *= factor;
222                         newqueue = Mem_Alloc (tempmempool, logq_size);
223                         memcpy (newqueue, logqueue, logq_ind);
224                         Mem_Free (logqueue);
225                         logqueue = newqueue;
226                         remain = logq_size - logq_ind;
227                 }
228                 memcpy (&logqueue[logq_ind], msg, len);
229                 logq_ind += len;
230
231                 inprogress = false;
232                 return;
233         }
234
235         // Check if log_file has changed
236         if (strcmp (crt_log_file, log_file.string) != 0)
237         {
238                 Log_Close ();
239                 Log_Open ();
240         }
241
242         // If a log file is available
243         if (logfile != NULL)
244         {
245                 FS_Print (logfile, msg);
246                 if (log_sync.integer)
247                         FS_Flush (logfile);
248         }
249         inprogress = false;
250 }
251
252
253 /*
254 ================
255 Log_Print
256 ================
257 */
258 void Log_Print (const char *logfilename, const char *msg)
259 {
260         qfile_t *file;
261         file = FS_Open(logfilename, "at", true);
262         if (file)
263         {
264                 FS_Print(file, msg);
265                 FS_Close(file);
266         }
267 }
268
269 /*
270 ================
271 Log_Printf
272 ================
273 */
274 void Log_Printf (const char *logfilename, const char *fmt, ...)
275 {
276         qfile_t *file;
277
278         file = FS_Open (logfilename, "at", true);
279         if (file != NULL)
280         {
281                 va_list argptr;
282
283                 va_start (argptr, fmt);
284                 FS_VPrintf (file, fmt, argptr);
285                 va_end (argptr);
286
287                 FS_Close (file);
288         }
289 }
290
291
292 /*
293 ==============================================================================
294
295 CONSOLE
296
297 ==============================================================================
298 */
299
300 /*
301 ================
302 Con_ToggleConsole_f
303 ================
304 */
305 void Con_ToggleConsole_f (void)
306 {
307         // toggle the 'user wants console' bit
308         key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
309         memset (con_times, 0, sizeof(con_times));
310 }
311
312 /*
313 ================
314 Con_Clear_f
315 ================
316 */
317 void Con_Clear_f (void)
318 {
319         if (con_text)
320                 memset (con_text, ' ', CON_TEXTSIZE);
321 }
322
323
324 /*
325 ================
326 Con_ClearNotify
327 ================
328 */
329 void Con_ClearNotify (void)
330 {
331         int i;
332
333         for (i=0 ; i<MAX_NOTIFYLINES ; i++)
334                 con_times[i] = 0;
335 }
336
337
338 /*
339 ================
340 Con_MessageMode_f
341 ================
342 */
343 void Con_MessageMode_f (void)
344 {
345         key_dest = key_message;
346         chat_team = false;
347 }
348
349
350 /*
351 ================
352 Con_MessageMode2_f
353 ================
354 */
355 void Con_MessageMode2_f (void)
356 {
357         key_dest = key_message;
358         chat_team = true;
359 }
360
361
362 /*
363 ================
364 Con_CheckResize
365
366 If the line width has changed, reformat the buffer.
367 ================
368 */
369 void Con_CheckResize (void)
370 {
371         int i, j, width, oldwidth, oldtotallines, numlines, numchars;
372         char tbuf[CON_TEXTSIZE];
373
374         width = (vid.conwidth >> 3);
375
376         if (width == con_linewidth)
377                 return;
378
379         if (width < 1)                  // video hasn't been initialized yet
380         {
381                 width = 80;
382                 con_linewidth = width;
383                 con_totallines = CON_TEXTSIZE / con_linewidth;
384                 memset (con_text, ' ', CON_TEXTSIZE);
385         }
386         else
387         {
388                 oldwidth = con_linewidth;
389                 con_linewidth = width;
390                 oldtotallines = con_totallines;
391                 con_totallines = CON_TEXTSIZE / con_linewidth;
392                 numlines = oldtotallines;
393
394                 if (con_totallines < numlines)
395                         numlines = con_totallines;
396
397                 numchars = oldwidth;
398
399                 if (con_linewidth < numchars)
400                         numchars = con_linewidth;
401
402                 memcpy (tbuf, con_text, CON_TEXTSIZE);
403                 memset (con_text, ' ', CON_TEXTSIZE);
404
405                 for (i=0 ; i<numlines ; i++)
406                 {
407                         for (j=0 ; j<numchars ; j++)
408                         {
409                                 con_text[(con_totallines - 1 - i) * con_linewidth + j] =
410                                                 tbuf[((con_current - i + oldtotallines) %
411                                                           oldtotallines) * oldwidth + j];
412                         }
413                 }
414
415                 Con_ClearNotify ();
416         }
417
418         con_backscroll = 0;
419         con_current = con_totallines - 1;
420 }
421
422 /*
423 ================
424 Con_Init
425 ================
426 */
427 void Con_Init (void)
428 {
429         console_mempool = Mem_AllocPool("console", 0, NULL);
430         con_text = Mem_Alloc(console_mempool, CON_TEXTSIZE);
431         memset (con_text, ' ', CON_TEXTSIZE);
432         con_linewidth = -1;
433         Con_CheckResize ();
434
435         // register our cvars
436         Cvar_RegisterVariable (&con_notifytime);
437         Cvar_RegisterVariable (&con_notify);
438
439         // register our commands
440         Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
441         Cmd_AddCommand ("messagemode", Con_MessageMode_f);
442         Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
443         Cmd_AddCommand ("clear", Con_Clear_f);
444
445         con_initialized = true;
446         Con_Print("Console initialized.\n");
447 }
448
449
450 /*
451 ===============
452 Con_Linefeed
453 ===============
454 */
455 void Con_Linefeed (void)
456 {
457         if (con_backscroll)
458                 con_backscroll++;
459
460         con_x = 0;
461         con_current++;
462         memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth);
463 }
464
465 /*
466 ================
467 Con_PrintToHistory
468
469 Handles cursor positioning, line wrapping, etc
470 All console printing must go through this in order to be displayed
471 If no console is visible, the notify window will pop up.
472 ================
473 */
474 void Con_PrintToHistory(const char *txt)
475 {
476         int y, c, l, mask;
477         static int cr;
478
479         if (txt[0] == 1)
480         {
481                 mask = 128;             // go to colored text
482                 S_LocalSound ("sound/misc/talk.wav");
483         // play talk wav
484                 txt++;
485         }
486         else if (txt[0] == 2)
487         {
488                 mask = 128;             // go to colored text
489                 txt++;
490         }
491         else
492                 mask = 0;
493
494
495         while ( (c = *txt) )
496         {
497         // count word length
498                 for (l=0 ; l< con_linewidth ; l++)
499                         if ( txt[l] <= ' ')
500                                 break;
501
502         // word wrap
503                 if (l != con_linewidth && (con_x + l > con_linewidth) )
504                         con_x = 0;
505
506                 txt++;
507
508                 if (cr)
509                 {
510                         con_current--;
511                         cr = false;
512                 }
513
514
515                 if (!con_x)
516                 {
517                         Con_Linefeed ();
518                 // mark time for transparent overlay
519                         if (con_current >= 0)
520                         {
521                                 if (con_notify.integer < 0)
522                                         Cvar_SetValueQuick(&con_notify, 0);
523                                 if (con_notify.integer > MAX_NOTIFYLINES)
524                                         Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
525                                 if (con_notify.integer > 0)
526                                         con_times[con_current % con_notify.integer] = cl.time;
527                         }
528                 }
529
530                 switch (c)
531                 {
532                 case '\n':
533                         con_x = 0;
534                         break;
535
536                 case '\r':
537                         con_x = 0;
538                         cr = 1;
539                         break;
540
541                 default:        // display character and advance
542                         y = con_current % con_totallines;
543                         con_text[y*con_linewidth+con_x] = c | mask;
544                         con_x++;
545                         if (con_x >= con_linewidth)
546                                 con_x = 0;
547                         break;
548                 }
549
550         }
551 }
552
553 /* The translation table between the graphical font and plain ASCII  --KB */
554 static char qfont_table[256] = {
555         '\0', '#',  '#',  '#',  '#',  '.',  '#',  '#',
556         '#',  9,    10,   '#',  ' ',  13,   '.',  '.',
557         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
558         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
559         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
560         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
561         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
562         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
563         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
564         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
565         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
566         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
567         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
568         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
569         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
570         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<',
571
572         '<',  '=',  '>',  '#',  '#',  '.',  '#',  '#',
573         '#',  '#',  ' ',  '#',  ' ',  '>',  '.',  '.',
574         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
575         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
576         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
577         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
578         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
579         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
580         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
581         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
582         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
583         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
584         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
585         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
586         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
587         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<'
588 };
589
590 /*
591 ================
592 Con_Print
593
594 Prints to all appropriate console targets, and adds timestamps
595 ================
596 */
597 extern cvar_t timestamps;
598 extern cvar_t timeformat;
599 extern qboolean sys_nostdout;
600 void Con_Print(const char *msg)
601 {
602         static int index = 0;
603         static char line[16384];
604
605         for (;*msg;msg++)
606         {
607                 if (index == 0)
608                 {
609                         // if this is the beginning of a new line, print timestamp
610                         char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
611                         // special color codes for chat messages must always come first
612                         // for Con_PrintToHistory to work properly
613                         if (*msg <= 2)
614                                 line[index++] = *msg++;
615                         // store timestamp
616                         for (;*timestamp;index++, timestamp++)
617                                 if (index < sizeof(line) - 2)
618                                         line[index] = *timestamp;
619                 }
620                 // append the character
621                 line[index++] = *msg;
622                 // if this is a newline character, we have a complete line to print
623                 if (*msg == '\n' || index >= 16000)
624                 {
625                         // terminate the line
626                         line[index] = 0;
627                         // send to log file
628                         Log_ConPrint(line);
629                         // send to scrollable buffer
630                         if (con_initialized && cls.state != ca_dedicated)
631                                 Con_PrintToHistory(line);
632                         // send to terminal or dedicated server window
633                         if (!sys_nostdout)
634                         {
635                                 unsigned char *p;
636                                 for (p = (unsigned char *) line;*p; p++)
637                                         *p = qfont_table[*p];
638                                 Sys_PrintToTerminal(line);
639                         }
640                         // empty the line buffer
641                         index = 0;
642                 }
643         }
644 }
645
646
647 // LordHavoc: increased from 4096 to 16384
648 #define MAXPRINTMSG     16384
649
650 /*
651 ================
652 Con_Printf
653
654 Prints to all appropriate console targets
655 ================
656 */
657 void Con_Printf(const char *fmt, ...)
658 {
659         va_list argptr;
660         char msg[MAXPRINTMSG];
661
662         va_start(argptr,fmt);
663         vsprintf(msg,fmt,argptr);
664         va_end(argptr);
665
666         Con_Print(msg);
667 }
668
669 /*
670 ================
671 Con_DPrint
672
673 A Con_Print that only shows up if the "developer" cvar is set
674 ================
675 */
676 void Con_DPrint(const char *msg)
677 {
678         if (!developer.integer)
679                 return;                 // don't confuse non-developers with techie stuff...
680         Con_Print(msg);
681 }
682
683 /*
684 ================
685 Con_DPrintf
686
687 A Con_Printf that only shows up if the "developer" cvar is set
688 ================
689 */
690 void Con_DPrintf(const char *fmt, ...)
691 {
692         va_list argptr;
693         char msg[MAXPRINTMSG];
694
695         if (!developer.integer)
696                 return;                 // don't confuse non-developers with techie stuff...
697
698         va_start(argptr,fmt);
699         vsprintf(msg,fmt,argptr);
700         va_end(argptr);
701
702         Con_Print(msg);
703 }
704
705
706 /*
707 ==============================================================================
708
709 DRAWING
710
711 ==============================================================================
712 */
713
714
715 /*
716 ================
717 Con_DrawInput
718
719 The input line scrolls horizontally if typing goes beyond the right edge
720
721 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
722 ================
723 */
724 void Con_DrawInput (void)
725 {
726         int             y;
727         int             i;
728         char editlinecopy[257], *text;
729
730         if (!key_consoleactive)
731                 return;         // don't draw anything
732
733         text = strcpy(editlinecopy, key_lines[edit_line]);
734
735         // Advanced Console Editing by Radix radix@planetquake.com
736         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
737         // use strlen of edit_line instead of key_linepos to allow editing
738         // of early characters w/o erasing
739
740         y = strlen(text);
741
742 // fill out remainder with spaces
743         for (i = y; i < 256; i++)
744                 text[i] = ' ';
745
746         // add the cursor frame
747         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
748                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
749
750 //      text[key_linepos + 1] = 0;
751
752         // prestep if horizontally scrolling
753         if (key_linepos >= con_linewidth)
754                 text += 1 + key_linepos - con_linewidth;
755
756         // draw it
757         DrawQ_String(0, con_vislines - 16, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
758
759         // remove cursor
760 //      key_lines[edit_line][key_linepos] = 0;
761 }
762
763
764 /*
765 ================
766 Con_DrawNotify
767
768 Draws the last few lines of output transparently over the game top
769 ================
770 */
771 void Con_DrawNotify (void)
772 {
773         int             x, v;
774         char    *text;
775         int             i;
776         float   time;
777         extern char chat_buffer[];
778         char    temptext[256];
779
780         if (con_notify.integer < 0)
781                 Cvar_SetValueQuick(&con_notify, 0);
782         if (con_notify.integer > MAX_NOTIFYLINES)
783                 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
784         if (gamemode == GAME_TRANSFUSION)
785                 v = 8;
786         else
787                 v = 0;
788         for (i= con_current-con_notify.integer+1 ; i<=con_current ; i++)
789         {
790                 if (i < 0)
791                         continue;
792                 time = con_times[i % con_notify.integer];
793                 if (time == 0)
794                         continue;
795                 time = cl.time - time;
796                 if (time > con_notifytime.value)
797                         continue;
798                 text = con_text + (i % con_totallines)*con_linewidth;
799
800                 DrawQ_String(0, v, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
801
802                 v += 8;
803         }
804
805
806         if (key_dest == key_message)
807         {
808                 x = 0;
809
810                 // LordHavoc: speedup, and other improvements
811                 if (chat_team)
812                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
813                 else
814                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
815                 while (strlen(temptext) >= (size_t) con_linewidth)
816                 {
817                         DrawQ_String (0, v, temptext, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
818                         strcpy(temptext, &temptext[con_linewidth]);
819                         v += 8;
820                 }
821                 if (strlen(temptext) > 0)
822                 {
823                         DrawQ_String (0, v, temptext, 0, 8, 8, 1, 1, 1, 1, 0);
824                         v += 8;
825                 }
826         }
827 }
828
829 /*
830 ================
831 Con_DrawConsole
832
833 Draws the console with the solid background
834 The typing input line at the bottom should only be drawn if typing is allowed
835 ================
836 */
837 extern char engineversion[40];
838 void Con_DrawConsole (int lines)
839 {
840         int i, y, rows, j;
841         char *text;
842
843         if (lines <= 0)
844                 return;
845
846 // draw the background
847         if (scr_conbrightness.value >= 0.01f)
848                 DrawQ_Pic(0, lines - vid.conheight, "gfx/conback", vid.conwidth, vid.conheight, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, scr_conalpha.value, 0);
849         else
850                 DrawQ_Fill(0, lines - vid.conheight, vid.conwidth, vid.conheight, 0, 0, 0, scr_conalpha.value, 0);
851         DrawQ_String(vid.conwidth - strlen(engineversion) * 8 - 8, lines - 8, engineversion, 0, 8, 8, 1, 0, 0, 1, 0);
852
853 // draw the text
854         con_vislines = lines;
855
856         rows = (lines-16)>>3;           // rows of text to draw
857         y = lines - 16 - (rows<<3);     // may start slightly negative
858
859         for (i = con_current - rows + 1;i <= con_current;i++, y += 8)
860         {
861                 j = max(i - con_backscroll, 0);
862                 text = con_text + (j % con_totallines)*con_linewidth;
863
864                 DrawQ_String(0, y, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
865         }
866
867 // draw the input prompt, user text, and cursor if desired
868         Con_DrawInput ();
869 }
870
871 /*
872         Con_DisplayList
873
874         New function for tab-completion system
875         Added by EvilTypeGuy
876         MEGA Thanks to Taniwha
877
878 */
879 void Con_DisplayList(const char **list)
880 {
881         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
882         const char **walk = list;
883
884         while (*walk) {
885                 len = strlen(*walk);
886                 if (len > maxlen)
887                         maxlen = len;
888                 walk++;
889         }
890         maxlen += 1;
891
892         while (*list) {
893                 len = strlen(*list);
894                 if (pos + maxlen >= width) {
895                         Con_Print("\n");
896                         pos = 0;
897                 }
898
899                 Con_Print(*list);
900                 for (i = 0; i < (maxlen - len); i++)
901                         Con_Print(" ");
902
903                 pos += maxlen;
904                 list++;
905         }
906
907         if (pos)
908                 Con_Print("\n\n");
909 }
910
911 /*
912         Con_CompleteCommandLine
913
914         New function for tab-completion system
915         Added by EvilTypeGuy
916         Thanks to Fett erich@heintz.com
917         Thanks to taniwha
918
919 */
920 void Con_CompleteCommandLine (void)
921 {
922         const char *cmd = "", *s;
923         const char **list[3] = {0, 0, 0};
924         int c, v, a, i, cmd_len;
925
926         s = key_lines[edit_line] + 1;
927         // Count number of possible matches
928         c = Cmd_CompleteCountPossible(s);
929         v = Cvar_CompleteCountPossible(s);
930         a = Cmd_CompleteAliasCountPossible(s);
931
932         if (!(c + v + a))       // No possible matches
933                 return;
934
935         if (c + v + a == 1) {
936                 if (c)
937                         list[0] = Cmd_CompleteBuildList(s);
938                 else if (v)
939                         list[0] = Cvar_CompleteBuildList(s);
940                 else
941                         list[0] = Cmd_CompleteAliasBuildList(s);
942                 cmd = *list[0];
943                 cmd_len = strlen (cmd);
944         } else {
945                 if (c)
946                         cmd = *(list[0] = Cmd_CompleteBuildList(s));
947                 if (v)
948                         cmd = *(list[1] = Cvar_CompleteBuildList(s));
949                 if (a)
950                         cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
951
952                 cmd_len = strlen (s);
953                 do {
954                         for (i = 0; i < 3; i++) {
955                                 char ch = cmd[cmd_len];
956                                 const char **l = list[i];
957                                 if (l) {
958                                         while (*l && (*l)[cmd_len] == ch)
959                                                 l++;
960                                         if (*l)
961                                                 break;
962                                 }
963                         }
964                         if (i == 3)
965                                 cmd_len++;
966                 } while (i == 3);
967                 // 'quakebar'
968                 Con_Print("\n\35");
969                 for (i = 0; i < con_linewidth - 4; i++)
970                         Con_Print("\36");
971                 Con_Print("\37\n");
972
973                 // Print Possible Commands
974                 if (c) {
975                         Con_Printf("%i possible command%s\n", c, (c > 1) ? "s: " : ":");
976                         Con_DisplayList(list[0]);
977                 }
978
979                 if (v) {
980                         Con_Printf("%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
981                         Con_DisplayList(list[1]);
982                 }
983
984                 if (a) {
985                         Con_Printf("%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
986                         Con_DisplayList(list[2]);
987                 }
988         }
989
990         if (cmd) {
991                 strncpy(key_lines[edit_line] + 1, cmd, cmd_len);
992                 key_linepos = cmd_len + 1;
993                 if (c + v + a == 1) {
994                         key_lines[edit_line][key_linepos] = ' ';
995                         key_linepos++;
996                 }
997                 key_lines[edit_line][key_linepos] = 0;
998         }
999         for (i = 0; i < 3; i++)
1000                 if (list[i])
1001                         Mem_Free((void *)list[i]);
1002 }
1003