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