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 unsigned int 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(int 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_TAG_CHAR && 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
1060 case STRING_COLOR_TAG:
1062 *out++ = STRING_COLOR_TAG;
1068 if(lastcolor == 0) break; else lastcolor = 0;
1069 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1074 if(lastcolor == 1) break; else lastcolor = 1;
1075 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1080 if(lastcolor == 2) break; else lastcolor = 2;
1081 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1086 if(lastcolor == 3) break; else lastcolor = 3;
1087 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1092 if(lastcolor == 4) break; else lastcolor = 4;
1093 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1098 if(lastcolor == 5) break; else lastcolor = 5;
1099 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1104 if(lastcolor == 6) break; else lastcolor = 6;
1105 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1110 // bold normal color
1112 if(lastcolor == 8) break; else lastcolor = 8;
1113 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1116 *out++ = STRING_COLOR_TAG;
1123 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1140 Sys_PrintToTerminal(printline);
1142 else if(sys_colortranslation.integer == 2) // Quake
1144 Sys_PrintToTerminal(line);
1148 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1151 for(in = line, out = printline; *in; ++in)
1155 case STRING_COLOR_TAG:
1158 case STRING_COLOR_RGB_TAG_CHAR:
1159 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1164 *out++ = STRING_COLOR_TAG;
1165 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1168 case STRING_COLOR_TAG:
1170 *out++ = STRING_COLOR_TAG;
1185 *out++ = STRING_COLOR_TAG;
1195 Sys_PrintToTerminal(printline);
1198 // empty the line buffer
1209 Prints to all appropriate console targets
1212 void Con_Printf(const char *fmt, ...)
1215 char msg[MAX_INPUTLINE];
1217 va_start(argptr,fmt);
1218 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1228 A Con_Print that only shows up if the "developer" cvar is set
1231 void Con_DPrint(const char *msg)
1233 if (!developer.integer)
1234 return; // don't confuse non-developers with techie stuff...
1242 A Con_Printf that only shows up if the "developer" cvar is set
1245 void Con_DPrintf(const char *fmt, ...)
1248 char msg[MAX_INPUTLINE];
1250 if (!developer.integer)
1251 return; // don't confuse non-developers with techie stuff...
1253 va_start(argptr,fmt);
1254 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1262 ==============================================================================
1266 ==============================================================================
1273 The input line scrolls horizontally if typing goes beyond the right edge
1275 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1278 void Con_DrawInput (void)
1282 char editlinecopy[MAX_INPUTLINE+1], *text;
1285 if (!key_consoleactive)
1286 return; // don't draw anything
1288 strlcpy(editlinecopy, key_lines[edit_line], sizeof(editlinecopy));
1289 text = editlinecopy;
1291 // Advanced Console Editing by Radix radix@planetquake.com
1292 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1293 // use strlen of edit_line instead of key_linepos to allow editing
1294 // of early characters w/o erasing
1296 y = (int)strlen(text);
1298 // fill out remainder with spaces
1299 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1302 // add the cursor frame
1303 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1304 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1306 // text[key_linepos + 1] = 0;
1308 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1313 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 );
1316 // key_lines[edit_line][key_linepos] = 0;
1322 float alignment; // 0 = left, 0.5 = center, 1 = right
1328 const char *continuationString;
1331 int colorindex; // init to -1
1335 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1337 con_text_info_t *ti = (con_text_info_t *) passthrough;
1340 ti->colorindex = -1;
1341 return ti->fontsize * ti->font->maxwidth;
1344 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1345 else if(maxWidth == -1)
1346 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1349 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1350 // Note: this is NOT a Con_Printf, as it could print recursively
1355 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1361 (void) isContinuation;
1365 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1367 con_text_info_t *ti = (con_text_info_t *) passthrough;
1369 if(ti->y < ti->ymin - 0.001)
1371 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1375 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1376 if(isContinuation && *ti->continuationString)
1377 x += (int) 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);
1379 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);
1382 ti->y += ti->fontsize;
1387 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)
1391 int maxlines = (int) floor(height / fontsize + 0.01f);
1394 int continuationWidth = 0;
1396 double t = cl.time; // saved so it won't change
1399 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1400 ti.fontsize = fontsize;
1401 ti.alignment = alignment_x;
1404 ti.ymax = y + height;
1405 ti.continuationString = continuationString;
1408 Con_WordWidthFunc(&ti, NULL, &l, -1);
1409 l = strlen(continuationString);
1410 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1412 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1413 startidx = con_lines_count;
1414 for(i = con_lines_count - 1; i >= 0; --i)
1416 con_lineinfo *l = &CON_LINES(i);
1419 if((l->mask & mask_must) != mask_must)
1421 if(l->mask & mask_mustnot)
1423 if(maxage && (l->addtime < t - maxage))
1427 // Calculate its actual height...
1428 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1429 if(lines + mylines >= maxlines)
1431 nskip = lines + mylines - maxlines;
1440 // then center according to the calculated amount of lines...
1442 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1444 // then actually draw
1445 for(i = startidx; i < con_lines_count; ++i)
1447 con_lineinfo *l = &CON_LINES(i);
1449 if((l->mask & mask_must) != mask_must)
1451 if(l->mask & mask_mustnot)
1453 if(maxage && (l->addtime < t - maxage))
1456 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1466 Draws the last few lines of output transparently over the game top
1469 void Con_DrawNotify (void)
1472 float chatstart, notifystart, inputsize;
1474 char temptext[MAX_INPUTLINE];
1480 numChatlines = con_chat.integer;
1481 chatpos = con_chatpos.integer;
1483 if (con_notify.integer < 0)
1484 Cvar_SetValueQuick(&con_notify, 0);
1485 if (gamemode == GAME_TRANSFUSION)
1486 v = 8; // vertical offset
1490 // GAME_NEXUIZ: center, otherwise left justify
1491 align = con_notifyalign.value;
1492 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1494 if(gamemode == GAME_NEXUIZ)
1502 // first chat, input line, then notify
1504 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1506 else if(chatpos > 0)
1508 // first notify, then (chatpos-1) empty lines, then chat, then input
1510 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1512 else // if(chatpos < 0)
1514 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1516 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1521 // just notify and input
1523 chatstart = 0; // shut off gcc warning
1526 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, "");
1531 v = chatstart + numChatlines * con_chatsize.value;
1532 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
1535 if (key_dest == key_message)
1537 int colorindex = -1;
1539 // LordHavoc: speedup, and other improvements
1541 dpsnprintf(temptext, sizeof(temptext), "]%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1543 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1545 dpsnprintf(temptext, sizeof(temptext), "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1548 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1549 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1552 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1558 Con_MeasureConsoleLine
1560 Counts the number of lines for a line on the console.
1563 int Con_MeasureConsoleLine(int lineno)
1565 float width = vid_conwidth.value;
1567 ti.fontsize = con_textsize.value;
1568 ti.font = FONT_CONSOLE;
1570 return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1577 Returns the height of a given console line; calculates it if necessary.
1580 int Con_LineHeight(int i)
1582 int h = con_lines[i].height;
1585 return con_lines[i].height = Con_MeasureConsoleLine(i);
1592 Draws a line of the console; returns its height in lines.
1593 If alpha is 0, the line is not drawn, but still wrapped and its height
1597 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1599 float width = vid_conwidth.value;
1602 ti.continuationString = "";
1604 ti.fontsize = con_textsize.value;
1605 ti.font = FONT_CONSOLE;
1607 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1612 return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1619 Calculates the last visible line index and how much to show of it based on
1623 void Con_LastVisibleLine(int *last, int *limitlast)
1628 if(con_backscroll < 0)
1631 // now count until we saw con_backscroll actual lines
1632 for(ic = 0; ic < con_lines_count; ++ic)
1634 int i = CON_LINES_IDX(con_lines_count - 1 - ic);
1635 int h = Con_LineHeight(i);
1637 // line is the last visible line?
1638 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1641 *limitlast = lines_seen + h - con_backscroll;
1648 // if we get here, no line was on screen - scroll so that one line is
1650 con_backscroll = lines_seen - 1;
1651 *last = con_lines_first;
1659 Draws the console with the solid background
1660 The typing input line at the bottom should only be drawn if typing is allowed
1663 void Con_DrawConsole (int lines)
1665 int i, last, limitlast;
1671 con_vislines = lines;
1673 // draw the background
1674 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
1675 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);
1678 if(con_lines_count > 0)
1680 float ymax = con_vislines - 2 * con_textsize.value;
1681 Con_LastVisibleLine(&last, &limitlast);
1682 y = ymax - con_textsize.value;
1685 y += (con_lines[last].height - limitlast) * con_textsize.value;
1690 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1691 if(i == con_lines_first)
1692 break; // top of console buffer
1694 break; // top of console window
1696 i = CON_LINES_PRED(i);
1700 // draw the input prompt, user text, and cursor if desired
1708 Prints not only map filename, but also
1709 its format (q1/q2/q3/hl) and even its message
1711 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1712 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1713 //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
1714 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1715 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1719 int i, k, max, p, o, min;
1722 unsigned char buf[1024];
1724 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1725 t = FS_Search(message, 1, true);
1728 if (t->numfilenames > 1)
1729 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1730 len = (unsigned char *)Z_Malloc(t->numfilenames);
1732 for(max=i=0;i<t->numfilenames;i++)
1734 k = (int)strlen(t->filenames[i]);
1744 for(i=0;i<t->numfilenames;i++)
1746 int lumpofs = 0, lumplen = 0;
1747 char *entities = NULL;
1748 const char *data = NULL;
1750 char entfilename[MAX_QPATH];
1751 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1753 f = FS_OpenVirtualFile(t->filenames[i], true);
1756 memset(buf, 0, 1024);
1757 FS_Read(f, buf, 1024);
1758 if (!memcmp(buf, "IBSP", 4))
1760 p = LittleLong(((int *)buf)[1]);
1761 if (p == Q3BSPVERSION)
1763 q3dheader_t *header = (q3dheader_t *)buf;
1764 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1765 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1767 else if (p == Q2BSPVERSION)
1769 q2dheader_t *header = (q2dheader_t *)buf;
1770 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1771 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1774 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1776 dheader_t *header = (dheader_t *)buf;
1777 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1778 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1782 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1783 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1784 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1785 if (!entities && lumplen >= 10)
1787 FS_Seek(f, lumpofs, SEEK_SET);
1788 entities = (char *)Z_Malloc(lumplen + 1);
1789 FS_Read(f, entities, lumplen);
1793 // if there are entities to parse, a missing message key just
1794 // means there is no title, so clear the message string now
1800 if (!COM_ParseToken_Simple(&data, false, false))
1802 if (com_token[0] == '{')
1804 if (com_token[0] == '}')
1806 // skip leading whitespace
1807 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
1808 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
1809 keyname[l] = com_token[k+l];
1811 if (!COM_ParseToken_Simple(&data, false, false))
1813 if (developer.integer >= 100)
1814 Con_Printf("key: %s %s\n", keyname, com_token);
1815 if (!strcmp(keyname, "message"))
1817 // get the message contents
1818 strlcpy(message, com_token, sizeof(message));
1828 *(t->filenames[i]+len[i]+5) = 0;
1831 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1832 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1833 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1834 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1835 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1837 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1842 k = *(t->filenames[0]+5+p);
1845 for(i=1;i<t->numfilenames;i++)
1846 if(*(t->filenames[i]+5+p) != k)
1850 if(p > o && completedname && completednamebufferlength > 0)
1852 memset(completedname, 0, completednamebufferlength);
1853 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1863 New function for tab-completion system
1864 Added by EvilTypeGuy
1865 MEGA Thanks to Taniwha
1868 void Con_DisplayList(const char **list)
1870 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1871 const char **walk = list;
1874 len = (int)strlen(*walk);
1882 len = (int)strlen(*list);
1883 if (pos + maxlen >= width) {
1889 for (i = 0; i < (maxlen - len); i++)
1901 SanitizeString strips color tags from the string in
1902 and writes the result on string out
1904 void SanitizeString(char *in, char *out)
1908 if(*in == STRING_COLOR_TAG)
1913 out[0] = STRING_COLOR_TAG;
1917 else if (*in >= '0' && *in <= '9') // ^[0-9] found
1924 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
1927 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
1929 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
1936 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
1941 else if (*in != STRING_COLOR_TAG)
1944 *out = qfont_table[*(unsigned char*)in];
1951 // Now it becomes TRICKY :D --blub
1952 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
1953 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
1954 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
1955 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
1956 static int Nicks_matchpos;
1958 // co against <<:BLASTER:>> is true!?
1959 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
1963 if(tolower(*a) == tolower(*b))
1977 return (*a < *b) ? -1 : 1;
1981 return (*a < *b) ? -1 : 1;
1985 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
1988 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
1990 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
1991 return Nicks_strncasecmp_nospaces(a, b, a_len);
1992 return strncasecmp(a, b, a_len);
1995 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
1997 // ignore non alphanumerics of B
1998 // if A contains a non-alphanumeric, B must contain it as well though!
2001 qboolean alnum_a, alnum_b;
2003 if(tolower(*a) == tolower(*b))
2005 if(*a == 0) // end of both strings, they're equal
2012 // not equal, end of one string?
2017 // ignore non alphanumerics
2018 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2019 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2020 if(!alnum_a) // b must contain this
2021 return (*a < *b) ? -1 : 1;
2024 // otherwise, both are alnum, they're just not equal, return the appropriate number
2026 return (*a < *b) ? -1 : 1;
2032 /* Nicks_CompleteCountPossible
2034 Count the number of possible nicks to complete
2036 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2045 if(!con_nickcompletion.integer)
2048 // changed that to 1
2049 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2052 for(i = 0; i < cl.maxclients; ++i)
2055 if(!cl.scores[p].name[0])
2058 SanitizeString(cl.scores[p].name, name);
2059 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2064 length = strlen(name);
2066 spos = pos - 1; // no need for a minimum of characters :)
2070 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2072 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2073 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2079 if(isCon && spos == 0)
2081 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2087 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2088 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2090 // the sanitized list
2091 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2094 Nicks_matchpos = match;
2097 Nicks_offset[count] = s - (&line[match]);
2098 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2105 void Cmd_CompleteNicksPrint(int count)
2108 for(i = 0; i < count; ++i)
2109 Con_Printf("%s\n", Nicks_list[i]);
2112 void Nicks_CutMatchesNormal(int count)
2114 // cut match 0 down to the longest possible completion
2117 c = strlen(Nicks_sanlist[0]) - 1;
2118 for(i = 1; i < count; ++i)
2120 l = strlen(Nicks_sanlist[i]) - 1;
2124 for(l = 0; l <= c; ++l)
2125 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2131 Nicks_sanlist[0][c+1] = 0;
2132 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2135 unsigned int Nicks_strcleanlen(const char *s)
2140 if( (*s >= 'a' && *s <= 'z') ||
2141 (*s >= 'A' && *s <= 'Z') ||
2142 (*s >= '0' && *s <= '9') ||
2150 void Nicks_CutMatchesAlphaNumeric(int count)
2152 // cut match 0 down to the longest possible completion
2155 char tempstr[sizeof(Nicks_sanlist[0])];
2157 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2159 c = strlen(Nicks_sanlist[0]);
2160 for(i = 0, l = 0; i < (int)c; ++i)
2162 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2163 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2164 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2166 tempstr[l++] = Nicks_sanlist[0][i];
2171 for(i = 1; i < count; ++i)
2174 b = Nicks_sanlist[i];
2184 if(tolower(*a) == tolower(*b))
2190 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2192 // b is alnum, so cut
2199 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2200 Nicks_CutMatchesNormal(count);
2201 //if(!Nicks_sanlist[0][0])
2202 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2204 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2205 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2209 void Nicks_CutMatchesNoSpaces(int count)
2211 // cut match 0 down to the longest possible completion
2214 char tempstr[sizeof(Nicks_sanlist[0])];
2217 c = strlen(Nicks_sanlist[0]);
2218 for(i = 0, l = 0; i < (int)c; ++i)
2220 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2222 tempstr[l++] = Nicks_sanlist[0][i];
2227 for(i = 1; i < count; ++i)
2230 b = Nicks_sanlist[i];
2240 if(tolower(*a) == tolower(*b))
2254 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2255 Nicks_CutMatchesNormal(count);
2256 //if(!Nicks_sanlist[0][0])
2257 //Con_Printf("TS: %s\n", tempstr);
2258 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2260 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2261 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2265 void Nicks_CutMatches(int count)
2267 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2268 Nicks_CutMatchesAlphaNumeric(count);
2269 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2270 Nicks_CutMatchesNoSpaces(count);
2272 Nicks_CutMatchesNormal(count);
2275 const char **Nicks_CompleteBuildList(int count)
2279 // the list is freed by Con_CompleteCommandLine, so create a char**
2280 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2282 for(; bpos < count; ++bpos)
2283 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2285 Nicks_CutMatches(count);
2293 Restores the previous used color, after the autocompleted name.
2295 int Nicks_AddLastColor(char *buffer, int pos)
2297 qboolean quote_added = false;
2299 int color = STRING_COLOR_DEFAULT + '0';
2300 char r = 0, g = 0, b = 0;
2302 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2304 // we'll have to add a quote :)
2305 buffer[pos++] = '\"';
2309 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2311 // add color when no quote was added, or when flags &4?
2313 for(match = Nicks_matchpos-1; match >= 0; --match)
2315 if(buffer[match] == STRING_COLOR_TAG)
2317 if( isdigit(buffer[match+1]) )
2319 color = buffer[match+1];
2322 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2324 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2326 r = buffer[match+2];
2327 g = buffer[match+3];
2328 b = buffer[match+4];
2337 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2339 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2340 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2343 buffer[pos++] = STRING_COLOR_TAG;
2346 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2352 buffer[pos++] = color;
2357 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2360 /*if(!con_nickcompletion.integer)
2361 return; is tested in Nicks_CompletionCountPossible */
2362 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2368 msg = Nicks_list[0];
2369 len = min(size - Nicks_matchpos - 3, strlen(msg));
2370 memcpy(&buffer[Nicks_matchpos], msg, len);
2371 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2372 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2373 buffer[len++] = ' ';
2380 Con_Printf("\n%i possible nicks:\n", n);
2381 Cmd_CompleteNicksPrint(n);
2383 Nicks_CutMatches(n);
2385 msg = Nicks_sanlist[0];
2386 len = min(size - Nicks_matchpos, strlen(msg));
2387 memcpy(&buffer[Nicks_matchpos], msg, len);
2388 buffer[Nicks_matchpos + len] = 0;
2390 return Nicks_matchpos + len;
2397 Con_CompleteCommandLine
2399 New function for tab-completion system
2400 Added by EvilTypeGuy
2401 Thanks to Fett erich@heintz.com
2403 Enhanced to tab-complete map names by [515]
2406 void Con_CompleteCommandLine (void)
2408 const char *cmd = "";
2410 const char **list[4] = {0, 0, 0, 0};
2413 int c, v, a, i, cmd_len, pos, k;
2414 int n; // nicks --blub
2415 const char *space, *patterns;
2417 //find what we want to complete
2421 k = key_lines[edit_line][pos];
2422 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2427 s = key_lines[edit_line] + pos;
2428 strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2)); //save chars after cursor
2429 key_lines[edit_line][key_linepos] = 0; //hide them
2431 space = strchr(key_lines[edit_line] + 1, ' ');
2432 if(space && pos == (space - key_lines[edit_line]) + 1)
2434 strlcpy(command, key_lines[edit_line] + 1, min(sizeof(command), (unsigned int)(space - key_lines[edit_line])));
2436 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2437 if(patterns && !*patterns)
2438 patterns = NULL; // get rid of the empty string
2440 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2444 if (GetMapList(s, t, sizeof(t)))
2446 // first move the cursor
2447 key_linepos += (int)strlen(t) - (int)strlen(s);
2449 // and now do the actual work
2451 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2452 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2454 // and fix the cursor
2455 if(key_linepos > (int) strlen(key_lines[edit_line]))
2456 key_linepos = (int) strlen(key_lines[edit_line]);
2465 stringlist_t resultbuf, dirbuf;
2468 // // store completion patterns (space separated) for command foo in con_completion_foo
2469 // set con_completion_foo "foodata/*.foodefault *.foo"
2472 // Note: patterns with slash are always treated as absolute
2473 // patterns; patterns without slash search in the innermost
2474 // directory the user specified. There is no way to "complete into"
2475 // a directory as of now, as directories seem to be unknown to the
2479 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2480 // set con_completion_playdemo "*.dem"
2481 // set con_completion_play "*.wav *.ogg"
2483 // TODO somehow add support for directories; these shall complete
2484 // to their name + an appended slash.
2486 stringlistinit(&resultbuf);
2487 stringlistinit(&dirbuf);
2488 while(COM_ParseToken_Simple(&patterns, false, false))
2491 if(strchr(com_token, '/'))
2493 search = FS_Search(com_token, true, true);
2497 const char *slash = strrchr(s, '/');
2500 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2501 strlcat(t, com_token, sizeof(t));
2502 search = FS_Search(t, true, true);
2505 search = FS_Search(com_token, true, true);
2509 for(i = 0; i < search->numfilenames; ++i)
2510 if(!strncmp(search->filenames[i], s, strlen(s)))
2511 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2512 stringlistappend(&resultbuf, search->filenames[i]);
2513 FS_FreeSearch(search);
2517 // In any case, add directory names
2520 const char *slash = strrchr(s, '/');
2523 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2524 strlcat(t, "*", sizeof(t));
2525 search = FS_Search(t, true, true);
2528 search = FS_Search("*", true, true);
2531 for(i = 0; i < search->numfilenames; ++i)
2532 if(!strncmp(search->filenames[i], s, strlen(s)))
2533 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2534 stringlistappend(&dirbuf, search->filenames[i]);
2535 FS_FreeSearch(search);
2539 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2542 unsigned int matchchars;
2543 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2545 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2548 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2550 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2554 stringlistsort(&resultbuf); // dirbuf is already sorted
2555 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2556 for(i = 0; i < dirbuf.numstrings; ++i)
2558 Con_Printf("%s/\n", dirbuf.strings[i]);
2560 for(i = 0; i < resultbuf.numstrings; ++i)
2562 Con_Printf("%s\n", resultbuf.strings[i]);
2564 matchchars = sizeof(t) - 1;
2565 if(resultbuf.numstrings > 0)
2567 p = resultbuf.strings[0];
2568 q = resultbuf.strings[resultbuf.numstrings - 1];
2569 for(; *p && *p == *q; ++p, ++q);
2570 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2572 if(dirbuf.numstrings > 0)
2574 p = dirbuf.strings[0];
2575 q = dirbuf.strings[dirbuf.numstrings - 1];
2576 for(; *p && *p == *q; ++p, ++q);
2577 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2579 // now p points to the first non-equal character, or to the end
2580 // of resultbuf.strings[0]. We want to append the characters
2581 // from resultbuf.strings[0] to (not including) p as these are
2582 // the unique prefix
2583 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2586 // first move the cursor
2587 key_linepos += (int)strlen(t) - (int)strlen(s);
2589 // and now do the actual work
2591 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2592 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2594 // and fix the cursor
2595 if(key_linepos > (int) strlen(key_lines[edit_line]))
2596 key_linepos = (int) strlen(key_lines[edit_line]);
2598 stringlistfreecontents(&resultbuf);
2599 stringlistfreecontents(&dirbuf);
2601 return; // bail out, when we complete for a command that wants a file name
2606 // Count number of possible matches and print them
2607 c = Cmd_CompleteCountPossible(s);
2610 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2611 Cmd_CompleteCommandPrint(s);
2613 v = Cvar_CompleteCountPossible(s);
2616 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2617 Cvar_CompleteCvarPrint(s);
2619 a = Cmd_CompleteAliasCountPossible(s);
2622 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2623 Cmd_CompleteAliasPrint(s);
2625 n = Nicks_CompleteCountPossible(key_lines[edit_line], key_linepos, s, true);
2628 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2629 Cmd_CompleteNicksPrint(n);
2632 if (!(c + v + a + n)) // No possible matches
2635 strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
2640 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2642 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2644 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2646 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2648 for (cmd_len = (int)strlen(s);;cmd_len++)
2651 for (i = 0; i < 3; i++)
2653 for (l = list[i];*l;l++)
2654 if ((*l)[cmd_len] != cmd[cmd_len])
2656 // all possible matches share this character, so we continue...
2659 // if all matches ended at the same position, stop
2660 // (this means there is only one match)
2666 // prevent a buffer overrun by limiting cmd_len according to remaining space
2667 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
2671 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
2672 key_linepos += cmd_len;
2673 // if there is only one match, add a space after it
2674 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
2677 { // was a nick, might have an offset, and needs colors ;) --blub
2678 key_linepos = pos - Nicks_offset[0];
2679 cmd_len = strlen(Nicks_list[0]);
2680 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 3 - pos);
2682 memcpy(&key_lines[edit_line][key_linepos] , Nicks_list[0], cmd_len);
2683 key_linepos += cmd_len;
2684 if(key_linepos < (int)(sizeof(key_lines[edit_line])-4)) // space for ^, X and space and \0
2685 key_linepos = Nicks_AddLastColor(key_lines[edit_line], key_linepos);
2687 key_lines[edit_line][key_linepos++] = ' ';
2691 // use strlcat to avoid a buffer overrun
2692 key_lines[edit_line][key_linepos] = 0;
2693 strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
2695 // free the command, cvar, and alias lists
2696 for (i = 0; i < 4; i++)
2698 Mem_Free((void *)list[i]);