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