]> icculus.org git repositories - divverent/darkplaces.git/blob - console.c
made an optimized SV_ClipMoveToWorld function to save time on large numbers of world...
[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 //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 qboolean con_initialized;
55
56
57 /*
58 ==============================================================================
59
60 LOGGING
61
62 ==============================================================================
63 */
64
65 cvar_t log_file = {0, "log_file",""};
66 char crt_log_file [MAX_OSPATH] = "";
67 qfile_t* logfile = NULL;
68
69 qbyte* 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                         qbyte* newqueue;
187
188                         logq_size *= factor;
189                         newqueue = (qbyte *)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         char tbuf[CON_TEXTSIZE];
320
321         width = (vid_conwidth.integer >> 3);
322
323         if (width == con_linewidth)
324                 return;
325
326         oldwidth = con_linewidth;
327         con_linewidth = width;
328         oldtotallines = con_totallines;
329         con_totallines = CON_TEXTSIZE / con_linewidth;
330         numlines = oldtotallines;
331
332         if (con_totallines < numlines)
333                 numlines = con_totallines;
334
335         numchars = oldwidth;
336
337         if (con_linewidth < numchars)
338                 numchars = con_linewidth;
339
340         memcpy (tbuf, con_text, CON_TEXTSIZE);
341         memset (con_text, ' ', CON_TEXTSIZE);
342
343         for (i=0 ; i<numlines ; i++)
344         {
345                 for (j=0 ; j<numchars ; j++)
346                 {
347                         con_text[(con_totallines - 1 - i) * con_linewidth + j] =
348                                         tbuf[((con_current - i + oldtotallines) %
349                                                   oldtotallines) * oldwidth + j];
350                 }
351         }
352
353         Con_ClearNotify ();
354
355         con_backscroll = 0;
356         con_current = con_totallines - 1;
357 }
358
359 /*
360 ================
361 Con_Init
362 ================
363 */
364 void Con_Init (void)
365 {
366         memset (con_text, ' ', CON_TEXTSIZE);
367         con_linewidth = 80;
368         con_totallines = CON_TEXTSIZE / con_linewidth;
369
370         // Allocate a log queue
371         logq_size = 512;
372         logqueue = (qbyte *)Mem_Alloc (tempmempool, logq_size);
373         logq_ind = 0;
374
375         Cvar_RegisterVariable (&log_file);
376
377         // support for the classic Quake option
378 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
379         if (COM_CheckParm ("-condebug") != 0)
380                 Cvar_SetQuick (&log_file, "qconsole.log");
381 }
382
383 void Con_Init_Commands (void)
384 {
385         // register our cvars
386         Cvar_RegisterVariable (&con_notifytime);
387         Cvar_RegisterVariable (&con_notify);
388
389         // register our commands
390         Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
391         Cmd_AddCommand ("messagemode", Con_MessageMode_f);
392         Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
393         Cmd_AddCommand ("clear", Con_Clear_f);
394
395         con_initialized = true;
396         Con_Print("Console initialized.\n");
397 }
398
399
400 /*
401 ===============
402 Con_Linefeed
403 ===============
404 */
405 void Con_Linefeed (void)
406 {
407         if (con_backscroll)
408                 con_backscroll++;
409
410         con_x = 0;
411         con_current++;
412         memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth);
413 }
414
415 /*
416 ================
417 Con_PrintToHistory
418
419 Handles cursor positioning, line wrapping, etc
420 All console printing must go through this in order to be displayed
421 If no console is visible, the notify window will pop up.
422 ================
423 */
424 void Con_PrintToHistory(const char *txt, int mask)
425 {
426         int y, c, l;
427         static int cr;
428
429         while ( (c = *txt) )
430         {
431         // count word length
432                 for (l=0 ; l< con_linewidth ; l++)
433                         if ( txt[l] <= ' ')
434                                 break;
435
436         // word wrap
437                 if (l != con_linewidth && (con_x + l > con_linewidth) )
438                         con_x = 0;
439
440                 txt++;
441
442                 if (cr)
443                 {
444                         con_current--;
445                         cr = false;
446                 }
447
448
449                 if (!con_x)
450                 {
451                         Con_Linefeed ();
452                 // mark time for transparent overlay
453                         if (con_current >= 0)
454                         {
455                                 if (con_notify.integer < 0)
456                                         Cvar_SetValueQuick(&con_notify, 0);
457                                 if (con_notify.integer > MAX_NOTIFYLINES)
458                                         Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
459                                 if (con_notify.integer > 0)
460                                         con_times[con_current % con_notify.integer] = cl.time;
461                         }
462                 }
463
464                 switch (c)
465                 {
466                 case '\n':
467                         con_x = 0;
468                         break;
469
470                 case '\r':
471                         con_x = 0;
472                         cr = 1;
473                         break;
474
475                 default:        // display character and advance
476                         y = con_current % con_totallines;
477                         con_text[y*con_linewidth+con_x] = c | mask;
478                         con_x++;
479                         if (con_x >= con_linewidth)
480                                 con_x = 0;
481                         break;
482                 }
483
484         }
485 }
486
487 /* The translation table between the graphical font and plain ASCII  --KB */
488 static char qfont_table[256] = {
489         '\0', '#',  '#',  '#',  '#',  '.',  '#',  '#',
490         '#',  9,    10,   '#',  ' ',  13,   '.',  '.',
491         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
492         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
493         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
494         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
495         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
496         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
497         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
498         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
499         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
500         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
501         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
502         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
503         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
504         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<',
505
506         '<',  '=',  '>',  '#',  '#',  '.',  '#',  '#',
507         '#',  '#',  ' ',  '#',  ' ',  '>',  '.',  '.',
508         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
509         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
510         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
511         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
512         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
513         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
514         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
515         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
516         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
517         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
518         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
519         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
520         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
521         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<'
522 };
523
524 /*
525 ================
526 Con_Print
527
528 Prints to all appropriate console targets, and adds timestamps
529 ================
530 */
531 extern cvar_t timestamps;
532 extern cvar_t timeformat;
533 extern qboolean sys_nostdout;
534 void Con_Print(const char *msg)
535 {
536         int mask = 0;
537         static int index = 0;
538         static char line[16384];
539
540         for (;*msg;msg++)
541         {
542                 if (index == 0)
543                 {
544                         // if this is the beginning of a new line, print timestamp
545                         const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
546                         // reset the color
547                         // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
548                         line[index++] = STRING_COLOR_TAG;
549                         // assert( STRING_COLOR_DEFAULT < 10 )
550                         line[index++] = STRING_COLOR_DEFAULT + '0';
551                         // special color codes for chat messages must always come first
552                         // for Con_PrintToHistory to work properly
553                         if (*msg <= 2)
554                         {
555                                 if (*msg == 1)
556                                 {
557                                         // play talk wav
558                                         S_LocalSound ("sound/misc/talk.wav");
559                                 }
560                                 if (gamemode == GAME_NEXUIZ)
561                                 {
562                                         line[index++] = '^';
563                                         line[index++] = '3';
564                                 }
565                                 else
566                                 {
567                                         // go to colored text
568                                         mask = 128;
569                                 }
570                                 msg++;
571                         }
572                         // store timestamp
573                         for (;*timestamp;index++, timestamp++)
574                                 if (index < (int)sizeof(line) - 2)
575                                         line[index] = *timestamp;
576                 }
577                 // append the character
578                 line[index++] = *msg;
579                 // if this is a newline character, we have a complete line to print
580                 if (*msg == '\n' || index >= 16000)
581                 {
582                         // terminate the line
583                         line[index] = 0;
584                         // send to log file
585                         Log_ConPrint(line);
586                         // send to scrollable buffer
587                         if (con_initialized && cls.state != ca_dedicated)
588                                 Con_PrintToHistory(line, mask);
589                         // send to terminal or dedicated server window
590                         if (!sys_nostdout)
591                         {
592                                 unsigned char *p;
593                                 for (p = (unsigned char *) line;*p; p++)
594                                         *p = qfont_table[*p];
595                                 Sys_PrintToTerminal(line);
596                         }
597                         // empty the line buffer
598                         index = 0;
599                 }
600         }
601 }
602
603
604 // LordHavoc: increased from 4096 to 16384
605 #define MAXPRINTMSG     16384
606
607 /*
608 ================
609 Con_Printf
610
611 Prints to all appropriate console targets
612 ================
613 */
614 void Con_Printf(const char *fmt, ...)
615 {
616         va_list argptr;
617         char msg[MAXPRINTMSG];
618
619         va_start(argptr,fmt);
620         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
621         va_end(argptr);
622
623         Con_Print(msg);
624 }
625
626 /*
627 ================
628 Con_DPrint
629
630 A Con_Print that only shows up if the "developer" cvar is set
631 ================
632 */
633 void Con_DPrint(const char *msg)
634 {
635         if (!developer.integer)
636                 return;                 // don't confuse non-developers with techie stuff...
637         Con_Print(msg);
638 }
639
640 /*
641 ================
642 Con_DPrintf
643
644 A Con_Printf that only shows up if the "developer" cvar is set
645 ================
646 */
647 void Con_DPrintf(const char *fmt, ...)
648 {
649         va_list argptr;
650         char msg[MAXPRINTMSG];
651
652         if (!developer.integer)
653                 return;                 // don't confuse non-developers with techie stuff...
654
655         va_start(argptr,fmt);
656         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
657         va_end(argptr);
658
659         Con_Print(msg);
660 }
661
662
663 /*
664 ==============================================================================
665
666 DRAWING
667
668 ==============================================================================
669 */
670
671 /*
672 ================
673 Con_DrawInput
674
675 The input line scrolls horizontally if typing goes beyond the right edge
676
677 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
678 ================
679 */
680 void Con_DrawInput (void)
681 {
682         int             y;
683         int             i;
684         char editlinecopy[257], *text;
685
686         if (!key_consoleactive)
687                 return;         // don't draw anything
688
689         text = strcpy(editlinecopy, key_lines[edit_line]);
690
691         // Advanced Console Editing by Radix radix@planetquake.com
692         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
693         // use strlen of edit_line instead of key_linepos to allow editing
694         // of early characters w/o erasing
695
696         y = (int)strlen(text);
697
698 // fill out remainder with spaces
699         for (i = y; i < 256; i++)
700                 text[i] = ' ';
701
702         // add the cursor frame
703         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
704                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
705
706 //      text[key_linepos + 1] = 0;
707
708         // prestep if horizontally scrolling
709         if (key_linepos >= con_linewidth)
710                 text += 1 + key_linepos - con_linewidth;
711
712         // draw it
713         DrawQ_ColoredString(0, con_vislines - 16, text, con_linewidth, 8, 8, 1.0, 1.0, 1.0, 1.0, 0, NULL );
714
715         // remove cursor
716 //      key_lines[edit_line][key_linepos] = 0;
717 }
718
719
720 /*
721 ================
722 Con_DrawNotify
723
724 Draws the last few lines of output transparently over the game top
725 ================
726 */
727 void Con_DrawNotify (void)
728 {
729         int             x, v;
730         char    *text;
731         int             i;
732         float   time;
733         extern char chat_buffer[256];
734         char    temptext[256];
735         int colorindex = -1; //-1 for default
736
737         if (con_notify.integer < 0)
738                 Cvar_SetValueQuick(&con_notify, 0);
739         if (con_notify.integer > MAX_NOTIFYLINES)
740                 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
741         if (gamemode == GAME_TRANSFUSION)
742                 v = 8;
743         else
744                 v = 0;
745         for (i= con_current-con_notify.integer+1 ; i<=con_current ; i++)
746         {
747
748                 if (i < 0)
749                         continue;
750                 time = con_times[i % con_notify.integer];
751                 if (time == 0)
752                         continue;
753                 time = cl.time - time;
754                 if (time > con_notifytime.value)
755                         continue;
756                 text = con_text + (i % con_totallines)*con_linewidth;
757
758                 if (gamemode == GAME_NEXUIZ) {
759                         int linewidth;
760
761                         for (linewidth = con_linewidth; linewidth && text[linewidth-1] == ' '; linewidth--);
762                         x = (vid_conwidth.integer - linewidth * 8) / 2;
763                 } else
764                         x = 0;
765
766                 DrawQ_ColoredString( x, v, text, con_linewidth, 8, 8, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
767
768                 v += 8;
769         }
770
771
772         if (key_dest == key_message)
773         {
774                 int colorindex = -1;
775
776                 x = 0;
777
778                 // LordHavoc: speedup, and other improvements
779                 if (chat_team)
780                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
781                 else
782                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
783                 while ((int)strlen(temptext) >= con_linewidth)
784                 {
785                         DrawQ_ColoredString( 0, v, temptext, con_linewidth, 8, 8, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
786                         strcpy(temptext, &temptext[con_linewidth]);
787                         v += 8;
788                 }
789                 if (strlen(temptext) > 0)
790                 {
791                         DrawQ_ColoredString( 0, v, temptext, 0, 8, 8, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
792                         v += 8;
793                 }
794         }
795 }
796
797 /*
798 ================
799 Con_DrawConsole
800
801 Draws the console with the solid background
802 The typing input line at the bottom should only be drawn if typing is allowed
803 ================
804 */
805 void Con_DrawConsole (int lines)
806 {
807         int i, y, rows, j;
808         char *text;
809         int colorindex = -1;
810
811         if (lines <= 0)
812                 return;
813
814 // draw the background
815         if (scr_conbrightness.value >= 0.01f)
816                 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);
817         else
818                 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0, 0, 0, scr_conalpha.value, 0);
819         DrawQ_String(vid_conwidth.integer - strlen(engineversion) * 8 - 8, lines - 8, engineversion, 0, 8, 8, 1, 0, 0, 1, 0);
820
821 // draw the text
822         con_vislines = lines;
823
824         rows = (lines-16)>>3;           // rows of text to draw
825         y = lines - 16 - (rows<<3);     // may start slightly negative
826
827         for (i = con_current - rows + 1;i <= con_current;i++, y += 8)
828         {
829                 j = max(i - con_backscroll, 0);
830                 text = con_text + (j % con_totallines)*con_linewidth;
831
832                 DrawQ_ColoredString( 0, y, text, con_linewidth, 8, 8, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
833         }
834
835 // draw the input prompt, user text, and cursor if desired
836         Con_DrawInput ();
837 }
838
839 /*
840         Con_DisplayList
841
842         New function for tab-completion system
843         Added by EvilTypeGuy
844         MEGA Thanks to Taniwha
845
846 */
847 void Con_DisplayList(const char **list)
848 {
849         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
850         const char **walk = list;
851
852         while (*walk) {
853                 len = (int)strlen(*walk);
854                 if (len > maxlen)
855                         maxlen = len;
856                 walk++;
857         }
858         maxlen += 1;
859
860         while (*list) {
861                 len = (int)strlen(*list);
862                 if (pos + maxlen >= width) {
863                         Con_Print("\n");
864                         pos = 0;
865                 }
866
867                 Con_Print(*list);
868                 for (i = 0; i < (maxlen - len); i++)
869                         Con_Print(" ");
870
871                 pos += maxlen;
872                 list++;
873         }
874
875         if (pos)
876                 Con_Print("\n\n");
877 }
878
879 /*
880         Con_CompleteCommandLine
881
882         New function for tab-completion system
883         Added by EvilTypeGuy
884         Thanks to Fett erich@heintz.com
885         Thanks to taniwha
886
887 */
888 void Con_CompleteCommandLine (void)
889 {
890         const char *cmd = "", *s;
891         const char **list[3] = {0, 0, 0};
892         int c, v, a, i, cmd_len;
893
894         s = key_lines[edit_line] + 1;
895         // Count number of possible matches
896         c = Cmd_CompleteCountPossible(s);
897         v = Cvar_CompleteCountPossible(s);
898         a = Cmd_CompleteAliasCountPossible(s);
899
900         if (!(c + v + a))       // No possible matches
901                 return;
902
903         if (c + v + a == 1) {
904                 if (c)
905                         list[0] = Cmd_CompleteBuildList(s);
906                 else if (v)
907                         list[0] = Cvar_CompleteBuildList(s);
908                 else
909                         list[0] = Cmd_CompleteAliasBuildList(s);
910                 cmd = *list[0];
911                 cmd_len = (int)strlen (cmd);
912         } else {
913                 if (c)
914                         cmd = *(list[0] = Cmd_CompleteBuildList(s));
915                 if (v)
916                         cmd = *(list[1] = Cvar_CompleteBuildList(s));
917                 if (a)
918                         cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
919
920                 cmd_len = (int)strlen (s);
921                 do {
922                         for (i = 0; i < 3; i++) {
923                                 char ch = cmd[cmd_len];
924                                 const char **l = list[i];
925                                 if (l) {
926                                         while (*l && (*l)[cmd_len] == ch)
927                                                 l++;
928                                         if (*l)
929                                                 break;
930                                 }
931                         }
932                         if (i == 3)
933                                 cmd_len++;
934                 } while (i == 3);
935                 // 'quakebar'
936                 Con_Print("\n\35");
937                 for (i = 0; i < con_linewidth - 4; i++)
938                         Con_Print("\36");
939                 Con_Print("\37\n");
940
941                 // Print Possible Commands
942                 if (c) {
943                         Con_Printf("%i possible command%s\n", c, (c > 1) ? "s: " : ":");
944                         Con_DisplayList(list[0]);
945                 }
946
947                 if (v) {
948                         Con_Printf("%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
949                         Con_DisplayList(list[1]);
950                 }
951
952                 if (a) {
953                         Con_Printf("%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
954                         Con_DisplayList(list[2]);
955                 }
956         }
957
958         if (cmd) {
959                 strncpy(key_lines[edit_line] + 1, cmd, cmd_len);
960                 key_linepos = cmd_len + 1;
961                 if (c + v + a == 1) {
962                         key_lines[edit_line][key_linepos] = ' ';
963                         key_linepos++;
964                 }
965                 key_lines[edit_line][key_linepos] = 0;
966         }
967         for (i = 0; i < 3; i++)
968                 if (list[i])
969                         Mem_Free((void *)list[i]);
970 }
971