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