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