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