2 Copyright (C) 1996-1997 Id Software, Inc.
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.
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.
13 See the GNU General Public License for more details.
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.
24 #if !defined(WIN32) || defined(__MINGW32__)
29 float con_cursorspeed = 4;
31 #define CON_TEXTSIZE 131072
32 #define CON_MAXLINES 4096
34 // lines up from bottom to display
38 char con_text[CON_TEXTSIZE];
40 #define CON_MASK_HIDENOTIFY 128
41 #define CON_MASK_CHAT 1
51 int height; // recalculated line height when needed (-1 to unset)
54 con_lineinfo con_lines[CON_MAXLINES];
56 int con_lines_first; // cyclic buffer
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)
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)"};
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)"};
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)"};
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)"};
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)"};
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
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"};
106 qboolean con_initialized;
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];
116 ==============================================================================
120 ==============================================================================
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;
131 unsigned char* logqueue = NULL;
133 size_t logq_size = 0;
135 void Log_ConPrint (const char *msg);
142 static void Log_DestBuffer_Init()
144 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
145 log_dest_buffer_pos = 5;
153 void Log_DestBuffer_Flush()
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)
161 ++log_dest_buffer_appending;
162 log_dest_buffer[log_dest_buffer_pos++] = 0;
164 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
166 have_opened_temp_sockets = true;
167 NetConn_OpenServerPorts(true);
170 while(COM_ParseToken_Console(&s))
171 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
173 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
175 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
177 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
180 if(have_opened_temp_sockets)
181 NetConn_CloseServerPorts();
182 --log_dest_buffer_appending;
184 log_dest_buffer_pos = 0;
192 const char* Log_Timestamp (const char *desc)
194 static char timestamp [128];
201 char timestring [64];
203 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
206 localtime_s (&crt_tm, &crt_time);
207 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
209 crt_tm = localtime (&crt_time);
210 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
214 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
216 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
229 if (logfile != NULL || log_file.string[0] == '\0')
232 logfile = FS_Open (log_file.string, "ab", false, false);
235 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
236 FS_Print (logfile, Log_Timestamp ("Log started"));
246 void Log_Close (void)
251 FS_Print (logfile, Log_Timestamp ("Log stopped"));
252 FS_Print (logfile, "\n");
256 crt_log_file[0] = '\0';
265 void Log_Start (void)
271 // Dump the contents of the log queue into the log file and free it
272 if (logqueue != NULL)
274 unsigned char *temp = logqueue;
279 FS_Write (logfile, temp, logq_ind);
280 if(*log_dest_udp.string)
282 for(pos = 0; pos < logq_ind; )
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();
306 void Log_ConPrint (const char *msg)
308 static qboolean inprogress = false;
310 // don't allow feedback loops with memory error reports
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)
319 size_t remain = logq_size - logq_ind;
320 size_t len = strlen (msg);
322 // If we need to enlarge the log queue
325 size_t factor = ((logq_ind + len) / logq_size) + 1;
326 unsigned char* newqueue;
329 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
330 memcpy (newqueue, logqueue, logq_ind);
333 remain = logq_size - logq_ind;
335 memcpy (&logqueue[logq_ind], msg, len);
342 // Check if log_file has changed
343 if (strcmp (crt_log_file, log_file.string) != 0)
349 // If a log file is available
351 FS_Print (logfile, msg);
362 void Log_Printf (const char *logfilename, const char *fmt, ...)
366 file = FS_Open (logfilename, "ab", true, false);
371 va_start (argptr, fmt);
372 FS_VPrintf (file, fmt, argptr);
381 ==============================================================================
385 ==============================================================================
393 void Con_ToggleConsole_f (void)
395 // toggle the 'user wants console' bit
396 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
405 void Con_Clear_f (void)
415 Clear all notify lines.
418 void Con_ClearNotify (void)
421 for(i = 0; i < con_lines_count; ++i)
422 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
431 void Con_MessageMode_f (void)
433 key_dest = key_message;
443 void Con_MessageMode2_f (void)
445 key_dest = key_message;
454 If the line width has changed, reformat the buffer.
457 void Con_CheckResize (void)
462 f = bound(1, con_textsize.value, 128);
463 if(f != con_textsize.value)
464 Cvar_SetValueQuick(&con_textsize, f);
465 width = (int)floor(vid_conwidth.value / con_textsize.value);
466 width = bound(1, width, CON_TEXTSIZE/4);
468 if (width == con_linewidth)
471 con_linewidth = width;
473 for(i = 0; i < con_lines_count; ++i)
474 CON_LINES(i).height = -1; // recalculate when next needed
480 //[515]: the simplest command ever
481 //LordHavoc: not so simple after I made it print usage...
482 static void Con_Maps_f (void)
486 Con_Printf("usage: maps [mapnameprefix]\n");
489 else if (Cmd_Argc() == 2)
490 GetMapList(Cmd_Argv(1), NULL, 0);
492 GetMapList("", NULL, 0);
495 void Con_ConDump_f (void)
501 Con_Printf("usage: condump <filename>\n");
504 file = FS_Open(Cmd_Argv(1), "wb", false, false);
507 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
510 for(i = 0; i < con_lines_count; ++i)
512 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
513 FS_Write(file, "\n", 1);
529 // Allocate a log queue, this will be freed after configs are parsed
530 logq_size = MAX_INPUTLINE;
531 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
534 Cvar_RegisterVariable (&sys_colortranslation);
535 Cvar_RegisterVariable (&sys_specialcharactertranslation);
537 Cvar_RegisterVariable (&log_file);
538 Cvar_RegisterVariable (&log_dest_udp);
540 // support for the classic Quake option
541 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
542 if (COM_CheckParm ("-condebug") != 0)
543 Cvar_SetQuick (&log_file, "qconsole.log");
545 // register our cvars
546 Cvar_RegisterVariable (&con_chat);
547 Cvar_RegisterVariable (&con_chatpos);
548 Cvar_RegisterVariable (&con_chatsize);
549 Cvar_RegisterVariable (&con_chattime);
550 Cvar_RegisterVariable (&con_chatwidth);
551 Cvar_RegisterVariable (&con_notify);
552 Cvar_RegisterVariable (&con_notifyalign);
553 Cvar_RegisterVariable (&con_notifysize);
554 Cvar_RegisterVariable (&con_notifytime);
555 Cvar_RegisterVariable (&con_textsize);
558 Cvar_RegisterVariable (&con_nickcompletion);
559 Cvar_RegisterVariable (&con_nickcompletion_flags);
561 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
562 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
563 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
565 // register our commands
566 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
567 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
568 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
569 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
570 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
571 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
573 con_initialized = true;
574 Con_DPrint("Console initialized.\n");
582 Deletes the first line from the console history.
585 void Con_DeleteLine()
587 if(con_lines_count == 0)
590 con_lines_first = CON_LINES_IDX(1);
597 Deletes the last line from the console history.
600 void Con_DeleteLastLine()
602 if(con_lines_count == 0)
611 Checks if there is space for a line of the given length, and if yes, returns a
612 pointer to the start of such a space, and NULL otherwise.
615 char *Con_BytesLeft(int len)
617 if(len > CON_TEXTSIZE)
619 if(con_lines_count == 0)
623 char *firstline_start = con_lines[con_lines_first].start;
624 char *lastline_onepastend = con_lines[CON_LINES_LAST].start + con_lines[CON_LINES_LAST].len;
625 // the buffer is cyclic, so we first have two cases...
626 if(firstline_start < lastline_onepastend) // buffer is contiguous
629 if(len <= con_text + CON_TEXTSIZE - lastline_onepastend)
630 return lastline_onepastend;
632 else if(len <= firstline_start - con_text)
637 else // buffer has a contiguous hole
639 if(len <= firstline_start - lastline_onepastend)
640 return lastline_onepastend;
651 Notifies the console code about the current time
652 (and shifts back times of other entries when the time
659 if(con_lines_count >= 1)
661 double diff = cl.time - (con_lines + CON_LINES_LAST)->addtime;
664 for(i = 0; i < con_lines_count; ++i)
665 CON_LINES(i).addtime += diff;
674 Appends a given string as a new line to the console.
677 void Con_AddLine(const char *line, int len, int mask)
684 if(len >= CON_TEXTSIZE)
687 // only display end of line.
688 line += len - CON_TEXTSIZE + 1;
689 len = CON_TEXTSIZE - 1;
691 while(!(putpos = Con_BytesLeft(len + 1)) || con_lines_count >= CON_MAXLINES)
693 memcpy(putpos, line, len);
697 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", con_lines_count, con_lines_first, CON_LINES_LAST);
699 p = con_lines + CON_LINES_LAST;
702 p->addtime = cl.time;
704 p->height = -1; // calculate when needed
711 Handles cursor positioning, line wrapping, etc
712 All console printing must go through this in order to be displayed
713 If no console is visible, the notify window will pop up.
716 void Con_PrintToHistory(const char *txt, int mask)
719 // \n goes to next line
720 // \r deletes current line and makes a new one
722 static int cr_pending = 0;
723 static char buf[CON_TEXTSIZE];
724 static int bufpos = 0;
730 Con_DeleteLastLine();
738 Con_AddLine(buf, bufpos, mask);
743 Con_AddLine(buf, bufpos, mask);
747 buf[bufpos++] = *txt;
748 if(bufpos >= CON_TEXTSIZE - 1)
750 Con_AddLine(buf, bufpos, mask);
758 /* The translation table between the graphical font and plain ASCII --KB */
759 static char qfont_table[256] = {
760 '\0', '#', '#', '#', '#', '.', '#', '#',
761 '#', 9, 10, '#', ' ', 13, '.', '.',
762 '[', ']', '0', '1', '2', '3', '4', '5',
763 '6', '7', '8', '9', '.', '<', '=', '>',
764 ' ', '!', '"', '#', '$', '%', '&', '\'',
765 '(', ')', '*', '+', ',', '-', '.', '/',
766 '0', '1', '2', '3', '4', '5', '6', '7',
767 '8', '9', ':', ';', '<', '=', '>', '?',
768 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
769 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
770 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
771 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
772 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
773 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
774 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
775 'x', 'y', 'z', '{', '|', '}', '~', '<',
777 '<', '=', '>', '#', '#', '.', '#', '#',
778 '#', '#', ' ', '#', ' ', '>', '.', '.',
779 '[', ']', '0', '1', '2', '3', '4', '5',
780 '6', '7', '8', '9', '.', '<', '=', '>',
781 ' ', '!', '"', '#', '$', '%', '&', '\'',
782 '(', ')', '*', '+', ',', '-', '.', '/',
783 '0', '1', '2', '3', '4', '5', '6', '7',
784 '8', '9', ':', ';', '<', '=', '>', '?',
785 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
786 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
787 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
788 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
789 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
790 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
791 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
792 'x', 'y', 'z', '{', '|', '}', '~', '<'
795 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest)
797 rcon_redirect_sock = sock;
798 rcon_redirect_dest = dest;
799 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
800 rcon_redirect_bufferpos = 5;
803 void Con_Rcon_Redirect_Flush()
805 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
806 NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
807 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
808 rcon_redirect_bufferpos = 5;
811 void Con_Rcon_Redirect_End()
813 Con_Rcon_Redirect_Flush();
814 rcon_redirect_dest = NULL;
815 rcon_redirect_sock = NULL;
818 void Con_Rcon_Redirect_Abort()
820 rcon_redirect_dest = NULL;
821 rcon_redirect_sock = NULL;
828 Adds a character to the rcon buffer
831 void Con_Rcon_AddChar(char c)
833 if(log_dest_buffer_appending)
835 ++log_dest_buffer_appending;
837 // if this print is in response to an rcon command, add the character
838 // to the rcon redirect buffer
840 if (rcon_redirect_dest)
842 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
843 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
844 Con_Rcon_Redirect_Flush();
846 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
848 if(log_dest_buffer_pos == 0)
849 Log_DestBuffer_Init();
850 log_dest_buffer[log_dest_buffer_pos++] = c;
851 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
852 Log_DestBuffer_Flush();
855 log_dest_buffer_pos = 0;
857 --log_dest_buffer_appending;
864 Prints to all appropriate console targets, and adds timestamps
867 extern cvar_t timestamps;
868 extern cvar_t timeformat;
869 extern qboolean sys_nostdout;
870 void Con_Print(const char *msg)
873 static int index = 0;
874 static char line[MAX_INPUTLINE];
878 Con_Rcon_AddChar(*msg);
879 // if this is the beginning of a new line, print timestamp
882 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
884 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
885 line[index++] = STRING_COLOR_TAG;
886 // assert( STRING_COLOR_DEFAULT < 10 )
887 line[index++] = STRING_COLOR_DEFAULT + '0';
888 // special color codes for chat messages must always come first
889 // for Con_PrintToHistory to work properly
890 if (*msg == 1 || *msg == 2)
895 if(gamemode == GAME_NEXUIZ)
897 if(msg[1] == '\r' && cl.foundtalk2wav)
898 S_LocalSound ("sound/misc/talk2.wav");
900 S_LocalSound ("sound/misc/talk.wav");
904 if (msg[1] == '(' && cl.foundtalk2wav)
905 S_LocalSound ("sound/misc/talk2.wav");
907 S_LocalSound ("sound/misc/talk.wav");
909 mask = CON_MASK_CHAT;
911 line[index++] = STRING_COLOR_TAG;
914 Con_Rcon_AddChar(*msg);
917 for (;*timestamp;index++, timestamp++)
918 if (index < (int)sizeof(line) - 2)
919 line[index] = *timestamp;
921 // append the character
922 line[index++] = *msg;
923 // if this is a newline character, we have a complete line to print
924 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
926 // terminate the line
930 // send to scrollable buffer
931 if (con_initialized && cls.state != ca_dedicated)
933 Con_PrintToHistory(line, mask);
936 // send to terminal or dedicated server window
940 if(sys_specialcharactertranslation.integer)
942 for (p = (unsigned char *) line;*p; p++)
943 *p = qfont_table[*p];
946 if(sys_colortranslation.integer == 1) // ANSI
948 static char printline[MAX_INPUTLINE * 4 + 3];
949 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
950 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
954 for(in = line, out = printline; *in; ++in)
958 case STRING_COLOR_TAG:
961 case STRING_COLOR_TAG:
963 *out++ = STRING_COLOR_TAG;
969 if(lastcolor == 0) break; else lastcolor = 0;
970 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
975 if(lastcolor == 1) break; else lastcolor = 1;
976 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
981 if(lastcolor == 2) break; else lastcolor = 2;
982 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
987 if(lastcolor == 3) break; else lastcolor = 3;
988 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
993 if(lastcolor == 4) break; else lastcolor = 4;
994 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
999 if(lastcolor == 5) break; else lastcolor = 5;
1000 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1005 if(lastcolor == 6) break; else lastcolor = 6;
1006 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1011 // bold normal color
1013 if(lastcolor == 8) break; else lastcolor = 8;
1014 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1017 *out++ = STRING_COLOR_TAG;
1024 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1041 Sys_PrintToTerminal(printline);
1043 else if(sys_colortranslation.integer == 2) // Quake
1045 Sys_PrintToTerminal(line);
1049 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1052 for(in = line, out = printline; *in; ++in)
1056 case STRING_COLOR_TAG:
1059 case STRING_COLOR_TAG:
1061 *out++ = STRING_COLOR_TAG;
1076 *out++ = STRING_COLOR_TAG;
1086 Sys_PrintToTerminal(printline);
1089 // empty the line buffer
1100 Prints to all appropriate console targets
1103 void Con_Printf(const char *fmt, ...)
1106 char msg[MAX_INPUTLINE];
1108 va_start(argptr,fmt);
1109 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1119 A Con_Print that only shows up if the "developer" cvar is set
1122 void Con_DPrint(const char *msg)
1124 if (!developer.integer)
1125 return; // don't confuse non-developers with techie stuff...
1133 A Con_Printf that only shows up if the "developer" cvar is set
1136 void Con_DPrintf(const char *fmt, ...)
1139 char msg[MAX_INPUTLINE];
1141 if (!developer.integer)
1142 return; // don't confuse non-developers with techie stuff...
1144 va_start(argptr,fmt);
1145 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1153 ==============================================================================
1157 ==============================================================================
1164 The input line scrolls horizontally if typing goes beyond the right edge
1166 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1169 void Con_DrawInput (void)
1173 char editlinecopy[MAX_INPUTLINE+1], *text;
1176 if (!key_consoleactive)
1177 return; // don't draw anything
1179 strlcpy(editlinecopy, key_lines[edit_line], sizeof(editlinecopy));
1180 text = editlinecopy;
1182 // Advanced Console Editing by Radix radix@planetquake.com
1183 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1184 // use strlen of edit_line instead of key_linepos to allow editing
1185 // of early characters w/o erasing
1187 y = (int)strlen(text);
1189 // fill out remainder with spaces
1190 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1193 // add the cursor frame
1194 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1195 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1197 // text[key_linepos + 1] = 0;
1199 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1204 DrawQ_String_Font(x, con_vislines - con_textsize.value*2, text, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, FONT_CONSOLE );
1207 // key_lines[edit_line][key_linepos] = 0;
1213 float alignment; // 0 = left, 0.5 = center, 1 = right
1219 const char *continuationString;
1222 int colorindex; // init to -1
1226 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1228 con_text_info_t *ti = (con_text_info_t *) passthrough;
1231 ti->colorindex = -1;
1232 return ti->fontsize * ti->font->maxwidth;
1235 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1236 else if(maxWidth == -1)
1237 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1240 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1241 // Note: this is NOT a Con_Printf, as it could print recursively
1246 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1252 (void) isContinuation;
1256 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1258 con_text_info_t *ti = (con_text_info_t *) passthrough;
1260 if(ti->y < ti->ymin - 0.001)
1262 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1266 int x = ti->x + (ti->width - width) * ti->alignment;
1267 if(isContinuation && *ti->continuationString)
1268 x += DrawQ_String_Font(x, ti->y, ti->continuationString, strlen(ti->continuationString), ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, ti->font);
1270 DrawQ_String_Font(x, ti->y, line, length, ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, &(ti->colorindex), false, ti->font);
1273 ti->y += ti->fontsize;
1278 int Con_DrawNotifyRect(int mask_must, int mask_mustnot, float maxage, float x, float y, float width, float height, float fontsize, float alignment_x, float alignment_y, const char *continuationString)
1282 int maxlines = (int) floor(height / fontsize + 0.01f);
1285 int continuationWidth = 0;
1287 double t = cl.time; // saved so it won't change
1290 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1291 ti.fontsize = fontsize;
1292 ti.alignment = alignment_x;
1295 ti.ymax = y + height;
1296 ti.continuationString = continuationString;
1299 Con_WordWidthFunc(&ti, NULL, &l, -1);
1300 l = strlen(continuationString);
1301 continuationWidth = Con_WordWidthFunc(&ti, continuationString, &l, -1);
1303 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1304 startidx = con_lines_count;
1305 for(i = con_lines_count - 1; i >= 0; --i)
1307 con_lineinfo *l = &CON_LINES(i);
1310 if((l->mask & mask_must) != mask_must)
1312 if(l->mask & mask_mustnot)
1314 if(maxage && (l->addtime < t - maxage))
1318 // Calculate its actual height...
1319 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1320 if(lines + mylines >= maxlines)
1322 nskip = lines + mylines - maxlines;
1331 // then center according to the calculated amount of lines...
1333 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1335 // then actually draw
1336 for(i = startidx; i < con_lines_count; ++i)
1338 con_lineinfo *l = &CON_LINES(i);
1340 if((l->mask & mask_must) != mask_must)
1342 if(l->mask & mask_mustnot)
1344 if(maxage && (l->addtime < t - maxage))
1347 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1357 Draws the last few lines of output transparently over the game top
1360 void Con_DrawNotify (void)
1363 float chatstart, notifystart, inputsize;
1365 char temptext[MAX_INPUTLINE];
1371 numChatlines = con_chat.integer;
1372 chatpos = con_chatpos.integer;
1374 if (con_notify.integer < 0)
1375 Cvar_SetValueQuick(&con_notify, 0);
1376 if (gamemode == GAME_TRANSFUSION)
1377 v = 8; // vertical offset
1381 // GAME_NEXUIZ: center, otherwise left justify
1382 align = con_notifyalign.value;
1383 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1385 if(gamemode == GAME_NEXUIZ)
1393 // first chat, input line, then notify
1395 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1397 else if(chatpos > 0)
1399 // first notify, then (chatpos-1) empty lines, then chat, then input
1401 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1403 else // if(chatpos < 0)
1405 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1407 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1412 // just notify and input
1414 chatstart = 0; // shut off gcc warning
1417 v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_HIDENOTIFY | (numChatlines ? CON_MASK_CHAT : 0), con_notifytime.value, 0, notifystart, vid_conwidth.value, con_notify.value * con_notifysize.value, con_notifysize.value, align, 0.0, "");
1422 v = chatstart + numChatlines * con_chatsize.value;
1423 Con_DrawNotifyRect(CON_MASK_CHAT, 0, con_chattime.value, 0, chatstart, vid_conwidth.value * con_chatwidth.value, v - chatstart, con_chatsize.value, 0.0, 1.0, "^3\014\014\014 "); // 015 is ·> character in conchars.tga
1426 if (key_dest == key_message)
1428 int colorindex = -1;
1430 // LordHavoc: speedup, and other improvements
1432 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1434 dpsnprintf(temptext, sizeof(temptext), "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1437 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1438 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1441 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1447 Con_MeasureConsoleLine
1449 Counts the number of lines for a line on the console.
1452 int Con_MeasureConsoleLine(int lineno)
1454 float width = vid_conwidth.value;
1456 ti.fontsize = con_textsize.value;
1457 ti.font = FONT_CONSOLE;
1459 return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1466 Returns the height of a given console line; calculates it if necessary.
1469 int Con_LineHeight(int i)
1471 int h = con_lines[i].height;
1474 return con_lines[i].height = Con_MeasureConsoleLine(i);
1481 Draws a line of the console; returns its height in lines.
1482 If alpha is 0, the line is not drawn, but still wrapped and its height
1486 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1488 float width = vid_conwidth.value;
1491 ti.continuationString = "";
1493 ti.fontsize = con_textsize.value;
1494 ti.font = FONT_CONSOLE;
1496 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1501 return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1508 Calculates the last visible line index and how much to show of it based on
1512 void Con_LastVisibleLine(int *last, int *limitlast)
1517 if(con_backscroll < 0)
1520 // now count until we saw con_backscroll actual lines
1521 for(ic = 0; ic < con_lines_count; ++ic)
1523 int i = CON_LINES_IDX(con_lines_count - 1 - ic);
1524 int h = Con_LineHeight(i);
1526 // line is the last visible line?
1527 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1530 *limitlast = lines_seen + h - con_backscroll;
1537 // if we get here, no line was on screen - scroll so that one line is
1539 con_backscroll = lines_seen - 1;
1540 *last = con_lines_first;
1548 Draws the console with the solid background
1549 The typing input line at the bottom should only be drawn if typing is allowed
1552 void Con_DrawConsole (int lines)
1554 int i, last, limitlast;
1560 con_vislines = lines;
1562 // draw the background
1563 DrawQ_Pic(0, lines - vid_conheight.integer, scr_conbrightness.value >= 0.01f ? Draw_CachePic ("gfx/conback") : NULL, vid_conwidth.integer, vid_conheight.integer, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, cls.signon == SIGNONS ? scr_conalpha.value : 1.0, 0); // always full alpha when not in game
1564 DrawQ_String_Font(vid_conwidth.integer - DrawQ_TextWidth_Font(engineversion, 0, false, FONT_CONSOLE) * con_textsize.value, lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0, NULL, true, FONT_CONSOLE);
1567 if(con_lines_count > 0)
1569 float ymax = con_vislines - 2 * con_textsize.value;
1570 Con_LastVisibleLine(&last, &limitlast);
1571 y = ymax - con_textsize.value;
1574 y += (con_lines[last].height - limitlast) * con_textsize.value;
1579 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1580 if(i == con_lines_first)
1581 break; // top of console buffer
1583 break; // top of console window
1585 i = CON_LINES_PRED(i);
1589 // draw the input prompt, user text, and cursor if desired
1597 Prints not only map filename, but also
1598 its format (q1/q2/q3/hl) and even its message
1600 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1601 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1602 //LordHavoc: added .ent file loading, and redesigned error handling to still try the .ent file even if the map format is not recognized, this also eliminated one goto
1603 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1604 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1608 int i, k, max, p, o, min;
1611 unsigned char buf[1024];
1613 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1614 t = FS_Search(message, 1, true);
1617 if (t->numfilenames > 1)
1618 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1619 len = (unsigned char *)Z_Malloc(t->numfilenames);
1621 for(max=i=0;i<t->numfilenames;i++)
1623 k = (int)strlen(t->filenames[i]);
1633 for(i=0;i<t->numfilenames;i++)
1635 int lumpofs = 0, lumplen = 0;
1636 char *entities = NULL;
1637 const char *data = NULL;
1639 char entfilename[MAX_QPATH];
1640 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1642 f = FS_Open(t->filenames[i], "rb", true, false);
1645 memset(buf, 0, 1024);
1646 FS_Read(f, buf, 1024);
1647 if (!memcmp(buf, "IBSP", 4))
1649 p = LittleLong(((int *)buf)[1]);
1650 if (p == Q3BSPVERSION)
1652 q3dheader_t *header = (q3dheader_t *)buf;
1653 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1654 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1656 else if (p == Q2BSPVERSION)
1658 q2dheader_t *header = (q2dheader_t *)buf;
1659 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1660 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1663 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1665 dheader_t *header = (dheader_t *)buf;
1666 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1667 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1671 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1672 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1673 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1674 if (!entities && lumplen >= 10)
1676 FS_Seek(f, lumpofs, SEEK_SET);
1677 entities = (char *)Z_Malloc(lumplen + 1);
1678 FS_Read(f, entities, lumplen);
1682 // if there are entities to parse, a missing message key just
1683 // means there is no title, so clear the message string now
1689 if (!COM_ParseToken_Simple(&data, false, false))
1691 if (com_token[0] == '{')
1693 if (com_token[0] == '}')
1695 // skip leading whitespace
1696 for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
1697 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
1698 keyname[l] = com_token[k+l];
1700 if (!COM_ParseToken_Simple(&data, false, false))
1702 if (developer.integer >= 100)
1703 Con_Printf("key: %s %s\n", keyname, com_token);
1704 if (!strcmp(keyname, "message"))
1706 // get the message contents
1707 strlcpy(message, com_token, sizeof(message));
1717 *(t->filenames[i]+len[i]+5) = 0;
1720 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1721 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1722 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1723 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1724 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1726 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1731 k = *(t->filenames[0]+5+p);
1734 for(i=1;i<t->numfilenames;i++)
1735 if(*(t->filenames[i]+5+p) != k)
1739 if(p > o && completedname && completednamebufferlength > 0)
1741 memset(completedname, 0, completednamebufferlength);
1742 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1752 New function for tab-completion system
1753 Added by EvilTypeGuy
1754 MEGA Thanks to Taniwha
1757 void Con_DisplayList(const char **list)
1759 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1760 const char **walk = list;
1763 len = (int)strlen(*walk);
1771 len = (int)strlen(*list);
1772 if (pos + maxlen >= width) {
1778 for (i = 0; i < (maxlen - len); i++)
1789 /* Nicks_CompleteCountPossible
1791 Count the number of possible nicks to complete
1793 //qboolean COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets);
1794 void SanitizeString(char *in, char *out)
1798 if(*in == STRING_COLOR_TAG)
1803 out[0] = STRING_COLOR_TAG;
1806 } else if(*in >= '0' && *in <= '9')
1813 } else if (*in == STRING_COLOR_TAG)
1815 } else if (*in != STRING_COLOR_TAG) {
1819 *out = qfont_table[*(unsigned char*)in];
1826 // Now it becomes TRICKY :D --blub
1827 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
1828 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
1829 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
1830 static int Nicks_offset[MAX_SCOREBOARD]; // when nicks use a space, we need this to move the completion list string starts to avoid invalid memcpys
1831 static int Nicks_matchpos;
1833 // co against <<:BLASTER:>> is true!?
1834 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
1838 if(tolower(*a) == tolower(*b))
1852 return (*a < *b) ? -1 : 1;
1856 return (*a < *b) ? -1 : 1;
1860 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
1863 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
1865 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
1866 return Nicks_strncasecmp_nospaces(a, b, a_len);
1867 return strncasecmp(a, b, a_len);
1870 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
1872 // ignore non alphanumerics of B
1873 // if A contains a non-alphanumeric, B must contain it as well though!
1876 qboolean alnum_a, alnum_b;
1878 if(tolower(*a) == tolower(*b))
1880 if(*a == 0) // end of both strings, they're equal
1887 // not equal, end of one string?
1892 // ignore non alphanumerics
1893 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
1894 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
1895 if(!alnum_a) // b must contain this
1896 return (*a < *b) ? -1 : 1;
1899 // otherwise, both are alnum, they're just not equal, return the appropriate number
1901 return (*a < *b) ? -1 : 1;
1906 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
1915 if(!con_nickcompletion.integer)
1918 // changed that to 1
1919 if(!line[0])// || !line[1]) // we want at least... 2 written characters
1922 for(i = 0; i < cl.maxclients; ++i)
1925 if(!cl.scores[p].name[0])
1928 SanitizeString(cl.scores[p].name, name);
1929 //Con_Printf("Sanitized: %s^7 -> %s", cl.scores[p].name, name);
1934 length = strlen(name);
1936 spos = pos - 1; // no need for a minimum of characters :)
1938 while(spos >= 0 && (spos - pos) < length) // search-string-length < name length
1940 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
1942 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
1943 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
1949 if(isCon && spos == 0)
1951 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
1957 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
1958 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
1960 // the sanitized list
1961 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
1964 Nicks_matchpos = match;
1967 Nicks_offset[count] = s - (&line[match]);
1968 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
1975 void Cmd_CompleteNicksPrint(int count)
1978 for(i = 0; i < count; ++i)
1979 Con_Printf("%s\n", Nicks_list[i]);
1982 void Nicks_CutMatchesNormal(int count)
1984 // cut match 0 down to the longest possible completion
1987 c = strlen(Nicks_sanlist[0]) - 1;
1988 for(i = 1; i < count; ++i)
1990 l = strlen(Nicks_sanlist[i]) - 1;
1994 for(l = 0; l <= c; ++l)
1995 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2001 Nicks_sanlist[0][c+1] = 0;
2002 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2005 unsigned int Nicks_strcleanlen(const char *s)
2010 if( (*s >= 'a' && *s <= 'z') ||
2011 (*s >= 'A' && *s <= 'Z') ||
2012 (*s >= '0' && *s <= '9') ||
2020 void Nicks_CutMatchesAlphaNumeric(int count)
2022 // cut match 0 down to the longest possible completion
2025 char tempstr[sizeof(Nicks_sanlist[0])];
2027 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2029 c = strlen(Nicks_sanlist[0]);
2030 for(i = 0, l = 0; i < (int)c; ++i)
2032 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2033 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2034 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2036 tempstr[l++] = Nicks_sanlist[0][i];
2041 for(i = 1; i < count; ++i)
2044 b = Nicks_sanlist[i];
2054 if(tolower(*a) == tolower(*b))
2060 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2062 // b is alnum, so cut
2069 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2070 Nicks_CutMatchesNormal(count);
2071 //if(!Nicks_sanlist[0][0])
2072 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2074 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2075 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2079 void Nicks_CutMatchesNoSpaces(int count)
2081 // cut match 0 down to the longest possible completion
2084 char tempstr[sizeof(Nicks_sanlist[0])];
2087 c = strlen(Nicks_sanlist[0]);
2088 for(i = 0, l = 0; i < (int)c; ++i)
2090 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2092 tempstr[l++] = Nicks_sanlist[0][i];
2097 for(i = 1; i < count; ++i)
2100 b = Nicks_sanlist[i];
2110 if(tolower(*a) == tolower(*b))
2124 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2125 Nicks_CutMatchesNormal(count);
2126 //if(!Nicks_sanlist[0][0])
2127 //Con_Printf("TS: %s\n", tempstr);
2128 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2130 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2131 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2135 void Nicks_CutMatches(int count)
2137 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2138 Nicks_CutMatchesAlphaNumeric(count);
2139 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2140 Nicks_CutMatchesNoSpaces(count);
2142 Nicks_CutMatchesNormal(count);
2145 const char **Nicks_CompleteBuildList(int count)
2149 // the list is freed by Con_CompleteCommandLine, so create a char**
2150 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2152 for(; bpos < count; ++bpos)
2153 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2155 Nicks_CutMatches(count);
2161 int Nicks_AddLastColor(char *buffer, int pos)
2163 qboolean quote_added = false;
2167 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2169 // we'll have to add a quote :)
2170 buffer[pos++] = '\"';
2174 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2176 // add color when no quote was added, or when flags &4?
2178 for(match = Nicks_matchpos-1; match >= 0; --match)
2180 if(buffer[match] == STRING_COLOR_TAG && buffer[match+1] >= '0' && buffer[match+1] <= '9')
2182 color = buffer[match+1];
2186 if(!quote_added && buffer[pos-2] == STRING_COLOR_TAG && buffer[pos-1] >= '0' && buffer[pos-1] <= '9') // when thes use &4
2188 buffer[pos++] = STRING_COLOR_TAG;
2189 buffer[pos++] = color;
2194 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2197 /*if(!con_nickcompletion.integer)
2198 return; is tested in Nicks_CompletionCountPossible */
2199 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2205 msg = Nicks_list[0];
2206 len = min(size - Nicks_matchpos - 3, strlen(msg));
2207 memcpy(&buffer[Nicks_matchpos], msg, len);
2208 if( len < (size - 4) ) // space for color and space and \0
2209 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2210 buffer[len++] = ' ';
2217 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2218 Cmd_CompleteNicksPrint(n);
2220 Nicks_CutMatches(n);
2222 msg = Nicks_sanlist[0];
2223 len = min(size - Nicks_matchpos, strlen(msg));
2224 memcpy(&buffer[Nicks_matchpos], msg, len);
2225 buffer[Nicks_matchpos + len] = 0;
2227 return Nicks_matchpos + len;
2234 Con_CompleteCommandLine
2236 New function for tab-completion system
2237 Added by EvilTypeGuy
2238 Thanks to Fett erich@heintz.com
2240 Enhanced to tab-complete map names by [515]
2243 void Con_CompleteCommandLine (void)
2245 const char *cmd = "";
2247 const char **list[4] = {0, 0, 0, 0};
2250 int c, v, a, i, cmd_len, pos, k;
2251 int n; // nicks --blub
2252 const char *space, *patterns;
2254 //find what we want to complete
2258 k = key_lines[edit_line][pos];
2259 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2264 s = key_lines[edit_line] + pos;
2265 strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2)); //save chars after cursor
2266 key_lines[edit_line][key_linepos] = 0; //hide them
2268 space = strchr(key_lines[edit_line] + 1, ' ');
2269 if(space && pos == (space - key_lines[edit_line]) + 1)
2271 strlcpy(command, key_lines[edit_line] + 1, min(sizeof(command), (unsigned int)(space - key_lines[edit_line])));
2273 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2274 if(patterns && !*patterns)
2275 patterns = NULL; // get rid of the empty string
2277 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2281 if (GetMapList(s, t, sizeof(t)))
2283 // first move the cursor
2284 key_linepos += (int)strlen(t) - (int)strlen(s);
2286 // and now do the actual work
2288 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2289 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2291 // and fix the cursor
2292 if(key_linepos > (int) strlen(key_lines[edit_line]))
2293 key_linepos = (int) strlen(key_lines[edit_line]);
2302 stringlist_t resultbuf, dirbuf;
2305 // // store completion patterns (space separated) for command foo in con_completion_foo
2306 // set con_completion_foo "foodata/*.foodefault *.foo"
2309 // Note: patterns with slash are always treated as absolute
2310 // patterns; patterns without slash search in the innermost
2311 // directory the user specified. There is no way to "complete into"
2312 // a directory as of now, as directories seem to be unknown to the
2316 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2317 // set con_completion_playdemo "*.dem"
2318 // set con_completion_play "*.wav *.ogg"
2320 // TODO somehow add support for directories; these shall complete
2321 // to their name + an appended slash.
2323 stringlistinit(&resultbuf);
2324 stringlistinit(&dirbuf);
2325 while(COM_ParseToken_Simple(&patterns, false, false))
2328 if(strchr(com_token, '/'))
2330 search = FS_Search(com_token, true, true);
2334 const char *slash = strrchr(s, '/');
2337 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2338 strlcat(t, com_token, sizeof(t));
2339 search = FS_Search(t, true, true);
2342 search = FS_Search(com_token, true, true);
2346 for(i = 0; i < search->numfilenames; ++i)
2347 if(!strncmp(search->filenames[i], s, strlen(s)))
2348 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2349 stringlistappend(&resultbuf, search->filenames[i]);
2350 FS_FreeSearch(search);
2354 // In any case, add directory names
2357 const char *slash = strrchr(s, '/');
2360 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2361 strlcat(t, "*", sizeof(t));
2362 search = FS_Search(t, true, true);
2365 search = FS_Search("*", true, true);
2368 for(i = 0; i < search->numfilenames; ++i)
2369 if(!strncmp(search->filenames[i], s, strlen(s)))
2370 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2371 stringlistappend(&dirbuf, search->filenames[i]);
2372 FS_FreeSearch(search);
2376 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2379 unsigned int matchchars;
2380 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2382 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2385 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2387 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2391 stringlistsort(&resultbuf); // dirbuf is already sorted
2392 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2393 for(i = 0; i < dirbuf.numstrings; ++i)
2395 Con_Printf("%s/\n", dirbuf.strings[i]);
2397 for(i = 0; i < resultbuf.numstrings; ++i)
2399 Con_Printf("%s\n", resultbuf.strings[i]);
2401 matchchars = sizeof(t) - 1;
2402 if(resultbuf.numstrings > 0)
2404 p = resultbuf.strings[0];
2405 q = resultbuf.strings[resultbuf.numstrings - 1];
2406 for(; *p && *p == *q; ++p, ++q);
2407 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2409 if(dirbuf.numstrings > 0)
2411 p = dirbuf.strings[0];
2412 q = dirbuf.strings[dirbuf.numstrings - 1];
2413 for(; *p && *p == *q; ++p, ++q);
2414 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2416 // now p points to the first non-equal character, or to the end
2417 // of resultbuf.strings[0]. We want to append the characters
2418 // from resultbuf.strings[0] to (not including) p as these are
2419 // the unique prefix
2420 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2423 // first move the cursor
2424 key_linepos += (int)strlen(t) - (int)strlen(s);
2426 // and now do the actual work
2428 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2429 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2431 // and fix the cursor
2432 if(key_linepos > (int) strlen(key_lines[edit_line]))
2433 key_linepos = (int) strlen(key_lines[edit_line]);
2435 stringlistfreecontents(&resultbuf);
2436 stringlistfreecontents(&dirbuf);
2438 return; // bail out, when we complete for a command that wants a file name
2443 // Count number of possible matches and print them
2444 c = Cmd_CompleteCountPossible(s);
2447 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2448 Cmd_CompleteCommandPrint(s);
2450 v = Cvar_CompleteCountPossible(s);
2453 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2454 Cvar_CompleteCvarPrint(s);
2456 a = Cmd_CompleteAliasCountPossible(s);
2459 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2460 Cmd_CompleteAliasPrint(s);
2462 n = Nicks_CompleteCountPossible(key_lines[edit_line], key_linepos, s, true);
2465 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2466 Cmd_CompleteNicksPrint(n);
2469 if (!(c + v + a + n)) // No possible matches
2472 strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
2477 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2479 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2481 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2483 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2485 for (cmd_len = (int)strlen(s);;cmd_len++)
2488 for (i = 0; i < 3; i++)
2490 for (l = list[i];*l;l++)
2491 if ((*l)[cmd_len] != cmd[cmd_len])
2493 // all possible matches share this character, so we continue...
2496 // if all matches ended at the same position, stop
2497 // (this means there is only one match)
2503 // prevent a buffer overrun by limiting cmd_len according to remaining space
2504 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
2508 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
2509 key_linepos += cmd_len;
2510 // if there is only one match, add a space after it
2511 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
2514 { // was a nick, might have an offset, and needs colors ;) --blub
2515 key_linepos = pos - Nicks_offset[0];
2516 cmd_len = strlen(Nicks_list[0]);
2517 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 3 - pos);
2519 memcpy(&key_lines[edit_line][key_linepos] , Nicks_list[0], cmd_len);
2520 key_linepos += cmd_len;
2521 if(key_linepos < (int)(sizeof(key_lines[edit_line])-4)) // space for ^, X and space and \0
2522 key_linepos = Nicks_AddLastColor(key_lines[edit_line], key_linepos);
2524 key_lines[edit_line][key_linepos++] = ' ';
2528 // use strlcat to avoid a buffer overrun
2529 key_lines[edit_line][key_linepos] = 0;
2530 strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
2532 // free the command, cvar, and alias lists
2533 for (i = 0; i < 4; i++)
2535 Mem_Free((void *)list[i]);