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