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