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