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 1048576
32 #define CON_MAXLINES 16384
34 // lines up from bottom to display
38 char con_text[CON_TEXTSIZE];
48 int height; // recalculated line height when needed (-1 to unset)
51 con_lineinfo con_lines[CON_MAXLINES];
53 int con_lines_first; // cyclic buffer
55 #define CON_LINES_IDX(i) ((con_lines_first + (i)) % CON_MAXLINES)
56 #define CON_LINES_UNIDX(i) (((i) - con_lines_first + CON_MAXLINES) % CON_MAXLINES)
57 #define CON_LINES_LAST CON_LINES_IDX(con_lines_count - 1)
58 #define CON_LINES(i) con_lines[CON_LINES_IDX(i)]
59 #define CON_LINES_PRED(i) (((i) + CON_MAXLINES - 1) % CON_MAXLINES)
60 #define CON_LINES_SUCC(i) (((i) + 1) % CON_MAXLINES)
62 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
63 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
64 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
66 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
67 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
68 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)"};
69 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
70 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
71 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
72 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
75 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)"};
77 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)"};
79 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)"};
83 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
84 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
85 "0: add nothing after completion. "
86 "1: add the last color after completion. "
87 "2: add a quote when starting a quote instead of the color. "
88 "4: will replace 1, will force color, even after a quote. "
89 "8: ignore non-alphanumerics. "
90 "16: ignore spaces. "};
91 #define NICKS_ADD_COLOR 1
92 #define NICKS_ADD_QUOTE 2
93 #define NICKS_FORCE_COLOR 4
94 #define NICKS_ALPHANUMERICS_ONLY 8
95 #define NICKS_NO_SPACES 16
97 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem"};
98 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem"};
99 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg"};
104 qboolean con_initialized;
106 // used for server replies to rcon command
107 lhnetsocket_t *rcon_redirect_sock = NULL;
108 lhnetaddress_t *rcon_redirect_dest = NULL;
109 int rcon_redirect_bufferpos = 0;
110 char rcon_redirect_buffer[1400];
114 ==============================================================================
118 ==============================================================================
121 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
122 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"};
123 char log_dest_buffer[1400]; // UDP packet
124 size_t log_dest_buffer_pos;
125 unsigned int log_dest_buffer_appending;
126 char crt_log_file [MAX_OSPATH] = "";
127 qfile_t* logfile = NULL;
129 unsigned char* logqueue = NULL;
131 size_t logq_size = 0;
133 void Log_ConPrint (const char *msg);
140 static void Log_DestBuffer_Init()
142 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
143 log_dest_buffer_pos = 5;
151 void Log_DestBuffer_Flush()
153 lhnetaddress_t log_dest_addr;
154 lhnetsocket_t *log_dest_socket;
155 const char *s = log_dest_udp.string;
156 qboolean have_opened_temp_sockets = false;
157 if(s) if(log_dest_buffer_pos > 5)
159 ++log_dest_buffer_appending;
160 log_dest_buffer[log_dest_buffer_pos++] = 0;
162 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
164 have_opened_temp_sockets = true;
165 NetConn_OpenServerPorts(true);
168 while(COM_ParseToken_Console(&s))
169 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
171 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
173 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
175 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
178 if(have_opened_temp_sockets)
179 NetConn_CloseServerPorts();
180 --log_dest_buffer_appending;
182 log_dest_buffer_pos = 0;
190 const char* Log_Timestamp (const char *desc)
192 static char timestamp [128];
199 char timestring [64];
201 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
204 localtime_s (&crt_tm, &crt_time);
205 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
207 crt_tm = localtime (&crt_time);
208 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
212 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
214 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
227 if (logfile != NULL || log_file.string[0] == '\0')
230 logfile = FS_OpenRealFile(log_file.string, "a", false);
233 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
234 FS_Print (logfile, Log_Timestamp ("Log started"));
244 void Log_Close (void)
249 FS_Print (logfile, Log_Timestamp ("Log stopped"));
250 FS_Print (logfile, "\n");
254 crt_log_file[0] = '\0';
263 void Log_Start (void)
269 // Dump the contents of the log queue into the log file and free it
270 if (logqueue != NULL)
272 unsigned char *temp = logqueue;
277 FS_Write (logfile, temp, logq_ind);
278 if(*log_dest_udp.string)
280 for(pos = 0; pos < logq_ind; )
282 if(log_dest_buffer_pos == 0)
283 Log_DestBuffer_Init();
284 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
285 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
286 log_dest_buffer_pos += n;
287 Log_DestBuffer_Flush();
304 void Log_ConPrint (const char *msg)
306 static qboolean inprogress = false;
308 // don't allow feedback loops with memory error reports
313 // Until the host is completely initialized, we maintain a log queue
314 // to store the messages, since the log can't be started before
315 if (logqueue != NULL)
317 size_t remain = logq_size - logq_ind;
318 size_t len = strlen (msg);
320 // If we need to enlarge the log queue
323 size_t factor = ((logq_ind + len) / logq_size) + 1;
324 unsigned char* newqueue;
327 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
328 memcpy (newqueue, logqueue, logq_ind);
331 remain = logq_size - logq_ind;
333 memcpy (&logqueue[logq_ind], msg, len);
340 // Check if log_file has changed
341 if (strcmp (crt_log_file, log_file.string) != 0)
347 // If a log file is available
349 FS_Print (logfile, msg);
360 void Log_Printf (const char *logfilename, const char *fmt, ...)
364 file = FS_OpenRealFile(logfilename, "a", true);
369 va_start (argptr, fmt);
370 FS_VPrintf (file, fmt, argptr);
379 ==============================================================================
383 ==============================================================================
391 void Con_ToggleConsole_f (void)
393 // toggle the 'user wants console' bit
394 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
403 void Con_Clear_f (void)
413 Clear all notify lines.
416 void Con_ClearNotify (void)
419 for(i = 0; i < con_lines_count; ++i)
420 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
429 void Con_MessageMode_f (void)
431 key_dest = key_message;
432 chat_mode = 0; // "say"
441 void Con_MessageMode2_f (void)
443 key_dest = key_message;
444 chat_mode = 1; // "say_team"
452 void Con_CommandMode_f (void)
454 key_dest = key_message;
457 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
458 chat_bufferlen = strlen(chat_buffer);
460 chat_mode = -1; // command
467 If the line width has changed, reformat the buffer.
470 void Con_CheckResize (void)
475 f = bound(1, con_textsize.value, 128);
476 if(f != con_textsize.value)
477 Cvar_SetValueQuick(&con_textsize, f);
478 width = (int)floor(vid_conwidth.value / con_textsize.value);
479 width = bound(1, width, CON_TEXTSIZE/4);
481 if (width == con_linewidth)
484 con_linewidth = width;
486 for(i = 0; i < con_lines_count; ++i)
487 CON_LINES(i).height = -1; // recalculate when next needed
493 //[515]: the simplest command ever
494 //LordHavoc: not so simple after I made it print usage...
495 static void Con_Maps_f (void)
499 Con_Printf("usage: maps [mapnameprefix]\n");
502 else if (Cmd_Argc() == 2)
503 GetMapList(Cmd_Argv(1), NULL, 0);
505 GetMapList("", NULL, 0);
508 void Con_ConDump_f (void)
514 Con_Printf("usage: condump <filename>\n");
517 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
520 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
523 for(i = 0; i < con_lines_count; ++i)
525 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
526 FS_Write(file, "\n", 1);
542 // Allocate a log queue, this will be freed after configs are parsed
543 logq_size = MAX_INPUTLINE;
544 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
547 Cvar_RegisterVariable (&sys_colortranslation);
548 Cvar_RegisterVariable (&sys_specialcharactertranslation);
550 Cvar_RegisterVariable (&log_file);
551 Cvar_RegisterVariable (&log_dest_udp);
553 // support for the classic Quake option
554 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
555 if (COM_CheckParm ("-condebug") != 0)
556 Cvar_SetQuick (&log_file, "qconsole.log");
558 // register our cvars
559 Cvar_RegisterVariable (&con_chat);
560 Cvar_RegisterVariable (&con_chatpos);
561 Cvar_RegisterVariable (&con_chatsize);
562 Cvar_RegisterVariable (&con_chattime);
563 Cvar_RegisterVariable (&con_chatwidth);
564 Cvar_RegisterVariable (&con_notify);
565 Cvar_RegisterVariable (&con_notifyalign);
566 Cvar_RegisterVariable (&con_notifysize);
567 Cvar_RegisterVariable (&con_notifytime);
568 Cvar_RegisterVariable (&con_textsize);
571 Cvar_RegisterVariable (&con_nickcompletion);
572 Cvar_RegisterVariable (&con_nickcompletion_flags);
574 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
575 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
576 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
578 // register our commands
579 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
580 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
581 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
582 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
583 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
584 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
585 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
587 con_initialized = true;
588 Con_DPrint("Console initialized.\n");
596 Deletes the first line from the console history.
599 void Con_DeleteLine()
601 if(con_lines_count == 0)
604 con_lines_first = CON_LINES_IDX(1);
611 Deletes the last line from the console history.
614 void Con_DeleteLastLine()
616 if(con_lines_count == 0)
625 Checks if there is space for a line of the given length, and if yes, returns a
626 pointer to the start of such a space, and NULL otherwise.
629 char *Con_BytesLeft(int len)
631 if(len > CON_TEXTSIZE)
633 if(con_lines_count == 0)
637 char *firstline_start = con_lines[con_lines_first].start;
638 char *lastline_onepastend = con_lines[CON_LINES_LAST].start + con_lines[CON_LINES_LAST].len;
639 // the buffer is cyclic, so we first have two cases...
640 if(firstline_start < lastline_onepastend) // buffer is contiguous
643 if(len <= con_text + CON_TEXTSIZE - lastline_onepastend)
644 return lastline_onepastend;
646 else if(len <= firstline_start - con_text)
651 else // buffer has a contiguous hole
653 if(len <= firstline_start - lastline_onepastend)
654 return lastline_onepastend;
665 Notifies the console code about the current time
666 (and shifts back times of other entries when the time
673 if(con_lines_count >= 1)
675 double diff = cl.time - (con_lines + CON_LINES_LAST)->addtime;
678 for(i = 0; i < con_lines_count; ++i)
679 CON_LINES(i).addtime += diff;
688 Appends a given string as a new line to the console.
691 void Con_AddLine(const char *line, int len, int mask)
698 if(len >= CON_TEXTSIZE)
701 // only display end of line.
702 line += len - CON_TEXTSIZE + 1;
703 len = CON_TEXTSIZE - 1;
705 while(!(putpos = Con_BytesLeft(len + 1)) || con_lines_count >= CON_MAXLINES)
707 memcpy(putpos, line, len);
711 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", con_lines_count, con_lines_first, CON_LINES_LAST);
713 p = con_lines + CON_LINES_LAST;
716 p->addtime = cl.time;
718 p->height = -1; // calculate when needed
725 Handles cursor positioning, line wrapping, etc
726 All console printing must go through this in order to be displayed
727 If no console is visible, the notify window will pop up.
730 void Con_PrintToHistory(const char *txt, int mask)
733 // \n goes to next line
734 // \r deletes current line and makes a new one
736 static int cr_pending = 0;
737 static char buf[CON_TEXTSIZE];
738 static int bufpos = 0;
744 Con_DeleteLastLine();
752 Con_AddLine(buf, bufpos, mask);
757 Con_AddLine(buf, bufpos, mask);
761 buf[bufpos++] = *txt;
762 if(bufpos >= CON_TEXTSIZE - 1)
764 Con_AddLine(buf, bufpos, mask);
772 /* The translation table between the graphical font and plain ASCII --KB */
773 static char qfont_table[256] = {
774 '\0', '#', '#', '#', '#', '.', '#', '#',
775 '#', 9, 10, '#', ' ', 13, '.', '.',
776 '[', ']', '0', '1', '2', '3', '4', '5',
777 '6', '7', '8', '9', '.', '<', '=', '>',
778 ' ', '!', '"', '#', '$', '%', '&', '\'',
779 '(', ')', '*', '+', ',', '-', '.', '/',
780 '0', '1', '2', '3', '4', '5', '6', '7',
781 '8', '9', ':', ';', '<', '=', '>', '?',
782 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
783 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
784 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
785 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
786 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
787 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
788 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
789 'x', 'y', 'z', '{', '|', '}', '~', '<',
791 '<', '=', '>', '#', '#', '.', '#', '#',
792 '#', '#', ' ', '#', ' ', '>', '.', '.',
793 '[', ']', '0', '1', '2', '3', '4', '5',
794 '6', '7', '8', '9', '.', '<', '=', '>',
795 ' ', '!', '"', '#', '$', '%', '&', '\'',
796 '(', ')', '*', '+', ',', '-', '.', '/',
797 '0', '1', '2', '3', '4', '5', '6', '7',
798 '8', '9', ':', ';', '<', '=', '>', '?',
799 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
800 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
801 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
802 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
803 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
804 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
805 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
806 'x', 'y', 'z', '{', '|', '}', '~', '<'
809 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest)
811 rcon_redirect_sock = sock;
812 rcon_redirect_dest = dest;
813 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
814 rcon_redirect_bufferpos = 5;
817 void Con_Rcon_Redirect_Flush()
819 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
820 NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
821 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
822 rcon_redirect_bufferpos = 5;
825 void Con_Rcon_Redirect_End()
827 Con_Rcon_Redirect_Flush();
828 rcon_redirect_dest = NULL;
829 rcon_redirect_sock = NULL;
832 void Con_Rcon_Redirect_Abort()
834 rcon_redirect_dest = NULL;
835 rcon_redirect_sock = NULL;
842 Adds a character to the rcon buffer
845 void Con_Rcon_AddChar(int c)
847 if(log_dest_buffer_appending)
849 ++log_dest_buffer_appending;
851 // if this print is in response to an rcon command, add the character
852 // to the rcon redirect buffer
854 if (rcon_redirect_dest)
856 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
857 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
858 Con_Rcon_Redirect_Flush();
860 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
862 if(log_dest_buffer_pos == 0)
863 Log_DestBuffer_Init();
864 log_dest_buffer[log_dest_buffer_pos++] = c;
865 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
866 Log_DestBuffer_Flush();
869 log_dest_buffer_pos = 0;
871 --log_dest_buffer_appending;
875 * Convert an RGB color to its nearest quake color.
876 * I'll cheat on this a bit by translating the colors to HSV first,
877 * S and V decide if it's black or white, otherwise, H will decide the
879 * @param _r Red (0-255)
880 * @param _g Green (0-255)
881 * @param _b Blue (0-255)
882 * @return A quake color character.
884 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
886 float r = ((float)_r)/255.0;
887 float g = ((float)_g)/255.0;
888 float b = ((float)_b)/255.0;
889 float min = min(r, min(g, b));
890 float max = max(r, max(g, b));
892 int h; ///< Hue angle [0,360]
893 float s; ///< Saturation [0,1]
894 float v = max; ///< In HSV v == max [0,1]
901 // Saturation threshold. We now say 0.2 is the minimum value for a color!
904 // If the value is less than half, return a black color code.
905 // Otherwise return a white one.
911 // Let's get the hue angle to define some colors:
915 h = (int)(60.0 * (g-b)/(max-min))%360;
917 h = (int)(60.0 * (b-r)/(max-min) + 120);
918 else // if(max == b) redundant check
919 h = (int)(60.0 * (r-g)/(max-min) + 240);
921 if(h < 36) // *red* to orange
923 else if(h < 80) // orange over *yellow* to evilish-bright-green
925 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
927 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
929 else if(h < 270) // darkish blue over *dark blue* to cool purple
931 else if(h < 330) // cool purple over *purple* to ugly swiny red
933 else // ugly red to red closes the circly
941 Prints to all appropriate console targets, and adds timestamps
944 extern cvar_t timestamps;
945 extern cvar_t timeformat;
946 extern qboolean sys_nostdout;
947 static void Con_Print_Internal(const char *msg, qboolean history)
950 static int index = 0;
951 static char line[MAX_INPUTLINE];
955 Con_Rcon_AddChar(*msg);
956 // if this is the beginning of a new line, print timestamp
959 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
961 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
962 line[index++] = STRING_COLOR_TAG;
963 // assert( STRING_COLOR_DEFAULT < 10 )
964 line[index++] = STRING_COLOR_DEFAULT + '0';
965 // special color codes for chat messages must always come first
966 // for Con_PrintToHistory to work properly
967 if (*msg == 1 || *msg == 2)
972 if(gamemode == GAME_NEXUIZ)
974 if(msg[1] == '\r' && cl.foundtalk2wav)
975 S_LocalSound ("sound/misc/talk2.wav");
977 S_LocalSound ("sound/misc/talk.wav");
981 if (msg[1] == '(' && cl.foundtalk2wav)
982 S_LocalSound ("sound/misc/talk2.wav");
984 S_LocalSound ("sound/misc/talk.wav");
986 mask = CON_MASK_CHAT;
988 line[index++] = STRING_COLOR_TAG;
991 Con_Rcon_AddChar(*msg);
994 for (;*timestamp;index++, timestamp++)
995 if (index < (int)sizeof(line) - 2)
996 line[index] = *timestamp;
998 // append the character
999 line[index++] = *msg;
1000 // if this is a newline character, we have a complete line to print
1001 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1003 // terminate the line
1007 // send to scrollable buffer
1008 if (history && con_initialized && cls.state != ca_dedicated)
1010 Con_PrintToHistory(line, mask);
1013 // send to terminal or dedicated server window
1017 if(sys_specialcharactertranslation.integer)
1019 for (p = (unsigned char *) line;*p; p++)
1020 *p = qfont_table[*p];
1023 if(sys_colortranslation.integer == 1) // ANSI
1025 static char printline[MAX_INPUTLINE * 4 + 3];
1026 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1027 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1032 for(in = line, out = printline; *in; ++in)
1036 case STRING_COLOR_TAG:
1037 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1039 char r = tolower(in[2]);
1040 char g = tolower(in[3]);
1041 char b = tolower(in[4]);
1042 // it's a hex digit already, so the else part needs no check --blub
1043 if(isdigit(r)) r -= '0';
1045 if(isdigit(g)) g -= '0';
1047 if(isdigit(b)) b -= '0';
1050 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1051 in += 3; // 3 only, the switch down there does the fourth
1058 case STRING_COLOR_TAG:
1060 *out++ = STRING_COLOR_TAG;
1066 if(lastcolor == 0) break; else lastcolor = 0;
1067 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1072 if(lastcolor == 1) break; else lastcolor = 1;
1073 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1078 if(lastcolor == 2) break; else lastcolor = 2;
1079 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1084 if(lastcolor == 3) break; else lastcolor = 3;
1085 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1090 if(lastcolor == 4) break; else lastcolor = 4;
1091 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1096 if(lastcolor == 5) break; else lastcolor = 5;
1097 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1102 if(lastcolor == 6) break; else lastcolor = 6;
1103 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1108 // bold normal color
1110 if(lastcolor == 8) break; else lastcolor = 8;
1111 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1114 *out++ = STRING_COLOR_TAG;
1121 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1138 Sys_PrintToTerminal(printline);
1140 else if(sys_colortranslation.integer == 2) // Quake
1142 Sys_PrintToTerminal(line);
1146 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1149 for(in = line, out = printline; *in; ++in)
1153 case STRING_COLOR_TAG:
1156 case STRING_COLOR_RGB_TAG_CHAR:
1157 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1162 *out++ = STRING_COLOR_TAG;
1163 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1166 case STRING_COLOR_TAG:
1168 *out++ = STRING_COLOR_TAG;
1183 *out++ = STRING_COLOR_TAG;
1193 Sys_PrintToTerminal(printline);
1196 // empty the line buffer
1201 void Con_Print(const char *msg)
1203 Con_Print_Internal(msg, true);
1205 void Con_PrintNotToHistory(const char *msg)
1207 Con_Print_Internal(msg, false);
1215 Prints to all appropriate console targets
1218 void Con_Printf(const char *fmt, ...)
1221 char msg[MAX_INPUTLINE];
1223 va_start(argptr,fmt);
1224 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1234 A Con_Print that only shows up if the "developer" cvar is set
1237 void Con_DPrint(const char *msg)
1239 if (!developer.integer)
1240 return; // don't confuse non-developers with techie stuff...
1248 A Con_Printf that only shows up if the "developer" cvar is set
1251 void Con_DPrintf(const char *fmt, ...)
1254 char msg[MAX_INPUTLINE];
1256 if (!developer.integer)
1257 return; // don't confuse non-developers with techie stuff...
1259 va_start(argptr,fmt);
1260 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1268 ==============================================================================
1272 ==============================================================================
1279 The input line scrolls horizontally if typing goes beyond the right edge
1281 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1284 void Con_DrawInput (void)
1288 char editlinecopy[MAX_INPUTLINE+1], *text;
1291 if (!key_consoleactive)
1292 return; // don't draw anything
1294 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1295 text = editlinecopy;
1297 // Advanced Console Editing by Radix radix@planetquake.com
1298 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1299 // use strlen of edit_line instead of key_linepos to allow editing
1300 // of early characters w/o erasing
1302 y = (int)strlen(text);
1304 // fill out remainder with spaces
1305 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1308 // add the cursor frame
1309 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1310 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1312 // text[key_linepos + 1] = 0;
1314 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1319 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 );
1322 // key_line[key_linepos] = 0;
1328 float alignment; // 0 = left, 0.5 = center, 1 = right
1334 const char *continuationString;
1337 int colorindex; // init to -1
1341 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1343 con_text_info_t *ti = (con_text_info_t *) passthrough;
1346 ti->colorindex = -1;
1347 return ti->fontsize * ti->font->maxwidth;
1350 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1351 else if(maxWidth == -1)
1352 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1355 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1356 // Note: this is NOT a Con_Printf, as it could print recursively
1361 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1367 (void) isContinuation;
1371 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1373 con_text_info_t *ti = (con_text_info_t *) passthrough;
1375 if(ti->y < ti->ymin - 0.001)
1377 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1381 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1382 if(isContinuation && *ti->continuationString)
1383 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);
1385 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);
1388 ti->y += ti->fontsize;
1392 int Con_FindPrevLine(int mask_must, int mask_mustnot, int start)
1396 start = con_lines_count;
1397 for(i = start - 1; i >= 0; --i)
1399 con_lineinfo *l = &CON_LINES(i);
1401 if((l->mask & mask_must) != mask_must)
1403 if(l->mask & mask_mustnot)
1412 int Con_FindNextLine(int mask_must, int mask_mustnot, int start)
1415 for(i = start + 1; i < con_lines_count; ++i)
1417 con_lineinfo *l = &CON_LINES(i);
1419 if((l->mask & mask_must) != mask_must)
1421 if(l->mask & mask_mustnot)
1430 const char *Con_GetLine(int i)
1432 static char buf[MAX_INPUTLINE];
1433 con_lineinfo *l = &CON_LINES(i);
1434 size_t sz = l->len+1 > sizeof(buf) ? sizeof(buf) : l->len+1;
1435 strlcpy(buf, l->start, sz);
1439 int Con_GetLineID(int i)
1441 return CON_LINES_IDX(i);
1444 int Con_GetLineByID(int i)
1446 i = CON_LINES_UNIDX(i);
1447 if(i >= con_lines_count)
1452 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)
1456 int maxlines = (int) floor(height / fontsize + 0.01f);
1459 int continuationWidth = 0;
1461 double t = cl.time; // saved so it won't change
1464 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1465 ti.fontsize = fontsize;
1466 ti.alignment = alignment_x;
1469 ti.ymax = y + height;
1470 ti.continuationString = continuationString;
1473 Con_WordWidthFunc(&ti, NULL, &l, -1);
1474 l = strlen(continuationString);
1475 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1477 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1478 startidx = con_lines_count;
1479 for(i = con_lines_count - 1; i >= 0; --i)
1481 con_lineinfo *l = &CON_LINES(i);
1484 if((l->mask & mask_must) != mask_must)
1486 if(l->mask & mask_mustnot)
1488 if(maxage && (l->addtime < t - maxage))
1492 // Calculate its actual height...
1493 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1494 if(lines + mylines >= maxlines)
1496 nskip = lines + mylines - maxlines;
1505 // then center according to the calculated amount of lines...
1507 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1509 // then actually draw
1510 for(i = startidx; i < con_lines_count; ++i)
1512 con_lineinfo *l = &CON_LINES(i);
1514 if((l->mask & mask_must) != mask_must)
1516 if(l->mask & mask_mustnot)
1518 if(maxage && (l->addtime < t - maxage))
1521 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1531 Draws the last few lines of output transparently over the game top
1534 void Con_DrawNotify (void)
1537 float chatstart, notifystart, inputsize;
1539 char temptext[MAX_INPUTLINE];
1545 numChatlines = con_chat.integer;
1546 chatpos = con_chatpos.integer;
1548 if (con_notify.integer < 0)
1549 Cvar_SetValueQuick(&con_notify, 0);
1550 if (gamemode == GAME_TRANSFUSION)
1551 v = 8; // vertical offset
1555 // GAME_NEXUIZ: center, otherwise left justify
1556 align = con_notifyalign.value;
1557 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1559 if(gamemode == GAME_NEXUIZ)
1567 // first chat, input line, then notify
1569 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1571 else if(chatpos > 0)
1573 // first notify, then (chatpos-1) empty lines, then chat, then input
1575 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1577 else // if(chatpos < 0)
1579 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1581 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1586 // just notify and input
1588 chatstart = 0; // shut off gcc warning
1591 v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_LOADEDHISTORY | CON_MASK_INPUT | 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, "");
1596 v = chatstart + numChatlines * con_chatsize.value;
1597 Con_DrawNotifyRect(CON_MASK_CHAT, CON_MASK_LOADEDHISTORY | CON_MASK_INPUT, 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
1600 if (key_dest == key_message)
1602 int colorindex = -1;
1604 // LordHavoc: speedup, and other improvements
1606 dpsnprintf(temptext, sizeof(temptext), "]%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1608 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1610 dpsnprintf(temptext, sizeof(temptext), "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1613 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1614 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1617 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1623 Con_MeasureConsoleLine
1625 Counts the number of lines for a line on the console.
1628 int Con_MeasureConsoleLine(int lineno)
1630 float width = vid_conwidth.value;
1633 if(con_lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1636 ti.fontsize = con_textsize.value;
1637 ti.font = FONT_CONSOLE;
1639 return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1646 Returns the height of a given console line; calculates it if necessary.
1649 int Con_LineHeight(int i)
1651 int h = con_lines[i].height;
1654 return con_lines[i].height = Con_MeasureConsoleLine(i);
1661 Draws a line of the console; returns its height in lines.
1662 If alpha is 0, the line is not drawn, but still wrapped and its height
1666 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1668 float width = vid_conwidth.value;
1671 if(con_lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1674 ti.continuationString = "";
1676 ti.fontsize = con_textsize.value;
1677 ti.font = FONT_CONSOLE;
1679 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1684 return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1691 Calculates the last visible line index and how much to show of it based on
1695 void Con_LastVisibleLine(int *last, int *limitlast)
1700 if(con_backscroll < 0)
1703 // now count until we saw con_backscroll actual lines
1704 for(ic = 0; ic < con_lines_count; ++ic)
1706 int i = CON_LINES_IDX(con_lines_count - 1 - ic);
1707 int h = Con_LineHeight(i);
1709 // line is the last visible line?
1710 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1713 *limitlast = lines_seen + h - con_backscroll;
1720 // if we get here, no line was on screen - scroll so that one line is
1722 con_backscroll = lines_seen - 1;
1723 *last = con_lines_first;
1731 Draws the console with the solid background
1732 The typing input line at the bottom should only be drawn if typing is allowed
1735 void Con_DrawConsole (int lines)
1737 int i, last, limitlast;
1743 con_vislines = lines;
1745 // draw the background
1746 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
1747 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);
1750 if(con_lines_count > 0)
1752 float ymax = con_vislines - 2 * con_textsize.value;
1753 Con_LastVisibleLine(&last, &limitlast);
1754 y = ymax - con_textsize.value;
1757 y += (con_lines[last].height - limitlast) * con_textsize.value;
1762 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1763 if(i == con_lines_first)
1764 break; // top of console buffer
1766 break; // top of console window
1768 i = CON_LINES_PRED(i);
1772 // draw the input prompt, user text, and cursor if desired
1780 Prints not only map filename, but also
1781 its format (q1/q2/q3/hl) and even its message
1783 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1784 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1785 //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
1786 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1787 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1791 int i, k, max, p, o, min;
1794 unsigned char buf[1024];
1796 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1797 t = FS_Search(message, 1, true);
1800 if (t->numfilenames > 1)
1801 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1802 len = (unsigned char *)Z_Malloc(t->numfilenames);
1804 for(max=i=0;i<t->numfilenames;i++)
1806 k = (int)strlen(t->filenames[i]);
1816 for(i=0;i<t->numfilenames;i++)
1818 int lumpofs = 0, lumplen = 0;
1819 char *entities = NULL;
1820 const char *data = NULL;
1822 char entfilename[MAX_QPATH];
1823 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1825 f = FS_OpenVirtualFile(t->filenames[i], true);
1828 memset(buf, 0, 1024);
1829 FS_Read(f, buf, 1024);
1830 if (!memcmp(buf, "IBSP", 4))
1832 p = LittleLong(((int *)buf)[1]);
1833 if (p == Q3BSPVERSION)
1835 q3dheader_t *header = (q3dheader_t *)buf;
1836 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1837 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1839 else if (p == Q2BSPVERSION)
1841 q2dheader_t *header = (q2dheader_t *)buf;
1842 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1843 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1846 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1848 dheader_t *header = (dheader_t *)buf;
1849 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1850 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1854 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1855 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1856 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1857 if (!entities && lumplen >= 10)
1859 FS_Seek(f, lumpofs, SEEK_SET);
1860 entities = (char *)Z_Malloc(lumplen + 1);
1861 FS_Read(f, entities, lumplen);
1865 // if there are entities to parse, a missing message key just
1866 // means there is no title, so clear the message string now
1872 if (!COM_ParseToken_Simple(&data, false, false))
1874 if (com_token[0] == '{')
1876 if (com_token[0] == '}')
1878 // skip leading whitespace
1879 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
1880 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
1881 keyname[l] = com_token[k+l];
1883 if (!COM_ParseToken_Simple(&data, false, false))
1885 if (developer.integer >= 100)
1886 Con_Printf("key: %s %s\n", keyname, com_token);
1887 if (!strcmp(keyname, "message"))
1889 // get the message contents
1890 strlcpy(message, com_token, sizeof(message));
1900 *(t->filenames[i]+len[i]+5) = 0;
1903 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1904 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1905 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1906 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1907 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1909 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1914 k = *(t->filenames[0]+5+p);
1917 for(i=1;i<t->numfilenames;i++)
1918 if(*(t->filenames[i]+5+p) != k)
1922 if(p > o && completedname && completednamebufferlength > 0)
1924 memset(completedname, 0, completednamebufferlength);
1925 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1935 New function for tab-completion system
1936 Added by EvilTypeGuy
1937 MEGA Thanks to Taniwha
1940 void Con_DisplayList(const char **list)
1942 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1943 const char **walk = list;
1946 len = (int)strlen(*walk);
1954 len = (int)strlen(*list);
1955 if (pos + maxlen >= width) {
1961 for (i = 0; i < (maxlen - len); i++)
1973 SanitizeString strips color tags from the string in
1974 and writes the result on string out
1976 void SanitizeString(char *in, char *out)
1980 if(*in == STRING_COLOR_TAG)
1985 out[0] = STRING_COLOR_TAG;
1989 else if (*in >= '0' && *in <= '9') // ^[0-9] found
1996 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
1999 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2001 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2008 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2013 else if (*in != STRING_COLOR_TAG)
2016 *out = qfont_table[*(unsigned char*)in];
2023 // Now it becomes TRICKY :D --blub
2024 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2025 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2026 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2027 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
2028 static int Nicks_matchpos;
2030 // co against <<:BLASTER:>> is true!?
2031 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2035 if(tolower(*a) == tolower(*b))
2049 return (*a < *b) ? -1 : 1;
2053 return (*a < *b) ? -1 : 1;
2057 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2060 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2062 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2063 return Nicks_strncasecmp_nospaces(a, b, a_len);
2064 return strncasecmp(a, b, a_len);
2067 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2069 // ignore non alphanumerics of B
2070 // if A contains a non-alphanumeric, B must contain it as well though!
2073 qboolean alnum_a, alnum_b;
2075 if(tolower(*a) == tolower(*b))
2077 if(*a == 0) // end of both strings, they're equal
2084 // not equal, end of one string?
2089 // ignore non alphanumerics
2090 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2091 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2092 if(!alnum_a) // b must contain this
2093 return (*a < *b) ? -1 : 1;
2096 // otherwise, both are alnum, they're just not equal, return the appropriate number
2098 return (*a < *b) ? -1 : 1;
2104 /* Nicks_CompleteCountPossible
2106 Count the number of possible nicks to complete
2108 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2117 if(!con_nickcompletion.integer)
2120 // changed that to 1
2121 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2124 for(i = 0; i < cl.maxclients; ++i)
2127 if(!cl.scores[p].name[0])
2130 SanitizeString(cl.scores[p].name, name);
2131 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2136 length = strlen(name);
2138 spos = pos - 1; // no need for a minimum of characters :)
2142 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2144 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2145 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2151 if(isCon && spos == 0)
2153 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2159 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2160 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2162 // the sanitized list
2163 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2166 Nicks_matchpos = match;
2169 Nicks_offset[count] = s - (&line[match]);
2170 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2177 void Cmd_CompleteNicksPrint(int count)
2180 for(i = 0; i < count; ++i)
2181 Con_Printf("%s\n", Nicks_list[i]);
2184 void Nicks_CutMatchesNormal(int count)
2186 // cut match 0 down to the longest possible completion
2189 c = strlen(Nicks_sanlist[0]) - 1;
2190 for(i = 1; i < count; ++i)
2192 l = strlen(Nicks_sanlist[i]) - 1;
2196 for(l = 0; l <= c; ++l)
2197 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2203 Nicks_sanlist[0][c+1] = 0;
2204 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2207 unsigned int Nicks_strcleanlen(const char *s)
2212 if( (*s >= 'a' && *s <= 'z') ||
2213 (*s >= 'A' && *s <= 'Z') ||
2214 (*s >= '0' && *s <= '9') ||
2222 void Nicks_CutMatchesAlphaNumeric(int count)
2224 // cut match 0 down to the longest possible completion
2227 char tempstr[sizeof(Nicks_sanlist[0])];
2229 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2231 c = strlen(Nicks_sanlist[0]);
2232 for(i = 0, l = 0; i < (int)c; ++i)
2234 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2235 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2236 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2238 tempstr[l++] = Nicks_sanlist[0][i];
2243 for(i = 1; i < count; ++i)
2246 b = Nicks_sanlist[i];
2256 if(tolower(*a) == tolower(*b))
2262 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2264 // b is alnum, so cut
2271 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2272 Nicks_CutMatchesNormal(count);
2273 //if(!Nicks_sanlist[0][0])
2274 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2276 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2277 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2281 void Nicks_CutMatchesNoSpaces(int count)
2283 // cut match 0 down to the longest possible completion
2286 char tempstr[sizeof(Nicks_sanlist[0])];
2289 c = strlen(Nicks_sanlist[0]);
2290 for(i = 0, l = 0; i < (int)c; ++i)
2292 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2294 tempstr[l++] = Nicks_sanlist[0][i];
2299 for(i = 1; i < count; ++i)
2302 b = Nicks_sanlist[i];
2312 if(tolower(*a) == tolower(*b))
2326 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2327 Nicks_CutMatchesNormal(count);
2328 //if(!Nicks_sanlist[0][0])
2329 //Con_Printf("TS: %s\n", tempstr);
2330 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2332 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2333 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2337 void Nicks_CutMatches(int count)
2339 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2340 Nicks_CutMatchesAlphaNumeric(count);
2341 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2342 Nicks_CutMatchesNoSpaces(count);
2344 Nicks_CutMatchesNormal(count);
2347 const char **Nicks_CompleteBuildList(int count)
2351 // the list is freed by Con_CompleteCommandLine, so create a char**
2352 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2354 for(; bpos < count; ++bpos)
2355 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2357 Nicks_CutMatches(count);
2365 Restores the previous used color, after the autocompleted name.
2367 int Nicks_AddLastColor(char *buffer, int pos)
2369 qboolean quote_added = false;
2371 int color = STRING_COLOR_DEFAULT + '0';
2372 char r = 0, g = 0, b = 0;
2374 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2376 // we'll have to add a quote :)
2377 buffer[pos++] = '\"';
2381 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2383 // add color when no quote was added, or when flags &4?
2385 for(match = Nicks_matchpos-1; match >= 0; --match)
2387 if(buffer[match] == STRING_COLOR_TAG)
2389 if( isdigit(buffer[match+1]) )
2391 color = buffer[match+1];
2394 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2396 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2398 r = buffer[match+2];
2399 g = buffer[match+3];
2400 b = buffer[match+4];
2409 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2411 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2412 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2415 buffer[pos++] = STRING_COLOR_TAG;
2418 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2424 buffer[pos++] = color;
2429 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2432 /*if(!con_nickcompletion.integer)
2433 return; is tested in Nicks_CompletionCountPossible */
2434 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2440 msg = Nicks_list[0];
2441 len = min(size - Nicks_matchpos - 3, strlen(msg));
2442 memcpy(&buffer[Nicks_matchpos], msg, len);
2443 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2444 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2445 buffer[len++] = ' ';
2452 Con_Printf("\n%i possible nicks:\n", n);
2453 Cmd_CompleteNicksPrint(n);
2455 Nicks_CutMatches(n);
2457 msg = Nicks_sanlist[0];
2458 len = min(size - Nicks_matchpos, strlen(msg));
2459 memcpy(&buffer[Nicks_matchpos], msg, len);
2460 buffer[Nicks_matchpos + len] = 0;
2462 return Nicks_matchpos + len;
2469 Con_CompleteCommandLine
2471 New function for tab-completion system
2472 Added by EvilTypeGuy
2473 Thanks to Fett erich@heintz.com
2475 Enhanced to tab-complete map names by [515]
2478 void Con_CompleteCommandLine (void)
2480 const char *cmd = "";
2482 const char **list[4] = {0, 0, 0, 0};
2485 int c, v, a, i, cmd_len, pos, k;
2486 int n; // nicks --blub
2487 const char *space, *patterns;
2489 //find what we want to complete
2494 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2500 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2501 key_line[key_linepos] = 0; //hide them
2503 space = strchr(key_line + 1, ' ');
2504 if(space && pos == (space - key_line) + 1)
2506 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2508 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2509 if(patterns && !*patterns)
2510 patterns = NULL; // get rid of the empty string
2512 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2516 if (GetMapList(s, t, sizeof(t)))
2518 // first move the cursor
2519 key_linepos += (int)strlen(t) - (int)strlen(s);
2521 // and now do the actual work
2523 strlcat(key_line, t, MAX_INPUTLINE);
2524 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2526 // and fix the cursor
2527 if(key_linepos > (int) strlen(key_line))
2528 key_linepos = (int) strlen(key_line);
2537 stringlist_t resultbuf, dirbuf;
2540 // // store completion patterns (space separated) for command foo in con_completion_foo
2541 // set con_completion_foo "foodata/*.foodefault *.foo"
2544 // Note: patterns with slash are always treated as absolute
2545 // patterns; patterns without slash search in the innermost
2546 // directory the user specified. There is no way to "complete into"
2547 // a directory as of now, as directories seem to be unknown to the
2551 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2552 // set con_completion_playdemo "*.dem"
2553 // set con_completion_play "*.wav *.ogg"
2555 // TODO somehow add support for directories; these shall complete
2556 // to their name + an appended slash.
2558 stringlistinit(&resultbuf);
2559 stringlistinit(&dirbuf);
2560 while(COM_ParseToken_Simple(&patterns, false, false))
2563 if(strchr(com_token, '/'))
2565 search = FS_Search(com_token, true, true);
2569 const char *slash = strrchr(s, '/');
2572 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2573 strlcat(t, com_token, sizeof(t));
2574 search = FS_Search(t, true, true);
2577 search = FS_Search(com_token, true, true);
2581 for(i = 0; i < search->numfilenames; ++i)
2582 if(!strncmp(search->filenames[i], s, strlen(s)))
2583 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2584 stringlistappend(&resultbuf, search->filenames[i]);
2585 FS_FreeSearch(search);
2589 // In any case, add directory names
2592 const char *slash = strrchr(s, '/');
2595 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2596 strlcat(t, "*", sizeof(t));
2597 search = FS_Search(t, true, true);
2600 search = FS_Search("*", true, true);
2603 for(i = 0; i < search->numfilenames; ++i)
2604 if(!strncmp(search->filenames[i], s, strlen(s)))
2605 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2606 stringlistappend(&dirbuf, search->filenames[i]);
2607 FS_FreeSearch(search);
2611 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2614 unsigned int matchchars;
2615 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2617 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2620 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2622 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2626 stringlistsort(&resultbuf); // dirbuf is already sorted
2627 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2628 for(i = 0; i < dirbuf.numstrings; ++i)
2630 Con_Printf("%s/\n", dirbuf.strings[i]);
2632 for(i = 0; i < resultbuf.numstrings; ++i)
2634 Con_Printf("%s\n", resultbuf.strings[i]);
2636 matchchars = sizeof(t) - 1;
2637 if(resultbuf.numstrings > 0)
2639 p = resultbuf.strings[0];
2640 q = resultbuf.strings[resultbuf.numstrings - 1];
2641 for(; *p && *p == *q; ++p, ++q);
2642 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2644 if(dirbuf.numstrings > 0)
2646 p = dirbuf.strings[0];
2647 q = dirbuf.strings[dirbuf.numstrings - 1];
2648 for(; *p && *p == *q; ++p, ++q);
2649 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2651 // now p points to the first non-equal character, or to the end
2652 // of resultbuf.strings[0]. We want to append the characters
2653 // from resultbuf.strings[0] to (not including) p as these are
2654 // the unique prefix
2655 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2658 // first move the cursor
2659 key_linepos += (int)strlen(t) - (int)strlen(s);
2661 // and now do the actual work
2663 strlcat(key_line, t, MAX_INPUTLINE);
2664 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2666 // and fix the cursor
2667 if(key_linepos > (int) strlen(key_line))
2668 key_linepos = (int) strlen(key_line);
2670 stringlistfreecontents(&resultbuf);
2671 stringlistfreecontents(&dirbuf);
2673 return; // bail out, when we complete for a command that wants a file name
2678 // Count number of possible matches and print them
2679 c = Cmd_CompleteCountPossible(s);
2682 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2683 Cmd_CompleteCommandPrint(s);
2685 v = Cvar_CompleteCountPossible(s);
2688 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2689 Cvar_CompleteCvarPrint(s);
2691 a = Cmd_CompleteAliasCountPossible(s);
2694 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2695 Cmd_CompleteAliasPrint(s);
2697 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2700 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2701 Cmd_CompleteNicksPrint(n);
2704 if (!(c + v + a + n)) // No possible matches
2707 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2712 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2714 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2716 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2718 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2720 for (cmd_len = (int)strlen(s);;cmd_len++)
2723 for (i = 0; i < 3; i++)
2725 for (l = list[i];*l;l++)
2726 if ((*l)[cmd_len] != cmd[cmd_len])
2728 // all possible matches share this character, so we continue...
2731 // if all matches ended at the same position, stop
2732 // (this means there is only one match)
2738 // prevent a buffer overrun by limiting cmd_len according to remaining space
2739 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2743 memcpy(&key_line[key_linepos], cmd, cmd_len);
2744 key_linepos += cmd_len;
2745 // if there is only one match, add a space after it
2746 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2749 { // was a nick, might have an offset, and needs colors ;) --blub
2750 key_linepos = pos - Nicks_offset[0];
2751 cmd_len = strlen(Nicks_list[0]);
2752 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2754 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2755 key_linepos += cmd_len;
2756 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2757 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2759 key_line[key_linepos++] = ' ';
2763 // use strlcat to avoid a buffer overrun
2764 key_line[key_linepos] = 0;
2765 strlcat(key_line, s2, sizeof(key_line));
2767 // free the command, cvar, and alias lists
2768 for (i = 0; i < 4; i++)
2770 Mem_Free((void *)list[i]);