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