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