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