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];
195 const struct tm *crt_tm;
196 char timestring [64];
198 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
200 crt_tm = localtime (&crt_time);
201 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
204 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
206 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
219 if (logfile != NULL || log_file.string[0] == '\0')
222 logfile = FS_Open (log_file.string, "ab", false, false);
225 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
226 FS_Print (logfile, Log_Timestamp ("Log started"));
236 void Log_Close (void)
241 FS_Print (logfile, Log_Timestamp ("Log stopped"));
242 FS_Print (logfile, "\n");
246 crt_log_file[0] = '\0';
255 void Log_Start (void)
261 // Dump the contents of the log queue into the log file and free it
262 if (logqueue != NULL)
264 unsigned char *temp = logqueue;
269 FS_Write (logfile, temp, logq_ind);
270 if(*log_dest_udp.string)
272 for(pos = 0; pos < logq_ind; )
274 if(log_dest_buffer_pos == 0)
275 Log_DestBuffer_Init();
276 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
277 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
278 log_dest_buffer_pos += n;
279 Log_DestBuffer_Flush();
296 void Log_ConPrint (const char *msg)
298 static qboolean inprogress = false;
300 // don't allow feedback loops with memory error reports
305 // Until the host is completely initialized, we maintain a log queue
306 // to store the messages, since the log can't be started before
307 if (logqueue != NULL)
309 size_t remain = logq_size - logq_ind;
310 size_t len = strlen (msg);
312 // If we need to enlarge the log queue
315 size_t factor = ((logq_ind + len) / logq_size) + 1;
316 unsigned char* newqueue;
319 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
320 memcpy (newqueue, logqueue, logq_ind);
323 remain = logq_size - logq_ind;
325 memcpy (&logqueue[logq_ind], msg, len);
332 // Check if log_file has changed
333 if (strcmp (crt_log_file, log_file.string) != 0)
339 // If a log file is available
341 FS_Print (logfile, msg);
352 void Log_Printf (const char *logfilename, const char *fmt, ...)
356 file = FS_Open (logfilename, "ab", true, false);
361 va_start (argptr, fmt);
362 FS_VPrintf (file, fmt, argptr);
371 ==============================================================================
375 ==============================================================================
383 void Con_ToggleConsole_f (void)
385 // toggle the 'user wants console' bit
386 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
395 void Con_Clear_f (void)
405 Clear all notify lines.
408 void Con_ClearNotify (void)
411 for(i = 0; i < con_lines_count; ++i)
412 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
421 void Con_MessageMode_f (void)
423 key_dest = key_message;
433 void Con_MessageMode2_f (void)
435 key_dest = key_message;
444 If the line width has changed, reformat the buffer.
447 void Con_CheckResize (void)
452 f = bound(1, con_textsize.value, 128);
453 if(f != con_textsize.value)
454 Cvar_SetValueQuick(&con_textsize, f);
455 width = (int)floor(vid_conwidth.value / con_textsize.value);
456 width = bound(1, width, CON_TEXTSIZE/4);
458 if (width == con_linewidth)
461 con_linewidth = width;
463 for(i = 0; i < con_lines_count; ++i)
464 CON_LINES(i).height = -1; // recalculate when next needed
470 //[515]: the simplest command ever
471 //LordHavoc: not so simple after I made it print usage...
472 static void Con_Maps_f (void)
476 Con_Printf("usage: maps [mapnameprefix]\n");
479 else if (Cmd_Argc() == 2)
480 GetMapList(Cmd_Argv(1), NULL, 0);
482 GetMapList("", NULL, 0);
485 void Con_ConDump_f (void)
491 Con_Printf("usage: condump <filename>\n");
494 file = FS_Open(Cmd_Argv(1), "wb", false, false);
497 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
500 for(i = 0; i < con_lines_count; ++i)
502 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
503 FS_Write(file, "\n", 1);
519 // Allocate a log queue, this will be freed after configs are parsed
520 logq_size = MAX_INPUTLINE;
521 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
524 Cvar_RegisterVariable (&sys_colortranslation);
525 Cvar_RegisterVariable (&sys_specialcharactertranslation);
527 Cvar_RegisterVariable (&log_file);
528 Cvar_RegisterVariable (&log_dest_udp);
530 // support for the classic Quake option
531 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
532 if (COM_CheckParm ("-condebug") != 0)
533 Cvar_SetQuick (&log_file, "qconsole.log");
535 // register our cvars
536 Cvar_RegisterVariable (&con_chat);
537 Cvar_RegisterVariable (&con_chatpos);
538 Cvar_RegisterVariable (&con_chatsize);
539 Cvar_RegisterVariable (&con_chattime);
540 Cvar_RegisterVariable (&con_chatwidth);
541 Cvar_RegisterVariable (&con_notify);
542 Cvar_RegisterVariable (&con_notifyalign);
543 Cvar_RegisterVariable (&con_notifysize);
544 Cvar_RegisterVariable (&con_notifytime);
545 Cvar_RegisterVariable (&con_textsize);
548 Cvar_RegisterVariable (&con_nickcompletion);
549 Cvar_RegisterVariable (&con_nickcompletion_flags);
551 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
552 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
553 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
555 // register our commands
556 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
557 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
558 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
559 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
560 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
561 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
563 con_initialized = true;
564 Con_DPrint("Console initialized.\n");
572 Deletes the first line from the console history.
575 void Con_DeleteLine()
577 if(con_lines_count == 0)
580 con_lines_first = CON_LINES_IDX(1);
587 Deletes the last line from the console history.
590 void Con_DeleteLastLine()
592 if(con_lines_count == 0)
601 Checks if there is space for a line of the given length, and if yes, returns a
602 pointer to the start of such a space, and NULL otherwise.
605 char *Con_BytesLeft(int len)
607 if(len > CON_TEXTSIZE)
609 if(con_lines_count == 0)
613 char *firstline_start = con_lines[con_lines_first].start;
614 char *lastline_onepastend = con_lines[CON_LINES_LAST].start + con_lines[CON_LINES_LAST].len;
615 // the buffer is cyclic, so we first have two cases...
616 if(firstline_start < lastline_onepastend) // buffer is contiguous
619 if(len <= con_text + CON_TEXTSIZE - lastline_onepastend)
620 return lastline_onepastend;
622 else if(len <= firstline_start - con_text)
627 else // buffer has a contiguous hole
629 if(len <= firstline_start - lastline_onepastend)
630 return lastline_onepastend;
641 Notifies the console code about the current time
642 (and shifts back times of other entries when the time
649 if(con_lines_count >= 1)
651 double diff = cl.time - (con_lines + CON_LINES_LAST)->addtime;
654 for(i = 0; i < con_lines_count; ++i)
655 CON_LINES(i).addtime += diff;
664 Appends a given string as a new line to the console.
667 void Con_AddLine(const char *line, int len, int mask)
674 if(len >= CON_TEXTSIZE)
677 // only display end of line.
678 line += len - CON_TEXTSIZE + 1;
679 len = CON_TEXTSIZE - 1;
681 while(!(putpos = Con_BytesLeft(len + 1)) || con_lines_count >= CON_MAXLINES)
683 memcpy(putpos, line, len);
687 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", con_lines_count, con_lines_first, CON_LINES_LAST);
689 p = con_lines + CON_LINES_LAST;
692 p->addtime = cl.time;
694 p->height = -1; // calculate when needed
701 Handles cursor positioning, line wrapping, etc
702 All console printing must go through this in order to be displayed
703 If no console is visible, the notify window will pop up.
706 void Con_PrintToHistory(const char *txt, int mask)
709 // \n goes to next line
710 // \r deletes current line and makes a new one
712 static int cr_pending = 0;
713 static char buf[CON_TEXTSIZE];
714 static int bufpos = 0;
720 Con_DeleteLastLine();
728 Con_AddLine(buf, bufpos, mask);
733 Con_AddLine(buf, bufpos, mask);
737 buf[bufpos++] = *txt;
738 if(bufpos >= CON_TEXTSIZE - 1)
740 Con_AddLine(buf, bufpos, mask);
748 /* The translation table between the graphical font and plain ASCII --KB */
749 static char qfont_table[256] = {
750 '\0', '#', '#', '#', '#', '.', '#', '#',
751 '#', 9, 10, '#', ' ', 13, '.', '.',
752 '[', ']', '0', '1', '2', '3', '4', '5',
753 '6', '7', '8', '9', '.', '<', '=', '>',
754 ' ', '!', '"', '#', '$', '%', '&', '\'',
755 '(', ')', '*', '+', ',', '-', '.', '/',
756 '0', '1', '2', '3', '4', '5', '6', '7',
757 '8', '9', ':', ';', '<', '=', '>', '?',
758 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
759 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
760 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
761 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
762 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
763 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
764 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
765 'x', 'y', 'z', '{', '|', '}', '~', '<',
767 '<', '=', '>', '#', '#', '.', '#', '#',
768 '#', '#', ' ', '#', ' ', '>', '.', '.',
769 '[', ']', '0', '1', '2', '3', '4', '5',
770 '6', '7', '8', '9', '.', '<', '=', '>',
771 ' ', '!', '"', '#', '$', '%', '&', '\'',
772 '(', ')', '*', '+', ',', '-', '.', '/',
773 '0', '1', '2', '3', '4', '5', '6', '7',
774 '8', '9', ':', ';', '<', '=', '>', '?',
775 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
776 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
777 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
778 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
779 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
780 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
781 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
782 'x', 'y', 'z', '{', '|', '}', '~', '<'
789 Adds a character to the rcon buffer
792 void Con_Rcon_AddChar(char c)
794 if(log_dest_buffer_appending)
796 ++log_dest_buffer_appending;
798 // if this print is in response to an rcon command, add the character
799 // to the rcon redirect buffer
801 if (rcon_redirect && rcon_redirect_bufferpos < (int)sizeof(rcon_redirect_buffer) - 1)
802 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
803 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
805 if(log_dest_buffer_pos == 0)
806 Log_DestBuffer_Init();
807 log_dest_buffer[log_dest_buffer_pos++] = c;
808 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
809 Log_DestBuffer_Flush();
812 log_dest_buffer_pos = 0;
814 --log_dest_buffer_appending;
821 Prints to all appropriate console targets, and adds timestamps
824 extern cvar_t timestamps;
825 extern cvar_t timeformat;
826 extern qboolean sys_nostdout;
827 void Con_Print(const char *msg)
830 static int index = 0;
831 static char line[MAX_INPUTLINE];
835 Con_Rcon_AddChar(*msg);
836 // if this is the beginning of a new line, print timestamp
839 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
841 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
842 line[index++] = STRING_COLOR_TAG;
843 // assert( STRING_COLOR_DEFAULT < 10 )
844 line[index++] = STRING_COLOR_DEFAULT + '0';
845 // special color codes for chat messages must always come first
846 // for Con_PrintToHistory to work properly
847 if (*msg == 1 || *msg == 2)
852 if(gamemode == GAME_NEXUIZ)
854 if(msg[1] == '\r' && cl.foundtalk2wav)
855 S_LocalSound ("sound/misc/talk2.wav");
857 S_LocalSound ("sound/misc/talk.wav");
861 if (msg[1] == '(' && cl.foundtalk2wav)
862 S_LocalSound ("sound/misc/talk2.wav");
864 S_LocalSound ("sound/misc/talk.wav");
866 mask = CON_MASK_CHAT;
868 line[index++] = STRING_COLOR_TAG;
871 Con_Rcon_AddChar(*msg);
874 for (;*timestamp;index++, timestamp++)
875 if (index < (int)sizeof(line) - 2)
876 line[index] = *timestamp;
878 // append the character
879 line[index++] = *msg;
880 // if this is a newline character, we have a complete line to print
881 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
883 // terminate the line
887 // send to scrollable buffer
888 if (con_initialized && cls.state != ca_dedicated)
890 Con_PrintToHistory(line, mask);
893 // send to terminal or dedicated server window
897 if(sys_specialcharactertranslation.integer)
899 for (p = (unsigned char *) line;*p; p++)
900 *p = qfont_table[*p];
903 if(sys_colortranslation.integer == 1) // ANSI
905 static char printline[MAX_INPUTLINE * 4 + 3];
906 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
907 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
911 for(in = line, out = printline; *in; ++in)
915 case STRING_COLOR_TAG:
918 case STRING_COLOR_TAG:
920 *out++ = STRING_COLOR_TAG;
926 if(lastcolor == 0) break; else lastcolor = 0;
927 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
932 if(lastcolor == 1) break; else lastcolor = 1;
933 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
938 if(lastcolor == 2) break; else lastcolor = 2;
939 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
944 if(lastcolor == 3) break; else lastcolor = 3;
945 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
950 if(lastcolor == 4) break; else lastcolor = 4;
951 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
956 if(lastcolor == 5) break; else lastcolor = 5;
957 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
962 if(lastcolor == 6) break; else lastcolor = 6;
963 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
970 if(lastcolor == 8) break; else lastcolor = 8;
971 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
974 *out++ = STRING_COLOR_TAG;
981 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
998 Sys_PrintToTerminal(printline);
1000 else if(sys_colortranslation.integer == 2) // Quake
1002 Sys_PrintToTerminal(line);
1006 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1009 for(in = line, out = printline; *in; ++in)
1013 case STRING_COLOR_TAG:
1016 case STRING_COLOR_TAG:
1018 *out++ = STRING_COLOR_TAG;
1033 *out++ = STRING_COLOR_TAG;
1043 Sys_PrintToTerminal(printline);
1046 // empty the line buffer
1057 Prints to all appropriate console targets
1060 void Con_Printf(const char *fmt, ...)
1063 char msg[MAX_INPUTLINE];
1065 va_start(argptr,fmt);
1066 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1076 A Con_Print that only shows up if the "developer" cvar is set
1079 void Con_DPrint(const char *msg)
1081 if (!developer.integer)
1082 return; // don't confuse non-developers with techie stuff...
1090 A Con_Printf that only shows up if the "developer" cvar is set
1093 void Con_DPrintf(const char *fmt, ...)
1096 char msg[MAX_INPUTLINE];
1098 if (!developer.integer)
1099 return; // don't confuse non-developers with techie stuff...
1101 va_start(argptr,fmt);
1102 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1110 ==============================================================================
1114 ==============================================================================
1121 The input line scrolls horizontally if typing goes beyond the right edge
1123 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1126 void Con_DrawInput (void)
1130 char editlinecopy[MAX_INPUTLINE+1], *text;
1133 if (!key_consoleactive)
1134 return; // don't draw anything
1136 strlcpy(editlinecopy, key_lines[edit_line], sizeof(editlinecopy));
1137 text = editlinecopy;
1139 // Advanced Console Editing by Radix radix@planetquake.com
1140 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1141 // use strlen of edit_line instead of key_linepos to allow editing
1142 // of early characters w/o erasing
1144 y = (int)strlen(text);
1146 // fill out remainder with spaces
1147 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1150 // add the cursor frame
1151 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1152 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1154 // text[key_linepos + 1] = 0;
1156 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1161 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 );
1164 // key_lines[edit_line][key_linepos] = 0;
1170 float alignment; // 0 = left, 0.5 = center, 1 = right
1176 const char *continuationString;
1179 int colorindex; // init to -1
1183 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1185 con_text_info_t *ti = (con_text_info_t *) passthrough;
1188 ti->colorindex = -1;
1189 return ti->fontsize * ti->font->width_of[0];
1192 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1193 else if(maxWidth == -1)
1194 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1197 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1198 // Note: this is NOT a Con_Printf, as it could print recursively
1203 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1209 (void) isContinuation;
1213 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1215 con_text_info_t *ti = (con_text_info_t *) passthrough;
1217 if(ti->y < ti->ymin - 0.001)
1219 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1223 int x = ti->x + (ti->width - width) * ti->alignment;
1224 if(isContinuation && *ti->continuationString)
1225 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);
1227 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);
1230 ti->y += ti->fontsize;
1235 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)
1239 int maxlines = (int) floor(height / fontsize + 0.01f);
1242 int continuationWidth = 0;
1244 double t = cl.time; // saved so it won't change
1247 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1248 ti.fontsize = fontsize;
1249 ti.alignment = alignment_x;
1252 ti.ymax = y + height;
1253 ti.continuationString = continuationString;
1256 Con_WordWidthFunc(&ti, NULL, &l, -1);
1257 l = strlen(continuationString);
1258 continuationWidth = Con_WordWidthFunc(&ti, continuationString, &l, -1);
1260 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1261 startidx = con_lines_count;
1262 for(i = con_lines_count - 1; i >= 0; --i)
1264 con_lineinfo *l = &CON_LINES(i);
1267 if((l->mask & mask_must) != mask_must)
1269 if(l->mask & mask_mustnot)
1271 if(maxage && (l->addtime < t - maxage))
1275 // Calculate its actual height...
1276 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1277 if(lines + mylines >= maxlines)
1279 nskip = lines + mylines - maxlines;
1288 // then center according to the calculated amount of lines...
1290 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1292 // then actually draw
1293 for(i = startidx; i < con_lines_count; ++i)
1295 con_lineinfo *l = &CON_LINES(i);
1297 if((l->mask & mask_must) != mask_must)
1299 if(l->mask & mask_mustnot)
1301 if(maxage && (l->addtime < t - maxage))
1304 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1314 Draws the last few lines of output transparently over the game top
1317 void Con_DrawNotify (void)
1320 float chatstart, notifystart, inputsize;
1322 char temptext[MAX_INPUTLINE];
1328 numChatlines = con_chat.integer;
1329 chatpos = con_chatpos.integer;
1331 if (con_notify.integer < 0)
1332 Cvar_SetValueQuick(&con_notify, 0);
1333 if (gamemode == GAME_TRANSFUSION)
1334 v = 8; // vertical offset
1338 // GAME_NEXUIZ: center, otherwise left justify
1339 align = con_notifyalign.value;
1340 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1342 if(gamemode == GAME_NEXUIZ)
1350 // first chat, input line, then notify
1352 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1354 else if(chatpos > 0)
1356 // first notify, then (chatpos-1) empty lines, then chat, then input
1358 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1360 else // if(chatpos < 0)
1362 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1364 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1369 // just notify and input
1371 chatstart = 0; // shut off gcc warning
1374 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, "");
1379 v = chatstart + numChatlines * con_chatsize.value;
1380 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
1383 if (key_dest == key_message)
1385 int colorindex = -1;
1387 // LordHavoc: speedup, and other improvements
1389 sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1391 sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1394 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1395 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1398 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1404 Con_MeasureConsoleLine
1406 Counts the number of lines for a line on the console.
1409 int Con_MeasureConsoleLine(int lineno)
1411 float width = vid_conwidth.value;
1413 ti.fontsize = con_textsize.value;
1414 ti.font = FONT_CONSOLE;
1416 return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1423 Returns the height of a given console line; calculates it if necessary.
1426 int Con_LineHeight(int i)
1428 int h = con_lines[i].height;
1431 return con_lines[i].height = Con_MeasureConsoleLine(i);
1438 Draws a line of the console; returns its height in lines.
1439 If alpha is 0, the line is not drawn, but still wrapped and its height
1443 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1445 float width = vid_conwidth.value;
1448 ti.continuationString = "";
1450 ti.fontsize = con_textsize.value;
1451 ti.font = FONT_CONSOLE;
1453 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1458 return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1465 Calculates the last visible line index and how much to show of it based on
1469 void Con_LastVisibleLine(int *last, int *limitlast)
1474 if(con_backscroll < 0)
1477 // now count until we saw con_backscroll actual lines
1478 for(ic = 0; ic < con_lines_count; ++ic)
1480 int i = CON_LINES_IDX(con_lines_count - 1 - ic);
1481 int h = Con_LineHeight(i);
1483 // line is the last visible line?
1484 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1487 *limitlast = lines_seen + h - con_backscroll;
1494 // if we get here, no line was on screen - scroll so that one line is
1496 con_backscroll = lines_seen - 1;
1497 *last = con_lines_first;
1505 Draws the console with the solid background
1506 The typing input line at the bottom should only be drawn if typing is allowed
1509 void Con_DrawConsole (int lines)
1511 int i, last, limitlast;
1517 con_vislines = lines;
1519 // draw the background
1520 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
1521 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);
1524 if(con_lines_count > 0)
1526 float ymax = con_vislines - 2 * con_textsize.value;
1527 Con_LastVisibleLine(&last, &limitlast);
1528 y = ymax - con_textsize.value;
1531 y += (con_lines[last].height - limitlast) * con_textsize.value;
1536 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1537 if(i == con_lines_first)
1538 break; // top of console buffer
1540 break; // top of console window
1542 i = CON_LINES_PRED(i);
1546 // draw the input prompt, user text, and cursor if desired
1554 Prints not only map filename, but also
1555 its format (q1/q2/q3/hl) and even its message
1557 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1558 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1559 //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
1560 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1561 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1565 int i, k, max, p, o, min;
1568 unsigned char buf[1024];
1570 sprintf(message, "maps/%s*.bsp", s);
1571 t = FS_Search(message, 1, true);
1574 if (t->numfilenames > 1)
1575 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1576 len = (unsigned char *)Z_Malloc(t->numfilenames);
1578 for(max=i=0;i<t->numfilenames;i++)
1580 k = (int)strlen(t->filenames[i]);
1590 for(i=0;i<t->numfilenames;i++)
1592 int lumpofs = 0, lumplen = 0;
1593 char *entities = NULL;
1594 const char *data = NULL;
1596 char entfilename[MAX_QPATH];
1597 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1599 f = FS_Open(t->filenames[i], "rb", true, false);
1602 memset(buf, 0, 1024);
1603 FS_Read(f, buf, 1024);
1604 if (!memcmp(buf, "IBSP", 4))
1606 p = LittleLong(((int *)buf)[1]);
1607 if (p == Q3BSPVERSION)
1609 q3dheader_t *header = (q3dheader_t *)buf;
1610 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1611 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1613 else if (p == Q2BSPVERSION)
1615 q2dheader_t *header = (q2dheader_t *)buf;
1616 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1617 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1620 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1622 dheader_t *header = (dheader_t *)buf;
1623 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1624 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1628 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1629 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1630 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1631 if (!entities && lumplen >= 10)
1633 FS_Seek(f, lumpofs, SEEK_SET);
1634 entities = (char *)Z_Malloc(lumplen + 1);
1635 FS_Read(f, entities, lumplen);
1639 // if there are entities to parse, a missing message key just
1640 // means there is no title, so clear the message string now
1646 if (!COM_ParseToken_Simple(&data, false, false))
1648 if (com_token[0] == '{')
1650 if (com_token[0] == '}')
1652 // skip leading whitespace
1653 for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
1654 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
1655 keyname[l] = com_token[k+l];
1657 if (!COM_ParseToken_Simple(&data, false, false))
1659 if (developer.integer >= 100)
1660 Con_Printf("key: %s %s\n", keyname, com_token);
1661 if (!strcmp(keyname, "message"))
1663 // get the message contents
1664 strlcpy(message, com_token, sizeof(message));
1674 *(t->filenames[i]+len[i]+5) = 0;
1677 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1678 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1679 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1680 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1681 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1683 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1688 k = *(t->filenames[0]+5+p);
1691 for(i=1;i<t->numfilenames;i++)
1692 if(*(t->filenames[i]+5+p) != k)
1696 if(p > o && completedname && completednamebufferlength > 0)
1698 memset(completedname, 0, completednamebufferlength);
1699 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1709 New function for tab-completion system
1710 Added by EvilTypeGuy
1711 MEGA Thanks to Taniwha
1714 void Con_DisplayList(const char **list)
1716 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1717 const char **walk = list;
1720 len = (int)strlen(*walk);
1728 len = (int)strlen(*list);
1729 if (pos + maxlen >= width) {
1735 for (i = 0; i < (maxlen - len); i++)
1746 /* Nicks_CompleteCountPossible
1748 Count the number of possible nicks to complete
1750 //qboolean COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets);
1751 void SanitizeString(char *in, char *out)
1755 if(*in == STRING_COLOR_TAG)
1760 out[0] = STRING_COLOR_TAG;
1763 } else if(*in >= '0' && *in <= '9')
1770 } else if (*in == STRING_COLOR_TAG)
1772 } else if (*in != STRING_COLOR_TAG) {
1776 *out = qfont_table[*(unsigned char*)in];
1783 // Now it becomes TRICKY :D --blub
1784 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
1785 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
1786 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
1787 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
1788 static int Nicks_matchpos;
1790 // co against <<:BLASTER:>> is true!?
1791 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
1795 if(tolower(*a) == tolower(*b))
1809 return (*a < *b) ? -1 : 1;
1813 return (*a < *b) ? -1 : 1;
1817 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
1820 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
1822 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
1823 return Nicks_strncasecmp_nospaces(a, b, a_len);
1824 return strncasecmp(a, b, a_len);
1827 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
1829 // ignore non alphanumerics of B
1830 // if A contains a non-alphanumeric, B must contain it as well though!
1833 qboolean alnum_a, alnum_b;
1835 if(tolower(*a) == tolower(*b))
1837 if(*a == 0) // end of both strings, they're equal
1844 // not equal, end of one string?
1849 // ignore non alphanumerics
1850 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
1851 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
1852 if(!alnum_a) // b must contain this
1853 return (*a < *b) ? -1 : 1;
1856 // otherwise, both are alnum, they're just not equal, return the appropriate number
1858 return (*a < *b) ? -1 : 1;
1863 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
1872 if(!con_nickcompletion.integer)
1875 // changed that to 1
1876 if(!line[0])// || !line[1]) // we want at least... 2 written characters
1879 for(i = 0; i < cl.maxclients; ++i)
1882 if(!cl.scores[p].name[0])
1885 SanitizeString(cl.scores[p].name, name);
1886 //Con_Printf("Sanitized: %s^7 -> %s", cl.scores[p].name, name);
1891 length = strlen(name);
1893 spos = pos - 1; // no need for a minimum of characters :)
1895 while(spos >= 0 && (spos - pos) < length) // search-string-length < name length
1897 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
1899 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
1900 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
1906 if(isCon && spos == 0)
1908 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
1914 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
1915 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
1917 // the sanitized list
1918 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
1921 Nicks_matchpos = match;
1924 Nicks_offset[count] = s - (&line[match]);
1925 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
1932 void Cmd_CompleteNicksPrint(int count)
1935 for(i = 0; i < count; ++i)
1936 Con_Printf("%s\n", Nicks_list[i]);
1939 void Nicks_CutMatchesNormal(int count)
1941 // cut match 0 down to the longest possible completion
1944 c = strlen(Nicks_sanlist[0]) - 1;
1945 for(i = 1; i < count; ++i)
1947 l = strlen(Nicks_sanlist[i]) - 1;
1951 for(l = 0; l <= c; ++l)
1952 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
1958 Nicks_sanlist[0][c+1] = 0;
1959 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
1962 unsigned int Nicks_strcleanlen(const char *s)
1967 if( (*s >= 'a' && *s <= 'z') ||
1968 (*s >= 'A' && *s <= 'Z') ||
1969 (*s >= '0' && *s <= '9') ||
1977 void Nicks_CutMatchesAlphaNumeric(int count)
1979 // cut match 0 down to the longest possible completion
1982 char tempstr[sizeof(Nicks_sanlist[0])];
1984 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
1986 c = strlen(Nicks_sanlist[0]);
1987 for(i = 0, l = 0; i < (int)c; ++i)
1989 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
1990 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
1991 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
1993 tempstr[l++] = Nicks_sanlist[0][i];
1998 for(i = 1; i < count; ++i)
2001 b = Nicks_sanlist[i];
2011 if(tolower(*a) == tolower(*b))
2017 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2019 // b is alnum, so cut
2026 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2027 Nicks_CutMatchesNormal(count);
2028 //if(!Nicks_sanlist[0][0])
2029 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2031 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2032 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2036 void Nicks_CutMatchesNoSpaces(int count)
2038 // cut match 0 down to the longest possible completion
2041 char tempstr[sizeof(Nicks_sanlist[0])];
2044 c = strlen(Nicks_sanlist[0]);
2045 for(i = 0, l = 0; i < (int)c; ++i)
2047 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2049 tempstr[l++] = Nicks_sanlist[0][i];
2054 for(i = 1; i < count; ++i)
2057 b = Nicks_sanlist[i];
2067 if(tolower(*a) == tolower(*b))
2081 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2082 Nicks_CutMatchesNormal(count);
2083 //if(!Nicks_sanlist[0][0])
2084 //Con_Printf("TS: %s\n", tempstr);
2085 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2087 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2088 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2092 void Nicks_CutMatches(int count)
2094 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2095 Nicks_CutMatchesAlphaNumeric(count);
2096 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2097 Nicks_CutMatchesNoSpaces(count);
2099 Nicks_CutMatchesNormal(count);
2102 const char **Nicks_CompleteBuildList(int count)
2106 // the list is freed by Con_CompleteCommandLine, so create a char**
2107 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2109 for(; bpos < count; ++bpos)
2110 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2112 Nicks_CutMatches(count);
2118 int Nicks_AddLastColor(char *buffer, int pos)
2120 qboolean quote_added = false;
2124 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2126 // we'll have to add a quote :)
2127 buffer[pos++] = '\"';
2131 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2133 // add color when no quote was added, or when flags &4?
2135 for(match = Nicks_matchpos-1; match >= 0; --match)
2137 if(buffer[match] == STRING_COLOR_TAG && buffer[match+1] >= '0' && buffer[match+1] <= '9')
2139 color = buffer[match+1];
2143 if(!quote_added && buffer[pos-2] == STRING_COLOR_TAG && buffer[pos-1] >= '0' && buffer[pos-1] <= '9') // when thes use &4
2145 buffer[pos++] = STRING_COLOR_TAG;
2146 buffer[pos++] = color;
2151 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2154 /*if(!con_nickcompletion.integer)
2155 return; is tested in Nicks_CompletionCountPossible */
2156 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2162 msg = Nicks_list[0];
2163 len = min(size - Nicks_matchpos - 3, strlen(msg));
2164 memcpy(&buffer[Nicks_matchpos], msg, len);
2165 if( len < (size - 4) ) // space for color and space and \0
2166 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2167 buffer[len++] = ' ';
2174 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2175 Cmd_CompleteNicksPrint(n);
2177 Nicks_CutMatches(n);
2179 msg = Nicks_sanlist[0];
2180 len = min(size - Nicks_matchpos, strlen(msg));
2181 memcpy(&buffer[Nicks_matchpos], msg, len);
2182 buffer[Nicks_matchpos + len] = 0;
2184 return Nicks_matchpos + len;
2191 Con_CompleteCommandLine
2193 New function for tab-completion system
2194 Added by EvilTypeGuy
2195 Thanks to Fett erich@heintz.com
2197 Enhanced to tab-complete map names by [515]
2200 void Con_CompleteCommandLine (void)
2202 const char *cmd = "";
2204 const char **list[4] = {0, 0, 0, 0};
2207 int c, v, a, i, cmd_len, pos, k;
2208 int n; // nicks --blub
2209 const char *space, *patterns;
2211 //find what we want to complete
2215 k = key_lines[edit_line][pos];
2216 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2221 s = key_lines[edit_line] + pos;
2222 strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2)); //save chars after cursor
2223 key_lines[edit_line][key_linepos] = 0; //hide them
2225 space = strchr(key_lines[edit_line] + 1, ' ');
2226 if(space && pos == (space - key_lines[edit_line]) + 1)
2228 strlcpy(command, key_lines[edit_line] + 1, min(sizeof(command), (unsigned int)(space - key_lines[edit_line])));
2230 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2231 if(patterns && !*patterns)
2232 patterns = NULL; // get rid of the empty string
2234 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2238 if (GetMapList(s, t, sizeof(t)))
2240 // first move the cursor
2241 key_linepos += (int)strlen(t) - (int)strlen(s);
2243 // and now do the actual work
2245 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2246 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2248 // and fix the cursor
2249 if(key_linepos > (int) strlen(key_lines[edit_line]))
2250 key_linepos = (int) strlen(key_lines[edit_line]);
2259 stringlist_t resultbuf, dirbuf;
2262 // // store completion patterns (space separated) for command foo in con_completion_foo
2263 // set con_completion_foo "foodata/*.foodefault *.foo"
2266 // Note: patterns with slash are always treated as absolute
2267 // patterns; patterns without slash search in the innermost
2268 // directory the user specified. There is no way to "complete into"
2269 // a directory as of now, as directories seem to be unknown to the
2273 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2274 // set con_completion_playdemo "*.dem"
2275 // set con_completion_play "*.wav *.ogg"
2277 // TODO somehow add support for directories; these shall complete
2278 // to their name + an appended slash.
2280 stringlistinit(&resultbuf);
2281 stringlistinit(&dirbuf);
2282 while(COM_ParseToken_Simple(&patterns, false, false))
2285 if(strchr(com_token, '/'))
2287 search = FS_Search(com_token, true, true);
2291 const char *slash = strrchr(s, '/');
2294 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2295 strlcat(t, com_token, sizeof(t));
2296 search = FS_Search(t, true, true);
2299 search = FS_Search(com_token, true, true);
2303 for(i = 0; i < search->numfilenames; ++i)
2304 if(!strncmp(search->filenames[i], s, strlen(s)))
2305 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2306 stringlistappend(&resultbuf, search->filenames[i]);
2307 FS_FreeSearch(search);
2311 // In any case, add directory names
2314 const char *slash = strrchr(s, '/');
2317 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2318 strlcat(t, "*", sizeof(t));
2319 search = FS_Search(t, true, true);
2322 search = FS_Search("*", true, true);
2325 for(i = 0; i < search->numfilenames; ++i)
2326 if(!strncmp(search->filenames[i], s, strlen(s)))
2327 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2328 stringlistappend(&dirbuf, search->filenames[i]);
2329 FS_FreeSearch(search);
2333 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2336 unsigned int matchchars;
2337 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2339 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2342 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2344 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2348 stringlistsort(&resultbuf); // dirbuf is already sorted
2349 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2350 for(i = 0; i < dirbuf.numstrings; ++i)
2352 Con_Printf("%s/\n", dirbuf.strings[i]);
2354 for(i = 0; i < resultbuf.numstrings; ++i)
2356 Con_Printf("%s\n", resultbuf.strings[i]);
2358 matchchars = sizeof(t) - 1;
2359 if(resultbuf.numstrings > 0)
2361 p = resultbuf.strings[0];
2362 q = resultbuf.strings[resultbuf.numstrings - 1];
2363 for(; *p && *p == *q; ++p, ++q);
2364 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2366 if(dirbuf.numstrings > 0)
2368 p = dirbuf.strings[0];
2369 q = dirbuf.strings[dirbuf.numstrings - 1];
2370 for(; *p && *p == *q; ++p, ++q);
2371 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2373 // now p points to the first non-equal character, or to the end
2374 // of resultbuf.strings[0]. We want to append the characters
2375 // from resultbuf.strings[0] to (not including) p as these are
2376 // the unique prefix
2377 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2380 // first move the cursor
2381 key_linepos += (int)strlen(t) - (int)strlen(s);
2383 // and now do the actual work
2385 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2386 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2388 // and fix the cursor
2389 if(key_linepos > (int) strlen(key_lines[edit_line]))
2390 key_linepos = (int) strlen(key_lines[edit_line]);
2392 stringlistfreecontents(&resultbuf);
2393 stringlistfreecontents(&dirbuf);
2395 return; // bail out, when we complete for a command that wants a file name
2400 // Count number of possible matches and print them
2401 c = Cmd_CompleteCountPossible(s);
2404 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2405 Cmd_CompleteCommandPrint(s);
2407 v = Cvar_CompleteCountPossible(s);
2410 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2411 Cvar_CompleteCvarPrint(s);
2413 a = Cmd_CompleteAliasCountPossible(s);
2416 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2417 Cmd_CompleteAliasPrint(s);
2419 n = Nicks_CompleteCountPossible(key_lines[edit_line], key_linepos, s, true);
2422 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2423 Cmd_CompleteNicksPrint(n);
2426 if (!(c + v + a + n)) // No possible matches
2429 strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
2434 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2436 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2438 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2440 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2442 for (cmd_len = (int)strlen(s);;cmd_len++)
2445 for (i = 0; i < 3; i++)
2447 for (l = list[i];*l;l++)
2448 if ((*l)[cmd_len] != cmd[cmd_len])
2450 // all possible matches share this character, so we continue...
2453 // if all matches ended at the same position, stop
2454 // (this means there is only one match)
2460 // prevent a buffer overrun by limiting cmd_len according to remaining space
2461 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
2465 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
2466 key_linepos += cmd_len;
2467 // if there is only one match, add a space after it
2468 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
2471 { // was a nick, might have an offset, and needs colors ;) --blub
2472 key_linepos = pos - Nicks_offset[0];
2473 cmd_len = strlen(Nicks_list[0]);
2474 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 3 - pos);
2476 memcpy(&key_lines[edit_line][key_linepos] , Nicks_list[0], cmd_len);
2477 key_linepos += cmd_len;
2478 if(key_linepos < (int)(sizeof(key_lines[edit_line])-4)) // space for ^, X and space and \0
2479 key_linepos = Nicks_AddLastColor(key_lines[edit_line], key_linepos);
2481 key_lines[edit_line][key_linepos++] = ' ';
2485 // use strlcat to avoid a buffer overrun
2486 key_lines[edit_line][key_linepos] = 0;
2487 strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
2489 // free the command, cvar, and alias lists
2490 for (i = 0; i < 4; i++)
2492 Mem_Free((void *)list[i]);