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