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 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
294 static char copybuf[MAX_INPUTLINE]; // client only
295 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
296 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
297 strlcpy(copybuf, l->start, sz);
302 ==============================================================================
306 ==============================================================================
311 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
312 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"};
313 char log_dest_buffer[1400]; // UDP packet
314 size_t log_dest_buffer_pos;
315 unsigned int log_dest_buffer_appending;
316 char crt_log_file [MAX_OSPATH] = "";
317 qfile_t* logfile = NULL;
319 unsigned char* logqueue = NULL;
321 size_t logq_size = 0;
323 void Log_ConPrint (const char *msg);
325 static void Log_DestBuffer_Init(void)
327 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
328 log_dest_buffer_pos = 5;
331 static void Log_DestBuffer_Flush_NoLock(void)
333 lhnetaddress_t log_dest_addr;
334 lhnetsocket_t *log_dest_socket;
335 const char *s = log_dest_udp.string;
336 qboolean have_opened_temp_sockets = false;
337 if(s) if(log_dest_buffer_pos > 5)
339 ++log_dest_buffer_appending;
340 log_dest_buffer[log_dest_buffer_pos++] = 0;
342 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
344 have_opened_temp_sockets = true;
345 NetConn_OpenServerPorts(true);
348 while(COM_ParseToken_Console(&s))
349 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
351 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
353 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
355 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
358 if(have_opened_temp_sockets)
359 NetConn_CloseServerPorts();
360 --log_dest_buffer_appending;
362 log_dest_buffer_pos = 0;
370 void Log_DestBuffer_Flush(void)
373 Thread_LockMutex(con_mutex);
374 Log_DestBuffer_Flush_NoLock();
376 Thread_UnlockMutex(con_mutex);
379 static const char* Log_Timestamp (const char *desc)
381 static char timestamp [128]; // init/shutdown only
388 char timestring [64];
390 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
393 localtime_s (&crt_tm, &crt_time);
394 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
396 crt_tm = localtime (&crt_time);
397 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
401 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
403 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
408 static void Log_Open (void)
410 if (logfile != NULL || log_file.string[0] == '\0')
413 logfile = FS_OpenRealFile(log_file.string, "a", false);
416 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
417 FS_Print (logfile, Log_Timestamp ("Log started"));
426 void Log_Close (void)
431 FS_Print (logfile, Log_Timestamp ("Log stopped"));
432 FS_Print (logfile, "\n");
436 crt_log_file[0] = '\0';
445 void Log_Start (void)
451 // Dump the contents of the log queue into the log file and free it
452 if (logqueue != NULL)
454 unsigned char *temp = logqueue;
459 FS_Write (logfile, temp, logq_ind);
460 if(*log_dest_udp.string)
462 for(pos = 0; pos < logq_ind; )
464 if(log_dest_buffer_pos == 0)
465 Log_DestBuffer_Init();
466 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
467 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
468 log_dest_buffer_pos += n;
469 Log_DestBuffer_Flush_NoLock();
486 void Log_ConPrint (const char *msg)
488 static qboolean inprogress = false;
490 // don't allow feedback loops with memory error reports
495 // Until the host is completely initialized, we maintain a log queue
496 // to store the messages, since the log can't be started before
497 if (logqueue != NULL)
499 size_t remain = logq_size - logq_ind;
500 size_t len = strlen (msg);
502 // If we need to enlarge the log queue
505 size_t factor = ((logq_ind + len) / logq_size) + 1;
506 unsigned char* newqueue;
509 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
510 memcpy (newqueue, logqueue, logq_ind);
513 remain = logq_size - logq_ind;
515 memcpy (&logqueue[logq_ind], msg, len);
522 // Check if log_file has changed
523 if (strcmp (crt_log_file, log_file.string) != 0)
529 // If a log file is available
531 FS_Print (logfile, msg);
542 void Log_Printf (const char *logfilename, const char *fmt, ...)
546 file = FS_OpenRealFile(logfilename, "a", true);
551 va_start (argptr, fmt);
552 FS_VPrintf (file, fmt, argptr);
561 ==============================================================================
565 ==============================================================================
573 void Con_ToggleConsole_f (void)
575 if (COM_CheckParm ("-noconsole"))
576 if (!(key_consoleactive & KEY_CONSOLEACTIVE_USER))
577 return; // only allow the key bind to turn off console
579 // toggle the 'user wants console' bit
580 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
589 void Con_ClearNotify (void)
592 for(i = 0; i < CON_LINES_COUNT; ++i)
593 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
602 static void Con_MessageMode_f (void)
604 key_dest = key_message;
605 chat_mode = 0; // "say"
608 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
609 chat_bufferlen = strlen(chat_buffer);
619 static void Con_MessageMode2_f (void)
621 key_dest = key_message;
622 chat_mode = 1; // "say_team"
625 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
626 chat_bufferlen = strlen(chat_buffer);
635 static void Con_CommandMode_f (void)
637 key_dest = key_message;
640 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
641 chat_bufferlen = strlen(chat_buffer);
643 chat_mode = -1; // command
651 void Con_CheckResize (void)
656 f = bound(1, con_textsize.value, 128);
657 if(f != con_textsize.value)
658 Cvar_SetValueQuick(&con_textsize, f);
659 width = (int)floor(vid_conwidth.value / con_textsize.value);
660 width = bound(1, width, con.textsize/4);
661 // FIXME uses con in a non abstracted way
663 if (width == con_linewidth)
666 con_linewidth = width;
668 for(i = 0; i < CON_LINES_COUNT; ++i)
669 CON_LINES(i).height = -1; // recalculate when next needed
675 //[515]: the simplest command ever
676 //LordHavoc: not so simple after I made it print usage...
677 static void Con_Maps_f (void)
681 Con_Printf("usage: maps [mapnameprefix]\n");
684 else if (Cmd_Argc() == 2)
685 GetMapList(Cmd_Argv(1), NULL, 0);
687 GetMapList("", NULL, 0);
690 static void Con_ConDump_f (void)
696 Con_Printf("usage: condump <filename>\n");
699 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
702 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
705 if (con_mutex) Thread_LockMutex(con_mutex);
706 for(i = 0; i < CON_LINES_COUNT; ++i)
708 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
709 FS_Write(file, "\n", 1);
711 if (con_mutex) Thread_UnlockMutex(con_mutex);
715 void Con_Clear_f (void)
717 if (con_mutex) Thread_LockMutex(con_mutex);
718 ConBuffer_Clear(&con);
719 if (con_mutex) Thread_UnlockMutex(con_mutex);
730 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
731 if (Thread_HasThreads())
732 con_mutex = Thread_CreateMutex();
734 // Allocate a log queue, this will be freed after configs are parsed
735 logq_size = MAX_INPUTLINE;
736 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
739 Cvar_RegisterVariable (&sys_colortranslation);
740 Cvar_RegisterVariable (&sys_specialcharactertranslation);
742 Cvar_RegisterVariable (&log_file);
743 Cvar_RegisterVariable (&log_dest_udp);
745 // support for the classic Quake option
746 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
747 if (COM_CheckParm ("-condebug") != 0)
748 Cvar_SetQuick (&log_file, "qconsole.log");
750 // register our cvars
751 Cvar_RegisterVariable (&con_chat);
752 Cvar_RegisterVariable (&con_chatpos);
753 Cvar_RegisterVariable (&con_chatrect_x);
754 Cvar_RegisterVariable (&con_chatrect_y);
755 Cvar_RegisterVariable (&con_chatrect);
756 Cvar_RegisterVariable (&con_chatsize);
757 Cvar_RegisterVariable (&con_chattime);
758 Cvar_RegisterVariable (&con_chatwidth);
759 Cvar_RegisterVariable (&con_notify);
760 Cvar_RegisterVariable (&con_notifyalign);
761 Cvar_RegisterVariable (&con_notifysize);
762 Cvar_RegisterVariable (&con_notifytime);
763 Cvar_RegisterVariable (&con_textsize);
764 Cvar_RegisterVariable (&con_chatsound);
767 Cvar_RegisterVariable (&con_nickcompletion);
768 Cvar_RegisterVariable (&con_nickcompletion_flags);
770 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
771 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
772 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
774 // register our commands
775 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
776 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
777 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
778 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
779 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
780 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
781 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
783 con_initialized = true;
784 Con_DPrint("Console initialized.\n");
787 void Con_Shutdown (void)
789 if (con_mutex) Thread_LockMutex(con_mutex);
790 ConBuffer_Shutdown(&con);
791 if (con_mutex) Thread_UnlockMutex(con_mutex);
792 if (con_mutex) Thread_DestroyMutex(con_mutex);con_mutex = NULL;
799 Handles cursor positioning, line wrapping, etc
800 All console printing must go through this in order to be displayed
801 If no console is visible, the notify window will pop up.
804 static void Con_PrintToHistory(const char *txt, int mask)
807 // \n goes to next line
808 // \r deletes current line and makes a new one
810 static int cr_pending = 0;
811 static char buf[CON_TEXTSIZE]; // con_mutex
812 static int bufpos = 0;
814 if(!con.text) // FIXME uses a non-abstracted property of con
817 if (con_mutex) Thread_LockMutex(con_mutex);
822 ConBuffer_DeleteLastLine(&con);
830 ConBuffer_AddLine(&con, buf, bufpos, mask);
835 ConBuffer_AddLine(&con, buf, bufpos, mask);
839 buf[bufpos++] = *txt;
840 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
842 ConBuffer_AddLine(&con, buf, bufpos, mask);
848 if (con_mutex) Thread_UnlockMutex(con_mutex);
851 /*! The translation table between the graphical font and plain ASCII --KB */
852 static char qfont_table[256] = {
853 '\0', '#', '#', '#', '#', '.', '#', '#',
854 '#', 9, 10, '#', ' ', 13, '.', '.',
855 '[', ']', '0', '1', '2', '3', '4', '5',
856 '6', '7', '8', '9', '.', '<', '=', '>',
857 ' ', '!', '"', '#', '$', '%', '&', '\'',
858 '(', ')', '*', '+', ',', '-', '.', '/',
859 '0', '1', '2', '3', '4', '5', '6', '7',
860 '8', '9', ':', ';', '<', '=', '>', '?',
861 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
862 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
863 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
864 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
865 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
866 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
867 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
868 'x', 'y', 'z', '{', '|', '}', '~', '<',
870 '<', '=', '>', '#', '#', '.', '#', '#',
871 '#', '#', ' ', '#', ' ', '>', '.', '.',
872 '[', ']', '0', '1', '2', '3', '4', '5',
873 '6', '7', '8', '9', '.', '<', '=', '>',
874 ' ', '!', '"', '#', '$', '%', '&', '\'',
875 '(', ')', '*', '+', ',', '-', '.', '/',
876 '0', '1', '2', '3', '4', '5', '6', '7',
877 '8', '9', ':', ';', '<', '=', '>', '?',
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', '[', '\\', ']', '^', '_',
882 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
883 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
884 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
885 'x', 'y', 'z', '{', '|', '}', '~', '<'
888 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol)
890 rcon_redirect_sock = sock;
891 rcon_redirect_dest = dest;
892 rcon_redirect_proquakeprotocol = proquakeprotocol;
893 if (rcon_redirect_proquakeprotocol)
895 // reserve space for the packet header
896 rcon_redirect_buffer[0] = 0;
897 rcon_redirect_buffer[1] = 0;
898 rcon_redirect_buffer[2] = 0;
899 rcon_redirect_buffer[3] = 0;
900 // this is a reply to a CCREQ_RCON
901 rcon_redirect_buffer[4] = (char)CCREP_RCON;
904 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
905 rcon_redirect_bufferpos = 5;
908 static void Con_Rcon_Redirect_Flush(void)
910 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
911 if (rcon_redirect_proquakeprotocol)
913 // update the length in the packet header
914 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
916 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
917 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
918 rcon_redirect_bufferpos = 5;
919 rcon_redirect_proquakeprotocol = false;
922 void Con_Rcon_Redirect_End(void)
924 Con_Rcon_Redirect_Flush();
925 rcon_redirect_dest = NULL;
926 rcon_redirect_sock = NULL;
929 void Con_Rcon_Redirect_Abort(void)
931 rcon_redirect_dest = NULL;
932 rcon_redirect_sock = NULL;
940 /// Adds a character to the rcon buffer.
941 static void Con_Rcon_AddChar(int c)
943 if(log_dest_buffer_appending)
945 ++log_dest_buffer_appending;
947 // if this print is in response to an rcon command, add the character
948 // to the rcon redirect buffer
950 if (rcon_redirect_dest)
952 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
953 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
954 Con_Rcon_Redirect_Flush();
956 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
958 if(log_dest_buffer_pos == 0)
959 Log_DestBuffer_Init();
960 log_dest_buffer[log_dest_buffer_pos++] = c;
961 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
962 Log_DestBuffer_Flush_NoLock();
965 log_dest_buffer_pos = 0;
967 --log_dest_buffer_appending;
971 * Convert an RGB color to its nearest quake color.
972 * I'll cheat on this a bit by translating the colors to HSV first,
973 * S and V decide if it's black or white, otherwise, H will decide the
975 * @param _r Red (0-255)
976 * @param _g Green (0-255)
977 * @param _b Blue (0-255)
978 * @return A quake color character.
980 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
982 float r = ((float)_r)/255.0;
983 float g = ((float)_g)/255.0;
984 float b = ((float)_b)/255.0;
985 float min = min(r, min(g, b));
986 float max = max(r, max(g, b));
988 int h; ///< Hue angle [0,360]
989 float s; ///< Saturation [0,1]
990 float v = max; ///< In HSV v == max [0,1]
997 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1000 // If the value is less than half, return a black color code.
1001 // Otherwise return a white one.
1007 // Let's get the hue angle to define some colors:
1011 h = (int)(60.0 * (g-b)/(max-min))%360;
1013 h = (int)(60.0 * (b-r)/(max-min) + 120);
1014 else // if(max == b) redundant check
1015 h = (int)(60.0 * (r-g)/(max-min) + 240);
1017 if(h < 36) // *red* to orange
1019 else if(h < 80) // orange over *yellow* to evilish-bright-green
1021 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1023 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1025 else if(h < 270) // darkish blue over *dark blue* to cool purple
1027 else if(h < 330) // cool purple over *purple* to ugly swiny red
1029 else // ugly red to red closes the circly
1038 extern cvar_t timestamps;
1039 extern cvar_t timeformat;
1040 extern qboolean sys_nostdout;
1041 void Con_MaskPrint(int additionalmask, const char *msg)
1043 static int mask = 0;
1044 static int index = 0;
1045 static char line[MAX_INPUTLINE];
1048 Thread_LockMutex(con_mutex);
1052 Con_Rcon_AddChar(*msg);
1053 // if this is the beginning of a new line, print timestamp
1056 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1058 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1059 line[index++] = STRING_COLOR_TAG;
1060 // assert( STRING_COLOR_DEFAULT < 10 )
1061 line[index++] = STRING_COLOR_DEFAULT + '0';
1062 // special color codes for chat messages must always come first
1063 // for Con_PrintToHistory to work properly
1064 if (*msg == 1 || *msg == 2)
1069 if (con_chatsound.value)
1071 if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
1073 if(msg[1] == '\r' && cl.foundtalk2wav)
1074 S_LocalSound ("sound/misc/talk2.wav");
1076 S_LocalSound ("sound/misc/talk.wav");
1080 if (msg[1] == '(' && cl.foundtalk2wav)
1081 S_LocalSound ("sound/misc/talk2.wav");
1083 S_LocalSound ("sound/misc/talk.wav");
1086 mask = CON_MASK_CHAT;
1088 line[index++] = STRING_COLOR_TAG;
1089 line[index++] = '3';
1091 Con_Rcon_AddChar(*msg);
1094 for (;*timestamp;index++, timestamp++)
1095 if (index < (int)sizeof(line) - 2)
1096 line[index] = *timestamp;
1098 mask |= additionalmask;
1100 // append the character
1101 line[index++] = *msg;
1102 // if this is a newline character, we have a complete line to print
1103 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1105 // terminate the line
1109 // send to scrollable buffer
1110 if (con_initialized && cls.state != ca_dedicated)
1112 Con_PrintToHistory(line, mask);
1114 // send to terminal or dedicated server window
1116 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1118 if(sys_specialcharactertranslation.integer)
1125 int ch = u8_getchar(p, &q);
1126 if(ch >= 0xE000 && ch <= 0xE0FF)
1128 *p = qfont_table[ch - 0xE000];
1130 memmove(p+1, q, strlen(q)+1);
1138 if(sys_colortranslation.integer == 1) // ANSI
1140 static char printline[MAX_INPUTLINE * 4 + 3];
1141 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1142 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1147 for(in = line, out = printline; *in; ++in)
1151 case STRING_COLOR_TAG:
1152 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1154 char r = tolower(in[2]);
1155 char g = tolower(in[3]);
1156 char b = tolower(in[4]);
1157 // it's a hex digit already, so the else part needs no check --blub
1158 if(isdigit(r)) r -= '0';
1160 if(isdigit(g)) g -= '0';
1162 if(isdigit(b)) b -= '0';
1165 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1166 in += 3; // 3 only, the switch down there does the fourth
1173 case STRING_COLOR_TAG:
1175 *out++ = STRING_COLOR_TAG;
1181 if(lastcolor == 0) break; else lastcolor = 0;
1182 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1187 if(lastcolor == 1) break; else lastcolor = 1;
1188 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1193 if(lastcolor == 2) break; else lastcolor = 2;
1194 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1199 if(lastcolor == 3) break; else lastcolor = 3;
1200 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1205 if(lastcolor == 4) break; else lastcolor = 4;
1206 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1211 if(lastcolor == 5) break; else lastcolor = 5;
1212 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1217 if(lastcolor == 6) break; else lastcolor = 6;
1218 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1223 // bold normal color
1225 if(lastcolor == 8) break; else lastcolor = 8;
1226 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1229 *out++ = STRING_COLOR_TAG;
1236 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1253 Sys_PrintToTerminal(printline);
1255 else if(sys_colortranslation.integer == 2) // Quake
1257 Sys_PrintToTerminal(line);
1261 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1264 for(in = line, out = printline; *in; ++in)
1268 case STRING_COLOR_TAG:
1271 case STRING_COLOR_RGB_TAG_CHAR:
1272 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1277 *out++ = STRING_COLOR_TAG;
1278 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1281 case STRING_COLOR_TAG:
1283 *out++ = STRING_COLOR_TAG;
1298 *out++ = STRING_COLOR_TAG;
1308 Sys_PrintToTerminal(printline);
1311 // empty the line buffer
1318 Thread_UnlockMutex(con_mutex);
1326 void Con_MaskPrintf(int mask, const char *fmt, ...)
1329 char msg[MAX_INPUTLINE];
1331 va_start(argptr,fmt);
1332 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1335 Con_MaskPrint(mask, msg);
1343 void Con_Print(const char *msg)
1345 Con_MaskPrint(CON_MASK_PRINT, msg);
1353 void Con_Printf(const char *fmt, ...)
1356 char msg[MAX_INPUTLINE];
1358 va_start(argptr,fmt);
1359 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1362 Con_MaskPrint(CON_MASK_PRINT, msg);
1370 void Con_DPrint(const char *msg)
1372 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1375 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1383 void Con_DPrintf(const char *fmt, ...)
1386 char msg[MAX_INPUTLINE];
1388 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1391 va_start(argptr,fmt);
1392 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1395 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1400 ==============================================================================
1404 ==============================================================================
1411 The input line scrolls horizontally if typing goes beyond the right edge
1413 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1416 extern cvar_t r_font_disable_freetype;
1417 static void Con_DrawInput (void)
1421 char editlinecopy[MAX_INPUTLINE+1], *text;
1426 if (!key_consoleactive)
1427 return; // don't draw anything
1429 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1430 text = editlinecopy;
1432 // Advanced Console Editing by Radix radix@planetquake.com
1433 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1434 // use strlen of edit_line instead of key_linepos to allow editing
1435 // of early characters w/o erasing
1437 y = (int)strlen(text);
1439 // append enoug nul-bytes to cover the utf8-versions of the cursor too
1440 for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i)
1443 // add the cursor frame
1444 if (r_font_disable_freetype.integer)
1446 // this code is freetype incompatible!
1447 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1449 if (!utf8_enable.integer)
1450 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1451 else if (y + 3 < (int)sizeof(editlinecopy)-1)
1453 int ofs = u8_bytelen(text + key_linepos, 1);
1457 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1461 memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1462 memcpy(text + key_linepos, curbuf, len);
1465 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1469 // text[key_linepos + 1] = 0;
1471 len_out = key_linepos;
1473 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1474 x = vid_conwidth.value * 0.95 - xo; // scroll
1479 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 );
1481 // add a cursor on top of this (when using freetype)
1482 if (!r_font_disable_freetype.integer)
1484 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1486 if (!utf8_enable.integer)
1488 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1496 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1497 memcpy(text, curbuf, len);
1500 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);
1505 // key_line[key_linepos] = 0;
1511 float alignment; // 0 = left, 0.5 = center, 1 = right
1517 const char *continuationString;
1520 int colorindex; // init to -1
1524 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1526 con_text_info_t *ti = (con_text_info_t *) passthrough;
1529 ti->colorindex = -1;
1530 return ti->fontsize * ti->font->maxwidth;
1533 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1534 else if(maxWidth == -1)
1535 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1538 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1539 // Note: this is NOT a Con_Printf, as it could print recursively
1544 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1550 (void) isContinuation;
1554 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1556 con_text_info_t *ti = (con_text_info_t *) passthrough;
1558 if(ti->y < ti->ymin - 0.001)
1560 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1564 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1565 if(isContinuation && *ti->continuationString)
1566 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);
1568 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);
1571 ti->y += ti->fontsize;
1575 static 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)
1579 int maxlines = (int) floor(height / fontsize + 0.01f);
1582 int continuationWidth = 0;
1584 double t = cl.time; // saved so it won't change
1587 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1588 ti.fontsize = fontsize;
1589 ti.alignment = alignment_x;
1592 ti.ymax = y + height;
1593 ti.continuationString = continuationString;
1596 Con_WordWidthFunc(&ti, NULL, &l, -1);
1597 l = strlen(continuationString);
1598 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1600 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1601 startidx = CON_LINES_COUNT;
1602 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1604 con_lineinfo_t *l = &CON_LINES(i);
1607 if((l->mask & mask_must) != mask_must)
1609 if(l->mask & mask_mustnot)
1611 if(maxage && (l->addtime < t - maxage))
1615 // Calculate its actual height...
1616 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1617 if(lines + mylines >= maxlines)
1619 nskip = lines + mylines - maxlines;
1628 // then center according to the calculated amount of lines...
1630 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1632 // then actually draw
1633 for(i = startidx; i < CON_LINES_COUNT; ++i)
1635 con_lineinfo_t *l = &CON_LINES(i);
1637 if((l->mask & mask_must) != mask_must)
1639 if(l->mask & mask_mustnot)
1641 if(maxage && (l->addtime < t - maxage))
1644 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1654 Draws the last few lines of output transparently over the game top
1657 void Con_DrawNotify (void)
1660 float chatstart, notifystart, inputsize, height;
1662 char temptext[MAX_INPUTLINE];
1666 if (con_mutex) Thread_LockMutex(con_mutex);
1667 ConBuffer_FixTimes(&con);
1669 numChatlines = con_chat.integer;
1671 chatpos = con_chatpos.integer;
1673 if (con_notify.integer < 0)
1674 Cvar_SetValueQuick(&con_notify, 0);
1675 if (gamemode == GAME_TRANSFUSION)
1676 v = 8; // vertical offset
1680 // GAME_NEXUIZ: center, otherwise left justify
1681 align = con_notifyalign.value;
1682 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1684 if(gamemode == GAME_NEXUIZ)
1688 if(numChatlines || !con_chatrect.integer)
1692 // first chat, input line, then notify
1694 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1696 else if(chatpos > 0)
1698 // first notify, then (chatpos-1) empty lines, then chat, then input
1700 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1702 else // if(chatpos < 0)
1704 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1706 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1711 // just notify and input
1713 chatstart = 0; // shut off gcc warning
1716 v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_INPUT | CON_MASK_HIDENOTIFY | (numChatlines ? CON_MASK_CHAT : 0) | CON_MASK_DEVELOPER, con_notifytime.value, 0, notifystart, vid_conwidth.value, con_notify.value * con_notifysize.value, con_notifysize.value, align, 0.0, "");
1718 if(con_chatrect.integer)
1720 x = con_chatrect_x.value * vid_conwidth.value;
1721 v = con_chatrect_y.value * vid_conheight.value;
1726 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1729 height = numChatlines * con_chatsize.value;
1733 Con_DrawNotifyRect(CON_MASK_CHAT, CON_MASK_INPUT, con_chattime.value, x, v, vid_conwidth.value * con_chatwidth.value, height, con_chatsize.value, 0.0, 1.0, (utf8_enable.integer ? "^3\xee\x80\x8c\xee\x80\x8c\xee\x80\x8c " : "^3\014\014\014 ")); // 015 is ·> character in conchars.tga
1736 if (key_dest == key_message)
1738 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1739 int colorindex = -1;
1742 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL, charbuf16);
1744 // LordHavoc: speedup, and other improvements
1746 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1748 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1750 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1753 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1754 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1756 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1758 if (con_mutex) Thread_UnlockMutex(con_mutex);
1765 Returns the height of a given console line; calculates it if necessary.
1768 static int Con_LineHeight(int lineno)
1770 con_lineinfo_t *li = &CON_LINES(lineno);
1771 if(li->height == -1)
1773 float width = vid_conwidth.value;
1775 con_lineinfo_t *li = &CON_LINES(lineno);
1776 ti.fontsize = con_textsize.value;
1777 ti.font = FONT_CONSOLE;
1778 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1787 Draws a line of the console; returns its height in lines.
1788 If alpha is 0, the line is not drawn, but still wrapped and its height
1792 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1794 float width = vid_conwidth.value;
1796 con_lineinfo_t *li = &CON_LINES(lineno);
1798 if((li->mask & mask_must) != mask_must)
1800 if((li->mask & mask_mustnot) != 0)
1803 ti.continuationString = "";
1805 ti.fontsize = con_textsize.value;
1806 ti.font = FONT_CONSOLE;
1808 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1813 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1820 Calculates the last visible line index and how much to show of it based on
1824 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1829 if(con_backscroll < 0)
1834 // now count until we saw con_backscroll actual lines
1835 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1836 if((CON_LINES(i).mask & mask_must) == mask_must)
1837 if((CON_LINES(i).mask & mask_mustnot) == 0)
1839 int h = Con_LineHeight(i);
1841 // line is the last visible line?
1843 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1845 *limitlast = lines_seen + h - con_backscroll;
1852 // if we get here, no line was on screen - scroll so that one line is
1854 con_backscroll = lines_seen - 1;
1862 Draws the console with the solid background
1863 The typing input line at the bottom should only be drawn if typing is allowed
1866 void Con_DrawConsole (int lines)
1868 float alpha, alpha0;
1871 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1872 cachepic_t *conbackpic;
1877 if (con_mutex) Thread_LockMutex(con_mutex);
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;
1985 if (con_mutex) Thread_UnlockMutex(con_mutex);
1992 Prints not only map filename, but also
1993 its format (q1/q2/q3/hl) and even its message
1995 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1996 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1997 //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
1998 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1999 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
2003 int i, k, max, p, o, min;
2006 unsigned char buf[1024];
2008 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2009 t = FS_Search(message, 1, true);
2012 if (t->numfilenames > 1)
2013 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2014 len = (unsigned char *)Z_Malloc(t->numfilenames);
2016 for(max=i=0;i<t->numfilenames;i++)
2018 k = (int)strlen(t->filenames[i]);
2028 for(i=0;i<t->numfilenames;i++)
2030 int lumpofs = 0, lumplen = 0;
2031 char *entities = NULL;
2032 const char *data = NULL;
2034 char entfilename[MAX_QPATH];
2035 strlcpy(message, "^1**ERROR**^7", sizeof(message));
2037 f = FS_OpenVirtualFile(t->filenames[i], true);
2040 memset(buf, 0, 1024);
2041 FS_Read(f, buf, 1024);
2042 if (!memcmp(buf, "IBSP", 4))
2044 p = LittleLong(((int *)buf)[1]);
2045 if (p == Q3BSPVERSION)
2047 q3dheader_t *header = (q3dheader_t *)buf;
2048 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2049 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2051 else if (p == Q2BSPVERSION)
2053 q2dheader_t *header = (q2dheader_t *)buf;
2054 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2055 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2058 else if((p = BuffLittleLong(buf)) == BSPVERSION || p == 30)
2060 dheader_t *header = (dheader_t *)buf;
2061 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
2062 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
2066 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2067 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2068 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2069 if (!entities && lumplen >= 10)
2071 FS_Seek(f, lumpofs, SEEK_SET);
2072 entities = (char *)Z_Malloc(lumplen + 1);
2073 FS_Read(f, entities, lumplen);
2077 // if there are entities to parse, a missing message key just
2078 // means there is no title, so clear the message string now
2084 if (!COM_ParseToken_Simple(&data, false, false))
2086 if (com_token[0] == '{')
2088 if (com_token[0] == '}')
2090 // skip leading whitespace
2091 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2092 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2093 keyname[l] = com_token[k+l];
2095 if (!COM_ParseToken_Simple(&data, false, false))
2097 if (developer_extra.integer)
2098 Con_DPrintf("key: %s %s\n", keyname, com_token);
2099 if (!strcmp(keyname, "message"))
2101 // get the message contents
2102 strlcpy(message, com_token, sizeof(message));
2112 *(t->filenames[i]+len[i]+5) = 0;
2115 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
2116 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
2117 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
2118 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
2119 default: strlcpy((char *)buf, "??", sizeof(buf));break;
2121 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
2126 k = *(t->filenames[0]+5+p);
2129 for(i=1;i<t->numfilenames;i++)
2130 if(*(t->filenames[i]+5+p) != k)
2134 if(p > o && completedname && completednamebufferlength > 0)
2136 memset(completedname, 0, completednamebufferlength);
2137 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2147 New function for tab-completion system
2148 Added by EvilTypeGuy
2149 MEGA Thanks to Taniwha
2152 void Con_DisplayList(const char **list)
2154 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2155 const char **walk = list;
2158 len = (int)strlen(*walk);
2166 len = (int)strlen(*list);
2167 if (pos + maxlen >= width) {
2173 for (i = 0; i < (maxlen - len); i++)
2185 SanitizeString strips color tags from the string in
2186 and writes the result on string out
2188 static void SanitizeString(char *in, char *out)
2192 if(*in == STRING_COLOR_TAG)
2197 out[0] = STRING_COLOR_TAG;
2201 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2208 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2211 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2213 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2220 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2225 else if (*in != STRING_COLOR_TAG)
2228 *out = qfont_table[*(unsigned char*)in];
2235 // Now it becomes TRICKY :D --blub
2236 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2237 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2238 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2239 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
2240 static int Nicks_matchpos;
2242 // co against <<:BLASTER:>> is true!?
2243 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2247 if(tolower(*a) == tolower(*b))
2261 return (*a < *b) ? -1 : 1;
2265 return (*a < *b) ? -1 : 1;
2269 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2272 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2274 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2275 return Nicks_strncasecmp_nospaces(a, b, a_len);
2276 return strncasecmp(a, b, a_len);
2279 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2281 // ignore non alphanumerics of B
2282 // if A contains a non-alphanumeric, B must contain it as well though!
2285 qboolean alnum_a, alnum_b;
2287 if(tolower(*a) == tolower(*b))
2289 if(*a == 0) // end of both strings, they're equal
2296 // not equal, end of one string?
2301 // ignore non alphanumerics
2302 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2303 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2304 if(!alnum_a) // b must contain this
2305 return (*a < *b) ? -1 : 1;
2308 // otherwise, both are alnum, they're just not equal, return the appropriate number
2310 return (*a < *b) ? -1 : 1;
2316 /* Nicks_CompleteCountPossible
2318 Count the number of possible nicks to complete
2320 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2328 if(!con_nickcompletion.integer)
2331 // changed that to 1
2332 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2335 for(i = 0; i < cl.maxclients; ++i)
2338 if(!cl.scores[p].name[0])
2341 SanitizeString(cl.scores[p].name, name);
2342 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2348 spos = pos - 1; // no need for a minimum of characters :)
2352 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2354 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2355 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2361 if(isCon && spos == 0)
2363 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2369 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2370 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2372 // the sanitized list
2373 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2376 Nicks_matchpos = match;
2379 Nicks_offset[count] = s - (&line[match]);
2380 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2387 static void Cmd_CompleteNicksPrint(int count)
2390 for(i = 0; i < count; ++i)
2391 Con_Printf("%s\n", Nicks_list[i]);
2394 static void Nicks_CutMatchesNormal(int count)
2396 // cut match 0 down to the longest possible completion
2399 c = strlen(Nicks_sanlist[0]) - 1;
2400 for(i = 1; i < count; ++i)
2402 l = strlen(Nicks_sanlist[i]) - 1;
2406 for(l = 0; l <= c; ++l)
2407 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2413 Nicks_sanlist[0][c+1] = 0;
2414 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2417 static unsigned int Nicks_strcleanlen(const char *s)
2422 if( (*s >= 'a' && *s <= 'z') ||
2423 (*s >= 'A' && *s <= 'Z') ||
2424 (*s >= '0' && *s <= '9') ||
2432 static void Nicks_CutMatchesAlphaNumeric(int count)
2434 // cut match 0 down to the longest possible completion
2437 char tempstr[sizeof(Nicks_sanlist[0])];
2439 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2441 c = strlen(Nicks_sanlist[0]);
2442 for(i = 0, l = 0; i < (int)c; ++i)
2444 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2445 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2446 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2448 tempstr[l++] = Nicks_sanlist[0][i];
2453 for(i = 1; i < count; ++i)
2456 b = Nicks_sanlist[i];
2466 if(tolower(*a) == tolower(*b))
2472 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2474 // b is alnum, so cut
2481 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2482 Nicks_CutMatchesNormal(count);
2483 //if(!Nicks_sanlist[0][0])
2484 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2486 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2487 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2491 static void Nicks_CutMatchesNoSpaces(int count)
2493 // cut match 0 down to the longest possible completion
2496 char tempstr[sizeof(Nicks_sanlist[0])];
2499 c = strlen(Nicks_sanlist[0]);
2500 for(i = 0, l = 0; i < (int)c; ++i)
2502 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2504 tempstr[l++] = Nicks_sanlist[0][i];
2509 for(i = 1; i < count; ++i)
2512 b = Nicks_sanlist[i];
2522 if(tolower(*a) == tolower(*b))
2536 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2537 Nicks_CutMatchesNormal(count);
2538 //if(!Nicks_sanlist[0][0])
2539 //Con_Printf("TS: %s\n", tempstr);
2540 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2542 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2543 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2547 static void Nicks_CutMatches(int count)
2549 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2550 Nicks_CutMatchesAlphaNumeric(count);
2551 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2552 Nicks_CutMatchesNoSpaces(count);
2554 Nicks_CutMatchesNormal(count);
2557 static const char **Nicks_CompleteBuildList(int count)
2561 // the list is freed by Con_CompleteCommandLine, so create a char**
2562 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2564 for(; bpos < count; ++bpos)
2565 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2567 Nicks_CutMatches(count);
2575 Restores the previous used color, after the autocompleted name.
2577 static int Nicks_AddLastColor(char *buffer, int pos)
2579 qboolean quote_added = false;
2581 int color = STRING_COLOR_DEFAULT + '0';
2582 char r = 0, g = 0, b = 0;
2584 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2586 // we'll have to add a quote :)
2587 buffer[pos++] = '\"';
2591 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2593 // add color when no quote was added, or when flags &4?
2595 for(match = Nicks_matchpos-1; match >= 0; --match)
2597 if(buffer[match] == STRING_COLOR_TAG)
2599 if( isdigit(buffer[match+1]) )
2601 color = buffer[match+1];
2604 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2606 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2608 r = buffer[match+2];
2609 g = buffer[match+3];
2610 b = buffer[match+4];
2619 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2621 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2622 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2625 buffer[pos++] = STRING_COLOR_TAG;
2628 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2634 buffer[pos++] = color;
2639 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2642 /*if(!con_nickcompletion.integer)
2643 return; is tested in Nicks_CompletionCountPossible */
2644 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2650 msg = Nicks_list[0];
2651 len = min(size - Nicks_matchpos - 3, strlen(msg));
2652 memcpy(&buffer[Nicks_matchpos], msg, len);
2653 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2654 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2655 buffer[len++] = ' ';
2662 Con_Printf("\n%i possible nicks:\n", n);
2663 Cmd_CompleteNicksPrint(n);
2665 Nicks_CutMatches(n);
2667 msg = Nicks_sanlist[0];
2668 len = min(size - Nicks_matchpos, strlen(msg));
2669 memcpy(&buffer[Nicks_matchpos], msg, len);
2670 buffer[Nicks_matchpos + len] = 0;
2672 return Nicks_matchpos + len;
2679 Con_CompleteCommandLine
2681 New function for tab-completion system
2682 Added by EvilTypeGuy
2683 Thanks to Fett erich@heintz.com
2685 Enhanced to tab-complete map names by [515]
2688 void Con_CompleteCommandLine (void)
2690 const char *cmd = "";
2692 const char **list[4] = {0, 0, 0, 0};
2695 int c, v, a, i, cmd_len, pos, k;
2696 int n; // nicks --blub
2697 const char *space, *patterns;
2700 //find what we want to complete
2705 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2711 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2712 key_line[key_linepos] = 0; //hide them
2714 space = strchr(key_line + 1, ' ');
2715 if(space && pos == (space - key_line) + 1)
2717 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2719 patterns = Cvar_VariableString(va(vabuf, sizeof(vabuf), "con_completion_%s", command)); // TODO maybe use a better place for this?
2720 if(patterns && !*patterns)
2721 patterns = NULL; // get rid of the empty string
2723 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2727 if (GetMapList(s, t, sizeof(t)))
2729 // first move the cursor
2730 key_linepos += (int)strlen(t) - (int)strlen(s);
2732 // and now do the actual work
2734 strlcat(key_line, t, MAX_INPUTLINE);
2735 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2737 // and fix the cursor
2738 if(key_linepos > (int) strlen(key_line))
2739 key_linepos = (int) strlen(key_line);
2748 stringlist_t resultbuf, dirbuf;
2751 // // store completion patterns (space separated) for command foo in con_completion_foo
2752 // set con_completion_foo "foodata/*.foodefault *.foo"
2755 // Note: patterns with slash are always treated as absolute
2756 // patterns; patterns without slash search in the innermost
2757 // directory the user specified. There is no way to "complete into"
2758 // a directory as of now, as directories seem to be unknown to the
2762 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2763 // set con_completion_playdemo "*.dem"
2764 // set con_completion_play "*.wav *.ogg"
2766 // TODO somehow add support for directories; these shall complete
2767 // to their name + an appended slash.
2769 stringlistinit(&resultbuf);
2770 stringlistinit(&dirbuf);
2771 while(COM_ParseToken_Simple(&patterns, false, false))
2774 if(strchr(com_token, '/'))
2776 search = FS_Search(com_token, true, true);
2780 const char *slash = strrchr(s, '/');
2783 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2784 strlcat(t, com_token, sizeof(t));
2785 search = FS_Search(t, true, true);
2788 search = FS_Search(com_token, true, true);
2792 for(i = 0; i < search->numfilenames; ++i)
2793 if(!strncmp(search->filenames[i], s, strlen(s)))
2794 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2795 stringlistappend(&resultbuf, search->filenames[i]);
2796 FS_FreeSearch(search);
2800 // In any case, add directory names
2803 const char *slash = strrchr(s, '/');
2806 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2807 strlcat(t, "*", sizeof(t));
2808 search = FS_Search(t, true, true);
2811 search = FS_Search("*", 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_DIRECTORY)
2817 stringlistappend(&dirbuf, search->filenames[i]);
2818 FS_FreeSearch(search);
2822 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2825 unsigned int matchchars;
2826 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2828 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2831 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2833 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2837 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2838 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2839 for(i = 0; i < dirbuf.numstrings; ++i)
2841 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2843 for(i = 0; i < resultbuf.numstrings; ++i)
2845 Con_Printf("%s\n", resultbuf.strings[i]);
2847 matchchars = sizeof(t) - 1;
2848 if(resultbuf.numstrings > 0)
2850 p = resultbuf.strings[0];
2851 q = resultbuf.strings[resultbuf.numstrings - 1];
2852 for(; *p && *p == *q; ++p, ++q);
2853 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2855 if(dirbuf.numstrings > 0)
2857 p = dirbuf.strings[0];
2858 q = dirbuf.strings[dirbuf.numstrings - 1];
2859 for(; *p && *p == *q; ++p, ++q);
2860 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2862 // now p points to the first non-equal character, or to the end
2863 // of resultbuf.strings[0]. We want to append the characters
2864 // from resultbuf.strings[0] to (not including) p as these are
2865 // the unique prefix
2866 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2869 // first move the cursor
2870 key_linepos += (int)strlen(t) - (int)strlen(s);
2872 // and now do the actual work
2874 strlcat(key_line, t, MAX_INPUTLINE);
2875 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2877 // and fix the cursor
2878 if(key_linepos > (int) strlen(key_line))
2879 key_linepos = (int) strlen(key_line);
2881 stringlistfreecontents(&resultbuf);
2882 stringlistfreecontents(&dirbuf);
2884 return; // bail out, when we complete for a command that wants a file name
2889 // Count number of possible matches and print them
2890 c = Cmd_CompleteCountPossible(s);
2893 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2894 Cmd_CompleteCommandPrint(s);
2896 v = Cvar_CompleteCountPossible(s);
2899 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2900 Cvar_CompleteCvarPrint(s);
2902 a = Cmd_CompleteAliasCountPossible(s);
2905 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2906 Cmd_CompleteAliasPrint(s);
2908 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2911 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2912 Cmd_CompleteNicksPrint(n);
2915 if (!(c + v + a + n)) // No possible matches
2918 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2923 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2925 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2927 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2929 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2931 for (cmd_len = (int)strlen(s);;cmd_len++)
2934 for (i = 0; i < 3; i++)
2936 for (l = list[i];*l;l++)
2937 if ((*l)[cmd_len] != cmd[cmd_len])
2939 // all possible matches share this character, so we continue...
2942 // if all matches ended at the same position, stop
2943 // (this means there is only one match)
2949 // prevent a buffer overrun by limiting cmd_len according to remaining space
2950 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2954 memcpy(&key_line[key_linepos], cmd, cmd_len);
2955 key_linepos += cmd_len;
2956 // if there is only one match, add a space after it
2957 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2960 { // was a nick, might have an offset, and needs colors ;) --blub
2961 key_linepos = pos - Nicks_offset[0];
2962 cmd_len = strlen(Nicks_list[0]);
2963 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2965 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2966 key_linepos += cmd_len;
2967 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2968 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2970 key_line[key_linepos++] = ' ';
2974 // use strlcat to avoid a buffer overrun
2975 key_line[key_linepos] = 0;
2976 strlcat(key_line, s2, sizeof(key_line));
2978 // free the command, cvar, and alias lists
2979 for (i = 0; i < 4; i++)
2981 Mem_Free((void *)list[i]);