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