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