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