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