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