]> icculus.org git repositories - divverent/darkplaces.git/blob - console.c
added cvar r_fixtrans_auto to automatically call fixtrans on all textures that are...
[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"};
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", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
52 #else
53 cvar_t sys_colortranslation = {0, "sys_colortranslation", "1", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
54 #endif
55
56
57 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
58 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
59                                    "0: add nothing after completion. "
60                                    "1: add the last color after completion. "
61                                    "2: add a quote when starting a quote instead of the color. "
62                                    "4: will replace 1, will force color, even after a quote. "
63                                    "8: ignore non-alphanumerics. "
64                                    "16: ignore spaces. "};
65 #define NICKS_ADD_COLOR 1
66 #define NICKS_ADD_QUOTE 2
67 #define NICKS_FORCE_COLOR 4
68 #define NICKS_ALPHANUMERICS_ONLY 8
69 #define NICKS_NO_SPACES 16
70
71 #define MAX_NOTIFYLINES 32
72 // cl.time time the line was generated for transparent notify lines
73 float con_times[MAX_NOTIFYLINES];
74
75 int con_vislines;
76
77 qboolean con_initialized;
78
79 // used for server replies to rcon command
80 qboolean rcon_redirect = false;
81 int rcon_redirect_bufferpos = 0;
82 char rcon_redirect_buffer[1400];
83
84
85 /*
86 ==============================================================================
87
88 LOGGING
89
90 ==============================================================================
91 */
92
93 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
94 cvar_t log_dest_udp = {0, "log_dest_udp","", "UDP address to log messages to (in QW rcon compatible format); multiple destinations can be separated by spaces; DO NOT SPECIFY DNS NAMES HERE"};
95 char log_dest_buffer[1400]; // UDP packet
96 size_t log_dest_buffer_pos;
97 qboolean log_dest_buffer_appending;
98 char crt_log_file [MAX_OSPATH] = "";
99 qfile_t* logfile = NULL;
100
101 unsigned char* logqueue = NULL;
102 size_t logq_ind = 0;
103 size_t logq_size = 0;
104
105 void Log_ConPrint (const char *msg);
106
107 /*
108 ====================
109 Log_DestBuffer_Init
110 ====================
111 */
112 static void Log_DestBuffer_Init()
113 {
114         memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
115         log_dest_buffer_pos = 5;
116 }
117
118 /*
119 ====================
120 Log_DestBuffer_Flush
121 ====================
122 */
123 void Log_DestBuffer_Flush()
124 {
125         lhnetaddress_t log_dest_addr;
126         lhnetsocket_t *log_dest_socket;
127         const char *s = log_dest_udp.string;
128         qboolean have_opened_temp_sockets = false;
129         if(s) if(log_dest_buffer_pos > 5)
130         {
131                 ++log_dest_buffer_appending;
132                 log_dest_buffer[log_dest_buffer_pos++] = 0;
133
134                 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
135                 {
136                         have_opened_temp_sockets = true;
137                         NetConn_OpenServerPorts(true);
138                 }
139
140                 while(COM_ParseToken_Console(&s))
141                         if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
142                         {
143                                 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
144                                 if(!log_dest_socket)
145                                         log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
146                                 if(log_dest_socket)
147                                         NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
148                         }
149
150                 if(have_opened_temp_sockets)
151                         NetConn_CloseServerPorts();
152                 --log_dest_buffer_appending;
153         }
154         log_dest_buffer_pos = 0;
155 }
156
157 /*
158 ====================
159 Log_Timestamp
160 ====================
161 */
162 const char* Log_Timestamp (const char *desc)
163 {
164         static char timestamp [128];
165         time_t crt_time;
166         const struct tm *crt_tm;
167         char timestring [64];
168
169         // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
170         time (&crt_time);
171         crt_tm = localtime (&crt_time);
172         strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
173
174         if (desc != NULL)
175                 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
176         else
177                 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
178
179         return timestamp;
180 }
181
182
183 /*
184 ====================
185 Log_Open
186 ====================
187 */
188 void Log_Open (void)
189 {
190         if (logfile != NULL || log_file.string[0] == '\0')
191                 return;
192
193         logfile = FS_Open (log_file.string, "ab", false, false);
194         if (logfile != NULL)
195         {
196                 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
197                 FS_Print (logfile, Log_Timestamp ("Log started"));
198         }
199 }
200
201
202 /*
203 ====================
204 Log_Close
205 ====================
206 */
207 void Log_Close (void)
208 {
209         if (logfile == NULL)
210                 return;
211
212         FS_Print (logfile, Log_Timestamp ("Log stopped"));
213         FS_Print (logfile, "\n");
214         FS_Close (logfile);
215
216         logfile = NULL;
217         crt_log_file[0] = '\0';
218 }
219
220
221 /*
222 ====================
223 Log_Start
224 ====================
225 */
226 void Log_Start (void)
227 {
228         size_t pos;
229         size_t n;
230         Log_Open ();
231
232         // Dump the contents of the log queue into the log file and free it
233         if (logqueue != NULL)
234         {
235                 unsigned char *temp = logqueue;
236                 logqueue = NULL;
237                 if(logq_ind != 0)
238                 {
239                         if (logfile != NULL)
240                                 FS_Write (logfile, temp, logq_ind);
241                         if(*log_dest_udp.string)
242                         {
243                                 for(pos = 0; pos < logq_ind; )
244                                 {
245                                         if(log_dest_buffer_pos == 0)
246                                                 Log_DestBuffer_Init();
247                                         n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
248                                         memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
249                                         log_dest_buffer_pos += n;
250                                         Log_DestBuffer_Flush();
251                                         pos += n;
252                                 }
253                         }
254                 }
255                 Mem_Free (temp);
256                 logq_ind = 0;
257                 logq_size = 0;
258         }
259 }
260
261
262 /*
263 ================
264 Log_ConPrint
265 ================
266 */
267 void Log_ConPrint (const char *msg)
268 {
269         static qboolean inprogress = false;
270
271         // don't allow feedback loops with memory error reports
272         if (inprogress)
273                 return;
274         inprogress = true;
275
276         // Until the host is completely initialized, we maintain a log queue
277         // to store the messages, since the log can't be started before
278         if (logqueue != NULL)
279         {
280                 size_t remain = logq_size - logq_ind;
281                 size_t len = strlen (msg);
282
283                 // If we need to enlarge the log queue
284                 if (len > remain)
285                 {
286                         size_t factor = ((logq_ind + len) / logq_size) + 1;
287                         unsigned char* newqueue;
288
289                         logq_size *= factor;
290                         newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
291                         memcpy (newqueue, logqueue, logq_ind);
292                         Mem_Free (logqueue);
293                         logqueue = newqueue;
294                         remain = logq_size - logq_ind;
295                 }
296                 memcpy (&logqueue[logq_ind], msg, len);
297                 logq_ind += len;
298
299                 inprogress = false;
300                 return;
301         }
302
303         // Check if log_file has changed
304         if (strcmp (crt_log_file, log_file.string) != 0)
305         {
306                 Log_Close ();
307                 Log_Open ();
308         }
309
310         // If a log file is available
311         if (logfile != NULL)
312                 FS_Print (logfile, msg);
313
314         inprogress = false;
315 }
316
317
318 /*
319 ================
320 Log_Printf
321 ================
322 */
323 void Log_Printf (const char *logfilename, const char *fmt, ...)
324 {
325         qfile_t *file;
326
327         file = FS_Open (logfilename, "ab", true, false);
328         if (file != NULL)
329         {
330                 va_list argptr;
331
332                 va_start (argptr, fmt);
333                 FS_VPrintf (file, fmt, argptr);
334                 va_end (argptr);
335
336                 FS_Close (file);
337         }
338 }
339
340
341 /*
342 ==============================================================================
343
344 CONSOLE
345
346 ==============================================================================
347 */
348
349 /*
350 ================
351 Con_ToggleConsole_f
352 ================
353 */
354 void Con_ToggleConsole_f (void)
355 {
356         // toggle the 'user wants console' bit
357         key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
358         memset (con_times, 0, sizeof(con_times));
359 }
360
361 /*
362 ================
363 Con_Clear_f
364 ================
365 */
366 void Con_Clear_f (void)
367 {
368         //if (con_text)
369                 memset (con_text, ' ', CON_TEXTSIZE);
370 }
371
372
373 /*
374 ================
375 Con_ClearNotify
376 ================
377 */
378 void Con_ClearNotify (void)
379 {
380         int i;
381
382         for (i=0 ; i<MAX_NOTIFYLINES ; i++)
383                 con_times[i] = 0;
384 }
385
386
387 /*
388 ================
389 Con_MessageMode_f
390 ================
391 */
392 void Con_MessageMode_f (void)
393 {
394         key_dest = key_message;
395         chat_team = false;
396 }
397
398
399 /*
400 ================
401 Con_MessageMode2_f
402 ================
403 */
404 void Con_MessageMode2_f (void)
405 {
406         key_dest = key_message;
407         chat_team = true;
408 }
409
410
411 /*
412 ================
413 Con_CheckResize
414
415 If the line width has changed, reformat the buffer.
416 ================
417 */
418 void Con_CheckResize (void)
419 {
420         int i, j, width, oldwidth, oldtotallines, numlines, numchars;
421         float f;
422         char tbuf[CON_TEXTSIZE];
423
424         f = bound(1, con_textsize.value, 128);
425         if(f != con_textsize.value)
426                 Cvar_SetValueQuick(&con_textsize, f);
427         width = (int)floor(vid_conwidth.value / con_textsize.value);
428         width = bound(1, width, CON_TEXTSIZE/4);
429
430         if (width == con_linewidth)
431                 return;
432
433         oldwidth = con_linewidth;
434         con_linewidth = width;
435         oldtotallines = con_totallines;
436         con_totallines = CON_TEXTSIZE / con_linewidth;
437         numlines = oldtotallines;
438
439         if (con_totallines < numlines)
440                 numlines = con_totallines;
441
442         numchars = oldwidth;
443
444         if (con_linewidth < numchars)
445                 numchars = con_linewidth;
446
447         memcpy (tbuf, con_text, CON_TEXTSIZE);
448         memset (con_text, ' ', CON_TEXTSIZE);
449
450         for (i=0 ; i<numlines ; i++)
451         {
452                 for (j=0 ; j<numchars ; j++)
453                 {
454                         con_text[(con_totallines - 1 - i) * con_linewidth + j] =
455                                         tbuf[((con_current - i + oldtotallines) %
456                                                   oldtotallines) * oldwidth + j];
457                 }
458         }
459
460         Con_ClearNotify ();
461
462         con_backscroll = 0;
463         con_current = con_totallines - 1;
464 }
465
466 //[515]: the simplest command ever
467 //LordHavoc: not so simple after I made it print usage...
468 static void Con_Maps_f (void)
469 {
470         if (Cmd_Argc() > 2)
471         {
472                 Con_Printf("usage: maps [mapnameprefix]\n");
473                 return;
474         }
475         else if (Cmd_Argc() == 2)
476                 GetMapList(Cmd_Argv(1), NULL, 0);
477         else
478                 GetMapList("", NULL, 0);
479 }
480
481 void Con_ConDump_f (void)
482 {
483         int i, l;
484         qboolean allblankssofar;
485         const char *text;
486         qfile_t *file;
487         char temp[MAX_INPUTLINE+2];
488         if (Cmd_Argc() != 2)
489         {
490                 Con_Printf("usage: condump <filename>\n");
491                 return;
492         }
493         file = FS_Open(Cmd_Argv(1), "wb", false, false);
494         if (!file)
495         {
496                 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
497                 return;
498         }
499         // iterate over the entire console history buffer line by line
500         allblankssofar = true;
501         for (i = 0;i < con_totallines;i++)
502         {
503                 text = con_text + ((con_current + 1 + i) % con_totallines)*con_linewidth;
504                 // count the used characters on this line
505                 for (l = min(con_linewidth, (int)sizeof(temp));l > 0 && text[l-1] == ' ';l--);
506                 // if not a blank line, begin output
507                 if (l)
508                         allblankssofar = false;
509                 // output the current line to the file
510                 if (!allblankssofar)
511                 {
512                         if (l)
513                                 memcpy(temp, text, l);
514                         temp[l] = '\n';
515                         temp[l+1] = 0;
516                         FS_Print(file, temp);
517                 }
518         }
519         FS_Close(file);
520 }
521
522 /*
523 ================
524 Con_Init
525 ================
526 */
527 void Con_Init (void)
528 {
529         memset (con_text, ' ', CON_TEXTSIZE);
530         con_linewidth = 80;
531         con_totallines = CON_TEXTSIZE / con_linewidth;
532
533         // Allocate a log queue, this will be freed after configs are parsed
534         logq_size = MAX_INPUTLINE;
535         logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
536         logq_ind = 0;
537
538         Cvar_RegisterVariable (&sys_colortranslation);
539         Cvar_RegisterVariable (&sys_specialcharactertranslation);
540
541         Cvar_RegisterVariable (&log_file);
542         Cvar_RegisterVariable (&log_dest_udp);
543
544         // support for the classic Quake option
545 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
546         if (COM_CheckParm ("-condebug") != 0)
547                 Cvar_SetQuick (&log_file, "qconsole.log");
548
549         // register our cvars
550         Cvar_RegisterVariable (&con_notifytime);
551         Cvar_RegisterVariable (&con_notify);
552         Cvar_RegisterVariable (&con_textsize);
553
554         // --blub
555         Cvar_RegisterVariable (&con_nickcompletion);
556         Cvar_RegisterVariable (&con_nickcompletion_flags);
557
558         // register our commands
559         Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
560         Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
561         Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
562         Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
563         Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
564         Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
565
566         con_initialized = true;
567         Con_Print("Console initialized.\n");
568 }
569
570
571 /*
572 ===============
573 Con_Linefeed
574 ===============
575 */
576 void Con_Linefeed (void)
577 {
578         if (con_backscroll)
579                 con_backscroll++;
580
581         con_x = 0;
582         con_current++;
583         memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth);
584 }
585
586 /*
587 ================
588 Con_PrintToHistory
589
590 Handles cursor positioning, line wrapping, etc
591 All console printing must go through this in order to be displayed
592 If no console is visible, the notify window will pop up.
593 ================
594 */
595 void Con_PrintToHistory(const char *txt, int mask)
596 {
597         int y, c, l;
598         static int cr;
599
600         while ( (c = *txt) )
601         {
602         // count word length
603                 for (l=0 ; l< con_linewidth ; l++)
604                         if ( txt[l] <= ' ')
605                                 break;
606
607         // word wrap
608                 if (l != con_linewidth && (con_x + l > con_linewidth) )
609                         con_x = 0;
610
611                 txt++;
612
613                 if (cr)
614                 {
615                         con_current--;
616                         cr = false;
617                 }
618
619
620                 if (!con_x)
621                 {
622                         Con_Linefeed ();
623                 // mark time for transparent overlay
624                         if (con_current >= 0)
625                         {
626                                 if (con_notify.integer < 0)
627                                         Cvar_SetValueQuick(&con_notify, 0);
628                                 if (con_notify.integer > MAX_NOTIFYLINES)
629                                         Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
630                                 if (con_notify.integer > 0)
631                                         con_times[con_current % con_notify.integer] = cl.time;
632                         }
633                 }
634
635                 switch (c)
636                 {
637                 case '\n':
638                         con_x = 0;
639                         break;
640
641                 case '\r':
642                         con_x = 0;
643                         cr = 1;
644                         break;
645
646                 default:        // display character and advance
647                         y = con_current % con_totallines;
648                         con_text[y*con_linewidth+con_x] = c | mask;
649                         con_x++;
650                         if (con_x >= con_linewidth)
651                                 con_x = 0;
652                         break;
653                 }
654
655         }
656 }
657
658 /* The translation table between the graphical font and plain ASCII  --KB */
659 static char qfont_table[256] = {
660         '\0', '#',  '#',  '#',  '#',  '.',  '#',  '#',
661         '#',  9,    10,   '#',  ' ',  13,   '.',  '.',
662         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
663         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
664         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
665         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
666         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
667         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
668         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
669         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
670         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
671         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
672         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
673         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
674         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
675         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<',
676
677         '<',  '=',  '>',  '#',  '#',  '.',  '#',  '#',
678         '#',  '#',  ' ',  '#',  ' ',  '>',  '.',  '.',
679         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
680         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
681         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
682         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
683         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
684         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
685         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
686         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
687         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
688         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
689         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
690         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
691         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
692         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<'
693 };
694
695 /*
696 ================
697 Con_Rcon_AddChar
698
699 Adds a character to the rcon buffer
700 ================
701 */
702 void Con_Rcon_AddChar(char c)
703 {
704         if(log_dest_buffer_appending)
705                 return;
706         ++log_dest_buffer_appending;
707
708         // if this print is in response to an rcon command, add the character
709         // to the rcon redirect buffer
710
711         if (rcon_redirect && rcon_redirect_bufferpos < (int)sizeof(rcon_redirect_buffer) - 1)
712                 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
713         else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
714         {
715                 if(log_dest_buffer_pos == 0)
716                         Log_DestBuffer_Init();
717                 log_dest_buffer[log_dest_buffer_pos++] = c;
718                 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
719                         Log_DestBuffer_Flush();
720         }
721         else
722                 log_dest_buffer_pos = 0;
723
724         --log_dest_buffer_appending;
725 }
726
727 /*
728 ================
729 Con_Print
730
731 Prints to all appropriate console targets, and adds timestamps
732 ================
733 */
734 extern cvar_t timestamps;
735 extern cvar_t timeformat;
736 extern qboolean sys_nostdout;
737 void Con_Print(const char *msg)
738 {
739         static int mask = 0;
740         static int index = 0;
741         static char line[MAX_INPUTLINE];
742
743         for (;*msg;msg++)
744         {
745                 Con_Rcon_AddChar(*msg);
746                 // if this is the beginning of a new line, print timestamp
747                 if (index == 0)
748                 {
749                         const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
750                         // reset the color
751                         // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
752                         line[index++] = STRING_COLOR_TAG;
753                         // assert( STRING_COLOR_DEFAULT < 10 )
754                         line[index++] = STRING_COLOR_DEFAULT + '0';
755                         // special color codes for chat messages must always come first
756                         // for Con_PrintToHistory to work properly
757                         if (*msg == 1 || *msg == 2)
758                         {
759                                 // play talk wav
760                                 if (*msg == 1)
761                                 {
762                                         if (msg[1] == '(' && cl.foundtalk2wav)
763                                                 S_LocalSound ("sound/misc/talk2.wav");
764                                         else
765                                                 S_LocalSound ("sound/misc/talk.wav");
766                                 }
767                                 line[index++] = STRING_COLOR_TAG;
768                                 line[index++] = '3';
769                                 msg++;
770                                 Con_Rcon_AddChar(*msg);
771                         }
772                         // store timestamp
773                         for (;*timestamp;index++, timestamp++)
774                                 if (index < (int)sizeof(line) - 2)
775                                         line[index] = *timestamp;
776                 }
777                 // append the character
778                 line[index++] = *msg;
779                 // if this is a newline character, we have a complete line to print
780                 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
781                 {
782                         // terminate the line
783                         line[index] = 0;
784                         // send to log file
785                         Log_ConPrint(line);
786                         // send to scrollable buffer
787                         if (con_initialized && cls.state != ca_dedicated)
788                                 Con_PrintToHistory(line, mask);
789                         // send to terminal or dedicated server window
790                         if (!sys_nostdout)
791                         {
792                                 unsigned char *p;
793                                 if(sys_specialcharactertranslation.integer)
794                                 {
795                                         for (p = (unsigned char *) line;*p; p++)
796                                                 *p = qfont_table[*p];
797                                 }
798
799                                 if(sys_colortranslation.integer == 1) // ANSI
800                                 {
801                                         static char printline[MAX_INPUTLINE * 4 + 3];
802                                                 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
803                                                 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
804                                         int lastcolor = 0;
805                                         const char *in;
806                                         char *out;
807                                         for(in = line, out = printline; *in; ++in)
808                                         {
809                                                 switch(*in)
810                                                 {
811                                                         case STRING_COLOR_TAG:
812                                                                 switch(in[1])
813                                                                 {
814                                                                         case STRING_COLOR_TAG:
815                                                                                 ++in;
816                                                                                 *out++ = STRING_COLOR_TAG;
817                                                                                 break;
818                                                                         case '0':
819                                                                         case '7':
820                                                                                 // normal color
821                                                                                 ++in;
822                                                                                 if(lastcolor == 0) break; else lastcolor = 0;
823                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
824                                                                                 break;
825                                                                         case '1':
826                                                                                 // light red
827                                                                                 ++in;
828                                                                                 if(lastcolor == 1) break; else lastcolor = 1;
829                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
830                                                                                 break;
831                                                                         case '2':
832                                                                                 // light green
833                                                                                 ++in;
834                                                                                 if(lastcolor == 2) break; else lastcolor = 2;
835                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
836                                                                                 break;
837                                                                         case '3':
838                                                                                 // yellow
839                                                                                 ++in;
840                                                                                 if(lastcolor == 3) break; else lastcolor = 3;
841                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
842                                                                                 break;
843                                                                         case '4':
844                                                                                 // light blue
845                                                                                 ++in;
846                                                                                 if(lastcolor == 4) break; else lastcolor = 4;
847                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
848                                                                                 break;
849                                                                         case '5':
850                                                                                 // light cyan
851                                                                                 ++in;
852                                                                                 if(lastcolor == 5) break; else lastcolor = 5;
853                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
854                                                                                 break;
855                                                                         case '6':
856                                                                                 // light magenta
857                                                                                 ++in;
858                                                                                 if(lastcolor == 6) break; else lastcolor = 6;
859                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
860                                                                                 break;
861                                                                         // 7 handled above
862                                                                         case '8':
863                                                                         case '9':
864                                                                                 // bold normal color
865                                                                                 ++in;
866                                                                                 if(lastcolor == 8) break; else lastcolor = 8;
867                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
868                                                                                 break;
869                                                                         default:
870                                                                                 *out++ = STRING_COLOR_TAG;
871                                                                                 break;
872                                                                 }
873                                                                 break;
874                                                         case '\n':
875                                                                 if(lastcolor != 0)
876                                                                 {
877                                                                         *out++ = 0x1B; *out++ = '['; *out++ = 'm';
878                                                                         lastcolor = 0;
879                                                                 }
880                                                                 *out++ = *in;
881                                                                 break;
882                                                         default:
883                                                                 *out++ = *in;
884                                                                 break;
885                                                 }
886                                         }
887                                         if(lastcolor != 0)
888                                         {
889                                                 *out++ = 0x1B;
890                                                 *out++ = '[';
891                                                 *out++ = 'm';
892                                         }
893                                         *out++ = 0;
894                                         Sys_PrintToTerminal(printline);
895                                 }
896                                 else if(sys_colortranslation.integer == 2) // Quake
897                                 {
898                                         Sys_PrintToTerminal(line);
899                                 }
900                                 else // strip
901                                 {
902                                         static char printline[MAX_INPUTLINE]; // it can only get shorter here
903                                         const char *in;
904                                         char *out;
905                                         for(in = line, out = printline; *in; ++in)
906                                         {
907                                                 switch(*in)
908                                                 {
909                                                         case STRING_COLOR_TAG:
910                                                                 switch(in[1])
911                                                                 {
912                                                                         case STRING_COLOR_TAG:
913                                                                                 ++in;
914                                                                                 *out++ = STRING_COLOR_TAG;
915                                                                                 break;
916                                                                         case '0':
917                                                                         case '1':
918                                                                         case '2':
919                                                                         case '3':
920                                                                         case '4':
921                                                                         case '5':
922                                                                         case '6':
923                                                                         case '7':
924                                                                         case '8':
925                                                                         case '9':
926                                                                                 ++in;
927                                                                                 break;
928                                                                         default:
929                                                                                 *out++ = STRING_COLOR_TAG;
930                                                                                 break;
931                                                                 }
932                                                                 break;
933                                                         default:
934                                                                 *out++ = *in;
935                                                                 break;
936                                                 }
937                                         }
938                                         *out++ = 0;
939                                         Sys_PrintToTerminal(printline);
940                                 }
941                         }
942                         // empty the line buffer
943                         index = 0;
944                 }
945         }
946 }
947
948
949 /*
950 ================
951 Con_Printf
952
953 Prints to all appropriate console targets
954 ================
955 */
956 void Con_Printf(const char *fmt, ...)
957 {
958         va_list argptr;
959         char msg[MAX_INPUTLINE];
960
961         va_start(argptr,fmt);
962         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
963         va_end(argptr);
964
965         Con_Print(msg);
966 }
967
968 /*
969 ================
970 Con_DPrint
971
972 A Con_Print that only shows up if the "developer" cvar is set
973 ================
974 */
975 void Con_DPrint(const char *msg)
976 {
977         if (!developer.integer)
978                 return;                 // don't confuse non-developers with techie stuff...
979         Con_Print(msg);
980 }
981
982 /*
983 ================
984 Con_DPrintf
985
986 A Con_Printf that only shows up if the "developer" cvar is set
987 ================
988 */
989 void Con_DPrintf(const char *fmt, ...)
990 {
991         va_list argptr;
992         char msg[MAX_INPUTLINE];
993
994         if (!developer.integer)
995                 return;                 // don't confuse non-developers with techie stuff...
996
997         va_start(argptr,fmt);
998         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
999         va_end(argptr);
1000
1001         Con_Print(msg);
1002 }
1003
1004
1005 /*
1006 ==============================================================================
1007
1008 DRAWING
1009
1010 ==============================================================================
1011 */
1012
1013 /*
1014 ================
1015 Con_DrawInput
1016
1017 The input line scrolls horizontally if typing goes beyond the right edge
1018
1019 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1020 ================
1021 */
1022 void Con_DrawInput (void)
1023 {
1024         int             y;
1025         int             i;
1026         char editlinecopy[MAX_INPUTLINE+1], *text;
1027
1028         if (!key_consoleactive)
1029                 return;         // don't draw anything
1030
1031         strlcpy(editlinecopy, key_lines[edit_line], sizeof(editlinecopy));
1032         text = editlinecopy;
1033
1034         // Advanced Console Editing by Radix radix@planetquake.com
1035         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1036         // use strlen of edit_line instead of key_linepos to allow editing
1037         // of early characters w/o erasing
1038
1039         y = (int)strlen(text);
1040
1041 // fill out remainder with spaces
1042         for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1043                 text[i] = ' ';
1044
1045         // add the cursor frame
1046         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
1047                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
1048
1049 //      text[key_linepos + 1] = 0;
1050
1051         // prestep if horizontally scrolling
1052         if (key_linepos >= con_linewidth)
1053                 text += 1 + key_linepos - con_linewidth;
1054
1055         // draw it
1056         DrawQ_String(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, false );
1057
1058         // remove cursor
1059 //      key_lines[edit_line][key_linepos] = 0;
1060 }
1061
1062
1063 /*
1064 ================
1065 Con_DrawNotify
1066
1067 Draws the last few lines of output transparently over the game top
1068 ================
1069 */
1070 void Con_DrawNotify (void)
1071 {
1072         float   x, v;
1073         char    *text;
1074         int             i, stop;
1075         float   time;
1076         char    temptext[MAX_INPUTLINE];
1077         int colorindex = -1; //-1 for default
1078
1079         if (con_notify.integer < 0)
1080                 Cvar_SetValueQuick(&con_notify, 0);
1081         if (con_notify.integer > MAX_NOTIFYLINES)
1082                 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
1083         if (gamemode == GAME_TRANSFUSION)
1084                 v = 8;
1085         else
1086                 v = 0;
1087         // 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
1088         stop = con_current;
1089         for (i= stop-con_notify.integer+1 ; i<=stop ; i++)
1090         {
1091
1092                 if (i < 0)
1093                         continue;
1094                 time = con_times[i % con_notify.integer];
1095                 if (time == 0)
1096                         continue;
1097                 time = cl.time - time;
1098                 if (time > con_notifytime.value)
1099                         continue;
1100                 text = con_text + (i % con_totallines)*con_linewidth;
1101
1102                 if (gamemode == GAME_NEXUIZ) {
1103                         int chars = 0;
1104                         int finalchars = 0;
1105                         int j;
1106
1107                         // count up to the last non-whitespace, and ignore color codes
1108                         for (j = 0;j < con_linewidth && text[j];j++)
1109                         {
1110                                 if (text[j] == STRING_COLOR_TAG && (text[j+1] >= '0' && text[j+1] <= '9'))
1111                                 {
1112                                         j++;
1113                                         continue;
1114                                 }
1115                                 chars++;
1116                                 if (text[j] == ' ')
1117                                         continue;
1118                                 finalchars = chars;
1119                         }
1120                         // center the line using the calculated width
1121                         x = (vid_conwidth.integer - finalchars * con_textsize.value) * 0.5;
1122                 } else
1123                         x = 0;
1124
1125                 DrawQ_String( x, v, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1126
1127                 v += con_textsize.value;
1128         }
1129
1130
1131         if (key_dest == key_message)
1132         {
1133                 int colorindex = -1;
1134
1135                 x = 0;
1136
1137                 // LordHavoc: speedup, and other improvements
1138                 if (chat_team)
1139                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1140                 else
1141                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1142                 while ((int)strlen(temptext) >= con_linewidth)
1143                 {
1144                         DrawQ_String( 0, v, temptext, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1145                         strlcpy(temptext, &temptext[con_linewidth], sizeof(temptext));
1146                         v += con_textsize.value;
1147                 }
1148                 if (strlen(temptext) > 0)
1149                 {
1150                         DrawQ_String( 0, v, temptext, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1151                         v += con_textsize.value;
1152                 }
1153         }
1154 }
1155
1156 /*
1157 ================
1158 Con_DrawConsole
1159
1160 Draws the console with the solid background
1161 The typing input line at the bottom should only be drawn if typing is allowed
1162 ================
1163 */
1164 void Con_DrawConsole (int lines)
1165 {
1166         int i, rows, j, stop;
1167         float y;
1168         char *text;
1169         int colorindex = -1;
1170
1171         if (lines <= 0)
1172                 return;
1173
1174 // draw the background
1175         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);
1176         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, NULL, true);
1177
1178 // draw the text
1179         con_vislines = lines;
1180
1181         rows = (int)ceil((lines/con_textsize.value)-2);         // rows of text to draw
1182         y = lines - (rows+2)*con_textsize.value;        // may start slightly negative
1183
1184         // 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
1185         stop = con_current;
1186         for (i = stop - rows + 1;i <= stop;i++, y += con_textsize.value)
1187         {
1188                 j = max(i - con_backscroll, 0);
1189                 text = con_text + (j % con_totallines)*con_linewidth;
1190
1191                 DrawQ_String( 0, y, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1192         }
1193
1194 // draw the input prompt, user text, and cursor if desired
1195         Con_DrawInput ();
1196 }
1197
1198 /*
1199 GetMapList
1200
1201 Made by [515]
1202 Prints not only map filename, but also
1203 its format (q1/q2/q3/hl) and even its message
1204 */
1205 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1206 //LordHavoc: rewrote bsp type detection, added mcbsp support and rewrote message extraction to do proper worldspawn parsing
1207 //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
1208 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1209 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1210 {
1211         fssearch_t      *t;
1212         char            message[64];
1213         int                     i, k, max, p, o, min;
1214         unsigned char *len;
1215         qfile_t         *f;
1216         unsigned char buf[1024];
1217
1218         sprintf(message, "maps/%s*.bsp", s);
1219         t = FS_Search(message, 1, true);
1220         if(!t)
1221                 return false;
1222         if (t->numfilenames > 1)
1223                 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1224         len = (unsigned char *)Z_Malloc(t->numfilenames);
1225         min = 666;
1226         for(max=i=0;i<t->numfilenames;i++)
1227         {
1228                 k = (int)strlen(t->filenames[i]);
1229                 k -= 9;
1230                 if(max < k)
1231                         max = k;
1232                 else
1233                 if(min > k)
1234                         min = k;
1235                 len[i] = k;
1236         }
1237         o = (int)strlen(s);
1238         for(i=0;i<t->numfilenames;i++)
1239         {
1240                 int lumpofs = 0, lumplen = 0;
1241                 char *entities = NULL;
1242                 const char *data = NULL;
1243                 char keyname[64];
1244                 char entfilename[MAX_QPATH];
1245                 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1246                 p = 0;
1247                 f = FS_Open(t->filenames[i], "rb", true, false);
1248                 if(f)
1249                 {
1250                         memset(buf, 0, 1024);
1251                         FS_Read(f, buf, 1024);
1252                         if (!memcmp(buf, "IBSP", 4))
1253                         {
1254                                 p = LittleLong(((int *)buf)[1]);
1255                                 if (p == Q3BSPVERSION)
1256                                 {
1257                                         q3dheader_t *header = (q3dheader_t *)buf;
1258                                         lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1259                                         lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1260                                 }
1261                                 else if (p == Q2BSPVERSION)
1262                                 {
1263                                         q2dheader_t *header = (q2dheader_t *)buf;
1264                                         lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1265                                         lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1266                                 }
1267                         }
1268                         else if (!memcmp(buf, "MCBSPpad", 8))
1269                         {
1270                                 p = LittleLong(((int *)buf)[2]);
1271                                 if (p == MCBSPVERSION)
1272                                 {
1273                                         int numhulls = LittleLong(((int *)buf)[3]);
1274                                         lumpofs = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+0]);
1275                                         lumplen = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+1]);
1276                                 }
1277                         }
1278                         else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1279                         {
1280                                 dheader_t *header = (dheader_t *)buf;
1281                                 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1282                                 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1283                         }
1284                         else
1285                                 p = 0;
1286                         strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1287                         memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1288                         entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1289                         if (!entities && lumplen >= 10)
1290                         {
1291                                 FS_Seek(f, lumpofs, SEEK_SET);
1292                                 entities = (char *)Z_Malloc(lumplen + 1);
1293                                 FS_Read(f, entities, lumplen);
1294                         }
1295                         if (entities)
1296                         {
1297                                 // if there are entities to parse, a missing message key just
1298                                 // means there is no title, so clear the message string now
1299                                 message[0] = 0;
1300                                 data = entities;
1301                                 for (;;)
1302                                 {
1303                                         int l;
1304                                         if (!COM_ParseToken_Simple(&data, false, false))
1305                                                 break;
1306                                         if (com_token[0] == '{')
1307                                                 continue;
1308                                         if (com_token[0] == '}')
1309                                                 break;
1310                                         // skip leading whitespace
1311                                         for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
1312                                         for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
1313                                                 keyname[l] = com_token[k+l];
1314                                         keyname[l] = 0;
1315                                         if (!COM_ParseToken_Simple(&data, false, false))
1316                                                 break;
1317                                         if (developer.integer >= 100)
1318                                                 Con_Printf("key: %s %s\n", keyname, com_token);
1319                                         if (!strcmp(keyname, "message"))
1320                                         {
1321                                                 // get the message contents
1322                                                 strlcpy(message, com_token, sizeof(message));
1323                                                 break;
1324                                         }
1325                                 }
1326                         }
1327                 }
1328                 if (entities)
1329                         Z_Free(entities);
1330                 if(f)
1331                         FS_Close(f);
1332                 *(t->filenames[i]+len[i]+5) = 0;
1333                 switch(p)
1334                 {
1335                 case Q3BSPVERSION:      strlcpy((char *)buf, "Q3", sizeof(buf));break;
1336                 case Q2BSPVERSION:      strlcpy((char *)buf, "Q2", sizeof(buf));break;
1337                 case BSPVERSION:        strlcpy((char *)buf, "Q1", sizeof(buf));break;
1338                 case MCBSPVERSION:      strlcpy((char *)buf, "MC", sizeof(buf));break;
1339                 case 30:                        strlcpy((char *)buf, "HL", sizeof(buf));break;
1340                 default:                        strlcpy((char *)buf, "??", sizeof(buf));break;
1341                 }
1342                 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1343         }
1344         Con_Print("\n");
1345         for(p=o;p<min;p++)
1346         {
1347                 k = *(t->filenames[0]+5+p);
1348                 if(k == 0)
1349                         goto endcomplete;
1350                 for(i=1;i<t->numfilenames;i++)
1351                         if(*(t->filenames[i]+5+p) != k)
1352                                 goto endcomplete;
1353         }
1354 endcomplete:
1355         if(p > o && completedname && completednamebufferlength > 0)
1356         {
1357                 memset(completedname, 0, completednamebufferlength);
1358                 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1359         }
1360         Z_Free(len);
1361         FS_FreeSearch(t);
1362         return p > o;
1363 }
1364
1365 /*
1366         Con_DisplayList
1367
1368         New function for tab-completion system
1369         Added by EvilTypeGuy
1370         MEGA Thanks to Taniwha
1371
1372 */
1373 void Con_DisplayList(const char **list)
1374 {
1375         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1376         const char **walk = list;
1377
1378         while (*walk) {
1379                 len = (int)strlen(*walk);
1380                 if (len > maxlen)
1381                         maxlen = len;
1382                 walk++;
1383         }
1384         maxlen += 1;
1385
1386         while (*list) {
1387                 len = (int)strlen(*list);
1388                 if (pos + maxlen >= width) {
1389                         Con_Print("\n");
1390                         pos = 0;
1391                 }
1392
1393                 Con_Print(*list);
1394                 for (i = 0; i < (maxlen - len); i++)
1395                         Con_Print(" ");
1396
1397                 pos += maxlen;
1398                 list++;
1399         }
1400
1401         if (pos)
1402                 Con_Print("\n\n");
1403 }
1404
1405 /* Nicks_CompleteCountPossible
1406
1407    Count the number of possible nicks to complete
1408  */
1409 //qboolean COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets);
1410 void SanitizeString(char *in, char *out)
1411 {
1412         while(*in)
1413         {
1414                 if(*in == STRING_COLOR_TAG)
1415                 {
1416                         ++in;
1417                         if(!*in)
1418                         {
1419                                 out[0] = STRING_COLOR_TAG;
1420                                 out[1] = 0;
1421                                 return;
1422                         } else if(*in >= '0' && *in <= '9')
1423                         {
1424                                 ++in;
1425                                 if(!*in) // end
1426                                 {
1427                                         *out = 0;
1428                                         return;
1429                                 } else if (*in == STRING_COLOR_TAG)
1430                                         continue;
1431                         } else if (*in != STRING_COLOR_TAG) {
1432                                 --in;
1433                         }
1434                 }
1435                 *out = qfont_table[*(unsigned char*)in];
1436                 ++in;
1437                 ++out;
1438         }
1439         *out = 0;
1440 }
1441 int Sbar_GetPlayer (int index); // <- safety?
1442
1443 // Now it becomes TRICKY :D --blub
1444 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];     // contains the nicks with colors and all that
1445 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];  // sanitized list for completion when there are other possible matches.
1446 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
1447 static int Nicks_offset[MAX_SCOREBOARD]; // when nicks use a space, we need this to move the completion list string starts to avoid invalid memcpys
1448 static int Nicks_matchpos;
1449
1450 // co against <<:BLASTER:>> is true!?
1451 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
1452 {
1453         while(a_len)
1454         {
1455                 if(tolower(*a) == tolower(*b))
1456                 {
1457                         if(*a == 0)
1458                                 return 0;
1459                         --a_len;
1460                         ++a;
1461                         ++b;
1462                         continue;
1463                 }
1464                 if(!*a)
1465                         return -1;
1466                 if(!*b)
1467                         return 1;
1468                 if(*a == ' ')
1469                         return (*a < *b) ? -1 : 1;
1470                 if(*b == ' ')
1471                         ++b;
1472                 else
1473                         return (*a < *b) ? -1 : 1;
1474         }
1475         return 0;
1476 }
1477 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
1478 {
1479         char space_char;
1480         if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
1481         {
1482                 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
1483                         return Nicks_strncasecmp_nospaces(a, b, a_len);
1484                 return strncasecmp(a, b, a_len);
1485         }
1486         
1487         space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
1488         
1489         // ignore non alphanumerics of B
1490         // if A contains a non-alphanumeric, B must contain it as well though!
1491         while(a_len)
1492         {
1493                 qboolean alnum_a, alnum_b;
1494                 
1495                 if(tolower(*a) == tolower(*b))
1496                 {
1497                         if(*a == 0) // end of both strings, they're equal
1498                                 return 0;
1499                         --a_len;
1500                         ++a;
1501                         ++b;
1502                         continue;
1503                 }
1504                 // not equal, end of one string?
1505                 if(!*a)
1506                         return -1;
1507                 if(!*b)
1508                         return 1;
1509                 // ignore non alphanumerics
1510                 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
1511                 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
1512                 if(!alnum_a) // b must contain this
1513                         return (*a < *b) ? -1 : 1;
1514                 if(!alnum_b)
1515                         ++b;
1516                 // otherwise, both are alnum, they're just not equal, return the appropriate number
1517                 else
1518                         return (*a < *b) ? -1 : 1;
1519         }
1520         return 0;
1521 }
1522
1523 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
1524 {
1525         char name[128];
1526         int i, p;
1527         int length;
1528         int match;
1529         int spos;
1530         int count = 0;
1531         
1532         if(!con_nickcompletion.integer)
1533                 return 0;
1534
1535         // changed that to 1
1536         if(!line[0])// || !line[1]) // we want at least... 2 written characters
1537                 return 0;
1538         
1539         for(i = 0; i < cl.maxclients; ++i)
1540         {
1541                 /*p = Sbar_GetPlayer(i);
1542                 if(p < 0)
1543                 break;*/
1544                 p = i;
1545                 if(!cl.scores[p].name[0])
1546                         continue;
1547
1548                 SanitizeString(cl.scores[p].name, name);
1549                 //Con_Printf("Sanitized: %s^7 -> %s", cl.scores[p].name, name);
1550                 
1551                 if(!name[0])
1552                         continue;
1553                 
1554                 length = strlen(name);
1555                 match = -1;
1556                 spos = pos - 1; // no need for a minimum of characters :)
1557                 
1558                 while(spos >= 0 && (spos - pos) < length) // search-string-length < name length
1559                 {
1560                         if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
1561                         {
1562                                 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
1563                                    !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
1564                                 {
1565                                         --spos;
1566                                         continue;
1567                                 }
1568                         }
1569                         if(isCon && spos == 0)
1570                                 break;
1571                         if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
1572                                 match = spos;
1573                         --spos;
1574                 }
1575                 if(match < 0)
1576                         continue;
1577                 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
1578                 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
1579
1580                 // the sanitized list
1581                 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
1582                 if(!count)
1583                 {
1584                         Nicks_matchpos = match;
1585                 }
1586                 
1587                 Nicks_offset[count] = s - (&line[match]);
1588                 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
1589
1590                 ++count;
1591         }
1592         return count;
1593 }
1594
1595 void Cmd_CompleteNicksPrint(int count)
1596 {
1597         int i;
1598         for(i = 0; i < count; ++i)
1599                 Con_Printf("%s\n", Nicks_list[i]);
1600 }
1601
1602 void Nicks_CutMatchesNormal(int count)
1603 {
1604         // cut match 0 down to the longest possible completion
1605         int i;
1606         unsigned int c, l;
1607         c = strlen(Nicks_sanlist[0]) - 1;
1608         for(i = 1; i < count; ++i)
1609         {
1610                 l = strlen(Nicks_sanlist[i]) - 1;
1611                 if(l < c)
1612                         c = l;
1613                 
1614                 for(l = 0; l <= c; ++l)
1615                         if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
1616                         {
1617                                 c = l-1;
1618                                 break;
1619                         }
1620         }
1621         Nicks_sanlist[0][c+1] = 0;
1622         //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
1623 }
1624
1625 unsigned int Nicks_strcleanlen(const char *s)
1626 {
1627         unsigned int l = 0;
1628         while(*s)
1629         {
1630                 if( (*s >= 'a' && *s <= 'z') ||
1631                     (*s >= 'A' && *s <= 'Z') ||
1632                     (*s >= '0' && *s <= '9') ||
1633                     *s == ' ')
1634                         ++l;
1635                 ++s;
1636         }
1637         return l;
1638 }
1639
1640 void Nicks_CutMatchesAlphaNumeric(int count)
1641 {
1642         // cut match 0 down to the longest possible completion
1643         int i;
1644         unsigned int c, l;
1645         char tempstr[sizeof(Nicks_sanlist[0])];
1646         char *a, *b;
1647         char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
1648         
1649         c = strlen(Nicks_sanlist[0]);
1650         for(i = 0, l = 0; i < (int)c; ++i)
1651         {
1652                 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
1653                     (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
1654                     (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
1655                 {
1656                         tempstr[l++] = Nicks_sanlist[0][i];
1657                 }
1658         }
1659         tempstr[l] = 0;
1660         
1661         for(i = 1; i < count; ++i)
1662         {
1663                 a = tempstr;
1664                 b = Nicks_sanlist[i];
1665                 while(1)
1666                 {
1667                         if(!*a)
1668                                 break;
1669                         if(!*b)
1670                         {
1671                                 *a = 0;
1672                                 break;
1673                         }
1674                         if(tolower(*a) == tolower(*b))
1675                         {
1676                                 ++a;
1677                                 ++b;
1678                                 continue;
1679                         }
1680                         if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
1681                         {
1682                                 // b is alnum, so cut
1683                                 *a = 0;
1684                                 break;
1685                         }
1686                         ++b;
1687                 }
1688         }
1689         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
1690         Nicks_CutMatchesNormal(count);
1691         //if(!Nicks_sanlist[0][0])
1692         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
1693         {
1694                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
1695                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
1696         }
1697 }
1698
1699 void Nicks_CutMatchesNoSpaces(int count)
1700 {
1701         // cut match 0 down to the longest possible completion
1702         int i;
1703         unsigned int c, l;
1704         char tempstr[sizeof(Nicks_sanlist[0])];
1705         char *a, *b;
1706         
1707         c = strlen(Nicks_sanlist[0]);
1708         for(i = 0, l = 0; i < (int)c; ++i)
1709         {
1710                 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
1711                 {
1712                         tempstr[l++] = Nicks_sanlist[0][i];
1713                 }
1714         }
1715         tempstr[l] = 0;
1716         
1717         for(i = 1; i < count; ++i)
1718         {
1719                 a = tempstr;
1720                 b = Nicks_sanlist[i];
1721                 while(1)
1722                 {
1723                         if(!*a)
1724                                 break;
1725                         if(!*b)
1726                         {
1727                                 *a = 0;
1728                                 break;
1729                         }
1730                         if(tolower(*a) == tolower(*b))
1731                         {
1732                                 ++a;
1733                                 ++b;
1734                                 continue;
1735                         }
1736                         if(*b != ' ')
1737                         {
1738                                 *a = 0;
1739                                 break;
1740                         }
1741                         ++b;
1742                 }
1743         }
1744         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
1745         Nicks_CutMatchesNormal(count);
1746         //if(!Nicks_sanlist[0][0])
1747         //Con_Printf("TS: %s\n", tempstr);
1748         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
1749         {
1750                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
1751                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
1752         }
1753 }
1754
1755 void Nicks_CutMatches(int count)
1756 {
1757         if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
1758                 Nicks_CutMatchesAlphaNumeric(count);
1759         else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
1760                 Nicks_CutMatchesNoSpaces(count);
1761         else
1762                 Nicks_CutMatchesNormal(count);
1763 }
1764
1765 const char **Nicks_CompleteBuildList(int count)
1766 {
1767         const char **buf;
1768         int bpos = 0;
1769         // the list is freed by Con_CompleteCommandLine, so create a char**
1770         buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
1771
1772         for(; bpos < count; ++bpos)
1773                 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
1774
1775         Nicks_CutMatches(count);
1776         
1777         buf[bpos] = NULL;
1778         return buf;
1779 }
1780
1781 int Nicks_AddLastColor(char *buffer, int pos)
1782 {
1783         qboolean quote_added = false;
1784         int match;
1785         char color = '7';
1786         
1787         if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
1788         {
1789                 // we'll have to add a quote :)
1790                 buffer[pos++] = '\"';
1791                 quote_added = true;
1792         }
1793         
1794         if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
1795         {
1796                 // add color when no quote was added, or when flags &4?
1797                 // find last color
1798                 for(match = Nicks_matchpos-1; match >= 0; --match)
1799                 {
1800                         if(buffer[match] == STRING_COLOR_TAG && buffer[match+1] >= '0' && buffer[match+1] <= '9')
1801                         {
1802                                 color = buffer[match+1];
1803                                 break;
1804                         }
1805                 }
1806                 if(!quote_added && buffer[pos-2] == STRING_COLOR_TAG && buffer[pos-1] >= '0' && buffer[pos-1] <= '9') // when thes use &4
1807                         pos -= 2;
1808                 buffer[pos++] = STRING_COLOR_TAG;
1809                 buffer[pos++] = color;
1810         }
1811         return pos;
1812 }
1813
1814 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
1815 {
1816         int n;
1817         /*if(!con_nickcompletion.integer)
1818           return; is tested in Nicks_CompletionCountPossible */
1819         n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
1820         if(n == 1)
1821         {
1822                 size_t len;
1823                 char *msg;
1824                 
1825                 msg = Nicks_list[0];
1826                 len = min(size - Nicks_matchpos - 3, strlen(msg));
1827                 memcpy(&buffer[Nicks_matchpos], msg, len);
1828                 if( len < (size - 4) ) // space for color and space and \0
1829                         len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
1830                 buffer[len++] = ' ';
1831                 buffer[len] = 0;
1832                 return len;
1833         } else if(n > 1)
1834         {
1835                 int len;
1836                 char *msg;
1837                 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
1838                 Cmd_CompleteNicksPrint(n);
1839
1840                 Nicks_CutMatches(n);
1841
1842                 msg = Nicks_sanlist[0];
1843                 len = min(size - Nicks_matchpos, strlen(msg));
1844                 memcpy(&buffer[Nicks_matchpos], msg, len);
1845                 buffer[Nicks_matchpos + len] = 0;
1846                 //pos += len;
1847                 return Nicks_matchpos + len;
1848         }
1849         return pos;
1850 }
1851
1852
1853 /*
1854         Con_CompleteCommandLine
1855
1856         New function for tab-completion system
1857         Added by EvilTypeGuy
1858         Thanks to Fett erich@heintz.com
1859         Thanks to taniwha
1860         Enhanced to tab-complete map names by [515]
1861
1862 */
1863 void Con_CompleteCommandLine (void)
1864 {
1865         const char *cmd = "";
1866         char *s;
1867         const char **list[4] = {0, 0, 0, 0};
1868         char s2[512];
1869         int c, v, a, i, cmd_len, pos, k;
1870         int n; // nicks --blub
1871         
1872         //find what we want to complete
1873         pos = key_linepos;
1874         while(--pos)
1875         {
1876                 k = key_lines[edit_line][pos];
1877                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
1878                         break;
1879         }
1880         pos++;
1881
1882         s = key_lines[edit_line] + pos;
1883         strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2));    //save chars after cursor
1884         key_lines[edit_line][key_linepos] = 0;                                  //hide them
1885
1886         //maps search
1887         for(k=pos-1;k>2;k--)
1888                 if(key_lines[edit_line][k] != ' ')
1889                 {
1890                         if(key_lines[edit_line][k] == '\"' || key_lines[edit_line][k] == ';' || key_lines[edit_line][k] == '\'')
1891                                 break;
1892                         if      ((pos+k > 2 && !strncmp(key_lines[edit_line]+k-2, "map", 3))
1893                                 || (pos+k > 10 && !strncmp(key_lines[edit_line]+k-10, "changelevel", 11)))
1894                         {
1895                                 char t[MAX_QPATH];
1896                                 if (GetMapList(s, t, sizeof(t)))
1897                                 {
1898                                         // first move the cursor
1899                                         key_linepos += (int)strlen(t) - (int)strlen(s);
1900
1901                                         // and now do the actual work
1902                                         *s = 0;
1903                                         strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
1904                                         strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
1905
1906                                         // and fix the cursor
1907                                         if(key_linepos > (int) strlen(key_lines[edit_line]))
1908                                                 key_linepos = (int) strlen(key_lines[edit_line]);
1909                                 }
1910                                 return;
1911                         }
1912                 }
1913
1914         // Count number of possible matches and print them
1915         c = Cmd_CompleteCountPossible(s);
1916         if (c)
1917         {
1918                 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
1919                 Cmd_CompleteCommandPrint(s);
1920         }
1921         v = Cvar_CompleteCountPossible(s);
1922         if (v)
1923         {
1924                 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
1925                 Cvar_CompleteCvarPrint(s);
1926         }
1927         a = Cmd_CompleteAliasCountPossible(s);
1928         if (a)
1929         {
1930                 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
1931                 Cmd_CompleteAliasPrint(s);
1932         }
1933         n = Nicks_CompleteCountPossible(key_lines[edit_line], key_linepos, s, true);
1934         if (n)
1935         {
1936                 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
1937                 Cmd_CompleteNicksPrint(n);
1938         }
1939
1940         if (!(c + v + a + n))   // No possible matches
1941         {               
1942                 if(s2[0])
1943                         strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
1944                 return;
1945         }
1946
1947         if (c)
1948                 cmd = *(list[0] = Cmd_CompleteBuildList(s));
1949         if (v)
1950                 cmd = *(list[1] = Cvar_CompleteBuildList(s));
1951         if (a)
1952                 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
1953         if (n)
1954                 cmd = *(list[3] = Nicks_CompleteBuildList(n));
1955         
1956         for (cmd_len = (int)strlen(s);;cmd_len++)
1957         {
1958                 const char **l;
1959                 for (i = 0; i < 3; i++)
1960                         if (list[i])
1961                                 for (l = list[i];*l;l++)
1962                                         if ((*l)[cmd_len] != cmd[cmd_len])
1963                                                 goto done;
1964                 // all possible matches share this character, so we continue...
1965                 if (!cmd[cmd_len])
1966                 {
1967                         // if all matches ended at the same position, stop
1968                         // (this means there is only one match)
1969                         break;
1970                 }
1971         }
1972 done:
1973
1974         // prevent a buffer overrun by limiting cmd_len according to remaining space
1975         cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
1976         if (cmd)
1977         {
1978                 key_linepos = pos;
1979                 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
1980                 key_linepos += cmd_len;
1981                 // if there is only one match, add a space after it
1982                 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
1983                 {
1984                         if(n)
1985                         { // was a nick, might have an offset, and needs colors ;) --blub
1986                                 key_linepos = pos - Nicks_offset[0];
1987                                 cmd_len = strlen(Nicks_list[0]);
1988                                 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 3 - pos);
1989
1990                                 memcpy(&key_lines[edit_line][key_linepos] , Nicks_list[0], cmd_len);
1991                                 key_linepos += cmd_len;
1992                                 if(key_linepos < (int)(sizeof(key_lines[edit_line])-4)) // space for ^, X and space and \0
1993                                         key_linepos = Nicks_AddLastColor(key_lines[edit_line], key_linepos);
1994                         }
1995                         key_lines[edit_line][key_linepos++] = ' ';
1996                 }
1997         }
1998
1999         // use strlcat to avoid a buffer overrun
2000         key_lines[edit_line][key_linepos] = 0;
2001         strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
2002
2003         // free the command, cvar, and alias lists
2004         for (i = 0; i < 4; i++)
2005                 if (list[i])
2006                         Mem_Free((void *)list[i]);
2007 }
2008