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 qboolean rcon_redirect = false;
110 int rcon_redirect_bufferpos = 0;
111 char rcon_redirect_buffer[1400];
115 ==============================================================================
119 ==============================================================================
122 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
123 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"};
124 char log_dest_buffer[1400]; // UDP packet
125 size_t log_dest_buffer_pos;
126 qboolean log_dest_buffer_appending;
127 char crt_log_file [MAX_OSPATH] = "";
128 qfile_t* logfile = NULL;
130 unsigned char* logqueue = NULL;
132 size_t logq_size = 0;
134 void Log_ConPrint (const char *msg);
141 static void Log_DestBuffer_Init()
143 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
144 log_dest_buffer_pos = 5;
152 void Log_DestBuffer_Flush()
154 lhnetaddress_t log_dest_addr;
155 lhnetsocket_t *log_dest_socket;
156 const char *s = log_dest_udp.string;
157 qboolean have_opened_temp_sockets = false;
158 if(s) if(log_dest_buffer_pos > 5)
160 ++log_dest_buffer_appending;
161 log_dest_buffer[log_dest_buffer_pos++] = 0;
163 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
165 have_opened_temp_sockets = true;
166 NetConn_OpenServerPorts(true);
169 while(COM_ParseToken_Console(&s))
170 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
172 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
174 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
176 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
179 if(have_opened_temp_sockets)
180 NetConn_CloseServerPorts();
181 --log_dest_buffer_appending;
183 log_dest_buffer_pos = 0;
191 const char* Log_Timestamp (const char *desc)
193 static char timestamp [128];
200 char timestring [64];
202 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
205 localtime_s (&crt_tm, &crt_time);
206 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
208 crt_tm = localtime (&crt_time);
209 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
213 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
215 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
228 if (logfile != NULL || log_file.string[0] == '\0')
231 logfile = FS_Open (log_file.string, "ab", false, false);
234 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
235 FS_Print (logfile, Log_Timestamp ("Log started"));
245 void Log_Close (void)
250 FS_Print (logfile, Log_Timestamp ("Log stopped"));
251 FS_Print (logfile, "\n");
255 crt_log_file[0] = '\0';
264 void Log_Start (void)
270 // Dump the contents of the log queue into the log file and free it
271 if (logqueue != NULL)
273 unsigned char *temp = logqueue;
278 FS_Write (logfile, temp, logq_ind);
279 if(*log_dest_udp.string)
281 for(pos = 0; pos < logq_ind; )
283 if(log_dest_buffer_pos == 0)
284 Log_DestBuffer_Init();
285 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
286 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
287 log_dest_buffer_pos += n;
288 Log_DestBuffer_Flush();
305 void Log_ConPrint (const char *msg)
307 static qboolean inprogress = false;
309 // don't allow feedback loops with memory error reports
314 // Until the host is completely initialized, we maintain a log queue
315 // to store the messages, since the log can't be started before
316 if (logqueue != NULL)
318 size_t remain = logq_size - logq_ind;
319 size_t len = strlen (msg);
321 // If we need to enlarge the log queue
324 size_t factor = ((logq_ind + len) / logq_size) + 1;
325 unsigned char* newqueue;
328 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
329 memcpy (newqueue, logqueue, logq_ind);
332 remain = logq_size - logq_ind;
334 memcpy (&logqueue[logq_ind], msg, len);
341 // Check if log_file has changed
342 if (strcmp (crt_log_file, log_file.string) != 0)
348 // If a log file is available
350 FS_Print (logfile, msg);
361 void Log_Printf (const char *logfilename, const char *fmt, ...)
365 file = FS_Open (logfilename, "ab", true, false);
370 va_start (argptr, fmt);
371 FS_VPrintf (file, fmt, argptr);
380 ==============================================================================
384 ==============================================================================
392 void Con_ToggleConsole_f (void)
394 // toggle the 'user wants console' bit
395 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
404 void Con_Clear_f (void)
414 Clear all notify lines.
417 void Con_ClearNotify (void)
420 for(i = 0; i < con_lines_count; ++i)
421 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
430 void Con_MessageMode_f (void)
432 key_dest = key_message;
442 void Con_MessageMode2_f (void)
444 key_dest = key_message;
453 If the line width has changed, reformat the buffer.
456 void Con_CheckResize (void)
461 f = bound(1, con_textsize.value, 128);
462 if(f != con_textsize.value)
463 Cvar_SetValueQuick(&con_textsize, f);
464 width = (int)floor(vid_conwidth.value / con_textsize.value);
465 width = bound(1, width, CON_TEXTSIZE/4);
467 if (width == con_linewidth)
470 con_linewidth = width;
472 for(i = 0; i < con_lines_count; ++i)
473 CON_LINES(i).height = -1; // recalculate when next needed
479 //[515]: the simplest command ever
480 //LordHavoc: not so simple after I made it print usage...
481 static void Con_Maps_f (void)
485 Con_Printf("usage: maps [mapnameprefix]\n");
488 else if (Cmd_Argc() == 2)
489 GetMapList(Cmd_Argv(1), NULL, 0);
491 GetMapList("", NULL, 0);
494 void Con_ConDump_f (void)
500 Con_Printf("usage: condump <filename>\n");
503 file = FS_Open(Cmd_Argv(1), "wb", false, false);
506 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
509 for(i = 0; i < con_lines_count; ++i)
511 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
512 FS_Write(file, "\n", 1);
528 // Allocate a log queue, this will be freed after configs are parsed
529 logq_size = MAX_INPUTLINE;
530 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
533 Cvar_RegisterVariable (&sys_colortranslation);
534 Cvar_RegisterVariable (&sys_specialcharactertranslation);
536 Cvar_RegisterVariable (&log_file);
537 Cvar_RegisterVariable (&log_dest_udp);
539 // support for the classic Quake option
540 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
541 if (COM_CheckParm ("-condebug") != 0)
542 Cvar_SetQuick (&log_file, "qconsole.log");
544 // register our cvars
545 Cvar_RegisterVariable (&con_chat);
546 Cvar_RegisterVariable (&con_chatpos);
547 Cvar_RegisterVariable (&con_chatsize);
548 Cvar_RegisterVariable (&con_chattime);
549 Cvar_RegisterVariable (&con_chatwidth);
550 Cvar_RegisterVariable (&con_notify);
551 Cvar_RegisterVariable (&con_notifyalign);
552 Cvar_RegisterVariable (&con_notifysize);
553 Cvar_RegisterVariable (&con_notifytime);
554 Cvar_RegisterVariable (&con_textsize);
557 Cvar_RegisterVariable (&con_nickcompletion);
558 Cvar_RegisterVariable (&con_nickcompletion_flags);
560 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
561 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
562 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
564 // register our commands
565 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
566 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
567 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
568 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
569 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
570 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
572 con_initialized = true;
573 Con_DPrint("Console initialized.\n");
581 Deletes the first line from the console history.
584 void Con_DeleteLine()
586 if(con_lines_count == 0)
589 con_lines_first = CON_LINES_IDX(1);
596 Deletes the last line from the console history.
599 void Con_DeleteLastLine()
601 if(con_lines_count == 0)
610 Checks if there is space for a line of the given length, and if yes, returns a
611 pointer to the start of such a space, and NULL otherwise.
614 char *Con_BytesLeft(int len)
616 if(len > CON_TEXTSIZE)
618 if(con_lines_count == 0)
622 char *firstline_start = con_lines[con_lines_first].start;
623 char *lastline_onepastend = con_lines[CON_LINES_LAST].start + con_lines[CON_LINES_LAST].len;
624 // the buffer is cyclic, so we first have two cases...
625 if(firstline_start < lastline_onepastend) // buffer is contiguous
628 if(len <= con_text + CON_TEXTSIZE - lastline_onepastend)
629 return lastline_onepastend;
631 else if(len <= firstline_start - con_text)
636 else // buffer has a contiguous hole
638 if(len <= firstline_start - lastline_onepastend)
639 return lastline_onepastend;
650 Notifies the console code about the current time
651 (and shifts back times of other entries when the time
658 if(con_lines_count >= 1)
660 double diff = cl.time - (con_lines + CON_LINES_LAST)->addtime;
663 for(i = 0; i < con_lines_count; ++i)
664 CON_LINES(i).addtime += diff;
673 Appends a given string as a new line to the console.
676 void Con_AddLine(const char *line, int len, int mask)
683 if(len >= CON_TEXTSIZE)
686 // only display end of line.
687 line += len - CON_TEXTSIZE + 1;
688 len = CON_TEXTSIZE - 1;
690 while(!(putpos = Con_BytesLeft(len + 1)) || con_lines_count >= CON_MAXLINES)
692 memcpy(putpos, line, len);
696 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", con_lines_count, con_lines_first, CON_LINES_LAST);
698 p = con_lines + CON_LINES_LAST;
701 p->addtime = cl.time;
703 p->height = -1; // calculate when needed
710 Handles cursor positioning, line wrapping, etc
711 All console printing must go through this in order to be displayed
712 If no console is visible, the notify window will pop up.
715 void Con_PrintToHistory(const char *txt, int mask)
718 // \n goes to next line
719 // \r deletes current line and makes a new one
721 static int cr_pending = 0;
722 static char buf[CON_TEXTSIZE];
723 static int bufpos = 0;
729 Con_DeleteLastLine();
737 Con_AddLine(buf, bufpos, mask);
742 Con_AddLine(buf, bufpos, mask);
746 buf[bufpos++] = *txt;
747 if(bufpos >= CON_TEXTSIZE - 1)
749 Con_AddLine(buf, bufpos, mask);
757 /* The translation table between the graphical font and plain ASCII --KB */
758 static char qfont_table[256] = {
759 '\0', '#', '#', '#', '#', '.', '#', '#',
760 '#', 9, 10, '#', ' ', 13, '.', '.',
761 '[', ']', '0', '1', '2', '3', '4', '5',
762 '6', '7', '8', '9', '.', '<', '=', '>',
763 ' ', '!', '"', '#', '$', '%', '&', '\'',
764 '(', ')', '*', '+', ',', '-', '.', '/',
765 '0', '1', '2', '3', '4', '5', '6', '7',
766 '8', '9', ':', ';', '<', '=', '>', '?',
767 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
768 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
769 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
770 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
771 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
772 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
773 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
774 'x', 'y', 'z', '{', '|', '}', '~', '<',
776 '<', '=', '>', '#', '#', '.', '#', '#',
777 '#', '#', ' ', '#', ' ', '>', '.', '.',
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', '{', '|', '}', '~', '<'
798 Adds a character to the rcon buffer
801 void Con_Rcon_AddChar(char c)
803 if(log_dest_buffer_appending)
805 ++log_dest_buffer_appending;
807 // if this print is in response to an rcon command, add the character
808 // to the rcon redirect buffer
810 if (rcon_redirect && rcon_redirect_bufferpos < (int)sizeof(rcon_redirect_buffer) - 1)
811 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
812 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
814 if(log_dest_buffer_pos == 0)
815 Log_DestBuffer_Init();
816 log_dest_buffer[log_dest_buffer_pos++] = c;
817 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
818 Log_DestBuffer_Flush();
821 log_dest_buffer_pos = 0;
823 --log_dest_buffer_appending;
830 Prints to all appropriate console targets, and adds timestamps
833 extern cvar_t timestamps;
834 extern cvar_t timeformat;
835 extern qboolean sys_nostdout;
836 void Con_Print(const char *msg)
839 static int index = 0;
840 static char line[MAX_INPUTLINE];
844 Con_Rcon_AddChar(*msg);
845 // if this is the beginning of a new line, print timestamp
848 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
850 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
851 line[index++] = STRING_COLOR_TAG;
852 // assert( STRING_COLOR_DEFAULT < 10 )
853 line[index++] = STRING_COLOR_DEFAULT + '0';
854 // special color codes for chat messages must always come first
855 // for Con_PrintToHistory to work properly
856 if (*msg == 1 || *msg == 2)
861 if(gamemode == GAME_NEXUIZ)
863 if(msg[1] == '\r' && cl.foundtalk2wav)
864 S_LocalSound ("sound/misc/talk2.wav");
866 S_LocalSound ("sound/misc/talk.wav");
870 if (msg[1] == '(' && cl.foundtalk2wav)
871 S_LocalSound ("sound/misc/talk2.wav");
873 S_LocalSound ("sound/misc/talk.wav");
875 mask = CON_MASK_CHAT;
877 line[index++] = STRING_COLOR_TAG;
880 Con_Rcon_AddChar(*msg);
883 for (;*timestamp;index++, timestamp++)
884 if (index < (int)sizeof(line) - 2)
885 line[index] = *timestamp;
887 // append the character
888 line[index++] = *msg;
889 // if this is a newline character, we have a complete line to print
890 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
892 // terminate the line
896 // send to scrollable buffer
897 if (con_initialized && cls.state != ca_dedicated)
899 Con_PrintToHistory(line, mask);
902 // send to terminal or dedicated server window
906 if(sys_specialcharactertranslation.integer)
908 for (p = (unsigned char *) line;*p; p++)
909 *p = qfont_table[*p];
912 if(sys_colortranslation.integer == 1) // ANSI
914 static char printline[MAX_INPUTLINE * 4 + 3];
915 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
916 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
920 for(in = line, out = printline; *in; ++in)
924 case STRING_COLOR_TAG:
927 case STRING_COLOR_TAG:
929 *out++ = STRING_COLOR_TAG;
935 if(lastcolor == 0) break; else lastcolor = 0;
936 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
941 if(lastcolor == 1) break; else lastcolor = 1;
942 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
947 if(lastcolor == 2) break; else lastcolor = 2;
948 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
953 if(lastcolor == 3) break; else lastcolor = 3;
954 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
959 if(lastcolor == 4) break; else lastcolor = 4;
960 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
965 if(lastcolor == 5) break; else lastcolor = 5;
966 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
971 if(lastcolor == 6) break; else lastcolor = 6;
972 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
979 if(lastcolor == 8) break; else lastcolor = 8;
980 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
983 *out++ = STRING_COLOR_TAG;
990 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1007 Sys_PrintToTerminal(printline);
1009 else if(sys_colortranslation.integer == 2) // Quake
1011 Sys_PrintToTerminal(line);
1015 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1018 for(in = line, out = printline; *in; ++in)
1022 case STRING_COLOR_TAG:
1025 case STRING_COLOR_TAG:
1027 *out++ = STRING_COLOR_TAG;
1042 *out++ = STRING_COLOR_TAG;
1052 Sys_PrintToTerminal(printline);
1055 // empty the line buffer
1066 Prints to all appropriate console targets
1069 void Con_Printf(const char *fmt, ...)
1072 char msg[MAX_INPUTLINE];
1074 va_start(argptr,fmt);
1075 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1085 A Con_Print that only shows up if the "developer" cvar is set
1088 void Con_DPrint(const char *msg)
1090 if (!developer.integer)
1091 return; // don't confuse non-developers with techie stuff...
1099 A Con_Printf that only shows up if the "developer" cvar is set
1102 void Con_DPrintf(const char *fmt, ...)
1105 char msg[MAX_INPUTLINE];
1107 if (!developer.integer)
1108 return; // don't confuse non-developers with techie stuff...
1110 va_start(argptr,fmt);
1111 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1119 ==============================================================================
1123 ==============================================================================
1130 The input line scrolls horizontally if typing goes beyond the right edge
1132 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1135 void Con_DrawInput (void)
1139 char editlinecopy[MAX_INPUTLINE+1], *text;
1142 if (!key_consoleactive)
1143 return; // don't draw anything
1145 strlcpy(editlinecopy, key_lines[edit_line], sizeof(editlinecopy));
1146 text = editlinecopy;
1148 // Advanced Console Editing by Radix radix@planetquake.com
1149 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1150 // use strlen of edit_line instead of key_linepos to allow editing
1151 // of early characters w/o erasing
1153 y = (int)strlen(text);
1155 // fill out remainder with spaces
1156 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1159 // add the cursor frame
1160 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1161 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1163 // text[key_linepos + 1] = 0;
1165 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1170 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 );
1173 // key_lines[edit_line][key_linepos] = 0;
1179 float alignment; // 0 = left, 0.5 = center, 1 = right
1185 const char *continuationString;
1188 int colorindex; // init to -1
1192 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1194 con_text_info_t *ti = (con_text_info_t *) passthrough;
1197 ti->colorindex = -1;
1198 return ti->fontsize * ti->font->maxwidth;
1201 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1202 else if(maxWidth == -1)
1203 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1206 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1207 // Note: this is NOT a Con_Printf, as it could print recursively
1212 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1218 (void) isContinuation;
1222 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1224 con_text_info_t *ti = (con_text_info_t *) passthrough;
1226 if(ti->y < ti->ymin - 0.001)
1228 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1232 int x = ti->x + (ti->width - width) * ti->alignment;
1233 if(isContinuation && *ti->continuationString)
1234 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);
1236 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);
1239 ti->y += ti->fontsize;
1244 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)
1248 int maxlines = (int) floor(height / fontsize + 0.01f);
1251 int continuationWidth = 0;
1253 double t = cl.time; // saved so it won't change
1256 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1257 ti.fontsize = fontsize;
1258 ti.alignment = alignment_x;
1261 ti.ymax = y + height;
1262 ti.continuationString = continuationString;
1265 Con_WordWidthFunc(&ti, NULL, &l, -1);
1266 l = strlen(continuationString);
1267 continuationWidth = Con_WordWidthFunc(&ti, continuationString, &l, -1);
1269 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1270 startidx = con_lines_count;
1271 for(i = con_lines_count - 1; i >= 0; --i)
1273 con_lineinfo *l = &CON_LINES(i);
1276 if((l->mask & mask_must) != mask_must)
1278 if(l->mask & mask_mustnot)
1280 if(maxage && (l->addtime < t - maxage))
1284 // Calculate its actual height...
1285 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1286 if(lines + mylines >= maxlines)
1288 nskip = lines + mylines - maxlines;
1297 // then center according to the calculated amount of lines...
1299 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1301 // then actually draw
1302 for(i = startidx; i < con_lines_count; ++i)
1304 con_lineinfo *l = &CON_LINES(i);
1306 if((l->mask & mask_must) != mask_must)
1308 if(l->mask & mask_mustnot)
1310 if(maxage && (l->addtime < t - maxage))
1313 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1323 Draws the last few lines of output transparently over the game top
1326 void Con_DrawNotify (void)
1329 float chatstart, notifystart, inputsize;
1331 char temptext[MAX_INPUTLINE];
1337 numChatlines = con_chat.integer;
1338 chatpos = con_chatpos.integer;
1340 if (con_notify.integer < 0)
1341 Cvar_SetValueQuick(&con_notify, 0);
1342 if (gamemode == GAME_TRANSFUSION)
1343 v = 8; // vertical offset
1347 // GAME_NEXUIZ: center, otherwise left justify
1348 align = con_notifyalign.value;
1349 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1351 if(gamemode == GAME_NEXUIZ)
1359 // first chat, input line, then notify
1361 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1363 else if(chatpos > 0)
1365 // first notify, then (chatpos-1) empty lines, then chat, then input
1367 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1369 else // if(chatpos < 0)
1371 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1373 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1378 // just notify and input
1380 chatstart = 0; // shut off gcc warning
1383 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, "");
1388 v = chatstart + numChatlines * con_chatsize.value;
1389 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
1392 if (key_dest == key_message)
1394 int colorindex = -1;
1396 // LordHavoc: speedup, and other improvements
1398 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1400 dpsnprintf(temptext, sizeof(temptext), "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1403 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1404 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1407 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1413 Con_MeasureConsoleLine
1415 Counts the number of lines for a line on the console.
1418 int Con_MeasureConsoleLine(int lineno)
1420 float width = vid_conwidth.value;
1422 ti.fontsize = con_textsize.value;
1423 ti.font = FONT_CONSOLE;
1425 return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1432 Returns the height of a given console line; calculates it if necessary.
1435 int Con_LineHeight(int i)
1437 int h = con_lines[i].height;
1440 return con_lines[i].height = Con_MeasureConsoleLine(i);
1447 Draws a line of the console; returns its height in lines.
1448 If alpha is 0, the line is not drawn, but still wrapped and its height
1452 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1454 float width = vid_conwidth.value;
1457 ti.continuationString = "";
1459 ti.fontsize = con_textsize.value;
1460 ti.font = FONT_CONSOLE;
1462 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1467 return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1474 Calculates the last visible line index and how much to show of it based on
1478 void Con_LastVisibleLine(int *last, int *limitlast)
1483 if(con_backscroll < 0)
1486 // now count until we saw con_backscroll actual lines
1487 for(ic = 0; ic < con_lines_count; ++ic)
1489 int i = CON_LINES_IDX(con_lines_count - 1 - ic);
1490 int h = Con_LineHeight(i);
1492 // line is the last visible line?
1493 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1496 *limitlast = lines_seen + h - con_backscroll;
1503 // if we get here, no line was on screen - scroll so that one line is
1505 con_backscroll = lines_seen - 1;
1506 *last = con_lines_first;
1514 Draws the console with the solid background
1515 The typing input line at the bottom should only be drawn if typing is allowed
1518 void Con_DrawConsole (int lines)
1520 int i, last, limitlast;
1526 con_vislines = lines;
1528 // draw the background
1529 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
1530 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);
1533 if(con_lines_count > 0)
1535 float ymax = con_vislines - 2 * con_textsize.value;
1536 Con_LastVisibleLine(&last, &limitlast);
1537 y = ymax - con_textsize.value;
1540 y += (con_lines[last].height - limitlast) * con_textsize.value;
1545 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1546 if(i == con_lines_first)
1547 break; // top of console buffer
1549 break; // top of console window
1551 i = CON_LINES_PRED(i);
1555 // draw the input prompt, user text, and cursor if desired
1563 Prints not only map filename, but also
1564 its format (q1/q2/q3/hl) and even its message
1566 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1567 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1568 //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
1569 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1570 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1574 int i, k, max, p, o, min;
1577 unsigned char buf[1024];
1579 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1580 t = FS_Search(message, 1, true);
1583 if (t->numfilenames > 1)
1584 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1585 len = (unsigned char *)Z_Malloc(t->numfilenames);
1587 for(max=i=0;i<t->numfilenames;i++)
1589 k = (int)strlen(t->filenames[i]);
1599 for(i=0;i<t->numfilenames;i++)
1601 int lumpofs = 0, lumplen = 0;
1602 char *entities = NULL;
1603 const char *data = NULL;
1605 char entfilename[MAX_QPATH];
1606 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1608 f = FS_Open(t->filenames[i], "rb", true, false);
1611 memset(buf, 0, 1024);
1612 FS_Read(f, buf, 1024);
1613 if (!memcmp(buf, "IBSP", 4))
1615 p = LittleLong(((int *)buf)[1]);
1616 if (p == Q3BSPVERSION)
1618 q3dheader_t *header = (q3dheader_t *)buf;
1619 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1620 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1622 else if (p == Q2BSPVERSION)
1624 q2dheader_t *header = (q2dheader_t *)buf;
1625 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1626 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1629 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1631 dheader_t *header = (dheader_t *)buf;
1632 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1633 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1637 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1638 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1639 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1640 if (!entities && lumplen >= 10)
1642 FS_Seek(f, lumpofs, SEEK_SET);
1643 entities = (char *)Z_Malloc(lumplen + 1);
1644 FS_Read(f, entities, lumplen);
1648 // if there are entities to parse, a missing message key just
1649 // means there is no title, so clear the message string now
1655 if (!COM_ParseToken_Simple(&data, false, false))
1657 if (com_token[0] == '{')
1659 if (com_token[0] == '}')
1661 // skip leading whitespace
1662 for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
1663 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
1664 keyname[l] = com_token[k+l];
1666 if (!COM_ParseToken_Simple(&data, false, false))
1668 if (developer.integer >= 100)
1669 Con_Printf("key: %s %s\n", keyname, com_token);
1670 if (!strcmp(keyname, "message"))
1672 // get the message contents
1673 strlcpy(message, com_token, sizeof(message));
1683 *(t->filenames[i]+len[i]+5) = 0;
1686 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1687 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1688 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1689 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1690 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1692 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1697 k = *(t->filenames[0]+5+p);
1700 for(i=1;i<t->numfilenames;i++)
1701 if(*(t->filenames[i]+5+p) != k)
1705 if(p > o && completedname && completednamebufferlength > 0)
1707 memset(completedname, 0, completednamebufferlength);
1708 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1718 New function for tab-completion system
1719 Added by EvilTypeGuy
1720 MEGA Thanks to Taniwha
1723 void Con_DisplayList(const char **list)
1725 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1726 const char **walk = list;
1729 len = (int)strlen(*walk);
1737 len = (int)strlen(*list);
1738 if (pos + maxlen >= width) {
1744 for (i = 0; i < (maxlen - len); i++)
1755 /* Nicks_CompleteCountPossible
1757 Count the number of possible nicks to complete
1759 //qboolean COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets);
1760 void SanitizeString(char *in, char *out)
1764 if(*in == STRING_COLOR_TAG)
1769 out[0] = STRING_COLOR_TAG;
1772 } else if(*in >= '0' && *in <= '9')
1779 } else if (*in == STRING_COLOR_TAG)
1781 } else if (*in != STRING_COLOR_TAG) {
1785 *out = qfont_table[*(unsigned char*)in];
1792 // Now it becomes TRICKY :D --blub
1793 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
1794 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
1795 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
1796 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
1797 static int Nicks_matchpos;
1799 // co against <<:BLASTER:>> is true!?
1800 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
1804 if(tolower(*a) == tolower(*b))
1818 return (*a < *b) ? -1 : 1;
1822 return (*a < *b) ? -1 : 1;
1826 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
1829 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
1831 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
1832 return Nicks_strncasecmp_nospaces(a, b, a_len);
1833 return strncasecmp(a, b, a_len);
1836 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
1838 // ignore non alphanumerics of B
1839 // if A contains a non-alphanumeric, B must contain it as well though!
1842 qboolean alnum_a, alnum_b;
1844 if(tolower(*a) == tolower(*b))
1846 if(*a == 0) // end of both strings, they're equal
1853 // not equal, end of one string?
1858 // ignore non alphanumerics
1859 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
1860 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
1861 if(!alnum_a) // b must contain this
1862 return (*a < *b) ? -1 : 1;
1865 // otherwise, both are alnum, they're just not equal, return the appropriate number
1867 return (*a < *b) ? -1 : 1;
1872 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
1881 if(!con_nickcompletion.integer)
1884 // changed that to 1
1885 if(!line[0])// || !line[1]) // we want at least... 2 written characters
1888 for(i = 0; i < cl.maxclients; ++i)
1891 if(!cl.scores[p].name[0])
1894 SanitizeString(cl.scores[p].name, name);
1895 //Con_Printf("Sanitized: %s^7 -> %s", cl.scores[p].name, name);
1900 length = strlen(name);
1902 spos = pos - 1; // no need for a minimum of characters :)
1904 while(spos >= 0 && (spos - pos) < length) // search-string-length < name length
1906 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
1908 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
1909 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
1915 if(isCon && spos == 0)
1917 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
1923 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
1924 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
1926 // the sanitized list
1927 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
1930 Nicks_matchpos = match;
1933 Nicks_offset[count] = s - (&line[match]);
1934 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
1941 void Cmd_CompleteNicksPrint(int count)
1944 for(i = 0; i < count; ++i)
1945 Con_Printf("%s\n", Nicks_list[i]);
1948 void Nicks_CutMatchesNormal(int count)
1950 // cut match 0 down to the longest possible completion
1953 c = strlen(Nicks_sanlist[0]) - 1;
1954 for(i = 1; i < count; ++i)
1956 l = strlen(Nicks_sanlist[i]) - 1;
1960 for(l = 0; l <= c; ++l)
1961 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
1967 Nicks_sanlist[0][c+1] = 0;
1968 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
1971 unsigned int Nicks_strcleanlen(const char *s)
1976 if( (*s >= 'a' && *s <= 'z') ||
1977 (*s >= 'A' && *s <= 'Z') ||
1978 (*s >= '0' && *s <= '9') ||
1986 void Nicks_CutMatchesAlphaNumeric(int count)
1988 // cut match 0 down to the longest possible completion
1991 char tempstr[sizeof(Nicks_sanlist[0])];
1993 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
1995 c = strlen(Nicks_sanlist[0]);
1996 for(i = 0, l = 0; i < (int)c; ++i)
1998 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
1999 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2000 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2002 tempstr[l++] = Nicks_sanlist[0][i];
2007 for(i = 1; i < count; ++i)
2010 b = Nicks_sanlist[i];
2020 if(tolower(*a) == tolower(*b))
2026 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2028 // b is alnum, so cut
2035 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2036 Nicks_CutMatchesNormal(count);
2037 //if(!Nicks_sanlist[0][0])
2038 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2040 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2041 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2045 void Nicks_CutMatchesNoSpaces(int count)
2047 // cut match 0 down to the longest possible completion
2050 char tempstr[sizeof(Nicks_sanlist[0])];
2053 c = strlen(Nicks_sanlist[0]);
2054 for(i = 0, l = 0; i < (int)c; ++i)
2056 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2058 tempstr[l++] = Nicks_sanlist[0][i];
2063 for(i = 1; i < count; ++i)
2066 b = Nicks_sanlist[i];
2076 if(tolower(*a) == tolower(*b))
2090 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2091 Nicks_CutMatchesNormal(count);
2092 //if(!Nicks_sanlist[0][0])
2093 //Con_Printf("TS: %s\n", tempstr);
2094 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2096 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2097 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2101 void Nicks_CutMatches(int count)
2103 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2104 Nicks_CutMatchesAlphaNumeric(count);
2105 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2106 Nicks_CutMatchesNoSpaces(count);
2108 Nicks_CutMatchesNormal(count);
2111 const char **Nicks_CompleteBuildList(int count)
2115 // the list is freed by Con_CompleteCommandLine, so create a char**
2116 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2118 for(; bpos < count; ++bpos)
2119 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2121 Nicks_CutMatches(count);
2127 int Nicks_AddLastColor(char *buffer, int pos)
2129 qboolean quote_added = false;
2133 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2135 // we'll have to add a quote :)
2136 buffer[pos++] = '\"';
2140 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2142 // add color when no quote was added, or when flags &4?
2144 for(match = Nicks_matchpos-1; match >= 0; --match)
2146 if(buffer[match] == STRING_COLOR_TAG && buffer[match+1] >= '0' && buffer[match+1] <= '9')
2148 color = buffer[match+1];
2152 if(!quote_added && buffer[pos-2] == STRING_COLOR_TAG && buffer[pos-1] >= '0' && buffer[pos-1] <= '9') // when thes use &4
2154 buffer[pos++] = STRING_COLOR_TAG;
2155 buffer[pos++] = color;
2160 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2163 /*if(!con_nickcompletion.integer)
2164 return; is tested in Nicks_CompletionCountPossible */
2165 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2171 msg = Nicks_list[0];
2172 len = min(size - Nicks_matchpos - 3, strlen(msg));
2173 memcpy(&buffer[Nicks_matchpos], msg, len);
2174 if( len < (size - 4) ) // space for color and space and \0
2175 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2176 buffer[len++] = ' ';
2183 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2184 Cmd_CompleteNicksPrint(n);
2186 Nicks_CutMatches(n);
2188 msg = Nicks_sanlist[0];
2189 len = min(size - Nicks_matchpos, strlen(msg));
2190 memcpy(&buffer[Nicks_matchpos], msg, len);
2191 buffer[Nicks_matchpos + len] = 0;
2193 return Nicks_matchpos + len;
2200 Con_CompleteCommandLine
2202 New function for tab-completion system
2203 Added by EvilTypeGuy
2204 Thanks to Fett erich@heintz.com
2206 Enhanced to tab-complete map names by [515]
2209 void Con_CompleteCommandLine (void)
2211 const char *cmd = "";
2213 const char **list[4] = {0, 0, 0, 0};
2216 int c, v, a, i, cmd_len, pos, k;
2217 int n; // nicks --blub
2218 const char *space, *patterns;
2220 //find what we want to complete
2224 k = key_lines[edit_line][pos];
2225 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2230 s = key_lines[edit_line] + pos;
2231 strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2)); //save chars after cursor
2232 key_lines[edit_line][key_linepos] = 0; //hide them
2234 space = strchr(key_lines[edit_line] + 1, ' ');
2235 if(space && pos == (space - key_lines[edit_line]) + 1)
2237 strlcpy(command, key_lines[edit_line] + 1, min(sizeof(command), (unsigned int)(space - key_lines[edit_line])));
2239 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2240 if(patterns && !*patterns)
2241 patterns = NULL; // get rid of the empty string
2243 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2247 if (GetMapList(s, t, sizeof(t)))
2249 // first move the cursor
2250 key_linepos += (int)strlen(t) - (int)strlen(s);
2252 // and now do the actual work
2254 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2255 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2257 // and fix the cursor
2258 if(key_linepos > (int) strlen(key_lines[edit_line]))
2259 key_linepos = (int) strlen(key_lines[edit_line]);
2268 stringlist_t resultbuf, dirbuf;
2271 // // store completion patterns (space separated) for command foo in con_completion_foo
2272 // set con_completion_foo "foodata/*.foodefault *.foo"
2275 // Note: patterns with slash are always treated as absolute
2276 // patterns; patterns without slash search in the innermost
2277 // directory the user specified. There is no way to "complete into"
2278 // a directory as of now, as directories seem to be unknown to the
2282 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2283 // set con_completion_playdemo "*.dem"
2284 // set con_completion_play "*.wav *.ogg"
2286 // TODO somehow add support for directories; these shall complete
2287 // to their name + an appended slash.
2289 stringlistinit(&resultbuf);
2290 stringlistinit(&dirbuf);
2291 while(COM_ParseToken_Simple(&patterns, false, false))
2294 if(strchr(com_token, '/'))
2296 search = FS_Search(com_token, true, true);
2300 const char *slash = strrchr(s, '/');
2303 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2304 strlcat(t, com_token, sizeof(t));
2305 search = FS_Search(t, true, true);
2308 search = FS_Search(com_token, true, true);
2312 for(i = 0; i < search->numfilenames; ++i)
2313 if(!strncmp(search->filenames[i], s, strlen(s)))
2314 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2315 stringlistappend(&resultbuf, search->filenames[i]);
2316 FS_FreeSearch(search);
2320 // In any case, add directory names
2323 const char *slash = strrchr(s, '/');
2326 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2327 strlcat(t, "*", sizeof(t));
2328 search = FS_Search(t, true, true);
2331 search = FS_Search("*", true, true);
2334 for(i = 0; i < search->numfilenames; ++i)
2335 if(!strncmp(search->filenames[i], s, strlen(s)))
2336 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2337 stringlistappend(&dirbuf, search->filenames[i]);
2338 FS_FreeSearch(search);
2342 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2345 unsigned int matchchars;
2346 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2348 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2351 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2353 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2357 stringlistsort(&resultbuf); // dirbuf is already sorted
2358 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2359 for(i = 0; i < dirbuf.numstrings; ++i)
2361 Con_Printf("%s/\n", dirbuf.strings[i]);
2363 for(i = 0; i < resultbuf.numstrings; ++i)
2365 Con_Printf("%s\n", resultbuf.strings[i]);
2367 matchchars = sizeof(t) - 1;
2368 if(resultbuf.numstrings > 0)
2370 p = resultbuf.strings[0];
2371 q = resultbuf.strings[resultbuf.numstrings - 1];
2372 for(; *p && *p == *q; ++p, ++q);
2373 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2375 if(dirbuf.numstrings > 0)
2377 p = dirbuf.strings[0];
2378 q = dirbuf.strings[dirbuf.numstrings - 1];
2379 for(; *p && *p == *q; ++p, ++q);
2380 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2382 // now p points to the first non-equal character, or to the end
2383 // of resultbuf.strings[0]. We want to append the characters
2384 // from resultbuf.strings[0] to (not including) p as these are
2385 // the unique prefix
2386 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2389 // first move the cursor
2390 key_linepos += (int)strlen(t) - (int)strlen(s);
2392 // and now do the actual work
2394 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2395 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2397 // and fix the cursor
2398 if(key_linepos > (int) strlen(key_lines[edit_line]))
2399 key_linepos = (int) strlen(key_lines[edit_line]);
2401 stringlistfreecontents(&resultbuf);
2402 stringlistfreecontents(&dirbuf);
2404 return; // bail out, when we complete for a command that wants a file name
2409 // Count number of possible matches and print them
2410 c = Cmd_CompleteCountPossible(s);
2413 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2414 Cmd_CompleteCommandPrint(s);
2416 v = Cvar_CompleteCountPossible(s);
2419 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2420 Cvar_CompleteCvarPrint(s);
2422 a = Cmd_CompleteAliasCountPossible(s);
2425 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2426 Cmd_CompleteAliasPrint(s);
2428 n = Nicks_CompleteCountPossible(key_lines[edit_line], key_linepos, s, true);
2431 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2432 Cmd_CompleteNicksPrint(n);
2435 if (!(c + v + a + n)) // No possible matches
2438 strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
2443 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2445 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2447 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2449 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2451 for (cmd_len = (int)strlen(s);;cmd_len++)
2454 for (i = 0; i < 3; i++)
2456 for (l = list[i];*l;l++)
2457 if ((*l)[cmd_len] != cmd[cmd_len])
2459 // all possible matches share this character, so we continue...
2462 // if all matches ended at the same position, stop
2463 // (this means there is only one match)
2469 // prevent a buffer overrun by limiting cmd_len according to remaining space
2470 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
2474 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
2475 key_linepos += cmd_len;
2476 // if there is only one match, add a space after it
2477 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
2480 { // was a nick, might have an offset, and needs colors ;) --blub
2481 key_linepos = pos - Nicks_offset[0];
2482 cmd_len = strlen(Nicks_list[0]);
2483 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 3 - pos);
2485 memcpy(&key_lines[edit_line][key_linepos] , Nicks_list[0], cmd_len);
2486 key_linepos += cmd_len;
2487 if(key_linepos < (int)(sizeof(key_lines[edit_line])-4)) // space for ^, X and space and \0
2488 key_linepos = Nicks_AddLastColor(key_lines[edit_line], key_linepos);
2490 key_lines[edit_line][key_linepos++] = ' ';
2494 // use strlcat to avoid a buffer overrun
2495 key_lines[edit_line][key_linepos] = 0;
2496 strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
2498 // free the command, cvar, and alias lists
2499 for (i = 0; i < 4; i++)
2501 Mem_Free((void *)list[i]);