]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - console.c
Redesigned TaskQueue to have a queue and distributor model so that threads can keep...
[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_Printf("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_DPrint
1464 ================
1465 */
1466 void Con_DPrint(const char *msg)
1467 {
1468         if(developer.integer < 0) // at 0, we still add to the buffer but hide
1469                 return;
1470
1471         Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1472 }
1473
1474 /*
1475 ================
1476 Con_DPrintf
1477 ================
1478 */
1479 void Con_DPrintf(const char *fmt, ...)
1480 {
1481         va_list argptr;
1482         char msg[MAX_INPUTLINE];
1483
1484         if(developer.integer < 0) // at 0, we still add to the buffer but hide
1485                 return;
1486
1487         va_start(argptr,fmt);
1488         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1489         va_end(argptr);
1490
1491         Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1492 }
1493
1494
1495 /*
1496 ==============================================================================
1497
1498 DRAWING
1499
1500 ==============================================================================
1501 */
1502
1503 /*
1504 ================
1505 Con_DrawInput
1506
1507 The input line scrolls horizontally if typing goes beyond the right edge
1508
1509 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1510 ================
1511 */
1512 static void Con_DrawInput (void)
1513 {
1514         int             y;
1515         int             i;
1516         char text[sizeof(key_line)+5+1]; // space for ^^xRGB too
1517         float x, xo;
1518         size_t len_out;
1519         int col_out;
1520
1521         if (!key_consoleactive)
1522                 return;         // don't draw anything
1523
1524         strlcpy(text, key_line, sizeof(text));
1525
1526         // Advanced Console Editing by Radix radix@planetquake.com
1527         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1528
1529         y = (int)strlen(text);
1530
1531         // make the color code visible when the cursor is inside it
1532         if(text[key_linepos] != 0)
1533         {
1534                 for(i=1; i < 5 && key_linepos - i > 0; ++i)
1535                         if(text[key_linepos-i] == STRING_COLOR_TAG)
1536                         {
1537                                 int caret_pos, ofs = 0;
1538                                 caret_pos = key_linepos - i;
1539                                 if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG)
1540                                         ofs = 1;
1541                                 else if(i == 1 && isdigit(text[caret_pos+1]))
1542                                         ofs = 2;
1543                                 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]))
1544                                         ofs = 5;
1545                                 if(ofs && (size_t)(y + ofs + 1) < sizeof(text))
1546                                 {
1547                                         int carets = 1;
1548                                         while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG)
1549                                                 ++carets;
1550                                         if(carets & 1)
1551                                         {
1552                                                 // str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing)
1553                                                 // str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing)
1554                                                 memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos);
1555                                                 text[caret_pos + ofs] = STRING_COLOR_TAG;
1556                                                 y += ofs + 1;
1557                                                 text[y] = 0;
1558                                         }
1559                                 }
1560                                 break;
1561                         }
1562         }
1563
1564         len_out = key_linepos;
1565         col_out = -1;
1566         xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1567         x = vid_conwidth.value * 0.95 - xo; // scroll
1568         if(x >= 0)
1569                 x = 0;
1570
1571         // draw it
1572         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 );
1573
1574         // draw a cursor on top of this
1575         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
1576         {
1577                 if (!utf8_enable.integer)
1578                 {
1579                         text[0] = 11 + 130 * key_insert;        // either solid or triangle facing right
1580                         text[1] = 0;
1581                 }
1582                 else
1583                 {
1584                         size_t len;
1585                         const char *curbuf;
1586                         char charbuf16[16];
1587                         curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1588                         memcpy(text, curbuf, len);
1589                         text[len] = 0;
1590                 }
1591                 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);
1592         }
1593 }
1594
1595 typedef struct
1596 {
1597         dp_font_t *font;
1598         float alignment; // 0 = left, 0.5 = center, 1 = right
1599         float fontsize;
1600         float x;
1601         float y;
1602         float width;
1603         float ymin, ymax;
1604         const char *continuationString;
1605
1606         // PRIVATE:
1607         int colorindex; // init to -1
1608 }
1609 con_text_info_t;
1610
1611 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1612 {
1613         con_text_info_t *ti = (con_text_info_t *) passthrough;
1614         if(w == NULL)
1615         {
1616                 ti->colorindex = -1;
1617                 return ti->fontsize * ti->font->maxwidth;
1618         }
1619         if(maxWidth >= 0)
1620                 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1621         else if(maxWidth == -1)
1622                 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1623         else
1624         {
1625                 Sys_PrintfToTerminal("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1626                 // Note: this is NOT a Con_Printf, as it could print recursively
1627                 return 0;
1628         }
1629 }
1630
1631 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1632 {
1633         (void) passthrough;
1634         (void) line;
1635         (void) length;
1636         (void) width;
1637         (void) isContinuation;
1638         return 1;
1639 }
1640
1641 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1642 {
1643         con_text_info_t *ti = (con_text_info_t *) passthrough;
1644
1645         if(ti->y < ti->ymin - 0.001)
1646                 (void) 0;
1647         else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1648                 (void) 0;
1649         else
1650         {
1651                 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1652                 if(isContinuation && *ti->continuationString)
1653                         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);
1654                 if(length > 0)
1655                         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);
1656         }
1657
1658         ti->y += ti->fontsize;
1659         return 1;
1660 }
1661
1662 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)
1663 {
1664         int i;
1665         int lines = 0;
1666         int maxlines = (int) floor(height / fontsize + 0.01f);
1667         int startidx;
1668         int nskip = 0;
1669         int continuationWidth = 0;
1670         size_t len;
1671         double t = cl.time; // saved so it won't change
1672         con_text_info_t ti;
1673
1674         ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1675         ti.fontsize = fontsize;
1676         ti.alignment = alignment_x;
1677         ti.width = width;
1678         ti.ymin = y;
1679         ti.ymax = y + height;
1680         ti.continuationString = continuationString;
1681
1682         len = 0;
1683         Con_WordWidthFunc(&ti, NULL, &len, -1);
1684         len = strlen(continuationString);
1685         continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &len, -1);
1686
1687         // first find the first line to draw by backwards iterating and word wrapping to find their length...
1688         startidx = CON_LINES_COUNT;
1689         for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1690         {
1691                 con_lineinfo_t *l = &CON_LINES(i);
1692                 int mylines;
1693
1694                 if((l->mask & mask_must) != mask_must)
1695                         continue;
1696                 if(l->mask & mask_mustnot)
1697                         continue;
1698                 if(maxage && (l->addtime < t - maxage))
1699                         continue;
1700
1701                 // WE FOUND ONE!
1702                 // Calculate its actual height...
1703                 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1704                 if(lines + mylines >= maxlines)
1705                 {
1706                         nskip = lines + mylines - maxlines;
1707                         lines = maxlines;
1708                         startidx = i;
1709                         break;
1710                 }
1711                 lines += mylines;
1712                 startidx = i;
1713         }
1714
1715         // then center according to the calculated amount of lines...
1716         ti.x = x;
1717         ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1718
1719         // then actually draw
1720         for(i = startidx; i < CON_LINES_COUNT; ++i)
1721         {
1722                 con_lineinfo_t *l = &CON_LINES(i);
1723
1724                 if((l->mask & mask_must) != mask_must)
1725                         continue;
1726                 if(l->mask & mask_mustnot)
1727                         continue;
1728                 if(maxage && (l->addtime < t - maxage))
1729                         continue;
1730
1731                 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1732         }
1733
1734         return lines;
1735 }
1736
1737 /*
1738 ================
1739 Con_DrawNotify
1740
1741 Draws the last few lines of output transparently over the game top
1742 ================
1743 */
1744 void Con_DrawNotify (void)
1745 {
1746         float   x, v, xr;
1747         float chatstart, notifystart, inputsize, height;
1748         float align;
1749         char    temptext[MAX_INPUTLINE];
1750         int numChatlines;
1751         int chatpos;
1752
1753         if (con_mutex) Thread_LockMutex(con_mutex);
1754         ConBuffer_FixTimes(&con);
1755
1756         numChatlines = con_chat.integer;
1757
1758         chatpos = con_chatpos.integer;
1759
1760         if (con_notify.integer < 0)
1761                 Cvar_SetValueQuick(&con_notify, 0);
1762         if (gamemode == GAME_TRANSFUSION)
1763                 v = 8; // vertical offset
1764         else
1765                 v = 0;
1766
1767         // GAME_NEXUIZ: center, otherwise left justify
1768         align = con_notifyalign.value;
1769         if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1770         {
1771                 if(IS_OLDNEXUIZ_DERIVED(gamemode))
1772                         align = 0.5;
1773         }
1774
1775         if(numChatlines || !con_chatrect.integer)
1776         {
1777                 if(chatpos == 0)
1778                 {
1779                         // first chat, input line, then notify
1780                         chatstart = v;
1781                         notifystart = v + (numChatlines + 1) * con_chatsize.value;
1782                 }
1783                 else if(chatpos > 0)
1784                 {
1785                         // first notify, then (chatpos-1) empty lines, then chat, then input
1786                         notifystart = v;
1787                         chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1788                 }
1789                 else // if(chatpos < 0)
1790                 {
1791                         // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1792                         notifystart = v;
1793                         chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1794                 }
1795         }
1796         else
1797         {
1798                 // just notify and input
1799                 notifystart = v;
1800                 chatstart = 0; // shut off gcc warning
1801         }
1802
1803         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, "");
1804
1805         if(con_chatrect.integer)
1806         {
1807                 x = con_chatrect_x.value * vid_conwidth.value;
1808                 v = con_chatrect_y.value * vid_conheight.value;
1809         }
1810         else
1811         {
1812                 x = 0;
1813                 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1814                         v = chatstart;
1815         }
1816         height = numChatlines * con_chatsize.value;
1817
1818         if(numChatlines)
1819         {
1820                 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 ... ");
1821                 v += height;
1822         }
1823         if (key_dest == key_message)
1824         {
1825                 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1826                 int colorindex = -1;
1827                 const char *cursor;
1828                 char charbuf16[16];
1829                 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL, charbuf16);
1830
1831                 // LadyHavoc: speedup, and other improvements
1832                 if (chat_mode < 0)
1833                         dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1834                 else if(chat_mode)
1835                         dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1836                 else
1837                         dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1838
1839                 // FIXME word wrap
1840                 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1841                 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1842                 x = min(xr, x);
1843                 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1844         }
1845         if (con_mutex) Thread_UnlockMutex(con_mutex);
1846 }
1847
1848 /*
1849 ================
1850 Con_LineHeight
1851
1852 Returns the height of a given console line; calculates it if necessary.
1853 ================
1854 */
1855 static int Con_LineHeight(int lineno)
1856 {
1857         con_lineinfo_t *li = &CON_LINES(lineno);
1858         if(li->height == -1)
1859         {
1860                 float width = vid_conwidth.value;
1861                 con_text_info_t ti;
1862                 ti.fontsize = con_textsize.value;
1863                 ti.font = FONT_CONSOLE;
1864                 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1865         }
1866         return li->height;
1867 }
1868
1869 /*
1870 ================
1871 Con_DrawConsoleLine
1872
1873 Draws a line of the console; returns its height in lines.
1874 If alpha is 0, the line is not drawn, but still wrapped and its height
1875 returned.
1876 ================
1877 */
1878 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1879 {
1880         float width = vid_conwidth.value;
1881         con_text_info_t ti;
1882         con_lineinfo_t *li = &CON_LINES(lineno);
1883
1884         if((li->mask & mask_must) != mask_must)
1885                 return 0;
1886         if((li->mask & mask_mustnot) != 0)
1887                 return 0;
1888
1889         ti.continuationString = "";
1890         ti.alignment = 0;
1891         ti.fontsize = con_textsize.value;
1892         ti.font = FONT_CONSOLE;
1893         ti.x = 0;
1894         ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1895         ti.ymin = ymin;
1896         ti.ymax = ymax;
1897         ti.width = width;
1898
1899         return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1900 }
1901
1902 /*
1903 ================
1904 Con_LastVisibleLine
1905
1906 Calculates the last visible line index and how much to show of it based on
1907 con_backscroll.
1908 ================
1909 */
1910 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1911 {
1912         int lines_seen = 0;
1913         int i;
1914
1915         if(con_backscroll < 0)
1916                 con_backscroll = 0;
1917
1918         *last = 0;
1919
1920         // now count until we saw con_backscroll actual lines
1921         for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1922         if((CON_LINES(i).mask & mask_must) == mask_must)
1923         if((CON_LINES(i).mask & mask_mustnot) == 0)
1924         {
1925                 int h = Con_LineHeight(i);
1926
1927                 // line is the last visible line?
1928                 *last = i;
1929                 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1930                 {
1931                         *limitlast = lines_seen + h - con_backscroll;
1932                         return;
1933                 }
1934
1935                 lines_seen += h;
1936         }
1937
1938         // if we get here, no line was on screen - scroll so that one line is
1939         // visible then.
1940         con_backscroll = lines_seen - 1;
1941         *limitlast = 1;
1942 }
1943
1944 /*
1945 ================
1946 Con_DrawConsole
1947
1948 Draws the console with the solid background
1949 The typing input line at the bottom should only be drawn if typing is allowed
1950 ================
1951 */
1952 void Con_DrawConsole (int lines)
1953 {
1954         float alpha, alpha0;
1955         double sx, sy;
1956         int mask_must = 0;
1957         int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1958         cachepic_t *conbackpic;
1959
1960         if (lines <= 0)
1961                 return;
1962
1963         if (con_mutex) Thread_LockMutex(con_mutex);
1964
1965         if (con_backscroll < 0)
1966                 con_backscroll = 0;
1967
1968         con_vislines = lines;
1969
1970         r_draw2d_force = true;
1971
1972 // draw the background
1973         alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
1974         if((alpha = alpha0 * scr_conalphafactor.value) > 0)
1975         {
1976                 sx = scr_conscroll_x.value;
1977                 sy = scr_conscroll_y.value;
1978                 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL;
1979                 sx *= realtime; sy *= realtime;
1980                 sx -= floor(sx); sy -= floor(sy);
1981                 if (Draw_IsPicLoaded(conbackpic))
1982                         DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1983                                         0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1984                                         1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1985                                         0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1986                                         1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1987                                         0);
1988                 else
1989                         DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
1990         }
1991         if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
1992         {
1993                 sx = scr_conscroll2_x.value;
1994                 sy = scr_conscroll2_y.value;
1995                 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1996                 sx *= realtime; sy *= realtime;
1997                 sx -= floor(sx); sy -= floor(sy);
1998                 if(Draw_IsPicLoaded(conbackpic))
1999                         DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2000                                         0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2001                                         1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2002                                         0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2003                                         1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2004                                         0);
2005         }
2006         if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
2007         {
2008                 sx = scr_conscroll3_x.value;
2009                 sy = scr_conscroll3_y.value;
2010                 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2011                 sx *= realtime; sy *= realtime;
2012                 sx -= floor(sx); sy -= floor(sy);
2013                 if(Draw_IsPicLoaded(conbackpic))
2014                         DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2015                                         0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2016                                         1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2017                                         0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2018                                         1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2019                                         0);
2020         }
2021         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);
2022
2023 // draw the text
2024 #if 0
2025         {
2026                 int i;
2027                 int count = CON_LINES_COUNT;
2028                 float ymax = con_vislines - 2 * con_textsize.value;
2029                 float y = ymax + con_textsize.value * con_backscroll;
2030                 for (i = 0;i < count && y >= 0;i++)
2031                         y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
2032                 // fix any excessive scrollback for the next frame
2033                 if (i >= count && y >= 0)
2034                 {
2035                         con_backscroll -= (int)(y / con_textsize.value);
2036                         if (con_backscroll < 0)
2037                                 con_backscroll = 0;
2038                 }
2039         }
2040 #else
2041         if(CON_LINES_COUNT > 0)
2042         {
2043                 int i, last, limitlast;
2044                 float y;
2045                 float ymax = con_vislines - 2 * con_textsize.value;
2046                 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2047                 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2048                 y = ymax - con_textsize.value;
2049
2050                 if(limitlast)
2051                         y += (CON_LINES(last).height - limitlast) * con_textsize.value;
2052                 i = last;
2053
2054                 for(;;)
2055                 {
2056                         y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
2057                         if(i == 0)
2058                                 break; // top of console buffer
2059                         if(y < 0)
2060                                 break; // top of console window
2061                         limitlast = 0;
2062                         --i;
2063                 }
2064         }
2065 #endif
2066
2067 // draw the input prompt, user text, and cursor if desired
2068         Con_DrawInput ();
2069
2070         r_draw2d_force = false;
2071         if (con_mutex) Thread_UnlockMutex(con_mutex);
2072 }
2073
2074 /*
2075 GetMapList
2076
2077 Made by [515]
2078 Prints not only map filename, but also
2079 its format (q1/q2/q3/hl) and even its message
2080 */
2081 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2082 //LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2083 //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
2084 //LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2085 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
2086 {
2087         fssearch_t      *t;
2088         char            message[1024];
2089         int                     i, k, max, p, o, min;
2090         unsigned char *len;
2091         qfile_t         *f;
2092         unsigned char buf[1024];
2093
2094         dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2095         t = FS_Search(message, 1, true);
2096         if(!t)
2097                 return false;
2098         if (t->numfilenames > 1)
2099                 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2100         len = (unsigned char *)Z_Malloc(t->numfilenames);
2101         min = 666;
2102         for(max=i=0;i<t->numfilenames;i++)
2103         {
2104                 k = (int)strlen(t->filenames[i]);
2105                 k -= 9;
2106                 if(max < k)
2107                         max = k;
2108                 else
2109                 if(min > k)
2110                         min = k;
2111                 len[i] = k;
2112         }
2113         o = (int)strlen(s);
2114         for(i=0;i<t->numfilenames;i++)
2115         {
2116                 int lumpofs = 0, lumplen = 0;
2117                 char *entities = NULL;
2118                 const char *data = NULL;
2119                 char keyname[64];
2120                 char entfilename[MAX_QPATH];
2121                 char desc[64];
2122                 desc[0] = 0;
2123                 strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2124                 p = 0;
2125                 f = FS_OpenVirtualFile(t->filenames[i], true);
2126                 if(f)
2127                 {
2128                         strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2129                         memset(buf, 0, 1024);
2130                         FS_Read(f, buf, 1024);
2131                         if (!memcmp(buf, "IBSP", 4))
2132                         {
2133                                 p = LittleLong(((int *)buf)[1]);
2134                                 if (p == Q3BSPVERSION)
2135                                 {
2136                                         q3dheader_t *header = (q3dheader_t *)buf;
2137                                         lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2138                                         lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2139                                         dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2140                                 }
2141                                 else if (p == Q2BSPVERSION)
2142                                 {
2143                                         q2dheader_t *header = (q2dheader_t *)buf;
2144                                         lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2145                                         lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2146                                         dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2147                                 }
2148                                 else
2149                                         dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2150                         }
2151                         else if (BuffLittleLong(buf) == BSPVERSION)
2152                         {
2153                                 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2154                                 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2155                                 dpsnprintf(desc, sizeof(desc), "BSP29");
2156                         }
2157                         else if (BuffLittleLong(buf) == 30)
2158                         {
2159                                 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2160                                 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2161                                 dpsnprintf(desc, sizeof(desc), "BSPHL");
2162                         }
2163                         else if (!memcmp(buf, "BSP2", 4))
2164                         {
2165                                 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2166                                 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2167                                 dpsnprintf(desc, sizeof(desc), "BSP2");
2168                         }
2169                         else if (!memcmp(buf, "2PSB", 4))
2170                         {
2171                                 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2172                                 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2173                                 dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2174                         }
2175                         else
2176                         {
2177                                 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2178                         }
2179                         strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2180                         memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2181                         entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2182                         if (!entities && lumplen >= 10)
2183                         {
2184                                 FS_Seek(f, lumpofs, SEEK_SET);
2185                                 entities = (char *)Z_Malloc(lumplen + 1);
2186                                 FS_Read(f, entities, lumplen);
2187                         }
2188                         if (entities)
2189                         {
2190                                 // if there are entities to parse, a missing message key just
2191                                 // means there is no title, so clear the message string now
2192                                 message[0] = 0;
2193                                 data = entities;
2194                                 for (;;)
2195                                 {
2196                                         int l;
2197                                         if (!COM_ParseToken_Simple(&data, false, false, true))
2198                                                 break;
2199                                         if (com_token[0] == '{')
2200                                                 continue;
2201                                         if (com_token[0] == '}')
2202                                                 break;
2203                                         // skip leading whitespace
2204                                         for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2205                                         for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2206                                                 keyname[l] = com_token[k+l];
2207                                         keyname[l] = 0;
2208                                         if (!COM_ParseToken_Simple(&data, false, false, true))
2209                                                 break;
2210                                         if (developer_extra.integer)
2211                                                 Con_DPrintf("key: %s %s\n", keyname, com_token);
2212                                         if (!strcmp(keyname, "message"))
2213                                         {
2214                                                 // get the message contents
2215                                                 strlcpy(message, com_token, sizeof(message));
2216                                                 break;
2217                                         }
2218                                 }
2219                         }
2220                 }
2221                 if (entities)
2222                         Z_Free(entities);
2223                 if(f)
2224                         FS_Close(f);
2225                 *(t->filenames[i]+len[i]+5) = 0;
2226                 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2227         }
2228         Con_Print("\n");
2229         for(p=o;p<min;p++)
2230         {
2231                 k = *(t->filenames[0]+5+p);
2232                 if(k == 0)
2233                         goto endcomplete;
2234                 for(i=1;i<t->numfilenames;i++)
2235                         if(*(t->filenames[i]+5+p) != k)
2236                                 goto endcomplete;
2237         }
2238 endcomplete:
2239         if(p > o && completedname && completednamebufferlength > 0)
2240         {
2241                 memset(completedname, 0, completednamebufferlength);
2242                 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2243         }
2244         Z_Free(len);
2245         FS_FreeSearch(t);
2246         return p > o;
2247 }
2248
2249 /*
2250         Con_DisplayList
2251
2252         New function for tab-completion system
2253         Added by EvilTypeGuy
2254         MEGA Thanks to Taniwha
2255
2256 */
2257 void Con_DisplayList(const char **list)
2258 {
2259         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2260         const char **walk = list;
2261
2262         while (*walk) {
2263                 len = (int)strlen(*walk);
2264                 if (len > maxlen)
2265                         maxlen = len;
2266                 walk++;
2267         }
2268         maxlen += 1;
2269
2270         while (*list) {
2271                 len = (int)strlen(*list);
2272                 if (pos + maxlen >= width) {
2273                         Con_Print("\n");
2274                         pos = 0;
2275                 }
2276
2277                 Con_Print(*list);
2278                 for (i = 0; i < (maxlen - len); i++)
2279                         Con_Print(" ");
2280
2281                 pos += maxlen;
2282                 list++;
2283         }
2284
2285         if (pos)
2286                 Con_Print("\n\n");
2287 }
2288
2289
2290 // Now it becomes TRICKY :D --blub
2291 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];     // contains the nicks with colors and all that
2292 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];  // sanitized list for completion when there are other possible matches.
2293 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2294 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
2295 static int Nicks_matchpos;
2296
2297 // co against <<:BLASTER:>> is true!?
2298 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2299 {
2300         while(a_len)
2301         {
2302                 if(tolower(*a) == tolower(*b))
2303                 {
2304                         if(*a == 0)
2305                                 return 0;
2306                         --a_len;
2307                         ++a;
2308                         ++b;
2309                         continue;
2310                 }
2311                 if(!*a)
2312                         return -1;
2313                 if(!*b)
2314                         return 1;
2315                 if(*a == ' ')
2316                         return (*a < *b) ? -1 : 1;
2317                 if(*b == ' ')
2318                         ++b;
2319                 else
2320                         return (*a < *b) ? -1 : 1;
2321         }
2322         return 0;
2323 }
2324 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2325 {
2326         char space_char;
2327         if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2328         {
2329                 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2330                         return Nicks_strncasecmp_nospaces(a, b, a_len);
2331                 return strncasecmp(a, b, a_len);
2332         }
2333
2334         space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2335
2336         // ignore non alphanumerics of B
2337         // if A contains a non-alphanumeric, B must contain it as well though!
2338         while(a_len)
2339         {
2340                 qboolean alnum_a, alnum_b;
2341
2342                 if(tolower(*a) == tolower(*b))
2343                 {
2344                         if(*a == 0) // end of both strings, they're equal
2345                                 return 0;
2346                         --a_len;
2347                         ++a;
2348                         ++b;
2349                         continue;
2350                 }
2351                 // not equal, end of one string?
2352                 if(!*a)
2353                         return -1;
2354                 if(!*b)
2355                         return 1;
2356                 // ignore non alphanumerics
2357                 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2358                 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2359                 if(!alnum_a) // b must contain this
2360                         return (*a < *b) ? -1 : 1;
2361                 if(!alnum_b)
2362                         ++b;
2363                 // otherwise, both are alnum, they're just not equal, return the appropriate number
2364                 else
2365                         return (*a < *b) ? -1 : 1;
2366         }
2367         return 0;
2368 }
2369
2370
2371 /* Nicks_CompleteCountPossible
2372
2373    Count the number of possible nicks to complete
2374  */
2375 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2376 {
2377         char name[128];
2378         int i, p;
2379         int match;
2380         int spos;
2381         int count = 0;
2382
2383         if(!con_nickcompletion.integer)
2384                 return 0;
2385
2386         // changed that to 1
2387         if(!line[0])// || !line[1]) // we want at least... 2 written characters
2388                 return 0;
2389
2390         for(i = 0; i < cl.maxclients; ++i)
2391         {
2392                 p = i;
2393                 if(!cl.scores[p].name[0])
2394                         continue;
2395
2396                 SanitizeString(cl.scores[p].name, name);
2397                 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2398
2399                 if(!name[0])
2400                         continue;
2401
2402                 match = -1;
2403                 spos = pos - 1; // no need for a minimum of characters :)
2404
2405                 while(spos >= 0)
2406                 {
2407                         if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2408                         {
2409                                 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2410                                    !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2411                                 {
2412                                         --spos;
2413                                         continue;
2414                                 }
2415                         }
2416                         if(isCon && spos == 0)
2417                                 break;
2418                         if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2419                                 match = spos;
2420                         --spos;
2421                 }
2422                 if(match < 0)
2423                         continue;
2424                 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2425                 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2426
2427                 // the sanitized list
2428                 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2429                 if(!count)
2430                 {
2431                         Nicks_matchpos = match;
2432                 }
2433
2434                 Nicks_offset[count] = s - (&line[match]);
2435                 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2436
2437                 ++count;
2438         }
2439         return count;
2440 }
2441
2442 static void Cmd_CompleteNicksPrint(int count)
2443 {
2444         int i;
2445         for(i = 0; i < count; ++i)
2446                 Con_Printf("%s\n", Nicks_list[i]);
2447 }
2448
2449 static void Nicks_CutMatchesNormal(int count)
2450 {
2451         // cut match 0 down to the longest possible completion
2452         int i;
2453         unsigned int c, l;
2454         c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2455         for(i = 1; i < count; ++i)
2456         {
2457                 l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2458                 if(l < c)
2459                         c = l;
2460
2461                 for(l = 0; l <= c; ++l)
2462                         if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2463                         {
2464                                 c = l-1;
2465                                 break;
2466                         }
2467         }
2468         Nicks_sanlist[0][c+1] = 0;
2469         //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2470 }
2471
2472 static unsigned int Nicks_strcleanlen(const char *s)
2473 {
2474         unsigned int l = 0;
2475         while(*s)
2476         {
2477                 if( (*s >= 'a' && *s <= 'z') ||
2478                     (*s >= 'A' && *s <= 'Z') ||
2479                     (*s >= '0' && *s <= '9') ||
2480                     *s == ' ')
2481                         ++l;
2482                 ++s;
2483         }
2484         return l;
2485 }
2486
2487 static void Nicks_CutMatchesAlphaNumeric(int count)
2488 {
2489         // cut match 0 down to the longest possible completion
2490         int i;
2491         unsigned int c, l;
2492         char tempstr[sizeof(Nicks_sanlist[0])];
2493         char *a, *b;
2494         char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2495
2496         c = (unsigned int)strlen(Nicks_sanlist[0]);
2497         for(i = 0, l = 0; i < (int)c; ++i)
2498         {
2499                 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2500                     (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2501                     (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2502                 {
2503                         tempstr[l++] = Nicks_sanlist[0][i];
2504                 }
2505         }
2506         tempstr[l] = 0;
2507
2508         for(i = 1; i < count; ++i)
2509         {
2510                 a = tempstr;
2511                 b = Nicks_sanlist[i];
2512                 while(1)
2513                 {
2514                         if(!*a)
2515                                 break;
2516                         if(!*b)
2517                         {
2518                                 *a = 0;
2519                                 break;
2520                         }
2521                         if(tolower(*a) == tolower(*b))
2522                         {
2523                                 ++a;
2524                                 ++b;
2525                                 continue;
2526                         }
2527                         if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2528                         {
2529                                 // b is alnum, so cut
2530                                 *a = 0;
2531                                 break;
2532                         }
2533                         ++b;
2534                 }
2535         }
2536         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2537         Nicks_CutMatchesNormal(count);
2538         //if(!Nicks_sanlist[0][0])
2539         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2540         {
2541                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2542                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2543         }
2544 }
2545
2546 static void Nicks_CutMatchesNoSpaces(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
2554         c = (unsigned int)strlen(Nicks_sanlist[0]);
2555         for(i = 0, l = 0; i < (int)c; ++i)
2556         {
2557                 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2558                 {
2559                         tempstr[l++] = Nicks_sanlist[0][i];
2560                 }
2561         }
2562         tempstr[l] = 0;
2563
2564         for(i = 1; i < count; ++i)
2565         {
2566                 a = tempstr;
2567                 b = Nicks_sanlist[i];
2568                 while(1)
2569                 {
2570                         if(!*a)
2571                                 break;
2572                         if(!*b)
2573                         {
2574                                 *a = 0;
2575                                 break;
2576                         }
2577                         if(tolower(*a) == tolower(*b))
2578                         {
2579                                 ++a;
2580                                 ++b;
2581                                 continue;
2582                         }
2583                         if(*b != ' ')
2584                         {
2585                                 *a = 0;
2586                                 break;
2587                         }
2588                         ++b;
2589                 }
2590         }
2591         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2592         Nicks_CutMatchesNormal(count);
2593         //if(!Nicks_sanlist[0][0])
2594         //Con_Printf("TS: %s\n", tempstr);
2595         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2596         {
2597                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2598                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2599         }
2600 }
2601
2602 static void Nicks_CutMatches(int count)
2603 {
2604         if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2605                 Nicks_CutMatchesAlphaNumeric(count);
2606         else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2607                 Nicks_CutMatchesNoSpaces(count);
2608         else
2609                 Nicks_CutMatchesNormal(count);
2610 }
2611
2612 static const char **Nicks_CompleteBuildList(int count)
2613 {
2614         const char **buf;
2615         int bpos = 0;
2616         // the list is freed by Con_CompleteCommandLine, so create a char**
2617         buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2618
2619         for(; bpos < count; ++bpos)
2620                 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2621
2622         Nicks_CutMatches(count);
2623
2624         buf[bpos] = NULL;
2625         return buf;
2626 }
2627
2628 /*
2629         Nicks_AddLastColor
2630         Restores the previous used color, after the autocompleted name.
2631 */
2632 static int Nicks_AddLastColor(char *buffer, int pos)
2633 {
2634         qboolean quote_added = false;
2635         int match;
2636         int color = STRING_COLOR_DEFAULT + '0';
2637         char r = 0, g = 0, b = 0;
2638
2639         if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2640         {
2641                 // we'll have to add a quote :)
2642                 buffer[pos++] = '\"';
2643                 quote_added = true;
2644         }
2645
2646         if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2647         {
2648                 // add color when no quote was added, or when flags &4?
2649                 // find last color
2650                 for(match = Nicks_matchpos-1; match >= 0; --match)
2651                 {
2652                         if(buffer[match] == STRING_COLOR_TAG)
2653                         {
2654                                 if( isdigit(buffer[match+1]) )
2655                                 {
2656                                         color = buffer[match+1];
2657                                         break;
2658                                 }
2659                                 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2660                                 {
2661                                         if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2662                                         {
2663                                                 r = buffer[match+2];
2664                                                 g = buffer[match+3];
2665                                                 b = buffer[match+4];
2666                                                 color = -1;
2667                                                 break;
2668                                         }
2669                                 }
2670                         }
2671                 }
2672                 if(!quote_added)
2673                 {
2674                         if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2675                                 pos -= 2;
2676                         else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2677                                          && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2678                                 pos -= 5;
2679                 }
2680                 buffer[pos++] = STRING_COLOR_TAG;
2681                 if (color == -1)
2682                 {
2683                         buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2684                         buffer[pos++] = r;
2685                         buffer[pos++] = g;
2686                         buffer[pos++] = b;
2687                 }
2688                 else
2689                         buffer[pos++] = color;
2690         }
2691         return pos;
2692 }
2693
2694 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2695 {
2696         int n;
2697         /*if(!con_nickcompletion.integer)
2698           return; is tested in Nicks_CompletionCountPossible */
2699         n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2700         if(n == 1)
2701         {
2702                 size_t len;
2703                 char *msg;
2704
2705                 msg = Nicks_list[0];
2706                 len = min(size - Nicks_matchpos - 3, strlen(msg));
2707                 memcpy(&buffer[Nicks_matchpos], msg, len);
2708                 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2709                         len = (int)Nicks_AddLastColor(buffer, Nicks_matchpos+(int)len);
2710                 buffer[len++] = ' ';
2711                 buffer[len] = 0;
2712                 return (int)len;
2713         } else if(n > 1)
2714         {
2715                 int len;
2716                 char *msg;
2717                 Con_Printf("\n%i possible nicks:\n", n);
2718                 Cmd_CompleteNicksPrint(n);
2719
2720                 Nicks_CutMatches(n);
2721
2722                 msg = Nicks_sanlist[0];
2723                 len = (int)min(size - Nicks_matchpos, strlen(msg));
2724                 memcpy(&buffer[Nicks_matchpos], msg, len);
2725                 buffer[Nicks_matchpos + len] = 0;
2726                 //pos += len;
2727                 return Nicks_matchpos + len;
2728         }
2729         return pos;
2730 }
2731
2732
2733 /*
2734         Con_CompleteCommandLine
2735
2736         New function for tab-completion system
2737         Added by EvilTypeGuy
2738         Thanks to Fett erich@heintz.com
2739         Thanks to taniwha
2740         Enhanced to tab-complete map names by [515]
2741
2742 */
2743 void Con_CompleteCommandLine (cmd_state_t *cmd)
2744 {
2745         const char *text = "";
2746         char *s;
2747         const char **list[4] = {0, 0, 0, 0};
2748         char s2[512];
2749         char command[512];
2750         int c, v, a, i, cmd_len, pos, k;
2751         int n; // nicks --blub
2752         const char *space, *patterns;
2753         char vabuf[1024];
2754
2755         //find what we want to complete
2756         pos = key_linepos;
2757         while(--pos)
2758         {
2759                 k = key_line[pos];
2760                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2761                         break;
2762         }
2763         pos++;
2764
2765         s = key_line + pos;
2766         strlcpy(s2, key_line + key_linepos, sizeof(s2));        //save chars after cursor
2767         key_line[key_linepos] = 0;                                      //hide them
2768
2769         space = strchr(key_line + 1, ' ');
2770         if(space && pos == (space - key_line) + 1)
2771         {
2772                 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2773
2774                 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?
2775                 if(patterns && !*patterns)
2776                         patterns = NULL; // get rid of the empty string
2777
2778                 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2779                 {
2780                         //maps search
2781                         char t[MAX_QPATH];
2782                         if (GetMapList(s, t, sizeof(t)))
2783                         {
2784                                 // first move the cursor
2785                                 key_linepos += (int)strlen(t) - (int)strlen(s);
2786
2787                                 // and now do the actual work
2788                                 *s = 0;
2789                                 strlcat(key_line, t, MAX_INPUTLINE);
2790                                 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2791
2792                                 // and fix the cursor
2793                                 if(key_linepos > (int) strlen(key_line))
2794                                         key_linepos = (int) strlen(key_line);
2795                         }
2796                         return;
2797                 }
2798                 else
2799                 {
2800                         if(patterns)
2801                         {
2802                                 char t[MAX_QPATH];
2803                                 stringlist_t resultbuf, dirbuf;
2804
2805                                 // Usage:
2806                                 //   // store completion patterns (space separated) for command foo in con_completion_foo
2807                                 //   set con_completion_foo "foodata/*.foodefault *.foo"
2808                                 //   foo <TAB>
2809                                 //
2810                                 // Note: patterns with slash are always treated as absolute
2811                                 // patterns; patterns without slash search in the innermost
2812                                 // directory the user specified. There is no way to "complete into"
2813                                 // a directory as of now, as directories seem to be unknown to the
2814                                 // FS subsystem.
2815                                 //
2816                                 // Examples:
2817                                 //   set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2818                                 //   set con_completion_playdemo "*.dem"
2819                                 //   set con_completion_play "*.wav *.ogg"
2820                                 //
2821                                 // TODO somehow add support for directories; these shall complete
2822                                 // to their name + an appended slash.
2823
2824                                 stringlistinit(&resultbuf);
2825                                 stringlistinit(&dirbuf);
2826                                 while(COM_ParseToken_Simple(&patterns, false, false, true))
2827                                 {
2828                                         fssearch_t *search;
2829                                         if(strchr(com_token, '/'))
2830                                         {
2831                                                 search = FS_Search(com_token, true, true);
2832                                         }
2833                                         else
2834                                         {
2835                                                 const char *slash = strrchr(s, '/');
2836                                                 if(slash)
2837                                                 {
2838                                                         strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2839                                                         strlcat(t, com_token, sizeof(t));
2840                                                         search = FS_Search(t, true, true);
2841                                                 }
2842                                                 else
2843                                                         search = FS_Search(com_token, true, true);
2844                                         }
2845                                         if(search)
2846                                         {
2847                                                 for(i = 0; i < search->numfilenames; ++i)
2848                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2849                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2850                                                                         stringlistappend(&resultbuf, search->filenames[i]);
2851                                                 FS_FreeSearch(search);
2852                                         }
2853                                 }
2854
2855                                 // In any case, add directory names
2856                                 {
2857                                         fssearch_t *search;
2858                                         const char *slash = strrchr(s, '/');
2859                                         if(slash)
2860                                         {
2861                                                 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2862                                                 strlcat(t, "*", sizeof(t));
2863                                                 search = FS_Search(t, true, true);
2864                                         }
2865                                         else
2866                                                 search = FS_Search("*", true, true);
2867                                         if(search)
2868                                         {
2869                                                 for(i = 0; i < search->numfilenames; ++i)
2870                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2871                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2872                                                                         stringlistappend(&dirbuf, search->filenames[i]);
2873                                                 FS_FreeSearch(search);
2874                                         }
2875                                 }
2876
2877                                 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2878                                 {
2879                                         const char *p, *q;
2880                                         unsigned int matchchars;
2881                                         if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2882                                         {
2883                                                 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2884                                         }
2885                                         else
2886                                         if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2887                                         {
2888                                                 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2889                                         }
2890                                         else
2891                                         {
2892                                                 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2893                                                 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2894                                                 for(i = 0; i < dirbuf.numstrings; ++i)
2895                                                 {
2896                                                         Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2897                                                 }
2898                                                 for(i = 0; i < resultbuf.numstrings; ++i)
2899                                                 {
2900                                                         Con_Printf("%s\n", resultbuf.strings[i]);
2901                                                 }
2902                                                 matchchars = sizeof(t) - 1;
2903                                                 if(resultbuf.numstrings > 0)
2904                                                 {
2905                                                         p = resultbuf.strings[0];
2906                                                         q = resultbuf.strings[resultbuf.numstrings - 1];
2907                                                         for(; *p && *p == *q; ++p, ++q);
2908                                                         matchchars = (unsigned int)(p - resultbuf.strings[0]);
2909                                                 }
2910                                                 if(dirbuf.numstrings > 0)
2911                                                 {
2912                                                         p = dirbuf.strings[0];
2913                                                         q = dirbuf.strings[dirbuf.numstrings - 1];
2914                                                         for(; *p && *p == *q; ++p, ++q);
2915                                                         matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2916                                                 }
2917                                                 // now p points to the first non-equal character, or to the end
2918                                                 // of resultbuf.strings[0]. We want to append the characters
2919                                                 // from resultbuf.strings[0] to (not including) p as these are
2920                                                 // the unique prefix
2921                                                 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2922                                         }
2923
2924                                         // first move the cursor
2925                                         key_linepos += (int)strlen(t) - (int)strlen(s);
2926
2927                                         // and now do the actual work
2928                                         *s = 0;
2929                                         strlcat(key_line, t, MAX_INPUTLINE);
2930                                         strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2931
2932                                         // and fix the cursor
2933                                         if(key_linepos > (int) strlen(key_line))
2934                                                 key_linepos = (int) strlen(key_line);
2935                                 }
2936                                 stringlistfreecontents(&resultbuf);
2937                                 stringlistfreecontents(&dirbuf);
2938
2939                                 return; // bail out, when we complete for a command that wants a file name
2940                         }
2941                 }
2942         }
2943
2944         // Count number of possible matches and print them
2945         c = Cmd_CompleteCountPossible(cmd, s);
2946         if (c)
2947         {
2948                 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2949                 Cmd_CompleteCommandPrint(cmd, s);
2950         }
2951         v = Cvar_CompleteCountPossible(cmd->cvars, s, CVAR_CLIENT | CVAR_SERVER);
2952         if (v)
2953         {
2954                 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2955                 Cvar_CompleteCvarPrint(cmd->cvars, s, CVAR_CLIENT | CVAR_SERVER);
2956         }
2957         a = Cmd_CompleteAliasCountPossible(cmd, s);
2958         if (a)
2959         {
2960                 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2961                 Cmd_CompleteAliasPrint(cmd, s);
2962         }
2963         n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2964         if (n)
2965         {
2966                 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2967                 Cmd_CompleteNicksPrint(n);
2968         }
2969
2970         if (!(c + v + a + n))   // No possible matches
2971         {
2972                 if(s2[0])
2973                         strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2974                 return;
2975         }
2976
2977         if (c)
2978                 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
2979         if (v)
2980                 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
2981         if (a)
2982                 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
2983         if (n)
2984                 text = *(list[3] = Nicks_CompleteBuildList(n));
2985
2986         for (cmd_len = (int)strlen(s);;cmd_len++)
2987         {
2988                 const char **l;
2989                 for (i = 0; i < 3; i++)
2990                         if (list[i])
2991                                 for (l = list[i];*l;l++)
2992                                         if ((*l)[cmd_len] != text[cmd_len])
2993                                                 goto done;
2994                 // all possible matches share this character, so we continue...
2995                 if (!text[cmd_len])
2996                 {
2997                         // if all matches ended at the same position, stop
2998                         // (this means there is only one match)
2999                         break;
3000                 }
3001         }
3002 done:
3003
3004         // prevent a buffer overrun by limiting cmd_len according to remaining space
3005         cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
3006         if (text)
3007         {
3008                 key_linepos = pos;
3009                 memcpy(&key_line[key_linepos], text, cmd_len);
3010                 key_linepos += cmd_len;
3011                 // if there is only one match, add a space after it
3012                 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
3013                 {
3014                         if(n)
3015                         { // was a nick, might have an offset, and needs colors ;) --blub
3016                                 key_linepos = pos - Nicks_offset[0];
3017                                 cmd_len = (int)strlen(Nicks_list[0]);
3018                                 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
3019
3020                                 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
3021                                 key_linepos += cmd_len;
3022                                 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
3023                                         key_linepos = Nicks_AddLastColor(key_line, key_linepos);
3024                         }
3025                         key_line[key_linepos++] = ' ';
3026                 }
3027         }
3028
3029         // use strlcat to avoid a buffer overrun
3030         key_line[key_linepos] = 0;
3031         strlcat(key_line, s2, sizeof(key_line));
3032
3033         // free the command, cvar, and alias lists
3034         for (i = 0; i < 4; i++)
3035                 if (list[i])
3036                         Mem_Free((void *)list[i]);
3037 }
3038