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