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