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)
130 Mem_Free(buf->lines);
139 Notifies the console code about the current time
140 (and shifts back times of other entries when the time
144 void ConBuffer_FixTimes(conbuffer_t *buf)
147 if(buf->lines_count >= 1)
149 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
152 for(i = 0; i < buf->lines_count; ++i)
153 CONBUFFER_LINES(buf, i).addtime += diff;
162 Deletes the first line from the console history.
165 void ConBuffer_DeleteLine(conbuffer_t *buf)
167 if(buf->lines_count == 0)
170 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
175 ConBuffer_DeleteLastLine
177 Deletes the last line from the console history.
180 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
182 if(buf->lines_count == 0)
191 Checks if there is space for a line of the given length, and if yes, returns a
192 pointer to the start of such a space, and NULL otherwise.
195 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
197 if(len > buf->textsize)
199 if(buf->lines_count == 0)
203 char *firstline_start = buf->lines[buf->lines_first].start;
204 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
205 // the buffer is cyclic, so we first have two cases...
206 if(firstline_start < lastline_onepastend) // buffer is contiguous
209 if(len <= buf->text + buf->textsize - lastline_onepastend)
210 return lastline_onepastend;
212 else if(len <= firstline_start - buf->text)
217 else // buffer has a contiguous hole
219 if(len <= firstline_start - lastline_onepastend)
220 return lastline_onepastend;
231 Appends a given string as a new line to the console.
234 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
239 // developer_memory 1 during shutdown prints while conbuffer_t is being freed
243 ConBuffer_FixTimes(buf);
245 if(len >= buf->textsize)
248 // only display end of line.
249 line += len - buf->textsize + 1;
250 len = buf->textsize - 1;
252 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
253 ConBuffer_DeleteLine(buf);
254 memcpy(putpos, line, len);
258 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
260 p = &CONBUFFER_LINES_LAST(buf);
263 p->addtime = cl.time;
265 p->height = -1; // calculate when needed
268 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
272 start = buf->lines_count;
273 for(i = start - 1; i >= 0; --i)
275 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
277 if((l->mask & mask_must) != mask_must)
279 if(l->mask & mask_mustnot)
288 int Con_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
291 for(i = start + 1; i < buf->lines_count; ++i)
293 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
295 if((l->mask & mask_must) != mask_must)
297 if(l->mask & mask_mustnot)
306 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
308 static char copybuf[MAX_INPUTLINE];
309 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
310 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
311 strlcpy(copybuf, l->start, sz);
316 ==============================================================================
320 ==============================================================================
325 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
326 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"};
327 char log_dest_buffer[1400]; // UDP packet
328 size_t log_dest_buffer_pos;
329 unsigned int log_dest_buffer_appending;
330 char crt_log_file [MAX_OSPATH] = "";
331 qfile_t* logfile = NULL;
333 unsigned char* logqueue = NULL;
335 size_t logq_size = 0;
337 void Log_ConPrint (const char *msg);
344 static void Log_DestBuffer_Init(void)
346 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
347 log_dest_buffer_pos = 5;
355 void Log_DestBuffer_Flush(void)
357 lhnetaddress_t log_dest_addr;
358 lhnetsocket_t *log_dest_socket;
359 const char *s = log_dest_udp.string;
360 qboolean have_opened_temp_sockets = false;
361 if(s) if(log_dest_buffer_pos > 5)
363 ++log_dest_buffer_appending;
364 log_dest_buffer[log_dest_buffer_pos++] = 0;
366 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
368 have_opened_temp_sockets = true;
369 NetConn_OpenServerPorts(true);
372 while(COM_ParseToken_Console(&s))
373 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
375 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
377 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
379 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
382 if(have_opened_temp_sockets)
383 NetConn_CloseServerPorts();
384 --log_dest_buffer_appending;
386 log_dest_buffer_pos = 0;
394 const char* Log_Timestamp (const char *desc)
396 static char timestamp [128];
403 char timestring [64];
405 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
408 localtime_s (&crt_tm, &crt_time);
409 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
411 crt_tm = localtime (&crt_time);
412 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
416 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
418 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
431 if (logfile != NULL || log_file.string[0] == '\0')
434 logfile = FS_OpenRealFile(log_file.string, "a", false);
437 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
438 FS_Print (logfile, Log_Timestamp ("Log started"));
448 void Log_Close (void)
453 FS_Print (logfile, Log_Timestamp ("Log stopped"));
454 FS_Print (logfile, "\n");
458 crt_log_file[0] = '\0';
467 void Log_Start (void)
473 // Dump the contents of the log queue into the log file and free it
474 if (logqueue != NULL)
476 unsigned char *temp = logqueue;
481 FS_Write (logfile, temp, logq_ind);
482 if(*log_dest_udp.string)
484 for(pos = 0; pos < logq_ind; )
486 if(log_dest_buffer_pos == 0)
487 Log_DestBuffer_Init();
488 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
489 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
490 log_dest_buffer_pos += n;
491 Log_DestBuffer_Flush();
508 void Log_ConPrint (const char *msg)
510 static qboolean inprogress = false;
512 // don't allow feedback loops with memory error reports
517 // Until the host is completely initialized, we maintain a log queue
518 // to store the messages, since the log can't be started before
519 if (logqueue != NULL)
521 size_t remain = logq_size - logq_ind;
522 size_t len = strlen (msg);
524 // If we need to enlarge the log queue
527 size_t factor = ((logq_ind + len) / logq_size) + 1;
528 unsigned char* newqueue;
531 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
532 memcpy (newqueue, logqueue, logq_ind);
535 remain = logq_size - logq_ind;
537 memcpy (&logqueue[logq_ind], msg, len);
544 // Check if log_file has changed
545 if (strcmp (crt_log_file, log_file.string) != 0)
551 // If a log file is available
553 FS_Print (logfile, msg);
564 void Log_Printf (const char *logfilename, const char *fmt, ...)
568 file = FS_OpenRealFile(logfilename, "a", true);
573 va_start (argptr, fmt);
574 FS_VPrintf (file, fmt, argptr);
583 ==============================================================================
587 ==============================================================================
595 void Con_ToggleConsole_f (void)
597 // toggle the 'user wants console' bit
598 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
607 void Con_ClearNotify (void)
610 for(i = 0; i < CON_LINES_COUNT; ++i)
611 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
620 void Con_MessageMode_f (void)
622 key_dest = key_message;
623 chat_mode = 0; // "say"
634 void Con_MessageMode2_f (void)
636 key_dest = key_message;
637 chat_mode = 1; // "say_team"
647 void Con_CommandMode_f (void)
649 key_dest = key_message;
652 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
653 chat_bufferlen = strlen(chat_buffer);
655 chat_mode = -1; // command
663 void Con_CheckResize (void)
668 f = bound(1, con_textsize.value, 128);
669 if(f != con_textsize.value)
670 Cvar_SetValueQuick(&con_textsize, f);
671 width = (int)floor(vid_conwidth.value / con_textsize.value);
672 width = bound(1, width, con.textsize/4);
673 // FIXME uses con in a non abstracted way
675 if (width == con_linewidth)
678 con_linewidth = width;
680 for(i = 0; i < CON_LINES_COUNT; ++i)
681 CON_LINES(i).height = -1; // recalculate when next needed
687 //[515]: the simplest command ever
688 //LordHavoc: not so simple after I made it print usage...
689 static void Con_Maps_f (void)
693 Con_Printf("usage: maps [mapnameprefix]\n");
696 else if (Cmd_Argc() == 2)
697 GetMapList(Cmd_Argv(1), NULL, 0);
699 GetMapList("", NULL, 0);
702 void Con_ConDump_f (void)
708 Con_Printf("usage: condump <filename>\n");
711 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
714 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
717 for(i = 0; i < CON_LINES_COUNT; ++i)
719 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
720 FS_Write(file, "\n", 1);
725 void Con_Clear_f (void)
727 ConBuffer_Clear(&con);
738 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
740 // Allocate a log queue, this will be freed after configs are parsed
741 logq_size = MAX_INPUTLINE;
742 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
745 Cvar_RegisterVariable (&sys_colortranslation);
746 Cvar_RegisterVariable (&sys_specialcharactertranslation);
748 Cvar_RegisterVariable (&log_file);
749 Cvar_RegisterVariable (&log_dest_udp);
751 // support for the classic Quake option
752 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
753 if (COM_CheckParm ("-condebug") != 0)
754 Cvar_SetQuick (&log_file, "qconsole.log");
756 // register our cvars
757 Cvar_RegisterVariable (&con_chat);
758 Cvar_RegisterVariable (&con_chatpos);
759 Cvar_RegisterVariable (&con_chatrect_x);
760 Cvar_RegisterVariable (&con_chatrect_y);
761 Cvar_RegisterVariable (&con_chatrect);
762 Cvar_RegisterVariable (&con_chatsize);
763 Cvar_RegisterVariable (&con_chattime);
764 Cvar_RegisterVariable (&con_chatwidth);
765 Cvar_RegisterVariable (&con_notify);
766 Cvar_RegisterVariable (&con_notifyalign);
767 Cvar_RegisterVariable (&con_notifysize);
768 Cvar_RegisterVariable (&con_notifytime);
769 Cvar_RegisterVariable (&con_textsize);
770 Cvar_RegisterVariable (&con_chatsound);
773 Cvar_RegisterVariable (&con_nickcompletion);
774 Cvar_RegisterVariable (&con_nickcompletion_flags);
776 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
777 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
778 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
780 // register our commands
781 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
782 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
783 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
784 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
785 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
786 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
787 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
789 con_initialized = true;
790 Con_DPrint("Console initialized.\n");
793 void Con_Shutdown (void)
795 ConBuffer_Shutdown(&con);
802 Handles cursor positioning, line wrapping, etc
803 All console printing must go through this in order to be displayed
804 If no console is visible, the notify window will pop up.
807 void Con_PrintToHistory(const char *txt, int mask)
810 // \n goes to next line
811 // \r deletes current line and makes a new one
813 static int cr_pending = 0;
814 static char buf[CON_TEXTSIZE];
815 static int bufpos = 0;
817 if(!con.text) // FIXME uses a non-abstracted property of con
824 ConBuffer_DeleteLastLine(&con);
832 ConBuffer_AddLine(&con, buf, bufpos, mask);
837 ConBuffer_AddLine(&con, buf, bufpos, mask);
841 buf[bufpos++] = *txt;
842 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
844 ConBuffer_AddLine(&con, buf, bufpos, mask);
852 /*! The translation table between the graphical font and plain ASCII --KB */
853 static char qfont_table[256] = {
854 '\0', '#', '#', '#', '#', '.', '#', '#',
855 '#', 9, 10, '#', ' ', 13, '.', '.',
856 '[', ']', '0', '1', '2', '3', '4', '5',
857 '6', '7', '8', '9', '.', '<', '=', '>',
858 ' ', '!', '"', '#', '$', '%', '&', '\'',
859 '(', ')', '*', '+', ',', '-', '.', '/',
860 '0', '1', '2', '3', '4', '5', '6', '7',
861 '8', '9', ':', ';', '<', '=', '>', '?',
862 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
863 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
864 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
865 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
866 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
867 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
868 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
869 'x', 'y', 'z', '{', '|', '}', '~', '<',
871 '<', '=', '>', '#', '#', '.', '#', '#',
872 '#', '#', ' ', '#', ' ', '>', '.', '.',
873 '[', ']', '0', '1', '2', '3', '4', '5',
874 '6', '7', '8', '9', '.', '<', '=', '>',
875 ' ', '!', '"', '#', '$', '%', '&', '\'',
876 '(', ')', '*', '+', ',', '-', '.', '/',
877 '0', '1', '2', '3', '4', '5', '6', '7',
878 '8', '9', ':', ';', '<', '=', '>', '?',
879 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
880 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
881 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
882 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
883 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
884 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
885 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
886 'x', 'y', 'z', '{', '|', '}', '~', '<'
889 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol)
891 rcon_redirect_sock = sock;
892 rcon_redirect_dest = dest;
893 rcon_redirect_proquakeprotocol = proquakeprotocol;
894 if (rcon_redirect_proquakeprotocol)
896 // reserve space for the packet header
897 rcon_redirect_buffer[0] = 0;
898 rcon_redirect_buffer[1] = 0;
899 rcon_redirect_buffer[2] = 0;
900 rcon_redirect_buffer[3] = 0;
901 // this is a reply to a CCREQ_RCON
902 rcon_redirect_buffer[4] = CCREP_RCON;
905 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
906 rcon_redirect_bufferpos = 5;
909 void Con_Rcon_Redirect_Flush(void)
911 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
912 if (rcon_redirect_proquakeprotocol)
914 // update the length in the packet header
915 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
917 NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
918 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
919 rcon_redirect_bufferpos = 5;
920 rcon_redirect_proquakeprotocol = false;
923 void Con_Rcon_Redirect_End(void)
925 Con_Rcon_Redirect_Flush();
926 rcon_redirect_dest = NULL;
927 rcon_redirect_sock = NULL;
930 void Con_Rcon_Redirect_Abort(void)
932 rcon_redirect_dest = NULL;
933 rcon_redirect_sock = NULL;
941 /// Adds a character to the rcon buffer.
942 void Con_Rcon_AddChar(int c)
944 if(log_dest_buffer_appending)
946 ++log_dest_buffer_appending;
948 // if this print is in response to an rcon command, add the character
949 // to the rcon redirect buffer
951 if (rcon_redirect_dest)
953 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
954 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
955 Con_Rcon_Redirect_Flush();
957 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
959 if(log_dest_buffer_pos == 0)
960 Log_DestBuffer_Init();
961 log_dest_buffer[log_dest_buffer_pos++] = c;
962 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
963 Log_DestBuffer_Flush();
966 log_dest_buffer_pos = 0;
968 --log_dest_buffer_appending;
972 * Convert an RGB color to its nearest quake color.
973 * I'll cheat on this a bit by translating the colors to HSV first,
974 * S and V decide if it's black or white, otherwise, H will decide the
976 * @param _r Red (0-255)
977 * @param _g Green (0-255)
978 * @param _b Blue (0-255)
979 * @return A quake color character.
981 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
983 float r = ((float)_r)/255.0;
984 float g = ((float)_g)/255.0;
985 float b = ((float)_b)/255.0;
986 float min = min(r, min(g, b));
987 float max = max(r, max(g, b));
989 int h; ///< Hue angle [0,360]
990 float s; ///< Saturation [0,1]
991 float v = max; ///< In HSV v == max [0,1]
998 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1001 // If the value is less than half, return a black color code.
1002 // Otherwise return a white one.
1008 // Let's get the hue angle to define some colors:
1012 h = (int)(60.0 * (g-b)/(max-min))%360;
1014 h = (int)(60.0 * (b-r)/(max-min) + 120);
1015 else // if(max == b) redundant check
1016 h = (int)(60.0 * (r-g)/(max-min) + 240);
1018 if(h < 36) // *red* to orange
1020 else if(h < 80) // orange over *yellow* to evilish-bright-green
1022 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1024 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1026 else if(h < 270) // darkish blue over *dark blue* to cool purple
1028 else if(h < 330) // cool purple over *purple* to ugly swiny red
1030 else // ugly red to red closes the circly
1039 extern cvar_t timestamps;
1040 extern cvar_t timeformat;
1041 extern qboolean sys_nostdout;
1042 void Con_MaskPrint(int additionalmask, const char *msg)
1044 static int mask = 0;
1045 static int index = 0;
1046 static char line[MAX_INPUTLINE];
1050 Con_Rcon_AddChar(*msg);
1052 mask |= additionalmask;
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 // append the character
1099 line[index++] = *msg;
1100 // if this is a newline character, we have a complete line to print
1101 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1103 // terminate the line
1107 // send to scrollable buffer
1108 if (con_initialized && cls.state != ca_dedicated)
1110 Con_PrintToHistory(line, mask);
1113 // send to terminal or dedicated server window
1117 if(sys_specialcharactertranslation.integer)
1119 for (p = (unsigned char *) line;*p; p++)
1120 *p = qfont_table[*p];
1123 if(sys_colortranslation.integer == 1) // ANSI
1125 static char printline[MAX_INPUTLINE * 4 + 3];
1126 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1127 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1132 for(in = line, out = printline; *in; ++in)
1136 case STRING_COLOR_TAG:
1137 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1139 char r = tolower(in[2]);
1140 char g = tolower(in[3]);
1141 char b = tolower(in[4]);
1142 // it's a hex digit already, so the else part needs no check --blub
1143 if(isdigit(r)) r -= '0';
1145 if(isdigit(g)) g -= '0';
1147 if(isdigit(b)) b -= '0';
1150 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1151 in += 3; // 3 only, the switch down there does the fourth
1158 case STRING_COLOR_TAG:
1160 *out++ = STRING_COLOR_TAG;
1166 if(lastcolor == 0) break; else lastcolor = 0;
1167 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1172 if(lastcolor == 1) break; else lastcolor = 1;
1173 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1178 if(lastcolor == 2) break; else lastcolor = 2;
1179 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1184 if(lastcolor == 3) break; else lastcolor = 3;
1185 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1190 if(lastcolor == 4) break; else lastcolor = 4;
1191 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1196 if(lastcolor == 5) break; else lastcolor = 5;
1197 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1202 if(lastcolor == 6) break; else lastcolor = 6;
1203 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1208 // bold normal color
1210 if(lastcolor == 8) break; else lastcolor = 8;
1211 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1214 *out++ = STRING_COLOR_TAG;
1221 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1238 Sys_PrintToTerminal(printline);
1240 else if(sys_colortranslation.integer == 2) // Quake
1242 Sys_PrintToTerminal(line);
1246 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1249 for(in = line, out = printline; *in; ++in)
1253 case STRING_COLOR_TAG:
1256 case STRING_COLOR_RGB_TAG_CHAR:
1257 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1262 *out++ = STRING_COLOR_TAG;
1263 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1266 case STRING_COLOR_TAG:
1268 *out++ = STRING_COLOR_TAG;
1283 *out++ = STRING_COLOR_TAG;
1293 Sys_PrintToTerminal(printline);
1296 // empty the line buffer
1307 void Con_MaskPrintf(int mask, const char *fmt, ...)
1310 char msg[MAX_INPUTLINE];
1312 va_start(argptr,fmt);
1313 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1316 Con_MaskPrint(mask, msg);
1324 void Con_Print(const char *msg)
1326 Con_MaskPrint(CON_MASK_PRINT, msg);
1334 void Con_Printf(const char *fmt, ...)
1337 char msg[MAX_INPUTLINE];
1339 va_start(argptr,fmt);
1340 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1343 Con_MaskPrint(CON_MASK_PRINT, msg);
1351 void Con_DPrint(const char *msg)
1353 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1356 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1364 void Con_DPrintf(const char *fmt, ...)
1367 char msg[MAX_INPUTLINE];
1369 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1372 va_start(argptr,fmt);
1373 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1376 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1381 ==============================================================================
1385 ==============================================================================
1392 The input line scrolls horizontally if typing goes beyond the right edge
1394 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1397 extern cvar_t r_font_disable_freetype;
1398 void Con_DrawInput (void)
1402 char editlinecopy[MAX_INPUTLINE+1], *text;
1407 if (!key_consoleactive)
1408 return; // don't draw anything
1410 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1411 text = editlinecopy;
1413 // Advanced Console Editing by Radix radix@planetquake.com
1414 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1415 // use strlen of edit_line instead of key_linepos to allow editing
1416 // of early characters w/o erasing
1418 y = (int)strlen(text);
1420 // append enoug nul-bytes to cover the utf8-versions of the cursor too
1421 for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i)
1424 // add the cursor frame
1425 if (r_font_disable_freetype.integer)
1427 // this code is freetype incompatible!
1428 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1430 if (!utf8_enable.integer)
1431 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1432 else if (y + 3 < (int)sizeof(editlinecopy)-1)
1434 int ofs = u8_bytelen(text + key_linepos, 1);
1437 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1441 memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1442 memcpy(text + key_linepos, curbuf, len);
1445 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1449 // text[key_linepos + 1] = 0;
1451 len_out = key_linepos;
1453 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1454 x = vid_conwidth.value * 0.95 - xo; // scroll
1459 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 );
1461 // add a cursor on top of this (when using freetype)
1462 if (!r_font_disable_freetype.integer)
1464 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1466 if (!utf8_enable.integer)
1468 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1475 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1476 memcpy(text, curbuf, len);
1479 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);
1484 // key_line[key_linepos] = 0;
1490 float alignment; // 0 = left, 0.5 = center, 1 = right
1496 const char *continuationString;
1499 int colorindex; // init to -1
1503 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1505 con_text_info_t *ti = (con_text_info_t *) passthrough;
1508 ti->colorindex = -1;
1509 return ti->fontsize * ti->font->maxwidth;
1512 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1513 else if(maxWidth == -1)
1514 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1517 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1518 // Note: this is NOT a Con_Printf, as it could print recursively
1523 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1529 (void) isContinuation;
1533 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1535 con_text_info_t *ti = (con_text_info_t *) passthrough;
1537 if(ti->y < ti->ymin - 0.001)
1539 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1543 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1544 if(isContinuation && *ti->continuationString)
1545 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);
1547 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);
1550 ti->y += ti->fontsize;
1554 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)
1558 int maxlines = (int) floor(height / fontsize + 0.01f);
1561 int continuationWidth = 0;
1563 double t = cl.time; // saved so it won't change
1566 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1567 ti.fontsize = fontsize;
1568 ti.alignment = alignment_x;
1571 ti.ymax = y + height;
1572 ti.continuationString = continuationString;
1575 Con_WordWidthFunc(&ti, NULL, &l, -1);
1576 l = strlen(continuationString);
1577 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1579 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1580 startidx = CON_LINES_COUNT;
1581 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1583 con_lineinfo_t *l = &CON_LINES(i);
1586 if((l->mask & mask_must) != mask_must)
1588 if(l->mask & mask_mustnot)
1590 if(maxage && (l->addtime < t - maxage))
1594 // Calculate its actual height...
1595 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1596 if(lines + mylines >= maxlines)
1598 nskip = lines + mylines - maxlines;
1607 // then center according to the calculated amount of lines...
1609 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1611 // then actually draw
1612 for(i = startidx; i < CON_LINES_COUNT; ++i)
1614 con_lineinfo_t *l = &CON_LINES(i);
1616 if((l->mask & mask_must) != mask_must)
1618 if(l->mask & mask_mustnot)
1620 if(maxage && (l->addtime < t - maxage))
1623 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1633 Draws the last few lines of output transparently over the game top
1636 void Con_DrawNotify (void)
1639 float chatstart, notifystart, inputsize, height;
1641 char temptext[MAX_INPUTLINE];
1645 ConBuffer_FixTimes(&con);
1647 numChatlines = con_chat.integer;
1649 chatpos = con_chatpos.integer;
1651 if (con_notify.integer < 0)
1652 Cvar_SetValueQuick(&con_notify, 0);
1653 if (gamemode == GAME_TRANSFUSION)
1654 v = 8; // vertical offset
1658 // GAME_NEXUIZ: center, otherwise left justify
1659 align = con_notifyalign.value;
1660 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1662 if(gamemode == GAME_NEXUIZ)
1666 if(numChatlines || !con_chatrect.integer)
1670 // first chat, input line, then notify
1672 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1674 else if(chatpos > 0)
1676 // first notify, then (chatpos-1) empty lines, then chat, then input
1678 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1680 else // if(chatpos < 0)
1682 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1684 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1689 // just notify and input
1691 chatstart = 0; // shut off gcc warning
1694 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, "");
1696 if(con_chatrect.integer)
1698 x = con_chatrect_x.value * vid_conwidth.value;
1699 v = con_chatrect_y.value * vid_conheight.value;
1704 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1707 height = numChatlines * con_chatsize.value;
1711 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
1714 if (key_dest == key_message)
1716 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1717 int colorindex = -1;
1719 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL);
1721 // LordHavoc: speedup, and other improvements
1723 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1725 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1727 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1730 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1731 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1733 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1741 Returns the height of a given console line; calculates it if necessary.
1744 int Con_LineHeight(int lineno)
1746 con_lineinfo_t *li = &CON_LINES(lineno);
1747 if(li->height == -1)
1749 float width = vid_conwidth.value;
1751 con_lineinfo_t *li = &CON_LINES(lineno);
1752 ti.fontsize = con_textsize.value;
1753 ti.font = FONT_CONSOLE;
1754 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1763 Draws a line of the console; returns its height in lines.
1764 If alpha is 0, the line is not drawn, but still wrapped and its height
1768 int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1770 float width = vid_conwidth.value;
1772 con_lineinfo_t *li = &CON_LINES(lineno);
1774 if((li->mask & mask_must) != mask_must)
1776 if((li->mask & mask_mustnot) != 0)
1779 ti.continuationString = "";
1781 ti.fontsize = con_textsize.value;
1782 ti.font = FONT_CONSOLE;
1784 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1789 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1796 Calculates the last visible line index and how much to show of it based on
1800 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1805 if(con_backscroll < 0)
1810 // now count until we saw con_backscroll actual lines
1811 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1812 if((CON_LINES(i).mask & mask_must) == mask_must)
1813 if((CON_LINES(i).mask & mask_mustnot) == 0)
1815 int h = Con_LineHeight(i);
1817 // line is the last visible line?
1819 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1821 *limitlast = lines_seen + h - con_backscroll;
1828 // if we get here, no line was on screen - scroll so that one line is
1830 con_backscroll = lines_seen - 1;
1838 Draws the console with the solid background
1839 The typing input line at the bottom should only be drawn if typing is allowed
1842 void Con_DrawConsole (int lines)
1844 float alpha, alpha0;
1847 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1848 cachepic_t *conbackpic;
1853 if (con_backscroll < 0)
1856 con_vislines = lines;
1858 r_draw2d_force = true;
1860 // draw the background
1861 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
1862 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
1864 sx = scr_conscroll_x.value;
1865 sy = scr_conscroll_y.value;
1866 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL;
1867 sx *= realtime; sy *= realtime;
1868 sx -= floor(sx); sy -= floor(sy);
1869 if (conbackpic && conbackpic->tex != r_texture_notexture)
1870 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1871 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1872 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1873 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1874 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1877 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
1879 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
1881 sx = scr_conscroll2_x.value;
1882 sy = scr_conscroll2_y.value;
1883 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1884 sx *= realtime; sy *= realtime;
1885 sx -= floor(sx); sy -= floor(sy);
1886 if(conbackpic && conbackpic->tex != r_texture_notexture)
1887 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1888 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1889 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1890 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1891 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1894 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
1896 sx = scr_conscroll3_x.value;
1897 sy = scr_conscroll3_y.value;
1898 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1899 sx *= realtime; sy *= realtime;
1900 sx -= floor(sx); sy -= floor(sy);
1901 if(conbackpic && conbackpic->tex != r_texture_notexture)
1902 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1903 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1904 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1905 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1906 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1909 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);
1915 int count = CON_LINES_COUNT;
1916 float ymax = con_vislines - 2 * con_textsize.value;
1917 float y = ymax + con_textsize.value * con_backscroll;
1918 for (i = 0;i < count && y >= 0;i++)
1919 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
1920 // fix any excessive scrollback for the next frame
1921 if (i >= count && y >= 0)
1923 con_backscroll -= (int)(y / con_textsize.value);
1924 if (con_backscroll < 0)
1929 if(CON_LINES_COUNT > 0)
1931 int i, last, limitlast;
1933 float ymax = con_vislines - 2 * con_textsize.value;
1934 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1935 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1936 y = ymax - con_textsize.value;
1939 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1944 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
1946 break; // top of console buffer
1948 break; // top of console window
1955 // draw the input prompt, user text, and cursor if desired
1958 r_draw2d_force = false;
1965 Prints not only map filename, but also
1966 its format (q1/q2/q3/hl) and even its message
1968 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1969 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1970 //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
1971 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1972 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1976 int i, k, max, p, o, min;
1979 unsigned char buf[1024];
1981 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1982 t = FS_Search(message, 1, true);
1985 if (t->numfilenames > 1)
1986 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1987 len = (unsigned char *)Z_Malloc(t->numfilenames);
1989 for(max=i=0;i<t->numfilenames;i++)
1991 k = (int)strlen(t->filenames[i]);
2001 for(i=0;i<t->numfilenames;i++)
2003 int lumpofs = 0, lumplen = 0;
2004 char *entities = NULL;
2005 const char *data = NULL;
2007 char entfilename[MAX_QPATH];
2008 strlcpy(message, "^1**ERROR**^7", sizeof(message));
2010 f = FS_OpenVirtualFile(t->filenames[i], true);
2013 memset(buf, 0, 1024);
2014 FS_Read(f, buf, 1024);
2015 if (!memcmp(buf, "IBSP", 4))
2017 p = LittleLong(((int *)buf)[1]);
2018 if (p == Q3BSPVERSION)
2020 q3dheader_t *header = (q3dheader_t *)buf;
2021 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2022 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2024 else if (p == Q2BSPVERSION)
2026 q2dheader_t *header = (q2dheader_t *)buf;
2027 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2028 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2031 else if((p = BuffLittleLong(buf)) == BSPVERSION || p == 30)
2033 dheader_t *header = (dheader_t *)buf;
2034 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
2035 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
2039 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2040 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2041 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2042 if (!entities && lumplen >= 10)
2044 FS_Seek(f, lumpofs, SEEK_SET);
2045 entities = (char *)Z_Malloc(lumplen + 1);
2046 FS_Read(f, entities, lumplen);
2050 // if there are entities to parse, a missing message key just
2051 // means there is no title, so clear the message string now
2057 if (!COM_ParseToken_Simple(&data, false, false))
2059 if (com_token[0] == '{')
2061 if (com_token[0] == '}')
2063 // skip leading whitespace
2064 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2065 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2066 keyname[l] = com_token[k+l];
2068 if (!COM_ParseToken_Simple(&data, false, false))
2070 if (developer_extra.integer)
2071 Con_DPrintf("key: %s %s\n", keyname, com_token);
2072 if (!strcmp(keyname, "message"))
2074 // get the message contents
2075 strlcpy(message, com_token, sizeof(message));
2085 *(t->filenames[i]+len[i]+5) = 0;
2088 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
2089 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
2090 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
2091 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
2092 default: strlcpy((char *)buf, "??", sizeof(buf));break;
2094 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
2099 k = *(t->filenames[0]+5+p);
2102 for(i=1;i<t->numfilenames;i++)
2103 if(*(t->filenames[i]+5+p) != k)
2107 if(p > o && completedname && completednamebufferlength > 0)
2109 memset(completedname, 0, completednamebufferlength);
2110 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2120 New function for tab-completion system
2121 Added by EvilTypeGuy
2122 MEGA Thanks to Taniwha
2125 void Con_DisplayList(const char **list)
2127 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2128 const char **walk = list;
2131 len = (int)strlen(*walk);
2139 len = (int)strlen(*list);
2140 if (pos + maxlen >= width) {
2146 for (i = 0; i < (maxlen - len); i++)
2158 SanitizeString strips color tags from the string in
2159 and writes the result on string out
2161 void SanitizeString(char *in, char *out)
2165 if(*in == STRING_COLOR_TAG)
2170 out[0] = STRING_COLOR_TAG;
2174 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2181 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2184 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2186 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2193 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2198 else if (*in != STRING_COLOR_TAG)
2201 *out = qfont_table[*(unsigned char*)in];
2208 // Now it becomes TRICKY :D --blub
2209 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2210 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2211 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2212 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
2213 static int Nicks_matchpos;
2215 // co against <<:BLASTER:>> is true!?
2216 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2220 if(tolower(*a) == tolower(*b))
2234 return (*a < *b) ? -1 : 1;
2238 return (*a < *b) ? -1 : 1;
2242 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2245 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2247 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2248 return Nicks_strncasecmp_nospaces(a, b, a_len);
2249 return strncasecmp(a, b, a_len);
2252 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2254 // ignore non alphanumerics of B
2255 // if A contains a non-alphanumeric, B must contain it as well though!
2258 qboolean alnum_a, alnum_b;
2260 if(tolower(*a) == tolower(*b))
2262 if(*a == 0) // end of both strings, they're equal
2269 // not equal, end of one string?
2274 // ignore non alphanumerics
2275 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2276 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2277 if(!alnum_a) // b must contain this
2278 return (*a < *b) ? -1 : 1;
2281 // otherwise, both are alnum, they're just not equal, return the appropriate number
2283 return (*a < *b) ? -1 : 1;
2289 /* Nicks_CompleteCountPossible
2291 Count the number of possible nicks to complete
2293 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2301 if(!con_nickcompletion.integer)
2304 // changed that to 1
2305 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2308 for(i = 0; i < cl.maxclients; ++i)
2311 if(!cl.scores[p].name[0])
2314 SanitizeString(cl.scores[p].name, name);
2315 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2321 spos = pos - 1; // no need for a minimum of characters :)
2325 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2327 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2328 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2334 if(isCon && spos == 0)
2336 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2342 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2343 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2345 // the sanitized list
2346 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2349 Nicks_matchpos = match;
2352 Nicks_offset[count] = s - (&line[match]);
2353 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2360 void Cmd_CompleteNicksPrint(int count)
2363 for(i = 0; i < count; ++i)
2364 Con_Printf("%s\n", Nicks_list[i]);
2367 void Nicks_CutMatchesNormal(int count)
2369 // cut match 0 down to the longest possible completion
2372 c = strlen(Nicks_sanlist[0]) - 1;
2373 for(i = 1; i < count; ++i)
2375 l = strlen(Nicks_sanlist[i]) - 1;
2379 for(l = 0; l <= c; ++l)
2380 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2386 Nicks_sanlist[0][c+1] = 0;
2387 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2390 unsigned int Nicks_strcleanlen(const char *s)
2395 if( (*s >= 'a' && *s <= 'z') ||
2396 (*s >= 'A' && *s <= 'Z') ||
2397 (*s >= '0' && *s <= '9') ||
2405 void Nicks_CutMatchesAlphaNumeric(int count)
2407 // cut match 0 down to the longest possible completion
2410 char tempstr[sizeof(Nicks_sanlist[0])];
2412 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2414 c = strlen(Nicks_sanlist[0]);
2415 for(i = 0, l = 0; i < (int)c; ++i)
2417 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2418 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2419 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2421 tempstr[l++] = Nicks_sanlist[0][i];
2426 for(i = 1; i < count; ++i)
2429 b = Nicks_sanlist[i];
2439 if(tolower(*a) == tolower(*b))
2445 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2447 // b is alnum, so cut
2454 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2455 Nicks_CutMatchesNormal(count);
2456 //if(!Nicks_sanlist[0][0])
2457 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2459 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2460 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2464 void Nicks_CutMatchesNoSpaces(int count)
2466 // cut match 0 down to the longest possible completion
2469 char tempstr[sizeof(Nicks_sanlist[0])];
2472 c = strlen(Nicks_sanlist[0]);
2473 for(i = 0, l = 0; i < (int)c; ++i)
2475 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2477 tempstr[l++] = Nicks_sanlist[0][i];
2482 for(i = 1; i < count; ++i)
2485 b = Nicks_sanlist[i];
2495 if(tolower(*a) == tolower(*b))
2509 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2510 Nicks_CutMatchesNormal(count);
2511 //if(!Nicks_sanlist[0][0])
2512 //Con_Printf("TS: %s\n", tempstr);
2513 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2515 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2516 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2520 void Nicks_CutMatches(int count)
2522 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2523 Nicks_CutMatchesAlphaNumeric(count);
2524 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2525 Nicks_CutMatchesNoSpaces(count);
2527 Nicks_CutMatchesNormal(count);
2530 const char **Nicks_CompleteBuildList(int count)
2534 // the list is freed by Con_CompleteCommandLine, so create a char**
2535 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2537 for(; bpos < count; ++bpos)
2538 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2540 Nicks_CutMatches(count);
2548 Restores the previous used color, after the autocompleted name.
2550 int Nicks_AddLastColor(char *buffer, int pos)
2552 qboolean quote_added = false;
2554 int color = STRING_COLOR_DEFAULT + '0';
2555 char r = 0, g = 0, b = 0;
2557 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2559 // we'll have to add a quote :)
2560 buffer[pos++] = '\"';
2564 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2566 // add color when no quote was added, or when flags &4?
2568 for(match = Nicks_matchpos-1; match >= 0; --match)
2570 if(buffer[match] == STRING_COLOR_TAG)
2572 if( isdigit(buffer[match+1]) )
2574 color = buffer[match+1];
2577 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2579 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2581 r = buffer[match+2];
2582 g = buffer[match+3];
2583 b = buffer[match+4];
2592 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2594 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2595 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2598 buffer[pos++] = STRING_COLOR_TAG;
2601 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2607 buffer[pos++] = color;
2612 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2615 /*if(!con_nickcompletion.integer)
2616 return; is tested in Nicks_CompletionCountPossible */
2617 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2623 msg = Nicks_list[0];
2624 len = min(size - Nicks_matchpos - 3, strlen(msg));
2625 memcpy(&buffer[Nicks_matchpos], msg, len);
2626 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2627 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2628 buffer[len++] = ' ';
2635 Con_Printf("\n%i possible nicks:\n", n);
2636 Cmd_CompleteNicksPrint(n);
2638 Nicks_CutMatches(n);
2640 msg = Nicks_sanlist[0];
2641 len = min(size - Nicks_matchpos, strlen(msg));
2642 memcpy(&buffer[Nicks_matchpos], msg, len);
2643 buffer[Nicks_matchpos + len] = 0;
2645 return Nicks_matchpos + len;
2652 Con_CompleteCommandLine
2654 New function for tab-completion system
2655 Added by EvilTypeGuy
2656 Thanks to Fett erich@heintz.com
2658 Enhanced to tab-complete map names by [515]
2661 void Con_CompleteCommandLine (void)
2663 const char *cmd = "";
2665 const char **list[4] = {0, 0, 0, 0};
2668 int c, v, a, i, cmd_len, pos, k;
2669 int n; // nicks --blub
2670 const char *space, *patterns;
2672 //find what we want to complete
2677 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2683 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2684 key_line[key_linepos] = 0; //hide them
2686 space = strchr(key_line + 1, ' ');
2687 if(space && pos == (space - key_line) + 1)
2689 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2691 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2692 if(patterns && !*patterns)
2693 patterns = NULL; // get rid of the empty string
2695 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2699 if (GetMapList(s, t, sizeof(t)))
2701 // first move the cursor
2702 key_linepos += (int)strlen(t) - (int)strlen(s);
2704 // and now do the actual work
2706 strlcat(key_line, t, MAX_INPUTLINE);
2707 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2709 // and fix the cursor
2710 if(key_linepos > (int) strlen(key_line))
2711 key_linepos = (int) strlen(key_line);
2720 stringlist_t resultbuf, dirbuf;
2723 // // store completion patterns (space separated) for command foo in con_completion_foo
2724 // set con_completion_foo "foodata/*.foodefault *.foo"
2727 // Note: patterns with slash are always treated as absolute
2728 // patterns; patterns without slash search in the innermost
2729 // directory the user specified. There is no way to "complete into"
2730 // a directory as of now, as directories seem to be unknown to the
2734 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2735 // set con_completion_playdemo "*.dem"
2736 // set con_completion_play "*.wav *.ogg"
2738 // TODO somehow add support for directories; these shall complete
2739 // to their name + an appended slash.
2741 stringlistinit(&resultbuf);
2742 stringlistinit(&dirbuf);
2743 while(COM_ParseToken_Simple(&patterns, false, false))
2746 if(strchr(com_token, '/'))
2748 search = FS_Search(com_token, true, true);
2752 const char *slash = strrchr(s, '/');
2755 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2756 strlcat(t, com_token, sizeof(t));
2757 search = FS_Search(t, true, true);
2760 search = FS_Search(com_token, true, true);
2764 for(i = 0; i < search->numfilenames; ++i)
2765 if(!strncmp(search->filenames[i], s, strlen(s)))
2766 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2767 stringlistappend(&resultbuf, search->filenames[i]);
2768 FS_FreeSearch(search);
2772 // In any case, add directory names
2775 const char *slash = strrchr(s, '/');
2778 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2779 strlcat(t, "*", sizeof(t));
2780 search = FS_Search(t, true, true);
2783 search = FS_Search("*", true, true);
2786 for(i = 0; i < search->numfilenames; ++i)
2787 if(!strncmp(search->filenames[i], s, strlen(s)))
2788 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2789 stringlistappend(&dirbuf, search->filenames[i]);
2790 FS_FreeSearch(search);
2794 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2797 unsigned int matchchars;
2798 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2800 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2803 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2805 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2809 stringlistsort(&resultbuf); // dirbuf is already sorted
2810 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2811 for(i = 0; i < dirbuf.numstrings; ++i)
2813 Con_Printf("%s/\n", dirbuf.strings[i]);
2815 for(i = 0; i < resultbuf.numstrings; ++i)
2817 Con_Printf("%s\n", resultbuf.strings[i]);
2819 matchchars = sizeof(t) - 1;
2820 if(resultbuf.numstrings > 0)
2822 p = resultbuf.strings[0];
2823 q = resultbuf.strings[resultbuf.numstrings - 1];
2824 for(; *p && *p == *q; ++p, ++q);
2825 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2827 if(dirbuf.numstrings > 0)
2829 p = dirbuf.strings[0];
2830 q = dirbuf.strings[dirbuf.numstrings - 1];
2831 for(; *p && *p == *q; ++p, ++q);
2832 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2834 // now p points to the first non-equal character, or to the end
2835 // of resultbuf.strings[0]. We want to append the characters
2836 // from resultbuf.strings[0] to (not including) p as these are
2837 // the unique prefix
2838 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2841 // first move the cursor
2842 key_linepos += (int)strlen(t) - (int)strlen(s);
2844 // and now do the actual work
2846 strlcat(key_line, t, MAX_INPUTLINE);
2847 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2849 // and fix the cursor
2850 if(key_linepos > (int) strlen(key_line))
2851 key_linepos = (int) strlen(key_line);
2853 stringlistfreecontents(&resultbuf);
2854 stringlistfreecontents(&dirbuf);
2856 return; // bail out, when we complete for a command that wants a file name
2861 // Count number of possible matches and print them
2862 c = Cmd_CompleteCountPossible(s);
2865 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2866 Cmd_CompleteCommandPrint(s);
2868 v = Cvar_CompleteCountPossible(s);
2871 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2872 Cvar_CompleteCvarPrint(s);
2874 a = Cmd_CompleteAliasCountPossible(s);
2877 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2878 Cmd_CompleteAliasPrint(s);
2880 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2883 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2884 Cmd_CompleteNicksPrint(n);
2887 if (!(c + v + a + n)) // No possible matches
2890 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2895 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2897 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2899 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2901 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2903 for (cmd_len = (int)strlen(s);;cmd_len++)
2906 for (i = 0; i < 3; i++)
2908 for (l = list[i];*l;l++)
2909 if ((*l)[cmd_len] != cmd[cmd_len])
2911 // all possible matches share this character, so we continue...
2914 // if all matches ended at the same position, stop
2915 // (this means there is only one match)
2921 // prevent a buffer overrun by limiting cmd_len according to remaining space
2922 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2926 memcpy(&key_line[key_linepos], cmd, cmd_len);
2927 key_linepos += cmd_len;
2928 // if there is only one match, add a space after it
2929 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2932 { // was a nick, might have an offset, and needs colors ;) --blub
2933 key_linepos = pos - Nicks_offset[0];
2934 cmd_len = strlen(Nicks_list[0]);
2935 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2937 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2938 key_linepos += cmd_len;
2939 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2940 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2942 key_line[key_linepos++] = ' ';
2946 // use strlcat to avoid a buffer overrun
2947 key_line[key_linepos] = 0;
2948 strlcat(key_line, s2, sizeof(key_line));
2950 // free the command, cvar, and alias lists
2951 for (i = 0; i < 4; i++)
2953 Mem_Free((void *)list[i]);