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