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