]> git.xonotic.org Git - xonotic/darkplaces.git/blob - console.c
96ea7af74589f0188fde4ff9b90c7c8c6c84c5f5
[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  * @brief      Returns a horizontal line
1508  * @details    Returns a graphical horizontal line of length len, but never wider than the
1509  *             console. Includes a newline, unless len is >= to the console width
1510  * @note       Authored by johnfitz
1511  *
1512  * @param[in]  len   Length of the horizontal line
1513  *
1514  * @return     A string of the line
1515  */
1516 const char *Con_Quakebar(int len, char *bar, size_t barsize)
1517 {
1518         assert(barsize >= 5);
1519
1520         len = min(len, (int)barsize - 2);
1521         len = min(len, con_linewidth);
1522
1523         bar[0] = '\35';
1524         memset(&bar[1], '\36', len - 2);
1525         bar[len - 1] = '\37';
1526
1527         if (len < con_linewidth)
1528         {
1529                 bar[len] = '\n';
1530                 bar[len + 1] = 0;
1531         }
1532         else
1533                 bar[len] = 0;
1534
1535         return bar;
1536 }
1537
1538 /**
1539  * @brief      Left-pad a string with spaces to make it appear centered
1540  * @note       Authored by johnfitz
1541  *
1542  * @param[in]  maxLineLength  Center-align
1543  * @param[in]  fmt        A printf format string
1544  * @param[in]  <unnamed>  Zero or more values used by fmt
1545  */
1546 void Con_CenterPrintf(int maxLineLength, const char *fmt, ...)
1547 {
1548         va_list argptr;
1549         char    msg[MAX_INPUTLINE];  // the original message
1550         char    line[MAX_INPUTLINE]; // one line from the message
1551         char    spaces[21];          // buffer for spaces
1552         char   *msgCursor, *lineEnding;
1553         int     lineLength, msgLength;
1554         size_t  indentSize;
1555
1556         va_start(argptr, fmt);
1557         msgLength = dpvsnprintf(msg, sizeof (msg), fmt, argptr);
1558         va_end(argptr);
1559
1560         if (msgLength < 0)
1561         {
1562                 Con_Printf(CON_WARN "The message given to Con_CenterPrintf was too long\n");
1563                 return;
1564         }
1565
1566         maxLineLength = min(maxLineLength, con_linewidth);
1567
1568         for (msgCursor = msg; *msgCursor;)
1569         {
1570                 lineEnding = strchr(msgCursor, '\n');
1571                 if (lineEnding)
1572                 {
1573                         lineLength = dp_ustr2stp(line, sizeof(line), msgCursor, lineEnding - msgCursor) - line;
1574                         msgCursor = lineEnding + 1;
1575                 }
1576                 else // last line
1577                 {
1578                         lineLength = dp_strlcpy(line, msgCursor, sizeof(line));
1579                         msgCursor = msg + msgLength;
1580                 }
1581
1582                 if (lineLength < maxLineLength)
1583                 {
1584                         indentSize = min(sizeof(spaces) - 1, (size_t)(maxLineLength - lineLength) / 2);
1585                         memset(spaces, ' ', indentSize);
1586                         spaces[indentSize] = 0;
1587                         Con_MaskPrintf(CON_MASK_HIDENOTIFY, "%s%s\n", spaces, line);
1588                 }
1589                 else
1590                         Con_MaskPrintf(CON_MASK_HIDENOTIFY, "%s\n", line);
1591         }
1592 }
1593
1594 /**
1595  * @brief      Prints a center-aligned message to the console
1596  * @note       Authored by johnfitz
1597  *
1598  * @param[in]  str   A multiline string to print
1599  */
1600 void Con_CenterPrint(const char *str)
1601 {
1602         char bar[42];
1603
1604         Con_MaskPrintf(CON_MASK_HIDENOTIFY, "%s", Con_Quakebar(40, bar, sizeof(bar)));
1605         Con_CenterPrintf(40, "%s\n", str);
1606         Con_MaskPrintf(CON_MASK_HIDENOTIFY, "%s", bar);
1607 }
1608
1609
1610
1611 /*
1612 ==============================================================================
1613
1614 DRAWING
1615
1616 ==============================================================================
1617 */
1618
1619 /*
1620 ================
1621 Con_DrawInput
1622
1623 It draws either the console input line or the chat input line (if is_console is false)
1624 The input line scrolls horizontally if typing goes beyond the right edge
1625
1626 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1627 ================
1628 */
1629 static void Con_DrawInput(qbool is_console, float x, float v, float inputsize)
1630 {
1631         int y, i, col_out, linepos, text_start, prefix_start = 0;
1632         char text[MAX_INPUTLINE + 5 + 9 + 1]; // space for ^xRGB, "say_team:" and \0
1633         float xo;
1634         size_t len_out;
1635         const char *prefix;
1636         dp_font_t *fnt;
1637
1638         if (is_console && !key_consoleactive)
1639                 return;         // don't draw anything
1640
1641         if (is_console)
1642         {
1643                 // empty prefix because ] is part of the console edit line
1644                 prefix = "";
1645                 dp_strlcpy(text, key_line, sizeof(text));
1646                 linepos = key_linepos;
1647                 fnt = FONT_CONSOLE;
1648         }
1649         else
1650         {
1651                 if (chat_mode < 0)
1652                         prefix = "]";
1653                 else if(chat_mode)
1654                         prefix = "say_team:";
1655                 else
1656                         prefix = "say:";
1657                 dp_strlcpy(text, chat_buffer, sizeof(text));
1658                 linepos = chat_bufferpos;
1659                 fnt = FONT_CHAT;
1660         }
1661
1662         y = (int)strlen(text);
1663
1664         // make the color code visible when the cursor is inside it
1665         if(text[linepos] != 0)
1666         {
1667                 for(i=1; i < 5 && linepos - i > 0; ++i)
1668                         if(text[linepos-i] == STRING_COLOR_TAG)
1669                         {
1670                                 int caret_pos, ofs = 0;
1671                                 caret_pos = linepos - i;
1672                                 if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG)
1673                                         ofs = 1;
1674                                 else if(i == 1 && isdigit(text[caret_pos+1]))
1675                                         ofs = 2;
1676                                 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]))
1677                                         ofs = 5;
1678                                 if(ofs && (size_t)(y + ofs + 1) < sizeof(text))
1679                                 {
1680                                         int carets = 1;
1681                                         while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG)
1682                                                 ++carets;
1683                                         if(carets & 1)
1684                                         {
1685                                                 // str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing)
1686                                                 // str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing)
1687                                                 memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos);
1688                                                 text[caret_pos + ofs] = STRING_COLOR_TAG;
1689                                                 y += ofs + 1;
1690                                                 text[y] = 0;
1691                                         }
1692                                 }
1693                                 break;
1694                         }
1695         }
1696
1697         if (!is_console)
1698         {
1699                 prefix_start = x;
1700                 x += DrawQ_TextWidth(prefix, 0, inputsize, inputsize, false, fnt);
1701         }
1702
1703         len_out = linepos;
1704         col_out = -1;
1705         xo = 0;
1706         if (linepos > 0)
1707                 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, inputsize, inputsize, &col_out, false, fnt, 1000000000);
1708
1709         text_start = x + (vid_conwidth.value - x) * 0.95 - xo; // scroll
1710         if(text_start >= x)
1711                 text_start = x;
1712         else if (!is_console)
1713                 prefix_start -= (x - text_start);
1714
1715         if (!is_console)
1716                 DrawQ_String(prefix_start, v, prefix, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1717
1718         DrawQ_String(text_start, v, text, y + 3, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1719
1720         // draw a cursor on top of this
1721         if ((int)(host.realtime*con_cursorspeed) & 1)           // cursor is visible
1722         {
1723                 if (!utf8_enable.integer)
1724                 {
1725                         text[0] = 11 + 130 * key_insert;        // either solid or triangle facing right
1726                         text[1] = 0;
1727                 }
1728                 else
1729                 {
1730                         size_t len;
1731                         const char *curbuf;
1732                         char charbuf16[16];
1733                         curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1734                         memcpy(text, curbuf, len);
1735                         text[len] = 0;
1736                 }
1737                 DrawQ_String(text_start + xo, v, text, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, fnt);
1738         }
1739 }
1740
1741 typedef struct
1742 {
1743         dp_font_t *font;
1744         float alignment; // 0 = left, 0.5 = center, 1 = right
1745         float fontsize;
1746         float x;
1747         float y;
1748         float width;
1749         float ymin, ymax;
1750         const char *continuationString;
1751
1752         // PRIVATE:
1753         int colorindex; // init to -1
1754 }
1755 con_text_info_t;
1756
1757 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1758 {
1759         con_text_info_t *ti = (con_text_info_t *) passthrough;
1760         if(w == NULL)
1761         {
1762                 ti->colorindex = -1;
1763                 return ti->fontsize * ti->font->maxwidth;
1764         }
1765         if(maxWidth >= 0)
1766                 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1767         else if(maxWidth == -1)
1768                 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1769         else
1770         {
1771                 Sys_Printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1772                 // Note: this is NOT a Con_Printf, as it could print recursively
1773                 return 0;
1774         }
1775 }
1776
1777 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1778 {
1779         (void) passthrough;
1780         (void) line;
1781         (void) length;
1782         (void) width;
1783         (void) isContinuation;
1784         return 1;
1785 }
1786
1787 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1788 {
1789         con_text_info_t *ti = (con_text_info_t *) passthrough;
1790
1791         if(ti->y < ti->ymin - 0.001)
1792                 (void) 0;
1793         else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1794                 (void) 0;
1795         else
1796         {
1797                 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1798                 if(isContinuation && *ti->continuationString)
1799                         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);
1800                 if(length > 0)
1801                         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);
1802         }
1803
1804         ti->y += ti->fontsize;
1805         return 1;
1806 }
1807
1808 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)
1809 {
1810         int i;
1811         int lines = 0;
1812         int maxlines = (int) floor(height / fontsize + 0.01f);
1813         int startidx;
1814         int nskip = 0;
1815         int continuationWidth = 0;
1816         size_t len;
1817         double t = cl.time; // saved so it won't change
1818         con_text_info_t ti;
1819
1820         ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1821         ti.fontsize = fontsize;
1822         ti.alignment = alignment_x;
1823         ti.width = width;
1824         ti.ymin = y;
1825         ti.ymax = y + height;
1826         ti.continuationString = continuationString;
1827
1828         len = 0;
1829         Con_WordWidthFunc(&ti, NULL, &len, -1);
1830         len = strlen(continuationString);
1831         continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &len, -1);
1832
1833         // first find the first line to draw by backwards iterating and word wrapping to find their length...
1834         startidx = CON_LINES_COUNT;
1835         for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1836         {
1837                 con_lineinfo_t *l = &CON_LINES(i);
1838                 int mylines;
1839
1840                 if((l->mask & mask_must) != mask_must)
1841                         continue;
1842                 if(l->mask & mask_mustnot)
1843                         continue;
1844                 if(maxage && (l->addtime < t - maxage))
1845                         continue;
1846
1847                 // WE FOUND ONE!
1848                 // Calculate its actual height...
1849                 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1850                 if(lines + mylines >= maxlines)
1851                 {
1852                         nskip = lines + mylines - maxlines;
1853                         lines = maxlines;
1854                         startidx = i;
1855                         break;
1856                 }
1857                 lines += mylines;
1858                 startidx = i;
1859         }
1860
1861         // then center according to the calculated amount of lines...
1862         ti.x = x;
1863         ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1864
1865         // then actually draw
1866         for(i = startidx; i < CON_LINES_COUNT; ++i)
1867         {
1868                 con_lineinfo_t *l = &CON_LINES(i);
1869
1870                 if((l->mask & mask_must) != mask_must)
1871                         continue;
1872                 if(l->mask & mask_mustnot)
1873                         continue;
1874                 if(maxage && (l->addtime < t - maxage))
1875                         continue;
1876
1877                 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1878         }
1879
1880         return lines;
1881 }
1882
1883 /*
1884 ================
1885 Con_DrawNotify
1886
1887 Draws the last few lines of output transparently over the game top
1888 ================
1889 */
1890 void Con_DrawNotify (void)
1891 {
1892         float x, v;
1893         float chatstart, notifystart, inputsize, height;
1894         float align;
1895         int numChatlines;
1896         int chatpos;
1897
1898         if (con_mutex) Thread_LockMutex(con_mutex);
1899         ConBuffer_FixTimes(&con);
1900
1901         numChatlines = con_chat.integer;
1902
1903         chatpos = con_chatpos.integer;
1904
1905         if (con_notify.integer < 0)
1906                 Cvar_SetValueQuick(&con_notify, 0);
1907         if (gamemode == GAME_TRANSFUSION)
1908                 v = 8; // vertical offset
1909         else
1910                 v = 0;
1911
1912         // GAME_NEXUIZ: center, otherwise left justify
1913         align = con_notifyalign.value;
1914         if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1915         {
1916                 if(IS_OLDNEXUIZ_DERIVED(gamemode))
1917                         align = 0.5;
1918         }
1919
1920         if(numChatlines || !con_chatrect.integer)
1921         {
1922                 if(chatpos == 0)
1923                 {
1924                         // first chat, input line, then notify
1925                         chatstart = v;
1926                         notifystart = v + (numChatlines + 1) * con_chatsize.value;
1927                 }
1928                 else if(chatpos > 0)
1929                 {
1930                         // first notify, then (chatpos-1) empty lines, then chat, then input
1931                         notifystart = v;
1932                         chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1933                 }
1934                 else // if(chatpos < 0)
1935                 {
1936                         // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1937                         notifystart = v;
1938                         chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1939                 }
1940         }
1941         else
1942         {
1943                 // just notify and input
1944                 notifystart = v;
1945                 chatstart = 0; // shut off gcc warning
1946         }
1947
1948         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, "");
1949
1950         if(con_chatrect.integer)
1951         {
1952                 x = con_chatrect_x.value * vid_conwidth.value;
1953                 v = con_chatrect_y.value * vid_conheight.value;
1954         }
1955         else
1956         {
1957                 x = 0;
1958                 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1959                         v = chatstart;
1960         }
1961         height = numChatlines * con_chatsize.value;
1962
1963         if(numChatlines)
1964         {
1965                 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 ... ");
1966                 v += height;
1967         }
1968         if (key_dest == key_message)
1969         {
1970                 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1971                 Con_DrawInput(false, x, v, inputsize);
1972         }
1973         else
1974                 chat_bufferpos = 0;
1975
1976         if (con_mutex) Thread_UnlockMutex(con_mutex);
1977 }
1978
1979 /*
1980 ================
1981 Con_LineHeight
1982
1983 Returns the height of a given console line; calculates it if necessary.
1984 ================
1985 */
1986 static int Con_LineHeight(int lineno)
1987 {
1988         con_lineinfo_t *li = &CON_LINES(lineno);
1989         if(li->height == -1)
1990         {
1991                 float width = vid_conwidth.value;
1992                 con_text_info_t ti;
1993                 ti.fontsize = con_textsize.value;
1994                 ti.font = FONT_CONSOLE;
1995                 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1996         }
1997         return li->height;
1998 }
1999
2000 /*
2001 ================
2002 Con_DrawConsoleLine
2003
2004 Draws a line of the console; returns its height in lines.
2005 If alpha is 0, the line is not drawn, but still wrapped and its height
2006 returned.
2007 ================
2008 */
2009 static int Con_DrawConsoleLine(unsigned mask_must, unsigned mask_mustnot, float y, int lineno, float ymin, float ymax)
2010 {
2011         float width = vid_conwidth.value;
2012         con_text_info_t ti;
2013         con_lineinfo_t *li = &CON_LINES(lineno);
2014
2015         if((li->mask & mask_must) != mask_must)
2016                 return 0;
2017         if((li->mask & mask_mustnot) != 0)
2018                 return 0;
2019
2020         ti.continuationString = "";
2021         ti.alignment = 0;
2022         ti.fontsize = con_textsize.value;
2023         ti.font = FONT_CONSOLE;
2024         ti.x = 0;
2025         ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
2026         ti.ymin = ymin;
2027         ti.ymax = ymax;
2028         ti.width = width;
2029
2030         return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
2031 }
2032
2033 /*
2034 ================
2035 Con_LastVisibleLine
2036
2037 Calculates the last visible line index and how much to show of it based on
2038 con_backscroll.
2039 ================
2040 */
2041 static void Con_LastVisibleLine(unsigned mask_must, unsigned mask_mustnot, int *last, int *limitlast)
2042 {
2043         int lines_seen = 0;
2044         int i;
2045
2046         if(con_backscroll < 0)
2047                 con_backscroll = 0;
2048
2049         *last = 0;
2050
2051         // now count until we saw con_backscroll actual lines
2052         for(i = CON_LINES_COUNT - 1; i >= 0; --i)
2053         if((CON_LINES(i).mask & mask_must) == mask_must)
2054         if((CON_LINES(i).mask & mask_mustnot) == 0)
2055         {
2056                 int h = Con_LineHeight(i);
2057
2058                 // line is the last visible line?
2059                 *last = i;
2060                 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
2061                 {
2062                         *limitlast = lines_seen + h - con_backscroll;
2063                         return;
2064                 }
2065
2066                 lines_seen += h;
2067         }
2068
2069         // if we get here, no line was on screen - scroll so that one line is
2070         // visible then.
2071         con_backscroll = lines_seen - 1;
2072         *limitlast = 1;
2073 }
2074
2075 /*
2076 ================
2077 Con_DrawConsole
2078
2079 Draws the console with the solid background
2080 The typing input line at the bottom should only be drawn if typing is allowed
2081 ================
2082 */
2083 void Con_DrawConsole (int lines, qbool forcedfullscreen)
2084 {
2085         float alpha, alpha0;
2086         double sx, sy;
2087         int mask_must = 0;
2088         int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
2089         cachepic_t *conbackpic;
2090         unsigned int conbackflags;
2091
2092         if (lines <= 0)
2093                 return;
2094
2095         if (con_mutex) Thread_LockMutex(con_mutex);
2096
2097         if (con_backscroll < 0)
2098                 con_backscroll = 0;
2099
2100         con_vislines = lines;
2101
2102         r_draw2d_force = true;
2103
2104 // draw the background
2105         alpha0 = forcedfullscreen ? 1.0f : scr_conalpha.value; // always full alpha when not forced fullscreen
2106         if((alpha = alpha0 * scr_conalphafactor.value) > 0)
2107         {
2108                 sx = scr_conscroll_x.value;
2109                 sy = scr_conscroll_y.value;
2110                 conbackflags = CACHEPICFLAG_FAILONMISSING; // So console is readable when game content is missing
2111                 if (sx != 0 || sy != 0)
2112                         conbackflags &= CACHEPICFLAG_NOCLAMP;
2113                 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", conbackflags) : NULL;
2114                 sx *= host.realtime; sy *= host.realtime;
2115                 sx -= floor(sx); sy -= floor(sy);
2116                 if (Draw_IsPicLoaded(conbackpic))
2117                         DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2118                                         0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2119                                         1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2120                                         0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2121                                         1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2122                                         0);
2123                 else
2124                         DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
2125         }
2126         if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
2127         {
2128                 sx = scr_conscroll2_x.value;
2129                 sy = scr_conscroll2_y.value;
2130                 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2131                 sx *= host.realtime; sy *= host.realtime;
2132                 sx -= floor(sx); sy -= floor(sy);
2133                 if(Draw_IsPicLoaded(conbackpic))
2134                         DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2135                                         0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2136                                         1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2137                                         0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2138                                         1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2139                                         0);
2140         }
2141         if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
2142         {
2143                 sx = scr_conscroll3_x.value;
2144                 sy = scr_conscroll3_y.value;
2145                 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2146                 sx *= host.realtime; sy *= host.realtime;
2147                 sx -= floor(sx); sy -= floor(sy);
2148                 if(Draw_IsPicLoaded(conbackpic))
2149                         DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2150                                         0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2151                                         1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2152                                         0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2153                                         1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2154                                         0);
2155         }
2156         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);
2157
2158 // draw the text
2159 #if 0
2160         {
2161                 int i;
2162                 int count = CON_LINES_COUNT;
2163                 float ymax = con_vislines - 2 * con_textsize.value;
2164                 float y = ymax + con_textsize.value * con_backscroll;
2165                 for (i = 0;i < count && y >= 0;i++)
2166                         y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
2167                 // fix any excessive scrollback for the next frame
2168                 if (i >= count && y >= 0)
2169                 {
2170                         con_backscroll -= (int)(y / con_textsize.value);
2171                         if (con_backscroll < 0)
2172                                 con_backscroll = 0;
2173                 }
2174         }
2175 #else
2176         if(CON_LINES_COUNT > 0)
2177         {
2178                 int i, last, limitlast;
2179                 float y;
2180                 float ymax = con_vislines - 2 * con_textsize.value;
2181                 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2182                 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2183                 y = ymax - con_textsize.value;
2184
2185                 if(limitlast)
2186                         y += (CON_LINES(last).height - limitlast) * con_textsize.value;
2187                 i = last;
2188
2189                 for(;;)
2190                 {
2191                         y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
2192                         if(i == 0)
2193                                 break; // top of console buffer
2194                         if(y < 0)
2195                                 break; // top of console window
2196                         limitlast = 0;
2197                         --i;
2198                 }
2199         }
2200 #endif
2201
2202 // draw the input prompt, user text, and cursor if desired
2203         Con_DrawInput(true, 0, con_vislines - con_textsize.value * 2, con_textsize.value);
2204
2205         r_draw2d_force = false;
2206         if (con_mutex) Thread_UnlockMutex(con_mutex);
2207 }
2208
2209 /*
2210 GetMapList
2211
2212 Made by [515]
2213 Prints not only map filename, but also
2214 its format (q1/q2/q3/hl) and even its message
2215 */
2216 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2217 //LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2218 //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
2219 //LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2220 qbool GetMapList (const char *s, char *completedname, int completednamebufferlength)
2221 {
2222         fssearch_t      *t;
2223         char            message[1024];
2224         int                     i, k, max, p, o, min;
2225         unsigned char *len;
2226         qfile_t         *f;
2227         unsigned char buf[1024];
2228
2229         dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2230         t = FS_Search(message, 1, true, NULL);
2231         if(!t)
2232                 return false;
2233         if (t->numfilenames > 1)
2234                 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2235         len = (unsigned char *)Z_Malloc(t->numfilenames);
2236         min = 666;
2237         for(max=i=0;i<t->numfilenames;i++)
2238         {
2239                 k = (int)strlen(t->filenames[i]);
2240                 k -= 9;
2241                 if(max < k)
2242                         max = k;
2243                 else
2244                 if(min > k)
2245                         min = k;
2246                 len[i] = k;
2247         }
2248         o = (int)strlen(s);
2249         for(i=0;i<t->numfilenames;i++)
2250         {
2251                 int lumpofs = 0, lumplen = 0;
2252                 char *entities = NULL;
2253                 const char *data = NULL;
2254                 char keyname[64];
2255                 char entfilename[MAX_QPATH];
2256                 char desc[64];
2257                 desc[0] = 0;
2258                 dp_strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2259                 p = 0;
2260                 f = FS_OpenVirtualFile(t->filenames[i], true);
2261                 if(f)
2262                 {
2263                         dp_strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2264                         memset(buf, 0, 1024);
2265                         FS_Read(f, buf, 1024);
2266                         if (!memcmp(buf, "IBSP", 4))
2267                         {
2268                                 p = LittleLong(((int *)buf)[1]);
2269                                 if (p == Q3BSPVERSION || p == Q3BSPVERSION_LIVE || p == Q3BSPVERSION_IG)
2270                                 {
2271                                         q3dheader_t *header = (q3dheader_t *)buf;
2272                                         lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2273                                         lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2274                                         dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2275                                 }
2276                                 else if (p == Q2BSPVERSION)
2277                                 {
2278                                         q2dheader_t *header = (q2dheader_t *)buf;
2279                                         lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2280                                         lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2281                                         dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2282                                 }
2283                                 else
2284                                         dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2285                         }
2286                         else if (BuffLittleLong(buf) == BSPVERSION)
2287                         {
2288                                 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2289                                 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2290                                 dpsnprintf(desc, sizeof(desc), "BSP29");
2291                         }
2292                         else if (BuffLittleLong(buf) == 30)
2293                         {
2294                                 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2295                                 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2296                                 dpsnprintf(desc, sizeof(desc), "BSPHL");
2297                         }
2298                         else if (!memcmp(buf, "BSP2", 4))
2299                         {
2300                                 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2301                                 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2302                                 dpsnprintf(desc, sizeof(desc), "BSP2");
2303                         }
2304                         else if (!memcmp(buf, "2PSB", 4))
2305                         {
2306                                 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2307                                 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2308                                 dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2309                         }
2310                         else if(!memcmp(buf, "VBSP", 4))
2311                         {
2312                                 hl2dheader_t *header = (hl2dheader_t *)buf;
2313                                 lumpofs = LittleLong(header->lumps[HL2LUMP_ENTITIES].fileofs);
2314                                 lumplen = LittleLong(header->lumps[HL2LUMP_ENTITIES].filelen);
2315                                 dpsnprintf(desc, sizeof(desc), "VBSP%i", LittleLong(((int *)buf)[1]));
2316                         }
2317                         else
2318                                 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2319                         dp_strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2320                         memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2321                         entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2322                         if (!entities && lumplen >= 10)
2323                         {
2324                                 FS_Seek(f, lumpofs, SEEK_SET);
2325                                 entities = (char *)Z_Malloc(lumplen + 1);
2326                                 FS_Read(f, entities, lumplen);
2327                         }
2328                         if (entities)
2329                         {
2330                                 // if there are entities to parse, a missing message key just
2331                                 // means there is no title, so clear the message string now
2332                                 message[0] = 0;
2333                                 data = entities;
2334                                 for (;;)
2335                                 {
2336                                         int l;
2337                                         if (!COM_ParseToken_Simple(&data, false, false, true))
2338                                                 break;
2339                                         if (com_token[0] == '{')
2340                                                 continue;
2341                                         if (com_token[0] == '}')
2342                                                 break;
2343                                         // skip leading whitespace
2344                                         for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2345                                         for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2346                                                 keyname[l] = com_token[k+l];
2347                                         keyname[l] = 0;
2348                                         if (!COM_ParseToken_Simple(&data, false, false, true))
2349                                                 break;
2350                                         if (developer_extra.integer)
2351                                                 Con_DPrintf("key: %s %s\n", keyname, com_token);
2352                                         if (!strcmp(keyname, "message"))
2353                                         {
2354                                                 // get the message contents
2355                                                 dp_strlcpy(message, com_token, sizeof(message));
2356                                                 break;
2357                                         }
2358                                 }
2359                         }
2360                 }
2361                 if (entities)
2362                         Z_Free(entities);
2363                 if(f)
2364                         FS_Close(f);
2365                 *(t->filenames[i]+len[i]+5) = 0;
2366                 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2367         }
2368         Con_Print("\n");
2369         for(p=o;p<min;p++)
2370         {
2371                 k = *(t->filenames[0]+5+p);
2372                 if(k == 0)
2373                         goto endcomplete;
2374                 for(i=1;i<t->numfilenames;i++)
2375                         if(*(t->filenames[i]+5+p) != k)
2376                                 goto endcomplete;
2377         }
2378 endcomplete:
2379         if(p > o && completedname && completednamebufferlength > 0)
2380         {
2381                 memset(completedname, 0, completednamebufferlength);
2382                 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2383         }
2384         Z_Free(len);
2385         FS_FreeSearch(t);
2386         return p > o;
2387 }
2388
2389 /*
2390         Con_DisplayList
2391
2392         New function for tab-completion system
2393         Added by EvilTypeGuy
2394         MEGA Thanks to Taniwha
2395
2396 */
2397 void Con_DisplayList(const char **list)
2398 {
2399         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2400         const char **walk = list;
2401
2402         while (*walk) {
2403                 len = (int)strlen(*walk);
2404                 if (len > maxlen)
2405                         maxlen = len;
2406                 walk++;
2407         }
2408         maxlen += 1;
2409
2410         while (*list) {
2411                 len = (int)strlen(*list);
2412                 if (pos + maxlen >= width) {
2413                         Con_Print("\n");
2414                         pos = 0;
2415                 }
2416
2417                 Con_Print(*list);
2418                 for (i = 0; i < (maxlen - len); i++)
2419                         Con_Print(" ");
2420
2421                 pos += maxlen;
2422                 list++;
2423         }
2424
2425         if (pos)
2426                 Con_Print("\n\n");
2427 }
2428
2429
2430 // Now it becomes TRICKY :D --blub
2431 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];     // contains the nicks with colors and all that
2432 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];  // sanitized list for completion when there are other possible matches.
2433 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2434 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
2435 static int Nicks_matchpos;
2436
2437 // co against <<:BLASTER:>> is true!?
2438 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2439 {
2440         while(a_len)
2441         {
2442                 if(tolower(*a) == tolower(*b))
2443                 {
2444                         if(*a == 0)
2445                                 return 0;
2446                         --a_len;
2447                         ++a;
2448                         ++b;
2449                         continue;
2450                 }
2451                 if(!*a)
2452                         return -1;
2453                 if(!*b)
2454                         return 1;
2455                 if(*a == ' ')
2456                         return (*a < *b) ? -1 : 1;
2457                 if(*b == ' ')
2458                         ++b;
2459                 else
2460                         return (*a < *b) ? -1 : 1;
2461         }
2462         return 0;
2463 }
2464 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2465 {
2466         char space_char;
2467         if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2468         {
2469                 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2470                         return Nicks_strncasecmp_nospaces(a, b, a_len);
2471                 return strncasecmp(a, b, a_len);
2472         }
2473
2474         space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2475
2476         // ignore non alphanumerics of B
2477         // if A contains a non-alphanumeric, B must contain it as well though!
2478         while(a_len)
2479         {
2480                 qbool alnum_a, alnum_b;
2481
2482                 if(tolower(*a) == tolower(*b))
2483                 {
2484                         if(*a == 0) // end of both strings, they're equal
2485                                 return 0;
2486                         --a_len;
2487                         ++a;
2488                         ++b;
2489                         continue;
2490                 }
2491                 // not equal, end of one string?
2492                 if(!*a)
2493                         return -1;
2494                 if(!*b)
2495                         return 1;
2496                 // ignore non alphanumerics
2497                 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2498                 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2499                 if(!alnum_a) // b must contain this
2500                         return (*a < *b) ? -1 : 1;
2501                 if(!alnum_b)
2502                         ++b;
2503                 // otherwise, both are alnum, they're just not equal, return the appropriate number
2504                 else
2505                         return (*a < *b) ? -1 : 1;
2506         }
2507         return 0;
2508 }
2509
2510
2511 /* Nicks_CompleteCountPossible
2512
2513    Count the number of possible nicks to complete
2514  */
2515 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qbool isCon)
2516 {
2517         char name[MAX_SCOREBOARDNAME];
2518         int i, p;
2519         int match;
2520         int spos;
2521         int count = 0;
2522
2523         if(!con_nickcompletion.integer)
2524                 return 0;
2525
2526         // changed that to 1
2527         if(!line[0])// || !line[1]) // we want at least... 2 written characters
2528                 return 0;
2529
2530         for(i = 0; i < cl.maxclients; ++i)
2531         {
2532                 p = i;
2533                 if(!cl.scores[p].name[0])
2534                         continue;
2535
2536                 SanitizeString(cl.scores[p].name, name);
2537                 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2538
2539                 if(!name[0])
2540                         continue;
2541
2542                 match = -1;
2543                 spos = pos - 1; // no need for a minimum of characters :)
2544
2545                 while(spos >= 0)
2546                 {
2547                         if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2548                         {
2549                                 if(!(isCon && spos == 1)) // console start
2550                                 {
2551                                         --spos;
2552                                         continue;
2553                                 }
2554                         }
2555                         if(isCon && spos == 0)
2556                                 break;
2557                         if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2558                                 match = spos;
2559                         --spos;
2560                 }
2561                 if(match < 0)
2562                         continue;
2563                 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2564                 dp_strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2565
2566                 // the sanitized list
2567                 dp_strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2568                 if(!count)
2569                 {
2570                         Nicks_matchpos = match;
2571                 }
2572
2573                 Nicks_offset[count] = s - (&line[match]);
2574                 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2575
2576                 ++count;
2577         }
2578         return count;
2579 }
2580
2581 static void Cmd_CompleteNicksPrint(int count)
2582 {
2583         int i;
2584         for(i = 0; i < count; ++i)
2585                 Con_Printf("%s\n", Nicks_list[i]);
2586 }
2587
2588 static void Nicks_CutMatchesNormal(int count)
2589 {
2590         // cut match 0 down to the longest possible completion
2591         int i;
2592         unsigned int c, l;
2593         c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2594         for(i = 1; i < count; ++i)
2595         {
2596                 l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2597                 if(l < c)
2598                         c = l;
2599
2600                 for(l = 0; l <= c; ++l)
2601                         if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2602                         {
2603                                 c = l-1;
2604                                 break;
2605                         }
2606         }
2607         Nicks_sanlist[0][c+1] = 0;
2608         //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2609 }
2610
2611 static unsigned int Nicks_strcleanlen(const char *s)
2612 {
2613         unsigned int l = 0;
2614         while(*s)
2615         {
2616                 if( (*s >= 'a' && *s <= 'z') ||
2617                     (*s >= 'A' && *s <= 'Z') ||
2618                     (*s >= '0' && *s <= '9') ||
2619                     *s == ' ')
2620                         ++l;
2621                 ++s;
2622         }
2623         return l;
2624 }
2625
2626 static void Nicks_CutMatchesAlphaNumeric(int count)
2627 {
2628         // cut match 0 down to the longest possible completion
2629         int i;
2630         unsigned int c, l;
2631         char tempstr[sizeof(Nicks_sanlist[0])];
2632         char *a, *b;
2633         char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2634
2635         c = (unsigned int)strlen(Nicks_sanlist[0]);
2636         for(i = 0, l = 0; i < (int)c; ++i)
2637         {
2638                 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2639                     (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2640                     (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2641                 {
2642                         tempstr[l++] = Nicks_sanlist[0][i];
2643                 }
2644         }
2645         tempstr[l] = 0;
2646
2647         for(i = 1; i < count; ++i)
2648         {
2649                 a = tempstr;
2650                 b = Nicks_sanlist[i];
2651                 while(1)
2652                 {
2653                         if(!*a)
2654                                 break;
2655                         if(!*b)
2656                         {
2657                                 *a = 0;
2658                                 break;
2659                         }
2660                         if(tolower(*a) == tolower(*b))
2661                         {
2662                                 ++a;
2663                                 ++b;
2664                                 continue;
2665                         }
2666                         if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2667                         {
2668                                 // b is alnum, so cut
2669                                 *a = 0;
2670                                 break;
2671                         }
2672                         ++b;
2673                 }
2674         }
2675         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2676         Nicks_CutMatchesNormal(count);
2677         //if(!Nicks_sanlist[0][0])
2678         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2679         {
2680                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2681                 dp_strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2682         }
2683 }
2684
2685 static void Nicks_CutMatchesNoSpaces(int count)
2686 {
2687         // cut match 0 down to the longest possible completion
2688         int i;
2689         unsigned int c, l;
2690         char tempstr[sizeof(Nicks_sanlist[0])];
2691         char *a, *b;
2692
2693         c = (unsigned int)strlen(Nicks_sanlist[0]);
2694         for(i = 0, l = 0; i < (int)c; ++i)
2695         {
2696                 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2697                 {
2698                         tempstr[l++] = Nicks_sanlist[0][i];
2699                 }
2700         }
2701         tempstr[l] = 0;
2702
2703         for(i = 1; i < count; ++i)
2704         {
2705                 a = tempstr;
2706                 b = Nicks_sanlist[i];
2707                 while(1)
2708                 {
2709                         if(!*a)
2710                                 break;
2711                         if(!*b)
2712                         {
2713                                 *a = 0;
2714                                 break;
2715                         }
2716                         if(tolower(*a) == tolower(*b))
2717                         {
2718                                 ++a;
2719                                 ++b;
2720                                 continue;
2721                         }
2722                         if(*b != ' ')
2723                         {
2724                                 *a = 0;
2725                                 break;
2726                         }
2727                         ++b;
2728                 }
2729         }
2730         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2731         Nicks_CutMatchesNormal(count);
2732         //if(!Nicks_sanlist[0][0])
2733         //Con_Printf("TS: %s\n", tempstr);
2734         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2735         {
2736                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2737                 dp_strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2738         }
2739 }
2740
2741 static void Nicks_CutMatches(int count)
2742 {
2743         if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2744                 Nicks_CutMatchesAlphaNumeric(count);
2745         else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2746                 Nicks_CutMatchesNoSpaces(count);
2747         else
2748                 Nicks_CutMatchesNormal(count);
2749 }
2750
2751 static const char **Nicks_CompleteBuildList(int count)
2752 {
2753         const char **buf;
2754         int bpos = 0;
2755         // the list is freed by Con_CompleteCommandLine, so create a char**
2756         buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2757
2758         for(; bpos < count; ++bpos)
2759                 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2760
2761         Nicks_CutMatches(count);
2762
2763         buf[bpos] = NULL;
2764         return buf;
2765 }
2766
2767 /*
2768         Nicks_AddLastColor
2769         Restores the previous used color, after the autocompleted name.
2770 */
2771 static int Nicks_AddLastColor(char *buffer, int pos)
2772 {
2773         qbool quote_added = false;
2774         int match;
2775         int color = STRING_COLOR_DEFAULT + '0';
2776         char r = 0, g = 0, b = 0;
2777
2778         if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2779         {
2780                 // we'll have to add a quote :)
2781                 buffer[pos++] = '\"';
2782                 quote_added = true;
2783         }
2784
2785         if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2786         {
2787                 // add color when no quote was added, or when flags &4?
2788                 // find last color
2789                 for(match = Nicks_matchpos-1; match >= 0; --match)
2790                 {
2791                         if(buffer[match] == STRING_COLOR_TAG)
2792                         {
2793                                 if( isdigit(buffer[match+1]) )
2794                                 {
2795                                         color = buffer[match+1];
2796                                         break;
2797                                 }
2798                                 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2799                                 {
2800                                         if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2801                                         {
2802                                                 r = buffer[match+2];
2803                                                 g = buffer[match+3];
2804                                                 b = buffer[match+4];
2805                                                 color = -1;
2806                                                 break;
2807                                         }
2808                                 }
2809                         }
2810                 }
2811                 if(!quote_added)
2812                 {
2813                         if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2814                                 pos -= 2;
2815                         else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2816                                          && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2817                                 pos -= 5;
2818                 }
2819                 buffer[pos++] = STRING_COLOR_TAG;
2820                 if (color == -1)
2821                 {
2822                         buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2823                         buffer[pos++] = r;
2824                         buffer[pos++] = g;
2825                         buffer[pos++] = b;
2826                 }
2827                 else
2828                         buffer[pos++] = color;
2829         }
2830         return pos;
2831 }
2832
2833 /*
2834         Con_CompleteCommandLine
2835
2836         New function for tab-completion system
2837         Added by EvilTypeGuy
2838         Thanks to Fett erich@heintz.com
2839         Thanks to taniwha
2840         Enhanced to tab-complete map names by [515]
2841
2842 */
2843 int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console)
2844 {
2845         const char *text = "";
2846         char *s;
2847         const char **list[4] = {0, 0, 0, 0};
2848         char s2[512];
2849         char command[512];
2850         int c, v, a, i, cmd_len, pos, k;
2851         int n; // nicks --blub
2852         const char *space, *patterns;
2853         char vabuf[1024];
2854
2855         char *line;
2856         int linestart, linepos;
2857         unsigned int linesize;
2858         if (is_console)
2859         {
2860                 line = key_line;
2861                 linepos = key_linepos;
2862                 linesize = sizeof(key_line);
2863                 linestart = 1;
2864         }
2865         else
2866         {
2867                 line = chat_buffer;
2868                 linepos = chat_bufferpos;
2869                 linesize = sizeof(chat_buffer);
2870                 linestart = 0;
2871         }
2872
2873         //find what we want to complete
2874         pos = linepos;
2875         while(--pos >= linestart)
2876         {
2877                 k = line[pos];
2878                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2879                         break;
2880         }
2881         pos++;
2882
2883         s = line + pos;
2884         dp_strlcpy(s2, line + linepos, sizeof(s2)); //save chars after cursor
2885         line[linepos] = 0; //hide them
2886
2887         c = v = a = n = cmd_len = 0;
2888         if (!is_console)
2889                 goto nicks;
2890
2891         space = strchr(line + 1, ' ');
2892         if(space && pos == (space - line) + 1)
2893         {
2894                 // adding 1 to line drops the leading ]
2895                 dp_ustr2stp(command, sizeof(command), line + 1, space - (line + 1));
2896
2897                 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?
2898                 if(patterns && !*patterns)
2899                         patterns = NULL; // get rid of the empty string
2900
2901                 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2902                 {
2903                         //maps search
2904                         char t[MAX_QPATH];
2905                         if (GetMapList(s, t, sizeof(t)))
2906                         {
2907                                 // first move the cursor
2908                                 linepos += (int)strlen(t) - (int)strlen(s);
2909
2910                                 // and now do the actual work
2911                                 *s = 0;
2912                                 dp_strlcat(line, t, MAX_INPUTLINE);
2913                                 dp_strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2914
2915                                 // and fix the cursor
2916                                 if(linepos > (int) strlen(line))
2917                                         linepos = (int) strlen(line);
2918                         }
2919                         return linepos;
2920                 }
2921                 else
2922                 {
2923                         if(patterns)
2924                         {
2925                                 char t[MAX_QPATH];
2926                                 stringlist_t resultbuf, dirbuf;
2927
2928                                 // Usage:
2929                                 //   // store completion patterns (space separated) for command foo in con_completion_foo
2930                                 //   set con_completion_foo "foodata/*.foodefault *.foo"
2931                                 //   foo <TAB>
2932                                 //
2933                                 // Note: patterns with slash are always treated as absolute
2934                                 // patterns; patterns without slash search in the innermost
2935                                 // directory the user specified. There is no way to "complete into"
2936                                 // a directory as of now, as directories seem to be unknown to the
2937                                 // FS subsystem.
2938                                 //
2939                                 // Examples:
2940                                 //   set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2941                                 //   set con_completion_playdemo "*.dem"
2942                                 //   set con_completion_play "*.wav *.ogg"
2943                                 //
2944                                 // TODO somehow add support for directories; these shall complete
2945                                 // to their name + an appended slash.
2946
2947                                 stringlistinit(&resultbuf);
2948                                 stringlistinit(&dirbuf);
2949                                 while(COM_ParseToken_Simple(&patterns, false, false, true))
2950                                 {
2951                                         fssearch_t *search;
2952                                         if(strchr(com_token, '/'))
2953                                         {
2954                                                 search = FS_Search(com_token, true, true, NULL);
2955                                         }
2956                                         else
2957                                         {
2958                                                 const char *slash = strrchr(s, '/');
2959                                                 if(slash)
2960                                                 {
2961                                                         dp_strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2962                                                         dp_strlcat(t, com_token, sizeof(t));
2963                                                         search = FS_Search(t, true, true, NULL);
2964                                                 }
2965                                                 else
2966                                                         search = FS_Search(com_token, true, true, NULL);
2967                                         }
2968                                         if(search)
2969                                         {
2970                                                 for(i = 0; i < search->numfilenames; ++i)
2971                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2972                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2973                                                                         stringlistappend(&resultbuf, search->filenames[i]);
2974                                                 FS_FreeSearch(search);
2975                                         }
2976                                 }
2977
2978                                 // In any case, add directory names
2979                                 {
2980                                         fssearch_t *search;
2981                                         const char *slash = strrchr(s, '/');
2982                                         if(slash)
2983                                         {
2984                                                 dp_strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2985                                                 dp_strlcat(t, "*", sizeof(t));
2986                                                 search = FS_Search(t, true, true, NULL);
2987                                         }
2988                                         else
2989                                                 search = FS_Search("*", true, true, NULL);
2990                                         if(search)
2991                                         {
2992                                                 for(i = 0; i < search->numfilenames; ++i)
2993                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2994                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2995                                                                         stringlistappend(&dirbuf, search->filenames[i]);
2996                                                 FS_FreeSearch(search);
2997                                         }
2998                                 }
2999
3000                                 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
3001                                 {
3002                                         const char *p, *q;
3003                                         unsigned int matchchars;
3004                                         if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
3005                                         {
3006                                                 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
3007                                         }
3008                                         else
3009                                         if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
3010                                         {
3011                                                 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
3012                                         }
3013                                         else
3014                                         {
3015                                                 stringlistsort(&resultbuf, true); // dirbuf is already sorted
3016                                                 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
3017                                                 for(i = 0; i < dirbuf.numstrings; ++i)
3018                                                 {
3019                                                         Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
3020                                                 }
3021                                                 for(i = 0; i < resultbuf.numstrings; ++i)
3022                                                 {
3023                                                         Con_Printf("%s\n", resultbuf.strings[i]);
3024                                                 }
3025                                                 matchchars = sizeof(t) - 1;
3026                                                 if(resultbuf.numstrings > 0)
3027                                                 {
3028                                                         p = resultbuf.strings[0];
3029                                                         q = resultbuf.strings[resultbuf.numstrings - 1];
3030                                                         for(; *p && *p == *q; ++p, ++q);
3031                                                         matchchars = (unsigned int)(p - resultbuf.strings[0]);
3032                                                 }
3033                                                 if(dirbuf.numstrings > 0)
3034                                                 {
3035                                                         p = dirbuf.strings[0];
3036                                                         q = dirbuf.strings[dirbuf.numstrings - 1];
3037                                                         for(; *p && *p == *q; ++p, ++q);
3038                                                         matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
3039                                                 }
3040                                                 // now p points to the first non-equal character, or to the end
3041                                                 // of resultbuf.strings[0]. We want to append the characters
3042                                                 // from resultbuf.strings[0] to (not including) p as these are
3043                                                 // the unique prefix
3044                                                 dp_strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
3045                                         }
3046
3047                                         // first move the cursor
3048                                         linepos += (int)strlen(t) - (int)strlen(s);
3049
3050                                         // and now do the actual work
3051                                         *s = 0;
3052                                         dp_strlcat(line, t, MAX_INPUTLINE);
3053                                         dp_strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
3054
3055                                         // and fix the cursor
3056                                         if(linepos > (int) strlen(line))
3057                                                 linepos = (int) strlen(line);
3058                                 }
3059                                 stringlistfreecontents(&resultbuf);
3060                                 stringlistfreecontents(&dirbuf);
3061
3062                                 return linepos; // bail out, when we complete for a command that wants a file name
3063                         }
3064                 }
3065         }
3066
3067         // Count number of possible matches and print them
3068         c = Cmd_CompleteCountPossible(cmd, s);
3069         if (c)
3070         {
3071                 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
3072                 Cmd_CompleteCommandPrint(cmd, s);
3073         }
3074         v = Cvar_CompleteCountPossible(cmd->cvars, s, CF_CLIENT | CF_SERVER);
3075         if (v)
3076         {
3077                 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
3078                 Cvar_CompleteCvarPrint(cmd->cvars, s, CF_CLIENT | CF_SERVER);
3079         }
3080         a = Cmd_CompleteAliasCountPossible(cmd, s);
3081         if (a)
3082         {
3083                 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
3084                 Cmd_CompleteAliasPrint(cmd, s);
3085         }
3086
3087 nicks:
3088         n = Nicks_CompleteCountPossible(line, linepos, s, is_console);
3089         if (n)
3090         {
3091                 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
3092                 Cmd_CompleteNicksPrint(n);
3093         }
3094
3095         if (!(c + v + a + n))   // No possible matches
3096         {
3097                 if(s2[0])
3098                         dp_strlcpy(&line[linepos], s2, linesize - linepos);
3099                 return linepos;
3100         }
3101
3102         if (c)
3103                 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
3104         if (v)
3105                 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
3106         if (a)
3107                 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
3108         if (n)
3109         {
3110                 if (is_console)
3111                         text = *(list[3] = Nicks_CompleteBuildList(n));
3112                 else
3113                         text = *(Nicks_CompleteBuildList(n));
3114         }
3115
3116         for (cmd_len = (int)strlen(s);;cmd_len++)
3117         {
3118                 const char **l;
3119                 for (i = 0; i < 3; i++)
3120                         if (list[i])
3121                                 for (l = list[i];*l;l++)
3122                                         if ((*l)[cmd_len] != text[cmd_len])
3123                                                 goto done;
3124                 // all possible matches share this character, so we continue...
3125                 if (!text[cmd_len])
3126                 {
3127                         // if all matches ended at the same position, stop
3128                         // (this means there is only one match)
3129                         break;
3130                 }
3131         }
3132 done:
3133
3134         // prevent a buffer overrun by limiting cmd_len according to remaining space
3135         cmd_len = min(cmd_len, (int)linesize - 1 - pos);
3136         if (text)
3137         {
3138                 linepos = pos;
3139                 memcpy(&line[linepos], text, cmd_len);
3140                 linepos += cmd_len;
3141                 // if there is only one match, add a space after it
3142                 if (c + v + a + n == 1 && linepos < (int)linesize - 1)
3143                 {
3144                         if(n)
3145                         { // was a nick, might have an offset, and needs colors ;) --blub
3146                                 linepos = pos - Nicks_offset[0];
3147                                 cmd_len = (int)strlen(Nicks_list[0]);
3148                                 cmd_len = min(cmd_len, (int)linesize - 3 - pos);
3149
3150                                 memcpy(&line[linepos] , Nicks_list[0], cmd_len);
3151                                 linepos += cmd_len;
3152                                 if(linepos < (int)(linesize - 7)) // space for color code (^[0-9] or ^xrgb), space and \0
3153                                         linepos = Nicks_AddLastColor(line, linepos);
3154                         }
3155                         line[linepos++] = ' ';
3156                 }
3157         }
3158
3159         // use strlcat to avoid a buffer overrun
3160         line[linepos] = 0;
3161         dp_strlcat(line, s2, linesize);
3162
3163         if (!is_console)
3164                 return linepos;
3165
3166         // free the command, cvar, and alias lists
3167         for (i = 0; i < 4; i++)
3168                 if (list[i])
3169                         Mem_Free((void *)list[i]);
3170
3171         return linepos;
3172 }
3173