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