]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - console.c
-Now a server is never pinged more than once.
[xonotic/darkplaces.git] / console.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20 // console.c
21
22 #if !defined(WIN32) || defined(__MINGW32__)
23 # include <unistd.h>
24 #endif
25 #include <time.h>
26 #include "quakedef.h"
27
28 int con_linewidth;
29
30 float con_cursorspeed = 4;
31
32 #define         CON_TEXTSIZE    131072
33
34 // total lines in console scrollback
35 int con_totallines;
36 // lines up from bottom to display
37 int con_backscroll;
38 // where next message will be printed
39 int con_current;
40 // offset in current line for next print
41 int con_x;
42 char *con_text = 0;
43
44 //seconds
45 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3"};
46 cvar_t con_notify = {CVAR_SAVE, "con_notify","4"};
47
48 #define MAX_NOTIFYLINES 32
49 // cl.time time the line was generated for transparent notify lines
50 float con_times[MAX_NOTIFYLINES];
51
52 int con_vislines;
53
54 #define MAXCMDLINE      256
55 extern char key_lines[32][MAXCMDLINE];
56 extern int edit_line;
57 extern int key_linepos;
58 extern int key_insert;
59
60
61 qboolean con_initialized;
62
63 mempool_t *console_mempool;
64
65
66 /*
67 ==============================================================================
68
69 LOGGING
70
71 ==============================================================================
72 */
73
74 cvar_t log_file = {0, "log_file",""};
75 char crt_log_file [MAX_OSPATH] = "";
76 qfile_t* logfile = NULL;
77
78 qbyte* logqueue = NULL;
79 size_t logq_ind = 0;
80 size_t logq_size = 0;
81
82 void Log_ConPrint (const char *msg);
83
84 /*
85 ====================
86 Log_Timestamp
87 ====================
88 */
89 const char* Log_Timestamp (const char *desc)
90 {
91         static char timestamp [128];
92         time_t crt_time;
93         const struct tm *crt_tm;
94         char timestring [64];
95
96         // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
97         time (&crt_time);
98         crt_tm = localtime (&crt_time);
99         strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
100
101         if (desc != NULL)
102                 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, 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, false);
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                 txt++;
460         }
461         else if (txt[0] == 2)
462         {
463                 mask = 128;             // go to colored text
464                 txt++;
465         }
466         else
467                 mask = 0;
468
469
470         while ( (c = *txt) )
471         {
472         // count word length
473                 for (l=0 ; l< con_linewidth ; l++)
474                         if ( txt[l] <= ' ')
475                                 break;
476
477         // word wrap
478                 if (l != con_linewidth && (con_x + l > con_linewidth) )
479                         con_x = 0;
480
481                 txt++;
482
483                 if (cr)
484                 {
485                         con_current--;
486                         cr = false;
487                 }
488
489
490                 if (!con_x)
491                 {
492                         Con_Linefeed ();
493                 // mark time for transparent overlay
494                         if (con_current >= 0)
495                         {
496                                 if (con_notify.integer < 0)
497                                         Cvar_SetValueQuick(&con_notify, 0);
498                                 if (con_notify.integer > MAX_NOTIFYLINES)
499                                         Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
500                                 if (con_notify.integer > 0)
501                                         con_times[con_current % con_notify.integer] = cl.time;
502                         }
503                 }
504
505                 switch (c)
506                 {
507                 case '\n':
508                         con_x = 0;
509                         break;
510
511                 case '\r':
512                         con_x = 0;
513                         cr = 1;
514                         break;
515
516                 default:        // display character and advance
517                         y = con_current % con_totallines;
518                         con_text[y*con_linewidth+con_x] = c | mask;
519                         con_x++;
520                         if (con_x >= con_linewidth)
521                                 con_x = 0;
522                         break;
523                 }
524
525         }
526
527         if( txt[0] == 1 ) {
528                 // play talk wav
529                 S_LocalSound ("sound/misc/talk.wav");
530         }
531 }
532
533 /* The translation table between the graphical font and plain ASCII  --KB */
534 static char qfont_table[256] = {
535         '\0', '#',  '#',  '#',  '#',  '.',  '#',  '#',
536         '#',  9,    10,   '#',  ' ',  13,   '.',  '.',
537         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
538         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
539         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
540         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
541         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
542         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
543         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
544         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
545         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
546         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
547         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
548         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
549         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
550         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<',
551
552         '<',  '=',  '>',  '#',  '#',  '.',  '#',  '#',
553         '#',  '#',  ' ',  '#',  ' ',  '>',  '.',  '.',
554         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
555         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
556         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
557         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
558         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
559         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
560         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
561         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
562         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
563         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
564         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
565         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
566         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
567         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<'
568 };
569
570 /*
571 ================
572 Con_Print
573
574 Prints to all appropriate console targets, and adds timestamps
575 ================
576 */
577 extern cvar_t timestamps;
578 extern cvar_t timeformat;
579 extern qboolean sys_nostdout;
580 void Con_Print(const char *msg)
581 {
582         static int index = 0;
583         static char line[16384];
584
585         for (;*msg;msg++)
586         {
587                 if (index == 0)
588                 {
589                         // if this is the beginning of a new line, print timestamp
590                         char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
591                         // special color codes for chat messages must always come first
592                         // for Con_PrintToHistory to work properly
593                         if (*msg <= 2)
594                                 line[index++] = *msg++;
595                         // store timestamp
596                         for (;*timestamp;index++, timestamp++)
597                                 if (index < sizeof(line) - 2)
598                                         line[index] = *timestamp;
599                 }
600                 // append the character
601                 line[index++] = *msg;
602                 // if this is a newline character, we have a complete line to print
603                 if (*msg == '\n' || index >= 16000)
604                 {
605                         // terminate the line
606                         line[index] = 0;
607                         // send to log file
608                         Log_ConPrint(line);
609                         // send to scrollable buffer
610                         if (con_initialized && cls.state != ca_dedicated)
611                                 Con_PrintToHistory(line);
612                         // send to terminal or dedicated server window
613                         if (!sys_nostdout)
614                         {
615                                 unsigned char *p;
616                                 for (p = (unsigned char *) line;*p; p++)
617                                         *p = qfont_table[*p];
618                                 Sys_PrintToTerminal(line);
619                         }
620                         // empty the line buffer
621                         index = 0;
622                 }
623         }
624 }
625
626
627 // LordHavoc: increased from 4096 to 16384
628 #define MAXPRINTMSG     16384
629
630 /*
631 ================
632 Con_Printf
633
634 Prints to all appropriate console targets
635 ================
636 */
637 void Con_Printf(const char *fmt, ...)
638 {
639         va_list argptr;
640         char msg[MAXPRINTMSG];
641
642         va_start(argptr,fmt);
643         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
644         va_end(argptr);
645
646         Con_Print(msg);
647 }
648
649 /*
650 ================
651 Con_DPrint
652
653 A Con_Print that only shows up if the "developer" cvar is set
654 ================
655 */
656 void Con_DPrint(const char *msg)
657 {
658         if (!developer.integer)
659                 return;                 // don't confuse non-developers with techie stuff...
660         Con_Print(msg);
661 }
662
663 /*
664 ================
665 Con_DPrintf
666
667 A Con_Printf that only shows up if the "developer" cvar is set
668 ================
669 */
670 void Con_DPrintf(const char *fmt, ...)
671 {
672         va_list argptr;
673         char msg[MAXPRINTMSG];
674
675         if (!developer.integer)
676                 return;                 // don't confuse non-developers with techie stuff...
677
678         va_start(argptr,fmt);
679         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
680         va_end(argptr);
681
682         Con_Print(msg);
683 }
684
685
686 /*
687 ==============================================================================
688
689 DRAWING
690
691 ==============================================================================
692 */
693
694 static vec4_t _con_colors[] =
695 {
696         {1.0, 1.0, 1.0, 1.0},
697         {1.0, 0.0, 0.0, 1.0},
698         {0.0, 1.0, 0.0, 1.0},
699         {0.0, 0.0, 1.0, 1.0},
700         {1.0, 1.0, 0.0, 1.0},
701         {0.0, 1.0, 1.0, 1.0},
702         {1.0, 0.0, 1.0, 1.0},
703         {0.1, 0.1, 0.1, 1.0}
704 };
705
706 #define _con_colors_count       (sizeof(_con_colors) / sizeof(vec3_t))
707 #define _con_color_tag          '^'
708
709 // color is read and changed in the end
710 static void _Con_DrawString( float x, float y, const char *text, int maxlen, float scalex, float scaley, int flags )
711 {
712         vec_t *color;
713         const char *first, *last;
714         int len;
715
716         color = _con_colors[0];
717         if( maxlen < 1)
718                 len = strlen( text );
719         else
720                 len = min( maxlen, (signed) strlen( text ));
721
722     first = last = text;
723         while( 1 ) {
724                 // iterate until we get the next color tag or reach the end of the text part to draw
725                 for( ; len && *last != _con_color_tag ; len--, last++ )
726                         ;
727                 // only draw the partial string if we have read anything
728                 if( last != first ) {
729                         // draw the string
730                         DrawQ_String( x, y, first, last - first, scalex, scaley, color[0], color[1], color[2], color[3], flags );
731                         // update x to be at the new start position
732                         x += (last - first) * scalex;
733                         // if we have reached the end, we have finished
734                         if( !len )
735                                 break;
736                 }
737                 first = last;
738                 // jump over the tag
739                 last++;
740                 len--;
741                 if( len && '0' <= *last && *last <= '9' ) {
742                         int index = 0;
743
744                         while( '0' <= *last && *last <= '9' && len ) {
745                                 index = index * 10 + *last - '0';
746                                 if( index < _con_colors_count ) {
747                                         last++;
748                                         len--;
749                                 } else {
750                                         index /= 10;
751                                         break;
752                                 }
753                         }
754
755                         color = _con_colors[index];
756                         // we dont want to display the color tag and the color index
757                         first = last;
758                 }
759         }
760 }
761
762 /*
763 ================
764 Con_DrawInput
765
766 The input line scrolls horizontally if typing goes beyond the right edge
767
768 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
769 ================
770 */
771 void Con_DrawInput (void)
772 {
773         int             y;
774         int             i;
775         char editlinecopy[257], *text;
776
777         if (!key_consoleactive)
778                 return;         // don't draw anything
779
780         text = strcpy(editlinecopy, key_lines[edit_line]);
781
782         // Advanced Console Editing by Radix radix@planetquake.com
783         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
784         // use strlen of edit_line instead of key_linepos to allow editing
785         // of early characters w/o erasing
786
787         y = strlen(text);
788
789 // fill out remainder with spaces
790         for (i = y; i < 256; i++)
791                 text[i] = ' ';
792
793         // add the cursor frame
794         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
795                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
796
797 //      text[key_linepos + 1] = 0;
798
799         // prestep if horizontally scrolling
800         if (key_linepos >= con_linewidth)
801                 text += 1 + key_linepos - con_linewidth;
802
803         // draw it
804         _Con_DrawString(0, con_vislines - 16, text, con_linewidth, 8, 8, 0);
805
806         // remove cursor
807 //      key_lines[edit_line][key_linepos] = 0;
808 }
809
810
811 /*
812 ================
813 Con_DrawNotify
814
815 Draws the last few lines of output transparently over the game top
816 ================
817 */
818 void Con_DrawNotify (void)
819 {
820         int             x, v;
821         char    *text;
822         int             i;
823         float   time;
824         extern char chat_buffer[];
825         char    temptext[256];
826
827         if (con_notify.integer < 0)
828                 Cvar_SetValueQuick(&con_notify, 0);
829         if (con_notify.integer > MAX_NOTIFYLINES)
830                 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
831         if (gamemode == GAME_TRANSFUSION)
832                 v = 8;
833         else
834                 v = 0;
835         for (i= con_current-con_notify.integer+1 ; i<=con_current ; i++)
836         {
837                 if (i < 0)
838                         continue;
839                 time = con_times[i % con_notify.integer];
840                 if (time == 0)
841                         continue;
842                 time = cl.time - time;
843                 if (time > con_notifytime.value)
844                         continue;
845                 text = con_text + (i % con_totallines)*con_linewidth;
846
847                 if (gamemode == GAME_NEXUIZ) {
848                         int linewidth;
849
850                         for (linewidth = con_linewidth; linewidth && text[linewidth-1] == ' '; linewidth--);
851                         x = (vid.conwidth - linewidth * 8) / 2;
852                 } else
853                         x = 0;
854
855                 _Con_DrawString(x, v, text, con_linewidth, 8, 8, 0);
856
857                 v += 8;
858         }
859
860
861         if (key_dest == key_message)
862         {
863                 x = 0;
864
865                 // LordHavoc: speedup, and other improvements
866                 if (chat_team)
867                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
868                 else
869                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
870                 while (strlen(temptext) >= (size_t) con_linewidth)
871                 {
872                         _Con_DrawString (0, v, temptext, con_linewidth, 8, 8, 0);
873                         strcpy(temptext, &temptext[con_linewidth]);
874                         v += 8;
875                 }
876                 if (strlen(temptext) > 0)
877                 {
878                         _Con_DrawString (0, v, temptext, 0, 8, 8, 0);
879                         v += 8;
880                 }
881         }
882 }
883
884 /*
885 ================
886 Con_DrawConsole
887
888 Draws the console with the solid background
889 The typing input line at the bottom should only be drawn if typing is allowed
890 ================
891 */
892 extern char engineversion[40];
893 void Con_DrawConsole (int lines)
894 {
895         int i, y, rows, j;
896         char *text;
897
898         if (lines <= 0)
899                 return;
900
901 // draw the background
902         if (scr_conbrightness.value >= 0.01f)
903                 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);
904         else
905                 DrawQ_Fill(0, lines - vid.conheight, vid.conwidth, vid.conheight, 0, 0, 0, scr_conalpha.value, 0);
906         DrawQ_String(vid.conwidth - strlen(engineversion) * 8 - 8, lines - 8, engineversion, 0, 8, 8, 1, 0, 0, 1, 0);
907
908 // draw the text
909         con_vislines = lines;
910
911         rows = (lines-16)>>3;           // rows of text to draw
912         y = lines - 16 - (rows<<3);     // may start slightly negative
913
914         for (i = con_current - rows + 1;i <= con_current;i++, y += 8)
915         {
916                 j = max(i - con_backscroll, 0);
917                 text = con_text + (j % con_totallines)*con_linewidth;
918
919                 _Con_DrawString(0, y, text, con_linewidth, 8, 8, 0);
920         }
921
922 // draw the input prompt, user text, and cursor if desired
923         Con_DrawInput ();
924 }
925
926 /*
927         Con_DisplayList
928
929         New function for tab-completion system
930         Added by EvilTypeGuy
931         MEGA Thanks to Taniwha
932
933 */
934 void Con_DisplayList(const char **list)
935 {
936         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
937         const char **walk = list;
938
939         while (*walk) {
940                 len = strlen(*walk);
941                 if (len > maxlen)
942                         maxlen = len;
943                 walk++;
944         }
945         maxlen += 1;
946
947         while (*list) {
948                 len = strlen(*list);
949                 if (pos + maxlen >= width) {
950                         Con_Print("\n");
951                         pos = 0;
952                 }
953
954                 Con_Print(*list);
955                 for (i = 0; i < (maxlen - len); i++)
956                         Con_Print(" ");
957
958                 pos += maxlen;
959                 list++;
960         }
961
962         if (pos)
963                 Con_Print("\n\n");
964 }
965
966 /*
967         Con_CompleteCommandLine
968
969         New function for tab-completion system
970         Added by EvilTypeGuy
971         Thanks to Fett erich@heintz.com
972         Thanks to taniwha
973
974 */
975 void Con_CompleteCommandLine (void)
976 {
977         const char *cmd = "", *s;
978         const char **list[3] = {0, 0, 0};
979         int c, v, a, i, cmd_len;
980
981         s = key_lines[edit_line] + 1;
982         // Count number of possible matches
983         c = Cmd_CompleteCountPossible(s);
984         v = Cvar_CompleteCountPossible(s);
985         a = Cmd_CompleteAliasCountPossible(s);
986
987         if (!(c + v + a))       // No possible matches
988                 return;
989
990         if (c + v + a == 1) {
991                 if (c)
992                         list[0] = Cmd_CompleteBuildList(s);
993                 else if (v)
994                         list[0] = Cvar_CompleteBuildList(s);
995                 else
996                         list[0] = Cmd_CompleteAliasBuildList(s);
997                 cmd = *list[0];
998                 cmd_len = strlen (cmd);
999         } else {
1000                 if (c)
1001                         cmd = *(list[0] = Cmd_CompleteBuildList(s));
1002                 if (v)
1003                         cmd = *(list[1] = Cvar_CompleteBuildList(s));
1004                 if (a)
1005                         cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
1006
1007                 cmd_len = strlen (s);
1008                 do {
1009                         for (i = 0; i < 3; i++) {
1010                                 char ch = cmd[cmd_len];
1011                                 const char **l = list[i];
1012                                 if (l) {
1013                                         while (*l && (*l)[cmd_len] == ch)
1014                                                 l++;
1015                                         if (*l)
1016                                                 break;
1017                                 }
1018                         }
1019                         if (i == 3)
1020                                 cmd_len++;
1021                 } while (i == 3);
1022                 // 'quakebar'
1023                 Con_Print("\n\35");
1024                 for (i = 0; i < con_linewidth - 4; i++)
1025                         Con_Print("\36");
1026                 Con_Print("\37\n");
1027
1028                 // Print Possible Commands
1029                 if (c) {
1030                         Con_Printf("%i possible command%s\n", c, (c > 1) ? "s: " : ":");
1031                         Con_DisplayList(list[0]);
1032                 }
1033
1034                 if (v) {
1035                         Con_Printf("%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
1036                         Con_DisplayList(list[1]);
1037                 }
1038
1039                 if (a) {
1040                         Con_Printf("%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
1041                         Con_DisplayList(list[2]);
1042                 }
1043         }
1044
1045         if (cmd) {
1046                 strncpy(key_lines[edit_line] + 1, cmd, cmd_len);
1047                 key_linepos = cmd_len + 1;
1048                 if (c + v + a == 1) {
1049                         key_lines[edit_line][key_linepos] = ' ';
1050                         key_linepos++;
1051                 }
1052                 key_lines[edit_line][key_linepos] = 0;
1053         }
1054         for (i = 0; i < 3; i++)
1055                 if (list[i])
1056                         Mem_Free((void *)list[i]);
1057 }
1058