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