Fix two bugs (everything should behave as usual now, when you're not using menu rende...
[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, false, FONT_CONSOLE) * con_textsize.value;
1149         if(x >= 0)
1150                 x = 0;
1151
1152         // draw it
1153         DrawQ_String_Font(x, con_vislines - con_textsize.value*2, text, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, FONT_CONSOLE );
1154
1155         // remove cursor
1156 //      key_lines[edit_line][key_linepos] = 0;
1157 }
1158
1159 typedef struct
1160 {
1161         dp_font_t *font;
1162         float alignment; // 0 = left, 0.5 = center, 1 = right
1163         float fontsize;
1164         float x;
1165         float y;
1166         float width;
1167         float ymin, ymax;
1168         const char *continuationString;
1169
1170         // PRIVATE:
1171         int colorindex; // init to -1
1172 }
1173 con_text_info_t;
1174
1175 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1176 {
1177         con_text_info_t *ti = (con_text_info_t *) passthrough;
1178         if(w == NULL)
1179         {
1180                 ti->colorindex = -1;
1181                 return ti->fontsize * ti->font->width_of[0];
1182         }
1183         if(maxWidth >= 0)
1184                 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1185         else if(maxWidth == -1)
1186                 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1187         else
1188         {
1189                 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1190                 // Note: this is NOT a Con_Printf, as it could print recursively
1191                 return 0;
1192         }
1193 }
1194
1195 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1196 {
1197         (void) passthrough;
1198         (void) line;
1199         (void) length;
1200         (void) width;
1201         (void) isContinuation;
1202         return 1;
1203 }
1204
1205 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1206 {
1207         con_text_info_t *ti = (con_text_info_t *) passthrough;
1208
1209         if(ti->y < ti->ymin - 0.001)
1210                 (void) 0;
1211         else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1212                 (void) 0;
1213         else
1214         {
1215                 int x = ti->x + (ti->width - width) * ti->alignment;
1216                 if(isContinuation && *ti->continuationString)
1217                         x += DrawQ_String_Font(x, ti->y, ti->continuationString, strlen(ti->continuationString), ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, ti->font);
1218                 if(length > 0)
1219                         DrawQ_String_Font(x, ti->y, line, length, ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, &(ti->colorindex), false, ti->font);
1220         }
1221
1222         ti->y += ti->fontsize;
1223         return 1;
1224 }
1225
1226
1227 int Con_DrawNotifyRect(int mask_must, int mask_mustnot, float maxage, float x, float y, float width, float height, float fontsize, float alignment_x, float alignment_y, const char *continuationString)
1228 {
1229         int i;
1230         int lines = 0;
1231         int maxlines = (int) floor(height / fontsize + 0.01f);
1232         int startidx;
1233         int nskip = 0;
1234         int continuationWidth = 0;
1235         size_t l;
1236         double t = cl.time; // saved so it won't change
1237         con_text_info_t ti;
1238
1239         ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1240         ti.fontsize = fontsize;
1241         ti.alignment = alignment_x;
1242         ti.width = width;
1243         ti.ymin = y;
1244         ti.ymax = y + height;
1245         ti.continuationString = continuationString;
1246
1247         l = 0;
1248         Con_WordWidthFunc(&ti, NULL, &l, -1);
1249         l = strlen(continuationString);
1250         continuationWidth = Con_WordWidthFunc(&ti, continuationString, &l, -1);
1251
1252         // first find the first line to draw by backwards iterating and word wrapping to find their length...
1253         startidx = con_lines_count;
1254         for(i = con_lines_count - 1; i >= 0; --i)
1255         {
1256                 con_lineinfo *l = &CON_LINES(i);
1257                 int mylines;
1258
1259                 if((l->mask & mask_must) != mask_must)
1260                         continue;
1261                 if(l->mask & mask_mustnot)
1262                         continue;
1263                 if(maxage && (l->addtime < t - maxage))
1264                         continue;
1265
1266                 // WE FOUND ONE!
1267                 // Calculate its actual height...
1268                 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1269                 if(lines + mylines >= maxlines)
1270                 {
1271                         nskip = lines + mylines - maxlines;
1272                         lines = maxlines;
1273                         startidx = i;
1274                         break;
1275                 }
1276                 lines += mylines;
1277                 startidx = i;
1278         }
1279
1280         // then center according to the calculated amount of lines...
1281         ti.x = x;
1282         ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1283
1284         // then actually draw
1285         for(i = startidx; i < con_lines_count; ++i)
1286         {
1287                 con_lineinfo *l = &CON_LINES(i);
1288
1289                 if((l->mask & mask_must) != mask_must)
1290                         continue;
1291                 if(l->mask & mask_mustnot)
1292                         continue;
1293                 if(maxage && (l->addtime < t - maxage))
1294                         continue;
1295
1296                 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1297         }
1298
1299         return lines;
1300 }
1301
1302 /*
1303 ================
1304 Con_DrawNotify
1305
1306 Draws the last few lines of output transparently over the game top
1307 ================
1308 */
1309 void Con_DrawNotify (void)
1310 {
1311         float   x, v;
1312         float chatstart, notifystart, inputsize;
1313         float align;
1314         char    temptext[MAX_INPUTLINE];
1315         int numChatlines;
1316         int chatpos;
1317
1318         Con_FixTimes();
1319
1320         numChatlines = con_chat.integer;
1321         chatpos = con_chatpos.integer;
1322
1323         if (con_notify.integer < 0)
1324                 Cvar_SetValueQuick(&con_notify, 0);
1325         if (gamemode == GAME_TRANSFUSION)
1326                 v = 8; // vertical offset
1327         else
1328                 v = 0;
1329
1330         // GAME_NEXUIZ: center, otherwise left justify
1331         align = con_notifyalign.value;
1332         if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1333         {
1334                 if(gamemode == GAME_NEXUIZ)
1335                         align = 0.5;
1336         }
1337
1338         if(numChatlines)
1339         {
1340                 if(chatpos == 0)
1341                 {
1342                         // first chat, input line, then notify
1343                         chatstart = v;
1344                         notifystart = v + (numChatlines + 1) * con_chatsize.value;
1345                 }
1346                 else if(chatpos > 0)
1347                 {
1348                         // first notify, then (chatpos-1) empty lines, then chat, then input
1349                         notifystart = v;
1350                         chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1351                 }
1352                 else // if(chatpos < 0)
1353                 {
1354                         // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1355                         notifystart = v;
1356                         chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1357                 }
1358         }
1359         else
1360         {
1361                 // just notify and input
1362                 notifystart = v;
1363                 chatstart = 0; // shut off gcc warning
1364         }
1365
1366         v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_HIDENOTIFY | (numChatlines ? CON_MASK_CHAT : 0), con_notifytime.value, 0, notifystart, vid_conwidth.value, con_notify.value * con_notifysize.value, con_notifysize.value, align, 0.0, "");
1367
1368         // chat?
1369         if(numChatlines)
1370         {
1371                 v = chatstart + numChatlines * con_chatsize.value;
1372                 Con_DrawNotifyRect(CON_MASK_CHAT, 0, con_chattime.value, 0, chatstart, vid_conwidth.value * con_chatwidth.value, v - chatstart, con_chatsize.value, 0.0, 1.0, "^3\014\014\014 "); // 015 is ยท> character in conchars.tga
1373         }
1374
1375         if (key_dest == key_message)
1376         {
1377                 int colorindex = -1;
1378
1379                 // LordHavoc: speedup, and other improvements
1380                 if (chat_team)
1381                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1382                 else
1383                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1384
1385                 // FIXME word wrap
1386                 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1387                 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1388                 if(x > 0)
1389                         x = 0;
1390                 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1391         }
1392 }
1393
1394 /*
1395 ================
1396 Con_MeasureConsoleLine
1397
1398 Counts the number of lines for a line on the console.
1399 ================
1400 */
1401 int Con_MeasureConsoleLine(int lineno)
1402 {
1403         float width = vid_conwidth.value;
1404         con_text_info_t ti;
1405         ti.fontsize = con_textsize.value;
1406         ti.font = FONT_CONSOLE;
1407
1408         return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1409 }
1410
1411 /*
1412 ================
1413 Con_LineHeight
1414
1415 Returns the height of a given console line; calculates it if necessary.
1416 ================
1417 */
1418 int Con_LineHeight(int i)
1419 {
1420         int h = con_lines[i].height;
1421         if(h != -1)
1422                 return h;
1423         return con_lines[i].height = Con_MeasureConsoleLine(i);
1424 }
1425
1426 /*
1427 ================
1428 Con_DrawConsoleLine
1429
1430 Draws a line of the console; returns its height in lines.
1431 If alpha is 0, the line is not drawn, but still wrapped and its height
1432 returned.
1433 ================
1434 */
1435 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1436 {
1437         float width = vid_conwidth.value;
1438
1439         con_text_info_t ti;
1440         ti.continuationString = "";
1441         ti.alignment = 0;
1442         ti.fontsize = con_textsize.value;
1443         ti.font = FONT_CONSOLE;
1444         ti.x = 0;
1445         ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1446         ti.ymin = ymin;
1447         ti.ymax = ymax;
1448         ti.width = width;
1449
1450         return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1451 }
1452
1453 /*
1454 ================
1455 Con_LastVisibleLine
1456
1457 Calculates the last visible line index and how much to show of it based on
1458 con_backscroll.
1459 ================
1460 */
1461 void Con_LastVisibleLine(int *last, int *limitlast)
1462 {
1463         int lines_seen = 0;
1464         int ic;
1465
1466         if(con_backscroll < 0)
1467                 con_backscroll = 0;
1468
1469         // now count until we saw con_backscroll actual lines
1470         for(ic = 0; ic < con_lines_count; ++ic)
1471         {
1472                 int i = CON_LINES_IDX(con_lines_count - 1 - ic);
1473                 int h = Con_LineHeight(i);
1474
1475                 // line is the last visible line?
1476                 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1477                 {
1478                         *last = i;
1479                         *limitlast = lines_seen + h - con_backscroll;
1480                         return;
1481                 }
1482
1483                 lines_seen += h;
1484         }
1485
1486         // if we get here, no line was on screen - scroll so that one line is
1487         // visible then.
1488         con_backscroll = lines_seen - 1;
1489         *last = con_lines_first;
1490         *limitlast = 1;
1491 }
1492
1493 /*
1494 ================
1495 Con_DrawConsole
1496
1497 Draws the console with the solid background
1498 The typing input line at the bottom should only be drawn if typing is allowed
1499 ================
1500 */
1501 void Con_DrawConsole (int lines)
1502 {
1503         int i, last, limitlast;
1504         float y;
1505
1506         if (lines <= 0)
1507                 return;
1508
1509         con_vislines = lines;
1510
1511 // draw the background
1512         DrawQ_Pic(0, lines - vid_conheight.integer, scr_conbrightness.value >= 0.01f ? Draw_CachePic("gfx/conback", true) : NULL, vid_conwidth.integer, vid_conheight.integer, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, cls.signon == SIGNONS ? scr_conalpha.value : 1.0, 0); // always full alpha when not in game
1513         DrawQ_String_Font(vid_conwidth.integer - DrawQ_TextWidth_Font(engineversion, 0, false, FONT_CONSOLE) * con_textsize.value, lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0, NULL, true, FONT_CONSOLE);
1514
1515 // draw the text
1516         if(con_lines_count > 0)
1517         {
1518                 float ymax = con_vislines - 2 * con_textsize.value;
1519                 Con_LastVisibleLine(&last, &limitlast);
1520                 y = ymax - con_textsize.value;
1521
1522                 if(limitlast)
1523                         y += (con_lines[last].height - limitlast) * con_textsize.value;
1524                 i = last;
1525
1526                 for(;;)
1527                 {
1528                         y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1529                         if(i == con_lines_first)
1530                                 break; // top of console buffer
1531                         if(y < 0)
1532                                 break; // top of console window
1533                         limitlast = 0;
1534                         i = CON_LINES_PRED(i);
1535                 }
1536         }
1537
1538 // draw the input prompt, user text, and cursor if desired
1539         Con_DrawInput ();
1540 }
1541
1542 /*
1543 GetMapList
1544
1545 Made by [515]
1546 Prints not only map filename, but also
1547 its format (q1/q2/q3/hl) and even its message
1548 */
1549 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1550 //LordHavoc: rewrote bsp type detection, added mcbsp support and rewrote message extraction to do proper worldspawn parsing
1551 //LordHavoc: added .ent file loading, and redesigned error handling to still try the .ent file even if the map format is not recognized, this also eliminated one goto
1552 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1553 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1554 {
1555         fssearch_t      *t;
1556         char            message[64];
1557         int                     i, k, max, p, o, min;
1558         unsigned char *len;
1559         qfile_t         *f;
1560         unsigned char buf[1024];
1561
1562         sprintf(message, "maps/%s*.bsp", s);
1563         t = FS_Search(message, 1, true);
1564         if(!t)
1565                 return false;
1566         if (t->numfilenames > 1)
1567                 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1568         len = (unsigned char *)Z_Malloc(t->numfilenames);
1569         min = 666;
1570         for(max=i=0;i<t->numfilenames;i++)
1571         {
1572                 k = (int)strlen(t->filenames[i]);
1573                 k -= 9;
1574                 if(max < k)
1575                         max = k;
1576                 else
1577                 if(min > k)
1578                         min = k;
1579                 len[i] = k;
1580         }
1581         o = (int)strlen(s);
1582         for(i=0;i<t->numfilenames;i++)
1583         {
1584                 int lumpofs = 0, lumplen = 0;
1585                 char *entities = NULL;
1586                 const char *data = NULL;
1587                 char keyname[64];
1588                 char entfilename[MAX_QPATH];
1589                 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1590                 p = 0;
1591                 f = FS_Open(t->filenames[i], "rb", true, false);
1592                 if(f)
1593                 {
1594                         memset(buf, 0, 1024);
1595                         FS_Read(f, buf, 1024);
1596                         if (!memcmp(buf, "IBSP", 4))
1597                         {
1598                                 p = LittleLong(((int *)buf)[1]);
1599                                 if (p == Q3BSPVERSION)
1600                                 {
1601                                         q3dheader_t *header = (q3dheader_t *)buf;
1602                                         lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1603                                         lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1604                                 }
1605                                 else if (p == Q2BSPVERSION)
1606                                 {
1607                                         q2dheader_t *header = (q2dheader_t *)buf;
1608                                         lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1609                                         lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1610                                 }
1611                         }
1612                         else if (!memcmp(buf, "MCBSPpad", 8))
1613                         {
1614                                 p = LittleLong(((int *)buf)[2]);
1615                                 if (p == MCBSPVERSION)
1616                                 {
1617                                         int numhulls = LittleLong(((int *)buf)[3]);
1618                                         lumpofs = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+0]);
1619                                         lumplen = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+1]);
1620                                 }
1621                         }
1622                         else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1623                         {
1624                                 dheader_t *header = (dheader_t *)buf;
1625                                 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1626                                 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1627                         }
1628                         else
1629                                 p = 0;
1630                         strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1631                         memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1632                         entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1633                         if (!entities && lumplen >= 10)
1634                         {
1635                                 FS_Seek(f, lumpofs, SEEK_SET);
1636                                 entities = (char *)Z_Malloc(lumplen + 1);
1637                                 FS_Read(f, entities, lumplen);
1638                         }
1639                         if (entities)
1640                         {
1641                                 // if there are entities to parse, a missing message key just
1642                                 // means there is no title, so clear the message string now
1643                                 message[0] = 0;
1644                                 data = entities;
1645                                 for (;;)
1646                                 {
1647                                         int l;
1648                                         if (!COM_ParseToken_Simple(&data, false, false))
1649                                                 break;
1650                                         if (com_token[0] == '{')
1651                                                 continue;
1652                                         if (com_token[0] == '}')
1653                                                 break;
1654                                         // skip leading whitespace
1655                                         for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
1656                                         for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
1657                                                 keyname[l] = com_token[k+l];
1658                                         keyname[l] = 0;
1659                                         if (!COM_ParseToken_Simple(&data, false, false))
1660                                                 break;
1661                                         if (developer.integer >= 100)
1662                                                 Con_Printf("key: %s %s\n", keyname, com_token);
1663                                         if (!strcmp(keyname, "message"))
1664                                         {
1665                                                 // get the message contents
1666                                                 strlcpy(message, com_token, sizeof(message));
1667                                                 break;
1668                                         }
1669                                 }
1670                         }
1671                 }
1672                 if (entities)
1673                         Z_Free(entities);
1674                 if(f)
1675                         FS_Close(f);
1676                 *(t->filenames[i]+len[i]+5) = 0;
1677                 switch(p)
1678                 {
1679                 case Q3BSPVERSION:      strlcpy((char *)buf, "Q3", sizeof(buf));break;
1680                 case Q2BSPVERSION:      strlcpy((char *)buf, "Q2", sizeof(buf));break;
1681                 case BSPVERSION:        strlcpy((char *)buf, "Q1", sizeof(buf));break;
1682                 case MCBSPVERSION:      strlcpy((char *)buf, "MC", sizeof(buf));break;
1683                 case 30:                        strlcpy((char *)buf, "HL", sizeof(buf));break;
1684                 default:                        strlcpy((char *)buf, "??", sizeof(buf));break;
1685                 }
1686                 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1687         }
1688         Con_Print("\n");
1689         for(p=o;p<min;p++)
1690         {
1691                 k = *(t->filenames[0]+5+p);
1692                 if(k == 0)
1693                         goto endcomplete;
1694                 for(i=1;i<t->numfilenames;i++)
1695                         if(*(t->filenames[i]+5+p) != k)
1696                                 goto endcomplete;
1697         }
1698 endcomplete:
1699         if(p > o && completedname && completednamebufferlength > 0)
1700         {
1701                 memset(completedname, 0, completednamebufferlength);
1702                 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1703         }
1704         Z_Free(len);
1705         FS_FreeSearch(t);
1706         return p > o;
1707 }
1708
1709 /*
1710         Con_DisplayList
1711
1712         New function for tab-completion system
1713         Added by EvilTypeGuy
1714         MEGA Thanks to Taniwha
1715
1716 */
1717 void Con_DisplayList(const char **list)
1718 {
1719         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1720         const char **walk = list;
1721
1722         while (*walk) {
1723                 len = (int)strlen(*walk);
1724                 if (len > maxlen)
1725                         maxlen = len;
1726                 walk++;
1727         }
1728         maxlen += 1;
1729
1730         while (*list) {
1731                 len = (int)strlen(*list);
1732                 if (pos + maxlen >= width) {
1733                         Con_Print("\n");
1734                         pos = 0;
1735                 }
1736
1737                 Con_Print(*list);
1738                 for (i = 0; i < (maxlen - len); i++)
1739                         Con_Print(" ");
1740
1741                 pos += maxlen;
1742                 list++;
1743         }
1744
1745         if (pos)
1746                 Con_Print("\n\n");
1747 }
1748
1749 /* Nicks_CompleteCountPossible
1750
1751    Count the number of possible nicks to complete
1752  */
1753 //qboolean COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets);
1754 void SanitizeString(char *in, char *out)
1755 {
1756         while(*in)
1757         {
1758                 if(*in == STRING_COLOR_TAG)
1759                 {
1760                         ++in;
1761                         if(!*in)
1762                         {
1763                                 out[0] = STRING_COLOR_TAG;
1764                                 out[1] = 0;
1765                                 return;
1766                         } else if(*in >= '0' && *in <= '9')
1767                         {
1768                                 ++in;
1769                                 if(!*in) // end
1770                                 {
1771                                         *out = 0;
1772                                         return;
1773                                 } else if (*in == STRING_COLOR_TAG)
1774                                         continue;
1775                         } else if (*in != STRING_COLOR_TAG) {
1776                                 --in;
1777                         }
1778                 }
1779                 *out = qfont_table[*(unsigned char*)in];
1780                 ++in;
1781                 ++out;
1782         }
1783         *out = 0;
1784 }
1785 int Sbar_GetPlayer (int index); // <- safety?
1786
1787 // Now it becomes TRICKY :D --blub
1788 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];     // contains the nicks with colors and all that
1789 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];  // sanitized list for completion when there are other possible matches.
1790 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
1791 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
1792 static int Nicks_matchpos;
1793
1794 // co against <<:BLASTER:>> is true!?
1795 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
1796 {
1797         while(a_len)
1798         {
1799                 if(tolower(*a) == tolower(*b))
1800                 {
1801                         if(*a == 0)
1802                                 return 0;
1803                         --a_len;
1804                         ++a;
1805                         ++b;
1806                         continue;
1807                 }
1808                 if(!*a)
1809                         return -1;
1810                 if(!*b)
1811                         return 1;
1812                 if(*a == ' ')
1813                         return (*a < *b) ? -1 : 1;
1814                 if(*b == ' ')
1815                         ++b;
1816                 else
1817                         return (*a < *b) ? -1 : 1;
1818         }
1819         return 0;
1820 }
1821 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
1822 {
1823         char space_char;
1824         if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
1825         {
1826                 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
1827                         return Nicks_strncasecmp_nospaces(a, b, a_len);
1828                 return strncasecmp(a, b, a_len);
1829         }
1830         
1831         space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
1832         
1833         // ignore non alphanumerics of B
1834         // if A contains a non-alphanumeric, B must contain it as well though!
1835         while(a_len)
1836         {
1837                 qboolean alnum_a, alnum_b;
1838                 
1839                 if(tolower(*a) == tolower(*b))
1840                 {
1841                         if(*a == 0) // end of both strings, they're equal
1842                                 return 0;
1843                         --a_len;
1844                         ++a;
1845                         ++b;
1846                         continue;
1847                 }
1848                 // not equal, end of one string?
1849                 if(!*a)
1850                         return -1;
1851                 if(!*b)
1852                         return 1;
1853                 // ignore non alphanumerics
1854                 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
1855                 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
1856                 if(!alnum_a) // b must contain this
1857                         return (*a < *b) ? -1 : 1;
1858                 if(!alnum_b)
1859                         ++b;
1860                 // otherwise, both are alnum, they're just not equal, return the appropriate number
1861                 else
1862                         return (*a < *b) ? -1 : 1;
1863         }
1864         return 0;
1865 }
1866
1867 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
1868 {
1869         char name[128];
1870         int i, p;
1871         int length;
1872         int match;
1873         int spos;
1874         int count = 0;
1875         
1876         if(!con_nickcompletion.integer)
1877                 return 0;
1878
1879         // changed that to 1
1880         if(!line[0])// || !line[1]) // we want at least... 2 written characters
1881                 return 0;
1882         
1883         for(i = 0; i < cl.maxclients; ++i)
1884         {
1885                 /*p = Sbar_GetPlayer(i);
1886                 if(p < 0)
1887                 break;*/
1888                 p = i;
1889                 if(!cl.scores[p].name[0])
1890                         continue;
1891
1892                 SanitizeString(cl.scores[p].name, name);
1893                 //Con_Printf("Sanitized: %s^7 -> %s", cl.scores[p].name, name);
1894                 
1895                 if(!name[0])
1896                         continue;
1897                 
1898                 length = strlen(name);
1899                 match = -1;
1900                 spos = pos - 1; // no need for a minimum of characters :)
1901                 
1902                 while(spos >= 0 && (spos - pos) < length) // search-string-length < name length
1903                 {
1904                         if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
1905                         {
1906                                 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
1907                                    !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
1908                                 {
1909                                         --spos;
1910                                         continue;
1911                                 }
1912                         }
1913                         if(isCon && spos == 0)
1914                                 break;
1915                         if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
1916                                 match = spos;
1917                         --spos;
1918                 }
1919                 if(match < 0)
1920                         continue;
1921                 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
1922                 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
1923
1924                 // the sanitized list
1925                 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
1926                 if(!count)
1927                 {
1928                         Nicks_matchpos = match;
1929                 }
1930                 
1931                 Nicks_offset[count] = s - (&line[match]);
1932                 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
1933
1934                 ++count;
1935         }
1936         return count;
1937 }
1938
1939 void Cmd_CompleteNicksPrint(int count)
1940 {
1941         int i;
1942         for(i = 0; i < count; ++i)
1943                 Con_Printf("%s\n", Nicks_list[i]);
1944 }
1945
1946 void Nicks_CutMatchesNormal(int count)
1947 {
1948         // cut match 0 down to the longest possible completion
1949         int i;
1950         unsigned int c, l;
1951         c = strlen(Nicks_sanlist[0]) - 1;
1952         for(i = 1; i < count; ++i)
1953         {
1954                 l = strlen(Nicks_sanlist[i]) - 1;
1955                 if(l < c)
1956                         c = l;
1957                 
1958                 for(l = 0; l <= c; ++l)
1959                         if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
1960                         {
1961                                 c = l-1;
1962                                 break;
1963                         }
1964         }
1965         Nicks_sanlist[0][c+1] = 0;
1966         //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
1967 }
1968
1969 unsigned int Nicks_strcleanlen(const char *s)
1970 {
1971         unsigned int l = 0;
1972         while(*s)
1973         {
1974                 if( (*s >= 'a' && *s <= 'z') ||
1975                     (*s >= 'A' && *s <= 'Z') ||
1976                     (*s >= '0' && *s <= '9') ||
1977                     *s == ' ')
1978                         ++l;
1979                 ++s;
1980         }
1981         return l;
1982 }
1983
1984 void Nicks_CutMatchesAlphaNumeric(int count)
1985 {
1986         // cut match 0 down to the longest possible completion
1987         int i;
1988         unsigned int c, l;
1989         char tempstr[sizeof(Nicks_sanlist[0])];
1990         char *a, *b;
1991         char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
1992         
1993         c = strlen(Nicks_sanlist[0]);
1994         for(i = 0, l = 0; i < (int)c; ++i)
1995         {
1996                 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
1997                     (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
1998                     (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
1999                 {
2000                         tempstr[l++] = Nicks_sanlist[0][i];
2001                 }
2002         }
2003         tempstr[l] = 0;
2004         
2005         for(i = 1; i < count; ++i)
2006         {
2007                 a = tempstr;
2008                 b = Nicks_sanlist[i];
2009                 while(1)
2010                 {
2011                         if(!*a)
2012                                 break;
2013                         if(!*b)
2014                         {
2015                                 *a = 0;
2016                                 break;
2017                         }
2018                         if(tolower(*a) == tolower(*b))
2019                         {
2020                                 ++a;
2021                                 ++b;
2022                                 continue;
2023                         }
2024                         if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2025                         {
2026                                 // b is alnum, so cut
2027                                 *a = 0;
2028                                 break;
2029                         }
2030                         ++b;
2031                 }
2032         }
2033         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2034         Nicks_CutMatchesNormal(count);
2035         //if(!Nicks_sanlist[0][0])
2036         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2037         {
2038                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2039                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2040         }
2041 }
2042
2043 void Nicks_CutMatchesNoSpaces(int count)
2044 {
2045         // cut match 0 down to the longest possible completion
2046         int i;
2047         unsigned int c, l;
2048         char tempstr[sizeof(Nicks_sanlist[0])];
2049         char *a, *b;
2050         
2051         c = strlen(Nicks_sanlist[0]);
2052         for(i = 0, l = 0; i < (int)c; ++i)
2053         {
2054                 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2055                 {
2056                         tempstr[l++] = Nicks_sanlist[0][i];
2057                 }
2058         }
2059         tempstr[l] = 0;
2060         
2061         for(i = 1; i < count; ++i)
2062         {
2063                 a = tempstr;
2064                 b = Nicks_sanlist[i];
2065                 while(1)
2066                 {
2067                         if(!*a)
2068                                 break;
2069                         if(!*b)
2070                         {
2071                                 *a = 0;
2072                                 break;
2073                         }
2074                         if(tolower(*a) == tolower(*b))
2075                         {
2076                                 ++a;
2077                                 ++b;
2078                                 continue;
2079                         }
2080                         if(*b != ' ')
2081                         {
2082                                 *a = 0;
2083                                 break;
2084                         }
2085                         ++b;
2086                 }
2087         }
2088         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2089         Nicks_CutMatchesNormal(count);
2090         //if(!Nicks_sanlist[0][0])
2091         //Con_Printf("TS: %s\n", tempstr);
2092         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2093         {
2094                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2095                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2096         }
2097 }
2098
2099 void Nicks_CutMatches(int count)
2100 {
2101         if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2102                 Nicks_CutMatchesAlphaNumeric(count);
2103         else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2104                 Nicks_CutMatchesNoSpaces(count);
2105         else
2106                 Nicks_CutMatchesNormal(count);
2107 }
2108
2109 const char **Nicks_CompleteBuildList(int count)
2110 {
2111         const char **buf;
2112         int bpos = 0;
2113         // the list is freed by Con_CompleteCommandLine, so create a char**
2114         buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2115
2116         for(; bpos < count; ++bpos)
2117                 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2118
2119         Nicks_CutMatches(count);
2120         
2121         buf[bpos] = NULL;
2122         return buf;
2123 }
2124
2125 int Nicks_AddLastColor(char *buffer, int pos)
2126 {
2127         qboolean quote_added = false;
2128         int match;
2129         char color = '7';
2130         
2131         if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2132         {
2133                 // we'll have to add a quote :)
2134                 buffer[pos++] = '\"';
2135                 quote_added = true;
2136         }
2137         
2138         if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2139         {
2140                 // add color when no quote was added, or when flags &4?
2141                 // find last color
2142                 for(match = Nicks_matchpos-1; match >= 0; --match)
2143                 {
2144                         if(buffer[match] == STRING_COLOR_TAG && buffer[match+1] >= '0' && buffer[match+1] <= '9')
2145                         {
2146                                 color = buffer[match+1];
2147                                 break;
2148                         }
2149                 }
2150                 if(!quote_added && buffer[pos-2] == STRING_COLOR_TAG && buffer[pos-1] >= '0' && buffer[pos-1] <= '9') // when thes use &4
2151                         pos -= 2;
2152                 buffer[pos++] = STRING_COLOR_TAG;
2153                 buffer[pos++] = color;
2154         }
2155         return pos;
2156 }
2157
2158 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2159 {
2160         int n;
2161         /*if(!con_nickcompletion.integer)
2162           return; is tested in Nicks_CompletionCountPossible */
2163         n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2164         if(n == 1)
2165         {
2166                 size_t len;
2167                 char *msg;
2168                 
2169                 msg = Nicks_list[0];
2170                 len = min(size - Nicks_matchpos - 3, strlen(msg));
2171                 memcpy(&buffer[Nicks_matchpos], msg, len);
2172                 if( len < (size - 4) ) // space for color and space and \0
2173                         len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2174                 buffer[len++] = ' ';
2175                 buffer[len] = 0;
2176                 return len;
2177         } else if(n > 1)
2178         {
2179                 int len;
2180                 char *msg;
2181                 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2182                 Cmd_CompleteNicksPrint(n);
2183
2184                 Nicks_CutMatches(n);
2185
2186                 msg = Nicks_sanlist[0];
2187                 len = min(size - Nicks_matchpos, strlen(msg));
2188                 memcpy(&buffer[Nicks_matchpos], msg, len);
2189                 buffer[Nicks_matchpos + len] = 0;
2190                 //pos += len;
2191                 return Nicks_matchpos + len;
2192         }
2193         return pos;
2194 }
2195
2196
2197 /*
2198         Con_CompleteCommandLine
2199
2200         New function for tab-completion system
2201         Added by EvilTypeGuy
2202         Thanks to Fett erich@heintz.com
2203         Thanks to taniwha
2204         Enhanced to tab-complete map names by [515]
2205
2206 */
2207 void Con_CompleteCommandLine (void)
2208 {
2209         const char *cmd = "";
2210         char *s;
2211         const char **list[4] = {0, 0, 0, 0};
2212         char s2[512];
2213         char command[512];
2214         int c, v, a, i, cmd_len, pos, k;
2215         int n; // nicks --blub
2216         const char *space;
2217         
2218         //find what we want to complete
2219         pos = key_linepos;
2220         while(--pos)
2221         {
2222                 k = key_lines[edit_line][pos];
2223                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2224                         break;
2225         }
2226         pos++;
2227
2228         s = key_lines[edit_line] + pos;
2229         strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2));    //save chars after cursor
2230         key_lines[edit_line][key_linepos] = 0;                                  //hide them
2231
2232         space = strchr(key_lines[edit_line] + 1, ' ');
2233         if(space && pos == (space - key_lines[edit_line]) + 1)
2234         {
2235                 strlcpy(command, key_lines[edit_line] + 1, min(sizeof(command), (unsigned int)(space - key_lines[edit_line])));
2236                 if(!strcmp(command, "map") || !strcmp(command, "changelevel"))
2237                 {
2238                         //maps search
2239                         char t[MAX_QPATH];
2240                         if (GetMapList(s, t, sizeof(t)))
2241                         {
2242                                 // first move the cursor
2243                                 key_linepos += (int)strlen(t) - (int)strlen(s);
2244
2245                                 // and now do the actual work
2246                                 *s = 0;
2247                                 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2248                                 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2249
2250                                 // and fix the cursor
2251                                 if(key_linepos > (int) strlen(key_lines[edit_line]))
2252                                         key_linepos = (int) strlen(key_lines[edit_line]);
2253                         }
2254                         return;
2255                 }
2256                 else
2257                 {
2258                         const char *patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2259                         char t[MAX_QPATH];
2260                         stringlist_t resultbuf;
2261
2262                         // Usage:
2263                         //   // store completion patterns (space separated) for command foo in con_completion_foo
2264                         //   set con_completion_foo "foodata/*.foodefault *.foo"
2265                         //   foo <TAB>
2266                         //
2267                         // Note: patterns with slash are always treated as absolute
2268                         // patterns; patterns without slash search in the innermost
2269                         // directory the user specified. There is no way to "complete into"
2270                         // a directory as of now, as directories seem to be unknown to the
2271                         // FS subsystem.
2272                         //
2273                         // Examples:
2274                         //   set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2275                         //   set con_completion_playdemo "*.dem"
2276                         //   set con_completion_play "*.wav *.ogg"
2277                         //
2278                         // TODO somehow add support for directories; these shall complete
2279                         // to their name + an appended slash.
2280
2281                         stringlistinit(&resultbuf);
2282                         while(COM_ParseToken_Simple(&patterns, false, false))
2283                         {
2284                                 fssearch_t *search;
2285                                 if(strchr(com_token, '/'))
2286                                 {
2287                                         search = FS_Search(com_token, true, true);
2288                                 }
2289                                 else
2290                                 {
2291                                         const char *slash = strrchr(s, '/');
2292                                         if(slash)
2293                                         {
2294                                                 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2295                                                 strlcat(t, com_token, sizeof(t));
2296                                                 search = FS_Search(t, true, true);
2297                                         }
2298                                         else
2299                                                 search = FS_Search(com_token, true, true);
2300                                 }
2301                                 if(search)
2302                                 {
2303                                         for(i = 0; i < search->numfilenames; ++i)
2304                                                 if(!strncmp(search->filenames[i], s, strlen(s)))
2305                                                         stringlistappend(&resultbuf, search->filenames[i]);
2306                                         FS_FreeSearch(search);
2307                                 }
2308                         }
2309                         
2310                         if(resultbuf.numstrings > 0)
2311                         {
2312                                 const char *p, *q;
2313                                 if(resultbuf.numstrings == 1)
2314                                 {
2315                                         dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2316                                 }
2317                                 else
2318                                 {
2319                                         stringlistsort(&resultbuf);
2320                                         Con_Printf("\n%i possible filenames\n", resultbuf.numstrings);
2321                                         for(i = 0; i < resultbuf.numstrings; ++i)
2322                                         {
2323                                                 Con_Printf("%s\n", resultbuf.strings[i]);
2324                                         }
2325                                         p = resultbuf.strings[0];
2326                                         q = resultbuf.strings[resultbuf.numstrings - 1];
2327                                         for(; *p && *p == *q; ++p, ++q);
2328                                         // now p points to the first non-equal character, or to the end
2329                                         // of resultbuf.strings[0]. We want to append the characters
2330                                         // from resultbuf.strings[0] to (not including) p as these are
2331                                         // the unique prefix
2332                                         strlcpy(t, resultbuf.strings[0], min((unsigned int)(p - resultbuf.strings[0] + 1), sizeof(t)));
2333                                 }
2334
2335                                 // first move the cursor
2336                                 key_linepos += (int)strlen(t) - (int)strlen(s);
2337
2338                                 // and now do the actual work
2339                                 *s = 0;
2340                                 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2341                                 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2342
2343                                 // and fix the cursor
2344                                 if(key_linepos > (int) strlen(key_lines[edit_line]))
2345                                         key_linepos = (int) strlen(key_lines[edit_line]);
2346                         }
2347                         stringlistfreecontents(&resultbuf);
2348                 }
2349         }
2350
2351         // Count number of possible matches and print them
2352         c = Cmd_CompleteCountPossible(s);
2353         if (c)
2354         {
2355                 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2356                 Cmd_CompleteCommandPrint(s);
2357         }
2358         v = Cvar_CompleteCountPossible(s);
2359         if (v)
2360         {
2361                 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2362                 Cvar_CompleteCvarPrint(s);
2363         }
2364         a = Cmd_CompleteAliasCountPossible(s);
2365         if (a)
2366         {
2367                 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2368                 Cmd_CompleteAliasPrint(s);
2369         }
2370         n = Nicks_CompleteCountPossible(key_lines[edit_line], key_linepos, s, true);
2371         if (n)
2372         {
2373                 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2374                 Cmd_CompleteNicksPrint(n);
2375         }
2376
2377         if (!(c + v + a + n))   // No possible matches
2378         {               
2379                 if(s2[0])
2380                         strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
2381                 return;
2382         }
2383
2384         if (c)
2385                 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2386         if (v)
2387                 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2388         if (a)
2389                 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2390         if (n)
2391                 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2392         
2393         for (cmd_len = (int)strlen(s);;cmd_len++)
2394         {
2395                 const char **l;
2396                 for (i = 0; i < 3; i++)
2397                         if (list[i])
2398                                 for (l = list[i];*l;l++)
2399                                         if ((*l)[cmd_len] != cmd[cmd_len])
2400                                                 goto done;
2401                 // all possible matches share this character, so we continue...
2402                 if (!cmd[cmd_len])
2403                 {
2404                         // if all matches ended at the same position, stop
2405                         // (this means there is only one match)
2406                         break;
2407                 }
2408         }
2409 done:
2410
2411         // prevent a buffer overrun by limiting cmd_len according to remaining space
2412         cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
2413         if (cmd)
2414         {
2415                 key_linepos = pos;
2416                 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
2417                 key_linepos += cmd_len;
2418                 // if there is only one match, add a space after it
2419                 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
2420                 {
2421                         if(n)
2422                         { // was a nick, might have an offset, and needs colors ;) --blub
2423                                 key_linepos = pos - Nicks_offset[0];
2424                                 cmd_len = strlen(Nicks_list[0]);
2425                                 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 3 - pos);
2426
2427                                 memcpy(&key_lines[edit_line][key_linepos] , Nicks_list[0], cmd_len);
2428                                 key_linepos += cmd_len;
2429                                 if(key_linepos < (int)(sizeof(key_lines[edit_line])-4)) // space for ^, X and space and \0
2430                                         key_linepos = Nicks_AddLastColor(key_lines[edit_line], key_linepos);
2431                         }
2432                         key_lines[edit_line][key_linepos++] = ' ';
2433                 }
2434         }
2435
2436         // use strlcat to avoid a buffer overrun
2437         key_lines[edit_line][key_linepos] = 0;
2438         strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
2439
2440         // free the command, cvar, and alias lists
2441         for (i = 0; i < 4; i++)
2442                 if (list[i])
2443                         Mem_Free((void *)list[i]);
2444 }
2445