]> git.xonotic.org Git - xonotic/darkplaces.git/blob - console.c
curves.c and .h: Remove whitespace at the top of both files
[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 = {CF_CLIENT | CF_ARCHIVE, "con_notifytime","3", "how long notify lines last, in seconds"};
46 cvar_t con_notify = {CF_CLIENT | CF_ARCHIVE, "con_notify","4", "how many notify lines to show"};
47 cvar_t con_notifyalign = {CF_CLIENT | CF_ARCHIVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
48
49 cvar_t con_chattime = {CF_CLIENT | CF_ARCHIVE, "con_chattime","30", "how long chat lines last, in seconds"};
50 cvar_t con_chat = {CF_CLIENT | CF_ARCHIVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
51 cvar_t con_chatpos = {CF_CLIENT | CF_ARCHIVE, "con_chatpos","0", "where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top)"};
52 cvar_t con_chatrect = {CF_CLIENT | CF_ARCHIVE, "con_chatrect","0", "use con_chatrect_x and _y to position con_notify and con_chat freely instead of con_chatpos"};
53 cvar_t con_chatrect_x = {CF_CLIENT | CF_ARCHIVE, "con_chatrect_x","", "where to put chat, relative x coordinate of left edge on screen (use con_chatwidth for width)"};
54 cvar_t con_chatrect_y = {CF_CLIENT | CF_ARCHIVE, "con_chatrect_y","", "where to put chat, relative y coordinate of top edge on screen (use con_chat for line count)"};
55 cvar_t con_chatwidth = {CF_CLIENT | CF_ARCHIVE, "con_chatwidth","1.0", "relative chat window width"};
56 cvar_t con_textsize = {CF_CLIENT | CF_ARCHIVE, "con_textsize","8", "console text size in virtual 2D pixels"};
57 cvar_t con_notifysize = {CF_CLIENT | CF_ARCHIVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
58 cvar_t con_chatsize = {CF_CLIENT | CF_ARCHIVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
59 cvar_t con_chatsound = {CF_CLIENT | CF_ARCHIVE, "con_chatsound","1", "enables chat sound to play on message"};
60 cvar_t con_chatsound_file = {CF_CLIENT, "con_chatsound_file","sound/misc/talk.wav", "The sound to play for chat messages"};
61 cvar_t con_chatsound_team_file = {CF_CLIENT, "con_chatsound_team_file","sound/misc/talk2.wav", "The sound to play for team chat messages"};
62 cvar_t con_chatsound_team_mask = {CF_CLIENT, "con_chatsound_team_mask","40","Magic ASCII code that denotes a team chat message"};
63
64 cvar_t sys_specialcharactertranslation = {CF_CLIENT | CF_SERVER, "sys_specialcharactertranslation", "1", "terminal console conchars to ASCII translation (set to 0 if your conchars.tga is for an 8bit character set or if you want raw output)"};
65 #ifdef WIN32
66 cvar_t sys_colortranslation = {CF_CLIENT | CF_SERVER, "sys_colortranslation", "0", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
67 #else
68 cvar_t sys_colortranslation = {CF_CLIENT | CF_SERVER, "sys_colortranslation", "1", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
69 #endif
70
71
72 cvar_t con_nickcompletion = {CF_CLIENT | CF_ARCHIVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
73 cvar_t con_nickcompletion_flags = {CF_CLIENT | CF_ARCHIVE, "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 = {CF_CLIENT | CF_ARCHIVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
87 cvar_t con_completion_timedemo = {CF_CLIENT | CF_ARCHIVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
88 cvar_t con_completion_exec = {CF_CLIENT | CF_ARCHIVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
89
90 cvar_t condump_stripcolors = {CF_CLIENT | CF_SERVER| CF_ARCHIVE, "condump_stripcolors", "0", "strip color codes from console dumps"};
91
92 cvar_t rcon_password = {CF_CLIENT | CF_SERVER | CF_PRIVATE, "rcon_password", "", "password to authenticate rcon commands; NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"};
93 cvar_t rcon_secure = {CF_CLIENT | CF_SERVER, "rcon_secure", "0", "force secure rcon authentication (1 = time based, 2 = challenge based); NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password"};
94 cvar_t rcon_secure_challengetimeout = {CF_CLIENT, "rcon_secure_challengetimeout", "5", "challenge-based secure rcon: time out requests if no challenge came within this time interval"};
95 cvar_t rcon_address = {CF_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 qbool 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 qbool 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 = {CF_CLIENT | CF_SERVER, "log_file", "", "filename to log messages to"};
409 cvar_t log_file_stripcolors = {CF_CLIENT | CF_SERVER, "log_file_stripcolors", "0", "strip color codes from log messages"};
410 cvar_t log_dest_udp = {CF_CLIENT | CF_SERVER, "log_dest_udp", "", "UDP address to log messages to (in QW rcon compatible format); multiple destinations can be separated by spaces; DO NOT SPECIFY DNS NAMES HERE"};
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         qbool 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 qbool 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 (Sys_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 (Sys_CheckParm ("-condebug") != 0)
886                 Cvar_SetQuick (&log_file, "qconsole.log");
887
888         // register our cvars
889         Cvar_RegisterVariable (&con_chat);
890         Cvar_RegisterVariable (&con_chatpos);
891         Cvar_RegisterVariable (&con_chatrect_x);
892         Cvar_RegisterVariable (&con_chatrect_y);
893         Cvar_RegisterVariable (&con_chatrect);
894         Cvar_RegisterVariable (&con_chatsize);
895         Cvar_RegisterVariable (&con_chattime);
896         Cvar_RegisterVariable (&con_chatwidth);
897         Cvar_RegisterVariable (&con_notify);
898         Cvar_RegisterVariable (&con_notifyalign);
899         Cvar_RegisterVariable (&con_notifysize);
900         Cvar_RegisterVariable (&con_notifytime);
901         Cvar_RegisterVariable (&con_textsize);
902         Cvar_RegisterVariable (&con_chatsound);
903         Cvar_RegisterVariable (&con_chatsound_file);
904         Cvar_RegisterVariable (&con_chatsound_team_file);
905         Cvar_RegisterVariable (&con_chatsound_team_mask);
906
907         // --blub
908         Cvar_RegisterVariable (&con_nickcompletion);
909         Cvar_RegisterVariable (&con_nickcompletion_flags);
910
911         Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
912         Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
913         Cvar_RegisterVariable (&con_completion_exec); // *.cfg
914
915         Cvar_RegisterVariable (&condump_stripcolors);
916
917         Cvar_RegisterVariable(&rcon_address);
918         Cvar_RegisterVariable(&rcon_secure);
919         Cvar_RegisterCallback(&rcon_secure, Con_RCon_ClearPassword_c);
920         Cvar_RegisterVariable(&rcon_secure_challengetimeout);
921         Cvar_RegisterVariable(&rcon_password);
922
923         // register our commands
924         Cmd_AddCommand(CF_CLIENT, "toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
925         Cmd_AddCommand(CF_CLIENT, "messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
926         Cmd_AddCommand(CF_CLIENT, "messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
927         Cmd_AddCommand(CF_CLIENT, "commandmode", Con_CommandMode_f, "input a console command");
928         Cmd_AddCommand(CF_SHARED, "clear", Con_Clear_f, "clear console history");
929         Cmd_AddCommand(CF_SHARED, "maps", Con_Maps_f, "list information about available maps");
930         Cmd_AddCommand(CF_SHARED, "condump", Con_ConDump_f, "output console history to a file (see also log_file)");
931
932         con_initialized = true;
933         // initialize console window (only used by sys_win.c)
934         Sys_InitConsole();
935         
936         Con_Print("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, qbool 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 qbool 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(qbool is_console, float x, float v, float inputsize)
1528 {
1529         int y, i, col_out, linepos, text_start, prefix_start = 0;
1530         char text[MAX_INPUTLINE + 5 + 9 + 1]; // space for ^xRGB, "say_team:" and \0
1531         float xo;
1532         size_t len_out;
1533         const 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, qbool 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, qbool 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 qbool 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, NULL);
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 if(!memcmp(buf, "VBSP", 4))
2209                         {
2210                                 hl2dheader_t *header = (hl2dheader_t *)buf;
2211                                 lumpofs = LittleLong(header->lumps[HL2LUMP_ENTITIES].fileofs);
2212                                 lumplen = LittleLong(header->lumps[HL2LUMP_ENTITIES].filelen);
2213                                 dpsnprintf(desc, sizeof(desc), "VBSP%i", LittleLong(((int *)buf)[1]));
2214                         }
2215                         else
2216                                 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2217                         strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2218                         memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2219                         entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2220                         if (!entities && lumplen >= 10)
2221                         {
2222                                 FS_Seek(f, lumpofs, SEEK_SET);
2223                                 entities = (char *)Z_Malloc(lumplen + 1);
2224                                 FS_Read(f, entities, lumplen);
2225                         }
2226                         if (entities)
2227                         {
2228                                 // if there are entities to parse, a missing message key just
2229                                 // means there is no title, so clear the message string now
2230                                 message[0] = 0;
2231                                 data = entities;
2232                                 for (;;)
2233                                 {
2234                                         int l;
2235                                         if (!COM_ParseToken_Simple(&data, false, false, true))
2236                                                 break;
2237                                         if (com_token[0] == '{')
2238                                                 continue;
2239                                         if (com_token[0] == '}')
2240                                                 break;
2241                                         // skip leading whitespace
2242                                         for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2243                                         for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2244                                                 keyname[l] = com_token[k+l];
2245                                         keyname[l] = 0;
2246                                         if (!COM_ParseToken_Simple(&data, false, false, true))
2247                                                 break;
2248                                         if (developer_extra.integer)
2249                                                 Con_DPrintf("key: %s %s\n", keyname, com_token);
2250                                         if (!strcmp(keyname, "message"))
2251                                         {
2252                                                 // get the message contents
2253                                                 strlcpy(message, com_token, sizeof(message));
2254                                                 break;
2255                                         }
2256                                 }
2257                         }
2258                 }
2259                 if (entities)
2260                         Z_Free(entities);
2261                 if(f)
2262                         FS_Close(f);
2263                 *(t->filenames[i]+len[i]+5) = 0;
2264                 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2265         }
2266         Con_Print("\n");
2267         for(p=o;p<min;p++)
2268         {
2269                 k = *(t->filenames[0]+5+p);
2270                 if(k == 0)
2271                         goto endcomplete;
2272                 for(i=1;i<t->numfilenames;i++)
2273                         if(*(t->filenames[i]+5+p) != k)
2274                                 goto endcomplete;
2275         }
2276 endcomplete:
2277         if(p > o && completedname && completednamebufferlength > 0)
2278         {
2279                 memset(completedname, 0, completednamebufferlength);
2280                 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2281         }
2282         Z_Free(len);
2283         FS_FreeSearch(t);
2284         return p > o;
2285 }
2286
2287 /*
2288         Con_DisplayList
2289
2290         New function for tab-completion system
2291         Added by EvilTypeGuy
2292         MEGA Thanks to Taniwha
2293
2294 */
2295 void Con_DisplayList(const char **list)
2296 {
2297         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2298         const char **walk = list;
2299
2300         while (*walk) {
2301                 len = (int)strlen(*walk);
2302                 if (len > maxlen)
2303                         maxlen = len;
2304                 walk++;
2305         }
2306         maxlen += 1;
2307
2308         while (*list) {
2309                 len = (int)strlen(*list);
2310                 if (pos + maxlen >= width) {
2311                         Con_Print("\n");
2312                         pos = 0;
2313                 }
2314
2315                 Con_Print(*list);
2316                 for (i = 0; i < (maxlen - len); i++)
2317                         Con_Print(" ");
2318
2319                 pos += maxlen;
2320                 list++;
2321         }
2322
2323         if (pos)
2324                 Con_Print("\n\n");
2325 }
2326
2327
2328 // Now it becomes TRICKY :D --blub
2329 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];     // contains the nicks with colors and all that
2330 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];  // sanitized list for completion when there are other possible matches.
2331 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2332 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
2333 static int Nicks_matchpos;
2334
2335 // co against <<:BLASTER:>> is true!?
2336 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2337 {
2338         while(a_len)
2339         {
2340                 if(tolower(*a) == tolower(*b))
2341                 {
2342                         if(*a == 0)
2343                                 return 0;
2344                         --a_len;
2345                         ++a;
2346                         ++b;
2347                         continue;
2348                 }
2349                 if(!*a)
2350                         return -1;
2351                 if(!*b)
2352                         return 1;
2353                 if(*a == ' ')
2354                         return (*a < *b) ? -1 : 1;
2355                 if(*b == ' ')
2356                         ++b;
2357                 else
2358                         return (*a < *b) ? -1 : 1;
2359         }
2360         return 0;
2361 }
2362 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2363 {
2364         char space_char;
2365         if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2366         {
2367                 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2368                         return Nicks_strncasecmp_nospaces(a, b, a_len);
2369                 return strncasecmp(a, b, a_len);
2370         }
2371
2372         space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2373
2374         // ignore non alphanumerics of B
2375         // if A contains a non-alphanumeric, B must contain it as well though!
2376         while(a_len)
2377         {
2378                 qbool alnum_a, alnum_b;
2379
2380                 if(tolower(*a) == tolower(*b))
2381                 {
2382                         if(*a == 0) // end of both strings, they're equal
2383                                 return 0;
2384                         --a_len;
2385                         ++a;
2386                         ++b;
2387                         continue;
2388                 }
2389                 // not equal, end of one string?
2390                 if(!*a)
2391                         return -1;
2392                 if(!*b)
2393                         return 1;
2394                 // ignore non alphanumerics
2395                 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2396                 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2397                 if(!alnum_a) // b must contain this
2398                         return (*a < *b) ? -1 : 1;
2399                 if(!alnum_b)
2400                         ++b;
2401                 // otherwise, both are alnum, they're just not equal, return the appropriate number
2402                 else
2403                         return (*a < *b) ? -1 : 1;
2404         }
2405         return 0;
2406 }
2407
2408
2409 /* Nicks_CompleteCountPossible
2410
2411    Count the number of possible nicks to complete
2412  */
2413 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qbool isCon)
2414 {
2415         char name[MAX_SCOREBOARDNAME];
2416         int i, p;
2417         int match;
2418         int spos;
2419         int count = 0;
2420
2421         if(!con_nickcompletion.integer)
2422                 return 0;
2423
2424         // changed that to 1
2425         if(!line[0])// || !line[1]) // we want at least... 2 written characters
2426                 return 0;
2427
2428         for(i = 0; i < cl.maxclients; ++i)
2429         {
2430                 p = i;
2431                 if(!cl.scores[p].name[0])
2432                         continue;
2433
2434                 SanitizeString(cl.scores[p].name, name);
2435                 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2436
2437                 if(!name[0])
2438                         continue;
2439
2440                 match = -1;
2441                 spos = pos - 1; // no need for a minimum of characters :)
2442
2443                 while(spos >= 0)
2444                 {
2445                         if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2446                         {
2447                                 if(!(isCon && spos == 1)) // console start
2448                                 {
2449                                         --spos;
2450                                         continue;
2451                                 }
2452                         }
2453                         if(isCon && spos == 0)
2454                                 break;
2455                         if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2456                                 match = spos;
2457                         --spos;
2458                 }
2459                 if(match < 0)
2460                         continue;
2461                 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2462                 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2463
2464                 // the sanitized list
2465                 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2466                 if(!count)
2467                 {
2468                         Nicks_matchpos = match;
2469                 }
2470
2471                 Nicks_offset[count] = s - (&line[match]);
2472                 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2473
2474                 ++count;
2475         }
2476         return count;
2477 }
2478
2479 static void Cmd_CompleteNicksPrint(int count)
2480 {
2481         int i;
2482         for(i = 0; i < count; ++i)
2483                 Con_Printf("%s\n", Nicks_list[i]);
2484 }
2485
2486 static void Nicks_CutMatchesNormal(int count)
2487 {
2488         // cut match 0 down to the longest possible completion
2489         int i;
2490         unsigned int c, l;
2491         c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2492         for(i = 1; i < count; ++i)
2493         {
2494                 l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2495                 if(l < c)
2496                         c = l;
2497
2498                 for(l = 0; l <= c; ++l)
2499                         if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2500                         {
2501                                 c = l-1;
2502                                 break;
2503                         }
2504         }
2505         Nicks_sanlist[0][c+1] = 0;
2506         //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2507 }
2508
2509 static unsigned int Nicks_strcleanlen(const char *s)
2510 {
2511         unsigned int l = 0;
2512         while(*s)
2513         {
2514                 if( (*s >= 'a' && *s <= 'z') ||
2515                     (*s >= 'A' && *s <= 'Z') ||
2516                     (*s >= '0' && *s <= '9') ||
2517                     *s == ' ')
2518                         ++l;
2519                 ++s;
2520         }
2521         return l;
2522 }
2523
2524 static void Nicks_CutMatchesAlphaNumeric(int count)
2525 {
2526         // cut match 0 down to the longest possible completion
2527         int i;
2528         unsigned int c, l;
2529         char tempstr[sizeof(Nicks_sanlist[0])];
2530         char *a, *b;
2531         char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2532
2533         c = (unsigned int)strlen(Nicks_sanlist[0]);
2534         for(i = 0, l = 0; i < (int)c; ++i)
2535         {
2536                 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2537                     (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2538                     (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2539                 {
2540                         tempstr[l++] = Nicks_sanlist[0][i];
2541                 }
2542         }
2543         tempstr[l] = 0;
2544
2545         for(i = 1; i < count; ++i)
2546         {
2547                 a = tempstr;
2548                 b = Nicks_sanlist[i];
2549                 while(1)
2550                 {
2551                         if(!*a)
2552                                 break;
2553                         if(!*b)
2554                         {
2555                                 *a = 0;
2556                                 break;
2557                         }
2558                         if(tolower(*a) == tolower(*b))
2559                         {
2560                                 ++a;
2561                                 ++b;
2562                                 continue;
2563                         }
2564                         if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2565                         {
2566                                 // b is alnum, so cut
2567                                 *a = 0;
2568                                 break;
2569                         }
2570                         ++b;
2571                 }
2572         }
2573         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2574         Nicks_CutMatchesNormal(count);
2575         //if(!Nicks_sanlist[0][0])
2576         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2577         {
2578                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2579                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2580         }
2581 }
2582
2583 static void Nicks_CutMatchesNoSpaces(int count)
2584 {
2585         // cut match 0 down to the longest possible completion
2586         int i;
2587         unsigned int c, l;
2588         char tempstr[sizeof(Nicks_sanlist[0])];
2589         char *a, *b;
2590
2591         c = (unsigned int)strlen(Nicks_sanlist[0]);
2592         for(i = 0, l = 0; i < (int)c; ++i)
2593         {
2594                 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2595                 {
2596                         tempstr[l++] = Nicks_sanlist[0][i];
2597                 }
2598         }
2599         tempstr[l] = 0;
2600
2601         for(i = 1; i < count; ++i)
2602         {
2603                 a = tempstr;
2604                 b = Nicks_sanlist[i];
2605                 while(1)
2606                 {
2607                         if(!*a)
2608                                 break;
2609                         if(!*b)
2610                         {
2611                                 *a = 0;
2612                                 break;
2613                         }
2614                         if(tolower(*a) == tolower(*b))
2615                         {
2616                                 ++a;
2617                                 ++b;
2618                                 continue;
2619                         }
2620                         if(*b != ' ')
2621                         {
2622                                 *a = 0;
2623                                 break;
2624                         }
2625                         ++b;
2626                 }
2627         }
2628         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2629         Nicks_CutMatchesNormal(count);
2630         //if(!Nicks_sanlist[0][0])
2631         //Con_Printf("TS: %s\n", tempstr);
2632         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2633         {
2634                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2635                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2636         }
2637 }
2638
2639 static void Nicks_CutMatches(int count)
2640 {
2641         if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2642                 Nicks_CutMatchesAlphaNumeric(count);
2643         else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2644                 Nicks_CutMatchesNoSpaces(count);
2645         else
2646                 Nicks_CutMatchesNormal(count);
2647 }
2648
2649 static const char **Nicks_CompleteBuildList(int count)
2650 {
2651         const char **buf;
2652         int bpos = 0;
2653         // the list is freed by Con_CompleteCommandLine, so create a char**
2654         buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2655
2656         for(; bpos < count; ++bpos)
2657                 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2658
2659         Nicks_CutMatches(count);
2660
2661         buf[bpos] = NULL;
2662         return buf;
2663 }
2664
2665 /*
2666         Nicks_AddLastColor
2667         Restores the previous used color, after the autocompleted name.
2668 */
2669 static int Nicks_AddLastColor(char *buffer, int pos)
2670 {
2671         qbool quote_added = false;
2672         int match;
2673         int color = STRING_COLOR_DEFAULT + '0';
2674         char r = 0, g = 0, b = 0;
2675
2676         if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2677         {
2678                 // we'll have to add a quote :)
2679                 buffer[pos++] = '\"';
2680                 quote_added = true;
2681         }
2682
2683         if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2684         {
2685                 // add color when no quote was added, or when flags &4?
2686                 // find last color
2687                 for(match = Nicks_matchpos-1; match >= 0; --match)
2688                 {
2689                         if(buffer[match] == STRING_COLOR_TAG)
2690                         {
2691                                 if( isdigit(buffer[match+1]) )
2692                                 {
2693                                         color = buffer[match+1];
2694                                         break;
2695                                 }
2696                                 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2697                                 {
2698                                         if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2699                                         {
2700                                                 r = buffer[match+2];
2701                                                 g = buffer[match+3];
2702                                                 b = buffer[match+4];
2703                                                 color = -1;
2704                                                 break;
2705                                         }
2706                                 }
2707                         }
2708                 }
2709                 if(!quote_added)
2710                 {
2711                         if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2712                                 pos -= 2;
2713                         else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2714                                          && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2715                                 pos -= 5;
2716                 }
2717                 buffer[pos++] = STRING_COLOR_TAG;
2718                 if (color == -1)
2719                 {
2720                         buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2721                         buffer[pos++] = r;
2722                         buffer[pos++] = g;
2723                         buffer[pos++] = b;
2724                 }
2725                 else
2726                         buffer[pos++] = color;
2727         }
2728         return pos;
2729 }
2730
2731 /*
2732         Con_CompleteCommandLine
2733
2734         New function for tab-completion system
2735         Added by EvilTypeGuy
2736         Thanks to Fett erich@heintz.com
2737         Thanks to taniwha
2738         Enhanced to tab-complete map names by [515]
2739
2740 */
2741 int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console)
2742 {
2743         const char *text = "";
2744         char *s;
2745         const char **list[4] = {0, 0, 0, 0};
2746         char s2[512];
2747         char command[512];
2748         int c, v, a, i, cmd_len, pos, k;
2749         int n; // nicks --blub
2750         const char *space, *patterns;
2751         char vabuf[1024];
2752
2753         char *line;
2754         int linestart, linepos;
2755         unsigned int linesize;
2756         if (is_console)
2757         {
2758                 line = key_line;
2759                 linepos = key_linepos;
2760                 linesize = sizeof(key_line);
2761                 linestart = 1;
2762         }
2763         else
2764         {
2765                 line = chat_buffer;
2766                 linepos = chat_bufferpos;
2767                 linesize = sizeof(chat_buffer);
2768                 linestart = 0;
2769         }
2770
2771         //find what we want to complete
2772         pos = linepos;
2773         while(--pos >= linestart)
2774         {
2775                 k = line[pos];
2776                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2777                         break;
2778         }
2779         pos++;
2780
2781         s = line + pos;
2782         strlcpy(s2, line + linepos, sizeof(s2)); //save chars after cursor
2783         line[linepos] = 0; //hide them
2784
2785         c = v = a = n = cmd_len = 0;
2786         if (!is_console)
2787                 goto nicks;
2788
2789         space = strchr(line + 1, ' ');
2790         if(space && pos == (space - line) + 1)
2791         {
2792                 strlcpy(command, line + 1, min(sizeof(command), (unsigned int)(space - line)));
2793
2794                 patterns = Cvar_VariableString(cmd->cvars, va(vabuf, sizeof(vabuf), "con_completion_%s", command), CF_CLIENT | CF_SERVER); // TODO maybe use a better place for this?
2795                 if(patterns && !*patterns)
2796                         patterns = NULL; // get rid of the empty string
2797
2798                 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2799                 {
2800                         //maps search
2801                         char t[MAX_QPATH];
2802                         if (GetMapList(s, t, sizeof(t)))
2803                         {
2804                                 // first move the cursor
2805                                 linepos += (int)strlen(t) - (int)strlen(s);
2806
2807                                 // and now do the actual work
2808                                 *s = 0;
2809                                 strlcat(line, t, MAX_INPUTLINE);
2810                                 strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2811
2812                                 // and fix the cursor
2813                                 if(linepos > (int) strlen(line))
2814                                         linepos = (int) strlen(line);
2815                         }
2816                         return linepos;
2817                 }
2818                 else
2819                 {
2820                         if(patterns)
2821                         {
2822                                 char t[MAX_QPATH];
2823                                 stringlist_t resultbuf, dirbuf;
2824
2825                                 // Usage:
2826                                 //   // store completion patterns (space separated) for command foo in con_completion_foo
2827                                 //   set con_completion_foo "foodata/*.foodefault *.foo"
2828                                 //   foo <TAB>
2829                                 //
2830                                 // Note: patterns with slash are always treated as absolute
2831                                 // patterns; patterns without slash search in the innermost
2832                                 // directory the user specified. There is no way to "complete into"
2833                                 // a directory as of now, as directories seem to be unknown to the
2834                                 // FS subsystem.
2835                                 //
2836                                 // Examples:
2837                                 //   set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2838                                 //   set con_completion_playdemo "*.dem"
2839                                 //   set con_completion_play "*.wav *.ogg"
2840                                 //
2841                                 // TODO somehow add support for directories; these shall complete
2842                                 // to their name + an appended slash.
2843
2844                                 stringlistinit(&resultbuf);
2845                                 stringlistinit(&dirbuf);
2846                                 while(COM_ParseToken_Simple(&patterns, false, false, true))
2847                                 {
2848                                         fssearch_t *search;
2849                                         if(strchr(com_token, '/'))
2850                                         {
2851                                                 search = FS_Search(com_token, true, true, NULL);
2852                                         }
2853                                         else
2854                                         {
2855                                                 const char *slash = strrchr(s, '/');
2856                                                 if(slash)
2857                                                 {
2858                                                         strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2859                                                         strlcat(t, com_token, sizeof(t));
2860                                                         search = FS_Search(t, true, true, NULL);
2861                                                 }
2862                                                 else
2863                                                         search = FS_Search(com_token, true, true, NULL);
2864                                         }
2865                                         if(search)
2866                                         {
2867                                                 for(i = 0; i < search->numfilenames; ++i)
2868                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2869                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2870                                                                         stringlistappend(&resultbuf, search->filenames[i]);
2871                                                 FS_FreeSearch(search);
2872                                         }
2873                                 }
2874
2875                                 // In any case, add directory names
2876                                 {
2877                                         fssearch_t *search;
2878                                         const char *slash = strrchr(s, '/');
2879                                         if(slash)
2880                                         {
2881                                                 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2882                                                 strlcat(t, "*", sizeof(t));
2883                                                 search = FS_Search(t, true, true, NULL);
2884                                         }
2885                                         else
2886                                                 search = FS_Search("*", true, true, NULL);
2887                                         if(search)
2888                                         {
2889                                                 for(i = 0; i < search->numfilenames; ++i)
2890                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2891                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2892                                                                         stringlistappend(&dirbuf, search->filenames[i]);
2893                                                 FS_FreeSearch(search);
2894                                         }
2895                                 }
2896
2897                                 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2898                                 {
2899                                         const char *p, *q;
2900                                         unsigned int matchchars;
2901                                         if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2902                                         {
2903                                                 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2904                                         }
2905                                         else
2906                                         if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2907                                         {
2908                                                 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2909                                         }
2910                                         else
2911                                         {
2912                                                 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2913                                                 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2914                                                 for(i = 0; i < dirbuf.numstrings; ++i)
2915                                                 {
2916                                                         Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2917                                                 }
2918                                                 for(i = 0; i < resultbuf.numstrings; ++i)
2919                                                 {
2920                                                         Con_Printf("%s\n", resultbuf.strings[i]);
2921                                                 }
2922                                                 matchchars = sizeof(t) - 1;
2923                                                 if(resultbuf.numstrings > 0)
2924                                                 {
2925                                                         p = resultbuf.strings[0];
2926                                                         q = resultbuf.strings[resultbuf.numstrings - 1];
2927                                                         for(; *p && *p == *q; ++p, ++q);
2928                                                         matchchars = (unsigned int)(p - resultbuf.strings[0]);
2929                                                 }
2930                                                 if(dirbuf.numstrings > 0)
2931                                                 {
2932                                                         p = dirbuf.strings[0];
2933                                                         q = dirbuf.strings[dirbuf.numstrings - 1];
2934                                                         for(; *p && *p == *q; ++p, ++q);
2935                                                         matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2936                                                 }
2937                                                 // now p points to the first non-equal character, or to the end
2938                                                 // of resultbuf.strings[0]. We want to append the characters
2939                                                 // from resultbuf.strings[0] to (not including) p as these are
2940                                                 // the unique prefix
2941                                                 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2942                                         }
2943
2944                                         // first move the cursor
2945                                         linepos += (int)strlen(t) - (int)strlen(s);
2946
2947                                         // and now do the actual work
2948                                         *s = 0;
2949                                         strlcat(line, t, MAX_INPUTLINE);
2950                                         strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2951
2952                                         // and fix the cursor
2953                                         if(linepos > (int) strlen(line))
2954                                                 linepos = (int) strlen(line);
2955                                 }
2956                                 stringlistfreecontents(&resultbuf);
2957                                 stringlistfreecontents(&dirbuf);
2958
2959                                 return linepos; // bail out, when we complete for a command that wants a file name
2960                         }
2961                 }
2962         }
2963
2964         // Count number of possible matches and print them
2965         c = Cmd_CompleteCountPossible(cmd, s);
2966         if (c)
2967         {
2968                 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2969                 Cmd_CompleteCommandPrint(cmd, s);
2970         }
2971         v = Cvar_CompleteCountPossible(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2972         if (v)
2973         {
2974                 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2975                 Cvar_CompleteCvarPrint(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2976         }
2977         a = Cmd_CompleteAliasCountPossible(cmd, s);
2978         if (a)
2979         {
2980                 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2981                 Cmd_CompleteAliasPrint(cmd, s);
2982         }
2983
2984 nicks:
2985         n = Nicks_CompleteCountPossible(line, linepos, s, is_console);
2986         if (n)
2987         {
2988                 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2989                 Cmd_CompleteNicksPrint(n);
2990         }
2991
2992         if (!(c + v + a + n))   // No possible matches
2993         {
2994                 if(s2[0])
2995                         strlcpy(&line[linepos], s2, linesize - linepos);
2996                 return linepos;
2997         }
2998
2999         if (c)
3000                 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
3001         if (v)
3002                 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
3003         if (a)
3004                 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
3005         if (n)
3006         {
3007                 if (is_console)
3008                         text = *(list[3] = Nicks_CompleteBuildList(n));
3009                 else
3010                         text = *(Nicks_CompleteBuildList(n));
3011         }
3012
3013         for (cmd_len = (int)strlen(s);;cmd_len++)
3014         {
3015                 const char **l;
3016                 for (i = 0; i < 3; i++)
3017                         if (list[i])
3018                                 for (l = list[i];*l;l++)
3019                                         if ((*l)[cmd_len] != text[cmd_len])
3020                                                 goto done;
3021                 // all possible matches share this character, so we continue...
3022                 if (!text[cmd_len])
3023                 {
3024                         // if all matches ended at the same position, stop
3025                         // (this means there is only one match)
3026                         break;
3027                 }
3028         }
3029 done:
3030
3031         // prevent a buffer overrun by limiting cmd_len according to remaining space
3032         cmd_len = min(cmd_len, (int)linesize - 1 - pos);
3033         if (text)
3034         {
3035                 linepos = pos;
3036                 memcpy(&line[linepos], text, cmd_len);
3037                 linepos += cmd_len;
3038                 // if there is only one match, add a space after it
3039                 if (c + v + a + n == 1 && linepos < (int)linesize - 1)
3040                 {
3041                         if(n)
3042                         { // was a nick, might have an offset, and needs colors ;) --blub
3043                                 linepos = pos - Nicks_offset[0];
3044                                 cmd_len = (int)strlen(Nicks_list[0]);
3045                                 cmd_len = min(cmd_len, (int)linesize - 3 - pos);
3046
3047                                 memcpy(&line[linepos] , Nicks_list[0], cmd_len);
3048                                 linepos += cmd_len;
3049                                 if(linepos < (int)(linesize - 7)) // space for color code (^[0-9] or ^xrgb), space and \0
3050                                         linepos = Nicks_AddLastColor(line, linepos);
3051                         }
3052                         line[linepos++] = ' ';
3053                 }
3054         }
3055
3056         // use strlcat to avoid a buffer overrun
3057         line[linepos] = 0;
3058         strlcat(line, s2, linesize);
3059
3060         if (!is_console)
3061                 return linepos;
3062
3063         // free the command, cvar, and alias lists
3064         for (i = 0; i < 4; i++)
3065                 if (list[i])
3066                         Mem_Free((void *)list[i]);
3067
3068         return linepos;
3069 }
3070