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