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_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]) )
1170 *out++ = STRING_COLOR_TAG;
1171 *out++ = STRING_COLOR_RGB_DEFAULT;
1175 if ( isxdigit(in[2]) || in[2] == '+' || in[2] == '-' )
1180 case STRING_COLOR_TAG:
1182 *out++ = STRING_COLOR_TAG;
1197 *out++ = STRING_COLOR_TAG;
1207 Sys_PrintToTerminal(printline);
1210 // empty the line buffer
1221 Prints to all appropriate console targets
1224 void Con_Printf(const char *fmt, ...)
1227 char msg[MAX_INPUTLINE];
1229 va_start(argptr,fmt);
1230 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1240 A Con_Print that only shows up if the "developer" cvar is set
1243 void Con_DPrint(const char *msg)
1245 if (!developer.integer)
1246 return; // don't confuse non-developers with techie stuff...
1254 A Con_Printf that only shows up if the "developer" cvar is set
1257 void Con_DPrintf(const char *fmt, ...)
1260 char msg[MAX_INPUTLINE];
1262 if (!developer.integer)
1263 return; // don't confuse non-developers with techie stuff...
1265 va_start(argptr,fmt);
1266 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1274 ==============================================================================
1278 ==============================================================================
1285 The input line scrolls horizontally if typing goes beyond the right edge
1287 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1290 void Con_DrawInput (void)
1294 char editlinecopy[MAX_INPUTLINE+1], *text;
1297 if (!key_consoleactive)
1298 return; // don't draw anything
1300 strlcpy(editlinecopy, key_lines[edit_line], sizeof(editlinecopy));
1301 text = editlinecopy;
1303 // Advanced Console Editing by Radix radix@planetquake.com
1304 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1305 // use strlen of edit_line instead of key_linepos to allow editing
1306 // of early characters w/o erasing
1308 y = (int)strlen(text);
1310 // fill out remainder with spaces
1311 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1314 // add the cursor frame
1315 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1316 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1318 // text[key_linepos + 1] = 0;
1320 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1325 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 );
1328 // key_lines[edit_line][key_linepos] = 0;
1334 float alignment; // 0 = left, 0.5 = center, 1 = right
1340 const char *continuationString;
1343 int colorindex; // init to -1
1347 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1349 con_text_info_t *ti = (con_text_info_t *) passthrough;
1352 ti->colorindex = -1;
1353 return ti->fontsize * ti->font->maxwidth;
1356 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1357 else if(maxWidth == -1)
1358 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1361 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1362 // Note: this is NOT a Con_Printf, as it could print recursively
1367 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1373 (void) isContinuation;
1377 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1379 con_text_info_t *ti = (con_text_info_t *) passthrough;
1381 if(ti->y < ti->ymin - 0.001)
1383 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1387 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1388 if(isContinuation && *ti->continuationString)
1389 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);
1391 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);
1394 ti->y += ti->fontsize;
1399 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)
1403 int maxlines = (int) floor(height / fontsize + 0.01f);
1406 int continuationWidth = 0;
1408 double t = cl.time; // saved so it won't change
1411 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1412 ti.fontsize = fontsize;
1413 ti.alignment = alignment_x;
1416 ti.ymax = y + height;
1417 ti.continuationString = continuationString;
1420 Con_WordWidthFunc(&ti, NULL, &l, -1);
1421 l = strlen(continuationString);
1422 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1424 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1425 startidx = con_lines_count;
1426 for(i = con_lines_count - 1; i >= 0; --i)
1428 con_lineinfo *l = &CON_LINES(i);
1431 if((l->mask & mask_must) != mask_must)
1433 if(l->mask & mask_mustnot)
1435 if(maxage && (l->addtime < t - maxage))
1439 // Calculate its actual height...
1440 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1441 if(lines + mylines >= maxlines)
1443 nskip = lines + mylines - maxlines;
1452 // then center according to the calculated amount of lines...
1454 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1456 // then actually draw
1457 for(i = startidx; i < con_lines_count; ++i)
1459 con_lineinfo *l = &CON_LINES(i);
1461 if((l->mask & mask_must) != mask_must)
1463 if(l->mask & mask_mustnot)
1465 if(maxage && (l->addtime < t - maxage))
1468 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1478 Draws the last few lines of output transparently over the game top
1481 void Con_DrawNotify (void)
1484 float chatstart, notifystart, inputsize;
1486 char temptext[MAX_INPUTLINE];
1492 numChatlines = con_chat.integer;
1493 chatpos = con_chatpos.integer;
1495 if (con_notify.integer < 0)
1496 Cvar_SetValueQuick(&con_notify, 0);
1497 if (gamemode == GAME_TRANSFUSION)
1498 v = 8; // vertical offset
1502 // GAME_NEXUIZ: center, otherwise left justify
1503 align = con_notifyalign.value;
1504 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1506 if(gamemode == GAME_NEXUIZ)
1514 // first chat, input line, then notify
1516 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1518 else if(chatpos > 0)
1520 // first notify, then (chatpos-1) empty lines, then chat, then input
1522 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1524 else // if(chatpos < 0)
1526 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1528 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1533 // just notify and input
1535 chatstart = 0; // shut off gcc warning
1538 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, "");
1543 v = chatstart + numChatlines * con_chatsize.value;
1544 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
1547 if (key_dest == key_message)
1549 int colorindex = -1;
1551 // LordHavoc: speedup, and other improvements
1553 dpsnprintf(temptext, sizeof(temptext), "]%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1555 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1557 dpsnprintf(temptext, sizeof(temptext), "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1560 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1561 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1564 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1570 Con_MeasureConsoleLine
1572 Counts the number of lines for a line on the console.
1575 int Con_MeasureConsoleLine(int lineno)
1577 float width = vid_conwidth.value;
1579 ti.fontsize = con_textsize.value;
1580 ti.font = FONT_CONSOLE;
1582 return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1589 Returns the height of a given console line; calculates it if necessary.
1592 int Con_LineHeight(int i)
1594 int h = con_lines[i].height;
1597 return con_lines[i].height = Con_MeasureConsoleLine(i);
1604 Draws a line of the console; returns its height in lines.
1605 If alpha is 0, the line is not drawn, but still wrapped and its height
1609 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1611 float width = vid_conwidth.value;
1614 ti.continuationString = "";
1616 ti.fontsize = con_textsize.value;
1617 ti.font = FONT_CONSOLE;
1619 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1624 return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1631 Calculates the last visible line index and how much to show of it based on
1635 void Con_LastVisibleLine(int *last, int *limitlast)
1640 if(con_backscroll < 0)
1643 // now count until we saw con_backscroll actual lines
1644 for(ic = 0; ic < con_lines_count; ++ic)
1646 int i = CON_LINES_IDX(con_lines_count - 1 - ic);
1647 int h = Con_LineHeight(i);
1649 // line is the last visible line?
1650 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1653 *limitlast = lines_seen + h - con_backscroll;
1660 // if we get here, no line was on screen - scroll so that one line is
1662 con_backscroll = lines_seen - 1;
1663 *last = con_lines_first;
1671 Draws the console with the solid background
1672 The typing input line at the bottom should only be drawn if typing is allowed
1675 void Con_DrawConsole (int lines)
1677 int i, last, limitlast;
1683 con_vislines = lines;
1685 // draw the background
1686 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
1687 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);
1690 if(con_lines_count > 0)
1692 float ymax = con_vislines - 2 * con_textsize.value;
1693 Con_LastVisibleLine(&last, &limitlast);
1694 y = ymax - con_textsize.value;
1697 y += (con_lines[last].height - limitlast) * con_textsize.value;
1702 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1703 if(i == con_lines_first)
1704 break; // top of console buffer
1706 break; // top of console window
1708 i = CON_LINES_PRED(i);
1712 // draw the input prompt, user text, and cursor if desired
1720 Prints not only map filename, but also
1721 its format (q1/q2/q3/hl) and even its message
1723 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1724 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1725 //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
1726 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1727 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1731 int i, k, max, p, o, min;
1734 unsigned char buf[1024];
1736 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1737 t = FS_Search(message, 1, true);
1740 if (t->numfilenames > 1)
1741 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1742 len = (unsigned char *)Z_Malloc(t->numfilenames);
1744 for(max=i=0;i<t->numfilenames;i++)
1746 k = (int)strlen(t->filenames[i]);
1756 for(i=0;i<t->numfilenames;i++)
1758 int lumpofs = 0, lumplen = 0;
1759 char *entities = NULL;
1760 const char *data = NULL;
1762 char entfilename[MAX_QPATH];
1763 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1765 f = FS_OpenVirtualFile(t->filenames[i], true);
1768 memset(buf, 0, 1024);
1769 FS_Read(f, buf, 1024);
1770 if (!memcmp(buf, "IBSP", 4))
1772 p = LittleLong(((int *)buf)[1]);
1773 if (p == Q3BSPVERSION)
1775 q3dheader_t *header = (q3dheader_t *)buf;
1776 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1777 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1779 else if (p == Q2BSPVERSION)
1781 q2dheader_t *header = (q2dheader_t *)buf;
1782 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1783 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1786 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1788 dheader_t *header = (dheader_t *)buf;
1789 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1790 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1794 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1795 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1796 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1797 if (!entities && lumplen >= 10)
1799 FS_Seek(f, lumpofs, SEEK_SET);
1800 entities = (char *)Z_Malloc(lumplen + 1);
1801 FS_Read(f, entities, lumplen);
1805 // if there are entities to parse, a missing message key just
1806 // means there is no title, so clear the message string now
1812 if (!COM_ParseToken_Simple(&data, false, false))
1814 if (com_token[0] == '{')
1816 if (com_token[0] == '}')
1818 // skip leading whitespace
1819 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
1820 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
1821 keyname[l] = com_token[k+l];
1823 if (!COM_ParseToken_Simple(&data, false, false))
1825 if (developer.integer >= 100)
1826 Con_Printf("key: %s %s\n", keyname, com_token);
1827 if (!strcmp(keyname, "message"))
1829 // get the message contents
1830 strlcpy(message, com_token, sizeof(message));
1840 *(t->filenames[i]+len[i]+5) = 0;
1843 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1844 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1845 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1846 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1847 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1849 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1854 k = *(t->filenames[0]+5+p);
1857 for(i=1;i<t->numfilenames;i++)
1858 if(*(t->filenames[i]+5+p) != k)
1862 if(p > o && completedname && completednamebufferlength > 0)
1864 memset(completedname, 0, completednamebufferlength);
1865 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1875 New function for tab-completion system
1876 Added by EvilTypeGuy
1877 MEGA Thanks to Taniwha
1880 void Con_DisplayList(const char **list)
1882 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1883 const char **walk = list;
1886 len = (int)strlen(*walk);
1894 len = (int)strlen(*list);
1895 if (pos + maxlen >= width) {
1901 for (i = 0; i < (maxlen - len); i++)
1913 SanitizeString strips color tags from the string in
1914 and writes the result on string out
1916 void SanitizeString(char *in, char *out)
1920 if(*in == STRING_COLOR_TAG)
1925 out[0] = STRING_COLOR_TAG;
1929 else if (*in >= '0' && *in <= '9') // ^[0-9] found
1936 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
1939 else if (*in == STRING_COLOR_RGB_DEFAULT) // ^x found
1941 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
1948 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
1953 /*else if (*in == 'a') // ^a found
1955 if ( isxdigit(in[1]) || isxdigit(in[1]) == '+' || isxdigit(in[1]) == '-')
1962 } else if (*in == STRING_COLOR_TAG) // ^ax^ found, don't print ^ax
1967 else if (*in != STRING_COLOR_TAG)
1970 *out = qfont_table[*(unsigned char*)in];
1977 // Now it becomes TRICKY :D --blub
1978 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
1979 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
1980 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
1981 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
1982 static int Nicks_matchpos;
1984 // co against <<:BLASTER:>> is true!?
1985 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
1989 if(tolower(*a) == tolower(*b))
2003 return (*a < *b) ? -1 : 1;
2007 return (*a < *b) ? -1 : 1;
2011 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2014 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2016 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2017 return Nicks_strncasecmp_nospaces(a, b, a_len);
2018 return strncasecmp(a, b, a_len);
2021 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2023 // ignore non alphanumerics of B
2024 // if A contains a non-alphanumeric, B must contain it as well though!
2027 qboolean alnum_a, alnum_b;
2029 if(tolower(*a) == tolower(*b))
2031 if(*a == 0) // end of both strings, they're equal
2038 // not equal, end of one string?
2043 // ignore non alphanumerics
2044 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2045 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2046 if(!alnum_a) // b must contain this
2047 return (*a < *b) ? -1 : 1;
2050 // otherwise, both are alnum, they're just not equal, return the appropriate number
2052 return (*a < *b) ? -1 : 1;
2058 /* Nicks_CompleteCountPossible
2060 Count the number of possible nicks to complete
2062 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2071 if(!con_nickcompletion.integer)
2074 // changed that to 1
2075 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2078 for(i = 0; i < cl.maxclients; ++i)
2081 if(!cl.scores[p].name[0])
2084 SanitizeString(cl.scores[p].name, name);
2085 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2090 length = strlen(name);
2092 spos = pos - 1; // no need for a minimum of characters :)
2094 while(spos >= 0 && (spos - pos) < length) // search-string-length < name length
2096 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2098 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2099 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2105 if(isCon && spos == 0)
2107 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2113 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2114 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2116 // the sanitized list
2117 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2120 Nicks_matchpos = match;
2123 Nicks_offset[count] = s - (&line[match]);
2124 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2131 void Cmd_CompleteNicksPrint(int count)
2134 for(i = 0; i < count; ++i)
2135 Con_Printf("%s\n", Nicks_list[i]);
2138 void Nicks_CutMatchesNormal(int count)
2140 // cut match 0 down to the longest possible completion
2143 c = strlen(Nicks_sanlist[0]) - 1;
2144 for(i = 1; i < count; ++i)
2146 l = strlen(Nicks_sanlist[i]) - 1;
2150 for(l = 0; l <= c; ++l)
2151 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2157 Nicks_sanlist[0][c+1] = 0;
2158 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2161 unsigned int Nicks_strcleanlen(const char *s)
2166 if( (*s >= 'a' && *s <= 'z') ||
2167 (*s >= 'A' && *s <= 'Z') ||
2168 (*s >= '0' && *s <= '9') ||
2176 void Nicks_CutMatchesAlphaNumeric(int count)
2178 // cut match 0 down to the longest possible completion
2181 char tempstr[sizeof(Nicks_sanlist[0])];
2183 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2185 c = strlen(Nicks_sanlist[0]);
2186 for(i = 0, l = 0; i < (int)c; ++i)
2188 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2189 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2190 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2192 tempstr[l++] = Nicks_sanlist[0][i];
2197 for(i = 1; i < count; ++i)
2200 b = Nicks_sanlist[i];
2210 if(tolower(*a) == tolower(*b))
2216 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2218 // b is alnum, so cut
2225 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2226 Nicks_CutMatchesNormal(count);
2227 //if(!Nicks_sanlist[0][0])
2228 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2230 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2231 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2235 void Nicks_CutMatchesNoSpaces(int count)
2237 // cut match 0 down to the longest possible completion
2240 char tempstr[sizeof(Nicks_sanlist[0])];
2243 c = strlen(Nicks_sanlist[0]);
2244 for(i = 0, l = 0; i < (int)c; ++i)
2246 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2248 tempstr[l++] = Nicks_sanlist[0][i];
2253 for(i = 1; i < count; ++i)
2256 b = Nicks_sanlist[i];
2266 if(tolower(*a) == tolower(*b))
2280 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2281 Nicks_CutMatchesNormal(count);
2282 //if(!Nicks_sanlist[0][0])
2283 //Con_Printf("TS: %s\n", tempstr);
2284 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2286 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2287 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2291 void Nicks_CutMatches(int count)
2293 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2294 Nicks_CutMatchesAlphaNumeric(count);
2295 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2296 Nicks_CutMatchesNoSpaces(count);
2298 Nicks_CutMatchesNormal(count);
2301 const char **Nicks_CompleteBuildList(int count)
2305 // the list is freed by Con_CompleteCommandLine, so create a char**
2306 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2308 for(; bpos < count; ++bpos)
2309 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2311 Nicks_CutMatches(count);
2319 Restores the previous used color, after the autocompleted name.
2321 int Nicks_AddLastColor(char *buffer, int pos)
2323 qboolean quote_added = false;
2325 int color = STRING_COLOR_DEFAULT + '0';
2326 char r = 0, g = 0, b = 0;
2328 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2330 // we'll have to add a quote :)
2331 buffer[pos++] = '\"';
2335 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2337 // add color when no quote was added, or when flags &4?
2339 for(match = Nicks_matchpos-1; match >= 0; --match)
2341 if(buffer[match] == STRING_COLOR_TAG)
2343 if( isdigit(buffer[match+1]) )
2345 color = buffer[match+1];
2348 else if(buffer[match+1] == STRING_COLOR_RGB_DEFAULT)
2350 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2352 r = buffer[match+2];
2353 g = buffer[match+3];
2354 b = buffer[match+4];
2363 if( buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2367 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_DEFAULT)
2369 if ( isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2378 buffer[pos++] = STRING_COLOR_TAG;
2379 buffer[pos++] = STRING_COLOR_RGB_DEFAULT;
2384 /*else if (color == -2)
2386 buffer[pos++] = STRING_COLOR_TAG;
2387 buffer[pos++] = 'a';
2392 buffer[pos++] = STRING_COLOR_TAG;
2393 buffer[pos++] = color;
2399 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2402 /*if(!con_nickcompletion.integer)
2403 return; is tested in Nicks_CompletionCountPossible */
2404 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2410 msg = Nicks_list[0];
2411 len = min(size - Nicks_matchpos - 3, strlen(msg));
2412 memcpy(&buffer[Nicks_matchpos], msg, len);
2413 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2414 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2415 buffer[len++] = ' ';
2422 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2423 Cmd_CompleteNicksPrint(n);
2425 Nicks_CutMatches(n);
2427 msg = Nicks_sanlist[0];
2428 len = min(size - Nicks_matchpos, strlen(msg));
2429 memcpy(&buffer[Nicks_matchpos], msg, len);
2430 buffer[Nicks_matchpos + len] = 0;
2432 return Nicks_matchpos + len;
2439 Con_CompleteCommandLine
2441 New function for tab-completion system
2442 Added by EvilTypeGuy
2443 Thanks to Fett erich@heintz.com
2445 Enhanced to tab-complete map names by [515]
2448 void Con_CompleteCommandLine (void)
2450 const char *cmd = "";
2452 const char **list[4] = {0, 0, 0, 0};
2455 int c, v, a, i, cmd_len, pos, k;
2456 int n; // nicks --blub
2457 const char *space, *patterns;
2459 //find what we want to complete
2463 k = key_lines[edit_line][pos];
2464 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2469 s = key_lines[edit_line] + pos;
2470 strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2)); //save chars after cursor
2471 key_lines[edit_line][key_linepos] = 0; //hide them
2473 space = strchr(key_lines[edit_line] + 1, ' ');
2474 if(space && pos == (space - key_lines[edit_line]) + 1)
2476 strlcpy(command, key_lines[edit_line] + 1, min(sizeof(command), (unsigned int)(space - key_lines[edit_line])));
2478 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2479 if(patterns && !*patterns)
2480 patterns = NULL; // get rid of the empty string
2482 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2486 if (GetMapList(s, t, sizeof(t)))
2488 // first move the cursor
2489 key_linepos += (int)strlen(t) - (int)strlen(s);
2491 // and now do the actual work
2493 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2494 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2496 // and fix the cursor
2497 if(key_linepos > (int) strlen(key_lines[edit_line]))
2498 key_linepos = (int) strlen(key_lines[edit_line]);
2507 stringlist_t resultbuf, dirbuf;
2510 // // store completion patterns (space separated) for command foo in con_completion_foo
2511 // set con_completion_foo "foodata/*.foodefault *.foo"
2514 // Note: patterns with slash are always treated as absolute
2515 // patterns; patterns without slash search in the innermost
2516 // directory the user specified. There is no way to "complete into"
2517 // a directory as of now, as directories seem to be unknown to the
2521 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2522 // set con_completion_playdemo "*.dem"
2523 // set con_completion_play "*.wav *.ogg"
2525 // TODO somehow add support for directories; these shall complete
2526 // to their name + an appended slash.
2528 stringlistinit(&resultbuf);
2529 stringlistinit(&dirbuf);
2530 while(COM_ParseToken_Simple(&patterns, false, false))
2533 if(strchr(com_token, '/'))
2535 search = FS_Search(com_token, true, true);
2539 const char *slash = strrchr(s, '/');
2542 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2543 strlcat(t, com_token, sizeof(t));
2544 search = FS_Search(t, true, true);
2547 search = FS_Search(com_token, true, true);
2551 for(i = 0; i < search->numfilenames; ++i)
2552 if(!strncmp(search->filenames[i], s, strlen(s)))
2553 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2554 stringlistappend(&resultbuf, search->filenames[i]);
2555 FS_FreeSearch(search);
2559 // In any case, add directory names
2562 const char *slash = strrchr(s, '/');
2565 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2566 strlcat(t, "*", sizeof(t));
2567 search = FS_Search(t, true, true);
2570 search = FS_Search("*", true, true);
2573 for(i = 0; i < search->numfilenames; ++i)
2574 if(!strncmp(search->filenames[i], s, strlen(s)))
2575 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2576 stringlistappend(&dirbuf, search->filenames[i]);
2577 FS_FreeSearch(search);
2581 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2584 unsigned int matchchars;
2585 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2587 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2590 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2592 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2596 stringlistsort(&resultbuf); // dirbuf is already sorted
2597 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2598 for(i = 0; i < dirbuf.numstrings; ++i)
2600 Con_Printf("%s/\n", dirbuf.strings[i]);
2602 for(i = 0; i < resultbuf.numstrings; ++i)
2604 Con_Printf("%s\n", resultbuf.strings[i]);
2606 matchchars = sizeof(t) - 1;
2607 if(resultbuf.numstrings > 0)
2609 p = resultbuf.strings[0];
2610 q = resultbuf.strings[resultbuf.numstrings - 1];
2611 for(; *p && *p == *q; ++p, ++q);
2612 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2614 if(dirbuf.numstrings > 0)
2616 p = dirbuf.strings[0];
2617 q = dirbuf.strings[dirbuf.numstrings - 1];
2618 for(; *p && *p == *q; ++p, ++q);
2619 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2621 // now p points to the first non-equal character, or to the end
2622 // of resultbuf.strings[0]. We want to append the characters
2623 // from resultbuf.strings[0] to (not including) p as these are
2624 // the unique prefix
2625 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2628 // first move the cursor
2629 key_linepos += (int)strlen(t) - (int)strlen(s);
2631 // and now do the actual work
2633 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
2634 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
2636 // and fix the cursor
2637 if(key_linepos > (int) strlen(key_lines[edit_line]))
2638 key_linepos = (int) strlen(key_lines[edit_line]);
2640 stringlistfreecontents(&resultbuf);
2641 stringlistfreecontents(&dirbuf);
2643 return; // bail out, when we complete for a command that wants a file name
2648 // Count number of possible matches and print them
2649 c = Cmd_CompleteCountPossible(s);
2652 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2653 Cmd_CompleteCommandPrint(s);
2655 v = Cvar_CompleteCountPossible(s);
2658 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2659 Cvar_CompleteCvarPrint(s);
2661 a = Cmd_CompleteAliasCountPossible(s);
2664 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2665 Cmd_CompleteAliasPrint(s);
2667 n = Nicks_CompleteCountPossible(key_lines[edit_line], key_linepos, s, true);
2670 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2671 Cmd_CompleteNicksPrint(n);
2674 if (!(c + v + a + n)) // No possible matches
2677 strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
2682 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2684 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2686 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2688 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2690 for (cmd_len = (int)strlen(s);;cmd_len++)
2693 for (i = 0; i < 3; i++)
2695 for (l = list[i];*l;l++)
2696 if ((*l)[cmd_len] != cmd[cmd_len])
2698 // all possible matches share this character, so we continue...
2701 // if all matches ended at the same position, stop
2702 // (this means there is only one match)
2708 // prevent a buffer overrun by limiting cmd_len according to remaining space
2709 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
2713 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
2714 key_linepos += cmd_len;
2715 // if there is only one match, add a space after it
2716 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
2719 { // was a nick, might have an offset, and needs colors ;) --blub
2720 key_linepos = pos - Nicks_offset[0];
2721 cmd_len = strlen(Nicks_list[0]);
2722 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 3 - pos);
2724 memcpy(&key_lines[edit_line][key_linepos] , Nicks_list[0], cmd_len);
2725 key_linepos += cmd_len;
2726 if(key_linepos < (int)(sizeof(key_lines[edit_line])-4)) // space for ^, X and space and \0
2727 key_linepos = Nicks_AddLastColor(key_lines[edit_line], key_linepos);
2729 key_lines[edit_line][key_linepos++] = ' ';
2733 // use strlcat to avoid a buffer overrun
2734 key_lines[edit_line][key_linepos] = 0;
2735 strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
2737 // free the command, cvar, and alias lists
2738 for (i = 0; i < 4; i++)
2740 Mem_Free((void *)list[i]);