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