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__)
33 float con_cursorspeed = 4;
35 // lines up from bottom to display
39 void *con_mutex = NULL;
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 if (COM_CheckParm ("-noconsole"))
602 if (!(key_consoleactive & KEY_CONSOLEACTIVE_USER))
603 return; // only allow the key bind to turn off console
605 // toggle the 'user wants console' bit
606 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
615 void Con_ClearNotify (void)
618 for(i = 0; i < CON_LINES_COUNT; ++i)
619 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
628 void Con_MessageMode_f (void)
630 key_dest = key_message;
631 chat_mode = 0; // "say"
634 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
635 chat_bufferlen = strlen(chat_buffer);
645 void Con_MessageMode2_f (void)
647 key_dest = key_message;
648 chat_mode = 1; // "say_team"
651 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
652 chat_bufferlen = strlen(chat_buffer);
661 void Con_CommandMode_f (void)
663 key_dest = key_message;
666 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
667 chat_bufferlen = strlen(chat_buffer);
669 chat_mode = -1; // command
677 void Con_CheckResize (void)
682 f = bound(1, con_textsize.value, 128);
683 if(f != con_textsize.value)
684 Cvar_SetValueQuick(&con_textsize, f);
685 width = (int)floor(vid_conwidth.value / con_textsize.value);
686 width = bound(1, width, con.textsize/4);
687 // FIXME uses con in a non abstracted way
689 if (width == con_linewidth)
692 con_linewidth = width;
694 for(i = 0; i < CON_LINES_COUNT; ++i)
695 CON_LINES(i).height = -1; // recalculate when next needed
701 //[515]: the simplest command ever
702 //LordHavoc: not so simple after I made it print usage...
703 static void Con_Maps_f (void)
707 Con_Printf("usage: maps [mapnameprefix]\n");
710 else if (Cmd_Argc() == 2)
711 GetMapList(Cmd_Argv(1), NULL, 0);
713 GetMapList("", NULL, 0);
716 void Con_ConDump_f (void)
722 Con_Printf("usage: condump <filename>\n");
725 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
728 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
731 if (con_mutex) Thread_LockMutex(con_mutex);
732 for(i = 0; i < CON_LINES_COUNT; ++i)
734 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
735 FS_Write(file, "\n", 1);
737 if (con_mutex) Thread_UnlockMutex(con_mutex);
741 void Con_Clear_f (void)
743 if (con_mutex) Thread_LockMutex(con_mutex);
744 ConBuffer_Clear(&con);
745 if (con_mutex) Thread_UnlockMutex(con_mutex);
756 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
757 if (Thread_HasThreads())
758 con_mutex = Thread_CreateMutex();
760 // Allocate a log queue, this will be freed after configs are parsed
761 logq_size = MAX_INPUTLINE;
762 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
765 Cvar_RegisterVariable (&sys_colortranslation);
766 Cvar_RegisterVariable (&sys_specialcharactertranslation);
768 Cvar_RegisterVariable (&log_file);
769 Cvar_RegisterVariable (&log_dest_udp);
771 // support for the classic Quake option
772 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
773 if (COM_CheckParm ("-condebug") != 0)
774 Cvar_SetQuick (&log_file, "qconsole.log");
776 // register our cvars
777 Cvar_RegisterVariable (&con_chat);
778 Cvar_RegisterVariable (&con_chatpos);
779 Cvar_RegisterVariable (&con_chatrect_x);
780 Cvar_RegisterVariable (&con_chatrect_y);
781 Cvar_RegisterVariable (&con_chatrect);
782 Cvar_RegisterVariable (&con_chatsize);
783 Cvar_RegisterVariable (&con_chattime);
784 Cvar_RegisterVariable (&con_chatwidth);
785 Cvar_RegisterVariable (&con_notify);
786 Cvar_RegisterVariable (&con_notifyalign);
787 Cvar_RegisterVariable (&con_notifysize);
788 Cvar_RegisterVariable (&con_notifytime);
789 Cvar_RegisterVariable (&con_textsize);
790 Cvar_RegisterVariable (&con_chatsound);
793 Cvar_RegisterVariable (&con_nickcompletion);
794 Cvar_RegisterVariable (&con_nickcompletion_flags);
796 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
797 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
798 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
800 // register our commands
801 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
802 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
803 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
804 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
805 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
806 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
807 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
809 con_initialized = true;
810 Con_DPrint("Console initialized.\n");
813 void Con_Shutdown (void)
815 if (con_mutex) Thread_LockMutex(con_mutex);
816 ConBuffer_Shutdown(&con);
817 if (con_mutex) Thread_UnlockMutex(con_mutex);
818 if (con_mutex) Thread_DestroyMutex(con_mutex);con_mutex = NULL;
825 Handles cursor positioning, line wrapping, etc
826 All console printing must go through this in order to be displayed
827 If no console is visible, the notify window will pop up.
830 void Con_PrintToHistory(const char *txt, int mask)
833 // \n goes to next line
834 // \r deletes current line and makes a new one
836 static int cr_pending = 0;
837 static char buf[CON_TEXTSIZE];
838 static int bufpos = 0;
840 if(!con.text) // FIXME uses a non-abstracted property of con
843 if (con_mutex) Thread_LockMutex(con_mutex);
848 ConBuffer_DeleteLastLine(&con);
856 ConBuffer_AddLine(&con, buf, bufpos, mask);
861 ConBuffer_AddLine(&con, buf, bufpos, mask);
865 buf[bufpos++] = *txt;
866 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
868 ConBuffer_AddLine(&con, buf, bufpos, mask);
874 if (con_mutex) Thread_UnlockMutex(con_mutex);
877 /*! The translation table between the graphical font and plain ASCII --KB */
878 static char qfont_table[256] = {
879 '\0', '#', '#', '#', '#', '.', '#', '#',
880 '#', 9, 10, '#', ' ', 13, '.', '.',
881 '[', ']', '0', '1', '2', '3', '4', '5',
882 '6', '7', '8', '9', '.', '<', '=', '>',
883 ' ', '!', '"', '#', '$', '%', '&', '\'',
884 '(', ')', '*', '+', ',', '-', '.', '/',
885 '0', '1', '2', '3', '4', '5', '6', '7',
886 '8', '9', ':', ';', '<', '=', '>', '?',
887 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
888 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
889 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
890 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
891 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
892 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
893 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
894 'x', 'y', 'z', '{', '|', '}', '~', '<',
896 '<', '=', '>', '#', '#', '.', '#', '#',
897 '#', '#', ' ', '#', ' ', '>', '.', '.',
898 '[', ']', '0', '1', '2', '3', '4', '5',
899 '6', '7', '8', '9', '.', '<', '=', '>',
900 ' ', '!', '"', '#', '$', '%', '&', '\'',
901 '(', ')', '*', '+', ',', '-', '.', '/',
902 '0', '1', '2', '3', '4', '5', '6', '7',
903 '8', '9', ':', ';', '<', '=', '>', '?',
904 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
905 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
906 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
907 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
908 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
909 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
910 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
911 'x', 'y', 'z', '{', '|', '}', '~', '<'
914 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol)
916 rcon_redirect_sock = sock;
917 rcon_redirect_dest = dest;
918 rcon_redirect_proquakeprotocol = proquakeprotocol;
919 if (rcon_redirect_proquakeprotocol)
921 // reserve space for the packet header
922 rcon_redirect_buffer[0] = 0;
923 rcon_redirect_buffer[1] = 0;
924 rcon_redirect_buffer[2] = 0;
925 rcon_redirect_buffer[3] = 0;
926 // this is a reply to a CCREQ_RCON
927 rcon_redirect_buffer[4] = (char)CCREP_RCON;
930 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
931 rcon_redirect_bufferpos = 5;
934 void Con_Rcon_Redirect_Flush(void)
936 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
937 if (rcon_redirect_proquakeprotocol)
939 // update the length in the packet header
940 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
942 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
943 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
944 rcon_redirect_bufferpos = 5;
945 rcon_redirect_proquakeprotocol = false;
948 void Con_Rcon_Redirect_End(void)
950 Con_Rcon_Redirect_Flush();
951 rcon_redirect_dest = NULL;
952 rcon_redirect_sock = NULL;
955 void Con_Rcon_Redirect_Abort(void)
957 rcon_redirect_dest = NULL;
958 rcon_redirect_sock = NULL;
966 /// Adds a character to the rcon buffer.
967 void Con_Rcon_AddChar(int c)
969 if(log_dest_buffer_appending)
971 ++log_dest_buffer_appending;
973 // if this print is in response to an rcon command, add the character
974 // to the rcon redirect buffer
976 if (rcon_redirect_dest)
978 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
979 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
980 Con_Rcon_Redirect_Flush();
982 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
984 if(log_dest_buffer_pos == 0)
985 Log_DestBuffer_Init();
986 log_dest_buffer[log_dest_buffer_pos++] = c;
987 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
988 Log_DestBuffer_Flush();
991 log_dest_buffer_pos = 0;
993 --log_dest_buffer_appending;
997 * Convert an RGB color to its nearest quake color.
998 * I'll cheat on this a bit by translating the colors to HSV first,
999 * S and V decide if it's black or white, otherwise, H will decide the
1001 * @param _r Red (0-255)
1002 * @param _g Green (0-255)
1003 * @param _b Blue (0-255)
1004 * @return A quake color character.
1006 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
1008 float r = ((float)_r)/255.0;
1009 float g = ((float)_g)/255.0;
1010 float b = ((float)_b)/255.0;
1011 float min = min(r, min(g, b));
1012 float max = max(r, max(g, b));
1014 int h; ///< Hue angle [0,360]
1015 float s; ///< Saturation [0,1]
1016 float v = max; ///< In HSV v == max [0,1]
1021 s = 1.0 - (min/max);
1023 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1026 // If the value is less than half, return a black color code.
1027 // Otherwise return a white one.
1033 // Let's get the hue angle to define some colors:
1037 h = (int)(60.0 * (g-b)/(max-min))%360;
1039 h = (int)(60.0 * (b-r)/(max-min) + 120);
1040 else // if(max == b) redundant check
1041 h = (int)(60.0 * (r-g)/(max-min) + 240);
1043 if(h < 36) // *red* to orange
1045 else if(h < 80) // orange over *yellow* to evilish-bright-green
1047 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1049 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1051 else if(h < 270) // darkish blue over *dark blue* to cool purple
1053 else if(h < 330) // cool purple over *purple* to ugly swiny red
1055 else // ugly red to red closes the circly
1064 extern cvar_t timestamps;
1065 extern cvar_t timeformat;
1066 extern qboolean sys_nostdout;
1067 void Con_MaskPrint(int additionalmask, const char *msg)
1069 static int mask = 0;
1070 static int index = 0;
1071 static char line[MAX_INPUTLINE];
1074 Thread_LockMutex(con_mutex);
1078 Con_Rcon_AddChar(*msg);
1079 // if this is the beginning of a new line, print timestamp
1082 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1084 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1085 line[index++] = STRING_COLOR_TAG;
1086 // assert( STRING_COLOR_DEFAULT < 10 )
1087 line[index++] = STRING_COLOR_DEFAULT + '0';
1088 // special color codes for chat messages must always come first
1089 // for Con_PrintToHistory to work properly
1090 if (*msg == 1 || *msg == 2)
1095 if (con_chatsound.value)
1097 if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
1099 if(msg[1] == '\r' && cl.foundtalk2wav)
1100 S_LocalSound ("sound/misc/talk2.wav");
1102 S_LocalSound ("sound/misc/talk.wav");
1106 if (msg[1] == '(' && cl.foundtalk2wav)
1107 S_LocalSound ("sound/misc/talk2.wav");
1109 S_LocalSound ("sound/misc/talk.wav");
1112 mask = CON_MASK_CHAT;
1114 line[index++] = STRING_COLOR_TAG;
1115 line[index++] = '3';
1117 Con_Rcon_AddChar(*msg);
1120 for (;*timestamp;index++, timestamp++)
1121 if (index < (int)sizeof(line) - 2)
1122 line[index] = *timestamp;
1124 mask |= additionalmask;
1126 // append the character
1127 line[index++] = *msg;
1128 // if this is a newline character, we have a complete line to print
1129 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1131 // terminate the line
1135 // send to scrollable buffer
1136 if (con_initialized && cls.state != ca_dedicated)
1138 Con_PrintToHistory(line, mask);
1140 // send to terminal or dedicated server window
1142 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1144 if(sys_specialcharactertranslation.integer)
1151 int ch = u8_getchar(p, &q);
1152 if(ch >= 0xE000 && ch <= 0xE0FF)
1154 *p = qfont_table[ch - 0xE000];
1156 memmove(p+1, q, strlen(q)+1);
1164 if(sys_colortranslation.integer == 1) // ANSI
1166 static char printline[MAX_INPUTLINE * 4 + 3];
1167 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1168 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1173 for(in = line, out = printline; *in; ++in)
1177 case STRING_COLOR_TAG:
1178 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1180 char r = tolower(in[2]);
1181 char g = tolower(in[3]);
1182 char b = tolower(in[4]);
1183 // it's a hex digit already, so the else part needs no check --blub
1184 if(isdigit(r)) r -= '0';
1186 if(isdigit(g)) g -= '0';
1188 if(isdigit(b)) b -= '0';
1191 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1192 in += 3; // 3 only, the switch down there does the fourth
1199 case STRING_COLOR_TAG:
1201 *out++ = STRING_COLOR_TAG;
1207 if(lastcolor == 0) break; else lastcolor = 0;
1208 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1213 if(lastcolor == 1) break; else lastcolor = 1;
1214 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1219 if(lastcolor == 2) break; else lastcolor = 2;
1220 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1225 if(lastcolor == 3) break; else lastcolor = 3;
1226 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1231 if(lastcolor == 4) break; else lastcolor = 4;
1232 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1237 if(lastcolor == 5) break; else lastcolor = 5;
1238 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1243 if(lastcolor == 6) break; else lastcolor = 6;
1244 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1249 // bold normal color
1251 if(lastcolor == 8) break; else lastcolor = 8;
1252 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1255 *out++ = STRING_COLOR_TAG;
1262 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1279 Sys_PrintToTerminal(printline);
1281 else if(sys_colortranslation.integer == 2) // Quake
1283 Sys_PrintToTerminal(line);
1287 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1290 for(in = line, out = printline; *in; ++in)
1294 case STRING_COLOR_TAG:
1297 case STRING_COLOR_RGB_TAG_CHAR:
1298 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1303 *out++ = STRING_COLOR_TAG;
1304 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1307 case STRING_COLOR_TAG:
1309 *out++ = STRING_COLOR_TAG;
1324 *out++ = STRING_COLOR_TAG;
1334 Sys_PrintToTerminal(printline);
1337 // empty the line buffer
1344 Thread_UnlockMutex(con_mutex);
1352 void Con_MaskPrintf(int mask, const char *fmt, ...)
1355 char msg[MAX_INPUTLINE];
1357 va_start(argptr,fmt);
1358 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1361 Con_MaskPrint(mask, msg);
1369 void Con_Print(const char *msg)
1371 Con_MaskPrint(CON_MASK_PRINT, msg);
1379 void Con_Printf(const char *fmt, ...)
1382 char msg[MAX_INPUTLINE];
1384 va_start(argptr,fmt);
1385 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1388 Con_MaskPrint(CON_MASK_PRINT, msg);
1396 void Con_DPrint(const char *msg)
1398 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1401 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1409 void Con_DPrintf(const char *fmt, ...)
1412 char msg[MAX_INPUTLINE];
1414 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1417 va_start(argptr,fmt);
1418 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1421 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1426 ==============================================================================
1430 ==============================================================================
1437 The input line scrolls horizontally if typing goes beyond the right edge
1439 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1442 extern cvar_t r_font_disable_freetype;
1443 void Con_DrawInput (void)
1447 char editlinecopy[MAX_INPUTLINE+1], *text;
1452 if (!key_consoleactive)
1453 return; // don't draw anything
1455 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1456 text = editlinecopy;
1458 // Advanced Console Editing by Radix radix@planetquake.com
1459 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1460 // use strlen of edit_line instead of key_linepos to allow editing
1461 // of early characters w/o erasing
1463 y = (int)strlen(text);
1465 // append enoug nul-bytes to cover the utf8-versions of the cursor too
1466 for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i)
1469 // add the cursor frame
1470 if (r_font_disable_freetype.integer)
1472 // this code is freetype incompatible!
1473 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1475 if (!utf8_enable.integer)
1476 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1477 else if (y + 3 < (int)sizeof(editlinecopy)-1)
1479 int ofs = u8_bytelen(text + key_linepos, 1);
1482 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1486 memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1487 memcpy(text + key_linepos, curbuf, len);
1490 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1494 // text[key_linepos + 1] = 0;
1496 len_out = key_linepos;
1498 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1499 x = vid_conwidth.value * 0.95 - xo; // scroll
1504 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 );
1506 // add a cursor on top of this (when using freetype)
1507 if (!r_font_disable_freetype.integer)
1509 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1511 if (!utf8_enable.integer)
1513 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1520 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1521 memcpy(text, curbuf, len);
1524 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);
1529 // key_line[key_linepos] = 0;
1535 float alignment; // 0 = left, 0.5 = center, 1 = right
1541 const char *continuationString;
1544 int colorindex; // init to -1
1548 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1550 con_text_info_t *ti = (con_text_info_t *) passthrough;
1553 ti->colorindex = -1;
1554 return ti->fontsize * ti->font->maxwidth;
1557 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1558 else if(maxWidth == -1)
1559 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1562 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1563 // Note: this is NOT a Con_Printf, as it could print recursively
1568 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1574 (void) isContinuation;
1578 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1580 con_text_info_t *ti = (con_text_info_t *) passthrough;
1582 if(ti->y < ti->ymin - 0.001)
1584 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1588 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1589 if(isContinuation && *ti->continuationString)
1590 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);
1592 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);
1595 ti->y += ti->fontsize;
1599 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)
1603 int maxlines = (int) floor(height / fontsize + 0.01f);
1606 int continuationWidth = 0;
1608 double t = cl.time; // saved so it won't change
1611 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1612 ti.fontsize = fontsize;
1613 ti.alignment = alignment_x;
1616 ti.ymax = y + height;
1617 ti.continuationString = continuationString;
1620 Con_WordWidthFunc(&ti, NULL, &l, -1);
1621 l = strlen(continuationString);
1622 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1624 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1625 startidx = CON_LINES_COUNT;
1626 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1628 con_lineinfo_t *l = &CON_LINES(i);
1631 if((l->mask & mask_must) != mask_must)
1633 if(l->mask & mask_mustnot)
1635 if(maxage && (l->addtime < t - maxage))
1639 // Calculate its actual height...
1640 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1641 if(lines + mylines >= maxlines)
1643 nskip = lines + mylines - maxlines;
1652 // then center according to the calculated amount of lines...
1654 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1656 // then actually draw
1657 for(i = startidx; i < CON_LINES_COUNT; ++i)
1659 con_lineinfo_t *l = &CON_LINES(i);
1661 if((l->mask & mask_must) != mask_must)
1663 if(l->mask & mask_mustnot)
1665 if(maxage && (l->addtime < t - maxage))
1668 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1678 Draws the last few lines of output transparently over the game top
1681 void Con_DrawNotify (void)
1684 float chatstart, notifystart, inputsize, height;
1686 char temptext[MAX_INPUTLINE];
1690 if (con_mutex) Thread_LockMutex(con_mutex);
1691 ConBuffer_FixTimes(&con);
1693 numChatlines = con_chat.integer;
1695 chatpos = con_chatpos.integer;
1697 if (con_notify.integer < 0)
1698 Cvar_SetValueQuick(&con_notify, 0);
1699 if (gamemode == GAME_TRANSFUSION)
1700 v = 8; // vertical offset
1704 // GAME_NEXUIZ: center, otherwise left justify
1705 align = con_notifyalign.value;
1706 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1708 if(gamemode == GAME_NEXUIZ)
1712 if(numChatlines || !con_chatrect.integer)
1716 // first chat, input line, then notify
1718 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1720 else if(chatpos > 0)
1722 // first notify, then (chatpos-1) empty lines, then chat, then input
1724 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1726 else // if(chatpos < 0)
1728 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1730 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1735 // just notify and input
1737 chatstart = 0; // shut off gcc warning
1740 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, "");
1742 if(con_chatrect.integer)
1744 x = con_chatrect_x.value * vid_conwidth.value;
1745 v = con_chatrect_y.value * vid_conheight.value;
1750 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1753 height = numChatlines * con_chatsize.value;
1757 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
1760 if (key_dest == key_message)
1762 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1763 int colorindex = -1;
1765 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL);
1767 // LordHavoc: speedup, and other improvements
1769 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1771 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1773 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1776 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1777 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1779 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1781 if (con_mutex) Thread_UnlockMutex(con_mutex);
1788 Returns the height of a given console line; calculates it if necessary.
1791 int Con_LineHeight(int lineno)
1793 con_lineinfo_t *li = &CON_LINES(lineno);
1794 if(li->height == -1)
1796 float width = vid_conwidth.value;
1798 con_lineinfo_t *li = &CON_LINES(lineno);
1799 ti.fontsize = con_textsize.value;
1800 ti.font = FONT_CONSOLE;
1801 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1810 Draws a line of the console; returns its height in lines.
1811 If alpha is 0, the line is not drawn, but still wrapped and its height
1815 int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1817 float width = vid_conwidth.value;
1819 con_lineinfo_t *li = &CON_LINES(lineno);
1821 if((li->mask & mask_must) != mask_must)
1823 if((li->mask & mask_mustnot) != 0)
1826 ti.continuationString = "";
1828 ti.fontsize = con_textsize.value;
1829 ti.font = FONT_CONSOLE;
1831 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1836 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1843 Calculates the last visible line index and how much to show of it based on
1847 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1852 if(con_backscroll < 0)
1857 // now count until we saw con_backscroll actual lines
1858 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1859 if((CON_LINES(i).mask & mask_must) == mask_must)
1860 if((CON_LINES(i).mask & mask_mustnot) == 0)
1862 int h = Con_LineHeight(i);
1864 // line is the last visible line?
1866 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1868 *limitlast = lines_seen + h - con_backscroll;
1875 // if we get here, no line was on screen - scroll so that one line is
1877 con_backscroll = lines_seen - 1;
1885 Draws the console with the solid background
1886 The typing input line at the bottom should only be drawn if typing is allowed
1889 void Con_DrawConsole (int lines)
1891 float alpha, alpha0;
1894 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1895 cachepic_t *conbackpic;
1900 if (con_mutex) Thread_LockMutex(con_mutex);
1902 if (con_backscroll < 0)
1905 con_vislines = lines;
1907 r_draw2d_force = true;
1909 // draw the background
1910 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
1911 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
1913 sx = scr_conscroll_x.value;
1914 sy = scr_conscroll_y.value;
1915 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL;
1916 sx *= realtime; sy *= realtime;
1917 sx -= floor(sx); sy -= floor(sy);
1918 if (conbackpic && conbackpic->tex != r_texture_notexture)
1919 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1920 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1921 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1922 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1923 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1926 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
1928 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
1930 sx = scr_conscroll2_x.value;
1931 sy = scr_conscroll2_y.value;
1932 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1933 sx *= realtime; sy *= realtime;
1934 sx -= floor(sx); sy -= floor(sy);
1935 if(conbackpic && conbackpic->tex != r_texture_notexture)
1936 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1937 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1938 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1939 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1940 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1943 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
1945 sx = scr_conscroll3_x.value;
1946 sy = scr_conscroll3_y.value;
1947 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1948 sx *= realtime; sy *= realtime;
1949 sx -= floor(sx); sy -= floor(sy);
1950 if(conbackpic && conbackpic->tex != r_texture_notexture)
1951 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1952 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1953 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1954 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1955 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1958 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);
1964 int count = CON_LINES_COUNT;
1965 float ymax = con_vislines - 2 * con_textsize.value;
1966 float y = ymax + con_textsize.value * con_backscroll;
1967 for (i = 0;i < count && y >= 0;i++)
1968 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
1969 // fix any excessive scrollback for the next frame
1970 if (i >= count && y >= 0)
1972 con_backscroll -= (int)(y / con_textsize.value);
1973 if (con_backscroll < 0)
1978 if(CON_LINES_COUNT > 0)
1980 int i, last, limitlast;
1982 float ymax = con_vislines - 2 * con_textsize.value;
1983 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1984 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1985 y = ymax - con_textsize.value;
1988 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1993 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
1995 break; // top of console buffer
1997 break; // top of console window
2004 // draw the input prompt, user text, and cursor if desired
2007 r_draw2d_force = false;
2008 if (con_mutex) Thread_UnlockMutex(con_mutex);
2015 Prints not only map filename, but also
2016 its format (q1/q2/q3/hl) and even its message
2018 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2019 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2020 //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
2021 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2022 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
2026 int i, k, max, p, o, min;
2029 unsigned char buf[1024];
2031 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2032 t = FS_Search(message, 1, true);
2035 if (t->numfilenames > 1)
2036 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2037 len = (unsigned char *)Z_Malloc(t->numfilenames);
2039 for(max=i=0;i<t->numfilenames;i++)
2041 k = (int)strlen(t->filenames[i]);
2051 for(i=0;i<t->numfilenames;i++)
2053 int lumpofs = 0, lumplen = 0;
2054 char *entities = NULL;
2055 const char *data = NULL;
2057 char entfilename[MAX_QPATH];
2058 strlcpy(message, "^1**ERROR**^7", sizeof(message));
2060 f = FS_OpenVirtualFile(t->filenames[i], true);
2063 memset(buf, 0, 1024);
2064 FS_Read(f, buf, 1024);
2065 if (!memcmp(buf, "IBSP", 4))
2067 p = LittleLong(((int *)buf)[1]);
2068 if (p == Q3BSPVERSION)
2070 q3dheader_t *header = (q3dheader_t *)buf;
2071 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2072 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2074 else if (p == Q2BSPVERSION)
2076 q2dheader_t *header = (q2dheader_t *)buf;
2077 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2078 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2081 else if((p = BuffLittleLong(buf)) == BSPVERSION || p == 30)
2083 dheader_t *header = (dheader_t *)buf;
2084 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
2085 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
2089 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2090 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2091 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2092 if (!entities && lumplen >= 10)
2094 FS_Seek(f, lumpofs, SEEK_SET);
2095 entities = (char *)Z_Malloc(lumplen + 1);
2096 FS_Read(f, entities, lumplen);
2100 // if there are entities to parse, a missing message key just
2101 // means there is no title, so clear the message string now
2107 if (!COM_ParseToken_Simple(&data, false, false))
2109 if (com_token[0] == '{')
2111 if (com_token[0] == '}')
2113 // skip leading whitespace
2114 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2115 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2116 keyname[l] = com_token[k+l];
2118 if (!COM_ParseToken_Simple(&data, false, false))
2120 if (developer_extra.integer)
2121 Con_DPrintf("key: %s %s\n", keyname, com_token);
2122 if (!strcmp(keyname, "message"))
2124 // get the message contents
2125 strlcpy(message, com_token, sizeof(message));
2135 *(t->filenames[i]+len[i]+5) = 0;
2138 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
2139 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
2140 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
2141 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
2142 default: strlcpy((char *)buf, "??", sizeof(buf));break;
2144 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
2149 k = *(t->filenames[0]+5+p);
2152 for(i=1;i<t->numfilenames;i++)
2153 if(*(t->filenames[i]+5+p) != k)
2157 if(p > o && completedname && completednamebufferlength > 0)
2159 memset(completedname, 0, completednamebufferlength);
2160 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2170 New function for tab-completion system
2171 Added by EvilTypeGuy
2172 MEGA Thanks to Taniwha
2175 void Con_DisplayList(const char **list)
2177 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2178 const char **walk = list;
2181 len = (int)strlen(*walk);
2189 len = (int)strlen(*list);
2190 if (pos + maxlen >= width) {
2196 for (i = 0; i < (maxlen - len); i++)
2208 SanitizeString strips color tags from the string in
2209 and writes the result on string out
2211 void SanitizeString(char *in, char *out)
2215 if(*in == STRING_COLOR_TAG)
2220 out[0] = STRING_COLOR_TAG;
2224 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2231 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2234 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2236 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2243 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2248 else if (*in != STRING_COLOR_TAG)
2251 *out = qfont_table[*(unsigned char*)in];
2258 // Now it becomes TRICKY :D --blub
2259 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2260 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2261 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2262 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
2263 static int Nicks_matchpos;
2265 // co against <<:BLASTER:>> is true!?
2266 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2270 if(tolower(*a) == tolower(*b))
2284 return (*a < *b) ? -1 : 1;
2288 return (*a < *b) ? -1 : 1;
2292 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2295 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2297 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2298 return Nicks_strncasecmp_nospaces(a, b, a_len);
2299 return strncasecmp(a, b, a_len);
2302 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2304 // ignore non alphanumerics of B
2305 // if A contains a non-alphanumeric, B must contain it as well though!
2308 qboolean alnum_a, alnum_b;
2310 if(tolower(*a) == tolower(*b))
2312 if(*a == 0) // end of both strings, they're equal
2319 // not equal, end of one string?
2324 // ignore non alphanumerics
2325 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2326 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2327 if(!alnum_a) // b must contain this
2328 return (*a < *b) ? -1 : 1;
2331 // otherwise, both are alnum, they're just not equal, return the appropriate number
2333 return (*a < *b) ? -1 : 1;
2339 /* Nicks_CompleteCountPossible
2341 Count the number of possible nicks to complete
2343 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2351 if(!con_nickcompletion.integer)
2354 // changed that to 1
2355 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2358 for(i = 0; i < cl.maxclients; ++i)
2361 if(!cl.scores[p].name[0])
2364 SanitizeString(cl.scores[p].name, name);
2365 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2371 spos = pos - 1; // no need for a minimum of characters :)
2375 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2377 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2378 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2384 if(isCon && spos == 0)
2386 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2392 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2393 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2395 // the sanitized list
2396 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2399 Nicks_matchpos = match;
2402 Nicks_offset[count] = s - (&line[match]);
2403 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2410 void Cmd_CompleteNicksPrint(int count)
2413 for(i = 0; i < count; ++i)
2414 Con_Printf("%s\n", Nicks_list[i]);
2417 void Nicks_CutMatchesNormal(int count)
2419 // cut match 0 down to the longest possible completion
2422 c = strlen(Nicks_sanlist[0]) - 1;
2423 for(i = 1; i < count; ++i)
2425 l = strlen(Nicks_sanlist[i]) - 1;
2429 for(l = 0; l <= c; ++l)
2430 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2436 Nicks_sanlist[0][c+1] = 0;
2437 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2440 unsigned int Nicks_strcleanlen(const char *s)
2445 if( (*s >= 'a' && *s <= 'z') ||
2446 (*s >= 'A' && *s <= 'Z') ||
2447 (*s >= '0' && *s <= '9') ||
2455 void Nicks_CutMatchesAlphaNumeric(int count)
2457 // cut match 0 down to the longest possible completion
2460 char tempstr[sizeof(Nicks_sanlist[0])];
2462 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2464 c = strlen(Nicks_sanlist[0]);
2465 for(i = 0, l = 0; i < (int)c; ++i)
2467 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2468 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2469 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2471 tempstr[l++] = Nicks_sanlist[0][i];
2476 for(i = 1; i < count; ++i)
2479 b = Nicks_sanlist[i];
2489 if(tolower(*a) == tolower(*b))
2495 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2497 // b is alnum, so cut
2504 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2505 Nicks_CutMatchesNormal(count);
2506 //if(!Nicks_sanlist[0][0])
2507 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2509 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2510 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2514 void Nicks_CutMatchesNoSpaces(int count)
2516 // cut match 0 down to the longest possible completion
2519 char tempstr[sizeof(Nicks_sanlist[0])];
2522 c = strlen(Nicks_sanlist[0]);
2523 for(i = 0, l = 0; i < (int)c; ++i)
2525 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2527 tempstr[l++] = Nicks_sanlist[0][i];
2532 for(i = 1; i < count; ++i)
2535 b = Nicks_sanlist[i];
2545 if(tolower(*a) == tolower(*b))
2559 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2560 Nicks_CutMatchesNormal(count);
2561 //if(!Nicks_sanlist[0][0])
2562 //Con_Printf("TS: %s\n", tempstr);
2563 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2565 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2566 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2570 void Nicks_CutMatches(int count)
2572 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2573 Nicks_CutMatchesAlphaNumeric(count);
2574 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2575 Nicks_CutMatchesNoSpaces(count);
2577 Nicks_CutMatchesNormal(count);
2580 const char **Nicks_CompleteBuildList(int count)
2584 // the list is freed by Con_CompleteCommandLine, so create a char**
2585 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2587 for(; bpos < count; ++bpos)
2588 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2590 Nicks_CutMatches(count);
2598 Restores the previous used color, after the autocompleted name.
2600 int Nicks_AddLastColor(char *buffer, int pos)
2602 qboolean quote_added = false;
2604 int color = STRING_COLOR_DEFAULT + '0';
2605 char r = 0, g = 0, b = 0;
2607 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2609 // we'll have to add a quote :)
2610 buffer[pos++] = '\"';
2614 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2616 // add color when no quote was added, or when flags &4?
2618 for(match = Nicks_matchpos-1; match >= 0; --match)
2620 if(buffer[match] == STRING_COLOR_TAG)
2622 if( isdigit(buffer[match+1]) )
2624 color = buffer[match+1];
2627 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2629 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2631 r = buffer[match+2];
2632 g = buffer[match+3];
2633 b = buffer[match+4];
2642 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2644 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2645 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2648 buffer[pos++] = STRING_COLOR_TAG;
2651 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2657 buffer[pos++] = color;
2662 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2665 /*if(!con_nickcompletion.integer)
2666 return; is tested in Nicks_CompletionCountPossible */
2667 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2673 msg = Nicks_list[0];
2674 len = min(size - Nicks_matchpos - 3, strlen(msg));
2675 memcpy(&buffer[Nicks_matchpos], msg, len);
2676 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2677 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2678 buffer[len++] = ' ';
2685 Con_Printf("\n%i possible nicks:\n", n);
2686 Cmd_CompleteNicksPrint(n);
2688 Nicks_CutMatches(n);
2690 msg = Nicks_sanlist[0];
2691 len = min(size - Nicks_matchpos, strlen(msg));
2692 memcpy(&buffer[Nicks_matchpos], msg, len);
2693 buffer[Nicks_matchpos + len] = 0;
2695 return Nicks_matchpos + len;
2702 Con_CompleteCommandLine
2704 New function for tab-completion system
2705 Added by EvilTypeGuy
2706 Thanks to Fett erich@heintz.com
2708 Enhanced to tab-complete map names by [515]
2711 void Con_CompleteCommandLine (void)
2713 const char *cmd = "";
2715 const char **list[4] = {0, 0, 0, 0};
2718 int c, v, a, i, cmd_len, pos, k;
2719 int n; // nicks --blub
2720 const char *space, *patterns;
2722 //find what we want to complete
2727 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2733 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2734 key_line[key_linepos] = 0; //hide them
2736 space = strchr(key_line + 1, ' ');
2737 if(space && pos == (space - key_line) + 1)
2739 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2741 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2742 if(patterns && !*patterns)
2743 patterns = NULL; // get rid of the empty string
2745 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2749 if (GetMapList(s, t, sizeof(t)))
2751 // first move the cursor
2752 key_linepos += (int)strlen(t) - (int)strlen(s);
2754 // and now do the actual work
2756 strlcat(key_line, t, MAX_INPUTLINE);
2757 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2759 // and fix the cursor
2760 if(key_linepos > (int) strlen(key_line))
2761 key_linepos = (int) strlen(key_line);
2770 stringlist_t resultbuf, dirbuf;
2773 // // store completion patterns (space separated) for command foo in con_completion_foo
2774 // set con_completion_foo "foodata/*.foodefault *.foo"
2777 // Note: patterns with slash are always treated as absolute
2778 // patterns; patterns without slash search in the innermost
2779 // directory the user specified. There is no way to "complete into"
2780 // a directory as of now, as directories seem to be unknown to the
2784 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2785 // set con_completion_playdemo "*.dem"
2786 // set con_completion_play "*.wav *.ogg"
2788 // TODO somehow add support for directories; these shall complete
2789 // to their name + an appended slash.
2791 stringlistinit(&resultbuf);
2792 stringlistinit(&dirbuf);
2793 while(COM_ParseToken_Simple(&patterns, false, false))
2796 if(strchr(com_token, '/'))
2798 search = FS_Search(com_token, true, true);
2802 const char *slash = strrchr(s, '/');
2805 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2806 strlcat(t, com_token, sizeof(t));
2807 search = FS_Search(t, true, true);
2810 search = FS_Search(com_token, true, true);
2814 for(i = 0; i < search->numfilenames; ++i)
2815 if(!strncmp(search->filenames[i], s, strlen(s)))
2816 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2817 stringlistappend(&resultbuf, search->filenames[i]);
2818 FS_FreeSearch(search);
2822 // In any case, add directory names
2825 const char *slash = strrchr(s, '/');
2828 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2829 strlcat(t, "*", sizeof(t));
2830 search = FS_Search(t, true, true);
2833 search = FS_Search("*", true, true);
2836 for(i = 0; i < search->numfilenames; ++i)
2837 if(!strncmp(search->filenames[i], s, strlen(s)))
2838 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2839 stringlistappend(&dirbuf, search->filenames[i]);
2840 FS_FreeSearch(search);
2844 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2847 unsigned int matchchars;
2848 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2850 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2853 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2855 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2859 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2860 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2861 for(i = 0; i < dirbuf.numstrings; ++i)
2863 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2865 for(i = 0; i < resultbuf.numstrings; ++i)
2867 Con_Printf("%s\n", resultbuf.strings[i]);
2869 matchchars = sizeof(t) - 1;
2870 if(resultbuf.numstrings > 0)
2872 p = resultbuf.strings[0];
2873 q = resultbuf.strings[resultbuf.numstrings - 1];
2874 for(; *p && *p == *q; ++p, ++q);
2875 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2877 if(dirbuf.numstrings > 0)
2879 p = dirbuf.strings[0];
2880 q = dirbuf.strings[dirbuf.numstrings - 1];
2881 for(; *p && *p == *q; ++p, ++q);
2882 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2884 // now p points to the first non-equal character, or to the end
2885 // of resultbuf.strings[0]. We want to append the characters
2886 // from resultbuf.strings[0] to (not including) p as these are
2887 // the unique prefix
2888 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2891 // first move the cursor
2892 key_linepos += (int)strlen(t) - (int)strlen(s);
2894 // and now do the actual work
2896 strlcat(key_line, t, MAX_INPUTLINE);
2897 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2899 // and fix the cursor
2900 if(key_linepos > (int) strlen(key_line))
2901 key_linepos = (int) strlen(key_line);
2903 stringlistfreecontents(&resultbuf);
2904 stringlistfreecontents(&dirbuf);
2906 return; // bail out, when we complete for a command that wants a file name
2911 // Count number of possible matches and print them
2912 c = Cmd_CompleteCountPossible(s);
2915 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2916 Cmd_CompleteCommandPrint(s);
2918 v = Cvar_CompleteCountPossible(s);
2921 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2922 Cvar_CompleteCvarPrint(s);
2924 a = Cmd_CompleteAliasCountPossible(s);
2927 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2928 Cmd_CompleteAliasPrint(s);
2930 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2933 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2934 Cmd_CompleteNicksPrint(n);
2937 if (!(c + v + a + n)) // No possible matches
2940 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2945 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2947 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2949 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2951 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2953 for (cmd_len = (int)strlen(s);;cmd_len++)
2956 for (i = 0; i < 3; i++)
2958 for (l = list[i];*l;l++)
2959 if ((*l)[cmd_len] != cmd[cmd_len])
2961 // all possible matches share this character, so we continue...
2964 // if all matches ended at the same position, stop
2965 // (this means there is only one match)
2971 // prevent a buffer overrun by limiting cmd_len according to remaining space
2972 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2976 memcpy(&key_line[key_linepos], cmd, cmd_len);
2977 key_linepos += cmd_len;
2978 // if there is only one match, add a space after it
2979 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2982 { // was a nick, might have an offset, and needs colors ;) --blub
2983 key_linepos = pos - Nicks_offset[0];
2984 cmd_len = strlen(Nicks_list[0]);
2985 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2987 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2988 key_linepos += cmd_len;
2989 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2990 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2992 key_line[key_linepos++] = ' ';
2996 // use strlcat to avoid a buffer overrun
2997 key_line[key_linepos] = 0;
2998 strlcat(key_line, s2, sizeof(key_line));
3000 // free the command, cvar, and alias lists
3001 for (i = 0; i < 4; i++)
3003 Mem_Free((void *)list[i]);