]> icculus.org git repositories - divverent/darkplaces.git/blob - console.c
Replaced snprintf and vnsprintf calls by dpsnprintf and dpvsnprintf calls, to ensure...
[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 = 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                 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
103         else
104                 dpsnprintf (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, "ab", 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, "ab", 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         dpvsnprintf(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         dpvsnprintf(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 static vec4_t _con_colors[] =
692 {
693         {1.0, 1.0, 1.0, 1.0},
694         {1.0, 0.0, 0.0, 1.0},
695         {0.0, 1.0, 0.0, 1.0},
696         {0.0, 0.0, 1.0, 1.0},
697         {1.0, 1.0, 0.0, 1.0},
698         {0.0, 1.0, 1.0, 1.0},
699         {1.0, 0.0, 1.0, 1.0},
700         {0.1, 0.1, 0.1, 1.0}
701 };
702
703 #define _con_colors_count       (sizeof(_con_colors) / sizeof(vec3_t))
704 #define _con_color_tag          '@'
705
706 // color is read and changed in the end
707 static void _Con_DrawString( float x, float y, const char *text, int maxlen, float scalex, float scaley, int flags )
708 {
709         vec_t *color;
710         const char *first, *last;
711         int len;
712
713         color = _con_colors[0];
714         if( maxlen < 1)
715                 len = strlen( text );
716         else
717                 len = min( maxlen, (signed) strlen( text ));
718
719     first = last = text;
720         while( 1 ) {
721                 // iterate until we get the next color tag or reach the end of the text part to draw
722                 for( ; len && *last != _con_color_tag ; len--, last++ )
723                         ;
724                 // dont do anything if we havent read anything yet
725                 if( last != text ) {
726                         // draw the string
727                         DrawQ_String( x, y, first, last - first, scalex, scaley, color[0], color[1], color[2], color[3], flags );
728                         // update x to be at the new start position
729                         x += (last - first) * scalex;
730                         // if we have reached the end, we have finished
731                         if( !len )
732                                 break;
733                 }
734                 first = last;
735                 // jump over the tag
736                 last++;
737                 len--;
738                 if( len && '0' <= *last && *last <= '9' ) {
739                         int index = 0;
740
741                         while( '0' <= *last && *last <= '9' && len ) {
742                                 index = index * 10 + *last - '0';
743                                 if( index < _con_colors_count ) {
744                                         last++;
745                                         len--;
746                                 } else {
747                                         index /= 10;
748                                         break;
749                                 }
750                         }
751
752                         color = _con_colors[index];
753                         // we dont want to display the color tag and the color index
754                         first = last;
755                 } 
756         }
757 }
758
759 /*
760 ================
761 Con_DrawInput
762
763 The input line scrolls horizontally if typing goes beyond the right edge
764
765 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
766 ================
767 */
768 void Con_DrawInput (void)
769 {
770         int             y;
771         int             i;
772         char editlinecopy[257], *text;
773
774         if (!key_consoleactive)
775                 return;         // don't draw anything
776
777         text = strcpy(editlinecopy, key_lines[edit_line]);
778
779         // Advanced Console Editing by Radix radix@planetquake.com
780         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
781         // use strlen of edit_line instead of key_linepos to allow editing
782         // of early characters w/o erasing
783
784         y = strlen(text);
785
786 // fill out remainder with spaces
787         for (i = y; i < 256; i++)
788                 text[i] = ' ';
789
790         // add the cursor frame
791         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
792                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
793
794 //      text[key_linepos + 1] = 0;
795
796         // prestep if horizontally scrolling
797         if (key_linepos >= con_linewidth)
798                 text += 1 + key_linepos - con_linewidth;
799
800         // draw it
801         _Con_DrawString(0, con_vislines - 16, text, con_linewidth, 8, 8, 0);
802
803         // remove cursor
804 //      key_lines[edit_line][key_linepos] = 0;
805 }
806
807
808 /*
809 ================
810 Con_DrawNotify
811
812 Draws the last few lines of output transparently over the game top
813 ================
814 */
815 void Con_DrawNotify (void)
816 {
817         int             x, v;
818         char    *text;
819         int             i;
820         float   time;
821         extern char chat_buffer[];
822         char    temptext[256];
823
824         if (con_notify.integer < 0)
825                 Cvar_SetValueQuick(&con_notify, 0);
826         if (con_notify.integer > MAX_NOTIFYLINES)
827                 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
828         if (gamemode == GAME_TRANSFUSION)
829                 v = 8;
830         else
831                 v = 0;
832         for (i= con_current-con_notify.integer+1 ; i<=con_current ; i++)
833         {
834                 if (i < 0)
835                         continue;
836                 time = con_times[i % con_notify.integer];
837                 if (time == 0)
838                         continue;
839                 time = cl.time - time;
840                 if (time > con_notifytime.value)
841                         continue;
842                 text = con_text + (i % con_totallines)*con_linewidth;
843
844                 if (gamemode == GAME_NEXUIZ) {
845                         int linewidth;
846
847                         for (linewidth = con_linewidth; linewidth && text[linewidth-1] == ' '; linewidth--);
848                         x = (vid.conwidth - linewidth * 8) / 2;
849                 } else
850                         x = 0;
851
852                 _Con_DrawString(x, v, text, con_linewidth, 8, 8, 0);
853
854                 v += 8;
855         }
856
857
858         if (key_dest == key_message)
859         {
860                 x = 0;
861
862                 // LordHavoc: speedup, and other improvements
863                 if (chat_team)
864                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
865                 else
866                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
867                 while (strlen(temptext) >= (size_t) con_linewidth)
868                 {
869                         _Con_DrawString (0, v, temptext, con_linewidth, 8, 8, 0);
870                         strcpy(temptext, &temptext[con_linewidth]);
871                         v += 8;
872                 }
873                 if (strlen(temptext) > 0)
874                 {
875                         _Con_DrawString (0, v, temptext, 0, 8, 8, 0);
876                         v += 8;
877                 }
878         }
879 }
880
881 /*
882 ================
883 Con_DrawConsole
884
885 Draws the console with the solid background
886 The typing input line at the bottom should only be drawn if typing is allowed
887 ================
888 */
889 extern char engineversion[40];
890 void Con_DrawConsole (int lines)
891 {
892         int i, y, rows, j;
893         char *text;
894
895         if (lines <= 0)
896                 return;
897
898 // draw the background
899         if (scr_conbrightness.value >= 0.01f)
900                 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);
901         else
902                 DrawQ_Fill(0, lines - vid.conheight, vid.conwidth, vid.conheight, 0, 0, 0, scr_conalpha.value, 0);
903         DrawQ_String(vid.conwidth - strlen(engineversion) * 8 - 8, lines - 8, engineversion, 0, 8, 8, 1, 0, 0, 1, 0);
904
905 // draw the text
906         con_vislines = lines;
907
908         rows = (lines-16)>>3;           // rows of text to draw
909         y = lines - 16 - (rows<<3);     // may start slightly negative
910     
911         for (i = con_current - rows + 1;i <= con_current;i++, y += 8)
912         {
913                 j = max(i - con_backscroll, 0);
914                 text = con_text + (j % con_totallines)*con_linewidth;
915
916                 _Con_DrawString(0, y, text, con_linewidth, 8, 8, 0);
917         }
918
919 // draw the input prompt, user text, and cursor if desired
920         Con_DrawInput ();
921 }
922
923 /*
924         Con_DisplayList
925
926         New function for tab-completion system
927         Added by EvilTypeGuy
928         MEGA Thanks to Taniwha
929
930 */
931 void Con_DisplayList(const char **list)
932 {
933         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
934         const char **walk = list;
935
936         while (*walk) {
937                 len = strlen(*walk);
938                 if (len > maxlen)
939                         maxlen = len;
940                 walk++;
941         }
942         maxlen += 1;
943
944         while (*list) {
945                 len = strlen(*list);
946                 if (pos + maxlen >= width) {
947                         Con_Print("\n");
948                         pos = 0;
949                 }
950
951                 Con_Print(*list);
952                 for (i = 0; i < (maxlen - len); i++)
953                         Con_Print(" ");
954
955                 pos += maxlen;
956                 list++;
957         }
958
959         if (pos)
960                 Con_Print("\n\n");
961 }
962
963 /*
964         Con_CompleteCommandLine
965
966         New function for tab-completion system
967         Added by EvilTypeGuy
968         Thanks to Fett erich@heintz.com
969         Thanks to taniwha
970
971 */
972 void Con_CompleteCommandLine (void)
973 {
974         const char *cmd = "", *s;
975         const char **list[3] = {0, 0, 0};
976         int c, v, a, i, cmd_len;
977
978         s = key_lines[edit_line] + 1;
979         // Count number of possible matches
980         c = Cmd_CompleteCountPossible(s);
981         v = Cvar_CompleteCountPossible(s);
982         a = Cmd_CompleteAliasCountPossible(s);
983
984         if (!(c + v + a))       // No possible matches
985                 return;
986
987         if (c + v + a == 1) {
988                 if (c)
989                         list[0] = Cmd_CompleteBuildList(s);
990                 else if (v)
991                         list[0] = Cvar_CompleteBuildList(s);
992                 else
993                         list[0] = Cmd_CompleteAliasBuildList(s);
994                 cmd = *list[0];
995                 cmd_len = strlen (cmd);
996         } else {
997                 if (c)
998                         cmd = *(list[0] = Cmd_CompleteBuildList(s));
999                 if (v)
1000                         cmd = *(list[1] = Cvar_CompleteBuildList(s));
1001                 if (a)
1002                         cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
1003
1004                 cmd_len = strlen (s);
1005                 do {
1006                         for (i = 0; i < 3; i++) {
1007                                 char ch = cmd[cmd_len];
1008                                 const char **l = list[i];
1009                                 if (l) {
1010                                         while (*l && (*l)[cmd_len] == ch)
1011                                                 l++;
1012                                         if (*l)
1013                                                 break;
1014                                 }
1015                         }
1016                         if (i == 3)
1017                                 cmd_len++;
1018                 } while (i == 3);
1019                 // 'quakebar'
1020                 Con_Print("\n\35");
1021                 for (i = 0; i < con_linewidth - 4; i++)
1022                         Con_Print("\36");
1023                 Con_Print("\37\n");
1024
1025                 // Print Possible Commands
1026                 if (c) {
1027                         Con_Printf("%i possible command%s\n", c, (c > 1) ? "s: " : ":");
1028                         Con_DisplayList(list[0]);
1029                 }
1030
1031                 if (v) {
1032                         Con_Printf("%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
1033                         Con_DisplayList(list[1]);
1034                 }
1035
1036                 if (a) {
1037                         Con_Printf("%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
1038                         Con_DisplayList(list[2]);
1039                 }
1040         }
1041
1042         if (cmd) {
1043                 strncpy(key_lines[edit_line] + 1, cmd, cmd_len);
1044                 key_linepos = cmd_len + 1;
1045                 if (c + v + a == 1) {
1046                         key_lines[edit_line][key_linepos] = ' ';
1047                         key_linepos++;
1048                 }
1049                 key_lines[edit_line][key_linepos] = 0;
1050         }
1051         for (i = 0; i < 3; i++)
1052                 if (list[i])
1053                         Mem_Free((void *)list[i]);
1054 }
1055