]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - console.c
fix bug in skybox render + r_glsl_usegeneric + fog; add gamma to postprocessing shader
[xonotic/darkplaces.git] / console.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20 // console.c
21
22 #include "quakedef.h"
23
24 #if !defined(WIN32) || defined(__MINGW32__)
25 # include <unistd.h>
26 #endif
27 #include <time.h>
28
29 float con_cursorspeed = 4;
30
31 #define         CON_TEXTSIZE    131072
32 #define         CON_MAXLINES      4096
33
34 // lines up from bottom to display
35 int con_backscroll;
36
37 // console buffer
38 char con_text[CON_TEXTSIZE];
39
40 #define CON_MASK_HIDENOTIFY 128
41 #define CON_MASK_CHAT 1
42
43 typedef struct
44 {
45         char *start;
46         int len;
47
48         double addtime;
49         int mask;
50
51         int height; // recalculated line height when needed (-1 to unset)
52 }
53 con_lineinfo;
54 con_lineinfo con_lines[CON_MAXLINES];
55
56 int con_lines_first; // cyclic buffer
57 int con_lines_count;
58 #define CON_LINES_IDX(i) ((con_lines_first + (i)) % CON_MAXLINES)
59 #define CON_LINES_LAST CON_LINES_IDX(con_lines_count - 1)
60 #define CON_LINES(i) con_lines[CON_LINES_IDX(i)]
61 #define CON_LINES_PRED(i) (((i) + CON_MAXLINES - 1) % CON_MAXLINES)
62 #define CON_LINES_SUCC(i) (((i) + 1) % CON_MAXLINES)
63
64 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
65 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
66 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
67
68 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
69 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
70 cvar_t con_chatpos = {CVAR_SAVE, "con_chatpos","0", "where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top)"};
71 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
72 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
73 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
74 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
75
76
77 cvar_t sys_specialcharactertranslation = {0, "sys_specialcharactertranslation", "1", "terminal console conchars to ASCII translation (set to 0 if your conchars.tga is for an 8bit character set or if you want raw output)"};
78 #ifdef WIN32
79 cvar_t sys_colortranslation = {0, "sys_colortranslation", "0", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
80 #else
81 cvar_t sys_colortranslation = {0, "sys_colortranslation", "1", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
82 #endif
83
84
85 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
86 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
87                                    "0: add nothing after completion. "
88                                    "1: add the last color after completion. "
89                                    "2: add a quote when starting a quote instead of the color. "
90                                    "4: will replace 1, will force color, even after a quote. "
91                                    "8: ignore non-alphanumerics. "
92                                    "16: ignore spaces. "};
93 #define NICKS_ADD_COLOR 1
94 #define NICKS_ADD_QUOTE 2
95 #define NICKS_FORCE_COLOR 4
96 #define NICKS_ALPHANUMERICS_ONLY 8
97 #define NICKS_NO_SPACES 16
98
99 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_Print("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", true) : 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 int Sbar_GetPlayer (int index); // <- safety?
1775
1776 // Now it becomes TRICKY :D --blub
1777 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];     // contains the nicks with colors and all that
1778 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];  // sanitized list for completion when there are other possible matches.
1779 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
1780 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
1781 static int Nicks_matchpos;
1782
1783 // co against <<:BLASTER:>> is true!?
1784 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
1785 {
1786         while(a_len)
1787         {
1788                 if(tolower(*a) == tolower(*b))
1789                 {
1790                         if(*a == 0)
1791                                 return 0;
1792                         --a_len;
1793                         ++a;
1794                         ++b;
1795                         continue;
1796                 }
1797                 if(!*a)
1798                         return -1;
1799                 if(!*b)
1800                         return 1;
1801                 if(*a == ' ')
1802                         return (*a < *b) ? -1 : 1;
1803                 if(*b == ' ')
1804                         ++b;
1805                 else
1806                         return (*a < *b) ? -1 : 1;
1807         }
1808         return 0;
1809 }
1810 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
1811 {
1812         char space_char;
1813         if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
1814         {
1815                 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
1816                         return Nicks_strncasecmp_nospaces(a, b, a_len);
1817                 return strncasecmp(a, b, a_len);
1818         }
1819         
1820         space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
1821         
1822         // ignore non alphanumerics of B
1823         // if A contains a non-alphanumeric, B must contain it as well though!
1824         while(a_len)
1825         {
1826                 qboolean alnum_a, alnum_b;
1827                 
1828                 if(tolower(*a) == tolower(*b))
1829                 {
1830                         if(*a == 0) // end of both strings, they're equal
1831                                 return 0;
1832                         --a_len;
1833                         ++a;
1834                         ++b;
1835                         continue;
1836                 }
1837                 // not equal, end of one string?
1838                 if(!*a)
1839                         return -1;
1840                 if(!*b)
1841                         return 1;
1842                 // ignore non alphanumerics
1843                 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
1844                 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
1845                 if(!alnum_a) // b must contain this
1846                         return (*a < *b) ? -1 : 1;
1847                 if(!alnum_b)
1848                         ++b;
1849                 // otherwise, both are alnum, they're just not equal, return the appropriate number
1850                 else
1851                         return (*a < *b) ? -1 : 1;
1852         }
1853         return 0;
1854 }
1855
1856 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
1857 {
1858         char name[128];
1859         int i, p;
1860         int length;
1861         int match;
1862         int spos;
1863         int count = 0;
1864         
1865         if(!con_nickcompletion.integer)
1866                 return 0;
1867
1868         // changed that to 1
1869         if(!line[0])// || !line[1]) // we want at least... 2 written characters
1870                 return 0;
1871         
1872         for(i = 0; i < cl.maxclients; ++i)
1873         {
1874                 /*p = Sbar_GetPlayer(i);
1875                 if(p < 0)
1876                 break;*/
1877                 p = i;
1878                 if(!cl.scores[p].name[0])
1879                         continue;
1880
1881                 SanitizeString(cl.scores[p].name, name);
1882                 //Con_Printf("Sanitized: %s^7 -> %s", cl.scores[p].name, name);
1883                 
1884                 if(!name[0])
1885                         continue;
1886                 
1887                 length = strlen(name);
1888                 match = -1;
1889                 spos = pos - 1; // no need for a minimum of characters :)
1890                 
1891                 while(spos >= 0 && (spos - pos) < length) // search-string-length < name length
1892                 {
1893                         if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
1894                         {
1895                                 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
1896                                    !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
1897                                 {
1898                                         --spos;
1899                                         continue;
1900                                 }
1901                         }
1902                         if(isCon && spos == 0)
1903                                 break;
1904                         if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
1905                                 match = spos;
1906                         --spos;
1907                 }
1908                 if(match < 0)
1909                         continue;
1910                 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
1911                 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
1912
1913                 // the sanitized list
1914                 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
1915                 if(!count)
1916                 {
1917                         Nicks_matchpos = match;
1918                 }
1919                 
1920                 Nicks_offset[count] = s - (&line[match]);
1921                 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
1922
1923                 ++count;
1924         }
1925         return count;
1926 }
1927
1928 void Cmd_CompleteNicksPrint(int count)
1929 {
1930         int i;
1931         for(i = 0; i < count; ++i)
1932                 Con_Printf("%s\n", Nicks_list[i]);
1933 }
1934
1935 void Nicks_CutMatchesNormal(int count)
1936 {
1937         // cut match 0 down to the longest possible completion
1938         int i;
1939         unsigned int c, l;
1940         c = strlen(Nicks_sanlist[0]) - 1;
1941         for(i = 1; i < count; ++i)
1942         {
1943                 l = strlen(Nicks_sanlist[i]) - 1;
1944                 if(l < c)
1945                         c = l;
1946                 
1947                 for(l = 0; l <= c; ++l)
1948                         if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
1949                         {
1950                                 c = l-1;
1951                                 break;
1952                         }
1953         }
1954         Nicks_sanlist[0][c+1] = 0;
1955         //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
1956 }
1957
1958 unsigned int Nicks_strcleanlen(const char *s)
1959 {
1960         unsigned int l = 0;
1961         while(*s)
1962         {
1963                 if( (*s >= 'a' && *s <= 'z') ||
1964                     (*s >= 'A' && *s <= 'Z') ||
1965                     (*s >= '0' && *s <= '9') ||
1966                     *s == ' ')
1967                         ++l;
1968                 ++s;
1969         }
1970         return l;
1971 }
1972
1973 void Nicks_CutMatchesAlphaNumeric(int count)
1974 {
1975         // cut match 0 down to the longest possible completion
1976         int i;
1977         unsigned int c, l;
1978         char tempstr[sizeof(Nicks_sanlist[0])];
1979         char *a, *b;
1980         char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
1981         
1982         c = strlen(Nicks_sanlist[0]);
1983         for(i = 0, l = 0; i < (int)c; ++i)
1984         {
1985                 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
1986                     (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
1987                     (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
1988                 {
1989                         tempstr[l++] = Nicks_sanlist[0][i];
1990                 }
1991         }
1992         tempstr[l] = 0;
1993         
1994         for(i = 1; i < count; ++i)
1995         {
1996                 a = tempstr;
1997                 b = Nicks_sanlist[i];
1998                 while(1)
1999                 {
2000                         if(!*a)
2001                                 break;
2002                         if(!*b)
2003                         {
2004                                 *a = 0;
2005                                 break;
2006                         }
2007                         if(tolower(*a) == tolower(*b))
2008                         {
2009                                 ++a;
2010                                 ++b;
2011                                 continue;
2012                         }
2013                         if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2014                         {
2015                                 // b is alnum, so cut
2016                                 *a = 0;
2017                                 break;
2018                         }
2019                         ++b;
2020                 }
2021         }
2022         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2023         Nicks_CutMatchesNormal(count);
2024         //if(!Nicks_sanlist[0][0])
2025         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2026         {
2027                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2028                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2029         }
2030 }
2031
2032 void Nicks_CutMatchesNoSpaces(int count)
2033 {
2034         // cut match 0 down to the longest possible completion
2035         int i;
2036         unsigned int c, l;
2037         char tempstr[sizeof(Nicks_sanlist[0])];
2038         char *a, *b;
2039         
2040         c = strlen(Nicks_sanlist[0]);
2041         for(i = 0, l = 0; i < (int)c; ++i)
2042         {
2043                 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2044                 {
2045                         tempstr[l++] = Nicks_sanlist[0][i];
2046                 }
2047         }
2048         tempstr[l] = 0;
2049         
2050         for(i = 1; i < count; ++i)
2051         {
2052                 a = tempstr;
2053                 b = Nicks_sanlist[i];
2054                 while(1)
2055                 {
2056                         if(!*a)
2057                                 break;
2058                         if(!*b)
2059                         {
2060                                 *a = 0;
2061                                 break;
2062                         }
2063                         if(tolower(*a) == tolower(*b))
2064                         {
2065                                 ++a;
2066                                 ++b;
2067                                 continue;
2068                         }
2069                         if(*b != ' ')
2070                         {
2071                                 *a = 0;
2072                                 break;
2073                         }
2074                         ++b;
2075                 }
2076         }
2077         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2078         Nicks_CutMatchesNormal(count);
2079         //if(!Nicks_sanlist[0][0])
2080         //Con_Printf("TS: %s\n", tempstr);
2081         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2082         {
2083                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2084                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2085         }
2086 }
2087
2088 void Nicks_CutMatches(int count)
2089 {
2090         if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2091                 Nicks_CutMatchesAlphaNumeric(count);
2092         else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2093                 Nicks_CutMatchesNoSpaces(count);
2094         else
2095                 Nicks_CutMatchesNormal(count);
2096 }
2097
2098 const char **Nicks_CompleteBuildList(int count)
2099 {
2100         const char **buf;
2101         int bpos = 0;
2102         // the list is freed by Con_CompleteCommandLine, so create a char**
2103         buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2104
2105         for(; bpos < count; ++bpos)
2106                 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2107
2108         Nicks_CutMatches(count);
2109         
2110         buf[bpos] = NULL;
2111         return buf;
2112 }
2113
2114 int Nicks_AddLastColor(char *buffer, int pos)
2115 {
2116         qboolean quote_added = false;
2117         int match;
2118         char color = '7';
2119         
2120         if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2121         {
2122                 // we'll have to add a quote :)
2123                 buffer[pos++] = '\"';
2124                 quote_added = true;
2125         }
2126         
2127         if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2128         {
2129                 // add color when no quote was added, or when flags &4?
2130                 // find last color
2131                 for(match = Nicks_matchpos-1; match >= 0; --match)
2132                 {
2133                         if(buffer[match] == STRING_COLOR_TAG && buffer[match+1] >= '0' && buffer[match+1] <= '9')
2134                         {
2135                                 color = buffer[match+1];
2136                                 break;
2137                         }
2138                 }
2139                 if(!quote_added && buffer[pos-2] == STRING_COLOR_TAG && buffer[pos-1] >= '0' && buffer[pos-1] <= '9') // when thes use &4
2140                         pos -= 2;
2141                 buffer[pos++] = STRING_COLOR_TAG;
2142                 buffer[pos++] = color;
2143         }
2144         return pos;
2145 }
2146
2147 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2148 {
2149         int n;
2150         /*if(!con_nickcompletion.integer)
2151           return; is tested in Nicks_CompletionCountPossible */
2152         n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2153         if(n == 1)
2154         {
2155                 size_t len;
2156                 char *msg;
2157                 
2158                 msg = Nicks_list[0];
2159                 len = min(size - Nicks_matchpos - 3, strlen(msg));
2160                 memcpy(&buffer[Nicks_matchpos], msg, len);
2161                 if( len < (size - 4) ) // space for color and space and \0
2162                         len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2163                 buffer[len++] = ' ';
2164                 buffer[len] = 0;
2165                 return len;
2166         } else if(n > 1)
2167         {
2168                 int len;
2169                 char *msg;
2170                 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2171                 Cmd_CompleteNicksPrint(n);
2172
2173                 Nicks_CutMatches(n);
2174
2175                 msg = Nicks_sanlist[0];
2176                 len = min(size - Nicks_matchpos, strlen(msg));
2177                 memcpy(&buffer[Nicks_matchpos], msg, len);
2178                 buffer[Nicks_matchpos + len] = 0;
2179                 //pos += len;
2180                 return Nicks_matchpos + len;
2181         }
2182         return pos;
2183 }
2184
2185
2186 /*
2187         Con_CompleteCommandLine
2188
2189         New function for tab-completion system
2190         Added by EvilTypeGuy
2191         Thanks to Fett erich@heintz.com
2192         Thanks to taniwha
2193         Enhanced to tab-complete map names by [515]
2194
2195 */
2196 void Con_CompleteCommandLine (void)
2197 {
2198         const char *cmd = "";
2199         char *s;
2200         const char **list[4] = {0, 0, 0, 0};
2201         char s2[512];
2202         char command[512];
2203         int c, v, a, i, cmd_len, pos, k;
2204         int n; // nicks --blub
2205         const char *space;
2206         
2207         //find what we want to complete
2208         pos = key_linepos;
2209         while(--pos)
2210         {
2211                 k = key_lines[edit_line][pos];
2212                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2213                         break;
2214         }
2215         pos++;
2216
2217         s = key_lines[edit_line] + pos;
2218         strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2));    //save chars after cursor
2219         key_lines[edit_line][key_linepos] = 0;                                  //hide them
2220
2221         space = strchr(key_lines[edit_line] + 1, ' ');
2222         if(space && pos == (space - key_lines[edit_line]) + 1)
2223         {
2224                 strlcpy(command, key_lines[edit_line] + 1, min(sizeof(command), (unsigned int)(space - key_lines[edit_line])));
2225                 if(!strcmp(command, "map") || !strcmp(command, "changelevel"))
2226                 {
2227                         //maps search
2228                         char t[MAX_QPATH];
2229                         if (GetMapList(s, t, sizeof(t)))
2230                         {
2231                                 // first move the cursor
2232                                 key_linepos += (int)strlen(t) - (int)strlen(s);
2233
2234                                 // and now do the actual work
2235                                 *s = 0;
2236                                 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2237                                 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2238
2239                                 // and fix the cursor
2240                                 if(key_linepos > (int) strlen(key_lines[edit_line]))
2241                                         key_linepos = (int) strlen(key_lines[edit_line]);
2242                         }
2243                         return;
2244                 }
2245                 else
2246                 {
2247                         const char *patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2248
2249                         if(patterns && *patterns)
2250                         {
2251                                 char t[MAX_QPATH];
2252                                 stringlist_t resultbuf, dirbuf;
2253
2254                                 // Usage:
2255                                 //   // store completion patterns (space separated) for command foo in con_completion_foo
2256                                 //   set con_completion_foo "foodata/*.foodefault *.foo"
2257                                 //   foo <TAB>
2258                                 //
2259                                 // Note: patterns with slash are always treated as absolute
2260                                 // patterns; patterns without slash search in the innermost
2261                                 // directory the user specified. There is no way to "complete into"
2262                                 // a directory as of now, as directories seem to be unknown to the
2263                                 // FS subsystem.
2264                                 //
2265                                 // Examples:
2266                                 //   set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2267                                 //   set con_completion_playdemo "*.dem"
2268                                 //   set con_completion_play "*.wav *.ogg"
2269                                 //
2270                                 // TODO somehow add support for directories; these shall complete
2271                                 // to their name + an appended slash.
2272
2273                                 stringlistinit(&resultbuf);
2274                                 stringlistinit(&dirbuf);
2275                                 while(COM_ParseToken_Simple(&patterns, false, false))
2276                                 {
2277                                         fssearch_t *search;
2278                                         if(strchr(com_token, '/'))
2279                                         {
2280                                                 search = FS_Search(com_token, true, true);
2281                                         }
2282                                         else
2283                                         {
2284                                                 const char *slash = strrchr(s, '/');
2285                                                 if(slash)
2286                                                 {
2287                                                         strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2288                                                         strlcat(t, com_token, sizeof(t));
2289                                                         search = FS_Search(t, true, true);
2290                                                 }
2291                                                 else
2292                                                         search = FS_Search(com_token, true, true);
2293                                         }
2294                                         if(search)
2295                                         {
2296                                                 for(i = 0; i < search->numfilenames; ++i)
2297                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2298                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2299                                                                         stringlistappend(&resultbuf, search->filenames[i]);
2300                                                 FS_FreeSearch(search);
2301                                         }
2302                                 }
2303
2304                                 // In any case, add directory names
2305                                 {
2306                                         fssearch_t *search;
2307                                         const char *slash = strrchr(s, '/');
2308                                         if(slash)
2309                                         {
2310                                                 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2311                                                 strlcat(t, "*", sizeof(t));
2312                                                 search = FS_Search(t, true, true);
2313                                         }
2314                                         else
2315                                                 search = FS_Search("*", true, true);
2316                                         if(search)
2317                                         {
2318                                                 for(i = 0; i < search->numfilenames; ++i)
2319                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2320                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2321                                                                         stringlistappend(&dirbuf, search->filenames[i]);
2322                                                 FS_FreeSearch(search);
2323                                         }
2324                                 }
2325
2326                                 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2327                                 {
2328                                         const char *p, *q;
2329                                         unsigned int matchchars;
2330                                         if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2331                                         {
2332                                                 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2333                                         }
2334                                         else
2335                                         if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2336                                         {
2337                                                 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2338                                         }
2339                                         else
2340                                         {
2341                                                 stringlistsort(&resultbuf); // dirbuf is already sorted
2342                                                 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2343                                                 for(i = 0; i < dirbuf.numstrings; ++i)
2344                                                 {
2345                                                         Con_Printf("%s/\n", dirbuf.strings[i]);
2346                                                 }
2347                                                 for(i = 0; i < resultbuf.numstrings; ++i)
2348                                                 {
2349                                                         Con_Printf("%s\n", resultbuf.strings[i]);
2350                                                 }
2351                                                 matchchars = sizeof(t) - 1;
2352                                                 if(resultbuf.numstrings > 0)
2353                                                 {
2354                                                         p = resultbuf.strings[0];
2355                                                         q = resultbuf.strings[resultbuf.numstrings - 1];
2356                                                         for(; *p && *p == *q; ++p, ++q);
2357                                                         matchchars = (unsigned int)(p - resultbuf.strings[0]);
2358                                                 }
2359                                                 if(dirbuf.numstrings > 0)
2360                                                 {
2361                                                         p = dirbuf.strings[0];
2362                                                         q = dirbuf.strings[dirbuf.numstrings - 1];
2363                                                         for(; *p && *p == *q; ++p, ++q);
2364                                                         matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2365                                                 }
2366                                                 // now p points to the first non-equal character, or to the end
2367                                                 // of resultbuf.strings[0]. We want to append the characters
2368                                                 // from resultbuf.strings[0] to (not including) p as these are
2369                                                 // the unique prefix
2370                                                 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2371                                         }
2372
2373                                         // first move the cursor
2374                                         key_linepos += (int)strlen(t) - (int)strlen(s);
2375
2376                                         // and now do the actual work
2377                                         *s = 0;
2378                                         strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2379                                         strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2380
2381                                         // and fix the cursor
2382                                         if(key_linepos > (int) strlen(key_lines[edit_line]))
2383                                                 key_linepos = (int) strlen(key_lines[edit_line]);
2384                                 }
2385                                 stringlistfreecontents(&resultbuf);
2386                                 stringlistfreecontents(&dirbuf);
2387
2388                                 return; // bail out, when we complete for a command that wants a file name
2389                         }
2390                 }
2391         }
2392
2393         // Count number of possible matches and print them
2394         c = Cmd_CompleteCountPossible(s);
2395         if (c)
2396         {
2397                 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2398                 Cmd_CompleteCommandPrint(s);
2399         }
2400         v = Cvar_CompleteCountPossible(s);
2401         if (v)
2402         {
2403                 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2404                 Cvar_CompleteCvarPrint(s);
2405         }
2406         a = Cmd_CompleteAliasCountPossible(s);
2407         if (a)
2408         {
2409                 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2410                 Cmd_CompleteAliasPrint(s);
2411         }
2412         n = Nicks_CompleteCountPossible(key_lines[edit_line], key_linepos, s, true);
2413         if (n)
2414         {
2415                 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2416                 Cmd_CompleteNicksPrint(n);
2417         }
2418
2419         if (!(c + v + a + n))   // No possible matches
2420         {               
2421                 if(s2[0])
2422                         strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
2423                 return;
2424         }
2425
2426         if (c)
2427                 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2428         if (v)
2429                 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2430         if (a)
2431                 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2432         if (n)
2433                 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2434         
2435         for (cmd_len = (int)strlen(s);;cmd_len++)
2436         {
2437                 const char **l;
2438                 for (i = 0; i < 3; i++)
2439                         if (list[i])
2440                                 for (l = list[i];*l;l++)
2441                                         if ((*l)[cmd_len] != cmd[cmd_len])
2442                                                 goto done;
2443                 // all possible matches share this character, so we continue...
2444                 if (!cmd[cmd_len])
2445                 {
2446                         // if all matches ended at the same position, stop
2447                         // (this means there is only one match)
2448                         break;
2449                 }
2450         }
2451 done:
2452
2453         // prevent a buffer overrun by limiting cmd_len according to remaining space
2454         cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
2455         if (cmd)
2456         {
2457                 key_linepos = pos;
2458                 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
2459                 key_linepos += cmd_len;
2460                 // if there is only one match, add a space after it
2461                 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
2462                 {
2463                         if(n)
2464                         { // was a nick, might have an offset, and needs colors ;) --blub
2465                                 key_linepos = pos - Nicks_offset[0];
2466                                 cmd_len = strlen(Nicks_list[0]);
2467                                 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 3 - pos);
2468
2469                                 memcpy(&key_lines[edit_line][key_linepos] , Nicks_list[0], cmd_len);
2470                                 key_linepos += cmd_len;
2471                                 if(key_linepos < (int)(sizeof(key_lines[edit_line])-4)) // space for ^, X and space and \0
2472                                         key_linepos = Nicks_AddLastColor(key_lines[edit_line], key_linepos);
2473                         }
2474                         key_lines[edit_line][key_linepos++] = ' ';
2475                 }
2476         }
2477
2478         // use strlcat to avoid a buffer overrun
2479         key_lines[edit_line][key_linepos] = 0;
2480         strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
2481
2482         // free the command, cvar, and alias lists
2483         for (i = 0; i < 4; i++)
2484                 if (list[i])
2485                         Mem_Free((void *)list[i]);
2486 }
2487