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__)
34 float con_cursorspeed = 4;
36 // lines up from bottom to display
41 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
42 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
43 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
45 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
46 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
47 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
49 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
50 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
51 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)"};
52 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"};
53 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)"};
54 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)"};
55 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
56 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
57 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
58 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
59 cvar_t con_chatsound = {CVAR_SAVE, "con_chatsound","1", "enables chat sound to play on message"};
62 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)"};
64 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)"};
66 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)"};
70 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
71 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
72 "0: add nothing after completion. "
73 "1: add the last color after completion. "
74 "2: add a quote when starting a quote instead of the color. "
75 "4: will replace 1, will force color, even after a quote. "
76 "8: ignore non-alphanumerics. "
77 "16: ignore spaces. "};
78 #define NICKS_ADD_COLOR 1
79 #define NICKS_ADD_QUOTE 2
80 #define NICKS_FORCE_COLOR 4
81 #define NICKS_ALPHANUMERICS_ONLY 8
82 #define NICKS_NO_SPACES 16
84 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
85 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
86 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
91 qboolean con_initialized;
93 // used for server replies to rcon command
94 lhnetsocket_t *rcon_redirect_sock = NULL;
95 lhnetaddress_t *rcon_redirect_dest = NULL;
96 int rcon_redirect_bufferpos = 0;
97 char rcon_redirect_buffer[1400];
98 qboolean rcon_redirect_proquakeprotocol = false;
100 // generic functions for console buffers
102 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
105 buf->textsize = textsize;
106 buf->text = (char *) Mem_Alloc(mempool, textsize);
107 buf->maxlines = maxlines;
108 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
109 buf->lines_first = 0;
110 buf->lines_count = 0;
118 void ConBuffer_Clear (conbuffer_t *buf)
120 buf->lines_count = 0;
128 void ConBuffer_Shutdown(conbuffer_t *buf)
134 Mem_Free(buf->lines);
143 Notifies the console code about the current time
144 (and shifts back times of other entries when the time
148 void ConBuffer_FixTimes(conbuffer_t *buf)
151 if(buf->lines_count >= 1)
153 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
156 for(i = 0; i < buf->lines_count; ++i)
157 CONBUFFER_LINES(buf, i).addtime += diff;
166 Deletes the first line from the console history.
169 void ConBuffer_DeleteLine(conbuffer_t *buf)
171 if(buf->lines_count == 0)
174 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
179 ConBuffer_DeleteLastLine
181 Deletes the last line from the console history.
184 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
186 if(buf->lines_count == 0)
195 Checks if there is space for a line of the given length, and if yes, returns a
196 pointer to the start of such a space, and NULL otherwise.
199 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
201 if(len > buf->textsize)
203 if(buf->lines_count == 0)
207 char *firstline_start = buf->lines[buf->lines_first].start;
208 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
209 // the buffer is cyclic, so we first have two cases...
210 if(firstline_start < lastline_onepastend) // buffer is contiguous
213 if(len <= buf->text + buf->textsize - lastline_onepastend)
214 return lastline_onepastend;
216 else if(len <= firstline_start - buf->text)
221 else // buffer has a contiguous hole
223 if(len <= firstline_start - lastline_onepastend)
224 return lastline_onepastend;
235 Appends a given string as a new line to the console.
238 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
243 // developer_memory 1 during shutdown prints while conbuffer_t is being freed
247 ConBuffer_FixTimes(buf);
249 if(len >= buf->textsize)
252 // only display end of line.
253 line += len - buf->textsize + 1;
254 len = buf->textsize - 1;
256 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
257 ConBuffer_DeleteLine(buf);
258 memcpy(putpos, line, len);
262 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
264 p = &CONBUFFER_LINES_LAST(buf);
267 p->addtime = cl.time;
269 p->height = -1; // calculate when needed
272 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
276 start = buf->lines_count;
277 for(i = start - 1; i >= 0; --i)
279 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
281 if((l->mask & mask_must) != mask_must)
283 if(l->mask & mask_mustnot)
292 int Con_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
295 for(i = start + 1; i < buf->lines_count; ++i)
297 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
299 if((l->mask & mask_must) != mask_must)
301 if(l->mask & mask_mustnot)
310 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
312 static char copybuf[MAX_INPUTLINE];
313 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
314 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
315 strlcpy(copybuf, l->start, sz);
320 ==============================================================================
324 ==============================================================================
329 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
330 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"};
331 char log_dest_buffer[1400]; // UDP packet
332 size_t log_dest_buffer_pos;
333 unsigned int log_dest_buffer_appending;
334 char crt_log_file [MAX_OSPATH] = "";
335 qfile_t* logfile = NULL;
337 unsigned char* logqueue = NULL;
339 size_t logq_size = 0;
341 void Log_ConPrint (const char *msg);
348 static void Log_DestBuffer_Init(void)
350 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
351 log_dest_buffer_pos = 5;
359 void Log_DestBuffer_Flush(void)
361 lhnetaddress_t log_dest_addr;
362 lhnetsocket_t *log_dest_socket;
363 const char *s = log_dest_udp.string;
364 qboolean have_opened_temp_sockets = false;
365 if(s) if(log_dest_buffer_pos > 5)
367 ++log_dest_buffer_appending;
368 log_dest_buffer[log_dest_buffer_pos++] = 0;
370 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
372 have_opened_temp_sockets = true;
373 NetConn_OpenServerPorts(true);
376 while(COM_ParseToken_Console(&s))
377 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
379 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
381 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
383 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
386 if(have_opened_temp_sockets)
387 NetConn_CloseServerPorts();
388 --log_dest_buffer_appending;
390 log_dest_buffer_pos = 0;
398 const char* Log_Timestamp (const char *desc)
400 static char timestamp [128];
407 char timestring [64];
409 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
412 localtime_s (&crt_tm, &crt_time);
413 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
415 crt_tm = localtime (&crt_time);
416 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
420 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
422 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
435 if (logfile != NULL || log_file.string[0] == '\0')
438 logfile = FS_OpenRealFile(log_file.string, "a", false);
441 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
442 FS_Print (logfile, Log_Timestamp ("Log started"));
452 void Log_Close (void)
457 FS_Print (logfile, Log_Timestamp ("Log stopped"));
458 FS_Print (logfile, "\n");
462 crt_log_file[0] = '\0';
471 void Log_Start (void)
477 // Dump the contents of the log queue into the log file and free it
478 if (logqueue != NULL)
480 unsigned char *temp = logqueue;
485 FS_Write (logfile, temp, logq_ind);
486 if(*log_dest_udp.string)
488 for(pos = 0; pos < logq_ind; )
490 if(log_dest_buffer_pos == 0)
491 Log_DestBuffer_Init();
492 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
493 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
494 log_dest_buffer_pos += n;
495 Log_DestBuffer_Flush();
512 void Log_ConPrint (const char *msg)
514 static qboolean inprogress = false;
516 // don't allow feedback loops with memory error reports
521 // Until the host is completely initialized, we maintain a log queue
522 // to store the messages, since the log can't be started before
523 if (logqueue != NULL)
525 size_t remain = logq_size - logq_ind;
526 size_t len = strlen (msg);
528 // If we need to enlarge the log queue
531 size_t factor = ((logq_ind + len) / logq_size) + 1;
532 unsigned char* newqueue;
535 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
536 memcpy (newqueue, logqueue, logq_ind);
539 remain = logq_size - logq_ind;
541 memcpy (&logqueue[logq_ind], msg, len);
548 // Check if log_file has changed
549 if (strcmp (crt_log_file, log_file.string) != 0)
555 // If a log file is available
557 FS_Print (logfile, msg);
568 void Log_Printf (const char *logfilename, const char *fmt, ...)
572 file = FS_OpenRealFile(logfilename, "a", true);
577 va_start (argptr, fmt);
578 FS_VPrintf (file, fmt, argptr);
587 ==============================================================================
591 ==============================================================================
599 void Con_ToggleConsole_f (void)
601 // toggle the 'user wants console' bit
602 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
611 void Con_ClearNotify (void)
614 for(i = 0; i < CON_LINES_COUNT; ++i)
615 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
624 void Con_MessageMode_f (void)
626 key_dest = key_message;
627 chat_mode = 0; // "say"
630 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
631 chat_bufferlen = strlen(chat_buffer);
641 void Con_MessageMode2_f (void)
643 key_dest = key_message;
644 chat_mode = 1; // "say_team"
647 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
648 chat_bufferlen = strlen(chat_buffer);
657 void Con_CommandMode_f (void)
659 key_dest = key_message;
662 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
663 chat_bufferlen = strlen(chat_buffer);
665 chat_mode = -1; // command
673 void Con_CheckResize (void)
678 f = bound(1, con_textsize.value, 128);
679 if(f != con_textsize.value)
680 Cvar_SetValueQuick(&con_textsize, f);
681 width = (int)floor(vid_conwidth.value / con_textsize.value);
682 width = bound(1, width, con.textsize/4);
683 // FIXME uses con in a non abstracted way
685 if (width == con_linewidth)
688 con_linewidth = width;
690 for(i = 0; i < CON_LINES_COUNT; ++i)
691 CON_LINES(i).height = -1; // recalculate when next needed
697 //[515]: the simplest command ever
698 //LordHavoc: not so simple after I made it print usage...
699 static void Con_Maps_f (void)
703 Con_Printf("usage: maps [mapnameprefix]\n");
706 else if (Cmd_Argc() == 2)
707 GetMapList(Cmd_Argv(1), NULL, 0);
709 GetMapList("", NULL, 0);
712 void Con_ConDump_f (void)
718 Con_Printf("usage: condump <filename>\n");
721 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
724 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
727 for(i = 0; i < CON_LINES_COUNT; ++i)
729 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
730 FS_Write(file, "\n", 1);
735 void Con_Clear_f (void)
737 ConBuffer_Clear(&con);
748 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
750 // Allocate a log queue, this will be freed after configs are parsed
751 logq_size = MAX_INPUTLINE;
752 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
755 Cvar_RegisterVariable (&sys_colortranslation);
756 Cvar_RegisterVariable (&sys_specialcharactertranslation);
758 Cvar_RegisterVariable (&log_file);
759 Cvar_RegisterVariable (&log_dest_udp);
761 // support for the classic Quake option
762 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
763 if (COM_CheckParm ("-condebug") != 0)
764 Cvar_SetQuick (&log_file, "qconsole.log");
766 // register our cvars
767 Cvar_RegisterVariable (&con_chat);
768 Cvar_RegisterVariable (&con_chatpos);
769 Cvar_RegisterVariable (&con_chatrect_x);
770 Cvar_RegisterVariable (&con_chatrect_y);
771 Cvar_RegisterVariable (&con_chatrect);
772 Cvar_RegisterVariable (&con_chatsize);
773 Cvar_RegisterVariable (&con_chattime);
774 Cvar_RegisterVariable (&con_chatwidth);
775 Cvar_RegisterVariable (&con_notify);
776 Cvar_RegisterVariable (&con_notifyalign);
777 Cvar_RegisterVariable (&con_notifysize);
778 Cvar_RegisterVariable (&con_notifytime);
779 Cvar_RegisterVariable (&con_textsize);
780 Cvar_RegisterVariable (&con_chatsound);
783 Cvar_RegisterVariable (&con_nickcompletion);
784 Cvar_RegisterVariable (&con_nickcompletion_flags);
786 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
787 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
788 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
790 // register our commands
791 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
792 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
793 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
794 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
795 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
796 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
797 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
799 con_initialized = true;
800 Con_DPrint("Console initialized.\n");
803 void Con_Shutdown (void)
805 ConBuffer_Shutdown(&con);
812 Handles cursor positioning, line wrapping, etc
813 All console printing must go through this in order to be displayed
814 If no console is visible, the notify window will pop up.
817 void Con_PrintToHistory(const char *txt, int mask)
820 // \n goes to next line
821 // \r deletes current line and makes a new one
823 static int cr_pending = 0;
824 static char buf[CON_TEXTSIZE];
825 static int bufpos = 0;
827 if(!con.text) // FIXME uses a non-abstracted property of con
834 ConBuffer_DeleteLastLine(&con);
842 ConBuffer_AddLine(&con, buf, bufpos, mask);
847 ConBuffer_AddLine(&con, buf, bufpos, mask);
851 buf[bufpos++] = *txt;
852 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
854 ConBuffer_AddLine(&con, buf, bufpos, mask);
862 /*! The translation table between the graphical font and plain ASCII --KB */
863 static char qfont_table[256] = {
864 '\0', '#', '#', '#', '#', '.', '#', '#',
865 '#', 9, 10, '#', ' ', 13, '.', '.',
866 '[', ']', '0', '1', '2', '3', '4', '5',
867 '6', '7', '8', '9', '.', '<', '=', '>',
868 ' ', '!', '"', '#', '$', '%', '&', '\'',
869 '(', ')', '*', '+', ',', '-', '.', '/',
870 '0', '1', '2', '3', '4', '5', '6', '7',
871 '8', '9', ':', ';', '<', '=', '>', '?',
872 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
873 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
874 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
875 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
876 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
877 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
878 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
879 'x', 'y', 'z', '{', '|', '}', '~', '<',
881 '<', '=', '>', '#', '#', '.', '#', '#',
882 '#', '#', ' ', '#', ' ', '>', '.', '.',
883 '[', ']', '0', '1', '2', '3', '4', '5',
884 '6', '7', '8', '9', '.', '<', '=', '>',
885 ' ', '!', '"', '#', '$', '%', '&', '\'',
886 '(', ')', '*', '+', ',', '-', '.', '/',
887 '0', '1', '2', '3', '4', '5', '6', '7',
888 '8', '9', ':', ';', '<', '=', '>', '?',
889 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
890 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
891 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
892 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
893 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
894 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
895 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
896 'x', 'y', 'z', '{', '|', '}', '~', '<'
899 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol)
901 rcon_redirect_sock = sock;
902 rcon_redirect_dest = dest;
903 rcon_redirect_proquakeprotocol = proquakeprotocol;
904 if (rcon_redirect_proquakeprotocol)
906 // reserve space for the packet header
907 rcon_redirect_buffer[0] = 0;
908 rcon_redirect_buffer[1] = 0;
909 rcon_redirect_buffer[2] = 0;
910 rcon_redirect_buffer[3] = 0;
911 // this is a reply to a CCREQ_RCON
912 rcon_redirect_buffer[4] = (char)CCREP_RCON;
915 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
916 rcon_redirect_bufferpos = 5;
919 void Con_Rcon_Redirect_Flush(void)
921 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
922 if (rcon_redirect_proquakeprotocol)
924 // update the length in the packet header
925 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
927 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
928 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
929 rcon_redirect_bufferpos = 5;
930 rcon_redirect_proquakeprotocol = false;
933 void Con_Rcon_Redirect_End(void)
935 Con_Rcon_Redirect_Flush();
936 rcon_redirect_dest = NULL;
937 rcon_redirect_sock = NULL;
940 void Con_Rcon_Redirect_Abort(void)
942 rcon_redirect_dest = NULL;
943 rcon_redirect_sock = NULL;
951 /// Adds a character to the rcon buffer.
952 void Con_Rcon_AddChar(int c)
954 if(log_dest_buffer_appending)
956 ++log_dest_buffer_appending;
958 // if this print is in response to an rcon command, add the character
959 // to the rcon redirect buffer
961 if (rcon_redirect_dest)
963 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
964 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
965 Con_Rcon_Redirect_Flush();
967 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
969 if(log_dest_buffer_pos == 0)
970 Log_DestBuffer_Init();
971 log_dest_buffer[log_dest_buffer_pos++] = c;
972 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
973 Log_DestBuffer_Flush();
976 log_dest_buffer_pos = 0;
978 --log_dest_buffer_appending;
982 * Convert an RGB color to its nearest quake color.
983 * I'll cheat on this a bit by translating the colors to HSV first,
984 * S and V decide if it's black or white, otherwise, H will decide the
986 * @param _r Red (0-255)
987 * @param _g Green (0-255)
988 * @param _b Blue (0-255)
989 * @return A quake color character.
991 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
993 float r = ((float)_r)/255.0;
994 float g = ((float)_g)/255.0;
995 float b = ((float)_b)/255.0;
996 float min = min(r, min(g, b));
997 float max = max(r, max(g, b));
999 int h; ///< Hue angle [0,360]
1000 float s; ///< Saturation [0,1]
1001 float v = max; ///< In HSV v == max [0,1]
1006 s = 1.0 - (min/max);
1008 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1011 // If the value is less than half, return a black color code.
1012 // Otherwise return a white one.
1018 // Let's get the hue angle to define some colors:
1022 h = (int)(60.0 * (g-b)/(max-min))%360;
1024 h = (int)(60.0 * (b-r)/(max-min) + 120);
1025 else // if(max == b) redundant check
1026 h = (int)(60.0 * (r-g)/(max-min) + 240);
1028 if(h < 36) // *red* to orange
1030 else if(h < 80) // orange over *yellow* to evilish-bright-green
1032 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1034 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1036 else if(h < 270) // darkish blue over *dark blue* to cool purple
1038 else if(h < 330) // cool purple over *purple* to ugly swiny red
1040 else // ugly red to red closes the circly
1049 extern cvar_t timestamps;
1050 extern cvar_t timeformat;
1051 extern qboolean sys_nostdout;
1053 // -- Akari: attempted to make this somewhat thread safe.... works.... sometimes
1055 pthread_mutex_t con_print_mutex = PTHREAD_MUTEX_INITIALIZER;
1057 void Con_MaskPrint(int additionalmask, const char *msg)
1059 static int mask = 0;
1060 static int index = 0;
1061 static char line[MAX_INPUTLINE];
1063 pthread_mutex_lock(&con_print_mutex);
1067 Con_Rcon_AddChar(*msg);
1068 // if this is the beginning of a new line, print timestamp
1071 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1073 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1074 line[index++] = STRING_COLOR_TAG;
1075 // assert( STRING_COLOR_DEFAULT < 10 )
1076 line[index++] = STRING_COLOR_DEFAULT + '0';
1077 // special color codes for chat messages must always come first
1078 // for Con_PrintToHistory to work properly
1079 if (*msg == 1 || *msg == 2)
1084 if (con_chatsound.value)
1086 if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
1088 if(msg[1] == '\r' && cl.foundtalk2wav)
1089 S_LocalSound ("sound/misc/talk2.wav");
1091 S_LocalSound ("sound/misc/talk.wav");
1095 if (msg[1] == '(' && cl.foundtalk2wav)
1096 S_LocalSound ("sound/misc/talk2.wav");
1098 S_LocalSound ("sound/misc/talk.wav");
1101 mask = CON_MASK_CHAT;
1103 line[index++] = STRING_COLOR_TAG;
1104 line[index++] = '3';
1106 Con_Rcon_AddChar(*msg);
1109 for (;*timestamp;index++, timestamp++)
1110 if (index < (int)sizeof(line) - 2)
1111 line[index] = *timestamp;
1113 mask |= additionalmask;
1115 // append the character
1116 line[index++] = *msg;
1117 // if this is a newline character, we have a complete line to print
1118 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1120 // terminate the line
1124 // send to scrollable buffer
1125 if (con_initialized && cls.state != ca_dedicated)
1127 Con_PrintToHistory(line, mask);
1129 // send to terminal or dedicated server window
1131 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1133 if(sys_specialcharactertranslation.integer)
1140 int ch = u8_getchar(p, &q);
1141 if(ch >= 0xE000 && ch <= 0xE0FF)
1143 *p = qfont_table[ch - 0xE000];
1145 memmove(p+1, q, strlen(q)+1);
1153 if(sys_colortranslation.integer == 1) // ANSI
1155 static char printline[MAX_INPUTLINE * 4 + 3];
1156 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1157 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1162 for(in = line, out = printline; *in; ++in)
1166 case STRING_COLOR_TAG:
1167 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1169 char r = tolower(in[2]);
1170 char g = tolower(in[3]);
1171 char b = tolower(in[4]);
1172 // it's a hex digit already, so the else part needs no check --blub
1173 if(isdigit(r)) r -= '0';
1175 if(isdigit(g)) g -= '0';
1177 if(isdigit(b)) b -= '0';
1180 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1181 in += 3; // 3 only, the switch down there does the fourth
1188 case STRING_COLOR_TAG:
1190 *out++ = STRING_COLOR_TAG;
1196 if(lastcolor == 0) break; else lastcolor = 0;
1197 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1202 if(lastcolor == 1) break; else lastcolor = 1;
1203 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1208 if(lastcolor == 2) break; else lastcolor = 2;
1209 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1214 if(lastcolor == 3) break; else lastcolor = 3;
1215 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1220 if(lastcolor == 4) break; else lastcolor = 4;
1221 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1226 if(lastcolor == 5) break; else lastcolor = 5;
1227 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1232 if(lastcolor == 6) break; else lastcolor = 6;
1233 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1238 // bold normal color
1240 if(lastcolor == 8) break; else lastcolor = 8;
1241 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1244 *out++ = STRING_COLOR_TAG;
1251 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1268 Sys_PrintToTerminal(printline);
1270 else if(sys_colortranslation.integer == 2) // Quake
1272 Sys_PrintToTerminal(line);
1276 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1279 for(in = line, out = printline; *in; ++in)
1283 case STRING_COLOR_TAG:
1286 case STRING_COLOR_RGB_TAG_CHAR:
1287 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1292 *out++ = STRING_COLOR_TAG;
1293 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1296 case STRING_COLOR_TAG:
1298 *out++ = STRING_COLOR_TAG;
1313 *out++ = STRING_COLOR_TAG;
1323 Sys_PrintToTerminal(printline);
1326 // empty the line buffer
1332 pthread_mutex_unlock(&con_print_mutex);
1340 void Con_MaskPrintf(int mask, const char *fmt, ...)
1343 char msg[MAX_INPUTLINE];
1345 va_start(argptr,fmt);
1346 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1349 Con_MaskPrint(mask, msg);
1357 void Con_Print(const char *msg)
1359 Con_MaskPrint(CON_MASK_PRINT, msg);
1367 void Con_Printf(const char *fmt, ...)
1370 char msg[MAX_INPUTLINE];
1372 va_start(argptr,fmt);
1373 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1376 Con_MaskPrint(CON_MASK_PRINT, msg);
1384 void Con_DPrint(const char *msg)
1386 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1389 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1397 void Con_DPrintf(const char *fmt, ...)
1400 char msg[MAX_INPUTLINE];
1402 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1405 va_start(argptr,fmt);
1406 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1409 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1414 ==============================================================================
1418 ==============================================================================
1425 The input line scrolls horizontally if typing goes beyond the right edge
1427 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1430 extern cvar_t r_font_disable_freetype;
1431 void Con_DrawInput (void)
1435 char editlinecopy[MAX_INPUTLINE+1], *text;
1440 if (!key_consoleactive)
1441 return; // don't draw anything
1443 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1444 text = editlinecopy;
1446 // Advanced Console Editing by Radix radix@planetquake.com
1447 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1448 // use strlen of edit_line instead of key_linepos to allow editing
1449 // of early characters w/o erasing
1451 y = (int)strlen(text);
1453 // append enoug nul-bytes to cover the utf8-versions of the cursor too
1454 for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i)
1457 // add the cursor frame
1458 if (r_font_disable_freetype.integer)
1460 // this code is freetype incompatible!
1461 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1463 if (!utf8_enable.integer)
1464 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1465 else if (y + 3 < (int)sizeof(editlinecopy)-1)
1467 int ofs = u8_bytelen(text + key_linepos, 1);
1470 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1474 memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1475 memcpy(text + key_linepos, curbuf, len);
1478 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1482 // text[key_linepos + 1] = 0;
1484 len_out = key_linepos;
1486 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1487 x = vid_conwidth.value * 0.95 - xo; // scroll
1492 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 );
1494 // add a cursor on top of this (when using freetype)
1495 if (!r_font_disable_freetype.integer)
1497 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1499 if (!utf8_enable.integer)
1501 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1508 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1509 memcpy(text, curbuf, len);
1512 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);
1517 // key_line[key_linepos] = 0;
1523 float alignment; // 0 = left, 0.5 = center, 1 = right
1529 const char *continuationString;
1532 int colorindex; // init to -1
1536 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1538 con_text_info_t *ti = (con_text_info_t *) passthrough;
1541 ti->colorindex = -1;
1542 return ti->fontsize * ti->font->maxwidth;
1545 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1546 else if(maxWidth == -1)
1547 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1550 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1551 // Note: this is NOT a Con_Printf, as it could print recursively
1556 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1562 (void) isContinuation;
1566 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1568 con_text_info_t *ti = (con_text_info_t *) passthrough;
1570 if(ti->y < ti->ymin - 0.001)
1572 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1576 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1577 if(isContinuation && *ti->continuationString)
1578 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);
1580 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);
1583 ti->y += ti->fontsize;
1587 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)
1591 int maxlines = (int) floor(height / fontsize + 0.01f);
1594 int continuationWidth = 0;
1596 double t = cl.time; // saved so it won't change
1599 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1600 ti.fontsize = fontsize;
1601 ti.alignment = alignment_x;
1604 ti.ymax = y + height;
1605 ti.continuationString = continuationString;
1608 Con_WordWidthFunc(&ti, NULL, &l, -1);
1609 l = strlen(continuationString);
1610 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1612 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1613 startidx = CON_LINES_COUNT;
1614 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1616 con_lineinfo_t *l = &CON_LINES(i);
1619 if((l->mask & mask_must) != mask_must)
1621 if(l->mask & mask_mustnot)
1623 if(maxage && (l->addtime < t - maxage))
1627 // Calculate its actual height...
1628 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1629 if(lines + mylines >= maxlines)
1631 nskip = lines + mylines - maxlines;
1640 // then center according to the calculated amount of lines...
1642 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1644 // then actually draw
1645 for(i = startidx; i < CON_LINES_COUNT; ++i)
1647 con_lineinfo_t *l = &CON_LINES(i);
1649 if((l->mask & mask_must) != mask_must)
1651 if(l->mask & mask_mustnot)
1653 if(maxage && (l->addtime < t - maxage))
1656 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1666 Draws the last few lines of output transparently over the game top
1669 void Con_DrawNotify (void)
1672 float chatstart, notifystart, inputsize, height;
1674 char temptext[MAX_INPUTLINE];
1678 ConBuffer_FixTimes(&con);
1680 numChatlines = con_chat.integer;
1682 chatpos = con_chatpos.integer;
1684 if (con_notify.integer < 0)
1685 Cvar_SetValueQuick(&con_notify, 0);
1686 if (gamemode == GAME_TRANSFUSION)
1687 v = 8; // vertical offset
1691 // GAME_NEXUIZ: center, otherwise left justify
1692 align = con_notifyalign.value;
1693 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1695 if(gamemode == GAME_NEXUIZ)
1699 if(numChatlines || !con_chatrect.integer)
1703 // first chat, input line, then notify
1705 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1707 else if(chatpos > 0)
1709 // first notify, then (chatpos-1) empty lines, then chat, then input
1711 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1713 else // if(chatpos < 0)
1715 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1717 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1722 // just notify and input
1724 chatstart = 0; // shut off gcc warning
1727 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, "");
1729 if(con_chatrect.integer)
1731 x = con_chatrect_x.value * vid_conwidth.value;
1732 v = con_chatrect_y.value * vid_conheight.value;
1737 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1740 height = numChatlines * con_chatsize.value;
1744 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
1747 if (key_dest == key_message)
1749 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1750 int colorindex = -1;
1752 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL);
1754 // LordHavoc: speedup, and other improvements
1756 dpsnprintf(temptext, sizeof(temptext), "]%s%c", chat_buffer, cursor);
1757 else if(chat_mode == 2)
1759 if(chat_buffer[0] == '#' || chat_buffer[0] == '&') //Channels are yellow, nicks are green
1760 dpsnprintf(temptext, sizeof(temptext), "(IRC)target:^3%s^7%c", chat_buffer, cursor);
1762 dpsnprintf(temptext, sizeof(temptext), "(IRC)target:^2%s^7%c", chat_buffer, cursor);
1764 else if(chat_mode == 3)
1765 dpsnprintf(temptext, sizeof(temptext), "(IRC)message:%s%c", chat_buffer, cursor);
1767 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%c", chat_buffer, cursor);
1769 dpsnprintf(temptext, sizeof(temptext), "say:%s%c", chat_buffer, cursor);
1772 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1773 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1775 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1783 Returns the height of a given console line; calculates it if necessary.
1786 int Con_LineHeight(int lineno)
1788 con_lineinfo_t *li = &CON_LINES(lineno);
1789 if(li->height == -1)
1791 float width = vid_conwidth.value;
1793 con_lineinfo_t *li = &CON_LINES(lineno);
1794 ti.fontsize = con_textsize.value;
1795 ti.font = FONT_CONSOLE;
1796 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1805 Draws a line of the console; returns its height in lines.
1806 If alpha is 0, the line is not drawn, but still wrapped and its height
1810 int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1812 float width = vid_conwidth.value;
1814 con_lineinfo_t *li = &CON_LINES(lineno);
1816 if((li->mask & mask_must) != mask_must)
1818 if((li->mask & mask_mustnot) != 0)
1821 ti.continuationString = "";
1823 ti.fontsize = con_textsize.value;
1824 ti.font = FONT_CONSOLE;
1826 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1831 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1838 Calculates the last visible line index and how much to show of it based on
1842 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1847 if(con_backscroll < 0)
1852 // now count until we saw con_backscroll actual lines
1853 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1854 if((CON_LINES(i).mask & mask_must) == mask_must)
1855 if((CON_LINES(i).mask & mask_mustnot) == 0)
1857 int h = Con_LineHeight(i);
1859 // line is the last visible line?
1861 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1863 *limitlast = lines_seen + h - con_backscroll;
1870 // if we get here, no line was on screen - scroll so that one line is
1872 con_backscroll = lines_seen - 1;
1880 Draws the console with the solid background
1881 The typing input line at the bottom should only be drawn if typing is allowed
1884 void Con_DrawConsole (int lines)
1886 float alpha, alpha0;
1889 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1890 cachepic_t *conbackpic;
1895 if (con_backscroll < 0)
1898 con_vislines = lines;
1900 r_draw2d_force = true;
1902 // draw the background
1903 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
1904 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
1906 sx = scr_conscroll_x.value;
1907 sy = scr_conscroll_y.value;
1908 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL;
1909 sx *= realtime; sy *= realtime;
1910 sx -= floor(sx); sy -= floor(sy);
1911 if (conbackpic && conbackpic->tex != r_texture_notexture)
1912 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1913 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1914 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1915 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1916 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1919 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
1921 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
1923 sx = scr_conscroll2_x.value;
1924 sy = scr_conscroll2_y.value;
1925 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1926 sx *= realtime; sy *= realtime;
1927 sx -= floor(sx); sy -= floor(sy);
1928 if(conbackpic && conbackpic->tex != r_texture_notexture)
1929 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1930 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1931 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1932 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1933 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1936 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
1938 sx = scr_conscroll3_x.value;
1939 sy = scr_conscroll3_y.value;
1940 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1941 sx *= realtime; sy *= realtime;
1942 sx -= floor(sx); sy -= floor(sy);
1943 if(conbackpic && conbackpic->tex != r_texture_notexture)
1944 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1945 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1946 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1947 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1948 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1951 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);
1957 int count = CON_LINES_COUNT;
1958 float ymax = con_vislines - 2 * con_textsize.value;
1959 float y = ymax + con_textsize.value * con_backscroll;
1960 for (i = 0;i < count && y >= 0;i++)
1961 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
1962 // fix any excessive scrollback for the next frame
1963 if (i >= count && y >= 0)
1965 con_backscroll -= (int)(y / con_textsize.value);
1966 if (con_backscroll < 0)
1971 if(CON_LINES_COUNT > 0)
1973 int i, last, limitlast;
1975 float ymax = con_vislines - 2 * con_textsize.value;
1976 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1977 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1978 y = ymax - con_textsize.value;
1981 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1986 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
1988 break; // top of console buffer
1990 break; // top of console window
1997 // draw the input prompt, user text, and cursor if desired
2000 r_draw2d_force = false;
2007 Prints not only map filename, but also
2008 its format (q1/q2/q3/hl) and even its message
2010 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2011 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2012 //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
2013 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2014 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
2018 int i, k, max, p, o, min;
2021 unsigned char buf[1024];
2023 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2024 t = FS_Search(message, 1, true);
2027 if (t->numfilenames > 1)
2028 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2029 len = (unsigned char *)Z_Malloc(t->numfilenames);
2031 for(max=i=0;i<t->numfilenames;i++)
2033 k = (int)strlen(t->filenames[i]);
2043 for(i=0;i<t->numfilenames;i++)
2045 int lumpofs = 0, lumplen = 0;
2046 char *entities = NULL;
2047 const char *data = NULL;
2049 char entfilename[MAX_QPATH];
2050 strlcpy(message, "^1**ERROR**^7", sizeof(message));
2052 f = FS_OpenVirtualFile(t->filenames[i], true);
2055 memset(buf, 0, 1024);
2056 FS_Read(f, buf, 1024);
2057 if (!memcmp(buf, "IBSP", 4))
2059 p = LittleLong(((int *)buf)[1]);
2060 if (p == Q3BSPVERSION)
2062 q3dheader_t *header = (q3dheader_t *)buf;
2063 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2064 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2066 else if (p == Q2BSPVERSION)
2068 q2dheader_t *header = (q2dheader_t *)buf;
2069 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2070 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2073 else if((p = BuffLittleLong(buf)) == BSPVERSION || p == 30)
2075 dheader_t *header = (dheader_t *)buf;
2076 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
2077 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
2081 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2082 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2083 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2084 if (!entities && lumplen >= 10)
2086 FS_Seek(f, lumpofs, SEEK_SET);
2087 entities = (char *)Z_Malloc(lumplen + 1);
2088 FS_Read(f, entities, lumplen);
2092 // if there are entities to parse, a missing message key just
2093 // means there is no title, so clear the message string now
2099 if (!COM_ParseToken_Simple(&data, false, false))
2101 if (com_token[0] == '{')
2103 if (com_token[0] == '}')
2105 // skip leading whitespace
2106 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2107 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2108 keyname[l] = com_token[k+l];
2110 if (!COM_ParseToken_Simple(&data, false, false))
2112 if (developer_extra.integer)
2113 Con_DPrintf("key: %s %s\n", keyname, com_token);
2114 if (!strcmp(keyname, "message"))
2116 // get the message contents
2117 strlcpy(message, com_token, sizeof(message));
2127 *(t->filenames[i]+len[i]+5) = 0;
2130 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
2131 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
2132 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
2133 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
2134 default: strlcpy((char *)buf, "??", sizeof(buf));break;
2136 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
2141 k = *(t->filenames[0]+5+p);
2144 for(i=1;i<t->numfilenames;i++)
2145 if(*(t->filenames[i]+5+p) != k)
2149 if(p > o && completedname && completednamebufferlength > 0)
2151 memset(completedname, 0, completednamebufferlength);
2152 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2162 New function for tab-completion system
2163 Added by EvilTypeGuy
2164 MEGA Thanks to Taniwha
2167 void Con_DisplayList(const char **list)
2169 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2170 const char **walk = list;
2173 len = (int)strlen(*walk);
2181 len = (int)strlen(*list);
2182 if (pos + maxlen >= width) {
2188 for (i = 0; i < (maxlen - len); i++)
2200 SanitizeString strips color tags from the string in
2201 and writes the result on string out
2203 void SanitizeString(char *in, char *out)
2207 if(*in == STRING_COLOR_TAG)
2212 out[0] = STRING_COLOR_TAG;
2216 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2223 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2226 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2228 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2235 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2240 else if (*in != STRING_COLOR_TAG)
2243 *out = qfont_table[*(unsigned char*)in];
2250 // Now it becomes TRICKY :D --blub
2251 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2252 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2253 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2254 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
2255 static int Nicks_matchpos;
2257 // co against <<:BLASTER:>> is true!?
2258 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2262 if(tolower(*a) == tolower(*b))
2276 return (*a < *b) ? -1 : 1;
2280 return (*a < *b) ? -1 : 1;
2284 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2287 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2289 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2290 return Nicks_strncasecmp_nospaces(a, b, a_len);
2291 return strncasecmp(a, b, a_len);
2294 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2296 // ignore non alphanumerics of B
2297 // if A contains a non-alphanumeric, B must contain it as well though!
2300 qboolean alnum_a, alnum_b;
2302 if(tolower(*a) == tolower(*b))
2304 if(*a == 0) // end of both strings, they're equal
2311 // not equal, end of one string?
2316 // ignore non alphanumerics
2317 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2318 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2319 if(!alnum_a) // b must contain this
2320 return (*a < *b) ? -1 : 1;
2323 // otherwise, both are alnum, they're just not equal, return the appropriate number
2325 return (*a < *b) ? -1 : 1;
2331 /* Nicks_CompleteCountPossible
2333 Count the number of possible nicks to complete
2335 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2343 if(!con_nickcompletion.integer)
2346 // changed that to 1
2347 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2350 for(i = 0; i < cl.maxclients; ++i)
2353 if(!cl.scores[p].name[0])
2356 SanitizeString(cl.scores[p].name, name);
2357 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2363 spos = pos - 1; // no need for a minimum of characters :)
2367 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2369 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2370 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2376 if(isCon && spos == 0)
2378 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2384 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2385 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2387 // the sanitized list
2388 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2391 Nicks_matchpos = match;
2394 Nicks_offset[count] = s - (&line[match]);
2395 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2402 void Cmd_CompleteNicksPrint(int count)
2405 for(i = 0; i < count; ++i)
2406 Con_Printf("%s\n", Nicks_list[i]);
2409 void Nicks_CutMatchesNormal(int count)
2411 // cut match 0 down to the longest possible completion
2414 c = strlen(Nicks_sanlist[0]) - 1;
2415 for(i = 1; i < count; ++i)
2417 l = strlen(Nicks_sanlist[i]) - 1;
2421 for(l = 0; l <= c; ++l)
2422 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2428 Nicks_sanlist[0][c+1] = 0;
2429 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2432 unsigned int Nicks_strcleanlen(const char *s)
2437 if( (*s >= 'a' && *s <= 'z') ||
2438 (*s >= 'A' && *s <= 'Z') ||
2439 (*s >= '0' && *s <= '9') ||
2447 void Nicks_CutMatchesAlphaNumeric(int count)
2449 // cut match 0 down to the longest possible completion
2452 char tempstr[sizeof(Nicks_sanlist[0])];
2454 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2456 c = strlen(Nicks_sanlist[0]);
2457 for(i = 0, l = 0; i < (int)c; ++i)
2459 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2460 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2461 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2463 tempstr[l++] = Nicks_sanlist[0][i];
2468 for(i = 1; i < count; ++i)
2471 b = Nicks_sanlist[i];
2481 if(tolower(*a) == tolower(*b))
2487 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2489 // b is alnum, so cut
2496 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2497 Nicks_CutMatchesNormal(count);
2498 //if(!Nicks_sanlist[0][0])
2499 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2501 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2502 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2506 void Nicks_CutMatchesNoSpaces(int count)
2508 // cut match 0 down to the longest possible completion
2511 char tempstr[sizeof(Nicks_sanlist[0])];
2514 c = strlen(Nicks_sanlist[0]);
2515 for(i = 0, l = 0; i < (int)c; ++i)
2517 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2519 tempstr[l++] = Nicks_sanlist[0][i];
2524 for(i = 1; i < count; ++i)
2527 b = Nicks_sanlist[i];
2537 if(tolower(*a) == tolower(*b))
2551 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2552 Nicks_CutMatchesNormal(count);
2553 //if(!Nicks_sanlist[0][0])
2554 //Con_Printf("TS: %s\n", tempstr);
2555 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2557 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2558 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2562 void Nicks_CutMatches(int count)
2564 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2565 Nicks_CutMatchesAlphaNumeric(count);
2566 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2567 Nicks_CutMatchesNoSpaces(count);
2569 Nicks_CutMatchesNormal(count);
2572 const char **Nicks_CompleteBuildList(int count)
2576 // the list is freed by Con_CompleteCommandLine, so create a char**
2577 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2579 for(; bpos < count; ++bpos)
2580 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2582 Nicks_CutMatches(count);
2590 Restores the previous used color, after the autocompleted name.
2592 int Nicks_AddLastColor(char *buffer, int pos)
2594 qboolean quote_added = false;
2596 int color = STRING_COLOR_DEFAULT + '0';
2597 char r = 0, g = 0, b = 0;
2599 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2601 // we'll have to add a quote :)
2602 buffer[pos++] = '\"';
2606 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2608 // add color when no quote was added, or when flags &4?
2610 for(match = Nicks_matchpos-1; match >= 0; --match)
2612 if(buffer[match] == STRING_COLOR_TAG)
2614 if( isdigit(buffer[match+1]) )
2616 color = buffer[match+1];
2619 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2621 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2623 r = buffer[match+2];
2624 g = buffer[match+3];
2625 b = buffer[match+4];
2634 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2636 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2637 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2640 buffer[pos++] = STRING_COLOR_TAG;
2643 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2649 buffer[pos++] = color;
2654 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2657 /*if(!con_nickcompletion.integer)
2658 return; is tested in Nicks_CompletionCountPossible */
2659 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2665 msg = Nicks_list[0];
2666 len = min(size - Nicks_matchpos - 3, strlen(msg));
2667 memcpy(&buffer[Nicks_matchpos], msg, len);
2668 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2669 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2670 buffer[len++] = ' ';
2677 Con_Printf("\n%i possible nicks:\n", n);
2678 Cmd_CompleteNicksPrint(n);
2680 Nicks_CutMatches(n);
2682 msg = Nicks_sanlist[0];
2683 len = min(size - Nicks_matchpos, strlen(msg));
2684 memcpy(&buffer[Nicks_matchpos], msg, len);
2685 buffer[Nicks_matchpos + len] = 0;
2687 return Nicks_matchpos + len;
2694 Con_CompleteCommandLine
2696 New function for tab-completion system
2697 Added by EvilTypeGuy
2698 Thanks to Fett erich@heintz.com
2700 Enhanced to tab-complete map names by [515]
2703 void Con_CompleteCommandLine (void)
2705 const char *cmd = "";
2707 const char **list[4] = {0, 0, 0, 0};
2710 int c, v, a, i, cmd_len, pos, k;
2711 int n; // nicks --blub
2712 const char *space, *patterns;
2714 //find what we want to complete
2719 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2725 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2726 key_line[key_linepos] = 0; //hide them
2728 space = strchr(key_line + 1, ' ');
2729 if(space && pos == (space - key_line) + 1)
2731 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2733 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2734 if(patterns && !*patterns)
2735 patterns = NULL; // get rid of the empty string
2737 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2741 if (GetMapList(s, t, sizeof(t)))
2743 // first move the cursor
2744 key_linepos += (int)strlen(t) - (int)strlen(s);
2746 // and now do the actual work
2748 strlcat(key_line, t, MAX_INPUTLINE);
2749 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2751 // and fix the cursor
2752 if(key_linepos > (int) strlen(key_line))
2753 key_linepos = (int) strlen(key_line);
2762 stringlist_t resultbuf, dirbuf;
2765 // // store completion patterns (space separated) for command foo in con_completion_foo
2766 // set con_completion_foo "foodata/*.foodefault *.foo"
2769 // Note: patterns with slash are always treated as absolute
2770 // patterns; patterns without slash search in the innermost
2771 // directory the user specified. There is no way to "complete into"
2772 // a directory as of now, as directories seem to be unknown to the
2776 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2777 // set con_completion_playdemo "*.dem"
2778 // set con_completion_play "*.wav *.ogg"
2780 // TODO somehow add support for directories; these shall complete
2781 // to their name + an appended slash.
2783 stringlistinit(&resultbuf);
2784 stringlistinit(&dirbuf);
2785 while(COM_ParseToken_Simple(&patterns, false, false))
2788 if(strchr(com_token, '/'))
2790 search = FS_Search(com_token, true, true);
2794 const char *slash = strrchr(s, '/');
2797 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2798 strlcat(t, com_token, sizeof(t));
2799 search = FS_Search(t, true, true);
2802 search = FS_Search(com_token, true, true);
2806 for(i = 0; i < search->numfilenames; ++i)
2807 if(!strncmp(search->filenames[i], s, strlen(s)))
2808 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2809 stringlistappend(&resultbuf, search->filenames[i]);
2810 FS_FreeSearch(search);
2814 // In any case, add directory names
2817 const char *slash = strrchr(s, '/');
2820 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2821 strlcat(t, "*", sizeof(t));
2822 search = FS_Search(t, true, true);
2825 search = FS_Search("*", true, true);
2828 for(i = 0; i < search->numfilenames; ++i)
2829 if(!strncmp(search->filenames[i], s, strlen(s)))
2830 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2831 stringlistappend(&dirbuf, search->filenames[i]);
2832 FS_FreeSearch(search);
2836 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2839 unsigned int matchchars;
2840 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2842 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2845 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2847 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2851 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2852 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2853 for(i = 0; i < dirbuf.numstrings; ++i)
2855 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2857 for(i = 0; i < resultbuf.numstrings; ++i)
2859 Con_Printf("%s\n", resultbuf.strings[i]);
2861 matchchars = sizeof(t) - 1;
2862 if(resultbuf.numstrings > 0)
2864 p = resultbuf.strings[0];
2865 q = resultbuf.strings[resultbuf.numstrings - 1];
2866 for(; *p && *p == *q; ++p, ++q);
2867 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2869 if(dirbuf.numstrings > 0)
2871 p = dirbuf.strings[0];
2872 q = dirbuf.strings[dirbuf.numstrings - 1];
2873 for(; *p && *p == *q; ++p, ++q);
2874 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2876 // now p points to the first non-equal character, or to the end
2877 // of resultbuf.strings[0]. We want to append the characters
2878 // from resultbuf.strings[0] to (not including) p as these are
2879 // the unique prefix
2880 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2883 // first move the cursor
2884 key_linepos += (int)strlen(t) - (int)strlen(s);
2886 // and now do the actual work
2888 strlcat(key_line, t, MAX_INPUTLINE);
2889 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2891 // and fix the cursor
2892 if(key_linepos > (int) strlen(key_line))
2893 key_linepos = (int) strlen(key_line);
2895 stringlistfreecontents(&resultbuf);
2896 stringlistfreecontents(&dirbuf);
2898 return; // bail out, when we complete for a command that wants a file name
2903 // Count number of possible matches and print them
2904 c = Cmd_CompleteCountPossible(s);
2907 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2908 Cmd_CompleteCommandPrint(s);
2910 v = Cvar_CompleteCountPossible(s);
2913 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2914 Cvar_CompleteCvarPrint(s);
2916 a = Cmd_CompleteAliasCountPossible(s);
2919 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2920 Cmd_CompleteAliasPrint(s);
2922 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2925 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2926 Cmd_CompleteNicksPrint(n);
2929 if (!(c + v + a + n)) // No possible matches
2932 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2937 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2939 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2941 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2943 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2945 for (cmd_len = (int)strlen(s);;cmd_len++)
2948 for (i = 0; i < 3; i++)
2950 for (l = list[i];*l;l++)
2951 if ((*l)[cmd_len] != cmd[cmd_len])
2953 // all possible matches share this character, so we continue...
2956 // if all matches ended at the same position, stop
2957 // (this means there is only one match)
2963 // prevent a buffer overrun by limiting cmd_len according to remaining space
2964 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2968 memcpy(&key_line[key_linepos], cmd, cmd_len);
2969 key_linepos += cmd_len;
2970 // if there is only one match, add a space after it
2971 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2974 { // was a nick, might have an offset, and needs colors ;) --blub
2975 key_linepos = pos - Nicks_offset[0];
2976 cmd_len = strlen(Nicks_list[0]);
2977 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2979 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2980 key_linepos += cmd_len;
2981 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2982 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2984 key_line[key_linepos++] = ' ';
2988 // use strlcat to avoid a buffer overrun
2989 key_line[key_linepos] = 0;
2990 strlcat(key_line, s2, sizeof(key_line));
2992 // free the command, cvar, and alias lists
2993 for (i = 0; i < 4; i++)
2995 Mem_Free((void *)list[i]);