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