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"
628 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
629 chat_bufferlen = strlen(chat_buffer);
639 void Con_MessageMode2_f (void)
641 key_dest = key_message;
642 chat_mode = 1; // "say_team"
645 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
646 chat_bufferlen = strlen(chat_buffer);
655 void Con_CommandMode_f (void)
657 key_dest = key_message;
660 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
661 chat_bufferlen = strlen(chat_buffer);
663 chat_mode = -1; // command
671 void Con_CheckResize (void)
676 f = bound(1, con_textsize.value, 128);
677 if(f != con_textsize.value)
678 Cvar_SetValueQuick(&con_textsize, f);
679 width = (int)floor(vid_conwidth.value / con_textsize.value);
680 width = bound(1, width, con.textsize/4);
681 // FIXME uses con in a non abstracted way
683 if (width == con_linewidth)
686 con_linewidth = width;
688 for(i = 0; i < CON_LINES_COUNT; ++i)
689 CON_LINES(i).height = -1; // recalculate when next needed
695 //[515]: the simplest command ever
696 //LordHavoc: not so simple after I made it print usage...
697 static void Con_Maps_f (void)
701 Con_Printf("usage: maps [mapnameprefix]\n");
704 else if (Cmd_Argc() == 2)
705 GetMapList(Cmd_Argv(1), NULL, 0);
707 GetMapList("", NULL, 0);
710 void Con_ConDump_f (void)
716 Con_Printf("usage: condump <filename>\n");
719 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
722 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
725 for(i = 0; i < CON_LINES_COUNT; ++i)
727 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
728 FS_Write(file, "\n", 1);
733 void Con_Clear_f (void)
735 ConBuffer_Clear(&con);
746 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
748 // Allocate a log queue, this will be freed after configs are parsed
749 logq_size = MAX_INPUTLINE;
750 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
753 Cvar_RegisterVariable (&sys_colortranslation);
754 Cvar_RegisterVariable (&sys_specialcharactertranslation);
756 Cvar_RegisterVariable (&log_file);
757 Cvar_RegisterVariable (&log_dest_udp);
759 // support for the classic Quake option
760 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
761 if (COM_CheckParm ("-condebug") != 0)
762 Cvar_SetQuick (&log_file, "qconsole.log");
764 // register our cvars
765 Cvar_RegisterVariable (&con_chat);
766 Cvar_RegisterVariable (&con_chatpos);
767 Cvar_RegisterVariable (&con_chatrect_x);
768 Cvar_RegisterVariable (&con_chatrect_y);
769 Cvar_RegisterVariable (&con_chatrect);
770 Cvar_RegisterVariable (&con_chatsize);
771 Cvar_RegisterVariable (&con_chattime);
772 Cvar_RegisterVariable (&con_chatwidth);
773 Cvar_RegisterVariable (&con_notify);
774 Cvar_RegisterVariable (&con_notifyalign);
775 Cvar_RegisterVariable (&con_notifysize);
776 Cvar_RegisterVariable (&con_notifytime);
777 Cvar_RegisterVariable (&con_textsize);
778 Cvar_RegisterVariable (&con_chatsound);
781 Cvar_RegisterVariable (&con_nickcompletion);
782 Cvar_RegisterVariable (&con_nickcompletion_flags);
784 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
785 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
786 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
788 // register our commands
789 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
790 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
791 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
792 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
793 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
794 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
795 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
797 con_initialized = true;
798 Con_DPrint("Console initialized.\n");
801 void Con_Shutdown (void)
803 ConBuffer_Shutdown(&con);
810 Handles cursor positioning, line wrapping, etc
811 All console printing must go through this in order to be displayed
812 If no console is visible, the notify window will pop up.
815 void Con_PrintToHistory(const char *txt, int mask)
818 // \n goes to next line
819 // \r deletes current line and makes a new one
821 static int cr_pending = 0;
822 static char buf[CON_TEXTSIZE];
823 static int bufpos = 0;
825 if(!con.text) // FIXME uses a non-abstracted property of con
832 ConBuffer_DeleteLastLine(&con);
840 ConBuffer_AddLine(&con, buf, bufpos, mask);
845 ConBuffer_AddLine(&con, buf, bufpos, mask);
849 buf[bufpos++] = *txt;
850 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
852 ConBuffer_AddLine(&con, buf, bufpos, mask);
860 /*! The translation table between the graphical font and plain ASCII --KB */
861 static char qfont_table[256] = {
862 '\0', '#', '#', '#', '#', '.', '#', '#',
863 '#', 9, 10, '#', ' ', 13, '.', '.',
864 '[', ']', '0', '1', '2', '3', '4', '5',
865 '6', '7', '8', '9', '.', '<', '=', '>',
866 ' ', '!', '"', '#', '$', '%', '&', '\'',
867 '(', ')', '*', '+', ',', '-', '.', '/',
868 '0', '1', '2', '3', '4', '5', '6', '7',
869 '8', '9', ':', ';', '<', '=', '>', '?',
870 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
871 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
872 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
873 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
874 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
875 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
876 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
877 'x', 'y', 'z', '{', '|', '}', '~', '<',
879 '<', '=', '>', '#', '#', '.', '#', '#',
880 '#', '#', ' ', '#', ' ', '>', '.', '.',
881 '[', ']', '0', '1', '2', '3', '4', '5',
882 '6', '7', '8', '9', '.', '<', '=', '>',
883 ' ', '!', '"', '#', '$', '%', '&', '\'',
884 '(', ')', '*', '+', ',', '-', '.', '/',
885 '0', '1', '2', '3', '4', '5', '6', '7',
886 '8', '9', ':', ';', '<', '=', '>', '?',
887 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
888 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
889 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
890 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
891 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
892 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
893 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
894 'x', 'y', 'z', '{', '|', '}', '~', '<'
897 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol)
899 rcon_redirect_sock = sock;
900 rcon_redirect_dest = dest;
901 rcon_redirect_proquakeprotocol = proquakeprotocol;
902 if (rcon_redirect_proquakeprotocol)
904 // reserve space for the packet header
905 rcon_redirect_buffer[0] = 0;
906 rcon_redirect_buffer[1] = 0;
907 rcon_redirect_buffer[2] = 0;
908 rcon_redirect_buffer[3] = 0;
909 // this is a reply to a CCREQ_RCON
910 rcon_redirect_buffer[4] = (char)CCREP_RCON;
913 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
914 rcon_redirect_bufferpos = 5;
917 void Con_Rcon_Redirect_Flush(void)
919 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
920 if (rcon_redirect_proquakeprotocol)
922 // update the length in the packet header
923 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
925 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
926 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
927 rcon_redirect_bufferpos = 5;
928 rcon_redirect_proquakeprotocol = false;
931 void Con_Rcon_Redirect_End(void)
933 Con_Rcon_Redirect_Flush();
934 rcon_redirect_dest = NULL;
935 rcon_redirect_sock = NULL;
938 void Con_Rcon_Redirect_Abort(void)
940 rcon_redirect_dest = NULL;
941 rcon_redirect_sock = NULL;
949 /// Adds a character to the rcon buffer.
950 void Con_Rcon_AddChar(int c)
952 if(log_dest_buffer_appending)
954 ++log_dest_buffer_appending;
956 // if this print is in response to an rcon command, add the character
957 // to the rcon redirect buffer
959 if (rcon_redirect_dest)
961 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
962 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
963 Con_Rcon_Redirect_Flush();
965 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
967 if(log_dest_buffer_pos == 0)
968 Log_DestBuffer_Init();
969 log_dest_buffer[log_dest_buffer_pos++] = c;
970 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
971 Log_DestBuffer_Flush();
974 log_dest_buffer_pos = 0;
976 --log_dest_buffer_appending;
980 * Convert an RGB color to its nearest quake color.
981 * I'll cheat on this a bit by translating the colors to HSV first,
982 * S and V decide if it's black or white, otherwise, H will decide the
984 * @param _r Red (0-255)
985 * @param _g Green (0-255)
986 * @param _b Blue (0-255)
987 * @return A quake color character.
989 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
991 float r = ((float)_r)/255.0;
992 float g = ((float)_g)/255.0;
993 float b = ((float)_b)/255.0;
994 float min = min(r, min(g, b));
995 float max = max(r, max(g, b));
997 int h; ///< Hue angle [0,360]
998 float s; ///< Saturation [0,1]
999 float v = max; ///< In HSV v == max [0,1]
1004 s = 1.0 - (min/max);
1006 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1009 // If the value is less than half, return a black color code.
1010 // Otherwise return a white one.
1016 // Let's get the hue angle to define some colors:
1020 h = (int)(60.0 * (g-b)/(max-min))%360;
1022 h = (int)(60.0 * (b-r)/(max-min) + 120);
1023 else // if(max == b) redundant check
1024 h = (int)(60.0 * (r-g)/(max-min) + 240);
1026 if(h < 36) // *red* to orange
1028 else if(h < 80) // orange over *yellow* to evilish-bright-green
1030 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1032 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1034 else if(h < 270) // darkish blue over *dark blue* to cool purple
1036 else if(h < 330) // cool purple over *purple* to ugly swiny red
1038 else // ugly red to red closes the circly
1047 extern cvar_t timestamps;
1048 extern cvar_t timeformat;
1049 extern qboolean sys_nostdout;
1050 void Con_MaskPrint(int additionalmask, const char *msg)
1052 static int mask = 0;
1053 static int index = 0;
1054 static char line[MAX_INPUTLINE];
1058 Con_Rcon_AddChar(*msg);
1059 // if this is the beginning of a new line, print timestamp
1062 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1064 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1065 line[index++] = STRING_COLOR_TAG;
1066 // assert( STRING_COLOR_DEFAULT < 10 )
1067 line[index++] = STRING_COLOR_DEFAULT + '0';
1068 // special color codes for chat messages must always come first
1069 // for Con_PrintToHistory to work properly
1070 if (*msg == 1 || *msg == 2)
1075 if (con_chatsound.value)
1077 if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
1079 if(msg[1] == '\r' && cl.foundtalk2wav)
1080 S_LocalSound ("sound/misc/talk2.wav");
1082 S_LocalSound ("sound/misc/talk.wav");
1086 if (msg[1] == '(' && cl.foundtalk2wav)
1087 S_LocalSound ("sound/misc/talk2.wav");
1089 S_LocalSound ("sound/misc/talk.wav");
1092 mask = CON_MASK_CHAT;
1094 line[index++] = STRING_COLOR_TAG;
1095 line[index++] = '3';
1097 Con_Rcon_AddChar(*msg);
1100 for (;*timestamp;index++, timestamp++)
1101 if (index < (int)sizeof(line) - 2)
1102 line[index] = *timestamp;
1104 mask |= additionalmask;
1106 // append the character
1107 line[index++] = *msg;
1108 // if this is a newline character, we have a complete line to print
1109 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1111 // terminate the line
1115 // send to scrollable buffer
1116 if (con_initialized && cls.state != ca_dedicated)
1118 Con_PrintToHistory(line, mask);
1120 // send to terminal or dedicated server window
1122 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1124 if(sys_specialcharactertranslation.integer)
1131 int ch = u8_getchar(p, &q);
1132 if(ch >= 0xE000 && ch <= 0xE0FF)
1134 *p = qfont_table[ch - 0xE000];
1136 memmove(p+1, q, strlen(q)+1);
1144 if(sys_colortranslation.integer == 1) // ANSI
1146 static char printline[MAX_INPUTLINE * 4 + 3];
1147 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1148 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1153 for(in = line, out = printline; *in; ++in)
1157 case STRING_COLOR_TAG:
1158 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1160 char r = tolower(in[2]);
1161 char g = tolower(in[3]);
1162 char b = tolower(in[4]);
1163 // it's a hex digit already, so the else part needs no check --blub
1164 if(isdigit(r)) r -= '0';
1166 if(isdigit(g)) g -= '0';
1168 if(isdigit(b)) b -= '0';
1171 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1172 in += 3; // 3 only, the switch down there does the fourth
1179 case STRING_COLOR_TAG:
1181 *out++ = STRING_COLOR_TAG;
1187 if(lastcolor == 0) break; else lastcolor = 0;
1188 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1193 if(lastcolor == 1) break; else lastcolor = 1;
1194 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1199 if(lastcolor == 2) break; else lastcolor = 2;
1200 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1205 if(lastcolor == 3) break; else lastcolor = 3;
1206 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1211 if(lastcolor == 4) break; else lastcolor = 4;
1212 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1217 if(lastcolor == 5) break; else lastcolor = 5;
1218 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1223 if(lastcolor == 6) break; else lastcolor = 6;
1224 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1229 // bold normal color
1231 if(lastcolor == 8) break; else lastcolor = 8;
1232 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1235 *out++ = STRING_COLOR_TAG;
1242 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1259 Sys_PrintToTerminal(printline);
1261 else if(sys_colortranslation.integer == 2) // Quake
1263 Sys_PrintToTerminal(line);
1267 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1270 for(in = line, out = printline; *in; ++in)
1274 case STRING_COLOR_TAG:
1277 case STRING_COLOR_RGB_TAG_CHAR:
1278 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1283 *out++ = STRING_COLOR_TAG;
1284 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1287 case STRING_COLOR_TAG:
1289 *out++ = STRING_COLOR_TAG;
1304 *out++ = STRING_COLOR_TAG;
1314 Sys_PrintToTerminal(printline);
1317 // empty the line buffer
1329 void Con_MaskPrintf(int mask, const char *fmt, ...)
1332 char msg[MAX_INPUTLINE];
1334 va_start(argptr,fmt);
1335 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1338 Con_MaskPrint(mask, msg);
1346 void Con_Print(const char *msg)
1348 Con_MaskPrint(CON_MASK_PRINT, msg);
1356 void Con_Printf(const char *fmt, ...)
1359 char msg[MAX_INPUTLINE];
1361 va_start(argptr,fmt);
1362 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1365 Con_MaskPrint(CON_MASK_PRINT, msg);
1373 void Con_DPrint(const char *msg)
1375 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1378 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1386 void Con_DPrintf(const char *fmt, ...)
1389 char msg[MAX_INPUTLINE];
1391 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1394 va_start(argptr,fmt);
1395 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1398 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1403 ==============================================================================
1407 ==============================================================================
1414 The input line scrolls horizontally if typing goes beyond the right edge
1416 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1419 extern cvar_t r_font_disable_freetype;
1420 void Con_DrawInput (void)
1424 char editlinecopy[MAX_INPUTLINE+1], *text;
1429 if (!key_consoleactive)
1430 return; // don't draw anything
1432 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1433 text = editlinecopy;
1435 // Advanced Console Editing by Radix radix@planetquake.com
1436 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1437 // use strlen of edit_line instead of key_linepos to allow editing
1438 // of early characters w/o erasing
1440 y = (int)strlen(text);
1442 // append enoug nul-bytes to cover the utf8-versions of the cursor too
1443 for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i)
1446 // add the cursor frame
1447 if (r_font_disable_freetype.integer)
1449 // this code is freetype incompatible!
1450 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1452 if (!utf8_enable.integer)
1453 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1454 else if (y + 3 < (int)sizeof(editlinecopy)-1)
1456 int ofs = u8_bytelen(text + key_linepos, 1);
1459 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1463 memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1464 memcpy(text + key_linepos, curbuf, len);
1467 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1471 // text[key_linepos + 1] = 0;
1473 len_out = key_linepos;
1475 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1476 x = vid_conwidth.value * 0.95 - xo; // scroll
1481 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 );
1483 // add a cursor on top of this (when using freetype)
1484 if (!r_font_disable_freetype.integer)
1486 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1488 if (!utf8_enable.integer)
1490 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1497 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1498 memcpy(text, curbuf, len);
1501 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);
1506 // key_line[key_linepos] = 0;
1512 float alignment; // 0 = left, 0.5 = center, 1 = right
1518 const char *continuationString;
1521 int colorindex; // init to -1
1525 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1527 con_text_info_t *ti = (con_text_info_t *) passthrough;
1530 ti->colorindex = -1;
1531 return ti->fontsize * ti->font->maxwidth;
1534 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1535 else if(maxWidth == -1)
1536 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1539 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1540 // Note: this is NOT a Con_Printf, as it could print recursively
1545 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1551 (void) isContinuation;
1555 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1557 con_text_info_t *ti = (con_text_info_t *) passthrough;
1559 if(ti->y < ti->ymin - 0.001)
1561 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1565 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1566 if(isContinuation && *ti->continuationString)
1567 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);
1569 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);
1572 ti->y += ti->fontsize;
1576 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)
1580 int maxlines = (int) floor(height / fontsize + 0.01f);
1583 int continuationWidth = 0;
1585 double t = cl.time; // saved so it won't change
1588 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1589 ti.fontsize = fontsize;
1590 ti.alignment = alignment_x;
1593 ti.ymax = y + height;
1594 ti.continuationString = continuationString;
1597 Con_WordWidthFunc(&ti, NULL, &l, -1);
1598 l = strlen(continuationString);
1599 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1601 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1602 startidx = CON_LINES_COUNT;
1603 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1605 con_lineinfo_t *l = &CON_LINES(i);
1608 if((l->mask & mask_must) != mask_must)
1610 if(l->mask & mask_mustnot)
1612 if(maxage && (l->addtime < t - maxage))
1616 // Calculate its actual height...
1617 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1618 if(lines + mylines >= maxlines)
1620 nskip = lines + mylines - maxlines;
1629 // then center according to the calculated amount of lines...
1631 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1633 // then actually draw
1634 for(i = startidx; i < CON_LINES_COUNT; ++i)
1636 con_lineinfo_t *l = &CON_LINES(i);
1638 if((l->mask & mask_must) != mask_must)
1640 if(l->mask & mask_mustnot)
1642 if(maxage && (l->addtime < t - maxage))
1645 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1655 Draws the last few lines of output transparently over the game top
1658 void Con_DrawNotify (void)
1661 float chatstart, notifystart, inputsize, height;
1663 char temptext[MAX_INPUTLINE];
1667 ConBuffer_FixTimes(&con);
1669 numChatlines = con_chat.integer;
1671 chatpos = con_chatpos.integer;
1673 if (con_notify.integer < 0)
1674 Cvar_SetValueQuick(&con_notify, 0);
1675 if (gamemode == GAME_TRANSFUSION)
1676 v = 8; // vertical offset
1680 // GAME_NEXUIZ: center, otherwise left justify
1681 align = con_notifyalign.value;
1682 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1684 if(gamemode == GAME_NEXUIZ)
1688 if(numChatlines || !con_chatrect.integer)
1692 // first chat, input line, then notify
1694 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1696 else if(chatpos > 0)
1698 // first notify, then (chatpos-1) empty lines, then chat, then input
1700 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1702 else // if(chatpos < 0)
1704 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1706 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1711 // just notify and input
1713 chatstart = 0; // shut off gcc warning
1716 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, "");
1718 if(con_chatrect.integer)
1720 x = con_chatrect_x.value * vid_conwidth.value;
1721 v = con_chatrect_y.value * vid_conheight.value;
1726 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1729 height = numChatlines * con_chatsize.value;
1733 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
1736 if (key_dest == key_message)
1738 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1739 int colorindex = -1;
1741 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL);
1743 // LordHavoc: speedup, and other improvements
1745 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1747 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1749 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1752 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1753 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1755 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1763 Returns the height of a given console line; calculates it if necessary.
1766 int Con_LineHeight(int lineno)
1768 con_lineinfo_t *li = &CON_LINES(lineno);
1769 if(li->height == -1)
1771 float width = vid_conwidth.value;
1773 con_lineinfo_t *li = &CON_LINES(lineno);
1774 ti.fontsize = con_textsize.value;
1775 ti.font = FONT_CONSOLE;
1776 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1785 Draws a line of the console; returns its height in lines.
1786 If alpha is 0, the line is not drawn, but still wrapped and its height
1790 int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1792 float width = vid_conwidth.value;
1794 con_lineinfo_t *li = &CON_LINES(lineno);
1796 if((li->mask & mask_must) != mask_must)
1798 if((li->mask & mask_mustnot) != 0)
1801 ti.continuationString = "";
1803 ti.fontsize = con_textsize.value;
1804 ti.font = FONT_CONSOLE;
1806 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1811 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1818 Calculates the last visible line index and how much to show of it based on
1822 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1827 if(con_backscroll < 0)
1832 // now count until we saw con_backscroll actual lines
1833 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1834 if((CON_LINES(i).mask & mask_must) == mask_must)
1835 if((CON_LINES(i).mask & mask_mustnot) == 0)
1837 int h = Con_LineHeight(i);
1839 // line is the last visible line?
1841 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1843 *limitlast = lines_seen + h - con_backscroll;
1850 // if we get here, no line was on screen - scroll so that one line is
1852 con_backscroll = lines_seen - 1;
1860 Draws the console with the solid background
1861 The typing input line at the bottom should only be drawn if typing is allowed
1864 void Con_DrawConsole (int lines)
1866 float alpha, alpha0;
1869 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1870 cachepic_t *conbackpic;
1875 if (con_backscroll < 0)
1878 con_vislines = lines;
1880 r_draw2d_force = true;
1882 // draw the background
1883 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
1884 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
1886 sx = scr_conscroll_x.value;
1887 sy = scr_conscroll_y.value;
1888 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL;
1889 sx *= realtime; sy *= realtime;
1890 sx -= floor(sx); sy -= floor(sy);
1891 if (conbackpic && conbackpic->tex != r_texture_notexture)
1892 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1893 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1894 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1895 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1896 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1899 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
1901 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
1903 sx = scr_conscroll2_x.value;
1904 sy = scr_conscroll2_y.value;
1905 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1906 sx *= realtime; sy *= realtime;
1907 sx -= floor(sx); sy -= floor(sy);
1908 if(conbackpic && conbackpic->tex != r_texture_notexture)
1909 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1910 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1911 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1912 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1913 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1916 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
1918 sx = scr_conscroll3_x.value;
1919 sy = scr_conscroll3_y.value;
1920 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1921 sx *= realtime; sy *= realtime;
1922 sx -= floor(sx); sy -= floor(sy);
1923 if(conbackpic && conbackpic->tex != r_texture_notexture)
1924 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1925 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1926 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1927 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1928 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1931 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);
1937 int count = CON_LINES_COUNT;
1938 float ymax = con_vislines - 2 * con_textsize.value;
1939 float y = ymax + con_textsize.value * con_backscroll;
1940 for (i = 0;i < count && y >= 0;i++)
1941 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
1942 // fix any excessive scrollback for the next frame
1943 if (i >= count && y >= 0)
1945 con_backscroll -= (int)(y / con_textsize.value);
1946 if (con_backscroll < 0)
1951 if(CON_LINES_COUNT > 0)
1953 int i, last, limitlast;
1955 float ymax = con_vislines - 2 * con_textsize.value;
1956 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1957 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1958 y = ymax - con_textsize.value;
1961 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1966 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
1968 break; // top of console buffer
1970 break; // top of console window
1977 // draw the input prompt, user text, and cursor if desired
1980 r_draw2d_force = false;
1987 Prints not only map filename, but also
1988 its format (q1/q2/q3/hl) and even its message
1990 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1991 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1992 //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
1993 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1994 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1998 int i, k, max, p, o, min;
2001 unsigned char buf[1024];
2003 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2004 t = FS_Search(message, 1, true);
2007 if (t->numfilenames > 1)
2008 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2009 len = (unsigned char *)Z_Malloc(t->numfilenames);
2011 for(max=i=0;i<t->numfilenames;i++)
2013 k = (int)strlen(t->filenames[i]);
2023 for(i=0;i<t->numfilenames;i++)
2025 int lumpofs = 0, lumplen = 0;
2026 char *entities = NULL;
2027 const char *data = NULL;
2029 char entfilename[MAX_QPATH];
2030 strlcpy(message, "^1**ERROR**^7", sizeof(message));
2032 f = FS_OpenVirtualFile(t->filenames[i], true);
2035 memset(buf, 0, 1024);
2036 FS_Read(f, buf, 1024);
2037 if (!memcmp(buf, "IBSP", 4))
2039 p = LittleLong(((int *)buf)[1]);
2040 if (p == Q3BSPVERSION)
2042 q3dheader_t *header = (q3dheader_t *)buf;
2043 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2044 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2046 else if (p == Q2BSPVERSION)
2048 q2dheader_t *header = (q2dheader_t *)buf;
2049 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2050 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2053 else if((p = BuffLittleLong(buf)) == BSPVERSION || p == 30)
2055 dheader_t *header = (dheader_t *)buf;
2056 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
2057 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
2061 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2062 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2063 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2064 if (!entities && lumplen >= 10)
2066 FS_Seek(f, lumpofs, SEEK_SET);
2067 entities = (char *)Z_Malloc(lumplen + 1);
2068 FS_Read(f, entities, lumplen);
2072 // if there are entities to parse, a missing message key just
2073 // means there is no title, so clear the message string now
2079 if (!COM_ParseToken_Simple(&data, false, false))
2081 if (com_token[0] == '{')
2083 if (com_token[0] == '}')
2085 // skip leading whitespace
2086 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2087 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2088 keyname[l] = com_token[k+l];
2090 if (!COM_ParseToken_Simple(&data, false, false))
2092 if (developer_extra.integer)
2093 Con_DPrintf("key: %s %s\n", keyname, com_token);
2094 if (!strcmp(keyname, "message"))
2096 // get the message contents
2097 strlcpy(message, com_token, sizeof(message));
2107 *(t->filenames[i]+len[i]+5) = 0;
2110 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
2111 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
2112 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
2113 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
2114 default: strlcpy((char *)buf, "??", sizeof(buf));break;
2116 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
2121 k = *(t->filenames[0]+5+p);
2124 for(i=1;i<t->numfilenames;i++)
2125 if(*(t->filenames[i]+5+p) != k)
2129 if(p > o && completedname && completednamebufferlength > 0)
2131 memset(completedname, 0, completednamebufferlength);
2132 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2142 New function for tab-completion system
2143 Added by EvilTypeGuy
2144 MEGA Thanks to Taniwha
2147 void Con_DisplayList(const char **list)
2149 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2150 const char **walk = list;
2153 len = (int)strlen(*walk);
2161 len = (int)strlen(*list);
2162 if (pos + maxlen >= width) {
2168 for (i = 0; i < (maxlen - len); i++)
2180 SanitizeString strips color tags from the string in
2181 and writes the result on string out
2183 void SanitizeString(char *in, char *out)
2187 if(*in == STRING_COLOR_TAG)
2192 out[0] = STRING_COLOR_TAG;
2196 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2203 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2206 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2208 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2215 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2220 else if (*in != STRING_COLOR_TAG)
2223 *out = qfont_table[*(unsigned char*)in];
2230 // Now it becomes TRICKY :D --blub
2231 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2232 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2233 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2234 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
2235 static int Nicks_matchpos;
2237 // co against <<:BLASTER:>> is true!?
2238 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2242 if(tolower(*a) == tolower(*b))
2256 return (*a < *b) ? -1 : 1;
2260 return (*a < *b) ? -1 : 1;
2264 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2267 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2269 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2270 return Nicks_strncasecmp_nospaces(a, b, a_len);
2271 return strncasecmp(a, b, a_len);
2274 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2276 // ignore non alphanumerics of B
2277 // if A contains a non-alphanumeric, B must contain it as well though!
2280 qboolean alnum_a, alnum_b;
2282 if(tolower(*a) == tolower(*b))
2284 if(*a == 0) // end of both strings, they're equal
2291 // not equal, end of one string?
2296 // ignore non alphanumerics
2297 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2298 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2299 if(!alnum_a) // b must contain this
2300 return (*a < *b) ? -1 : 1;
2303 // otherwise, both are alnum, they're just not equal, return the appropriate number
2305 return (*a < *b) ? -1 : 1;
2311 /* Nicks_CompleteCountPossible
2313 Count the number of possible nicks to complete
2315 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2323 if(!con_nickcompletion.integer)
2326 // changed that to 1
2327 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2330 for(i = 0; i < cl.maxclients; ++i)
2333 if(!cl.scores[p].name[0])
2336 SanitizeString(cl.scores[p].name, name);
2337 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2343 spos = pos - 1; // no need for a minimum of characters :)
2347 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2349 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2350 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2356 if(isCon && spos == 0)
2358 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2364 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2365 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2367 // the sanitized list
2368 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2371 Nicks_matchpos = match;
2374 Nicks_offset[count] = s - (&line[match]);
2375 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2382 void Cmd_CompleteNicksPrint(int count)
2385 for(i = 0; i < count; ++i)
2386 Con_Printf("%s\n", Nicks_list[i]);
2389 void Nicks_CutMatchesNormal(int count)
2391 // cut match 0 down to the longest possible completion
2394 c = strlen(Nicks_sanlist[0]) - 1;
2395 for(i = 1; i < count; ++i)
2397 l = strlen(Nicks_sanlist[i]) - 1;
2401 for(l = 0; l <= c; ++l)
2402 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2408 Nicks_sanlist[0][c+1] = 0;
2409 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2412 unsigned int Nicks_strcleanlen(const char *s)
2417 if( (*s >= 'a' && *s <= 'z') ||
2418 (*s >= 'A' && *s <= 'Z') ||
2419 (*s >= '0' && *s <= '9') ||
2427 void Nicks_CutMatchesAlphaNumeric(int count)
2429 // cut match 0 down to the longest possible completion
2432 char tempstr[sizeof(Nicks_sanlist[0])];
2434 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2436 c = strlen(Nicks_sanlist[0]);
2437 for(i = 0, l = 0; i < (int)c; ++i)
2439 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2440 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2441 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2443 tempstr[l++] = Nicks_sanlist[0][i];
2448 for(i = 1; i < count; ++i)
2451 b = Nicks_sanlist[i];
2461 if(tolower(*a) == tolower(*b))
2467 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2469 // b is alnum, so cut
2476 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2477 Nicks_CutMatchesNormal(count);
2478 //if(!Nicks_sanlist[0][0])
2479 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2481 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2482 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2486 void Nicks_CutMatchesNoSpaces(int count)
2488 // cut match 0 down to the longest possible completion
2491 char tempstr[sizeof(Nicks_sanlist[0])];
2494 c = strlen(Nicks_sanlist[0]);
2495 for(i = 0, l = 0; i < (int)c; ++i)
2497 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2499 tempstr[l++] = Nicks_sanlist[0][i];
2504 for(i = 1; i < count; ++i)
2507 b = Nicks_sanlist[i];
2517 if(tolower(*a) == tolower(*b))
2531 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2532 Nicks_CutMatchesNormal(count);
2533 //if(!Nicks_sanlist[0][0])
2534 //Con_Printf("TS: %s\n", tempstr);
2535 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2537 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2538 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2542 void Nicks_CutMatches(int count)
2544 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2545 Nicks_CutMatchesAlphaNumeric(count);
2546 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2547 Nicks_CutMatchesNoSpaces(count);
2549 Nicks_CutMatchesNormal(count);
2552 const char **Nicks_CompleteBuildList(int count)
2556 // the list is freed by Con_CompleteCommandLine, so create a char**
2557 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2559 for(; bpos < count; ++bpos)
2560 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2562 Nicks_CutMatches(count);
2570 Restores the previous used color, after the autocompleted name.
2572 int Nicks_AddLastColor(char *buffer, int pos)
2574 qboolean quote_added = false;
2576 int color = STRING_COLOR_DEFAULT + '0';
2577 char r = 0, g = 0, b = 0;
2579 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2581 // we'll have to add a quote :)
2582 buffer[pos++] = '\"';
2586 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2588 // add color when no quote was added, or when flags &4?
2590 for(match = Nicks_matchpos-1; match >= 0; --match)
2592 if(buffer[match] == STRING_COLOR_TAG)
2594 if( isdigit(buffer[match+1]) )
2596 color = buffer[match+1];
2599 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2601 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2603 r = buffer[match+2];
2604 g = buffer[match+3];
2605 b = buffer[match+4];
2614 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2616 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2617 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2620 buffer[pos++] = STRING_COLOR_TAG;
2623 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2629 buffer[pos++] = color;
2634 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2637 /*if(!con_nickcompletion.integer)
2638 return; is tested in Nicks_CompletionCountPossible */
2639 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2645 msg = Nicks_list[0];
2646 len = min(size - Nicks_matchpos - 3, strlen(msg));
2647 memcpy(&buffer[Nicks_matchpos], msg, len);
2648 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2649 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2650 buffer[len++] = ' ';
2657 Con_Printf("\n%i possible nicks:\n", n);
2658 Cmd_CompleteNicksPrint(n);
2660 Nicks_CutMatches(n);
2662 msg = Nicks_sanlist[0];
2663 len = min(size - Nicks_matchpos, strlen(msg));
2664 memcpy(&buffer[Nicks_matchpos], msg, len);
2665 buffer[Nicks_matchpos + len] = 0;
2667 return Nicks_matchpos + len;
2674 Con_CompleteCommandLine
2676 New function for tab-completion system
2677 Added by EvilTypeGuy
2678 Thanks to Fett erich@heintz.com
2680 Enhanced to tab-complete map names by [515]
2683 void Con_CompleteCommandLine (void)
2685 const char *cmd = "";
2687 const char **list[4] = {0, 0, 0, 0};
2690 int c, v, a, i, cmd_len, pos, k;
2691 int n; // nicks --blub
2692 const char *space, *patterns;
2694 //find what we want to complete
2699 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2705 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2706 key_line[key_linepos] = 0; //hide them
2708 space = strchr(key_line + 1, ' ');
2709 if(space && pos == (space - key_line) + 1)
2711 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2713 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2714 if(patterns && !*patterns)
2715 patterns = NULL; // get rid of the empty string
2717 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2721 if (GetMapList(s, t, sizeof(t)))
2723 // first move the cursor
2724 key_linepos += (int)strlen(t) - (int)strlen(s);
2726 // and now do the actual work
2728 strlcat(key_line, t, MAX_INPUTLINE);
2729 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2731 // and fix the cursor
2732 if(key_linepos > (int) strlen(key_line))
2733 key_linepos = (int) strlen(key_line);
2742 stringlist_t resultbuf, dirbuf;
2745 // // store completion patterns (space separated) for command foo in con_completion_foo
2746 // set con_completion_foo "foodata/*.foodefault *.foo"
2749 // Note: patterns with slash are always treated as absolute
2750 // patterns; patterns without slash search in the innermost
2751 // directory the user specified. There is no way to "complete into"
2752 // a directory as of now, as directories seem to be unknown to the
2756 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2757 // set con_completion_playdemo "*.dem"
2758 // set con_completion_play "*.wav *.ogg"
2760 // TODO somehow add support for directories; these shall complete
2761 // to their name + an appended slash.
2763 stringlistinit(&resultbuf);
2764 stringlistinit(&dirbuf);
2765 while(COM_ParseToken_Simple(&patterns, false, false))
2768 if(strchr(com_token, '/'))
2770 search = FS_Search(com_token, true, true);
2774 const char *slash = strrchr(s, '/');
2777 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2778 strlcat(t, com_token, sizeof(t));
2779 search = FS_Search(t, true, true);
2782 search = FS_Search(com_token, 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_FILE)
2789 stringlistappend(&resultbuf, search->filenames[i]);
2790 FS_FreeSearch(search);
2794 // In any case, add directory names
2797 const char *slash = strrchr(s, '/');
2800 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2801 strlcat(t, "*", sizeof(t));
2802 search = FS_Search(t, true, true);
2805 search = FS_Search("*", true, true);
2808 for(i = 0; i < search->numfilenames; ++i)
2809 if(!strncmp(search->filenames[i], s, strlen(s)))
2810 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2811 stringlistappend(&dirbuf, search->filenames[i]);
2812 FS_FreeSearch(search);
2816 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2819 unsigned int matchchars;
2820 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2822 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2825 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2827 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2831 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2832 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2833 for(i = 0; i < dirbuf.numstrings; ++i)
2835 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2837 for(i = 0; i < resultbuf.numstrings; ++i)
2839 Con_Printf("%s\n", resultbuf.strings[i]);
2841 matchchars = sizeof(t) - 1;
2842 if(resultbuf.numstrings > 0)
2844 p = resultbuf.strings[0];
2845 q = resultbuf.strings[resultbuf.numstrings - 1];
2846 for(; *p && *p == *q; ++p, ++q);
2847 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2849 if(dirbuf.numstrings > 0)
2851 p = dirbuf.strings[0];
2852 q = dirbuf.strings[dirbuf.numstrings - 1];
2853 for(; *p && *p == *q; ++p, ++q);
2854 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2856 // now p points to the first non-equal character, or to the end
2857 // of resultbuf.strings[0]. We want to append the characters
2858 // from resultbuf.strings[0] to (not including) p as these are
2859 // the unique prefix
2860 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2863 // first move the cursor
2864 key_linepos += (int)strlen(t) - (int)strlen(s);
2866 // and now do the actual work
2868 strlcat(key_line, t, MAX_INPUTLINE);
2869 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2871 // and fix the cursor
2872 if(key_linepos > (int) strlen(key_line))
2873 key_linepos = (int) strlen(key_line);
2875 stringlistfreecontents(&resultbuf);
2876 stringlistfreecontents(&dirbuf);
2878 return; // bail out, when we complete for a command that wants a file name
2883 // Count number of possible matches and print them
2884 c = Cmd_CompleteCountPossible(s);
2887 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2888 Cmd_CompleteCommandPrint(s);
2890 v = Cvar_CompleteCountPossible(s);
2893 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2894 Cvar_CompleteCvarPrint(s);
2896 a = Cmd_CompleteAliasCountPossible(s);
2899 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2900 Cmd_CompleteAliasPrint(s);
2902 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2905 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2906 Cmd_CompleteNicksPrint(n);
2909 if (!(c + v + a + n)) // No possible matches
2912 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2917 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2919 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2921 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2923 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2925 for (cmd_len = (int)strlen(s);;cmd_len++)
2928 for (i = 0; i < 3; i++)
2930 for (l = list[i];*l;l++)
2931 if ((*l)[cmd_len] != cmd[cmd_len])
2933 // all possible matches share this character, so we continue...
2936 // if all matches ended at the same position, stop
2937 // (this means there is only one match)
2943 // prevent a buffer overrun by limiting cmd_len according to remaining space
2944 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2948 memcpy(&key_line[key_linepos], cmd, cmd_len);
2949 key_linepos += cmd_len;
2950 // if there is only one match, add a space after it
2951 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2954 { // was a nick, might have an offset, and needs colors ;) --blub
2955 key_linepos = pos - Nicks_offset[0];
2956 cmd_len = strlen(Nicks_list[0]);
2957 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2959 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2960 key_linepos += cmd_len;
2961 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2962 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2964 key_line[key_linepos++] = ' ';
2968 // use strlcat to avoid a buffer overrun
2969 key_line[key_linepos] = 0;
2970 strlcat(key_line, s2, sizeof(key_line));
2972 // free the command, cvar, and alias lists
2973 for (i = 0; i < 4; i++)
2975 Mem_Free((void *)list[i]);