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__)
32 float con_cursorspeed = 4;
34 // lines up from bottom to display
39 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
40 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
41 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
43 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
44 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
45 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
47 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
48 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
49 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)"};
50 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
51 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
52 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
53 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
54 cvar_t con_chatsound = {CVAR_SAVE, "con_chatsound","1", "enables chat sound to play on message"};
57 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)"};
59 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)"};
61 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)"};
65 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
66 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
67 "0: add nothing after completion. "
68 "1: add the last color after completion. "
69 "2: add a quote when starting a quote instead of the color. "
70 "4: will replace 1, will force color, even after a quote. "
71 "8: ignore non-alphanumerics. "
72 "16: ignore spaces. "};
73 #define NICKS_ADD_COLOR 1
74 #define NICKS_ADD_QUOTE 2
75 #define NICKS_FORCE_COLOR 4
76 #define NICKS_ALPHANUMERICS_ONLY 8
77 #define NICKS_NO_SPACES 16
79 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
80 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
81 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
86 qboolean con_initialized;
88 // used for server replies to rcon command
89 lhnetsocket_t *rcon_redirect_sock = NULL;
90 lhnetaddress_t *rcon_redirect_dest = NULL;
91 int rcon_redirect_bufferpos = 0;
92 char rcon_redirect_buffer[1400];
94 // generic functions for console buffers
96 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
99 buf->textsize = textsize;
100 buf->text = (char *) Mem_Alloc(mempool, textsize);
101 buf->maxlines = maxlines;
102 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
103 buf->lines_first = 0;
104 buf->lines_count = 0;
112 void ConBuffer_Clear (conbuffer_t *buf)
114 buf->lines_count = 0;
122 void ConBuffer_Shutdown(conbuffer_t *buf)
126 Mem_Free(buf->lines);
135 Notifies the console code about the current time
136 (and shifts back times of other entries when the time
140 void ConBuffer_FixTimes(conbuffer_t *buf)
143 if(buf->lines_count >= 1)
145 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
148 for(i = 0; i < buf->lines_count; ++i)
149 CONBUFFER_LINES(buf, i).addtime += diff;
158 Deletes the first line from the console history.
161 void ConBuffer_DeleteLine(conbuffer_t *buf)
163 if(buf->lines_count == 0)
166 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
171 ConBuffer_DeleteLastLine
173 Deletes the last line from the console history.
176 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
178 if(buf->lines_count == 0)
187 Checks if there is space for a line of the given length, and if yes, returns a
188 pointer to the start of such a space, and NULL otherwise.
191 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
193 if(len > buf->textsize)
195 if(buf->lines_count == 0)
199 char *firstline_start = buf->lines[buf->lines_first].start;
200 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
201 // the buffer is cyclic, so we first have two cases...
202 if(firstline_start < lastline_onepastend) // buffer is contiguous
205 if(len <= buf->text + buf->textsize - lastline_onepastend)
206 return lastline_onepastend;
208 else if(len <= firstline_start - buf->text)
213 else // buffer has a contiguous hole
215 if(len <= firstline_start - lastline_onepastend)
216 return lastline_onepastend;
227 Appends a given string as a new line to the console.
230 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
235 // developer_memory 1 during shutdown prints while conbuffer_t is being freed
239 ConBuffer_FixTimes(buf);
241 if(len >= buf->textsize)
244 // only display end of line.
245 line += len - buf->textsize + 1;
246 len = buf->textsize - 1;
248 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
249 ConBuffer_DeleteLine(buf);
250 memcpy(putpos, line, len);
254 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
256 p = &CONBUFFER_LINES_LAST(buf);
259 p->addtime = cl.time;
261 p->height = -1; // calculate when needed
264 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
268 start = buf->lines_count;
269 for(i = start - 1; i >= 0; --i)
271 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
273 if((l->mask & mask_must) != mask_must)
275 if(l->mask & mask_mustnot)
284 int Con_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
287 for(i = start + 1; i < buf->lines_count; ++i)
289 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
291 if((l->mask & mask_must) != mask_must)
293 if(l->mask & mask_mustnot)
302 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
304 static char copybuf[MAX_INPUTLINE];
305 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
306 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
307 strlcpy(copybuf, l->start, sz);
312 ==============================================================================
316 ==============================================================================
321 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
322 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"};
323 char log_dest_buffer[1400]; // UDP packet
324 size_t log_dest_buffer_pos;
325 unsigned int log_dest_buffer_appending;
326 char crt_log_file [MAX_OSPATH] = "";
327 qfile_t* logfile = NULL;
329 unsigned char* logqueue = NULL;
331 size_t logq_size = 0;
333 void Log_ConPrint (const char *msg);
340 static void Log_DestBuffer_Init(void)
342 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
343 log_dest_buffer_pos = 5;
351 void Log_DestBuffer_Flush(void)
353 lhnetaddress_t log_dest_addr;
354 lhnetsocket_t *log_dest_socket;
355 const char *s = log_dest_udp.string;
356 qboolean have_opened_temp_sockets = false;
357 if(s) if(log_dest_buffer_pos > 5)
359 ++log_dest_buffer_appending;
360 log_dest_buffer[log_dest_buffer_pos++] = 0;
362 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
364 have_opened_temp_sockets = true;
365 NetConn_OpenServerPorts(true);
368 while(COM_ParseToken_Console(&s))
369 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
371 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
373 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
375 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
378 if(have_opened_temp_sockets)
379 NetConn_CloseServerPorts();
380 --log_dest_buffer_appending;
382 log_dest_buffer_pos = 0;
390 const char* Log_Timestamp (const char *desc)
392 static char timestamp [128];
399 char timestring [64];
401 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
404 localtime_s (&crt_tm, &crt_time);
405 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
407 crt_tm = localtime (&crt_time);
408 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
412 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
414 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
427 if (logfile != NULL || log_file.string[0] == '\0')
430 logfile = FS_OpenRealFile(log_file.string, "a", false);
433 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
434 FS_Print (logfile, Log_Timestamp ("Log started"));
444 void Log_Close (void)
449 FS_Print (logfile, Log_Timestamp ("Log stopped"));
450 FS_Print (logfile, "\n");
454 crt_log_file[0] = '\0';
463 void Log_Start (void)
469 // Dump the contents of the log queue into the log file and free it
470 if (logqueue != NULL)
472 unsigned char *temp = logqueue;
477 FS_Write (logfile, temp, logq_ind);
478 if(*log_dest_udp.string)
480 for(pos = 0; pos < logq_ind; )
482 if(log_dest_buffer_pos == 0)
483 Log_DestBuffer_Init();
484 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
485 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
486 log_dest_buffer_pos += n;
487 Log_DestBuffer_Flush();
504 void Log_ConPrint (const char *msg)
506 static qboolean inprogress = false;
508 // don't allow feedback loops with memory error reports
513 // Until the host is completely initialized, we maintain a log queue
514 // to store the messages, since the log can't be started before
515 if (logqueue != NULL)
517 size_t remain = logq_size - logq_ind;
518 size_t len = strlen (msg);
520 // If we need to enlarge the log queue
523 size_t factor = ((logq_ind + len) / logq_size) + 1;
524 unsigned char* newqueue;
527 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
528 memcpy (newqueue, logqueue, logq_ind);
531 remain = logq_size - logq_ind;
533 memcpy (&logqueue[logq_ind], msg, len);
540 // Check if log_file has changed
541 if (strcmp (crt_log_file, log_file.string) != 0)
547 // If a log file is available
549 FS_Print (logfile, msg);
560 void Log_Printf (const char *logfilename, const char *fmt, ...)
564 file = FS_OpenRealFile(logfilename, "a", true);
569 va_start (argptr, fmt);
570 FS_VPrintf (file, fmt, argptr);
579 ==============================================================================
583 ==============================================================================
591 void Con_ToggleConsole_f (void)
593 // toggle the 'user wants console' bit
594 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
603 void Con_ClearNotify (void)
606 for(i = 0; i < CON_LINES_COUNT; ++i)
607 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
616 void Con_MessageMode_f (void)
618 key_dest = key_message;
619 chat_mode = 0; // "say"
630 void Con_MessageMode2_f (void)
632 key_dest = key_message;
633 chat_mode = 1; // "say_team"
643 void Con_CommandMode_f (void)
645 key_dest = key_message;
648 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
649 chat_bufferlen = strlen(chat_buffer);
651 chat_mode = -1; // command
659 void Con_CheckResize (void)
664 f = bound(1, con_textsize.value, 128);
665 if(f != con_textsize.value)
666 Cvar_SetValueQuick(&con_textsize, f);
667 width = (int)floor(vid_conwidth.value / con_textsize.value);
668 width = bound(1, width, con.textsize/4);
669 // FIXME uses con in a non abstracted way
671 if (width == con_linewidth)
674 con_linewidth = width;
676 for(i = 0; i < CON_LINES_COUNT; ++i)
677 CON_LINES(i).height = -1; // recalculate when next needed
683 //[515]: the simplest command ever
684 //LordHavoc: not so simple after I made it print usage...
685 static void Con_Maps_f (void)
689 Con_Printf("usage: maps [mapnameprefix]\n");
692 else if (Cmd_Argc() == 2)
693 GetMapList(Cmd_Argv(1), NULL, 0);
695 GetMapList("", NULL, 0);
698 void Con_ConDump_f (void)
704 Con_Printf("usage: condump <filename>\n");
707 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
710 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
713 for(i = 0; i < CON_LINES_COUNT; ++i)
715 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
716 FS_Write(file, "\n", 1);
721 void Con_Clear_f (void)
723 ConBuffer_Clear(&con);
734 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
736 // Allocate a log queue, this will be freed after configs are parsed
737 logq_size = MAX_INPUTLINE;
738 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
741 Cvar_RegisterVariable (&sys_colortranslation);
742 Cvar_RegisterVariable (&sys_specialcharactertranslation);
744 Cvar_RegisterVariable (&log_file);
745 Cvar_RegisterVariable (&log_dest_udp);
747 // support for the classic Quake option
748 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
749 if (COM_CheckParm ("-condebug") != 0)
750 Cvar_SetQuick (&log_file, "qconsole.log");
752 // register our cvars
753 Cvar_RegisterVariable (&con_chat);
754 Cvar_RegisterVariable (&con_chatpos);
755 Cvar_RegisterVariable (&con_chatsize);
756 Cvar_RegisterVariable (&con_chattime);
757 Cvar_RegisterVariable (&con_chatwidth);
758 Cvar_RegisterVariable (&con_notify);
759 Cvar_RegisterVariable (&con_notifyalign);
760 Cvar_RegisterVariable (&con_notifysize);
761 Cvar_RegisterVariable (&con_notifytime);
762 Cvar_RegisterVariable (&con_textsize);
763 Cvar_RegisterVariable (&con_chatsound);
766 Cvar_RegisterVariable (&con_nickcompletion);
767 Cvar_RegisterVariable (&con_nickcompletion_flags);
769 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
770 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
771 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
773 // register our commands
774 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
775 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
776 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
777 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
778 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
779 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
780 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
782 con_initialized = true;
783 Con_DPrint("Console initialized.\n");
786 void Con_Shutdown (void)
788 ConBuffer_Shutdown(&con);
795 Handles cursor positioning, line wrapping, etc
796 All console printing must go through this in order to be displayed
797 If no console is visible, the notify window will pop up.
800 void Con_PrintToHistory(const char *txt, int mask)
803 // \n goes to next line
804 // \r deletes current line and makes a new one
806 static int cr_pending = 0;
807 static char buf[CON_TEXTSIZE];
808 static int bufpos = 0;
810 if(!con.text) // FIXME uses a non-abstracted property of con
817 ConBuffer_DeleteLastLine(&con);
825 ConBuffer_AddLine(&con, buf, bufpos, mask);
830 ConBuffer_AddLine(&con, buf, bufpos, mask);
834 buf[bufpos++] = *txt;
835 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
837 ConBuffer_AddLine(&con, buf, bufpos, mask);
845 /*! The translation table between the graphical font and plain ASCII --KB */
846 static char qfont_table[256] = {
847 '\0', '#', '#', '#', '#', '.', '#', '#',
848 '#', 9, 10, '#', ' ', 13, '.', '.',
849 '[', ']', '0', '1', '2', '3', '4', '5',
850 '6', '7', '8', '9', '.', '<', '=', '>',
851 ' ', '!', '"', '#', '$', '%', '&', '\'',
852 '(', ')', '*', '+', ',', '-', '.', '/',
853 '0', '1', '2', '3', '4', '5', '6', '7',
854 '8', '9', ':', ';', '<', '=', '>', '?',
855 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
856 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
857 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
858 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
859 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
860 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
861 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
862 'x', 'y', 'z', '{', '|', '}', '~', '<',
864 '<', '=', '>', '#', '#', '.', '#', '#',
865 '#', '#', ' ', '#', ' ', '>', '.', '.',
866 '[', ']', '0', '1', '2', '3', '4', '5',
867 '6', '7', '8', '9', '.', '<', '=', '>',
868 ' ', '!', '"', '#', '$', '%', '&', '\'',
869 '(', ')', '*', '+', ',', '-', '.', '/',
870 '0', '1', '2', '3', '4', '5', '6', '7',
871 '8', '9', ':', ';', '<', '=', '>', '?',
872 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
873 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
874 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
875 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
876 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
877 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
878 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
879 'x', 'y', 'z', '{', '|', '}', '~', '<'
882 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest)
884 rcon_redirect_sock = sock;
885 rcon_redirect_dest = dest;
886 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
887 rcon_redirect_bufferpos = 5;
890 void Con_Rcon_Redirect_Flush(void)
892 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
893 NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
894 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
895 rcon_redirect_bufferpos = 5;
898 void Con_Rcon_Redirect_End(void)
900 Con_Rcon_Redirect_Flush();
901 rcon_redirect_dest = NULL;
902 rcon_redirect_sock = NULL;
905 void Con_Rcon_Redirect_Abort(void)
907 rcon_redirect_dest = NULL;
908 rcon_redirect_sock = NULL;
916 /// Adds a character to the rcon buffer.
917 void Con_Rcon_AddChar(int c)
919 if(log_dest_buffer_appending)
921 ++log_dest_buffer_appending;
923 // if this print is in response to an rcon command, add the character
924 // to the rcon redirect buffer
926 if (rcon_redirect_dest)
928 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
929 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
930 Con_Rcon_Redirect_Flush();
932 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
934 if(log_dest_buffer_pos == 0)
935 Log_DestBuffer_Init();
936 log_dest_buffer[log_dest_buffer_pos++] = c;
937 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
938 Log_DestBuffer_Flush();
941 log_dest_buffer_pos = 0;
943 --log_dest_buffer_appending;
947 * Convert an RGB color to its nearest quake color.
948 * I'll cheat on this a bit by translating the colors to HSV first,
949 * S and V decide if it's black or white, otherwise, H will decide the
951 * @param _r Red (0-255)
952 * @param _g Green (0-255)
953 * @param _b Blue (0-255)
954 * @return A quake color character.
956 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
958 float r = ((float)_r)/255.0;
959 float g = ((float)_g)/255.0;
960 float b = ((float)_b)/255.0;
961 float min = min(r, min(g, b));
962 float max = max(r, max(g, b));
964 int h; ///< Hue angle [0,360]
965 float s; ///< Saturation [0,1]
966 float v = max; ///< In HSV v == max [0,1]
973 // Saturation threshold. We now say 0.2 is the minimum value for a color!
976 // If the value is less than half, return a black color code.
977 // Otherwise return a white one.
983 // Let's get the hue angle to define some colors:
987 h = (int)(60.0 * (g-b)/(max-min))%360;
989 h = (int)(60.0 * (b-r)/(max-min) + 120);
990 else // if(max == b) redundant check
991 h = (int)(60.0 * (r-g)/(max-min) + 240);
993 if(h < 36) // *red* to orange
995 else if(h < 80) // orange over *yellow* to evilish-bright-green
997 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
999 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1001 else if(h < 270) // darkish blue over *dark blue* to cool purple
1003 else if(h < 330) // cool purple over *purple* to ugly swiny red
1005 else // ugly red to red closes the circly
1014 extern cvar_t timestamps;
1015 extern cvar_t timeformat;
1016 extern qboolean sys_nostdout;
1017 void Con_MaskPrint(int additionalmask, const char *msg)
1019 static int mask = 0;
1020 static int index = 0;
1021 static char line[MAX_INPUTLINE];
1025 Con_Rcon_AddChar(*msg);
1027 mask |= additionalmask;
1028 // if this is the beginning of a new line, print timestamp
1031 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1033 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1034 line[index++] = STRING_COLOR_TAG;
1035 // assert( STRING_COLOR_DEFAULT < 10 )
1036 line[index++] = STRING_COLOR_DEFAULT + '0';
1037 // special color codes for chat messages must always come first
1038 // for Con_PrintToHistory to work properly
1039 if (*msg == 1 || *msg == 2)
1044 if (con_chatsound.value)
1046 if(gamemode == GAME_NEXUIZ)
1048 if(msg[1] == '\r' && cl.foundtalk2wav)
1049 S_LocalSound ("sound/misc/talk2.wav");
1051 S_LocalSound ("sound/misc/talk.wav");
1055 if (msg[1] == '(' && cl.foundtalk2wav)
1056 S_LocalSound ("sound/misc/talk2.wav");
1058 S_LocalSound ("sound/misc/talk.wav");
1061 mask = CON_MASK_CHAT;
1063 line[index++] = STRING_COLOR_TAG;
1064 line[index++] = '3';
1066 Con_Rcon_AddChar(*msg);
1069 for (;*timestamp;index++, timestamp++)
1070 if (index < (int)sizeof(line) - 2)
1071 line[index] = *timestamp;
1073 // append the character
1074 line[index++] = *msg;
1075 // if this is a newline character, we have a complete line to print
1076 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1078 // terminate the line
1082 // send to scrollable buffer
1083 if (con_initialized && cls.state != ca_dedicated)
1085 Con_PrintToHistory(line, mask);
1088 // send to terminal or dedicated server window
1092 if(sys_specialcharactertranslation.integer)
1094 for (p = (unsigned char *) line;*p; p++)
1095 *p = qfont_table[*p];
1098 if(sys_colortranslation.integer == 1) // ANSI
1100 static char printline[MAX_INPUTLINE * 4 + 3];
1101 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1102 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1107 for(in = line, out = printline; *in; ++in)
1111 case STRING_COLOR_TAG:
1112 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1114 char r = tolower(in[2]);
1115 char g = tolower(in[3]);
1116 char b = tolower(in[4]);
1117 // it's a hex digit already, so the else part needs no check --blub
1118 if(isdigit(r)) r -= '0';
1120 if(isdigit(g)) g -= '0';
1122 if(isdigit(b)) b -= '0';
1125 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1126 in += 3; // 3 only, the switch down there does the fourth
1133 case STRING_COLOR_TAG:
1135 *out++ = STRING_COLOR_TAG;
1141 if(lastcolor == 0) break; else lastcolor = 0;
1142 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1147 if(lastcolor == 1) break; else lastcolor = 1;
1148 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1153 if(lastcolor == 2) break; else lastcolor = 2;
1154 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1159 if(lastcolor == 3) break; else lastcolor = 3;
1160 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1165 if(lastcolor == 4) break; else lastcolor = 4;
1166 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1171 if(lastcolor == 5) break; else lastcolor = 5;
1172 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1177 if(lastcolor == 6) break; else lastcolor = 6;
1178 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1183 // bold normal color
1185 if(lastcolor == 8) break; else lastcolor = 8;
1186 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1189 *out++ = STRING_COLOR_TAG;
1196 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1213 Sys_PrintToTerminal(printline);
1215 else if(sys_colortranslation.integer == 2) // Quake
1217 Sys_PrintToTerminal(line);
1221 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1224 for(in = line, out = printline; *in; ++in)
1228 case STRING_COLOR_TAG:
1231 case STRING_COLOR_RGB_TAG_CHAR:
1232 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1237 *out++ = STRING_COLOR_TAG;
1238 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1241 case STRING_COLOR_TAG:
1243 *out++ = STRING_COLOR_TAG;
1258 *out++ = STRING_COLOR_TAG;
1268 Sys_PrintToTerminal(printline);
1271 // empty the line buffer
1282 void Con_MaskPrintf(int mask, const char *fmt, ...)
1285 char msg[MAX_INPUTLINE];
1287 va_start(argptr,fmt);
1288 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1291 Con_MaskPrint(mask, msg);
1299 void Con_Print(const char *msg)
1301 Con_MaskPrint(CON_MASK_PRINT, msg);
1309 void Con_Printf(const char *fmt, ...)
1312 char msg[MAX_INPUTLINE];
1314 va_start(argptr,fmt);
1315 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1318 Con_MaskPrint(CON_MASK_PRINT, msg);
1326 void Con_DPrint(const char *msg)
1328 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1331 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1339 void Con_DPrintf(const char *fmt, ...)
1342 char msg[MAX_INPUTLINE];
1344 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1347 va_start(argptr,fmt);
1348 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1351 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1356 ==============================================================================
1360 ==============================================================================
1367 The input line scrolls horizontally if typing goes beyond the right edge
1369 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1372 void Con_DrawInput (void)
1376 char editlinecopy[MAX_INPUTLINE+1], *text;
1379 if (!key_consoleactive)
1380 return; // don't draw anything
1382 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1383 text = editlinecopy;
1385 // Advanced Console Editing by Radix radix@planetquake.com
1386 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1387 // use strlen of edit_line instead of key_linepos to allow editing
1388 // of early characters w/o erasing
1390 y = (int)strlen(text);
1392 // append enoug nul-bytes to cover the utf8-versions of the cursor too
1393 for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i)
1396 // add the cursor frame
1397 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1399 if (!utf8_enable.integer)
1400 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1401 else if (y + 3 < (int)sizeof(editlinecopy)-1)
1403 int ofs = u8_bytelen(text + key_linepos, 1);
1406 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1410 memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1411 memcpy(text + key_linepos, curbuf, len);
1414 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1417 // text[key_linepos + 1] = 0;
1419 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth(text, key_linepos, con_textsize.value, con_textsize.value, false, FONT_CONSOLE);
1424 DrawQ_String(x, con_vislines - con_textsize.value*2, text, y + 3, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, FONT_CONSOLE );
1427 // key_line[key_linepos] = 0;
1433 float alignment; // 0 = left, 0.5 = center, 1 = right
1439 const char *continuationString;
1442 int colorindex; // init to -1
1446 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1448 con_text_info_t *ti = (con_text_info_t *) passthrough;
1451 ti->colorindex = -1;
1452 return ti->fontsize * ti->font->maxwidth;
1455 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1456 else if(maxWidth == -1)
1457 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1460 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1461 // Note: this is NOT a Con_Printf, as it could print recursively
1466 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1472 (void) isContinuation;
1476 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1478 con_text_info_t *ti = (con_text_info_t *) passthrough;
1480 if(ti->y < ti->ymin - 0.001)
1482 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1486 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1487 if(isContinuation && *ti->continuationString)
1488 x += (int) DrawQ_String(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);
1490 DrawQ_String(x, ti->y, line, length, ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, &(ti->colorindex), false, ti->font);
1493 ti->y += ti->fontsize;
1497 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)
1501 int maxlines = (int) floor(height / fontsize + 0.01f);
1504 int continuationWidth = 0;
1506 double t = cl.time; // saved so it won't change
1509 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1510 ti.fontsize = fontsize;
1511 ti.alignment = alignment_x;
1514 ti.ymax = y + height;
1515 ti.continuationString = continuationString;
1518 Con_WordWidthFunc(&ti, NULL, &l, -1);
1519 l = strlen(continuationString);
1520 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1522 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1523 startidx = CON_LINES_COUNT;
1524 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1526 con_lineinfo_t *l = &CON_LINES(i);
1529 if((l->mask & mask_must) != mask_must)
1531 if(l->mask & mask_mustnot)
1533 if(maxage && (l->addtime < t - maxage))
1537 // Calculate its actual height...
1538 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1539 if(lines + mylines >= maxlines)
1541 nskip = lines + mylines - maxlines;
1550 // then center according to the calculated amount of lines...
1552 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1554 // then actually draw
1555 for(i = startidx; i < CON_LINES_COUNT; ++i)
1557 con_lineinfo_t *l = &CON_LINES(i);
1559 if((l->mask & mask_must) != mask_must)
1561 if(l->mask & mask_mustnot)
1563 if(maxage && (l->addtime < t - maxage))
1566 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1576 Draws the last few lines of output transparently over the game top
1579 void Con_DrawNotify (void)
1582 float chatstart, notifystart, inputsize;
1584 char temptext[MAX_INPUTLINE];
1588 ConBuffer_FixTimes(&con);
1590 numChatlines = con_chat.integer;
1591 chatpos = con_chatpos.integer;
1593 if (con_notify.integer < 0)
1594 Cvar_SetValueQuick(&con_notify, 0);
1595 if (gamemode == GAME_TRANSFUSION)
1596 v = 8; // vertical offset
1600 // GAME_NEXUIZ: center, otherwise left justify
1601 align = con_notifyalign.value;
1602 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1604 if(gamemode == GAME_NEXUIZ)
1612 // first chat, input line, then notify
1614 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1616 else if(chatpos > 0)
1618 // first notify, then (chatpos-1) empty lines, then chat, then input
1620 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1622 else // if(chatpos < 0)
1624 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1626 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1631 // just notify and input
1633 chatstart = 0; // shut off gcc warning
1636 v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_INPUT | CON_MASK_HIDENOTIFY | (numChatlines ? CON_MASK_CHAT : 0) | CON_MASK_DEVELOPER, con_notifytime.value, 0, notifystart, vid_conwidth.value, con_notify.value * con_notifysize.value, con_notifysize.value, align, 0.0, "");
1641 v = chatstart + numChatlines * con_chatsize.value;
1642 Con_DrawNotifyRect(CON_MASK_CHAT, CON_MASK_INPUT, con_chattime.value, 0, chatstart, vid_conwidth.value * con_chatwidth.value, v - chatstart, con_chatsize.value, 0.0, 1.0, (utf8_enable.integer ? "^3\xee\x80\x8c\xee\x80\x8c\xee\x80\x8c " : "^3\014\014\014 ")); // 015 is ·> character in conchars.tga
1645 if (key_dest == key_message)
1647 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1648 int colorindex = -1;
1650 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL);
1652 // LordHavoc: speedup, and other improvements
1654 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1656 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1658 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1661 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1662 x = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1665 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1673 Returns the height of a given console line; calculates it if necessary.
1676 int Con_LineHeight(int lineno)
1678 con_lineinfo_t *li = &CON_LINES(lineno);
1679 if(li->height == -1)
1681 float width = vid_conwidth.value;
1683 con_lineinfo_t *li = &CON_LINES(lineno);
1684 ti.fontsize = con_textsize.value;
1685 ti.font = FONT_CONSOLE;
1686 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1695 Draws a line of the console; returns its height in lines.
1696 If alpha is 0, the line is not drawn, but still wrapped and its height
1700 int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1702 float width = vid_conwidth.value;
1704 con_lineinfo_t *li = &CON_LINES(lineno);
1706 if((li->mask & mask_must) != mask_must)
1708 if((li->mask & mask_mustnot) != 0)
1711 ti.continuationString = "";
1713 ti.fontsize = con_textsize.value;
1714 ti.font = FONT_CONSOLE;
1716 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1721 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1728 Calculates the last visible line index and how much to show of it based on
1732 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1737 if(con_backscroll < 0)
1742 // now count until we saw con_backscroll actual lines
1743 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1744 if((CON_LINES(i).mask & mask_must) == mask_must)
1745 if((CON_LINES(i).mask & mask_mustnot) == 0)
1747 int h = Con_LineHeight(i);
1749 // line is the last visible line?
1751 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1753 *limitlast = lines_seen + h - con_backscroll;
1760 // if we get here, no line was on screen - scroll so that one line is
1762 con_backscroll = lines_seen - 1;
1770 Draws the console with the solid background
1771 The typing input line at the bottom should only be drawn if typing is allowed
1774 void Con_DrawConsole (int lines)
1777 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1778 cachepic_t *conbackpic;
1783 if (con_backscroll < 0)
1786 con_vislines = lines;
1788 // draw the background
1789 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic("gfx/conback") : NULL;
1790 if (conbackpic && conbackpic->tex != r_texture_notexture)
1791 DrawQ_Pic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, cls.signon == SIGNONS ? scr_conalpha.value : 1.0f, 0); // always full alpha when not in game
1793 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, cls.signon == SIGNONS ? scr_conalpha.value : 1.0f, 0); // always full alpha when not in game
1794 DrawQ_String(vid_conwidth.integer - DrawQ_TextWidth(engineversion, 0, con_textsize.value, con_textsize.value, false, FONT_CONSOLE), lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0, NULL, true, FONT_CONSOLE);
1800 int count = CON_LINES_COUNT;
1801 float ymax = con_vislines - 2 * con_textsize.value;
1802 float y = ymax + con_textsize.value * con_backscroll;
1803 for (i = 0;i < count && y >= 0;i++)
1804 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
1805 // fix any excessive scrollback for the next frame
1806 if (i >= count && y >= 0)
1808 con_backscroll -= (int)(y / con_textsize.value);
1809 if (con_backscroll < 0)
1814 if(CON_LINES_COUNT > 0)
1816 int i, last, limitlast;
1818 float ymax = con_vislines - 2 * con_textsize.value;
1819 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1820 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1821 y = ymax - con_textsize.value;
1824 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1829 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
1831 break; // top of console buffer
1833 break; // top of console window
1840 // draw the input prompt, user text, and cursor if desired
1848 Prints not only map filename, but also
1849 its format (q1/q2/q3/hl) and even its message
1851 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1852 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1853 //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
1854 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1855 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1859 int i, k, max, p, o, min;
1862 unsigned char buf[1024];
1864 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1865 t = FS_Search(message, 1, true);
1868 if (t->numfilenames > 1)
1869 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1870 len = (unsigned char *)Z_Malloc(t->numfilenames);
1872 for(max=i=0;i<t->numfilenames;i++)
1874 k = (int)strlen(t->filenames[i]);
1884 for(i=0;i<t->numfilenames;i++)
1886 int lumpofs = 0, lumplen = 0;
1887 char *entities = NULL;
1888 const char *data = NULL;
1890 char entfilename[MAX_QPATH];
1891 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1893 f = FS_OpenVirtualFile(t->filenames[i], true);
1896 memset(buf, 0, 1024);
1897 FS_Read(f, buf, 1024);
1898 if (!memcmp(buf, "IBSP", 4))
1900 p = LittleLong(((int *)buf)[1]);
1901 if (p == Q3BSPVERSION)
1903 q3dheader_t *header = (q3dheader_t *)buf;
1904 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1905 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1907 else if (p == Q2BSPVERSION)
1909 q2dheader_t *header = (q2dheader_t *)buf;
1910 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1911 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1914 else if((p = BuffLittleLong(buf)) == BSPVERSION || p == 30)
1916 dheader_t *header = (dheader_t *)buf;
1917 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1918 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1922 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1923 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1924 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1925 if (!entities && lumplen >= 10)
1927 FS_Seek(f, lumpofs, SEEK_SET);
1928 entities = (char *)Z_Malloc(lumplen + 1);
1929 FS_Read(f, entities, lumplen);
1933 // if there are entities to parse, a missing message key just
1934 // means there is no title, so clear the message string now
1940 if (!COM_ParseToken_Simple(&data, false, false))
1942 if (com_token[0] == '{')
1944 if (com_token[0] == '}')
1946 // skip leading whitespace
1947 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
1948 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
1949 keyname[l] = com_token[k+l];
1951 if (!COM_ParseToken_Simple(&data, false, false))
1953 if (developer_extra.integer)
1954 Con_DPrintf("key: %s %s\n", keyname, com_token);
1955 if (!strcmp(keyname, "message"))
1957 // get the message contents
1958 strlcpy(message, com_token, sizeof(message));
1968 *(t->filenames[i]+len[i]+5) = 0;
1971 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1972 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1973 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1974 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1975 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1977 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1982 k = *(t->filenames[0]+5+p);
1985 for(i=1;i<t->numfilenames;i++)
1986 if(*(t->filenames[i]+5+p) != k)
1990 if(p > o && completedname && completednamebufferlength > 0)
1992 memset(completedname, 0, completednamebufferlength);
1993 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2003 New function for tab-completion system
2004 Added by EvilTypeGuy
2005 MEGA Thanks to Taniwha
2008 void Con_DisplayList(const char **list)
2010 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2011 const char **walk = list;
2014 len = (int)strlen(*walk);
2022 len = (int)strlen(*list);
2023 if (pos + maxlen >= width) {
2029 for (i = 0; i < (maxlen - len); i++)
2041 SanitizeString strips color tags from the string in
2042 and writes the result on string out
2044 void SanitizeString(char *in, char *out)
2048 if(*in == STRING_COLOR_TAG)
2053 out[0] = STRING_COLOR_TAG;
2057 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2064 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2067 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2069 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2076 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2081 else if (*in != STRING_COLOR_TAG)
2084 *out = qfont_table[*(unsigned char*)in];
2091 // Now it becomes TRICKY :D --blub
2092 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2093 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2094 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2095 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
2096 static int Nicks_matchpos;
2098 // co against <<:BLASTER:>> is true!?
2099 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2103 if(tolower(*a) == tolower(*b))
2117 return (*a < *b) ? -1 : 1;
2121 return (*a < *b) ? -1 : 1;
2125 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2128 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2130 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2131 return Nicks_strncasecmp_nospaces(a, b, a_len);
2132 return strncasecmp(a, b, a_len);
2135 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2137 // ignore non alphanumerics of B
2138 // if A contains a non-alphanumeric, B must contain it as well though!
2141 qboolean alnum_a, alnum_b;
2143 if(tolower(*a) == tolower(*b))
2145 if(*a == 0) // end of both strings, they're equal
2152 // not equal, end of one string?
2157 // ignore non alphanumerics
2158 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2159 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2160 if(!alnum_a) // b must contain this
2161 return (*a < *b) ? -1 : 1;
2164 // otherwise, both are alnum, they're just not equal, return the appropriate number
2166 return (*a < *b) ? -1 : 1;
2172 /* Nicks_CompleteCountPossible
2174 Count the number of possible nicks to complete
2176 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2184 if(!con_nickcompletion.integer)
2187 // changed that to 1
2188 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2191 for(i = 0; i < cl.maxclients; ++i)
2194 if(!cl.scores[p].name[0])
2197 SanitizeString(cl.scores[p].name, name);
2198 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2204 spos = pos - 1; // no need for a minimum of characters :)
2208 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2210 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2211 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2217 if(isCon && spos == 0)
2219 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2225 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2226 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2228 // the sanitized list
2229 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2232 Nicks_matchpos = match;
2235 Nicks_offset[count] = s - (&line[match]);
2236 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2243 void Cmd_CompleteNicksPrint(int count)
2246 for(i = 0; i < count; ++i)
2247 Con_Printf("%s\n", Nicks_list[i]);
2250 void Nicks_CutMatchesNormal(int count)
2252 // cut match 0 down to the longest possible completion
2255 c = strlen(Nicks_sanlist[0]) - 1;
2256 for(i = 1; i < count; ++i)
2258 l = strlen(Nicks_sanlist[i]) - 1;
2262 for(l = 0; l <= c; ++l)
2263 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2269 Nicks_sanlist[0][c+1] = 0;
2270 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2273 unsigned int Nicks_strcleanlen(const char *s)
2278 if( (*s >= 'a' && *s <= 'z') ||
2279 (*s >= 'A' && *s <= 'Z') ||
2280 (*s >= '0' && *s <= '9') ||
2288 void Nicks_CutMatchesAlphaNumeric(int count)
2290 // cut match 0 down to the longest possible completion
2293 char tempstr[sizeof(Nicks_sanlist[0])];
2295 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2297 c = strlen(Nicks_sanlist[0]);
2298 for(i = 0, l = 0; i < (int)c; ++i)
2300 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2301 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2302 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2304 tempstr[l++] = Nicks_sanlist[0][i];
2309 for(i = 1; i < count; ++i)
2312 b = Nicks_sanlist[i];
2322 if(tolower(*a) == tolower(*b))
2328 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2330 // b is alnum, so cut
2337 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2338 Nicks_CutMatchesNormal(count);
2339 //if(!Nicks_sanlist[0][0])
2340 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2342 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2343 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2347 void Nicks_CutMatchesNoSpaces(int count)
2349 // cut match 0 down to the longest possible completion
2352 char tempstr[sizeof(Nicks_sanlist[0])];
2355 c = strlen(Nicks_sanlist[0]);
2356 for(i = 0, l = 0; i < (int)c; ++i)
2358 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2360 tempstr[l++] = Nicks_sanlist[0][i];
2365 for(i = 1; i < count; ++i)
2368 b = Nicks_sanlist[i];
2378 if(tolower(*a) == tolower(*b))
2392 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2393 Nicks_CutMatchesNormal(count);
2394 //if(!Nicks_sanlist[0][0])
2395 //Con_Printf("TS: %s\n", tempstr);
2396 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2398 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2399 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2403 void Nicks_CutMatches(int count)
2405 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2406 Nicks_CutMatchesAlphaNumeric(count);
2407 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2408 Nicks_CutMatchesNoSpaces(count);
2410 Nicks_CutMatchesNormal(count);
2413 const char **Nicks_CompleteBuildList(int count)
2417 // the list is freed by Con_CompleteCommandLine, so create a char**
2418 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2420 for(; bpos < count; ++bpos)
2421 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2423 Nicks_CutMatches(count);
2431 Restores the previous used color, after the autocompleted name.
2433 int Nicks_AddLastColor(char *buffer, int pos)
2435 qboolean quote_added = false;
2437 int color = STRING_COLOR_DEFAULT + '0';
2438 char r = 0, g = 0, b = 0;
2440 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2442 // we'll have to add a quote :)
2443 buffer[pos++] = '\"';
2447 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2449 // add color when no quote was added, or when flags &4?
2451 for(match = Nicks_matchpos-1; match >= 0; --match)
2453 if(buffer[match] == STRING_COLOR_TAG)
2455 if( isdigit(buffer[match+1]) )
2457 color = buffer[match+1];
2460 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2462 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2464 r = buffer[match+2];
2465 g = buffer[match+3];
2466 b = buffer[match+4];
2475 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2477 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2478 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2481 buffer[pos++] = STRING_COLOR_TAG;
2484 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2490 buffer[pos++] = color;
2495 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2498 /*if(!con_nickcompletion.integer)
2499 return; is tested in Nicks_CompletionCountPossible */
2500 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2506 msg = Nicks_list[0];
2507 len = min(size - Nicks_matchpos - 3, strlen(msg));
2508 memcpy(&buffer[Nicks_matchpos], msg, len);
2509 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2510 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2511 buffer[len++] = ' ';
2518 Con_Printf("\n%i possible nicks:\n", n);
2519 Cmd_CompleteNicksPrint(n);
2521 Nicks_CutMatches(n);
2523 msg = Nicks_sanlist[0];
2524 len = min(size - Nicks_matchpos, strlen(msg));
2525 memcpy(&buffer[Nicks_matchpos], msg, len);
2526 buffer[Nicks_matchpos + len] = 0;
2528 return Nicks_matchpos + len;
2535 Con_CompleteCommandLine
2537 New function for tab-completion system
2538 Added by EvilTypeGuy
2539 Thanks to Fett erich@heintz.com
2541 Enhanced to tab-complete map names by [515]
2544 void Con_CompleteCommandLine (void)
2546 const char *cmd = "";
2548 const char **list[4] = {0, 0, 0, 0};
2551 int c, v, a, i, cmd_len, pos, k;
2552 int n; // nicks --blub
2553 const char *space, *patterns;
2555 //find what we want to complete
2560 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2566 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2567 key_line[key_linepos] = 0; //hide them
2569 space = strchr(key_line + 1, ' ');
2570 if(space && pos == (space - key_line) + 1)
2572 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2574 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2575 if(patterns && !*patterns)
2576 patterns = NULL; // get rid of the empty string
2578 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2582 if (GetMapList(s, t, sizeof(t)))
2584 // first move the cursor
2585 key_linepos += (int)strlen(t) - (int)strlen(s);
2587 // and now do the actual work
2589 strlcat(key_line, t, MAX_INPUTLINE);
2590 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2592 // and fix the cursor
2593 if(key_linepos > (int) strlen(key_line))
2594 key_linepos = (int) strlen(key_line);
2603 stringlist_t resultbuf, dirbuf;
2606 // // store completion patterns (space separated) for command foo in con_completion_foo
2607 // set con_completion_foo "foodata/*.foodefault *.foo"
2610 // Note: patterns with slash are always treated as absolute
2611 // patterns; patterns without slash search in the innermost
2612 // directory the user specified. There is no way to "complete into"
2613 // a directory as of now, as directories seem to be unknown to the
2617 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2618 // set con_completion_playdemo "*.dem"
2619 // set con_completion_play "*.wav *.ogg"
2621 // TODO somehow add support for directories; these shall complete
2622 // to their name + an appended slash.
2624 stringlistinit(&resultbuf);
2625 stringlistinit(&dirbuf);
2626 while(COM_ParseToken_Simple(&patterns, false, false))
2629 if(strchr(com_token, '/'))
2631 search = FS_Search(com_token, true, true);
2635 const char *slash = strrchr(s, '/');
2638 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2639 strlcat(t, com_token, sizeof(t));
2640 search = FS_Search(t, true, true);
2643 search = FS_Search(com_token, true, true);
2647 for(i = 0; i < search->numfilenames; ++i)
2648 if(!strncmp(search->filenames[i], s, strlen(s)))
2649 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2650 stringlistappend(&resultbuf, search->filenames[i]);
2651 FS_FreeSearch(search);
2655 // In any case, add directory names
2658 const char *slash = strrchr(s, '/');
2661 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2662 strlcat(t, "*", sizeof(t));
2663 search = FS_Search(t, true, true);
2666 search = FS_Search("*", true, true);
2669 for(i = 0; i < search->numfilenames; ++i)
2670 if(!strncmp(search->filenames[i], s, strlen(s)))
2671 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2672 stringlistappend(&dirbuf, search->filenames[i]);
2673 FS_FreeSearch(search);
2677 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2680 unsigned int matchchars;
2681 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2683 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2686 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2688 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2692 stringlistsort(&resultbuf); // dirbuf is already sorted
2693 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2694 for(i = 0; i < dirbuf.numstrings; ++i)
2696 Con_Printf("%s/\n", dirbuf.strings[i]);
2698 for(i = 0; i < resultbuf.numstrings; ++i)
2700 Con_Printf("%s\n", resultbuf.strings[i]);
2702 matchchars = sizeof(t) - 1;
2703 if(resultbuf.numstrings > 0)
2705 p = resultbuf.strings[0];
2706 q = resultbuf.strings[resultbuf.numstrings - 1];
2707 for(; *p && *p == *q; ++p, ++q);
2708 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2710 if(dirbuf.numstrings > 0)
2712 p = dirbuf.strings[0];
2713 q = dirbuf.strings[dirbuf.numstrings - 1];
2714 for(; *p && *p == *q; ++p, ++q);
2715 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2717 // now p points to the first non-equal character, or to the end
2718 // of resultbuf.strings[0]. We want to append the characters
2719 // from resultbuf.strings[0] to (not including) p as these are
2720 // the unique prefix
2721 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2724 // first move the cursor
2725 key_linepos += (int)strlen(t) - (int)strlen(s);
2727 // and now do the actual work
2729 strlcat(key_line, t, MAX_INPUTLINE);
2730 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2732 // and fix the cursor
2733 if(key_linepos > (int) strlen(key_line))
2734 key_linepos = (int) strlen(key_line);
2736 stringlistfreecontents(&resultbuf);
2737 stringlistfreecontents(&dirbuf);
2739 return; // bail out, when we complete for a command that wants a file name
2744 // Count number of possible matches and print them
2745 c = Cmd_CompleteCountPossible(s);
2748 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2749 Cmd_CompleteCommandPrint(s);
2751 v = Cvar_CompleteCountPossible(s);
2754 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2755 Cvar_CompleteCvarPrint(s);
2757 a = Cmd_CompleteAliasCountPossible(s);
2760 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2761 Cmd_CompleteAliasPrint(s);
2763 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2766 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2767 Cmd_CompleteNicksPrint(n);
2770 if (!(c + v + a + n)) // No possible matches
2773 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2778 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2780 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2782 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2784 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2786 for (cmd_len = (int)strlen(s);;cmd_len++)
2789 for (i = 0; i < 3; i++)
2791 for (l = list[i];*l;l++)
2792 if ((*l)[cmd_len] != cmd[cmd_len])
2794 // all possible matches share this character, so we continue...
2797 // if all matches ended at the same position, stop
2798 // (this means there is only one match)
2804 // prevent a buffer overrun by limiting cmd_len according to remaining space
2805 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2809 memcpy(&key_line[key_linepos], cmd, cmd_len);
2810 key_linepos += cmd_len;
2811 // if there is only one match, add a space after it
2812 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2815 { // was a nick, might have an offset, and needs colors ;) --blub
2816 key_linepos = pos - Nicks_offset[0];
2817 cmd_len = strlen(Nicks_list[0]);
2818 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2820 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2821 key_linepos += cmd_len;
2822 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2823 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2825 key_line[key_linepos++] = ' ';
2829 // use strlcat to avoid a buffer overrun
2830 key_line[key_linepos] = 0;
2831 strlcat(key_line, s2, sizeof(key_line));
2833 // free the command, cvar, and alias lists
2834 for (i = 0; i < 4; i++)
2836 Mem_Free((void *)list[i]);