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