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