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