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