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