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