]> git.xonotic.org Git - xonotic/darkplaces.git/blob - console.c
framegroups: add support for group names specified as // comment
[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         rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
909         if (rcon_redirect_proquakeprotocol)
910         {
911                 // update the length in the packet header
912                 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
913         }
914         NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
915         memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
916         rcon_redirect_bufferpos = 5;
917         rcon_redirect_proquakeprotocol = false;
918 }
919
920 void Con_Rcon_Redirect_End(void)
921 {
922         Con_Rcon_Redirect_Flush();
923         rcon_redirect_dest = NULL;
924         rcon_redirect_sock = NULL;
925 }
926
927 void Con_Rcon_Redirect_Abort(void)
928 {
929         rcon_redirect_dest = NULL;
930         rcon_redirect_sock = NULL;
931 }
932
933 /*
934 ================
935 Con_Rcon_AddChar
936 ================
937 */
938 /// Adds a character to the rcon buffer.
939 static void Con_Rcon_AddChar(int c)
940 {
941         if(log_dest_buffer_appending)
942                 return;
943         ++log_dest_buffer_appending;
944
945         // if this print is in response to an rcon command, add the character
946         // to the rcon redirect buffer
947
948         if (rcon_redirect_dest)
949         {
950                 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
951                 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
952                         Con_Rcon_Redirect_Flush();
953         }
954         else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
955         {
956                 if(log_dest_buffer_pos == 0)
957                         Log_DestBuffer_Init();
958                 log_dest_buffer[log_dest_buffer_pos++] = c;
959                 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
960                         Log_DestBuffer_Flush_NoLock();
961         }
962         else
963                 log_dest_buffer_pos = 0;
964
965         --log_dest_buffer_appending;
966 }
967
968 /**
969  * Convert an RGB color to its nearest quake color.
970  * I'll cheat on this a bit by translating the colors to HSV first,
971  * S and V decide if it's black or white, otherwise, H will decide the
972  * actual color.
973  * @param _r Red (0-255)
974  * @param _g Green (0-255)
975  * @param _b Blue (0-255)
976  * @return A quake color character.
977  */
978 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
979 {
980         float r = ((float)_r)/255.0;
981         float g = ((float)_g)/255.0;
982         float b = ((float)_b)/255.0;
983         float min = min(r, min(g, b));
984         float max = max(r, max(g, b));
985
986         int h; ///< Hue angle [0,360]
987         float s; ///< Saturation [0,1]
988         float v = max; ///< In HSV v == max [0,1]
989
990         if(max == min)
991                 s = 0;
992         else
993                 s = 1.0 - (min/max);
994
995         // Saturation threshold. We now say 0.2 is the minimum value for a color!
996         if(s < 0.2)
997         {
998                 // If the value is less than half, return a black color code.
999                 // Otherwise return a white one.
1000                 if(v < 0.5)
1001                         return '0';
1002                 return '7';
1003         }
1004
1005         // Let's get the hue angle to define some colors:
1006         if(max == min)
1007                 h = 0;
1008         else if(max == r)
1009                 h = (int)(60.0 * (g-b)/(max-min))%360;
1010         else if(max == g)
1011                 h = (int)(60.0 * (b-r)/(max-min) + 120);
1012         else // if(max == b) redundant check
1013                 h = (int)(60.0 * (r-g)/(max-min) + 240);
1014
1015         if(h < 36) // *red* to orange
1016                 return '1';
1017         else if(h < 80) // orange over *yellow* to evilish-bright-green
1018                 return '3';
1019         else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1020                 return '2';
1021         else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1022                 return '5';
1023         else if(h < 270) // darkish blue over *dark blue* to cool purple
1024                 return '4';
1025         else if(h < 330) // cool purple over *purple* to ugly swiny red
1026                 return '6';
1027         else // ugly red to red closes the circly
1028                 return '1';
1029 }
1030
1031 /*
1032 ================
1033 Con_MaskPrint
1034 ================
1035 */
1036 extern cvar_t timestamps;
1037 extern cvar_t timeformat;
1038 extern qboolean sys_nostdout;
1039 void Con_MaskPrint(int additionalmask, const char *msg)
1040 {
1041         static int mask = 0;
1042         static int index = 0;
1043         static char line[MAX_INPUTLINE];
1044
1045         if (con_mutex)
1046                 Thread_LockMutex(con_mutex);
1047
1048         for (;*msg;msg++)
1049         {
1050                 Con_Rcon_AddChar(*msg);
1051                 // if this is the beginning of a new line, print timestamp
1052                 if (index == 0)
1053                 {
1054                         const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1055                         // reset the color
1056                         // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1057                         line[index++] = STRING_COLOR_TAG;
1058                         // assert( STRING_COLOR_DEFAULT < 10 )
1059                         line[index++] = STRING_COLOR_DEFAULT + '0';
1060                         // special color codes for chat messages must always come first
1061                         // for Con_PrintToHistory to work properly
1062                         if (*msg == 1 || *msg == 2)
1063                         {
1064                                 // play talk wav
1065                                 if (*msg == 1)
1066                                 {
1067                                         if (con_chatsound.value)
1068                                         {
1069                                                 if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
1070                                                 {
1071                                                         if(msg[1] == '\r' && cl.foundtalk2wav)
1072                                                                 S_LocalSound ("sound/misc/talk2.wav");
1073                                                         else
1074                                                                 S_LocalSound ("sound/misc/talk.wav");
1075                                                 }
1076                                                 else
1077                                                 {
1078                                                         if (msg[1] == '(' && cl.foundtalk2wav)
1079                                                                 S_LocalSound ("sound/misc/talk2.wav");
1080                                                         else
1081                                                                 S_LocalSound ("sound/misc/talk.wav");
1082                                                 }
1083                                         }
1084                                         mask = CON_MASK_CHAT;
1085                                 }
1086                                 line[index++] = STRING_COLOR_TAG;
1087                                 line[index++] = '3';
1088                                 msg++;
1089                                 Con_Rcon_AddChar(*msg);
1090                         }
1091                         // store timestamp
1092                         for (;*timestamp;index++, timestamp++)
1093                                 if (index < (int)sizeof(line) - 2)
1094                                         line[index] = *timestamp;
1095                         // add the mask
1096                         mask |= additionalmask;
1097                 }
1098                 // append the character
1099                 line[index++] = *msg;
1100                 // if this is a newline character, we have a complete line to print
1101                 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1102                 {
1103                         // terminate the line
1104                         line[index] = 0;
1105                         // send to log file
1106                         Log_ConPrint(line);
1107                         // send to scrollable buffer
1108                         if (con_initialized && cls.state != ca_dedicated)
1109                         {
1110                                 Con_PrintToHistory(line, mask);
1111                         }
1112                         // send to terminal or dedicated server window
1113                         if (!sys_nostdout)
1114                         if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1115                         {
1116                                 if(sys_specialcharactertranslation.integer)
1117                                 {
1118                                         char *p;
1119                                         const char *q;
1120                                         p = line;
1121                                         while(*p)
1122                                         {
1123                                                 int ch = u8_getchar(p, &q);
1124                                                 if(ch >= 0xE000 && ch <= 0xE0FF)
1125                                                 {
1126                                                         *p = qfont_table[ch - 0xE000];
1127                                                         if(q > p+1)
1128                                                                 memmove(p+1, q, strlen(q)+1);
1129                                                         p = p + 1;
1130                                                 }
1131                                                 else
1132                                                         p = p + (q - p);
1133                                         }
1134                                 }
1135
1136                                 if(sys_colortranslation.integer == 1) // ANSI
1137                                 {
1138                                         static char printline[MAX_INPUTLINE * 4 + 3];
1139                                                 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1140                                                 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1141                                         int lastcolor = 0;
1142                                         const char *in;
1143                                         char *out;
1144                                         int color;
1145                                         for(in = line, out = printline; *in; ++in)
1146                                         {
1147                                                 switch(*in)
1148                                                 {
1149                                                         case STRING_COLOR_TAG:
1150                                                                 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1151                                                                 {
1152                                                                         char r = tolower(in[2]);
1153                                                                         char g = tolower(in[3]);
1154                                                                         char b = tolower(in[4]);
1155                                                                         // it's a hex digit already, so the else part needs no check --blub
1156                                                                         if(isdigit(r)) r -= '0';
1157                                                                         else r -= 87;
1158                                                                         if(isdigit(g)) g -= '0';
1159                                                                         else g -= 87;
1160                                                                         if(isdigit(b)) b -= '0';
1161                                                                         else b -= 87;
1162                                                                         
1163                                                                         color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1164                                                                         in += 3; // 3 only, the switch down there does the fourth
1165                                                                 }
1166                                                                 else
1167                                                                         color = in[1];
1168                                                                 
1169                                                                 switch(color)
1170                                                                 {
1171                                                                         case STRING_COLOR_TAG:
1172                                                                                 ++in;
1173                                                                                 *out++ = STRING_COLOR_TAG;
1174                                                                                 break;
1175                                                                         case '0':
1176                                                                         case '7':
1177                                                                                 // normal color
1178                                                                                 ++in;
1179                                                                                 if(lastcolor == 0) break; else lastcolor = 0;
1180                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1181                                                                                 break;
1182                                                                         case '1':
1183                                                                                 // light red
1184                                                                                 ++in;
1185                                                                                 if(lastcolor == 1) break; else lastcolor = 1;
1186                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1187                                                                                 break;
1188                                                                         case '2':
1189                                                                                 // light green
1190                                                                                 ++in;
1191                                                                                 if(lastcolor == 2) break; else lastcolor = 2;
1192                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1193                                                                                 break;
1194                                                                         case '3':
1195                                                                                 // yellow
1196                                                                                 ++in;
1197                                                                                 if(lastcolor == 3) break; else lastcolor = 3;
1198                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1199                                                                                 break;
1200                                                                         case '4':
1201                                                                                 // light blue
1202                                                                                 ++in;
1203                                                                                 if(lastcolor == 4) break; else lastcolor = 4;
1204                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1205                                                                                 break;
1206                                                                         case '5':
1207                                                                                 // light cyan
1208                                                                                 ++in;
1209                                                                                 if(lastcolor == 5) break; else lastcolor = 5;
1210                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1211                                                                                 break;
1212                                                                         case '6':
1213                                                                                 // light magenta
1214                                                                                 ++in;
1215                                                                                 if(lastcolor == 6) break; else lastcolor = 6;
1216                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1217                                                                                 break;
1218                                                                         // 7 handled above
1219                                                                         case '8':
1220                                                                         case '9':
1221                                                                                 // bold normal color
1222                                                                                 ++in;
1223                                                                                 if(lastcolor == 8) break; else lastcolor = 8;
1224                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1225                                                                                 break;
1226                                                                         default:
1227                                                                                 *out++ = STRING_COLOR_TAG;
1228                                                                                 break;
1229                                                                 }
1230                                                                 break;
1231                                                         case '\n':
1232                                                                 if(lastcolor != 0)
1233                                                                 {
1234                                                                         *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1235                                                                         lastcolor = 0;
1236                                                                 }
1237                                                                 *out++ = *in;
1238                                                                 break;
1239                                                         default:
1240                                                                 *out++ = *in;
1241                                                                 break;
1242                                                 }
1243                                         }
1244                                         if(lastcolor != 0)
1245                                         {
1246                                                 *out++ = 0x1B;
1247                                                 *out++ = '[';
1248                                                 *out++ = 'm';
1249                                         }
1250                                         *out++ = 0;
1251                                         Sys_PrintToTerminal(printline);
1252                                 }
1253                                 else if(sys_colortranslation.integer == 2) // Quake
1254                                 {
1255                                         Sys_PrintToTerminal(line);
1256                                 }
1257                                 else // strip
1258                                 {
1259                                         static char printline[MAX_INPUTLINE]; // it can only get shorter here
1260                                         const char *in;
1261                                         char *out;
1262                                         for(in = line, out = printline; *in; ++in)
1263                                         {
1264                                                 switch(*in)
1265                                                 {
1266                                                         case STRING_COLOR_TAG:
1267                                                                 switch(in[1])
1268                                                                 {
1269                                                                         case STRING_COLOR_RGB_TAG_CHAR:
1270                                                                                 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1271                                                                                 {
1272                                                                                         in+=4;
1273                                                                                         break;
1274                                                                                 }
1275                                                                                 *out++ = STRING_COLOR_TAG;
1276                                                                                 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1277                                                                                 ++in;
1278                                                                                 break;
1279                                                                         case STRING_COLOR_TAG:
1280                                                                                 ++in;
1281                                                                                 *out++ = STRING_COLOR_TAG;
1282                                                                                 break;
1283                                                                         case '0':
1284                                                                         case '1':
1285                                                                         case '2':
1286                                                                         case '3':
1287                                                                         case '4':
1288                                                                         case '5':
1289                                                                         case '6':
1290                                                                         case '7':
1291                                                                         case '8':
1292                                                                         case '9':
1293                                                                                 ++in;
1294                                                                                 break;
1295                                                                         default:
1296                                                                                 *out++ = STRING_COLOR_TAG;
1297                                                                                 break;
1298                                                                 }
1299                                                                 break;
1300                                                         default:
1301                                                                 *out++ = *in;
1302                                                                 break;
1303                                                 }
1304                                         }
1305                                         *out++ = 0;
1306                                         Sys_PrintToTerminal(printline);
1307                                 }
1308                         }
1309                         // empty the line buffer
1310                         index = 0;
1311                         mask = 0;
1312                 }
1313         }
1314
1315         if (con_mutex)
1316                 Thread_UnlockMutex(con_mutex);
1317 }
1318
1319 /*
1320 ================
1321 Con_MaskPrintf
1322 ================
1323 */
1324 void Con_MaskPrintf(int mask, const char *fmt, ...)
1325 {
1326         va_list argptr;
1327         char msg[MAX_INPUTLINE];
1328
1329         va_start(argptr,fmt);
1330         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1331         va_end(argptr);
1332
1333         Con_MaskPrint(mask, msg);
1334 }
1335
1336 /*
1337 ================
1338 Con_Print
1339 ================
1340 */
1341 void Con_Print(const char *msg)
1342 {
1343         Con_MaskPrint(CON_MASK_PRINT, msg);
1344 }
1345
1346 /*
1347 ================
1348 Con_Printf
1349 ================
1350 */
1351 void Con_Printf(const char *fmt, ...)
1352 {
1353         va_list argptr;
1354         char msg[MAX_INPUTLINE];
1355
1356         va_start(argptr,fmt);
1357         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1358         va_end(argptr);
1359
1360         Con_MaskPrint(CON_MASK_PRINT, msg);
1361 }
1362
1363 /*
1364 ================
1365 Con_DPrint
1366 ================
1367 */
1368 void Con_DPrint(const char *msg)
1369 {
1370         if(developer.integer < 0) // at 0, we still add to the buffer but hide
1371                 return;
1372
1373         Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1374 }
1375
1376 /*
1377 ================
1378 Con_DPrintf
1379 ================
1380 */
1381 void Con_DPrintf(const char *fmt, ...)
1382 {
1383         va_list argptr;
1384         char msg[MAX_INPUTLINE];
1385
1386         if(developer.integer < 0) // at 0, we still add to the buffer but hide
1387                 return;
1388
1389         va_start(argptr,fmt);
1390         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1391         va_end(argptr);
1392
1393         Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1394 }
1395
1396
1397 /*
1398 ==============================================================================
1399
1400 DRAWING
1401
1402 ==============================================================================
1403 */
1404
1405 /*
1406 ================
1407 Con_DrawInput
1408
1409 The input line scrolls horizontally if typing goes beyond the right edge
1410
1411 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1412 ================
1413 */
1414 extern cvar_t r_font_disable_freetype;
1415 static void Con_DrawInput (void)
1416 {
1417         int             y;
1418         int             i;
1419         char editlinecopy[MAX_INPUTLINE+1], *text;
1420         float x, xo;
1421         size_t len_out;
1422         int col_out;
1423
1424         if (!key_consoleactive)
1425                 return;         // don't draw anything
1426
1427         strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1428         text = editlinecopy;
1429
1430         // Advanced Console Editing by Radix radix@planetquake.com
1431         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1432         // use strlen of edit_line instead of key_linepos to allow editing
1433         // of early characters w/o erasing
1434
1435         y = (int)strlen(text);
1436
1437         // append enoug nul-bytes to cover the utf8-versions of the cursor too
1438         for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i)
1439                 text[i] = 0;
1440
1441         // add the cursor frame
1442         if (r_font_disable_freetype.integer)
1443         {
1444                 // this code is freetype incompatible!
1445                 if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
1446                 {
1447                         if (!utf8_enable.integer)
1448                                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
1449                         else if (y + 3 < (int)sizeof(editlinecopy)-1)
1450                         {
1451                                 int ofs = u8_bytelen(text + key_linepos, 1);
1452                                 size_t len;
1453                                 const char *curbuf;
1454                                 char charbuf16[16];
1455                                 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1456
1457                                 if (curbuf)
1458                                 {
1459                                         memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1460                                         memcpy(text + key_linepos, curbuf, len);
1461                                 }
1462                         } else
1463                                 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1464                 }
1465         }
1466
1467 //      text[key_linepos + 1] = 0;
1468
1469         len_out = key_linepos;
1470         col_out = -1;
1471         xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1472         x = vid_conwidth.value * 0.95 - xo; // scroll
1473         if(x >= 0)
1474                 x = 0;
1475
1476         // draw it
1477         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 );
1478
1479         // add a cursor on top of this (when using freetype)
1480         if (!r_font_disable_freetype.integer)
1481         {
1482                 if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
1483                 {
1484                         if (!utf8_enable.integer)
1485                         {
1486                                 text[0] = 11 + 130 * key_insert;        // either solid or triangle facing right
1487                                 text[1] = 0;
1488                         }
1489                         else
1490                         {
1491                                 size_t len;
1492                                 const char *curbuf;
1493                                 char charbuf16[16];
1494                                 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1495                                 memcpy(text, curbuf, len);
1496                                 text[len] = 0;
1497                         }
1498                         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);
1499                 }
1500         }
1501
1502         // remove cursor
1503 //      key_line[key_linepos] = 0;
1504 }
1505
1506 typedef struct
1507 {
1508         dp_font_t *font;
1509         float alignment; // 0 = left, 0.5 = center, 1 = right
1510         float fontsize;
1511         float x;
1512         float y;
1513         float width;
1514         float ymin, ymax;
1515         const char *continuationString;
1516
1517         // PRIVATE:
1518         int colorindex; // init to -1
1519 }
1520 con_text_info_t;
1521
1522 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1523 {
1524         con_text_info_t *ti = (con_text_info_t *) passthrough;
1525         if(w == NULL)
1526         {
1527                 ti->colorindex = -1;
1528                 return ti->fontsize * ti->font->maxwidth;
1529         }
1530         if(maxWidth >= 0)
1531                 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1532         else if(maxWidth == -1)
1533                 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1534         else
1535         {
1536                 Sys_PrintfToTerminal("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1537                 // Note: this is NOT a Con_Printf, as it could print recursively
1538                 return 0;
1539         }
1540 }
1541
1542 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1543 {
1544         (void) passthrough;
1545         (void) line;
1546         (void) length;
1547         (void) width;
1548         (void) isContinuation;
1549         return 1;
1550 }
1551
1552 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1553 {
1554         con_text_info_t *ti = (con_text_info_t *) passthrough;
1555
1556         if(ti->y < ti->ymin - 0.001)
1557                 (void) 0;
1558         else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1559                 (void) 0;
1560         else
1561         {
1562                 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1563                 if(isContinuation && *ti->continuationString)
1564                         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);
1565                 if(length > 0)
1566                         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);
1567         }
1568
1569         ti->y += ti->fontsize;
1570         return 1;
1571 }
1572
1573 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)
1574 {
1575         int i;
1576         int lines = 0;
1577         int maxlines = (int) floor(height / fontsize + 0.01f);
1578         int startidx;
1579         int nskip = 0;
1580         int continuationWidth = 0;
1581         size_t l;
1582         double t = cl.time; // saved so it won't change
1583         con_text_info_t ti;
1584
1585         ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1586         ti.fontsize = fontsize;
1587         ti.alignment = alignment_x;
1588         ti.width = width;
1589         ti.ymin = y;
1590         ti.ymax = y + height;
1591         ti.continuationString = continuationString;
1592
1593         l = 0;
1594         Con_WordWidthFunc(&ti, NULL, &l, -1);
1595         l = strlen(continuationString);
1596         continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1597
1598         // first find the first line to draw by backwards iterating and word wrapping to find their length...
1599         startidx = CON_LINES_COUNT;
1600         for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1601         {
1602                 con_lineinfo_t *l = &CON_LINES(i);
1603                 int mylines;
1604
1605                 if((l->mask & mask_must) != mask_must)
1606                         continue;
1607                 if(l->mask & mask_mustnot)
1608                         continue;
1609                 if(maxage && (l->addtime < t - maxage))
1610                         continue;
1611
1612                 // WE FOUND ONE!
1613                 // Calculate its actual height...
1614                 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1615                 if(lines + mylines >= maxlines)
1616                 {
1617                         nskip = lines + mylines - maxlines;
1618                         lines = maxlines;
1619                         startidx = i;
1620                         break;
1621                 }
1622                 lines += mylines;
1623                 startidx = i;
1624         }
1625
1626         // then center according to the calculated amount of lines...
1627         ti.x = x;
1628         ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1629
1630         // then actually draw
1631         for(i = startidx; i < CON_LINES_COUNT; ++i)
1632         {
1633                 con_lineinfo_t *l = &CON_LINES(i);
1634
1635                 if((l->mask & mask_must) != mask_must)
1636                         continue;
1637                 if(l->mask & mask_mustnot)
1638                         continue;
1639                 if(maxage && (l->addtime < t - maxage))
1640                         continue;
1641
1642                 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1643         }
1644
1645         return lines;
1646 }
1647
1648 /*
1649 ================
1650 Con_DrawNotify
1651
1652 Draws the last few lines of output transparently over the game top
1653 ================
1654 */
1655 void Con_DrawNotify (void)
1656 {
1657         float   x, v, xr;
1658         float chatstart, notifystart, inputsize, height;
1659         float align;
1660         char    temptext[MAX_INPUTLINE];
1661         int numChatlines;
1662         int chatpos;
1663
1664         if (con_mutex) Thread_LockMutex(con_mutex);
1665         ConBuffer_FixTimes(&con);
1666
1667         numChatlines = con_chat.integer;
1668
1669         chatpos = con_chatpos.integer;
1670
1671         if (con_notify.integer < 0)
1672                 Cvar_SetValueQuick(&con_notify, 0);
1673         if (gamemode == GAME_TRANSFUSION)
1674                 v = 8; // vertical offset
1675         else
1676                 v = 0;
1677
1678         // GAME_NEXUIZ: center, otherwise left justify
1679         align = con_notifyalign.value;
1680         if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1681         {
1682                 if(gamemode == GAME_NEXUIZ)
1683                         align = 0.5;
1684         }
1685
1686         if(numChatlines || !con_chatrect.integer)
1687         {
1688                 if(chatpos == 0)
1689                 {
1690                         // first chat, input line, then notify
1691                         chatstart = v;
1692                         notifystart = v + (numChatlines + 1) * con_chatsize.value;
1693                 }
1694                 else if(chatpos > 0)
1695                 {
1696                         // first notify, then (chatpos-1) empty lines, then chat, then input
1697                         notifystart = v;
1698                         chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1699                 }
1700                 else // if(chatpos < 0)
1701                 {
1702                         // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1703                         notifystart = v;
1704                         chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1705                 }
1706         }
1707         else
1708         {
1709                 // just notify and input
1710                 notifystart = v;
1711                 chatstart = 0; // shut off gcc warning
1712         }
1713
1714         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, "");
1715
1716         if(con_chatrect.integer)
1717         {
1718                 x = con_chatrect_x.value * vid_conwidth.value;
1719                 v = con_chatrect_y.value * vid_conheight.value;
1720         }
1721         else
1722         {
1723                 x = 0;
1724                 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1725                         v = chatstart;
1726         }
1727         height = numChatlines * con_chatsize.value;
1728
1729         if(numChatlines)
1730         {
1731                 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
1732                 v += height;
1733         }
1734         if (key_dest == key_message)
1735         {
1736                 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1737                 int colorindex = -1;
1738                 const char *cursor;
1739                 char charbuf16[16];
1740                 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL, charbuf16);
1741
1742                 // LordHavoc: speedup, and other improvements
1743                 if (chat_mode < 0)
1744                         dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1745                 else if(chat_mode)
1746                         dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1747                 else
1748                         dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1749
1750                 // FIXME word wrap
1751                 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1752                 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1753                 x = min(xr, x);
1754                 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1755         }
1756         if (con_mutex) Thread_UnlockMutex(con_mutex);
1757 }
1758
1759 /*
1760 ================
1761 Con_LineHeight
1762
1763 Returns the height of a given console line; calculates it if necessary.
1764 ================
1765 */
1766 static int Con_LineHeight(int lineno)
1767 {
1768         con_lineinfo_t *li = &CON_LINES(lineno);
1769         if(li->height == -1)
1770         {
1771                 float width = vid_conwidth.value;
1772                 con_text_info_t ti;
1773                 con_lineinfo_t *li = &CON_LINES(lineno);
1774                 ti.fontsize = con_textsize.value;
1775                 ti.font = FONT_CONSOLE;
1776                 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1777         }
1778         return li->height;
1779 }
1780
1781 /*
1782 ================
1783 Con_DrawConsoleLine
1784
1785 Draws a line of the console; returns its height in lines.
1786 If alpha is 0, the line is not drawn, but still wrapped and its height
1787 returned.
1788 ================
1789 */
1790 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1791 {
1792         float width = vid_conwidth.value;
1793         con_text_info_t ti;
1794         con_lineinfo_t *li = &CON_LINES(lineno);
1795
1796         if((li->mask & mask_must) != mask_must)
1797                 return 0;
1798         if((li->mask & mask_mustnot) != 0)
1799                 return 0;
1800
1801         ti.continuationString = "";
1802         ti.alignment = 0;
1803         ti.fontsize = con_textsize.value;
1804         ti.font = FONT_CONSOLE;
1805         ti.x = 0;
1806         ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1807         ti.ymin = ymin;
1808         ti.ymax = ymax;
1809         ti.width = width;
1810
1811         return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1812 }
1813
1814 /*
1815 ================
1816 Con_LastVisibleLine
1817
1818 Calculates the last visible line index and how much to show of it based on
1819 con_backscroll.
1820 ================
1821 */
1822 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1823 {
1824         int lines_seen = 0;
1825         int i;
1826
1827         if(con_backscroll < 0)
1828                 con_backscroll = 0;
1829
1830         *last = 0;
1831
1832         // now count until we saw con_backscroll actual lines
1833         for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1834         if((CON_LINES(i).mask & mask_must) == mask_must)
1835         if((CON_LINES(i).mask & mask_mustnot) == 0)
1836         {
1837                 int h = Con_LineHeight(i);
1838
1839                 // line is the last visible line?
1840                 *last = i;
1841                 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1842                 {
1843                         *limitlast = lines_seen + h - con_backscroll;
1844                         return;
1845                 }
1846
1847                 lines_seen += h;
1848         }
1849
1850         // if we get here, no line was on screen - scroll so that one line is
1851         // visible then.
1852         con_backscroll = lines_seen - 1;
1853         *limitlast = 1;
1854 }
1855
1856 /*
1857 ================
1858 Con_DrawConsole
1859
1860 Draws the console with the solid background
1861 The typing input line at the bottom should only be drawn if typing is allowed
1862 ================
1863 */
1864 void Con_DrawConsole (int lines)
1865 {
1866         float alpha, alpha0;
1867         double sx, sy;
1868         int mask_must = 0;
1869         int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1870         cachepic_t *conbackpic;
1871
1872         if (lines <= 0)
1873                 return;
1874
1875         if (con_mutex) Thread_LockMutex(con_mutex);
1876
1877         if (con_backscroll < 0)
1878                 con_backscroll = 0;
1879
1880         con_vislines = lines;
1881
1882         r_draw2d_force = true;
1883
1884 // draw the background
1885         alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
1886         if((alpha = alpha0 * scr_conalphafactor.value) > 0)
1887         {
1888                 sx = scr_conscroll_x.value;
1889                 sy = scr_conscroll_y.value;
1890                 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL;
1891                 sx *= realtime; sy *= realtime;
1892                 sx -= floor(sx); sy -= floor(sy);
1893                 if (conbackpic && conbackpic->tex != r_texture_notexture)
1894                         DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1895                                         0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1896                                         1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1897                                         0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1898                                         1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1899                                         0);
1900                 else
1901                         DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
1902         }
1903         if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
1904         {
1905                 sx = scr_conscroll2_x.value;
1906                 sy = scr_conscroll2_y.value;
1907                 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1908                 sx *= realtime; sy *= realtime;
1909                 sx -= floor(sx); sy -= floor(sy);
1910                 if(conbackpic && conbackpic->tex != r_texture_notexture)
1911                         DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1912                                         0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1913                                         1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1914                                         0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1915                                         1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1916                                         0);
1917         }
1918         if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
1919         {
1920                 sx = scr_conscroll3_x.value;
1921                 sy = scr_conscroll3_y.value;
1922                 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1923                 sx *= realtime; sy *= realtime;
1924                 sx -= floor(sx); sy -= floor(sy);
1925                 if(conbackpic && conbackpic->tex != r_texture_notexture)
1926                         DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1927                                         0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1928                                         1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1929                                         0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1930                                         1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1931                                         0);
1932         }
1933         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);
1934
1935 // draw the text
1936 #if 0
1937         {
1938                 int i;
1939                 int count = CON_LINES_COUNT;
1940                 float ymax = con_vislines - 2 * con_textsize.value;
1941                 float y = ymax + con_textsize.value * con_backscroll;
1942                 for (i = 0;i < count && y >= 0;i++)
1943                         y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
1944                 // fix any excessive scrollback for the next frame
1945                 if (i >= count && y >= 0)
1946                 {
1947                         con_backscroll -= (int)(y / con_textsize.value);
1948                         if (con_backscroll < 0)
1949                                 con_backscroll = 0;
1950                 }
1951         }
1952 #else
1953         if(CON_LINES_COUNT > 0)
1954         {
1955                 int i, last, limitlast;
1956                 float y;
1957                 float ymax = con_vislines - 2 * con_textsize.value;
1958                 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1959                 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1960                 y = ymax - con_textsize.value;
1961
1962                 if(limitlast)
1963                         y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1964                 i = last;
1965
1966                 for(;;)
1967                 {
1968                         y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
1969                         if(i == 0)
1970                                 break; // top of console buffer
1971                         if(y < 0)
1972                                 break; // top of console window
1973                         limitlast = 0;
1974                         --i;
1975                 }
1976         }
1977 #endif
1978
1979 // draw the input prompt, user text, and cursor if desired
1980         Con_DrawInput ();
1981
1982         r_draw2d_force = false;
1983         if (con_mutex) Thread_UnlockMutex(con_mutex);
1984 }
1985
1986 /*
1987 GetMapList
1988
1989 Made by [515]
1990 Prints not only map filename, but also
1991 its format (q1/q2/q3/hl) and even its message
1992 */
1993 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1994 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1995 //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
1996 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1997 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1998 {
1999         fssearch_t      *t;
2000         char            message[1024];
2001         int                     i, k, max, p, o, min;
2002         unsigned char *len;
2003         qfile_t         *f;
2004         unsigned char buf[1024];
2005
2006         dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2007         t = FS_Search(message, 1, true);
2008         if(!t)
2009                 return false;
2010         if (t->numfilenames > 1)
2011                 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2012         len = (unsigned char *)Z_Malloc(t->numfilenames);
2013         min = 666;
2014         for(max=i=0;i<t->numfilenames;i++)
2015         {
2016                 k = (int)strlen(t->filenames[i]);
2017                 k -= 9;
2018                 if(max < k)
2019                         max = k;
2020                 else
2021                 if(min > k)
2022                         min = k;
2023                 len[i] = k;
2024         }
2025         o = (int)strlen(s);
2026         for(i=0;i<t->numfilenames;i++)
2027         {
2028                 int lumpofs = 0, lumplen = 0;
2029                 char *entities = NULL;
2030                 const char *data = NULL;
2031                 char keyname[64];
2032                 char entfilename[MAX_QPATH];
2033                 strlcpy(message, "^1**ERROR**^7", sizeof(message));
2034                 p = 0;
2035                 f = FS_OpenVirtualFile(t->filenames[i], true);
2036                 if(f)
2037                 {
2038                         memset(buf, 0, 1024);
2039                         FS_Read(f, buf, 1024);
2040                         if (!memcmp(buf, "IBSP", 4))
2041                         {
2042                                 p = LittleLong(((int *)buf)[1]);
2043                                 if (p == Q3BSPVERSION)
2044                                 {
2045                                         q3dheader_t *header = (q3dheader_t *)buf;
2046                                         lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2047                                         lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2048                                 }
2049                                 else if (p == Q2BSPVERSION)
2050                                 {
2051                                         q2dheader_t *header = (q2dheader_t *)buf;
2052                                         lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2053                                         lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2054                                 }
2055                         }
2056                         else if((p = BuffLittleLong(buf)) == BSPVERSION || p == 30)
2057                         {
2058                                 dheader_t *header = (dheader_t *)buf;
2059                                 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
2060                                 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
2061                         }
2062                         else
2063                                 p = 0;
2064                         strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2065                         memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2066                         entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2067                         if (!entities && lumplen >= 10)
2068                         {
2069                                 FS_Seek(f, lumpofs, SEEK_SET);
2070                                 entities = (char *)Z_Malloc(lumplen + 1);
2071                                 FS_Read(f, entities, lumplen);
2072                         }
2073                         if (entities)
2074                         {
2075                                 // if there are entities to parse, a missing message key just
2076                                 // means there is no title, so clear the message string now
2077                                 message[0] = 0;
2078                                 data = entities;
2079                                 for (;;)
2080                                 {
2081                                         int l;
2082                                         if (!COM_ParseToken_Simple(&data, false, false, true))
2083                                                 break;
2084                                         if (com_token[0] == '{')
2085                                                 continue;
2086                                         if (com_token[0] == '}')
2087                                                 break;
2088                                         // skip leading whitespace
2089                                         for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2090                                         for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2091                                                 keyname[l] = com_token[k+l];
2092                                         keyname[l] = 0;
2093                                         if (!COM_ParseToken_Simple(&data, false, false, true))
2094                                                 break;
2095                                         if (developer_extra.integer)
2096                                                 Con_DPrintf("key: %s %s\n", keyname, com_token);
2097                                         if (!strcmp(keyname, "message"))
2098                                         {
2099                                                 // get the message contents
2100                                                 strlcpy(message, com_token, sizeof(message));
2101                                                 break;
2102                                         }
2103                                 }
2104                         }
2105                 }
2106                 if (entities)
2107                         Z_Free(entities);
2108                 if(f)
2109                         FS_Close(f);
2110                 *(t->filenames[i]+len[i]+5) = 0;
2111                 switch(p)
2112                 {
2113                 case Q3BSPVERSION:      strlcpy((char *)buf, "Q3", sizeof(buf));break;
2114                 case Q2BSPVERSION:      strlcpy((char *)buf, "Q2", sizeof(buf));break;
2115                 case BSPVERSION:        strlcpy((char *)buf, "Q1", sizeof(buf));break;
2116                 case 30:                        strlcpy((char *)buf, "HL", sizeof(buf));break;
2117                 default:                        strlcpy((char *)buf, "??", sizeof(buf));break;
2118                 }
2119                 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
2120         }
2121         Con_Print("\n");
2122         for(p=o;p<min;p++)
2123         {
2124                 k = *(t->filenames[0]+5+p);
2125                 if(k == 0)
2126                         goto endcomplete;
2127                 for(i=1;i<t->numfilenames;i++)
2128                         if(*(t->filenames[i]+5+p) != k)
2129                                 goto endcomplete;
2130         }
2131 endcomplete:
2132         if(p > o && completedname && completednamebufferlength > 0)
2133         {
2134                 memset(completedname, 0, completednamebufferlength);
2135                 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2136         }
2137         Z_Free(len);
2138         FS_FreeSearch(t);
2139         return p > o;
2140 }
2141
2142 /*
2143         Con_DisplayList
2144
2145         New function for tab-completion system
2146         Added by EvilTypeGuy
2147         MEGA Thanks to Taniwha
2148
2149 */
2150 void Con_DisplayList(const char **list)
2151 {
2152         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2153         const char **walk = list;
2154
2155         while (*walk) {
2156                 len = (int)strlen(*walk);
2157                 if (len > maxlen)
2158                         maxlen = len;
2159                 walk++;
2160         }
2161         maxlen += 1;
2162
2163         while (*list) {
2164                 len = (int)strlen(*list);
2165                 if (pos + maxlen >= width) {
2166                         Con_Print("\n");
2167                         pos = 0;
2168                 }
2169
2170                 Con_Print(*list);
2171                 for (i = 0; i < (maxlen - len); i++)
2172                         Con_Print(" ");
2173
2174                 pos += maxlen;
2175                 list++;
2176         }
2177
2178         if (pos)
2179                 Con_Print("\n\n");
2180 }
2181
2182 /*
2183         SanitizeString strips color tags from the string in
2184         and writes the result on string out
2185 */
2186 static void SanitizeString(char *in, char *out)
2187 {
2188         while(*in)
2189         {
2190                 if(*in == STRING_COLOR_TAG)
2191                 {
2192                         ++in;
2193                         if(!*in)
2194                         {
2195                                 out[0] = STRING_COLOR_TAG;
2196                                 out[1] = 0;
2197                                 return;
2198                         }
2199                         else if (*in >= '0' && *in <= '9') // ^[0-9] found
2200                         {
2201                                 ++in;
2202                                 if(!*in)
2203                                 {
2204                                         *out = 0;
2205                                         return;
2206                                 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2207                                         continue;
2208                         }
2209                         else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2210                         {
2211                                 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2212                                 {
2213                                         in+=4;
2214                                         if (!*in)
2215                                         {
2216                                                 *out = 0;
2217                                                 return;
2218                                         } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2219                                                 continue;
2220                                 }
2221                                 else in--;
2222                         }
2223                         else if (*in != STRING_COLOR_TAG)
2224                                 --in;
2225                 }
2226                 *out = qfont_table[*(unsigned char*)in];
2227                 ++in;
2228                 ++out;
2229         }
2230         *out = 0;
2231 }
2232
2233 // Now it becomes TRICKY :D --blub
2234 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];     // contains the nicks with colors and all that
2235 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];  // sanitized list for completion when there are other possible matches.
2236 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2237 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
2238 static int Nicks_matchpos;
2239
2240 // co against <<:BLASTER:>> is true!?
2241 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2242 {
2243         while(a_len)
2244         {
2245                 if(tolower(*a) == tolower(*b))
2246                 {
2247                         if(*a == 0)
2248                                 return 0;
2249                         --a_len;
2250                         ++a;
2251                         ++b;
2252                         continue;
2253                 }
2254                 if(!*a)
2255                         return -1;
2256                 if(!*b)
2257                         return 1;
2258                 if(*a == ' ')
2259                         return (*a < *b) ? -1 : 1;
2260                 if(*b == ' ')
2261                         ++b;
2262                 else
2263                         return (*a < *b) ? -1 : 1;
2264         }
2265         return 0;
2266 }
2267 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2268 {
2269         char space_char;
2270         if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2271         {
2272                 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2273                         return Nicks_strncasecmp_nospaces(a, b, a_len);
2274                 return strncasecmp(a, b, a_len);
2275         }
2276
2277         space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2278
2279         // ignore non alphanumerics of B
2280         // if A contains a non-alphanumeric, B must contain it as well though!
2281         while(a_len)
2282         {
2283                 qboolean alnum_a, alnum_b;
2284
2285                 if(tolower(*a) == tolower(*b))
2286                 {
2287                         if(*a == 0) // end of both strings, they're equal
2288                                 return 0;
2289                         --a_len;
2290                         ++a;
2291                         ++b;
2292                         continue;
2293                 }
2294                 // not equal, end of one string?
2295                 if(!*a)
2296                         return -1;
2297                 if(!*b)
2298                         return 1;
2299                 // ignore non alphanumerics
2300                 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2301                 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2302                 if(!alnum_a) // b must contain this
2303                         return (*a < *b) ? -1 : 1;
2304                 if(!alnum_b)
2305                         ++b;
2306                 // otherwise, both are alnum, they're just not equal, return the appropriate number
2307                 else
2308                         return (*a < *b) ? -1 : 1;
2309         }
2310         return 0;
2311 }
2312
2313
2314 /* Nicks_CompleteCountPossible
2315
2316    Count the number of possible nicks to complete
2317  */
2318 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2319 {
2320         char name[128];
2321         int i, p;
2322         int match;
2323         int spos;
2324         int count = 0;
2325
2326         if(!con_nickcompletion.integer)
2327                 return 0;
2328
2329         // changed that to 1
2330         if(!line[0])// || !line[1]) // we want at least... 2 written characters
2331                 return 0;
2332
2333         for(i = 0; i < cl.maxclients; ++i)
2334         {
2335                 p = i;
2336                 if(!cl.scores[p].name[0])
2337                         continue;
2338
2339                 SanitizeString(cl.scores[p].name, name);
2340                 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2341
2342                 if(!name[0])
2343                         continue;
2344
2345                 match = -1;
2346                 spos = pos - 1; // no need for a minimum of characters :)
2347
2348                 while(spos >= 0)
2349                 {
2350                         if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2351                         {
2352                                 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2353                                    !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2354                                 {
2355                                         --spos;
2356                                         continue;
2357                                 }
2358                         }
2359                         if(isCon && spos == 0)
2360                                 break;
2361                         if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2362                                 match = spos;
2363                         --spos;
2364                 }
2365                 if(match < 0)
2366                         continue;
2367                 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2368                 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2369
2370                 // the sanitized list
2371                 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2372                 if(!count)
2373                 {
2374                         Nicks_matchpos = match;
2375                 }
2376
2377                 Nicks_offset[count] = s - (&line[match]);
2378                 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2379
2380                 ++count;
2381         }
2382         return count;
2383 }
2384
2385 static void Cmd_CompleteNicksPrint(int count)
2386 {
2387         int i;
2388         for(i = 0; i < count; ++i)
2389                 Con_Printf("%s\n", Nicks_list[i]);
2390 }
2391
2392 static void Nicks_CutMatchesNormal(int count)
2393 {
2394         // cut match 0 down to the longest possible completion
2395         int i;
2396         unsigned int c, l;
2397         c = strlen(Nicks_sanlist[0]) - 1;
2398         for(i = 1; i < count; ++i)
2399         {
2400                 l = strlen(Nicks_sanlist[i]) - 1;
2401                 if(l < c)
2402                         c = l;
2403
2404                 for(l = 0; l <= c; ++l)
2405                         if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2406                         {
2407                                 c = l-1;
2408                                 break;
2409                         }
2410         }
2411         Nicks_sanlist[0][c+1] = 0;
2412         //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2413 }
2414
2415 static unsigned int Nicks_strcleanlen(const char *s)
2416 {
2417         unsigned int l = 0;
2418         while(*s)
2419         {
2420                 if( (*s >= 'a' && *s <= 'z') ||
2421                     (*s >= 'A' && *s <= 'Z') ||
2422                     (*s >= '0' && *s <= '9') ||
2423                     *s == ' ')
2424                         ++l;
2425                 ++s;
2426         }
2427         return l;
2428 }
2429
2430 static void Nicks_CutMatchesAlphaNumeric(int count)
2431 {
2432         // cut match 0 down to the longest possible completion
2433         int i;
2434         unsigned int c, l;
2435         char tempstr[sizeof(Nicks_sanlist[0])];
2436         char *a, *b;
2437         char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2438
2439         c = strlen(Nicks_sanlist[0]);
2440         for(i = 0, l = 0; i < (int)c; ++i)
2441         {
2442                 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2443                     (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2444                     (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2445                 {
2446                         tempstr[l++] = Nicks_sanlist[0][i];
2447                 }
2448         }
2449         tempstr[l] = 0;
2450
2451         for(i = 1; i < count; ++i)
2452         {
2453                 a = tempstr;
2454                 b = Nicks_sanlist[i];
2455                 while(1)
2456                 {
2457                         if(!*a)
2458                                 break;
2459                         if(!*b)
2460                         {
2461                                 *a = 0;
2462                                 break;
2463                         }
2464                         if(tolower(*a) == tolower(*b))
2465                         {
2466                                 ++a;
2467                                 ++b;
2468                                 continue;
2469                         }
2470                         if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2471                         {
2472                                 // b is alnum, so cut
2473                                 *a = 0;
2474                                 break;
2475                         }
2476                         ++b;
2477                 }
2478         }
2479         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2480         Nicks_CutMatchesNormal(count);
2481         //if(!Nicks_sanlist[0][0])
2482         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2483         {
2484                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2485                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2486         }
2487 }
2488
2489 static void Nicks_CutMatchesNoSpaces(int count)
2490 {
2491         // cut match 0 down to the longest possible completion
2492         int i;
2493         unsigned int c, l;
2494         char tempstr[sizeof(Nicks_sanlist[0])];
2495         char *a, *b;
2496
2497         c = strlen(Nicks_sanlist[0]);
2498         for(i = 0, l = 0; i < (int)c; ++i)
2499         {
2500                 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2501                 {
2502                         tempstr[l++] = Nicks_sanlist[0][i];
2503                 }
2504         }
2505         tempstr[l] = 0;
2506
2507         for(i = 1; i < count; ++i)
2508         {
2509                 a = tempstr;
2510                 b = Nicks_sanlist[i];
2511                 while(1)
2512                 {
2513                         if(!*a)
2514                                 break;
2515                         if(!*b)
2516                         {
2517                                 *a = 0;
2518                                 break;
2519                         }
2520                         if(tolower(*a) == tolower(*b))
2521                         {
2522                                 ++a;
2523                                 ++b;
2524                                 continue;
2525                         }
2526                         if(*b != ' ')
2527                         {
2528                                 *a = 0;
2529                                 break;
2530                         }
2531                         ++b;
2532                 }
2533         }
2534         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2535         Nicks_CutMatchesNormal(count);
2536         //if(!Nicks_sanlist[0][0])
2537         //Con_Printf("TS: %s\n", tempstr);
2538         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2539         {
2540                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2541                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2542         }
2543 }
2544
2545 static void Nicks_CutMatches(int count)
2546 {
2547         if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2548                 Nicks_CutMatchesAlphaNumeric(count);
2549         else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2550                 Nicks_CutMatchesNoSpaces(count);
2551         else
2552                 Nicks_CutMatchesNormal(count);
2553 }
2554
2555 static const char **Nicks_CompleteBuildList(int count)
2556 {
2557         const char **buf;
2558         int bpos = 0;
2559         // the list is freed by Con_CompleteCommandLine, so create a char**
2560         buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2561
2562         for(; bpos < count; ++bpos)
2563                 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2564
2565         Nicks_CutMatches(count);
2566
2567         buf[bpos] = NULL;
2568         return buf;
2569 }
2570
2571 /*
2572         Nicks_AddLastColor
2573         Restores the previous used color, after the autocompleted name.
2574 */
2575 static int Nicks_AddLastColor(char *buffer, int pos)
2576 {
2577         qboolean quote_added = false;
2578         int match;
2579         int color = STRING_COLOR_DEFAULT + '0';
2580         char r = 0, g = 0, b = 0;
2581
2582         if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2583         {
2584                 // we'll have to add a quote :)
2585                 buffer[pos++] = '\"';
2586                 quote_added = true;
2587         }
2588
2589         if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2590         {
2591                 // add color when no quote was added, or when flags &4?
2592                 // find last color
2593                 for(match = Nicks_matchpos-1; match >= 0; --match)
2594                 {
2595                         if(buffer[match] == STRING_COLOR_TAG)
2596                         {
2597                                 if( isdigit(buffer[match+1]) )
2598                                 {
2599                                         color = buffer[match+1];
2600                                         break;
2601                                 }
2602                                 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2603                                 {
2604                                         if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2605                                         {
2606                                                 r = buffer[match+2];
2607                                                 g = buffer[match+3];
2608                                                 b = buffer[match+4];
2609                                                 color = -1;
2610                                                 break;
2611                                         }
2612                                 }
2613                         }
2614                 }
2615                 if(!quote_added)
2616                 {
2617                         if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2618                                 pos -= 2;
2619                         else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2620                                          && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2621                                 pos -= 5;
2622                 }
2623                 buffer[pos++] = STRING_COLOR_TAG;
2624                 if (color == -1)
2625                 {
2626                         buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2627                         buffer[pos++] = r;
2628                         buffer[pos++] = g;
2629                         buffer[pos++] = b;
2630                 }
2631                 else
2632                         buffer[pos++] = color;
2633         }
2634         return pos;
2635 }
2636
2637 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2638 {
2639         int n;
2640         /*if(!con_nickcompletion.integer)
2641           return; is tested in Nicks_CompletionCountPossible */
2642         n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2643         if(n == 1)
2644         {
2645                 size_t len;
2646                 char *msg;
2647
2648                 msg = Nicks_list[0];
2649                 len = min(size - Nicks_matchpos - 3, strlen(msg));
2650                 memcpy(&buffer[Nicks_matchpos], msg, len);
2651                 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2652                         len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2653                 buffer[len++] = ' ';
2654                 buffer[len] = 0;
2655                 return len;
2656         } else if(n > 1)
2657         {
2658                 int len;
2659                 char *msg;
2660                 Con_Printf("\n%i possible nicks:\n", n);
2661                 Cmd_CompleteNicksPrint(n);
2662
2663                 Nicks_CutMatches(n);
2664
2665                 msg = Nicks_sanlist[0];
2666                 len = min(size - Nicks_matchpos, strlen(msg));
2667                 memcpy(&buffer[Nicks_matchpos], msg, len);
2668                 buffer[Nicks_matchpos + len] = 0;
2669                 //pos += len;
2670                 return Nicks_matchpos + len;
2671         }
2672         return pos;
2673 }
2674
2675
2676 /*
2677         Con_CompleteCommandLine
2678
2679         New function for tab-completion system
2680         Added by EvilTypeGuy
2681         Thanks to Fett erich@heintz.com
2682         Thanks to taniwha
2683         Enhanced to tab-complete map names by [515]
2684
2685 */
2686 void Con_CompleteCommandLine (void)
2687 {
2688         const char *cmd = "";
2689         char *s;
2690         const char **list[4] = {0, 0, 0, 0};
2691         char s2[512];
2692         char command[512];
2693         int c, v, a, i, cmd_len, pos, k;
2694         int n; // nicks --blub
2695         const char *space, *patterns;
2696         char vabuf[1024];
2697
2698         //find what we want to complete
2699         pos = key_linepos;
2700         while(--pos)
2701         {
2702                 k = key_line[pos];
2703                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2704                         break;
2705         }
2706         pos++;
2707
2708         s = key_line + pos;
2709         strlcpy(s2, key_line + key_linepos, sizeof(s2));        //save chars after cursor
2710         key_line[key_linepos] = 0;                                      //hide them
2711
2712         space = strchr(key_line + 1, ' ');
2713         if(space && pos == (space - key_line) + 1)
2714         {
2715                 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2716
2717                 patterns = Cvar_VariableString(va(vabuf, sizeof(vabuf), "con_completion_%s", command)); // TODO maybe use a better place for this?
2718                 if(patterns && !*patterns)
2719                         patterns = NULL; // get rid of the empty string
2720
2721                 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2722                 {
2723                         //maps search
2724                         char t[MAX_QPATH];
2725                         if (GetMapList(s, t, sizeof(t)))
2726                         {
2727                                 // first move the cursor
2728                                 key_linepos += (int)strlen(t) - (int)strlen(s);
2729
2730                                 // and now do the actual work
2731                                 *s = 0;
2732                                 strlcat(key_line, t, MAX_INPUTLINE);
2733                                 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2734
2735                                 // and fix the cursor
2736                                 if(key_linepos > (int) strlen(key_line))
2737                                         key_linepos = (int) strlen(key_line);
2738                         }
2739                         return;
2740                 }
2741                 else
2742                 {
2743                         if(patterns)
2744                         {
2745                                 char t[MAX_QPATH];
2746                                 stringlist_t resultbuf, dirbuf;
2747
2748                                 // Usage:
2749                                 //   // store completion patterns (space separated) for command foo in con_completion_foo
2750                                 //   set con_completion_foo "foodata/*.foodefault *.foo"
2751                                 //   foo <TAB>
2752                                 //
2753                                 // Note: patterns with slash are always treated as absolute
2754                                 // patterns; patterns without slash search in the innermost
2755                                 // directory the user specified. There is no way to "complete into"
2756                                 // a directory as of now, as directories seem to be unknown to the
2757                                 // FS subsystem.
2758                                 //
2759                                 // Examples:
2760                                 //   set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2761                                 //   set con_completion_playdemo "*.dem"
2762                                 //   set con_completion_play "*.wav *.ogg"
2763                                 //
2764                                 // TODO somehow add support for directories; these shall complete
2765                                 // to their name + an appended slash.
2766
2767                                 stringlistinit(&resultbuf);
2768                                 stringlistinit(&dirbuf);
2769                                 while(COM_ParseToken_Simple(&patterns, false, false, true))
2770                                 {
2771                                         fssearch_t *search;
2772                                         if(strchr(com_token, '/'))
2773                                         {
2774                                                 search = FS_Search(com_token, true, true);
2775                                         }
2776                                         else
2777                                         {
2778                                                 const char *slash = strrchr(s, '/');
2779                                                 if(slash)
2780                                                 {
2781                                                         strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2782                                                         strlcat(t, com_token, sizeof(t));
2783                                                         search = FS_Search(t, true, true);
2784                                                 }
2785                                                 else
2786                                                         search = FS_Search(com_token, true, true);
2787                                         }
2788                                         if(search)
2789                                         {
2790                                                 for(i = 0; i < search->numfilenames; ++i)
2791                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2792                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2793                                                                         stringlistappend(&resultbuf, search->filenames[i]);
2794                                                 FS_FreeSearch(search);
2795                                         }
2796                                 }
2797
2798                                 // In any case, add directory names
2799                                 {
2800                                         fssearch_t *search;
2801                                         const char *slash = strrchr(s, '/');
2802                                         if(slash)
2803                                         {
2804                                                 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2805                                                 strlcat(t, "*", sizeof(t));
2806                                                 search = FS_Search(t, true, true);
2807                                         }
2808                                         else
2809                                                 search = FS_Search("*", true, true);
2810                                         if(search)
2811                                         {
2812                                                 for(i = 0; i < search->numfilenames; ++i)
2813                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2814                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2815                                                                         stringlistappend(&dirbuf, search->filenames[i]);
2816                                                 FS_FreeSearch(search);
2817                                         }
2818                                 }
2819
2820                                 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2821                                 {
2822                                         const char *p, *q;
2823                                         unsigned int matchchars;
2824                                         if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2825                                         {
2826                                                 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2827                                         }
2828                                         else
2829                                         if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2830                                         {
2831                                                 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2832                                         }
2833                                         else
2834                                         {
2835                                                 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2836                                                 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2837                                                 for(i = 0; i < dirbuf.numstrings; ++i)
2838                                                 {
2839                                                         Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2840                                                 }
2841                                                 for(i = 0; i < resultbuf.numstrings; ++i)
2842                                                 {
2843                                                         Con_Printf("%s\n", resultbuf.strings[i]);
2844                                                 }
2845                                                 matchchars = sizeof(t) - 1;
2846                                                 if(resultbuf.numstrings > 0)
2847                                                 {
2848                                                         p = resultbuf.strings[0];
2849                                                         q = resultbuf.strings[resultbuf.numstrings - 1];
2850                                                         for(; *p && *p == *q; ++p, ++q);
2851                                                         matchchars = (unsigned int)(p - resultbuf.strings[0]);
2852                                                 }
2853                                                 if(dirbuf.numstrings > 0)
2854                                                 {
2855                                                         p = dirbuf.strings[0];
2856                                                         q = dirbuf.strings[dirbuf.numstrings - 1];
2857                                                         for(; *p && *p == *q; ++p, ++q);
2858                                                         matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2859                                                 }
2860                                                 // now p points to the first non-equal character, or to the end
2861                                                 // of resultbuf.strings[0]. We want to append the characters
2862                                                 // from resultbuf.strings[0] to (not including) p as these are
2863                                                 // the unique prefix
2864                                                 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2865                                         }
2866
2867                                         // first move the cursor
2868                                         key_linepos += (int)strlen(t) - (int)strlen(s);
2869
2870                                         // and now do the actual work
2871                                         *s = 0;
2872                                         strlcat(key_line, t, MAX_INPUTLINE);
2873                                         strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2874
2875                                         // and fix the cursor
2876                                         if(key_linepos > (int) strlen(key_line))
2877                                                 key_linepos = (int) strlen(key_line);
2878                                 }
2879                                 stringlistfreecontents(&resultbuf);
2880                                 stringlistfreecontents(&dirbuf);
2881
2882                                 return; // bail out, when we complete for a command that wants a file name
2883                         }
2884                 }
2885         }
2886
2887         // Count number of possible matches and print them
2888         c = Cmd_CompleteCountPossible(s);
2889         if (c)
2890         {
2891                 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2892                 Cmd_CompleteCommandPrint(s);
2893         }
2894         v = Cvar_CompleteCountPossible(s);
2895         if (v)
2896         {
2897                 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2898                 Cvar_CompleteCvarPrint(s);
2899         }
2900         a = Cmd_CompleteAliasCountPossible(s);
2901         if (a)
2902         {
2903                 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2904                 Cmd_CompleteAliasPrint(s);
2905         }
2906         n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2907         if (n)
2908         {
2909                 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2910                 Cmd_CompleteNicksPrint(n);
2911         }
2912
2913         if (!(c + v + a + n))   // No possible matches
2914         {
2915                 if(s2[0])
2916                         strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2917                 return;
2918         }
2919
2920         if (c)
2921                 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2922         if (v)
2923                 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2924         if (a)
2925                 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2926         if (n)
2927                 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2928
2929         for (cmd_len = (int)strlen(s);;cmd_len++)
2930         {
2931                 const char **l;
2932                 for (i = 0; i < 3; i++)
2933                         if (list[i])
2934                                 for (l = list[i];*l;l++)
2935                                         if ((*l)[cmd_len] != cmd[cmd_len])
2936                                                 goto done;
2937                 // all possible matches share this character, so we continue...
2938                 if (!cmd[cmd_len])
2939                 {
2940                         // if all matches ended at the same position, stop
2941                         // (this means there is only one match)
2942                         break;
2943                 }
2944         }
2945 done:
2946
2947         // prevent a buffer overrun by limiting cmd_len according to remaining space
2948         cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2949         if (cmd)
2950         {
2951                 key_linepos = pos;
2952                 memcpy(&key_line[key_linepos], cmd, cmd_len);
2953                 key_linepos += cmd_len;
2954                 // if there is only one match, add a space after it
2955                 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2956                 {
2957                         if(n)
2958                         { // was a nick, might have an offset, and needs colors ;) --blub
2959                                 key_linepos = pos - Nicks_offset[0];
2960                                 cmd_len = strlen(Nicks_list[0]);
2961                                 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2962
2963                                 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2964                                 key_linepos += cmd_len;
2965                                 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2966                                         key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2967                         }
2968                         key_line[key_linepos++] = ' ';
2969                 }
2970         }
2971
2972         // use strlcat to avoid a buffer overrun
2973         key_line[key_linepos] = 0;
2974         strlcat(key_line, s2, sizeof(key_line));
2975
2976         // free the command, cvar, and alias lists
2977         for (i = 0; i < 4; i++)
2978                 if (list[i])
2979                         Mem_Free((void *)list[i]);
2980 }
2981