]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - console.c
hush warnings about strcat and other redefinitions by undefining them first
[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 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
45 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show (0-32)"};
46 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};        //[515]: console text size in pixels
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 // used for server replies to rcon command
57 qboolean rcon_redirect = false;
58 int rcon_redirect_bufferpos = 0;
59 char rcon_redirect_buffer[1400];
60
61
62 /*
63 ==============================================================================
64
65 LOGGING
66
67 ==============================================================================
68 */
69
70 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
71 char crt_log_file [MAX_OSPATH] = "";
72 qfile_t* logfile = NULL;
73
74 unsigned char* logqueue = NULL;
75 size_t logq_ind = 0;
76 size_t logq_size = 0;
77
78 void Log_ConPrint (const char *msg);
79
80 /*
81 ====================
82 Log_Timestamp
83 ====================
84 */
85 const char* Log_Timestamp (const char *desc)
86 {
87         static char timestamp [128];
88         time_t crt_time;
89         const struct tm *crt_tm;
90         char timestring [64];
91
92         // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
93         time (&crt_time);
94         crt_tm = localtime (&crt_time);
95         strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
96
97         if (desc != NULL)
98                 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
99         else
100                 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
101
102         return timestamp;
103 }
104
105
106 /*
107 ====================
108 Log_Open
109 ====================
110 */
111 void Log_Open (void)
112 {
113         if (logfile != NULL || log_file.string[0] == '\0')
114                 return;
115
116         logfile = FS_Open (log_file.string, "ab", false, false);
117         if (logfile != NULL)
118         {
119                 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
120                 FS_Print (logfile, Log_Timestamp ("Log started"));
121         }
122 }
123
124
125 /*
126 ====================
127 Log_Close
128 ====================
129 */
130 void Log_Close (void)
131 {
132         if (logfile == NULL)
133                 return;
134
135         FS_Print (logfile, Log_Timestamp ("Log stopped"));
136         FS_Print (logfile, "\n");
137         FS_Close (logfile);
138
139         logfile = NULL;
140         crt_log_file[0] = '\0';
141 }
142
143
144 /*
145 ====================
146 Log_Start
147 ====================
148 */
149 void Log_Start (void)
150 {
151         Log_Open ();
152
153         // Dump the contents of the log queue into the log file and free it
154         if (logqueue != NULL)
155         {
156                 unsigned char *temp = logqueue;
157                 logqueue = NULL;
158                 if (logfile != NULL && logq_ind != 0)
159                         FS_Write (logfile, temp, logq_ind);
160                 Mem_Free (temp);
161                 logq_ind = 0;
162                 logq_size = 0;
163         }
164 }
165
166
167 /*
168 ================
169 Log_ConPrint
170 ================
171 */
172 void Log_ConPrint (const char *msg)
173 {
174         static qboolean inprogress = false;
175
176         // don't allow feedback loops with memory error reports
177         if (inprogress)
178                 return;
179         inprogress = true;
180
181         // Until the host is completely initialized, we maintain a log queue
182         // to store the messages, since the log can't be started before
183         if (logqueue != NULL)
184         {
185                 size_t remain = logq_size - logq_ind;
186                 size_t len = strlen (msg);
187
188                 // If we need to enlarge the log queue
189                 if (len > remain)
190                 {
191                         size_t factor = ((logq_ind + len) / logq_size) + 1;
192                         unsigned char* newqueue;
193
194                         logq_size *= factor;
195                         newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
196                         memcpy (newqueue, logqueue, logq_ind);
197                         Mem_Free (logqueue);
198                         logqueue = newqueue;
199                         remain = logq_size - logq_ind;
200                 }
201                 memcpy (&logqueue[logq_ind], msg, len);
202                 logq_ind += len;
203
204                 inprogress = false;
205                 return;
206         }
207
208         // Check if log_file has changed
209         if (strcmp (crt_log_file, log_file.string) != 0)
210         {
211                 Log_Close ();
212                 Log_Open ();
213         }
214
215         // If a log file is available
216         if (logfile != NULL)
217                 FS_Print (logfile, msg);
218         inprogress = false;
219 }
220
221
222 /*
223 ================
224 Log_Printf
225 ================
226 */
227 void Log_Printf (const char *logfilename, const char *fmt, ...)
228 {
229         qfile_t *file;
230
231         file = FS_Open (logfilename, "ab", true, false);
232         if (file != NULL)
233         {
234                 va_list argptr;
235
236                 va_start (argptr, fmt);
237                 FS_VPrintf (file, fmt, argptr);
238                 va_end (argptr);
239
240                 FS_Close (file);
241         }
242 }
243
244
245 /*
246 ==============================================================================
247
248 CONSOLE
249
250 ==============================================================================
251 */
252
253 /*
254 ================
255 Con_ToggleConsole_f
256 ================
257 */
258 void Con_ToggleConsole_f (void)
259 {
260         // toggle the 'user wants console' bit
261         key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
262         memset (con_times, 0, sizeof(con_times));
263 }
264
265 /*
266 ================
267 Con_Clear_f
268 ================
269 */
270 void Con_Clear_f (void)
271 {
272         if (con_text)
273                 memset (con_text, ' ', CON_TEXTSIZE);
274 }
275
276
277 /*
278 ================
279 Con_ClearNotify
280 ================
281 */
282 void Con_ClearNotify (void)
283 {
284         int i;
285
286         for (i=0 ; i<MAX_NOTIFYLINES ; i++)
287                 con_times[i] = 0;
288 }
289
290
291 /*
292 ================
293 Con_MessageMode_f
294 ================
295 */
296 void Con_MessageMode_f (void)
297 {
298         key_dest = key_message;
299         chat_team = false;
300 }
301
302
303 /*
304 ================
305 Con_MessageMode2_f
306 ================
307 */
308 void Con_MessageMode2_f (void)
309 {
310         key_dest = key_message;
311         chat_team = true;
312 }
313
314
315 /*
316 ================
317 Con_CheckResize
318
319 If the line width has changed, reformat the buffer.
320 ================
321 */
322 void Con_CheckResize (void)
323 {
324         int i, j, width, oldwidth, oldtotallines, numlines, numchars;
325         float f;
326         char tbuf[CON_TEXTSIZE];
327
328         f = bound(1, con_textsize.value, 128);
329         if(f != con_textsize.value)
330                 Cvar_SetValueQuick(&con_textsize, f);
331         width = (int)floor(vid_conwidth.value / con_textsize.value);
332         width = bound(1, width, CON_TEXTSIZE/4);
333
334         if (width == con_linewidth)
335                 return;
336
337         oldwidth = con_linewidth;
338         con_linewidth = width;
339         oldtotallines = con_totallines;
340         con_totallines = CON_TEXTSIZE / con_linewidth;
341         numlines = oldtotallines;
342
343         if (con_totallines < numlines)
344                 numlines = con_totallines;
345
346         numchars = oldwidth;
347
348         if (con_linewidth < numchars)
349                 numchars = con_linewidth;
350
351         memcpy (tbuf, con_text, CON_TEXTSIZE);
352         memset (con_text, ' ', CON_TEXTSIZE);
353
354         for (i=0 ; i<numlines ; i++)
355         {
356                 for (j=0 ; j<numchars ; j++)
357                 {
358                         con_text[(con_totallines - 1 - i) * con_linewidth + j] =
359                                         tbuf[((con_current - i + oldtotallines) %
360                                                   oldtotallines) * oldwidth + j];
361                 }
362         }
363
364         Con_ClearNotify ();
365
366         con_backscroll = 0;
367         con_current = con_totallines - 1;
368 }
369
370 //[515]: the simplest command ever
371 //LordHavoc: not so simple after I made it print usage...
372 static void Con_Maps_f (void)
373 {
374         if (Cmd_Argc() > 2)
375         {
376                 Con_Printf("usage: maps [mapnameprefix]\n");
377                 return;
378         }
379         else if (Cmd_Argc() == 2)
380                 GetMapList(Cmd_Argv(1), NULL, 0);
381         else
382                 GetMapList("", NULL, 0);
383 }
384
385 /*
386 ================
387 Con_Init
388 ================
389 */
390 void Con_Init (void)
391 {
392         memset (con_text, ' ', CON_TEXTSIZE);
393         con_linewidth = 80;
394         con_totallines = CON_TEXTSIZE / con_linewidth;
395
396         // Allocate a log queue, this will be freed after configs are parsed
397         logq_size = MAX_INPUTLINE;
398         logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
399         logq_ind = 0;
400
401         Cvar_RegisterVariable (&log_file);
402
403         // support for the classic Quake option
404 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
405         if (COM_CheckParm ("-condebug") != 0)
406                 Cvar_SetQuick (&log_file, "qconsole.log");
407 }
408
409 void Con_Init_Commands (void)
410 {
411         // register our cvars
412         Cvar_RegisterVariable (&con_notifytime);
413         Cvar_RegisterVariable (&con_notify);
414         Cvar_RegisterVariable (&con_textsize);
415
416         // register our commands
417         Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
418         Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
419         Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
420         Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
421         Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");   // By [515]
422
423         con_initialized = true;
424         Con_Print("Console initialized.\n");
425 }
426
427
428 /*
429 ===============
430 Con_Linefeed
431 ===============
432 */
433 void Con_Linefeed (void)
434 {
435         if (con_backscroll)
436                 con_backscroll++;
437
438         con_x = 0;
439         con_current++;
440         memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth);
441 }
442
443 /*
444 ================
445 Con_PrintToHistory
446
447 Handles cursor positioning, line wrapping, etc
448 All console printing must go through this in order to be displayed
449 If no console is visible, the notify window will pop up.
450 ================
451 */
452 void Con_PrintToHistory(const char *txt, int mask)
453 {
454         int y, c, l;
455         static int cr;
456
457         while ( (c = *txt) )
458         {
459         // count word length
460                 for (l=0 ; l< con_linewidth ; l++)
461                         if ( txt[l] <= ' ')
462                                 break;
463
464         // word wrap
465                 if (l != con_linewidth && (con_x + l > con_linewidth) )
466                         con_x = 0;
467
468                 txt++;
469
470                 if (cr)
471                 {
472                         con_current--;
473                         cr = false;
474                 }
475
476
477                 if (!con_x)
478                 {
479                         Con_Linefeed ();
480                 // mark time for transparent overlay
481                         if (con_current >= 0)
482                         {
483                                 if (con_notify.integer < 0)
484                                         Cvar_SetValueQuick(&con_notify, 0);
485                                 if (con_notify.integer > MAX_NOTIFYLINES)
486                                         Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
487                                 if (con_notify.integer > 0)
488                                         con_times[con_current % con_notify.integer] = cl.time;
489                         }
490                 }
491
492                 switch (c)
493                 {
494                 case '\n':
495                         con_x = 0;
496                         break;
497
498                 case '\r':
499                         con_x = 0;
500                         cr = 1;
501                         break;
502
503                 default:        // display character and advance
504                         y = con_current % con_totallines;
505                         con_text[y*con_linewidth+con_x] = c | mask;
506                         con_x++;
507                         if (con_x >= con_linewidth)
508                                 con_x = 0;
509                         break;
510                 }
511
512         }
513 }
514
515 /* The translation table between the graphical font and plain ASCII  --KB */
516 static char qfont_table[256] = {
517         '\0', '#',  '#',  '#',  '#',  '.',  '#',  '#',
518         '#',  9,    10,   '#',  ' ',  13,   '.',  '.',
519         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
520         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
521         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
522         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
523         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
524         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
525         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
526         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
527         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
528         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
529         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
530         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
531         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
532         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<',
533
534         '<',  '=',  '>',  '#',  '#',  '.',  '#',  '#',
535         '#',  '#',  ' ',  '#',  ' ',  '>',  '.',  '.',
536         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
537         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
538         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
539         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
540         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
541         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
542         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
543         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
544         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
545         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
546         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
547         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
548         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
549         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<'
550 };
551
552 /*
553 ================
554 Con_Print
555
556 Prints to all appropriate console targets, and adds timestamps
557 ================
558 */
559 extern cvar_t timestamps;
560 extern cvar_t timeformat;
561 extern qboolean sys_nostdout;
562 void Con_Print(const char *msg)
563 {
564         int mask = 0;
565         static int index = 0;
566         static char line[MAX_INPUTLINE];
567
568         for (;*msg;msg++)
569         {
570                 // if this print is in response to an rcon command, add the character
571                 // to the rcon redirect buffer
572                 if (rcon_redirect && rcon_redirect_bufferpos < (int)sizeof(rcon_redirect_buffer) - 1)
573                         rcon_redirect_buffer[rcon_redirect_bufferpos++] = *msg;
574                 // if this is the beginning of a new line, print timestamp
575                 if (index == 0)
576                 {
577                         const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
578                         // reset the color
579                         // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
580                         line[index++] = STRING_COLOR_TAG;
581                         // assert( STRING_COLOR_DEFAULT < 10 )
582                         line[index++] = STRING_COLOR_DEFAULT + '0';
583                         // special color codes for chat messages must always come first
584                         // for Con_PrintToHistory to work properly
585                         if (*msg == 1 || *msg == 2)
586                         {
587                                 // play talk wav
588                                 if (*msg == 1)
589                                         S_LocalSound ("sound/misc/talk.wav");
590                                 line[index++] = STRING_COLOR_TAG;
591                                 line[index++] = '3';
592                                 msg++;
593                         }
594                         // store timestamp
595                         for (;*timestamp;index++, timestamp++)
596                                 if (index < (int)sizeof(line) - 2)
597                                         line[index] = *timestamp;
598                 }
599                 // append the character
600                 line[index++] = *msg;
601                 // if this is a newline character, we have a complete line to print
602                 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
603                 {
604                         // terminate the line
605                         line[index] = 0;
606                         // send to log file
607                         Log_ConPrint(line);
608                         // send to scrollable buffer
609                         if (con_initialized && cls.state != ca_dedicated)
610                                 Con_PrintToHistory(line, mask);
611                         // send to terminal or dedicated server window
612                         if (!sys_nostdout)
613                         {
614                                 unsigned char *p;
615                                 for (p = (unsigned char *) line;*p; p++)
616                                         *p = qfont_table[*p];
617                                 Sys_PrintToTerminal(line);
618                         }
619                         // empty the line buffer
620                         index = 0;
621                 }
622         }
623 }
624
625
626 /*
627 ================
628 Con_Printf
629
630 Prints to all appropriate console targets
631 ================
632 */
633 void Con_Printf(const char *fmt, ...)
634 {
635         va_list argptr;
636         char msg[MAX_INPUTLINE];
637
638         va_start(argptr,fmt);
639         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
640         va_end(argptr);
641
642         Con_Print(msg);
643 }
644
645 /*
646 ================
647 Con_DPrint
648
649 A Con_Print that only shows up if the "developer" cvar is set
650 ================
651 */
652 void Con_DPrint(const char *msg)
653 {
654         if (!developer.integer)
655                 return;                 // don't confuse non-developers with techie stuff...
656         Con_Print(msg);
657 }
658
659 /*
660 ================
661 Con_DPrintf
662
663 A Con_Printf that only shows up if the "developer" cvar is set
664 ================
665 */
666 void Con_DPrintf(const char *fmt, ...)
667 {
668         va_list argptr;
669         char msg[MAX_INPUTLINE];
670
671         if (!developer.integer)
672                 return;                 // don't confuse non-developers with techie stuff...
673
674         va_start(argptr,fmt);
675         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
676         va_end(argptr);
677
678         Con_Print(msg);
679 }
680
681
682 /*
683 ==============================================================================
684
685 DRAWING
686
687 ==============================================================================
688 */
689
690 /*
691 ================
692 Con_DrawInput
693
694 The input line scrolls horizontally if typing goes beyond the right edge
695
696 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
697 ================
698 */
699 void Con_DrawInput (void)
700 {
701         int             y;
702         int             i;
703         char editlinecopy[MAX_INPUTLINE+1], *text;
704
705         if (!key_consoleactive)
706                 return;         // don't draw anything
707
708         strlcpy(editlinecopy, key_lines[edit_line], sizeof(editlinecopy));
709         text = editlinecopy;
710
711         // Advanced Console Editing by Radix radix@planetquake.com
712         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
713         // use strlen of edit_line instead of key_linepos to allow editing
714         // of early characters w/o erasing
715
716         y = (int)strlen(text);
717
718 // fill out remainder with spaces
719         for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
720                 text[i] = ' ';
721
722         // add the cursor frame
723         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
724                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
725
726 //      text[key_linepos + 1] = 0;
727
728         // prestep if horizontally scrolling
729         if (key_linepos >= con_linewidth)
730                 text += 1 + key_linepos - con_linewidth;
731
732         // draw it
733         DrawQ_ColoredString(0, con_vislines - con_textsize.value*2, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL );
734
735         // remove cursor
736 //      key_lines[edit_line][key_linepos] = 0;
737 }
738
739
740 /*
741 ================
742 Con_DrawNotify
743
744 Draws the last few lines of output transparently over the game top
745 ================
746 */
747 void Con_DrawNotify (void)
748 {
749         float   x, v;
750         char    *text;
751         int             i, stop;
752         float   time;
753         char    temptext[MAX_INPUTLINE];
754         int colorindex = -1; //-1 for default
755
756         if (con_notify.integer < 0)
757                 Cvar_SetValueQuick(&con_notify, 0);
758         if (con_notify.integer > MAX_NOTIFYLINES)
759                 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
760         if (gamemode == GAME_TRANSFUSION)
761                 v = 8;
762         else
763                 v = 0;
764         // make a copy of con_current here so that we can't get in a runaway loop printing new messages while drawing the notify text
765         stop = con_current;
766         for (i= stop-con_notify.integer+1 ; i<=stop ; i++)
767         {
768
769                 if (i < 0)
770                         continue;
771                 time = con_times[i % con_notify.integer];
772                 if (time == 0)
773                         continue;
774                 time = cl.time - time;
775                 if (time > con_notifytime.value)
776                         continue;
777                 text = con_text + (i % con_totallines)*con_linewidth;
778
779                 if (gamemode == GAME_NEXUIZ) {
780                         int chars = 0;
781                         int finalchars = 0;
782                         int j;
783
784                         // count up to the last non-whitespace, and ignore color codes
785                         for (j = 0;j < con_linewidth && text[j];j++)
786                         {
787                                 if (text[j] == '^' && (text[j+1] >= '0' && text[j+1] <= '9'))
788                                 {
789                                         j++;
790                                         continue;
791                                 }
792                                 chars++;
793                                 if (text[j] == ' ')
794                                         continue;
795                                 finalchars = chars;
796                         }
797                         // center the line using the calculated width
798                         x = (vid_conwidth.integer - finalchars * con_textsize.value) * 0.5;
799                 } else
800                         x = 0;
801
802                 DrawQ_ColoredString( x, v, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
803
804                 v += con_textsize.value;
805         }
806
807
808         if (key_dest == key_message)
809         {
810                 int colorindex = -1;
811
812                 x = 0;
813
814                 // LordHavoc: speedup, and other improvements
815                 if (chat_team)
816                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
817                 else
818                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
819                 while ((int)strlen(temptext) >= con_linewidth)
820                 {
821                         DrawQ_ColoredString( 0, v, temptext, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
822                         strlcpy(temptext, &temptext[con_linewidth], sizeof(temptext));
823                         v += con_textsize.value;
824                 }
825                 if (strlen(temptext) > 0)
826                 {
827                         DrawQ_ColoredString( 0, v, temptext, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
828                         v += con_textsize.value;
829                 }
830         }
831 }
832
833 /*
834 ================
835 Con_DrawConsole
836
837 Draws the console with the solid background
838 The typing input line at the bottom should only be drawn if typing is allowed
839 ================
840 */
841 void Con_DrawConsole (int lines)
842 {
843         int i, rows, j, stop;
844         float y;
845         char *text;
846         int colorindex = -1;
847
848         if (lines <= 0)
849                 return;
850
851 // draw the background
852         DrawQ_Pic(0, lines - vid_conheight.integer, scr_conbrightness.value >= 0.01f ? Draw_CachePic("gfx/conback", true) : NULL, vid_conwidth.integer, vid_conheight.integer, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, scr_conalpha.value, 0);
853         DrawQ_String(vid_conwidth.integer - strlen(engineversion) * con_textsize.value - con_textsize.value, lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0);
854
855 // draw the text
856         con_vislines = lines;
857
858         rows = (int)ceil((lines/con_textsize.value)-2);         // rows of text to draw
859         y = lines - (rows+2)*con_textsize.value;        // may start slightly negative
860
861         // make a copy of con_current here so that we can't get in a runaway loop printing new messages while drawing the notify text
862         stop = con_current;
863         for (i = stop - rows + 1;i <= stop;i++, y += con_textsize.value)
864         {
865                 j = max(i - con_backscroll, 0);
866                 text = con_text + (j % con_totallines)*con_linewidth;
867
868                 DrawQ_ColoredString( 0, y, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
869         }
870
871 // draw the input prompt, user text, and cursor if desired
872         Con_DrawInput ();
873 }
874
875 /*
876 GetMapList
877
878 Made by [515]
879 Prints not only map filename, but also
880 its format (q1/q2/q3/hl) and even its message
881 */
882 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
883 //LordHavoc: rewrote bsp type detection, added mcbsp support and rewrote message extraction to do proper worldspawn parsing
884 //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
885 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
886 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
887 {
888         fssearch_t      *t;
889         char            message[64];
890         int                     i, k, max, p, o, min;
891         unsigned char *len;
892         qfile_t         *f;
893         unsigned char buf[1024];
894
895         sprintf(message, "maps/%s*.bsp", s);
896         t = FS_Search(message, 1, true);
897         if(!t)
898                 return false;
899         if (t->numfilenames > 1)
900                 Con_Printf("^1 %i maps found :\n", t->numfilenames);
901         len = (unsigned char *)Z_Malloc(t->numfilenames);
902         min = 666;
903         for(max=i=0;i<t->numfilenames;i++)
904         {
905                 k = (int)strlen(t->filenames[i]);
906                 k -= 9;
907                 if(max < k)
908                         max = k;
909                 else
910                 if(min > k)
911                         min = k;
912                 len[i] = k;
913         }
914         o = (int)strlen(s);
915         for(i=0;i<t->numfilenames;i++)
916         {
917                 int lumpofs = 0, lumplen = 0;
918                 char *entities = NULL;
919                 const char *data = NULL;
920                 char keyname[64];
921                 char entfilename[MAX_QPATH];
922                 strlcpy(message, "^1**ERROR**^7", sizeof(message));
923                 p = 0;
924                 f = FS_Open(t->filenames[i], "rb", true, false);
925                 if(f)
926                 {
927                         memset(buf, 0, 1024);
928                         FS_Read(f, buf, 1024);
929                         if (!memcmp(buf, "IBSP", 4))
930                         {
931                                 p = LittleLong(((int *)buf)[1]);
932                                 if (p == Q3BSPVERSION)
933                                 {
934                                         q3dheader_t *header = (q3dheader_t *)buf;
935                                         lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
936                                         lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
937                                 }
938                                 else if (p == Q2BSPVERSION)
939                                 {
940                                         q2dheader_t *header = (q2dheader_t *)buf;
941                                         lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
942                                         lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
943                                 }
944                         }
945                         else if (!memcmp(buf, "MCBSPpad", 8))
946                         {
947                                 p = LittleLong(((int *)buf)[2]);
948                                 if (p == MCBSPVERSION)
949                                 {
950                                         int numhulls = LittleLong(((int *)buf)[3]);
951                                         lumpofs = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+0]);
952                                         lumplen = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+1]);
953                                 }
954                         }
955                         else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
956                         {
957                                 dheader_t *header = (dheader_t *)buf;
958                                 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
959                                 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
960                         }
961                         else
962                                 p = 0;
963                         strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
964                         memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
965                         entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
966                         if (!entities && lumplen >= 10)
967                         {
968                                 FS_Seek(f, lumpofs, SEEK_SET);
969                                 entities = (char *)Z_Malloc(lumplen + 1);
970                                 FS_Read(f, entities, lumplen);
971                         }
972                         if (entities)
973                         {
974                                 // if there are entities to parse, a missing message key just
975                                 // means there is no title, so clear the message string now
976                                 message[0] = 0;
977                                 data = entities;
978                                 for (;;)
979                                 {
980                                         int l;
981                                         if (!COM_ParseTokenConsole(&data))
982                                                 break;
983                                         if (com_token[0] == '{')
984                                                 continue;
985                                         if (com_token[0] == '}')
986                                                 break;
987                                         // skip leading whitespace
988                                         for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
989                                         for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
990                                                 keyname[l] = com_token[k+l];
991                                         keyname[l] = 0;
992                                         if (!COM_ParseTokenConsole(&data))
993                                                 break;
994                                         if (developer.integer >= 100)
995                                                 Con_Printf("key: %s %s\n", keyname, com_token);
996                                         if (!strcmp(keyname, "message"))
997                                         {
998                                                 // get the message contents
999                                                 strlcpy(message, com_token, sizeof(message));
1000                                                 break;
1001                                         }
1002                                 }
1003                         }
1004                 }
1005                 if (entities)
1006                         Z_Free(entities);
1007                 if(f)
1008                         FS_Close(f);
1009                 *(t->filenames[i]+len[i]+5) = 0;
1010                 switch(p)
1011                 {
1012                 case Q3BSPVERSION:      strlcpy((char *)buf, "Q3", sizeof(buf));break;
1013                 case Q2BSPVERSION:      strlcpy((char *)buf, "Q2", sizeof(buf));break;
1014                 case BSPVERSION:        strlcpy((char *)buf, "Q1", sizeof(buf));break;
1015                 case MCBSPVERSION:      strlcpy((char *)buf, "MC", sizeof(buf));break;
1016                 case 30:                        strlcpy((char *)buf, "HL", sizeof(buf));break;
1017                 default:                        strlcpy((char *)buf, "??", sizeof(buf));break;
1018                 }
1019                 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1020         }
1021         Con_Print("\n");
1022         for(p=o;p<min;p++)
1023         {
1024                 k = *(t->filenames[0]+5+p);
1025                 if(k == 0)
1026                         goto endcomplete;
1027                 for(i=1;i<t->numfilenames;i++)
1028                         if(*(t->filenames[i]+5+p) != k)
1029                                 goto endcomplete;
1030         }
1031 endcomplete:
1032         if(p > o)
1033         {
1034                 memset(completedname, 0, completednamebufferlength);
1035                 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1036         }
1037         Z_Free(len);
1038         FS_FreeSearch(t);
1039         return p > o;
1040 }
1041
1042 /*
1043         Con_DisplayList
1044
1045         New function for tab-completion system
1046         Added by EvilTypeGuy
1047         MEGA Thanks to Taniwha
1048
1049 */
1050 void Con_DisplayList(const char **list)
1051 {
1052         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1053         const char **walk = list;
1054
1055         while (*walk) {
1056                 len = (int)strlen(*walk);
1057                 if (len > maxlen)
1058                         maxlen = len;
1059                 walk++;
1060         }
1061         maxlen += 1;
1062
1063         while (*list) {
1064                 len = (int)strlen(*list);
1065                 if (pos + maxlen >= width) {
1066                         Con_Print("\n");
1067                         pos = 0;
1068                 }
1069
1070                 Con_Print(*list);
1071                 for (i = 0; i < (maxlen - len); i++)
1072                         Con_Print(" ");
1073
1074                 pos += maxlen;
1075                 list++;
1076         }
1077
1078         if (pos)
1079                 Con_Print("\n\n");
1080 }
1081
1082 /*
1083         Con_CompleteCommandLine
1084
1085         New function for tab-completion system
1086         Added by EvilTypeGuy
1087         Thanks to Fett erich@heintz.com
1088         Thanks to taniwha
1089         Enhanced to tab-complete map names by [515]
1090
1091 */
1092 void Con_CompleteCommandLine (void)
1093 {
1094         const char *cmd = "";
1095         char *s;
1096         const char **list[3] = {0, 0, 0};
1097         char s2[512];
1098         int c, v, a, i, cmd_len, pos, k;
1099
1100         //find what we want to complete
1101         pos = key_linepos;
1102         while(--pos)
1103         {
1104                 k = key_lines[edit_line][pos];
1105                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
1106                         break;
1107         }
1108         pos++;
1109
1110         s = key_lines[edit_line] + pos;
1111         strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2));    //save chars after cursor
1112         key_lines[edit_line][key_linepos] = 0;                                  //hide them
1113
1114         //maps search
1115         for(k=pos-1;k>2;k--)
1116                 if(key_lines[edit_line][k] != ' ')
1117                 {
1118                         if(key_lines[edit_line][k] == '\"' || key_lines[edit_line][k] == ';' || key_lines[edit_line][k] == '\'')
1119                                 break;
1120                         if      ((pos+k > 2 && !strncmp(key_lines[edit_line]+k-2, "map", 3))
1121                                 || (pos+k > 10 && !strncmp(key_lines[edit_line]+k-10, "changelevel", 11)))
1122                         {
1123                                 char t[MAX_QPATH];
1124                                 if (GetMapList(s, t, sizeof(t)))
1125                                 {
1126                                         // first move the cursor
1127                                         key_linepos += (int)strlen(t) - (int)strlen(s);
1128
1129                                         // and now do the actual work
1130                                         *s = 0;
1131                                         strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
1132                                         strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
1133
1134                                         // and fix the cursor
1135                                         if(key_linepos > (int) strlen(key_lines[edit_line]))
1136                                                 key_linepos = (int) strlen(key_lines[edit_line]);
1137                                 }
1138                                 return;
1139                         }
1140                 }
1141
1142         // Count number of possible matches and print them
1143         c = Cmd_CompleteCountPossible(s);
1144         if (c)
1145         {
1146                 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
1147                 Cmd_CompleteCommandPrint(s);
1148         }
1149         v = Cvar_CompleteCountPossible(s);
1150         if (v)
1151         {
1152                 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
1153                 Cvar_CompleteCvarPrint(s);
1154         }
1155         a = Cmd_CompleteAliasCountPossible(s);
1156         if (a)
1157         {
1158                 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
1159                 Cmd_CompleteAliasPrint(s);
1160         }
1161
1162         if (!(c + v + a))       // No possible matches
1163         {
1164                 if(s2[0])
1165                         strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
1166                 return;
1167         }
1168
1169         if (c)
1170                 cmd = *(list[0] = Cmd_CompleteBuildList(s));
1171         if (v)
1172                 cmd = *(list[1] = Cvar_CompleteBuildList(s));
1173         if (a)
1174                 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
1175
1176         for (cmd_len = (int)strlen(s);;cmd_len++)
1177         {
1178                 const char **l;
1179                 for (i = 0; i < 3; i++)
1180                         if (list[i])
1181                                 for (l = list[i];*l;l++)
1182                                         if ((*l)[cmd_len] != cmd[cmd_len])
1183                                                 goto done;
1184                 // all possible matches share this character, so we continue...
1185                 if (!cmd[cmd_len])
1186                 {
1187                         // if all matches ended at the same position, stop
1188                         // (this means there is only one match)
1189                         break;
1190                 }
1191         }
1192 done:
1193
1194         // prevent a buffer overrun by limiting cmd_len according to remaining space
1195         cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
1196         if (cmd)
1197         {
1198                 key_linepos = pos;
1199                 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
1200                 key_linepos += cmd_len;
1201                 // if there is only one match, add a space after it
1202                 if (c + v + a == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
1203                         key_lines[edit_line][key_linepos++] = ' ';
1204         }
1205
1206         // use strlcat to avoid a buffer overrun
1207         key_lines[edit_line][key_linepos] = 0;
1208         strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
1209
1210         // free the command, cvar, and alias lists
1211         for (i = 0; i < 3; i++)
1212                 if (list[i])
1213                         Mem_Free((void *)list[i]);
1214 }
1215