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 // lines up from bottom to display
40 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
41 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
42 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
44 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
45 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
46 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
48 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
49 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
50 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)"};
51 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
52 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
53 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
54 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
55 cvar_t con_chatsound = {CVAR_SAVE, "con_chatsound","1", "enables chat sound to play on message"};
58 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)"};
60 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)"};
62 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)"};
66 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
67 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
68 "0: add nothing after completion. "
69 "1: add the last color after completion. "
70 "2: add a quote when starting a quote instead of the color. "
71 "4: will replace 1, will force color, even after a quote. "
72 "8: ignore non-alphanumerics. "
73 "16: ignore spaces. "};
74 #define NICKS_ADD_COLOR 1
75 #define NICKS_ADD_QUOTE 2
76 #define NICKS_FORCE_COLOR 4
77 #define NICKS_ALPHANUMERICS_ONLY 8
78 #define NICKS_NO_SPACES 16
80 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
81 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
82 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
87 qboolean con_initialized;
89 // used for server replies to rcon command
90 lhnetsocket_t *rcon_redirect_sock = NULL;
91 lhnetaddress_t *rcon_redirect_dest = NULL;
92 int rcon_redirect_bufferpos = 0;
93 char rcon_redirect_buffer[1400];
95 // generic functions for console buffers
97 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
100 buf->textsize = textsize;
101 buf->text = (char *) Mem_Alloc(mempool, textsize);
102 buf->maxlines = maxlines;
103 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
104 buf->lines_first = 0;
105 buf->lines_count = 0;
113 void ConBuffer_Clear (conbuffer_t *buf)
115 buf->lines_count = 0;
123 void ConBuffer_Shutdown(conbuffer_t *buf)
127 Mem_Free(buf->lines);
136 Notifies the console code about the current time
137 (and shifts back times of other entries when the time
141 void ConBuffer_FixTimes(conbuffer_t *buf)
144 if(buf->lines_count >= 1)
146 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
149 for(i = 0; i < buf->lines_count; ++i)
150 CONBUFFER_LINES(buf, i).addtime += diff;
159 Deletes the first line from the console history.
162 void ConBuffer_DeleteLine(conbuffer_t *buf)
164 if(buf->lines_count == 0)
167 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
172 ConBuffer_DeleteLastLine
174 Deletes the last line from the console history.
177 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
179 if(buf->lines_count == 0)
188 Checks if there is space for a line of the given length, and if yes, returns a
189 pointer to the start of such a space, and NULL otherwise.
192 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
194 if(len > buf->textsize)
196 if(buf->lines_count == 0)
200 char *firstline_start = buf->lines[buf->lines_first].start;
201 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
202 // the buffer is cyclic, so we first have two cases...
203 if(firstline_start < lastline_onepastend) // buffer is contiguous
206 if(len <= buf->text + buf->textsize - lastline_onepastend)
207 return lastline_onepastend;
209 else if(len <= firstline_start - buf->text)
214 else // buffer has a contiguous hole
216 if(len <= firstline_start - lastline_onepastend)
217 return lastline_onepastend;
228 Appends a given string as a new line to the console.
231 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
236 // developer_memory 1 during shutdown prints while conbuffer_t is being freed
240 ConBuffer_FixTimes(buf);
242 if(len >= buf->textsize)
245 // only display end of line.
246 line += len - buf->textsize + 1;
247 len = buf->textsize - 1;
249 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
250 ConBuffer_DeleteLine(buf);
251 memcpy(putpos, line, len);
255 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
257 p = &CONBUFFER_LINES_LAST(buf);
260 p->addtime = cl.time;
262 p->height = -1; // calculate when needed
265 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
269 start = buf->lines_count;
270 for(i = start - 1; i >= 0; --i)
272 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
274 if((l->mask & mask_must) != mask_must)
276 if(l->mask & mask_mustnot)
285 int Con_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
288 for(i = start + 1; i < buf->lines_count; ++i)
290 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
292 if((l->mask & mask_must) != mask_must)
294 if(l->mask & mask_mustnot)
303 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
305 static char copybuf[MAX_INPUTLINE];
306 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
307 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
308 strlcpy(copybuf, l->start, sz);
313 ==============================================================================
317 ==============================================================================
322 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
323 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"};
324 char log_dest_buffer[1400]; // UDP packet
325 size_t log_dest_buffer_pos;
326 unsigned int log_dest_buffer_appending;
327 char crt_log_file [MAX_OSPATH] = "";
328 qfile_t* logfile = NULL;
330 unsigned char* logqueue = NULL;
332 size_t logq_size = 0;
334 void Log_ConPrint (const char *msg);
341 static void Log_DestBuffer_Init(void)
343 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
344 log_dest_buffer_pos = 5;
352 void Log_DestBuffer_Flush(void)
354 lhnetaddress_t log_dest_addr;
355 lhnetsocket_t *log_dest_socket;
356 const char *s = log_dest_udp.string;
357 qboolean have_opened_temp_sockets = false;
358 if(s) if(log_dest_buffer_pos > 5)
360 ++log_dest_buffer_appending;
361 log_dest_buffer[log_dest_buffer_pos++] = 0;
363 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
365 have_opened_temp_sockets = true;
366 NetConn_OpenServerPorts(true);
369 while(COM_ParseToken_Console(&s))
370 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
372 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
374 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
376 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
379 if(have_opened_temp_sockets)
380 NetConn_CloseServerPorts();
381 --log_dest_buffer_appending;
383 log_dest_buffer_pos = 0;
391 const char* Log_Timestamp (const char *desc)
393 static char timestamp [128];
400 char timestring [64];
402 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
405 localtime_s (&crt_tm, &crt_time);
406 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
408 crt_tm = localtime (&crt_time);
409 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
413 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
415 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
428 if (logfile != NULL || log_file.string[0] == '\0')
431 logfile = FS_OpenRealFile(log_file.string, "a", false);
434 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
435 FS_Print (logfile, Log_Timestamp ("Log started"));
445 void Log_Close (void)
450 FS_Print (logfile, Log_Timestamp ("Log stopped"));
451 FS_Print (logfile, "\n");
455 crt_log_file[0] = '\0';
464 void Log_Start (void)
470 // Dump the contents of the log queue into the log file and free it
471 if (logqueue != NULL)
473 unsigned char *temp = logqueue;
478 FS_Write (logfile, temp, logq_ind);
479 if(*log_dest_udp.string)
481 for(pos = 0; pos < logq_ind; )
483 if(log_dest_buffer_pos == 0)
484 Log_DestBuffer_Init();
485 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
486 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
487 log_dest_buffer_pos += n;
488 Log_DestBuffer_Flush();
505 void Log_ConPrint (const char *msg)
507 static qboolean inprogress = false;
509 // don't allow feedback loops with memory error reports
514 // Until the host is completely initialized, we maintain a log queue
515 // to store the messages, since the log can't be started before
516 if (logqueue != NULL)
518 size_t remain = logq_size - logq_ind;
519 size_t len = strlen (msg);
521 // If we need to enlarge the log queue
524 size_t factor = ((logq_ind + len) / logq_size) + 1;
525 unsigned char* newqueue;
528 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
529 memcpy (newqueue, logqueue, logq_ind);
532 remain = logq_size - logq_ind;
534 memcpy (&logqueue[logq_ind], msg, len);
541 // Check if log_file has changed
542 if (strcmp (crt_log_file, log_file.string) != 0)
548 // If a log file is available
550 FS_Print (logfile, msg);
561 void Log_Printf (const char *logfilename, const char *fmt, ...)
565 file = FS_OpenRealFile(logfilename, "a", true);
570 va_start (argptr, fmt);
571 FS_VPrintf (file, fmt, argptr);
580 ==============================================================================
584 ==============================================================================
592 void Con_ToggleConsole_f (void)
594 // toggle the 'user wants console' bit
595 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
604 void Con_ClearNotify (void)
607 for(i = 0; i < CON_LINES_COUNT; ++i)
608 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
611 void Con_MessageMode_SetCursor(size_t pos)
613 chat_bufferlen = pos;
614 //chat_bufferlen = strlen(chat_buffer);
622 void Con_MessageMode_f (void)
624 key_dest = key_message;
625 chat_mode = 0; // "say"
629 UIM_EnterBuffer(chat_buffer, sizeof(chat_buffer), 0, &Con_MessageMode_SetCursor);
638 void Con_MessageMode2_f (void)
640 key_dest = key_message;
641 chat_mode = 1; // "say_team"
645 UIM_EnterBuffer(chat_buffer, sizeof(chat_buffer), 0, &Con_MessageMode_SetCursor);
653 void Con_CommandMode_f (void)
655 key_dest = key_message;
658 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
659 chat_bufferlen = strlen(chat_buffer);
661 chat_mode = -1; // command
663 UIM_EnterBuffer(chat_buffer, sizeof(chat_buffer), 0, &Con_MessageMode_SetCursor);
671 void Con_CheckResize (void)
676 f = bound(1, con_textsize.value, 128);
677 if(f != con_textsize.value)
678 Cvar_SetValueQuick(&con_textsize, f);
679 width = (int)floor(vid_conwidth.value / con_textsize.value);
680 width = bound(1, width, con.textsize/4);
681 // FIXME uses con in a non abstracted way
683 if (width == con_linewidth)
686 con_linewidth = width;
688 for(i = 0; i < CON_LINES_COUNT; ++i)
689 CON_LINES(i).height = -1; // recalculate when next needed
695 //[515]: the simplest command ever
696 //LordHavoc: not so simple after I made it print usage...
697 static void Con_Maps_f (void)
701 Con_Printf("usage: maps [mapnameprefix]\n");
704 else if (Cmd_Argc() == 2)
705 GetMapList(Cmd_Argv(1), NULL, 0);
707 GetMapList("", NULL, 0);
710 void Con_ConDump_f (void)
716 Con_Printf("usage: condump <filename>\n");
719 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
722 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
725 for(i = 0; i < CON_LINES_COUNT; ++i)
727 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
728 FS_Write(file, "\n", 1);
733 void Con_Clear_f (void)
735 ConBuffer_Clear(&con);
746 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
748 // Allocate a log queue, this will be freed after configs are parsed
749 logq_size = MAX_INPUTLINE;
750 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
753 Cvar_RegisterVariable (&sys_colortranslation);
754 Cvar_RegisterVariable (&sys_specialcharactertranslation);
756 Cvar_RegisterVariable (&log_file);
757 Cvar_RegisterVariable (&log_dest_udp);
759 // support for the classic Quake option
760 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
761 if (COM_CheckParm ("-condebug") != 0)
762 Cvar_SetQuick (&log_file, "qconsole.log");
764 // register our cvars
765 Cvar_RegisterVariable (&con_chat);
766 Cvar_RegisterVariable (&con_chatpos);
767 Cvar_RegisterVariable (&con_chatsize);
768 Cvar_RegisterVariable (&con_chattime);
769 Cvar_RegisterVariable (&con_chatwidth);
770 Cvar_RegisterVariable (&con_notify);
771 Cvar_RegisterVariable (&con_notifyalign);
772 Cvar_RegisterVariable (&con_notifysize);
773 Cvar_RegisterVariable (&con_notifytime);
774 Cvar_RegisterVariable (&con_textsize);
775 Cvar_RegisterVariable (&con_chatsound);
778 Cvar_RegisterVariable (&con_nickcompletion);
779 Cvar_RegisterVariable (&con_nickcompletion_flags);
781 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
782 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
783 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
785 // register our commands
786 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
787 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
788 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
789 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
790 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
791 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
792 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
794 con_initialized = true;
795 Con_DPrint("Console initialized.\n");
798 void Con_Shutdown (void)
800 ConBuffer_Shutdown(&con);
807 Handles cursor positioning, line wrapping, etc
808 All console printing must go through this in order to be displayed
809 If no console is visible, the notify window will pop up.
812 void Con_PrintToHistory(const char *txt, int mask)
815 // \n goes to next line
816 // \r deletes current line and makes a new one
818 static int cr_pending = 0;
819 static char buf[CON_TEXTSIZE];
820 static int bufpos = 0;
822 if(!con.text) // FIXME uses a non-abstracted property of con
829 ConBuffer_DeleteLastLine(&con);
837 ConBuffer_AddLine(&con, buf, bufpos, mask);
842 ConBuffer_AddLine(&con, buf, bufpos, mask);
846 buf[bufpos++] = *txt;
847 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
849 ConBuffer_AddLine(&con, buf, bufpos, mask);
857 /*! The translation table between the graphical font and plain ASCII --KB */
858 static char qfont_table[256] = {
859 '\0', '#', '#', '#', '#', '.', '#', '#',
860 '#', 9, 10, '#', ' ', 13, '.', '.',
861 '[', ']', '0', '1', '2', '3', '4', '5',
862 '6', '7', '8', '9', '.', '<', '=', '>',
863 ' ', '!', '"', '#', '$', '%', '&', '\'',
864 '(', ')', '*', '+', ',', '-', '.', '/',
865 '0', '1', '2', '3', '4', '5', '6', '7',
866 '8', '9', ':', ';', '<', '=', '>', '?',
867 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
868 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
869 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
870 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
871 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
872 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
873 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
874 'x', 'y', 'z', '{', '|', '}', '~', '<',
876 '<', '=', '>', '#', '#', '.', '#', '#',
877 '#', '#', ' ', '#', ' ', '>', '.', '.',
878 '[', ']', '0', '1', '2', '3', '4', '5',
879 '6', '7', '8', '9', '.', '<', '=', '>',
880 ' ', '!', '"', '#', '$', '%', '&', '\'',
881 '(', ')', '*', '+', ',', '-', '.', '/',
882 '0', '1', '2', '3', '4', '5', '6', '7',
883 '8', '9', ':', ';', '<', '=', '>', '?',
884 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
885 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
886 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
887 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
888 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
889 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
890 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
891 'x', 'y', 'z', '{', '|', '}', '~', '<'
894 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest)
896 rcon_redirect_sock = sock;
897 rcon_redirect_dest = dest;
898 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
899 rcon_redirect_bufferpos = 5;
902 void Con_Rcon_Redirect_Flush(void)
904 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
905 NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
906 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
907 rcon_redirect_bufferpos = 5;
910 void Con_Rcon_Redirect_End(void)
912 Con_Rcon_Redirect_Flush();
913 rcon_redirect_dest = NULL;
914 rcon_redirect_sock = NULL;
917 void Con_Rcon_Redirect_Abort(void)
919 rcon_redirect_dest = NULL;
920 rcon_redirect_sock = NULL;
928 /// Adds a character to the rcon buffer.
929 void Con_Rcon_AddChar(int c)
931 if(log_dest_buffer_appending)
933 ++log_dest_buffer_appending;
935 // if this print is in response to an rcon command, add the character
936 // to the rcon redirect buffer
938 if (rcon_redirect_dest)
940 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
941 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
942 Con_Rcon_Redirect_Flush();
944 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
946 if(log_dest_buffer_pos == 0)
947 Log_DestBuffer_Init();
948 log_dest_buffer[log_dest_buffer_pos++] = c;
949 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
950 Log_DestBuffer_Flush();
953 log_dest_buffer_pos = 0;
955 --log_dest_buffer_appending;
959 * Convert an RGB color to its nearest quake color.
960 * I'll cheat on this a bit by translating the colors to HSV first,
961 * S and V decide if it's black or white, otherwise, H will decide the
963 * @param _r Red (0-255)
964 * @param _g Green (0-255)
965 * @param _b Blue (0-255)
966 * @return A quake color character.
968 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
970 float r = ((float)_r)/255.0;
971 float g = ((float)_g)/255.0;
972 float b = ((float)_b)/255.0;
973 float min = min(r, min(g, b));
974 float max = max(r, max(g, b));
976 int h; ///< Hue angle [0,360]
977 float s; ///< Saturation [0,1]
978 float v = max; ///< In HSV v == max [0,1]
985 // Saturation threshold. We now say 0.2 is the minimum value for a color!
988 // If the value is less than half, return a black color code.
989 // Otherwise return a white one.
995 // Let's get the hue angle to define some colors:
999 h = (int)(60.0 * (g-b)/(max-min))%360;
1001 h = (int)(60.0 * (b-r)/(max-min) + 120);
1002 else // if(max == b) redundant check
1003 h = (int)(60.0 * (r-g)/(max-min) + 240);
1005 if(h < 36) // *red* to orange
1007 else if(h < 80) // orange over *yellow* to evilish-bright-green
1009 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1011 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1013 else if(h < 270) // darkish blue over *dark blue* to cool purple
1015 else if(h < 330) // cool purple over *purple* to ugly swiny red
1017 else // ugly red to red closes the circly
1026 extern cvar_t timestamps;
1027 extern cvar_t timeformat;
1028 extern qboolean sys_nostdout;
1029 void Con_Print(const char *msg)
1031 static int mask = 0;
1032 static int index = 0;
1033 static char line[MAX_INPUTLINE];
1037 Con_Rcon_AddChar(*msg);
1038 // if this is the beginning of a new line, print timestamp
1041 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1043 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1044 line[index++] = STRING_COLOR_TAG;
1045 // assert( STRING_COLOR_DEFAULT < 10 )
1046 line[index++] = STRING_COLOR_DEFAULT + '0';
1047 // special color codes for chat messages must always come first
1048 // for Con_PrintToHistory to work properly
1049 if (*msg == 1 || *msg == 2)
1054 if (con_chatsound.value)
1056 if(gamemode == GAME_NEXUIZ)
1058 if(msg[1] == '\r' && cl.foundtalk2wav)
1059 S_LocalSound ("sound/misc/talk2.wav");
1061 S_LocalSound ("sound/misc/talk.wav");
1065 if (msg[1] == '(' && cl.foundtalk2wav)
1066 S_LocalSound ("sound/misc/talk2.wav");
1068 S_LocalSound ("sound/misc/talk.wav");
1071 mask = CON_MASK_CHAT;
1073 line[index++] = STRING_COLOR_TAG;
1074 line[index++] = '3';
1076 Con_Rcon_AddChar(*msg);
1079 for (;*timestamp;index++, timestamp++)
1080 if (index < (int)sizeof(line) - 2)
1081 line[index] = *timestamp;
1083 // append the character
1084 line[index++] = *msg;
1085 // if this is a newline character, we have a complete line to print
1086 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1088 // terminate the line
1092 // send to scrollable buffer
1093 if (con_initialized && cls.state != ca_dedicated)
1095 Con_PrintToHistory(line, mask);
1098 // send to terminal or dedicated server window
1102 if(sys_specialcharactertranslation.integer)
1104 for (p = (unsigned char *) line;*p; p++)
1105 *p = qfont_table[*p];
1108 if(sys_colortranslation.integer == 1) // ANSI
1110 static char printline[MAX_INPUTLINE * 4 + 3];
1111 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1112 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1117 for(in = line, out = printline; *in; ++in)
1121 case STRING_COLOR_TAG:
1122 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1124 char r = tolower(in[2]);
1125 char g = tolower(in[3]);
1126 char b = tolower(in[4]);
1127 // it's a hex digit already, so the else part needs no check --blub
1128 if(isdigit(r)) r -= '0';
1130 if(isdigit(g)) g -= '0';
1132 if(isdigit(b)) b -= '0';
1135 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1136 in += 3; // 3 only, the switch down there does the fourth
1143 case STRING_COLOR_TAG:
1145 *out++ = STRING_COLOR_TAG;
1151 if(lastcolor == 0) break; else lastcolor = 0;
1152 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1157 if(lastcolor == 1) break; else lastcolor = 1;
1158 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1163 if(lastcolor == 2) break; else lastcolor = 2;
1164 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1169 if(lastcolor == 3) break; else lastcolor = 3;
1170 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1175 if(lastcolor == 4) break; else lastcolor = 4;
1176 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1181 if(lastcolor == 5) break; else lastcolor = 5;
1182 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1187 if(lastcolor == 6) break; else lastcolor = 6;
1188 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1193 // bold normal color
1195 if(lastcolor == 8) break; else lastcolor = 8;
1196 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1199 *out++ = STRING_COLOR_TAG;
1206 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1223 Sys_PrintToTerminal(printline);
1225 else if(sys_colortranslation.integer == 2) // Quake
1227 Sys_PrintToTerminal(line);
1231 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1234 for(in = line, out = printline; *in; ++in)
1238 case STRING_COLOR_TAG:
1241 case STRING_COLOR_RGB_TAG_CHAR:
1242 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1247 *out++ = STRING_COLOR_TAG;
1248 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1251 case STRING_COLOR_TAG:
1253 *out++ = STRING_COLOR_TAG;
1268 *out++ = STRING_COLOR_TAG;
1278 Sys_PrintToTerminal(printline);
1281 // empty the line buffer
1293 void Con_Printf(const char *fmt, ...)
1296 char msg[MAX_INPUTLINE];
1298 va_start(argptr,fmt);
1299 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1310 void Con_DPrint(const char *msg)
1312 if (!developer.integer)
1313 return; // don't confuse non-developers with techie stuff...
1322 void Con_DPrintf(const char *fmt, ...)
1325 char msg[MAX_INPUTLINE];
1327 if (!developer.integer)
1328 return; // don't confuse non-developers with techie stuff...
1330 va_start(argptr,fmt);
1331 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1339 ==============================================================================
1343 ==============================================================================
1350 The input line scrolls horizontally if typing goes beyond the right edge
1352 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1355 void Con_DrawInput (void)
1359 char editlinecopy[MAX_INPUTLINE+1], *text;
1362 if (!key_consoleactive)
1363 return; // don't draw anything
1365 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1366 text = editlinecopy;
1368 // Advanced Console Editing by Radix radix@planetquake.com
1369 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1370 // use strlen of edit_line instead of key_linepos to allow editing
1371 // of early characters w/o erasing
1373 y = (int)strlen(text);
1375 // fill out remainder with spaces
1376 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1379 // add the cursor frame
1380 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1382 if (utf8_disabled.integer)
1383 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1384 else if (y + 3 < (int)sizeof(editlinecopy)-1)
1386 int ofs = u8_bytelen(text + key_linepos, 1);
1389 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1393 memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1394 memcpy(text + key_linepos, curbuf, len);
1397 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1400 // text[key_linepos + 1] = 0;
1402 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1407 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 );
1410 // key_line[key_linepos] = 0;
1416 float alignment; // 0 = left, 0.5 = center, 1 = right
1422 const char *continuationString;
1425 int colorindex; // init to -1
1429 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1431 con_text_info_t *ti = (con_text_info_t *) passthrough;
1434 ti->colorindex = -1;
1435 return ti->fontsize * ti->font->maxwidth;
1439 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1440 else if(maxWidth == -1)
1441 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1444 return DrawQ_TextWidth_Font_UntilWidth_Size(w, ti->fontsize, ti->fontsize, length, false, ti->font, maxWidth);
1445 else if(maxWidth == -1)
1446 return DrawQ_TextWidth_Font_Size(w, ti->fontsize, ti->fontsize, *length, false, ti->font);
1449 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1450 // Note: this is NOT a Con_Printf, as it could print recursively
1455 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1461 (void) isContinuation;
1465 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1467 con_text_info_t *ti = (con_text_info_t *) passthrough;
1469 if(ti->y < ti->ymin - 0.001)
1471 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1475 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1476 if(isContinuation && *ti->continuationString)
1477 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);
1479 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);
1482 ti->y += ti->fontsize;
1486 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)
1490 int maxlines = (int) floor(height / fontsize + 0.01f);
1493 int continuationWidth = 0;
1495 double t = cl.time; // saved so it won't change
1498 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1499 ti.fontsize = fontsize;
1500 ti.alignment = alignment_x;
1503 ti.ymax = y + height;
1504 ti.continuationString = continuationString;
1507 Con_WordWidthFunc(&ti, NULL, &l, -1);
1508 l = strlen(continuationString);
1509 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1511 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1512 startidx = CON_LINES_COUNT;
1513 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1515 con_lineinfo_t *l = &CON_LINES(i);
1518 if((l->mask & mask_must) != mask_must)
1520 if(l->mask & mask_mustnot)
1522 if(maxage && (l->addtime < t - maxage))
1526 // Calculate its actual height...
1527 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1528 if(lines + mylines >= maxlines)
1530 nskip = lines + mylines - maxlines;
1539 // then center according to the calculated amount of lines...
1541 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1543 // then actually draw
1544 for(i = startidx; i < CON_LINES_COUNT; ++i)
1546 con_lineinfo_t *l = &CON_LINES(i);
1548 if((l->mask & mask_must) != mask_must)
1550 if(l->mask & mask_mustnot)
1552 if(maxage && (l->addtime < t - maxage))
1555 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1565 Draws the last few lines of output transparently over the game top
1568 void Con_DrawNotify (void)
1571 float chatstart, notifystart, inputsize;
1573 char temptext[MAX_INPUTLINE];
1577 ConBuffer_FixTimes(&con);
1579 numChatlines = con_chat.integer;
1580 chatpos = con_chatpos.integer;
1582 if (con_notify.integer < 0)
1583 Cvar_SetValueQuick(&con_notify, 0);
1584 if (gamemode == GAME_TRANSFUSION)
1585 v = 8; // vertical offset
1589 // GAME_NEXUIZ: center, otherwise left justify
1590 align = con_notifyalign.value;
1591 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1593 if(gamemode == GAME_NEXUIZ)
1601 // first chat, input line, then notify
1603 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1605 else if(chatpos > 0)
1607 // first notify, then (chatpos-1) empty lines, then chat, then input
1609 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1611 else // if(chatpos < 0)
1613 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1615 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1620 // just notify and input
1622 chatstart = 0; // shut off gcc warning
1625 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, "");
1630 v = chatstart + numChatlines * con_chatsize.value;
1631 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
1634 if (key_dest == key_message)
1636 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1637 int colorindex = -1;
1639 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL);
1641 // LordHavoc: speedup, and other improvements
1643 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1645 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1647 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1650 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1651 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1654 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1660 Con_MeasureConsoleLine
1662 Counts the number of lines for a line on the console.
1665 int Con_MeasureConsoleLine(int lineno)
1667 float width = vid_conwidth.value;
1669 con_lineinfo_t *li = &CON_LINES(lineno);
1671 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1674 ti.fontsize = con_textsize.value;
1675 ti.font = FONT_CONSOLE;
1677 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1684 Returns the height of a given console line; calculates it if necessary.
1687 int Con_LineHeight(int i)
1689 con_lineinfo_t *li = &CON_LINES(i);
1693 return li->height = Con_MeasureConsoleLine(i);
1700 Draws a line of the console; returns its height in lines.
1701 If alpha is 0, the line is not drawn, but still wrapped and its height
1705 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1707 float width = vid_conwidth.value;
1709 con_lineinfo_t *li = &CON_LINES(lineno);
1711 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1714 ti.continuationString = "";
1716 ti.fontsize = con_textsize.value;
1717 ti.font = FONT_CONSOLE;
1719 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1724 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1731 Calculates the last visible line index and how much to show of it based on
1735 void Con_LastVisibleLine(int *last, int *limitlast)
1740 if(con_backscroll < 0)
1743 // now count until we saw con_backscroll actual lines
1744 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1746 int h = Con_LineHeight(i);
1748 // line is the last visible line?
1749 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1752 *limitlast = lines_seen + h - con_backscroll;
1759 // if we get here, no line was on screen - scroll so that one line is
1761 con_backscroll = lines_seen - 1;
1762 *last = con.lines_first;
1763 // FIXME uses con in a non abstracted way
1771 Draws the console with the solid background
1772 The typing input line at the bottom should only be drawn if typing is allowed
1775 void Con_DrawConsole (int lines)
1777 int i, last, limitlast;
1783 con_vislines = lines;
1785 // draw the background
1786 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
1787 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);
1790 if(CON_LINES_COUNT > 0)
1792 float ymax = con_vislines - 2 * con_textsize.value;
1793 Con_LastVisibleLine(&last, &limitlast);
1794 y = ymax - con_textsize.value;
1797 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1798 // FIXME uses con in a non abstracted way
1803 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1805 break; // top of console buffer
1807 break; // top of console window
1813 // draw the input prompt, user text, and cursor if desired
1821 Prints not only map filename, but also
1822 its format (q1/q2/q3/hl) and even its message
1824 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1825 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1826 //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
1827 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1828 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1832 int i, k, max, p, o, min;
1835 unsigned char buf[1024];
1837 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1838 t = FS_Search(message, 1, true);
1841 if (t->numfilenames > 1)
1842 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1843 len = (unsigned char *)Z_Malloc(t->numfilenames);
1845 for(max=i=0;i<t->numfilenames;i++)
1847 k = (int)strlen(t->filenames[i]);
1857 for(i=0;i<t->numfilenames;i++)
1859 int lumpofs = 0, lumplen = 0;
1860 char *entities = NULL;
1861 const char *data = NULL;
1863 char entfilename[MAX_QPATH];
1864 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1866 f = FS_OpenVirtualFile(t->filenames[i], true);
1869 memset(buf, 0, 1024);
1870 FS_Read(f, buf, 1024);
1871 if (!memcmp(buf, "IBSP", 4))
1873 p = LittleLong(((int *)buf)[1]);
1874 if (p == Q3BSPVERSION)
1876 q3dheader_t *header = (q3dheader_t *)buf;
1877 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1878 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1880 else if (p == Q2BSPVERSION)
1882 q2dheader_t *header = (q2dheader_t *)buf;
1883 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1884 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1887 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1889 dheader_t *header = (dheader_t *)buf;
1890 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1891 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1895 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1896 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1897 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1898 if (!entities && lumplen >= 10)
1900 FS_Seek(f, lumpofs, SEEK_SET);
1901 entities = (char *)Z_Malloc(lumplen + 1);
1902 FS_Read(f, entities, lumplen);
1906 // if there are entities to parse, a missing message key just
1907 // means there is no title, so clear the message string now
1913 if (!COM_ParseToken_Simple(&data, false, false))
1915 if (com_token[0] == '{')
1917 if (com_token[0] == '}')
1919 // skip leading whitespace
1920 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
1921 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
1922 keyname[l] = com_token[k+l];
1924 if (!COM_ParseToken_Simple(&data, false, false))
1926 if (developer.integer >= 100)
1927 Con_Printf("key: %s %s\n", keyname, com_token);
1928 if (!strcmp(keyname, "message"))
1930 // get the message contents
1931 strlcpy(message, com_token, sizeof(message));
1941 *(t->filenames[i]+len[i]+5) = 0;
1944 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1945 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1946 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1947 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1948 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1950 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1955 k = *(t->filenames[0]+5+p);
1958 for(i=1;i<t->numfilenames;i++)
1959 if(*(t->filenames[i]+5+p) != k)
1963 if(p > o && completedname && completednamebufferlength > 0)
1965 memset(completedname, 0, completednamebufferlength);
1966 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1976 New function for tab-completion system
1977 Added by EvilTypeGuy
1978 MEGA Thanks to Taniwha
1981 void Con_DisplayList(const char **list)
1983 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1984 const char **walk = list;
1987 len = (int)strlen(*walk);
1995 len = (int)strlen(*list);
1996 if (pos + maxlen >= width) {
2002 for (i = 0; i < (maxlen - len); i++)
2014 SanitizeString strips color tags from the string in
2015 and writes the result on string out
2017 void SanitizeString(char *in, char *out)
2021 if(*in == STRING_COLOR_TAG)
2026 out[0] = STRING_COLOR_TAG;
2030 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2037 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2040 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2042 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2049 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2054 else if (*in != STRING_COLOR_TAG)
2057 *out = qfont_table[*(unsigned char*)in];
2064 // Now it becomes TRICKY :D --blub
2065 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2066 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2067 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2068 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
2069 static int Nicks_matchpos;
2071 // co against <<:BLASTER:>> is true!?
2072 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2076 if(tolower(*a) == tolower(*b))
2090 return (*a < *b) ? -1 : 1;
2094 return (*a < *b) ? -1 : 1;
2098 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2101 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2103 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2104 return Nicks_strncasecmp_nospaces(a, b, a_len);
2105 return strncasecmp(a, b, a_len);
2108 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2110 // ignore non alphanumerics of B
2111 // if A contains a non-alphanumeric, B must contain it as well though!
2114 qboolean alnum_a, alnum_b;
2116 if(tolower(*a) == tolower(*b))
2118 if(*a == 0) // end of both strings, they're equal
2125 // not equal, end of one string?
2130 // ignore non alphanumerics
2131 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2132 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2133 if(!alnum_a) // b must contain this
2134 return (*a < *b) ? -1 : 1;
2137 // otherwise, both are alnum, they're just not equal, return the appropriate number
2139 return (*a < *b) ? -1 : 1;
2145 /* Nicks_CompleteCountPossible
2147 Count the number of possible nicks to complete
2149 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2158 if(!con_nickcompletion.integer)
2161 // changed that to 1
2162 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2165 for(i = 0; i < cl.maxclients; ++i)
2168 if(!cl.scores[p].name[0])
2171 SanitizeString(cl.scores[p].name, name);
2172 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2177 length = strlen(name);
2179 spos = pos - 1; // no need for a minimum of characters :)
2183 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2185 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2186 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2192 if(isCon && spos == 0)
2194 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2200 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2201 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2203 // the sanitized list
2204 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2207 Nicks_matchpos = match;
2210 Nicks_offset[count] = s - (&line[match]);
2211 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2218 void Cmd_CompleteNicksPrint(int count)
2221 for(i = 0; i < count; ++i)
2222 Con_Printf("%s\n", Nicks_list[i]);
2225 void Nicks_CutMatchesNormal(int count)
2227 // cut match 0 down to the longest possible completion
2230 c = strlen(Nicks_sanlist[0]) - 1;
2231 for(i = 1; i < count; ++i)
2233 l = strlen(Nicks_sanlist[i]) - 1;
2237 for(l = 0; l <= c; ++l)
2238 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2244 Nicks_sanlist[0][c+1] = 0;
2245 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2248 unsigned int Nicks_strcleanlen(const char *s)
2253 if( (*s >= 'a' && *s <= 'z') ||
2254 (*s >= 'A' && *s <= 'Z') ||
2255 (*s >= '0' && *s <= '9') ||
2263 void Nicks_CutMatchesAlphaNumeric(int count)
2265 // cut match 0 down to the longest possible completion
2268 char tempstr[sizeof(Nicks_sanlist[0])];
2270 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2272 c = strlen(Nicks_sanlist[0]);
2273 for(i = 0, l = 0; i < (int)c; ++i)
2275 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2276 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2277 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2279 tempstr[l++] = Nicks_sanlist[0][i];
2284 for(i = 1; i < count; ++i)
2287 b = Nicks_sanlist[i];
2297 if(tolower(*a) == tolower(*b))
2303 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2305 // b is alnum, so cut
2312 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2313 Nicks_CutMatchesNormal(count);
2314 //if(!Nicks_sanlist[0][0])
2315 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2317 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2318 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2322 void Nicks_CutMatchesNoSpaces(int count)
2324 // cut match 0 down to the longest possible completion
2327 char tempstr[sizeof(Nicks_sanlist[0])];
2330 c = strlen(Nicks_sanlist[0]);
2331 for(i = 0, l = 0; i < (int)c; ++i)
2333 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2335 tempstr[l++] = Nicks_sanlist[0][i];
2340 for(i = 1; i < count; ++i)
2343 b = Nicks_sanlist[i];
2353 if(tolower(*a) == tolower(*b))
2367 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2368 Nicks_CutMatchesNormal(count);
2369 //if(!Nicks_sanlist[0][0])
2370 //Con_Printf("TS: %s\n", tempstr);
2371 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2373 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2374 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2378 void Nicks_CutMatches(int count)
2380 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2381 Nicks_CutMatchesAlphaNumeric(count);
2382 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2383 Nicks_CutMatchesNoSpaces(count);
2385 Nicks_CutMatchesNormal(count);
2388 const char **Nicks_CompleteBuildList(int count)
2392 // the list is freed by Con_CompleteCommandLine, so create a char**
2393 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2395 for(; bpos < count; ++bpos)
2396 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2398 Nicks_CutMatches(count);
2406 Restores the previous used color, after the autocompleted name.
2408 int Nicks_AddLastColor(char *buffer, int pos)
2410 qboolean quote_added = false;
2412 int color = STRING_COLOR_DEFAULT + '0';
2413 char r = 0, g = 0, b = 0;
2415 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2417 // we'll have to add a quote :)
2418 buffer[pos++] = '\"';
2422 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2424 // add color when no quote was added, or when flags &4?
2426 for(match = Nicks_matchpos-1; match >= 0; --match)
2428 if(buffer[match] == STRING_COLOR_TAG)
2430 if( isdigit(buffer[match+1]) )
2432 color = buffer[match+1];
2435 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2437 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2439 r = buffer[match+2];
2440 g = buffer[match+3];
2441 b = buffer[match+4];
2450 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2452 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2453 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2456 buffer[pos++] = STRING_COLOR_TAG;
2459 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2465 buffer[pos++] = color;
2470 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2473 /*if(!con_nickcompletion.integer)
2474 return; is tested in Nicks_CompletionCountPossible */
2475 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2481 msg = Nicks_list[0];
2482 len = min(size - Nicks_matchpos - 3, strlen(msg));
2483 memcpy(&buffer[Nicks_matchpos], msg, len);
2484 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2485 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2486 buffer[len++] = ' ';
2493 Con_Printf("\n%i possible nicks:\n", n);
2494 Cmd_CompleteNicksPrint(n);
2496 Nicks_CutMatches(n);
2498 msg = Nicks_sanlist[0];
2499 len = min(size - Nicks_matchpos, strlen(msg));
2500 memcpy(&buffer[Nicks_matchpos], msg, len);
2501 buffer[Nicks_matchpos + len] = 0;
2503 return Nicks_matchpos + len;
2510 Con_CompleteCommandLine
2512 New function for tab-completion system
2513 Added by EvilTypeGuy
2514 Thanks to Fett erich@heintz.com
2516 Enhanced to tab-complete map names by [515]
2519 void Con_CompleteCommandLine (void)
2521 const char *cmd = "";
2523 const char **list[4] = {0, 0, 0, 0};
2526 int c, v, a, i, cmd_len, pos, k;
2527 int n; // nicks --blub
2528 const char *space, *patterns;
2530 //find what we want to complete
2535 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2541 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2542 key_line[key_linepos] = 0; //hide them
2544 space = strchr(key_line + 1, ' ');
2545 if(space && pos == (space - key_line) + 1)
2547 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2549 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2550 if(patterns && !*patterns)
2551 patterns = NULL; // get rid of the empty string
2553 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2557 if (GetMapList(s, t, sizeof(t)))
2559 // first move the cursor
2560 key_linepos += (int)strlen(t) - (int)strlen(s);
2562 // and now do the actual work
2564 strlcat(key_line, t, MAX_INPUTLINE);
2565 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2567 // and fix the cursor
2568 if(key_linepos > (int) strlen(key_line))
2569 key_linepos = (int) strlen(key_line);
2578 stringlist_t resultbuf, dirbuf;
2581 // // store completion patterns (space separated) for command foo in con_completion_foo
2582 // set con_completion_foo "foodata/*.foodefault *.foo"
2585 // Note: patterns with slash are always treated as absolute
2586 // patterns; patterns without slash search in the innermost
2587 // directory the user specified. There is no way to "complete into"
2588 // a directory as of now, as directories seem to be unknown to the
2592 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2593 // set con_completion_playdemo "*.dem"
2594 // set con_completion_play "*.wav *.ogg"
2596 // TODO somehow add support for directories; these shall complete
2597 // to their name + an appended slash.
2599 stringlistinit(&resultbuf);
2600 stringlistinit(&dirbuf);
2601 while(COM_ParseToken_Simple(&patterns, false, false))
2604 if(strchr(com_token, '/'))
2606 search = FS_Search(com_token, true, true);
2610 const char *slash = strrchr(s, '/');
2613 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2614 strlcat(t, com_token, sizeof(t));
2615 search = FS_Search(t, true, true);
2618 search = FS_Search(com_token, true, true);
2622 for(i = 0; i < search->numfilenames; ++i)
2623 if(!strncmp(search->filenames[i], s, strlen(s)))
2624 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2625 stringlistappend(&resultbuf, search->filenames[i]);
2626 FS_FreeSearch(search);
2630 // In any case, add directory names
2633 const char *slash = strrchr(s, '/');
2636 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2637 strlcat(t, "*", sizeof(t));
2638 search = FS_Search(t, true, true);
2641 search = FS_Search("*", true, true);
2644 for(i = 0; i < search->numfilenames; ++i)
2645 if(!strncmp(search->filenames[i], s, strlen(s)))
2646 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2647 stringlistappend(&dirbuf, search->filenames[i]);
2648 FS_FreeSearch(search);
2652 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2655 unsigned int matchchars;
2656 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2658 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2661 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2663 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2667 stringlistsort(&resultbuf); // dirbuf is already sorted
2668 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2669 for(i = 0; i < dirbuf.numstrings; ++i)
2671 Con_Printf("%s/\n", dirbuf.strings[i]);
2673 for(i = 0; i < resultbuf.numstrings; ++i)
2675 Con_Printf("%s\n", resultbuf.strings[i]);
2677 matchchars = sizeof(t) - 1;
2678 if(resultbuf.numstrings > 0)
2680 p = resultbuf.strings[0];
2681 q = resultbuf.strings[resultbuf.numstrings - 1];
2682 for(; *p && *p == *q; ++p, ++q);
2683 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2685 if(dirbuf.numstrings > 0)
2687 p = dirbuf.strings[0];
2688 q = dirbuf.strings[dirbuf.numstrings - 1];
2689 for(; *p && *p == *q; ++p, ++q);
2690 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2692 // now p points to the first non-equal character, or to the end
2693 // of resultbuf.strings[0]. We want to append the characters
2694 // from resultbuf.strings[0] to (not including) p as these are
2695 // the unique prefix
2696 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2699 // first move the cursor
2700 key_linepos += (int)strlen(t) - (int)strlen(s);
2702 // and now do the actual work
2704 strlcat(key_line, t, MAX_INPUTLINE);
2705 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2707 // and fix the cursor
2708 if(key_linepos > (int) strlen(key_line))
2709 key_linepos = (int) strlen(key_line);
2711 stringlistfreecontents(&resultbuf);
2712 stringlistfreecontents(&dirbuf);
2714 return; // bail out, when we complete for a command that wants a file name
2719 // Count number of possible matches and print them
2720 c = Cmd_CompleteCountPossible(s);
2723 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2724 Cmd_CompleteCommandPrint(s);
2726 v = Cvar_CompleteCountPossible(s);
2729 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2730 Cvar_CompleteCvarPrint(s);
2732 a = Cmd_CompleteAliasCountPossible(s);
2735 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2736 Cmd_CompleteAliasPrint(s);
2738 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2741 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2742 Cmd_CompleteNicksPrint(n);
2745 if (!(c + v + a + n)) // No possible matches
2748 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2753 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2755 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2757 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2759 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2761 for (cmd_len = (int)strlen(s);;cmd_len++)
2764 for (i = 0; i < 3; i++)
2766 for (l = list[i];*l;l++)
2767 if ((*l)[cmd_len] != cmd[cmd_len])
2769 // all possible matches share this character, so we continue...
2772 // if all matches ended at the same position, stop
2773 // (this means there is only one match)
2779 // prevent a buffer overrun by limiting cmd_len according to remaining space
2780 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2784 memcpy(&key_line[key_linepos], cmd, cmd_len);
2785 key_linepos += cmd_len;
2786 // if there is only one match, add a space after it
2787 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2790 { // was a nick, might have an offset, and needs colors ;) --blub
2791 key_linepos = pos - Nicks_offset[0];
2792 cmd_len = strlen(Nicks_list[0]);
2793 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2795 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2796 key_linepos += cmd_len;
2797 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2798 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2800 key_line[key_linepos++] = ' ';
2804 // use strlcat to avoid a buffer overrun
2805 key_line[key_linepos] = 0;
2806 strlcat(key_line, s2, sizeof(key_line));
2808 // free the command, cvar, and alias lists
2809 for (i = 0; i < 4; i++)
2811 Mem_Free((void *)list[i]);