]> icculus.org git repositories - divverent/darkplaces.git/blob - console.c
fix whitespace handling:
[divverent/darkplaces.git] / console.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20 // console.c
21
22 #include "quakedef.h"
23
24 #if !defined(WIN32) || defined(__MINGW32__)
25 # include <unistd.h>
26 #endif
27 #include <time.h>
28
29 float con_cursorspeed = 4;
30
31 #define         CON_TEXTSIZE    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 qboolean 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(char 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                                         char 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_DEFAULT && 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 'a':
1061                                                                                 if ( isxdigit(in[2]) || in[2] == '+' || in[2] == '-' )
1062                                                                                 {
1063                                                                                         in+=2;
1064                                                                                         break;
1065                                                                                 }*/
1066                                                                         case STRING_COLOR_TAG:
1067                                                                                 ++in;
1068                                                                                 *out++ = STRING_COLOR_TAG;
1069                                                                                 break;
1070                                                                         case '0':
1071                                                                         case '7':
1072                                                                                 // normal color
1073                                                                                 ++in;
1074                                                                                 if(lastcolor == 0) break; else lastcolor = 0;
1075                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1076                                                                                 break;
1077                                                                         case '1':
1078                                                                                 // light red
1079                                                                                 ++in;
1080                                                                                 if(lastcolor == 1) break; else lastcolor = 1;
1081                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1082                                                                                 break;
1083                                                                         case '2':
1084                                                                                 // light green
1085                                                                                 ++in;
1086                                                                                 if(lastcolor == 2) break; else lastcolor = 2;
1087                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1088                                                                                 break;
1089                                                                         case '3':
1090                                                                                 // yellow
1091                                                                                 ++in;
1092                                                                                 if(lastcolor == 3) break; else lastcolor = 3;
1093                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1094                                                                                 break;
1095                                                                         case '4':
1096                                                                                 // light blue
1097                                                                                 ++in;
1098                                                                                 if(lastcolor == 4) break; else lastcolor = 4;
1099                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1100                                                                                 break;
1101                                                                         case '5':
1102                                                                                 // light cyan
1103                                                                                 ++in;
1104                                                                                 if(lastcolor == 5) break; else lastcolor = 5;
1105                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1106                                                                                 break;
1107                                                                         case '6':
1108                                                                                 // light magenta
1109                                                                                 ++in;
1110                                                                                 if(lastcolor == 6) break; else lastcolor = 6;
1111                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1112                                                                                 break;
1113                                                                         // 7 handled above
1114                                                                         case '8':
1115                                                                         case '9':
1116                                                                                 // bold normal color
1117                                                                                 ++in;
1118                                                                                 if(lastcolor == 8) break; else lastcolor = 8;
1119                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1120                                                                                 break;
1121                                                                         default:
1122                                                                                 *out++ = STRING_COLOR_TAG;
1123                                                                                 break;
1124                                                                 }
1125                                                                 break;
1126                                                         case '\n':
1127                                                                 if(lastcolor != 0)
1128                                                                 {
1129                                                                         *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1130                                                                         lastcolor = 0;
1131                                                                 }
1132                                                                 *out++ = *in;
1133                                                                 break;
1134                                                         default:
1135                                                                 *out++ = *in;
1136                                                                 break;
1137                                                 }
1138                                         }
1139                                         if(lastcolor != 0)
1140                                         {
1141                                                 *out++ = 0x1B;
1142                                                 *out++ = '[';
1143                                                 *out++ = 'm';
1144                                         }
1145                                         *out++ = 0;
1146                                         Sys_PrintToTerminal(printline);
1147                                 }
1148                                 else if(sys_colortranslation.integer == 2) // Quake
1149                                 {
1150                                         Sys_PrintToTerminal(line);
1151                                 }
1152                                 else // strip
1153                                 {
1154                                         static char printline[MAX_INPUTLINE]; // it can only get shorter here
1155                                         const char *in;
1156                                         char *out;
1157                                         for(in = line, out = printline; *in; ++in)
1158                                         {
1159                                                 switch(*in)
1160                                                 {
1161                                                         case STRING_COLOR_TAG:
1162                                                                 switch(in[1])
1163                                                                 {
1164                                                                         case STRING_COLOR_RGB_DEFAULT:
1165                                                                                 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1166                                                                                 {
1167                                                                                         in+=4;
1168                                                                                         break;
1169                                                                                 }
1170                                                                         /*case 'a':
1171                                                                                 if ( isxdigit(in[2]) || in[2] == '+' || in[2] == '-' )
1172                                                                                 {
1173                                                                                         in+=2;
1174                                                                                         break;
1175                                                                                 }*/
1176                                                                         case STRING_COLOR_TAG:
1177                                                                                 ++in;
1178                                                                                 *out++ = STRING_COLOR_TAG;
1179                                                                                 break;
1180                                                                         case '0':
1181                                                                         case '1':
1182                                                                         case '2':
1183                                                                         case '3':
1184                                                                         case '4':
1185                                                                         case '5':
1186                                                                         case '6':
1187                                                                         case '7':
1188                                                                         case '8':
1189                                                                         case '9':
1190                                                                                 ++in;
1191                                                                                 break;
1192                                                                         default:
1193                                                                                 *out++ = STRING_COLOR_TAG;
1194                                                                                 break;
1195                                                                 }
1196                                                                 break;
1197                                                         default:
1198                                                                 *out++ = *in;
1199                                                                 break;
1200                                                 }
1201                                         }
1202                                         *out++ = 0;
1203                                         Sys_PrintToTerminal(printline);
1204                                 }
1205                         }
1206                         // empty the line buffer
1207                         index = 0;
1208                 }
1209         }
1210 }
1211
1212
1213 /*
1214 ================
1215 Con_Printf
1216
1217 Prints to all appropriate console targets
1218 ================
1219 */
1220 void Con_Printf(const char *fmt, ...)
1221 {
1222         va_list argptr;
1223         char msg[MAX_INPUTLINE];
1224
1225         va_start(argptr,fmt);
1226         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1227         va_end(argptr);
1228
1229         Con_Print(msg);
1230 }
1231
1232 /*
1233 ================
1234 Con_DPrint
1235
1236 A Con_Print that only shows up if the "developer" cvar is set
1237 ================
1238 */
1239 void Con_DPrint(const char *msg)
1240 {
1241         if (!developer.integer)
1242                 return;                 // don't confuse non-developers with techie stuff...
1243         Con_Print(msg);
1244 }
1245
1246 /*
1247 ================
1248 Con_DPrintf
1249
1250 A Con_Printf that only shows up if the "developer" cvar is set
1251 ================
1252 */
1253 void Con_DPrintf(const char *fmt, ...)
1254 {
1255         va_list argptr;
1256         char msg[MAX_INPUTLINE];
1257
1258         if (!developer.integer)
1259                 return;                 // don't confuse non-developers with techie stuff...
1260
1261         va_start(argptr,fmt);
1262         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1263         va_end(argptr);
1264
1265         Con_Print(msg);
1266 }
1267
1268
1269 /*
1270 ==============================================================================
1271
1272 DRAWING
1273
1274 ==============================================================================
1275 */
1276
1277 /*
1278 ================
1279 Con_DrawInput
1280
1281 The input line scrolls horizontally if typing goes beyond the right edge
1282
1283 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1284 ================
1285 */
1286 void Con_DrawInput (void)
1287 {
1288         int             y;
1289         int             i;
1290         char editlinecopy[MAX_INPUTLINE+1], *text;
1291         float x;
1292
1293         if (!key_consoleactive)
1294                 return;         // don't draw anything
1295
1296         strlcpy(editlinecopy, key_lines[edit_line], sizeof(editlinecopy));
1297         text = editlinecopy;
1298
1299         // Advanced Console Editing by Radix radix@planetquake.com
1300         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1301         // use strlen of edit_line instead of key_linepos to allow editing
1302         // of early characters w/o erasing
1303
1304         y = (int)strlen(text);
1305
1306 // fill out remainder with spaces
1307         for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1308                 text[i] = ' ';
1309
1310         // add the cursor frame
1311         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
1312                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
1313
1314 //      text[key_linepos + 1] = 0;
1315
1316         x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1317         if(x >= 0)
1318                 x = 0;
1319
1320         // draw it
1321         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 );
1322
1323         // remove cursor
1324 //      key_lines[edit_line][key_linepos] = 0;
1325 }
1326
1327 typedef struct
1328 {
1329         dp_font_t *font;
1330         float alignment; // 0 = left, 0.5 = center, 1 = right
1331         float fontsize;
1332         float x;
1333         float y;
1334         float width;
1335         float ymin, ymax;
1336         const char *continuationString;
1337
1338         // PRIVATE:
1339         int colorindex; // init to -1
1340 }
1341 con_text_info_t;
1342
1343 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1344 {
1345         con_text_info_t *ti = (con_text_info_t *) passthrough;
1346         if(w == NULL)
1347         {
1348                 ti->colorindex = -1;
1349                 return ti->fontsize * ti->font->maxwidth;
1350         }
1351         if(maxWidth >= 0)
1352                 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1353         else if(maxWidth == -1)
1354                 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1355         else
1356         {
1357                 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1358                 // Note: this is NOT a Con_Printf, as it could print recursively
1359                 return 0;
1360         }
1361 }
1362
1363 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1364 {
1365         (void) passthrough;
1366         (void) line;
1367         (void) length;
1368         (void) width;
1369         (void) isContinuation;
1370         return 1;
1371 }
1372
1373 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1374 {
1375         con_text_info_t *ti = (con_text_info_t *) passthrough;
1376
1377         if(ti->y < ti->ymin - 0.001)
1378                 (void) 0;
1379         else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1380                 (void) 0;
1381         else
1382         {
1383                 int x = ti->x + (ti->width - width) * ti->alignment;
1384                 if(isContinuation && *ti->continuationString)
1385                         x += 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);
1386                 if(length > 0)
1387                         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);
1388         }
1389
1390         ti->y += ti->fontsize;
1391         return 1;
1392 }
1393
1394
1395 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)
1396 {
1397         int i;
1398         int lines = 0;
1399         int maxlines = (int) floor(height / fontsize + 0.01f);
1400         int startidx;
1401         int nskip = 0;
1402         int continuationWidth = 0;
1403         size_t l;
1404         double t = cl.time; // saved so it won't change
1405         con_text_info_t ti;
1406
1407         ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1408         ti.fontsize = fontsize;
1409         ti.alignment = alignment_x;
1410         ti.width = width;
1411         ti.ymin = y;
1412         ti.ymax = y + height;
1413         ti.continuationString = continuationString;
1414
1415         l = 0;
1416         Con_WordWidthFunc(&ti, NULL, &l, -1);
1417         l = strlen(continuationString);
1418         continuationWidth = Con_WordWidthFunc(&ti, continuationString, &l, -1);
1419
1420         // first find the first line to draw by backwards iterating and word wrapping to find their length...
1421         startidx = con_lines_count;
1422         for(i = con_lines_count - 1; i >= 0; --i)
1423         {
1424                 con_lineinfo *l = &CON_LINES(i);
1425                 int mylines;
1426
1427                 if((l->mask & mask_must) != mask_must)
1428                         continue;
1429                 if(l->mask & mask_mustnot)
1430                         continue;
1431                 if(maxage && (l->addtime < t - maxage))
1432                         continue;
1433
1434                 // WE FOUND ONE!
1435                 // Calculate its actual height...
1436                 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1437                 if(lines + mylines >= maxlines)
1438                 {
1439                         nskip = lines + mylines - maxlines;
1440                         lines = maxlines;
1441                         startidx = i;
1442                         break;
1443                 }
1444                 lines += mylines;
1445                 startidx = i;
1446         }
1447
1448         // then center according to the calculated amount of lines...
1449         ti.x = x;
1450         ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1451
1452         // then actually draw
1453         for(i = startidx; i < con_lines_count; ++i)
1454         {
1455                 con_lineinfo *l = &CON_LINES(i);
1456
1457                 if((l->mask & mask_must) != mask_must)
1458                         continue;
1459                 if(l->mask & mask_mustnot)
1460                         continue;
1461                 if(maxage && (l->addtime < t - maxage))
1462                         continue;
1463
1464                 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1465         }
1466
1467         return lines;
1468 }
1469
1470 /*
1471 ================
1472 Con_DrawNotify
1473
1474 Draws the last few lines of output transparently over the game top
1475 ================
1476 */
1477 void Con_DrawNotify (void)
1478 {
1479         float   x, v;
1480         float chatstart, notifystart, inputsize;
1481         float align;
1482         char    temptext[MAX_INPUTLINE];
1483         int numChatlines;
1484         int chatpos;
1485
1486         Con_FixTimes();
1487
1488         numChatlines = con_chat.integer;
1489         chatpos = con_chatpos.integer;
1490
1491         if (con_notify.integer < 0)
1492                 Cvar_SetValueQuick(&con_notify, 0);
1493         if (gamemode == GAME_TRANSFUSION)
1494                 v = 8; // vertical offset
1495         else
1496                 v = 0;
1497
1498         // GAME_NEXUIZ: center, otherwise left justify
1499         align = con_notifyalign.value;
1500         if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1501         {
1502                 if(gamemode == GAME_NEXUIZ)
1503                         align = 0.5;
1504         }
1505
1506         if(numChatlines)
1507         {
1508                 if(chatpos == 0)
1509                 {
1510                         // first chat, input line, then notify
1511                         chatstart = v;
1512                         notifystart = v + (numChatlines + 1) * con_chatsize.value;
1513                 }
1514                 else if(chatpos > 0)
1515                 {
1516                         // first notify, then (chatpos-1) empty lines, then chat, then input
1517                         notifystart = v;
1518                         chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1519                 }
1520                 else // if(chatpos < 0)
1521                 {
1522                         // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1523                         notifystart = v;
1524                         chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1525                 }
1526         }
1527         else
1528         {
1529                 // just notify and input
1530                 notifystart = v;
1531                 chatstart = 0; // shut off gcc warning
1532         }
1533
1534         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, "");
1535
1536         // chat?
1537         if(numChatlines)
1538         {
1539                 v = chatstart + numChatlines * con_chatsize.value;
1540                 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
1541         }
1542
1543         if (key_dest == key_message)
1544         {
1545                 int colorindex = -1;
1546
1547                 // LordHavoc: speedup, and other improvements
1548                 if (chat_mode < 0)
1549                         dpsnprintf(temptext, sizeof(temptext), "]%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1550                 else if(chat_mode)
1551                         dpsnprintf(temptext, sizeof(temptext), "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1552                 else
1553                         dpsnprintf(temptext, sizeof(temptext), "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1554
1555                 // FIXME word wrap
1556                 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1557                 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1558                 if(x > 0)
1559                         x = 0;
1560                 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1561         }
1562 }
1563
1564 /*
1565 ================
1566 Con_MeasureConsoleLine
1567
1568 Counts the number of lines for a line on the console.
1569 ================
1570 */
1571 int Con_MeasureConsoleLine(int lineno)
1572 {
1573         float width = vid_conwidth.value;
1574         con_text_info_t ti;
1575         ti.fontsize = con_textsize.value;
1576         ti.font = FONT_CONSOLE;
1577
1578         return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1579 }
1580
1581 /*
1582 ================
1583 Con_LineHeight
1584
1585 Returns the height of a given console line; calculates it if necessary.
1586 ================
1587 */
1588 int Con_LineHeight(int i)
1589 {
1590         int h = con_lines[i].height;
1591         if(h != -1)
1592                 return h;
1593         return con_lines[i].height = Con_MeasureConsoleLine(i);
1594 }
1595
1596 /*
1597 ================
1598 Con_DrawConsoleLine
1599
1600 Draws a line of the console; returns its height in lines.
1601 If alpha is 0, the line is not drawn, but still wrapped and its height
1602 returned.
1603 ================
1604 */
1605 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1606 {
1607         float width = vid_conwidth.value;
1608
1609         con_text_info_t ti;
1610         ti.continuationString = "";
1611         ti.alignment = 0;
1612         ti.fontsize = con_textsize.value;
1613         ti.font = FONT_CONSOLE;
1614         ti.x = 0;
1615         ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1616         ti.ymin = ymin;
1617         ti.ymax = ymax;
1618         ti.width = width;
1619
1620         return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1621 }
1622
1623 /*
1624 ================
1625 Con_LastVisibleLine
1626
1627 Calculates the last visible line index and how much to show of it based on
1628 con_backscroll.
1629 ================
1630 */
1631 void Con_LastVisibleLine(int *last, int *limitlast)
1632 {
1633         int lines_seen = 0;
1634         int ic;
1635
1636         if(con_backscroll < 0)
1637                 con_backscroll = 0;
1638
1639         // now count until we saw con_backscroll actual lines
1640         for(ic = 0; ic < con_lines_count; ++ic)
1641         {
1642                 int i = CON_LINES_IDX(con_lines_count - 1 - ic);
1643                 int h = Con_LineHeight(i);
1644
1645                 // line is the last visible line?
1646                 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1647                 {
1648                         *last = i;
1649                         *limitlast = lines_seen + h - con_backscroll;
1650                         return;
1651                 }
1652
1653                 lines_seen += h;
1654         }
1655
1656         // if we get here, no line was on screen - scroll so that one line is
1657         // visible then.
1658         con_backscroll = lines_seen - 1;
1659         *last = con_lines_first;
1660         *limitlast = 1;
1661 }
1662
1663 /*
1664 ================
1665 Con_DrawConsole
1666
1667 Draws the console with the solid background
1668 The typing input line at the bottom should only be drawn if typing is allowed
1669 ================
1670 */
1671 void Con_DrawConsole (int lines)
1672 {
1673         int i, last, limitlast;
1674         float y;
1675
1676         if (lines <= 0)
1677                 return;
1678
1679         con_vislines = lines;
1680
1681 // draw the background
1682         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
1683         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);
1684
1685 // draw the text
1686         if(con_lines_count > 0)
1687         {
1688                 float ymax = con_vislines - 2 * con_textsize.value;
1689                 Con_LastVisibleLine(&last, &limitlast);
1690                 y = ymax - con_textsize.value;
1691
1692                 if(limitlast)
1693                         y += (con_lines[last].height - limitlast) * con_textsize.value;
1694                 i = last;
1695
1696                 for(;;)
1697                 {
1698                         y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1699                         if(i == con_lines_first)
1700                                 break; // top of console buffer
1701                         if(y < 0)
1702                                 break; // top of console window
1703                         limitlast = 0;
1704                         i = CON_LINES_PRED(i);
1705                 }
1706         }
1707
1708 // draw the input prompt, user text, and cursor if desired
1709         Con_DrawInput ();
1710 }
1711
1712 /*
1713 GetMapList
1714
1715 Made by [515]
1716 Prints not only map filename, but also
1717 its format (q1/q2/q3/hl) and even its message
1718 */
1719 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1720 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1721 //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
1722 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1723 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1724 {
1725         fssearch_t      *t;
1726         char            message[1024];
1727         int                     i, k, max, p, o, min;
1728         unsigned char *len;
1729         qfile_t         *f;
1730         unsigned char buf[1024];
1731
1732         dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1733         t = FS_Search(message, 1, true);
1734         if(!t)
1735                 return false;
1736         if (t->numfilenames > 1)
1737                 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1738         len = (unsigned char *)Z_Malloc(t->numfilenames);
1739         min = 666;
1740         for(max=i=0;i<t->numfilenames;i++)
1741         {
1742                 k = (int)strlen(t->filenames[i]);
1743                 k -= 9;
1744                 if(max < k)
1745                         max = k;
1746                 else
1747                 if(min > k)
1748                         min = k;
1749                 len[i] = k;
1750         }
1751         o = (int)strlen(s);
1752         for(i=0;i<t->numfilenames;i++)
1753         {
1754                 int lumpofs = 0, lumplen = 0;
1755                 char *entities = NULL;
1756                 const char *data = NULL;
1757                 char keyname[64];
1758                 char entfilename[MAX_QPATH];
1759                 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1760                 p = 0;
1761                 f = FS_OpenVirtualFile(t->filenames[i], true);
1762                 if(f)
1763                 {
1764                         memset(buf, 0, 1024);
1765                         FS_Read(f, buf, 1024);
1766                         if (!memcmp(buf, "IBSP", 4))
1767                         {
1768                                 p = LittleLong(((int *)buf)[1]);
1769                                 if (p == Q3BSPVERSION)
1770                                 {
1771                                         q3dheader_t *header = (q3dheader_t *)buf;
1772                                         lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1773                                         lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1774                                 }
1775                                 else if (p == Q2BSPVERSION)
1776                                 {
1777                                         q2dheader_t *header = (q2dheader_t *)buf;
1778                                         lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1779                                         lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1780                                 }
1781                         }
1782                         else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1783                         {
1784                                 dheader_t *header = (dheader_t *)buf;
1785                                 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1786                                 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1787                         }
1788                         else
1789                                 p = 0;
1790                         strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1791                         memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1792                         entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1793                         if (!entities && lumplen >= 10)
1794                         {
1795                                 FS_Seek(f, lumpofs, SEEK_SET);
1796                                 entities = (char *)Z_Malloc(lumplen + 1);
1797                                 FS_Read(f, entities, lumplen);
1798                         }
1799                         if (entities)
1800                         {
1801                                 // if there are entities to parse, a missing message key just
1802                                 // means there is no title, so clear the message string now
1803                                 message[0] = 0;
1804                                 data = entities;
1805                                 for (;;)
1806                                 {
1807                                         int l;
1808                                         if (!COM_ParseToken_Simple(&data, false, false))
1809                                                 break;
1810                                         if (com_token[0] == '{')
1811                                                 continue;
1812                                         if (com_token[0] == '}')
1813                                                 break;
1814                                         // skip leading whitespace
1815                                         for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
1816                                         for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
1817                                                 keyname[l] = com_token[k+l];
1818                                         keyname[l] = 0;
1819                                         if (!COM_ParseToken_Simple(&data, false, false))
1820                                                 break;
1821                                         if (developer.integer >= 100)
1822                                                 Con_Printf("key: %s %s\n", keyname, com_token);
1823                                         if (!strcmp(keyname, "message"))
1824                                         {
1825                                                 // get the message contents
1826                                                 strlcpy(message, com_token, sizeof(message));
1827                                                 break;
1828                                         }
1829                                 }
1830                         }
1831                 }
1832                 if (entities)
1833                         Z_Free(entities);
1834                 if(f)
1835                         FS_Close(f);
1836                 *(t->filenames[i]+len[i]+5) = 0;
1837                 switch(p)
1838                 {
1839                 case Q3BSPVERSION:      strlcpy((char *)buf, "Q3", sizeof(buf));break;
1840                 case Q2BSPVERSION:      strlcpy((char *)buf, "Q2", sizeof(buf));break;
1841                 case BSPVERSION:        strlcpy((char *)buf, "Q1", sizeof(buf));break;
1842                 case 30:                        strlcpy((char *)buf, "HL", sizeof(buf));break;
1843                 default:                        strlcpy((char *)buf, "??", sizeof(buf));break;
1844                 }
1845                 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1846         }
1847         Con_Print("\n");
1848         for(p=o;p<min;p++)
1849         {
1850                 k = *(t->filenames[0]+5+p);
1851                 if(k == 0)
1852                         goto endcomplete;
1853                 for(i=1;i<t->numfilenames;i++)
1854                         if(*(t->filenames[i]+5+p) != k)
1855                                 goto endcomplete;
1856         }
1857 endcomplete:
1858         if(p > o && completedname && completednamebufferlength > 0)
1859         {
1860                 memset(completedname, 0, completednamebufferlength);
1861                 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1862         }
1863         Z_Free(len);
1864         FS_FreeSearch(t);
1865         return p > o;
1866 }
1867
1868 /*
1869         Con_DisplayList
1870
1871         New function for tab-completion system
1872         Added by EvilTypeGuy
1873         MEGA Thanks to Taniwha
1874
1875 */
1876 void Con_DisplayList(const char **list)
1877 {
1878         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1879         const char **walk = list;
1880
1881         while (*walk) {
1882                 len = (int)strlen(*walk);
1883                 if (len > maxlen)
1884                         maxlen = len;
1885                 walk++;
1886         }
1887         maxlen += 1;
1888
1889         while (*list) {
1890                 len = (int)strlen(*list);
1891                 if (pos + maxlen >= width) {
1892                         Con_Print("\n");
1893                         pos = 0;
1894                 }
1895
1896                 Con_Print(*list);
1897                 for (i = 0; i < (maxlen - len); i++)
1898                         Con_Print(" ");
1899
1900                 pos += maxlen;
1901                 list++;
1902         }
1903
1904         if (pos)
1905                 Con_Print("\n\n");
1906 }
1907
1908 /*
1909         SanitizeString strips color tags from the string in
1910         and writes the result on string out
1911 */
1912 void SanitizeString(char *in, char *out)
1913 {
1914         while(*in)
1915         {
1916                 if(*in == STRING_COLOR_TAG)
1917                 {
1918                         ++in;
1919                         if(!*in)
1920                         {
1921                                 out[0] = STRING_COLOR_TAG;
1922                                 out[1] = 0;
1923                                 return;
1924                         }
1925                         else if (*in >= '0' && *in <= '9') // ^[0-9] found
1926                         {
1927                                 ++in;
1928                                 if(!*in)
1929                                 {
1930                                         *out = 0;
1931                                         return;
1932                                 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
1933                                         continue;
1934                         }
1935                         else if (*in == STRING_COLOR_RGB_DEFAULT) // ^x found
1936                         {
1937                                 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
1938                                 {
1939                                         in+=4;
1940                                         if (!*in)
1941                                         {
1942                                                 *out = 0;
1943                                                 return;
1944                                         } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
1945                                                 continue;
1946                                 }
1947                                 else in--;
1948                         }
1949                         /*else if (*in == 'a') // ^a found
1950                         {
1951                                 if ( isxdigit(in[1]) || isxdigit(in[1]) == '+' || isxdigit(in[1]) == '-')
1952                                 {
1953                                         in+=2;
1954                                         if (!*in)
1955                                         {
1956                                                 *out = 0;
1957                                                 return;
1958                                         } else if (*in == STRING_COLOR_TAG) // ^ax^ found, don't print ^ax
1959                                                 continue;
1960                                 }
1961                                 else in = in--;
1962                         }*/
1963                         else if (*in != STRING_COLOR_TAG)
1964                                 --in;
1965                 }
1966                 *out = qfont_table[*(unsigned char*)in];
1967                 ++in;
1968                 ++out;
1969         }
1970         *out = 0;
1971 }
1972
1973 // Now it becomes TRICKY :D --blub
1974 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];     // contains the nicks with colors and all that
1975 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];  // sanitized list for completion when there are other possible matches.
1976 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
1977 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
1978 static int Nicks_matchpos;
1979
1980 // co against <<:BLASTER:>> is true!?
1981 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
1982 {
1983         while(a_len)
1984         {
1985                 if(tolower(*a) == tolower(*b))
1986                 {
1987                         if(*a == 0)
1988                                 return 0;
1989                         --a_len;
1990                         ++a;
1991                         ++b;
1992                         continue;
1993                 }
1994                 if(!*a)
1995                         return -1;
1996                 if(!*b)
1997                         return 1;
1998                 if(*a == ' ')
1999                         return (*a < *b) ? -1 : 1;
2000                 if(*b == ' ')
2001                         ++b;
2002                 else
2003                         return (*a < *b) ? -1 : 1;
2004         }
2005         return 0;
2006 }
2007 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2008 {
2009         char space_char;
2010         if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2011         {
2012                 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2013                         return Nicks_strncasecmp_nospaces(a, b, a_len);
2014                 return strncasecmp(a, b, a_len);
2015         }
2016
2017         space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2018
2019         // ignore non alphanumerics of B
2020         // if A contains a non-alphanumeric, B must contain it as well though!
2021         while(a_len)
2022         {
2023                 qboolean alnum_a, alnum_b;
2024
2025                 if(tolower(*a) == tolower(*b))
2026                 {
2027                         if(*a == 0) // end of both strings, they're equal
2028                                 return 0;
2029                         --a_len;
2030                         ++a;
2031                         ++b;
2032                         continue;
2033                 }
2034                 // not equal, end of one string?
2035                 if(!*a)
2036                         return -1;
2037                 if(!*b)
2038                         return 1;
2039                 // ignore non alphanumerics
2040                 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2041                 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2042                 if(!alnum_a) // b must contain this
2043                         return (*a < *b) ? -1 : 1;
2044                 if(!alnum_b)
2045                         ++b;
2046                 // otherwise, both are alnum, they're just not equal, return the appropriate number
2047                 else
2048                         return (*a < *b) ? -1 : 1;
2049         }
2050         return 0;
2051 }
2052
2053
2054 /* Nicks_CompleteCountPossible
2055
2056    Count the number of possible nicks to complete
2057  */
2058 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2059 {
2060         char name[128];
2061         int i, p;
2062         int length;
2063         int match;
2064         int spos;
2065         int count = 0;
2066
2067         if(!con_nickcompletion.integer)
2068                 return 0;
2069
2070         // changed that to 1
2071         if(!line[0])// || !line[1]) // we want at least... 2 written characters
2072                 return 0;
2073
2074         for(i = 0; i < cl.maxclients; ++i)
2075         {
2076                 p = i;
2077                 if(!cl.scores[p].name[0])
2078                         continue;
2079
2080                 SanitizeString(cl.scores[p].name, name);
2081                 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2082
2083                 if(!name[0])
2084                         continue;
2085
2086                 length = strlen(name);
2087                 match = -1;
2088                 spos = pos - 1; // no need for a minimum of characters :)
2089
2090                 while(spos >= 0 && (spos - pos) < length) // search-string-length < name length
2091                 {
2092                         if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2093                         {
2094                                 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2095                                    !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2096                                 {
2097                                         --spos;
2098                                         continue;
2099                                 }
2100                         }
2101                         if(isCon && spos == 0)
2102                                 break;
2103                         if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2104                                 match = spos;
2105                         --spos;
2106                 }
2107                 if(match < 0)
2108                         continue;
2109                 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2110                 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2111
2112                 // the sanitized list
2113                 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2114                 if(!count)
2115                 {
2116                         Nicks_matchpos = match;
2117                 }
2118
2119                 Nicks_offset[count] = s - (&line[match]);
2120                 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2121
2122                 ++count;
2123         }
2124         return count;
2125 }
2126
2127 void Cmd_CompleteNicksPrint(int count)
2128 {
2129         int i;
2130         for(i = 0; i < count; ++i)
2131                 Con_Printf("%s\n", Nicks_list[i]);
2132 }
2133
2134 void Nicks_CutMatchesNormal(int count)
2135 {
2136         // cut match 0 down to the longest possible completion
2137         int i;
2138         unsigned int c, l;
2139         c = strlen(Nicks_sanlist[0]) - 1;
2140         for(i = 1; i < count; ++i)
2141         {
2142                 l = strlen(Nicks_sanlist[i]) - 1;
2143                 if(l < c)
2144                         c = l;
2145
2146                 for(l = 0; l <= c; ++l)
2147                         if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2148                         {
2149                                 c = l-1;
2150                                 break;
2151                         }
2152         }
2153         Nicks_sanlist[0][c+1] = 0;
2154         //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2155 }
2156
2157 unsigned int Nicks_strcleanlen(const char *s)
2158 {
2159         unsigned int l = 0;
2160         while(*s)
2161         {
2162                 if( (*s >= 'a' && *s <= 'z') ||
2163                     (*s >= 'A' && *s <= 'Z') ||
2164                     (*s >= '0' && *s <= '9') ||
2165                     *s == ' ')
2166                         ++l;
2167                 ++s;
2168         }
2169         return l;
2170 }
2171
2172 void Nicks_CutMatchesAlphaNumeric(int count)
2173 {
2174         // cut match 0 down to the longest possible completion
2175         int i;
2176         unsigned int c, l;
2177         char tempstr[sizeof(Nicks_sanlist[0])];
2178         char *a, *b;
2179         char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2180
2181         c = strlen(Nicks_sanlist[0]);
2182         for(i = 0, l = 0; i < (int)c; ++i)
2183         {
2184                 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2185                     (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2186                     (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2187                 {
2188                         tempstr[l++] = Nicks_sanlist[0][i];
2189                 }
2190         }
2191         tempstr[l] = 0;
2192
2193         for(i = 1; i < count; ++i)
2194         {
2195                 a = tempstr;
2196                 b = Nicks_sanlist[i];
2197                 while(1)
2198                 {
2199                         if(!*a)
2200                                 break;
2201                         if(!*b)
2202                         {
2203                                 *a = 0;
2204                                 break;
2205                         }
2206                         if(tolower(*a) == tolower(*b))
2207                         {
2208                                 ++a;
2209                                 ++b;
2210                                 continue;
2211                         }
2212                         if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2213                         {
2214                                 // b is alnum, so cut
2215                                 *a = 0;
2216                                 break;
2217                         }
2218                         ++b;
2219                 }
2220         }
2221         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2222         Nicks_CutMatchesNormal(count);
2223         //if(!Nicks_sanlist[0][0])
2224         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2225         {
2226                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2227                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2228         }
2229 }
2230
2231 void Nicks_CutMatchesNoSpaces(int count)
2232 {
2233         // cut match 0 down to the longest possible completion
2234         int i;
2235         unsigned int c, l;
2236         char tempstr[sizeof(Nicks_sanlist[0])];
2237         char *a, *b;
2238
2239         c = strlen(Nicks_sanlist[0]);
2240         for(i = 0, l = 0; i < (int)c; ++i)
2241         {
2242                 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2243                 {
2244                         tempstr[l++] = Nicks_sanlist[0][i];
2245                 }
2246         }
2247         tempstr[l] = 0;
2248
2249         for(i = 1; i < count; ++i)
2250         {
2251                 a = tempstr;
2252                 b = Nicks_sanlist[i];
2253                 while(1)
2254                 {
2255                         if(!*a)
2256                                 break;
2257                         if(!*b)
2258                         {
2259                                 *a = 0;
2260                                 break;
2261                         }
2262                         if(tolower(*a) == tolower(*b))
2263                         {
2264                                 ++a;
2265                                 ++b;
2266                                 continue;
2267                         }
2268                         if(*b != ' ')
2269                         {
2270                                 *a = 0;
2271                                 break;
2272                         }
2273                         ++b;
2274                 }
2275         }
2276         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2277         Nicks_CutMatchesNormal(count);
2278         //if(!Nicks_sanlist[0][0])
2279         //Con_Printf("TS: %s\n", tempstr);
2280         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2281         {
2282                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2283                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2284         }
2285 }
2286
2287 void Nicks_CutMatches(int count)
2288 {
2289         if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2290                 Nicks_CutMatchesAlphaNumeric(count);
2291         else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2292                 Nicks_CutMatchesNoSpaces(count);
2293         else
2294                 Nicks_CutMatchesNormal(count);
2295 }
2296
2297 const char **Nicks_CompleteBuildList(int count)
2298 {
2299         const char **buf;
2300         int bpos = 0;
2301         // the list is freed by Con_CompleteCommandLine, so create a char**
2302         buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2303
2304         for(; bpos < count; ++bpos)
2305                 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2306
2307         Nicks_CutMatches(count);
2308
2309         buf[bpos] = NULL;
2310         return buf;
2311 }
2312
2313 /*
2314         Nicks_AddLastColor
2315         Restores the previous used color, after the autocompleted name.
2316 */
2317 int Nicks_AddLastColor(char *buffer, int pos)
2318 {
2319         qboolean quote_added = false;
2320         int match;
2321         char color = STRING_COLOR_DEFAULT + '0';
2322         char r = 0, g = 0, b = 0;
2323
2324         if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2325         {
2326                 // we'll have to add a quote :)
2327                 buffer[pos++] = '\"';
2328                 quote_added = true;
2329         }
2330
2331         if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2332         {
2333                 // add color when no quote was added, or when flags &4?
2334                 // find last color
2335                 for(match = Nicks_matchpos-1; match >= 0; --match)
2336                 {
2337                         if(buffer[match] == STRING_COLOR_TAG)
2338                         {
2339                                 if( isdigit(buffer[match+1]) )
2340                                 {
2341                                         color = buffer[match+1];
2342                                         break;
2343                                 }
2344                                 else if(buffer[match+1] == STRING_COLOR_RGB_DEFAULT)
2345                                 {
2346                                         if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2347                                                 r = buffer[match+2];
2348                                                 g = buffer[match+3];
2349                                                 b = buffer[match+4];
2350                                                 color = -1;
2351                                                 break;
2352                                 }
2353                         }
2354                 }
2355                 if(!quote_added)
2356                 {
2357                         if( buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2358                         {
2359                                 pos -= 2;
2360                         }
2361                         else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_DEFAULT)
2362                         {
2363                                 if ( isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2364                                 {
2365                                         pos -= 5;
2366                                         color = -1;
2367                                 }
2368                         }
2369                 }
2370                 if (color == -1)
2371                 {
2372                         buffer[pos++] = STRING_COLOR_TAG;
2373                         buffer[pos++] = STRING_COLOR_RGB_DEFAULT;
2374                         buffer[pos++] = r;
2375                         buffer[pos++] = g;
2376                         buffer[pos++] = b;
2377                 }
2378                 /*else if (color == -2)
2379                 {
2380                         buffer[pos++] = STRING_COLOR_TAG;
2381                         buffer[pos++] = 'a';
2382                         buffer[pos++] = a;
2383                 }*/
2384                 else
2385                 {
2386                         buffer[pos++] = STRING_COLOR_TAG;
2387                         buffer[pos++] = color;
2388                 }
2389         }
2390         return pos;
2391 }
2392
2393 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2394 {
2395         int n;
2396         /*if(!con_nickcompletion.integer)
2397           return; is tested in Nicks_CompletionCountPossible */
2398         n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2399         if(n == 1)
2400         {
2401                 size_t len;
2402                 char *msg;
2403
2404                 msg = Nicks_list[0];
2405                 len = min(size - Nicks_matchpos - 3, strlen(msg));
2406                 memcpy(&buffer[Nicks_matchpos], msg, len);
2407                 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2408                         len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2409                 buffer[len++] = ' ';
2410                 buffer[len] = 0;
2411                 return len;
2412         } else if(n > 1)
2413         {
2414                 int len;
2415                 char *msg;
2416                 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2417                 Cmd_CompleteNicksPrint(n);
2418
2419                 Nicks_CutMatches(n);
2420
2421                 msg = Nicks_sanlist[0];
2422                 len = min(size - Nicks_matchpos, strlen(msg));
2423                 memcpy(&buffer[Nicks_matchpos], msg, len);
2424                 buffer[Nicks_matchpos + len] = 0;
2425                 //pos += len;
2426                 return Nicks_matchpos + len;
2427         }
2428         return pos;
2429 }
2430
2431
2432 /*
2433         Con_CompleteCommandLine
2434
2435         New function for tab-completion system
2436         Added by EvilTypeGuy
2437         Thanks to Fett erich@heintz.com
2438         Thanks to taniwha
2439         Enhanced to tab-complete map names by [515]
2440
2441 */
2442 void Con_CompleteCommandLine (void)
2443 {
2444         const char *cmd = "";
2445         char *s;
2446         const char **list[4] = {0, 0, 0, 0};
2447         char s2[512];
2448         char command[512];
2449         int c, v, a, i, cmd_len, pos, k;
2450         int n; // nicks --blub
2451         const char *space, *patterns;
2452
2453         //find what we want to complete
2454         pos = key_linepos;
2455         while(--pos)
2456         {
2457                 k = key_lines[edit_line][pos];
2458                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2459                         break;
2460         }
2461         pos++;
2462
2463         s = key_lines[edit_line] + pos;
2464         strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2));    //save chars after cursor
2465         key_lines[edit_line][key_linepos] = 0;                                  //hide them
2466
2467         space = strchr(key_lines[edit_line] + 1, ' ');
2468         if(space && pos == (space - key_lines[edit_line]) + 1)
2469         {
2470                 strlcpy(command, key_lines[edit_line] + 1, min(sizeof(command), (unsigned int)(space - key_lines[edit_line])));
2471
2472                 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2473                 if(patterns && !*patterns)
2474                         patterns = NULL; // get rid of the empty string
2475
2476                 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2477                 {
2478                         //maps search
2479                         char t[MAX_QPATH];
2480                         if (GetMapList(s, t, sizeof(t)))
2481                         {
2482                                 // first move the cursor
2483                                 key_linepos += (int)strlen(t) - (int)strlen(s);
2484
2485                                 // and now do the actual work
2486                                 *s = 0;
2487                                 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2488                                 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2489
2490                                 // and fix the cursor
2491                                 if(key_linepos > (int) strlen(key_lines[edit_line]))
2492                                         key_linepos = (int) strlen(key_lines[edit_line]);
2493                         }
2494                         return;
2495                 }
2496                 else
2497                 {
2498                         if(patterns)
2499                         {
2500                                 char t[MAX_QPATH];
2501                                 stringlist_t resultbuf, dirbuf;
2502
2503                                 // Usage:
2504                                 //   // store completion patterns (space separated) for command foo in con_completion_foo
2505                                 //   set con_completion_foo "foodata/*.foodefault *.foo"
2506                                 //   foo <TAB>
2507                                 //
2508                                 // Note: patterns with slash are always treated as absolute
2509                                 // patterns; patterns without slash search in the innermost
2510                                 // directory the user specified. There is no way to "complete into"
2511                                 // a directory as of now, as directories seem to be unknown to the
2512                                 // FS subsystem.
2513                                 //
2514                                 // Examples:
2515                                 //   set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2516                                 //   set con_completion_playdemo "*.dem"
2517                                 //   set con_completion_play "*.wav *.ogg"
2518                                 //
2519                                 // TODO somehow add support for directories; these shall complete
2520                                 // to their name + an appended slash.
2521
2522                                 stringlistinit(&resultbuf);
2523                                 stringlistinit(&dirbuf);
2524                                 while(COM_ParseToken_Simple(&patterns, false, false))
2525                                 {
2526                                         fssearch_t *search;
2527                                         if(strchr(com_token, '/'))
2528                                         {
2529                                                 search = FS_Search(com_token, true, true);
2530                                         }
2531                                         else
2532                                         {
2533                                                 const char *slash = strrchr(s, '/');
2534                                                 if(slash)
2535                                                 {
2536                                                         strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2537                                                         strlcat(t, com_token, sizeof(t));
2538                                                         search = FS_Search(t, true, true);
2539                                                 }
2540                                                 else
2541                                                         search = FS_Search(com_token, true, true);
2542                                         }
2543                                         if(search)
2544                                         {
2545                                                 for(i = 0; i < search->numfilenames; ++i)
2546                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2547                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2548                                                                         stringlistappend(&resultbuf, search->filenames[i]);
2549                                                 FS_FreeSearch(search);
2550                                         }
2551                                 }
2552
2553                                 // In any case, add directory names
2554                                 {
2555                                         fssearch_t *search;
2556                                         const char *slash = strrchr(s, '/');
2557                                         if(slash)
2558                                         {
2559                                                 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2560                                                 strlcat(t, "*", sizeof(t));
2561                                                 search = FS_Search(t, true, true);
2562                                         }
2563                                         else
2564                                                 search = FS_Search("*", true, true);
2565                                         if(search)
2566                                         {
2567                                                 for(i = 0; i < search->numfilenames; ++i)
2568                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2569                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2570                                                                         stringlistappend(&dirbuf, search->filenames[i]);
2571                                                 FS_FreeSearch(search);
2572                                         }
2573                                 }
2574
2575                                 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2576                                 {
2577                                         const char *p, *q;
2578                                         unsigned int matchchars;
2579                                         if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2580                                         {
2581                                                 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2582                                         }
2583                                         else
2584                                         if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2585                                         {
2586                                                 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2587                                         }
2588                                         else
2589                                         {
2590                                                 stringlistsort(&resultbuf); // dirbuf is already sorted
2591                                                 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2592                                                 for(i = 0; i < dirbuf.numstrings; ++i)
2593                                                 {
2594                                                         Con_Printf("%s/\n", dirbuf.strings[i]);
2595                                                 }
2596                                                 for(i = 0; i < resultbuf.numstrings; ++i)
2597                                                 {
2598                                                         Con_Printf("%s\n", resultbuf.strings[i]);
2599                                                 }
2600                                                 matchchars = sizeof(t) - 1;
2601                                                 if(resultbuf.numstrings > 0)
2602                                                 {
2603                                                         p = resultbuf.strings[0];
2604                                                         q = resultbuf.strings[resultbuf.numstrings - 1];
2605                                                         for(; *p && *p == *q; ++p, ++q);
2606                                                         matchchars = (unsigned int)(p - resultbuf.strings[0]);
2607                                                 }
2608                                                 if(dirbuf.numstrings > 0)
2609                                                 {
2610                                                         p = dirbuf.strings[0];
2611                                                         q = dirbuf.strings[dirbuf.numstrings - 1];
2612                                                         for(; *p && *p == *q; ++p, ++q);
2613                                                         matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2614                                                 }
2615                                                 // now p points to the first non-equal character, or to the end
2616                                                 // of resultbuf.strings[0]. We want to append the characters
2617                                                 // from resultbuf.strings[0] to (not including) p as these are
2618                                                 // the unique prefix
2619                                                 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2620                                         }
2621
2622                                         // first move the cursor
2623                                         key_linepos += (int)strlen(t) - (int)strlen(s);
2624
2625                                         // and now do the actual work
2626                                         *s = 0;
2627                                         strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2628                                         strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2629
2630                                         // and fix the cursor
2631                                         if(key_linepos > (int) strlen(key_lines[edit_line]))
2632                                                 key_linepos = (int) strlen(key_lines[edit_line]);
2633                                 }
2634                                 stringlistfreecontents(&resultbuf);
2635                                 stringlistfreecontents(&dirbuf);
2636
2637                                 return; // bail out, when we complete for a command that wants a file name
2638                         }
2639                 }
2640         }
2641
2642         // Count number of possible matches and print them
2643         c = Cmd_CompleteCountPossible(s);
2644         if (c)
2645         {
2646                 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2647                 Cmd_CompleteCommandPrint(s);
2648         }
2649         v = Cvar_CompleteCountPossible(s);
2650         if (v)
2651         {
2652                 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2653                 Cvar_CompleteCvarPrint(s);
2654         }
2655         a = Cmd_CompleteAliasCountPossible(s);
2656         if (a)
2657         {
2658                 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2659                 Cmd_CompleteAliasPrint(s);
2660         }
2661         n = Nicks_CompleteCountPossible(key_lines[edit_line], key_linepos, s, true);
2662         if (n)
2663         {
2664                 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2665                 Cmd_CompleteNicksPrint(n);
2666         }
2667
2668         if (!(c + v + a + n))   // No possible matches
2669         {
2670                 if(s2[0])
2671                         strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
2672                 return;
2673         }
2674
2675         if (c)
2676                 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2677         if (v)
2678                 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2679         if (a)
2680                 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2681         if (n)
2682                 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2683
2684         for (cmd_len = (int)strlen(s);;cmd_len++)
2685         {
2686                 const char **l;
2687                 for (i = 0; i < 3; i++)
2688                         if (list[i])
2689                                 for (l = list[i];*l;l++)
2690                                         if ((*l)[cmd_len] != cmd[cmd_len])
2691                                                 goto done;
2692                 // all possible matches share this character, so we continue...
2693                 if (!cmd[cmd_len])
2694                 {
2695                         // if all matches ended at the same position, stop
2696                         // (this means there is only one match)
2697                         break;
2698                 }
2699         }
2700 done:
2701
2702         // prevent a buffer overrun by limiting cmd_len according to remaining space
2703         cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
2704         if (cmd)
2705         {
2706                 key_linepos = pos;
2707                 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
2708                 key_linepos += cmd_len;
2709                 // if there is only one match, add a space after it
2710                 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
2711                 {
2712                         if(n)
2713                         { // was a nick, might have an offset, and needs colors ;) --blub
2714                                 key_linepos = pos - Nicks_offset[0];
2715                                 cmd_len = strlen(Nicks_list[0]);
2716                                 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 3 - pos);
2717
2718                                 memcpy(&key_lines[edit_line][key_linepos] , Nicks_list[0], cmd_len);
2719                                 key_linepos += cmd_len;
2720                                 if(key_linepos < (int)(sizeof(key_lines[edit_line])-4)) // space for ^, X and space and \0
2721                                         key_linepos = Nicks_AddLastColor(key_lines[edit_line], key_linepos);
2722                         }
2723                         key_lines[edit_line][key_linepos++] = ' ';
2724                 }
2725         }
2726
2727         // use strlcat to avoid a buffer overrun
2728         key_lines[edit_line][key_linepos] = 0;
2729         strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
2730
2731         // free the command, cvar, and alias lists
2732         for (i = 0; i < 4; i++)
2733                 if (list[i])
2734                         Mem_Free((void *)list[i]);
2735 }
2736