]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - console.c
added tab-completion of map/changelevel command + maps command patch from [515],...
[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 unsigned char* 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                         size_t factor = ((logq_ind + len) / logq_size) + 1;
186                         unsigned char* newqueue;
187
188                         logq_size *= factor;
189                         newqueue = (unsigned char *)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 //[515]: the simplest command ever
360 //LordHavoc: not so simple after I made it print usage...
361 static void Con_Maps_f (void)
362 {
363         if (Cmd_Argc() > 2)
364         {
365                 Con_Printf("usage: maps [mapnameprefix]\n");
366                 return;
367         }
368         else if (Cmd_Argc() == 2)
369                 GetMapList(Cmd_Argv(1), NULL, 0);
370         else
371                 GetMapList("", NULL, 0);
372 }
373
374 /*
375 ================
376 Con_Init
377 ================
378 */
379 void Con_Init (void)
380 {
381         memset (con_text, ' ', CON_TEXTSIZE);
382         con_linewidth = 80;
383         con_totallines = CON_TEXTSIZE / con_linewidth;
384
385         // Allocate a log queue
386         logq_size = MAX_INPUTLINE;
387         logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
388         logq_ind = 0;
389
390         Cvar_RegisterVariable (&log_file);
391
392         // support for the classic Quake option
393 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
394         if (COM_CheckParm ("-condebug") != 0)
395                 Cvar_SetQuick (&log_file, "qconsole.log");
396 }
397
398 void Con_Init_Commands (void)
399 {
400         // register our cvars
401         Cvar_RegisterVariable (&con_notifytime);
402         Cvar_RegisterVariable (&con_notify);
403
404         // register our commands
405         Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
406         Cmd_AddCommand ("messagemode", Con_MessageMode_f);
407         Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
408         Cmd_AddCommand ("clear", Con_Clear_f);
409         Cmd_AddCommand ("maps", Con_Maps_f);                                                    // By [515]
410
411         con_initialized = true;
412         Con_Print("Console initialized.\n");
413 }
414
415
416 /*
417 ===============
418 Con_Linefeed
419 ===============
420 */
421 void Con_Linefeed (void)
422 {
423         if (con_backscroll)
424                 con_backscroll++;
425
426         con_x = 0;
427         con_current++;
428         memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth);
429 }
430
431 /*
432 ================
433 Con_PrintToHistory
434
435 Handles cursor positioning, line wrapping, etc
436 All console printing must go through this in order to be displayed
437 If no console is visible, the notify window will pop up.
438 ================
439 */
440 void Con_PrintToHistory(const char *txt, int mask)
441 {
442         int y, c, l;
443         static int cr;
444
445         while ( (c = *txt) )
446         {
447         // count word length
448                 for (l=0 ; l< con_linewidth ; l++)
449                         if ( txt[l] <= ' ')
450                                 break;
451
452         // word wrap
453                 if (l != con_linewidth && (con_x + l > con_linewidth) )
454                         con_x = 0;
455
456                 txt++;
457
458                 if (cr)
459                 {
460                         con_current--;
461                         cr = false;
462                 }
463
464
465                 if (!con_x)
466                 {
467                         Con_Linefeed ();
468                 // mark time for transparent overlay
469                         if (con_current >= 0)
470                         {
471                                 if (con_notify.integer < 0)
472                                         Cvar_SetValueQuick(&con_notify, 0);
473                                 if (con_notify.integer > MAX_NOTIFYLINES)
474                                         Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
475                                 if (con_notify.integer > 0)
476                                         con_times[con_current % con_notify.integer] = cl.time;
477                         }
478                 }
479
480                 switch (c)
481                 {
482                 case '\n':
483                         con_x = 0;
484                         break;
485
486                 case '\r':
487                         con_x = 0;
488                         cr = 1;
489                         break;
490
491                 default:        // display character and advance
492                         y = con_current % con_totallines;
493                         con_text[y*con_linewidth+con_x] = c | mask;
494                         con_x++;
495                         if (con_x >= con_linewidth)
496                                 con_x = 0;
497                         break;
498                 }
499
500         }
501 }
502
503 /* The translation table between the graphical font and plain ASCII  --KB */
504 static char qfont_table[256] = {
505         '\0', '#',  '#',  '#',  '#',  '.',  '#',  '#',
506         '#',  9,    10,   '#',  ' ',  13,   '.',  '.',
507         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
508         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
509         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
510         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
511         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
512         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
513         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
514         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
515         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
516         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
517         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
518         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
519         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
520         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<',
521
522         '<',  '=',  '>',  '#',  '#',  '.',  '#',  '#',
523         '#',  '#',  ' ',  '#',  ' ',  '>',  '.',  '.',
524         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
525         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
526         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
527         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
528         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
529         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
530         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
531         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
532         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
533         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
534         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
535         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
536         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
537         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<'
538 };
539
540 /*
541 ================
542 Con_Print
543
544 Prints to all appropriate console targets, and adds timestamps
545 ================
546 */
547 extern cvar_t timestamps;
548 extern cvar_t timeformat;
549 extern qboolean sys_nostdout;
550 void Con_Print(const char *msg)
551 {
552         int mask = 0;
553         static int index = 0;
554         static char line[MAX_INPUTLINE];
555
556         for (;*msg;msg++)
557         {
558                 if (index == 0)
559                 {
560                         // if this is the beginning of a new line, print timestamp
561                         const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
562                         // reset the color
563                         // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
564                         line[index++] = STRING_COLOR_TAG;
565                         // assert( STRING_COLOR_DEFAULT < 10 )
566                         line[index++] = STRING_COLOR_DEFAULT + '0';
567                         // special color codes for chat messages must always come first
568                         // for Con_PrintToHistory to work properly
569                         if (*msg <= 2)
570                         {
571                                 if (*msg == 1)
572                                 {
573                                         // play talk wav
574                                         S_LocalSound ("sound/misc/talk.wav");
575                                 }
576                                 //if (gamemode == GAME_NEXUIZ)
577                                 //{
578                                         line[index++] = STRING_COLOR_TAG;
579                                         line[index++] = '3';
580                                 //}
581                                 //else
582                                 //{
583                                 //      // go to colored text
584                                 //      mask = 128;
585                                 //}
586                                 msg++;
587                         }
588                         // store timestamp
589                         for (;*timestamp;index++, timestamp++)
590                                 if (index < (int)sizeof(line) - 2)
591                                         line[index] = *timestamp;
592                 }
593                 // append the character
594                 line[index++] = *msg;
595                 // if this is a newline character, we have a complete line to print
596                 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
597                 {
598                         // terminate the line
599                         line[index] = 0;
600                         // send to log file
601                         Log_ConPrint(line);
602                         // send to scrollable buffer
603                         if (con_initialized && cls.state != ca_dedicated)
604                                 Con_PrintToHistory(line, mask);
605                         // send to terminal or dedicated server window
606                         if (!sys_nostdout)
607                         {
608                                 unsigned char *p;
609                                 for (p = (unsigned char *) line;*p; p++)
610                                         *p = qfont_table[*p];
611                                 Sys_PrintToTerminal(line);
612                         }
613                         // empty the line buffer
614                         index = 0;
615                 }
616         }
617 }
618
619
620 /*
621 ================
622 Con_Printf
623
624 Prints to all appropriate console targets
625 ================
626 */
627 void Con_Printf(const char *fmt, ...)
628 {
629         va_list argptr;
630         char msg[MAX_INPUTLINE];
631
632         va_start(argptr,fmt);
633         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
634         va_end(argptr);
635
636         Con_Print(msg);
637 }
638
639 /*
640 ================
641 Con_DPrint
642
643 A Con_Print that only shows up if the "developer" cvar is set
644 ================
645 */
646 void Con_DPrint(const char *msg)
647 {
648         if (!developer.integer)
649                 return;                 // don't confuse non-developers with techie stuff...
650         Con_Print(msg);
651 }
652
653 /*
654 ================
655 Con_DPrintf
656
657 A Con_Printf that only shows up if the "developer" cvar is set
658 ================
659 */
660 void Con_DPrintf(const char *fmt, ...)
661 {
662         va_list argptr;
663         char msg[MAX_INPUTLINE];
664
665         if (!developer.integer)
666                 return;                 // don't confuse non-developers with techie stuff...
667
668         va_start(argptr,fmt);
669         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
670         va_end(argptr);
671
672         Con_Print(msg);
673 }
674
675
676 /*
677 ==============================================================================
678
679 DRAWING
680
681 ==============================================================================
682 */
683
684 /*
685 ================
686 Con_DrawInput
687
688 The input line scrolls horizontally if typing goes beyond the right edge
689
690 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
691 ================
692 */
693 void Con_DrawInput (void)
694 {
695         int             y;
696         int             i;
697         char editlinecopy[MAX_INPUTLINE+1], *text;
698
699         if (!key_consoleactive)
700                 return;         // don't draw anything
701
702         text = strcpy(editlinecopy, key_lines[edit_line]);
703
704         // Advanced Console Editing by Radix radix@planetquake.com
705         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
706         // use strlen of edit_line instead of key_linepos to allow editing
707         // of early characters w/o erasing
708
709         y = (int)strlen(text);
710
711 // fill out remainder with spaces
712         for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
713                 text[i] = ' ';
714
715         // add the cursor frame
716         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
717                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
718
719 //      text[key_linepos + 1] = 0;
720
721         // prestep if horizontally scrolling
722         if (key_linepos >= con_linewidth)
723                 text += 1 + key_linepos - con_linewidth;
724
725         // draw it
726         DrawQ_ColoredString(0, con_vislines - 16, text, con_linewidth, 8, 8, 1.0, 1.0, 1.0, 1.0, 0, NULL );
727
728         // remove cursor
729 //      key_lines[edit_line][key_linepos] = 0;
730 }
731
732
733 /*
734 ================
735 Con_DrawNotify
736
737 Draws the last few lines of output transparently over the game top
738 ================
739 */
740 void Con_DrawNotify (void)
741 {
742         int             x, v;
743         char    *text;
744         int             i;
745         float   time;
746         char    temptext[MAX_INPUTLINE];
747         int colorindex = -1; //-1 for default
748
749         if (con_notify.integer < 0)
750                 Cvar_SetValueQuick(&con_notify, 0);
751         if (con_notify.integer > MAX_NOTIFYLINES)
752                 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
753         if (gamemode == GAME_TRANSFUSION)
754                 v = 8;
755         else
756                 v = 0;
757         for (i= con_current-con_notify.integer+1 ; i<=con_current ; i++)
758         {
759
760                 if (i < 0)
761                         continue;
762                 time = con_times[i % con_notify.integer];
763                 if (time == 0)
764                         continue;
765                 time = cl.time - time;
766                 if (time > con_notifytime.value)
767                         continue;
768                 text = con_text + (i % con_totallines)*con_linewidth;
769
770                 if (gamemode == GAME_NEXUIZ) {
771                         int linewidth;
772
773                         for (linewidth = con_linewidth; linewidth && text[linewidth-1] == ' '; linewidth--);
774                         x = (vid_conwidth.integer - linewidth * 8) / 2;
775                 } else
776                         x = 0;
777
778                 DrawQ_ColoredString( x, v, text, con_linewidth, 8, 8, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
779
780                 v += 8;
781         }
782
783
784         if (key_dest == key_message)
785         {
786                 int colorindex = -1;
787
788                 x = 0;
789
790                 // LordHavoc: speedup, and other improvements
791                 if (chat_team)
792                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
793                 else
794                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
795                 while ((int)strlen(temptext) >= con_linewidth)
796                 {
797                         DrawQ_ColoredString( 0, v, temptext, con_linewidth, 8, 8, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
798                         strcpy(temptext, &temptext[con_linewidth]);
799                         v += 8;
800                 }
801                 if (strlen(temptext) > 0)
802                 {
803                         DrawQ_ColoredString( 0, v, temptext, 0, 8, 8, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
804                         v += 8;
805                 }
806         }
807 }
808
809 /*
810 ================
811 Con_DrawConsole
812
813 Draws the console with the solid background
814 The typing input line at the bottom should only be drawn if typing is allowed
815 ================
816 */
817 void Con_DrawConsole (int lines)
818 {
819         int i, y, rows, j;
820         char *text;
821         int colorindex = -1;
822
823         if (lines <= 0)
824                 return;
825
826 // draw the background
827         if (scr_conbrightness.value >= 0.01f)
828                 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);
829         else
830                 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0, 0, 0, scr_conalpha.value, 0);
831         DrawQ_String(vid_conwidth.integer - strlen(engineversion) * 8 - 8, lines - 8, engineversion, 0, 8, 8, 1, 0, 0, 1, 0);
832
833 // draw the text
834         con_vislines = lines;
835
836         rows = (lines-16)>>3;           // rows of text to draw
837         y = lines - 16 - (rows<<3);     // may start slightly negative
838
839         for (i = con_current - rows + 1;i <= con_current;i++, y += 8)
840         {
841                 j = max(i - con_backscroll, 0);
842                 text = con_text + (j % con_totallines)*con_linewidth;
843
844                 DrawQ_ColoredString( 0, y, text, con_linewidth, 8, 8, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
845         }
846
847 // draw the input prompt, user text, and cursor if desired
848         Con_DrawInput ();
849 }
850
851 /*
852 GetMapList
853
854 Made by [515]
855 Prints not only map filename, but also
856 its format (q1/q2/q3/hl) and even its message
857 */
858 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
859 //LordHavoc: rewrote bsp type detection, added mcbsp support and rewrote message extraction to do proper worldspawn parsing
860 //LordHavoc: added .ent file loading, and redesigned error handling to still try the .ent file even if the map format is not recognized, this also eliminated one goto
861 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
862 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
863 {
864         fssearch_t      *t;
865         char            message[64];
866         int                     i, k, max, p, o, min;
867         unsigned char *len;
868         qfile_t         *f;
869         unsigned char buf[1024];
870
871         sprintf(message, "maps/%s*.bsp", s);
872         t = FS_Search(message, 1, true);
873         if(!t)
874                 return false;
875         if (t->numfilenames > 1)
876                 Con_Printf("^1 %i maps found :\n", t->numfilenames);
877         len = Z_Malloc(t->numfilenames);
878         min = 666;
879         for(max=i=0;i<t->numfilenames;i++)
880         {
881                 k = strlen(t->filenames[i]);
882                 k -= 9;
883                 if(max < k)
884                         max = k;
885                 else
886                 if(min > k)
887                         min = k;
888                 len[i] = k;
889         }
890         o = strlen(s);
891         for(i=0;i<t->numfilenames;i++)
892         {
893                 int lumpofs = 0, lumplen = 0;
894                 char *entities = NULL;
895                 const char *data = NULL;
896                 char keyname[64];
897                 char entfilename[MAX_QPATH];
898                 strcpy(message, "^1**ERROR**^7");
899                 f = FS_Open(t->filenames[i], "rb", true, false);
900                 if(f)
901                 {
902                         memset(buf, 0, 1024);
903                         FS_Read(f, buf, 1024);
904                         if (!memcmp(buf, "IBSP", 4))
905                         {
906                                 p = LittleLong(((int *)buf)[1]);
907                                 if (p == Q3BSPVERSION)
908                                 {
909                                         q3dheader_t *header = (q3dheader_t *)buf;
910                                         lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
911                                         lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
912                                 }
913                                 else if (p == Q2BSPVERSION)
914                                 {
915                                         q2dheader_t *header = (q2dheader_t *)buf;
916                                         lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
917                                         lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
918                                 }
919                         }
920                         else if (!memcmp(buf, "MCBSPpad", 8))
921                         {
922                                 p = LittleLong(((int *)buf)[2]);
923                                 if (p == MCBSPVERSION)
924                                 {
925                                         int numhulls = LittleLong(((int *)buf)[3]);
926                                         lumpofs = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+0]);
927                                         lumplen = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+1]);
928                                 }
929                         }
930                         else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
931                         {
932                                 dheader_t *header = (dheader_t *)buf;
933                                 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
934                                 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
935                         }
936                         else
937                                 p = 0;
938                         strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
939                         strcpy(entfilename + strlen(entfilename) - 4, ".ent");
940                         entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
941                         if (!entities && lumplen >= 10)
942                         {
943                                 FS_Seek(f, lumpofs, SEEK_SET);
944                                 entities = Z_Malloc(lumplen + 1);
945                                 FS_Read(f, entities, lumplen);
946                         }
947                         if (entities)
948                         {
949                                 // if there are entities to parse, a missing message key just
950                                 // means there is no title, so clear the message string now
951                                 message[0] = 0;
952                                 data = entities;
953                                 for (;;)
954                                 {
955                                         int l;
956                                         if (!COM_ParseToken(&data, false))
957                                                 break;
958                                         if (com_token[0] == '{')
959                                                 continue;
960                                         if (com_token[0] == '}')
961                                                 break;
962                                         // skip leading whitespace
963                                         for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
964                                         for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
965                                                 keyname[l] = com_token[k+l];
966                                         keyname[l] = 0;
967                                         if (!COM_ParseToken(&data, false))
968                                                 break;
969                                         if (developer.integer >= 2)
970                                                 Con_Printf("key: %s %s\n", keyname, com_token);
971                                         if (!strcmp(keyname, "message"))
972                                         {
973                                                 // get the message contents
974                                                 strlcpy(message, com_token, sizeof(message));
975                                                 break;
976                                         }
977                                 }
978                         }
979                 }
980                 if (entities)
981                         Z_Free(entities);
982                 if(f)
983                         FS_Close(f);
984                 *(t->filenames[i]+len[i]+5) = 0;
985                 switch(p)
986                 {
987                 case Q3BSPVERSION:      strcpy((char *)buf, "Q3");break;
988                 case Q2BSPVERSION:      strcpy((char *)buf, "Q2");break;
989                 case BSPVERSION:        strcpy((char *)buf, "Q1");break;
990                 case MCBSPVERSION:      strcpy((char *)buf, "MC");break;
991                 case 30:                        strcpy((char *)buf, "HL");break;
992                 default:                        strcpy((char *)buf, "??");break;
993                 }
994                 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
995         }
996         Con_Print("\n");
997         for(p=o;p<min;p++)
998         {
999                 k = *(t->filenames[0]+5+p);
1000                 for(i=1;i<t->numfilenames;i++)
1001                         if(*(t->filenames[i]+5+p) != k)
1002                                 goto endcomplete;
1003         }
1004 endcomplete:
1005         if(p > o)
1006         {
1007                 memset(completedname, 0, completednamebufferlength);
1008                 memcpy(completedname, (t->filenames[0]+5), p);
1009         }
1010         Z_Free(len);
1011         FS_FreeSearch(t);
1012         return p > o;
1013 }
1014
1015 /*
1016         Con_DisplayList
1017
1018         New function for tab-completion system
1019         Added by EvilTypeGuy
1020         MEGA Thanks to Taniwha
1021
1022 */
1023 void Con_DisplayList(const char **list)
1024 {
1025         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1026         const char **walk = list;
1027
1028         while (*walk) {
1029                 len = (int)strlen(*walk);
1030                 if (len > maxlen)
1031                         maxlen = len;
1032                 walk++;
1033         }
1034         maxlen += 1;
1035
1036         while (*list) {
1037                 len = (int)strlen(*list);
1038                 if (pos + maxlen >= width) {
1039                         Con_Print("\n");
1040                         pos = 0;
1041                 }
1042
1043                 Con_Print(*list);
1044                 for (i = 0; i < (maxlen - len); i++)
1045                         Con_Print(" ");
1046
1047                 pos += maxlen;
1048                 list++;
1049         }
1050
1051         if (pos)
1052                 Con_Print("\n\n");
1053 }
1054
1055 /*
1056         Con_CompleteCommandLine
1057
1058         New function for tab-completion system
1059         Added by EvilTypeGuy
1060         Thanks to Fett erich@heintz.com
1061         Thanks to taniwha
1062         Enhanced to tab-complete map names by [515]
1063
1064 */
1065 void Con_CompleteCommandLine (void)
1066 {
1067         const char *cmd = "", *s;
1068         const char **list[3] = {0, 0, 0};
1069         char s2[512];
1070         int c, v, a, i, cmd_len, pos, k;
1071
1072         //find what we want to complete
1073         pos = key_linepos;
1074         while(--pos)
1075         {
1076                 k = key_lines[edit_line][pos];
1077                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
1078                         break;
1079         }
1080         pos++;
1081
1082         s = key_lines[edit_line] + pos;
1083         strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2));    //save chars after cursor
1084         key_lines[edit_line][key_linepos] = 0;                                  //hide them
1085
1086         //maps search
1087         for(k=pos-1;k>2;k--)
1088                 if(key_lines[edit_line][k] != ' ')
1089                 {
1090                         if(key_lines[edit_line][k] == '\"' || key_lines[edit_line][k] == ';' || key_lines[edit_line][k] == '\'')
1091                                 break;
1092                         if      ((pos+k > 2 && !strncmp(key_lines[edit_line]+k-2, "map", 3))
1093                                 || (pos+k > 10 && !strncmp(key_lines[edit_line]+k-10, "changelevel", 11)))
1094                         {
1095                                 char t[MAX_QPATH];
1096                                 if (GetMapList(s, t, sizeof(t)))
1097                                 {
1098                                         i = strlen(t) - strlen(s);
1099                                         strcpy((char*)s, t);
1100                                         if(s2[0])       //add back chars after cursor
1101                                                 strcpy(&key_lines[edit_line][key_linepos], s2);
1102                                         key_linepos += i;
1103                                 }
1104                                 return;
1105                         }
1106                 }
1107
1108         // Count number of possible matches
1109         c = Cmd_CompleteCountPossible(s);
1110         v = Cvar_CompleteCountPossible(s);
1111         a = Cmd_CompleteAliasCountPossible(s);
1112
1113         if (!(c + v + a))       // No possible matches
1114         {
1115                 if(s2[0])
1116                         strcpy(&key_lines[edit_line][key_linepos], s2);
1117                 return;
1118         }
1119
1120         if (c + v + a == 1) {
1121                 if (c)
1122                         list[0] = Cmd_CompleteBuildList(s);
1123                 else if (v)
1124                         list[0] = Cvar_CompleteBuildList(s);
1125                 else
1126                         list[0] = Cmd_CompleteAliasBuildList(s);
1127                 cmd = *list[0];
1128                 cmd_len = (int)strlen (cmd);
1129         } else {
1130                 if (c)
1131                         cmd = *(list[0] = Cmd_CompleteBuildList(s));
1132                 if (v)
1133                         cmd = *(list[1] = Cvar_CompleteBuildList(s));
1134                 if (a)
1135                         cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
1136
1137                 cmd_len = (int)strlen (s);
1138                 do {
1139                         for (i = 0; i < 3; i++) {
1140                                 char ch = cmd[cmd_len];
1141                                 const char **l = list[i];
1142                                 if (l) {
1143                                         while (*l && (*l)[cmd_len] == ch)
1144                                                 l++;
1145                                         if (*l)
1146                                                 break;
1147                                 }
1148                         }
1149                         if (i == 3)
1150                                 cmd_len++;
1151                 } while (i == 3);
1152                 // 'quakebar'
1153                 Con_Print("\n\35");
1154                 for (i = 0; i < con_linewidth - 4; i++)
1155                         Con_Print("\36");
1156                 Con_Print("\37\n");
1157
1158                 // Print Possible Commands
1159                 if (c) {
1160                         Con_Printf("%i possible command%s\n", c, (c > 1) ? "s: " : ":");
1161                         Con_DisplayList(list[0]);
1162                 }
1163
1164                 if (v) {
1165                         Con_Printf("%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
1166                         Con_DisplayList(list[1]);
1167                 }
1168
1169                 if (a) {
1170                         Con_Printf("%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
1171                         Con_DisplayList(list[2]);
1172                 }
1173         }
1174
1175         if (cmd) {
1176                 strncpy(key_lines[edit_line] + pos, cmd, cmd_len);
1177                 key_linepos = cmd_len + pos;
1178                 if (c + v + a == 1) {
1179                         key_lines[edit_line][key_linepos] = ' ';
1180                         key_linepos++;
1181                 }
1182                 if(s2[0])
1183                         strcpy(&key_lines[edit_line][key_linepos], s2);
1184                 else
1185                         key_lines[edit_line][key_linepos] = 0;
1186         }
1187         else
1188                 if(s2[0])
1189                         strcpy(&key_lines[edit_line][key_linepos], s2);
1190         for (i = 0; i < 3; i++)
1191                 if (list[i])
1192                         Mem_Free((void *)list[i]);
1193 }
1194