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