]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - console.c
5f14b83f1f7aed5dc5cadec72c48361fabc8ffcc
[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         unsigned int conbackflags;
1960
1961         if (lines <= 0)
1962                 return;
1963
1964         if (con_mutex) Thread_LockMutex(con_mutex);
1965
1966         if (con_backscroll < 0)
1967                 con_backscroll = 0;
1968
1969         con_vislines = lines;
1970
1971         r_draw2d_force = true;
1972
1973 // draw the background
1974         alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
1975         if((alpha = alpha0 * scr_conalphafactor.value) > 0)
1976         {
1977                 sx = scr_conscroll_x.value;
1978                 sy = scr_conscroll_y.value;
1979                 conbackflags = CACHEPICFLAG_FAILONMISSING; // So console is readable when game content is missing
1980                 if (sx != 0 || sy != 0)
1981                         conbackflags &= CACHEPICFLAG_NOCLAMP;
1982                 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", conbackflags) : NULL;
1983                 sx *= realtime; sy *= realtime;
1984                 sx -= floor(sx); sy -= floor(sy);
1985                 if (Draw_IsPicLoaded(conbackpic))
1986                         DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1987                                         0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1988                                         1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1989                                         0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1990                                         1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1991                                         0);
1992                 else
1993                         DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
1994         }
1995         if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
1996         {
1997                 sx = scr_conscroll2_x.value;
1998                 sy = scr_conscroll2_y.value;
1999                 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2000                 sx *= realtime; sy *= realtime;
2001                 sx -= floor(sx); sy -= floor(sy);
2002                 if(Draw_IsPicLoaded(conbackpic))
2003                         DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2004                                         0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2005                                         1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2006                                         0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2007                                         1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2008                                         0);
2009         }
2010         if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
2011         {
2012                 sx = scr_conscroll3_x.value;
2013                 sy = scr_conscroll3_y.value;
2014                 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2015                 sx *= realtime; sy *= realtime;
2016                 sx -= floor(sx); sy -= floor(sy);
2017                 if(Draw_IsPicLoaded(conbackpic))
2018                         DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2019                                         0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2020                                         1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2021                                         0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2022                                         1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2023                                         0);
2024         }
2025         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);
2026
2027 // draw the text
2028 #if 0
2029         {
2030                 int i;
2031                 int count = CON_LINES_COUNT;
2032                 float ymax = con_vislines - 2 * con_textsize.value;
2033                 float y = ymax + con_textsize.value * con_backscroll;
2034                 for (i = 0;i < count && y >= 0;i++)
2035                         y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
2036                 // fix any excessive scrollback for the next frame
2037                 if (i >= count && y >= 0)
2038                 {
2039                         con_backscroll -= (int)(y / con_textsize.value);
2040                         if (con_backscroll < 0)
2041                                 con_backscroll = 0;
2042                 }
2043         }
2044 #else
2045         if(CON_LINES_COUNT > 0)
2046         {
2047                 int i, last, limitlast;
2048                 float y;
2049                 float ymax = con_vislines - 2 * con_textsize.value;
2050                 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2051                 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2052                 y = ymax - con_textsize.value;
2053
2054                 if(limitlast)
2055                         y += (CON_LINES(last).height - limitlast) * con_textsize.value;
2056                 i = last;
2057
2058                 for(;;)
2059                 {
2060                         y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
2061                         if(i == 0)
2062                                 break; // top of console buffer
2063                         if(y < 0)
2064                                 break; // top of console window
2065                         limitlast = 0;
2066                         --i;
2067                 }
2068         }
2069 #endif
2070
2071 // draw the input prompt, user text, and cursor if desired
2072         Con_DrawInput ();
2073
2074         r_draw2d_force = false;
2075         if (con_mutex) Thread_UnlockMutex(con_mutex);
2076 }
2077
2078 /*
2079 GetMapList
2080
2081 Made by [515]
2082 Prints not only map filename, but also
2083 its format (q1/q2/q3/hl) and even its message
2084 */
2085 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2086 //LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2087 //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
2088 //LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2089 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
2090 {
2091         fssearch_t      *t;
2092         char            message[1024];
2093         int                     i, k, max, p, o, min;
2094         unsigned char *len;
2095         qfile_t         *f;
2096         unsigned char buf[1024];
2097
2098         dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2099         t = FS_Search(message, 1, true);
2100         if(!t)
2101                 return false;
2102         if (t->numfilenames > 1)
2103                 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2104         len = (unsigned char *)Z_Malloc(t->numfilenames);
2105         min = 666;
2106         for(max=i=0;i<t->numfilenames;i++)
2107         {
2108                 k = (int)strlen(t->filenames[i]);
2109                 k -= 9;
2110                 if(max < k)
2111                         max = k;
2112                 else
2113                 if(min > k)
2114                         min = k;
2115                 len[i] = k;
2116         }
2117         o = (int)strlen(s);
2118         for(i=0;i<t->numfilenames;i++)
2119         {
2120                 int lumpofs = 0, lumplen = 0;
2121                 char *entities = NULL;
2122                 const char *data = NULL;
2123                 char keyname[64];
2124                 char entfilename[MAX_QPATH];
2125                 char desc[64];
2126                 desc[0] = 0;
2127                 strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2128                 p = 0;
2129                 f = FS_OpenVirtualFile(t->filenames[i], true);
2130                 if(f)
2131                 {
2132                         strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2133                         memset(buf, 0, 1024);
2134                         FS_Read(f, buf, 1024);
2135                         if (!memcmp(buf, "IBSP", 4))
2136                         {
2137                                 p = LittleLong(((int *)buf)[1]);
2138                                 if (p == Q3BSPVERSION)
2139                                 {
2140                                         q3dheader_t *header = (q3dheader_t *)buf;
2141                                         lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2142                                         lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2143                                         dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2144                                 }
2145                                 else if (p == Q2BSPVERSION)
2146                                 {
2147                                         q2dheader_t *header = (q2dheader_t *)buf;
2148                                         lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2149                                         lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2150                                         dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2151                                 }
2152                                 else
2153                                         dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2154                         }
2155                         else if (BuffLittleLong(buf) == BSPVERSION)
2156                         {
2157                                 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2158                                 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2159                                 dpsnprintf(desc, sizeof(desc), "BSP29");
2160                         }
2161                         else if (BuffLittleLong(buf) == 30)
2162                         {
2163                                 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2164                                 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2165                                 dpsnprintf(desc, sizeof(desc), "BSPHL");
2166                         }
2167                         else if (!memcmp(buf, "BSP2", 4))
2168                         {
2169                                 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2170                                 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2171                                 dpsnprintf(desc, sizeof(desc), "BSP2");
2172                         }
2173                         else if (!memcmp(buf, "2PSB", 4))
2174                         {
2175                                 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2176                                 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2177                                 dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2178                         }
2179                         else
2180                         {
2181                                 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2182                         }
2183                         strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2184                         memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2185                         entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2186                         if (!entities && lumplen >= 10)
2187                         {
2188                                 FS_Seek(f, lumpofs, SEEK_SET);
2189                                 entities = (char *)Z_Malloc(lumplen + 1);
2190                                 FS_Read(f, entities, lumplen);
2191                         }
2192                         if (entities)
2193                         {
2194                                 // if there are entities to parse, a missing message key just
2195                                 // means there is no title, so clear the message string now
2196                                 message[0] = 0;
2197                                 data = entities;
2198                                 for (;;)
2199                                 {
2200                                         int l;
2201                                         if (!COM_ParseToken_Simple(&data, false, false, true))
2202                                                 break;
2203                                         if (com_token[0] == '{')
2204                                                 continue;
2205                                         if (com_token[0] == '}')
2206                                                 break;
2207                                         // skip leading whitespace
2208                                         for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2209                                         for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2210                                                 keyname[l] = com_token[k+l];
2211                                         keyname[l] = 0;
2212                                         if (!COM_ParseToken_Simple(&data, false, false, true))
2213                                                 break;
2214                                         if (developer_extra.integer)
2215                                                 Con_DPrintf("key: %s %s\n", keyname, com_token);
2216                                         if (!strcmp(keyname, "message"))
2217                                         {
2218                                                 // get the message contents
2219                                                 strlcpy(message, com_token, sizeof(message));
2220                                                 break;
2221                                         }
2222                                 }
2223                         }
2224                 }
2225                 if (entities)
2226                         Z_Free(entities);
2227                 if(f)
2228                         FS_Close(f);
2229                 *(t->filenames[i]+len[i]+5) = 0;
2230                 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2231         }
2232         Con_Print("\n");
2233         for(p=o;p<min;p++)
2234         {
2235                 k = *(t->filenames[0]+5+p);
2236                 if(k == 0)
2237                         goto endcomplete;
2238                 for(i=1;i<t->numfilenames;i++)
2239                         if(*(t->filenames[i]+5+p) != k)
2240                                 goto endcomplete;
2241         }
2242 endcomplete:
2243         if(p > o && completedname && completednamebufferlength > 0)
2244         {
2245                 memset(completedname, 0, completednamebufferlength);
2246                 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2247         }
2248         Z_Free(len);
2249         FS_FreeSearch(t);
2250         return p > o;
2251 }
2252
2253 /*
2254         Con_DisplayList
2255
2256         New function for tab-completion system
2257         Added by EvilTypeGuy
2258         MEGA Thanks to Taniwha
2259
2260 */
2261 void Con_DisplayList(const char **list)
2262 {
2263         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2264         const char **walk = list;
2265
2266         while (*walk) {
2267                 len = (int)strlen(*walk);
2268                 if (len > maxlen)
2269                         maxlen = len;
2270                 walk++;
2271         }
2272         maxlen += 1;
2273
2274         while (*list) {
2275                 len = (int)strlen(*list);
2276                 if (pos + maxlen >= width) {
2277                         Con_Print("\n");
2278                         pos = 0;
2279                 }
2280
2281                 Con_Print(*list);
2282                 for (i = 0; i < (maxlen - len); i++)
2283                         Con_Print(" ");
2284
2285                 pos += maxlen;
2286                 list++;
2287         }
2288
2289         if (pos)
2290                 Con_Print("\n\n");
2291 }
2292
2293
2294 // Now it becomes TRICKY :D --blub
2295 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];     // contains the nicks with colors and all that
2296 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];  // sanitized list for completion when there are other possible matches.
2297 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2298 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
2299 static int Nicks_matchpos;
2300
2301 // co against <<:BLASTER:>> is true!?
2302 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2303 {
2304         while(a_len)
2305         {
2306                 if(tolower(*a) == tolower(*b))
2307                 {
2308                         if(*a == 0)
2309                                 return 0;
2310                         --a_len;
2311                         ++a;
2312                         ++b;
2313                         continue;
2314                 }
2315                 if(!*a)
2316                         return -1;
2317                 if(!*b)
2318                         return 1;
2319                 if(*a == ' ')
2320                         return (*a < *b) ? -1 : 1;
2321                 if(*b == ' ')
2322                         ++b;
2323                 else
2324                         return (*a < *b) ? -1 : 1;
2325         }
2326         return 0;
2327 }
2328 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2329 {
2330         char space_char;
2331         if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2332         {
2333                 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2334                         return Nicks_strncasecmp_nospaces(a, b, a_len);
2335                 return strncasecmp(a, b, a_len);
2336         }
2337
2338         space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2339
2340         // ignore non alphanumerics of B
2341         // if A contains a non-alphanumeric, B must contain it as well though!
2342         while(a_len)
2343         {
2344                 qboolean alnum_a, alnum_b;
2345
2346                 if(tolower(*a) == tolower(*b))
2347                 {
2348                         if(*a == 0) // end of both strings, they're equal
2349                                 return 0;
2350                         --a_len;
2351                         ++a;
2352                         ++b;
2353                         continue;
2354                 }
2355                 // not equal, end of one string?
2356                 if(!*a)
2357                         return -1;
2358                 if(!*b)
2359                         return 1;
2360                 // ignore non alphanumerics
2361                 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2362                 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2363                 if(!alnum_a) // b must contain this
2364                         return (*a < *b) ? -1 : 1;
2365                 if(!alnum_b)
2366                         ++b;
2367                 // otherwise, both are alnum, they're just not equal, return the appropriate number
2368                 else
2369                         return (*a < *b) ? -1 : 1;
2370         }
2371         return 0;
2372 }
2373
2374
2375 /* Nicks_CompleteCountPossible
2376
2377    Count the number of possible nicks to complete
2378  */
2379 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2380 {
2381         char name[128];
2382         int i, p;
2383         int match;
2384         int spos;
2385         int count = 0;
2386
2387         if(!con_nickcompletion.integer)
2388                 return 0;
2389
2390         // changed that to 1
2391         if(!line[0])// || !line[1]) // we want at least... 2 written characters
2392                 return 0;
2393
2394         for(i = 0; i < cl.maxclients; ++i)
2395         {
2396                 p = i;
2397                 if(!cl.scores[p].name[0])
2398                         continue;
2399
2400                 SanitizeString(cl.scores[p].name, name);
2401                 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2402
2403                 if(!name[0])
2404                         continue;
2405
2406                 match = -1;
2407                 spos = pos - 1; // no need for a minimum of characters :)
2408
2409                 while(spos >= 0)
2410                 {
2411                         if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2412                         {
2413                                 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2414                                    !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2415                                 {
2416                                         --spos;
2417                                         continue;
2418                                 }
2419                         }
2420                         if(isCon && spos == 0)
2421                                 break;
2422                         if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2423                                 match = spos;
2424                         --spos;
2425                 }
2426                 if(match < 0)
2427                         continue;
2428                 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2429                 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2430
2431                 // the sanitized list
2432                 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2433                 if(!count)
2434                 {
2435                         Nicks_matchpos = match;
2436                 }
2437
2438                 Nicks_offset[count] = s - (&line[match]);
2439                 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2440
2441                 ++count;
2442         }
2443         return count;
2444 }
2445
2446 static void Cmd_CompleteNicksPrint(int count)
2447 {
2448         int i;
2449         for(i = 0; i < count; ++i)
2450                 Con_Printf("%s\n", Nicks_list[i]);
2451 }
2452
2453 static void Nicks_CutMatchesNormal(int count)
2454 {
2455         // cut match 0 down to the longest possible completion
2456         int i;
2457         unsigned int c, l;
2458         c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2459         for(i = 1; i < count; ++i)
2460         {
2461                 l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2462                 if(l < c)
2463                         c = l;
2464
2465                 for(l = 0; l <= c; ++l)
2466                         if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2467                         {
2468                                 c = l-1;
2469                                 break;
2470                         }
2471         }
2472         Nicks_sanlist[0][c+1] = 0;
2473         //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2474 }
2475
2476 static unsigned int Nicks_strcleanlen(const char *s)
2477 {
2478         unsigned int l = 0;
2479         while(*s)
2480         {
2481                 if( (*s >= 'a' && *s <= 'z') ||
2482                     (*s >= 'A' && *s <= 'Z') ||
2483                     (*s >= '0' && *s <= '9') ||
2484                     *s == ' ')
2485                         ++l;
2486                 ++s;
2487         }
2488         return l;
2489 }
2490
2491 static void Nicks_CutMatchesAlphaNumeric(int count)
2492 {
2493         // cut match 0 down to the longest possible completion
2494         int i;
2495         unsigned int c, l;
2496         char tempstr[sizeof(Nicks_sanlist[0])];
2497         char *a, *b;
2498         char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2499
2500         c = (unsigned int)strlen(Nicks_sanlist[0]);
2501         for(i = 0, l = 0; i < (int)c; ++i)
2502         {
2503                 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2504                     (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2505                     (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2506                 {
2507                         tempstr[l++] = Nicks_sanlist[0][i];
2508                 }
2509         }
2510         tempstr[l] = 0;
2511
2512         for(i = 1; i < count; ++i)
2513         {
2514                 a = tempstr;
2515                 b = Nicks_sanlist[i];
2516                 while(1)
2517                 {
2518                         if(!*a)
2519                                 break;
2520                         if(!*b)
2521                         {
2522                                 *a = 0;
2523                                 break;
2524                         }
2525                         if(tolower(*a) == tolower(*b))
2526                         {
2527                                 ++a;
2528                                 ++b;
2529                                 continue;
2530                         }
2531                         if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2532                         {
2533                                 // b is alnum, so cut
2534                                 *a = 0;
2535                                 break;
2536                         }
2537                         ++b;
2538                 }
2539         }
2540         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2541         Nicks_CutMatchesNormal(count);
2542         //if(!Nicks_sanlist[0][0])
2543         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2544         {
2545                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2546                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2547         }
2548 }
2549
2550 static void Nicks_CutMatchesNoSpaces(int count)
2551 {
2552         // cut match 0 down to the longest possible completion
2553         int i;
2554         unsigned int c, l;
2555         char tempstr[sizeof(Nicks_sanlist[0])];
2556         char *a, *b;
2557
2558         c = (unsigned int)strlen(Nicks_sanlist[0]);
2559         for(i = 0, l = 0; i < (int)c; ++i)
2560         {
2561                 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2562                 {
2563                         tempstr[l++] = Nicks_sanlist[0][i];
2564                 }
2565         }
2566         tempstr[l] = 0;
2567
2568         for(i = 1; i < count; ++i)
2569         {
2570                 a = tempstr;
2571                 b = Nicks_sanlist[i];
2572                 while(1)
2573                 {
2574                         if(!*a)
2575                                 break;
2576                         if(!*b)
2577                         {
2578                                 *a = 0;
2579                                 break;
2580                         }
2581                         if(tolower(*a) == tolower(*b))
2582                         {
2583                                 ++a;
2584                                 ++b;
2585                                 continue;
2586                         }
2587                         if(*b != ' ')
2588                         {
2589                                 *a = 0;
2590                                 break;
2591                         }
2592                         ++b;
2593                 }
2594         }
2595         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2596         Nicks_CutMatchesNormal(count);
2597         //if(!Nicks_sanlist[0][0])
2598         //Con_Printf("TS: %s\n", tempstr);
2599         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2600         {
2601                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2602                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2603         }
2604 }
2605
2606 static void Nicks_CutMatches(int count)
2607 {
2608         if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2609                 Nicks_CutMatchesAlphaNumeric(count);
2610         else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2611                 Nicks_CutMatchesNoSpaces(count);
2612         else
2613                 Nicks_CutMatchesNormal(count);
2614 }
2615
2616 static const char **Nicks_CompleteBuildList(int count)
2617 {
2618         const char **buf;
2619         int bpos = 0;
2620         // the list is freed by Con_CompleteCommandLine, so create a char**
2621         buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2622
2623         for(; bpos < count; ++bpos)
2624                 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2625
2626         Nicks_CutMatches(count);
2627
2628         buf[bpos] = NULL;
2629         return buf;
2630 }
2631
2632 /*
2633         Nicks_AddLastColor
2634         Restores the previous used color, after the autocompleted name.
2635 */
2636 static int Nicks_AddLastColor(char *buffer, int pos)
2637 {
2638         qboolean quote_added = false;
2639         int match;
2640         int color = STRING_COLOR_DEFAULT + '0';
2641         char r = 0, g = 0, b = 0;
2642
2643         if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2644         {
2645                 // we'll have to add a quote :)
2646                 buffer[pos++] = '\"';
2647                 quote_added = true;
2648         }
2649
2650         if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2651         {
2652                 // add color when no quote was added, or when flags &4?
2653                 // find last color
2654                 for(match = Nicks_matchpos-1; match >= 0; --match)
2655                 {
2656                         if(buffer[match] == STRING_COLOR_TAG)
2657                         {
2658                                 if( isdigit(buffer[match+1]) )
2659                                 {
2660                                         color = buffer[match+1];
2661                                         break;
2662                                 }
2663                                 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2664                                 {
2665                                         if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2666                                         {
2667                                                 r = buffer[match+2];
2668                                                 g = buffer[match+3];
2669                                                 b = buffer[match+4];
2670                                                 color = -1;
2671                                                 break;
2672                                         }
2673                                 }
2674                         }
2675                 }
2676                 if(!quote_added)
2677                 {
2678                         if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2679                                 pos -= 2;
2680                         else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2681                                          && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2682                                 pos -= 5;
2683                 }
2684                 buffer[pos++] = STRING_COLOR_TAG;
2685                 if (color == -1)
2686                 {
2687                         buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2688                         buffer[pos++] = r;
2689                         buffer[pos++] = g;
2690                         buffer[pos++] = b;
2691                 }
2692                 else
2693                         buffer[pos++] = color;
2694         }
2695         return pos;
2696 }
2697
2698 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2699 {
2700         int n;
2701         /*if(!con_nickcompletion.integer)
2702           return; is tested in Nicks_CompletionCountPossible */
2703         n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2704         if(n == 1)
2705         {
2706                 size_t len;
2707                 char *msg;
2708
2709                 msg = Nicks_list[0];
2710                 len = min(size - Nicks_matchpos - 3, strlen(msg));
2711                 memcpy(&buffer[Nicks_matchpos], msg, len);
2712                 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2713                         len = (int)Nicks_AddLastColor(buffer, Nicks_matchpos+(int)len);
2714                 buffer[len++] = ' ';
2715                 buffer[len] = 0;
2716                 return (int)len;
2717         } else if(n > 1)
2718         {
2719                 int len;
2720                 char *msg;
2721                 Con_Printf("\n%i possible nicks:\n", n);
2722                 Cmd_CompleteNicksPrint(n);
2723
2724                 Nicks_CutMatches(n);
2725
2726                 msg = Nicks_sanlist[0];
2727                 len = (int)min(size - Nicks_matchpos, strlen(msg));
2728                 memcpy(&buffer[Nicks_matchpos], msg, len);
2729                 buffer[Nicks_matchpos + len] = 0;
2730                 //pos += len;
2731                 return Nicks_matchpos + len;
2732         }
2733         return pos;
2734 }
2735
2736
2737 /*
2738         Con_CompleteCommandLine
2739
2740         New function for tab-completion system
2741         Added by EvilTypeGuy
2742         Thanks to Fett erich@heintz.com
2743         Thanks to taniwha
2744         Enhanced to tab-complete map names by [515]
2745
2746 */
2747 void Con_CompleteCommandLine (cmd_state_t *cmd)
2748 {
2749         const char *text = "";
2750         char *s;
2751         const char **list[4] = {0, 0, 0, 0};
2752         char s2[512];
2753         char command[512];
2754         int c, v, a, i, cmd_len, pos, k;
2755         int n; // nicks --blub
2756         const char *space, *patterns;
2757         char vabuf[1024];
2758
2759         //find what we want to complete
2760         pos = key_linepos;
2761         while(--pos)
2762         {
2763                 k = key_line[pos];
2764                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2765                         break;
2766         }
2767         pos++;
2768
2769         s = key_line + pos;
2770         strlcpy(s2, key_line + key_linepos, sizeof(s2));        //save chars after cursor
2771         key_line[key_linepos] = 0;                                      //hide them
2772
2773         space = strchr(key_line + 1, ' ');
2774         if(space && pos == (space - key_line) + 1)
2775         {
2776                 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2777
2778                 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?
2779                 if(patterns && !*patterns)
2780                         patterns = NULL; // get rid of the empty string
2781
2782                 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2783                 {
2784                         //maps search
2785                         char t[MAX_QPATH];
2786                         if (GetMapList(s, t, sizeof(t)))
2787                         {
2788                                 // first move the cursor
2789                                 key_linepos += (int)strlen(t) - (int)strlen(s);
2790
2791                                 // and now do the actual work
2792                                 *s = 0;
2793                                 strlcat(key_line, t, MAX_INPUTLINE);
2794                                 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2795
2796                                 // and fix the cursor
2797                                 if(key_linepos > (int) strlen(key_line))
2798                                         key_linepos = (int) strlen(key_line);
2799                         }
2800                         return;
2801                 }
2802                 else
2803                 {
2804                         if(patterns)
2805                         {
2806                                 char t[MAX_QPATH];
2807                                 stringlist_t resultbuf, dirbuf;
2808
2809                                 // Usage:
2810                                 //   // store completion patterns (space separated) for command foo in con_completion_foo
2811                                 //   set con_completion_foo "foodata/*.foodefault *.foo"
2812                                 //   foo <TAB>
2813                                 //
2814                                 // Note: patterns with slash are always treated as absolute
2815                                 // patterns; patterns without slash search in the innermost
2816                                 // directory the user specified. There is no way to "complete into"
2817                                 // a directory as of now, as directories seem to be unknown to the
2818                                 // FS subsystem.
2819                                 //
2820                                 // Examples:
2821                                 //   set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2822                                 //   set con_completion_playdemo "*.dem"
2823                                 //   set con_completion_play "*.wav *.ogg"
2824                                 //
2825                                 // TODO somehow add support for directories; these shall complete
2826                                 // to their name + an appended slash.
2827
2828                                 stringlistinit(&resultbuf);
2829                                 stringlistinit(&dirbuf);
2830                                 while(COM_ParseToken_Simple(&patterns, false, false, true))
2831                                 {
2832                                         fssearch_t *search;
2833                                         if(strchr(com_token, '/'))
2834                                         {
2835                                                 search = FS_Search(com_token, true, true);
2836                                         }
2837                                         else
2838                                         {
2839                                                 const char *slash = strrchr(s, '/');
2840                                                 if(slash)
2841                                                 {
2842                                                         strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2843                                                         strlcat(t, com_token, sizeof(t));
2844                                                         search = FS_Search(t, true, true);
2845                                                 }
2846                                                 else
2847                                                         search = FS_Search(com_token, true, true);
2848                                         }
2849                                         if(search)
2850                                         {
2851                                                 for(i = 0; i < search->numfilenames; ++i)
2852                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2853                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2854                                                                         stringlistappend(&resultbuf, search->filenames[i]);
2855                                                 FS_FreeSearch(search);
2856                                         }
2857                                 }
2858
2859                                 // In any case, add directory names
2860                                 {
2861                                         fssearch_t *search;
2862                                         const char *slash = strrchr(s, '/');
2863                                         if(slash)
2864                                         {
2865                                                 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2866                                                 strlcat(t, "*", sizeof(t));
2867                                                 search = FS_Search(t, true, true);
2868                                         }
2869                                         else
2870                                                 search = FS_Search("*", true, true);
2871                                         if(search)
2872                                         {
2873                                                 for(i = 0; i < search->numfilenames; ++i)
2874                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2875                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2876                                                                         stringlistappend(&dirbuf, search->filenames[i]);
2877                                                 FS_FreeSearch(search);
2878                                         }
2879                                 }
2880
2881                                 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2882                                 {
2883                                         const char *p, *q;
2884                                         unsigned int matchchars;
2885                                         if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2886                                         {
2887                                                 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2888                                         }
2889                                         else
2890                                         if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2891                                         {
2892                                                 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2893                                         }
2894                                         else
2895                                         {
2896                                                 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2897                                                 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2898                                                 for(i = 0; i < dirbuf.numstrings; ++i)
2899                                                 {
2900                                                         Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2901                                                 }
2902                                                 for(i = 0; i < resultbuf.numstrings; ++i)
2903                                                 {
2904                                                         Con_Printf("%s\n", resultbuf.strings[i]);
2905                                                 }
2906                                                 matchchars = sizeof(t) - 1;
2907                                                 if(resultbuf.numstrings > 0)
2908                                                 {
2909                                                         p = resultbuf.strings[0];
2910                                                         q = resultbuf.strings[resultbuf.numstrings - 1];
2911                                                         for(; *p && *p == *q; ++p, ++q);
2912                                                         matchchars = (unsigned int)(p - resultbuf.strings[0]);
2913                                                 }
2914                                                 if(dirbuf.numstrings > 0)
2915                                                 {
2916                                                         p = dirbuf.strings[0];
2917                                                         q = dirbuf.strings[dirbuf.numstrings - 1];
2918                                                         for(; *p && *p == *q; ++p, ++q);
2919                                                         matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2920                                                 }
2921                                                 // now p points to the first non-equal character, or to the end
2922                                                 // of resultbuf.strings[0]. We want to append the characters
2923                                                 // from resultbuf.strings[0] to (not including) p as these are
2924                                                 // the unique prefix
2925                                                 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2926                                         }
2927
2928                                         // first move the cursor
2929                                         key_linepos += (int)strlen(t) - (int)strlen(s);
2930
2931                                         // and now do the actual work
2932                                         *s = 0;
2933                                         strlcat(key_line, t, MAX_INPUTLINE);
2934                                         strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2935
2936                                         // and fix the cursor
2937                                         if(key_linepos > (int) strlen(key_line))
2938                                                 key_linepos = (int) strlen(key_line);
2939                                 }
2940                                 stringlistfreecontents(&resultbuf);
2941                                 stringlistfreecontents(&dirbuf);
2942
2943                                 return; // bail out, when we complete for a command that wants a file name
2944                         }
2945                 }
2946         }
2947
2948         // Count number of possible matches and print them
2949         c = Cmd_CompleteCountPossible(cmd, s);
2950         if (c)
2951         {
2952                 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2953                 Cmd_CompleteCommandPrint(cmd, s);
2954         }
2955         v = Cvar_CompleteCountPossible(cmd->cvars, s, CVAR_CLIENT | CVAR_SERVER);
2956         if (v)
2957         {
2958                 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2959                 Cvar_CompleteCvarPrint(cmd->cvars, s, CVAR_CLIENT | CVAR_SERVER);
2960         }
2961         a = Cmd_CompleteAliasCountPossible(cmd, s);
2962         if (a)
2963         {
2964                 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2965                 Cmd_CompleteAliasPrint(cmd, s);
2966         }
2967         n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2968         if (n)
2969         {
2970                 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2971                 Cmd_CompleteNicksPrint(n);
2972         }
2973
2974         if (!(c + v + a + n))   // No possible matches
2975         {
2976                 if(s2[0])
2977                         strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2978                 return;
2979         }
2980
2981         if (c)
2982                 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
2983         if (v)
2984                 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
2985         if (a)
2986                 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
2987         if (n)
2988                 text = *(list[3] = Nicks_CompleteBuildList(n));
2989
2990         for (cmd_len = (int)strlen(s);;cmd_len++)
2991         {
2992                 const char **l;
2993                 for (i = 0; i < 3; i++)
2994                         if (list[i])
2995                                 for (l = list[i];*l;l++)
2996                                         if ((*l)[cmd_len] != text[cmd_len])
2997                                                 goto done;
2998                 // all possible matches share this character, so we continue...
2999                 if (!text[cmd_len])
3000                 {
3001                         // if all matches ended at the same position, stop
3002                         // (this means there is only one match)
3003                         break;
3004                 }
3005         }
3006 done:
3007
3008         // prevent a buffer overrun by limiting cmd_len according to remaining space
3009         cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
3010         if (text)
3011         {
3012                 key_linepos = pos;
3013                 memcpy(&key_line[key_linepos], text, cmd_len);
3014                 key_linepos += cmd_len;
3015                 // if there is only one match, add a space after it
3016                 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
3017                 {
3018                         if(n)
3019                         { // was a nick, might have an offset, and needs colors ;) --blub
3020                                 key_linepos = pos - Nicks_offset[0];
3021                                 cmd_len = (int)strlen(Nicks_list[0]);
3022                                 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
3023
3024                                 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
3025                                 key_linepos += cmd_len;
3026                                 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
3027                                         key_linepos = Nicks_AddLastColor(key_line, key_linepos);
3028                         }
3029                         key_line[key_linepos++] = ' ';
3030                 }
3031         }
3032
3033         // use strlcat to avoid a buffer overrun
3034         key_line[key_linepos] = 0;
3035         strlcat(key_line, s2, sizeof(key_line));
3036
3037         // free the command, cvar, and alias lists
3038         for (i = 0; i < 4; i++)
3039                 if (list[i])
3040                         Mem_Free((void *)list[i]);
3041 }
3042