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