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