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] = (char)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);
1053 // if this is the beginning of a new line, print timestamp
1056 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1058 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1059 line[index++] = STRING_COLOR_TAG;
1060 // assert( STRING_COLOR_DEFAULT < 10 )
1061 line[index++] = STRING_COLOR_DEFAULT + '0';
1062 // special color codes for chat messages must always come first
1063 // for Con_PrintToHistory to work properly
1064 if (*msg == 1 || *msg == 2)
1069 if (con_chatsound.value)
1071 if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
1073 if(msg[1] == '\r' && cl.foundtalk2wav)
1074 S_LocalSound ("sound/misc/talk2.wav");
1076 S_LocalSound ("sound/misc/talk.wav");
1080 if (msg[1] == '(' && cl.foundtalk2wav)
1081 S_LocalSound ("sound/misc/talk2.wav");
1083 S_LocalSound ("sound/misc/talk.wav");
1086 mask = CON_MASK_CHAT;
1088 line[index++] = STRING_COLOR_TAG;
1089 line[index++] = '3';
1091 Con_Rcon_AddChar(*msg);
1094 for (;*timestamp;index++, timestamp++)
1095 if (index < (int)sizeof(line) - 2)
1096 line[index] = *timestamp;
1098 mask |= additionalmask;
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);
1114 // send to terminal or dedicated server window
1116 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
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)+1);
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
1323 void Con_MaskPrintf(int mask, const char *fmt, ...)
1326 char msg[MAX_INPUTLINE];
1328 va_start(argptr,fmt);
1329 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1332 Con_MaskPrint(mask, msg);
1340 void Con_Print(const char *msg)
1342 Con_MaskPrint(CON_MASK_PRINT, msg);
1350 void Con_Printf(const char *fmt, ...)
1353 char msg[MAX_INPUTLINE];
1355 va_start(argptr,fmt);
1356 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1359 Con_MaskPrint(CON_MASK_PRINT, msg);
1367 void Con_DPrint(const char *msg)
1369 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1372 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1380 void Con_DPrintf(const char *fmt, ...)
1383 char msg[MAX_INPUTLINE];
1385 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1388 va_start(argptr,fmt);
1389 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1392 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1397 ==============================================================================
1401 ==============================================================================
1408 The input line scrolls horizontally if typing goes beyond the right edge
1410 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1413 extern cvar_t r_font_disable_freetype;
1414 void Con_DrawInput (void)
1418 char editlinecopy[MAX_INPUTLINE+1], *text;
1423 if (!key_consoleactive)
1424 return; // don't draw anything
1426 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1427 text = editlinecopy;
1429 // Advanced Console Editing by Radix radix@planetquake.com
1430 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1431 // use strlen of edit_line instead of key_linepos to allow editing
1432 // of early characters w/o erasing
1434 y = (int)strlen(text);
1436 // append enoug nul-bytes to cover the utf8-versions of the cursor too
1437 for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i)
1440 // add the cursor frame
1441 if (r_font_disable_freetype.integer)
1443 // this code is freetype incompatible!
1444 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1446 if (!utf8_enable.integer)
1447 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1448 else if (y + 3 < (int)sizeof(editlinecopy)-1)
1450 int ofs = u8_bytelen(text + key_linepos, 1);
1453 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1457 memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1458 memcpy(text + key_linepos, curbuf, len);
1461 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1465 // text[key_linepos + 1] = 0;
1467 len_out = key_linepos;
1469 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1470 x = vid_conwidth.value * 0.95 - xo; // scroll
1475 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 );
1477 // add a cursor on top of this (when using freetype)
1478 if (!r_font_disable_freetype.integer)
1480 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1482 if (!utf8_enable.integer)
1484 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1491 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1492 memcpy(text, curbuf, len);
1495 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);
1500 // key_line[key_linepos] = 0;
1506 float alignment; // 0 = left, 0.5 = center, 1 = right
1512 const char *continuationString;
1515 int colorindex; // init to -1
1519 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1521 con_text_info_t *ti = (con_text_info_t *) passthrough;
1524 ti->colorindex = -1;
1525 return ti->fontsize * ti->font->maxwidth;
1528 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1529 else if(maxWidth == -1)
1530 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1533 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1534 // Note: this is NOT a Con_Printf, as it could print recursively
1539 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1545 (void) isContinuation;
1549 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1551 con_text_info_t *ti = (con_text_info_t *) passthrough;
1553 if(ti->y < ti->ymin - 0.001)
1555 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1559 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1560 if(isContinuation && *ti->continuationString)
1561 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);
1563 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);
1566 ti->y += ti->fontsize;
1570 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)
1574 int maxlines = (int) floor(height / fontsize + 0.01f);
1577 int continuationWidth = 0;
1579 double t = cl.time; // saved so it won't change
1582 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1583 ti.fontsize = fontsize;
1584 ti.alignment = alignment_x;
1587 ti.ymax = y + height;
1588 ti.continuationString = continuationString;
1591 Con_WordWidthFunc(&ti, NULL, &l, -1);
1592 l = strlen(continuationString);
1593 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1595 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1596 startidx = CON_LINES_COUNT;
1597 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1599 con_lineinfo_t *l = &CON_LINES(i);
1602 if((l->mask & mask_must) != mask_must)
1604 if(l->mask & mask_mustnot)
1606 if(maxage && (l->addtime < t - maxage))
1610 // Calculate its actual height...
1611 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1612 if(lines + mylines >= maxlines)
1614 nskip = lines + mylines - maxlines;
1623 // then center according to the calculated amount of lines...
1625 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1627 // then actually draw
1628 for(i = startidx; i < CON_LINES_COUNT; ++i)
1630 con_lineinfo_t *l = &CON_LINES(i);
1632 if((l->mask & mask_must) != mask_must)
1634 if(l->mask & mask_mustnot)
1636 if(maxage && (l->addtime < t - maxage))
1639 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1649 Draws the last few lines of output transparently over the game top
1652 void Con_DrawNotify (void)
1655 float chatstart, notifystart, inputsize, height;
1657 char temptext[MAX_INPUTLINE];
1661 ConBuffer_FixTimes(&con);
1663 numChatlines = con_chat.integer;
1665 chatpos = con_chatpos.integer;
1667 if (con_notify.integer < 0)
1668 Cvar_SetValueQuick(&con_notify, 0);
1669 if (gamemode == GAME_TRANSFUSION)
1670 v = 8; // vertical offset
1674 // GAME_NEXUIZ: center, otherwise left justify
1675 align = con_notifyalign.value;
1676 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1678 if(gamemode == GAME_NEXUIZ)
1682 if(numChatlines || !con_chatrect.integer)
1686 // first chat, input line, then notify
1688 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1690 else if(chatpos > 0)
1692 // first notify, then (chatpos-1) empty lines, then chat, then input
1694 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1696 else // if(chatpos < 0)
1698 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1700 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1705 // just notify and input
1707 chatstart = 0; // shut off gcc warning
1710 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, "");
1712 if(con_chatrect.integer)
1714 x = con_chatrect_x.value * vid_conwidth.value;
1715 v = con_chatrect_y.value * vid_conheight.value;
1720 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1723 height = numChatlines * con_chatsize.value;
1727 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
1730 if (key_dest == key_message)
1732 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1733 int colorindex = -1;
1735 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL);
1737 // LordHavoc: speedup, and other improvements
1739 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1741 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1743 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1746 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1747 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1749 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1757 Returns the height of a given console line; calculates it if necessary.
1760 int Con_LineHeight(int lineno)
1762 con_lineinfo_t *li = &CON_LINES(lineno);
1763 if(li->height == -1)
1765 float width = vid_conwidth.value;
1767 con_lineinfo_t *li = &CON_LINES(lineno);
1768 ti.fontsize = con_textsize.value;
1769 ti.font = FONT_CONSOLE;
1770 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1779 Draws a line of the console; returns its height in lines.
1780 If alpha is 0, the line is not drawn, but still wrapped and its height
1784 int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1786 float width = vid_conwidth.value;
1788 con_lineinfo_t *li = &CON_LINES(lineno);
1790 if((li->mask & mask_must) != mask_must)
1792 if((li->mask & mask_mustnot) != 0)
1795 ti.continuationString = "";
1797 ti.fontsize = con_textsize.value;
1798 ti.font = FONT_CONSOLE;
1800 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1805 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1812 Calculates the last visible line index and how much to show of it based on
1816 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1821 if(con_backscroll < 0)
1826 // now count until we saw con_backscroll actual lines
1827 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1828 if((CON_LINES(i).mask & mask_must) == mask_must)
1829 if((CON_LINES(i).mask & mask_mustnot) == 0)
1831 int h = Con_LineHeight(i);
1833 // line is the last visible line?
1835 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1837 *limitlast = lines_seen + h - con_backscroll;
1844 // if we get here, no line was on screen - scroll so that one line is
1846 con_backscroll = lines_seen - 1;
1854 Draws the console with the solid background
1855 The typing input line at the bottom should only be drawn if typing is allowed
1858 void Con_DrawConsole (int lines)
1860 float alpha, alpha0;
1863 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1864 cachepic_t *conbackpic;
1869 if (con_backscroll < 0)
1872 con_vislines = lines;
1874 r_draw2d_force = true;
1876 // draw the background
1877 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
1878 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
1880 sx = scr_conscroll_x.value;
1881 sy = scr_conscroll_y.value;
1882 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL;
1883 sx *= realtime; sy *= realtime;
1884 sx -= floor(sx); sy -= floor(sy);
1885 if (conbackpic && conbackpic->tex != r_texture_notexture)
1886 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1887 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1888 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1889 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1890 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1893 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
1895 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
1897 sx = scr_conscroll2_x.value;
1898 sy = scr_conscroll2_y.value;
1899 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1900 sx *= realtime; sy *= realtime;
1901 sx -= floor(sx); sy -= floor(sy);
1902 if(conbackpic && conbackpic->tex != r_texture_notexture)
1903 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1904 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1905 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1906 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1907 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1910 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
1912 sx = scr_conscroll3_x.value;
1913 sy = scr_conscroll3_y.value;
1914 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1915 sx *= realtime; sy *= realtime;
1916 sx -= floor(sx); sy -= floor(sy);
1917 if(conbackpic && conbackpic->tex != r_texture_notexture)
1918 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1919 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1920 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1921 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1922 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1925 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);
1931 int count = CON_LINES_COUNT;
1932 float ymax = con_vislines - 2 * con_textsize.value;
1933 float y = ymax + con_textsize.value * con_backscroll;
1934 for (i = 0;i < count && y >= 0;i++)
1935 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
1936 // fix any excessive scrollback for the next frame
1937 if (i >= count && y >= 0)
1939 con_backscroll -= (int)(y / con_textsize.value);
1940 if (con_backscroll < 0)
1945 if(CON_LINES_COUNT > 0)
1947 int i, last, limitlast;
1949 float ymax = con_vislines - 2 * con_textsize.value;
1950 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1951 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1952 y = ymax - con_textsize.value;
1955 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1960 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
1962 break; // top of console buffer
1964 break; // top of console window
1971 // draw the input prompt, user text, and cursor if desired
1974 r_draw2d_force = false;
1981 Prints not only map filename, but also
1982 its format (q1/q2/q3/hl) and even its message
1984 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1985 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1986 //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
1987 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1988 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1992 int i, k, max, p, o, min;
1995 unsigned char buf[1024];
1997 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1998 t = FS_Search(message, 1, true);
2001 if (t->numfilenames > 1)
2002 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2003 len = (unsigned char *)Z_Malloc(t->numfilenames);
2005 for(max=i=0;i<t->numfilenames;i++)
2007 k = (int)strlen(t->filenames[i]);
2017 for(i=0;i<t->numfilenames;i++)
2019 int lumpofs = 0, lumplen = 0;
2020 char *entities = NULL;
2021 const char *data = NULL;
2023 char entfilename[MAX_QPATH];
2024 strlcpy(message, "^1**ERROR**^7", sizeof(message));
2026 f = FS_OpenVirtualFile(t->filenames[i], true);
2029 memset(buf, 0, 1024);
2030 FS_Read(f, buf, 1024);
2031 if (!memcmp(buf, "IBSP", 4))
2033 p = LittleLong(((int *)buf)[1]);
2034 if (p == Q3BSPVERSION)
2036 q3dheader_t *header = (q3dheader_t *)buf;
2037 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2038 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2040 else if (p == Q2BSPVERSION)
2042 q2dheader_t *header = (q2dheader_t *)buf;
2043 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2044 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2047 else if((p = BuffLittleLong(buf)) == BSPVERSION || p == 30)
2049 dheader_t *header = (dheader_t *)buf;
2050 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
2051 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
2055 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2056 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2057 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2058 if (!entities && lumplen >= 10)
2060 FS_Seek(f, lumpofs, SEEK_SET);
2061 entities = (char *)Z_Malloc(lumplen + 1);
2062 FS_Read(f, entities, lumplen);
2066 // if there are entities to parse, a missing message key just
2067 // means there is no title, so clear the message string now
2073 if (!COM_ParseToken_Simple(&data, false, false))
2075 if (com_token[0] == '{')
2077 if (com_token[0] == '}')
2079 // skip leading whitespace
2080 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2081 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2082 keyname[l] = com_token[k+l];
2084 if (!COM_ParseToken_Simple(&data, false, false))
2086 if (developer_extra.integer)
2087 Con_DPrintf("key: %s %s\n", keyname, com_token);
2088 if (!strcmp(keyname, "message"))
2090 // get the message contents
2091 strlcpy(message, com_token, sizeof(message));
2101 *(t->filenames[i]+len[i]+5) = 0;
2104 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
2105 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
2106 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
2107 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
2108 default: strlcpy((char *)buf, "??", sizeof(buf));break;
2110 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
2115 k = *(t->filenames[0]+5+p);
2118 for(i=1;i<t->numfilenames;i++)
2119 if(*(t->filenames[i]+5+p) != k)
2123 if(p > o && completedname && completednamebufferlength > 0)
2125 memset(completedname, 0, completednamebufferlength);
2126 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2136 New function for tab-completion system
2137 Added by EvilTypeGuy
2138 MEGA Thanks to Taniwha
2141 void Con_DisplayList(const char **list)
2143 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2144 const char **walk = list;
2147 len = (int)strlen(*walk);
2155 len = (int)strlen(*list);
2156 if (pos + maxlen >= width) {
2162 for (i = 0; i < (maxlen - len); i++)
2174 SanitizeString strips color tags from the string in
2175 and writes the result on string out
2177 void SanitizeString(char *in, char *out)
2181 if(*in == STRING_COLOR_TAG)
2186 out[0] = STRING_COLOR_TAG;
2190 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2197 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2200 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2202 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2209 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2214 else if (*in != STRING_COLOR_TAG)
2217 *out = qfont_table[*(unsigned char*)in];
2224 // Now it becomes TRICKY :D --blub
2225 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2226 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2227 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2228 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
2229 static int Nicks_matchpos;
2231 // co against <<:BLASTER:>> is true!?
2232 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2236 if(tolower(*a) == tolower(*b))
2250 return (*a < *b) ? -1 : 1;
2254 return (*a < *b) ? -1 : 1;
2258 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2261 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2263 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2264 return Nicks_strncasecmp_nospaces(a, b, a_len);
2265 return strncasecmp(a, b, a_len);
2268 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2270 // ignore non alphanumerics of B
2271 // if A contains a non-alphanumeric, B must contain it as well though!
2274 qboolean alnum_a, alnum_b;
2276 if(tolower(*a) == tolower(*b))
2278 if(*a == 0) // end of both strings, they're equal
2285 // not equal, end of one string?
2290 // ignore non alphanumerics
2291 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2292 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2293 if(!alnum_a) // b must contain this
2294 return (*a < *b) ? -1 : 1;
2297 // otherwise, both are alnum, they're just not equal, return the appropriate number
2299 return (*a < *b) ? -1 : 1;
2305 /* Nicks_CompleteCountPossible
2307 Count the number of possible nicks to complete
2309 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2317 if(!con_nickcompletion.integer)
2320 // changed that to 1
2321 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2324 for(i = 0; i < cl.maxclients; ++i)
2327 if(!cl.scores[p].name[0])
2330 SanitizeString(cl.scores[p].name, name);
2331 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2337 spos = pos - 1; // no need for a minimum of characters :)
2341 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2343 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2344 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2350 if(isCon && spos == 0)
2352 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2358 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2359 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2361 // the sanitized list
2362 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2365 Nicks_matchpos = match;
2368 Nicks_offset[count] = s - (&line[match]);
2369 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2376 void Cmd_CompleteNicksPrint(int count)
2379 for(i = 0; i < count; ++i)
2380 Con_Printf("%s\n", Nicks_list[i]);
2383 void Nicks_CutMatchesNormal(int count)
2385 // cut match 0 down to the longest possible completion
2388 c = strlen(Nicks_sanlist[0]) - 1;
2389 for(i = 1; i < count; ++i)
2391 l = strlen(Nicks_sanlist[i]) - 1;
2395 for(l = 0; l <= c; ++l)
2396 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2402 Nicks_sanlist[0][c+1] = 0;
2403 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2406 unsigned int Nicks_strcleanlen(const char *s)
2411 if( (*s >= 'a' && *s <= 'z') ||
2412 (*s >= 'A' && *s <= 'Z') ||
2413 (*s >= '0' && *s <= '9') ||
2421 void Nicks_CutMatchesAlphaNumeric(int count)
2423 // cut match 0 down to the longest possible completion
2426 char tempstr[sizeof(Nicks_sanlist[0])];
2428 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2430 c = strlen(Nicks_sanlist[0]);
2431 for(i = 0, l = 0; i < (int)c; ++i)
2433 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2434 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2435 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2437 tempstr[l++] = Nicks_sanlist[0][i];
2442 for(i = 1; i < count; ++i)
2445 b = Nicks_sanlist[i];
2455 if(tolower(*a) == tolower(*b))
2461 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2463 // b is alnum, so cut
2470 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2471 Nicks_CutMatchesNormal(count);
2472 //if(!Nicks_sanlist[0][0])
2473 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2475 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2476 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2480 void Nicks_CutMatchesNoSpaces(int count)
2482 // cut match 0 down to the longest possible completion
2485 char tempstr[sizeof(Nicks_sanlist[0])];
2488 c = strlen(Nicks_sanlist[0]);
2489 for(i = 0, l = 0; i < (int)c; ++i)
2491 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2493 tempstr[l++] = Nicks_sanlist[0][i];
2498 for(i = 1; i < count; ++i)
2501 b = Nicks_sanlist[i];
2511 if(tolower(*a) == tolower(*b))
2525 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2526 Nicks_CutMatchesNormal(count);
2527 //if(!Nicks_sanlist[0][0])
2528 //Con_Printf("TS: %s\n", tempstr);
2529 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2531 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2532 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2536 void Nicks_CutMatches(int count)
2538 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2539 Nicks_CutMatchesAlphaNumeric(count);
2540 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2541 Nicks_CutMatchesNoSpaces(count);
2543 Nicks_CutMatchesNormal(count);
2546 const char **Nicks_CompleteBuildList(int count)
2550 // the list is freed by Con_CompleteCommandLine, so create a char**
2551 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2553 for(; bpos < count; ++bpos)
2554 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2556 Nicks_CutMatches(count);
2564 Restores the previous used color, after the autocompleted name.
2566 int Nicks_AddLastColor(char *buffer, int pos)
2568 qboolean quote_added = false;
2570 int color = STRING_COLOR_DEFAULT + '0';
2571 char r = 0, g = 0, b = 0;
2573 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2575 // we'll have to add a quote :)
2576 buffer[pos++] = '\"';
2580 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2582 // add color when no quote was added, or when flags &4?
2584 for(match = Nicks_matchpos-1; match >= 0; --match)
2586 if(buffer[match] == STRING_COLOR_TAG)
2588 if( isdigit(buffer[match+1]) )
2590 color = buffer[match+1];
2593 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2595 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2597 r = buffer[match+2];
2598 g = buffer[match+3];
2599 b = buffer[match+4];
2608 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2610 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2611 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2614 buffer[pos++] = STRING_COLOR_TAG;
2617 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2623 buffer[pos++] = color;
2628 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2631 /*if(!con_nickcompletion.integer)
2632 return; is tested in Nicks_CompletionCountPossible */
2633 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2639 msg = Nicks_list[0];
2640 len = min(size - Nicks_matchpos - 3, strlen(msg));
2641 memcpy(&buffer[Nicks_matchpos], msg, len);
2642 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2643 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2644 buffer[len++] = ' ';
2651 Con_Printf("\n%i possible nicks:\n", n);
2652 Cmd_CompleteNicksPrint(n);
2654 Nicks_CutMatches(n);
2656 msg = Nicks_sanlist[0];
2657 len = min(size - Nicks_matchpos, strlen(msg));
2658 memcpy(&buffer[Nicks_matchpos], msg, len);
2659 buffer[Nicks_matchpos + len] = 0;
2661 return Nicks_matchpos + len;
2668 Con_CompleteCommandLine
2670 New function for tab-completion system
2671 Added by EvilTypeGuy
2672 Thanks to Fett erich@heintz.com
2674 Enhanced to tab-complete map names by [515]
2677 void Con_CompleteCommandLine (void)
2679 const char *cmd = "";
2681 const char **list[4] = {0, 0, 0, 0};
2684 int c, v, a, i, cmd_len, pos, k;
2685 int n; // nicks --blub
2686 const char *space, *patterns;
2688 //find what we want to complete
2693 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2699 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2700 key_line[key_linepos] = 0; //hide them
2702 space = strchr(key_line + 1, ' ');
2703 if(space && pos == (space - key_line) + 1)
2705 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2707 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2708 if(patterns && !*patterns)
2709 patterns = NULL; // get rid of the empty string
2711 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2715 if (GetMapList(s, t, sizeof(t)))
2717 // first move the cursor
2718 key_linepos += (int)strlen(t) - (int)strlen(s);
2720 // and now do the actual work
2722 strlcat(key_line, t, MAX_INPUTLINE);
2723 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2725 // and fix the cursor
2726 if(key_linepos > (int) strlen(key_line))
2727 key_linepos = (int) strlen(key_line);
2736 stringlist_t resultbuf, dirbuf;
2739 // // store completion patterns (space separated) for command foo in con_completion_foo
2740 // set con_completion_foo "foodata/*.foodefault *.foo"
2743 // Note: patterns with slash are always treated as absolute
2744 // patterns; patterns without slash search in the innermost
2745 // directory the user specified. There is no way to "complete into"
2746 // a directory as of now, as directories seem to be unknown to the
2750 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2751 // set con_completion_playdemo "*.dem"
2752 // set con_completion_play "*.wav *.ogg"
2754 // TODO somehow add support for directories; these shall complete
2755 // to their name + an appended slash.
2757 stringlistinit(&resultbuf);
2758 stringlistinit(&dirbuf);
2759 while(COM_ParseToken_Simple(&patterns, false, false))
2762 if(strchr(com_token, '/'))
2764 search = FS_Search(com_token, true, true);
2768 const char *slash = strrchr(s, '/');
2771 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2772 strlcat(t, com_token, sizeof(t));
2773 search = FS_Search(t, true, true);
2776 search = FS_Search(com_token, true, true);
2780 for(i = 0; i < search->numfilenames; ++i)
2781 if(!strncmp(search->filenames[i], s, strlen(s)))
2782 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2783 stringlistappend(&resultbuf, search->filenames[i]);
2784 FS_FreeSearch(search);
2788 // In any case, add directory names
2791 const char *slash = strrchr(s, '/');
2794 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2795 strlcat(t, "*", sizeof(t));
2796 search = FS_Search(t, true, true);
2799 search = FS_Search("*", true, true);
2802 for(i = 0; i < search->numfilenames; ++i)
2803 if(!strncmp(search->filenames[i], s, strlen(s)))
2804 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2805 stringlistappend(&dirbuf, search->filenames[i]);
2806 FS_FreeSearch(search);
2810 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2813 unsigned int matchchars;
2814 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2816 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2819 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2821 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2825 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2826 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2827 for(i = 0; i < dirbuf.numstrings; ++i)
2829 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2831 for(i = 0; i < resultbuf.numstrings; ++i)
2833 Con_Printf("%s\n", resultbuf.strings[i]);
2835 matchchars = sizeof(t) - 1;
2836 if(resultbuf.numstrings > 0)
2838 p = resultbuf.strings[0];
2839 q = resultbuf.strings[resultbuf.numstrings - 1];
2840 for(; *p && *p == *q; ++p, ++q);
2841 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2843 if(dirbuf.numstrings > 0)
2845 p = dirbuf.strings[0];
2846 q = dirbuf.strings[dirbuf.numstrings - 1];
2847 for(; *p && *p == *q; ++p, ++q);
2848 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2850 // now p points to the first non-equal character, or to the end
2851 // of resultbuf.strings[0]. We want to append the characters
2852 // from resultbuf.strings[0] to (not including) p as these are
2853 // the unique prefix
2854 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2857 // first move the cursor
2858 key_linepos += (int)strlen(t) - (int)strlen(s);
2860 // and now do the actual work
2862 strlcat(key_line, t, MAX_INPUTLINE);
2863 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2865 // and fix the cursor
2866 if(key_linepos > (int) strlen(key_line))
2867 key_linepos = (int) strlen(key_line);
2869 stringlistfreecontents(&resultbuf);
2870 stringlistfreecontents(&dirbuf);
2872 return; // bail out, when we complete for a command that wants a file name
2877 // Count number of possible matches and print them
2878 c = Cmd_CompleteCountPossible(s);
2881 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2882 Cmd_CompleteCommandPrint(s);
2884 v = Cvar_CompleteCountPossible(s);
2887 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2888 Cvar_CompleteCvarPrint(s);
2890 a = Cmd_CompleteAliasCountPossible(s);
2893 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2894 Cmd_CompleteAliasPrint(s);
2896 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2899 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2900 Cmd_CompleteNicksPrint(n);
2903 if (!(c + v + a + n)) // No possible matches
2906 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2911 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2913 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2915 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2917 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2919 for (cmd_len = (int)strlen(s);;cmd_len++)
2922 for (i = 0; i < 3; i++)
2924 for (l = list[i];*l;l++)
2925 if ((*l)[cmd_len] != cmd[cmd_len])
2927 // all possible matches share this character, so we continue...
2930 // if all matches ended at the same position, stop
2931 // (this means there is only one match)
2937 // prevent a buffer overrun by limiting cmd_len according to remaining space
2938 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2942 memcpy(&key_line[key_linepos], cmd, cmd_len);
2943 key_linepos += cmd_len;
2944 // if there is only one match, add a space after it
2945 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2948 { // was a nick, might have an offset, and needs colors ;) --blub
2949 key_linepos = pos - Nicks_offset[0];
2950 cmd_len = strlen(Nicks_list[0]);
2951 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2953 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2954 key_linepos += cmd_len;
2955 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2956 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2958 key_line[key_linepos++] = ' ';
2962 // use strlcat to avoid a buffer overrun
2963 key_line[key_linepos] = 0;
2964 strlcat(key_line, s2, sizeof(key_line));
2966 // free the command, cvar, and alias lists
2967 for (i = 0; i < 4; i++)
2969 Mem_Free((void *)list[i]);