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