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