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