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
1119 if(sys_specialcharactertranslation.integer)
1121 for (p = (unsigned char *) line;*p; p++)
1122 *p = qfont_table[*p];
1125 if(sys_colortranslation.integer == 1) // ANSI
1127 static char printline[MAX_INPUTLINE * 4 + 3];
1128 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1129 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1134 for(in = line, out = printline; *in; ++in)
1138 case STRING_COLOR_TAG:
1139 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1141 char r = tolower(in[2]);
1142 char g = tolower(in[3]);
1143 char b = tolower(in[4]);
1144 // it's a hex digit already, so the else part needs no check --blub
1145 if(isdigit(r)) r -= '0';
1147 if(isdigit(g)) g -= '0';
1149 if(isdigit(b)) b -= '0';
1152 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1153 in += 3; // 3 only, the switch down there does the fourth
1160 case STRING_COLOR_TAG:
1162 *out++ = STRING_COLOR_TAG;
1168 if(lastcolor == 0) break; else lastcolor = 0;
1169 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1174 if(lastcolor == 1) break; else lastcolor = 1;
1175 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1180 if(lastcolor == 2) break; else lastcolor = 2;
1181 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1186 if(lastcolor == 3) break; else lastcolor = 3;
1187 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1192 if(lastcolor == 4) break; else lastcolor = 4;
1193 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1198 if(lastcolor == 5) break; else lastcolor = 5;
1199 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1204 if(lastcolor == 6) break; else lastcolor = 6;
1205 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1210 // bold normal color
1212 if(lastcolor == 8) break; else lastcolor = 8;
1213 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1216 *out++ = STRING_COLOR_TAG;
1223 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1240 Sys_PrintToTerminal(printline);
1242 else if(sys_colortranslation.integer == 2) // Quake
1244 Sys_PrintToTerminal(line);
1248 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1251 for(in = line, out = printline; *in; ++in)
1255 case STRING_COLOR_TAG:
1258 case STRING_COLOR_RGB_TAG_CHAR:
1259 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1264 *out++ = STRING_COLOR_TAG;
1265 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1268 case STRING_COLOR_TAG:
1270 *out++ = STRING_COLOR_TAG;
1285 *out++ = STRING_COLOR_TAG;
1295 Sys_PrintToTerminal(printline);
1298 // empty the line buffer
1309 void Con_MaskPrintf(int mask, const char *fmt, ...)
1312 char msg[MAX_INPUTLINE];
1314 va_start(argptr,fmt);
1315 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1318 Con_MaskPrint(mask, msg);
1326 void Con_Print(const char *msg)
1328 Con_MaskPrint(CON_MASK_PRINT, msg);
1336 void Con_Printf(const char *fmt, ...)
1339 char msg[MAX_INPUTLINE];
1341 va_start(argptr,fmt);
1342 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1345 Con_MaskPrint(CON_MASK_PRINT, msg);
1353 void Con_DPrint(const char *msg)
1355 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1358 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1366 void Con_DPrintf(const char *fmt, ...)
1369 char msg[MAX_INPUTLINE];
1371 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1374 va_start(argptr,fmt);
1375 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1378 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1383 ==============================================================================
1387 ==============================================================================
1394 The input line scrolls horizontally if typing goes beyond the right edge
1396 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1399 extern cvar_t r_font_disable_freetype;
1400 void Con_DrawInput (void)
1404 char editlinecopy[MAX_INPUTLINE+1], *text;
1409 if (!key_consoleactive)
1410 return; // don't draw anything
1412 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1413 text = editlinecopy;
1415 // Advanced Console Editing by Radix radix@planetquake.com
1416 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1417 // use strlen of edit_line instead of key_linepos to allow editing
1418 // of early characters w/o erasing
1420 y = (int)strlen(text);
1422 // append enoug nul-bytes to cover the utf8-versions of the cursor too
1423 for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i)
1426 // add the cursor frame
1427 if (r_font_disable_freetype.integer)
1429 // this code is freetype incompatible!
1430 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1432 if (!utf8_enable.integer)
1433 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1434 else if (y + 3 < (int)sizeof(editlinecopy)-1)
1436 int ofs = u8_bytelen(text + key_linepos, 1);
1439 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1443 memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1444 memcpy(text + key_linepos, curbuf, len);
1447 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1451 // text[key_linepos + 1] = 0;
1453 len_out = key_linepos;
1455 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1456 x = vid_conwidth.value * 0.95 - xo; // scroll
1461 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 );
1463 // add a cursor on top of this (when using freetype)
1464 if (!r_font_disable_freetype.integer)
1466 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1468 if (!utf8_enable.integer)
1470 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1477 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1478 memcpy(text, curbuf, len);
1481 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);
1486 // key_line[key_linepos] = 0;
1492 float alignment; // 0 = left, 0.5 = center, 1 = right
1498 const char *continuationString;
1501 int colorindex; // init to -1
1505 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1507 con_text_info_t *ti = (con_text_info_t *) passthrough;
1510 ti->colorindex = -1;
1511 return ti->fontsize * ti->font->maxwidth;
1514 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1515 else if(maxWidth == -1)
1516 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1519 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1520 // Note: this is NOT a Con_Printf, as it could print recursively
1525 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1531 (void) isContinuation;
1535 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1537 con_text_info_t *ti = (con_text_info_t *) passthrough;
1539 if(ti->y < ti->ymin - 0.001)
1541 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1545 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1546 if(isContinuation && *ti->continuationString)
1547 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);
1549 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);
1552 ti->y += ti->fontsize;
1556 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)
1560 int maxlines = (int) floor(height / fontsize + 0.01f);
1563 int continuationWidth = 0;
1565 double t = cl.time; // saved so it won't change
1568 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1569 ti.fontsize = fontsize;
1570 ti.alignment = alignment_x;
1573 ti.ymax = y + height;
1574 ti.continuationString = continuationString;
1577 Con_WordWidthFunc(&ti, NULL, &l, -1);
1578 l = strlen(continuationString);
1579 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1581 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1582 startidx = CON_LINES_COUNT;
1583 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1585 con_lineinfo_t *l = &CON_LINES(i);
1588 if((l->mask & mask_must) != mask_must)
1590 if(l->mask & mask_mustnot)
1592 if(maxage && (l->addtime < t - maxage))
1596 // Calculate its actual height...
1597 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1598 if(lines + mylines >= maxlines)
1600 nskip = lines + mylines - maxlines;
1609 // then center according to the calculated amount of lines...
1611 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1613 // then actually draw
1614 for(i = startidx; i < CON_LINES_COUNT; ++i)
1616 con_lineinfo_t *l = &CON_LINES(i);
1618 if((l->mask & mask_must) != mask_must)
1620 if(l->mask & mask_mustnot)
1622 if(maxage && (l->addtime < t - maxage))
1625 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1635 Draws the last few lines of output transparently over the game top
1638 void Con_DrawNotify (void)
1641 float chatstart, notifystart, inputsize, height;
1643 char temptext[MAX_INPUTLINE];
1647 ConBuffer_FixTimes(&con);
1649 numChatlines = con_chat.integer;
1651 chatpos = con_chatpos.integer;
1653 if (con_notify.integer < 0)
1654 Cvar_SetValueQuick(&con_notify, 0);
1655 if (gamemode == GAME_TRANSFUSION)
1656 v = 8; // vertical offset
1660 // GAME_NEXUIZ: center, otherwise left justify
1661 align = con_notifyalign.value;
1662 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1664 if(gamemode == GAME_NEXUIZ)
1668 if(numChatlines || !con_chatrect.integer)
1672 // first chat, input line, then notify
1674 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1676 else if(chatpos > 0)
1678 // first notify, then (chatpos-1) empty lines, then chat, then input
1680 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1682 else // if(chatpos < 0)
1684 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1686 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1691 // just notify and input
1693 chatstart = 0; // shut off gcc warning
1696 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, "");
1698 if(con_chatrect.integer)
1700 x = con_chatrect_x.value * vid_conwidth.value;
1701 v = con_chatrect_y.value * vid_conheight.value;
1706 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1709 height = numChatlines * con_chatsize.value;
1713 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
1716 if (key_dest == key_message)
1718 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1719 int colorindex = -1;
1721 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL);
1723 // LordHavoc: speedup, and other improvements
1725 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1727 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1729 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1732 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1733 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1735 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1743 Returns the height of a given console line; calculates it if necessary.
1746 int Con_LineHeight(int lineno)
1748 con_lineinfo_t *li = &CON_LINES(lineno);
1749 if(li->height == -1)
1751 float width = vid_conwidth.value;
1753 con_lineinfo_t *li = &CON_LINES(lineno);
1754 ti.fontsize = con_textsize.value;
1755 ti.font = FONT_CONSOLE;
1756 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1765 Draws a line of the console; returns its height in lines.
1766 If alpha is 0, the line is not drawn, but still wrapped and its height
1770 int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1772 float width = vid_conwidth.value;
1774 con_lineinfo_t *li = &CON_LINES(lineno);
1776 if((li->mask & mask_must) != mask_must)
1778 if((li->mask & mask_mustnot) != 0)
1781 ti.continuationString = "";
1783 ti.fontsize = con_textsize.value;
1784 ti.font = FONT_CONSOLE;
1786 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1791 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1798 Calculates the last visible line index and how much to show of it based on
1802 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1807 if(con_backscroll < 0)
1812 // now count until we saw con_backscroll actual lines
1813 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1814 if((CON_LINES(i).mask & mask_must) == mask_must)
1815 if((CON_LINES(i).mask & mask_mustnot) == 0)
1817 int h = Con_LineHeight(i);
1819 // line is the last visible line?
1821 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1823 *limitlast = lines_seen + h - con_backscroll;
1830 // if we get here, no line was on screen - scroll so that one line is
1832 con_backscroll = lines_seen - 1;
1840 Draws the console with the solid background
1841 The typing input line at the bottom should only be drawn if typing is allowed
1844 void Con_DrawConsole (int lines)
1846 float alpha, alpha0;
1849 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1850 cachepic_t *conbackpic;
1855 if (con_backscroll < 0)
1858 con_vislines = lines;
1860 r_draw2d_force = true;
1862 // draw the background
1863 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
1864 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
1866 sx = scr_conscroll_x.value;
1867 sy = scr_conscroll_y.value;
1868 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL;
1869 sx *= realtime; sy *= realtime;
1870 sx -= floor(sx); sy -= floor(sy);
1871 if (conbackpic && conbackpic->tex != r_texture_notexture)
1872 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1873 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1874 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1875 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1876 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1879 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
1881 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
1883 sx = scr_conscroll2_x.value;
1884 sy = scr_conscroll2_y.value;
1885 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1886 sx *= realtime; sy *= realtime;
1887 sx -= floor(sx); sy -= floor(sy);
1888 if(conbackpic && conbackpic->tex != r_texture_notexture)
1889 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1890 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1891 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1892 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1893 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1896 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
1898 sx = scr_conscroll3_x.value;
1899 sy = scr_conscroll3_y.value;
1900 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1901 sx *= realtime; sy *= realtime;
1902 sx -= floor(sx); sy -= floor(sy);
1903 if(conbackpic && conbackpic->tex != r_texture_notexture)
1904 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1905 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1906 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1907 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1908 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1911 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);
1917 int count = CON_LINES_COUNT;
1918 float ymax = con_vislines - 2 * con_textsize.value;
1919 float y = ymax + con_textsize.value * con_backscroll;
1920 for (i = 0;i < count && y >= 0;i++)
1921 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
1922 // fix any excessive scrollback for the next frame
1923 if (i >= count && y >= 0)
1925 con_backscroll -= (int)(y / con_textsize.value);
1926 if (con_backscroll < 0)
1931 if(CON_LINES_COUNT > 0)
1933 int i, last, limitlast;
1935 float ymax = con_vislines - 2 * con_textsize.value;
1936 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1937 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1938 y = ymax - con_textsize.value;
1941 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1946 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
1948 break; // top of console buffer
1950 break; // top of console window
1957 // draw the input prompt, user text, and cursor if desired
1960 r_draw2d_force = false;
1967 Prints not only map filename, but also
1968 its format (q1/q2/q3/hl) and even its message
1970 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1971 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1972 //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
1973 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1974 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1978 int i, k, max, p, o, min;
1981 unsigned char buf[1024];
1983 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1984 t = FS_Search(message, 1, true);
1987 if (t->numfilenames > 1)
1988 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1989 len = (unsigned char *)Z_Malloc(t->numfilenames);
1991 for(max=i=0;i<t->numfilenames;i++)
1993 k = (int)strlen(t->filenames[i]);
2003 for(i=0;i<t->numfilenames;i++)
2005 int lumpofs = 0, lumplen = 0;
2006 char *entities = NULL;
2007 const char *data = NULL;
2009 char entfilename[MAX_QPATH];
2010 strlcpy(message, "^1**ERROR**^7", sizeof(message));
2012 f = FS_OpenVirtualFile(t->filenames[i], true);
2015 memset(buf, 0, 1024);
2016 FS_Read(f, buf, 1024);
2017 if (!memcmp(buf, "IBSP", 4))
2019 p = LittleLong(((int *)buf)[1]);
2020 if (p == Q3BSPVERSION)
2022 q3dheader_t *header = (q3dheader_t *)buf;
2023 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2024 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2026 else if (p == Q2BSPVERSION)
2028 q2dheader_t *header = (q2dheader_t *)buf;
2029 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2030 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2033 else if((p = BuffLittleLong(buf)) == BSPVERSION || p == 30)
2035 dheader_t *header = (dheader_t *)buf;
2036 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
2037 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
2041 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2042 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2043 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2044 if (!entities && lumplen >= 10)
2046 FS_Seek(f, lumpofs, SEEK_SET);
2047 entities = (char *)Z_Malloc(lumplen + 1);
2048 FS_Read(f, entities, lumplen);
2052 // if there are entities to parse, a missing message key just
2053 // means there is no title, so clear the message string now
2059 if (!COM_ParseToken_Simple(&data, false, false))
2061 if (com_token[0] == '{')
2063 if (com_token[0] == '}')
2065 // skip leading whitespace
2066 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2067 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2068 keyname[l] = com_token[k+l];
2070 if (!COM_ParseToken_Simple(&data, false, false))
2072 if (developer_extra.integer)
2073 Con_DPrintf("key: %s %s\n", keyname, com_token);
2074 if (!strcmp(keyname, "message"))
2076 // get the message contents
2077 strlcpy(message, com_token, sizeof(message));
2087 *(t->filenames[i]+len[i]+5) = 0;
2090 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
2091 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
2092 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
2093 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
2094 default: strlcpy((char *)buf, "??", sizeof(buf));break;
2096 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
2101 k = *(t->filenames[0]+5+p);
2104 for(i=1;i<t->numfilenames;i++)
2105 if(*(t->filenames[i]+5+p) != k)
2109 if(p > o && completedname && completednamebufferlength > 0)
2111 memset(completedname, 0, completednamebufferlength);
2112 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2122 New function for tab-completion system
2123 Added by EvilTypeGuy
2124 MEGA Thanks to Taniwha
2127 void Con_DisplayList(const char **list)
2129 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2130 const char **walk = list;
2133 len = (int)strlen(*walk);
2141 len = (int)strlen(*list);
2142 if (pos + maxlen >= width) {
2148 for (i = 0; i < (maxlen - len); i++)
2160 SanitizeString strips color tags from the string in
2161 and writes the result on string out
2163 void SanitizeString(char *in, char *out)
2167 if(*in == STRING_COLOR_TAG)
2172 out[0] = STRING_COLOR_TAG;
2176 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2183 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2186 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2188 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2195 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2200 else if (*in != STRING_COLOR_TAG)
2203 *out = qfont_table[*(unsigned char*)in];
2210 // Now it becomes TRICKY :D --blub
2211 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2212 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2213 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2214 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
2215 static int Nicks_matchpos;
2217 // co against <<:BLASTER:>> is true!?
2218 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2222 if(tolower(*a) == tolower(*b))
2236 return (*a < *b) ? -1 : 1;
2240 return (*a < *b) ? -1 : 1;
2244 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2247 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2249 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2250 return Nicks_strncasecmp_nospaces(a, b, a_len);
2251 return strncasecmp(a, b, a_len);
2254 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2256 // ignore non alphanumerics of B
2257 // if A contains a non-alphanumeric, B must contain it as well though!
2260 qboolean alnum_a, alnum_b;
2262 if(tolower(*a) == tolower(*b))
2264 if(*a == 0) // end of both strings, they're equal
2271 // not equal, end of one string?
2276 // ignore non alphanumerics
2277 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2278 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2279 if(!alnum_a) // b must contain this
2280 return (*a < *b) ? -1 : 1;
2283 // otherwise, both are alnum, they're just not equal, return the appropriate number
2285 return (*a < *b) ? -1 : 1;
2291 /* Nicks_CompleteCountPossible
2293 Count the number of possible nicks to complete
2295 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2303 if(!con_nickcompletion.integer)
2306 // changed that to 1
2307 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2310 for(i = 0; i < cl.maxclients; ++i)
2313 if(!cl.scores[p].name[0])
2316 SanitizeString(cl.scores[p].name, name);
2317 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2323 spos = pos - 1; // no need for a minimum of characters :)
2327 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2329 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2330 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2336 if(isCon && spos == 0)
2338 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2344 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2345 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2347 // the sanitized list
2348 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2351 Nicks_matchpos = match;
2354 Nicks_offset[count] = s - (&line[match]);
2355 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2362 void Cmd_CompleteNicksPrint(int count)
2365 for(i = 0; i < count; ++i)
2366 Con_Printf("%s\n", Nicks_list[i]);
2369 void Nicks_CutMatchesNormal(int count)
2371 // cut match 0 down to the longest possible completion
2374 c = strlen(Nicks_sanlist[0]) - 1;
2375 for(i = 1; i < count; ++i)
2377 l = strlen(Nicks_sanlist[i]) - 1;
2381 for(l = 0; l <= c; ++l)
2382 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2388 Nicks_sanlist[0][c+1] = 0;
2389 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2392 unsigned int Nicks_strcleanlen(const char *s)
2397 if( (*s >= 'a' && *s <= 'z') ||
2398 (*s >= 'A' && *s <= 'Z') ||
2399 (*s >= '0' && *s <= '9') ||
2407 void Nicks_CutMatchesAlphaNumeric(int count)
2409 // cut match 0 down to the longest possible completion
2412 char tempstr[sizeof(Nicks_sanlist[0])];
2414 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2416 c = strlen(Nicks_sanlist[0]);
2417 for(i = 0, l = 0; i < (int)c; ++i)
2419 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2420 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2421 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2423 tempstr[l++] = Nicks_sanlist[0][i];
2428 for(i = 1; i < count; ++i)
2431 b = Nicks_sanlist[i];
2441 if(tolower(*a) == tolower(*b))
2447 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2449 // b is alnum, so cut
2456 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2457 Nicks_CutMatchesNormal(count);
2458 //if(!Nicks_sanlist[0][0])
2459 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2461 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2462 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2466 void Nicks_CutMatchesNoSpaces(int count)
2468 // cut match 0 down to the longest possible completion
2471 char tempstr[sizeof(Nicks_sanlist[0])];
2474 c = strlen(Nicks_sanlist[0]);
2475 for(i = 0, l = 0; i < (int)c; ++i)
2477 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2479 tempstr[l++] = Nicks_sanlist[0][i];
2484 for(i = 1; i < count; ++i)
2487 b = Nicks_sanlist[i];
2497 if(tolower(*a) == tolower(*b))
2511 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2512 Nicks_CutMatchesNormal(count);
2513 //if(!Nicks_sanlist[0][0])
2514 //Con_Printf("TS: %s\n", tempstr);
2515 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2517 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2518 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2522 void Nicks_CutMatches(int count)
2524 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2525 Nicks_CutMatchesAlphaNumeric(count);
2526 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2527 Nicks_CutMatchesNoSpaces(count);
2529 Nicks_CutMatchesNormal(count);
2532 const char **Nicks_CompleteBuildList(int count)
2536 // the list is freed by Con_CompleteCommandLine, so create a char**
2537 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2539 for(; bpos < count; ++bpos)
2540 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2542 Nicks_CutMatches(count);
2550 Restores the previous used color, after the autocompleted name.
2552 int Nicks_AddLastColor(char *buffer, int pos)
2554 qboolean quote_added = false;
2556 int color = STRING_COLOR_DEFAULT + '0';
2557 char r = 0, g = 0, b = 0;
2559 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2561 // we'll have to add a quote :)
2562 buffer[pos++] = '\"';
2566 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2568 // add color when no quote was added, or when flags &4?
2570 for(match = Nicks_matchpos-1; match >= 0; --match)
2572 if(buffer[match] == STRING_COLOR_TAG)
2574 if( isdigit(buffer[match+1]) )
2576 color = buffer[match+1];
2579 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2581 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2583 r = buffer[match+2];
2584 g = buffer[match+3];
2585 b = buffer[match+4];
2594 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2596 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2597 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2600 buffer[pos++] = STRING_COLOR_TAG;
2603 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2609 buffer[pos++] = color;
2614 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2617 /*if(!con_nickcompletion.integer)
2618 return; is tested in Nicks_CompletionCountPossible */
2619 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2625 msg = Nicks_list[0];
2626 len = min(size - Nicks_matchpos - 3, strlen(msg));
2627 memcpy(&buffer[Nicks_matchpos], msg, len);
2628 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2629 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2630 buffer[len++] = ' ';
2637 Con_Printf("\n%i possible nicks:\n", n);
2638 Cmd_CompleteNicksPrint(n);
2640 Nicks_CutMatches(n);
2642 msg = Nicks_sanlist[0];
2643 len = min(size - Nicks_matchpos, strlen(msg));
2644 memcpy(&buffer[Nicks_matchpos], msg, len);
2645 buffer[Nicks_matchpos + len] = 0;
2647 return Nicks_matchpos + len;
2654 Con_CompleteCommandLine
2656 New function for tab-completion system
2657 Added by EvilTypeGuy
2658 Thanks to Fett erich@heintz.com
2660 Enhanced to tab-complete map names by [515]
2663 void Con_CompleteCommandLine (void)
2665 const char *cmd = "";
2667 const char **list[4] = {0, 0, 0, 0};
2670 int c, v, a, i, cmd_len, pos, k;
2671 int n; // nicks --blub
2672 const char *space, *patterns;
2674 //find what we want to complete
2679 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2685 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2686 key_line[key_linepos] = 0; //hide them
2688 space = strchr(key_line + 1, ' ');
2689 if(space && pos == (space - key_line) + 1)
2691 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2693 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2694 if(patterns && !*patterns)
2695 patterns = NULL; // get rid of the empty string
2697 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2701 if (GetMapList(s, t, sizeof(t)))
2703 // first move the cursor
2704 key_linepos += (int)strlen(t) - (int)strlen(s);
2706 // and now do the actual work
2708 strlcat(key_line, t, MAX_INPUTLINE);
2709 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2711 // and fix the cursor
2712 if(key_linepos > (int) strlen(key_line))
2713 key_linepos = (int) strlen(key_line);
2722 stringlist_t resultbuf, dirbuf;
2725 // // store completion patterns (space separated) for command foo in con_completion_foo
2726 // set con_completion_foo "foodata/*.foodefault *.foo"
2729 // Note: patterns with slash are always treated as absolute
2730 // patterns; patterns without slash search in the innermost
2731 // directory the user specified. There is no way to "complete into"
2732 // a directory as of now, as directories seem to be unknown to the
2736 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2737 // set con_completion_playdemo "*.dem"
2738 // set con_completion_play "*.wav *.ogg"
2740 // TODO somehow add support for directories; these shall complete
2741 // to their name + an appended slash.
2743 stringlistinit(&resultbuf);
2744 stringlistinit(&dirbuf);
2745 while(COM_ParseToken_Simple(&patterns, false, false))
2748 if(strchr(com_token, '/'))
2750 search = FS_Search(com_token, true, true);
2754 const char *slash = strrchr(s, '/');
2757 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2758 strlcat(t, com_token, sizeof(t));
2759 search = FS_Search(t, true, true);
2762 search = FS_Search(com_token, true, true);
2766 for(i = 0; i < search->numfilenames; ++i)
2767 if(!strncmp(search->filenames[i], s, strlen(s)))
2768 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2769 stringlistappend(&resultbuf, search->filenames[i]);
2770 FS_FreeSearch(search);
2774 // In any case, add directory names
2777 const char *slash = strrchr(s, '/');
2780 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2781 strlcat(t, "*", sizeof(t));
2782 search = FS_Search(t, true, true);
2785 search = FS_Search("*", true, true);
2788 for(i = 0; i < search->numfilenames; ++i)
2789 if(!strncmp(search->filenames[i], s, strlen(s)))
2790 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2791 stringlistappend(&dirbuf, search->filenames[i]);
2792 FS_FreeSearch(search);
2796 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2799 unsigned int matchchars;
2800 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2802 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2805 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2807 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2811 stringlistsort(&resultbuf); // dirbuf is already sorted
2812 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2813 for(i = 0; i < dirbuf.numstrings; ++i)
2815 Con_Printf("%s/\n", dirbuf.strings[i]);
2817 for(i = 0; i < resultbuf.numstrings; ++i)
2819 Con_Printf("%s\n", resultbuf.strings[i]);
2821 matchchars = sizeof(t) - 1;
2822 if(resultbuf.numstrings > 0)
2824 p = resultbuf.strings[0];
2825 q = resultbuf.strings[resultbuf.numstrings - 1];
2826 for(; *p && *p == *q; ++p, ++q);
2827 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2829 if(dirbuf.numstrings > 0)
2831 p = dirbuf.strings[0];
2832 q = dirbuf.strings[dirbuf.numstrings - 1];
2833 for(; *p && *p == *q; ++p, ++q);
2834 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2836 // now p points to the first non-equal character, or to the end
2837 // of resultbuf.strings[0]. We want to append the characters
2838 // from resultbuf.strings[0] to (not including) p as these are
2839 // the unique prefix
2840 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2843 // first move the cursor
2844 key_linepos += (int)strlen(t) - (int)strlen(s);
2846 // and now do the actual work
2848 strlcat(key_line, t, MAX_INPUTLINE);
2849 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2851 // and fix the cursor
2852 if(key_linepos > (int) strlen(key_line))
2853 key_linepos = (int) strlen(key_line);
2855 stringlistfreecontents(&resultbuf);
2856 stringlistfreecontents(&dirbuf);
2858 return; // bail out, when we complete for a command that wants a file name
2863 // Count number of possible matches and print them
2864 c = Cmd_CompleteCountPossible(s);
2867 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2868 Cmd_CompleteCommandPrint(s);
2870 v = Cvar_CompleteCountPossible(s);
2873 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2874 Cvar_CompleteCvarPrint(s);
2876 a = Cmd_CompleteAliasCountPossible(s);
2879 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2880 Cmd_CompleteAliasPrint(s);
2882 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2885 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2886 Cmd_CompleteNicksPrint(n);
2889 if (!(c + v + a + n)) // No possible matches
2892 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2897 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2899 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2901 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2903 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2905 for (cmd_len = (int)strlen(s);;cmd_len++)
2908 for (i = 0; i < 3; i++)
2910 for (l = list[i];*l;l++)
2911 if ((*l)[cmd_len] != cmd[cmd_len])
2913 // all possible matches share this character, so we continue...
2916 // if all matches ended at the same position, stop
2917 // (this means there is only one match)
2923 // prevent a buffer overrun by limiting cmd_len according to remaining space
2924 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2928 memcpy(&key_line[key_linepos], cmd, cmd_len);
2929 key_linepos += cmd_len;
2930 // if there is only one match, add a space after it
2931 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2934 { // was a nick, might have an offset, and needs colors ;) --blub
2935 key_linepos = pos - Nicks_offset[0];
2936 cmd_len = strlen(Nicks_list[0]);
2937 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2939 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2940 key_linepos += cmd_len;
2941 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2942 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2944 key_line[key_linepos++] = ' ';
2948 // use strlcat to avoid a buffer overrun
2949 key_line[key_linepos] = 0;
2950 strlcat(key_line, s2, sizeof(key_line));
2952 // free the command, cvar, and alias lists
2953 for (i = 0; i < 4; i++)
2955 Mem_Free((void *)list[i]);