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