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 extern qbool sys_nostdout;
1154 void Con_MaskPrint(int additionalmask, const char *msg)
1156 static int mask = 0;
1157 static int index = 0;
1158 static char line[MAX_INPUTLINE];
1161 Thread_LockMutex(con_mutex);
1165 Con_Rcon_AddChar(*msg);
1166 // if this is the beginning of a new line, print timestamp
1169 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1171 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1172 line[index++] = STRING_COLOR_TAG;
1173 // assert( STRING_COLOR_DEFAULT < 10 )
1174 line[index++] = STRING_COLOR_DEFAULT + '0';
1175 // special color codes for chat messages must always come first
1176 // for Con_PrintToHistory to work properly
1177 if (*msg == 1 || *msg == 2 || *msg == 3)
1182 if (con_chatsound.value)
1184 if(msg[1] == con_chatsound_team_mask.integer && cl.foundteamchatsound)
1185 S_LocalSound (con_chatsound_team_file.string);
1187 S_LocalSound (con_chatsound_file.string);
1190 // Send to chatbox for say/tell (1) and messages (3)
1191 // 3 is just so that a message can be sent to the chatbox without a sound.
1192 if (*msg == 1 || *msg == 3)
1193 mask = CON_MASK_CHAT;
1195 line[index++] = STRING_COLOR_TAG;
1196 line[index++] = '3';
1198 Con_Rcon_AddChar(*msg);
1201 for (;*timestamp;index++, timestamp++)
1202 if (index < (int)sizeof(line) - 2)
1203 line[index] = *timestamp;
1205 mask |= additionalmask;
1207 // append the character
1208 line[index++] = *msg;
1209 // if this is a newline character, we have a complete line to print
1210 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1212 // terminate the line
1216 // send to scrollable buffer
1217 if (con_initialized && cls.state != ca_dedicated)
1219 Con_PrintToHistory(line, mask);
1221 // send to terminal or dedicated server window
1223 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1225 if(sys_specialcharactertranslation.integer)
1232 int ch = u8_getchar(p, &q);
1233 if(ch >= 0xE000 && ch <= 0xE0FF && ((unsigned char) qfont_table[ch - 0xE000]) >= 0x20)
1235 *p = qfont_table[ch - 0xE000];
1237 memmove(p+1, q, strlen(q)+1);
1245 if(sys_colortranslation.integer == 1) // ANSI
1247 static char printline[MAX_INPUTLINE * 4 + 3];
1248 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1249 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1254 for(in = line, out = printline; *in; ++in)
1258 case STRING_COLOR_TAG:
1259 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1261 char r = tolower(in[2]);
1262 char g = tolower(in[3]);
1263 char b = tolower(in[4]);
1264 // it's a hex digit already, so the else part needs no check --blub
1265 if(isdigit(r)) r -= '0';
1267 if(isdigit(g)) g -= '0';
1269 if(isdigit(b)) b -= '0';
1272 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1273 in += 3; // 3 only, the switch down there does the fourth
1280 case STRING_COLOR_TAG:
1282 *out++ = STRING_COLOR_TAG;
1288 if(lastcolor == 0) break; else lastcolor = 0;
1289 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1294 if(lastcolor == 1) break; else lastcolor = 1;
1295 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1300 if(lastcolor == 2) break; else lastcolor = 2;
1301 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1306 if(lastcolor == 3) break; else lastcolor = 3;
1307 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1312 if(lastcolor == 4) break; else lastcolor = 4;
1313 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1318 if(lastcolor == 5) break; else lastcolor = 5;
1319 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1324 if(lastcolor == 6) break; else lastcolor = 6;
1325 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1330 // bold normal color
1332 if(lastcolor == 8) break; else lastcolor = 8;
1333 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1336 *out++ = STRING_COLOR_TAG;
1343 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1360 Sys_Print(printline);
1362 else if(sys_colortranslation.integer == 2) // Quake
1368 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1371 for(in = line, out = printline; *in; ++in)
1375 case STRING_COLOR_TAG:
1378 case STRING_COLOR_RGB_TAG_CHAR:
1379 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1384 *out++ = STRING_COLOR_TAG;
1385 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1388 case STRING_COLOR_TAG:
1390 *out++ = STRING_COLOR_TAG;
1405 *out++ = STRING_COLOR_TAG;
1415 Sys_Print(printline);
1418 // empty the line buffer
1425 Thread_UnlockMutex(con_mutex);
1433 void Con_MaskPrintf(int mask, const char *fmt, ...)
1436 char msg[MAX_INPUTLINE];
1438 va_start(argptr,fmt);
1439 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1442 Con_MaskPrint(mask, msg);
1450 void Con_Print(const char *msg)
1452 Con_MaskPrint(CON_MASK_PRINT, msg);
1460 void Con_Printf(const char *fmt, ...)
1463 char msg[MAX_INPUTLINE];
1465 va_start(argptr,fmt);
1466 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1469 Con_MaskPrint(CON_MASK_PRINT, msg);
1477 void Con_DPrint(const char *msg)
1479 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1482 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1490 void Con_DPrintf(const char *fmt, ...)
1493 char msg[MAX_INPUTLINE];
1495 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1498 va_start(argptr,fmt);
1499 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1502 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1507 ==============================================================================
1511 ==============================================================================
1518 It draws either the console input line or the chat input line (if is_console is false)
1519 The input line scrolls horizontally if typing goes beyond the right edge
1521 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1524 static void Con_DrawInput(qbool is_console, float x, float v, float inputsize)
1526 int y, i, col_out, linepos, text_start, prefix_start = 0;
1527 char text[MAX_INPUTLINE + 5 + 9 + 1]; // space for ^xRGB, "say_team:" and \0
1533 if (is_console && !key_consoleactive)
1534 return; // don't draw anything
1538 // empty prefix because ] is part of the console edit line
1540 strlcpy(text, key_line, sizeof(text));
1541 linepos = key_linepos;
1549 prefix = "say_team:";
1552 strlcpy(text, chat_buffer, sizeof(text));
1553 linepos = chat_bufferpos;
1557 y = (int)strlen(text);
1559 // make the color code visible when the cursor is inside it
1560 if(text[linepos] != 0)
1562 for(i=1; i < 5 && linepos - i > 0; ++i)
1563 if(text[linepos-i] == STRING_COLOR_TAG)
1565 int caret_pos, ofs = 0;
1566 caret_pos = linepos - i;
1567 if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG)
1569 else if(i == 1 && isdigit(text[caret_pos+1]))
1571 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]))
1573 if(ofs && (size_t)(y + ofs + 1) < sizeof(text))
1576 while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG)
1580 // str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing)
1581 // str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing)
1582 memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos);
1583 text[caret_pos + ofs] = STRING_COLOR_TAG;
1595 x += DrawQ_TextWidth(prefix, 0, inputsize, inputsize, false, fnt);
1602 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, inputsize, inputsize, &col_out, false, fnt, 1000000000);
1604 text_start = x + (vid_conwidth.value - x) * 0.95 - xo; // scroll
1607 else if (!is_console)
1608 prefix_start -= (x - text_start);
1611 DrawQ_String(prefix_start, v, prefix, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1613 DrawQ_String(text_start, v, text, y + 3, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1615 // draw a cursor on top of this
1616 if ((int)(host.realtime*con_cursorspeed) & 1) // cursor is visible
1618 if (!utf8_enable.integer)
1620 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1628 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1629 memcpy(text, curbuf, len);
1632 DrawQ_String(text_start + xo, v, text, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, fnt);
1639 float alignment; // 0 = left, 0.5 = center, 1 = right
1645 const char *continuationString;
1648 int colorindex; // init to -1
1652 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1654 con_text_info_t *ti = (con_text_info_t *) passthrough;
1657 ti->colorindex = -1;
1658 return ti->fontsize * ti->font->maxwidth;
1661 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1662 else if(maxWidth == -1)
1663 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1666 Sys_Printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1667 // Note: this is NOT a Con_Printf, as it could print recursively
1672 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1678 (void) isContinuation;
1682 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1684 con_text_info_t *ti = (con_text_info_t *) passthrough;
1686 if(ti->y < ti->ymin - 0.001)
1688 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1692 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1693 if(isContinuation && *ti->continuationString)
1694 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);
1696 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);
1699 ti->y += ti->fontsize;
1703 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)
1707 int maxlines = (int) floor(height / fontsize + 0.01f);
1710 int continuationWidth = 0;
1712 double t = cl.time; // saved so it won't change
1715 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1716 ti.fontsize = fontsize;
1717 ti.alignment = alignment_x;
1720 ti.ymax = y + height;
1721 ti.continuationString = continuationString;
1724 Con_WordWidthFunc(&ti, NULL, &len, -1);
1725 len = strlen(continuationString);
1726 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &len, -1);
1728 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1729 startidx = CON_LINES_COUNT;
1730 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1732 con_lineinfo_t *l = &CON_LINES(i);
1735 if((l->mask & mask_must) != mask_must)
1737 if(l->mask & mask_mustnot)
1739 if(maxage && (l->addtime < t - maxage))
1743 // Calculate its actual height...
1744 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1745 if(lines + mylines >= maxlines)
1747 nskip = lines + mylines - maxlines;
1756 // then center according to the calculated amount of lines...
1758 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1760 // then actually draw
1761 for(i = startidx; i < CON_LINES_COUNT; ++i)
1763 con_lineinfo_t *l = &CON_LINES(i);
1765 if((l->mask & mask_must) != mask_must)
1767 if(l->mask & mask_mustnot)
1769 if(maxage && (l->addtime < t - maxage))
1772 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1782 Draws the last few lines of output transparently over the game top
1785 void Con_DrawNotify (void)
1788 float chatstart, notifystart, inputsize, height;
1793 if (con_mutex) Thread_LockMutex(con_mutex);
1794 ConBuffer_FixTimes(&con);
1796 numChatlines = con_chat.integer;
1798 chatpos = con_chatpos.integer;
1800 if (con_notify.integer < 0)
1801 Cvar_SetValueQuick(&con_notify, 0);
1802 if (gamemode == GAME_TRANSFUSION)
1803 v = 8; // vertical offset
1807 // GAME_NEXUIZ: center, otherwise left justify
1808 align = con_notifyalign.value;
1809 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1811 if(IS_OLDNEXUIZ_DERIVED(gamemode))
1815 if(numChatlines || !con_chatrect.integer)
1819 // first chat, input line, then notify
1821 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1823 else if(chatpos > 0)
1825 // first notify, then (chatpos-1) empty lines, then chat, then input
1827 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1829 else // if(chatpos < 0)
1831 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1833 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1838 // just notify and input
1840 chatstart = 0; // shut off gcc warning
1843 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, "");
1845 if(con_chatrect.integer)
1847 x = con_chatrect_x.value * vid_conwidth.value;
1848 v = con_chatrect_y.value * vid_conheight.value;
1853 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1856 height = numChatlines * con_chatsize.value;
1860 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 ... ");
1863 if (key_dest == key_message)
1865 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1866 Con_DrawInput(false, x, v, inputsize);
1871 if (con_mutex) Thread_UnlockMutex(con_mutex);
1878 Returns the height of a given console line; calculates it if necessary.
1881 static int Con_LineHeight(int lineno)
1883 con_lineinfo_t *li = &CON_LINES(lineno);
1884 if(li->height == -1)
1886 float width = vid_conwidth.value;
1888 ti.fontsize = con_textsize.value;
1889 ti.font = FONT_CONSOLE;
1890 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1899 Draws a line of the console; returns its height in lines.
1900 If alpha is 0, the line is not drawn, but still wrapped and its height
1904 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1906 float width = vid_conwidth.value;
1908 con_lineinfo_t *li = &CON_LINES(lineno);
1910 if((li->mask & mask_must) != mask_must)
1912 if((li->mask & mask_mustnot) != 0)
1915 ti.continuationString = "";
1917 ti.fontsize = con_textsize.value;
1918 ti.font = FONT_CONSOLE;
1920 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1925 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1932 Calculates the last visible line index and how much to show of it based on
1936 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1941 if(con_backscroll < 0)
1946 // now count until we saw con_backscroll actual lines
1947 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1948 if((CON_LINES(i).mask & mask_must) == mask_must)
1949 if((CON_LINES(i).mask & mask_mustnot) == 0)
1951 int h = Con_LineHeight(i);
1953 // line is the last visible line?
1955 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1957 *limitlast = lines_seen + h - con_backscroll;
1964 // if we get here, no line was on screen - scroll so that one line is
1966 con_backscroll = lines_seen - 1;
1974 Draws the console with the solid background
1975 The typing input line at the bottom should only be drawn if typing is allowed
1978 void Con_DrawConsole (int lines)
1980 float alpha, alpha0;
1983 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1984 cachepic_t *conbackpic;
1985 unsigned int conbackflags;
1990 if (con_mutex) Thread_LockMutex(con_mutex);
1992 if (con_backscroll < 0)
1995 con_vislines = lines;
1997 r_draw2d_force = true;
1999 // draw the background
2000 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
2001 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
2003 sx = scr_conscroll_x.value;
2004 sy = scr_conscroll_y.value;
2005 conbackflags = CACHEPICFLAG_FAILONMISSING; // So console is readable when game content is missing
2006 if (sx != 0 || sy != 0)
2007 conbackflags &= CACHEPICFLAG_NOCLAMP;
2008 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", conbackflags) : NULL;
2009 sx *= host.realtime; sy *= host.realtime;
2010 sx -= floor(sx); sy -= floor(sy);
2011 if (Draw_IsPicLoaded(conbackpic))
2012 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2013 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2014 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2015 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2016 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2019 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
2021 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
2023 sx = scr_conscroll2_x.value;
2024 sy = scr_conscroll2_y.value;
2025 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2026 sx *= host.realtime; sy *= host.realtime;
2027 sx -= floor(sx); sy -= floor(sy);
2028 if(Draw_IsPicLoaded(conbackpic))
2029 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2030 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2031 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2032 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2033 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2036 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
2038 sx = scr_conscroll3_x.value;
2039 sy = scr_conscroll3_y.value;
2040 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2041 sx *= host.realtime; sy *= host.realtime;
2042 sx -= floor(sx); sy -= floor(sy);
2043 if(Draw_IsPicLoaded(conbackpic))
2044 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2045 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2046 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2047 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2048 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2051 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);
2057 int count = CON_LINES_COUNT;
2058 float ymax = con_vislines - 2 * con_textsize.value;
2059 float y = ymax + con_textsize.value * con_backscroll;
2060 for (i = 0;i < count && y >= 0;i++)
2061 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
2062 // fix any excessive scrollback for the next frame
2063 if (i >= count && y >= 0)
2065 con_backscroll -= (int)(y / con_textsize.value);
2066 if (con_backscroll < 0)
2071 if(CON_LINES_COUNT > 0)
2073 int i, last, limitlast;
2075 float ymax = con_vislines - 2 * con_textsize.value;
2076 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2077 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2078 y = ymax - con_textsize.value;
2081 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
2086 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
2088 break; // top of console buffer
2090 break; // top of console window
2097 // draw the input prompt, user text, and cursor if desired
2098 Con_DrawInput(true, 0, con_vislines - con_textsize.value * 2, con_textsize.value);
2100 r_draw2d_force = false;
2101 if (con_mutex) Thread_UnlockMutex(con_mutex);
2108 Prints not only map filename, but also
2109 its format (q1/q2/q3/hl) and even its message
2111 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2112 //LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2113 //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
2114 //LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2115 qbool GetMapList (const char *s, char *completedname, int completednamebufferlength)
2119 int i, k, max, p, o, min;
2122 unsigned char buf[1024];
2124 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2125 t = FS_Search(message, 1, true, NULL);
2128 if (t->numfilenames > 1)
2129 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2130 len = (unsigned char *)Z_Malloc(t->numfilenames);
2132 for(max=i=0;i<t->numfilenames;i++)
2134 k = (int)strlen(t->filenames[i]);
2144 for(i=0;i<t->numfilenames;i++)
2146 int lumpofs = 0, lumplen = 0;
2147 char *entities = NULL;
2148 const char *data = NULL;
2150 char entfilename[MAX_QPATH];
2153 strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2155 f = FS_OpenVirtualFile(t->filenames[i], true);
2158 strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2159 memset(buf, 0, 1024);
2160 FS_Read(f, buf, 1024);
2161 if (!memcmp(buf, "IBSP", 4))
2163 p = LittleLong(((int *)buf)[1]);
2164 if (p == Q3BSPVERSION)
2166 q3dheader_t *header = (q3dheader_t *)buf;
2167 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2168 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2169 dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2171 else if (p == Q2BSPVERSION)
2173 q2dheader_t *header = (q2dheader_t *)buf;
2174 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2175 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2176 dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2179 dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2181 else if (BuffLittleLong(buf) == BSPVERSION)
2183 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2184 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2185 dpsnprintf(desc, sizeof(desc), "BSP29");
2187 else if (BuffLittleLong(buf) == 30)
2189 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2190 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2191 dpsnprintf(desc, sizeof(desc), "BSPHL");
2193 else if (!memcmp(buf, "BSP2", 4))
2195 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2196 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2197 dpsnprintf(desc, sizeof(desc), "BSP2");
2199 else if (!memcmp(buf, "2PSB", 4))
2201 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2202 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2203 dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2205 else if(!memcmp(buf, "VBSP", 4))
2207 hl2dheader_t *header = (hl2dheader_t *)buf;
2208 lumpofs = LittleLong(header->lumps[HL2LUMP_ENTITIES].fileofs);
2209 lumplen = LittleLong(header->lumps[HL2LUMP_ENTITIES].filelen);
2210 dpsnprintf(desc, sizeof(desc), "VBSP%i", LittleLong(((int *)buf)[1]));
2213 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2214 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2215 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2216 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2217 if (!entities && lumplen >= 10)
2219 FS_Seek(f, lumpofs, SEEK_SET);
2220 entities = (char *)Z_Malloc(lumplen + 1);
2221 FS_Read(f, entities, lumplen);
2225 // if there are entities to parse, a missing message key just
2226 // means there is no title, so clear the message string now
2232 if (!COM_ParseToken_Simple(&data, false, false, true))
2234 if (com_token[0] == '{')
2236 if (com_token[0] == '}')
2238 // skip leading whitespace
2239 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2240 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2241 keyname[l] = com_token[k+l];
2243 if (!COM_ParseToken_Simple(&data, false, false, true))
2245 if (developer_extra.integer)
2246 Con_DPrintf("key: %s %s\n", keyname, com_token);
2247 if (!strcmp(keyname, "message"))
2249 // get the message contents
2250 strlcpy(message, com_token, sizeof(message));
2260 *(t->filenames[i]+len[i]+5) = 0;
2261 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2266 k = *(t->filenames[0]+5+p);
2269 for(i=1;i<t->numfilenames;i++)
2270 if(*(t->filenames[i]+5+p) != k)
2274 if(p > o && completedname && completednamebufferlength > 0)
2276 memset(completedname, 0, completednamebufferlength);
2277 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2287 New function for tab-completion system
2288 Added by EvilTypeGuy
2289 MEGA Thanks to Taniwha
2292 void Con_DisplayList(const char **list)
2294 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2295 const char **walk = list;
2298 len = (int)strlen(*walk);
2306 len = (int)strlen(*list);
2307 if (pos + maxlen >= width) {
2313 for (i = 0; i < (maxlen - len); i++)
2325 // Now it becomes TRICKY :D --blub
2326 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2327 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2328 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2329 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
2330 static int Nicks_matchpos;
2332 // co against <<:BLASTER:>> is true!?
2333 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2337 if(tolower(*a) == tolower(*b))
2351 return (*a < *b) ? -1 : 1;
2355 return (*a < *b) ? -1 : 1;
2359 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2362 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2364 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2365 return Nicks_strncasecmp_nospaces(a, b, a_len);
2366 return strncasecmp(a, b, a_len);
2369 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2371 // ignore non alphanumerics of B
2372 // if A contains a non-alphanumeric, B must contain it as well though!
2375 qbool alnum_a, alnum_b;
2377 if(tolower(*a) == tolower(*b))
2379 if(*a == 0) // end of both strings, they're equal
2386 // not equal, end of one string?
2391 // ignore non alphanumerics
2392 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2393 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2394 if(!alnum_a) // b must contain this
2395 return (*a < *b) ? -1 : 1;
2398 // otherwise, both are alnum, they're just not equal, return the appropriate number
2400 return (*a < *b) ? -1 : 1;
2406 /* Nicks_CompleteCountPossible
2408 Count the number of possible nicks to complete
2410 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qbool isCon)
2412 char name[MAX_SCOREBOARDNAME];
2418 if(!con_nickcompletion.integer)
2421 // changed that to 1
2422 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2425 for(i = 0; i < cl.maxclients; ++i)
2428 if(!cl.scores[p].name[0])
2431 SanitizeString(cl.scores[p].name, name);
2432 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2438 spos = pos - 1; // no need for a minimum of characters :)
2442 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2444 if(!(isCon && spos == 1)) // console start
2450 if(isCon && spos == 0)
2452 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2458 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2459 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2461 // the sanitized list
2462 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2465 Nicks_matchpos = match;
2468 Nicks_offset[count] = s - (&line[match]);
2469 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2476 static void Cmd_CompleteNicksPrint(int count)
2479 for(i = 0; i < count; ++i)
2480 Con_Printf("%s\n", Nicks_list[i]);
2483 static void Nicks_CutMatchesNormal(int count)
2485 // cut match 0 down to the longest possible completion
2488 c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2489 for(i = 1; i < count; ++i)
2491 l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2495 for(l = 0; l <= c; ++l)
2496 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2502 Nicks_sanlist[0][c+1] = 0;
2503 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2506 static unsigned int Nicks_strcleanlen(const char *s)
2511 if( (*s >= 'a' && *s <= 'z') ||
2512 (*s >= 'A' && *s <= 'Z') ||
2513 (*s >= '0' && *s <= '9') ||
2521 static void Nicks_CutMatchesAlphaNumeric(int count)
2523 // cut match 0 down to the longest possible completion
2526 char tempstr[sizeof(Nicks_sanlist[0])];
2528 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2530 c = (unsigned int)strlen(Nicks_sanlist[0]);
2531 for(i = 0, l = 0; i < (int)c; ++i)
2533 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2534 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2535 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2537 tempstr[l++] = Nicks_sanlist[0][i];
2542 for(i = 1; i < count; ++i)
2545 b = Nicks_sanlist[i];
2555 if(tolower(*a) == tolower(*b))
2561 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2563 // b is alnum, so cut
2570 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2571 Nicks_CutMatchesNormal(count);
2572 //if(!Nicks_sanlist[0][0])
2573 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2575 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2576 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2580 static void Nicks_CutMatchesNoSpaces(int count)
2582 // cut match 0 down to the longest possible completion
2585 char tempstr[sizeof(Nicks_sanlist[0])];
2588 c = (unsigned int)strlen(Nicks_sanlist[0]);
2589 for(i = 0, l = 0; i < (int)c; ++i)
2591 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2593 tempstr[l++] = Nicks_sanlist[0][i];
2598 for(i = 1; i < count; ++i)
2601 b = Nicks_sanlist[i];
2611 if(tolower(*a) == tolower(*b))
2625 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2626 Nicks_CutMatchesNormal(count);
2627 //if(!Nicks_sanlist[0][0])
2628 //Con_Printf("TS: %s\n", tempstr);
2629 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2631 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2632 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2636 static void Nicks_CutMatches(int count)
2638 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2639 Nicks_CutMatchesAlphaNumeric(count);
2640 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2641 Nicks_CutMatchesNoSpaces(count);
2643 Nicks_CutMatchesNormal(count);
2646 static const char **Nicks_CompleteBuildList(int count)
2650 // the list is freed by Con_CompleteCommandLine, so create a char**
2651 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2653 for(; bpos < count; ++bpos)
2654 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2656 Nicks_CutMatches(count);
2664 Restores the previous used color, after the autocompleted name.
2666 static int Nicks_AddLastColor(char *buffer, int pos)
2668 qbool quote_added = false;
2670 int color = STRING_COLOR_DEFAULT + '0';
2671 char r = 0, g = 0, b = 0;
2673 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2675 // we'll have to add a quote :)
2676 buffer[pos++] = '\"';
2680 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2682 // add color when no quote was added, or when flags &4?
2684 for(match = Nicks_matchpos-1; match >= 0; --match)
2686 if(buffer[match] == STRING_COLOR_TAG)
2688 if( isdigit(buffer[match+1]) )
2690 color = buffer[match+1];
2693 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2695 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2697 r = buffer[match+2];
2698 g = buffer[match+3];
2699 b = buffer[match+4];
2708 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2710 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2711 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2714 buffer[pos++] = STRING_COLOR_TAG;
2717 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2723 buffer[pos++] = color;
2729 Con_CompleteCommandLine
2731 New function for tab-completion system
2732 Added by EvilTypeGuy
2733 Thanks to Fett erich@heintz.com
2735 Enhanced to tab-complete map names by [515]
2738 int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console)
2740 const char *text = "";
2742 const char **list[4] = {0, 0, 0, 0};
2745 int c, v, a, i, cmd_len, pos, k;
2746 int n; // nicks --blub
2747 const char *space, *patterns;
2751 int linestart, linepos;
2752 unsigned int linesize;
2756 linepos = key_linepos;
2757 linesize = sizeof(key_line);
2763 linepos = chat_bufferpos;
2764 linesize = sizeof(chat_buffer);
2768 //find what we want to complete
2770 while(--pos >= linestart)
2773 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2779 strlcpy(s2, line + linepos, sizeof(s2)); //save chars after cursor
2780 line[linepos] = 0; //hide them
2782 c = v = a = n = cmd_len = 0;
2786 space = strchr(line + 1, ' ');
2787 if(space && pos == (space - line) + 1)
2789 strlcpy(command, line + 1, min(sizeof(command), (unsigned int)(space - line)));
2791 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?
2792 if(patterns && !*patterns)
2793 patterns = NULL; // get rid of the empty string
2795 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2799 if (GetMapList(s, t, sizeof(t)))
2801 // first move the cursor
2802 linepos += (int)strlen(t) - (int)strlen(s);
2804 // and now do the actual work
2806 strlcat(line, t, MAX_INPUTLINE);
2807 strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2809 // and fix the cursor
2810 if(linepos > (int) strlen(line))
2811 linepos = (int) strlen(line);
2820 stringlist_t resultbuf, dirbuf;
2823 // // store completion patterns (space separated) for command foo in con_completion_foo
2824 // set con_completion_foo "foodata/*.foodefault *.foo"
2827 // Note: patterns with slash are always treated as absolute
2828 // patterns; patterns without slash search in the innermost
2829 // directory the user specified. There is no way to "complete into"
2830 // a directory as of now, as directories seem to be unknown to the
2834 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2835 // set con_completion_playdemo "*.dem"
2836 // set con_completion_play "*.wav *.ogg"
2838 // TODO somehow add support for directories; these shall complete
2839 // to their name + an appended slash.
2841 stringlistinit(&resultbuf);
2842 stringlistinit(&dirbuf);
2843 while(COM_ParseToken_Simple(&patterns, false, false, true))
2846 if(strchr(com_token, '/'))
2848 search = FS_Search(com_token, true, true, NULL);
2852 const char *slash = strrchr(s, '/');
2855 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2856 strlcat(t, com_token, sizeof(t));
2857 search = FS_Search(t, true, true, NULL);
2860 search = FS_Search(com_token, true, true, NULL);
2864 for(i = 0; i < search->numfilenames; ++i)
2865 if(!strncmp(search->filenames[i], s, strlen(s)))
2866 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2867 stringlistappend(&resultbuf, search->filenames[i]);
2868 FS_FreeSearch(search);
2872 // In any case, add directory names
2875 const char *slash = strrchr(s, '/');
2878 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2879 strlcat(t, "*", sizeof(t));
2880 search = FS_Search(t, true, true, NULL);
2883 search = FS_Search("*", true, true, NULL);
2886 for(i = 0; i < search->numfilenames; ++i)
2887 if(!strncmp(search->filenames[i], s, strlen(s)))
2888 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2889 stringlistappend(&dirbuf, search->filenames[i]);
2890 FS_FreeSearch(search);
2894 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2897 unsigned int matchchars;
2898 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2900 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2903 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2905 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2909 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2910 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2911 for(i = 0; i < dirbuf.numstrings; ++i)
2913 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2915 for(i = 0; i < resultbuf.numstrings; ++i)
2917 Con_Printf("%s\n", resultbuf.strings[i]);
2919 matchchars = sizeof(t) - 1;
2920 if(resultbuf.numstrings > 0)
2922 p = resultbuf.strings[0];
2923 q = resultbuf.strings[resultbuf.numstrings - 1];
2924 for(; *p && *p == *q; ++p, ++q);
2925 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2927 if(dirbuf.numstrings > 0)
2929 p = dirbuf.strings[0];
2930 q = dirbuf.strings[dirbuf.numstrings - 1];
2931 for(; *p && *p == *q; ++p, ++q);
2932 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2934 // now p points to the first non-equal character, or to the end
2935 // of resultbuf.strings[0]. We want to append the characters
2936 // from resultbuf.strings[0] to (not including) p as these are
2937 // the unique prefix
2938 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2941 // first move the cursor
2942 linepos += (int)strlen(t) - (int)strlen(s);
2944 // and now do the actual work
2946 strlcat(line, t, MAX_INPUTLINE);
2947 strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2949 // and fix the cursor
2950 if(linepos > (int) strlen(line))
2951 linepos = (int) strlen(line);
2953 stringlistfreecontents(&resultbuf);
2954 stringlistfreecontents(&dirbuf);
2956 return linepos; // bail out, when we complete for a command that wants a file name
2961 // Count number of possible matches and print them
2962 c = Cmd_CompleteCountPossible(cmd, s);
2965 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2966 Cmd_CompleteCommandPrint(cmd, s);
2968 v = Cvar_CompleteCountPossible(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2971 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2972 Cvar_CompleteCvarPrint(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2974 a = Cmd_CompleteAliasCountPossible(cmd, s);
2977 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2978 Cmd_CompleteAliasPrint(cmd, s);
2982 n = Nicks_CompleteCountPossible(line, linepos, s, is_console);
2985 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2986 Cmd_CompleteNicksPrint(n);
2989 if (!(c + v + a + n)) // No possible matches
2992 strlcpy(&line[linepos], s2, linesize - linepos);
2997 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
2999 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
3001 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
3005 text = *(list[3] = Nicks_CompleteBuildList(n));
3007 text = *(Nicks_CompleteBuildList(n));
3010 for (cmd_len = (int)strlen(s);;cmd_len++)
3013 for (i = 0; i < 3; i++)
3015 for (l = list[i];*l;l++)
3016 if ((*l)[cmd_len] != text[cmd_len])
3018 // all possible matches share this character, so we continue...
3021 // if all matches ended at the same position, stop
3022 // (this means there is only one match)
3028 // prevent a buffer overrun by limiting cmd_len according to remaining space
3029 cmd_len = min(cmd_len, (int)linesize - 1 - pos);
3033 memcpy(&line[linepos], text, cmd_len);
3035 // if there is only one match, add a space after it
3036 if (c + v + a + n == 1 && linepos < (int)linesize - 1)
3039 { // was a nick, might have an offset, and needs colors ;) --blub
3040 linepos = pos - Nicks_offset[0];
3041 cmd_len = (int)strlen(Nicks_list[0]);
3042 cmd_len = min(cmd_len, (int)linesize - 3 - pos);
3044 memcpy(&line[linepos] , Nicks_list[0], cmd_len);
3046 if(linepos < (int)(linesize - 7)) // space for color code (^[0-9] or ^xrgb), space and \0
3047 linepos = Nicks_AddLastColor(line, linepos);
3049 line[linepos++] = ' ';
3053 // use strlcat to avoid a buffer overrun
3055 strlcat(line, s2, linesize);
3060 // free the command, cvar, and alias lists
3061 for (i = 0; i < 4; i++)
3063 Mem_Free((void *)list[i]);