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