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