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
102 qboolean con_initialized;
104 // used for server replies to rcon command
105 qboolean rcon_redirect = false;
106 int rcon_redirect_bufferpos = 0;
107 char rcon_redirect_buffer[1400];
111 ==============================================================================
115 ==============================================================================
118 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
119 cvar_t log_dest_udp = {0, "log_dest_udp","", "UDP address to log messages to (in QW rcon compatible format); multiple destinations can be separated by spaces; DO NOT SPECIFY DNS NAMES HERE"};
120 char log_dest_buffer[1400]; // UDP packet
121 size_t log_dest_buffer_pos;
122 qboolean log_dest_buffer_appending;
123 char crt_log_file [MAX_OSPATH] = "";
124 qfile_t* logfile = NULL;
126 unsigned char* logqueue = NULL;
128 size_t logq_size = 0;
130 void Log_ConPrint (const char *msg);
137 static void Log_DestBuffer_Init()
139 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
140 log_dest_buffer_pos = 5;
148 void Log_DestBuffer_Flush()
150 lhnetaddress_t log_dest_addr;
151 lhnetsocket_t *log_dest_socket;
152 const char *s = log_dest_udp.string;
153 qboolean have_opened_temp_sockets = false;
154 if(s) if(log_dest_buffer_pos > 5)
156 ++log_dest_buffer_appending;
157 log_dest_buffer[log_dest_buffer_pos++] = 0;
159 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
161 have_opened_temp_sockets = true;
162 NetConn_OpenServerPorts(true);
165 while(COM_ParseToken_Console(&s))
166 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
168 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
170 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
172 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
175 if(have_opened_temp_sockets)
176 NetConn_CloseServerPorts();
177 --log_dest_buffer_appending;
179 log_dest_buffer_pos = 0;
187 const char* Log_Timestamp (const char *desc)
189 static char timestamp [128];
191 const struct tm *crt_tm;
192 char timestring [64];
194 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
196 crt_tm = localtime (&crt_time);
197 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
200 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
202 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
215 if (logfile != NULL || log_file.string[0] == '\0')
218 logfile = FS_Open (log_file.string, "ab", false, false);
221 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
222 FS_Print (logfile, Log_Timestamp ("Log started"));
232 void Log_Close (void)
237 FS_Print (logfile, Log_Timestamp ("Log stopped"));
238 FS_Print (logfile, "\n");
242 crt_log_file[0] = '\0';
251 void Log_Start (void)
257 // Dump the contents of the log queue into the log file and free it
258 if (logqueue != NULL)
260 unsigned char *temp = logqueue;
265 FS_Write (logfile, temp, logq_ind);
266 if(*log_dest_udp.string)
268 for(pos = 0; pos < logq_ind; )
270 if(log_dest_buffer_pos == 0)
271 Log_DestBuffer_Init();
272 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
273 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
274 log_dest_buffer_pos += n;
275 Log_DestBuffer_Flush();
292 void Log_ConPrint (const char *msg)
294 static qboolean inprogress = false;
296 // don't allow feedback loops with memory error reports
301 // Until the host is completely initialized, we maintain a log queue
302 // to store the messages, since the log can't be started before
303 if (logqueue != NULL)
305 size_t remain = logq_size - logq_ind;
306 size_t len = strlen (msg);
308 // If we need to enlarge the log queue
311 size_t factor = ((logq_ind + len) / logq_size) + 1;
312 unsigned char* newqueue;
315 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
316 memcpy (newqueue, logqueue, logq_ind);
319 remain = logq_size - logq_ind;
321 memcpy (&logqueue[logq_ind], msg, len);
328 // Check if log_file has changed
329 if (strcmp (crt_log_file, log_file.string) != 0)
335 // If a log file is available
337 FS_Print (logfile, msg);
348 void Log_Printf (const char *logfilename, const char *fmt, ...)
352 file = FS_Open (logfilename, "ab", true, false);
357 va_start (argptr, fmt);
358 FS_VPrintf (file, fmt, argptr);
367 ==============================================================================
371 ==============================================================================
379 void Con_ToggleConsole_f (void)
381 // toggle the 'user wants console' bit
382 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
391 void Con_Clear_f (void)
401 Clear all notify lines.
404 void Con_ClearNotify (void)
407 for(i = 0; i < con_lines_count; ++i)
408 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
417 void Con_MessageMode_f (void)
419 key_dest = key_message;
429 void Con_MessageMode2_f (void)
431 key_dest = key_message;
440 If the line width has changed, reformat the buffer.
443 void Con_CheckResize (void)
448 f = bound(1, con_textsize.value, 128);
449 if(f != con_textsize.value)
450 Cvar_SetValueQuick(&con_textsize, f);
451 width = (int)floor(vid_conwidth.value / con_textsize.value);
452 width = bound(1, width, CON_TEXTSIZE/4);
454 if (width == con_linewidth)
457 con_linewidth = width;
459 for(i = 0; i < con_lines_count; ++i)
460 CON_LINES(i).height = -1; // recalculate when next needed
466 //[515]: the simplest command ever
467 //LordHavoc: not so simple after I made it print usage...
468 static void Con_Maps_f (void)
472 Con_Printf("usage: maps [mapnameprefix]\n");
475 else if (Cmd_Argc() == 2)
476 GetMapList(Cmd_Argv(1), NULL, 0);
478 GetMapList("", NULL, 0);
481 void Con_ConDump_f (void)
487 Con_Printf("usage: condump <filename>\n");
490 file = FS_Open(Cmd_Argv(1), "wb", false, false);
493 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
496 for(i = 0; i < con_lines_count; ++i)
498 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
499 FS_Write(file, "\n", 1);
515 // Allocate a log queue, this will be freed after configs are parsed
516 logq_size = MAX_INPUTLINE;
517 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
520 Cvar_RegisterVariable (&sys_colortranslation);
521 Cvar_RegisterVariable (&sys_specialcharactertranslation);
523 Cvar_RegisterVariable (&log_file);
524 Cvar_RegisterVariable (&log_dest_udp);
526 // support for the classic Quake option
527 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
528 if (COM_CheckParm ("-condebug") != 0)
529 Cvar_SetQuick (&log_file, "qconsole.log");
531 // register our cvars
532 Cvar_RegisterVariable (&con_chat);
533 Cvar_RegisterVariable (&con_chatpos);
534 Cvar_RegisterVariable (&con_chatsize);
535 Cvar_RegisterVariable (&con_chattime);
536 Cvar_RegisterVariable (&con_chatwidth);
537 Cvar_RegisterVariable (&con_notify);
538 Cvar_RegisterVariable (&con_notifyalign);
539 Cvar_RegisterVariable (&con_notifysize);
540 Cvar_RegisterVariable (&con_notifytime);
541 Cvar_RegisterVariable (&con_textsize);
544 Cvar_RegisterVariable (&con_nickcompletion);
545 Cvar_RegisterVariable (&con_nickcompletion_flags);
547 // register our commands
548 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
549 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
550 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
551 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
552 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
553 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
555 con_initialized = true;
556 Con_DPrint("Console initialized.\n");
564 Deletes the first line from the console history.
567 void Con_DeleteLine()
569 if(con_lines_count == 0)
572 con_lines_first = CON_LINES_IDX(1);
579 Deletes the last line from the console history.
582 void Con_DeleteLastLine()
584 if(con_lines_count == 0)
593 Checks if there is space for a line of the given length, and if yes, returns a
594 pointer to the start of such a space, and NULL otherwise.
597 char *Con_BytesLeft(int len)
599 if(len > CON_TEXTSIZE)
601 if(con_lines_count == 0)
605 char *firstline_start = con_lines[con_lines_first].start;
606 char *lastline_onepastend = con_lines[CON_LINES_LAST].start + con_lines[CON_LINES_LAST].len;
607 // the buffer is cyclic, so we first have two cases...
608 if(firstline_start < lastline_onepastend) // buffer is contiguous
611 if(len <= con_text + CON_TEXTSIZE - lastline_onepastend)
612 return lastline_onepastend;
614 else if(len <= firstline_start - con_text)
619 else // buffer has a contiguous hole
621 if(len <= firstline_start - lastline_onepastend)
622 return lastline_onepastend;
633 Notifies the console code about the current time
634 (and shifts back times of other entries when the time
641 if(con_lines_count >= 1)
643 double diff = cl.time - (con_lines + CON_LINES_LAST)->addtime;
646 for(i = 0; i < con_lines_count; ++i)
647 CON_LINES(i).addtime += diff;
656 Appends a given string as a new line to the console.
659 void Con_AddLine(const char *line, int len, int mask)
666 if(len >= CON_TEXTSIZE)
669 // only display end of line.
670 line += len - CON_TEXTSIZE + 1;
671 len = CON_TEXTSIZE - 1;
673 while(!(putpos = Con_BytesLeft(len + 1)) || con_lines_count >= CON_MAXLINES)
675 memcpy(putpos, line, len);
679 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", con_lines_count, con_lines_first, CON_LINES_LAST);
681 p = con_lines + CON_LINES_LAST;
684 p->addtime = cl.time;
686 p->height = -1; // calculate when needed
693 Handles cursor positioning, line wrapping, etc
694 All console printing must go through this in order to be displayed
695 If no console is visible, the notify window will pop up.
698 void Con_PrintToHistory(const char *txt, int mask)
701 // \n goes to next line
702 // \r deletes current line and makes a new one
704 static int cr_pending = 0;
705 static char buf[CON_TEXTSIZE];
706 static int bufpos = 0;
712 Con_DeleteLastLine();
720 Con_AddLine(buf, bufpos, mask);
725 Con_AddLine(buf, bufpos, mask);
729 buf[bufpos++] = *txt;
730 if(bufpos >= CON_TEXTSIZE - 1)
732 Con_AddLine(buf, bufpos, mask);
740 /* The translation table between the graphical font and plain ASCII --KB */
741 static char qfont_table[256] = {
742 '\0', '#', '#', '#', '#', '.', '#', '#',
743 '#', 9, 10, '#', ' ', 13, '.', '.',
744 '[', ']', '0', '1', '2', '3', '4', '5',
745 '6', '7', '8', '9', '.', '<', '=', '>',
746 ' ', '!', '"', '#', '$', '%', '&', '\'',
747 '(', ')', '*', '+', ',', '-', '.', '/',
748 '0', '1', '2', '3', '4', '5', '6', '7',
749 '8', '9', ':', ';', '<', '=', '>', '?',
750 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
751 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
752 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
753 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
754 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
755 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
756 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
757 'x', 'y', 'z', '{', '|', '}', '~', '<',
759 '<', '=', '>', '#', '#', '.', '#', '#',
760 '#', '#', ' ', '#', ' ', '>', '.', '.',
761 '[', ']', '0', '1', '2', '3', '4', '5',
762 '6', '7', '8', '9', '.', '<', '=', '>',
763 ' ', '!', '"', '#', '$', '%', '&', '\'',
764 '(', ')', '*', '+', ',', '-', '.', '/',
765 '0', '1', '2', '3', '4', '5', '6', '7',
766 '8', '9', ':', ';', '<', '=', '>', '?',
767 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
768 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
769 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
770 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
771 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
772 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
773 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
774 'x', 'y', 'z', '{', '|', '}', '~', '<'
781 Adds a character to the rcon buffer
784 void Con_Rcon_AddChar(char c)
786 if(log_dest_buffer_appending)
788 ++log_dest_buffer_appending;
790 // if this print is in response to an rcon command, add the character
791 // to the rcon redirect buffer
793 if (rcon_redirect && rcon_redirect_bufferpos < (int)sizeof(rcon_redirect_buffer) - 1)
794 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
795 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
797 if(log_dest_buffer_pos == 0)
798 Log_DestBuffer_Init();
799 log_dest_buffer[log_dest_buffer_pos++] = c;
800 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
801 Log_DestBuffer_Flush();
804 log_dest_buffer_pos = 0;
806 --log_dest_buffer_appending;
813 Prints to all appropriate console targets, and adds timestamps
816 extern cvar_t timestamps;
817 extern cvar_t timeformat;
818 extern qboolean sys_nostdout;
819 void Con_Print(const char *msg)
822 static int index = 0;
823 static char line[MAX_INPUTLINE];
827 Con_Rcon_AddChar(*msg);
828 // if this is the beginning of a new line, print timestamp
831 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
833 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
834 line[index++] = STRING_COLOR_TAG;
835 // assert( STRING_COLOR_DEFAULT < 10 )
836 line[index++] = STRING_COLOR_DEFAULT + '0';
837 // special color codes for chat messages must always come first
838 // for Con_PrintToHistory to work properly
839 if (*msg == 1 || *msg == 2)
844 if(gamemode == GAME_NEXUIZ)
846 if(msg[1] == '\r' && cl.foundtalk2wav)
847 S_LocalSound ("sound/misc/talk2.wav");
849 S_LocalSound ("sound/misc/talk.wav");
853 if (msg[1] == '(' && cl.foundtalk2wav)
854 S_LocalSound ("sound/misc/talk2.wav");
856 S_LocalSound ("sound/misc/talk.wav");
858 mask = CON_MASK_CHAT;
860 line[index++] = STRING_COLOR_TAG;
863 Con_Rcon_AddChar(*msg);
866 for (;*timestamp;index++, timestamp++)
867 if (index < (int)sizeof(line) - 2)
868 line[index] = *timestamp;
870 // append the character
871 line[index++] = *msg;
872 // if this is a newline character, we have a complete line to print
873 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
875 // terminate the line
879 // send to scrollable buffer
880 if (con_initialized && cls.state != ca_dedicated)
882 Con_PrintToHistory(line, mask);
885 // send to terminal or dedicated server window
889 if(sys_specialcharactertranslation.integer)
891 for (p = (unsigned char *) line;*p; p++)
892 *p = qfont_table[*p];
895 if(sys_colortranslation.integer == 1) // ANSI
897 static char printline[MAX_INPUTLINE * 4 + 3];
898 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
899 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
903 for(in = line, out = printline; *in; ++in)
907 case STRING_COLOR_TAG:
910 case STRING_COLOR_TAG:
912 *out++ = STRING_COLOR_TAG;
918 if(lastcolor == 0) break; else lastcolor = 0;
919 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
924 if(lastcolor == 1) break; else lastcolor = 1;
925 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
930 if(lastcolor == 2) break; else lastcolor = 2;
931 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
936 if(lastcolor == 3) break; else lastcolor = 3;
937 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
942 if(lastcolor == 4) break; else lastcolor = 4;
943 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
948 if(lastcolor == 5) break; else lastcolor = 5;
949 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
954 if(lastcolor == 6) break; else lastcolor = 6;
955 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
962 if(lastcolor == 8) break; else lastcolor = 8;
963 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
966 *out++ = STRING_COLOR_TAG;
973 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
990 Sys_PrintToTerminal(printline);
992 else if(sys_colortranslation.integer == 2) // Quake
994 Sys_PrintToTerminal(line);
998 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1001 for(in = line, out = printline; *in; ++in)
1005 case STRING_COLOR_TAG:
1008 case STRING_COLOR_TAG:
1010 *out++ = STRING_COLOR_TAG;
1025 *out++ = STRING_COLOR_TAG;
1035 Sys_PrintToTerminal(printline);
1038 // empty the line buffer
1049 Prints to all appropriate console targets
1052 void Con_Printf(const char *fmt, ...)
1055 char msg[MAX_INPUTLINE];
1057 va_start(argptr,fmt);
1058 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1068 A Con_Print that only shows up if the "developer" cvar is set
1071 void Con_DPrint(const char *msg)
1073 if (!developer.integer)
1074 return; // don't confuse non-developers with techie stuff...
1082 A Con_Printf that only shows up if the "developer" cvar is set
1085 void Con_DPrintf(const char *fmt, ...)
1088 char msg[MAX_INPUTLINE];
1090 if (!developer.integer)
1091 return; // don't confuse non-developers with techie stuff...
1093 va_start(argptr,fmt);
1094 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1102 ==============================================================================
1106 ==============================================================================
1113 The input line scrolls horizontally if typing goes beyond the right edge
1115 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1118 void Con_DrawInput (void)
1122 char editlinecopy[MAX_INPUTLINE+1], *text;
1125 if (!key_consoleactive)
1126 return; // don't draw anything
1128 strlcpy(editlinecopy, key_lines[edit_line], sizeof(editlinecopy));
1129 text = editlinecopy;
1131 // Advanced Console Editing by Radix radix@planetquake.com
1132 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1133 // use strlen of edit_line instead of key_linepos to allow editing
1134 // of early characters w/o erasing
1136 y = (int)strlen(text);
1138 // fill out remainder with spaces
1139 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1142 // add the cursor frame
1143 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1144 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1146 // text[key_linepos + 1] = 0;
1148 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1153 DrawQ_String_Font(x, con_vislines - con_textsize.value*2, text, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, FONT_CONSOLE );
1156 // key_lines[edit_line][key_linepos] = 0;
1162 float alignment; // 0 = left, 0.5 = center, 1 = right
1168 const char *continuationString;
1171 int colorindex; // init to -1
1175 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1177 con_text_info_t *ti = (con_text_info_t *) passthrough;
1180 ti->colorindex = -1;
1181 return ti->fontsize * ti->font->width_of[0];
1184 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1185 else if(maxWidth == -1)
1186 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1189 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1190 // Note: this is NOT a Con_Printf, as it could print recursively
1195 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1201 (void) isContinuation;
1205 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1207 con_text_info_t *ti = (con_text_info_t *) passthrough;
1209 if(ti->y < ti->ymin - 0.001)
1211 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1215 int x = ti->x + (ti->width - width) * ti->alignment;
1216 if(isContinuation && *ti->continuationString)
1217 x += DrawQ_String_Font(x, ti->y, ti->continuationString, strlen(ti->continuationString), ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, ti->font);
1219 DrawQ_String_Font(x, ti->y, line, length, ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, &(ti->colorindex), false, ti->font);
1222 ti->y += ti->fontsize;
1227 int Con_DrawNotifyRect(int mask_must, int mask_mustnot, float maxage, float x, float y, float width, float height, float fontsize, float alignment_x, float alignment_y, const char *continuationString)
1231 int maxlines = (int) floor(height / fontsize + 0.01f);
1234 int continuationWidth = 0;
1236 double t = cl.time; // saved so it won't change
1239 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1240 ti.fontsize = fontsize;
1241 ti.alignment = alignment_x;
1244 ti.ymax = y + height;
1245 ti.continuationString = continuationString;
1248 Con_WordWidthFunc(&ti, NULL, &l, -1);
1249 l = strlen(continuationString);
1250 continuationWidth = Con_WordWidthFunc(&ti, continuationString, &l, -1);
1252 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1253 startidx = con_lines_count;
1254 for(i = con_lines_count - 1; i >= 0; --i)
1256 con_lineinfo *l = &CON_LINES(i);
1259 if((l->mask & mask_must) != mask_must)
1261 if(l->mask & mask_mustnot)
1263 if(maxage && (l->addtime < t - maxage))
1267 // Calculate its actual height...
1268 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1269 if(lines + mylines >= maxlines)
1271 nskip = lines + mylines - maxlines;
1280 // then center according to the calculated amount of lines...
1282 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1284 // then actually draw
1285 for(i = startidx; i < con_lines_count; ++i)
1287 con_lineinfo *l = &CON_LINES(i);
1289 if((l->mask & mask_must) != mask_must)
1291 if(l->mask & mask_mustnot)
1293 if(maxage && (l->addtime < t - maxage))
1296 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1306 Draws the last few lines of output transparently over the game top
1309 void Con_DrawNotify (void)
1312 float chatstart, notifystart, inputsize;
1314 char temptext[MAX_INPUTLINE];
1320 numChatlines = con_chat.integer;
1321 chatpos = con_chatpos.integer;
1323 if (con_notify.integer < 0)
1324 Cvar_SetValueQuick(&con_notify, 0);
1325 if (gamemode == GAME_TRANSFUSION)
1326 v = 8; // vertical offset
1330 // GAME_NEXUIZ: center, otherwise left justify
1331 align = con_notifyalign.value;
1332 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1334 if(gamemode == GAME_NEXUIZ)
1342 // first chat, input line, then notify
1344 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1346 else if(chatpos > 0)
1348 // first notify, then (chatpos-1) empty lines, then chat, then input
1350 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1352 else // if(chatpos < 0)
1354 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1356 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1361 // just notify and input
1363 chatstart = 0; // shut off gcc warning
1366 v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_HIDENOTIFY | (numChatlines ? CON_MASK_CHAT : 0), con_notifytime.value, 0, notifystart, vid_conwidth.value, con_notify.value * con_notifysize.value, con_notifysize.value, align, 0.0, "");
1371 v = chatstart + numChatlines * con_chatsize.value;
1372 Con_DrawNotifyRect(CON_MASK_CHAT, 0, con_chattime.value, 0, chatstart, vid_conwidth.value * con_chatwidth.value, v - chatstart, con_chatsize.value, 0.0, 1.0, "^3\014\014\014 "); // 015 is ·> character in conchars.tga
1375 if (key_dest == key_message)
1377 int colorindex = -1;
1379 // LordHavoc: speedup, and other improvements
1381 sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1383 sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1386 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1387 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1390 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1396 Con_MeasureConsoleLine
1398 Counts the number of lines for a line on the console.
1401 int Con_MeasureConsoleLine(int lineno)
1403 float width = vid_conwidth.value;
1405 ti.fontsize = con_textsize.value;
1406 ti.font = FONT_CONSOLE;
1408 return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1415 Returns the height of a given console line; calculates it if necessary.
1418 int Con_LineHeight(int i)
1420 int h = con_lines[i].height;
1423 return con_lines[i].height = Con_MeasureConsoleLine(i);
1430 Draws a line of the console; returns its height in lines.
1431 If alpha is 0, the line is not drawn, but still wrapped and its height
1435 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1437 float width = vid_conwidth.value;
1440 ti.continuationString = "";
1442 ti.fontsize = con_textsize.value;
1443 ti.font = FONT_CONSOLE;
1445 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1450 return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1457 Calculates the last visible line index and how much to show of it based on
1461 void Con_LastVisibleLine(int *last, int *limitlast)
1466 if(con_backscroll < 0)
1469 // now count until we saw con_backscroll actual lines
1470 for(ic = 0; ic < con_lines_count; ++ic)
1472 int i = CON_LINES_IDX(con_lines_count - 1 - ic);
1473 int h = Con_LineHeight(i);
1475 // line is the last visible line?
1476 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1479 *limitlast = lines_seen + h - con_backscroll;
1486 // if we get here, no line was on screen - scroll so that one line is
1488 con_backscroll = lines_seen - 1;
1489 *last = con_lines_first;
1497 Draws the console with the solid background
1498 The typing input line at the bottom should only be drawn if typing is allowed
1501 void Con_DrawConsole (int lines)
1503 int i, last, limitlast;
1509 con_vislines = lines;
1511 // draw the background
1512 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
1513 DrawQ_String_Font(vid_conwidth.integer - DrawQ_TextWidth_Font(engineversion, 0, false, FONT_CONSOLE) * con_textsize.value, lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0, NULL, true, FONT_CONSOLE);
1516 if(con_lines_count > 0)
1518 float ymax = con_vislines - 2 * con_textsize.value;
1519 Con_LastVisibleLine(&last, &limitlast);
1520 y = ymax - con_textsize.value;
1523 y += (con_lines[last].height - limitlast) * con_textsize.value;
1528 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1529 if(i == con_lines_first)
1530 break; // top of console buffer
1532 break; // top of console window
1534 i = CON_LINES_PRED(i);
1538 // draw the input prompt, user text, and cursor if desired
1546 Prints not only map filename, but also
1547 its format (q1/q2/q3/hl) and even its message
1549 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1550 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1551 //LordHavoc: added .ent file loading, and redesigned error handling to still try the .ent file even if the map format is not recognized, this also eliminated one goto
1552 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1553 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1557 int i, k, max, p, o, min;
1560 unsigned char buf[1024];
1562 sprintf(message, "maps/%s*.bsp", s);
1563 t = FS_Search(message, 1, true);
1566 if (t->numfilenames > 1)
1567 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1568 len = (unsigned char *)Z_Malloc(t->numfilenames);
1570 for(max=i=0;i<t->numfilenames;i++)
1572 k = (int)strlen(t->filenames[i]);
1582 for(i=0;i<t->numfilenames;i++)
1584 int lumpofs = 0, lumplen = 0;
1585 char *entities = NULL;
1586 const char *data = NULL;
1588 char entfilename[MAX_QPATH];
1589 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1591 f = FS_Open(t->filenames[i], "rb", true, false);
1594 memset(buf, 0, 1024);
1595 FS_Read(f, buf, 1024);
1596 if (!memcmp(buf, "IBSP", 4))
1598 p = LittleLong(((int *)buf)[1]);
1599 if (p == Q3BSPVERSION)
1601 q3dheader_t *header = (q3dheader_t *)buf;
1602 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1603 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1605 else if (p == Q2BSPVERSION)
1607 q2dheader_t *header = (q2dheader_t *)buf;
1608 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1609 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1612 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1614 dheader_t *header = (dheader_t *)buf;
1615 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1616 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1620 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1621 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1622 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1623 if (!entities && lumplen >= 10)
1625 FS_Seek(f, lumpofs, SEEK_SET);
1626 entities = (char *)Z_Malloc(lumplen + 1);
1627 FS_Read(f, entities, lumplen);
1631 // if there are entities to parse, a missing message key just
1632 // means there is no title, so clear the message string now
1638 if (!COM_ParseToken_Simple(&data, false, false))
1640 if (com_token[0] == '{')
1642 if (com_token[0] == '}')
1644 // skip leading whitespace
1645 for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
1646 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
1647 keyname[l] = com_token[k+l];
1649 if (!COM_ParseToken_Simple(&data, false, false))
1651 if (developer.integer >= 100)
1652 Con_Printf("key: %s %s\n", keyname, com_token);
1653 if (!strcmp(keyname, "message"))
1655 // get the message contents
1656 strlcpy(message, com_token, sizeof(message));
1666 *(t->filenames[i]+len[i]+5) = 0;
1669 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1670 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1671 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1672 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1673 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1675 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1680 k = *(t->filenames[0]+5+p);
1683 for(i=1;i<t->numfilenames;i++)
1684 if(*(t->filenames[i]+5+p) != k)
1688 if(p > o && completedname && completednamebufferlength > 0)
1690 memset(completedname, 0, completednamebufferlength);
1691 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1701 New function for tab-completion system
1702 Added by EvilTypeGuy
1703 MEGA Thanks to Taniwha
1706 void Con_DisplayList(const char **list)
1708 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1709 const char **walk = list;
1712 len = (int)strlen(*walk);
1720 len = (int)strlen(*list);
1721 if (pos + maxlen >= width) {
1727 for (i = 0; i < (maxlen - len); i++)
1738 /* Nicks_CompleteCountPossible
1740 Count the number of possible nicks to complete
1742 //qboolean COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets);
1743 void SanitizeString(char *in, char *out)
1747 if(*in == STRING_COLOR_TAG)
1752 out[0] = STRING_COLOR_TAG;
1755 } else if(*in >= '0' && *in <= '9')
1762 } else if (*in == STRING_COLOR_TAG)
1764 } else if (*in != STRING_COLOR_TAG) {
1768 *out = qfont_table[*(unsigned char*)in];
1774 int Sbar_GetPlayer (int index); // <- safety?
1776 // Now it becomes TRICKY :D --blub
1777 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
1778 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
1779 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
1780 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
1781 static int Nicks_matchpos;
1783 // co against <<:BLASTER:>> is true!?
1784 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
1788 if(tolower(*a) == tolower(*b))
1802 return (*a < *b) ? -1 : 1;
1806 return (*a < *b) ? -1 : 1;
1810 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
1813 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
1815 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
1816 return Nicks_strncasecmp_nospaces(a, b, a_len);
1817 return strncasecmp(a, b, a_len);
1820 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
1822 // ignore non alphanumerics of B
1823 // if A contains a non-alphanumeric, B must contain it as well though!
1826 qboolean alnum_a, alnum_b;
1828 if(tolower(*a) == tolower(*b))
1830 if(*a == 0) // end of both strings, they're equal
1837 // not equal, end of one string?
1842 // ignore non alphanumerics
1843 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
1844 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
1845 if(!alnum_a) // b must contain this
1846 return (*a < *b) ? -1 : 1;
1849 // otherwise, both are alnum, they're just not equal, return the appropriate number
1851 return (*a < *b) ? -1 : 1;
1856 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
1865 if(!con_nickcompletion.integer)
1868 // changed that to 1
1869 if(!line[0])// || !line[1]) // we want at least... 2 written characters
1872 for(i = 0; i < cl.maxclients; ++i)
1874 /*p = Sbar_GetPlayer(i);
1878 if(!cl.scores[p].name[0])
1881 SanitizeString(cl.scores[p].name, name);
1882 //Con_Printf("Sanitized: %s^7 -> %s", cl.scores[p].name, name);
1887 length = strlen(name);
1889 spos = pos - 1; // no need for a minimum of characters :)
1891 while(spos >= 0 && (spos - pos) < length) // search-string-length < name length
1893 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
1895 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
1896 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
1902 if(isCon && spos == 0)
1904 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
1910 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
1911 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
1913 // the sanitized list
1914 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
1917 Nicks_matchpos = match;
1920 Nicks_offset[count] = s - (&line[match]);
1921 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
1928 void Cmd_CompleteNicksPrint(int count)
1931 for(i = 0; i < count; ++i)
1932 Con_Printf("%s\n", Nicks_list[i]);
1935 void Nicks_CutMatchesNormal(int count)
1937 // cut match 0 down to the longest possible completion
1940 c = strlen(Nicks_sanlist[0]) - 1;
1941 for(i = 1; i < count; ++i)
1943 l = strlen(Nicks_sanlist[i]) - 1;
1947 for(l = 0; l <= c; ++l)
1948 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
1954 Nicks_sanlist[0][c+1] = 0;
1955 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
1958 unsigned int Nicks_strcleanlen(const char *s)
1963 if( (*s >= 'a' && *s <= 'z') ||
1964 (*s >= 'A' && *s <= 'Z') ||
1965 (*s >= '0' && *s <= '9') ||
1973 void Nicks_CutMatchesAlphaNumeric(int count)
1975 // cut match 0 down to the longest possible completion
1978 char tempstr[sizeof(Nicks_sanlist[0])];
1980 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
1982 c = strlen(Nicks_sanlist[0]);
1983 for(i = 0, l = 0; i < (int)c; ++i)
1985 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
1986 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
1987 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
1989 tempstr[l++] = Nicks_sanlist[0][i];
1994 for(i = 1; i < count; ++i)
1997 b = Nicks_sanlist[i];
2007 if(tolower(*a) == tolower(*b))
2013 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2015 // b is alnum, so cut
2022 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2023 Nicks_CutMatchesNormal(count);
2024 //if(!Nicks_sanlist[0][0])
2025 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2027 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2028 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2032 void Nicks_CutMatchesNoSpaces(int count)
2034 // cut match 0 down to the longest possible completion
2037 char tempstr[sizeof(Nicks_sanlist[0])];
2040 c = strlen(Nicks_sanlist[0]);
2041 for(i = 0, l = 0; i < (int)c; ++i)
2043 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2045 tempstr[l++] = Nicks_sanlist[0][i];
2050 for(i = 1; i < count; ++i)
2053 b = Nicks_sanlist[i];
2063 if(tolower(*a) == tolower(*b))
2077 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2078 Nicks_CutMatchesNormal(count);
2079 //if(!Nicks_sanlist[0][0])
2080 //Con_Printf("TS: %s\n", tempstr);
2081 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2083 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2084 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2088 void Nicks_CutMatches(int count)
2090 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2091 Nicks_CutMatchesAlphaNumeric(count);
2092 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2093 Nicks_CutMatchesNoSpaces(count);
2095 Nicks_CutMatchesNormal(count);
2098 const char **Nicks_CompleteBuildList(int count)
2102 // the list is freed by Con_CompleteCommandLine, so create a char**
2103 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2105 for(; bpos < count; ++bpos)
2106 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2108 Nicks_CutMatches(count);
2114 int Nicks_AddLastColor(char *buffer, int pos)
2116 qboolean quote_added = false;
2120 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2122 // we'll have to add a quote :)
2123 buffer[pos++] = '\"';
2127 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2129 // add color when no quote was added, or when flags &4?
2131 for(match = Nicks_matchpos-1; match >= 0; --match)
2133 if(buffer[match] == STRING_COLOR_TAG && buffer[match+1] >= '0' && buffer[match+1] <= '9')
2135 color = buffer[match+1];
2139 if(!quote_added && buffer[pos-2] == STRING_COLOR_TAG && buffer[pos-1] >= '0' && buffer[pos-1] <= '9') // when thes use &4
2141 buffer[pos++] = STRING_COLOR_TAG;
2142 buffer[pos++] = color;
2147 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2150 /*if(!con_nickcompletion.integer)
2151 return; is tested in Nicks_CompletionCountPossible */
2152 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2158 msg = Nicks_list[0];
2159 len = min(size - Nicks_matchpos - 3, strlen(msg));
2160 memcpy(&buffer[Nicks_matchpos], msg, len);
2161 if( len < (size - 4) ) // space for color and space and \0
2162 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2163 buffer[len++] = ' ';
2170 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2171 Cmd_CompleteNicksPrint(n);
2173 Nicks_CutMatches(n);
2175 msg = Nicks_sanlist[0];
2176 len = min(size - Nicks_matchpos, strlen(msg));
2177 memcpy(&buffer[Nicks_matchpos], msg, len);
2178 buffer[Nicks_matchpos + len] = 0;
2180 return Nicks_matchpos + len;
2187 Con_CompleteCommandLine
2189 New function for tab-completion system
2190 Added by EvilTypeGuy
2191 Thanks to Fett erich@heintz.com
2193 Enhanced to tab-complete map names by [515]
2196 void Con_CompleteCommandLine (void)
2198 const char *cmd = "";
2200 const char **list[4] = {0, 0, 0, 0};
2203 int c, v, a, i, cmd_len, pos, k;
2204 int n; // nicks --blub
2205 const char *space, *patterns;
2207 //find what we want to complete
2211 k = key_lines[edit_line][pos];
2212 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2217 s = key_lines[edit_line] + pos;
2218 strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2)); //save chars after cursor
2219 key_lines[edit_line][key_linepos] = 0; //hide them
2221 space = strchr(key_lines[edit_line] + 1, ' ');
2222 if(space && pos == (space - key_lines[edit_line]) + 1)
2224 strlcpy(command, key_lines[edit_line] + 1, min(sizeof(command), (unsigned int)(space - key_lines[edit_line])));
2226 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2227 if(patterns && !*patterns)
2228 patterns = NULL; // get rid of the empty string
2230 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2234 if (GetMapList(s, t, sizeof(t)))
2236 // first move the cursor
2237 key_linepos += (int)strlen(t) - (int)strlen(s);
2239 // and now do the actual work
2241 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2242 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2244 // and fix the cursor
2245 if(key_linepos > (int) strlen(key_lines[edit_line]))
2246 key_linepos = (int) strlen(key_lines[edit_line]);
2255 stringlist_t resultbuf, dirbuf;
2258 // // store completion patterns (space separated) for command foo in con_completion_foo
2259 // set con_completion_foo "foodata/*.foodefault *.foo"
2262 // Note: patterns with slash are always treated as absolute
2263 // patterns; patterns without slash search in the innermost
2264 // directory the user specified. There is no way to "complete into"
2265 // a directory as of now, as directories seem to be unknown to the
2269 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2270 // set con_completion_playdemo "*.dem"
2271 // set con_completion_play "*.wav *.ogg"
2273 // TODO somehow add support for directories; these shall complete
2274 // to their name + an appended slash.
2276 stringlistinit(&resultbuf);
2277 stringlistinit(&dirbuf);
2278 while(COM_ParseToken_Simple(&patterns, false, false))
2281 if(strchr(com_token, '/'))
2283 search = FS_Search(com_token, true, true);
2287 const char *slash = strrchr(s, '/');
2290 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2291 strlcat(t, com_token, sizeof(t));
2292 search = FS_Search(t, true, true);
2295 search = FS_Search(com_token, true, true);
2299 for(i = 0; i < search->numfilenames; ++i)
2300 if(!strncmp(search->filenames[i], s, strlen(s)))
2301 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2302 stringlistappend(&resultbuf, search->filenames[i]);
2303 FS_FreeSearch(search);
2307 // In any case, add directory names
2310 const char *slash = strrchr(s, '/');
2313 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2314 strlcat(t, "*", sizeof(t));
2315 search = FS_Search(t, true, true);
2318 search = FS_Search("*", true, true);
2321 for(i = 0; i < search->numfilenames; ++i)
2322 if(!strncmp(search->filenames[i], s, strlen(s)))
2323 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2324 stringlistappend(&dirbuf, search->filenames[i]);
2325 FS_FreeSearch(search);
2329 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2332 unsigned int matchchars;
2333 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2335 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2338 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2340 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2344 stringlistsort(&resultbuf); // dirbuf is already sorted
2345 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2346 for(i = 0; i < dirbuf.numstrings; ++i)
2348 Con_Printf("%s/\n", dirbuf.strings[i]);
2350 for(i = 0; i < resultbuf.numstrings; ++i)
2352 Con_Printf("%s\n", resultbuf.strings[i]);
2354 matchchars = sizeof(t) - 1;
2355 if(resultbuf.numstrings > 0)
2357 p = resultbuf.strings[0];
2358 q = resultbuf.strings[resultbuf.numstrings - 1];
2359 for(; *p && *p == *q; ++p, ++q);
2360 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2362 if(dirbuf.numstrings > 0)
2364 p = dirbuf.strings[0];
2365 q = dirbuf.strings[dirbuf.numstrings - 1];
2366 for(; *p && *p == *q; ++p, ++q);
2367 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2369 // now p points to the first non-equal character, or to the end
2370 // of resultbuf.strings[0]. We want to append the characters
2371 // from resultbuf.strings[0] to (not including) p as these are
2372 // the unique prefix
2373 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2376 // first move the cursor
2377 key_linepos += (int)strlen(t) - (int)strlen(s);
2379 // and now do the actual work
2381 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2382 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2384 // and fix the cursor
2385 if(key_linepos > (int) strlen(key_lines[edit_line]))
2386 key_linepos = (int) strlen(key_lines[edit_line]);
2388 stringlistfreecontents(&resultbuf);
2389 stringlistfreecontents(&dirbuf);
2391 return; // bail out, when we complete for a command that wants a file name
2396 // Count number of possible matches and print them
2397 c = Cmd_CompleteCountPossible(s);
2400 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2401 Cmd_CompleteCommandPrint(s);
2403 v = Cvar_CompleteCountPossible(s);
2406 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2407 Cvar_CompleteCvarPrint(s);
2409 a = Cmd_CompleteAliasCountPossible(s);
2412 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2413 Cmd_CompleteAliasPrint(s);
2415 n = Nicks_CompleteCountPossible(key_lines[edit_line], key_linepos, s, true);
2418 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2419 Cmd_CompleteNicksPrint(n);
2422 if (!(c + v + a + n)) // No possible matches
2425 strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
2430 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2432 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2434 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2436 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2438 for (cmd_len = (int)strlen(s);;cmd_len++)
2441 for (i = 0; i < 3; i++)
2443 for (l = list[i];*l;l++)
2444 if ((*l)[cmd_len] != cmd[cmd_len])
2446 // all possible matches share this character, so we continue...
2449 // if all matches ended at the same position, stop
2450 // (this means there is only one match)
2456 // prevent a buffer overrun by limiting cmd_len according to remaining space
2457 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
2461 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
2462 key_linepos += cmd_len;
2463 // if there is only one match, add a space after it
2464 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
2467 { // was a nick, might have an offset, and needs colors ;) --blub
2468 key_linepos = pos - Nicks_offset[0];
2469 cmd_len = strlen(Nicks_list[0]);
2470 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 3 - pos);
2472 memcpy(&key_lines[edit_line][key_linepos] , Nicks_list[0], cmd_len);
2473 key_linepos += cmd_len;
2474 if(key_linepos < (int)(sizeof(key_lines[edit_line])-4)) // space for ^, X and space and \0
2475 key_linepos = Nicks_AddLastColor(key_lines[edit_line], key_linepos);
2477 key_lines[edit_line][key_linepos++] = ' ';
2481 // use strlcat to avoid a buffer overrun
2482 key_lines[edit_line][key_linepos] = 0;
2483 strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
2485 // free the command, cvar, and alias lists
2486 for (i = 0; i < 4; i++)
2488 Mem_Free((void *)list[i]);