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