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