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