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