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