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