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_MaskPrint(int additionalmask, const char *msg)
1031 static int mask = 0;
1032 static int index = 0;
1033 static char line[MAX_INPUTLINE];
1037 Con_Rcon_AddChar(*msg);
1039 mask |= additionalmask;
1040 // if this is the beginning of a new line, print timestamp
1043 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1045 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1046 line[index++] = STRING_COLOR_TAG;
1047 // assert( STRING_COLOR_DEFAULT < 10 )
1048 line[index++] = STRING_COLOR_DEFAULT + '0';
1049 // special color codes for chat messages must always come first
1050 // for Con_PrintToHistory to work properly
1051 if (*msg == 1 || *msg == 2)
1056 if (con_chatsound.value)
1058 if(gamemode == GAME_NEXUIZ)
1060 if(msg[1] == '\r' && cl.foundtalk2wav)
1061 S_LocalSound ("sound/misc/talk2.wav");
1063 S_LocalSound ("sound/misc/talk.wav");
1067 if (msg[1] == '(' && cl.foundtalk2wav)
1068 S_LocalSound ("sound/misc/talk2.wav");
1070 S_LocalSound ("sound/misc/talk.wav");
1073 mask = CON_MASK_CHAT;
1075 line[index++] = STRING_COLOR_TAG;
1076 line[index++] = '3';
1078 Con_Rcon_AddChar(*msg);
1081 for (;*timestamp;index++, timestamp++)
1082 if (index < (int)sizeof(line) - 2)
1083 line[index] = *timestamp;
1085 // append the character
1086 line[index++] = *msg;
1087 // if this is a newline character, we have a complete line to print
1088 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1090 // terminate the line
1094 // send to scrollable buffer
1095 if (con_initialized && cls.state != ca_dedicated)
1097 Con_PrintToHistory(line, mask);
1100 // send to terminal or dedicated server window
1104 if(sys_specialcharactertranslation.integer)
1106 for (p = (unsigned char *) line;*p; p++)
1107 *p = qfont_table[*p];
1110 if(sys_colortranslation.integer == 1) // ANSI
1112 static char printline[MAX_INPUTLINE * 4 + 3];
1113 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1114 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1119 for(in = line, out = printline; *in; ++in)
1123 case STRING_COLOR_TAG:
1124 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1126 char r = tolower(in[2]);
1127 char g = tolower(in[3]);
1128 char b = tolower(in[4]);
1129 // it's a hex digit already, so the else part needs no check --blub
1130 if(isdigit(r)) r -= '0';
1132 if(isdigit(g)) g -= '0';
1134 if(isdigit(b)) b -= '0';
1137 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1138 in += 3; // 3 only, the switch down there does the fourth
1145 case STRING_COLOR_TAG:
1147 *out++ = STRING_COLOR_TAG;
1153 if(lastcolor == 0) break; else lastcolor = 0;
1154 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1159 if(lastcolor == 1) break; else lastcolor = 1;
1160 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1165 if(lastcolor == 2) break; else lastcolor = 2;
1166 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1171 if(lastcolor == 3) break; else lastcolor = 3;
1172 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1177 if(lastcolor == 4) break; else lastcolor = 4;
1178 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1183 if(lastcolor == 5) break; else lastcolor = 5;
1184 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1189 if(lastcolor == 6) break; else lastcolor = 6;
1190 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1195 // bold normal color
1197 if(lastcolor == 8) break; else lastcolor = 8;
1198 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1201 *out++ = STRING_COLOR_TAG;
1208 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1225 Sys_PrintToTerminal(printline);
1227 else if(sys_colortranslation.integer == 2) // Quake
1229 Sys_PrintToTerminal(line);
1233 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1236 for(in = line, out = printline; *in; ++in)
1240 case STRING_COLOR_TAG:
1243 case STRING_COLOR_RGB_TAG_CHAR:
1244 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1249 *out++ = STRING_COLOR_TAG;
1250 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1253 case STRING_COLOR_TAG:
1255 *out++ = STRING_COLOR_TAG;
1270 *out++ = STRING_COLOR_TAG;
1280 Sys_PrintToTerminal(printline);
1283 // empty the line buffer
1294 void Con_MaskPrintf(int mask, const char *fmt, ...)
1297 char msg[MAX_INPUTLINE];
1299 va_start(argptr,fmt);
1300 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1303 Con_MaskPrint(mask, msg);
1311 void Con_Print(const char *msg)
1313 Con_MaskPrint(CON_MASK_PRINT, msg);
1321 void Con_Printf(const char *fmt, ...)
1324 char msg[MAX_INPUTLINE];
1326 va_start(argptr,fmt);
1327 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1330 Con_MaskPrint(CON_MASK_PRINT, msg);
1338 void Con_DPrint(const char *msg)
1340 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1348 void Con_DPrintf(const char *fmt, ...)
1351 char msg[MAX_INPUTLINE];
1353 va_start(argptr,fmt);
1354 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1357 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1362 ==============================================================================
1366 ==============================================================================
1373 The input line scrolls horizontally if typing goes beyond the right edge
1375 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1378 void Con_DrawInput (void)
1382 char editlinecopy[MAX_INPUTLINE+1], *text;
1385 if (!key_consoleactive)
1386 return; // don't draw anything
1388 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1389 text = editlinecopy;
1391 // Advanced Console Editing by Radix radix@planetquake.com
1392 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1393 // use strlen of edit_line instead of key_linepos to allow editing
1394 // of early characters w/o erasing
1396 y = (int)strlen(text);
1398 // append enoug nul-bytes to cover the utf8-versions of the cursor too
1399 for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i)
1402 // add the cursor frame
1403 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1405 if (!utf8_enable.integer)
1406 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1407 else if (y + 3 < (int)sizeof(editlinecopy)-1)
1409 int ofs = u8_bytelen(text + key_linepos, 1);
1412 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1416 memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1417 memcpy(text + key_linepos, curbuf, len);
1420 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1423 // text[key_linepos + 1] = 0;
1425 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth(text, key_linepos, con_textsize.value, con_textsize.value, false, FONT_CONSOLE);
1430 DrawQ_String(x, con_vislines - con_textsize.value*2, text, y + 3, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, FONT_CONSOLE );
1433 // key_line[key_linepos] = 0;
1439 float alignment; // 0 = left, 0.5 = center, 1 = right
1445 const char *continuationString;
1448 int colorindex; // init to -1
1452 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1454 con_text_info_t *ti = (con_text_info_t *) passthrough;
1457 ti->colorindex = -1;
1458 return ti->fontsize * ti->font->maxwidth;
1461 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1462 else if(maxWidth == -1)
1463 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1466 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1467 // Note: this is NOT a Con_Printf, as it could print recursively
1472 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1478 (void) isContinuation;
1482 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1484 con_text_info_t *ti = (con_text_info_t *) passthrough;
1486 if(ti->y < ti->ymin - 0.001)
1488 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1492 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1493 if(isContinuation && *ti->continuationString)
1494 x += (int) DrawQ_String(x, ti->y, ti->continuationString, strlen(ti->continuationString), ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, ti->font);
1496 DrawQ_String(x, ti->y, line, length, ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, &(ti->colorindex), false, ti->font);
1499 ti->y += ti->fontsize;
1503 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)
1507 int maxlines = (int) floor(height / fontsize + 0.01f);
1510 int continuationWidth = 0;
1512 double t = cl.time; // saved so it won't change
1515 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1516 ti.fontsize = fontsize;
1517 ti.alignment = alignment_x;
1520 ti.ymax = y + height;
1521 ti.continuationString = continuationString;
1524 Con_WordWidthFunc(&ti, NULL, &l, -1);
1525 l = strlen(continuationString);
1526 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1528 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1529 startidx = CON_LINES_COUNT;
1530 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1532 con_lineinfo_t *l = &CON_LINES(i);
1535 if((l->mask & mask_must) != mask_must)
1537 if(l->mask & mask_mustnot)
1539 if(maxage && (l->addtime < t - maxage))
1543 // Calculate its actual height...
1544 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1545 if(lines + mylines >= maxlines)
1547 nskip = lines + mylines - maxlines;
1556 // then center according to the calculated amount of lines...
1558 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1560 // then actually draw
1561 for(i = startidx; i < CON_LINES_COUNT; ++i)
1563 con_lineinfo_t *l = &CON_LINES(i);
1565 if((l->mask & mask_must) != mask_must)
1567 if(l->mask & mask_mustnot)
1569 if(maxage && (l->addtime < t - maxage))
1572 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1582 Draws the last few lines of output transparently over the game top
1585 void Con_DrawNotify (void)
1588 float chatstart, notifystart, inputsize;
1590 char temptext[MAX_INPUTLINE];
1594 ConBuffer_FixTimes(&con);
1596 numChatlines = con_chat.integer;
1597 chatpos = con_chatpos.integer;
1599 if (con_notify.integer < 0)
1600 Cvar_SetValueQuick(&con_notify, 0);
1601 if (gamemode == GAME_TRANSFUSION)
1602 v = 8; // vertical offset
1606 // GAME_NEXUIZ: center, otherwise left justify
1607 align = con_notifyalign.value;
1608 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1610 if(gamemode == GAME_NEXUIZ)
1618 // first chat, input line, then notify
1620 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1622 else if(chatpos > 0)
1624 // first notify, then (chatpos-1) empty lines, then chat, then input
1626 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1628 else // if(chatpos < 0)
1630 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1632 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1637 // just notify and input
1639 chatstart = 0; // shut off gcc warning
1642 v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_INPUT | CON_MASK_HIDENOTIFY | (numChatlines ? CON_MASK_CHAT : 0) | CON_MASK_DEVELOPER, con_notifytime.value, 0, notifystart, vid_conwidth.value, con_notify.value * con_notifysize.value, con_notifysize.value, align, 0.0, "");
1647 v = chatstart + numChatlines * con_chatsize.value;
1648 Con_DrawNotifyRect(CON_MASK_CHAT, CON_MASK_INPUT, con_chattime.value, 0, chatstart, vid_conwidth.value * con_chatwidth.value, v - chatstart, con_chatsize.value, 0.0, 1.0, (utf8_enable.integer ? "^3\xee\x80\x8c\xee\x80\x8c\xee\x80\x8c " : "^3\014\014\014 ")); // 015 is ·> character in conchars.tga
1651 if (key_dest == key_message)
1653 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1654 int colorindex = -1;
1656 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL);
1658 // LordHavoc: speedup, and other improvements
1660 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1662 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1664 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1667 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1668 x = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1671 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1679 Returns the height of a given console line; calculates it if necessary.
1682 int Con_LineHeight(int lineno)
1684 con_lineinfo_t *li = &CON_LINES(lineno);
1685 if(li->height == -1)
1687 float width = vid_conwidth.value;
1689 con_lineinfo_t *li = &CON_LINES(lineno);
1690 ti.fontsize = con_textsize.value;
1691 ti.font = FONT_CONSOLE;
1692 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1701 Draws a line of the console; returns its height in lines.
1702 If alpha is 0, the line is not drawn, but still wrapped and its height
1706 int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1708 float width = vid_conwidth.value;
1710 con_lineinfo_t *li = &CON_LINES(lineno);
1712 if((li->mask & mask_must) != mask_must)
1714 if((li->mask & mask_mustnot) != 0)
1717 ti.continuationString = "";
1719 ti.fontsize = con_textsize.value;
1720 ti.font = FONT_CONSOLE;
1722 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1727 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1734 Calculates the last visible line index and how much to show of it based on
1738 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1743 if(con_backscroll < 0)
1748 // now count until we saw con_backscroll actual lines
1749 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1750 if((CON_LINES(i).mask & mask_must) == mask_must)
1751 if((CON_LINES(i).mask & mask_mustnot) == 0)
1753 int h = Con_LineHeight(i);
1755 // line is the last visible line?
1757 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1759 *limitlast = lines_seen + h - con_backscroll;
1766 // if we get here, no line was on screen - scroll so that one line is
1768 con_backscroll = lines_seen - 1;
1776 Draws the console with the solid background
1777 The typing input line at the bottom should only be drawn if typing is allowed
1780 void Con_DrawConsole (int lines)
1783 int mask_mustnot = developer.integer ? 0 : CON_MASK_DEVELOPER;
1784 cachepic_t *conbackpic;
1789 if (con_backscroll < 0)
1792 con_vislines = lines;
1794 // draw the background
1795 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic("gfx/conback") : NULL;
1796 if (conbackpic && conbackpic->tex != r_texture_notexture)
1797 DrawQ_Pic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, cls.signon == SIGNONS ? scr_conalpha.value : 1.0f, 0); // always full alpha when not in game
1799 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, cls.signon == SIGNONS ? scr_conalpha.value : 1.0f, 0); // always full alpha when not in game
1800 DrawQ_String(vid_conwidth.integer - DrawQ_TextWidth(engineversion, 0, con_textsize.value, con_textsize.value, false, FONT_CONSOLE), lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0, NULL, true, FONT_CONSOLE);
1806 int count = CON_LINES_COUNT;
1807 float ymax = con_vislines - 2 * con_textsize.value;
1808 float y = ymax + con_textsize.value * con_backscroll;
1809 for (i = 0;i < count && y >= 0;i++)
1810 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
1811 // fix any excessive scrollback for the next frame
1812 if (i >= count && y >= 0)
1814 con_backscroll -= (int)(y / con_textsize.value);
1815 if (con_backscroll < 0)
1820 if(CON_LINES_COUNT > 0)
1822 int i, last, limitlast;
1824 float ymax = con_vislines - 2 * con_textsize.value;
1825 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1826 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1827 y = ymax - con_textsize.value;
1830 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1835 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
1837 break; // top of console buffer
1839 break; // top of console window
1846 // draw the input prompt, user text, and cursor if desired
1854 Prints not only map filename, but also
1855 its format (q1/q2/q3/hl) and even its message
1857 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1858 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1859 //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
1860 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1861 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1865 int i, k, max, p, o, min;
1868 unsigned char buf[1024];
1870 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1871 t = FS_Search(message, 1, true);
1874 if (t->numfilenames > 1)
1875 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1876 len = (unsigned char *)Z_Malloc(t->numfilenames);
1878 for(max=i=0;i<t->numfilenames;i++)
1880 k = (int)strlen(t->filenames[i]);
1890 for(i=0;i<t->numfilenames;i++)
1892 int lumpofs = 0, lumplen = 0;
1893 char *entities = NULL;
1894 const char *data = NULL;
1896 char entfilename[MAX_QPATH];
1897 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1899 f = FS_OpenVirtualFile(t->filenames[i], true);
1902 memset(buf, 0, 1024);
1903 FS_Read(f, buf, 1024);
1904 if (!memcmp(buf, "IBSP", 4))
1906 p = LittleLong(((int *)buf)[1]);
1907 if (p == Q3BSPVERSION)
1909 q3dheader_t *header = (q3dheader_t *)buf;
1910 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1911 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1913 else if (p == Q2BSPVERSION)
1915 q2dheader_t *header = (q2dheader_t *)buf;
1916 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1917 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1920 else if((p = BuffLittleLong(buf)) == BSPVERSION || p == 30)
1922 dheader_t *header = (dheader_t *)buf;
1923 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1924 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1928 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1929 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1930 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1931 if (!entities && lumplen >= 10)
1933 FS_Seek(f, lumpofs, SEEK_SET);
1934 entities = (char *)Z_Malloc(lumplen + 1);
1935 FS_Read(f, entities, lumplen);
1939 // if there are entities to parse, a missing message key just
1940 // means there is no title, so clear the message string now
1946 if (!COM_ParseToken_Simple(&data, false, false))
1948 if (com_token[0] == '{')
1950 if (com_token[0] == '}')
1952 // skip leading whitespace
1953 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
1954 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
1955 keyname[l] = com_token[k+l];
1957 if (!COM_ParseToken_Simple(&data, false, false))
1959 if (developer_extra.integer)
1960 Con_DPrintf("key: %s %s\n", keyname, com_token);
1961 if (!strcmp(keyname, "message"))
1963 // get the message contents
1964 strlcpy(message, com_token, sizeof(message));
1974 *(t->filenames[i]+len[i]+5) = 0;
1977 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1978 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1979 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1980 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1981 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1983 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1988 k = *(t->filenames[0]+5+p);
1991 for(i=1;i<t->numfilenames;i++)
1992 if(*(t->filenames[i]+5+p) != k)
1996 if(p > o && completedname && completednamebufferlength > 0)
1998 memset(completedname, 0, completednamebufferlength);
1999 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2009 New function for tab-completion system
2010 Added by EvilTypeGuy
2011 MEGA Thanks to Taniwha
2014 void Con_DisplayList(const char **list)
2016 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2017 const char **walk = list;
2020 len = (int)strlen(*walk);
2028 len = (int)strlen(*list);
2029 if (pos + maxlen >= width) {
2035 for (i = 0; i < (maxlen - len); i++)
2047 SanitizeString strips color tags from the string in
2048 and writes the result on string out
2050 void SanitizeString(char *in, char *out)
2054 if(*in == STRING_COLOR_TAG)
2059 out[0] = STRING_COLOR_TAG;
2063 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2070 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2073 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2075 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2082 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2087 else if (*in != STRING_COLOR_TAG)
2090 *out = qfont_table[*(unsigned char*)in];
2097 // Now it becomes TRICKY :D --blub
2098 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2099 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2100 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2101 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
2102 static int Nicks_matchpos;
2104 // co against <<:BLASTER:>> is true!?
2105 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2109 if(tolower(*a) == tolower(*b))
2123 return (*a < *b) ? -1 : 1;
2127 return (*a < *b) ? -1 : 1;
2131 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2134 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2136 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2137 return Nicks_strncasecmp_nospaces(a, b, a_len);
2138 return strncasecmp(a, b, a_len);
2141 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2143 // ignore non alphanumerics of B
2144 // if A contains a non-alphanumeric, B must contain it as well though!
2147 qboolean alnum_a, alnum_b;
2149 if(tolower(*a) == tolower(*b))
2151 if(*a == 0) // end of both strings, they're equal
2158 // not equal, end of one string?
2163 // ignore non alphanumerics
2164 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2165 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2166 if(!alnum_a) // b must contain this
2167 return (*a < *b) ? -1 : 1;
2170 // otherwise, both are alnum, they're just not equal, return the appropriate number
2172 return (*a < *b) ? -1 : 1;
2178 /* Nicks_CompleteCountPossible
2180 Count the number of possible nicks to complete
2182 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2191 if(!con_nickcompletion.integer)
2194 // changed that to 1
2195 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2198 for(i = 0; i < cl.maxclients; ++i)
2201 if(!cl.scores[p].name[0])
2204 SanitizeString(cl.scores[p].name, name);
2205 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2210 length = strlen(name);
2212 spos = pos - 1; // no need for a minimum of characters :)
2216 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2218 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2219 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2225 if(isCon && spos == 0)
2227 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2233 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2234 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2236 // the sanitized list
2237 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2240 Nicks_matchpos = match;
2243 Nicks_offset[count] = s - (&line[match]);
2244 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2251 void Cmd_CompleteNicksPrint(int count)
2254 for(i = 0; i < count; ++i)
2255 Con_Printf("%s\n", Nicks_list[i]);
2258 void Nicks_CutMatchesNormal(int count)
2260 // cut match 0 down to the longest possible completion
2263 c = strlen(Nicks_sanlist[0]) - 1;
2264 for(i = 1; i < count; ++i)
2266 l = strlen(Nicks_sanlist[i]) - 1;
2270 for(l = 0; l <= c; ++l)
2271 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2277 Nicks_sanlist[0][c+1] = 0;
2278 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2281 unsigned int Nicks_strcleanlen(const char *s)
2286 if( (*s >= 'a' && *s <= 'z') ||
2287 (*s >= 'A' && *s <= 'Z') ||
2288 (*s >= '0' && *s <= '9') ||
2296 void Nicks_CutMatchesAlphaNumeric(int count)
2298 // cut match 0 down to the longest possible completion
2301 char tempstr[sizeof(Nicks_sanlist[0])];
2303 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2305 c = strlen(Nicks_sanlist[0]);
2306 for(i = 0, l = 0; i < (int)c; ++i)
2308 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2309 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2310 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2312 tempstr[l++] = Nicks_sanlist[0][i];
2317 for(i = 1; i < count; ++i)
2320 b = Nicks_sanlist[i];
2330 if(tolower(*a) == tolower(*b))
2336 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2338 // b is alnum, so cut
2345 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2346 Nicks_CutMatchesNormal(count);
2347 //if(!Nicks_sanlist[0][0])
2348 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2350 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2351 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2355 void Nicks_CutMatchesNoSpaces(int count)
2357 // cut match 0 down to the longest possible completion
2360 char tempstr[sizeof(Nicks_sanlist[0])];
2363 c = strlen(Nicks_sanlist[0]);
2364 for(i = 0, l = 0; i < (int)c; ++i)
2366 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2368 tempstr[l++] = Nicks_sanlist[0][i];
2373 for(i = 1; i < count; ++i)
2376 b = Nicks_sanlist[i];
2386 if(tolower(*a) == tolower(*b))
2400 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2401 Nicks_CutMatchesNormal(count);
2402 //if(!Nicks_sanlist[0][0])
2403 //Con_Printf("TS: %s\n", tempstr);
2404 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2406 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2407 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2411 void Nicks_CutMatches(int count)
2413 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2414 Nicks_CutMatchesAlphaNumeric(count);
2415 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2416 Nicks_CutMatchesNoSpaces(count);
2418 Nicks_CutMatchesNormal(count);
2421 const char **Nicks_CompleteBuildList(int count)
2425 // the list is freed by Con_CompleteCommandLine, so create a char**
2426 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2428 for(; bpos < count; ++bpos)
2429 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2431 Nicks_CutMatches(count);
2439 Restores the previous used color, after the autocompleted name.
2441 int Nicks_AddLastColor(char *buffer, int pos)
2443 qboolean quote_added = false;
2445 int color = STRING_COLOR_DEFAULT + '0';
2446 char r = 0, g = 0, b = 0;
2448 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2450 // we'll have to add a quote :)
2451 buffer[pos++] = '\"';
2455 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2457 // add color when no quote was added, or when flags &4?
2459 for(match = Nicks_matchpos-1; match >= 0; --match)
2461 if(buffer[match] == STRING_COLOR_TAG)
2463 if( isdigit(buffer[match+1]) )
2465 color = buffer[match+1];
2468 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2470 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2472 r = buffer[match+2];
2473 g = buffer[match+3];
2474 b = buffer[match+4];
2483 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2485 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2486 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2489 buffer[pos++] = STRING_COLOR_TAG;
2492 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2498 buffer[pos++] = color;
2503 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2506 /*if(!con_nickcompletion.integer)
2507 return; is tested in Nicks_CompletionCountPossible */
2508 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2514 msg = Nicks_list[0];
2515 len = min(size - Nicks_matchpos - 3, strlen(msg));
2516 memcpy(&buffer[Nicks_matchpos], msg, len);
2517 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2518 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2519 buffer[len++] = ' ';
2526 Con_Printf("\n%i possible nicks:\n", n);
2527 Cmd_CompleteNicksPrint(n);
2529 Nicks_CutMatches(n);
2531 msg = Nicks_sanlist[0];
2532 len = min(size - Nicks_matchpos, strlen(msg));
2533 memcpy(&buffer[Nicks_matchpos], msg, len);
2534 buffer[Nicks_matchpos + len] = 0;
2536 return Nicks_matchpos + len;
2543 Con_CompleteCommandLine
2545 New function for tab-completion system
2546 Added by EvilTypeGuy
2547 Thanks to Fett erich@heintz.com
2549 Enhanced to tab-complete map names by [515]
2552 void Con_CompleteCommandLine (void)
2554 const char *cmd = "";
2556 const char **list[4] = {0, 0, 0, 0};
2559 int c, v, a, i, cmd_len, pos, k;
2560 int n; // nicks --blub
2561 const char *space, *patterns;
2563 //find what we want to complete
2568 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2574 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2575 key_line[key_linepos] = 0; //hide them
2577 space = strchr(key_line + 1, ' ');
2578 if(space && pos == (space - key_line) + 1)
2580 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2582 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2583 if(patterns && !*patterns)
2584 patterns = NULL; // get rid of the empty string
2586 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2590 if (GetMapList(s, t, sizeof(t)))
2592 // first move the cursor
2593 key_linepos += (int)strlen(t) - (int)strlen(s);
2595 // and now do the actual work
2597 strlcat(key_line, t, MAX_INPUTLINE);
2598 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2600 // and fix the cursor
2601 if(key_linepos > (int) strlen(key_line))
2602 key_linepos = (int) strlen(key_line);
2611 stringlist_t resultbuf, dirbuf;
2614 // // store completion patterns (space separated) for command foo in con_completion_foo
2615 // set con_completion_foo "foodata/*.foodefault *.foo"
2618 // Note: patterns with slash are always treated as absolute
2619 // patterns; patterns without slash search in the innermost
2620 // directory the user specified. There is no way to "complete into"
2621 // a directory as of now, as directories seem to be unknown to the
2625 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2626 // set con_completion_playdemo "*.dem"
2627 // set con_completion_play "*.wav *.ogg"
2629 // TODO somehow add support for directories; these shall complete
2630 // to their name + an appended slash.
2632 stringlistinit(&resultbuf);
2633 stringlistinit(&dirbuf);
2634 while(COM_ParseToken_Simple(&patterns, false, false))
2637 if(strchr(com_token, '/'))
2639 search = FS_Search(com_token, true, true);
2643 const char *slash = strrchr(s, '/');
2646 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2647 strlcat(t, com_token, sizeof(t));
2648 search = FS_Search(t, true, true);
2651 search = FS_Search(com_token, true, true);
2655 for(i = 0; i < search->numfilenames; ++i)
2656 if(!strncmp(search->filenames[i], s, strlen(s)))
2657 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2658 stringlistappend(&resultbuf, search->filenames[i]);
2659 FS_FreeSearch(search);
2663 // In any case, add directory names
2666 const char *slash = strrchr(s, '/');
2669 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2670 strlcat(t, "*", sizeof(t));
2671 search = FS_Search(t, true, true);
2674 search = FS_Search("*", true, true);
2677 for(i = 0; i < search->numfilenames; ++i)
2678 if(!strncmp(search->filenames[i], s, strlen(s)))
2679 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2680 stringlistappend(&dirbuf, search->filenames[i]);
2681 FS_FreeSearch(search);
2685 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2688 unsigned int matchchars;
2689 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2691 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2694 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2696 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2700 stringlistsort(&resultbuf); // dirbuf is already sorted
2701 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2702 for(i = 0; i < dirbuf.numstrings; ++i)
2704 Con_Printf("%s/\n", dirbuf.strings[i]);
2706 for(i = 0; i < resultbuf.numstrings; ++i)
2708 Con_Printf("%s\n", resultbuf.strings[i]);
2710 matchchars = sizeof(t) - 1;
2711 if(resultbuf.numstrings > 0)
2713 p = resultbuf.strings[0];
2714 q = resultbuf.strings[resultbuf.numstrings - 1];
2715 for(; *p && *p == *q; ++p, ++q);
2716 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2718 if(dirbuf.numstrings > 0)
2720 p = dirbuf.strings[0];
2721 q = dirbuf.strings[dirbuf.numstrings - 1];
2722 for(; *p && *p == *q; ++p, ++q);
2723 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2725 // now p points to the first non-equal character, or to the end
2726 // of resultbuf.strings[0]. We want to append the characters
2727 // from resultbuf.strings[0] to (not including) p as these are
2728 // the unique prefix
2729 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2732 // first move the cursor
2733 key_linepos += (int)strlen(t) - (int)strlen(s);
2735 // and now do the actual work
2737 strlcat(key_line, t, MAX_INPUTLINE);
2738 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2740 // and fix the cursor
2741 if(key_linepos > (int) strlen(key_line))
2742 key_linepos = (int) strlen(key_line);
2744 stringlistfreecontents(&resultbuf);
2745 stringlistfreecontents(&dirbuf);
2747 return; // bail out, when we complete for a command that wants a file name
2752 // Count number of possible matches and print them
2753 c = Cmd_CompleteCountPossible(s);
2756 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2757 Cmd_CompleteCommandPrint(s);
2759 v = Cvar_CompleteCountPossible(s);
2762 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2763 Cvar_CompleteCvarPrint(s);
2765 a = Cmd_CompleteAliasCountPossible(s);
2768 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2769 Cmd_CompleteAliasPrint(s);
2771 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2774 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2775 Cmd_CompleteNicksPrint(n);
2778 if (!(c + v + a + n)) // No possible matches
2781 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2786 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2788 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2790 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2792 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2794 for (cmd_len = (int)strlen(s);;cmd_len++)
2797 for (i = 0; i < 3; i++)
2799 for (l = list[i];*l;l++)
2800 if ((*l)[cmd_len] != cmd[cmd_len])
2802 // all possible matches share this character, so we continue...
2805 // if all matches ended at the same position, stop
2806 // (this means there is only one match)
2812 // prevent a buffer overrun by limiting cmd_len according to remaining space
2813 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2817 memcpy(&key_line[key_linepos], cmd, cmd_len);
2818 key_linepos += cmd_len;
2819 // if there is only one match, add a space after it
2820 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2823 { // was a nick, might have an offset, and needs colors ;) --blub
2824 key_linepos = pos - Nicks_offset[0];
2825 cmd_len = strlen(Nicks_list[0]);
2826 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2828 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2829 key_linepos += cmd_len;
2830 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2831 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2833 key_line[key_linepos++] = ' ';
2837 // use strlcat to avoid a buffer overrun
2838 key_line[key_linepos] = 0;
2839 strlcat(key_line, s2, sizeof(key_line));
2841 // free the command, cvar, and alias lists
2842 for (i = 0; i < 4; i++)
2844 Mem_Free((void *)list[i]);