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