2 Copyright (C) 1996-1997 Id Software, Inc.
3 Copyright (C) 2000-2020 DarkPlaces contributors
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 See the GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #if !defined(WIN32) || defined(__MINGW32__)
34 float con_cursorspeed = 4;
36 // lines up from bottom to display
40 void *con_mutex = NULL;
42 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
43 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
44 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
46 cvar_t con_notifytime = {CF_CLIENT | CF_ARCHIVE, "con_notifytime","3", "how long notify lines last, in seconds"};
47 cvar_t con_notify = {CF_CLIENT | CF_ARCHIVE, "con_notify","4", "how many notify lines to show"};
48 cvar_t con_notifyalign = {CF_CLIENT | CF_ARCHIVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
50 cvar_t con_chattime = {CF_CLIENT | CF_ARCHIVE, "con_chattime","30", "how long chat lines last, in seconds"};
51 cvar_t con_chat = {CF_CLIENT | CF_ARCHIVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
52 cvar_t con_chatpos = {CF_CLIENT | CF_ARCHIVE, "con_chatpos","0", "where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top)"};
53 cvar_t con_chatrect = {CF_CLIENT | CF_ARCHIVE, "con_chatrect","0", "use con_chatrect_x and _y to position con_notify and con_chat freely instead of con_chatpos"};
54 cvar_t con_chatrect_x = {CF_CLIENT | CF_ARCHIVE, "con_chatrect_x","", "where to put chat, relative x coordinate of left edge on screen (use con_chatwidth for width)"};
55 cvar_t con_chatrect_y = {CF_CLIENT | CF_ARCHIVE, "con_chatrect_y","", "where to put chat, relative y coordinate of top edge on screen (use con_chat for line count)"};
56 cvar_t con_chatwidth = {CF_CLIENT | CF_ARCHIVE, "con_chatwidth","1.0", "relative chat window width"};
57 cvar_t con_textsize = {CF_CLIENT | CF_ARCHIVE, "con_textsize","8", "console text size in virtual 2D pixels"};
58 cvar_t con_notifysize = {CF_CLIENT | CF_ARCHIVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
59 cvar_t con_chatsize = {CF_CLIENT | CF_ARCHIVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
60 cvar_t con_chatsound = {CF_CLIENT | CF_ARCHIVE, "con_chatsound","1", "enables chat sound to play on message"};
61 cvar_t con_chatsound_file = {CF_CLIENT, "con_chatsound_file","sound/misc/talk.wav", "The sound to play for chat messages"};
62 cvar_t con_chatsound_team_file = {CF_CLIENT, "con_chatsound_team_file","sound/misc/talk2.wav", "The sound to play for team chat messages"};
63 cvar_t con_chatsound_team_mask = {CF_CLIENT, "con_chatsound_team_mask","40","Magic ASCII code that denotes a team chat message"};
65 cvar_t sys_specialcharactertranslation = {CF_CLIENT | CF_SERVER, "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)"};
67 cvar_t sys_colortranslation = {CF_CLIENT | CF_SERVER, "sys_colortranslation", "0", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
69 cvar_t sys_colortranslation = {CF_CLIENT | CF_SERVER, "sys_colortranslation", "1", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
73 cvar_t con_nickcompletion = {CF_CLIENT | CF_ARCHIVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
74 cvar_t con_nickcompletion_flags = {CF_CLIENT | CF_ARCHIVE, "con_nickcompletion_flags", "11", "Bitfield: "
75 "0: add nothing after completion. "
76 "1: add the last color after completion. "
77 "2: add a quote when starting a quote instead of the color. "
78 "4: will replace 1, will force color, even after a quote. "
79 "8: ignore non-alphanumerics. "
80 "16: ignore spaces. "};
81 #define NICKS_ADD_COLOR 1
82 #define NICKS_ADD_QUOTE 2
83 #define NICKS_FORCE_COLOR 4
84 #define NICKS_ALPHANUMERICS_ONLY 8
85 #define NICKS_NO_SPACES 16
87 cvar_t con_completion_playdemo = {CF_CLIENT | CF_ARCHIVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
88 cvar_t con_completion_timedemo = {CF_CLIENT | CF_ARCHIVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
89 cvar_t con_completion_exec = {CF_CLIENT | CF_ARCHIVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
91 cvar_t condump_stripcolors = {CF_CLIENT | CF_SERVER| CF_ARCHIVE, "condump_stripcolors", "0", "strip color codes from console dumps"};
93 cvar_t rcon_password = {CF_CLIENT | CF_SERVER | CF_PRIVATE, "rcon_password", "", "password to authenticate rcon commands; NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"};
94 cvar_t rcon_secure = {CF_CLIENT | CF_SERVER, "rcon_secure", "0", "force secure rcon authentication (1 = time based, 2 = challenge based); NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password"};
95 cvar_t rcon_secure_challengetimeout = {CF_CLIENT, "rcon_secure_challengetimeout", "5", "challenge-based secure rcon: time out requests if no challenge came within this time interval"};
96 cvar_t rcon_address = {CF_CLIENT, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"};
101 qbool con_initialized;
103 // used for server replies to rcon command
104 lhnetsocket_t *rcon_redirect_sock = NULL;
105 lhnetaddress_t *rcon_redirect_dest = NULL;
106 int rcon_redirect_bufferpos = 0;
107 char rcon_redirect_buffer[1400];
108 qbool rcon_redirect_proquakeprotocol = false;
110 // generic functions for console buffers
112 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
115 buf->textsize = textsize;
116 buf->text = (char *) Mem_Alloc(mempool, textsize);
117 buf->maxlines = maxlines;
118 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
119 buf->lines_first = 0;
120 buf->lines_count = 0;
123 /*! The translation table between the graphical font and plain ASCII --KB */
124 static char qfont_table[256] = {
125 '\0', '#', '#', '#', '#', '.', '#', '#',
126 '#', 9, 10, '#', ' ', 13, '.', '.',
127 '[', ']', '0', '1', '2', '3', '4', '5',
128 '6', '7', '8', '9', '.', '<', '=', '>',
129 ' ', '!', '"', '#', '$', '%', '&', '\'',
130 '(', ')', '*', '+', ',', '-', '.', '/',
131 '0', '1', '2', '3', '4', '5', '6', '7',
132 '8', '9', ':', ';', '<', '=', '>', '?',
133 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
134 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
135 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
136 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
137 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
138 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
139 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
140 'x', 'y', 'z', '{', '|', '}', '~', '<',
142 '<', '=', '>', '#', '#', '.', '#', '#',
143 '#', '#', ' ', '#', ' ', '>', '.', '.',
144 '[', ']', '0', '1', '2', '3', '4', '5',
145 '6', '7', '8', '9', '.', '<', '=', '>',
146 ' ', '!', '"', '#', '$', '%', '&', '\'',
147 '(', ')', '*', '+', ',', '-', '.', '/',
148 '0', '1', '2', '3', '4', '5', '6', '7',
149 '8', '9', ':', ';', '<', '=', '>', '?',
150 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
151 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
152 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
153 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
154 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
155 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
156 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
157 'x', 'y', 'z', '{', '|', '}', '~', '<'
161 SanitizeString strips color tags from the string in
162 and writes the result on string out
164 static void SanitizeString(char *in, char *out)
168 if(*in == STRING_COLOR_TAG)
173 out[0] = STRING_COLOR_TAG;
177 else if (*in >= '0' && *in <= '9') // ^[0-9] found
184 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
187 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
189 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
196 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
201 else if (*in != STRING_COLOR_TAG)
204 *out = qfont_table[*(unsigned char*)in];
216 void ConBuffer_Clear (conbuffer_t *buf)
218 buf->lines_count = 0;
226 void ConBuffer_Shutdown(conbuffer_t *buf)
232 Mem_Free(buf->lines);
241 Notifies the console code about the current time
242 (and shifts back times of other entries when the time
246 void ConBuffer_FixTimes(conbuffer_t *buf)
249 if(buf->lines_count >= 1)
251 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
254 for(i = 0; i < buf->lines_count; ++i)
255 CONBUFFER_LINES(buf, i).addtime += diff;
264 Deletes the first line from the console history.
267 void ConBuffer_DeleteLine(conbuffer_t *buf)
269 if(buf->lines_count == 0)
272 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
277 ConBuffer_DeleteLastLine
279 Deletes the last line from the console history.
282 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
284 if(buf->lines_count == 0)
293 Checks if there is space for a line of the given length, and if yes, returns a
294 pointer to the start of such a space, and NULL otherwise.
297 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
299 if(len > buf->textsize)
301 if(buf->lines_count == 0)
305 char *firstline_start = buf->lines[buf->lines_first].start;
306 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
307 // the buffer is cyclic, so we first have two cases...
308 if(firstline_start < lastline_onepastend) // buffer is contiguous
311 if(len <= buf->text + buf->textsize - lastline_onepastend)
312 return lastline_onepastend;
314 else if(len <= firstline_start - buf->text)
319 else // buffer has a contiguous hole
321 if(len <= firstline_start - lastline_onepastend)
322 return lastline_onepastend;
333 Appends a given string as a new line to the console.
336 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
341 // developer_memory 1 during shutdown prints while conbuffer_t is being freed
345 ConBuffer_FixTimes(buf);
347 if(len >= buf->textsize)
350 // only display end of line.
351 line += len - buf->textsize + 1;
352 len = buf->textsize - 1;
354 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
355 ConBuffer_DeleteLine(buf);
356 memcpy(putpos, line, len);
360 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
362 p = &CONBUFFER_LINES_LAST(buf);
365 p->addtime = cl.time;
367 p->height = -1; // calculate when needed
370 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
374 start = buf->lines_count;
375 for(i = start - 1; i >= 0; --i)
377 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
379 if((l->mask & mask_must) != mask_must)
381 if(l->mask & mask_mustnot)
390 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
392 static char copybuf[MAX_INPUTLINE]; // client only
393 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
394 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
395 strlcpy(copybuf, l->start, sz);
400 ==============================================================================
404 ==============================================================================
409 cvar_t log_file = {CF_CLIENT | CF_SERVER, "log_file", "", "filename to log messages to"};
410 cvar_t log_file_stripcolors = {CF_CLIENT | CF_SERVER, "log_file_stripcolors", "0", "strip color codes from log messages"};
411 cvar_t log_dest_udp = {CF_CLIENT | CF_SERVER, "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"};
412 char log_dest_buffer[1400]; // UDP packet
413 size_t log_dest_buffer_pos;
414 unsigned int log_dest_buffer_appending;
415 char crt_log_file [MAX_OSPATH] = "";
416 qfile_t* logfile = NULL;
418 unsigned char* logqueue = NULL;
420 size_t logq_size = 0;
422 void Log_ConPrint (const char *msg);
424 static void Log_DestBuffer_Init(void)
426 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
427 log_dest_buffer_pos = 5;
430 static void Log_DestBuffer_Flush_NoLock(void)
432 lhnetaddress_t log_dest_addr;
433 lhnetsocket_t *log_dest_socket;
434 const char *s = log_dest_udp.string;
435 qbool have_opened_temp_sockets = false;
436 if(s) if(log_dest_buffer_pos > 5)
438 ++log_dest_buffer_appending;
439 log_dest_buffer[log_dest_buffer_pos++] = 0;
441 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
443 have_opened_temp_sockets = true;
444 NetConn_OpenServerPorts(true);
447 while(COM_ParseToken_Console(&s))
448 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
450 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
452 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
454 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
457 if(have_opened_temp_sockets)
458 NetConn_CloseServerPorts();
459 --log_dest_buffer_appending;
461 log_dest_buffer_pos = 0;
469 void Log_DestBuffer_Flush(void)
472 Thread_LockMutex(con_mutex);
473 Log_DestBuffer_Flush_NoLock();
475 Thread_UnlockMutex(con_mutex);
478 static const char* Log_Timestamp (const char *desc)
480 static char timestamp [128]; // init/shutdown only
487 char timestring [64];
489 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
492 localtime_s (&crt_tm, &crt_time);
493 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
495 crt_tm = localtime (&crt_time);
496 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
500 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
502 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
507 static void Log_Open (void)
509 if (logfile != NULL || log_file.string[0] == '\0')
512 logfile = FS_OpenRealFile(log_file.string, "a", false);
515 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
516 FS_Print (logfile, Log_Timestamp ("Log started"));
525 void Log_Close (void)
527 qfile_t* l = logfile;
532 FS_Print (l, Log_Timestamp ("Log stopped"));
537 crt_log_file[0] = '\0';
546 void Log_Start (void)
552 // Dump the contents of the log queue into the log file and free it
553 if (logqueue != NULL)
555 unsigned char *temp = logqueue;
560 FS_Write (logfile, temp, logq_ind);
561 if(*log_dest_udp.string)
563 for(pos = 0; pos < logq_ind; )
565 if(log_dest_buffer_pos == 0)
566 Log_DestBuffer_Init();
567 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
568 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
569 log_dest_buffer_pos += n;
570 Log_DestBuffer_Flush_NoLock();
588 void Log_ConPrint (const char *msg)
590 static qbool inprogress = false;
592 // don't allow feedback loops with memory error reports
597 // Until the host is completely initialized, we maintain a log queue
598 // to store the messages, since the log can't be started before
599 if (logqueue != NULL)
601 size_t remain = logq_size - logq_ind;
602 size_t len = strlen (msg);
604 // If we need to enlarge the log queue
607 size_t factor = ((logq_ind + len) / logq_size) + 1;
608 unsigned char* newqueue;
611 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
612 memcpy (newqueue, logqueue, logq_ind);
615 remain = logq_size - logq_ind;
617 memcpy (&logqueue[logq_ind], msg, len);
624 // Check if log_file has changed
625 if (strcmp (crt_log_file, log_file.string) != 0)
631 // If a log file is available
634 if (log_file_stripcolors.integer)
637 size_t len = strlen(msg);
638 char* sanitizedmsg = (char*)Mem_Alloc(tempmempool, len + 1);
639 memcpy (sanitizedmsg, msg, len);
640 SanitizeString(sanitizedmsg, sanitizedmsg); // SanitizeString's in pointer is always ahead of the out pointer, so this should work.
641 FS_Print (logfile, sanitizedmsg);
642 Mem_Free(sanitizedmsg);
646 FS_Print (logfile, msg);
659 void Log_Printf (const char *logfilename, const char *fmt, ...)
663 file = FS_OpenRealFile(logfilename, "a", true);
668 va_start (argptr, fmt);
669 FS_VPrintf (file, fmt, argptr);
678 ==============================================================================
682 ==============================================================================
690 void Con_ToggleConsole_f(cmd_state_t *cmd)
692 if (Sys_CheckParm ("-noconsole"))
693 if (!(key_consoleactive & KEY_CONSOLEACTIVE_USER))
694 return; // only allow the key bind to turn off console
696 // toggle the 'user wants console' bit
697 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
706 void Con_ClearNotify (void)
709 for(i = 0; i < CON_LINES_COUNT; ++i)
710 if(!(CON_LINES(i).mask & CON_MASK_CHAT))
711 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
714 static void Con_MsgCmdMode(cmd_state_t *cmd, signed char mode)
716 if (cls.demoplayback && mode >= 0)
718 key_dest = key_message;
720 if(Cmd_Argc(cmd) > 1)
722 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
723 chat_bufferpos = (unsigned int)strlen(chat_buffer);
734 static void Con_MessageMode_f(cmd_state_t *cmd)
736 Con_MsgCmdMode(cmd, 0);
746 static void Con_MessageMode2_f(cmd_state_t *cmd)
748 Con_MsgCmdMode(cmd, 1);
756 static void Con_CommandMode_f(cmd_state_t *cmd)
758 Con_MsgCmdMode(cmd, -1);
766 void Con_CheckResize (void)
771 f = bound(1, con_textsize.value, 128);
772 if(f != con_textsize.value)
773 Cvar_SetValueQuick(&con_textsize, f);
774 width = (int)floor(vid_conwidth.value / con_textsize.value);
775 width = bound(1, width, con.textsize/4);
776 // FIXME uses con in a non abstracted way
778 if (width == con_linewidth)
781 con_linewidth = width;
783 for(i = 0; i < CON_LINES_COUNT; ++i)
784 CON_LINES(i).height = -1; // recalculate when next needed
790 //[515]: the simplest command ever
791 //LadyHavoc: not so simple after I made it print usage...
792 static void Con_Maps_f(cmd_state_t *cmd)
794 if (Cmd_Argc(cmd) > 2)
796 Con_Printf("usage: maps [mapnameprefix]\n");
799 else if (Cmd_Argc(cmd) == 2)
800 GetMapList(Cmd_Argv(cmd, 1), NULL, 0);
802 GetMapList("", NULL, 0);
805 static void Con_ConDump_f(cmd_state_t *cmd)
809 if (Cmd_Argc(cmd) != 2)
811 Con_Printf("usage: condump <filename>\n");
814 file = FS_OpenRealFile(Cmd_Argv(cmd, 1), "w", false);
817 Con_Printf(CON_ERROR "condump: unable to write file \"%s\"\n", Cmd_Argv(cmd, 1));
820 if (con_mutex) Thread_LockMutex(con_mutex);
821 for(i = 0; i < CON_LINES_COUNT; ++i)
823 if (condump_stripcolors.integer)
826 size_t len = CON_LINES(i).len;
827 char* sanitizedmsg = (char*)Mem_Alloc(tempmempool, len + 1);
828 memcpy (sanitizedmsg, CON_LINES(i).start, len);
829 SanitizeString(sanitizedmsg, sanitizedmsg); // SanitizeString's in pointer is always ahead of the out pointer, so this should work.
830 FS_Write(file, sanitizedmsg, strlen(sanitizedmsg));
831 Mem_Free(sanitizedmsg);
835 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
837 FS_Write(file, "\n", 1);
839 if (con_mutex) Thread_UnlockMutex(con_mutex);
843 void Con_Clear_f(cmd_state_t *cmd)
845 if (con_mutex) Thread_LockMutex(con_mutex);
846 ConBuffer_Clear(&con);
847 if (con_mutex) Thread_UnlockMutex(con_mutex);
850 static void Con_RCon_ClearPassword_c(cvar_t *var)
852 // whenever rcon_secure is changed to 0, clear rcon_password for
853 // security reasons (prevents a send-rcon-password-as-plaintext
854 // attack based on NQ protocol session takeover and svc_stufftext)
855 if(var->integer <= 0)
856 Cvar_SetQuick(&rcon_password, "");
867 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
868 if (Thread_HasThreads())
869 con_mutex = Thread_CreateMutex();
871 // Allocate a log queue, this will be freed after configs are parsed
872 logq_size = MAX_INPUTLINE;
873 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
876 Cvar_RegisterVariable (&sys_colortranslation);
877 Cvar_RegisterVariable (&sys_specialcharactertranslation);
879 Cvar_RegisterVariable (&log_file);
880 Cvar_RegisterVariable (&log_file_stripcolors);
881 Cvar_RegisterVariable (&log_dest_udp);
883 // support for the classic Quake option
884 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
885 if (Sys_CheckParm ("-condebug") != 0)
886 Cvar_SetQuick (&log_file, "qconsole.log");
888 // register our cvars
889 Cvar_RegisterVariable (&con_chat);
890 Cvar_RegisterVariable (&con_chatpos);
891 Cvar_RegisterVariable (&con_chatrect_x);
892 Cvar_RegisterVariable (&con_chatrect_y);
893 Cvar_RegisterVariable (&con_chatrect);
894 Cvar_RegisterVariable (&con_chatsize);
895 Cvar_RegisterVariable (&con_chattime);
896 Cvar_RegisterVariable (&con_chatwidth);
897 Cvar_RegisterVariable (&con_notify);
898 Cvar_RegisterVariable (&con_notifyalign);
899 Cvar_RegisterVariable (&con_notifysize);
900 Cvar_RegisterVariable (&con_notifytime);
901 Cvar_RegisterVariable (&con_textsize);
902 Cvar_RegisterVariable (&con_chatsound);
903 Cvar_RegisterVariable (&con_chatsound_file);
904 Cvar_RegisterVariable (&con_chatsound_team_file);
905 Cvar_RegisterVariable (&con_chatsound_team_mask);
908 Cvar_RegisterVariable (&con_nickcompletion);
909 Cvar_RegisterVariable (&con_nickcompletion_flags);
911 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
912 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
913 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
915 Cvar_RegisterVariable (&condump_stripcolors);
917 Cvar_RegisterVariable(&rcon_address);
918 Cvar_RegisterVariable(&rcon_secure);
919 Cvar_RegisterCallback(&rcon_secure, Con_RCon_ClearPassword_c);
920 Cvar_RegisterVariable(&rcon_secure_challengetimeout);
921 Cvar_RegisterVariable(&rcon_password);
923 // register our commands
924 Cmd_AddCommand(CF_CLIENT, "toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
925 Cmd_AddCommand(CF_CLIENT, "messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
926 Cmd_AddCommand(CF_CLIENT, "messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
927 Cmd_AddCommand(CF_CLIENT, "commandmode", Con_CommandMode_f, "input a console command");
928 Cmd_AddCommand(CF_SHARED, "clear", Con_Clear_f, "clear console history");
929 Cmd_AddCommand(CF_SHARED, "maps", Con_Maps_f, "list information about available maps");
930 Cmd_AddCommand(CF_SHARED, "condump", Con_ConDump_f, "output console history to a file (see also log_file)");
932 con_initialized = true;
935 void Con_Shutdown (void)
937 if (con_mutex) Thread_LockMutex(con_mutex);
938 ConBuffer_Shutdown(&con);
939 if (con_mutex) Thread_UnlockMutex(con_mutex);
940 if (con_mutex) Thread_DestroyMutex(con_mutex);
948 Handles cursor positioning, line wrapping, etc
949 All console printing must go through this in order to be displayed
950 If no console is visible, the notify window will pop up.
953 static void Con_PrintToHistory(const char *txt, int mask)
956 // \n goes to next line
957 // \r deletes current line and makes a new one
959 static int cr_pending = 0;
960 static char buf[CON_TEXTSIZE]; // con_mutex
961 static int bufpos = 0;
963 if(!con.text) // FIXME uses a non-abstracted property of con
970 ConBuffer_DeleteLastLine(&con);
978 ConBuffer_AddLine(&con, buf, bufpos, mask);
983 ConBuffer_AddLine(&con, buf, bufpos, mask);
987 buf[bufpos++] = *txt;
988 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
990 ConBuffer_AddLine(&con, buf, bufpos, mask);
998 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qbool proquakeprotocol)
1000 rcon_redirect_sock = sock;
1001 rcon_redirect_dest = dest;
1002 rcon_redirect_proquakeprotocol = proquakeprotocol;
1003 if (rcon_redirect_proquakeprotocol)
1005 // reserve space for the packet header
1006 rcon_redirect_buffer[0] = 0;
1007 rcon_redirect_buffer[1] = 0;
1008 rcon_redirect_buffer[2] = 0;
1009 rcon_redirect_buffer[3] = 0;
1010 // this is a reply to a CCREQ_RCON
1011 rcon_redirect_buffer[4] = (unsigned char)CCREP_RCON;
1014 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1015 rcon_redirect_bufferpos = 5;
1018 static void Con_Rcon_Redirect_Flush(void)
1020 if(rcon_redirect_sock)
1022 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
1023 if (rcon_redirect_proquakeprotocol)
1025 // update the length in the packet header
1026 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
1028 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
1030 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1031 rcon_redirect_bufferpos = 5;
1032 rcon_redirect_proquakeprotocol = false;
1035 void Con_Rcon_Redirect_End(void)
1037 Con_Rcon_Redirect_Flush();
1038 rcon_redirect_dest = NULL;
1039 rcon_redirect_sock = NULL;
1042 void Con_Rcon_Redirect_Abort(void)
1044 rcon_redirect_dest = NULL;
1045 rcon_redirect_sock = NULL;
1053 /// Adds a character to the rcon buffer.
1054 static void Con_Rcon_AddChar(int c)
1056 if(log_dest_buffer_appending)
1058 ++log_dest_buffer_appending;
1060 // if this print is in response to an rcon command, add the character
1061 // to the rcon redirect buffer
1063 if (rcon_redirect_dest)
1065 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
1066 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
1067 Con_Rcon_Redirect_Flush();
1069 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
1071 if(log_dest_buffer_pos == 0)
1072 Log_DestBuffer_Init();
1073 log_dest_buffer[log_dest_buffer_pos++] = c;
1074 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
1075 Log_DestBuffer_Flush_NoLock();
1078 log_dest_buffer_pos = 0;
1080 --log_dest_buffer_appending;
1084 * Convert an RGB color to its nearest quake color.
1085 * I'll cheat on this a bit by translating the colors to HSV first,
1086 * S and V decide if it's black or white, otherwise, H will decide the
1088 * @param _r Red (0-255)
1089 * @param _g Green (0-255)
1090 * @param _b Blue (0-255)
1091 * @return A quake color character.
1093 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
1095 float r = ((float)_r)/255.0;
1096 float g = ((float)_g)/255.0;
1097 float b = ((float)_b)/255.0;
1098 float min = min(r, min(g, b));
1099 float max = max(r, max(g, b));
1101 int h; ///< Hue angle [0,360]
1102 float s; ///< Saturation [0,1]
1103 float v = max; ///< In HSV v == max [0,1]
1108 s = 1.0 - (min/max);
1110 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1113 // If the value is less than half, return a black color code.
1114 // Otherwise return a white one.
1120 // Let's get the hue angle to define some colors:
1124 h = (int)(60.0 * (g-b)/(max-min))%360;
1126 h = (int)(60.0 * (b-r)/(max-min) + 120);
1127 else // if(max == b) redundant check
1128 h = (int)(60.0 * (r-g)/(max-min) + 240);
1130 if(h < 36) // *red* to orange
1132 else if(h < 80) // orange over *yellow* to evilish-bright-green
1134 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1136 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1138 else if(h < 270) // darkish blue over *dark blue* to cool purple
1140 else if(h < 330) // cool purple over *purple* to ugly swiny red
1142 else // ugly red to red closes the circly
1151 extern cvar_t timestamps;
1152 extern cvar_t timeformat;
1153 void Con_MaskPrint(int additionalmask, const char *msg)
1155 static int mask = 0;
1156 static int index = 0;
1157 static char line[MAX_INPUTLINE];
1160 Thread_LockMutex(con_mutex);
1164 Con_Rcon_AddChar(*msg);
1165 // if this is the beginning of a new line, print timestamp
1168 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1170 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1171 line[index++] = STRING_COLOR_TAG;
1172 // assert( STRING_COLOR_DEFAULT < 10 )
1173 line[index++] = STRING_COLOR_DEFAULT + '0';
1174 // special color codes for chat messages must always come first
1175 // for Con_PrintToHistory to work properly
1176 if (*msg == 1 || *msg == 2 || *msg == 3)
1181 if (con_chatsound.value)
1183 if(msg[1] == con_chatsound_team_mask.integer && cl.foundteamchatsound)
1184 S_LocalSound (con_chatsound_team_file.string);
1186 S_LocalSound (con_chatsound_file.string);
1189 // Send to chatbox for say/tell (1) and messages (3)
1190 // 3 is just so that a message can be sent to the chatbox without a sound.
1191 if (*msg == 1 || *msg == 3)
1192 mask = CON_MASK_CHAT;
1194 line[index++] = STRING_COLOR_TAG;
1195 line[index++] = '3';
1197 Con_Rcon_AddChar(*msg);
1200 for (;*timestamp;index++, timestamp++)
1201 if (index < (int)sizeof(line) - 2)
1202 line[index] = *timestamp;
1204 mask |= additionalmask;
1206 // append the character
1207 line[index++] = *msg;
1208 // if this is a newline character, we have a complete line to print
1209 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1211 // terminate the line
1215 // send to scrollable buffer
1216 if (con_initialized && cls.state != ca_dedicated)
1218 Con_PrintToHistory(line, mask);
1220 // send to terminal or dedicated server window
1222 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1224 if(sys_specialcharactertranslation.integer)
1231 int ch = u8_getchar(p, &q);
1232 if(ch >= 0xE000 && ch <= 0xE0FF && ((unsigned char) qfont_table[ch - 0xE000]) >= 0x20)
1234 *p = qfont_table[ch - 0xE000];
1236 memmove(p+1, q, strlen(q)+1);
1244 if(sys_colortranslation.integer == 1) // ANSI
1246 static char printline[MAX_INPUTLINE * 4 + 3];
1247 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1248 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1253 for(in = line, out = printline; *in; ++in)
1257 case STRING_COLOR_TAG:
1258 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1260 char r = tolower(in[2]);
1261 char g = tolower(in[3]);
1262 char b = tolower(in[4]);
1263 // it's a hex digit already, so the else part needs no check --blub
1264 if(isdigit(r)) r -= '0';
1266 if(isdigit(g)) g -= '0';
1268 if(isdigit(b)) b -= '0';
1271 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1272 in += 3; // 3 only, the switch down there does the fourth
1279 case STRING_COLOR_TAG:
1281 *out++ = STRING_COLOR_TAG;
1287 if(lastcolor == 0) break; else lastcolor = 0;
1288 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1293 if(lastcolor == 1) break; else lastcolor = 1;
1294 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1299 if(lastcolor == 2) break; else lastcolor = 2;
1300 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1305 if(lastcolor == 3) break; else lastcolor = 3;
1306 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1311 if(lastcolor == 4) break; else lastcolor = 4;
1312 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1317 if(lastcolor == 5) break; else lastcolor = 5;
1318 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1323 if(lastcolor == 6) break; else lastcolor = 6;
1324 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1329 // bold normal color
1331 if(lastcolor == 8) break; else lastcolor = 8;
1332 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1335 *out++ = STRING_COLOR_TAG;
1342 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1359 Sys_Print(printline);
1361 else if(sys_colortranslation.integer == 2) // Quake
1367 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1370 for(in = line, out = printline; *in; ++in)
1374 case STRING_COLOR_TAG:
1377 case STRING_COLOR_RGB_TAG_CHAR:
1378 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1383 *out++ = STRING_COLOR_TAG;
1384 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1387 case STRING_COLOR_TAG:
1389 *out++ = STRING_COLOR_TAG;
1404 *out++ = STRING_COLOR_TAG;
1414 Sys_Print(printline);
1417 // empty the line buffer
1424 Thread_UnlockMutex(con_mutex);
1432 void Con_MaskPrintf(int mask, const char *fmt, ...)
1435 char msg[MAX_INPUTLINE];
1437 va_start(argptr,fmt);
1438 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1441 Con_MaskPrint(mask, msg);
1449 void Con_Print(const char *msg)
1451 Con_MaskPrint(CON_MASK_PRINT, msg);
1459 void Con_Printf(const char *fmt, ...)
1462 char msg[MAX_INPUTLINE];
1464 va_start(argptr,fmt);
1465 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1468 Con_MaskPrint(CON_MASK_PRINT, msg);
1476 void Con_DPrint(const char *msg)
1478 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1481 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1489 void Con_DPrintf(const char *fmt, ...)
1492 char msg[MAX_INPUTLINE];
1494 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1497 va_start(argptr,fmt);
1498 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1501 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1506 ==============================================================================
1510 ==============================================================================
1517 It draws either the console input line or the chat input line (if is_console is false)
1518 The input line scrolls horizontally if typing goes beyond the right edge
1520 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1523 static void Con_DrawInput(qbool is_console, float x, float v, float inputsize)
1525 int y, i, col_out, linepos, text_start, prefix_start = 0;
1526 char text[MAX_INPUTLINE + 5 + 9 + 1]; // space for ^xRGB, "say_team:" and \0
1532 if (is_console && !key_consoleactive)
1533 return; // don't draw anything
1537 // empty prefix because ] is part of the console edit line
1539 strlcpy(text, key_line, sizeof(text));
1540 linepos = key_linepos;
1548 prefix = "say_team:";
1551 strlcpy(text, chat_buffer, sizeof(text));
1552 linepos = chat_bufferpos;
1556 y = (int)strlen(text);
1558 // make the color code visible when the cursor is inside it
1559 if(text[linepos] != 0)
1561 for(i=1; i < 5 && linepos - i > 0; ++i)
1562 if(text[linepos-i] == STRING_COLOR_TAG)
1564 int caret_pos, ofs = 0;
1565 caret_pos = linepos - i;
1566 if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG)
1568 else if(i == 1 && isdigit(text[caret_pos+1]))
1570 else if(text[caret_pos+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(text[caret_pos+2]) && isxdigit(text[caret_pos+3]) && isxdigit(text[caret_pos+4]))
1572 if(ofs && (size_t)(y + ofs + 1) < sizeof(text))
1575 while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG)
1579 // str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing)
1580 // str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing)
1581 memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos);
1582 text[caret_pos + ofs] = STRING_COLOR_TAG;
1594 x += DrawQ_TextWidth(prefix, 0, inputsize, inputsize, false, fnt);
1601 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, inputsize, inputsize, &col_out, false, fnt, 1000000000);
1603 text_start = x + (vid_conwidth.value - x) * 0.95 - xo; // scroll
1606 else if (!is_console)
1607 prefix_start -= (x - text_start);
1610 DrawQ_String(prefix_start, v, prefix, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1612 DrawQ_String(text_start, v, text, y + 3, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1614 // draw a cursor on top of this
1615 if ((int)(host.realtime*con_cursorspeed) & 1) // cursor is visible
1617 if (!utf8_enable.integer)
1619 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1627 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1628 memcpy(text, curbuf, len);
1631 DrawQ_String(text_start + xo, v, text, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, fnt);
1638 float alignment; // 0 = left, 0.5 = center, 1 = right
1644 const char *continuationString;
1647 int colorindex; // init to -1
1651 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1653 con_text_info_t *ti = (con_text_info_t *) passthrough;
1656 ti->colorindex = -1;
1657 return ti->fontsize * ti->font->maxwidth;
1660 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1661 else if(maxWidth == -1)
1662 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1665 Sys_Printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1666 // Note: this is NOT a Con_Printf, as it could print recursively
1671 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1677 (void) isContinuation;
1681 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1683 con_text_info_t *ti = (con_text_info_t *) passthrough;
1685 if(ti->y < ti->ymin - 0.001)
1687 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1691 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1692 if(isContinuation && *ti->continuationString)
1693 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);
1695 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);
1698 ti->y += ti->fontsize;
1702 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)
1706 int maxlines = (int) floor(height / fontsize + 0.01f);
1709 int continuationWidth = 0;
1711 double t = cl.time; // saved so it won't change
1714 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1715 ti.fontsize = fontsize;
1716 ti.alignment = alignment_x;
1719 ti.ymax = y + height;
1720 ti.continuationString = continuationString;
1723 Con_WordWidthFunc(&ti, NULL, &len, -1);
1724 len = strlen(continuationString);
1725 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &len, -1);
1727 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1728 startidx = CON_LINES_COUNT;
1729 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1731 con_lineinfo_t *l = &CON_LINES(i);
1734 if((l->mask & mask_must) != mask_must)
1736 if(l->mask & mask_mustnot)
1738 if(maxage && (l->addtime < t - maxage))
1742 // Calculate its actual height...
1743 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1744 if(lines + mylines >= maxlines)
1746 nskip = lines + mylines - maxlines;
1755 // then center according to the calculated amount of lines...
1757 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1759 // then actually draw
1760 for(i = startidx; i < CON_LINES_COUNT; ++i)
1762 con_lineinfo_t *l = &CON_LINES(i);
1764 if((l->mask & mask_must) != mask_must)
1766 if(l->mask & mask_mustnot)
1768 if(maxage && (l->addtime < t - maxage))
1771 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1781 Draws the last few lines of output transparently over the game top
1784 void Con_DrawNotify (void)
1787 float chatstart, notifystart, inputsize, height;
1792 if (con_mutex) Thread_LockMutex(con_mutex);
1793 ConBuffer_FixTimes(&con);
1795 numChatlines = con_chat.integer;
1797 chatpos = con_chatpos.integer;
1799 if (con_notify.integer < 0)
1800 Cvar_SetValueQuick(&con_notify, 0);
1801 if (gamemode == GAME_TRANSFUSION)
1802 v = 8; // vertical offset
1806 // GAME_NEXUIZ: center, otherwise left justify
1807 align = con_notifyalign.value;
1808 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1810 if(IS_OLDNEXUIZ_DERIVED(gamemode))
1814 if(numChatlines || !con_chatrect.integer)
1818 // first chat, input line, then notify
1820 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1822 else if(chatpos > 0)
1824 // first notify, then (chatpos-1) empty lines, then chat, then input
1826 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1828 else // if(chatpos < 0)
1830 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1832 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1837 // just notify and input
1839 chatstart = 0; // shut off gcc warning
1842 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, "");
1844 if(con_chatrect.integer)
1846 x = con_chatrect_x.value * vid_conwidth.value;
1847 v = con_chatrect_y.value * vid_conheight.value;
1852 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1855 height = numChatlines * con_chatsize.value;
1859 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, "^3 ... ");
1862 if (key_dest == key_message)
1864 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1865 Con_DrawInput(false, x, v, inputsize);
1870 if (con_mutex) Thread_UnlockMutex(con_mutex);
1877 Returns the height of a given console line; calculates it if necessary.
1880 static int Con_LineHeight(int lineno)
1882 con_lineinfo_t *li = &CON_LINES(lineno);
1883 if(li->height == -1)
1885 float width = vid_conwidth.value;
1887 ti.fontsize = con_textsize.value;
1888 ti.font = FONT_CONSOLE;
1889 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1898 Draws a line of the console; returns its height in lines.
1899 If alpha is 0, the line is not drawn, but still wrapped and its height
1903 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1905 float width = vid_conwidth.value;
1907 con_lineinfo_t *li = &CON_LINES(lineno);
1909 if((li->mask & mask_must) != mask_must)
1911 if((li->mask & mask_mustnot) != 0)
1914 ti.continuationString = "";
1916 ti.fontsize = con_textsize.value;
1917 ti.font = FONT_CONSOLE;
1919 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1924 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1931 Calculates the last visible line index and how much to show of it based on
1935 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1940 if(con_backscroll < 0)
1945 // now count until we saw con_backscroll actual lines
1946 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1947 if((CON_LINES(i).mask & mask_must) == mask_must)
1948 if((CON_LINES(i).mask & mask_mustnot) == 0)
1950 int h = Con_LineHeight(i);
1952 // line is the last visible line?
1954 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1956 *limitlast = lines_seen + h - con_backscroll;
1963 // if we get here, no line was on screen - scroll so that one line is
1965 con_backscroll = lines_seen - 1;
1973 Draws the console with the solid background
1974 The typing input line at the bottom should only be drawn if typing is allowed
1977 void Con_DrawConsole (int lines)
1979 float alpha, alpha0;
1982 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1983 cachepic_t *conbackpic;
1984 unsigned int conbackflags;
1989 if (con_mutex) Thread_LockMutex(con_mutex);
1991 if (con_backscroll < 0)
1994 con_vislines = lines;
1996 r_draw2d_force = true;
1998 // draw the background
1999 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
2000 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
2002 sx = scr_conscroll_x.value;
2003 sy = scr_conscroll_y.value;
2004 conbackflags = CACHEPICFLAG_FAILONMISSING; // So console is readable when game content is missing
2005 if (sx != 0 || sy != 0)
2006 conbackflags &= CACHEPICFLAG_NOCLAMP;
2007 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", conbackflags) : NULL;
2008 sx *= host.realtime; sy *= host.realtime;
2009 sx -= floor(sx); sy -= floor(sy);
2010 if (Draw_IsPicLoaded(conbackpic))
2011 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2012 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2013 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2014 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2015 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2018 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
2020 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
2022 sx = scr_conscroll2_x.value;
2023 sy = scr_conscroll2_y.value;
2024 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2025 sx *= host.realtime; sy *= host.realtime;
2026 sx -= floor(sx); sy -= floor(sy);
2027 if(Draw_IsPicLoaded(conbackpic))
2028 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2029 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2030 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2031 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2032 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2035 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
2037 sx = scr_conscroll3_x.value;
2038 sy = scr_conscroll3_y.value;
2039 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2040 sx *= host.realtime; sy *= host.realtime;
2041 sx -= floor(sx); sy -= floor(sy);
2042 if(Draw_IsPicLoaded(conbackpic))
2043 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2044 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2045 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2046 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2047 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2050 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);
2056 int count = CON_LINES_COUNT;
2057 float ymax = con_vislines - 2 * con_textsize.value;
2058 float y = ymax + con_textsize.value * con_backscroll;
2059 for (i = 0;i < count && y >= 0;i++)
2060 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
2061 // fix any excessive scrollback for the next frame
2062 if (i >= count && y >= 0)
2064 con_backscroll -= (int)(y / con_textsize.value);
2065 if (con_backscroll < 0)
2070 if(CON_LINES_COUNT > 0)
2072 int i, last, limitlast;
2074 float ymax = con_vislines - 2 * con_textsize.value;
2075 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2076 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2077 y = ymax - con_textsize.value;
2080 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
2085 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
2087 break; // top of console buffer
2089 break; // top of console window
2096 // draw the input prompt, user text, and cursor if desired
2097 Con_DrawInput(true, 0, con_vislines - con_textsize.value * 2, con_textsize.value);
2099 r_draw2d_force = false;
2100 if (con_mutex) Thread_UnlockMutex(con_mutex);
2107 Prints not only map filename, but also
2108 its format (q1/q2/q3/hl) and even its message
2110 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2111 //LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2112 //LadyHavoc: 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
2113 //LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2114 qbool GetMapList (const char *s, char *completedname, int completednamebufferlength)
2118 int i, k, max, p, o, min;
2121 unsigned char buf[1024];
2123 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2124 t = FS_Search(message, 1, true, NULL);
2127 if (t->numfilenames > 1)
2128 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2129 len = (unsigned char *)Z_Malloc(t->numfilenames);
2131 for(max=i=0;i<t->numfilenames;i++)
2133 k = (int)strlen(t->filenames[i]);
2143 for(i=0;i<t->numfilenames;i++)
2145 int lumpofs = 0, lumplen = 0;
2146 char *entities = NULL;
2147 const char *data = NULL;
2149 char entfilename[MAX_QPATH];
2152 strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2154 f = FS_OpenVirtualFile(t->filenames[i], true);
2157 strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2158 memset(buf, 0, 1024);
2159 FS_Read(f, buf, 1024);
2160 if (!memcmp(buf, "IBSP", 4))
2162 p = LittleLong(((int *)buf)[1]);
2163 if (p == Q3BSPVERSION)
2165 q3dheader_t *header = (q3dheader_t *)buf;
2166 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2167 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2168 dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2170 else if (p == Q2BSPVERSION)
2172 q2dheader_t *header = (q2dheader_t *)buf;
2173 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2174 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2175 dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2178 dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2180 else if (BuffLittleLong(buf) == BSPVERSION)
2182 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2183 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2184 dpsnprintf(desc, sizeof(desc), "BSP29");
2186 else if (BuffLittleLong(buf) == 30)
2188 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2189 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2190 dpsnprintf(desc, sizeof(desc), "BSPHL");
2192 else if (!memcmp(buf, "BSP2", 4))
2194 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2195 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2196 dpsnprintf(desc, sizeof(desc), "BSP2");
2198 else if (!memcmp(buf, "2PSB", 4))
2200 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2201 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2202 dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2204 else if(!memcmp(buf, "VBSP", 4))
2206 hl2dheader_t *header = (hl2dheader_t *)buf;
2207 lumpofs = LittleLong(header->lumps[HL2LUMP_ENTITIES].fileofs);
2208 lumplen = LittleLong(header->lumps[HL2LUMP_ENTITIES].filelen);
2209 dpsnprintf(desc, sizeof(desc), "VBSP%i", LittleLong(((int *)buf)[1]));
2212 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2213 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2214 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2215 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2216 if (!entities && lumplen >= 10)
2218 FS_Seek(f, lumpofs, SEEK_SET);
2219 entities = (char *)Z_Malloc(lumplen + 1);
2220 FS_Read(f, entities, lumplen);
2224 // if there are entities to parse, a missing message key just
2225 // means there is no title, so clear the message string now
2231 if (!COM_ParseToken_Simple(&data, false, false, true))
2233 if (com_token[0] == '{')
2235 if (com_token[0] == '}')
2237 // skip leading whitespace
2238 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2239 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2240 keyname[l] = com_token[k+l];
2242 if (!COM_ParseToken_Simple(&data, false, false, true))
2244 if (developer_extra.integer)
2245 Con_DPrintf("key: %s %s\n", keyname, com_token);
2246 if (!strcmp(keyname, "message"))
2248 // get the message contents
2249 strlcpy(message, com_token, sizeof(message));
2259 *(t->filenames[i]+len[i]+5) = 0;
2260 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2265 k = *(t->filenames[0]+5+p);
2268 for(i=1;i<t->numfilenames;i++)
2269 if(*(t->filenames[i]+5+p) != k)
2273 if(p > o && completedname && completednamebufferlength > 0)
2275 memset(completedname, 0, completednamebufferlength);
2276 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2286 New function for tab-completion system
2287 Added by EvilTypeGuy
2288 MEGA Thanks to Taniwha
2291 void Con_DisplayList(const char **list)
2293 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2294 const char **walk = list;
2297 len = (int)strlen(*walk);
2305 len = (int)strlen(*list);
2306 if (pos + maxlen >= width) {
2312 for (i = 0; i < (maxlen - len); i++)
2324 // Now it becomes TRICKY :D --blub
2325 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2326 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2327 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2328 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
2329 static int Nicks_matchpos;
2331 // co against <<:BLASTER:>> is true!?
2332 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2336 if(tolower(*a) == tolower(*b))
2350 return (*a < *b) ? -1 : 1;
2354 return (*a < *b) ? -1 : 1;
2358 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2361 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2363 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2364 return Nicks_strncasecmp_nospaces(a, b, a_len);
2365 return strncasecmp(a, b, a_len);
2368 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2370 // ignore non alphanumerics of B
2371 // if A contains a non-alphanumeric, B must contain it as well though!
2374 qbool alnum_a, alnum_b;
2376 if(tolower(*a) == tolower(*b))
2378 if(*a == 0) // end of both strings, they're equal
2385 // not equal, end of one string?
2390 // ignore non alphanumerics
2391 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2392 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2393 if(!alnum_a) // b must contain this
2394 return (*a < *b) ? -1 : 1;
2397 // otherwise, both are alnum, they're just not equal, return the appropriate number
2399 return (*a < *b) ? -1 : 1;
2405 /* Nicks_CompleteCountPossible
2407 Count the number of possible nicks to complete
2409 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qbool isCon)
2411 char name[MAX_SCOREBOARDNAME];
2417 if(!con_nickcompletion.integer)
2420 // changed that to 1
2421 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2424 for(i = 0; i < cl.maxclients; ++i)
2427 if(!cl.scores[p].name[0])
2430 SanitizeString(cl.scores[p].name, name);
2431 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2437 spos = pos - 1; // no need for a minimum of characters :)
2441 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2443 if(!(isCon && spos == 1)) // console start
2449 if(isCon && spos == 0)
2451 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2457 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2458 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2460 // the sanitized list
2461 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2464 Nicks_matchpos = match;
2467 Nicks_offset[count] = s - (&line[match]);
2468 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2475 static void Cmd_CompleteNicksPrint(int count)
2478 for(i = 0; i < count; ++i)
2479 Con_Printf("%s\n", Nicks_list[i]);
2482 static void Nicks_CutMatchesNormal(int count)
2484 // cut match 0 down to the longest possible completion
2487 c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2488 for(i = 1; i < count; ++i)
2490 l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2494 for(l = 0; l <= c; ++l)
2495 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2501 Nicks_sanlist[0][c+1] = 0;
2502 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2505 static unsigned int Nicks_strcleanlen(const char *s)
2510 if( (*s >= 'a' && *s <= 'z') ||
2511 (*s >= 'A' && *s <= 'Z') ||
2512 (*s >= '0' && *s <= '9') ||
2520 static void Nicks_CutMatchesAlphaNumeric(int count)
2522 // cut match 0 down to the longest possible completion
2525 char tempstr[sizeof(Nicks_sanlist[0])];
2527 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2529 c = (unsigned int)strlen(Nicks_sanlist[0]);
2530 for(i = 0, l = 0; i < (int)c; ++i)
2532 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2533 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2534 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2536 tempstr[l++] = Nicks_sanlist[0][i];
2541 for(i = 1; i < count; ++i)
2544 b = Nicks_sanlist[i];
2554 if(tolower(*a) == tolower(*b))
2560 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2562 // b is alnum, so cut
2569 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2570 Nicks_CutMatchesNormal(count);
2571 //if(!Nicks_sanlist[0][0])
2572 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2574 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2575 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2579 static void Nicks_CutMatchesNoSpaces(int count)
2581 // cut match 0 down to the longest possible completion
2584 char tempstr[sizeof(Nicks_sanlist[0])];
2587 c = (unsigned int)strlen(Nicks_sanlist[0]);
2588 for(i = 0, l = 0; i < (int)c; ++i)
2590 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2592 tempstr[l++] = Nicks_sanlist[0][i];
2597 for(i = 1; i < count; ++i)
2600 b = Nicks_sanlist[i];
2610 if(tolower(*a) == tolower(*b))
2624 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2625 Nicks_CutMatchesNormal(count);
2626 //if(!Nicks_sanlist[0][0])
2627 //Con_Printf("TS: %s\n", tempstr);
2628 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2630 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2631 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2635 static void Nicks_CutMatches(int count)
2637 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2638 Nicks_CutMatchesAlphaNumeric(count);
2639 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2640 Nicks_CutMatchesNoSpaces(count);
2642 Nicks_CutMatchesNormal(count);
2645 static const char **Nicks_CompleteBuildList(int count)
2649 // the list is freed by Con_CompleteCommandLine, so create a char**
2650 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2652 for(; bpos < count; ++bpos)
2653 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2655 Nicks_CutMatches(count);
2663 Restores the previous used color, after the autocompleted name.
2665 static int Nicks_AddLastColor(char *buffer, int pos)
2667 qbool quote_added = false;
2669 int color = STRING_COLOR_DEFAULT + '0';
2670 char r = 0, g = 0, b = 0;
2672 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2674 // we'll have to add a quote :)
2675 buffer[pos++] = '\"';
2679 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2681 // add color when no quote was added, or when flags &4?
2683 for(match = Nicks_matchpos-1; match >= 0; --match)
2685 if(buffer[match] == STRING_COLOR_TAG)
2687 if( isdigit(buffer[match+1]) )
2689 color = buffer[match+1];
2692 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2694 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2696 r = buffer[match+2];
2697 g = buffer[match+3];
2698 b = buffer[match+4];
2707 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2709 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2710 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2713 buffer[pos++] = STRING_COLOR_TAG;
2716 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2722 buffer[pos++] = color;
2728 Con_CompleteCommandLine
2730 New function for tab-completion system
2731 Added by EvilTypeGuy
2732 Thanks to Fett erich@heintz.com
2734 Enhanced to tab-complete map names by [515]
2737 int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console)
2739 const char *text = "";
2741 const char **list[4] = {0, 0, 0, 0};
2744 int c, v, a, i, cmd_len, pos, k;
2745 int n; // nicks --blub
2746 const char *space, *patterns;
2750 int linestart, linepos;
2751 unsigned int linesize;
2755 linepos = key_linepos;
2756 linesize = sizeof(key_line);
2762 linepos = chat_bufferpos;
2763 linesize = sizeof(chat_buffer);
2767 //find what we want to complete
2769 while(--pos >= linestart)
2772 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2778 strlcpy(s2, line + linepos, sizeof(s2)); //save chars after cursor
2779 line[linepos] = 0; //hide them
2781 c = v = a = n = cmd_len = 0;
2785 space = strchr(line + 1, ' ');
2786 if(space && pos == (space - line) + 1)
2788 strlcpy(command, line + 1, min(sizeof(command), (unsigned int)(space - line)));
2790 patterns = Cvar_VariableString(cmd->cvars, va(vabuf, sizeof(vabuf), "con_completion_%s", command), CF_CLIENT | CF_SERVER); // TODO maybe use a better place for this?
2791 if(patterns && !*patterns)
2792 patterns = NULL; // get rid of the empty string
2794 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2798 if (GetMapList(s, t, sizeof(t)))
2800 // first move the cursor
2801 linepos += (int)strlen(t) - (int)strlen(s);
2803 // and now do the actual work
2805 strlcat(line, t, MAX_INPUTLINE);
2806 strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2808 // and fix the cursor
2809 if(linepos > (int) strlen(line))
2810 linepos = (int) strlen(line);
2819 stringlist_t resultbuf, dirbuf;
2822 // // store completion patterns (space separated) for command foo in con_completion_foo
2823 // set con_completion_foo "foodata/*.foodefault *.foo"
2826 // Note: patterns with slash are always treated as absolute
2827 // patterns; patterns without slash search in the innermost
2828 // directory the user specified. There is no way to "complete into"
2829 // a directory as of now, as directories seem to be unknown to the
2833 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2834 // set con_completion_playdemo "*.dem"
2835 // set con_completion_play "*.wav *.ogg"
2837 // TODO somehow add support for directories; these shall complete
2838 // to their name + an appended slash.
2840 stringlistinit(&resultbuf);
2841 stringlistinit(&dirbuf);
2842 while(COM_ParseToken_Simple(&patterns, false, false, true))
2845 if(strchr(com_token, '/'))
2847 search = FS_Search(com_token, true, true, NULL);
2851 const char *slash = strrchr(s, '/');
2854 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2855 strlcat(t, com_token, sizeof(t));
2856 search = FS_Search(t, true, true, NULL);
2859 search = FS_Search(com_token, true, true, NULL);
2863 for(i = 0; i < search->numfilenames; ++i)
2864 if(!strncmp(search->filenames[i], s, strlen(s)))
2865 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2866 stringlistappend(&resultbuf, search->filenames[i]);
2867 FS_FreeSearch(search);
2871 // In any case, add directory names
2874 const char *slash = strrchr(s, '/');
2877 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2878 strlcat(t, "*", sizeof(t));
2879 search = FS_Search(t, true, true, NULL);
2882 search = FS_Search("*", true, true, NULL);
2885 for(i = 0; i < search->numfilenames; ++i)
2886 if(!strncmp(search->filenames[i], s, strlen(s)))
2887 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2888 stringlistappend(&dirbuf, search->filenames[i]);
2889 FS_FreeSearch(search);
2893 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2896 unsigned int matchchars;
2897 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2899 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2902 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2904 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2908 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2909 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2910 for(i = 0; i < dirbuf.numstrings; ++i)
2912 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2914 for(i = 0; i < resultbuf.numstrings; ++i)
2916 Con_Printf("%s\n", resultbuf.strings[i]);
2918 matchchars = sizeof(t) - 1;
2919 if(resultbuf.numstrings > 0)
2921 p = resultbuf.strings[0];
2922 q = resultbuf.strings[resultbuf.numstrings - 1];
2923 for(; *p && *p == *q; ++p, ++q);
2924 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2926 if(dirbuf.numstrings > 0)
2928 p = dirbuf.strings[0];
2929 q = dirbuf.strings[dirbuf.numstrings - 1];
2930 for(; *p && *p == *q; ++p, ++q);
2931 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2933 // now p points to the first non-equal character, or to the end
2934 // of resultbuf.strings[0]. We want to append the characters
2935 // from resultbuf.strings[0] to (not including) p as these are
2936 // the unique prefix
2937 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2940 // first move the cursor
2941 linepos += (int)strlen(t) - (int)strlen(s);
2943 // and now do the actual work
2945 strlcat(line, t, MAX_INPUTLINE);
2946 strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2948 // and fix the cursor
2949 if(linepos > (int) strlen(line))
2950 linepos = (int) strlen(line);
2952 stringlistfreecontents(&resultbuf);
2953 stringlistfreecontents(&dirbuf);
2955 return linepos; // bail out, when we complete for a command that wants a file name
2960 // Count number of possible matches and print them
2961 c = Cmd_CompleteCountPossible(cmd, s);
2964 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2965 Cmd_CompleteCommandPrint(cmd, s);
2967 v = Cvar_CompleteCountPossible(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2970 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2971 Cvar_CompleteCvarPrint(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2973 a = Cmd_CompleteAliasCountPossible(cmd, s);
2976 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2977 Cmd_CompleteAliasPrint(cmd, s);
2981 n = Nicks_CompleteCountPossible(line, linepos, s, is_console);
2984 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2985 Cmd_CompleteNicksPrint(n);
2988 if (!(c + v + a + n)) // No possible matches
2991 strlcpy(&line[linepos], s2, linesize - linepos);
2996 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
2998 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
3000 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
3004 text = *(list[3] = Nicks_CompleteBuildList(n));
3006 text = *(Nicks_CompleteBuildList(n));
3009 for (cmd_len = (int)strlen(s);;cmd_len++)
3012 for (i = 0; i < 3; i++)
3014 for (l = list[i];*l;l++)
3015 if ((*l)[cmd_len] != text[cmd_len])
3017 // all possible matches share this character, so we continue...
3020 // if all matches ended at the same position, stop
3021 // (this means there is only one match)
3027 // prevent a buffer overrun by limiting cmd_len according to remaining space
3028 cmd_len = min(cmd_len, (int)linesize - 1 - pos);
3032 memcpy(&line[linepos], text, cmd_len);
3034 // if there is only one match, add a space after it
3035 if (c + v + a + n == 1 && linepos < (int)linesize - 1)
3038 { // was a nick, might have an offset, and needs colors ;) --blub
3039 linepos = pos - Nicks_offset[0];
3040 cmd_len = (int)strlen(Nicks_list[0]);
3041 cmd_len = min(cmd_len, (int)linesize - 3 - pos);
3043 memcpy(&line[linepos] , Nicks_list[0], cmd_len);
3045 if(linepos < (int)(linesize - 7)) // space for color code (^[0-9] or ^xrgb), space and \0
3046 linepos = Nicks_AddLastColor(line, linepos);
3048 line[linepos++] = ' ';
3052 // use strlcat to avoid a buffer overrun
3054 strlcat(line, s2, linesize);
3059 // free the command, cvar, and alias lists
3060 for (i = 0; i < 4; i++)
3062 Mem_Free((void *)list[i]);