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__)
33 float con_cursorspeed = 4;
35 #define CON_TEXTSIZE 1048576
36 #define CON_MAXLINES 16384
38 // lines up from bottom to display
43 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
44 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
45 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
47 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
48 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
49 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
51 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
52 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
53 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)"};
54 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
55 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
56 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
57 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
58 cvar_t con_chatsound = {CVAR_SAVE, "con_chatsound","1", "enables chat sound to play on message"};
61 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)"};
63 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)"};
65 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)"};
69 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
70 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
71 "0: add nothing after completion. "
72 "1: add the last color after completion. "
73 "2: add a quote when starting a quote instead of the color. "
74 "4: will replace 1, will force color, even after a quote. "
75 "8: ignore non-alphanumerics. "
76 "16: ignore spaces. "};
77 #define NICKS_ADD_COLOR 1
78 #define NICKS_ADD_QUOTE 2
79 #define NICKS_FORCE_COLOR 4
80 #define NICKS_ALPHANUMERICS_ONLY 8
81 #define NICKS_NO_SPACES 16
83 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
84 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
85 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
90 qboolean con_initialized;
92 // used for server replies to rcon command
93 lhnetsocket_t *rcon_redirect_sock = NULL;
94 lhnetaddress_t *rcon_redirect_dest = NULL;
95 int rcon_redirect_bufferpos = 0;
96 char rcon_redirect_buffer[1400];
98 // generic functions for console buffers
100 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
102 buf->textsize = textsize;
103 buf->text = (char *) Mem_Alloc(mempool, textsize);
104 buf->maxlines = maxlines;
105 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
106 buf->lines_first = 0;
107 buf->lines_count = 0;
115 void ConBuffer_Clear (conbuffer_t *buf)
117 buf->lines_count = 0;
125 void ConBuffer_Shutdown(conbuffer_t *buf)
128 Mem_Free(buf->lines);
137 Notifies the console code about the current time
138 (and shifts back times of other entries when the time
142 void ConBuffer_FixTimes(conbuffer_t *buf)
145 if(buf->lines_count >= 1)
147 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
150 for(i = 0; i < buf->lines_count; ++i)
151 CONBUFFER_LINES(buf, i).addtime += diff;
160 Deletes the first line from the console history.
163 void ConBuffer_DeleteLine(conbuffer_t *buf)
165 if(buf->lines_count == 0)
168 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
173 ConBuffer_DeleteLastLine
175 Deletes the last line from the console history.
178 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
180 if(buf->lines_count == 0)
189 Checks if there is space for a line of the given length, and if yes, returns a
190 pointer to the start of such a space, and NULL otherwise.
193 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
195 if(len > buf->textsize)
197 if(buf->lines_count == 0)
201 char *firstline_start = buf->lines[buf->lines_first].start;
202 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
203 // the buffer is cyclic, so we first have two cases...
204 if(firstline_start < lastline_onepastend) // buffer is contiguous
207 if(len <= buf->text + buf->textsize - lastline_onepastend)
208 return lastline_onepastend;
210 else if(len <= firstline_start - buf->text)
215 else // buffer has a contiguous hole
217 if(len <= firstline_start - lastline_onepastend)
218 return lastline_onepastend;
229 Appends a given string as a new line to the console.
232 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
237 ConBuffer_FixTimes(buf);
239 if(len >= buf->textsize)
242 // only display end of line.
243 line += len - buf->textsize + 1;
244 len = buf->textsize - 1;
246 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
247 ConBuffer_DeleteLine(buf);
248 memcpy(putpos, line, len);
252 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
254 p = &CONBUFFER_LINES_LAST(buf);
257 p->addtime = cl.time;
259 p->height = -1; // calculate when needed
262 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
266 start = buf->lines_count;
267 for(i = start - 1; i >= 0; --i)
269 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
271 if((l->mask & mask_must) != mask_must)
273 if(l->mask & mask_mustnot)
282 int Con_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
285 for(i = start + 1; i < buf->lines_count; ++i)
287 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
289 if((l->mask & mask_must) != mask_must)
291 if(l->mask & mask_mustnot)
300 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
302 static char copybuf[MAX_INPUTLINE];
303 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
304 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
305 strlcpy(copybuf, l->start, sz);
310 ==============================================================================
314 ==============================================================================
319 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
320 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"};
321 char log_dest_buffer[1400]; // UDP packet
322 size_t log_dest_buffer_pos;
323 unsigned int log_dest_buffer_appending;
324 char crt_log_file [MAX_OSPATH] = "";
325 qfile_t* logfile = NULL;
327 unsigned char* logqueue = NULL;
329 size_t logq_size = 0;
331 void Log_ConPrint (const char *msg);
338 static void Log_DestBuffer_Init(void)
340 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
341 log_dest_buffer_pos = 5;
349 void Log_DestBuffer_Flush(void)
351 lhnetaddress_t log_dest_addr;
352 lhnetsocket_t *log_dest_socket;
353 const char *s = log_dest_udp.string;
354 qboolean have_opened_temp_sockets = false;
355 if(s) if(log_dest_buffer_pos > 5)
357 ++log_dest_buffer_appending;
358 log_dest_buffer[log_dest_buffer_pos++] = 0;
360 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
362 have_opened_temp_sockets = true;
363 NetConn_OpenServerPorts(true);
366 while(COM_ParseToken_Console(&s))
367 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
369 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
371 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
373 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
376 if(have_opened_temp_sockets)
377 NetConn_CloseServerPorts();
378 --log_dest_buffer_appending;
380 log_dest_buffer_pos = 0;
388 const char* Log_Timestamp (const char *desc)
390 static char timestamp [128];
397 char timestring [64];
399 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
402 localtime_s (&crt_tm, &crt_time);
403 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
405 crt_tm = localtime (&crt_time);
406 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
410 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
412 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
425 if (logfile != NULL || log_file.string[0] == '\0')
428 logfile = FS_OpenRealFile(log_file.string, "a", false);
431 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
432 FS_Print (logfile, Log_Timestamp ("Log started"));
442 void Log_Close (void)
447 FS_Print (logfile, Log_Timestamp ("Log stopped"));
448 FS_Print (logfile, "\n");
452 crt_log_file[0] = '\0';
461 void Log_Start (void)
467 // Dump the contents of the log queue into the log file and free it
468 if (logqueue != NULL)
470 unsigned char *temp = logqueue;
475 FS_Write (logfile, temp, logq_ind);
476 if(*log_dest_udp.string)
478 for(pos = 0; pos < logq_ind; )
480 if(log_dest_buffer_pos == 0)
481 Log_DestBuffer_Init();
482 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
483 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
484 log_dest_buffer_pos += n;
485 Log_DestBuffer_Flush();
502 void Log_ConPrint (const char *msg)
504 static qboolean inprogress = false;
506 // don't allow feedback loops with memory error reports
511 // Until the host is completely initialized, we maintain a log queue
512 // to store the messages, since the log can't be started before
513 if (logqueue != NULL)
515 size_t remain = logq_size - logq_ind;
516 size_t len = strlen (msg);
518 // If we need to enlarge the log queue
521 size_t factor = ((logq_ind + len) / logq_size) + 1;
522 unsigned char* newqueue;
525 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
526 memcpy (newqueue, logqueue, logq_ind);
529 remain = logq_size - logq_ind;
531 memcpy (&logqueue[logq_ind], msg, len);
538 // Check if log_file has changed
539 if (strcmp (crt_log_file, log_file.string) != 0)
545 // If a log file is available
547 FS_Print (logfile, msg);
558 void Log_Printf (const char *logfilename, const char *fmt, ...)
562 file = FS_OpenRealFile(logfilename, "a", true);
567 va_start (argptr, fmt);
568 FS_VPrintf (file, fmt, argptr);
577 ==============================================================================
581 ==============================================================================
589 void Con_ToggleConsole_f (void)
591 // toggle the 'user wants console' bit
592 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
601 void Con_ClearNotify (void)
604 for(i = 0; i < CON_LINES_COUNT; ++i)
605 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
608 void Con_MessageMode_SetCursor(size_t pos)
610 chat_bufferlen = pos;
611 //chat_bufferlen = strlen(chat_buffer);
619 void Con_MessageMode_f (void)
621 key_dest = key_message;
622 chat_mode = 0; // "say"
626 UIM_EnterBuffer(chat_buffer, sizeof(chat_buffer), 0, &Con_MessageMode_SetCursor);
635 void Con_MessageMode2_f (void)
637 key_dest = key_message;
638 chat_mode = 1; // "say_team"
642 UIM_EnterBuffer(chat_buffer, sizeof(chat_buffer), 0, &Con_MessageMode_SetCursor);
650 void Con_CommandMode_f (void)
652 key_dest = key_message;
655 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
656 chat_bufferlen = strlen(chat_buffer);
658 chat_mode = -1; // command
660 UIM_EnterBuffer(chat_buffer, sizeof(chat_buffer), 0, &Con_MessageMode_SetCursor);
668 void Con_CheckResize (void)
673 f = bound(1, con_textsize.value, 128);
674 if(f != con_textsize.value)
675 Cvar_SetValueQuick(&con_textsize, f);
676 width = (int)floor(vid_conwidth.value / con_textsize.value);
677 width = bound(1, width, con.textsize/4);
678 // FIXME uses con in a non abstracted way
680 if (width == con_linewidth)
683 con_linewidth = width;
685 for(i = 0; i < CON_LINES_COUNT; ++i)
686 CON_LINES(i).height = -1; // recalculate when next needed
692 //[515]: the simplest command ever
693 //LordHavoc: not so simple after I made it print usage...
694 static void Con_Maps_f (void)
698 Con_Printf("usage: maps [mapnameprefix]\n");
701 else if (Cmd_Argc() == 2)
702 GetMapList(Cmd_Argv(1), NULL, 0);
704 GetMapList("", NULL, 0);
707 void Con_ConDump_f (void)
713 Con_Printf("usage: condump <filename>\n");
716 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
719 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
722 for(i = 0; i < CON_LINES_COUNT; ++i)
724 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
725 FS_Write(file, "\n", 1);
730 void Con_Clear_f (void)
732 ConBuffer_Clear(&con);
743 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
745 // Allocate a log queue, this will be freed after configs are parsed
746 logq_size = MAX_INPUTLINE;
747 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
750 Cvar_RegisterVariable (&sys_colortranslation);
751 Cvar_RegisterVariable (&sys_specialcharactertranslation);
753 Cvar_RegisterVariable (&log_file);
754 Cvar_RegisterVariable (&log_dest_udp);
756 // support for the classic Quake option
757 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
758 if (COM_CheckParm ("-condebug") != 0)
759 Cvar_SetQuick (&log_file, "qconsole.log");
761 // register our cvars
762 Cvar_RegisterVariable (&con_chat);
763 Cvar_RegisterVariable (&con_chatpos);
764 Cvar_RegisterVariable (&con_chatsize);
765 Cvar_RegisterVariable (&con_chattime);
766 Cvar_RegisterVariable (&con_chatwidth);
767 Cvar_RegisterVariable (&con_notify);
768 Cvar_RegisterVariable (&con_notifyalign);
769 Cvar_RegisterVariable (&con_notifysize);
770 Cvar_RegisterVariable (&con_notifytime);
771 Cvar_RegisterVariable (&con_textsize);
772 Cvar_RegisterVariable (&con_chatsound);
775 Cvar_RegisterVariable (&con_nickcompletion);
776 Cvar_RegisterVariable (&con_nickcompletion_flags);
778 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
779 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
780 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
782 // register our commands
783 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
784 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
785 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
786 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
787 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
788 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
789 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
791 con_initialized = true;
792 Con_DPrint("Console initialized.\n");
795 void Con_Shutdown (void)
797 ConBuffer_Shutdown(&con);
804 Handles cursor positioning, line wrapping, etc
805 All console printing must go through this in order to be displayed
806 If no console is visible, the notify window will pop up.
809 void Con_PrintToHistory(const char *txt, int mask)
812 // \n goes to next line
813 // \r deletes current line and makes a new one
815 static int cr_pending = 0;
816 static char buf[CON_TEXTSIZE];
817 static int bufpos = 0;
819 if(!con.text) // FIXME uses a non-abstracted property of con
826 ConBuffer_DeleteLastLine(&con);
834 ConBuffer_AddLine(&con, buf, bufpos, mask);
839 ConBuffer_AddLine(&con, buf, bufpos, mask);
843 buf[bufpos++] = *txt;
844 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
846 ConBuffer_AddLine(&con, buf, bufpos, mask);
854 /*! The translation table between the graphical font and plain ASCII --KB */
855 static char qfont_table[256] = {
856 '\0', '#', '#', '#', '#', '.', '#', '#',
857 '#', 9, 10, '#', ' ', 13, '.', '.',
858 '[', ']', '0', '1', '2', '3', '4', '5',
859 '6', '7', '8', '9', '.', '<', '=', '>',
860 ' ', '!', '"', '#', '$', '%', '&', '\'',
861 '(', ')', '*', '+', ',', '-', '.', '/',
862 '0', '1', '2', '3', '4', '5', '6', '7',
863 '8', '9', ':', ';', '<', '=', '>', '?',
864 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
865 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
866 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
867 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
868 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
869 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
870 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
871 'x', 'y', 'z', '{', '|', '}', '~', '<',
873 '<', '=', '>', '#', '#', '.', '#', '#',
874 '#', '#', ' ', '#', ' ', '>', '.', '.',
875 '[', ']', '0', '1', '2', '3', '4', '5',
876 '6', '7', '8', '9', '.', '<', '=', '>',
877 ' ', '!', '"', '#', '$', '%', '&', '\'',
878 '(', ')', '*', '+', ',', '-', '.', '/',
879 '0', '1', '2', '3', '4', '5', '6', '7',
880 '8', '9', ':', ';', '<', '=', '>', '?',
881 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
882 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
883 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
884 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
885 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
886 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
887 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
888 'x', 'y', 'z', '{', '|', '}', '~', '<'
891 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest)
893 rcon_redirect_sock = sock;
894 rcon_redirect_dest = dest;
895 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
896 rcon_redirect_bufferpos = 5;
899 void Con_Rcon_Redirect_Flush(void)
901 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
902 NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
903 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
904 rcon_redirect_bufferpos = 5;
907 void Con_Rcon_Redirect_End(void)
909 Con_Rcon_Redirect_Flush();
910 rcon_redirect_dest = NULL;
911 rcon_redirect_sock = NULL;
914 void Con_Rcon_Redirect_Abort(void)
916 rcon_redirect_dest = NULL;
917 rcon_redirect_sock = NULL;
925 /// Adds a character to the rcon buffer.
926 void Con_Rcon_AddChar(int c)
928 if(log_dest_buffer_appending)
930 ++log_dest_buffer_appending;
932 // if this print is in response to an rcon command, add the character
933 // to the rcon redirect buffer
935 if (rcon_redirect_dest)
937 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
938 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
939 Con_Rcon_Redirect_Flush();
941 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
943 if(log_dest_buffer_pos == 0)
944 Log_DestBuffer_Init();
945 log_dest_buffer[log_dest_buffer_pos++] = c;
946 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
947 Log_DestBuffer_Flush();
950 log_dest_buffer_pos = 0;
952 --log_dest_buffer_appending;
956 * Convert an RGB color to its nearest quake color.
957 * I'll cheat on this a bit by translating the colors to HSV first,
958 * S and V decide if it's black or white, otherwise, H will decide the
960 * @param _r Red (0-255)
961 * @param _g Green (0-255)
962 * @param _b Blue (0-255)
963 * @return A quake color character.
965 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
967 float r = ((float)_r)/255.0;
968 float g = ((float)_g)/255.0;
969 float b = ((float)_b)/255.0;
970 float min = min(r, min(g, b));
971 float max = max(r, max(g, b));
973 int h; ///< Hue angle [0,360]
974 float s; ///< Saturation [0,1]
975 float v = max; ///< In HSV v == max [0,1]
982 // Saturation threshold. We now say 0.2 is the minimum value for a color!
985 // If the value is less than half, return a black color code.
986 // Otherwise return a white one.
992 // Let's get the hue angle to define some colors:
996 h = (int)(60.0 * (g-b)/(max-min))%360;
998 h = (int)(60.0 * (b-r)/(max-min) + 120);
999 else // if(max == b) redundant check
1000 h = (int)(60.0 * (r-g)/(max-min) + 240);
1002 if(h < 36) // *red* to orange
1004 else if(h < 80) // orange over *yellow* to evilish-bright-green
1006 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1008 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1010 else if(h < 270) // darkish blue over *dark blue* to cool purple
1012 else if(h < 330) // cool purple over *purple* to ugly swiny red
1014 else // ugly red to red closes the circly
1023 extern cvar_t timestamps;
1024 extern cvar_t timeformat;
1025 extern qboolean sys_nostdout;
1026 void Con_Print(const char *msg)
1028 static int mask = 0;
1029 static int index = 0;
1030 static char line[MAX_INPUTLINE];
1034 Con_Rcon_AddChar(*msg);
1035 // if this is the beginning of a new line, print timestamp
1038 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1040 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1041 line[index++] = STRING_COLOR_TAG;
1042 // assert( STRING_COLOR_DEFAULT < 10 )
1043 line[index++] = STRING_COLOR_DEFAULT + '0';
1044 // special color codes for chat messages must always come first
1045 // for Con_PrintToHistory to work properly
1046 if (*msg == 1 || *msg == 2)
1051 if (con_chatsound.value)
1053 if(gamemode == GAME_NEXUIZ)
1055 if(msg[1] == '\r' && cl.foundtalk2wav)
1056 S_LocalSound ("sound/misc/talk2.wav");
1058 S_LocalSound ("sound/misc/talk.wav");
1062 if (msg[1] == '(' && cl.foundtalk2wav)
1063 S_LocalSound ("sound/misc/talk2.wav");
1065 S_LocalSound ("sound/misc/talk.wav");
1068 mask = CON_MASK_CHAT;
1070 line[index++] = STRING_COLOR_TAG;
1071 line[index++] = '3';
1073 Con_Rcon_AddChar(*msg);
1076 for (;*timestamp;index++, timestamp++)
1077 if (index < (int)sizeof(line) - 2)
1078 line[index] = *timestamp;
1080 // append the character
1081 line[index++] = *msg;
1082 // if this is a newline character, we have a complete line to print
1083 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1085 // terminate the line
1089 // send to scrollable buffer
1090 if (con_initialized && cls.state != ca_dedicated)
1092 Con_PrintToHistory(line, mask);
1095 // send to terminal or dedicated server window
1099 if(sys_specialcharactertranslation.integer)
1101 for (p = (unsigned char *) line;*p; p++)
1102 *p = qfont_table[*p];
1105 if(sys_colortranslation.integer == 1) // ANSI
1107 static char printline[MAX_INPUTLINE * 4 + 3];
1108 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1109 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1114 for(in = line, out = printline; *in; ++in)
1118 case STRING_COLOR_TAG:
1119 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1121 char r = tolower(in[2]);
1122 char g = tolower(in[3]);
1123 char b = tolower(in[4]);
1124 // it's a hex digit already, so the else part needs no check --blub
1125 if(isdigit(r)) r -= '0';
1127 if(isdigit(g)) g -= '0';
1129 if(isdigit(b)) b -= '0';
1132 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1133 in += 3; // 3 only, the switch down there does the fourth
1140 case STRING_COLOR_TAG:
1142 *out++ = STRING_COLOR_TAG;
1148 if(lastcolor == 0) break; else lastcolor = 0;
1149 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1154 if(lastcolor == 1) break; else lastcolor = 1;
1155 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1160 if(lastcolor == 2) break; else lastcolor = 2;
1161 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1166 if(lastcolor == 3) break; else lastcolor = 3;
1167 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1172 if(lastcolor == 4) break; else lastcolor = 4;
1173 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1178 if(lastcolor == 5) break; else lastcolor = 5;
1179 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1184 if(lastcolor == 6) break; else lastcolor = 6;
1185 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1190 // bold normal color
1192 if(lastcolor == 8) break; else lastcolor = 8;
1193 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1196 *out++ = STRING_COLOR_TAG;
1203 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1220 Sys_PrintToTerminal(printline);
1222 else if(sys_colortranslation.integer == 2) // Quake
1224 Sys_PrintToTerminal(line);
1228 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1231 for(in = line, out = printline; *in; ++in)
1235 case STRING_COLOR_TAG:
1238 case STRING_COLOR_RGB_TAG_CHAR:
1239 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1244 *out++ = STRING_COLOR_TAG;
1245 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1248 case STRING_COLOR_TAG:
1250 *out++ = STRING_COLOR_TAG;
1265 *out++ = STRING_COLOR_TAG;
1275 Sys_PrintToTerminal(printline);
1278 // empty the line buffer
1290 void Con_Printf(const char *fmt, ...)
1293 char msg[MAX_INPUTLINE];
1295 va_start(argptr,fmt);
1296 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1307 void Con_DPrint(const char *msg)
1309 if (!developer.integer)
1310 return; // don't confuse non-developers with techie stuff...
1319 void Con_DPrintf(const char *fmt, ...)
1322 char msg[MAX_INPUTLINE];
1324 if (!developer.integer)
1325 return; // don't confuse non-developers with techie stuff...
1327 va_start(argptr,fmt);
1328 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1336 ==============================================================================
1340 ==============================================================================
1347 The input line scrolls horizontally if typing goes beyond the right edge
1349 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1352 void Con_DrawInput (void)
1356 char editlinecopy[MAX_INPUTLINE+1], *text;
1359 if (!key_consoleactive)
1360 return; // don't draw anything
1362 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1363 text = editlinecopy;
1365 // Advanced Console Editing by Radix radix@planetquake.com
1366 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1367 // use strlen of edit_line instead of key_linepos to allow editing
1368 // of early characters w/o erasing
1370 y = (int)strlen(text);
1372 // fill out remainder with spaces
1373 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1376 // add the cursor frame
1377 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1379 if (utf8_disabled.integer)
1380 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1381 else if (y + 3 < (int)sizeof(editlinecopy)-1)
1383 int ofs = u8_bytelen(text + key_linepos, 1);
1386 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1390 memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1391 memcpy(text + key_linepos, curbuf, len);
1394 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1397 // text[key_linepos + 1] = 0;
1399 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1404 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 );
1407 // key_line[key_linepos] = 0;
1413 float alignment; // 0 = left, 0.5 = center, 1 = right
1419 const char *continuationString;
1422 int colorindex; // init to -1
1426 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1428 con_text_info_t *ti = (con_text_info_t *) passthrough;
1431 ti->colorindex = -1;
1432 return ti->fontsize * ti->font->maxwidth;
1436 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1437 else if(maxWidth == -1)
1438 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1441 return DrawQ_TextWidth_Font_UntilWidth_Size(w, ti->fontsize, ti->fontsize, length, false, ti->font, maxWidth);
1442 else if(maxWidth == -1)
1443 return DrawQ_TextWidth_Font_Size(w, ti->fontsize, ti->fontsize, *length, false, ti->font);
1446 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1447 // Note: this is NOT a Con_Printf, as it could print recursively
1452 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1458 (void) isContinuation;
1462 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1464 con_text_info_t *ti = (con_text_info_t *) passthrough;
1466 if(ti->y < ti->ymin - 0.001)
1468 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1472 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1473 if(isContinuation && *ti->continuationString)
1474 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);
1476 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);
1479 ti->y += ti->fontsize;
1483 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)
1487 int maxlines = (int) floor(height / fontsize + 0.01f);
1490 int continuationWidth = 0;
1492 double t = cl.time; // saved so it won't change
1495 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1496 ti.fontsize = fontsize;
1497 ti.alignment = alignment_x;
1500 ti.ymax = y + height;
1501 ti.continuationString = continuationString;
1504 Con_WordWidthFunc(&ti, NULL, &l, -1);
1505 l = strlen(continuationString);
1506 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1508 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1509 startidx = CON_LINES_COUNT;
1510 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1512 con_lineinfo_t *l = &CON_LINES(i);
1515 if((l->mask & mask_must) != mask_must)
1517 if(l->mask & mask_mustnot)
1519 if(maxage && (l->addtime < t - maxage))
1523 // Calculate its actual height...
1524 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1525 if(lines + mylines >= maxlines)
1527 nskip = lines + mylines - maxlines;
1536 // then center according to the calculated amount of lines...
1538 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1540 // then actually draw
1541 for(i = startidx; i < CON_LINES_COUNT; ++i)
1543 con_lineinfo_t *l = &CON_LINES(i);
1545 if((l->mask & mask_must) != mask_must)
1547 if(l->mask & mask_mustnot)
1549 if(maxage && (l->addtime < t - maxage))
1552 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1562 Draws the last few lines of output transparently over the game top
1565 void Con_DrawNotify (void)
1568 float chatstart, notifystart, inputsize;
1570 char temptext[MAX_INPUTLINE];
1574 ConBuffer_FixTimes(&con);
1576 numChatlines = con_chat.integer;
1577 chatpos = con_chatpos.integer;
1579 if (con_notify.integer < 0)
1580 Cvar_SetValueQuick(&con_notify, 0);
1581 if (gamemode == GAME_TRANSFUSION)
1582 v = 8; // vertical offset
1586 // GAME_NEXUIZ: center, otherwise left justify
1587 align = con_notifyalign.value;
1588 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1590 if(gamemode == GAME_NEXUIZ)
1598 // first chat, input line, then notify
1600 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1602 else if(chatpos > 0)
1604 // first notify, then (chatpos-1) empty lines, then chat, then input
1606 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1608 else // if(chatpos < 0)
1610 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1612 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1617 // just notify and input
1619 chatstart = 0; // shut off gcc warning
1622 v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, 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, "");
1627 v = chatstart + numChatlines * con_chatsize.value;
1628 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, /*"^3\014\014\014 "*/ "^3\xee\x80\x8d\xee\x80\x8d\xee\x80\x8d "); // 015 is ·> character in conchars.tga
1631 if (key_dest == key_message)
1633 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1634 int colorindex = -1;
1636 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL);
1638 // LordHavoc: speedup, and other improvements
1640 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1642 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1644 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1647 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1648 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1651 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1657 Con_MeasureConsoleLine
1659 Counts the number of lines for a line on the console.
1662 int Con_MeasureConsoleLine(int lineno)
1664 float width = vid_conwidth.value;
1666 con_lineinfo_t *li = &CON_LINES(lineno);
1668 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1671 ti.fontsize = con_textsize.value;
1672 ti.font = FONT_CONSOLE;
1674 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1681 Returns the height of a given console line; calculates it if necessary.
1684 int Con_LineHeight(int i)
1686 con_lineinfo_t *li = &CON_LINES(i);
1690 return li->height = Con_MeasureConsoleLine(i);
1697 Draws a line of the console; returns its height in lines.
1698 If alpha is 0, the line is not drawn, but still wrapped and its height
1702 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1704 float width = vid_conwidth.value;
1706 con_lineinfo_t *li = &CON_LINES(lineno);
1708 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
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 void Con_LastVisibleLine(int *last, int *limitlast)
1737 if(con_backscroll < 0)
1740 // now count until we saw con_backscroll actual lines
1741 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1743 int h = Con_LineHeight(i);
1745 // line is the last visible line?
1746 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1749 *limitlast = lines_seen + h - con_backscroll;
1756 // if we get here, no line was on screen - scroll so that one line is
1758 con_backscroll = lines_seen - 1;
1759 *last = con.lines_first;
1760 // FIXME uses con in a non abstracted way
1768 Draws the console with the solid background
1769 The typing input line at the bottom should only be drawn if typing is allowed
1772 void Con_DrawConsole (int lines)
1774 int i, last, limitlast;
1780 con_vislines = lines;
1782 // draw the background
1783 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
1784 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);
1787 if(CON_LINES_COUNT > 0)
1789 float ymax = con_vislines - 2 * con_textsize.value;
1790 Con_LastVisibleLine(&last, &limitlast);
1791 y = ymax - con_textsize.value;
1794 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1795 // FIXME uses con in a non abstracted way
1800 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1802 break; // top of console buffer
1804 break; // top of console window
1810 // draw the input prompt, user text, and cursor if desired
1818 Prints not only map filename, but also
1819 its format (q1/q2/q3/hl) and even its message
1821 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1822 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1823 //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
1824 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1825 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1829 int i, k, max, p, o, min;
1832 unsigned char buf[1024];
1834 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1835 t = FS_Search(message, 1, true);
1838 if (t->numfilenames > 1)
1839 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1840 len = (unsigned char *)Z_Malloc(t->numfilenames);
1842 for(max=i=0;i<t->numfilenames;i++)
1844 k = (int)strlen(t->filenames[i]);
1854 for(i=0;i<t->numfilenames;i++)
1856 int lumpofs = 0, lumplen = 0;
1857 char *entities = NULL;
1858 const char *data = NULL;
1860 char entfilename[MAX_QPATH];
1861 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1863 f = FS_OpenVirtualFile(t->filenames[i], true);
1866 memset(buf, 0, 1024);
1867 FS_Read(f, buf, 1024);
1868 if (!memcmp(buf, "IBSP", 4))
1870 p = LittleLong(((int *)buf)[1]);
1871 if (p == Q3BSPVERSION)
1873 q3dheader_t *header = (q3dheader_t *)buf;
1874 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1875 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1877 else if (p == Q2BSPVERSION)
1879 q2dheader_t *header = (q2dheader_t *)buf;
1880 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1881 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1884 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1886 dheader_t *header = (dheader_t *)buf;
1887 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1888 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1892 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1893 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1894 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1895 if (!entities && lumplen >= 10)
1897 FS_Seek(f, lumpofs, SEEK_SET);
1898 entities = (char *)Z_Malloc(lumplen + 1);
1899 FS_Read(f, entities, lumplen);
1903 // if there are entities to parse, a missing message key just
1904 // means there is no title, so clear the message string now
1910 if (!COM_ParseToken_Simple(&data, false, false))
1912 if (com_token[0] == '{')
1914 if (com_token[0] == '}')
1916 // skip leading whitespace
1917 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
1918 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
1919 keyname[l] = com_token[k+l];
1921 if (!COM_ParseToken_Simple(&data, false, false))
1923 if (developer.integer >= 100)
1924 Con_Printf("key: %s %s\n", keyname, com_token);
1925 if (!strcmp(keyname, "message"))
1927 // get the message contents
1928 strlcpy(message, com_token, sizeof(message));
1938 *(t->filenames[i]+len[i]+5) = 0;
1941 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1942 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1943 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1944 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1945 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1947 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1952 k = *(t->filenames[0]+5+p);
1955 for(i=1;i<t->numfilenames;i++)
1956 if(*(t->filenames[i]+5+p) != k)
1960 if(p > o && completedname && completednamebufferlength > 0)
1962 memset(completedname, 0, completednamebufferlength);
1963 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1973 New function for tab-completion system
1974 Added by EvilTypeGuy
1975 MEGA Thanks to Taniwha
1978 void Con_DisplayList(const char **list)
1980 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1981 const char **walk = list;
1984 len = (int)strlen(*walk);
1992 len = (int)strlen(*list);
1993 if (pos + maxlen >= width) {
1999 for (i = 0; i < (maxlen - len); i++)
2011 SanitizeString strips color tags from the string in
2012 and writes the result on string out
2014 void SanitizeString(char *in, char *out)
2018 if(*in == STRING_COLOR_TAG)
2023 out[0] = STRING_COLOR_TAG;
2027 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2034 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2037 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2039 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2046 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2051 else if (*in != STRING_COLOR_TAG)
2054 *out = qfont_table[*(unsigned char*)in];
2061 // Now it becomes TRICKY :D --blub
2062 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2063 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2064 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2065 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
2066 static int Nicks_matchpos;
2068 // co against <<:BLASTER:>> is true!?
2069 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2073 if(tolower(*a) == tolower(*b))
2087 return (*a < *b) ? -1 : 1;
2091 return (*a < *b) ? -1 : 1;
2095 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2098 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2100 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2101 return Nicks_strncasecmp_nospaces(a, b, a_len);
2102 return strncasecmp(a, b, a_len);
2105 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2107 // ignore non alphanumerics of B
2108 // if A contains a non-alphanumeric, B must contain it as well though!
2111 qboolean alnum_a, alnum_b;
2113 if(tolower(*a) == tolower(*b))
2115 if(*a == 0) // end of both strings, they're equal
2122 // not equal, end of one string?
2127 // ignore non alphanumerics
2128 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2129 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2130 if(!alnum_a) // b must contain this
2131 return (*a < *b) ? -1 : 1;
2134 // otherwise, both are alnum, they're just not equal, return the appropriate number
2136 return (*a < *b) ? -1 : 1;
2142 /* Nicks_CompleteCountPossible
2144 Count the number of possible nicks to complete
2146 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2155 if(!con_nickcompletion.integer)
2158 // changed that to 1
2159 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2162 for(i = 0; i < cl.maxclients; ++i)
2165 if(!cl.scores[p].name[0])
2168 SanitizeString(cl.scores[p].name, name);
2169 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2174 length = strlen(name);
2176 spos = pos - 1; // no need for a minimum of characters :)
2180 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2182 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2183 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2189 if(isCon && spos == 0)
2191 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2197 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2198 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2200 // the sanitized list
2201 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2204 Nicks_matchpos = match;
2207 Nicks_offset[count] = s - (&line[match]);
2208 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2215 void Cmd_CompleteNicksPrint(int count)
2218 for(i = 0; i < count; ++i)
2219 Con_Printf("%s\n", Nicks_list[i]);
2222 void Nicks_CutMatchesNormal(int count)
2224 // cut match 0 down to the longest possible completion
2227 c = strlen(Nicks_sanlist[0]) - 1;
2228 for(i = 1; i < count; ++i)
2230 l = strlen(Nicks_sanlist[i]) - 1;
2234 for(l = 0; l <= c; ++l)
2235 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2241 Nicks_sanlist[0][c+1] = 0;
2242 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2245 unsigned int Nicks_strcleanlen(const char *s)
2250 if( (*s >= 'a' && *s <= 'z') ||
2251 (*s >= 'A' && *s <= 'Z') ||
2252 (*s >= '0' && *s <= '9') ||
2260 void Nicks_CutMatchesAlphaNumeric(int count)
2262 // cut match 0 down to the longest possible completion
2265 char tempstr[sizeof(Nicks_sanlist[0])];
2267 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2269 c = strlen(Nicks_sanlist[0]);
2270 for(i = 0, l = 0; i < (int)c; ++i)
2272 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2273 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2274 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2276 tempstr[l++] = Nicks_sanlist[0][i];
2281 for(i = 1; i < count; ++i)
2284 b = Nicks_sanlist[i];
2294 if(tolower(*a) == tolower(*b))
2300 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2302 // b is alnum, so cut
2309 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2310 Nicks_CutMatchesNormal(count);
2311 //if(!Nicks_sanlist[0][0])
2312 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2314 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2315 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2319 void Nicks_CutMatchesNoSpaces(int count)
2321 // cut match 0 down to the longest possible completion
2324 char tempstr[sizeof(Nicks_sanlist[0])];
2327 c = strlen(Nicks_sanlist[0]);
2328 for(i = 0, l = 0; i < (int)c; ++i)
2330 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2332 tempstr[l++] = Nicks_sanlist[0][i];
2337 for(i = 1; i < count; ++i)
2340 b = Nicks_sanlist[i];
2350 if(tolower(*a) == tolower(*b))
2364 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2365 Nicks_CutMatchesNormal(count);
2366 //if(!Nicks_sanlist[0][0])
2367 //Con_Printf("TS: %s\n", tempstr);
2368 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2370 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2371 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2375 void Nicks_CutMatches(int count)
2377 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2378 Nicks_CutMatchesAlphaNumeric(count);
2379 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2380 Nicks_CutMatchesNoSpaces(count);
2382 Nicks_CutMatchesNormal(count);
2385 const char **Nicks_CompleteBuildList(int count)
2389 // the list is freed by Con_CompleteCommandLine, so create a char**
2390 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2392 for(; bpos < count; ++bpos)
2393 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2395 Nicks_CutMatches(count);
2403 Restores the previous used color, after the autocompleted name.
2405 int Nicks_AddLastColor(char *buffer, int pos)
2407 qboolean quote_added = false;
2409 int color = STRING_COLOR_DEFAULT + '0';
2410 char r = 0, g = 0, b = 0;
2412 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2414 // we'll have to add a quote :)
2415 buffer[pos++] = '\"';
2419 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2421 // add color when no quote was added, or when flags &4?
2423 for(match = Nicks_matchpos-1; match >= 0; --match)
2425 if(buffer[match] == STRING_COLOR_TAG)
2427 if( isdigit(buffer[match+1]) )
2429 color = buffer[match+1];
2432 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2434 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2436 r = buffer[match+2];
2437 g = buffer[match+3];
2438 b = buffer[match+4];
2447 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2449 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2450 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2453 buffer[pos++] = STRING_COLOR_TAG;
2456 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2462 buffer[pos++] = color;
2467 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2470 /*if(!con_nickcompletion.integer)
2471 return; is tested in Nicks_CompletionCountPossible */
2472 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2478 msg = Nicks_list[0];
2479 len = min(size - Nicks_matchpos - 3, strlen(msg));
2480 memcpy(&buffer[Nicks_matchpos], msg, len);
2481 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2482 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2483 buffer[len++] = ' ';
2490 Con_Printf("\n%i possible nicks:\n", n);
2491 Cmd_CompleteNicksPrint(n);
2493 Nicks_CutMatches(n);
2495 msg = Nicks_sanlist[0];
2496 len = min(size - Nicks_matchpos, strlen(msg));
2497 memcpy(&buffer[Nicks_matchpos], msg, len);
2498 buffer[Nicks_matchpos + len] = 0;
2500 return Nicks_matchpos + len;
2507 Con_CompleteCommandLine
2509 New function for tab-completion system
2510 Added by EvilTypeGuy
2511 Thanks to Fett erich@heintz.com
2513 Enhanced to tab-complete map names by [515]
2516 void Con_CompleteCommandLine (void)
2518 const char *cmd = "";
2520 const char **list[4] = {0, 0, 0, 0};
2523 int c, v, a, i, cmd_len, pos, k;
2524 int n; // nicks --blub
2525 const char *space, *patterns;
2527 //find what we want to complete
2532 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2538 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2539 key_line[key_linepos] = 0; //hide them
2541 space = strchr(key_line + 1, ' ');
2542 if(space && pos == (space - key_line) + 1)
2544 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2546 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2547 if(patterns && !*patterns)
2548 patterns = NULL; // get rid of the empty string
2550 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2554 if (GetMapList(s, t, sizeof(t)))
2556 // first move the cursor
2557 key_linepos += (int)strlen(t) - (int)strlen(s);
2559 // and now do the actual work
2561 strlcat(key_line, t, MAX_INPUTLINE);
2562 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2564 // and fix the cursor
2565 if(key_linepos > (int) strlen(key_line))
2566 key_linepos = (int) strlen(key_line);
2575 stringlist_t resultbuf, dirbuf;
2578 // // store completion patterns (space separated) for command foo in con_completion_foo
2579 // set con_completion_foo "foodata/*.foodefault *.foo"
2582 // Note: patterns with slash are always treated as absolute
2583 // patterns; patterns without slash search in the innermost
2584 // directory the user specified. There is no way to "complete into"
2585 // a directory as of now, as directories seem to be unknown to the
2589 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2590 // set con_completion_playdemo "*.dem"
2591 // set con_completion_play "*.wav *.ogg"
2593 // TODO somehow add support for directories; these shall complete
2594 // to their name + an appended slash.
2596 stringlistinit(&resultbuf);
2597 stringlistinit(&dirbuf);
2598 while(COM_ParseToken_Simple(&patterns, false, false))
2601 if(strchr(com_token, '/'))
2603 search = FS_Search(com_token, true, true);
2607 const char *slash = strrchr(s, '/');
2610 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2611 strlcat(t, com_token, sizeof(t));
2612 search = FS_Search(t, true, true);
2615 search = FS_Search(com_token, true, true);
2619 for(i = 0; i < search->numfilenames; ++i)
2620 if(!strncmp(search->filenames[i], s, strlen(s)))
2621 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2622 stringlistappend(&resultbuf, search->filenames[i]);
2623 FS_FreeSearch(search);
2627 // In any case, add directory names
2630 const char *slash = strrchr(s, '/');
2633 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2634 strlcat(t, "*", sizeof(t));
2635 search = FS_Search(t, true, true);
2638 search = FS_Search("*", true, true);
2641 for(i = 0; i < search->numfilenames; ++i)
2642 if(!strncmp(search->filenames[i], s, strlen(s)))
2643 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2644 stringlistappend(&dirbuf, search->filenames[i]);
2645 FS_FreeSearch(search);
2649 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2652 unsigned int matchchars;
2653 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2655 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2658 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2660 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2664 stringlistsort(&resultbuf); // dirbuf is already sorted
2665 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2666 for(i = 0; i < dirbuf.numstrings; ++i)
2668 Con_Printf("%s/\n", dirbuf.strings[i]);
2670 for(i = 0; i < resultbuf.numstrings; ++i)
2672 Con_Printf("%s\n", resultbuf.strings[i]);
2674 matchchars = sizeof(t) - 1;
2675 if(resultbuf.numstrings > 0)
2677 p = resultbuf.strings[0];
2678 q = resultbuf.strings[resultbuf.numstrings - 1];
2679 for(; *p && *p == *q; ++p, ++q);
2680 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2682 if(dirbuf.numstrings > 0)
2684 p = dirbuf.strings[0];
2685 q = dirbuf.strings[dirbuf.numstrings - 1];
2686 for(; *p && *p == *q; ++p, ++q);
2687 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2689 // now p points to the first non-equal character, or to the end
2690 // of resultbuf.strings[0]. We want to append the characters
2691 // from resultbuf.strings[0] to (not including) p as these are
2692 // the unique prefix
2693 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2696 // first move the cursor
2697 key_linepos += (int)strlen(t) - (int)strlen(s);
2699 // and now do the actual work
2701 strlcat(key_line, t, MAX_INPUTLINE);
2702 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2704 // and fix the cursor
2705 if(key_linepos > (int) strlen(key_line))
2706 key_linepos = (int) strlen(key_line);
2708 stringlistfreecontents(&resultbuf);
2709 stringlistfreecontents(&dirbuf);
2711 return; // bail out, when we complete for a command that wants a file name
2716 // Count number of possible matches and print them
2717 c = Cmd_CompleteCountPossible(s);
2720 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2721 Cmd_CompleteCommandPrint(s);
2723 v = Cvar_CompleteCountPossible(s);
2726 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2727 Cvar_CompleteCvarPrint(s);
2729 a = Cmd_CompleteAliasCountPossible(s);
2732 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2733 Cmd_CompleteAliasPrint(s);
2735 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2738 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2739 Cmd_CompleteNicksPrint(n);
2742 if (!(c + v + a + n)) // No possible matches
2745 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2750 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2752 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2754 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2756 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2758 for (cmd_len = (int)strlen(s);;cmd_len++)
2761 for (i = 0; i < 3; i++)
2763 for (l = list[i];*l;l++)
2764 if ((*l)[cmd_len] != cmd[cmd_len])
2766 // all possible matches share this character, so we continue...
2769 // if all matches ended at the same position, stop
2770 // (this means there is only one match)
2776 // prevent a buffer overrun by limiting cmd_len according to remaining space
2777 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2781 memcpy(&key_line[key_linepos], cmd, cmd_len);
2782 key_linepos += cmd_len;
2783 // if there is only one match, add a space after it
2784 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2787 { // was a nick, might have an offset, and needs colors ;) --blub
2788 key_linepos = pos - Nicks_offset[0];
2789 cmd_len = strlen(Nicks_list[0]);
2790 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2792 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2793 key_linepos += cmd_len;
2794 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2795 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2797 key_line[key_linepos++] = ' ';
2801 // use strlcat to avoid a buffer overrun
2802 key_line[key_linepos] = 0;
2803 strlcat(key_line, s2, sizeof(key_line));
2805 // free the command, cvar, and alias lists
2806 for (i = 0; i < 4; i++)
2808 Mem_Free((void *)list[i]);