2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 #if !defined(WIN32) || defined(__MINGW32__)
29 float con_cursorspeed = 4;
31 #define CON_TEXTSIZE 131072
32 #define CON_MAXLINES 4096
34 // lines up from bottom to display
38 char con_text[CON_TEXTSIZE];
40 #define CON_MASK_HIDENOTIFY 128
41 #define CON_MASK_CHAT 1
51 int height; // recalculated line height when needed (-1 to unset)
54 con_lineinfo con_lines[CON_MAXLINES];
56 int con_lines_first; // cyclic buffer
58 #define CON_LINES_IDX(i) ((con_lines_first + (i)) % CON_MAXLINES)
59 #define CON_LINES_LAST CON_LINES_IDX(con_lines_count - 1)
60 #define CON_LINES(i) con_lines[CON_LINES_IDX(i)]
61 #define CON_LINES_PRED(i) (((i) + CON_MAXLINES - 1) % CON_MAXLINES)
62 #define CON_LINES_SUCC(i) (((i) + 1) % CON_MAXLINES)
64 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
65 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
66 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
68 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
69 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
70 cvar_t con_chatpos = {CVAR_SAVE, "con_chatpos","0", "where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top)"};
71 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
72 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
73 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
74 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
77 cvar_t sys_specialcharactertranslation = {0, "sys_specialcharactertranslation", "1", "terminal console conchars to ASCII translation (set to 0 if your conchars.tga is for an 8bit character set or if you want raw output)"};
79 cvar_t sys_colortranslation = {0, "sys_colortranslation", "0", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
81 cvar_t sys_colortranslation = {0, "sys_colortranslation", "1", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
85 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
86 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
87 "0: add nothing after completion. "
88 "1: add the last color after completion. "
89 "2: add a quote when starting a quote instead of the color. "
90 "4: will replace 1, will force color, even after a quote. "
91 "8: ignore non-alphanumerics. "
92 "16: ignore spaces. "};
93 #define NICKS_ADD_COLOR 1
94 #define NICKS_ADD_QUOTE 2
95 #define NICKS_FORCE_COLOR 4
96 #define NICKS_ALPHANUMERICS_ONLY 8
97 #define NICKS_NO_SPACES 16
99 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem"};
100 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem"};
101 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg"};
106 qboolean con_initialized;
108 // used for server replies to rcon command
109 lhnetsocket_t *rcon_redirect_sock = NULL;
110 lhnetaddress_t *rcon_redirect_dest = NULL;
111 int rcon_redirect_bufferpos = 0;
112 char rcon_redirect_buffer[1400];
116 ==============================================================================
120 ==============================================================================
123 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
124 cvar_t log_dest_udp = {0, "log_dest_udp","", "UDP address to log messages to (in QW rcon compatible format); multiple destinations can be separated by spaces; DO NOT SPECIFY DNS NAMES HERE"};
125 char log_dest_buffer[1400]; // UDP packet
126 size_t log_dest_buffer_pos;
127 qboolean log_dest_buffer_appending;
128 char crt_log_file [MAX_OSPATH] = "";
129 qfile_t* logfile = NULL;
131 unsigned char* logqueue = NULL;
133 size_t logq_size = 0;
135 void Log_ConPrint (const char *msg);
142 static void Log_DestBuffer_Init()
144 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
145 log_dest_buffer_pos = 5;
153 void Log_DestBuffer_Flush()
155 lhnetaddress_t log_dest_addr;
156 lhnetsocket_t *log_dest_socket;
157 const char *s = log_dest_udp.string;
158 qboolean have_opened_temp_sockets = false;
159 if(s) if(log_dest_buffer_pos > 5)
161 ++log_dest_buffer_appending;
162 log_dest_buffer[log_dest_buffer_pos++] = 0;
164 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
166 have_opened_temp_sockets = true;
167 NetConn_OpenServerPorts(true);
170 while(COM_ParseToken_Console(&s))
171 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
173 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
175 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
177 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
180 if(have_opened_temp_sockets)
181 NetConn_CloseServerPorts();
182 --log_dest_buffer_appending;
184 log_dest_buffer_pos = 0;
192 const char* Log_Timestamp (const char *desc)
194 static char timestamp [128];
201 char timestring [64];
203 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
206 localtime_s (&crt_tm, &crt_time);
207 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
209 crt_tm = localtime (&crt_time);
210 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
214 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
216 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
229 if (logfile != NULL || log_file.string[0] == '\0')
232 logfile = FS_OpenRealFile(log_file.string, "a", false);
235 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
236 FS_Print (logfile, Log_Timestamp ("Log started"));
246 void Log_Close (void)
251 FS_Print (logfile, Log_Timestamp ("Log stopped"));
252 FS_Print (logfile, "\n");
256 crt_log_file[0] = '\0';
265 void Log_Start (void)
271 // Dump the contents of the log queue into the log file and free it
272 if (logqueue != NULL)
274 unsigned char *temp = logqueue;
279 FS_Write (logfile, temp, logq_ind);
280 if(*log_dest_udp.string)
282 for(pos = 0; pos < logq_ind; )
284 if(log_dest_buffer_pos == 0)
285 Log_DestBuffer_Init();
286 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
287 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
288 log_dest_buffer_pos += n;
289 Log_DestBuffer_Flush();
306 void Log_ConPrint (const char *msg)
308 static qboolean inprogress = false;
310 // don't allow feedback loops with memory error reports
315 // Until the host is completely initialized, we maintain a log queue
316 // to store the messages, since the log can't be started before
317 if (logqueue != NULL)
319 size_t remain = logq_size - logq_ind;
320 size_t len = strlen (msg);
322 // If we need to enlarge the log queue
325 size_t factor = ((logq_ind + len) / logq_size) + 1;
326 unsigned char* newqueue;
329 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
330 memcpy (newqueue, logqueue, logq_ind);
333 remain = logq_size - logq_ind;
335 memcpy (&logqueue[logq_ind], msg, len);
342 // Check if log_file has changed
343 if (strcmp (crt_log_file, log_file.string) != 0)
349 // If a log file is available
351 FS_Print (logfile, msg);
362 void Log_Printf (const char *logfilename, const char *fmt, ...)
366 file = FS_OpenRealFile(logfilename, "a", true);
371 va_start (argptr, fmt);
372 FS_VPrintf (file, fmt, argptr);
381 ==============================================================================
385 ==============================================================================
393 void Con_ToggleConsole_f (void)
395 // toggle the 'user wants console' bit
396 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
405 void Con_Clear_f (void)
415 Clear all notify lines.
418 void Con_ClearNotify (void)
421 for(i = 0; i < con_lines_count; ++i)
422 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
431 void Con_MessageMode_f (void)
433 key_dest = key_message;
434 chat_mode = 0; // "say"
443 void Con_MessageMode2_f (void)
445 key_dest = key_message;
446 chat_mode = 1; // "say_team"
454 void Con_CommandMode_f (void)
456 key_dest = key_message;
459 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
460 chat_bufferlen = strlen(chat_buffer);
462 chat_mode = -1; // command
469 If the line width has changed, reformat the buffer.
472 void Con_CheckResize (void)
477 f = bound(1, con_textsize.value, 128);
478 if(f != con_textsize.value)
479 Cvar_SetValueQuick(&con_textsize, f);
480 width = (int)floor(vid_conwidth.value / con_textsize.value);
481 width = bound(1, width, CON_TEXTSIZE/4);
483 if (width == con_linewidth)
486 con_linewidth = width;
488 for(i = 0; i < con_lines_count; ++i)
489 CON_LINES(i).height = -1; // recalculate when next needed
495 //[515]: the simplest command ever
496 //LordHavoc: not so simple after I made it print usage...
497 static void Con_Maps_f (void)
501 Con_Printf("usage: maps [mapnameprefix]\n");
504 else if (Cmd_Argc() == 2)
505 GetMapList(Cmd_Argv(1), NULL, 0);
507 GetMapList("", NULL, 0);
510 void Con_ConDump_f (void)
516 Con_Printf("usage: condump <filename>\n");
519 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
522 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
525 for(i = 0; i < con_lines_count; ++i)
527 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
528 FS_Write(file, "\n", 1);
544 // Allocate a log queue, this will be freed after configs are parsed
545 logq_size = MAX_INPUTLINE;
546 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
549 Cvar_RegisterVariable (&sys_colortranslation);
550 Cvar_RegisterVariable (&sys_specialcharactertranslation);
552 Cvar_RegisterVariable (&log_file);
553 Cvar_RegisterVariable (&log_dest_udp);
555 // support for the classic Quake option
556 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
557 if (COM_CheckParm ("-condebug") != 0)
558 Cvar_SetQuick (&log_file, "qconsole.log");
560 // register our cvars
561 Cvar_RegisterVariable (&con_chat);
562 Cvar_RegisterVariable (&con_chatpos);
563 Cvar_RegisterVariable (&con_chatsize);
564 Cvar_RegisterVariable (&con_chattime);
565 Cvar_RegisterVariable (&con_chatwidth);
566 Cvar_RegisterVariable (&con_notify);
567 Cvar_RegisterVariable (&con_notifyalign);
568 Cvar_RegisterVariable (&con_notifysize);
569 Cvar_RegisterVariable (&con_notifytime);
570 Cvar_RegisterVariable (&con_textsize);
573 Cvar_RegisterVariable (&con_nickcompletion);
574 Cvar_RegisterVariable (&con_nickcompletion_flags);
576 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
577 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
578 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
580 // register our commands
581 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
582 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
583 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
584 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
585 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
586 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
587 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
589 con_initialized = true;
590 Con_DPrint("Console initialized.\n");
598 Deletes the first line from the console history.
601 void Con_DeleteLine()
603 if(con_lines_count == 0)
606 con_lines_first = CON_LINES_IDX(1);
613 Deletes the last line from the console history.
616 void Con_DeleteLastLine()
618 if(con_lines_count == 0)
627 Checks if there is space for a line of the given length, and if yes, returns a
628 pointer to the start of such a space, and NULL otherwise.
631 char *Con_BytesLeft(int len)
633 if(len > CON_TEXTSIZE)
635 if(con_lines_count == 0)
639 char *firstline_start = con_lines[con_lines_first].start;
640 char *lastline_onepastend = con_lines[CON_LINES_LAST].start + con_lines[CON_LINES_LAST].len;
641 // the buffer is cyclic, so we first have two cases...
642 if(firstline_start < lastline_onepastend) // buffer is contiguous
645 if(len <= con_text + CON_TEXTSIZE - lastline_onepastend)
646 return lastline_onepastend;
648 else if(len <= firstline_start - con_text)
653 else // buffer has a contiguous hole
655 if(len <= firstline_start - lastline_onepastend)
656 return lastline_onepastend;
667 Notifies the console code about the current time
668 (and shifts back times of other entries when the time
675 if(con_lines_count >= 1)
677 double diff = cl.time - (con_lines + CON_LINES_LAST)->addtime;
680 for(i = 0; i < con_lines_count; ++i)
681 CON_LINES(i).addtime += diff;
690 Appends a given string as a new line to the console.
693 void Con_AddLine(const char *line, int len, int mask)
700 if(len >= CON_TEXTSIZE)
703 // only display end of line.
704 line += len - CON_TEXTSIZE + 1;
705 len = CON_TEXTSIZE - 1;
707 while(!(putpos = Con_BytesLeft(len + 1)) || con_lines_count >= CON_MAXLINES)
709 memcpy(putpos, line, len);
713 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", con_lines_count, con_lines_first, CON_LINES_LAST);
715 p = con_lines + CON_LINES_LAST;
718 p->addtime = cl.time;
720 p->height = -1; // calculate when needed
727 Handles cursor positioning, line wrapping, etc
728 All console printing must go through this in order to be displayed
729 If no console is visible, the notify window will pop up.
732 void Con_PrintToHistory(const char *txt, int mask)
735 // \n goes to next line
736 // \r deletes current line and makes a new one
738 static int cr_pending = 0;
739 static char buf[CON_TEXTSIZE];
740 static int bufpos = 0;
746 Con_DeleteLastLine();
754 Con_AddLine(buf, bufpos, mask);
759 Con_AddLine(buf, bufpos, mask);
763 buf[bufpos++] = *txt;
764 if(bufpos >= CON_TEXTSIZE - 1)
766 Con_AddLine(buf, bufpos, mask);
774 /* The translation table between the graphical font and plain ASCII --KB */
775 static char qfont_table[256] = {
776 '\0', '#', '#', '#', '#', '.', '#', '#',
777 '#', 9, 10, '#', ' ', 13, '.', '.',
778 '[', ']', '0', '1', '2', '3', '4', '5',
779 '6', '7', '8', '9', '.', '<', '=', '>',
780 ' ', '!', '"', '#', '$', '%', '&', '\'',
781 '(', ')', '*', '+', ',', '-', '.', '/',
782 '0', '1', '2', '3', '4', '5', '6', '7',
783 '8', '9', ':', ';', '<', '=', '>', '?',
784 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
785 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
786 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
787 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
788 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
789 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
790 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
791 'x', 'y', 'z', '{', '|', '}', '~', '<',
793 '<', '=', '>', '#', '#', '.', '#', '#',
794 '#', '#', ' ', '#', ' ', '>', '.', '.',
795 '[', ']', '0', '1', '2', '3', '4', '5',
796 '6', '7', '8', '9', '.', '<', '=', '>',
797 ' ', '!', '"', '#', '$', '%', '&', '\'',
798 '(', ')', '*', '+', ',', '-', '.', '/',
799 '0', '1', '2', '3', '4', '5', '6', '7',
800 '8', '9', ':', ';', '<', '=', '>', '?',
801 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
802 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
803 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
804 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
805 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
806 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
807 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
808 'x', 'y', 'z', '{', '|', '}', '~', '<'
811 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest)
813 rcon_redirect_sock = sock;
814 rcon_redirect_dest = dest;
815 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
816 rcon_redirect_bufferpos = 5;
819 void Con_Rcon_Redirect_Flush()
821 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
822 NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
823 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
824 rcon_redirect_bufferpos = 5;
827 void Con_Rcon_Redirect_End()
829 Con_Rcon_Redirect_Flush();
830 rcon_redirect_dest = NULL;
831 rcon_redirect_sock = NULL;
834 void Con_Rcon_Redirect_Abort()
836 rcon_redirect_dest = NULL;
837 rcon_redirect_sock = NULL;
844 Adds a character to the rcon buffer
847 void Con_Rcon_AddChar(char c)
849 if(log_dest_buffer_appending)
851 ++log_dest_buffer_appending;
853 // if this print is in response to an rcon command, add the character
854 // to the rcon redirect buffer
856 if (rcon_redirect_dest)
858 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
859 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
860 Con_Rcon_Redirect_Flush();
862 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
864 if(log_dest_buffer_pos == 0)
865 Log_DestBuffer_Init();
866 log_dest_buffer[log_dest_buffer_pos++] = c;
867 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
868 Log_DestBuffer_Flush();
871 log_dest_buffer_pos = 0;
873 --log_dest_buffer_appending;
880 Prints to all appropriate console targets, and adds timestamps
883 extern cvar_t timestamps;
884 extern cvar_t timeformat;
885 extern qboolean sys_nostdout;
886 void Con_Print(const char *msg)
889 static int index = 0;
890 static char line[MAX_INPUTLINE];
894 Con_Rcon_AddChar(*msg);
895 // if this is the beginning of a new line, print timestamp
898 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
900 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
901 line[index++] = STRING_COLOR_TAG;
902 // assert( STRING_COLOR_DEFAULT < 10 )
903 line[index++] = STRING_COLOR_DEFAULT + '0';
904 // special color codes for chat messages must always come first
905 // for Con_PrintToHistory to work properly
906 if (*msg == 1 || *msg == 2)
911 if(gamemode == GAME_NEXUIZ)
913 if(msg[1] == '\r' && cl.foundtalk2wav)
914 S_LocalSound ("sound/misc/talk2.wav");
916 S_LocalSound ("sound/misc/talk.wav");
920 if (msg[1] == '(' && cl.foundtalk2wav)
921 S_LocalSound ("sound/misc/talk2.wav");
923 S_LocalSound ("sound/misc/talk.wav");
925 mask = CON_MASK_CHAT;
927 line[index++] = STRING_COLOR_TAG;
930 Con_Rcon_AddChar(*msg);
933 for (;*timestamp;index++, timestamp++)
934 if (index < (int)sizeof(line) - 2)
935 line[index] = *timestamp;
937 // append the character
938 line[index++] = *msg;
939 // if this is a newline character, we have a complete line to print
940 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
942 // terminate the line
946 // send to scrollable buffer
947 if (con_initialized && cls.state != ca_dedicated)
949 Con_PrintToHistory(line, mask);
952 // send to terminal or dedicated server window
956 if(sys_specialcharactertranslation.integer)
958 for (p = (unsigned char *) line;*p; p++)
959 *p = qfont_table[*p];
962 if(sys_colortranslation.integer == 1) // ANSI
964 static char printline[MAX_INPUTLINE * 4 + 3];
965 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
966 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
970 for(in = line, out = printline; *in; ++in)
974 case STRING_COLOR_TAG:
977 case STRING_COLOR_TAG:
979 *out++ = STRING_COLOR_TAG;
985 if(lastcolor == 0) break; else lastcolor = 0;
986 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
991 if(lastcolor == 1) break; else lastcolor = 1;
992 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
997 if(lastcolor == 2) break; else lastcolor = 2;
998 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1003 if(lastcolor == 3) break; else lastcolor = 3;
1004 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1009 if(lastcolor == 4) break; else lastcolor = 4;
1010 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1015 if(lastcolor == 5) break; else lastcolor = 5;
1016 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1021 if(lastcolor == 6) break; else lastcolor = 6;
1022 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1027 // bold normal color
1029 if(lastcolor == 8) break; else lastcolor = 8;
1030 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1033 *out++ = STRING_COLOR_TAG;
1040 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1057 Sys_PrintToTerminal(printline);
1059 else if(sys_colortranslation.integer == 2) // Quake
1061 Sys_PrintToTerminal(line);
1065 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1068 for(in = line, out = printline; *in; ++in)
1072 case STRING_COLOR_TAG:
1075 case STRING_COLOR_TAG:
1077 *out++ = STRING_COLOR_TAG;
1092 *out++ = STRING_COLOR_TAG;
1102 Sys_PrintToTerminal(printline);
1105 // empty the line buffer
1116 Prints to all appropriate console targets
1119 void Con_Printf(const char *fmt, ...)
1122 char msg[MAX_INPUTLINE];
1124 va_start(argptr,fmt);
1125 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1135 A Con_Print that only shows up if the "developer" cvar is set
1138 void Con_DPrint(const char *msg)
1140 if (!developer.integer)
1141 return; // don't confuse non-developers with techie stuff...
1149 A Con_Printf that only shows up if the "developer" cvar is set
1152 void Con_DPrintf(const char *fmt, ...)
1155 char msg[MAX_INPUTLINE];
1157 if (!developer.integer)
1158 return; // don't confuse non-developers with techie stuff...
1160 va_start(argptr,fmt);
1161 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1169 ==============================================================================
1173 ==============================================================================
1180 The input line scrolls horizontally if typing goes beyond the right edge
1182 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1185 void Con_DrawInput (void)
1189 char editlinecopy[MAX_INPUTLINE+1], *text;
1192 if (!key_consoleactive)
1193 return; // don't draw anything
1195 strlcpy(editlinecopy, key_lines[edit_line], sizeof(editlinecopy));
1196 text = editlinecopy;
1198 // Advanced Console Editing by Radix radix@planetquake.com
1199 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1200 // use strlen of edit_line instead of key_linepos to allow editing
1201 // of early characters w/o erasing
1203 y = (int)strlen(text);
1205 // fill out remainder with spaces
1206 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1209 // add the cursor frame
1210 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1211 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1213 // text[key_linepos + 1] = 0;
1215 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1220 DrawQ_String_Font(x, con_vislines - con_textsize.value*2, text, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, FONT_CONSOLE );
1223 // key_lines[edit_line][key_linepos] = 0;
1229 float alignment; // 0 = left, 0.5 = center, 1 = right
1235 const char *continuationString;
1238 int colorindex; // init to -1
1242 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1244 con_text_info_t *ti = (con_text_info_t *) passthrough;
1247 ti->colorindex = -1;
1248 return ti->fontsize * ti->font->maxwidth;
1251 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1252 else if(maxWidth == -1)
1253 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1256 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1257 // Note: this is NOT a Con_Printf, as it could print recursively
1262 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1268 (void) isContinuation;
1272 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1274 con_text_info_t *ti = (con_text_info_t *) passthrough;
1276 if(ti->y < ti->ymin - 0.001)
1278 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1282 int x = ti->x + (ti->width - width) * ti->alignment;
1283 if(isContinuation && *ti->continuationString)
1284 x += DrawQ_String_Font(x, ti->y, ti->continuationString, strlen(ti->continuationString), ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, ti->font);
1286 DrawQ_String_Font(x, ti->y, line, length, ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, &(ti->colorindex), false, ti->font);
1289 ti->y += ti->fontsize;
1294 int Con_DrawNotifyRect(int mask_must, int mask_mustnot, float maxage, float x, float y, float width, float height, float fontsize, float alignment_x, float alignment_y, const char *continuationString)
1298 int maxlines = (int) floor(height / fontsize + 0.01f);
1301 int continuationWidth = 0;
1303 double t = cl.time; // saved so it won't change
1306 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1307 ti.fontsize = fontsize;
1308 ti.alignment = alignment_x;
1311 ti.ymax = y + height;
1312 ti.continuationString = continuationString;
1315 Con_WordWidthFunc(&ti, NULL, &l, -1);
1316 l = strlen(continuationString);
1317 continuationWidth = Con_WordWidthFunc(&ti, continuationString, &l, -1);
1319 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1320 startidx = con_lines_count;
1321 for(i = con_lines_count - 1; i >= 0; --i)
1323 con_lineinfo *l = &CON_LINES(i);
1326 if((l->mask & mask_must) != mask_must)
1328 if(l->mask & mask_mustnot)
1330 if(maxage && (l->addtime < t - maxage))
1334 // Calculate its actual height...
1335 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1336 if(lines + mylines >= maxlines)
1338 nskip = lines + mylines - maxlines;
1347 // then center according to the calculated amount of lines...
1349 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1351 // then actually draw
1352 for(i = startidx; i < con_lines_count; ++i)
1354 con_lineinfo *l = &CON_LINES(i);
1356 if((l->mask & mask_must) != mask_must)
1358 if(l->mask & mask_mustnot)
1360 if(maxage && (l->addtime < t - maxage))
1363 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1373 Draws the last few lines of output transparently over the game top
1376 void Con_DrawNotify (void)
1379 float chatstart, notifystart, inputsize;
1381 char temptext[MAX_INPUTLINE];
1387 numChatlines = con_chat.integer;
1388 chatpos = con_chatpos.integer;
1390 if (con_notify.integer < 0)
1391 Cvar_SetValueQuick(&con_notify, 0);
1392 if (gamemode == GAME_TRANSFUSION)
1393 v = 8; // vertical offset
1397 // GAME_NEXUIZ: center, otherwise left justify
1398 align = con_notifyalign.value;
1399 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1401 if(gamemode == GAME_NEXUIZ)
1409 // first chat, input line, then notify
1411 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1413 else if(chatpos > 0)
1415 // first notify, then (chatpos-1) empty lines, then chat, then input
1417 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1419 else // if(chatpos < 0)
1421 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1423 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1428 // just notify and input
1430 chatstart = 0; // shut off gcc warning
1433 v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_HIDENOTIFY | (numChatlines ? CON_MASK_CHAT : 0), con_notifytime.value, 0, notifystart, vid_conwidth.value, con_notify.value * con_notifysize.value, con_notifysize.value, align, 0.0, "");
1438 v = chatstart + numChatlines * con_chatsize.value;
1439 Con_DrawNotifyRect(CON_MASK_CHAT, 0, con_chattime.value, 0, chatstart, vid_conwidth.value * con_chatwidth.value, v - chatstart, con_chatsize.value, 0.0, 1.0, "^3\014\014\014 "); // 015 is ·> character in conchars.tga
1442 if (key_dest == key_message)
1444 int colorindex = -1;
1446 // LordHavoc: speedup, and other improvements
1448 dpsnprintf(temptext, sizeof(temptext), "]%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1450 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1452 dpsnprintf(temptext, sizeof(temptext), "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1455 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1456 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1459 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1465 Con_MeasureConsoleLine
1467 Counts the number of lines for a line on the console.
1470 int Con_MeasureConsoleLine(int lineno)
1472 float width = vid_conwidth.value;
1474 ti.fontsize = con_textsize.value;
1475 ti.font = FONT_CONSOLE;
1477 return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1484 Returns the height of a given console line; calculates it if necessary.
1487 int Con_LineHeight(int i)
1489 int h = con_lines[i].height;
1492 return con_lines[i].height = Con_MeasureConsoleLine(i);
1499 Draws a line of the console; returns its height in lines.
1500 If alpha is 0, the line is not drawn, but still wrapped and its height
1504 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1506 float width = vid_conwidth.value;
1509 ti.continuationString = "";
1511 ti.fontsize = con_textsize.value;
1512 ti.font = FONT_CONSOLE;
1514 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1519 return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1526 Calculates the last visible line index and how much to show of it based on
1530 void Con_LastVisibleLine(int *last, int *limitlast)
1535 if(con_backscroll < 0)
1538 // now count until we saw con_backscroll actual lines
1539 for(ic = 0; ic < con_lines_count; ++ic)
1541 int i = CON_LINES_IDX(con_lines_count - 1 - ic);
1542 int h = Con_LineHeight(i);
1544 // line is the last visible line?
1545 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1548 *limitlast = lines_seen + h - con_backscroll;
1555 // if we get here, no line was on screen - scroll so that one line is
1557 con_backscroll = lines_seen - 1;
1558 *last = con_lines_first;
1566 Draws the console with the solid background
1567 The typing input line at the bottom should only be drawn if typing is allowed
1570 void Con_DrawConsole (int lines)
1572 int i, last, limitlast;
1578 con_vislines = lines;
1580 // draw the background
1581 DrawQ_Pic(0, lines - vid_conheight.integer, scr_conbrightness.value >= 0.01f ? Draw_CachePic ("gfx/conback") : NULL, vid_conwidth.integer, vid_conheight.integer, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, cls.signon == SIGNONS ? scr_conalpha.value : 1.0, 0); // always full alpha when not in game
1582 DrawQ_String_Font(vid_conwidth.integer - DrawQ_TextWidth_Font(engineversion, 0, false, FONT_CONSOLE) * con_textsize.value, lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0, NULL, true, FONT_CONSOLE);
1585 if(con_lines_count > 0)
1587 float ymax = con_vislines - 2 * con_textsize.value;
1588 Con_LastVisibleLine(&last, &limitlast);
1589 y = ymax - con_textsize.value;
1592 y += (con_lines[last].height - limitlast) * con_textsize.value;
1597 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1598 if(i == con_lines_first)
1599 break; // top of console buffer
1601 break; // top of console window
1603 i = CON_LINES_PRED(i);
1607 // draw the input prompt, user text, and cursor if desired
1615 Prints not only map filename, but also
1616 its format (q1/q2/q3/hl) and even its message
1618 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1619 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1620 //LordHavoc: added .ent file loading, and redesigned error handling to still try the .ent file even if the map format is not recognized, this also eliminated one goto
1621 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1622 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1626 int i, k, max, p, o, min;
1629 unsigned char buf[1024];
1631 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1632 t = FS_Search(message, 1, true);
1635 if (t->numfilenames > 1)
1636 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1637 len = (unsigned char *)Z_Malloc(t->numfilenames);
1639 for(max=i=0;i<t->numfilenames;i++)
1641 k = (int)strlen(t->filenames[i]);
1651 for(i=0;i<t->numfilenames;i++)
1653 int lumpofs = 0, lumplen = 0;
1654 char *entities = NULL;
1655 const char *data = NULL;
1657 char entfilename[MAX_QPATH];
1658 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1660 f = FS_OpenVirtualFile(t->filenames[i], true);
1663 memset(buf, 0, 1024);
1664 FS_Read(f, buf, 1024);
1665 if (!memcmp(buf, "IBSP", 4))
1667 p = LittleLong(((int *)buf)[1]);
1668 if (p == Q3BSPVERSION)
1670 q3dheader_t *header = (q3dheader_t *)buf;
1671 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1672 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1674 else if (p == Q2BSPVERSION)
1676 q2dheader_t *header = (q2dheader_t *)buf;
1677 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1678 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1681 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1683 dheader_t *header = (dheader_t *)buf;
1684 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1685 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1689 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1690 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1691 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1692 if (!entities && lumplen >= 10)
1694 FS_Seek(f, lumpofs, SEEK_SET);
1695 entities = (char *)Z_Malloc(lumplen + 1);
1696 FS_Read(f, entities, lumplen);
1700 // if there are entities to parse, a missing message key just
1701 // means there is no title, so clear the message string now
1707 if (!COM_ParseToken_Simple(&data, false, false))
1709 if (com_token[0] == '{')
1711 if (com_token[0] == '}')
1713 // skip leading whitespace
1714 for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
1715 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
1716 keyname[l] = com_token[k+l];
1718 if (!COM_ParseToken_Simple(&data, false, false))
1720 if (developer.integer >= 100)
1721 Con_Printf("key: %s %s\n", keyname, com_token);
1722 if (!strcmp(keyname, "message"))
1724 // get the message contents
1725 strlcpy(message, com_token, sizeof(message));
1735 *(t->filenames[i]+len[i]+5) = 0;
1738 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1739 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1740 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1741 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1742 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1744 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1749 k = *(t->filenames[0]+5+p);
1752 for(i=1;i<t->numfilenames;i++)
1753 if(*(t->filenames[i]+5+p) != k)
1757 if(p > o && completedname && completednamebufferlength > 0)
1759 memset(completedname, 0, completednamebufferlength);
1760 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1770 New function for tab-completion system
1771 Added by EvilTypeGuy
1772 MEGA Thanks to Taniwha
1775 void Con_DisplayList(const char **list)
1777 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1778 const char **walk = list;
1781 len = (int)strlen(*walk);
1789 len = (int)strlen(*list);
1790 if (pos + maxlen >= width) {
1796 for (i = 0; i < (maxlen - len); i++)
1807 /* Nicks_CompleteCountPossible
1809 Count the number of possible nicks to complete
1811 //qboolean COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets);
1812 void SanitizeString(char *in, char *out)
1816 if(*in == STRING_COLOR_TAG)
1821 out[0] = STRING_COLOR_TAG;
1824 } else if(*in >= '0' && *in <= '9')
1831 } else if (*in == STRING_COLOR_TAG)
1833 } else if (*in != STRING_COLOR_TAG) {
1837 *out = qfont_table[*(unsigned char*)in];
1844 // Now it becomes TRICKY :D --blub
1845 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
1846 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
1847 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
1848 static int Nicks_offset[MAX_SCOREBOARD]; // when nicks use a space, we need this to move the completion list string starts to avoid invalid memcpys
1849 static int Nicks_matchpos;
1851 // co against <<:BLASTER:>> is true!?
1852 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
1856 if(tolower(*a) == tolower(*b))
1870 return (*a < *b) ? -1 : 1;
1874 return (*a < *b) ? -1 : 1;
1878 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
1881 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
1883 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
1884 return Nicks_strncasecmp_nospaces(a, b, a_len);
1885 return strncasecmp(a, b, a_len);
1888 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
1890 // ignore non alphanumerics of B
1891 // if A contains a non-alphanumeric, B must contain it as well though!
1894 qboolean alnum_a, alnum_b;
1896 if(tolower(*a) == tolower(*b))
1898 if(*a == 0) // end of both strings, they're equal
1905 // not equal, end of one string?
1910 // ignore non alphanumerics
1911 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
1912 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
1913 if(!alnum_a) // b must contain this
1914 return (*a < *b) ? -1 : 1;
1917 // otherwise, both are alnum, they're just not equal, return the appropriate number
1919 return (*a < *b) ? -1 : 1;
1924 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
1933 if(!con_nickcompletion.integer)
1936 // changed that to 1
1937 if(!line[0])// || !line[1]) // we want at least... 2 written characters
1940 for(i = 0; i < cl.maxclients; ++i)
1943 if(!cl.scores[p].name[0])
1946 SanitizeString(cl.scores[p].name, name);
1947 //Con_Printf("Sanitized: %s^7 -> %s", cl.scores[p].name, name);
1952 length = strlen(name);
1954 spos = pos - 1; // no need for a minimum of characters :)
1956 while(spos >= 0 && (spos - pos) < length) // search-string-length < name length
1958 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
1960 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
1961 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
1967 if(isCon && spos == 0)
1969 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
1975 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
1976 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
1978 // the sanitized list
1979 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
1982 Nicks_matchpos = match;
1985 Nicks_offset[count] = s - (&line[match]);
1986 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
1993 void Cmd_CompleteNicksPrint(int count)
1996 for(i = 0; i < count; ++i)
1997 Con_Printf("%s\n", Nicks_list[i]);
2000 void Nicks_CutMatchesNormal(int count)
2002 // cut match 0 down to the longest possible completion
2005 c = strlen(Nicks_sanlist[0]) - 1;
2006 for(i = 1; i < count; ++i)
2008 l = strlen(Nicks_sanlist[i]) - 1;
2012 for(l = 0; l <= c; ++l)
2013 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2019 Nicks_sanlist[0][c+1] = 0;
2020 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2023 unsigned int Nicks_strcleanlen(const char *s)
2028 if( (*s >= 'a' && *s <= 'z') ||
2029 (*s >= 'A' && *s <= 'Z') ||
2030 (*s >= '0' && *s <= '9') ||
2038 void Nicks_CutMatchesAlphaNumeric(int count)
2040 // cut match 0 down to the longest possible completion
2043 char tempstr[sizeof(Nicks_sanlist[0])];
2045 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2047 c = strlen(Nicks_sanlist[0]);
2048 for(i = 0, l = 0; i < (int)c; ++i)
2050 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2051 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2052 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2054 tempstr[l++] = Nicks_sanlist[0][i];
2059 for(i = 1; i < count; ++i)
2062 b = Nicks_sanlist[i];
2072 if(tolower(*a) == tolower(*b))
2078 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2080 // b is alnum, so cut
2087 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2088 Nicks_CutMatchesNormal(count);
2089 //if(!Nicks_sanlist[0][0])
2090 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2092 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2093 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2097 void Nicks_CutMatchesNoSpaces(int count)
2099 // cut match 0 down to the longest possible completion
2102 char tempstr[sizeof(Nicks_sanlist[0])];
2105 c = strlen(Nicks_sanlist[0]);
2106 for(i = 0, l = 0; i < (int)c; ++i)
2108 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2110 tempstr[l++] = Nicks_sanlist[0][i];
2115 for(i = 1; i < count; ++i)
2118 b = Nicks_sanlist[i];
2128 if(tolower(*a) == tolower(*b))
2142 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2143 Nicks_CutMatchesNormal(count);
2144 //if(!Nicks_sanlist[0][0])
2145 //Con_Printf("TS: %s\n", tempstr);
2146 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2148 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2149 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2153 void Nicks_CutMatches(int count)
2155 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2156 Nicks_CutMatchesAlphaNumeric(count);
2157 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2158 Nicks_CutMatchesNoSpaces(count);
2160 Nicks_CutMatchesNormal(count);
2163 const char **Nicks_CompleteBuildList(int count)
2167 // the list is freed by Con_CompleteCommandLine, so create a char**
2168 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2170 for(; bpos < count; ++bpos)
2171 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2173 Nicks_CutMatches(count);
2179 int Nicks_AddLastColor(char *buffer, int pos)
2181 qboolean quote_added = false;
2185 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2187 // we'll have to add a quote :)
2188 buffer[pos++] = '\"';
2192 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2194 // add color when no quote was added, or when flags &4?
2196 for(match = Nicks_matchpos-1; match >= 0; --match)
2198 if(buffer[match] == STRING_COLOR_TAG && buffer[match+1] >= '0' && buffer[match+1] <= '9')
2200 color = buffer[match+1];
2204 if(!quote_added && buffer[pos-2] == STRING_COLOR_TAG && buffer[pos-1] >= '0' && buffer[pos-1] <= '9') // when thes use &4
2206 buffer[pos++] = STRING_COLOR_TAG;
2207 buffer[pos++] = color;
2212 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2215 /*if(!con_nickcompletion.integer)
2216 return; is tested in Nicks_CompletionCountPossible */
2217 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2223 msg = Nicks_list[0];
2224 len = min(size - Nicks_matchpos - 3, strlen(msg));
2225 memcpy(&buffer[Nicks_matchpos], msg, len);
2226 if( len < (size - 4) ) // space for color and space and \0
2227 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2228 buffer[len++] = ' ';
2235 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2236 Cmd_CompleteNicksPrint(n);
2238 Nicks_CutMatches(n);
2240 msg = Nicks_sanlist[0];
2241 len = min(size - Nicks_matchpos, strlen(msg));
2242 memcpy(&buffer[Nicks_matchpos], msg, len);
2243 buffer[Nicks_matchpos + len] = 0;
2245 return Nicks_matchpos + len;
2252 Con_CompleteCommandLine
2254 New function for tab-completion system
2255 Added by EvilTypeGuy
2256 Thanks to Fett erich@heintz.com
2258 Enhanced to tab-complete map names by [515]
2261 void Con_CompleteCommandLine (void)
2263 const char *cmd = "";
2265 const char **list[4] = {0, 0, 0, 0};
2268 int c, v, a, i, cmd_len, pos, k;
2269 int n; // nicks --blub
2270 const char *space, *patterns;
2272 //find what we want to complete
2276 k = key_lines[edit_line][pos];
2277 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2282 s = key_lines[edit_line] + pos;
2283 strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2)); //save chars after cursor
2284 key_lines[edit_line][key_linepos] = 0; //hide them
2286 space = strchr(key_lines[edit_line] + 1, ' ');
2287 if(space && pos == (space - key_lines[edit_line]) + 1)
2289 strlcpy(command, key_lines[edit_line] + 1, min(sizeof(command), (unsigned int)(space - key_lines[edit_line])));
2291 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2292 if(patterns && !*patterns)
2293 patterns = NULL; // get rid of the empty string
2295 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2299 if (GetMapList(s, t, sizeof(t)))
2301 // first move the cursor
2302 key_linepos += (int)strlen(t) - (int)strlen(s);
2304 // and now do the actual work
2306 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2307 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2309 // and fix the cursor
2310 if(key_linepos > (int) strlen(key_lines[edit_line]))
2311 key_linepos = (int) strlen(key_lines[edit_line]);
2320 stringlist_t resultbuf, dirbuf;
2323 // // store completion patterns (space separated) for command foo in con_completion_foo
2324 // set con_completion_foo "foodata/*.foodefault *.foo"
2327 // Note: patterns with slash are always treated as absolute
2328 // patterns; patterns without slash search in the innermost
2329 // directory the user specified. There is no way to "complete into"
2330 // a directory as of now, as directories seem to be unknown to the
2334 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2335 // set con_completion_playdemo "*.dem"
2336 // set con_completion_play "*.wav *.ogg"
2338 // TODO somehow add support for directories; these shall complete
2339 // to their name + an appended slash.
2341 stringlistinit(&resultbuf);
2342 stringlistinit(&dirbuf);
2343 while(COM_ParseToken_Simple(&patterns, false, false))
2346 if(strchr(com_token, '/'))
2348 search = FS_Search(com_token, true, true);
2352 const char *slash = strrchr(s, '/');
2355 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2356 strlcat(t, com_token, sizeof(t));
2357 search = FS_Search(t, true, true);
2360 search = FS_Search(com_token, true, true);
2364 for(i = 0; i < search->numfilenames; ++i)
2365 if(!strncmp(search->filenames[i], s, strlen(s)))
2366 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2367 stringlistappend(&resultbuf, search->filenames[i]);
2368 FS_FreeSearch(search);
2372 // In any case, add directory names
2375 const char *slash = strrchr(s, '/');
2378 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2379 strlcat(t, "*", sizeof(t));
2380 search = FS_Search(t, true, true);
2383 search = FS_Search("*", true, true);
2386 for(i = 0; i < search->numfilenames; ++i)
2387 if(!strncmp(search->filenames[i], s, strlen(s)))
2388 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2389 stringlistappend(&dirbuf, search->filenames[i]);
2390 FS_FreeSearch(search);
2394 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2397 unsigned int matchchars;
2398 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2400 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2403 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2405 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2409 stringlistsort(&resultbuf); // dirbuf is already sorted
2410 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2411 for(i = 0; i < dirbuf.numstrings; ++i)
2413 Con_Printf("%s/\n", dirbuf.strings[i]);
2415 for(i = 0; i < resultbuf.numstrings; ++i)
2417 Con_Printf("%s\n", resultbuf.strings[i]);
2419 matchchars = sizeof(t) - 1;
2420 if(resultbuf.numstrings > 0)
2422 p = resultbuf.strings[0];
2423 q = resultbuf.strings[resultbuf.numstrings - 1];
2424 for(; *p && *p == *q; ++p, ++q);
2425 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2427 if(dirbuf.numstrings > 0)
2429 p = dirbuf.strings[0];
2430 q = dirbuf.strings[dirbuf.numstrings - 1];
2431 for(; *p && *p == *q; ++p, ++q);
2432 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2434 // now p points to the first non-equal character, or to the end
2435 // of resultbuf.strings[0]. We want to append the characters
2436 // from resultbuf.strings[0] to (not including) p as these are
2437 // the unique prefix
2438 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2441 // first move the cursor
2442 key_linepos += (int)strlen(t) - (int)strlen(s);
2444 // and now do the actual work
2446 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2447 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2449 // and fix the cursor
2450 if(key_linepos > (int) strlen(key_lines[edit_line]))
2451 key_linepos = (int) strlen(key_lines[edit_line]);
2453 stringlistfreecontents(&resultbuf);
2454 stringlistfreecontents(&dirbuf);
2456 return; // bail out, when we complete for a command that wants a file name
2461 // Count number of possible matches and print them
2462 c = Cmd_CompleteCountPossible(s);
2465 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2466 Cmd_CompleteCommandPrint(s);
2468 v = Cvar_CompleteCountPossible(s);
2471 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2472 Cvar_CompleteCvarPrint(s);
2474 a = Cmd_CompleteAliasCountPossible(s);
2477 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2478 Cmd_CompleteAliasPrint(s);
2480 n = Nicks_CompleteCountPossible(key_lines[edit_line], key_linepos, s, true);
2483 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2484 Cmd_CompleteNicksPrint(n);
2487 if (!(c + v + a + n)) // No possible matches
2490 strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
2495 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2497 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2499 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2501 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2503 for (cmd_len = (int)strlen(s);;cmd_len++)
2506 for (i = 0; i < 3; i++)
2508 for (l = list[i];*l;l++)
2509 if ((*l)[cmd_len] != cmd[cmd_len])
2511 // all possible matches share this character, so we continue...
2514 // if all matches ended at the same position, stop
2515 // (this means there is only one match)
2521 // prevent a buffer overrun by limiting cmd_len according to remaining space
2522 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
2526 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
2527 key_linepos += cmd_len;
2528 // if there is only one match, add a space after it
2529 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
2532 { // was a nick, might have an offset, and needs colors ;) --blub
2533 key_linepos = pos - Nicks_offset[0];
2534 cmd_len = strlen(Nicks_list[0]);
2535 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 3 - pos);
2537 memcpy(&key_lines[edit_line][key_linepos] , Nicks_list[0], cmd_len);
2538 key_linepos += cmd_len;
2539 if(key_linepos < (int)(sizeof(key_lines[edit_line])-4)) // space for ^, X and space and \0
2540 key_linepos = Nicks_AddLastColor(key_lines[edit_line], key_linepos);
2542 key_lines[edit_line][key_linepos++] = ' ';
2546 // use strlcat to avoid a buffer overrun
2547 key_lines[edit_line][key_linepos] = 0;
2548 strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
2550 // free the command, cvar, and alias lists
2551 for (i = 0; i < 4; i++)
2553 Mem_Free((void *)list[i]);