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