]> git.xonotic.org Git - xonotic/darkplaces.git/blob - console.c
cl_main: Fix double free of meshentities texture pool when running gamedir
[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         // initialize console window (only used by sys_win.c)
935         Sys_InitConsole();
936         
937         Con_Print("Console initialized.\n");
938 }
939
940 void Con_Shutdown (void)
941 {
942         if (con_mutex) Thread_LockMutex(con_mutex);
943         ConBuffer_Shutdown(&con);
944         if (con_mutex) Thread_UnlockMutex(con_mutex);
945         if (con_mutex) Thread_DestroyMutex(con_mutex);con_mutex = NULL;
946 }
947
948 /*
949 ================
950 Con_PrintToHistory
951
952 Handles cursor positioning, line wrapping, etc
953 All console printing must go through this in order to be displayed
954 If no console is visible, the notify window will pop up.
955 ================
956 */
957 static void Con_PrintToHistory(const char *txt, int mask)
958 {
959         // process:
960         //   \n goes to next line
961         //   \r deletes current line and makes a new one
962
963         static int cr_pending = 0;
964         static char buf[CON_TEXTSIZE]; // con_mutex
965         static int bufpos = 0;
966
967         if(!con.text) // FIXME uses a non-abstracted property of con
968                 return;
969
970         for(; *txt; ++txt)
971         {
972                 if(cr_pending)
973                 {
974                         ConBuffer_DeleteLastLine(&con);
975                         cr_pending = 0;
976                 }
977                 switch(*txt)
978                 {
979                         case 0:
980                                 break;
981                         case '\r':
982                                 ConBuffer_AddLine(&con, buf, bufpos, mask);
983                                 bufpos = 0;
984                                 cr_pending = 1;
985                                 break;
986                         case '\n':
987                                 ConBuffer_AddLine(&con, buf, bufpos, mask);
988                                 bufpos = 0;
989                                 break;
990                         default:
991                                 buf[bufpos++] = *txt;
992                                 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
993                                 {
994                                         ConBuffer_AddLine(&con, buf, bufpos, mask);
995                                         bufpos = 0;
996                                 }
997                                 break;
998                 }
999         }
1000 }
1001
1002 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qbool proquakeprotocol)
1003 {
1004         rcon_redirect_sock = sock;
1005         rcon_redirect_dest = dest;
1006         rcon_redirect_proquakeprotocol = proquakeprotocol;
1007         if (rcon_redirect_proquakeprotocol)
1008         {
1009                 // reserve space for the packet header
1010                 rcon_redirect_buffer[0] = 0;
1011                 rcon_redirect_buffer[1] = 0;
1012                 rcon_redirect_buffer[2] = 0;
1013                 rcon_redirect_buffer[3] = 0;
1014                 // this is a reply to a CCREQ_RCON
1015                 rcon_redirect_buffer[4] = (unsigned char)CCREP_RCON;
1016         }
1017         else
1018                 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1019         rcon_redirect_bufferpos = 5;
1020 }
1021
1022 static void Con_Rcon_Redirect_Flush(void)
1023 {
1024         if(rcon_redirect_sock)
1025         {
1026                 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
1027                 if (rcon_redirect_proquakeprotocol)
1028                 {
1029                         // update the length in the packet header
1030                         StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
1031                 }
1032                 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
1033         }
1034         memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1035         rcon_redirect_bufferpos = 5;
1036         rcon_redirect_proquakeprotocol = false;
1037 }
1038
1039 void Con_Rcon_Redirect_End(void)
1040 {
1041         Con_Rcon_Redirect_Flush();
1042         rcon_redirect_dest = NULL;
1043         rcon_redirect_sock = NULL;
1044 }
1045
1046 void Con_Rcon_Redirect_Abort(void)
1047 {
1048         rcon_redirect_dest = NULL;
1049         rcon_redirect_sock = NULL;
1050 }
1051
1052 /*
1053 ================
1054 Con_Rcon_AddChar
1055 ================
1056 */
1057 /// Adds a character to the rcon buffer.
1058 static void Con_Rcon_AddChar(int c)
1059 {
1060         if(log_dest_buffer_appending)
1061                 return;
1062         ++log_dest_buffer_appending;
1063
1064         // if this print is in response to an rcon command, add the character
1065         // to the rcon redirect buffer
1066
1067         if (rcon_redirect_dest)
1068         {
1069                 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
1070                 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
1071                         Con_Rcon_Redirect_Flush();
1072         }
1073         else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
1074         {
1075                 if(log_dest_buffer_pos == 0)
1076                         Log_DestBuffer_Init();
1077                 log_dest_buffer[log_dest_buffer_pos++] = c;
1078                 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
1079                         Log_DestBuffer_Flush_NoLock();
1080         }
1081         else
1082                 log_dest_buffer_pos = 0;
1083
1084         --log_dest_buffer_appending;
1085 }
1086
1087 /**
1088  * Convert an RGB color to its nearest quake color.
1089  * I'll cheat on this a bit by translating the colors to HSV first,
1090  * S and V decide if it's black or white, otherwise, H will decide the
1091  * actual color.
1092  * @param _r Red (0-255)
1093  * @param _g Green (0-255)
1094  * @param _b Blue (0-255)
1095  * @return A quake color character.
1096  */
1097 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
1098 {
1099         float r = ((float)_r)/255.0;
1100         float g = ((float)_g)/255.0;
1101         float b = ((float)_b)/255.0;
1102         float min = min(r, min(g, b));
1103         float max = max(r, max(g, b));
1104
1105         int h; ///< Hue angle [0,360]
1106         float s; ///< Saturation [0,1]
1107         float v = max; ///< In HSV v == max [0,1]
1108
1109         if(max == min)
1110                 s = 0;
1111         else
1112                 s = 1.0 - (min/max);
1113
1114         // Saturation threshold. We now say 0.2 is the minimum value for a color!
1115         if(s < 0.2)
1116         {
1117                 // If the value is less than half, return a black color code.
1118                 // Otherwise return a white one.
1119                 if(v < 0.5)
1120                         return '0';
1121                 return '7';
1122         }
1123
1124         // Let's get the hue angle to define some colors:
1125         if(max == min)
1126                 h = 0;
1127         else if(max == r)
1128                 h = (int)(60.0 * (g-b)/(max-min))%360;
1129         else if(max == g)
1130                 h = (int)(60.0 * (b-r)/(max-min) + 120);
1131         else // if(max == b) redundant check
1132                 h = (int)(60.0 * (r-g)/(max-min) + 240);
1133
1134         if(h < 36) // *red* to orange
1135                 return '1';
1136         else if(h < 80) // orange over *yellow* to evilish-bright-green
1137                 return '3';
1138         else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1139                 return '2';
1140         else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1141                 return '5';
1142         else if(h < 270) // darkish blue over *dark blue* to cool purple
1143                 return '4';
1144         else if(h < 330) // cool purple over *purple* to ugly swiny red
1145                 return '6';
1146         else // ugly red to red closes the circly
1147                 return '1';
1148 }
1149
1150 /*
1151 ================
1152 Con_MaskPrint
1153 ================
1154 */
1155 extern cvar_t timestamps;
1156 extern cvar_t timeformat;
1157 extern qbool sys_nostdout;
1158 void Con_MaskPrint(int additionalmask, const char *msg)
1159 {
1160         static int mask = 0;
1161         static int index = 0;
1162         static char line[MAX_INPUTLINE];
1163
1164         if (con_mutex)
1165                 Thread_LockMutex(con_mutex);
1166
1167         for (;*msg;msg++)
1168         {
1169                 Con_Rcon_AddChar(*msg);
1170                 // if this is the beginning of a new line, print timestamp
1171                 if (index == 0)
1172                 {
1173                         const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1174                         // reset the color
1175                         // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1176                         line[index++] = STRING_COLOR_TAG;
1177                         // assert( STRING_COLOR_DEFAULT < 10 )
1178                         line[index++] = STRING_COLOR_DEFAULT + '0';
1179                         // special color codes for chat messages must always come first
1180                         // for Con_PrintToHistory to work properly
1181                         if (*msg == 1 || *msg == 2 || *msg == 3)
1182                         {
1183                                 // play talk wav
1184                                 if (*msg == 1)
1185                                 {
1186                                         if (con_chatsound.value)
1187                                         {
1188                                                 if(msg[1] == con_chatsound_team_mask.integer && cl.foundteamchatsound)
1189                                                         S_LocalSound (con_chatsound_team_file.string);
1190                                                 else
1191                                                         S_LocalSound (con_chatsound_file.string);
1192                                         }
1193                                 }
1194                                 // Send to chatbox for say/tell (1) and messages (3)
1195                                 // 3 is just so that a message can be sent to the chatbox without a sound.
1196                                 if (*msg == 1 || *msg == 3)
1197                                         mask = CON_MASK_CHAT;
1198
1199                                 line[index++] = STRING_COLOR_TAG;
1200                                 line[index++] = '3';
1201                                 msg++;
1202                                 Con_Rcon_AddChar(*msg);
1203                         }
1204                         // store timestamp
1205                         for (;*timestamp;index++, timestamp++)
1206                                 if (index < (int)sizeof(line) - 2)
1207                                         line[index] = *timestamp;
1208                         // add the mask
1209                         mask |= additionalmask;
1210                 }
1211                 // append the character
1212                 line[index++] = *msg;
1213                 // if this is a newline character, we have a complete line to print
1214                 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1215                 {
1216                         // terminate the line
1217                         line[index] = 0;
1218                         // send to log file
1219                         Log_ConPrint(line);
1220                         // send to scrollable buffer
1221                         if (con_initialized && cls.state != ca_dedicated)
1222                         {
1223                                 Con_PrintToHistory(line, mask);
1224                         }
1225                         // send to terminal or dedicated server window
1226                         if (!sys_nostdout)
1227                         if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1228                         {
1229                                 if(sys_specialcharactertranslation.integer)
1230                                 {
1231                                         char *p;
1232                                         const char *q;
1233                                         p = line;
1234                                         while(*p)
1235                                         {
1236                                                 int ch = u8_getchar(p, &q);
1237                                                 if(ch >= 0xE000 && ch <= 0xE0FF && ((unsigned char) qfont_table[ch - 0xE000]) >= 0x20)
1238                                                 {
1239                                                         *p = qfont_table[ch - 0xE000];
1240                                                         if(q > p+1)
1241                                                                 memmove(p+1, q, strlen(q)+1);
1242                                                         p = p + 1;
1243                                                 }
1244                                                 else
1245                                                         p = p + (q - p);
1246                                         }
1247                                 }
1248
1249                                 if(sys_colortranslation.integer == 1) // ANSI
1250                                 {
1251                                         static char printline[MAX_INPUTLINE * 4 + 3];
1252                                                 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1253                                                 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1254                                         int lastcolor = 0;
1255                                         const char *in;
1256                                         char *out;
1257                                         int color;
1258                                         for(in = line, out = printline; *in; ++in)
1259                                         {
1260                                                 switch(*in)
1261                                                 {
1262                                                         case STRING_COLOR_TAG:
1263                                                                 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1264                                                                 {
1265                                                                         char r = tolower(in[2]);
1266                                                                         char g = tolower(in[3]);
1267                                                                         char b = tolower(in[4]);
1268                                                                         // it's a hex digit already, so the else part needs no check --blub
1269                                                                         if(isdigit(r)) r -= '0';
1270                                                                         else r -= 87;
1271                                                                         if(isdigit(g)) g -= '0';
1272                                                                         else g -= 87;
1273                                                                         if(isdigit(b)) b -= '0';
1274                                                                         else b -= 87;
1275                                                                         
1276                                                                         color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1277                                                                         in += 3; // 3 only, the switch down there does the fourth
1278                                                                 }
1279                                                                 else
1280                                                                         color = in[1];
1281                                                                 
1282                                                                 switch(color)
1283                                                                 {
1284                                                                         case STRING_COLOR_TAG:
1285                                                                                 ++in;
1286                                                                                 *out++ = STRING_COLOR_TAG;
1287                                                                                 break;
1288                                                                         case '0':
1289                                                                         case '7':
1290                                                                                 // normal color
1291                                                                                 ++in;
1292                                                                                 if(lastcolor == 0) break; else lastcolor = 0;
1293                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1294                                                                                 break;
1295                                                                         case '1':
1296                                                                                 // light red
1297                                                                                 ++in;
1298                                                                                 if(lastcolor == 1) break; else lastcolor = 1;
1299                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1300                                                                                 break;
1301                                                                         case '2':
1302                                                                                 // light green
1303                                                                                 ++in;
1304                                                                                 if(lastcolor == 2) break; else lastcolor = 2;
1305                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1306                                                                                 break;
1307                                                                         case '3':
1308                                                                                 // yellow
1309                                                                                 ++in;
1310                                                                                 if(lastcolor == 3) break; else lastcolor = 3;
1311                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1312                                                                                 break;
1313                                                                         case '4':
1314                                                                                 // light blue
1315                                                                                 ++in;
1316                                                                                 if(lastcolor == 4) break; else lastcolor = 4;
1317                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1318                                                                                 break;
1319                                                                         case '5':
1320                                                                                 // light cyan
1321                                                                                 ++in;
1322                                                                                 if(lastcolor == 5) break; else lastcolor = 5;
1323                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1324                                                                                 break;
1325                                                                         case '6':
1326                                                                                 // light magenta
1327                                                                                 ++in;
1328                                                                                 if(lastcolor == 6) break; else lastcolor = 6;
1329                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1330                                                                                 break;
1331                                                                         // 7 handled above
1332                                                                         case '8':
1333                                                                         case '9':
1334                                                                                 // bold normal color
1335                                                                                 ++in;
1336                                                                                 if(lastcolor == 8) break; else lastcolor = 8;
1337                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1338                                                                                 break;
1339                                                                         default:
1340                                                                                 *out++ = STRING_COLOR_TAG;
1341                                                                                 break;
1342                                                                 }
1343                                                                 break;
1344                                                         case '\n':
1345                                                                 if(lastcolor != 0)
1346                                                                 {
1347                                                                         *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1348                                                                         lastcolor = 0;
1349                                                                 }
1350                                                                 *out++ = *in;
1351                                                                 break;
1352                                                         default:
1353                                                                 *out++ = *in;
1354                                                                 break;
1355                                                 }
1356                                         }
1357                                         if(lastcolor != 0)
1358                                         {
1359                                                 *out++ = 0x1B;
1360                                                 *out++ = '[';
1361                                                 *out++ = 'm';
1362                                         }
1363                                         *out++ = 0;
1364                                         Sys_PrintToTerminal(printline);
1365                                 }
1366                                 else if(sys_colortranslation.integer == 2) // Quake
1367                                 {
1368                                         Sys_PrintToTerminal(line);
1369                                 }
1370                                 else // strip
1371                                 {
1372                                         static char printline[MAX_INPUTLINE]; // it can only get shorter here
1373                                         const char *in;
1374                                         char *out;
1375                                         for(in = line, out = printline; *in; ++in)
1376                                         {
1377                                                 switch(*in)
1378                                                 {
1379                                                         case STRING_COLOR_TAG:
1380                                                                 switch(in[1])
1381                                                                 {
1382                                                                         case STRING_COLOR_RGB_TAG_CHAR:
1383                                                                                 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1384                                                                                 {
1385                                                                                         in+=4;
1386                                                                                         break;
1387                                                                                 }
1388                                                                                 *out++ = STRING_COLOR_TAG;
1389                                                                                 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1390                                                                                 ++in;
1391                                                                                 break;
1392                                                                         case STRING_COLOR_TAG:
1393                                                                                 ++in;
1394                                                                                 *out++ = STRING_COLOR_TAG;
1395                                                                                 break;
1396                                                                         case '0':
1397                                                                         case '1':
1398                                                                         case '2':
1399                                                                         case '3':
1400                                                                         case '4':
1401                                                                         case '5':
1402                                                                         case '6':
1403                                                                         case '7':
1404                                                                         case '8':
1405                                                                         case '9':
1406                                                                                 ++in;
1407                                                                                 break;
1408                                                                         default:
1409                                                                                 *out++ = STRING_COLOR_TAG;
1410                                                                                 break;
1411                                                                 }
1412                                                                 break;
1413                                                         default:
1414                                                                 *out++ = *in;
1415                                                                 break;
1416                                                 }
1417                                         }
1418                                         *out++ = 0;
1419                                         Sys_PrintToTerminal(printline);
1420                                 }
1421                         }
1422                         // empty the line buffer
1423                         index = 0;
1424                         mask = 0;
1425                 }
1426         }
1427
1428         if (con_mutex)
1429                 Thread_UnlockMutex(con_mutex);
1430 }
1431
1432 /*
1433 ================
1434 Con_MaskPrintf
1435 ================
1436 */
1437 void Con_MaskPrintf(int mask, const char *fmt, ...)
1438 {
1439         va_list argptr;
1440         char msg[MAX_INPUTLINE];
1441
1442         va_start(argptr,fmt);
1443         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1444         va_end(argptr);
1445
1446         Con_MaskPrint(mask, msg);
1447 }
1448
1449 /*
1450 ================
1451 Con_Print
1452 ================
1453 */
1454 void Con_Print(const char *msg)
1455 {
1456         Con_MaskPrint(CON_MASK_PRINT, msg);
1457 }
1458
1459 /*
1460 ================
1461 Con_Printf
1462 ================
1463 */
1464 void Con_Printf(const char *fmt, ...)
1465 {
1466         va_list argptr;
1467         char msg[MAX_INPUTLINE];
1468
1469         va_start(argptr,fmt);
1470         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1471         va_end(argptr);
1472
1473         Con_MaskPrint(CON_MASK_PRINT, msg);
1474 }
1475
1476 /*
1477 ================
1478 Con_DPrint
1479 ================
1480 */
1481 void Con_DPrint(const char *msg)
1482 {
1483         if(developer.integer < 0) // at 0, we still add to the buffer but hide
1484                 return;
1485
1486         Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1487 }
1488
1489 /*
1490 ================
1491 Con_DPrintf
1492 ================
1493 */
1494 void Con_DPrintf(const char *fmt, ...)
1495 {
1496         va_list argptr;
1497         char msg[MAX_INPUTLINE];
1498
1499         if(developer.integer < 0) // at 0, we still add to the buffer but hide
1500                 return;
1501
1502         va_start(argptr,fmt);
1503         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1504         va_end(argptr);
1505
1506         Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1507 }
1508
1509
1510 /*
1511 ==============================================================================
1512
1513 DRAWING
1514
1515 ==============================================================================
1516 */
1517
1518 /*
1519 ================
1520 Con_DrawInput
1521
1522 It draws either the console input line or the chat input line (if is_console is false)
1523 The input line scrolls horizontally if typing goes beyond the right edge
1524
1525 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1526 ================
1527 */
1528 static void Con_DrawInput(qbool is_console, float x, float v, float inputsize)
1529 {
1530         int y, i, col_out, linepos, text_start, prefix_start = 0;
1531         char text[MAX_INPUTLINE + 5 + 9 + 1]; // space for ^xRGB, "say_team:" and \0
1532         float xo;
1533         size_t len_out;
1534         const char *prefix;
1535         dp_font_t *fnt;
1536
1537         if (is_console && !key_consoleactive)
1538                 return;         // don't draw anything
1539
1540         if (is_console)
1541         {
1542                 // empty prefix because ] is part of the console edit line
1543                 prefix = "";
1544                 strlcpy(text, key_line, sizeof(text));
1545                 linepos = key_linepos;
1546                 fnt = FONT_CONSOLE;
1547         }
1548         else
1549         {
1550                 if (chat_mode < 0)
1551                         prefix = "]";
1552                 else if(chat_mode)
1553                         prefix = "say_team:";
1554                 else
1555                         prefix = "say:";
1556                 strlcpy(text, chat_buffer, sizeof(text));
1557                 linepos = chat_bufferpos;
1558                 fnt = FONT_CHAT;
1559         }
1560
1561         y = (int)strlen(text);
1562
1563         // make the color code visible when the cursor is inside it
1564         if(text[linepos] != 0)
1565         {
1566                 for(i=1; i < 5 && linepos - i > 0; ++i)
1567                         if(text[linepos-i] == STRING_COLOR_TAG)
1568                         {
1569                                 int caret_pos, ofs = 0;
1570                                 caret_pos = linepos - i;
1571                                 if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG)
1572                                         ofs = 1;
1573                                 else if(i == 1 && isdigit(text[caret_pos+1]))
1574                                         ofs = 2;
1575                                 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]))
1576                                         ofs = 5;
1577                                 if(ofs && (size_t)(y + ofs + 1) < sizeof(text))
1578                                 {
1579                                         int carets = 1;
1580                                         while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG)
1581                                                 ++carets;
1582                                         if(carets & 1)
1583                                         {
1584                                                 // str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing)
1585                                                 // str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing)
1586                                                 memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos);
1587                                                 text[caret_pos + ofs] = STRING_COLOR_TAG;
1588                                                 y += ofs + 1;
1589                                                 text[y] = 0;
1590                                         }
1591                                 }
1592                                 break;
1593                         }
1594         }
1595
1596         if (!is_console)
1597         {
1598                 prefix_start = x;
1599                 x += DrawQ_TextWidth(prefix, 0, inputsize, inputsize, false, fnt);
1600         }
1601
1602         len_out = linepos;
1603         col_out = -1;
1604         xo = 0;
1605         if (linepos > 0)
1606                 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, inputsize, inputsize, &col_out, false, fnt, 1000000000);
1607
1608         text_start = x + (vid_conwidth.value - x) * 0.95 - xo; // scroll
1609         if(text_start >= x)
1610                 text_start = x;
1611         else if (!is_console)
1612                 prefix_start -= (x - text_start);
1613
1614         if (!is_console)
1615                 DrawQ_String(prefix_start, v, prefix, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1616
1617         DrawQ_String(text_start, v, text, y + 3, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1618
1619         // draw a cursor on top of this
1620         if ((int)(host.realtime*con_cursorspeed) & 1)           // cursor is visible
1621         {
1622                 if (!utf8_enable.integer)
1623                 {
1624                         text[0] = 11 + 130 * key_insert;        // either solid or triangle facing right
1625                         text[1] = 0;
1626                 }
1627                 else
1628                 {
1629                         size_t len;
1630                         const char *curbuf;
1631                         char charbuf16[16];
1632                         curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1633                         memcpy(text, curbuf, len);
1634                         text[len] = 0;
1635                 }
1636                 DrawQ_String(text_start + xo, v, text, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, fnt);
1637         }
1638 }
1639
1640 typedef struct
1641 {
1642         dp_font_t *font;
1643         float alignment; // 0 = left, 0.5 = center, 1 = right
1644         float fontsize;
1645         float x;
1646         float y;
1647         float width;
1648         float ymin, ymax;
1649         const char *continuationString;
1650
1651         // PRIVATE:
1652         int colorindex; // init to -1
1653 }
1654 con_text_info_t;
1655
1656 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1657 {
1658         con_text_info_t *ti = (con_text_info_t *) passthrough;
1659         if(w == NULL)
1660         {
1661                 ti->colorindex = -1;
1662                 return ti->fontsize * ti->font->maxwidth;
1663         }
1664         if(maxWidth >= 0)
1665                 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1666         else if(maxWidth == -1)
1667                 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1668         else
1669         {
1670                 Sys_PrintfToTerminal("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1671                 // Note: this is NOT a Con_Printf, as it could print recursively
1672                 return 0;
1673         }
1674 }
1675
1676 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1677 {
1678         (void) passthrough;
1679         (void) line;
1680         (void) length;
1681         (void) width;
1682         (void) isContinuation;
1683         return 1;
1684 }
1685
1686 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1687 {
1688         con_text_info_t *ti = (con_text_info_t *) passthrough;
1689
1690         if(ti->y < ti->ymin - 0.001)
1691                 (void) 0;
1692         else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1693                 (void) 0;
1694         else
1695         {
1696                 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1697                 if(isContinuation && *ti->continuationString)
1698                         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);
1699                 if(length > 0)
1700                         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);
1701         }
1702
1703         ti->y += ti->fontsize;
1704         return 1;
1705 }
1706
1707 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)
1708 {
1709         int i;
1710         int lines = 0;
1711         int maxlines = (int) floor(height / fontsize + 0.01f);
1712         int startidx;
1713         int nskip = 0;
1714         int continuationWidth = 0;
1715         size_t len;
1716         double t = cl.time; // saved so it won't change
1717         con_text_info_t ti;
1718
1719         ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1720         ti.fontsize = fontsize;
1721         ti.alignment = alignment_x;
1722         ti.width = width;
1723         ti.ymin = y;
1724         ti.ymax = y + height;
1725         ti.continuationString = continuationString;
1726
1727         len = 0;
1728         Con_WordWidthFunc(&ti, NULL, &len, -1);
1729         len = strlen(continuationString);
1730         continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &len, -1);
1731
1732         // first find the first line to draw by backwards iterating and word wrapping to find their length...
1733         startidx = CON_LINES_COUNT;
1734         for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1735         {
1736                 con_lineinfo_t *l = &CON_LINES(i);
1737                 int mylines;
1738
1739                 if((l->mask & mask_must) != mask_must)
1740                         continue;
1741                 if(l->mask & mask_mustnot)
1742                         continue;
1743                 if(maxage && (l->addtime < t - maxage))
1744                         continue;
1745
1746                 // WE FOUND ONE!
1747                 // Calculate its actual height...
1748                 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1749                 if(lines + mylines >= maxlines)
1750                 {
1751                         nskip = lines + mylines - maxlines;
1752                         lines = maxlines;
1753                         startidx = i;
1754                         break;
1755                 }
1756                 lines += mylines;
1757                 startidx = i;
1758         }
1759
1760         // then center according to the calculated amount of lines...
1761         ti.x = x;
1762         ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1763
1764         // then actually draw
1765         for(i = startidx; i < CON_LINES_COUNT; ++i)
1766         {
1767                 con_lineinfo_t *l = &CON_LINES(i);
1768
1769                 if((l->mask & mask_must) != mask_must)
1770                         continue;
1771                 if(l->mask & mask_mustnot)
1772                         continue;
1773                 if(maxage && (l->addtime < t - maxage))
1774                         continue;
1775
1776                 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1777         }
1778
1779         return lines;
1780 }
1781
1782 /*
1783 ================
1784 Con_DrawNotify
1785
1786 Draws the last few lines of output transparently over the game top
1787 ================
1788 */
1789 void Con_DrawNotify (void)
1790 {
1791         float x, v;
1792         float chatstart, notifystart, inputsize, height;
1793         float align;
1794         int numChatlines;
1795         int chatpos;
1796
1797         if (con_mutex) Thread_LockMutex(con_mutex);
1798         ConBuffer_FixTimes(&con);
1799
1800         numChatlines = con_chat.integer;
1801
1802         chatpos = con_chatpos.integer;
1803
1804         if (con_notify.integer < 0)
1805                 Cvar_SetValueQuick(&con_notify, 0);
1806         if (gamemode == GAME_TRANSFUSION)
1807                 v = 8; // vertical offset
1808         else
1809                 v = 0;
1810
1811         // GAME_NEXUIZ: center, otherwise left justify
1812         align = con_notifyalign.value;
1813         if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1814         {
1815                 if(IS_OLDNEXUIZ_DERIVED(gamemode))
1816                         align = 0.5;
1817         }
1818
1819         if(numChatlines || !con_chatrect.integer)
1820         {
1821                 if(chatpos == 0)
1822                 {
1823                         // first chat, input line, then notify
1824                         chatstart = v;
1825                         notifystart = v + (numChatlines + 1) * con_chatsize.value;
1826                 }
1827                 else if(chatpos > 0)
1828                 {
1829                         // first notify, then (chatpos-1) empty lines, then chat, then input
1830                         notifystart = v;
1831                         chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1832                 }
1833                 else // if(chatpos < 0)
1834                 {
1835                         // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1836                         notifystart = v;
1837                         chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1838                 }
1839         }
1840         else
1841         {
1842                 // just notify and input
1843                 notifystart = v;
1844                 chatstart = 0; // shut off gcc warning
1845         }
1846
1847         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, "");
1848
1849         if(con_chatrect.integer)
1850         {
1851                 x = con_chatrect_x.value * vid_conwidth.value;
1852                 v = con_chatrect_y.value * vid_conheight.value;
1853         }
1854         else
1855         {
1856                 x = 0;
1857                 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1858                         v = chatstart;
1859         }
1860         height = numChatlines * con_chatsize.value;
1861
1862         if(numChatlines)
1863         {
1864                 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 ... ");
1865                 v += height;
1866         }
1867         if (key_dest == key_message)
1868         {
1869                 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1870                 Con_DrawInput(false, x, v, inputsize);
1871         }
1872         else
1873                 chat_bufferpos = 0;
1874
1875         if (con_mutex) Thread_UnlockMutex(con_mutex);
1876 }
1877
1878 /*
1879 ================
1880 Con_LineHeight
1881
1882 Returns the height of a given console line; calculates it if necessary.
1883 ================
1884 */
1885 static int Con_LineHeight(int lineno)
1886 {
1887         con_lineinfo_t *li = &CON_LINES(lineno);
1888         if(li->height == -1)
1889         {
1890                 float width = vid_conwidth.value;
1891                 con_text_info_t ti;
1892                 ti.fontsize = con_textsize.value;
1893                 ti.font = FONT_CONSOLE;
1894                 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1895         }
1896         return li->height;
1897 }
1898
1899 /*
1900 ================
1901 Con_DrawConsoleLine
1902
1903 Draws a line of the console; returns its height in lines.
1904 If alpha is 0, the line is not drawn, but still wrapped and its height
1905 returned.
1906 ================
1907 */
1908 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1909 {
1910         float width = vid_conwidth.value;
1911         con_text_info_t ti;
1912         con_lineinfo_t *li = &CON_LINES(lineno);
1913
1914         if((li->mask & mask_must) != mask_must)
1915                 return 0;
1916         if((li->mask & mask_mustnot) != 0)
1917                 return 0;
1918
1919         ti.continuationString = "";
1920         ti.alignment = 0;
1921         ti.fontsize = con_textsize.value;
1922         ti.font = FONT_CONSOLE;
1923         ti.x = 0;
1924         ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1925         ti.ymin = ymin;
1926         ti.ymax = ymax;
1927         ti.width = width;
1928
1929         return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1930 }
1931
1932 /*
1933 ================
1934 Con_LastVisibleLine
1935
1936 Calculates the last visible line index and how much to show of it based on
1937 con_backscroll.
1938 ================
1939 */
1940 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1941 {
1942         int lines_seen = 0;
1943         int i;
1944
1945         if(con_backscroll < 0)
1946                 con_backscroll = 0;
1947
1948         *last = 0;
1949
1950         // now count until we saw con_backscroll actual lines
1951         for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1952         if((CON_LINES(i).mask & mask_must) == mask_must)
1953         if((CON_LINES(i).mask & mask_mustnot) == 0)
1954         {
1955                 int h = Con_LineHeight(i);
1956
1957                 // line is the last visible line?
1958                 *last = i;
1959                 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1960                 {
1961                         *limitlast = lines_seen + h - con_backscroll;
1962                         return;
1963                 }
1964
1965                 lines_seen += h;
1966         }
1967
1968         // if we get here, no line was on screen - scroll so that one line is
1969         // visible then.
1970         con_backscroll = lines_seen - 1;
1971         *limitlast = 1;
1972 }
1973
1974 /*
1975 ================
1976 Con_DrawConsole
1977
1978 Draws the console with the solid background
1979 The typing input line at the bottom should only be drawn if typing is allowed
1980 ================
1981 */
1982 void Con_DrawConsole (int lines)
1983 {
1984         float alpha, alpha0;
1985         double sx, sy;
1986         int mask_must = 0;
1987         int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1988         cachepic_t *conbackpic;
1989         unsigned int conbackflags;
1990
1991         if (lines <= 0)
1992                 return;
1993
1994         if (con_mutex) Thread_LockMutex(con_mutex);
1995
1996         if (con_backscroll < 0)
1997                 con_backscroll = 0;
1998
1999         con_vislines = lines;
2000
2001         r_draw2d_force = true;
2002
2003 // draw the background
2004         alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
2005         if((alpha = alpha0 * scr_conalphafactor.value) > 0)
2006         {
2007                 sx = scr_conscroll_x.value;
2008                 sy = scr_conscroll_y.value;
2009                 conbackflags = CACHEPICFLAG_FAILONMISSING; // So console is readable when game content is missing
2010                 if (sx != 0 || sy != 0)
2011                         conbackflags &= CACHEPICFLAG_NOCLAMP;
2012                 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", conbackflags) : NULL;
2013                 sx *= host.realtime; sy *= host.realtime;
2014                 sx -= floor(sx); sy -= floor(sy);
2015                 if (Draw_IsPicLoaded(conbackpic))
2016                         DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2017                                         0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2018                                         1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2019                                         0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2020                                         1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2021                                         0);
2022                 else
2023                         DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
2024         }
2025         if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
2026         {
2027                 sx = scr_conscroll2_x.value;
2028                 sy = scr_conscroll2_y.value;
2029                 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2030                 sx *= host.realtime; sy *= host.realtime;
2031                 sx -= floor(sx); sy -= floor(sy);
2032                 if(Draw_IsPicLoaded(conbackpic))
2033                         DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2034                                         0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2035                                         1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2036                                         0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2037                                         1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2038                                         0);
2039         }
2040         if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
2041         {
2042                 sx = scr_conscroll3_x.value;
2043                 sy = scr_conscroll3_y.value;
2044                 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2045                 sx *= host.realtime; sy *= host.realtime;
2046                 sx -= floor(sx); sy -= floor(sy);
2047                 if(Draw_IsPicLoaded(conbackpic))
2048                         DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2049                                         0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2050                                         1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2051                                         0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2052                                         1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2053                                         0);
2054         }
2055         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);
2056
2057 // draw the text
2058 #if 0
2059         {
2060                 int i;
2061                 int count = CON_LINES_COUNT;
2062                 float ymax = con_vislines - 2 * con_textsize.value;
2063                 float y = ymax + con_textsize.value * con_backscroll;
2064                 for (i = 0;i < count && y >= 0;i++)
2065                         y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
2066                 // fix any excessive scrollback for the next frame
2067                 if (i >= count && y >= 0)
2068                 {
2069                         con_backscroll -= (int)(y / con_textsize.value);
2070                         if (con_backscroll < 0)
2071                                 con_backscroll = 0;
2072                 }
2073         }
2074 #else
2075         if(CON_LINES_COUNT > 0)
2076         {
2077                 int i, last, limitlast;
2078                 float y;
2079                 float ymax = con_vislines - 2 * con_textsize.value;
2080                 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2081                 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2082                 y = ymax - con_textsize.value;
2083
2084                 if(limitlast)
2085                         y += (CON_LINES(last).height - limitlast) * con_textsize.value;
2086                 i = last;
2087
2088                 for(;;)
2089                 {
2090                         y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
2091                         if(i == 0)
2092                                 break; // top of console buffer
2093                         if(y < 0)
2094                                 break; // top of console window
2095                         limitlast = 0;
2096                         --i;
2097                 }
2098         }
2099 #endif
2100
2101 // draw the input prompt, user text, and cursor if desired
2102         Con_DrawInput(true, 0, con_vislines - con_textsize.value * 2, con_textsize.value);
2103
2104         r_draw2d_force = false;
2105         if (con_mutex) Thread_UnlockMutex(con_mutex);
2106 }
2107
2108 /*
2109 GetMapList
2110
2111 Made by [515]
2112 Prints not only map filename, but also
2113 its format (q1/q2/q3/hl) and even its message
2114 */
2115 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2116 //LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2117 //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
2118 //LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2119 qbool GetMapList (const char *s, char *completedname, int completednamebufferlength)
2120 {
2121         fssearch_t      *t;
2122         char            message[1024];
2123         int                     i, k, max, p, o, min;
2124         unsigned char *len;
2125         qfile_t         *f;
2126         unsigned char buf[1024];
2127
2128         dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2129         t = FS_Search(message, 1, true, NULL);
2130         if(!t)
2131                 return false;
2132         if (t->numfilenames > 1)
2133                 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2134         len = (unsigned char *)Z_Malloc(t->numfilenames);
2135         min = 666;
2136         for(max=i=0;i<t->numfilenames;i++)
2137         {
2138                 k = (int)strlen(t->filenames[i]);
2139                 k -= 9;
2140                 if(max < k)
2141                         max = k;
2142                 else
2143                 if(min > k)
2144                         min = k;
2145                 len[i] = k;
2146         }
2147         o = (int)strlen(s);
2148         for(i=0;i<t->numfilenames;i++)
2149         {
2150                 int lumpofs = 0, lumplen = 0;
2151                 char *entities = NULL;
2152                 const char *data = NULL;
2153                 char keyname[64];
2154                 char entfilename[MAX_QPATH];
2155                 char desc[64];
2156                 desc[0] = 0;
2157                 strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2158                 p = 0;
2159                 f = FS_OpenVirtualFile(t->filenames[i], true);
2160                 if(f)
2161                 {
2162                         strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2163                         memset(buf, 0, 1024);
2164                         FS_Read(f, buf, 1024);
2165                         if (!memcmp(buf, "IBSP", 4))
2166                         {
2167                                 p = LittleLong(((int *)buf)[1]);
2168                                 if (p == Q3BSPVERSION)
2169                                 {
2170                                         q3dheader_t *header = (q3dheader_t *)buf;
2171                                         lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2172                                         lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2173                                         dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2174                                 }
2175                                 else if (p == Q2BSPVERSION)
2176                                 {
2177                                         q2dheader_t *header = (q2dheader_t *)buf;
2178                                         lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2179                                         lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2180                                         dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2181                                 }
2182                                 else
2183                                         dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2184                         }
2185                         else if (BuffLittleLong(buf) == BSPVERSION)
2186                         {
2187                                 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2188                                 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2189                                 dpsnprintf(desc, sizeof(desc), "BSP29");
2190                         }
2191                         else if (BuffLittleLong(buf) == 30)
2192                         {
2193                                 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2194                                 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2195                                 dpsnprintf(desc, sizeof(desc), "BSPHL");
2196                         }
2197                         else if (!memcmp(buf, "BSP2", 4))
2198                         {
2199                                 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2200                                 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2201                                 dpsnprintf(desc, sizeof(desc), "BSP2");
2202                         }
2203                         else if (!memcmp(buf, "2PSB", 4))
2204                         {
2205                                 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2206                                 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2207                                 dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2208                         }
2209                         else if(!memcmp(buf, "VBSP", 4))
2210                         {
2211                                 hl2dheader_t *header = (hl2dheader_t *)buf;
2212                                 lumpofs = LittleLong(header->lumps[HL2LUMP_ENTITIES].fileofs);
2213                                 lumplen = LittleLong(header->lumps[HL2LUMP_ENTITIES].filelen);
2214                                 dpsnprintf(desc, sizeof(desc), "VBSP%i", LittleLong(((int *)buf)[1]));
2215                         }
2216                         else
2217                                 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2218                         strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2219                         memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2220                         entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2221                         if (!entities && lumplen >= 10)
2222                         {
2223                                 FS_Seek(f, lumpofs, SEEK_SET);
2224                                 entities = (char *)Z_Malloc(lumplen + 1);
2225                                 FS_Read(f, entities, lumplen);
2226                         }
2227                         if (entities)
2228                         {
2229                                 // if there are entities to parse, a missing message key just
2230                                 // means there is no title, so clear the message string now
2231                                 message[0] = 0;
2232                                 data = entities;
2233                                 for (;;)
2234                                 {
2235                                         int l;
2236                                         if (!COM_ParseToken_Simple(&data, false, false, true))
2237                                                 break;
2238                                         if (com_token[0] == '{')
2239                                                 continue;
2240                                         if (com_token[0] == '}')
2241                                                 break;
2242                                         // skip leading whitespace
2243                                         for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2244                                         for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2245                                                 keyname[l] = com_token[k+l];
2246                                         keyname[l] = 0;
2247                                         if (!COM_ParseToken_Simple(&data, false, false, true))
2248                                                 break;
2249                                         if (developer_extra.integer)
2250                                                 Con_DPrintf("key: %s %s\n", keyname, com_token);
2251                                         if (!strcmp(keyname, "message"))
2252                                         {
2253                                                 // get the message contents
2254                                                 strlcpy(message, com_token, sizeof(message));
2255                                                 break;
2256                                         }
2257                                 }
2258                         }
2259                 }
2260                 if (entities)
2261                         Z_Free(entities);
2262                 if(f)
2263                         FS_Close(f);
2264                 *(t->filenames[i]+len[i]+5) = 0;
2265                 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2266         }
2267         Con_Print("\n");
2268         for(p=o;p<min;p++)
2269         {
2270                 k = *(t->filenames[0]+5+p);
2271                 if(k == 0)
2272                         goto endcomplete;
2273                 for(i=1;i<t->numfilenames;i++)
2274                         if(*(t->filenames[i]+5+p) != k)
2275                                 goto endcomplete;
2276         }
2277 endcomplete:
2278         if(p > o && completedname && completednamebufferlength > 0)
2279         {
2280                 memset(completedname, 0, completednamebufferlength);
2281                 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2282         }
2283         Z_Free(len);
2284         FS_FreeSearch(t);
2285         return p > o;
2286 }
2287
2288 /*
2289         Con_DisplayList
2290
2291         New function for tab-completion system
2292         Added by EvilTypeGuy
2293         MEGA Thanks to Taniwha
2294
2295 */
2296 void Con_DisplayList(const char **list)
2297 {
2298         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2299         const char **walk = list;
2300
2301         while (*walk) {
2302                 len = (int)strlen(*walk);
2303                 if (len > maxlen)
2304                         maxlen = len;
2305                 walk++;
2306         }
2307         maxlen += 1;
2308
2309         while (*list) {
2310                 len = (int)strlen(*list);
2311                 if (pos + maxlen >= width) {
2312                         Con_Print("\n");
2313                         pos = 0;
2314                 }
2315
2316                 Con_Print(*list);
2317                 for (i = 0; i < (maxlen - len); i++)
2318                         Con_Print(" ");
2319
2320                 pos += maxlen;
2321                 list++;
2322         }
2323
2324         if (pos)
2325                 Con_Print("\n\n");
2326 }
2327
2328
2329 // Now it becomes TRICKY :D --blub
2330 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];     // contains the nicks with colors and all that
2331 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];  // sanitized list for completion when there are other possible matches.
2332 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2333 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
2334 static int Nicks_matchpos;
2335
2336 // co against <<:BLASTER:>> is true!?
2337 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2338 {
2339         while(a_len)
2340         {
2341                 if(tolower(*a) == tolower(*b))
2342                 {
2343                         if(*a == 0)
2344                                 return 0;
2345                         --a_len;
2346                         ++a;
2347                         ++b;
2348                         continue;
2349                 }
2350                 if(!*a)
2351                         return -1;
2352                 if(!*b)
2353                         return 1;
2354                 if(*a == ' ')
2355                         return (*a < *b) ? -1 : 1;
2356                 if(*b == ' ')
2357                         ++b;
2358                 else
2359                         return (*a < *b) ? -1 : 1;
2360         }
2361         return 0;
2362 }
2363 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2364 {
2365         char space_char;
2366         if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2367         {
2368                 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2369                         return Nicks_strncasecmp_nospaces(a, b, a_len);
2370                 return strncasecmp(a, b, a_len);
2371         }
2372
2373         space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2374
2375         // ignore non alphanumerics of B
2376         // if A contains a non-alphanumeric, B must contain it as well though!
2377         while(a_len)
2378         {
2379                 qbool alnum_a, alnum_b;
2380
2381                 if(tolower(*a) == tolower(*b))
2382                 {
2383                         if(*a == 0) // end of both strings, they're equal
2384                                 return 0;
2385                         --a_len;
2386                         ++a;
2387                         ++b;
2388                         continue;
2389                 }
2390                 // not equal, end of one string?
2391                 if(!*a)
2392                         return -1;
2393                 if(!*b)
2394                         return 1;
2395                 // ignore non alphanumerics
2396                 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2397                 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2398                 if(!alnum_a) // b must contain this
2399                         return (*a < *b) ? -1 : 1;
2400                 if(!alnum_b)
2401                         ++b;
2402                 // otherwise, both are alnum, they're just not equal, return the appropriate number
2403                 else
2404                         return (*a < *b) ? -1 : 1;
2405         }
2406         return 0;
2407 }
2408
2409
2410 /* Nicks_CompleteCountPossible
2411
2412    Count the number of possible nicks to complete
2413  */
2414 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qbool isCon)
2415 {
2416         char name[MAX_SCOREBOARDNAME];
2417         int i, p;
2418         int match;
2419         int spos;
2420         int count = 0;
2421
2422         if(!con_nickcompletion.integer)
2423                 return 0;
2424
2425         // changed that to 1
2426         if(!line[0])// || !line[1]) // we want at least... 2 written characters
2427                 return 0;
2428
2429         for(i = 0; i < cl.maxclients; ++i)
2430         {
2431                 p = i;
2432                 if(!cl.scores[p].name[0])
2433                         continue;
2434
2435                 SanitizeString(cl.scores[p].name, name);
2436                 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2437
2438                 if(!name[0])
2439                         continue;
2440
2441                 match = -1;
2442                 spos = pos - 1; // no need for a minimum of characters :)
2443
2444                 while(spos >= 0)
2445                 {
2446                         if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2447                         {
2448                                 if(!(isCon && spos == 1)) // console start
2449                                 {
2450                                         --spos;
2451                                         continue;
2452                                 }
2453                         }
2454                         if(isCon && spos == 0)
2455                                 break;
2456                         if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2457                                 match = spos;
2458                         --spos;
2459                 }
2460                 if(match < 0)
2461                         continue;
2462                 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2463                 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2464
2465                 // the sanitized list
2466                 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2467                 if(!count)
2468                 {
2469                         Nicks_matchpos = match;
2470                 }
2471
2472                 Nicks_offset[count] = s - (&line[match]);
2473                 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2474
2475                 ++count;
2476         }
2477         return count;
2478 }
2479
2480 static void Cmd_CompleteNicksPrint(int count)
2481 {
2482         int i;
2483         for(i = 0; i < count; ++i)
2484                 Con_Printf("%s\n", Nicks_list[i]);
2485 }
2486
2487 static void Nicks_CutMatchesNormal(int count)
2488 {
2489         // cut match 0 down to the longest possible completion
2490         int i;
2491         unsigned int c, l;
2492         c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2493         for(i = 1; i < count; ++i)
2494         {
2495                 l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2496                 if(l < c)
2497                         c = l;
2498
2499                 for(l = 0; l <= c; ++l)
2500                         if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2501                         {
2502                                 c = l-1;
2503                                 break;
2504                         }
2505         }
2506         Nicks_sanlist[0][c+1] = 0;
2507         //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2508 }
2509
2510 static unsigned int Nicks_strcleanlen(const char *s)
2511 {
2512         unsigned int l = 0;
2513         while(*s)
2514         {
2515                 if( (*s >= 'a' && *s <= 'z') ||
2516                     (*s >= 'A' && *s <= 'Z') ||
2517                     (*s >= '0' && *s <= '9') ||
2518                     *s == ' ')
2519                         ++l;
2520                 ++s;
2521         }
2522         return l;
2523 }
2524
2525 static void Nicks_CutMatchesAlphaNumeric(int count)
2526 {
2527         // cut match 0 down to the longest possible completion
2528         int i;
2529         unsigned int c, l;
2530         char tempstr[sizeof(Nicks_sanlist[0])];
2531         char *a, *b;
2532         char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2533
2534         c = (unsigned int)strlen(Nicks_sanlist[0]);
2535         for(i = 0, l = 0; i < (int)c; ++i)
2536         {
2537                 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2538                     (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2539                     (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2540                 {
2541                         tempstr[l++] = Nicks_sanlist[0][i];
2542                 }
2543         }
2544         tempstr[l] = 0;
2545
2546         for(i = 1; i < count; ++i)
2547         {
2548                 a = tempstr;
2549                 b = Nicks_sanlist[i];
2550                 while(1)
2551                 {
2552                         if(!*a)
2553                                 break;
2554                         if(!*b)
2555                         {
2556                                 *a = 0;
2557                                 break;
2558                         }
2559                         if(tolower(*a) == tolower(*b))
2560                         {
2561                                 ++a;
2562                                 ++b;
2563                                 continue;
2564                         }
2565                         if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2566                         {
2567                                 // b is alnum, so cut
2568                                 *a = 0;
2569                                 break;
2570                         }
2571                         ++b;
2572                 }
2573         }
2574         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2575         Nicks_CutMatchesNormal(count);
2576         //if(!Nicks_sanlist[0][0])
2577         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2578         {
2579                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2580                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2581         }
2582 }
2583
2584 static void Nicks_CutMatchesNoSpaces(int count)
2585 {
2586         // cut match 0 down to the longest possible completion
2587         int i;
2588         unsigned int c, l;
2589         char tempstr[sizeof(Nicks_sanlist[0])];
2590         char *a, *b;
2591
2592         c = (unsigned int)strlen(Nicks_sanlist[0]);
2593         for(i = 0, l = 0; i < (int)c; ++i)
2594         {
2595                 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2596                 {
2597                         tempstr[l++] = Nicks_sanlist[0][i];
2598                 }
2599         }
2600         tempstr[l] = 0;
2601
2602         for(i = 1; i < count; ++i)
2603         {
2604                 a = tempstr;
2605                 b = Nicks_sanlist[i];
2606                 while(1)
2607                 {
2608                         if(!*a)
2609                                 break;
2610                         if(!*b)
2611                         {
2612                                 *a = 0;
2613                                 break;
2614                         }
2615                         if(tolower(*a) == tolower(*b))
2616                         {
2617                                 ++a;
2618                                 ++b;
2619                                 continue;
2620                         }
2621                         if(*b != ' ')
2622                         {
2623                                 *a = 0;
2624                                 break;
2625                         }
2626                         ++b;
2627                 }
2628         }
2629         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2630         Nicks_CutMatchesNormal(count);
2631         //if(!Nicks_sanlist[0][0])
2632         //Con_Printf("TS: %s\n", tempstr);
2633         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2634         {
2635                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2636                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2637         }
2638 }
2639
2640 static void Nicks_CutMatches(int count)
2641 {
2642         if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2643                 Nicks_CutMatchesAlphaNumeric(count);
2644         else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2645                 Nicks_CutMatchesNoSpaces(count);
2646         else
2647                 Nicks_CutMatchesNormal(count);
2648 }
2649
2650 static const char **Nicks_CompleteBuildList(int count)
2651 {
2652         const char **buf;
2653         int bpos = 0;
2654         // the list is freed by Con_CompleteCommandLine, so create a char**
2655         buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2656
2657         for(; bpos < count; ++bpos)
2658                 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2659
2660         Nicks_CutMatches(count);
2661
2662         buf[bpos] = NULL;
2663         return buf;
2664 }
2665
2666 /*
2667         Nicks_AddLastColor
2668         Restores the previous used color, after the autocompleted name.
2669 */
2670 static int Nicks_AddLastColor(char *buffer, int pos)
2671 {
2672         qbool quote_added = false;
2673         int match;
2674         int color = STRING_COLOR_DEFAULT + '0';
2675         char r = 0, g = 0, b = 0;
2676
2677         if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2678         {
2679                 // we'll have to add a quote :)
2680                 buffer[pos++] = '\"';
2681                 quote_added = true;
2682         }
2683
2684         if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2685         {
2686                 // add color when no quote was added, or when flags &4?
2687                 // find last color
2688                 for(match = Nicks_matchpos-1; match >= 0; --match)
2689                 {
2690                         if(buffer[match] == STRING_COLOR_TAG)
2691                         {
2692                                 if( isdigit(buffer[match+1]) )
2693                                 {
2694                                         color = buffer[match+1];
2695                                         break;
2696                                 }
2697                                 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2698                                 {
2699                                         if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2700                                         {
2701                                                 r = buffer[match+2];
2702                                                 g = buffer[match+3];
2703                                                 b = buffer[match+4];
2704                                                 color = -1;
2705                                                 break;
2706                                         }
2707                                 }
2708                         }
2709                 }
2710                 if(!quote_added)
2711                 {
2712                         if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2713                                 pos -= 2;
2714                         else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2715                                          && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2716                                 pos -= 5;
2717                 }
2718                 buffer[pos++] = STRING_COLOR_TAG;
2719                 if (color == -1)
2720                 {
2721                         buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2722                         buffer[pos++] = r;
2723                         buffer[pos++] = g;
2724                         buffer[pos++] = b;
2725                 }
2726                 else
2727                         buffer[pos++] = color;
2728         }
2729         return pos;
2730 }
2731
2732 /*
2733         Con_CompleteCommandLine
2734
2735         New function for tab-completion system
2736         Added by EvilTypeGuy
2737         Thanks to Fett erich@heintz.com
2738         Thanks to taniwha
2739         Enhanced to tab-complete map names by [515]
2740
2741 */
2742 int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console)
2743 {
2744         const char *text = "";
2745         char *s;
2746         const char **list[4] = {0, 0, 0, 0};
2747         char s2[512];
2748         char command[512];
2749         int c, v, a, i, cmd_len, pos, k;
2750         int n; // nicks --blub
2751         const char *space, *patterns;
2752         char vabuf[1024];
2753
2754         char *line;
2755         int linestart, linepos;
2756         unsigned int linesize;
2757         if (is_console)
2758         {
2759                 line = key_line;
2760                 linepos = key_linepos;
2761                 linesize = sizeof(key_line);
2762                 linestart = 1;
2763         }
2764         else
2765         {
2766                 line = chat_buffer;
2767                 linepos = chat_bufferpos;
2768                 linesize = sizeof(chat_buffer);
2769                 linestart = 0;
2770         }
2771
2772         //find what we want to complete
2773         pos = linepos;
2774         while(--pos >= linestart)
2775         {
2776                 k = line[pos];
2777                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2778                         break;
2779         }
2780         pos++;
2781
2782         s = line + pos;
2783         strlcpy(s2, line + linepos, sizeof(s2)); //save chars after cursor
2784         line[linepos] = 0; //hide them
2785
2786         c = v = a = n = cmd_len = 0;
2787         if (!is_console)
2788                 goto nicks;
2789
2790         space = strchr(line + 1, ' ');
2791         if(space && pos == (space - line) + 1)
2792         {
2793                 strlcpy(command, line + 1, min(sizeof(command), (unsigned int)(space - line)));
2794
2795                 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?
2796                 if(patterns && !*patterns)
2797                         patterns = NULL; // get rid of the empty string
2798
2799                 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2800                 {
2801                         //maps search
2802                         char t[MAX_QPATH];
2803                         if (GetMapList(s, t, sizeof(t)))
2804                         {
2805                                 // first move the cursor
2806                                 linepos += (int)strlen(t) - (int)strlen(s);
2807
2808                                 // and now do the actual work
2809                                 *s = 0;
2810                                 strlcat(line, t, MAX_INPUTLINE);
2811                                 strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2812
2813                                 // and fix the cursor
2814                                 if(linepos > (int) strlen(line))
2815                                         linepos = (int) strlen(line);
2816                         }
2817                         return linepos;
2818                 }
2819                 else
2820                 {
2821                         if(patterns)
2822                         {
2823                                 char t[MAX_QPATH];
2824                                 stringlist_t resultbuf, dirbuf;
2825
2826                                 // Usage:
2827                                 //   // store completion patterns (space separated) for command foo in con_completion_foo
2828                                 //   set con_completion_foo "foodata/*.foodefault *.foo"
2829                                 //   foo <TAB>
2830                                 //
2831                                 // Note: patterns with slash are always treated as absolute
2832                                 // patterns; patterns without slash search in the innermost
2833                                 // directory the user specified. There is no way to "complete into"
2834                                 // a directory as of now, as directories seem to be unknown to the
2835                                 // FS subsystem.
2836                                 //
2837                                 // Examples:
2838                                 //   set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2839                                 //   set con_completion_playdemo "*.dem"
2840                                 //   set con_completion_play "*.wav *.ogg"
2841                                 //
2842                                 // TODO somehow add support for directories; these shall complete
2843                                 // to their name + an appended slash.
2844
2845                                 stringlistinit(&resultbuf);
2846                                 stringlistinit(&dirbuf);
2847                                 while(COM_ParseToken_Simple(&patterns, false, false, true))
2848                                 {
2849                                         fssearch_t *search;
2850                                         if(strchr(com_token, '/'))
2851                                         {
2852                                                 search = FS_Search(com_token, true, true, NULL);
2853                                         }
2854                                         else
2855                                         {
2856                                                 const char *slash = strrchr(s, '/');
2857                                                 if(slash)
2858                                                 {
2859                                                         strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2860                                                         strlcat(t, com_token, sizeof(t));
2861                                                         search = FS_Search(t, true, true, NULL);
2862                                                 }
2863                                                 else
2864                                                         search = FS_Search(com_token, true, true, NULL);
2865                                         }
2866                                         if(search)
2867                                         {
2868                                                 for(i = 0; i < search->numfilenames; ++i)
2869                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2870                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2871                                                                         stringlistappend(&resultbuf, search->filenames[i]);
2872                                                 FS_FreeSearch(search);
2873                                         }
2874                                 }
2875
2876                                 // In any case, add directory names
2877                                 {
2878                                         fssearch_t *search;
2879                                         const char *slash = strrchr(s, '/');
2880                                         if(slash)
2881                                         {
2882                                                 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2883                                                 strlcat(t, "*", sizeof(t));
2884                                                 search = FS_Search(t, true, true, NULL);
2885                                         }
2886                                         else
2887                                                 search = FS_Search("*", true, true, NULL);
2888                                         if(search)
2889                                         {
2890                                                 for(i = 0; i < search->numfilenames; ++i)
2891                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2892                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2893                                                                         stringlistappend(&dirbuf, search->filenames[i]);
2894                                                 FS_FreeSearch(search);
2895                                         }
2896                                 }
2897
2898                                 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2899                                 {
2900                                         const char *p, *q;
2901                                         unsigned int matchchars;
2902                                         if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2903                                         {
2904                                                 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2905                                         }
2906                                         else
2907                                         if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2908                                         {
2909                                                 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2910                                         }
2911                                         else
2912                                         {
2913                                                 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2914                                                 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2915                                                 for(i = 0; i < dirbuf.numstrings; ++i)
2916                                                 {
2917                                                         Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2918                                                 }
2919                                                 for(i = 0; i < resultbuf.numstrings; ++i)
2920                                                 {
2921                                                         Con_Printf("%s\n", resultbuf.strings[i]);
2922                                                 }
2923                                                 matchchars = sizeof(t) - 1;
2924                                                 if(resultbuf.numstrings > 0)
2925                                                 {
2926                                                         p = resultbuf.strings[0];
2927                                                         q = resultbuf.strings[resultbuf.numstrings - 1];
2928                                                         for(; *p && *p == *q; ++p, ++q);
2929                                                         matchchars = (unsigned int)(p - resultbuf.strings[0]);
2930                                                 }
2931                                                 if(dirbuf.numstrings > 0)
2932                                                 {
2933                                                         p = dirbuf.strings[0];
2934                                                         q = dirbuf.strings[dirbuf.numstrings - 1];
2935                                                         for(; *p && *p == *q; ++p, ++q);
2936                                                         matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2937                                                 }
2938                                                 // now p points to the first non-equal character, or to the end
2939                                                 // of resultbuf.strings[0]. We want to append the characters
2940                                                 // from resultbuf.strings[0] to (not including) p as these are
2941                                                 // the unique prefix
2942                                                 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2943                                         }
2944
2945                                         // first move the cursor
2946                                         linepos += (int)strlen(t) - (int)strlen(s);
2947
2948                                         // and now do the actual work
2949                                         *s = 0;
2950                                         strlcat(line, t, MAX_INPUTLINE);
2951                                         strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2952
2953                                         // and fix the cursor
2954                                         if(linepos > (int) strlen(line))
2955                                                 linepos = (int) strlen(line);
2956                                 }
2957                                 stringlistfreecontents(&resultbuf);
2958                                 stringlistfreecontents(&dirbuf);
2959
2960                                 return linepos; // bail out, when we complete for a command that wants a file name
2961                         }
2962                 }
2963         }
2964
2965         // Count number of possible matches and print them
2966         c = Cmd_CompleteCountPossible(cmd, s);
2967         if (c)
2968         {
2969                 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2970                 Cmd_CompleteCommandPrint(cmd, s);
2971         }
2972         v = Cvar_CompleteCountPossible(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2973         if (v)
2974         {
2975                 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2976                 Cvar_CompleteCvarPrint(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2977         }
2978         a = Cmd_CompleteAliasCountPossible(cmd, s);
2979         if (a)
2980         {
2981                 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2982                 Cmd_CompleteAliasPrint(cmd, s);
2983         }
2984
2985 nicks:
2986         n = Nicks_CompleteCountPossible(line, linepos, s, is_console);
2987         if (n)
2988         {
2989                 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2990                 Cmd_CompleteNicksPrint(n);
2991         }
2992
2993         if (!(c + v + a + n))   // No possible matches
2994         {
2995                 if(s2[0])
2996                         strlcpy(&line[linepos], s2, linesize - linepos);
2997                 return linepos;
2998         }
2999
3000         if (c)
3001                 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
3002         if (v)
3003                 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
3004         if (a)
3005                 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
3006         if (n)
3007         {
3008                 if (is_console)
3009                         text = *(list[3] = Nicks_CompleteBuildList(n));
3010                 else
3011                         text = *(Nicks_CompleteBuildList(n));
3012         }
3013
3014         for (cmd_len = (int)strlen(s);;cmd_len++)
3015         {
3016                 const char **l;
3017                 for (i = 0; i < 3; i++)
3018                         if (list[i])
3019                                 for (l = list[i];*l;l++)
3020                                         if ((*l)[cmd_len] != text[cmd_len])
3021                                                 goto done;
3022                 // all possible matches share this character, so we continue...
3023                 if (!text[cmd_len])
3024                 {
3025                         // if all matches ended at the same position, stop
3026                         // (this means there is only one match)
3027                         break;
3028                 }
3029         }
3030 done:
3031
3032         // prevent a buffer overrun by limiting cmd_len according to remaining space
3033         cmd_len = min(cmd_len, (int)linesize - 1 - pos);
3034         if (text)
3035         {
3036                 linepos = pos;
3037                 memcpy(&line[linepos], text, cmd_len);
3038                 linepos += cmd_len;
3039                 // if there is only one match, add a space after it
3040                 if (c + v + a + n == 1 && linepos < (int)linesize - 1)
3041                 {
3042                         if(n)
3043                         { // was a nick, might have an offset, and needs colors ;) --blub
3044                                 linepos = pos - Nicks_offset[0];
3045                                 cmd_len = (int)strlen(Nicks_list[0]);
3046                                 cmd_len = min(cmd_len, (int)linesize - 3 - pos);
3047
3048                                 memcpy(&line[linepos] , Nicks_list[0], cmd_len);
3049                                 linepos += cmd_len;
3050                                 if(linepos < (int)(linesize - 7)) // space for color code (^[0-9] or ^xrgb), space and \0
3051                                         linepos = Nicks_AddLastColor(line, linepos);
3052                         }
3053                         line[linepos++] = ' ';
3054                 }
3055         }
3056
3057         // use strlcat to avoid a buffer overrun
3058         line[linepos] = 0;
3059         strlcat(line, s2, linesize);
3060
3061         if (!is_console)
3062                 return linepos;
3063
3064         // free the command, cvar, and alias lists
3065         for (i = 0; i < 4; i++)
3066                 if (list[i])
3067                         Mem_Free((void *)list[i]);
3068
3069         return linepos;
3070 }
3071