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