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