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