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 if (COM_CheckParm ("-noconsole"))
600 if (!(key_consoleactive & KEY_CONSOLEACTIVE_USER))
601 return; // only allow the key bind to turn off console
603 // toggle the 'user wants console' bit
604 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
613 void Con_ClearNotify (void)
616 for(i = 0; i < CON_LINES_COUNT; ++i)
617 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
626 void Con_MessageMode_f (void)
628 key_dest = key_message;
629 chat_mode = 0; // "say"
632 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
633 chat_bufferlen = strlen(chat_buffer);
643 void Con_MessageMode2_f (void)
645 key_dest = key_message;
646 chat_mode = 1; // "say_team"
649 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
650 chat_bufferlen = strlen(chat_buffer);
659 void Con_CommandMode_f (void)
661 key_dest = key_message;
664 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
665 chat_bufferlen = strlen(chat_buffer);
667 chat_mode = -1; // command
675 void Con_CheckResize (void)
680 f = bound(1, con_textsize.value, 128);
681 if(f != con_textsize.value)
682 Cvar_SetValueQuick(&con_textsize, f);
683 width = (int)floor(vid_conwidth.value / con_textsize.value);
684 width = bound(1, width, con.textsize/4);
685 // FIXME uses con in a non abstracted way
687 if (width == con_linewidth)
690 con_linewidth = width;
692 for(i = 0; i < CON_LINES_COUNT; ++i)
693 CON_LINES(i).height = -1; // recalculate when next needed
699 //[515]: the simplest command ever
700 //LordHavoc: not so simple after I made it print usage...
701 static void Con_Maps_f (void)
705 Con_Printf("usage: maps [mapnameprefix]\n");
708 else if (Cmd_Argc() == 2)
709 GetMapList(Cmd_Argv(1), NULL, 0);
711 GetMapList("", NULL, 0);
714 void Con_ConDump_f (void)
720 Con_Printf("usage: condump <filename>\n");
723 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
726 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
729 for(i = 0; i < CON_LINES_COUNT; ++i)
731 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
732 FS_Write(file, "\n", 1);
737 void Con_Clear_f (void)
739 ConBuffer_Clear(&con);
750 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
752 // Allocate a log queue, this will be freed after configs are parsed
753 logq_size = MAX_INPUTLINE;
754 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
757 Cvar_RegisterVariable (&sys_colortranslation);
758 Cvar_RegisterVariable (&sys_specialcharactertranslation);
760 Cvar_RegisterVariable (&log_file);
761 Cvar_RegisterVariable (&log_dest_udp);
763 // support for the classic Quake option
764 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
765 if (COM_CheckParm ("-condebug") != 0)
766 Cvar_SetQuick (&log_file, "qconsole.log");
768 // register our cvars
769 Cvar_RegisterVariable (&con_chat);
770 Cvar_RegisterVariable (&con_chatpos);
771 Cvar_RegisterVariable (&con_chatrect_x);
772 Cvar_RegisterVariable (&con_chatrect_y);
773 Cvar_RegisterVariable (&con_chatrect);
774 Cvar_RegisterVariable (&con_chatsize);
775 Cvar_RegisterVariable (&con_chattime);
776 Cvar_RegisterVariable (&con_chatwidth);
777 Cvar_RegisterVariable (&con_notify);
778 Cvar_RegisterVariable (&con_notifyalign);
779 Cvar_RegisterVariable (&con_notifysize);
780 Cvar_RegisterVariable (&con_notifytime);
781 Cvar_RegisterVariable (&con_textsize);
782 Cvar_RegisterVariable (&con_chatsound);
785 Cvar_RegisterVariable (&con_nickcompletion);
786 Cvar_RegisterVariable (&con_nickcompletion_flags);
788 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
789 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
790 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
792 // register our commands
793 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
794 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
795 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
796 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
797 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
798 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
799 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
801 con_initialized = true;
802 Con_DPrint("Console initialized.\n");
805 void Con_Shutdown (void)
807 ConBuffer_Shutdown(&con);
814 Handles cursor positioning, line wrapping, etc
815 All console printing must go through this in order to be displayed
816 If no console is visible, the notify window will pop up.
819 void Con_PrintToHistory(const char *txt, int mask)
822 // \n goes to next line
823 // \r deletes current line and makes a new one
825 static int cr_pending = 0;
826 static char buf[CON_TEXTSIZE];
827 static int bufpos = 0;
829 if(!con.text) // FIXME uses a non-abstracted property of con
836 ConBuffer_DeleteLastLine(&con);
844 ConBuffer_AddLine(&con, buf, bufpos, mask);
849 ConBuffer_AddLine(&con, buf, bufpos, mask);
853 buf[bufpos++] = *txt;
854 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
856 ConBuffer_AddLine(&con, buf, bufpos, mask);
864 /*! The translation table between the graphical font and plain ASCII --KB */
865 static char qfont_table[256] = {
866 '\0', '#', '#', '#', '#', '.', '#', '#',
867 '#', 9, 10, '#', ' ', 13, '.', '.',
868 '[', ']', '0', '1', '2', '3', '4', '5',
869 '6', '7', '8', '9', '.', '<', '=', '>',
870 ' ', '!', '"', '#', '$', '%', '&', '\'',
871 '(', ')', '*', '+', ',', '-', '.', '/',
872 '0', '1', '2', '3', '4', '5', '6', '7',
873 '8', '9', ':', ';', '<', '=', '>', '?',
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', '[', '\\', ']', '^', '_',
878 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
879 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
880 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
881 'x', 'y', 'z', '{', '|', '}', '~', '<',
883 '<', '=', '>', '#', '#', '.', '#', '#',
884 '#', '#', ' ', '#', ' ', '>', '.', '.',
885 '[', ']', '0', '1', '2', '3', '4', '5',
886 '6', '7', '8', '9', '.', '<', '=', '>',
887 ' ', '!', '"', '#', '$', '%', '&', '\'',
888 '(', ')', '*', '+', ',', '-', '.', '/',
889 '0', '1', '2', '3', '4', '5', '6', '7',
890 '8', '9', ':', ';', '<', '=', '>', '?',
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', '[', '\\', ']', '^', '_',
895 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
896 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
897 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
898 'x', 'y', 'z', '{', '|', '}', '~', '<'
901 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol)
903 rcon_redirect_sock = sock;
904 rcon_redirect_dest = dest;
905 rcon_redirect_proquakeprotocol = proquakeprotocol;
906 if (rcon_redirect_proquakeprotocol)
908 // reserve space for the packet header
909 rcon_redirect_buffer[0] = 0;
910 rcon_redirect_buffer[1] = 0;
911 rcon_redirect_buffer[2] = 0;
912 rcon_redirect_buffer[3] = 0;
913 // this is a reply to a CCREQ_RCON
914 rcon_redirect_buffer[4] = (char)CCREP_RCON;
917 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
918 rcon_redirect_bufferpos = 5;
921 void Con_Rcon_Redirect_Flush(void)
923 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
924 if (rcon_redirect_proquakeprotocol)
926 // update the length in the packet header
927 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
929 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
930 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
931 rcon_redirect_bufferpos = 5;
932 rcon_redirect_proquakeprotocol = false;
935 void Con_Rcon_Redirect_End(void)
937 Con_Rcon_Redirect_Flush();
938 rcon_redirect_dest = NULL;
939 rcon_redirect_sock = NULL;
942 void Con_Rcon_Redirect_Abort(void)
944 rcon_redirect_dest = NULL;
945 rcon_redirect_sock = NULL;
953 /// Adds a character to the rcon buffer.
954 void Con_Rcon_AddChar(int c)
956 if(log_dest_buffer_appending)
958 ++log_dest_buffer_appending;
960 // if this print is in response to an rcon command, add the character
961 // to the rcon redirect buffer
963 if (rcon_redirect_dest)
965 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
966 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
967 Con_Rcon_Redirect_Flush();
969 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
971 if(log_dest_buffer_pos == 0)
972 Log_DestBuffer_Init();
973 log_dest_buffer[log_dest_buffer_pos++] = c;
974 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
975 Log_DestBuffer_Flush();
978 log_dest_buffer_pos = 0;
980 --log_dest_buffer_appending;
984 * Convert an RGB color to its nearest quake color.
985 * I'll cheat on this a bit by translating the colors to HSV first,
986 * S and V decide if it's black or white, otherwise, H will decide the
988 * @param _r Red (0-255)
989 * @param _g Green (0-255)
990 * @param _b Blue (0-255)
991 * @return A quake color character.
993 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
995 float r = ((float)_r)/255.0;
996 float g = ((float)_g)/255.0;
997 float b = ((float)_b)/255.0;
998 float min = min(r, min(g, b));
999 float max = max(r, max(g, b));
1001 int h; ///< Hue angle [0,360]
1002 float s; ///< Saturation [0,1]
1003 float v = max; ///< In HSV v == max [0,1]
1008 s = 1.0 - (min/max);
1010 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1013 // If the value is less than half, return a black color code.
1014 // Otherwise return a white one.
1020 // Let's get the hue angle to define some colors:
1024 h = (int)(60.0 * (g-b)/(max-min))%360;
1026 h = (int)(60.0 * (b-r)/(max-min) + 120);
1027 else // if(max == b) redundant check
1028 h = (int)(60.0 * (r-g)/(max-min) + 240);
1030 if(h < 36) // *red* to orange
1032 else if(h < 80) // orange over *yellow* to evilish-bright-green
1034 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1036 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1038 else if(h < 270) // darkish blue over *dark blue* to cool purple
1040 else if(h < 330) // cool purple over *purple* to ugly swiny red
1042 else // ugly red to red closes the circly
1051 extern cvar_t timestamps;
1052 extern cvar_t timeformat;
1053 extern qboolean sys_nostdout;
1054 void Con_MaskPrint(int additionalmask, const char *msg)
1056 static int mask = 0;
1057 static int index = 0;
1058 static char line[MAX_INPUTLINE];
1062 Con_Rcon_AddChar(*msg);
1063 // if this is the beginning of a new line, print timestamp
1066 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1068 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1069 line[index++] = STRING_COLOR_TAG;
1070 // assert( STRING_COLOR_DEFAULT < 10 )
1071 line[index++] = STRING_COLOR_DEFAULT + '0';
1072 // special color codes for chat messages must always come first
1073 // for Con_PrintToHistory to work properly
1074 if (*msg == 1 || *msg == 2)
1079 if (con_chatsound.value)
1081 if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
1083 if(msg[1] == '\r' && cl.foundtalk2wav)
1084 S_LocalSound ("sound/misc/talk2.wav");
1086 S_LocalSound ("sound/misc/talk.wav");
1090 if (msg[1] == '(' && cl.foundtalk2wav)
1091 S_LocalSound ("sound/misc/talk2.wav");
1093 S_LocalSound ("sound/misc/talk.wav");
1096 mask = CON_MASK_CHAT;
1098 line[index++] = STRING_COLOR_TAG;
1099 line[index++] = '3';
1101 Con_Rcon_AddChar(*msg);
1104 for (;*timestamp;index++, timestamp++)
1105 if (index < (int)sizeof(line) - 2)
1106 line[index] = *timestamp;
1108 mask |= additionalmask;
1110 // append the character
1111 line[index++] = *msg;
1112 // if this is a newline character, we have a complete line to print
1113 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1115 // terminate the line
1119 // send to scrollable buffer
1120 if (con_initialized && cls.state != ca_dedicated)
1122 Con_PrintToHistory(line, mask);
1124 // send to terminal or dedicated server window
1126 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1128 if(sys_specialcharactertranslation.integer)
1135 int ch = u8_getchar(p, &q);
1136 if(ch >= 0xE000 && ch <= 0xE0FF)
1138 *p = qfont_table[ch - 0xE000];
1140 memmove(p+1, q, strlen(q)+1);
1148 if(sys_colortranslation.integer == 1) // ANSI
1150 static char printline[MAX_INPUTLINE * 4 + 3];
1151 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1152 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1157 for(in = line, out = printline; *in; ++in)
1161 case STRING_COLOR_TAG:
1162 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1164 char r = tolower(in[2]);
1165 char g = tolower(in[3]);
1166 char b = tolower(in[4]);
1167 // it's a hex digit already, so the else part needs no check --blub
1168 if(isdigit(r)) r -= '0';
1170 if(isdigit(g)) g -= '0';
1172 if(isdigit(b)) b -= '0';
1175 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1176 in += 3; // 3 only, the switch down there does the fourth
1183 case STRING_COLOR_TAG:
1185 *out++ = STRING_COLOR_TAG;
1191 if(lastcolor == 0) break; else lastcolor = 0;
1192 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1197 if(lastcolor == 1) break; else lastcolor = 1;
1198 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1203 if(lastcolor == 2) break; else lastcolor = 2;
1204 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1209 if(lastcolor == 3) break; else lastcolor = 3;
1210 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1215 if(lastcolor == 4) break; else lastcolor = 4;
1216 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1221 if(lastcolor == 5) break; else lastcolor = 5;
1222 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1227 if(lastcolor == 6) break; else lastcolor = 6;
1228 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1233 // bold normal color
1235 if(lastcolor == 8) break; else lastcolor = 8;
1236 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1239 *out++ = STRING_COLOR_TAG;
1246 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1263 Sys_PrintToTerminal(printline);
1265 else if(sys_colortranslation.integer == 2) // Quake
1267 Sys_PrintToTerminal(line);
1271 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1274 for(in = line, out = printline; *in; ++in)
1278 case STRING_COLOR_TAG:
1281 case STRING_COLOR_RGB_TAG_CHAR:
1282 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1287 *out++ = STRING_COLOR_TAG;
1288 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1291 case STRING_COLOR_TAG:
1293 *out++ = STRING_COLOR_TAG;
1308 *out++ = STRING_COLOR_TAG;
1318 Sys_PrintToTerminal(printline);
1321 // empty the line buffer
1333 void Con_MaskPrintf(int mask, const char *fmt, ...)
1336 char msg[MAX_INPUTLINE];
1338 va_start(argptr,fmt);
1339 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1342 Con_MaskPrint(mask, msg);
1350 void Con_Print(const char *msg)
1352 Con_MaskPrint(CON_MASK_PRINT, msg);
1360 void Con_Printf(const char *fmt, ...)
1363 char msg[MAX_INPUTLINE];
1365 va_start(argptr,fmt);
1366 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1369 Con_MaskPrint(CON_MASK_PRINT, msg);
1377 void Con_DPrint(const char *msg)
1379 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1382 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1390 void Con_DPrintf(const char *fmt, ...)
1393 char msg[MAX_INPUTLINE];
1395 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1398 va_start(argptr,fmt);
1399 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1402 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1407 ==============================================================================
1411 ==============================================================================
1418 The input line scrolls horizontally if typing goes beyond the right edge
1420 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1423 extern cvar_t r_font_disable_freetype;
1424 void Con_DrawInput (void)
1428 char editlinecopy[MAX_INPUTLINE+1], *text;
1433 if (!key_consoleactive)
1434 return; // don't draw anything
1436 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1437 text = editlinecopy;
1439 // Advanced Console Editing by Radix radix@planetquake.com
1440 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1441 // use strlen of edit_line instead of key_linepos to allow editing
1442 // of early characters w/o erasing
1444 y = (int)strlen(text);
1446 // append enoug nul-bytes to cover the utf8-versions of the cursor too
1447 for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i)
1450 // add the cursor frame
1451 if (r_font_disable_freetype.integer)
1453 // this code is freetype incompatible!
1454 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1456 if (!utf8_enable.integer)
1457 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1458 else if (y + 3 < (int)sizeof(editlinecopy)-1)
1460 int ofs = u8_bytelen(text + key_linepos, 1);
1463 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1467 memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1468 memcpy(text + key_linepos, curbuf, len);
1471 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1475 // text[key_linepos + 1] = 0;
1477 len_out = key_linepos;
1479 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1480 x = vid_conwidth.value * 0.95 - xo; // scroll
1485 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 );
1487 // add a cursor on top of this (when using freetype)
1488 if (!r_font_disable_freetype.integer)
1490 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1492 if (!utf8_enable.integer)
1494 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1501 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1502 memcpy(text, curbuf, len);
1505 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);
1510 // key_line[key_linepos] = 0;
1516 float alignment; // 0 = left, 0.5 = center, 1 = right
1522 const char *continuationString;
1525 int colorindex; // init to -1
1529 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1531 con_text_info_t *ti = (con_text_info_t *) passthrough;
1534 ti->colorindex = -1;
1535 return ti->fontsize * ti->font->maxwidth;
1538 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1539 else if(maxWidth == -1)
1540 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1543 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1544 // Note: this is NOT a Con_Printf, as it could print recursively
1549 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1555 (void) isContinuation;
1559 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1561 con_text_info_t *ti = (con_text_info_t *) passthrough;
1563 if(ti->y < ti->ymin - 0.001)
1565 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1569 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1570 if(isContinuation && *ti->continuationString)
1571 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);
1573 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);
1576 ti->y += ti->fontsize;
1580 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)
1584 int maxlines = (int) floor(height / fontsize + 0.01f);
1587 int continuationWidth = 0;
1589 double t = cl.time; // saved so it won't change
1592 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1593 ti.fontsize = fontsize;
1594 ti.alignment = alignment_x;
1597 ti.ymax = y + height;
1598 ti.continuationString = continuationString;
1601 Con_WordWidthFunc(&ti, NULL, &l, -1);
1602 l = strlen(continuationString);
1603 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1605 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1606 startidx = CON_LINES_COUNT;
1607 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1609 con_lineinfo_t *l = &CON_LINES(i);
1612 if((l->mask & mask_must) != mask_must)
1614 if(l->mask & mask_mustnot)
1616 if(maxage && (l->addtime < t - maxage))
1620 // Calculate its actual height...
1621 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1622 if(lines + mylines >= maxlines)
1624 nskip = lines + mylines - maxlines;
1633 // then center according to the calculated amount of lines...
1635 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1637 // then actually draw
1638 for(i = startidx; i < CON_LINES_COUNT; ++i)
1640 con_lineinfo_t *l = &CON_LINES(i);
1642 if((l->mask & mask_must) != mask_must)
1644 if(l->mask & mask_mustnot)
1646 if(maxage && (l->addtime < t - maxage))
1649 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1659 Draws the last few lines of output transparently over the game top
1662 void Con_DrawNotify (void)
1665 float chatstart, notifystart, inputsize, height;
1667 char temptext[MAX_INPUTLINE];
1671 ConBuffer_FixTimes(&con);
1673 numChatlines = con_chat.integer;
1675 chatpos = con_chatpos.integer;
1677 if (con_notify.integer < 0)
1678 Cvar_SetValueQuick(&con_notify, 0);
1679 if (gamemode == GAME_TRANSFUSION)
1680 v = 8; // vertical offset
1684 // GAME_NEXUIZ: center, otherwise left justify
1685 align = con_notifyalign.value;
1686 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1688 if(gamemode == GAME_NEXUIZ)
1692 if(numChatlines || !con_chatrect.integer)
1696 // first chat, input line, then notify
1698 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1700 else if(chatpos > 0)
1702 // first notify, then (chatpos-1) empty lines, then chat, then input
1704 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1706 else // if(chatpos < 0)
1708 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1710 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1715 // just notify and input
1717 chatstart = 0; // shut off gcc warning
1720 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, "");
1722 if(con_chatrect.integer)
1724 x = con_chatrect_x.value * vid_conwidth.value;
1725 v = con_chatrect_y.value * vid_conheight.value;
1730 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1733 height = numChatlines * con_chatsize.value;
1737 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
1740 if (key_dest == key_message)
1742 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1743 int colorindex = -1;
1745 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL);
1747 // LordHavoc: speedup, and other improvements
1749 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1751 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1753 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1756 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1757 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1759 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1767 Returns the height of a given console line; calculates it if necessary.
1770 int Con_LineHeight(int lineno)
1772 con_lineinfo_t *li = &CON_LINES(lineno);
1773 if(li->height == -1)
1775 float width = vid_conwidth.value;
1777 con_lineinfo_t *li = &CON_LINES(lineno);
1778 ti.fontsize = con_textsize.value;
1779 ti.font = FONT_CONSOLE;
1780 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1789 Draws a line of the console; returns its height in lines.
1790 If alpha is 0, the line is not drawn, but still wrapped and its height
1794 int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1796 float width = vid_conwidth.value;
1798 con_lineinfo_t *li = &CON_LINES(lineno);
1800 if((li->mask & mask_must) != mask_must)
1802 if((li->mask & mask_mustnot) != 0)
1805 ti.continuationString = "";
1807 ti.fontsize = con_textsize.value;
1808 ti.font = FONT_CONSOLE;
1810 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1815 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1822 Calculates the last visible line index and how much to show of it based on
1826 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1831 if(con_backscroll < 0)
1836 // now count until we saw con_backscroll actual lines
1837 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1838 if((CON_LINES(i).mask & mask_must) == mask_must)
1839 if((CON_LINES(i).mask & mask_mustnot) == 0)
1841 int h = Con_LineHeight(i);
1843 // line is the last visible line?
1845 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1847 *limitlast = lines_seen + h - con_backscroll;
1854 // if we get here, no line was on screen - scroll so that one line is
1856 con_backscroll = lines_seen - 1;
1864 Draws the console with the solid background
1865 The typing input line at the bottom should only be drawn if typing is allowed
1868 void Con_DrawConsole (int lines)
1870 float alpha, alpha0;
1873 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1874 cachepic_t *conbackpic;
1879 if (con_backscroll < 0)
1882 con_vislines = lines;
1884 r_draw2d_force = true;
1886 // draw the background
1887 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
1888 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
1890 sx = scr_conscroll_x.value;
1891 sy = scr_conscroll_y.value;
1892 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL;
1893 sx *= realtime; sy *= realtime;
1894 sx -= floor(sx); sy -= floor(sy);
1895 if (conbackpic && conbackpic->tex != r_texture_notexture)
1896 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1897 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1898 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1899 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1900 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1903 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
1905 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
1907 sx = scr_conscroll2_x.value;
1908 sy = scr_conscroll2_y.value;
1909 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1910 sx *= realtime; sy *= realtime;
1911 sx -= floor(sx); sy -= floor(sy);
1912 if(conbackpic && conbackpic->tex != r_texture_notexture)
1913 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1914 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1915 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1916 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1917 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1920 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
1922 sx = scr_conscroll3_x.value;
1923 sy = scr_conscroll3_y.value;
1924 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1925 sx *= realtime; sy *= realtime;
1926 sx -= floor(sx); sy -= floor(sy);
1927 if(conbackpic && conbackpic->tex != r_texture_notexture)
1928 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1929 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1930 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1931 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1932 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1935 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);
1941 int count = CON_LINES_COUNT;
1942 float ymax = con_vislines - 2 * con_textsize.value;
1943 float y = ymax + con_textsize.value * con_backscroll;
1944 for (i = 0;i < count && y >= 0;i++)
1945 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
1946 // fix any excessive scrollback for the next frame
1947 if (i >= count && y >= 0)
1949 con_backscroll -= (int)(y / con_textsize.value);
1950 if (con_backscroll < 0)
1955 if(CON_LINES_COUNT > 0)
1957 int i, last, limitlast;
1959 float ymax = con_vislines - 2 * con_textsize.value;
1960 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1961 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1962 y = ymax - con_textsize.value;
1965 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1970 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
1972 break; // top of console buffer
1974 break; // top of console window
1981 // draw the input prompt, user text, and cursor if desired
1984 r_draw2d_force = false;
1991 Prints not only map filename, but also
1992 its format (q1/q2/q3/hl) and even its message
1994 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1995 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1996 //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
1997 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1998 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
2002 int i, k, max, p, o, min;
2005 unsigned char buf[1024];
2007 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2008 t = FS_Search(message, 1, true);
2011 if (t->numfilenames > 1)
2012 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2013 len = (unsigned char *)Z_Malloc(t->numfilenames);
2015 for(max=i=0;i<t->numfilenames;i++)
2017 k = (int)strlen(t->filenames[i]);
2027 for(i=0;i<t->numfilenames;i++)
2029 int lumpofs = 0, lumplen = 0;
2030 char *entities = NULL;
2031 const char *data = NULL;
2033 char entfilename[MAX_QPATH];
2034 strlcpy(message, "^1**ERROR**^7", sizeof(message));
2036 f = FS_OpenVirtualFile(t->filenames[i], true);
2039 memset(buf, 0, 1024);
2040 FS_Read(f, buf, 1024);
2041 if (!memcmp(buf, "IBSP", 4))
2043 p = LittleLong(((int *)buf)[1]);
2044 if (p == Q3BSPVERSION)
2046 q3dheader_t *header = (q3dheader_t *)buf;
2047 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2048 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2050 else if (p == Q2BSPVERSION)
2052 q2dheader_t *header = (q2dheader_t *)buf;
2053 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2054 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2057 else if((p = BuffLittleLong(buf)) == BSPVERSION || p == 30)
2059 dheader_t *header = (dheader_t *)buf;
2060 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
2061 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
2065 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2066 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2067 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2068 if (!entities && lumplen >= 10)
2070 FS_Seek(f, lumpofs, SEEK_SET);
2071 entities = (char *)Z_Malloc(lumplen + 1);
2072 FS_Read(f, entities, lumplen);
2076 // if there are entities to parse, a missing message key just
2077 // means there is no title, so clear the message string now
2083 if (!COM_ParseToken_Simple(&data, false, false))
2085 if (com_token[0] == '{')
2087 if (com_token[0] == '}')
2089 // skip leading whitespace
2090 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2091 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2092 keyname[l] = com_token[k+l];
2094 if (!COM_ParseToken_Simple(&data, false, false))
2096 if (developer_extra.integer)
2097 Con_DPrintf("key: %s %s\n", keyname, com_token);
2098 if (!strcmp(keyname, "message"))
2100 // get the message contents
2101 strlcpy(message, com_token, sizeof(message));
2111 *(t->filenames[i]+len[i]+5) = 0;
2114 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
2115 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
2116 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
2117 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
2118 default: strlcpy((char *)buf, "??", sizeof(buf));break;
2120 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
2125 k = *(t->filenames[0]+5+p);
2128 for(i=1;i<t->numfilenames;i++)
2129 if(*(t->filenames[i]+5+p) != k)
2133 if(p > o && completedname && completednamebufferlength > 0)
2135 memset(completedname, 0, completednamebufferlength);
2136 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2146 New function for tab-completion system
2147 Added by EvilTypeGuy
2148 MEGA Thanks to Taniwha
2151 void Con_DisplayList(const char **list)
2153 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2154 const char **walk = list;
2157 len = (int)strlen(*walk);
2165 len = (int)strlen(*list);
2166 if (pos + maxlen >= width) {
2172 for (i = 0; i < (maxlen - len); i++)
2184 SanitizeString strips color tags from the string in
2185 and writes the result on string out
2187 void SanitizeString(char *in, char *out)
2191 if(*in == STRING_COLOR_TAG)
2196 out[0] = STRING_COLOR_TAG;
2200 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2207 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2210 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2212 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2219 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2224 else if (*in != STRING_COLOR_TAG)
2227 *out = qfont_table[*(unsigned char*)in];
2234 // Now it becomes TRICKY :D --blub
2235 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2236 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2237 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2238 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
2239 static int Nicks_matchpos;
2241 // co against <<:BLASTER:>> is true!?
2242 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2246 if(tolower(*a) == tolower(*b))
2260 return (*a < *b) ? -1 : 1;
2264 return (*a < *b) ? -1 : 1;
2268 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2271 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2273 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2274 return Nicks_strncasecmp_nospaces(a, b, a_len);
2275 return strncasecmp(a, b, a_len);
2278 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2280 // ignore non alphanumerics of B
2281 // if A contains a non-alphanumeric, B must contain it as well though!
2284 qboolean alnum_a, alnum_b;
2286 if(tolower(*a) == tolower(*b))
2288 if(*a == 0) // end of both strings, they're equal
2295 // not equal, end of one string?
2300 // ignore non alphanumerics
2301 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2302 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2303 if(!alnum_a) // b must contain this
2304 return (*a < *b) ? -1 : 1;
2307 // otherwise, both are alnum, they're just not equal, return the appropriate number
2309 return (*a < *b) ? -1 : 1;
2315 /* Nicks_CompleteCountPossible
2317 Count the number of possible nicks to complete
2319 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2327 if(!con_nickcompletion.integer)
2330 // changed that to 1
2331 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2334 for(i = 0; i < cl.maxclients; ++i)
2337 if(!cl.scores[p].name[0])
2340 SanitizeString(cl.scores[p].name, name);
2341 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2347 spos = pos - 1; // no need for a minimum of characters :)
2351 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2353 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2354 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2360 if(isCon && spos == 0)
2362 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2368 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2369 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2371 // the sanitized list
2372 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2375 Nicks_matchpos = match;
2378 Nicks_offset[count] = s - (&line[match]);
2379 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2386 void Cmd_CompleteNicksPrint(int count)
2389 for(i = 0; i < count; ++i)
2390 Con_Printf("%s\n", Nicks_list[i]);
2393 void Nicks_CutMatchesNormal(int count)
2395 // cut match 0 down to the longest possible completion
2398 c = strlen(Nicks_sanlist[0]) - 1;
2399 for(i = 1; i < count; ++i)
2401 l = strlen(Nicks_sanlist[i]) - 1;
2405 for(l = 0; l <= c; ++l)
2406 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2412 Nicks_sanlist[0][c+1] = 0;
2413 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2416 unsigned int Nicks_strcleanlen(const char *s)
2421 if( (*s >= 'a' && *s <= 'z') ||
2422 (*s >= 'A' && *s <= 'Z') ||
2423 (*s >= '0' && *s <= '9') ||
2431 void Nicks_CutMatchesAlphaNumeric(int count)
2433 // cut match 0 down to the longest possible completion
2436 char tempstr[sizeof(Nicks_sanlist[0])];
2438 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2440 c = strlen(Nicks_sanlist[0]);
2441 for(i = 0, l = 0; i < (int)c; ++i)
2443 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2444 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2445 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2447 tempstr[l++] = Nicks_sanlist[0][i];
2452 for(i = 1; i < count; ++i)
2455 b = Nicks_sanlist[i];
2465 if(tolower(*a) == tolower(*b))
2471 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2473 // b is alnum, so cut
2480 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2481 Nicks_CutMatchesNormal(count);
2482 //if(!Nicks_sanlist[0][0])
2483 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2485 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2486 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2490 void Nicks_CutMatchesNoSpaces(int count)
2492 // cut match 0 down to the longest possible completion
2495 char tempstr[sizeof(Nicks_sanlist[0])];
2498 c = strlen(Nicks_sanlist[0]);
2499 for(i = 0, l = 0; i < (int)c; ++i)
2501 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2503 tempstr[l++] = Nicks_sanlist[0][i];
2508 for(i = 1; i < count; ++i)
2511 b = Nicks_sanlist[i];
2521 if(tolower(*a) == tolower(*b))
2535 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2536 Nicks_CutMatchesNormal(count);
2537 //if(!Nicks_sanlist[0][0])
2538 //Con_Printf("TS: %s\n", tempstr);
2539 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2541 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2542 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2546 void Nicks_CutMatches(int count)
2548 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2549 Nicks_CutMatchesAlphaNumeric(count);
2550 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2551 Nicks_CutMatchesNoSpaces(count);
2553 Nicks_CutMatchesNormal(count);
2556 const char **Nicks_CompleteBuildList(int count)
2560 // the list is freed by Con_CompleteCommandLine, so create a char**
2561 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2563 for(; bpos < count; ++bpos)
2564 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2566 Nicks_CutMatches(count);
2574 Restores the previous used color, after the autocompleted name.
2576 int Nicks_AddLastColor(char *buffer, int pos)
2578 qboolean quote_added = false;
2580 int color = STRING_COLOR_DEFAULT + '0';
2581 char r = 0, g = 0, b = 0;
2583 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2585 // we'll have to add a quote :)
2586 buffer[pos++] = '\"';
2590 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2592 // add color when no quote was added, or when flags &4?
2594 for(match = Nicks_matchpos-1; match >= 0; --match)
2596 if(buffer[match] == STRING_COLOR_TAG)
2598 if( isdigit(buffer[match+1]) )
2600 color = buffer[match+1];
2603 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2605 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2607 r = buffer[match+2];
2608 g = buffer[match+3];
2609 b = buffer[match+4];
2618 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2620 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2621 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2624 buffer[pos++] = STRING_COLOR_TAG;
2627 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2633 buffer[pos++] = color;
2638 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2641 /*if(!con_nickcompletion.integer)
2642 return; is tested in Nicks_CompletionCountPossible */
2643 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2649 msg = Nicks_list[0];
2650 len = min(size - Nicks_matchpos - 3, strlen(msg));
2651 memcpy(&buffer[Nicks_matchpos], msg, len);
2652 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2653 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2654 buffer[len++] = ' ';
2661 Con_Printf("\n%i possible nicks:\n", n);
2662 Cmd_CompleteNicksPrint(n);
2664 Nicks_CutMatches(n);
2666 msg = Nicks_sanlist[0];
2667 len = min(size - Nicks_matchpos, strlen(msg));
2668 memcpy(&buffer[Nicks_matchpos], msg, len);
2669 buffer[Nicks_matchpos + len] = 0;
2671 return Nicks_matchpos + len;
2678 Con_CompleteCommandLine
2680 New function for tab-completion system
2681 Added by EvilTypeGuy
2682 Thanks to Fett erich@heintz.com
2684 Enhanced to tab-complete map names by [515]
2687 void Con_CompleteCommandLine (void)
2689 const char *cmd = "";
2691 const char **list[4] = {0, 0, 0, 0};
2694 int c, v, a, i, cmd_len, pos, k;
2695 int n; // nicks --blub
2696 const char *space, *patterns;
2698 //find what we want to complete
2703 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2709 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2710 key_line[key_linepos] = 0; //hide them
2712 space = strchr(key_line + 1, ' ');
2713 if(space && pos == (space - key_line) + 1)
2715 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2717 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2718 if(patterns && !*patterns)
2719 patterns = NULL; // get rid of the empty string
2721 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2725 if (GetMapList(s, t, sizeof(t)))
2727 // first move the cursor
2728 key_linepos += (int)strlen(t) - (int)strlen(s);
2730 // and now do the actual work
2732 strlcat(key_line, t, MAX_INPUTLINE);
2733 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2735 // and fix the cursor
2736 if(key_linepos > (int) strlen(key_line))
2737 key_linepos = (int) strlen(key_line);
2746 stringlist_t resultbuf, dirbuf;
2749 // // store completion patterns (space separated) for command foo in con_completion_foo
2750 // set con_completion_foo "foodata/*.foodefault *.foo"
2753 // Note: patterns with slash are always treated as absolute
2754 // patterns; patterns without slash search in the innermost
2755 // directory the user specified. There is no way to "complete into"
2756 // a directory as of now, as directories seem to be unknown to the
2760 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2761 // set con_completion_playdemo "*.dem"
2762 // set con_completion_play "*.wav *.ogg"
2764 // TODO somehow add support for directories; these shall complete
2765 // to their name + an appended slash.
2767 stringlistinit(&resultbuf);
2768 stringlistinit(&dirbuf);
2769 while(COM_ParseToken_Simple(&patterns, false, false))
2772 if(strchr(com_token, '/'))
2774 search = FS_Search(com_token, true, true);
2778 const char *slash = strrchr(s, '/');
2781 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2782 strlcat(t, com_token, sizeof(t));
2783 search = FS_Search(t, true, true);
2786 search = FS_Search(com_token, true, true);
2790 for(i = 0; i < search->numfilenames; ++i)
2791 if(!strncmp(search->filenames[i], s, strlen(s)))
2792 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2793 stringlistappend(&resultbuf, search->filenames[i]);
2794 FS_FreeSearch(search);
2798 // In any case, add directory names
2801 const char *slash = strrchr(s, '/');
2804 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2805 strlcat(t, "*", sizeof(t));
2806 search = FS_Search(t, true, true);
2809 search = FS_Search("*", true, true);
2812 for(i = 0; i < search->numfilenames; ++i)
2813 if(!strncmp(search->filenames[i], s, strlen(s)))
2814 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2815 stringlistappend(&dirbuf, search->filenames[i]);
2816 FS_FreeSearch(search);
2820 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2823 unsigned int matchchars;
2824 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2826 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2829 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2831 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2835 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2836 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2837 for(i = 0; i < dirbuf.numstrings; ++i)
2839 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2841 for(i = 0; i < resultbuf.numstrings; ++i)
2843 Con_Printf("%s\n", resultbuf.strings[i]);
2845 matchchars = sizeof(t) - 1;
2846 if(resultbuf.numstrings > 0)
2848 p = resultbuf.strings[0];
2849 q = resultbuf.strings[resultbuf.numstrings - 1];
2850 for(; *p && *p == *q; ++p, ++q);
2851 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2853 if(dirbuf.numstrings > 0)
2855 p = dirbuf.strings[0];
2856 q = dirbuf.strings[dirbuf.numstrings - 1];
2857 for(; *p && *p == *q; ++p, ++q);
2858 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2860 // now p points to the first non-equal character, or to the end
2861 // of resultbuf.strings[0]. We want to append the characters
2862 // from resultbuf.strings[0] to (not including) p as these are
2863 // the unique prefix
2864 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2867 // first move the cursor
2868 key_linepos += (int)strlen(t) - (int)strlen(s);
2870 // and now do the actual work
2872 strlcat(key_line, t, MAX_INPUTLINE);
2873 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2875 // and fix the cursor
2876 if(key_linepos > (int) strlen(key_line))
2877 key_linepos = (int) strlen(key_line);
2879 stringlistfreecontents(&resultbuf);
2880 stringlistfreecontents(&dirbuf);
2882 return; // bail out, when we complete for a command that wants a file name
2887 // Count number of possible matches and print them
2888 c = Cmd_CompleteCountPossible(s);
2891 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2892 Cmd_CompleteCommandPrint(s);
2894 v = Cvar_CompleteCountPossible(s);
2897 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2898 Cvar_CompleteCvarPrint(s);
2900 a = Cmd_CompleteAliasCountPossible(s);
2903 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2904 Cmd_CompleteAliasPrint(s);
2906 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2909 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2910 Cmd_CompleteNicksPrint(n);
2913 if (!(c + v + a + n)) // No possible matches
2916 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2921 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2923 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2925 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2927 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2929 for (cmd_len = (int)strlen(s);;cmd_len++)
2932 for (i = 0; i < 3; i++)
2934 for (l = list[i];*l;l++)
2935 if ((*l)[cmd_len] != cmd[cmd_len])
2937 // all possible matches share this character, so we continue...
2940 // if all matches ended at the same position, stop
2941 // (this means there is only one match)
2947 // prevent a buffer overrun by limiting cmd_len according to remaining space
2948 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2952 memcpy(&key_line[key_linepos], cmd, cmd_len);
2953 key_linepos += cmd_len;
2954 // if there is only one match, add a space after it
2955 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2958 { // was a nick, might have an offset, and needs colors ;) --blub
2959 key_linepos = pos - Nicks_offset[0];
2960 cmd_len = strlen(Nicks_list[0]);
2961 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2963 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2964 key_linepos += cmd_len;
2965 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2966 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2968 key_line[key_linepos++] = ' ';
2972 // use strlcat to avoid a buffer overrun
2973 key_line[key_linepos] = 0;
2974 strlcat(key_line, s2, sizeof(key_line));
2976 // free the command, cvar, and alias lists
2977 for (i = 0; i < 4; i++)
2979 Mem_Free((void *)list[i]);