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