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;
877 * Convert an RGB color to its nearest quake color.
878 * I'll cheat on this a bit by translating the colors to HSV first,
879 * S and V decide if it's black or white, otherwise, H will decide the
881 * @param _r Red (0-255)
882 * @param _g Green (0-255)
883 * @param _b Blue (0-255)
884 * @return A quake color character.
886 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
888 float r = ((float)_r)/255.0;
889 float g = ((float)_g)/255.0;
890 float b = ((float)_b)/255.0;
891 float min = min(r, min(g, b));
892 float max = max(r, max(g, b));
894 int h; ///< Hue angle [0,360]
895 float s; ///< Saturation [0,1]
896 float v = max; ///< In HSV v == max [0,1]
903 // Saturation threshold. We now say 0.2 is the minimum value for a color!
906 // If the value is less than half, return a black color code.
907 // Otherwise return a white one.
913 // Let's get the hue angle to define some colors:
917 h = (int)(60.0 * (g-b)/(max-min))%360;
919 h = (int)(60.0 * (b-r)/(max-min) + 120);
920 else // if(max == b) redundant check
921 h = (int)(60.0 * (r-g)/(max-min) + 240);
923 if(h < 36) // *red* to orange
925 else if(h < 80) // orange over *yellow* to evilish-bright-green
927 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
929 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
931 else if(h < 270) // darkish blue over *dark blue* to cool purple
933 else if(h < 330) // cool purple over *purple* to ugly swiny red
935 else // ugly red to red closes the circly
943 Prints to all appropriate console targets, and adds timestamps
946 extern cvar_t timestamps;
947 extern cvar_t timeformat;
948 extern qboolean sys_nostdout;
949 void Con_Print(const char *msg)
952 static int index = 0;
953 static char line[MAX_INPUTLINE];
957 Con_Rcon_AddChar(*msg);
958 // if this is the beginning of a new line, print timestamp
961 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
963 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
964 line[index++] = STRING_COLOR_TAG;
965 // assert( STRING_COLOR_DEFAULT < 10 )
966 line[index++] = STRING_COLOR_DEFAULT + '0';
967 // special color codes for chat messages must always come first
968 // for Con_PrintToHistory to work properly
969 if (*msg == 1 || *msg == 2)
974 if(gamemode == GAME_NEXUIZ)
976 if(msg[1] == '\r' && cl.foundtalk2wav)
977 S_LocalSound ("sound/misc/talk2.wav");
979 S_LocalSound ("sound/misc/talk.wav");
983 if (msg[1] == '(' && cl.foundtalk2wav)
984 S_LocalSound ("sound/misc/talk2.wav");
986 S_LocalSound ("sound/misc/talk.wav");
988 mask = CON_MASK_CHAT;
990 line[index++] = STRING_COLOR_TAG;
993 Con_Rcon_AddChar(*msg);
996 for (;*timestamp;index++, timestamp++)
997 if (index < (int)sizeof(line) - 2)
998 line[index] = *timestamp;
1000 // append the character
1001 line[index++] = *msg;
1002 // if this is a newline character, we have a complete line to print
1003 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1005 // terminate the line
1009 // send to scrollable buffer
1010 if (con_initialized && cls.state != ca_dedicated)
1012 Con_PrintToHistory(line, mask);
1015 // send to terminal or dedicated server window
1019 if(sys_specialcharactertranslation.integer)
1021 for (p = (unsigned char *) line;*p; p++)
1022 *p = qfont_table[*p];
1025 if(sys_colortranslation.integer == 1) // ANSI
1027 static char printline[MAX_INPUTLINE * 4 + 3];
1028 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1029 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1034 for(in = line, out = printline; *in; ++in)
1038 case STRING_COLOR_TAG:
1039 if( in[1] == STRING_COLOR_RGB_DEFAULT && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1041 char r = tolower(in[2]);
1042 char g = tolower(in[3]);
1043 char b = tolower(in[4]);
1044 // it's a hex digit already, so the else part needs no check --blub
1045 if(isdigit(r)) r -= '0';
1047 if(isdigit(g)) g -= '0';
1049 if(isdigit(b)) b -= '0';
1052 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1053 in += 3; // 3 only, the switch down there does the fourth
1061 if ( isxdigit(in[2]) || in[2] == '+' || in[2] == '-' )
1066 case STRING_COLOR_TAG:
1068 *out++ = STRING_COLOR_TAG;
1074 if(lastcolor == 0) break; else lastcolor = 0;
1075 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1080 if(lastcolor == 1) break; else lastcolor = 1;
1081 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1086 if(lastcolor == 2) break; else lastcolor = 2;
1087 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1092 if(lastcolor == 3) break; else lastcolor = 3;
1093 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1098 if(lastcolor == 4) break; else lastcolor = 4;
1099 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1104 if(lastcolor == 5) break; else lastcolor = 5;
1105 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1110 if(lastcolor == 6) break; else lastcolor = 6;
1111 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1116 // bold normal color
1118 if(lastcolor == 8) break; else lastcolor = 8;
1119 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1122 *out++ = STRING_COLOR_TAG;
1129 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1146 Sys_PrintToTerminal(printline);
1148 else if(sys_colortranslation.integer == 2) // Quake
1150 Sys_PrintToTerminal(line);
1154 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1157 for(in = line, out = printline; *in; ++in)
1161 case STRING_COLOR_TAG:
1164 case STRING_COLOR_RGB_DEFAULT:
1165 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1171 if ( isxdigit(in[2]) || in[2] == '+' || in[2] == '-' )
1176 case STRING_COLOR_TAG:
1178 *out++ = STRING_COLOR_TAG;
1193 *out++ = STRING_COLOR_TAG;
1203 Sys_PrintToTerminal(printline);
1206 // empty the line buffer
1217 Prints to all appropriate console targets
1220 void Con_Printf(const char *fmt, ...)
1223 char msg[MAX_INPUTLINE];
1225 va_start(argptr,fmt);
1226 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1236 A Con_Print that only shows up if the "developer" cvar is set
1239 void Con_DPrint(const char *msg)
1241 if (!developer.integer)
1242 return; // don't confuse non-developers with techie stuff...
1250 A Con_Printf that only shows up if the "developer" cvar is set
1253 void Con_DPrintf(const char *fmt, ...)
1256 char msg[MAX_INPUTLINE];
1258 if (!developer.integer)
1259 return; // don't confuse non-developers with techie stuff...
1261 va_start(argptr,fmt);
1262 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1270 ==============================================================================
1274 ==============================================================================
1281 The input line scrolls horizontally if typing goes beyond the right edge
1283 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1286 void Con_DrawInput (void)
1290 char editlinecopy[MAX_INPUTLINE+1], *text;
1293 if (!key_consoleactive)
1294 return; // don't draw anything
1296 strlcpy(editlinecopy, key_lines[edit_line], sizeof(editlinecopy));
1297 text = editlinecopy;
1299 // Advanced Console Editing by Radix radix@planetquake.com
1300 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1301 // use strlen of edit_line instead of key_linepos to allow editing
1302 // of early characters w/o erasing
1304 y = (int)strlen(text);
1306 // fill out remainder with spaces
1307 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1310 // add the cursor frame
1311 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1312 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1314 // text[key_linepos + 1] = 0;
1316 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1321 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 );
1324 // key_lines[edit_line][key_linepos] = 0;
1330 float alignment; // 0 = left, 0.5 = center, 1 = right
1336 const char *continuationString;
1339 int colorindex; // init to -1
1343 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1345 con_text_info_t *ti = (con_text_info_t *) passthrough;
1348 ti->colorindex = -1;
1349 return ti->fontsize * ti->font->maxwidth;
1352 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1353 else if(maxWidth == -1)
1354 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1357 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1358 // Note: this is NOT a Con_Printf, as it could print recursively
1363 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1369 (void) isContinuation;
1373 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1375 con_text_info_t *ti = (con_text_info_t *) passthrough;
1377 if(ti->y < ti->ymin - 0.001)
1379 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1383 int x = ti->x + (ti->width - width) * ti->alignment;
1384 if(isContinuation && *ti->continuationString)
1385 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);
1387 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);
1390 ti->y += ti->fontsize;
1395 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)
1399 int maxlines = (int) floor(height / fontsize + 0.01f);
1402 int continuationWidth = 0;
1404 double t = cl.time; // saved so it won't change
1407 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1408 ti.fontsize = fontsize;
1409 ti.alignment = alignment_x;
1412 ti.ymax = y + height;
1413 ti.continuationString = continuationString;
1416 Con_WordWidthFunc(&ti, NULL, &l, -1);
1417 l = strlen(continuationString);
1418 continuationWidth = Con_WordWidthFunc(&ti, continuationString, &l, -1);
1420 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1421 startidx = con_lines_count;
1422 for(i = con_lines_count - 1; i >= 0; --i)
1424 con_lineinfo *l = &CON_LINES(i);
1427 if((l->mask & mask_must) != mask_must)
1429 if(l->mask & mask_mustnot)
1431 if(maxage && (l->addtime < t - maxage))
1435 // Calculate its actual height...
1436 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1437 if(lines + mylines >= maxlines)
1439 nskip = lines + mylines - maxlines;
1448 // then center according to the calculated amount of lines...
1450 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1452 // then actually draw
1453 for(i = startidx; i < con_lines_count; ++i)
1455 con_lineinfo *l = &CON_LINES(i);
1457 if((l->mask & mask_must) != mask_must)
1459 if(l->mask & mask_mustnot)
1461 if(maxage && (l->addtime < t - maxage))
1464 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1474 Draws the last few lines of output transparently over the game top
1477 void Con_DrawNotify (void)
1480 float chatstart, notifystart, inputsize;
1482 char temptext[MAX_INPUTLINE];
1488 numChatlines = con_chat.integer;
1489 chatpos = con_chatpos.integer;
1491 if (con_notify.integer < 0)
1492 Cvar_SetValueQuick(&con_notify, 0);
1493 if (gamemode == GAME_TRANSFUSION)
1494 v = 8; // vertical offset
1498 // GAME_NEXUIZ: center, otherwise left justify
1499 align = con_notifyalign.value;
1500 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1502 if(gamemode == GAME_NEXUIZ)
1510 // first chat, input line, then notify
1512 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1514 else if(chatpos > 0)
1516 // first notify, then (chatpos-1) empty lines, then chat, then input
1518 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1520 else // if(chatpos < 0)
1522 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1524 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1529 // just notify and input
1531 chatstart = 0; // shut off gcc warning
1534 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, "");
1539 v = chatstart + numChatlines * con_chatsize.value;
1540 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
1543 if (key_dest == key_message)
1545 int colorindex = -1;
1547 // LordHavoc: speedup, and other improvements
1549 dpsnprintf(temptext, sizeof(temptext), "]%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1551 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1553 dpsnprintf(temptext, sizeof(temptext), "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1556 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1557 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1560 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1566 Con_MeasureConsoleLine
1568 Counts the number of lines for a line on the console.
1571 int Con_MeasureConsoleLine(int lineno)
1573 float width = vid_conwidth.value;
1575 ti.fontsize = con_textsize.value;
1576 ti.font = FONT_CONSOLE;
1578 return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1585 Returns the height of a given console line; calculates it if necessary.
1588 int Con_LineHeight(int i)
1590 int h = con_lines[i].height;
1593 return con_lines[i].height = Con_MeasureConsoleLine(i);
1600 Draws a line of the console; returns its height in lines.
1601 If alpha is 0, the line is not drawn, but still wrapped and its height
1605 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1607 float width = vid_conwidth.value;
1610 ti.continuationString = "";
1612 ti.fontsize = con_textsize.value;
1613 ti.font = FONT_CONSOLE;
1615 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1620 return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1627 Calculates the last visible line index and how much to show of it based on
1631 void Con_LastVisibleLine(int *last, int *limitlast)
1636 if(con_backscroll < 0)
1639 // now count until we saw con_backscroll actual lines
1640 for(ic = 0; ic < con_lines_count; ++ic)
1642 int i = CON_LINES_IDX(con_lines_count - 1 - ic);
1643 int h = Con_LineHeight(i);
1645 // line is the last visible line?
1646 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1649 *limitlast = lines_seen + h - con_backscroll;
1656 // if we get here, no line was on screen - scroll so that one line is
1658 con_backscroll = lines_seen - 1;
1659 *last = con_lines_first;
1667 Draws the console with the solid background
1668 The typing input line at the bottom should only be drawn if typing is allowed
1671 void Con_DrawConsole (int lines)
1673 int i, last, limitlast;
1679 con_vislines = lines;
1681 // draw the background
1682 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
1683 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);
1686 if(con_lines_count > 0)
1688 float ymax = con_vislines - 2 * con_textsize.value;
1689 Con_LastVisibleLine(&last, &limitlast);
1690 y = ymax - con_textsize.value;
1693 y += (con_lines[last].height - limitlast) * con_textsize.value;
1698 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1699 if(i == con_lines_first)
1700 break; // top of console buffer
1702 break; // top of console window
1704 i = CON_LINES_PRED(i);
1708 // draw the input prompt, user text, and cursor if desired
1716 Prints not only map filename, but also
1717 its format (q1/q2/q3/hl) and even its message
1719 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1720 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1721 //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
1722 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1723 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1727 int i, k, max, p, o, min;
1730 unsigned char buf[1024];
1732 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1733 t = FS_Search(message, 1, true);
1736 if (t->numfilenames > 1)
1737 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1738 len = (unsigned char *)Z_Malloc(t->numfilenames);
1740 for(max=i=0;i<t->numfilenames;i++)
1742 k = (int)strlen(t->filenames[i]);
1752 for(i=0;i<t->numfilenames;i++)
1754 int lumpofs = 0, lumplen = 0;
1755 char *entities = NULL;
1756 const char *data = NULL;
1758 char entfilename[MAX_QPATH];
1759 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1761 f = FS_OpenVirtualFile(t->filenames[i], true);
1764 memset(buf, 0, 1024);
1765 FS_Read(f, buf, 1024);
1766 if (!memcmp(buf, "IBSP", 4))
1768 p = LittleLong(((int *)buf)[1]);
1769 if (p == Q3BSPVERSION)
1771 q3dheader_t *header = (q3dheader_t *)buf;
1772 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1773 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1775 else if (p == Q2BSPVERSION)
1777 q2dheader_t *header = (q2dheader_t *)buf;
1778 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1779 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1782 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1784 dheader_t *header = (dheader_t *)buf;
1785 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1786 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1790 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1791 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1792 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1793 if (!entities && lumplen >= 10)
1795 FS_Seek(f, lumpofs, SEEK_SET);
1796 entities = (char *)Z_Malloc(lumplen + 1);
1797 FS_Read(f, entities, lumplen);
1801 // if there are entities to parse, a missing message key just
1802 // means there is no title, so clear the message string now
1808 if (!COM_ParseToken_Simple(&data, false, false))
1810 if (com_token[0] == '{')
1812 if (com_token[0] == '}')
1814 // skip leading whitespace
1815 for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
1816 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
1817 keyname[l] = com_token[k+l];
1819 if (!COM_ParseToken_Simple(&data, false, false))
1821 if (developer.integer >= 100)
1822 Con_Printf("key: %s %s\n", keyname, com_token);
1823 if (!strcmp(keyname, "message"))
1825 // get the message contents
1826 strlcpy(message, com_token, sizeof(message));
1836 *(t->filenames[i]+len[i]+5) = 0;
1839 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1840 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1841 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1842 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1843 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1845 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1850 k = *(t->filenames[0]+5+p);
1853 for(i=1;i<t->numfilenames;i++)
1854 if(*(t->filenames[i]+5+p) != k)
1858 if(p > o && completedname && completednamebufferlength > 0)
1860 memset(completedname, 0, completednamebufferlength);
1861 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1871 New function for tab-completion system
1872 Added by EvilTypeGuy
1873 MEGA Thanks to Taniwha
1876 void Con_DisplayList(const char **list)
1878 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1879 const char **walk = list;
1882 len = (int)strlen(*walk);
1890 len = (int)strlen(*list);
1891 if (pos + maxlen >= width) {
1897 for (i = 0; i < (maxlen - len); i++)
1909 SanitizeString strips color tags from the string in
1910 and writes the result on string out
1912 void SanitizeString(char *in, char *out)
1916 if(*in == STRING_COLOR_TAG)
1921 out[0] = STRING_COLOR_TAG;
1925 else if (*in >= '0' && *in <= '9') // ^[0-9] found
1932 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
1935 else if (*in == STRING_COLOR_RGB_DEFAULT) // ^x found
1937 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
1944 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
1949 /*else if (*in == 'a') // ^a found
1951 if ( isxdigit(in[1]) || isxdigit(in[1]) == '+' || isxdigit(in[1]) == '-')
1958 } else if (*in == STRING_COLOR_TAG) // ^ax^ found, don't print ^ax
1963 else if (*in != STRING_COLOR_TAG)
1966 *out = qfont_table[*(unsigned char*)in];
1973 // Now it becomes TRICKY :D --blub
1974 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
1975 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
1976 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
1977 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
1978 static int Nicks_matchpos;
1980 // co against <<:BLASTER:>> is true!?
1981 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
1985 if(tolower(*a) == tolower(*b))
1999 return (*a < *b) ? -1 : 1;
2003 return (*a < *b) ? -1 : 1;
2007 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2010 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2012 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2013 return Nicks_strncasecmp_nospaces(a, b, a_len);
2014 return strncasecmp(a, b, a_len);
2017 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2019 // ignore non alphanumerics of B
2020 // if A contains a non-alphanumeric, B must contain it as well though!
2023 qboolean alnum_a, alnum_b;
2025 if(tolower(*a) == tolower(*b))
2027 if(*a == 0) // end of both strings, they're equal
2034 // not equal, end of one string?
2039 // ignore non alphanumerics
2040 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2041 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2042 if(!alnum_a) // b must contain this
2043 return (*a < *b) ? -1 : 1;
2046 // otherwise, both are alnum, they're just not equal, return the appropriate number
2048 return (*a < *b) ? -1 : 1;
2054 /* Nicks_CompleteCountPossible
2056 Count the number of possible nicks to complete
2058 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2067 if(!con_nickcompletion.integer)
2070 // changed that to 1
2071 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2074 for(i = 0; i < cl.maxclients; ++i)
2077 if(!cl.scores[p].name[0])
2080 SanitizeString(cl.scores[p].name, name);
2081 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2086 length = strlen(name);
2088 spos = pos - 1; // no need for a minimum of characters :)
2090 while(spos >= 0 && (spos - pos) < length) // search-string-length < name length
2092 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2094 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2095 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2101 if(isCon && spos == 0)
2103 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2109 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2110 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2112 // the sanitized list
2113 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2116 Nicks_matchpos = match;
2119 Nicks_offset[count] = s - (&line[match]);
2120 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2127 void Cmd_CompleteNicksPrint(int count)
2130 for(i = 0; i < count; ++i)
2131 Con_Printf("%s\n", Nicks_list[i]);
2134 void Nicks_CutMatchesNormal(int count)
2136 // cut match 0 down to the longest possible completion
2139 c = strlen(Nicks_sanlist[0]) - 1;
2140 for(i = 1; i < count; ++i)
2142 l = strlen(Nicks_sanlist[i]) - 1;
2146 for(l = 0; l <= c; ++l)
2147 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2153 Nicks_sanlist[0][c+1] = 0;
2154 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2157 unsigned int Nicks_strcleanlen(const char *s)
2162 if( (*s >= 'a' && *s <= 'z') ||
2163 (*s >= 'A' && *s <= 'Z') ||
2164 (*s >= '0' && *s <= '9') ||
2172 void Nicks_CutMatchesAlphaNumeric(int count)
2174 // cut match 0 down to the longest possible completion
2177 char tempstr[sizeof(Nicks_sanlist[0])];
2179 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2181 c = strlen(Nicks_sanlist[0]);
2182 for(i = 0, l = 0; i < (int)c; ++i)
2184 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2185 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2186 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2188 tempstr[l++] = Nicks_sanlist[0][i];
2193 for(i = 1; i < count; ++i)
2196 b = Nicks_sanlist[i];
2206 if(tolower(*a) == tolower(*b))
2212 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2214 // b is alnum, so cut
2221 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2222 Nicks_CutMatchesNormal(count);
2223 //if(!Nicks_sanlist[0][0])
2224 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2226 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2227 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2231 void Nicks_CutMatchesNoSpaces(int count)
2233 // cut match 0 down to the longest possible completion
2236 char tempstr[sizeof(Nicks_sanlist[0])];
2239 c = strlen(Nicks_sanlist[0]);
2240 for(i = 0, l = 0; i < (int)c; ++i)
2242 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2244 tempstr[l++] = Nicks_sanlist[0][i];
2249 for(i = 1; i < count; ++i)
2252 b = Nicks_sanlist[i];
2262 if(tolower(*a) == tolower(*b))
2276 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2277 Nicks_CutMatchesNormal(count);
2278 //if(!Nicks_sanlist[0][0])
2279 //Con_Printf("TS: %s\n", tempstr);
2280 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2282 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2283 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2287 void Nicks_CutMatches(int count)
2289 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2290 Nicks_CutMatchesAlphaNumeric(count);
2291 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2292 Nicks_CutMatchesNoSpaces(count);
2294 Nicks_CutMatchesNormal(count);
2297 const char **Nicks_CompleteBuildList(int count)
2301 // the list is freed by Con_CompleteCommandLine, so create a char**
2302 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2304 for(; bpos < count; ++bpos)
2305 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2307 Nicks_CutMatches(count);
2315 Restores the previous used color, after the autocompleted name.
2317 int Nicks_AddLastColor(char *buffer, int pos)
2319 qboolean quote_added = false;
2321 char color = STRING_COLOR_DEFAULT + '0';
2322 char r = 0, g = 0, b = 0;
2324 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2326 // we'll have to add a quote :)
2327 buffer[pos++] = '\"';
2331 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2333 // add color when no quote was added, or when flags &4?
2335 for(match = Nicks_matchpos-1; match >= 0; --match)
2337 if(buffer[match] == STRING_COLOR_TAG)
2339 if( isdigit(buffer[match+1]) )
2341 color = buffer[match+1];
2344 else if(buffer[match+1] == STRING_COLOR_RGB_DEFAULT)
2346 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2347 r = buffer[match+2];
2348 g = buffer[match+3];
2349 b = buffer[match+4];
2357 if( buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2361 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_DEFAULT)
2363 if ( isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2372 buffer[pos++] = STRING_COLOR_TAG;
2373 buffer[pos++] = STRING_COLOR_RGB_DEFAULT;
2378 /*else if (color == -2)
2380 buffer[pos++] = STRING_COLOR_TAG;
2381 buffer[pos++] = 'a';
2386 buffer[pos++] = STRING_COLOR_TAG;
2387 buffer[pos++] = color;
2393 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2396 /*if(!con_nickcompletion.integer)
2397 return; is tested in Nicks_CompletionCountPossible */
2398 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2404 msg = Nicks_list[0];
2405 len = min(size - Nicks_matchpos - 3, strlen(msg));
2406 memcpy(&buffer[Nicks_matchpos], msg, len);
2407 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2408 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2409 buffer[len++] = ' ';
2416 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2417 Cmd_CompleteNicksPrint(n);
2419 Nicks_CutMatches(n);
2421 msg = Nicks_sanlist[0];
2422 len = min(size - Nicks_matchpos, strlen(msg));
2423 memcpy(&buffer[Nicks_matchpos], msg, len);
2424 buffer[Nicks_matchpos + len] = 0;
2426 return Nicks_matchpos + len;
2433 Con_CompleteCommandLine
2435 New function for tab-completion system
2436 Added by EvilTypeGuy
2437 Thanks to Fett erich@heintz.com
2439 Enhanced to tab-complete map names by [515]
2442 void Con_CompleteCommandLine (void)
2444 const char *cmd = "";
2446 const char **list[4] = {0, 0, 0, 0};
2449 int c, v, a, i, cmd_len, pos, k;
2450 int n; // nicks --blub
2451 const char *space, *patterns;
2453 //find what we want to complete
2457 k = key_lines[edit_line][pos];
2458 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2463 s = key_lines[edit_line] + pos;
2464 strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2)); //save chars after cursor
2465 key_lines[edit_line][key_linepos] = 0; //hide them
2467 space = strchr(key_lines[edit_line] + 1, ' ');
2468 if(space && pos == (space - key_lines[edit_line]) + 1)
2470 strlcpy(command, key_lines[edit_line] + 1, min(sizeof(command), (unsigned int)(space - key_lines[edit_line])));
2472 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2473 if(patterns && !*patterns)
2474 patterns = NULL; // get rid of the empty string
2476 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2480 if (GetMapList(s, t, sizeof(t)))
2482 // first move the cursor
2483 key_linepos += (int)strlen(t) - (int)strlen(s);
2485 // and now do the actual work
2487 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2488 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2490 // and fix the cursor
2491 if(key_linepos > (int) strlen(key_lines[edit_line]))
2492 key_linepos = (int) strlen(key_lines[edit_line]);
2501 stringlist_t resultbuf, dirbuf;
2504 // // store completion patterns (space separated) for command foo in con_completion_foo
2505 // set con_completion_foo "foodata/*.foodefault *.foo"
2508 // Note: patterns with slash are always treated as absolute
2509 // patterns; patterns without slash search in the innermost
2510 // directory the user specified. There is no way to "complete into"
2511 // a directory as of now, as directories seem to be unknown to the
2515 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2516 // set con_completion_playdemo "*.dem"
2517 // set con_completion_play "*.wav *.ogg"
2519 // TODO somehow add support for directories; these shall complete
2520 // to their name + an appended slash.
2522 stringlistinit(&resultbuf);
2523 stringlistinit(&dirbuf);
2524 while(COM_ParseToken_Simple(&patterns, false, false))
2527 if(strchr(com_token, '/'))
2529 search = FS_Search(com_token, true, true);
2533 const char *slash = strrchr(s, '/');
2536 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2537 strlcat(t, com_token, sizeof(t));
2538 search = FS_Search(t, true, true);
2541 search = FS_Search(com_token, true, true);
2545 for(i = 0; i < search->numfilenames; ++i)
2546 if(!strncmp(search->filenames[i], s, strlen(s)))
2547 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2548 stringlistappend(&resultbuf, search->filenames[i]);
2549 FS_FreeSearch(search);
2553 // In any case, add directory names
2556 const char *slash = strrchr(s, '/');
2559 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2560 strlcat(t, "*", sizeof(t));
2561 search = FS_Search(t, true, true);
2564 search = FS_Search("*", true, true);
2567 for(i = 0; i < search->numfilenames; ++i)
2568 if(!strncmp(search->filenames[i], s, strlen(s)))
2569 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2570 stringlistappend(&dirbuf, search->filenames[i]);
2571 FS_FreeSearch(search);
2575 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2578 unsigned int matchchars;
2579 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2581 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2584 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2586 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2590 stringlistsort(&resultbuf); // dirbuf is already sorted
2591 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2592 for(i = 0; i < dirbuf.numstrings; ++i)
2594 Con_Printf("%s/\n", dirbuf.strings[i]);
2596 for(i = 0; i < resultbuf.numstrings; ++i)
2598 Con_Printf("%s\n", resultbuf.strings[i]);
2600 matchchars = sizeof(t) - 1;
2601 if(resultbuf.numstrings > 0)
2603 p = resultbuf.strings[0];
2604 q = resultbuf.strings[resultbuf.numstrings - 1];
2605 for(; *p && *p == *q; ++p, ++q);
2606 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2608 if(dirbuf.numstrings > 0)
2610 p = dirbuf.strings[0];
2611 q = dirbuf.strings[dirbuf.numstrings - 1];
2612 for(; *p && *p == *q; ++p, ++q);
2613 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2615 // now p points to the first non-equal character, or to the end
2616 // of resultbuf.strings[0]. We want to append the characters
2617 // from resultbuf.strings[0] to (not including) p as these are
2618 // the unique prefix
2619 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2622 // first move the cursor
2623 key_linepos += (int)strlen(t) - (int)strlen(s);
2625 // and now do the actual work
2627 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2628 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2630 // and fix the cursor
2631 if(key_linepos > (int) strlen(key_lines[edit_line]))
2632 key_linepos = (int) strlen(key_lines[edit_line]);
2634 stringlistfreecontents(&resultbuf);
2635 stringlistfreecontents(&dirbuf);
2637 return; // bail out, when we complete for a command that wants a file name
2642 // Count number of possible matches and print them
2643 c = Cmd_CompleteCountPossible(s);
2646 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2647 Cmd_CompleteCommandPrint(s);
2649 v = Cvar_CompleteCountPossible(s);
2652 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2653 Cvar_CompleteCvarPrint(s);
2655 a = Cmd_CompleteAliasCountPossible(s);
2658 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2659 Cmd_CompleteAliasPrint(s);
2661 n = Nicks_CompleteCountPossible(key_lines[edit_line], key_linepos, s, true);
2664 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2665 Cmd_CompleteNicksPrint(n);
2668 if (!(c + v + a + n)) // No possible matches
2671 strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
2676 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2678 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2680 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2682 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2684 for (cmd_len = (int)strlen(s);;cmd_len++)
2687 for (i = 0; i < 3; i++)
2689 for (l = list[i];*l;l++)
2690 if ((*l)[cmd_len] != cmd[cmd_len])
2692 // all possible matches share this character, so we continue...
2695 // if all matches ended at the same position, stop
2696 // (this means there is only one match)
2702 // prevent a buffer overrun by limiting cmd_len according to remaining space
2703 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
2707 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
2708 key_linepos += cmd_len;
2709 // if there is only one match, add a space after it
2710 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
2713 { // was a nick, might have an offset, and needs colors ;) --blub
2714 key_linepos = pos - Nicks_offset[0];
2715 cmd_len = strlen(Nicks_list[0]);
2716 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 3 - pos);
2718 memcpy(&key_lines[edit_line][key_linepos] , Nicks_list[0], cmd_len);
2719 key_linepos += cmd_len;
2720 if(key_linepos < (int)(sizeof(key_lines[edit_line])-4)) // space for ^, X and space and \0
2721 key_linepos = Nicks_AddLastColor(key_lines[edit_line], key_linepos);
2723 key_lines[edit_line][key_linepos++] = ' ';
2727 // use strlcat to avoid a buffer overrun
2728 key_lines[edit_line][key_linepos] = 0;
2729 strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
2731 // free the command, cvar, and alias lists
2732 for (i = 0; i < 4; i++)
2734 Mem_Free((void *)list[i]);