]> icculus.org git repositories - divverent/darkplaces.git/blob - console.c
fixed drifting mouse pointer problems in menu
[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 float con_cursorspeed = 4;
30
31 #define         CON_TEXTSIZE    131072
32 #define         CON_MAXLINES      4096
33
34 // lines up from bottom to display
35 int con_backscroll;
36
37 // console buffer
38 char con_text[CON_TEXTSIZE];
39
40 #define CON_MASK_HIDENOTIFY 128
41 #define CON_MASK_CHAT 1
42
43 typedef struct
44 {
45         char *start;
46         int len;
47
48         double addtime;
49         int mask;
50
51         int height; // recalculated line height when needed (-1 to unset)
52 }
53 con_lineinfo;
54 con_lineinfo con_lines[CON_MAXLINES];
55
56 int con_lines_first; // cyclic buffer
57 int con_lines_count;
58 #define CON_LINES_IDX(i) ((con_lines_first + (i)) % CON_MAXLINES)
59 #define CON_LINES_LAST CON_LINES_IDX(con_lines_count - 1)
60 #define CON_LINES(i) con_lines[CON_LINES_IDX(i)]
61 #define CON_LINES_PRED(i) (((i) + CON_MAXLINES - 1) % CON_MAXLINES)
62 #define CON_LINES_SUCC(i) (((i) + 1) % CON_MAXLINES)
63
64 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
65 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
66 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
67
68 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
69 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
70 cvar_t con_chatpos = {CVAR_SAVE, "con_chatpos","0", "where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top)"};
71 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
72 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
73 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
74 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
75
76
77 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)"};
78 #ifdef WIN32
79 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)"};
80 #else
81 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)"};
82 #endif
83
84
85 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
86 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
87                                    "0: add nothing after completion. "
88                                    "1: add the last color after completion. "
89                                    "2: add a quote when starting a quote instead of the color. "
90                                    "4: will replace 1, will force color, even after a quote. "
91                                    "8: ignore non-alphanumerics. "
92                                    "16: ignore spaces. "};
93 #define NICKS_ADD_COLOR 1
94 #define NICKS_ADD_QUOTE 2
95 #define NICKS_FORCE_COLOR 4
96 #define NICKS_ALPHANUMERICS_ONLY 8
97 #define NICKS_NO_SPACES 16
98
99 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem"};
100 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem"};
101 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg"};
102
103 int con_linewidth;
104 int con_vislines;
105
106 qboolean con_initialized;
107
108 // used for server replies to rcon command
109 qboolean rcon_redirect = false;
110 int rcon_redirect_bufferpos = 0;
111 char rcon_redirect_buffer[1400];
112
113
114 /*
115 ==============================================================================
116
117 LOGGING
118
119 ==============================================================================
120 */
121
122 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
123 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"};
124 char log_dest_buffer[1400]; // UDP packet
125 size_t log_dest_buffer_pos;
126 qboolean log_dest_buffer_appending;
127 char crt_log_file [MAX_OSPATH] = "";
128 qfile_t* logfile = NULL;
129
130 unsigned char* logqueue = NULL;
131 size_t logq_ind = 0;
132 size_t logq_size = 0;
133
134 void Log_ConPrint (const char *msg);
135
136 /*
137 ====================
138 Log_DestBuffer_Init
139 ====================
140 */
141 static void Log_DestBuffer_Init()
142 {
143         memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
144         log_dest_buffer_pos = 5;
145 }
146
147 /*
148 ====================
149 Log_DestBuffer_Flush
150 ====================
151 */
152 void Log_DestBuffer_Flush()
153 {
154         lhnetaddress_t log_dest_addr;
155         lhnetsocket_t *log_dest_socket;
156         const char *s = log_dest_udp.string;
157         qboolean have_opened_temp_sockets = false;
158         if(s) if(log_dest_buffer_pos > 5)
159         {
160                 ++log_dest_buffer_appending;
161                 log_dest_buffer[log_dest_buffer_pos++] = 0;
162
163                 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
164                 {
165                         have_opened_temp_sockets = true;
166                         NetConn_OpenServerPorts(true);
167                 }
168
169                 while(COM_ParseToken_Console(&s))
170                         if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
171                         {
172                                 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
173                                 if(!log_dest_socket)
174                                         log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
175                                 if(log_dest_socket)
176                                         NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
177                         }
178
179                 if(have_opened_temp_sockets)
180                         NetConn_CloseServerPorts();
181                 --log_dest_buffer_appending;
182         }
183         log_dest_buffer_pos = 0;
184 }
185
186 /*
187 ====================
188 Log_Timestamp
189 ====================
190 */
191 const char* Log_Timestamp (const char *desc)
192 {
193         static char timestamp [128];
194         time_t crt_time;
195         const struct tm *crt_tm;
196         char timestring [64];
197
198         // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
199         time (&crt_time);
200         crt_tm = localtime (&crt_time);
201         strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
202
203         if (desc != NULL)
204                 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
205         else
206                 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
207
208         return timestamp;
209 }
210
211
212 /*
213 ====================
214 Log_Open
215 ====================
216 */
217 void Log_Open (void)
218 {
219         if (logfile != NULL || log_file.string[0] == '\0')
220                 return;
221
222         logfile = FS_Open (log_file.string, "ab", false, false);
223         if (logfile != NULL)
224         {
225                 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
226                 FS_Print (logfile, Log_Timestamp ("Log started"));
227         }
228 }
229
230
231 /*
232 ====================
233 Log_Close
234 ====================
235 */
236 void Log_Close (void)
237 {
238         if (logfile == NULL)
239                 return;
240
241         FS_Print (logfile, Log_Timestamp ("Log stopped"));
242         FS_Print (logfile, "\n");
243         FS_Close (logfile);
244
245         logfile = NULL;
246         crt_log_file[0] = '\0';
247 }
248
249
250 /*
251 ====================
252 Log_Start
253 ====================
254 */
255 void Log_Start (void)
256 {
257         size_t pos;
258         size_t n;
259         Log_Open ();
260
261         // Dump the contents of the log queue into the log file and free it
262         if (logqueue != NULL)
263         {
264                 unsigned char *temp = logqueue;
265                 logqueue = NULL;
266                 if(logq_ind != 0)
267                 {
268                         if (logfile != NULL)
269                                 FS_Write (logfile, temp, logq_ind);
270                         if(*log_dest_udp.string)
271                         {
272                                 for(pos = 0; pos < logq_ind; )
273                                 {
274                                         if(log_dest_buffer_pos == 0)
275                                                 Log_DestBuffer_Init();
276                                         n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
277                                         memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
278                                         log_dest_buffer_pos += n;
279                                         Log_DestBuffer_Flush();
280                                         pos += n;
281                                 }
282                         }
283                 }
284                 Mem_Free (temp);
285                 logq_ind = 0;
286                 logq_size = 0;
287         }
288 }
289
290
291 /*
292 ================
293 Log_ConPrint
294 ================
295 */
296 void Log_ConPrint (const char *msg)
297 {
298         static qboolean inprogress = false;
299
300         // don't allow feedback loops with memory error reports
301         if (inprogress)
302                 return;
303         inprogress = true;
304
305         // Until the host is completely initialized, we maintain a log queue
306         // to store the messages, since the log can't be started before
307         if (logqueue != NULL)
308         {
309                 size_t remain = logq_size - logq_ind;
310                 size_t len = strlen (msg);
311
312                 // If we need to enlarge the log queue
313                 if (len > remain)
314                 {
315                         size_t factor = ((logq_ind + len) / logq_size) + 1;
316                         unsigned char* newqueue;
317
318                         logq_size *= factor;
319                         newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
320                         memcpy (newqueue, logqueue, logq_ind);
321                         Mem_Free (logqueue);
322                         logqueue = newqueue;
323                         remain = logq_size - logq_ind;
324                 }
325                 memcpy (&logqueue[logq_ind], msg, len);
326                 logq_ind += len;
327
328                 inprogress = false;
329                 return;
330         }
331
332         // Check if log_file has changed
333         if (strcmp (crt_log_file, log_file.string) != 0)
334         {
335                 Log_Close ();
336                 Log_Open ();
337         }
338
339         // If a log file is available
340         if (logfile != NULL)
341                 FS_Print (logfile, msg);
342
343         inprogress = false;
344 }
345
346
347 /*
348 ================
349 Log_Printf
350 ================
351 */
352 void Log_Printf (const char *logfilename, const char *fmt, ...)
353 {
354         qfile_t *file;
355
356         file = FS_Open (logfilename, "ab", true, false);
357         if (file != NULL)
358         {
359                 va_list argptr;
360
361                 va_start (argptr, fmt);
362                 FS_VPrintf (file, fmt, argptr);
363                 va_end (argptr);
364
365                 FS_Close (file);
366         }
367 }
368
369
370 /*
371 ==============================================================================
372
373 CONSOLE
374
375 ==============================================================================
376 */
377
378 /*
379 ================
380 Con_ToggleConsole_f
381 ================
382 */
383 void Con_ToggleConsole_f (void)
384 {
385         // toggle the 'user wants console' bit
386         key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
387         Con_ClearNotify();
388 }
389
390 /*
391 ================
392 Con_Clear_f
393 ================
394 */
395 void Con_Clear_f (void)
396 {
397         con_lines_count = 0;
398 }
399
400
401 /*
402 ================
403 Con_ClearNotify
404
405 Clear all notify lines.
406 ================
407 */
408 void Con_ClearNotify (void)
409 {
410         int i;
411         for(i = 0; i < con_lines_count; ++i)
412                 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
413 }
414
415
416 /*
417 ================
418 Con_MessageMode_f
419 ================
420 */
421 void Con_MessageMode_f (void)
422 {
423         key_dest = key_message;
424         chat_team = false;
425 }
426
427
428 /*
429 ================
430 Con_MessageMode2_f
431 ================
432 */
433 void Con_MessageMode2_f (void)
434 {
435         key_dest = key_message;
436         chat_team = true;
437 }
438
439
440 /*
441 ================
442 Con_CheckResize
443
444 If the line width has changed, reformat the buffer.
445 ================
446 */
447 void Con_CheckResize (void)
448 {
449         int i, width;
450         float f;
451
452         f = bound(1, con_textsize.value, 128);
453         if(f != con_textsize.value)
454                 Cvar_SetValueQuick(&con_textsize, f);
455         width = (int)floor(vid_conwidth.value / con_textsize.value);
456         width = bound(1, width, CON_TEXTSIZE/4);
457
458         if (width == con_linewidth)
459                 return;
460
461         con_linewidth = width;
462
463         for(i = 0; i < con_lines_count; ++i)
464                 CON_LINES(i).height = -1; // recalculate when next needed
465
466         Con_ClearNotify();
467         con_backscroll = 0;
468 }
469
470 //[515]: the simplest command ever
471 //LordHavoc: not so simple after I made it print usage...
472 static void Con_Maps_f (void)
473 {
474         if (Cmd_Argc() > 2)
475         {
476                 Con_Printf("usage: maps [mapnameprefix]\n");
477                 return;
478         }
479         else if (Cmd_Argc() == 2)
480                 GetMapList(Cmd_Argv(1), NULL, 0);
481         else
482                 GetMapList("", NULL, 0);
483 }
484
485 void Con_ConDump_f (void)
486 {
487         int i;
488         qfile_t *file;
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         for(i = 0; i < con_lines_count; ++i)
501         {
502                 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
503                 FS_Write(file, "\n", 1);
504         }
505         FS_Close(file);
506 }
507
508 /*
509 ================
510 Con_Init
511 ================
512 */
513 void Con_Init (void)
514 {
515         con_linewidth = 80;
516         con_lines_first = 0;
517         con_lines_count = 0;
518
519         // Allocate a log queue, this will be freed after configs are parsed
520         logq_size = MAX_INPUTLINE;
521         logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
522         logq_ind = 0;
523
524         Cvar_RegisterVariable (&sys_colortranslation);
525         Cvar_RegisterVariable (&sys_specialcharactertranslation);
526
527         Cvar_RegisterVariable (&log_file);
528         Cvar_RegisterVariable (&log_dest_udp);
529
530         // support for the classic Quake option
531 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
532         if (COM_CheckParm ("-condebug") != 0)
533                 Cvar_SetQuick (&log_file, "qconsole.log");
534
535         // register our cvars
536         Cvar_RegisterVariable (&con_chat);
537         Cvar_RegisterVariable (&con_chatpos);
538         Cvar_RegisterVariable (&con_chatsize);
539         Cvar_RegisterVariable (&con_chattime);
540         Cvar_RegisterVariable (&con_chatwidth);
541         Cvar_RegisterVariable (&con_notify);
542         Cvar_RegisterVariable (&con_notifyalign);
543         Cvar_RegisterVariable (&con_notifysize);
544         Cvar_RegisterVariable (&con_notifytime);
545         Cvar_RegisterVariable (&con_textsize);
546
547         // --blub
548         Cvar_RegisterVariable (&con_nickcompletion);
549         Cvar_RegisterVariable (&con_nickcompletion_flags);
550
551         Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
552         Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
553         Cvar_RegisterVariable (&con_completion_exec); // *.cfg
554
555         // register our commands
556         Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
557         Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
558         Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
559         Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
560         Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
561         Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
562
563         con_initialized = true;
564         Con_DPrint("Console initialized.\n");
565 }
566
567
568 /*
569 ================
570 Con_DeleteLine
571
572 Deletes the first line from the console history.
573 ================
574 */
575 void Con_DeleteLine()
576 {
577         if(con_lines_count == 0)
578                 return;
579         --con_lines_count;
580         con_lines_first = CON_LINES_IDX(1);
581 }
582
583 /*
584 ================
585 Con_DeleteLastLine
586
587 Deletes the last line from the console history.
588 ================
589 */
590 void Con_DeleteLastLine()
591 {
592         if(con_lines_count == 0)
593                 return;
594         --con_lines_count;
595 }
596
597 /*
598 ================
599 Con_BytesLeft
600
601 Checks if there is space for a line of the given length, and if yes, returns a
602 pointer to the start of such a space, and NULL otherwise.
603 ================
604 */
605 char *Con_BytesLeft(int len)
606 {
607         if(len > CON_TEXTSIZE)
608                 return NULL;
609         if(con_lines_count == 0)
610                 return con_text;
611         else
612         {
613                 char *firstline_start = con_lines[con_lines_first].start;
614                 char *lastline_onepastend = con_lines[CON_LINES_LAST].start + con_lines[CON_LINES_LAST].len;
615                 // the buffer is cyclic, so we first have two cases...
616                 if(firstline_start < lastline_onepastend) // buffer is contiguous
617                 {
618                         // put at end?
619                         if(len <= con_text + CON_TEXTSIZE - lastline_onepastend)
620                                 return lastline_onepastend;
621                         // put at beginning?
622                         else if(len <= firstline_start - con_text)
623                                 return con_text;
624                         else
625                                 return NULL;
626                 }
627                 else // buffer has a contiguous hole
628                 {
629                         if(len <= firstline_start - lastline_onepastend)
630                                 return lastline_onepastend;
631                         else
632                                 return NULL;
633                 }
634         }
635 }
636
637 /*
638 ================
639 Con_FixTimes
640
641 Notifies the console code about the current time
642 (and shifts back times of other entries when the time
643 went backwards)
644 ================
645 */
646 void Con_FixTimes()
647 {
648         int i;
649         if(con_lines_count >= 1)
650         {
651                 double diff = cl.time - (con_lines + CON_LINES_LAST)->addtime;
652                 if(diff < 0)
653                 {
654                         for(i = 0; i < con_lines_count; ++i)
655                                 CON_LINES(i).addtime += diff;
656                 }
657         }
658 }
659
660 /*
661 ================
662 Con_AddLine
663
664 Appends a given string as a new line to the console.
665 ================
666 */
667 void Con_AddLine(const char *line, int len, int mask)
668 {
669         char *putpos;
670         con_lineinfo *p;
671
672         Con_FixTimes();
673
674         if(len >= CON_TEXTSIZE)
675         {
676                 // line too large?
677                 // only display end of line.
678                 line += len - CON_TEXTSIZE + 1;
679                 len = CON_TEXTSIZE - 1;
680         }
681         while(!(putpos = Con_BytesLeft(len + 1)) || con_lines_count >= CON_MAXLINES)
682                 Con_DeleteLine();
683         memcpy(putpos, line, len);
684         putpos[len] = 0;
685         ++con_lines_count;
686
687         //fprintf(stderr, "Now have %d lines (%d -> %d).\n", con_lines_count, con_lines_first, CON_LINES_LAST);
688
689         p = con_lines + CON_LINES_LAST;
690         p->start = putpos;
691         p->len = len;
692         p->addtime = cl.time;
693         p->mask = mask;
694         p->height = -1; // calculate when needed
695 }
696
697 /*
698 ================
699 Con_PrintToHistory
700
701 Handles cursor positioning, line wrapping, etc
702 All console printing must go through this in order to be displayed
703 If no console is visible, the notify window will pop up.
704 ================
705 */
706 void Con_PrintToHistory(const char *txt, int mask)
707 {
708         // process:
709         //   \n goes to next line
710         //   \r deletes current line and makes a new one
711
712         static int cr_pending = 0;
713         static char buf[CON_TEXTSIZE];
714         static int bufpos = 0;
715
716         for(; *txt; ++txt)
717         {
718                 if(cr_pending)
719                 {
720                         Con_DeleteLastLine();
721                         cr_pending = 0;
722                 }
723                 switch(*txt)
724                 {
725                         case 0:
726                                 break;
727                         case '\r':
728                                 Con_AddLine(buf, bufpos, mask);
729                                 bufpos = 0;
730                                 cr_pending = 1;
731                                 break;
732                         case '\n':
733                                 Con_AddLine(buf, bufpos, mask);
734                                 bufpos = 0;
735                                 break;
736                         default:
737                                 buf[bufpos++] = *txt;
738                                 if(bufpos >= CON_TEXTSIZE - 1)
739                                 {
740                                         Con_AddLine(buf, bufpos, mask);
741                                         bufpos = 0;
742                                 }
743                                 break;
744                 }
745         }
746 }
747
748 /* The translation table between the graphical font and plain ASCII  --KB */
749 static char qfont_table[256] = {
750         '\0', '#',  '#',  '#',  '#',  '.',  '#',  '#',
751         '#',  9,    10,   '#',  ' ',  13,   '.',  '.',
752         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
753         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
754         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
755         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
756         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
757         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
758         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
759         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
760         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
761         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
762         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
763         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
764         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
765         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<',
766
767         '<',  '=',  '>',  '#',  '#',  '.',  '#',  '#',
768         '#',  '#',  ' ',  '#',  ' ',  '>',  '.',  '.',
769         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
770         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
771         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
772         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
773         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
774         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
775         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
776         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
777         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
778         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
779         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
780         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
781         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
782         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<'
783 };
784
785 /*
786 ================
787 Con_Rcon_AddChar
788
789 Adds a character to the rcon buffer
790 ================
791 */
792 void Con_Rcon_AddChar(char c)
793 {
794         if(log_dest_buffer_appending)
795                 return;
796         ++log_dest_buffer_appending;
797
798         // if this print is in response to an rcon command, add the character
799         // to the rcon redirect buffer
800
801         if (rcon_redirect && rcon_redirect_bufferpos < (int)sizeof(rcon_redirect_buffer) - 1)
802                 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
803         else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
804         {
805                 if(log_dest_buffer_pos == 0)
806                         Log_DestBuffer_Init();
807                 log_dest_buffer[log_dest_buffer_pos++] = c;
808                 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
809                         Log_DestBuffer_Flush();
810         }
811         else
812                 log_dest_buffer_pos = 0;
813
814         --log_dest_buffer_appending;
815 }
816
817 /*
818 ================
819 Con_Print
820
821 Prints to all appropriate console targets, and adds timestamps
822 ================
823 */
824 extern cvar_t timestamps;
825 extern cvar_t timeformat;
826 extern qboolean sys_nostdout;
827 void Con_Print(const char *msg)
828 {
829         static int mask = 0;
830         static int index = 0;
831         static char line[MAX_INPUTLINE];
832
833         for (;*msg;msg++)
834         {
835                 Con_Rcon_AddChar(*msg);
836                 // if this is the beginning of a new line, print timestamp
837                 if (index == 0)
838                 {
839                         const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
840                         // reset the color
841                         // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
842                         line[index++] = STRING_COLOR_TAG;
843                         // assert( STRING_COLOR_DEFAULT < 10 )
844                         line[index++] = STRING_COLOR_DEFAULT + '0';
845                         // special color codes for chat messages must always come first
846                         // for Con_PrintToHistory to work properly
847                         if (*msg == 1 || *msg == 2)
848                         {
849                                 // play talk wav
850                                 if (*msg == 1)
851                                 {
852                                         if(gamemode == GAME_NEXUIZ)
853                                         {
854                                                 if(msg[1] == '\r' && cl.foundtalk2wav)
855                                                         S_LocalSound ("sound/misc/talk2.wav");
856                                                 else
857                                                         S_LocalSound ("sound/misc/talk.wav");
858                                         }
859                                         else
860                                         {
861                                                 if (msg[1] == '(' && cl.foundtalk2wav)
862                                                         S_LocalSound ("sound/misc/talk2.wav");
863                                                 else
864                                                         S_LocalSound ("sound/misc/talk.wav");
865                                         }
866                                         mask = CON_MASK_CHAT;
867                                 }
868                                 line[index++] = STRING_COLOR_TAG;
869                                 line[index++] = '3';
870                                 msg++;
871                                 Con_Rcon_AddChar(*msg);
872                         }
873                         // store timestamp
874                         for (;*timestamp;index++, timestamp++)
875                                 if (index < (int)sizeof(line) - 2)
876                                         line[index] = *timestamp;
877                 }
878                 // append the character
879                 line[index++] = *msg;
880                 // if this is a newline character, we have a complete line to print
881                 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
882                 {
883                         // terminate the line
884                         line[index] = 0;
885                         // send to log file
886                         Log_ConPrint(line);
887                         // send to scrollable buffer
888                         if (con_initialized && cls.state != ca_dedicated)
889                         {
890                                 Con_PrintToHistory(line, mask);
891                                 mask = 0;
892                         }
893                         // send to terminal or dedicated server window
894                         if (!sys_nostdout)
895                         {
896                                 unsigned char *p;
897                                 if(sys_specialcharactertranslation.integer)
898                                 {
899                                         for (p = (unsigned char *) line;*p; p++)
900                                                 *p = qfont_table[*p];
901                                 }
902
903                                 if(sys_colortranslation.integer == 1) // ANSI
904                                 {
905                                         static char printline[MAX_INPUTLINE * 4 + 3];
906                                                 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
907                                                 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
908                                         int lastcolor = 0;
909                                         const char *in;
910                                         char *out;
911                                         for(in = line, out = printline; *in; ++in)
912                                         {
913                                                 switch(*in)
914                                                 {
915                                                         case STRING_COLOR_TAG:
916                                                                 switch(in[1])
917                                                                 {
918                                                                         case STRING_COLOR_TAG:
919                                                                                 ++in;
920                                                                                 *out++ = STRING_COLOR_TAG;
921                                                                                 break;
922                                                                         case '0':
923                                                                         case '7':
924                                                                                 // normal color
925                                                                                 ++in;
926                                                                                 if(lastcolor == 0) break; else lastcolor = 0;
927                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
928                                                                                 break;
929                                                                         case '1':
930                                                                                 // light red
931                                                                                 ++in;
932                                                                                 if(lastcolor == 1) break; else lastcolor = 1;
933                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
934                                                                                 break;
935                                                                         case '2':
936                                                                                 // light green
937                                                                                 ++in;
938                                                                                 if(lastcolor == 2) break; else lastcolor = 2;
939                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
940                                                                                 break;
941                                                                         case '3':
942                                                                                 // yellow
943                                                                                 ++in;
944                                                                                 if(lastcolor == 3) break; else lastcolor = 3;
945                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
946                                                                                 break;
947                                                                         case '4':
948                                                                                 // light blue
949                                                                                 ++in;
950                                                                                 if(lastcolor == 4) break; else lastcolor = 4;
951                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
952                                                                                 break;
953                                                                         case '5':
954                                                                                 // light cyan
955                                                                                 ++in;
956                                                                                 if(lastcolor == 5) break; else lastcolor = 5;
957                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
958                                                                                 break;
959                                                                         case '6':
960                                                                                 // light magenta
961                                                                                 ++in;
962                                                                                 if(lastcolor == 6) break; else lastcolor = 6;
963                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
964                                                                                 break;
965                                                                         // 7 handled above
966                                                                         case '8':
967                                                                         case '9':
968                                                                                 // bold normal color
969                                                                                 ++in;
970                                                                                 if(lastcolor == 8) break; else lastcolor = 8;
971                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
972                                                                                 break;
973                                                                         default:
974                                                                                 *out++ = STRING_COLOR_TAG;
975                                                                                 break;
976                                                                 }
977                                                                 break;
978                                                         case '\n':
979                                                                 if(lastcolor != 0)
980                                                                 {
981                                                                         *out++ = 0x1B; *out++ = '['; *out++ = 'm';
982                                                                         lastcolor = 0;
983                                                                 }
984                                                                 *out++ = *in;
985                                                                 break;
986                                                         default:
987                                                                 *out++ = *in;
988                                                                 break;
989                                                 }
990                                         }
991                                         if(lastcolor != 0)
992                                         {
993                                                 *out++ = 0x1B;
994                                                 *out++ = '[';
995                                                 *out++ = 'm';
996                                         }
997                                         *out++ = 0;
998                                         Sys_PrintToTerminal(printline);
999                                 }
1000                                 else if(sys_colortranslation.integer == 2) // Quake
1001                                 {
1002                                         Sys_PrintToTerminal(line);
1003                                 }
1004                                 else // strip
1005                                 {
1006                                         static char printline[MAX_INPUTLINE]; // it can only get shorter here
1007                                         const char *in;
1008                                         char *out;
1009                                         for(in = line, out = printline; *in; ++in)
1010                                         {
1011                                                 switch(*in)
1012                                                 {
1013                                                         case STRING_COLOR_TAG:
1014                                                                 switch(in[1])
1015                                                                 {
1016                                                                         case STRING_COLOR_TAG:
1017                                                                                 ++in;
1018                                                                                 *out++ = STRING_COLOR_TAG;
1019                                                                                 break;
1020                                                                         case '0':
1021                                                                         case '1':
1022                                                                         case '2':
1023                                                                         case '3':
1024                                                                         case '4':
1025                                                                         case '5':
1026                                                                         case '6':
1027                                                                         case '7':
1028                                                                         case '8':
1029                                                                         case '9':
1030                                                                                 ++in;
1031                                                                                 break;
1032                                                                         default:
1033                                                                                 *out++ = STRING_COLOR_TAG;
1034                                                                                 break;
1035                                                                 }
1036                                                                 break;
1037                                                         default:
1038                                                                 *out++ = *in;
1039                                                                 break;
1040                                                 }
1041                                         }
1042                                         *out++ = 0;
1043                                         Sys_PrintToTerminal(printline);
1044                                 }
1045                         }
1046                         // empty the line buffer
1047                         index = 0;
1048                 }
1049         }
1050 }
1051
1052
1053 /*
1054 ================
1055 Con_Printf
1056
1057 Prints to all appropriate console targets
1058 ================
1059 */
1060 void Con_Printf(const char *fmt, ...)
1061 {
1062         va_list argptr;
1063         char msg[MAX_INPUTLINE];
1064
1065         va_start(argptr,fmt);
1066         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1067         va_end(argptr);
1068
1069         Con_Print(msg);
1070 }
1071
1072 /*
1073 ================
1074 Con_DPrint
1075
1076 A Con_Print that only shows up if the "developer" cvar is set
1077 ================
1078 */
1079 void Con_DPrint(const char *msg)
1080 {
1081         if (!developer.integer)
1082                 return;                 // don't confuse non-developers with techie stuff...
1083         Con_Print(msg);
1084 }
1085
1086 /*
1087 ================
1088 Con_DPrintf
1089
1090 A Con_Printf that only shows up if the "developer" cvar is set
1091 ================
1092 */
1093 void Con_DPrintf(const char *fmt, ...)
1094 {
1095         va_list argptr;
1096         char msg[MAX_INPUTLINE];
1097
1098         if (!developer.integer)
1099                 return;                 // don't confuse non-developers with techie stuff...
1100
1101         va_start(argptr,fmt);
1102         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1103         va_end(argptr);
1104
1105         Con_Print(msg);
1106 }
1107
1108
1109 /*
1110 ==============================================================================
1111
1112 DRAWING
1113
1114 ==============================================================================
1115 */
1116
1117 /*
1118 ================
1119 Con_DrawInput
1120
1121 The input line scrolls horizontally if typing goes beyond the right edge
1122
1123 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1124 ================
1125 */
1126 void Con_DrawInput (void)
1127 {
1128         int             y;
1129         int             i;
1130         char editlinecopy[MAX_INPUTLINE+1], *text;
1131         float x;
1132
1133         if (!key_consoleactive)
1134                 return;         // don't draw anything
1135
1136         strlcpy(editlinecopy, key_lines[edit_line], sizeof(editlinecopy));
1137         text = editlinecopy;
1138
1139         // Advanced Console Editing by Radix radix@planetquake.com
1140         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1141         // use strlen of edit_line instead of key_linepos to allow editing
1142         // of early characters w/o erasing
1143
1144         y = (int)strlen(text);
1145
1146 // fill out remainder with spaces
1147         for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1148                 text[i] = ' ';
1149
1150         // add the cursor frame
1151         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
1152                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
1153
1154 //      text[key_linepos + 1] = 0;
1155
1156         x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1157         if(x >= 0)
1158                 x = 0;
1159
1160         // draw it
1161         DrawQ_String_Font(x, con_vislines - con_textsize.value*2, text, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, FONT_CONSOLE );
1162
1163         // remove cursor
1164 //      key_lines[edit_line][key_linepos] = 0;
1165 }
1166
1167 typedef struct
1168 {
1169         dp_font_t *font;
1170         float alignment; // 0 = left, 0.5 = center, 1 = right
1171         float fontsize;
1172         float x;
1173         float y;
1174         float width;
1175         float ymin, ymax;
1176         const char *continuationString;
1177
1178         // PRIVATE:
1179         int colorindex; // init to -1
1180 }
1181 con_text_info_t;
1182
1183 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1184 {
1185         con_text_info_t *ti = (con_text_info_t *) passthrough;
1186         if(w == NULL)
1187         {
1188                 ti->colorindex = -1;
1189                 return ti->fontsize * ti->font->width_of[0];
1190         }
1191         if(maxWidth >= 0)
1192                 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1193         else if(maxWidth == -1)
1194                 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1195         else
1196         {
1197                 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1198                 // Note: this is NOT a Con_Printf, as it could print recursively
1199                 return 0;
1200         }
1201 }
1202
1203 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1204 {
1205         (void) passthrough;
1206         (void) line;
1207         (void) length;
1208         (void) width;
1209         (void) isContinuation;
1210         return 1;
1211 }
1212
1213 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1214 {
1215         con_text_info_t *ti = (con_text_info_t *) passthrough;
1216
1217         if(ti->y < ti->ymin - 0.001)
1218                 (void) 0;
1219         else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1220                 (void) 0;
1221         else
1222         {
1223                 int x = ti->x + (ti->width - width) * ti->alignment;
1224                 if(isContinuation && *ti->continuationString)
1225                         x += DrawQ_String_Font(x, ti->y, ti->continuationString, strlen(ti->continuationString), ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, ti->font);
1226                 if(length > 0)
1227                         DrawQ_String_Font(x, ti->y, line, length, ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, &(ti->colorindex), false, ti->font);
1228         }
1229
1230         ti->y += ti->fontsize;
1231         return 1;
1232 }
1233
1234
1235 int Con_DrawNotifyRect(int mask_must, int mask_mustnot, float maxage, float x, float y, float width, float height, float fontsize, float alignment_x, float alignment_y, const char *continuationString)
1236 {
1237         int i;
1238         int lines = 0;
1239         int maxlines = (int) floor(height / fontsize + 0.01f);
1240         int startidx;
1241         int nskip = 0;
1242         int continuationWidth = 0;
1243         size_t l;
1244         double t = cl.time; // saved so it won't change
1245         con_text_info_t ti;
1246
1247         ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1248         ti.fontsize = fontsize;
1249         ti.alignment = alignment_x;
1250         ti.width = width;
1251         ti.ymin = y;
1252         ti.ymax = y + height;
1253         ti.continuationString = continuationString;
1254
1255         l = 0;
1256         Con_WordWidthFunc(&ti, NULL, &l, -1);
1257         l = strlen(continuationString);
1258         continuationWidth = Con_WordWidthFunc(&ti, continuationString, &l, -1);
1259
1260         // first find the first line to draw by backwards iterating and word wrapping to find their length...
1261         startidx = con_lines_count;
1262         for(i = con_lines_count - 1; i >= 0; --i)
1263         {
1264                 con_lineinfo *l = &CON_LINES(i);
1265                 int mylines;
1266
1267                 if((l->mask & mask_must) != mask_must)
1268                         continue;
1269                 if(l->mask & mask_mustnot)
1270                         continue;
1271                 if(maxage && (l->addtime < t - maxage))
1272                         continue;
1273
1274                 // WE FOUND ONE!
1275                 // Calculate its actual height...
1276                 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1277                 if(lines + mylines >= maxlines)
1278                 {
1279                         nskip = lines + mylines - maxlines;
1280                         lines = maxlines;
1281                         startidx = i;
1282                         break;
1283                 }
1284                 lines += mylines;
1285                 startidx = i;
1286         }
1287
1288         // then center according to the calculated amount of lines...
1289         ti.x = x;
1290         ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1291
1292         // then actually draw
1293         for(i = startidx; i < con_lines_count; ++i)
1294         {
1295                 con_lineinfo *l = &CON_LINES(i);
1296
1297                 if((l->mask & mask_must) != mask_must)
1298                         continue;
1299                 if(l->mask & mask_mustnot)
1300                         continue;
1301                 if(maxage && (l->addtime < t - maxage))
1302                         continue;
1303
1304                 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1305         }
1306
1307         return lines;
1308 }
1309
1310 /*
1311 ================
1312 Con_DrawNotify
1313
1314 Draws the last few lines of output transparently over the game top
1315 ================
1316 */
1317 void Con_DrawNotify (void)
1318 {
1319         float   x, v;
1320         float chatstart, notifystart, inputsize;
1321         float align;
1322         char    temptext[MAX_INPUTLINE];
1323         int numChatlines;
1324         int chatpos;
1325
1326         Con_FixTimes();
1327
1328         numChatlines = con_chat.integer;
1329         chatpos = con_chatpos.integer;
1330
1331         if (con_notify.integer < 0)
1332                 Cvar_SetValueQuick(&con_notify, 0);
1333         if (gamemode == GAME_TRANSFUSION)
1334                 v = 8; // vertical offset
1335         else
1336                 v = 0;
1337
1338         // GAME_NEXUIZ: center, otherwise left justify
1339         align = con_notifyalign.value;
1340         if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1341         {
1342                 if(gamemode == GAME_NEXUIZ)
1343                         align = 0.5;
1344         }
1345
1346         if(numChatlines)
1347         {
1348                 if(chatpos == 0)
1349                 {
1350                         // first chat, input line, then notify
1351                         chatstart = v;
1352                         notifystart = v + (numChatlines + 1) * con_chatsize.value;
1353                 }
1354                 else if(chatpos > 0)
1355                 {
1356                         // first notify, then (chatpos-1) empty lines, then chat, then input
1357                         notifystart = v;
1358                         chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1359                 }
1360                 else // if(chatpos < 0)
1361                 {
1362                         // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1363                         notifystart = v;
1364                         chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1365                 }
1366         }
1367         else
1368         {
1369                 // just notify and input
1370                 notifystart = v;
1371                 chatstart = 0; // shut off gcc warning
1372         }
1373
1374         v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_HIDENOTIFY | (numChatlines ? CON_MASK_CHAT : 0), con_notifytime.value, 0, notifystart, vid_conwidth.value, con_notify.value * con_notifysize.value, con_notifysize.value, align, 0.0, "");
1375
1376         // chat?
1377         if(numChatlines)
1378         {
1379                 v = chatstart + numChatlines * con_chatsize.value;
1380                 Con_DrawNotifyRect(CON_MASK_CHAT, 0, con_chattime.value, 0, chatstart, vid_conwidth.value * con_chatwidth.value, v - chatstart, con_chatsize.value, 0.0, 1.0, "^3\014\014\014 "); // 015 is Â·> character in conchars.tga
1381         }
1382
1383         if (key_dest == key_message)
1384         {
1385                 int colorindex = -1;
1386
1387                 // LordHavoc: speedup, and other improvements
1388                 if (chat_team)
1389                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1390                 else
1391                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1392
1393                 // FIXME word wrap
1394                 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1395                 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1396                 if(x > 0)
1397                         x = 0;
1398                 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1399         }
1400 }
1401
1402 /*
1403 ================
1404 Con_MeasureConsoleLine
1405
1406 Counts the number of lines for a line on the console.
1407 ================
1408 */
1409 int Con_MeasureConsoleLine(int lineno)
1410 {
1411         float width = vid_conwidth.value;
1412         con_text_info_t ti;
1413         ti.fontsize = con_textsize.value;
1414         ti.font = FONT_CONSOLE;
1415
1416         return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1417 }
1418
1419 /*
1420 ================
1421 Con_LineHeight
1422
1423 Returns the height of a given console line; calculates it if necessary.
1424 ================
1425 */
1426 int Con_LineHeight(int i)
1427 {
1428         int h = con_lines[i].height;
1429         if(h != -1)
1430                 return h;
1431         return con_lines[i].height = Con_MeasureConsoleLine(i);
1432 }
1433
1434 /*
1435 ================
1436 Con_DrawConsoleLine
1437
1438 Draws a line of the console; returns its height in lines.
1439 If alpha is 0, the line is not drawn, but still wrapped and its height
1440 returned.
1441 ================
1442 */
1443 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1444 {
1445         float width = vid_conwidth.value;
1446
1447         con_text_info_t ti;
1448         ti.continuationString = "";
1449         ti.alignment = 0;
1450         ti.fontsize = con_textsize.value;
1451         ti.font = FONT_CONSOLE;
1452         ti.x = 0;
1453         ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1454         ti.ymin = ymin;
1455         ti.ymax = ymax;
1456         ti.width = width;
1457
1458         return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1459 }
1460
1461 /*
1462 ================
1463 Con_LastVisibleLine
1464
1465 Calculates the last visible line index and how much to show of it based on
1466 con_backscroll.
1467 ================
1468 */
1469 void Con_LastVisibleLine(int *last, int *limitlast)
1470 {
1471         int lines_seen = 0;
1472         int ic;
1473
1474         if(con_backscroll < 0)
1475                 con_backscroll = 0;
1476
1477         // now count until we saw con_backscroll actual lines
1478         for(ic = 0; ic < con_lines_count; ++ic)
1479         {
1480                 int i = CON_LINES_IDX(con_lines_count - 1 - ic);
1481                 int h = Con_LineHeight(i);
1482
1483                 // line is the last visible line?
1484                 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1485                 {
1486                         *last = i;
1487                         *limitlast = lines_seen + h - con_backscroll;
1488                         return;
1489                 }
1490
1491                 lines_seen += h;
1492         }
1493
1494         // if we get here, no line was on screen - scroll so that one line is
1495         // visible then.
1496         con_backscroll = lines_seen - 1;
1497         *last = con_lines_first;
1498         *limitlast = 1;
1499 }
1500
1501 /*
1502 ================
1503 Con_DrawConsole
1504
1505 Draws the console with the solid background
1506 The typing input line at the bottom should only be drawn if typing is allowed
1507 ================
1508 */
1509 void Con_DrawConsole (int lines)
1510 {
1511         int i, last, limitlast;
1512         float y;
1513
1514         if (lines <= 0)
1515                 return;
1516
1517         con_vislines = lines;
1518
1519 // draw the background
1520         DrawQ_Pic(0, lines - vid_conheight.integer, scr_conbrightness.value >= 0.01f ? Draw_CachePic ("gfx/conback") : NULL, vid_conwidth.integer, vid_conheight.integer, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, cls.signon == SIGNONS ? scr_conalpha.value : 1.0, 0); // always full alpha when not in game
1521         DrawQ_String_Font(vid_conwidth.integer - DrawQ_TextWidth_Font(engineversion, 0, false, FONT_CONSOLE) * con_textsize.value, lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0, NULL, true, FONT_CONSOLE);
1522
1523 // draw the text
1524         if(con_lines_count > 0)
1525         {
1526                 float ymax = con_vislines - 2 * con_textsize.value;
1527                 Con_LastVisibleLine(&last, &limitlast);
1528                 y = ymax - con_textsize.value;
1529
1530                 if(limitlast)
1531                         y += (con_lines[last].height - limitlast) * con_textsize.value;
1532                 i = last;
1533
1534                 for(;;)
1535                 {
1536                         y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1537                         if(i == con_lines_first)
1538                                 break; // top of console buffer
1539                         if(y < 0)
1540                                 break; // top of console window
1541                         limitlast = 0;
1542                         i = CON_LINES_PRED(i);
1543                 }
1544         }
1545
1546 // draw the input prompt, user text, and cursor if desired
1547         Con_DrawInput ();
1548 }
1549
1550 /*
1551 GetMapList
1552
1553 Made by [515]
1554 Prints not only map filename, but also
1555 its format (q1/q2/q3/hl) and even its message
1556 */
1557 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1558 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1559 //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
1560 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1561 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1562 {
1563         fssearch_t      *t;
1564         char            message[64];
1565         int                     i, k, max, p, o, min;
1566         unsigned char *len;
1567         qfile_t         *f;
1568         unsigned char buf[1024];
1569
1570         sprintf(message, "maps/%s*.bsp", s);
1571         t = FS_Search(message, 1, true);
1572         if(!t)
1573                 return false;
1574         if (t->numfilenames > 1)
1575                 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1576         len = (unsigned char *)Z_Malloc(t->numfilenames);
1577         min = 666;
1578         for(max=i=0;i<t->numfilenames;i++)
1579         {
1580                 k = (int)strlen(t->filenames[i]);
1581                 k -= 9;
1582                 if(max < k)
1583                         max = k;
1584                 else
1585                 if(min > k)
1586                         min = k;
1587                 len[i] = k;
1588         }
1589         o = (int)strlen(s);
1590         for(i=0;i<t->numfilenames;i++)
1591         {
1592                 int lumpofs = 0, lumplen = 0;
1593                 char *entities = NULL;
1594                 const char *data = NULL;
1595                 char keyname[64];
1596                 char entfilename[MAX_QPATH];
1597                 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1598                 p = 0;
1599                 f = FS_Open(t->filenames[i], "rb", true, false);
1600                 if(f)
1601                 {
1602                         memset(buf, 0, 1024);
1603                         FS_Read(f, buf, 1024);
1604                         if (!memcmp(buf, "IBSP", 4))
1605                         {
1606                                 p = LittleLong(((int *)buf)[1]);
1607                                 if (p == Q3BSPVERSION)
1608                                 {
1609                                         q3dheader_t *header = (q3dheader_t *)buf;
1610                                         lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1611                                         lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1612                                 }
1613                                 else if (p == Q2BSPVERSION)
1614                                 {
1615                                         q2dheader_t *header = (q2dheader_t *)buf;
1616                                         lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1617                                         lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1618                                 }
1619                         }
1620                         else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1621                         {
1622                                 dheader_t *header = (dheader_t *)buf;
1623                                 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1624                                 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1625                         }
1626                         else
1627                                 p = 0;
1628                         strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1629                         memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1630                         entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1631                         if (!entities && lumplen >= 10)
1632                         {
1633                                 FS_Seek(f, lumpofs, SEEK_SET);
1634                                 entities = (char *)Z_Malloc(lumplen + 1);
1635                                 FS_Read(f, entities, lumplen);
1636                         }
1637                         if (entities)
1638                         {
1639                                 // if there are entities to parse, a missing message key just
1640                                 // means there is no title, so clear the message string now
1641                                 message[0] = 0;
1642                                 data = entities;
1643                                 for (;;)
1644                                 {
1645                                         int l;
1646                                         if (!COM_ParseToken_Simple(&data, false, false))
1647                                                 break;
1648                                         if (com_token[0] == '{')
1649                                                 continue;
1650                                         if (com_token[0] == '}')
1651                                                 break;
1652                                         // skip leading whitespace
1653                                         for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
1654                                         for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
1655                                                 keyname[l] = com_token[k+l];
1656                                         keyname[l] = 0;
1657                                         if (!COM_ParseToken_Simple(&data, false, false))
1658                                                 break;
1659                                         if (developer.integer >= 100)
1660                                                 Con_Printf("key: %s %s\n", keyname, com_token);
1661                                         if (!strcmp(keyname, "message"))
1662                                         {
1663                                                 // get the message contents
1664                                                 strlcpy(message, com_token, sizeof(message));
1665                                                 break;
1666                                         }
1667                                 }
1668                         }
1669                 }
1670                 if (entities)
1671                         Z_Free(entities);
1672                 if(f)
1673                         FS_Close(f);
1674                 *(t->filenames[i]+len[i]+5) = 0;
1675                 switch(p)
1676                 {
1677                 case Q3BSPVERSION:      strlcpy((char *)buf, "Q3", sizeof(buf));break;
1678                 case Q2BSPVERSION:      strlcpy((char *)buf, "Q2", sizeof(buf));break;
1679                 case BSPVERSION:        strlcpy((char *)buf, "Q1", sizeof(buf));break;
1680                 case 30:                        strlcpy((char *)buf, "HL", sizeof(buf));break;
1681                 default:                        strlcpy((char *)buf, "??", sizeof(buf));break;
1682                 }
1683                 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1684         }
1685         Con_Print("\n");
1686         for(p=o;p<min;p++)
1687         {
1688                 k = *(t->filenames[0]+5+p);
1689                 if(k == 0)
1690                         goto endcomplete;
1691                 for(i=1;i<t->numfilenames;i++)
1692                         if(*(t->filenames[i]+5+p) != k)
1693                                 goto endcomplete;
1694         }
1695 endcomplete:
1696         if(p > o && completedname && completednamebufferlength > 0)
1697         {
1698                 memset(completedname, 0, completednamebufferlength);
1699                 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1700         }
1701         Z_Free(len);
1702         FS_FreeSearch(t);
1703         return p > o;
1704 }
1705
1706 /*
1707         Con_DisplayList
1708
1709         New function for tab-completion system
1710         Added by EvilTypeGuy
1711         MEGA Thanks to Taniwha
1712
1713 */
1714 void Con_DisplayList(const char **list)
1715 {
1716         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1717         const char **walk = list;
1718
1719         while (*walk) {
1720                 len = (int)strlen(*walk);
1721                 if (len > maxlen)
1722                         maxlen = len;
1723                 walk++;
1724         }
1725         maxlen += 1;
1726
1727         while (*list) {
1728                 len = (int)strlen(*list);
1729                 if (pos + maxlen >= width) {
1730                         Con_Print("\n");
1731                         pos = 0;
1732                 }
1733
1734                 Con_Print(*list);
1735                 for (i = 0; i < (maxlen - len); i++)
1736                         Con_Print(" ");
1737
1738                 pos += maxlen;
1739                 list++;
1740         }
1741
1742         if (pos)
1743                 Con_Print("\n\n");
1744 }
1745
1746 /* Nicks_CompleteCountPossible
1747
1748    Count the number of possible nicks to complete
1749  */
1750 //qboolean COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets);
1751 void SanitizeString(char *in, char *out)
1752 {
1753         while(*in)
1754         {
1755                 if(*in == STRING_COLOR_TAG)
1756                 {
1757                         ++in;
1758                         if(!*in)
1759                         {
1760                                 out[0] = STRING_COLOR_TAG;
1761                                 out[1] = 0;
1762                                 return;
1763                         } else if(*in >= '0' && *in <= '9')
1764                         {
1765                                 ++in;
1766                                 if(!*in) // end
1767                                 {
1768                                         *out = 0;
1769                                         return;
1770                                 } else if (*in == STRING_COLOR_TAG)
1771                                         continue;
1772                         } else if (*in != STRING_COLOR_TAG) {
1773                                 --in;
1774                         }
1775                 }
1776                 *out = qfont_table[*(unsigned char*)in];
1777                 ++in;
1778                 ++out;
1779         }
1780         *out = 0;
1781 }
1782
1783 // Now it becomes TRICKY :D --blub
1784 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];     // contains the nicks with colors and all that
1785 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];  // sanitized list for completion when there are other possible matches.
1786 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
1787 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
1788 static int Nicks_matchpos;
1789
1790 // co against <<:BLASTER:>> is true!?
1791 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
1792 {
1793         while(a_len)
1794         {
1795                 if(tolower(*a) == tolower(*b))
1796                 {
1797                         if(*a == 0)
1798                                 return 0;
1799                         --a_len;
1800                         ++a;
1801                         ++b;
1802                         continue;
1803                 }
1804                 if(!*a)
1805                         return -1;
1806                 if(!*b)
1807                         return 1;
1808                 if(*a == ' ')
1809                         return (*a < *b) ? -1 : 1;
1810                 if(*b == ' ')
1811                         ++b;
1812                 else
1813                         return (*a < *b) ? -1 : 1;
1814         }
1815         return 0;
1816 }
1817 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
1818 {
1819         char space_char;
1820         if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
1821         {
1822                 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
1823                         return Nicks_strncasecmp_nospaces(a, b, a_len);
1824                 return strncasecmp(a, b, a_len);
1825         }
1826
1827         space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
1828
1829         // ignore non alphanumerics of B
1830         // if A contains a non-alphanumeric, B must contain it as well though!
1831         while(a_len)
1832         {
1833                 qboolean alnum_a, alnum_b;
1834
1835                 if(tolower(*a) == tolower(*b))
1836                 {
1837                         if(*a == 0) // end of both strings, they're equal
1838                                 return 0;
1839                         --a_len;
1840                         ++a;
1841                         ++b;
1842                         continue;
1843                 }
1844                 // not equal, end of one string?
1845                 if(!*a)
1846                         return -1;
1847                 if(!*b)
1848                         return 1;
1849                 // ignore non alphanumerics
1850                 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
1851                 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
1852                 if(!alnum_a) // b must contain this
1853                         return (*a < *b) ? -1 : 1;
1854                 if(!alnum_b)
1855                         ++b;
1856                 // otherwise, both are alnum, they're just not equal, return the appropriate number
1857                 else
1858                         return (*a < *b) ? -1 : 1;
1859         }
1860         return 0;
1861 }
1862
1863 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
1864 {
1865         char name[128];
1866         int i, p;
1867         int length;
1868         int match;
1869         int spos;
1870         int count = 0;
1871
1872         if(!con_nickcompletion.integer)
1873                 return 0;
1874
1875         // changed that to 1
1876         if(!line[0])// || !line[1]) // we want at least... 2 written characters
1877                 return 0;
1878
1879         for(i = 0; i < cl.maxclients; ++i)
1880         {
1881                 p = i;
1882                 if(!cl.scores[p].name[0])
1883                         continue;
1884
1885                 SanitizeString(cl.scores[p].name, name);
1886                 //Con_Printf("Sanitized: %s^7 -> %s", cl.scores[p].name, name);
1887
1888                 if(!name[0])
1889                         continue;
1890
1891                 length = strlen(name);
1892                 match = -1;
1893                 spos = pos - 1; // no need for a minimum of characters :)
1894
1895                 while(spos >= 0 && (spos - pos) < length) // search-string-length < name length
1896                 {
1897                         if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
1898                         {
1899                                 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
1900                                    !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
1901                                 {
1902                                         --spos;
1903                                         continue;
1904                                 }
1905                         }
1906                         if(isCon && spos == 0)
1907                                 break;
1908                         if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
1909                                 match = spos;
1910                         --spos;
1911                 }
1912                 if(match < 0)
1913                         continue;
1914                 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
1915                 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
1916
1917                 // the sanitized list
1918                 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
1919                 if(!count)
1920                 {
1921                         Nicks_matchpos = match;
1922                 }
1923
1924                 Nicks_offset[count] = s - (&line[match]);
1925                 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
1926
1927                 ++count;
1928         }
1929         return count;
1930 }
1931
1932 void Cmd_CompleteNicksPrint(int count)
1933 {
1934         int i;
1935         for(i = 0; i < count; ++i)
1936                 Con_Printf("%s\n", Nicks_list[i]);
1937 }
1938
1939 void Nicks_CutMatchesNormal(int count)
1940 {
1941         // cut match 0 down to the longest possible completion
1942         int i;
1943         unsigned int c, l;
1944         c = strlen(Nicks_sanlist[0]) - 1;
1945         for(i = 1; i < count; ++i)
1946         {
1947                 l = strlen(Nicks_sanlist[i]) - 1;
1948                 if(l < c)
1949                         c = l;
1950
1951                 for(l = 0; l <= c; ++l)
1952                         if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
1953                         {
1954                                 c = l-1;
1955                                 break;
1956                         }
1957         }
1958         Nicks_sanlist[0][c+1] = 0;
1959         //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
1960 }
1961
1962 unsigned int Nicks_strcleanlen(const char *s)
1963 {
1964         unsigned int l = 0;
1965         while(*s)
1966         {
1967                 if( (*s >= 'a' && *s <= 'z') ||
1968                     (*s >= 'A' && *s <= 'Z') ||
1969                     (*s >= '0' && *s <= '9') ||
1970                     *s == ' ')
1971                         ++l;
1972                 ++s;
1973         }
1974         return l;
1975 }
1976
1977 void Nicks_CutMatchesAlphaNumeric(int count)
1978 {
1979         // cut match 0 down to the longest possible completion
1980         int i;
1981         unsigned int c, l;
1982         char tempstr[sizeof(Nicks_sanlist[0])];
1983         char *a, *b;
1984         char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
1985
1986         c = strlen(Nicks_sanlist[0]);
1987         for(i = 0, l = 0; i < (int)c; ++i)
1988         {
1989                 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
1990                     (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
1991                     (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
1992                 {
1993                         tempstr[l++] = Nicks_sanlist[0][i];
1994                 }
1995         }
1996         tempstr[l] = 0;
1997
1998         for(i = 1; i < count; ++i)
1999         {
2000                 a = tempstr;
2001                 b = Nicks_sanlist[i];
2002                 while(1)
2003                 {
2004                         if(!*a)
2005                                 break;
2006                         if(!*b)
2007                         {
2008                                 *a = 0;
2009                                 break;
2010                         }
2011                         if(tolower(*a) == tolower(*b))
2012                         {
2013                                 ++a;
2014                                 ++b;
2015                                 continue;
2016                         }
2017                         if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2018                         {
2019                                 // b is alnum, so cut
2020                                 *a = 0;
2021                                 break;
2022                         }
2023                         ++b;
2024                 }
2025         }
2026         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2027         Nicks_CutMatchesNormal(count);
2028         //if(!Nicks_sanlist[0][0])
2029         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2030         {
2031                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2032                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2033         }
2034 }
2035
2036 void Nicks_CutMatchesNoSpaces(int count)
2037 {
2038         // cut match 0 down to the longest possible completion
2039         int i;
2040         unsigned int c, l;
2041         char tempstr[sizeof(Nicks_sanlist[0])];
2042         char *a, *b;
2043
2044         c = strlen(Nicks_sanlist[0]);
2045         for(i = 0, l = 0; i < (int)c; ++i)
2046         {
2047                 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2048                 {
2049                         tempstr[l++] = Nicks_sanlist[0][i];
2050                 }
2051         }
2052         tempstr[l] = 0;
2053
2054         for(i = 1; i < count; ++i)
2055         {
2056                 a = tempstr;
2057                 b = Nicks_sanlist[i];
2058                 while(1)
2059                 {
2060                         if(!*a)
2061                                 break;
2062                         if(!*b)
2063                         {
2064                                 *a = 0;
2065                                 break;
2066                         }
2067                         if(tolower(*a) == tolower(*b))
2068                         {
2069                                 ++a;
2070                                 ++b;
2071                                 continue;
2072                         }
2073                         if(*b != ' ')
2074                         {
2075                                 *a = 0;
2076                                 break;
2077                         }
2078                         ++b;
2079                 }
2080         }
2081         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2082         Nicks_CutMatchesNormal(count);
2083         //if(!Nicks_sanlist[0][0])
2084         //Con_Printf("TS: %s\n", tempstr);
2085         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2086         {
2087                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2088                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2089         }
2090 }
2091
2092 void Nicks_CutMatches(int count)
2093 {
2094         if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2095                 Nicks_CutMatchesAlphaNumeric(count);
2096         else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2097                 Nicks_CutMatchesNoSpaces(count);
2098         else
2099                 Nicks_CutMatchesNormal(count);
2100 }
2101
2102 const char **Nicks_CompleteBuildList(int count)
2103 {
2104         const char **buf;
2105         int bpos = 0;
2106         // the list is freed by Con_CompleteCommandLine, so create a char**
2107         buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2108
2109         for(; bpos < count; ++bpos)
2110                 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2111
2112         Nicks_CutMatches(count);
2113
2114         buf[bpos] = NULL;
2115         return buf;
2116 }
2117
2118 int Nicks_AddLastColor(char *buffer, int pos)
2119 {
2120         qboolean quote_added = false;
2121         int match;
2122         char color = '7';
2123
2124         if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2125         {
2126                 // we'll have to add a quote :)
2127                 buffer[pos++] = '\"';
2128                 quote_added = true;
2129         }
2130
2131         if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2132         {
2133                 // add color when no quote was added, or when flags &4?
2134                 // find last color
2135                 for(match = Nicks_matchpos-1; match >= 0; --match)
2136                 {
2137                         if(buffer[match] == STRING_COLOR_TAG && buffer[match+1] >= '0' && buffer[match+1] <= '9')
2138                         {
2139                                 color = buffer[match+1];
2140                                 break;
2141                         }
2142                 }
2143                 if(!quote_added && buffer[pos-2] == STRING_COLOR_TAG && buffer[pos-1] >= '0' && buffer[pos-1] <= '9') // when thes use &4
2144                         pos -= 2;
2145                 buffer[pos++] = STRING_COLOR_TAG;
2146                 buffer[pos++] = color;
2147         }
2148         return pos;
2149 }
2150
2151 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2152 {
2153         int n;
2154         /*if(!con_nickcompletion.integer)
2155           return; is tested in Nicks_CompletionCountPossible */
2156         n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2157         if(n == 1)
2158         {
2159                 size_t len;
2160                 char *msg;
2161
2162                 msg = Nicks_list[0];
2163                 len = min(size - Nicks_matchpos - 3, strlen(msg));
2164                 memcpy(&buffer[Nicks_matchpos], msg, len);
2165                 if( len < (size - 4) ) // space for color and space and \0
2166                         len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2167                 buffer[len++] = ' ';
2168                 buffer[len] = 0;
2169                 return len;
2170         } else if(n > 1)
2171         {
2172                 int len;
2173                 char *msg;
2174                 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2175                 Cmd_CompleteNicksPrint(n);
2176
2177                 Nicks_CutMatches(n);
2178
2179                 msg = Nicks_sanlist[0];
2180                 len = min(size - Nicks_matchpos, strlen(msg));
2181                 memcpy(&buffer[Nicks_matchpos], msg, len);
2182                 buffer[Nicks_matchpos + len] = 0;
2183                 //pos += len;
2184                 return Nicks_matchpos + len;
2185         }
2186         return pos;
2187 }
2188
2189
2190 /*
2191         Con_CompleteCommandLine
2192
2193         New function for tab-completion system
2194         Added by EvilTypeGuy
2195         Thanks to Fett erich@heintz.com
2196         Thanks to taniwha
2197         Enhanced to tab-complete map names by [515]
2198
2199 */
2200 void Con_CompleteCommandLine (void)
2201 {
2202         const char *cmd = "";
2203         char *s;
2204         const char **list[4] = {0, 0, 0, 0};
2205         char s2[512];
2206         char command[512];
2207         int c, v, a, i, cmd_len, pos, k;
2208         int n; // nicks --blub
2209         const char *space, *patterns;
2210
2211         //find what we want to complete
2212         pos = key_linepos;
2213         while(--pos)
2214         {
2215                 k = key_lines[edit_line][pos];
2216                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2217                         break;
2218         }
2219         pos++;
2220
2221         s = key_lines[edit_line] + pos;
2222         strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2));    //save chars after cursor
2223         key_lines[edit_line][key_linepos] = 0;                                  //hide them
2224
2225         space = strchr(key_lines[edit_line] + 1, ' ');
2226         if(space && pos == (space - key_lines[edit_line]) + 1)
2227         {
2228                 strlcpy(command, key_lines[edit_line] + 1, min(sizeof(command), (unsigned int)(space - key_lines[edit_line])));
2229
2230                 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2231                 if(patterns && !*patterns)
2232                         patterns = NULL; // get rid of the empty string
2233
2234                 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2235                 {
2236                         //maps search
2237                         char t[MAX_QPATH];
2238                         if (GetMapList(s, t, sizeof(t)))
2239                         {
2240                                 // first move the cursor
2241                                 key_linepos += (int)strlen(t) - (int)strlen(s);
2242
2243                                 // and now do the actual work
2244                                 *s = 0;
2245                                 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2246                                 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2247
2248                                 // and fix the cursor
2249                                 if(key_linepos > (int) strlen(key_lines[edit_line]))
2250                                         key_linepos = (int) strlen(key_lines[edit_line]);
2251                         }
2252                         return;
2253                 }
2254                 else
2255                 {
2256                         if(patterns)
2257                         {
2258                                 char t[MAX_QPATH];
2259                                 stringlist_t resultbuf, dirbuf;
2260
2261                                 // Usage:
2262                                 //   // store completion patterns (space separated) for command foo in con_completion_foo
2263                                 //   set con_completion_foo "foodata/*.foodefault *.foo"
2264                                 //   foo <TAB>
2265                                 //
2266                                 // Note: patterns with slash are always treated as absolute
2267                                 // patterns; patterns without slash search in the innermost
2268                                 // directory the user specified. There is no way to "complete into"
2269                                 // a directory as of now, as directories seem to be unknown to the
2270                                 // FS subsystem.
2271                                 //
2272                                 // Examples:
2273                                 //   set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2274                                 //   set con_completion_playdemo "*.dem"
2275                                 //   set con_completion_play "*.wav *.ogg"
2276                                 //
2277                                 // TODO somehow add support for directories; these shall complete
2278                                 // to their name + an appended slash.
2279
2280                                 stringlistinit(&resultbuf);
2281                                 stringlistinit(&dirbuf);
2282                                 while(COM_ParseToken_Simple(&patterns, false, false))
2283                                 {
2284                                         fssearch_t *search;
2285                                         if(strchr(com_token, '/'))
2286                                         {
2287                                                 search = FS_Search(com_token, true, true);
2288                                         }
2289                                         else
2290                                         {
2291                                                 const char *slash = strrchr(s, '/');
2292                                                 if(slash)
2293                                                 {
2294                                                         strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2295                                                         strlcat(t, com_token, sizeof(t));
2296                                                         search = FS_Search(t, true, true);
2297                                                 }
2298                                                 else
2299                                                         search = FS_Search(com_token, true, true);
2300                                         }
2301                                         if(search)
2302                                         {
2303                                                 for(i = 0; i < search->numfilenames; ++i)
2304                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2305                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2306                                                                         stringlistappend(&resultbuf, search->filenames[i]);
2307                                                 FS_FreeSearch(search);
2308                                         }
2309                                 }
2310
2311                                 // In any case, add directory names
2312                                 {
2313                                         fssearch_t *search;
2314                                         const char *slash = strrchr(s, '/');
2315                                         if(slash)
2316                                         {
2317                                                 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2318                                                 strlcat(t, "*", sizeof(t));
2319                                                 search = FS_Search(t, true, true);
2320                                         }
2321                                         else
2322                                                 search = FS_Search("*", true, true);
2323                                         if(search)
2324                                         {
2325                                                 for(i = 0; i < search->numfilenames; ++i)
2326                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2327                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2328                                                                         stringlistappend(&dirbuf, search->filenames[i]);
2329                                                 FS_FreeSearch(search);
2330                                         }
2331                                 }
2332
2333                                 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2334                                 {
2335                                         const char *p, *q;
2336                                         unsigned int matchchars;
2337                                         if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2338                                         {
2339                                                 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2340                                         }
2341                                         else
2342                                         if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2343                                         {
2344                                                 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2345                                         }
2346                                         else
2347                                         {
2348                                                 stringlistsort(&resultbuf); // dirbuf is already sorted
2349                                                 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2350                                                 for(i = 0; i < dirbuf.numstrings; ++i)
2351                                                 {
2352                                                         Con_Printf("%s/\n", dirbuf.strings[i]);
2353                                                 }
2354                                                 for(i = 0; i < resultbuf.numstrings; ++i)
2355                                                 {
2356                                                         Con_Printf("%s\n", resultbuf.strings[i]);
2357                                                 }
2358                                                 matchchars = sizeof(t) - 1;
2359                                                 if(resultbuf.numstrings > 0)
2360                                                 {
2361                                                         p = resultbuf.strings[0];
2362                                                         q = resultbuf.strings[resultbuf.numstrings - 1];
2363                                                         for(; *p && *p == *q; ++p, ++q);
2364                                                         matchchars = (unsigned int)(p - resultbuf.strings[0]);
2365                                                 }
2366                                                 if(dirbuf.numstrings > 0)
2367                                                 {
2368                                                         p = dirbuf.strings[0];
2369                                                         q = dirbuf.strings[dirbuf.numstrings - 1];
2370                                                         for(; *p && *p == *q; ++p, ++q);
2371                                                         matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2372                                                 }
2373                                                 // now p points to the first non-equal character, or to the end
2374                                                 // of resultbuf.strings[0]. We want to append the characters
2375                                                 // from resultbuf.strings[0] to (not including) p as these are
2376                                                 // the unique prefix
2377                                                 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2378                                         }
2379
2380                                         // first move the cursor
2381                                         key_linepos += (int)strlen(t) - (int)strlen(s);
2382
2383                                         // and now do the actual work
2384                                         *s = 0;
2385                                         strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2386                                         strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2387
2388                                         // and fix the cursor
2389                                         if(key_linepos > (int) strlen(key_lines[edit_line]))
2390                                                 key_linepos = (int) strlen(key_lines[edit_line]);
2391                                 }
2392                                 stringlistfreecontents(&resultbuf);
2393                                 stringlistfreecontents(&dirbuf);
2394
2395                                 return; // bail out, when we complete for a command that wants a file name
2396                         }
2397                 }
2398         }
2399
2400         // Count number of possible matches and print them
2401         c = Cmd_CompleteCountPossible(s);
2402         if (c)
2403         {
2404                 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2405                 Cmd_CompleteCommandPrint(s);
2406         }
2407         v = Cvar_CompleteCountPossible(s);
2408         if (v)
2409         {
2410                 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2411                 Cvar_CompleteCvarPrint(s);
2412         }
2413         a = Cmd_CompleteAliasCountPossible(s);
2414         if (a)
2415         {
2416                 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2417                 Cmd_CompleteAliasPrint(s);
2418         }
2419         n = Nicks_CompleteCountPossible(key_lines[edit_line], key_linepos, s, true);
2420         if (n)
2421         {
2422                 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2423                 Cmd_CompleteNicksPrint(n);
2424         }
2425
2426         if (!(c + v + a + n))   // No possible matches
2427         {
2428                 if(s2[0])
2429                         strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
2430                 return;
2431         }
2432
2433         if (c)
2434                 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2435         if (v)
2436                 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2437         if (a)
2438                 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2439         if (n)
2440                 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2441
2442         for (cmd_len = (int)strlen(s);;cmd_len++)
2443         {
2444                 const char **l;
2445                 for (i = 0; i < 3; i++)
2446                         if (list[i])
2447                                 for (l = list[i];*l;l++)
2448                                         if ((*l)[cmd_len] != cmd[cmd_len])
2449                                                 goto done;
2450                 // all possible matches share this character, so we continue...
2451                 if (!cmd[cmd_len])
2452                 {
2453                         // if all matches ended at the same position, stop
2454                         // (this means there is only one match)
2455                         break;
2456                 }
2457         }
2458 done:
2459
2460         // prevent a buffer overrun by limiting cmd_len according to remaining space
2461         cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
2462         if (cmd)
2463         {
2464                 key_linepos = pos;
2465                 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
2466                 key_linepos += cmd_len;
2467                 // if there is only one match, add a space after it
2468                 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
2469                 {
2470                         if(n)
2471                         { // was a nick, might have an offset, and needs colors ;) --blub
2472                                 key_linepos = pos - Nicks_offset[0];
2473                                 cmd_len = strlen(Nicks_list[0]);
2474                                 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 3 - pos);
2475
2476                                 memcpy(&key_lines[edit_line][key_linepos] , Nicks_list[0], cmd_len);
2477                                 key_linepos += cmd_len;
2478                                 if(key_linepos < (int)(sizeof(key_lines[edit_line])-4)) // space for ^, X and space and \0
2479                                         key_linepos = Nicks_AddLastColor(key_lines[edit_line], key_linepos);
2480                         }
2481                         key_lines[edit_line][key_linepos++] = ' ';
2482                 }
2483         }
2484
2485         // use strlcat to avoid a buffer overrun
2486         key_lines[edit_line][key_linepos] = 0;
2487         strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
2488
2489         // free the command, cvar, and alias lists
2490         for (i = 0; i < 4; i++)
2491                 if (list[i])
2492                         Mem_Free((void *)list[i]);
2493 }
2494