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