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.
22 #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];
96 qboolean rcon_redirect_proquakeprotocol = false;
98 // generic functions for console buffers
100 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
103 buf->textsize = textsize;
104 buf->text = (char *) Mem_Alloc(mempool, textsize);
105 buf->maxlines = maxlines;
106 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
107 buf->lines_first = 0;
108 buf->lines_count = 0;
116 void ConBuffer_Clear (conbuffer_t *buf)
118 buf->lines_count = 0;
126 void ConBuffer_Shutdown(conbuffer_t *buf)
132 Mem_Free(buf->lines);
141 Notifies the console code about the current time
142 (and shifts back times of other entries when the time
146 void ConBuffer_FixTimes(conbuffer_t *buf)
149 if(buf->lines_count >= 1)
151 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
154 for(i = 0; i < buf->lines_count; ++i)
155 CONBUFFER_LINES(buf, i).addtime += diff;
164 Deletes the first line from the console history.
167 void ConBuffer_DeleteLine(conbuffer_t *buf)
169 if(buf->lines_count == 0)
172 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
177 ConBuffer_DeleteLastLine
179 Deletes the last line from the console history.
182 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
184 if(buf->lines_count == 0)
193 Checks if there is space for a line of the given length, and if yes, returns a
194 pointer to the start of such a space, and NULL otherwise.
197 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
199 if(len > buf->textsize)
201 if(buf->lines_count == 0)
205 char *firstline_start = buf->lines[buf->lines_first].start;
206 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
207 // the buffer is cyclic, so we first have two cases...
208 if(firstline_start < lastline_onepastend) // buffer is contiguous
211 if(len <= buf->text + buf->textsize - lastline_onepastend)
212 return lastline_onepastend;
214 else if(len <= firstline_start - buf->text)
219 else // buffer has a contiguous hole
221 if(len <= firstline_start - lastline_onepastend)
222 return lastline_onepastend;
233 Appends a given string as a new line to the console.
236 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
241 // developer_memory 1 during shutdown prints while conbuffer_t is being freed
245 ConBuffer_FixTimes(buf);
247 if(len >= buf->textsize)
250 // only display end of line.
251 line += len - buf->textsize + 1;
252 len = buf->textsize - 1;
254 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
255 ConBuffer_DeleteLine(buf);
256 memcpy(putpos, line, len);
260 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
262 p = &CONBUFFER_LINES_LAST(buf);
265 p->addtime = cl.time;
267 p->height = -1; // calculate when needed
270 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
274 start = buf->lines_count;
275 for(i = start - 1; i >= 0; --i)
277 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
279 if((l->mask & mask_must) != mask_must)
281 if(l->mask & mask_mustnot)
290 int Con_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
293 for(i = start + 1; i < buf->lines_count; ++i)
295 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
297 if((l->mask & mask_must) != mask_must)
299 if(l->mask & mask_mustnot)
308 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
310 static char copybuf[MAX_INPUTLINE];
311 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
312 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
313 strlcpy(copybuf, l->start, sz);
318 ==============================================================================
322 ==============================================================================
327 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
328 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"};
329 char log_dest_buffer[1400]; // UDP packet
330 size_t log_dest_buffer_pos;
331 unsigned int log_dest_buffer_appending;
332 char crt_log_file [MAX_OSPATH] = "";
333 qfile_t* logfile = NULL;
335 unsigned char* logqueue = NULL;
337 size_t logq_size = 0;
339 void Log_ConPrint (const char *msg);
346 static void Log_DestBuffer_Init(void)
348 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
349 log_dest_buffer_pos = 5;
357 void Log_DestBuffer_Flush(void)
359 lhnetaddress_t log_dest_addr;
360 lhnetsocket_t *log_dest_socket;
361 const char *s = log_dest_udp.string;
362 qboolean have_opened_temp_sockets = false;
363 if(s) if(log_dest_buffer_pos > 5)
365 ++log_dest_buffer_appending;
366 log_dest_buffer[log_dest_buffer_pos++] = 0;
368 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
370 have_opened_temp_sockets = true;
371 NetConn_OpenServerPorts(true);
374 while(COM_ParseToken_Console(&s))
375 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
377 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
379 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
381 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
384 if(have_opened_temp_sockets)
385 NetConn_CloseServerPorts();
386 --log_dest_buffer_appending;
388 log_dest_buffer_pos = 0;
396 const char* Log_Timestamp (const char *desc)
398 static char timestamp [128];
405 char timestring [64];
407 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
410 localtime_s (&crt_tm, &crt_time);
411 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
413 crt_tm = localtime (&crt_time);
414 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
418 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
420 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
433 if (logfile != NULL || log_file.string[0] == '\0')
436 logfile = FS_OpenRealFile(log_file.string, "a", false);
439 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
440 FS_Print (logfile, Log_Timestamp ("Log started"));
450 void Log_Close (void)
455 FS_Print (logfile, Log_Timestamp ("Log stopped"));
456 FS_Print (logfile, "\n");
460 crt_log_file[0] = '\0';
469 void Log_Start (void)
475 // Dump the contents of the log queue into the log file and free it
476 if (logqueue != NULL)
478 unsigned char *temp = logqueue;
483 FS_Write (logfile, temp, logq_ind);
484 if(*log_dest_udp.string)
486 for(pos = 0; pos < logq_ind; )
488 if(log_dest_buffer_pos == 0)
489 Log_DestBuffer_Init();
490 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
491 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
492 log_dest_buffer_pos += n;
493 Log_DestBuffer_Flush();
510 void Log_ConPrint (const char *msg)
512 static qboolean inprogress = false;
514 // don't allow feedback loops with memory error reports
519 // Until the host is completely initialized, we maintain a log queue
520 // to store the messages, since the log can't be started before
521 if (logqueue != NULL)
523 size_t remain = logq_size - logq_ind;
524 size_t len = strlen (msg);
526 // If we need to enlarge the log queue
529 size_t factor = ((logq_ind + len) / logq_size) + 1;
530 unsigned char* newqueue;
533 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
534 memcpy (newqueue, logqueue, logq_ind);
537 remain = logq_size - logq_ind;
539 memcpy (&logqueue[logq_ind], msg, len);
546 // Check if log_file has changed
547 if (strcmp (crt_log_file, log_file.string) != 0)
553 // If a log file is available
555 FS_Print (logfile, msg);
566 void Log_Printf (const char *logfilename, const char *fmt, ...)
570 file = FS_OpenRealFile(logfilename, "a", true);
575 va_start (argptr, fmt);
576 FS_VPrintf (file, fmt, argptr);
585 ==============================================================================
589 ==============================================================================
597 void Con_ToggleConsole_f (void)
599 // toggle the 'user wants console' bit
600 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
609 void Con_ClearNotify (void)
612 for(i = 0; i < CON_LINES_COUNT; ++i)
613 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
622 void Con_MessageMode_f (void)
624 key_dest = key_message;
625 chat_mode = 0; // "say"
636 void Con_MessageMode2_f (void)
638 key_dest = key_message;
639 chat_mode = 1; // "say_team"
649 void Con_CommandMode_f (void)
651 key_dest = key_message;
654 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
655 chat_bufferlen = strlen(chat_buffer);
657 chat_mode = -1; // command
665 void Con_CheckResize (void)
670 f = bound(1, con_textsize.value, 128);
671 if(f != con_textsize.value)
672 Cvar_SetValueQuick(&con_textsize, f);
673 width = (int)floor(vid_conwidth.value / con_textsize.value);
674 width = bound(1, width, con.textsize/4);
675 // FIXME uses con in a non abstracted way
677 if (width == con_linewidth)
680 con_linewidth = width;
682 for(i = 0; i < CON_LINES_COUNT; ++i)
683 CON_LINES(i).height = -1; // recalculate when next needed
689 //[515]: the simplest command ever
690 //LordHavoc: not so simple after I made it print usage...
691 static void Con_Maps_f (void)
695 Con_Printf("usage: maps [mapnameprefix]\n");
698 else if (Cmd_Argc() == 2)
699 GetMapList(Cmd_Argv(1), NULL, 0);
701 GetMapList("", NULL, 0);
704 void Con_ConDump_f (void)
710 Con_Printf("usage: condump <filename>\n");
713 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
716 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
719 for(i = 0; i < CON_LINES_COUNT; ++i)
721 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
722 FS_Write(file, "\n", 1);
727 void Con_Clear_f (void)
729 ConBuffer_Clear(&con);
740 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
742 // Allocate a log queue, this will be freed after configs are parsed
743 logq_size = MAX_INPUTLINE;
744 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
747 Cvar_RegisterVariable (&sys_colortranslation);
748 Cvar_RegisterVariable (&sys_specialcharactertranslation);
750 Cvar_RegisterVariable (&log_file);
751 Cvar_RegisterVariable (&log_dest_udp);
753 // support for the classic Quake option
754 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
755 if (COM_CheckParm ("-condebug") != 0)
756 Cvar_SetQuick (&log_file, "qconsole.log");
758 // register our cvars
759 Cvar_RegisterVariable (&con_chat);
760 Cvar_RegisterVariable (&con_chatpos);
761 Cvar_RegisterVariable (&con_chatrect_x);
762 Cvar_RegisterVariable (&con_chatrect_y);
763 Cvar_RegisterVariable (&con_chatrect);
764 Cvar_RegisterVariable (&con_chatsize);
765 Cvar_RegisterVariable (&con_chattime);
766 Cvar_RegisterVariable (&con_chatwidth);
767 Cvar_RegisterVariable (&con_notify);
768 Cvar_RegisterVariable (&con_notifyalign);
769 Cvar_RegisterVariable (&con_notifysize);
770 Cvar_RegisterVariable (&con_notifytime);
771 Cvar_RegisterVariable (&con_textsize);
772 Cvar_RegisterVariable (&con_chatsound);
775 Cvar_RegisterVariable (&con_nickcompletion);
776 Cvar_RegisterVariable (&con_nickcompletion_flags);
778 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
779 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
780 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
782 // register our commands
783 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
784 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
785 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
786 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
787 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
788 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
789 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
791 con_initialized = true;
792 Con_DPrint("Console initialized.\n");
795 void Con_Shutdown (void)
797 ConBuffer_Shutdown(&con);
804 Handles cursor positioning, line wrapping, etc
805 All console printing must go through this in order to be displayed
806 If no console is visible, the notify window will pop up.
809 void Con_PrintToHistory(const char *txt, int mask)
812 // \n goes to next line
813 // \r deletes current line and makes a new one
815 static int cr_pending = 0;
816 static char buf[CON_TEXTSIZE];
817 static int bufpos = 0;
819 if(!con.text) // FIXME uses a non-abstracted property of con
826 ConBuffer_DeleteLastLine(&con);
834 ConBuffer_AddLine(&con, buf, bufpos, mask);
839 ConBuffer_AddLine(&con, buf, bufpos, mask);
843 buf[bufpos++] = *txt;
844 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
846 ConBuffer_AddLine(&con, buf, bufpos, mask);
854 /*! The translation table between the graphical font and plain ASCII --KB */
855 static char qfont_table[256] = {
856 '\0', '#', '#', '#', '#', '.', '#', '#',
857 '#', 9, 10, '#', ' ', 13, '.', '.',
858 '[', ']', '0', '1', '2', '3', '4', '5',
859 '6', '7', '8', '9', '.', '<', '=', '>',
860 ' ', '!', '"', '#', '$', '%', '&', '\'',
861 '(', ')', '*', '+', ',', '-', '.', '/',
862 '0', '1', '2', '3', '4', '5', '6', '7',
863 '8', '9', ':', ';', '<', '=', '>', '?',
864 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
865 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
866 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
867 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
868 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
869 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
870 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
871 'x', 'y', 'z', '{', '|', '}', '~', '<',
873 '<', '=', '>', '#', '#', '.', '#', '#',
874 '#', '#', ' ', '#', ' ', '>', '.', '.',
875 '[', ']', '0', '1', '2', '3', '4', '5',
876 '6', '7', '8', '9', '.', '<', '=', '>',
877 ' ', '!', '"', '#', '$', '%', '&', '\'',
878 '(', ')', '*', '+', ',', '-', '.', '/',
879 '0', '1', '2', '3', '4', '5', '6', '7',
880 '8', '9', ':', ';', '<', '=', '>', '?',
881 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
882 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
883 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
884 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
885 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
886 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
887 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
888 'x', 'y', 'z', '{', '|', '}', '~', '<'
891 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol)
893 rcon_redirect_sock = sock;
894 rcon_redirect_dest = dest;
895 rcon_redirect_proquakeprotocol = proquakeprotocol;
896 if (rcon_redirect_proquakeprotocol)
898 // reserve space for the packet header
899 rcon_redirect_buffer[0] = 0;
900 rcon_redirect_buffer[1] = 0;
901 rcon_redirect_buffer[2] = 0;
902 rcon_redirect_buffer[3] = 0;
903 // this is a reply to a CCREQ_RCON
904 rcon_redirect_buffer[4] = CCREP_RCON;
907 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
908 rcon_redirect_bufferpos = 5;
911 void Con_Rcon_Redirect_Flush(void)
913 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
914 if (rcon_redirect_proquakeprotocol)
916 // update the length in the packet header
917 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
919 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
920 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
921 rcon_redirect_bufferpos = 5;
922 rcon_redirect_proquakeprotocol = false;
925 void Con_Rcon_Redirect_End(void)
927 Con_Rcon_Redirect_Flush();
928 rcon_redirect_dest = NULL;
929 rcon_redirect_sock = NULL;
932 void Con_Rcon_Redirect_Abort(void)
934 rcon_redirect_dest = NULL;
935 rcon_redirect_sock = NULL;
943 /// Adds a character to the rcon buffer.
944 void Con_Rcon_AddChar(int c)
946 if(log_dest_buffer_appending)
948 ++log_dest_buffer_appending;
950 // if this print is in response to an rcon command, add the character
951 // to the rcon redirect buffer
953 if (rcon_redirect_dest)
955 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
956 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
957 Con_Rcon_Redirect_Flush();
959 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
961 if(log_dest_buffer_pos == 0)
962 Log_DestBuffer_Init();
963 log_dest_buffer[log_dest_buffer_pos++] = c;
964 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
965 Log_DestBuffer_Flush();
968 log_dest_buffer_pos = 0;
970 --log_dest_buffer_appending;
974 * Convert an RGB color to its nearest quake color.
975 * I'll cheat on this a bit by translating the colors to HSV first,
976 * S and V decide if it's black or white, otherwise, H will decide the
978 * @param _r Red (0-255)
979 * @param _g Green (0-255)
980 * @param _b Blue (0-255)
981 * @return A quake color character.
983 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
985 float r = ((float)_r)/255.0;
986 float g = ((float)_g)/255.0;
987 float b = ((float)_b)/255.0;
988 float min = min(r, min(g, b));
989 float max = max(r, max(g, b));
991 int h; ///< Hue angle [0,360]
992 float s; ///< Saturation [0,1]
993 float v = max; ///< In HSV v == max [0,1]
1000 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1003 // If the value is less than half, return a black color code.
1004 // Otherwise return a white one.
1010 // Let's get the hue angle to define some colors:
1014 h = (int)(60.0 * (g-b)/(max-min))%360;
1016 h = (int)(60.0 * (b-r)/(max-min) + 120);
1017 else // if(max == b) redundant check
1018 h = (int)(60.0 * (r-g)/(max-min) + 240);
1020 if(h < 36) // *red* to orange
1022 else if(h < 80) // orange over *yellow* to evilish-bright-green
1024 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1026 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1028 else if(h < 270) // darkish blue over *dark blue* to cool purple
1030 else if(h < 330) // cool purple over *purple* to ugly swiny red
1032 else // ugly red to red closes the circly
1041 extern cvar_t timestamps;
1042 extern cvar_t timeformat;
1043 extern qboolean sys_nostdout;
1044 void Con_MaskPrint(int additionalmask, const char *msg)
1046 static int mask = 0;
1047 static int index = 0;
1048 static char line[MAX_INPUTLINE];
1052 Con_Rcon_AddChar(*msg);
1054 mask |= additionalmask;
1055 // if this is the beginning of a new line, print timestamp
1058 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1060 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1061 line[index++] = STRING_COLOR_TAG;
1062 // assert( STRING_COLOR_DEFAULT < 10 )
1063 line[index++] = STRING_COLOR_DEFAULT + '0';
1064 // special color codes for chat messages must always come first
1065 // for Con_PrintToHistory to work properly
1066 if (*msg == 1 || *msg == 2)
1071 if (con_chatsound.value)
1073 if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
1075 if(msg[1] == '\r' && cl.foundtalk2wav)
1076 S_LocalSound ("sound/misc/talk2.wav");
1078 S_LocalSound ("sound/misc/talk.wav");
1082 if (msg[1] == '(' && cl.foundtalk2wav)
1083 S_LocalSound ("sound/misc/talk2.wav");
1085 S_LocalSound ("sound/misc/talk.wav");
1088 mask = CON_MASK_CHAT;
1090 line[index++] = STRING_COLOR_TAG;
1091 line[index++] = '3';
1093 Con_Rcon_AddChar(*msg);
1096 for (;*timestamp;index++, timestamp++)
1097 if (index < (int)sizeof(line) - 2)
1098 line[index] = *timestamp;
1100 // append the character
1101 line[index++] = *msg;
1102 // if this is a newline character, we have a complete line to print
1103 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1105 // terminate the line
1109 // send to scrollable buffer
1110 if (con_initialized && cls.state != ca_dedicated)
1112 Con_PrintToHistory(line, mask);
1115 // send to terminal or dedicated server window
1118 if(sys_specialcharactertranslation.integer)
1125 int ch = u8_getchar(p, &q);
1126 if(ch >= 0xE000 && ch <= 0xE0FF)
1128 *p = qfont_table[ch - 0xE000];
1130 memmove(p+1, q, strlen(q));
1138 if(sys_colortranslation.integer == 1) // ANSI
1140 static char printline[MAX_INPUTLINE * 4 + 3];
1141 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1142 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1147 for(in = line, out = printline; *in; ++in)
1151 case STRING_COLOR_TAG:
1152 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1154 char r = tolower(in[2]);
1155 char g = tolower(in[3]);
1156 char b = tolower(in[4]);
1157 // it's a hex digit already, so the else part needs no check --blub
1158 if(isdigit(r)) r -= '0';
1160 if(isdigit(g)) g -= '0';
1162 if(isdigit(b)) b -= '0';
1165 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1166 in += 3; // 3 only, the switch down there does the fourth
1173 case STRING_COLOR_TAG:
1175 *out++ = STRING_COLOR_TAG;
1181 if(lastcolor == 0) break; else lastcolor = 0;
1182 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1187 if(lastcolor == 1) break; else lastcolor = 1;
1188 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1193 if(lastcolor == 2) break; else lastcolor = 2;
1194 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1199 if(lastcolor == 3) break; else lastcolor = 3;
1200 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1205 if(lastcolor == 4) break; else lastcolor = 4;
1206 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1211 if(lastcolor == 5) break; else lastcolor = 5;
1212 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1217 if(lastcolor == 6) break; else lastcolor = 6;
1218 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1223 // bold normal color
1225 if(lastcolor == 8) break; else lastcolor = 8;
1226 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1229 *out++ = STRING_COLOR_TAG;
1236 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1253 Sys_PrintToTerminal(printline);
1255 else if(sys_colortranslation.integer == 2) // Quake
1257 Sys_PrintToTerminal(line);
1261 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1264 for(in = line, out = printline; *in; ++in)
1268 case STRING_COLOR_TAG:
1271 case STRING_COLOR_RGB_TAG_CHAR:
1272 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1277 *out++ = STRING_COLOR_TAG;
1278 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1281 case STRING_COLOR_TAG:
1283 *out++ = STRING_COLOR_TAG;
1298 *out++ = STRING_COLOR_TAG;
1308 Sys_PrintToTerminal(printline);
1311 // empty the line buffer
1322 void Con_MaskPrintf(int mask, const char *fmt, ...)
1325 char msg[MAX_INPUTLINE];
1327 va_start(argptr,fmt);
1328 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1331 Con_MaskPrint(mask, msg);
1339 void Con_Print(const char *msg)
1341 Con_MaskPrint(CON_MASK_PRINT, msg);
1349 void Con_Printf(const char *fmt, ...)
1352 char msg[MAX_INPUTLINE];
1354 va_start(argptr,fmt);
1355 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1358 Con_MaskPrint(CON_MASK_PRINT, msg);
1366 void Con_DPrint(const char *msg)
1368 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1371 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1379 void Con_DPrintf(const char *fmt, ...)
1382 char msg[MAX_INPUTLINE];
1384 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1387 va_start(argptr,fmt);
1388 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1391 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1396 ==============================================================================
1400 ==============================================================================
1407 The input line scrolls horizontally if typing goes beyond the right edge
1409 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1412 extern cvar_t r_font_disable_freetype;
1413 void Con_DrawInput (void)
1417 char editlinecopy[MAX_INPUTLINE+1], *text;
1422 if (!key_consoleactive)
1423 return; // don't draw anything
1425 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1426 text = editlinecopy;
1428 // Advanced Console Editing by Radix radix@planetquake.com
1429 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1430 // use strlen of edit_line instead of key_linepos to allow editing
1431 // of early characters w/o erasing
1433 y = (int)strlen(text);
1435 // append enoug nul-bytes to cover the utf8-versions of the cursor too
1436 for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i)
1439 // add the cursor frame
1440 if (r_font_disable_freetype.integer)
1442 // this code is freetype incompatible!
1443 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1445 if (!utf8_enable.integer)
1446 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1447 else if (y + 3 < (int)sizeof(editlinecopy)-1)
1449 int ofs = u8_bytelen(text + key_linepos, 1);
1452 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1456 memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1457 memcpy(text + key_linepos, curbuf, len);
1460 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1464 // text[key_linepos + 1] = 0;
1466 len_out = key_linepos;
1468 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1469 x = vid_conwidth.value * 0.95 - xo; // scroll
1474 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 );
1476 // add a cursor on top of this (when using freetype)
1477 if (!r_font_disable_freetype.integer)
1479 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1481 if (!utf8_enable.integer)
1483 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1490 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1491 memcpy(text, curbuf, len);
1494 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);
1499 // key_line[key_linepos] = 0;
1505 float alignment; // 0 = left, 0.5 = center, 1 = right
1511 const char *continuationString;
1514 int colorindex; // init to -1
1518 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1520 con_text_info_t *ti = (con_text_info_t *) passthrough;
1523 ti->colorindex = -1;
1524 return ti->fontsize * ti->font->maxwidth;
1527 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1528 else if(maxWidth == -1)
1529 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1532 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1533 // Note: this is NOT a Con_Printf, as it could print recursively
1538 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1544 (void) isContinuation;
1548 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1550 con_text_info_t *ti = (con_text_info_t *) passthrough;
1552 if(ti->y < ti->ymin - 0.001)
1554 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1558 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1559 if(isContinuation && *ti->continuationString)
1560 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);
1562 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);
1565 ti->y += ti->fontsize;
1569 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)
1573 int maxlines = (int) floor(height / fontsize + 0.01f);
1576 int continuationWidth = 0;
1578 double t = cl.time; // saved so it won't change
1581 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1582 ti.fontsize = fontsize;
1583 ti.alignment = alignment_x;
1586 ti.ymax = y + height;
1587 ti.continuationString = continuationString;
1590 Con_WordWidthFunc(&ti, NULL, &l, -1);
1591 l = strlen(continuationString);
1592 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1594 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1595 startidx = CON_LINES_COUNT;
1596 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1598 con_lineinfo_t *l = &CON_LINES(i);
1601 if((l->mask & mask_must) != mask_must)
1603 if(l->mask & mask_mustnot)
1605 if(maxage && (l->addtime < t - maxage))
1609 // Calculate its actual height...
1610 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1611 if(lines + mylines >= maxlines)
1613 nskip = lines + mylines - maxlines;
1622 // then center according to the calculated amount of lines...
1624 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1626 // then actually draw
1627 for(i = startidx; i < CON_LINES_COUNT; ++i)
1629 con_lineinfo_t *l = &CON_LINES(i);
1631 if((l->mask & mask_must) != mask_must)
1633 if(l->mask & mask_mustnot)
1635 if(maxage && (l->addtime < t - maxage))
1638 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1648 Draws the last few lines of output transparently over the game top
1651 void Con_DrawNotify (void)
1654 float chatstart, notifystart, inputsize, height;
1656 char temptext[MAX_INPUTLINE];
1660 ConBuffer_FixTimes(&con);
1662 numChatlines = con_chat.integer;
1664 chatpos = con_chatpos.integer;
1666 if (con_notify.integer < 0)
1667 Cvar_SetValueQuick(&con_notify, 0);
1668 if (gamemode == GAME_TRANSFUSION)
1669 v = 8; // vertical offset
1673 // GAME_NEXUIZ: center, otherwise left justify
1674 align = con_notifyalign.value;
1675 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1677 if(gamemode == GAME_NEXUIZ)
1681 if(numChatlines || !con_chatrect.integer)
1685 // first chat, input line, then notify
1687 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1689 else if(chatpos > 0)
1691 // first notify, then (chatpos-1) empty lines, then chat, then input
1693 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1695 else // if(chatpos < 0)
1697 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1699 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1704 // just notify and input
1706 chatstart = 0; // shut off gcc warning
1709 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, "");
1711 if(con_chatrect.integer)
1713 x = con_chatrect_x.value * vid_conwidth.value;
1714 v = con_chatrect_y.value * vid_conheight.value;
1719 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1722 height = numChatlines * con_chatsize.value;
1726 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
1729 if (key_dest == key_message)
1731 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1732 int colorindex = -1;
1734 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL);
1736 // LordHavoc: speedup, and other improvements
1738 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1740 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1742 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1745 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1746 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1748 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1756 Returns the height of a given console line; calculates it if necessary.
1759 int Con_LineHeight(int lineno)
1761 con_lineinfo_t *li = &CON_LINES(lineno);
1762 if(li->height == -1)
1764 float width = vid_conwidth.value;
1766 con_lineinfo_t *li = &CON_LINES(lineno);
1767 ti.fontsize = con_textsize.value;
1768 ti.font = FONT_CONSOLE;
1769 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1778 Draws a line of the console; returns its height in lines.
1779 If alpha is 0, the line is not drawn, but still wrapped and its height
1783 int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1785 float width = vid_conwidth.value;
1787 con_lineinfo_t *li = &CON_LINES(lineno);
1789 if((li->mask & mask_must) != mask_must)
1791 if((li->mask & mask_mustnot) != 0)
1794 ti.continuationString = "";
1796 ti.fontsize = con_textsize.value;
1797 ti.font = FONT_CONSOLE;
1799 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1804 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1811 Calculates the last visible line index and how much to show of it based on
1815 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1820 if(con_backscroll < 0)
1825 // now count until we saw con_backscroll actual lines
1826 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1827 if((CON_LINES(i).mask & mask_must) == mask_must)
1828 if((CON_LINES(i).mask & mask_mustnot) == 0)
1830 int h = Con_LineHeight(i);
1832 // line is the last visible line?
1834 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1836 *limitlast = lines_seen + h - con_backscroll;
1843 // if we get here, no line was on screen - scroll so that one line is
1845 con_backscroll = lines_seen - 1;
1853 Draws the console with the solid background
1854 The typing input line at the bottom should only be drawn if typing is allowed
1857 void Con_DrawConsole (int lines)
1859 float alpha, alpha0;
1862 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1863 cachepic_t *conbackpic;
1868 if (con_backscroll < 0)
1871 con_vislines = lines;
1873 r_draw2d_force = true;
1875 // draw the background
1876 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
1877 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
1879 sx = scr_conscroll_x.value;
1880 sy = scr_conscroll_y.value;
1881 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL;
1882 sx *= realtime; sy *= realtime;
1883 sx -= floor(sx); sy -= floor(sy);
1884 if (conbackpic && conbackpic->tex != r_texture_notexture)
1885 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1886 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1887 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1888 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1889 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1892 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
1894 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
1896 sx = scr_conscroll2_x.value;
1897 sy = scr_conscroll2_y.value;
1898 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1899 sx *= realtime; sy *= realtime;
1900 sx -= floor(sx); sy -= floor(sy);
1901 if(conbackpic && conbackpic->tex != r_texture_notexture)
1902 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1903 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1904 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1905 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1906 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1909 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
1911 sx = scr_conscroll3_x.value;
1912 sy = scr_conscroll3_y.value;
1913 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1914 sx *= realtime; sy *= realtime;
1915 sx -= floor(sx); sy -= floor(sy);
1916 if(conbackpic && conbackpic->tex != r_texture_notexture)
1917 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1918 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1919 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1920 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1921 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1924 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);
1930 int count = CON_LINES_COUNT;
1931 float ymax = con_vislines - 2 * con_textsize.value;
1932 float y = ymax + con_textsize.value * con_backscroll;
1933 for (i = 0;i < count && y >= 0;i++)
1934 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
1935 // fix any excessive scrollback for the next frame
1936 if (i >= count && y >= 0)
1938 con_backscroll -= (int)(y / con_textsize.value);
1939 if (con_backscroll < 0)
1944 if(CON_LINES_COUNT > 0)
1946 int i, last, limitlast;
1948 float ymax = con_vislines - 2 * con_textsize.value;
1949 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1950 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1951 y = ymax - con_textsize.value;
1954 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1959 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
1961 break; // top of console buffer
1963 break; // top of console window
1970 // draw the input prompt, user text, and cursor if desired
1973 r_draw2d_force = false;
1980 Prints not only map filename, but also
1981 its format (q1/q2/q3/hl) and even its message
1983 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1984 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1985 //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
1986 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1987 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1991 int i, k, max, p, o, min;
1994 unsigned char buf[1024];
1996 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1997 t = FS_Search(message, 1, true);
2000 if (t->numfilenames > 1)
2001 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2002 len = (unsigned char *)Z_Malloc(t->numfilenames);
2004 for(max=i=0;i<t->numfilenames;i++)
2006 k = (int)strlen(t->filenames[i]);
2016 for(i=0;i<t->numfilenames;i++)
2018 int lumpofs = 0, lumplen = 0;
2019 char *entities = NULL;
2020 const char *data = NULL;
2022 char entfilename[MAX_QPATH];
2023 strlcpy(message, "^1**ERROR**^7", sizeof(message));
2025 f = FS_OpenVirtualFile(t->filenames[i], true);
2028 memset(buf, 0, 1024);
2029 FS_Read(f, buf, 1024);
2030 if (!memcmp(buf, "IBSP", 4))
2032 p = LittleLong(((int *)buf)[1]);
2033 if (p == Q3BSPVERSION)
2035 q3dheader_t *header = (q3dheader_t *)buf;
2036 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2037 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2039 else if (p == Q2BSPVERSION)
2041 q2dheader_t *header = (q2dheader_t *)buf;
2042 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2043 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2046 else if((p = BuffLittleLong(buf)) == BSPVERSION || p == 30)
2048 dheader_t *header = (dheader_t *)buf;
2049 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
2050 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
2054 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2055 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2056 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2057 if (!entities && lumplen >= 10)
2059 FS_Seek(f, lumpofs, SEEK_SET);
2060 entities = (char *)Z_Malloc(lumplen + 1);
2061 FS_Read(f, entities, lumplen);
2065 // if there are entities to parse, a missing message key just
2066 // means there is no title, so clear the message string now
2072 if (!COM_ParseToken_Simple(&data, false, false))
2074 if (com_token[0] == '{')
2076 if (com_token[0] == '}')
2078 // skip leading whitespace
2079 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2080 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2081 keyname[l] = com_token[k+l];
2083 if (!COM_ParseToken_Simple(&data, false, false))
2085 if (developer_extra.integer)
2086 Con_DPrintf("key: %s %s\n", keyname, com_token);
2087 if (!strcmp(keyname, "message"))
2089 // get the message contents
2090 strlcpy(message, com_token, sizeof(message));
2100 *(t->filenames[i]+len[i]+5) = 0;
2103 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
2104 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
2105 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
2106 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
2107 default: strlcpy((char *)buf, "??", sizeof(buf));break;
2109 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
2114 k = *(t->filenames[0]+5+p);
2117 for(i=1;i<t->numfilenames;i++)
2118 if(*(t->filenames[i]+5+p) != k)
2122 if(p > o && completedname && completednamebufferlength > 0)
2124 memset(completedname, 0, completednamebufferlength);
2125 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2135 New function for tab-completion system
2136 Added by EvilTypeGuy
2137 MEGA Thanks to Taniwha
2140 void Con_DisplayList(const char **list)
2142 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2143 const char **walk = list;
2146 len = (int)strlen(*walk);
2154 len = (int)strlen(*list);
2155 if (pos + maxlen >= width) {
2161 for (i = 0; i < (maxlen - len); i++)
2173 SanitizeString strips color tags from the string in
2174 and writes the result on string out
2176 void SanitizeString(char *in, char *out)
2180 if(*in == STRING_COLOR_TAG)
2185 out[0] = STRING_COLOR_TAG;
2189 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2196 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2199 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2201 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2208 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2213 else if (*in != STRING_COLOR_TAG)
2216 *out = qfont_table[*(unsigned char*)in];
2223 // Now it becomes TRICKY :D --blub
2224 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2225 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2226 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2227 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
2228 static int Nicks_matchpos;
2230 // co against <<:BLASTER:>> is true!?
2231 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2235 if(tolower(*a) == tolower(*b))
2249 return (*a < *b) ? -1 : 1;
2253 return (*a < *b) ? -1 : 1;
2257 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2260 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2262 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2263 return Nicks_strncasecmp_nospaces(a, b, a_len);
2264 return strncasecmp(a, b, a_len);
2267 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2269 // ignore non alphanumerics of B
2270 // if A contains a non-alphanumeric, B must contain it as well though!
2273 qboolean alnum_a, alnum_b;
2275 if(tolower(*a) == tolower(*b))
2277 if(*a == 0) // end of both strings, they're equal
2284 // not equal, end of one string?
2289 // ignore non alphanumerics
2290 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2291 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2292 if(!alnum_a) // b must contain this
2293 return (*a < *b) ? -1 : 1;
2296 // otherwise, both are alnum, they're just not equal, return the appropriate number
2298 return (*a < *b) ? -1 : 1;
2304 /* Nicks_CompleteCountPossible
2306 Count the number of possible nicks to complete
2308 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2316 if(!con_nickcompletion.integer)
2319 // changed that to 1
2320 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2323 for(i = 0; i < cl.maxclients; ++i)
2326 if(!cl.scores[p].name[0])
2329 SanitizeString(cl.scores[p].name, name);
2330 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2336 spos = pos - 1; // no need for a minimum of characters :)
2340 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2342 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2343 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2349 if(isCon && spos == 0)
2351 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2357 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2358 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2360 // the sanitized list
2361 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2364 Nicks_matchpos = match;
2367 Nicks_offset[count] = s - (&line[match]);
2368 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2375 void Cmd_CompleteNicksPrint(int count)
2378 for(i = 0; i < count; ++i)
2379 Con_Printf("%s\n", Nicks_list[i]);
2382 void Nicks_CutMatchesNormal(int count)
2384 // cut match 0 down to the longest possible completion
2387 c = strlen(Nicks_sanlist[0]) - 1;
2388 for(i = 1; i < count; ++i)
2390 l = strlen(Nicks_sanlist[i]) - 1;
2394 for(l = 0; l <= c; ++l)
2395 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2401 Nicks_sanlist[0][c+1] = 0;
2402 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2405 unsigned int Nicks_strcleanlen(const char *s)
2410 if( (*s >= 'a' && *s <= 'z') ||
2411 (*s >= 'A' && *s <= 'Z') ||
2412 (*s >= '0' && *s <= '9') ||
2420 void Nicks_CutMatchesAlphaNumeric(int count)
2422 // cut match 0 down to the longest possible completion
2425 char tempstr[sizeof(Nicks_sanlist[0])];
2427 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2429 c = strlen(Nicks_sanlist[0]);
2430 for(i = 0, l = 0; i < (int)c; ++i)
2432 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2433 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2434 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2436 tempstr[l++] = Nicks_sanlist[0][i];
2441 for(i = 1; i < count; ++i)
2444 b = Nicks_sanlist[i];
2454 if(tolower(*a) == tolower(*b))
2460 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2462 // b is alnum, so cut
2469 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2470 Nicks_CutMatchesNormal(count);
2471 //if(!Nicks_sanlist[0][0])
2472 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2474 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2475 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2479 void Nicks_CutMatchesNoSpaces(int count)
2481 // cut match 0 down to the longest possible completion
2484 char tempstr[sizeof(Nicks_sanlist[0])];
2487 c = strlen(Nicks_sanlist[0]);
2488 for(i = 0, l = 0; i < (int)c; ++i)
2490 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2492 tempstr[l++] = Nicks_sanlist[0][i];
2497 for(i = 1; i < count; ++i)
2500 b = Nicks_sanlist[i];
2510 if(tolower(*a) == tolower(*b))
2524 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2525 Nicks_CutMatchesNormal(count);
2526 //if(!Nicks_sanlist[0][0])
2527 //Con_Printf("TS: %s\n", tempstr);
2528 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2530 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2531 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2535 void Nicks_CutMatches(int count)
2537 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2538 Nicks_CutMatchesAlphaNumeric(count);
2539 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2540 Nicks_CutMatchesNoSpaces(count);
2542 Nicks_CutMatchesNormal(count);
2545 const char **Nicks_CompleteBuildList(int count)
2549 // the list is freed by Con_CompleteCommandLine, so create a char**
2550 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2552 for(; bpos < count; ++bpos)
2553 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2555 Nicks_CutMatches(count);
2563 Restores the previous used color, after the autocompleted name.
2565 int Nicks_AddLastColor(char *buffer, int pos)
2567 qboolean quote_added = false;
2569 int color = STRING_COLOR_DEFAULT + '0';
2570 char r = 0, g = 0, b = 0;
2572 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2574 // we'll have to add a quote :)
2575 buffer[pos++] = '\"';
2579 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2581 // add color when no quote was added, or when flags &4?
2583 for(match = Nicks_matchpos-1; match >= 0; --match)
2585 if(buffer[match] == STRING_COLOR_TAG)
2587 if( isdigit(buffer[match+1]) )
2589 color = buffer[match+1];
2592 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2594 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2596 r = buffer[match+2];
2597 g = buffer[match+3];
2598 b = buffer[match+4];
2607 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2609 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2610 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2613 buffer[pos++] = STRING_COLOR_TAG;
2616 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2622 buffer[pos++] = color;
2627 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2630 /*if(!con_nickcompletion.integer)
2631 return; is tested in Nicks_CompletionCountPossible */
2632 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2638 msg = Nicks_list[0];
2639 len = min(size - Nicks_matchpos - 3, strlen(msg));
2640 memcpy(&buffer[Nicks_matchpos], msg, len);
2641 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2642 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2643 buffer[len++] = ' ';
2650 Con_Printf("\n%i possible nicks:\n", n);
2651 Cmd_CompleteNicksPrint(n);
2653 Nicks_CutMatches(n);
2655 msg = Nicks_sanlist[0];
2656 len = min(size - Nicks_matchpos, strlen(msg));
2657 memcpy(&buffer[Nicks_matchpos], msg, len);
2658 buffer[Nicks_matchpos + len] = 0;
2660 return Nicks_matchpos + len;
2667 Con_CompleteCommandLine
2669 New function for tab-completion system
2670 Added by EvilTypeGuy
2671 Thanks to Fett erich@heintz.com
2673 Enhanced to tab-complete map names by [515]
2676 void Con_CompleteCommandLine (void)
2678 const char *cmd = "";
2680 const char **list[4] = {0, 0, 0, 0};
2683 int c, v, a, i, cmd_len, pos, k;
2684 int n; // nicks --blub
2685 const char *space, *patterns;
2687 //find what we want to complete
2692 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2698 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2699 key_line[key_linepos] = 0; //hide them
2701 space = strchr(key_line + 1, ' ');
2702 if(space && pos == (space - key_line) + 1)
2704 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2706 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2707 if(patterns && !*patterns)
2708 patterns = NULL; // get rid of the empty string
2710 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2714 if (GetMapList(s, t, sizeof(t)))
2716 // first move the cursor
2717 key_linepos += (int)strlen(t) - (int)strlen(s);
2719 // and now do the actual work
2721 strlcat(key_line, t, MAX_INPUTLINE);
2722 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2724 // and fix the cursor
2725 if(key_linepos > (int) strlen(key_line))
2726 key_linepos = (int) strlen(key_line);
2735 stringlist_t resultbuf, dirbuf;
2738 // // store completion patterns (space separated) for command foo in con_completion_foo
2739 // set con_completion_foo "foodata/*.foodefault *.foo"
2742 // Note: patterns with slash are always treated as absolute
2743 // patterns; patterns without slash search in the innermost
2744 // directory the user specified. There is no way to "complete into"
2745 // a directory as of now, as directories seem to be unknown to the
2749 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2750 // set con_completion_playdemo "*.dem"
2751 // set con_completion_play "*.wav *.ogg"
2753 // TODO somehow add support for directories; these shall complete
2754 // to their name + an appended slash.
2756 stringlistinit(&resultbuf);
2757 stringlistinit(&dirbuf);
2758 while(COM_ParseToken_Simple(&patterns, false, false))
2761 if(strchr(com_token, '/'))
2763 search = FS_Search(com_token, true, true);
2767 const char *slash = strrchr(s, '/');
2770 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2771 strlcat(t, com_token, sizeof(t));
2772 search = FS_Search(t, true, true);
2775 search = FS_Search(com_token, true, true);
2779 for(i = 0; i < search->numfilenames; ++i)
2780 if(!strncmp(search->filenames[i], s, strlen(s)))
2781 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2782 stringlistappend(&resultbuf, search->filenames[i]);
2783 FS_FreeSearch(search);
2787 // In any case, add directory names
2790 const char *slash = strrchr(s, '/');
2793 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2794 strlcat(t, "*", sizeof(t));
2795 search = FS_Search(t, true, true);
2798 search = FS_Search("*", true, true);
2801 for(i = 0; i < search->numfilenames; ++i)
2802 if(!strncmp(search->filenames[i], s, strlen(s)))
2803 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2804 stringlistappend(&dirbuf, search->filenames[i]);
2805 FS_FreeSearch(search);
2809 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2812 unsigned int matchchars;
2813 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2815 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2818 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2820 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2824 stringlistsort(&resultbuf); // dirbuf is already sorted
2825 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2826 for(i = 0; i < dirbuf.numstrings; ++i)
2828 Con_Printf("%s/\n", dirbuf.strings[i]);
2830 for(i = 0; i < resultbuf.numstrings; ++i)
2832 Con_Printf("%s\n", resultbuf.strings[i]);
2834 matchchars = sizeof(t) - 1;
2835 if(resultbuf.numstrings > 0)
2837 p = resultbuf.strings[0];
2838 q = resultbuf.strings[resultbuf.numstrings - 1];
2839 for(; *p && *p == *q; ++p, ++q);
2840 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2842 if(dirbuf.numstrings > 0)
2844 p = dirbuf.strings[0];
2845 q = dirbuf.strings[dirbuf.numstrings - 1];
2846 for(; *p && *p == *q; ++p, ++q);
2847 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2849 // now p points to the first non-equal character, or to the end
2850 // of resultbuf.strings[0]. We want to append the characters
2851 // from resultbuf.strings[0] to (not including) p as these are
2852 // the unique prefix
2853 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2856 // first move the cursor
2857 key_linepos += (int)strlen(t) - (int)strlen(s);
2859 // and now do the actual work
2861 strlcat(key_line, t, MAX_INPUTLINE);
2862 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2864 // and fix the cursor
2865 if(key_linepos > (int) strlen(key_line))
2866 key_linepos = (int) strlen(key_line);
2868 stringlistfreecontents(&resultbuf);
2869 stringlistfreecontents(&dirbuf);
2871 return; // bail out, when we complete for a command that wants a file name
2876 // Count number of possible matches and print them
2877 c = Cmd_CompleteCountPossible(s);
2880 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2881 Cmd_CompleteCommandPrint(s);
2883 v = Cvar_CompleteCountPossible(s);
2886 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2887 Cvar_CompleteCvarPrint(s);
2889 a = Cmd_CompleteAliasCountPossible(s);
2892 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2893 Cmd_CompleteAliasPrint(s);
2895 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2898 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2899 Cmd_CompleteNicksPrint(n);
2902 if (!(c + v + a + n)) // No possible matches
2905 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2910 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2912 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2914 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2916 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2918 for (cmd_len = (int)strlen(s);;cmd_len++)
2921 for (i = 0; i < 3; i++)
2923 for (l = list[i];*l;l++)
2924 if ((*l)[cmd_len] != cmd[cmd_len])
2926 // all possible matches share this character, so we continue...
2929 // if all matches ended at the same position, stop
2930 // (this means there is only one match)
2936 // prevent a buffer overrun by limiting cmd_len according to remaining space
2937 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2941 memcpy(&key_line[key_linepos], cmd, cmd_len);
2942 key_linepos += cmd_len;
2943 // if there is only one match, add a space after it
2944 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2947 { // was a nick, might have an offset, and needs colors ;) --blub
2948 key_linepos = pos - Nicks_offset[0];
2949 cmd_len = strlen(Nicks_list[0]);
2950 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2952 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2953 key_linepos += cmd_len;
2954 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2955 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2957 key_line[key_linepos++] = ' ';
2961 // use strlcat to avoid a buffer overrun
2962 key_line[key_linepos] = 0;
2963 strlcat(key_line, s2, sizeof(key_line));
2965 // free the command, cvar, and alias lists
2966 for (i = 0; i < 4; i++)
2968 Mem_Free((void *)list[i]);