2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 #if !defined(WIN32) || defined(__MINGW32__)
32 float con_cursorspeed = 4;
34 // lines up from bottom to display
39 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
40 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
41 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
43 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
44 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
45 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
47 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
48 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
49 cvar_t con_chatpos = {CVAR_SAVE, "con_chatpos","0", "where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top)"};
50 cvar_t con_chatrect = {CVAR_SAVE, "con_chatrect","0", "use con_chatrect_x and _y to position con_notify and con_chat freely instead of con_chatpos"};
51 cvar_t con_chatrect_x = {CVAR_SAVE, "con_chatrect_x","", "where to put chat, relative x coordinate of left edge on screen (use con_chatwidth for width)"};
52 cvar_t con_chatrect_y = {CVAR_SAVE, "con_chatrect_y","", "where to put chat, relative y coordinate of top edge on screen (use con_chat for line count)"};
53 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
54 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
55 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
56 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
57 cvar_t con_chatsound = {CVAR_SAVE, "con_chatsound","1", "enables chat sound to play on message"};
60 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)"};
62 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)"};
64 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)"};
68 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
69 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
70 "0: add nothing after completion. "
71 "1: add the last color after completion. "
72 "2: add a quote when starting a quote instead of the color. "
73 "4: will replace 1, will force color, even after a quote. "
74 "8: ignore non-alphanumerics. "
75 "16: ignore spaces. "};
76 #define NICKS_ADD_COLOR 1
77 #define NICKS_ADD_QUOTE 2
78 #define NICKS_FORCE_COLOR 4
79 #define NICKS_ALPHANUMERICS_ONLY 8
80 #define NICKS_NO_SPACES 16
82 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
83 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
84 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
89 qboolean con_initialized;
91 // used for server replies to rcon command
92 lhnetsocket_t *rcon_redirect_sock = NULL;
93 lhnetaddress_t *rcon_redirect_dest = NULL;
94 int rcon_redirect_bufferpos = 0;
95 char rcon_redirect_buffer[1400];
97 // generic functions for console buffers
99 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
102 buf->textsize = textsize;
103 buf->text = (char *) Mem_Alloc(mempool, textsize);
104 buf->maxlines = maxlines;
105 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
106 buf->lines_first = 0;
107 buf->lines_count = 0;
115 void ConBuffer_Clear (conbuffer_t *buf)
117 buf->lines_count = 0;
125 void ConBuffer_Shutdown(conbuffer_t *buf)
129 Mem_Free(buf->lines);
138 Notifies the console code about the current time
139 (and shifts back times of other entries when the time
143 void ConBuffer_FixTimes(conbuffer_t *buf)
146 if(buf->lines_count >= 1)
148 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
151 for(i = 0; i < buf->lines_count; ++i)
152 CONBUFFER_LINES(buf, i).addtime += diff;
161 Deletes the first line from the console history.
164 void ConBuffer_DeleteLine(conbuffer_t *buf)
166 if(buf->lines_count == 0)
169 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
174 ConBuffer_DeleteLastLine
176 Deletes the last line from the console history.
179 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
181 if(buf->lines_count == 0)
190 Checks if there is space for a line of the given length, and if yes, returns a
191 pointer to the start of such a space, and NULL otherwise.
194 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
196 if(len > buf->textsize)
198 if(buf->lines_count == 0)
202 char *firstline_start = buf->lines[buf->lines_first].start;
203 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
204 // the buffer is cyclic, so we first have two cases...
205 if(firstline_start < lastline_onepastend) // buffer is contiguous
208 if(len <= buf->text + buf->textsize - lastline_onepastend)
209 return lastline_onepastend;
211 else if(len <= firstline_start - buf->text)
216 else // buffer has a contiguous hole
218 if(len <= firstline_start - lastline_onepastend)
219 return lastline_onepastend;
230 Appends a given string as a new line to the console.
233 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
238 // developer_memory 1 during shutdown prints while conbuffer_t is being freed
242 ConBuffer_FixTimes(buf);
244 if(len >= buf->textsize)
247 // only display end of line.
248 line += len - buf->textsize + 1;
249 len = buf->textsize - 1;
251 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
252 ConBuffer_DeleteLine(buf);
253 memcpy(putpos, line, len);
257 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
259 p = &CONBUFFER_LINES_LAST(buf);
262 p->addtime = cl.time;
264 p->height = -1; // calculate when needed
267 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
271 start = buf->lines_count;
272 for(i = start - 1; i >= 0; --i)
274 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
276 if((l->mask & mask_must) != mask_must)
278 if(l->mask & mask_mustnot)
287 int Con_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
290 for(i = start + 1; i < buf->lines_count; ++i)
292 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
294 if((l->mask & mask_must) != mask_must)
296 if(l->mask & mask_mustnot)
305 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
307 static char copybuf[MAX_INPUTLINE];
308 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
309 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
310 strlcpy(copybuf, l->start, sz);
315 ==============================================================================
319 ==============================================================================
324 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
325 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"};
326 char log_dest_buffer[1400]; // UDP packet
327 size_t log_dest_buffer_pos;
328 unsigned int log_dest_buffer_appending;
329 char crt_log_file [MAX_OSPATH] = "";
330 qfile_t* logfile = NULL;
332 unsigned char* logqueue = NULL;
334 size_t logq_size = 0;
336 void Log_ConPrint (const char *msg);
343 static void Log_DestBuffer_Init(void)
345 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
346 log_dest_buffer_pos = 5;
354 void Log_DestBuffer_Flush(void)
356 lhnetaddress_t log_dest_addr;
357 lhnetsocket_t *log_dest_socket;
358 const char *s = log_dest_udp.string;
359 qboolean have_opened_temp_sockets = false;
360 if(s) if(log_dest_buffer_pos > 5)
362 ++log_dest_buffer_appending;
363 log_dest_buffer[log_dest_buffer_pos++] = 0;
365 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
367 have_opened_temp_sockets = true;
368 NetConn_OpenServerPorts(true);
371 while(COM_ParseToken_Console(&s))
372 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
374 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
376 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
378 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
381 if(have_opened_temp_sockets)
382 NetConn_CloseServerPorts();
383 --log_dest_buffer_appending;
385 log_dest_buffer_pos = 0;
393 const char* Log_Timestamp (const char *desc)
395 static char timestamp [128];
402 char timestring [64];
404 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
407 localtime_s (&crt_tm, &crt_time);
408 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
410 crt_tm = localtime (&crt_time);
411 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
415 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
417 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
430 if (logfile != NULL || log_file.string[0] == '\0')
433 logfile = FS_OpenRealFile(log_file.string, "a", false);
436 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
437 FS_Print (logfile, Log_Timestamp ("Log started"));
447 void Log_Close (void)
452 FS_Print (logfile, Log_Timestamp ("Log stopped"));
453 FS_Print (logfile, "\n");
457 crt_log_file[0] = '\0';
466 void Log_Start (void)
472 // Dump the contents of the log queue into the log file and free it
473 if (logqueue != NULL)
475 unsigned char *temp = logqueue;
480 FS_Write (logfile, temp, logq_ind);
481 if(*log_dest_udp.string)
483 for(pos = 0; pos < logq_ind; )
485 if(log_dest_buffer_pos == 0)
486 Log_DestBuffer_Init();
487 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
488 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
489 log_dest_buffer_pos += n;
490 Log_DestBuffer_Flush();
507 void Log_ConPrint (const char *msg)
509 static qboolean inprogress = false;
511 // don't allow feedback loops with memory error reports
516 // Until the host is completely initialized, we maintain a log queue
517 // to store the messages, since the log can't be started before
518 if (logqueue != NULL)
520 size_t remain = logq_size - logq_ind;
521 size_t len = strlen (msg);
523 // If we need to enlarge the log queue
526 size_t factor = ((logq_ind + len) / logq_size) + 1;
527 unsigned char* newqueue;
530 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
531 memcpy (newqueue, logqueue, logq_ind);
534 remain = logq_size - logq_ind;
536 memcpy (&logqueue[logq_ind], msg, len);
543 // Check if log_file has changed
544 if (strcmp (crt_log_file, log_file.string) != 0)
550 // If a log file is available
552 FS_Print (logfile, msg);
563 void Log_Printf (const char *logfilename, const char *fmt, ...)
567 file = FS_OpenRealFile(logfilename, "a", true);
572 va_start (argptr, fmt);
573 FS_VPrintf (file, fmt, argptr);
582 ==============================================================================
586 ==============================================================================
594 void Con_ToggleConsole_f (void)
596 // toggle the 'user wants console' bit
597 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
606 void Con_ClearNotify (void)
609 for(i = 0; i < CON_LINES_COUNT; ++i)
610 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
619 void Con_MessageMode_f (void)
621 key_dest = key_message;
622 chat_mode = 0; // "say"
633 void Con_MessageMode2_f (void)
635 key_dest = key_message;
636 chat_mode = 1; // "say_team"
646 void Con_CommandMode_f (void)
648 key_dest = key_message;
651 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
652 chat_bufferlen = strlen(chat_buffer);
654 chat_mode = -1; // command
662 void Con_CheckResize (void)
667 f = bound(1, con_textsize.value, 128);
668 if(f != con_textsize.value)
669 Cvar_SetValueQuick(&con_textsize, f);
670 width = (int)floor(vid_conwidth.value / con_textsize.value);
671 width = bound(1, width, con.textsize/4);
672 // FIXME uses con in a non abstracted way
674 if (width == con_linewidth)
677 con_linewidth = width;
679 for(i = 0; i < CON_LINES_COUNT; ++i)
680 CON_LINES(i).height = -1; // recalculate when next needed
686 //[515]: the simplest command ever
687 //LordHavoc: not so simple after I made it print usage...
688 static void Con_Maps_f (void)
692 Con_Printf("usage: maps [mapnameprefix]\n");
695 else if (Cmd_Argc() == 2)
696 GetMapList(Cmd_Argv(1), NULL, 0);
698 GetMapList("", NULL, 0);
701 void Con_ConDump_f (void)
707 Con_Printf("usage: condump <filename>\n");
710 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
713 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
716 for(i = 0; i < CON_LINES_COUNT; ++i)
718 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
719 FS_Write(file, "\n", 1);
724 void Con_Clear_f (void)
726 ConBuffer_Clear(&con);
737 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
739 // Allocate a log queue, this will be freed after configs are parsed
740 logq_size = MAX_INPUTLINE;
741 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
744 Cvar_RegisterVariable (&sys_colortranslation);
745 Cvar_RegisterVariable (&sys_specialcharactertranslation);
747 Cvar_RegisterVariable (&log_file);
748 Cvar_RegisterVariable (&log_dest_udp);
750 // support for the classic Quake option
751 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
752 if (COM_CheckParm ("-condebug") != 0)
753 Cvar_SetQuick (&log_file, "qconsole.log");
755 // register our cvars
756 Cvar_RegisterVariable (&con_chat);
757 Cvar_RegisterVariable (&con_chatpos);
758 Cvar_RegisterVariable (&con_chatrect_x);
759 Cvar_RegisterVariable (&con_chatrect_y);
760 Cvar_RegisterVariable (&con_chatrect);
761 Cvar_RegisterVariable (&con_chatsize);
762 Cvar_RegisterVariable (&con_chattime);
763 Cvar_RegisterVariable (&con_chatwidth);
764 Cvar_RegisterVariable (&con_notify);
765 Cvar_RegisterVariable (&con_notifyalign);
766 Cvar_RegisterVariable (&con_notifysize);
767 Cvar_RegisterVariable (&con_notifytime);
768 Cvar_RegisterVariable (&con_textsize);
769 Cvar_RegisterVariable (&con_chatsound);
772 Cvar_RegisterVariable (&con_nickcompletion);
773 Cvar_RegisterVariable (&con_nickcompletion_flags);
775 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
776 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
777 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
779 // register our commands
780 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
781 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
782 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
783 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
784 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
785 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
786 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
788 con_initialized = true;
789 Con_DPrint("Console initialized.\n");
792 void Con_Shutdown (void)
794 ConBuffer_Shutdown(&con);
801 Handles cursor positioning, line wrapping, etc
802 All console printing must go through this in order to be displayed
803 If no console is visible, the notify window will pop up.
806 void Con_PrintToHistory(const char *txt, int mask)
809 // \n goes to next line
810 // \r deletes current line and makes a new one
812 static int cr_pending = 0;
813 static char buf[CON_TEXTSIZE];
814 static int bufpos = 0;
816 if(!con.text) // FIXME uses a non-abstracted property of con
823 ConBuffer_DeleteLastLine(&con);
831 ConBuffer_AddLine(&con, buf, bufpos, mask);
836 ConBuffer_AddLine(&con, buf, bufpos, mask);
840 buf[bufpos++] = *txt;
841 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
843 ConBuffer_AddLine(&con, buf, bufpos, mask);
851 /*! The translation table between the graphical font and plain ASCII --KB */
852 static char qfont_table[256] = {
853 '\0', '#', '#', '#', '#', '.', '#', '#',
854 '#', 9, 10, '#', ' ', 13, '.', '.',
855 '[', ']', '0', '1', '2', '3', '4', '5',
856 '6', '7', '8', '9', '.', '<', '=', '>',
857 ' ', '!', '"', '#', '$', '%', '&', '\'',
858 '(', ')', '*', '+', ',', '-', '.', '/',
859 '0', '1', '2', '3', '4', '5', '6', '7',
860 '8', '9', ':', ';', '<', '=', '>', '?',
861 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
862 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
863 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
864 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
865 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
866 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
867 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
868 'x', 'y', 'z', '{', '|', '}', '~', '<',
870 '<', '=', '>', '#', '#', '.', '#', '#',
871 '#', '#', ' ', '#', ' ', '>', '.', '.',
872 '[', ']', '0', '1', '2', '3', '4', '5',
873 '6', '7', '8', '9', '.', '<', '=', '>',
874 ' ', '!', '"', '#', '$', '%', '&', '\'',
875 '(', ')', '*', '+', ',', '-', '.', '/',
876 '0', '1', '2', '3', '4', '5', '6', '7',
877 '8', '9', ':', ';', '<', '=', '>', '?',
878 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
879 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
880 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
881 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
882 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
883 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
884 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
885 'x', 'y', 'z', '{', '|', '}', '~', '<'
888 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest)
890 rcon_redirect_sock = sock;
891 rcon_redirect_dest = dest;
892 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
893 rcon_redirect_bufferpos = 5;
896 void Con_Rcon_Redirect_Flush(void)
898 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
899 NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
900 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
901 rcon_redirect_bufferpos = 5;
904 void Con_Rcon_Redirect_End(void)
906 Con_Rcon_Redirect_Flush();
907 rcon_redirect_dest = NULL;
908 rcon_redirect_sock = NULL;
911 void Con_Rcon_Redirect_Abort(void)
913 rcon_redirect_dest = NULL;
914 rcon_redirect_sock = NULL;
922 /// Adds a character to the rcon buffer.
923 void Con_Rcon_AddChar(int c)
925 if(log_dest_buffer_appending)
927 ++log_dest_buffer_appending;
929 // if this print is in response to an rcon command, add the character
930 // to the rcon redirect buffer
932 if (rcon_redirect_dest)
934 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
935 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
936 Con_Rcon_Redirect_Flush();
938 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
940 if(log_dest_buffer_pos == 0)
941 Log_DestBuffer_Init();
942 log_dest_buffer[log_dest_buffer_pos++] = c;
943 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
944 Log_DestBuffer_Flush();
947 log_dest_buffer_pos = 0;
949 --log_dest_buffer_appending;
953 * Convert an RGB color to its nearest quake color.
954 * I'll cheat on this a bit by translating the colors to HSV first,
955 * S and V decide if it's black or white, otherwise, H will decide the
957 * @param _r Red (0-255)
958 * @param _g Green (0-255)
959 * @param _b Blue (0-255)
960 * @return A quake color character.
962 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
964 float r = ((float)_r)/255.0;
965 float g = ((float)_g)/255.0;
966 float b = ((float)_b)/255.0;
967 float min = min(r, min(g, b));
968 float max = max(r, max(g, b));
970 int h; ///< Hue angle [0,360]
971 float s; ///< Saturation [0,1]
972 float v = max; ///< In HSV v == max [0,1]
979 // Saturation threshold. We now say 0.2 is the minimum value for a color!
982 // If the value is less than half, return a black color code.
983 // Otherwise return a white one.
989 // Let's get the hue angle to define some colors:
993 h = (int)(60.0 * (g-b)/(max-min))%360;
995 h = (int)(60.0 * (b-r)/(max-min) + 120);
996 else // if(max == b) redundant check
997 h = (int)(60.0 * (r-g)/(max-min) + 240);
999 if(h < 36) // *red* to orange
1001 else if(h < 80) // orange over *yellow* to evilish-bright-green
1003 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1005 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1007 else if(h < 270) // darkish blue over *dark blue* to cool purple
1009 else if(h < 330) // cool purple over *purple* to ugly swiny red
1011 else // ugly red to red closes the circly
1020 extern cvar_t timestamps;
1021 extern cvar_t timeformat;
1022 extern qboolean sys_nostdout;
1023 void Con_MaskPrint(int additionalmask, const char *msg)
1025 static int mask = 0;
1026 static int index = 0;
1027 static char line[MAX_INPUTLINE];
1031 Con_Rcon_AddChar(*msg);
1033 mask |= additionalmask;
1034 // if this is the beginning of a new line, print timestamp
1037 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1039 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1040 line[index++] = STRING_COLOR_TAG;
1041 // assert( STRING_COLOR_DEFAULT < 10 )
1042 line[index++] = STRING_COLOR_DEFAULT + '0';
1043 // special color codes for chat messages must always come first
1044 // for Con_PrintToHistory to work properly
1045 if (*msg == 1 || *msg == 2)
1050 if (con_chatsound.value)
1052 if(gamemode == GAME_NEXUIZ)
1054 if(msg[1] == '\r' && cl.foundtalk2wav)
1055 S_LocalSound ("sound/misc/talk2.wav");
1057 S_LocalSound ("sound/misc/talk.wav");
1061 if (msg[1] == '(' && cl.foundtalk2wav)
1062 S_LocalSound ("sound/misc/talk2.wav");
1064 S_LocalSound ("sound/misc/talk.wav");
1067 mask = CON_MASK_CHAT;
1069 line[index++] = STRING_COLOR_TAG;
1070 line[index++] = '3';
1072 Con_Rcon_AddChar(*msg);
1075 for (;*timestamp;index++, timestamp++)
1076 if (index < (int)sizeof(line) - 2)
1077 line[index] = *timestamp;
1079 // append the character
1080 line[index++] = *msg;
1081 // if this is a newline character, we have a complete line to print
1082 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1084 // terminate the line
1088 // send to scrollable buffer
1089 if (con_initialized && cls.state != ca_dedicated)
1091 Con_PrintToHistory(line, mask);
1094 // send to terminal or dedicated server window
1098 if(sys_specialcharactertranslation.integer)
1100 for (p = (unsigned char *) line;*p; p++)
1101 *p = qfont_table[*p];
1104 if(sys_colortranslation.integer == 1) // ANSI
1106 static char printline[MAX_INPUTLINE * 4 + 3];
1107 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1108 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1113 for(in = line, out = printline; *in; ++in)
1117 case STRING_COLOR_TAG:
1118 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1120 char r = tolower(in[2]);
1121 char g = tolower(in[3]);
1122 char b = tolower(in[4]);
1123 // it's a hex digit already, so the else part needs no check --blub
1124 if(isdigit(r)) r -= '0';
1126 if(isdigit(g)) g -= '0';
1128 if(isdigit(b)) b -= '0';
1131 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1132 in += 3; // 3 only, the switch down there does the fourth
1139 case STRING_COLOR_TAG:
1141 *out++ = STRING_COLOR_TAG;
1147 if(lastcolor == 0) break; else lastcolor = 0;
1148 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1153 if(lastcolor == 1) break; else lastcolor = 1;
1154 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1159 if(lastcolor == 2) break; else lastcolor = 2;
1160 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1165 if(lastcolor == 3) break; else lastcolor = 3;
1166 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1171 if(lastcolor == 4) break; else lastcolor = 4;
1172 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1177 if(lastcolor == 5) break; else lastcolor = 5;
1178 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1183 if(lastcolor == 6) break; else lastcolor = 6;
1184 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1189 // bold normal color
1191 if(lastcolor == 8) break; else lastcolor = 8;
1192 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1195 *out++ = STRING_COLOR_TAG;
1202 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1219 Sys_PrintToTerminal(printline);
1221 else if(sys_colortranslation.integer == 2) // Quake
1223 Sys_PrintToTerminal(line);
1227 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1230 for(in = line, out = printline; *in; ++in)
1234 case STRING_COLOR_TAG:
1237 case STRING_COLOR_RGB_TAG_CHAR:
1238 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1243 *out++ = STRING_COLOR_TAG;
1244 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1247 case STRING_COLOR_TAG:
1249 *out++ = STRING_COLOR_TAG;
1264 *out++ = STRING_COLOR_TAG;
1274 Sys_PrintToTerminal(printline);
1277 // empty the line buffer
1288 void Con_MaskPrintf(int mask, const char *fmt, ...)
1291 char msg[MAX_INPUTLINE];
1293 va_start(argptr,fmt);
1294 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1297 Con_MaskPrint(mask, msg);
1305 void Con_Print(const char *msg)
1307 Con_MaskPrint(CON_MASK_PRINT, msg);
1315 void Con_Printf(const char *fmt, ...)
1318 char msg[MAX_INPUTLINE];
1320 va_start(argptr,fmt);
1321 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1324 Con_MaskPrint(CON_MASK_PRINT, msg);
1332 void Con_DPrint(const char *msg)
1334 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1337 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1345 void Con_DPrintf(const char *fmt, ...)
1348 char msg[MAX_INPUTLINE];
1350 if(developer.integer < 0) // at 0, we still add to the buffer but hide
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 extern cvar_t r_font_disable_freetype;
1379 void Con_DrawInput (void)
1383 char editlinecopy[MAX_INPUTLINE+1], *text;
1388 if (!key_consoleactive)
1389 return; // don't draw anything
1391 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1392 text = editlinecopy;
1394 // Advanced Console Editing by Radix radix@planetquake.com
1395 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1396 // use strlen of edit_line instead of key_linepos to allow editing
1397 // of early characters w/o erasing
1399 y = (int)strlen(text);
1401 // append enoug nul-bytes to cover the utf8-versions of the cursor too
1402 for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i)
1405 // add the cursor frame
1406 if (r_font_disable_freetype.integer)
1408 // this code is freetype incompatible!
1409 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1411 if (!utf8_enable.integer)
1412 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1413 else if (y + 3 < (int)sizeof(editlinecopy)-1)
1415 int ofs = u8_bytelen(text + key_linepos, 1);
1418 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1422 memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1423 memcpy(text + key_linepos, curbuf, len);
1426 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1430 // text[key_linepos + 1] = 0;
1432 len_out = key_linepos;
1434 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1435 x = vid_conwidth.value * 0.95 - xo; // scroll
1440 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 );
1442 // add a cursor on top of this (when using freetype)
1443 if (!r_font_disable_freetype.integer)
1445 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1447 if (!utf8_enable.integer)
1449 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1456 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1457 memcpy(text, curbuf, len);
1460 DrawQ_String(x + xo, con_vislines - con_textsize.value*2, text, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, FONT_CONSOLE);
1465 // key_line[key_linepos] = 0;
1471 float alignment; // 0 = left, 0.5 = center, 1 = right
1477 const char *continuationString;
1480 int colorindex; // init to -1
1484 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1486 con_text_info_t *ti = (con_text_info_t *) passthrough;
1489 ti->colorindex = -1;
1490 return ti->fontsize * ti->font->maxwidth;
1493 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1494 else if(maxWidth == -1)
1495 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1498 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1499 // Note: this is NOT a Con_Printf, as it could print recursively
1504 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1510 (void) isContinuation;
1514 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1516 con_text_info_t *ti = (con_text_info_t *) passthrough;
1518 if(ti->y < ti->ymin - 0.001)
1520 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1524 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1525 if(isContinuation && *ti->continuationString)
1526 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);
1528 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);
1531 ti->y += ti->fontsize;
1535 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)
1539 int maxlines = (int) floor(height / fontsize + 0.01f);
1542 int continuationWidth = 0;
1544 double t = cl.time; // saved so it won't change
1547 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1548 ti.fontsize = fontsize;
1549 ti.alignment = alignment_x;
1552 ti.ymax = y + height;
1553 ti.continuationString = continuationString;
1556 Con_WordWidthFunc(&ti, NULL, &l, -1);
1557 l = strlen(continuationString);
1558 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1560 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1561 startidx = CON_LINES_COUNT;
1562 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1564 con_lineinfo_t *l = &CON_LINES(i);
1567 if((l->mask & mask_must) != mask_must)
1569 if(l->mask & mask_mustnot)
1571 if(maxage && (l->addtime < t - maxage))
1575 // Calculate its actual height...
1576 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1577 if(lines + mylines >= maxlines)
1579 nskip = lines + mylines - maxlines;
1588 // then center according to the calculated amount of lines...
1590 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1592 // then actually draw
1593 for(i = startidx; i < CON_LINES_COUNT; ++i)
1595 con_lineinfo_t *l = &CON_LINES(i);
1597 if((l->mask & mask_must) != mask_must)
1599 if(l->mask & mask_mustnot)
1601 if(maxage && (l->addtime < t - maxage))
1604 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1614 Draws the last few lines of output transparently over the game top
1617 void Con_DrawNotify (void)
1620 float chatstart, notifystart, inputsize, height;
1622 char temptext[MAX_INPUTLINE];
1626 ConBuffer_FixTimes(&con);
1628 numChatlines = con_chat.integer;
1630 chatpos = con_chatpos.integer;
1632 if (con_notify.integer < 0)
1633 Cvar_SetValueQuick(&con_notify, 0);
1634 if (gamemode == GAME_TRANSFUSION)
1635 v = 8; // vertical offset
1639 // GAME_NEXUIZ: center, otherwise left justify
1640 align = con_notifyalign.value;
1641 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1643 if(gamemode == GAME_NEXUIZ)
1647 if(numChatlines || !con_chatrect.integer)
1651 // first chat, input line, then notify
1653 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1655 else if(chatpos > 0)
1657 // first notify, then (chatpos-1) empty lines, then chat, then input
1659 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1661 else // if(chatpos < 0)
1663 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1665 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1670 // just notify and input
1672 chatstart = 0; // shut off gcc warning
1675 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, "");
1677 if(con_chatrect.integer)
1679 x = con_chatrect_x.value * vid_conwidth.value;
1680 v = con_chatrect_y.value * vid_conheight.value;
1685 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1688 height = numChatlines * con_chatsize.value;
1692 Con_DrawNotifyRect(CON_MASK_CHAT, CON_MASK_INPUT, con_chattime.value, x, v, vid_conwidth.value * con_chatwidth.value, height, 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
1695 if (key_dest == key_message)
1697 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1698 int colorindex = -1;
1700 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL);
1702 // LordHavoc: speedup, and other improvements
1704 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1706 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1708 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1711 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1712 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1714 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1722 Returns the height of a given console line; calculates it if necessary.
1725 int Con_LineHeight(int lineno)
1727 con_lineinfo_t *li = &CON_LINES(lineno);
1728 if(li->height == -1)
1730 float width = vid_conwidth.value;
1732 con_lineinfo_t *li = &CON_LINES(lineno);
1733 ti.fontsize = con_textsize.value;
1734 ti.font = FONT_CONSOLE;
1735 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1744 Draws a line of the console; returns its height in lines.
1745 If alpha is 0, the line is not drawn, but still wrapped and its height
1749 int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1751 float width = vid_conwidth.value;
1753 con_lineinfo_t *li = &CON_LINES(lineno);
1755 if((li->mask & mask_must) != mask_must)
1757 if((li->mask & mask_mustnot) != 0)
1760 ti.continuationString = "";
1762 ti.fontsize = con_textsize.value;
1763 ti.font = FONT_CONSOLE;
1765 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1770 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1777 Calculates the last visible line index and how much to show of it based on
1781 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1786 if(con_backscroll < 0)
1791 // now count until we saw con_backscroll actual lines
1792 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1793 if((CON_LINES(i).mask & mask_must) == mask_must)
1794 if((CON_LINES(i).mask & mask_mustnot) == 0)
1796 int h = Con_LineHeight(i);
1798 // line is the last visible line?
1800 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1802 *limitlast = lines_seen + h - con_backscroll;
1809 // if we get here, no line was on screen - scroll so that one line is
1811 con_backscroll = lines_seen - 1;
1819 Draws the console with the solid background
1820 The typing input line at the bottom should only be drawn if typing is allowed
1823 void Con_DrawConsole (int lines)
1825 float alpha, alpha0;
1828 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1829 cachepic_t *conbackpic;
1834 if (con_backscroll < 0)
1837 con_vislines = lines;
1839 // draw the background
1840 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
1841 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
1843 sx = scr_conscroll_x.value;
1844 sy = scr_conscroll_y.value;
1845 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL;
1846 sx *= realtime; sy *= realtime;
1847 sx -= floor(sx); sy -= floor(sy);
1848 if (conbackpic && conbackpic->tex != r_texture_notexture)
1849 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1850 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1851 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1852 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1853 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1856 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
1858 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
1860 sx = scr_conscroll2_x.value;
1861 sy = scr_conscroll2_y.value;
1862 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1863 sx *= realtime; sy *= realtime;
1864 sx -= floor(sx); sy -= floor(sy);
1865 if(conbackpic && conbackpic->tex != r_texture_notexture)
1866 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1867 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1868 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1869 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1870 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1873 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
1875 sx = scr_conscroll3_x.value;
1876 sy = scr_conscroll3_y.value;
1877 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1878 sx *= realtime; sy *= realtime;
1879 sx -= floor(sx); sy -= floor(sy);
1880 if(conbackpic && conbackpic->tex != r_texture_notexture)
1881 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1882 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1883 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1884 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1885 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1888 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);
1894 int count = CON_LINES_COUNT;
1895 float ymax = con_vislines - 2 * con_textsize.value;
1896 float y = ymax + con_textsize.value * con_backscroll;
1897 for (i = 0;i < count && y >= 0;i++)
1898 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
1899 // fix any excessive scrollback for the next frame
1900 if (i >= count && y >= 0)
1902 con_backscroll -= (int)(y / con_textsize.value);
1903 if (con_backscroll < 0)
1908 if(CON_LINES_COUNT > 0)
1910 int i, last, limitlast;
1912 float ymax = con_vislines - 2 * con_textsize.value;
1913 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1914 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1915 y = ymax - con_textsize.value;
1918 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1923 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
1925 break; // top of console buffer
1927 break; // top of console window
1934 // draw the input prompt, user text, and cursor if desired
1942 Prints not only map filename, but also
1943 its format (q1/q2/q3/hl) and even its message
1945 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1946 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1947 //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
1948 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1949 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1953 int i, k, max, p, o, min;
1956 unsigned char buf[1024];
1958 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1959 t = FS_Search(message, 1, true);
1962 if (t->numfilenames > 1)
1963 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1964 len = (unsigned char *)Z_Malloc(t->numfilenames);
1966 for(max=i=0;i<t->numfilenames;i++)
1968 k = (int)strlen(t->filenames[i]);
1978 for(i=0;i<t->numfilenames;i++)
1980 int lumpofs = 0, lumplen = 0;
1981 char *entities = NULL;
1982 const char *data = NULL;
1984 char entfilename[MAX_QPATH];
1985 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1987 f = FS_OpenVirtualFile(t->filenames[i], true);
1990 memset(buf, 0, 1024);
1991 FS_Read(f, buf, 1024);
1992 if (!memcmp(buf, "IBSP", 4))
1994 p = LittleLong(((int *)buf)[1]);
1995 if (p == Q3BSPVERSION)
1997 q3dheader_t *header = (q3dheader_t *)buf;
1998 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1999 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2001 else if (p == Q2BSPVERSION)
2003 q2dheader_t *header = (q2dheader_t *)buf;
2004 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2005 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2008 else if((p = BuffLittleLong(buf)) == BSPVERSION || p == 30)
2010 dheader_t *header = (dheader_t *)buf;
2011 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
2012 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
2016 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2017 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2018 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2019 if (!entities && lumplen >= 10)
2021 FS_Seek(f, lumpofs, SEEK_SET);
2022 entities = (char *)Z_Malloc(lumplen + 1);
2023 FS_Read(f, entities, lumplen);
2027 // if there are entities to parse, a missing message key just
2028 // means there is no title, so clear the message string now
2034 if (!COM_ParseToken_Simple(&data, false, false))
2036 if (com_token[0] == '{')
2038 if (com_token[0] == '}')
2040 // skip leading whitespace
2041 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2042 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2043 keyname[l] = com_token[k+l];
2045 if (!COM_ParseToken_Simple(&data, false, false))
2047 if (developer_extra.integer)
2048 Con_DPrintf("key: %s %s\n", keyname, com_token);
2049 if (!strcmp(keyname, "message"))
2051 // get the message contents
2052 strlcpy(message, com_token, sizeof(message));
2062 *(t->filenames[i]+len[i]+5) = 0;
2065 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
2066 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
2067 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
2068 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
2069 default: strlcpy((char *)buf, "??", sizeof(buf));break;
2071 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
2076 k = *(t->filenames[0]+5+p);
2079 for(i=1;i<t->numfilenames;i++)
2080 if(*(t->filenames[i]+5+p) != k)
2084 if(p > o && completedname && completednamebufferlength > 0)
2086 memset(completedname, 0, completednamebufferlength);
2087 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2097 New function for tab-completion system
2098 Added by EvilTypeGuy
2099 MEGA Thanks to Taniwha
2102 void Con_DisplayList(const char **list)
2104 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2105 const char **walk = list;
2108 len = (int)strlen(*walk);
2116 len = (int)strlen(*list);
2117 if (pos + maxlen >= width) {
2123 for (i = 0; i < (maxlen - len); i++)
2135 SanitizeString strips color tags from the string in
2136 and writes the result on string out
2138 void SanitizeString(char *in, char *out)
2142 if(*in == STRING_COLOR_TAG)
2147 out[0] = STRING_COLOR_TAG;
2151 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2158 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2161 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2163 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2170 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2175 else if (*in != STRING_COLOR_TAG)
2178 *out = qfont_table[*(unsigned char*)in];
2185 // Now it becomes TRICKY :D --blub
2186 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2187 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2188 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2189 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
2190 static int Nicks_matchpos;
2192 // co against <<:BLASTER:>> is true!?
2193 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2197 if(tolower(*a) == tolower(*b))
2211 return (*a < *b) ? -1 : 1;
2215 return (*a < *b) ? -1 : 1;
2219 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2222 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2224 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2225 return Nicks_strncasecmp_nospaces(a, b, a_len);
2226 return strncasecmp(a, b, a_len);
2229 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2231 // ignore non alphanumerics of B
2232 // if A contains a non-alphanumeric, B must contain it as well though!
2235 qboolean alnum_a, alnum_b;
2237 if(tolower(*a) == tolower(*b))
2239 if(*a == 0) // end of both strings, they're equal
2246 // not equal, end of one string?
2251 // ignore non alphanumerics
2252 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2253 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2254 if(!alnum_a) // b must contain this
2255 return (*a < *b) ? -1 : 1;
2258 // otherwise, both are alnum, they're just not equal, return the appropriate number
2260 return (*a < *b) ? -1 : 1;
2266 /* Nicks_CompleteCountPossible
2268 Count the number of possible nicks to complete
2270 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2278 if(!con_nickcompletion.integer)
2281 // changed that to 1
2282 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2285 for(i = 0; i < cl.maxclients; ++i)
2288 if(!cl.scores[p].name[0])
2291 SanitizeString(cl.scores[p].name, name);
2292 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2298 spos = pos - 1; // no need for a minimum of characters :)
2302 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2304 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2305 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2311 if(isCon && spos == 0)
2313 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2319 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2320 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2322 // the sanitized list
2323 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2326 Nicks_matchpos = match;
2329 Nicks_offset[count] = s - (&line[match]);
2330 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2337 void Cmd_CompleteNicksPrint(int count)
2340 for(i = 0; i < count; ++i)
2341 Con_Printf("%s\n", Nicks_list[i]);
2344 void Nicks_CutMatchesNormal(int count)
2346 // cut match 0 down to the longest possible completion
2349 c = strlen(Nicks_sanlist[0]) - 1;
2350 for(i = 1; i < count; ++i)
2352 l = strlen(Nicks_sanlist[i]) - 1;
2356 for(l = 0; l <= c; ++l)
2357 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2363 Nicks_sanlist[0][c+1] = 0;
2364 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2367 unsigned int Nicks_strcleanlen(const char *s)
2372 if( (*s >= 'a' && *s <= 'z') ||
2373 (*s >= 'A' && *s <= 'Z') ||
2374 (*s >= '0' && *s <= '9') ||
2382 void Nicks_CutMatchesAlphaNumeric(int count)
2384 // cut match 0 down to the longest possible completion
2387 char tempstr[sizeof(Nicks_sanlist[0])];
2389 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2391 c = strlen(Nicks_sanlist[0]);
2392 for(i = 0, l = 0; i < (int)c; ++i)
2394 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2395 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2396 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2398 tempstr[l++] = Nicks_sanlist[0][i];
2403 for(i = 1; i < count; ++i)
2406 b = Nicks_sanlist[i];
2416 if(tolower(*a) == tolower(*b))
2422 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2424 // b is alnum, so cut
2431 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2432 Nicks_CutMatchesNormal(count);
2433 //if(!Nicks_sanlist[0][0])
2434 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2436 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2437 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2441 void Nicks_CutMatchesNoSpaces(int count)
2443 // cut match 0 down to the longest possible completion
2446 char tempstr[sizeof(Nicks_sanlist[0])];
2449 c = strlen(Nicks_sanlist[0]);
2450 for(i = 0, l = 0; i < (int)c; ++i)
2452 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2454 tempstr[l++] = Nicks_sanlist[0][i];
2459 for(i = 1; i < count; ++i)
2462 b = Nicks_sanlist[i];
2472 if(tolower(*a) == tolower(*b))
2486 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2487 Nicks_CutMatchesNormal(count);
2488 //if(!Nicks_sanlist[0][0])
2489 //Con_Printf("TS: %s\n", tempstr);
2490 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2492 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2493 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2497 void Nicks_CutMatches(int count)
2499 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2500 Nicks_CutMatchesAlphaNumeric(count);
2501 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2502 Nicks_CutMatchesNoSpaces(count);
2504 Nicks_CutMatchesNormal(count);
2507 const char **Nicks_CompleteBuildList(int count)
2511 // the list is freed by Con_CompleteCommandLine, so create a char**
2512 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2514 for(; bpos < count; ++bpos)
2515 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2517 Nicks_CutMatches(count);
2525 Restores the previous used color, after the autocompleted name.
2527 int Nicks_AddLastColor(char *buffer, int pos)
2529 qboolean quote_added = false;
2531 int color = STRING_COLOR_DEFAULT + '0';
2532 char r = 0, g = 0, b = 0;
2534 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2536 // we'll have to add a quote :)
2537 buffer[pos++] = '\"';
2541 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2543 // add color when no quote was added, or when flags &4?
2545 for(match = Nicks_matchpos-1; match >= 0; --match)
2547 if(buffer[match] == STRING_COLOR_TAG)
2549 if( isdigit(buffer[match+1]) )
2551 color = buffer[match+1];
2554 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2556 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2558 r = buffer[match+2];
2559 g = buffer[match+3];
2560 b = buffer[match+4];
2569 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2571 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2572 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2575 buffer[pos++] = STRING_COLOR_TAG;
2578 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2584 buffer[pos++] = color;
2589 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2592 /*if(!con_nickcompletion.integer)
2593 return; is tested in Nicks_CompletionCountPossible */
2594 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2600 msg = Nicks_list[0];
2601 len = min(size - Nicks_matchpos - 3, strlen(msg));
2602 memcpy(&buffer[Nicks_matchpos], msg, len);
2603 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2604 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2605 buffer[len++] = ' ';
2612 Con_Printf("\n%i possible nicks:\n", n);
2613 Cmd_CompleteNicksPrint(n);
2615 Nicks_CutMatches(n);
2617 msg = Nicks_sanlist[0];
2618 len = min(size - Nicks_matchpos, strlen(msg));
2619 memcpy(&buffer[Nicks_matchpos], msg, len);
2620 buffer[Nicks_matchpos + len] = 0;
2622 return Nicks_matchpos + len;
2629 Con_CompleteCommandLine
2631 New function for tab-completion system
2632 Added by EvilTypeGuy
2633 Thanks to Fett erich@heintz.com
2635 Enhanced to tab-complete map names by [515]
2638 void Con_CompleteCommandLine (void)
2640 const char *cmd = "";
2642 const char **list[4] = {0, 0, 0, 0};
2645 int c, v, a, i, cmd_len, pos, k;
2646 int n; // nicks --blub
2647 const char *space, *patterns;
2649 //find what we want to complete
2654 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2660 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2661 key_line[key_linepos] = 0; //hide them
2663 space = strchr(key_line + 1, ' ');
2664 if(space && pos == (space - key_line) + 1)
2666 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2668 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2669 if(patterns && !*patterns)
2670 patterns = NULL; // get rid of the empty string
2672 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2676 if (GetMapList(s, t, sizeof(t)))
2678 // first move the cursor
2679 key_linepos += (int)strlen(t) - (int)strlen(s);
2681 // and now do the actual work
2683 strlcat(key_line, t, MAX_INPUTLINE);
2684 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2686 // and fix the cursor
2687 if(key_linepos > (int) strlen(key_line))
2688 key_linepos = (int) strlen(key_line);
2697 stringlist_t resultbuf, dirbuf;
2700 // // store completion patterns (space separated) for command foo in con_completion_foo
2701 // set con_completion_foo "foodata/*.foodefault *.foo"
2704 // Note: patterns with slash are always treated as absolute
2705 // patterns; patterns without slash search in the innermost
2706 // directory the user specified. There is no way to "complete into"
2707 // a directory as of now, as directories seem to be unknown to the
2711 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2712 // set con_completion_playdemo "*.dem"
2713 // set con_completion_play "*.wav *.ogg"
2715 // TODO somehow add support for directories; these shall complete
2716 // to their name + an appended slash.
2718 stringlistinit(&resultbuf);
2719 stringlistinit(&dirbuf);
2720 while(COM_ParseToken_Simple(&patterns, false, false))
2723 if(strchr(com_token, '/'))
2725 search = FS_Search(com_token, true, true);
2729 const char *slash = strrchr(s, '/');
2732 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2733 strlcat(t, com_token, sizeof(t));
2734 search = FS_Search(t, true, true);
2737 search = FS_Search(com_token, true, true);
2741 for(i = 0; i < search->numfilenames; ++i)
2742 if(!strncmp(search->filenames[i], s, strlen(s)))
2743 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2744 stringlistappend(&resultbuf, search->filenames[i]);
2745 FS_FreeSearch(search);
2749 // In any case, add directory names
2752 const char *slash = strrchr(s, '/');
2755 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2756 strlcat(t, "*", sizeof(t));
2757 search = FS_Search(t, true, true);
2760 search = FS_Search("*", true, true);
2763 for(i = 0; i < search->numfilenames; ++i)
2764 if(!strncmp(search->filenames[i], s, strlen(s)))
2765 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2766 stringlistappend(&dirbuf, search->filenames[i]);
2767 FS_FreeSearch(search);
2771 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2774 unsigned int matchchars;
2775 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2777 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2780 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2782 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2786 stringlistsort(&resultbuf); // dirbuf is already sorted
2787 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2788 for(i = 0; i < dirbuf.numstrings; ++i)
2790 Con_Printf("%s/\n", dirbuf.strings[i]);
2792 for(i = 0; i < resultbuf.numstrings; ++i)
2794 Con_Printf("%s\n", resultbuf.strings[i]);
2796 matchchars = sizeof(t) - 1;
2797 if(resultbuf.numstrings > 0)
2799 p = resultbuf.strings[0];
2800 q = resultbuf.strings[resultbuf.numstrings - 1];
2801 for(; *p && *p == *q; ++p, ++q);
2802 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2804 if(dirbuf.numstrings > 0)
2806 p = dirbuf.strings[0];
2807 q = dirbuf.strings[dirbuf.numstrings - 1];
2808 for(; *p && *p == *q; ++p, ++q);
2809 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2811 // now p points to the first non-equal character, or to the end
2812 // of resultbuf.strings[0]. We want to append the characters
2813 // from resultbuf.strings[0] to (not including) p as these are
2814 // the unique prefix
2815 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2818 // first move the cursor
2819 key_linepos += (int)strlen(t) - (int)strlen(s);
2821 // and now do the actual work
2823 strlcat(key_line, t, MAX_INPUTLINE);
2824 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2826 // and fix the cursor
2827 if(key_linepos > (int) strlen(key_line))
2828 key_linepos = (int) strlen(key_line);
2830 stringlistfreecontents(&resultbuf);
2831 stringlistfreecontents(&dirbuf);
2833 return; // bail out, when we complete for a command that wants a file name
2838 // Count number of possible matches and print them
2839 c = Cmd_CompleteCountPossible(s);
2842 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2843 Cmd_CompleteCommandPrint(s);
2845 v = Cvar_CompleteCountPossible(s);
2848 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2849 Cvar_CompleteCvarPrint(s);
2851 a = Cmd_CompleteAliasCountPossible(s);
2854 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2855 Cmd_CompleteAliasPrint(s);
2857 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2860 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2861 Cmd_CompleteNicksPrint(n);
2864 if (!(c + v + a + n)) // No possible matches
2867 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2872 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2874 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2876 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2878 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2880 for (cmd_len = (int)strlen(s);;cmd_len++)
2883 for (i = 0; i < 3; i++)
2885 for (l = list[i];*l;l++)
2886 if ((*l)[cmd_len] != cmd[cmd_len])
2888 // all possible matches share this character, so we continue...
2891 // if all matches ended at the same position, stop
2892 // (this means there is only one match)
2898 // prevent a buffer overrun by limiting cmd_len according to remaining space
2899 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2903 memcpy(&key_line[key_linepos], cmd, cmd_len);
2904 key_linepos += cmd_len;
2905 // if there is only one match, add a space after it
2906 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2909 { // was a nick, might have an offset, and needs colors ;) --blub
2910 key_linepos = pos - Nicks_offset[0];
2911 cmd_len = strlen(Nicks_list[0]);
2912 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2914 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2915 key_linepos += cmd_len;
2916 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2917 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2919 key_line[key_linepos++] = ' ';
2923 // use strlcat to avoid a buffer overrun
2924 key_line[key_linepos] = 0;
2925 strlcat(key_line, s2, sizeof(key_line));
2927 // free the command, cvar, and alias lists
2928 for (i = 0; i < 4; i++)
2930 Mem_Free((void *)list[i]);