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;
934 Con_Print("Console initialized.\n");
937 void Con_Shutdown (void)
939 if (con_mutex) Thread_LockMutex(con_mutex);
940 ConBuffer_Shutdown(&con);
941 if (con_mutex) Thread_UnlockMutex(con_mutex);
942 if (con_mutex) Thread_DestroyMutex(con_mutex);
950 Handles cursor positioning, line wrapping, etc
951 All console printing must go through this in order to be displayed
952 If no console is visible, the notify window will pop up.
955 static void Con_PrintToHistory(const char *txt, int mask)
958 // \n goes to next line
959 // \r deletes current line and makes a new one
961 static int cr_pending = 0;
962 static char buf[CON_TEXTSIZE]; // con_mutex
963 static int bufpos = 0;
965 if(!con.text) // FIXME uses a non-abstracted property of con
972 ConBuffer_DeleteLastLine(&con);
980 ConBuffer_AddLine(&con, buf, bufpos, mask);
985 ConBuffer_AddLine(&con, buf, bufpos, mask);
989 buf[bufpos++] = *txt;
990 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
992 ConBuffer_AddLine(&con, buf, bufpos, mask);
1000 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qbool proquakeprotocol)
1002 rcon_redirect_sock = sock;
1003 rcon_redirect_dest = dest;
1004 rcon_redirect_proquakeprotocol = proquakeprotocol;
1005 if (rcon_redirect_proquakeprotocol)
1007 // reserve space for the packet header
1008 rcon_redirect_buffer[0] = 0;
1009 rcon_redirect_buffer[1] = 0;
1010 rcon_redirect_buffer[2] = 0;
1011 rcon_redirect_buffer[3] = 0;
1012 // this is a reply to a CCREQ_RCON
1013 rcon_redirect_buffer[4] = (unsigned char)CCREP_RCON;
1016 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1017 rcon_redirect_bufferpos = 5;
1020 static void Con_Rcon_Redirect_Flush(void)
1022 if(rcon_redirect_sock)
1024 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
1025 if (rcon_redirect_proquakeprotocol)
1027 // update the length in the packet header
1028 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
1030 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
1032 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1033 rcon_redirect_bufferpos = 5;
1034 rcon_redirect_proquakeprotocol = false;
1037 void Con_Rcon_Redirect_End(void)
1039 Con_Rcon_Redirect_Flush();
1040 rcon_redirect_dest = NULL;
1041 rcon_redirect_sock = NULL;
1044 void Con_Rcon_Redirect_Abort(void)
1046 rcon_redirect_dest = NULL;
1047 rcon_redirect_sock = NULL;
1055 /// Adds a character to the rcon buffer.
1056 static void Con_Rcon_AddChar(int c)
1058 if(log_dest_buffer_appending)
1060 ++log_dest_buffer_appending;
1062 // if this print is in response to an rcon command, add the character
1063 // to the rcon redirect buffer
1065 if (rcon_redirect_dest)
1067 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
1068 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
1069 Con_Rcon_Redirect_Flush();
1071 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
1073 if(log_dest_buffer_pos == 0)
1074 Log_DestBuffer_Init();
1075 log_dest_buffer[log_dest_buffer_pos++] = c;
1076 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
1077 Log_DestBuffer_Flush_NoLock();
1080 log_dest_buffer_pos = 0;
1082 --log_dest_buffer_appending;
1086 * Convert an RGB color to its nearest quake color.
1087 * I'll cheat on this a bit by translating the colors to HSV first,
1088 * S and V decide if it's black or white, otherwise, H will decide the
1090 * @param _r Red (0-255)
1091 * @param _g Green (0-255)
1092 * @param _b Blue (0-255)
1093 * @return A quake color character.
1095 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
1097 float r = ((float)_r)/255.0;
1098 float g = ((float)_g)/255.0;
1099 float b = ((float)_b)/255.0;
1100 float min = min(r, min(g, b));
1101 float max = max(r, max(g, b));
1103 int h; ///< Hue angle [0,360]
1104 float s; ///< Saturation [0,1]
1105 float v = max; ///< In HSV v == max [0,1]
1110 s = 1.0 - (min/max);
1112 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1115 // If the value is less than half, return a black color code.
1116 // Otherwise return a white one.
1122 // Let's get the hue angle to define some colors:
1126 h = (int)(60.0 * (g-b)/(max-min))%360;
1128 h = (int)(60.0 * (b-r)/(max-min) + 120);
1129 else // if(max == b) redundant check
1130 h = (int)(60.0 * (r-g)/(max-min) + 240);
1132 if(h < 36) // *red* to orange
1134 else if(h < 80) // orange over *yellow* to evilish-bright-green
1136 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1138 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1140 else if(h < 270) // darkish blue over *dark blue* to cool purple
1142 else if(h < 330) // cool purple over *purple* to ugly swiny red
1144 else // ugly red to red closes the circly
1153 extern cvar_t timestamps;
1154 extern cvar_t timeformat;
1155 extern qbool sys_nostdout;
1156 void Con_MaskPrint(int additionalmask, const char *msg)
1158 static int mask = 0;
1159 static int index = 0;
1160 static char line[MAX_INPUTLINE];
1163 Thread_LockMutex(con_mutex);
1167 Con_Rcon_AddChar(*msg);
1168 // if this is the beginning of a new line, print timestamp
1171 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1173 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1174 line[index++] = STRING_COLOR_TAG;
1175 // assert( STRING_COLOR_DEFAULT < 10 )
1176 line[index++] = STRING_COLOR_DEFAULT + '0';
1177 // special color codes for chat messages must always come first
1178 // for Con_PrintToHistory to work properly
1179 if (*msg == 1 || *msg == 2 || *msg == 3)
1184 if (con_chatsound.value)
1186 if(msg[1] == con_chatsound_team_mask.integer && cl.foundteamchatsound)
1187 S_LocalSound (con_chatsound_team_file.string);
1189 S_LocalSound (con_chatsound_file.string);
1192 // Send to chatbox for say/tell (1) and messages (3)
1193 // 3 is just so that a message can be sent to the chatbox without a sound.
1194 if (*msg == 1 || *msg == 3)
1195 mask = CON_MASK_CHAT;
1197 line[index++] = STRING_COLOR_TAG;
1198 line[index++] = '3';
1200 Con_Rcon_AddChar(*msg);
1203 for (;*timestamp;index++, timestamp++)
1204 if (index < (int)sizeof(line) - 2)
1205 line[index] = *timestamp;
1207 mask |= additionalmask;
1209 // append the character
1210 line[index++] = *msg;
1211 // if this is a newline character, we have a complete line to print
1212 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1214 // terminate the line
1218 // send to scrollable buffer
1219 if (con_initialized && cls.state != ca_dedicated)
1221 Con_PrintToHistory(line, mask);
1223 // send to terminal or dedicated server window
1225 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1227 if(sys_specialcharactertranslation.integer)
1234 int ch = u8_getchar(p, &q);
1235 if(ch >= 0xE000 && ch <= 0xE0FF && ((unsigned char) qfont_table[ch - 0xE000]) >= 0x20)
1237 *p = qfont_table[ch - 0xE000];
1239 memmove(p+1, q, strlen(q)+1);
1247 if(sys_colortranslation.integer == 1) // ANSI
1249 static char printline[MAX_INPUTLINE * 4 + 3];
1250 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1251 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1256 for(in = line, out = printline; *in; ++in)
1260 case STRING_COLOR_TAG:
1261 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1263 char r = tolower(in[2]);
1264 char g = tolower(in[3]);
1265 char b = tolower(in[4]);
1266 // it's a hex digit already, so the else part needs no check --blub
1267 if(isdigit(r)) r -= '0';
1269 if(isdigit(g)) g -= '0';
1271 if(isdigit(b)) b -= '0';
1274 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1275 in += 3; // 3 only, the switch down there does the fourth
1282 case STRING_COLOR_TAG:
1284 *out++ = STRING_COLOR_TAG;
1290 if(lastcolor == 0) break; else lastcolor = 0;
1291 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1296 if(lastcolor == 1) break; else lastcolor = 1;
1297 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1302 if(lastcolor == 2) break; else lastcolor = 2;
1303 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1308 if(lastcolor == 3) break; else lastcolor = 3;
1309 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1314 if(lastcolor == 4) break; else lastcolor = 4;
1315 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1320 if(lastcolor == 5) break; else lastcolor = 5;
1321 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1326 if(lastcolor == 6) break; else lastcolor = 6;
1327 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1332 // bold normal color
1334 if(lastcolor == 8) break; else lastcolor = 8;
1335 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1338 *out++ = STRING_COLOR_TAG;
1345 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1362 Sys_Print(printline);
1364 else if(sys_colortranslation.integer == 2) // Quake
1370 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1373 for(in = line, out = printline; *in; ++in)
1377 case STRING_COLOR_TAG:
1380 case STRING_COLOR_RGB_TAG_CHAR:
1381 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1386 *out++ = STRING_COLOR_TAG;
1387 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1390 case STRING_COLOR_TAG:
1392 *out++ = STRING_COLOR_TAG;
1407 *out++ = STRING_COLOR_TAG;
1417 Sys_Print(printline);
1420 // empty the line buffer
1427 Thread_UnlockMutex(con_mutex);
1435 void Con_MaskPrintf(int mask, const char *fmt, ...)
1438 char msg[MAX_INPUTLINE];
1440 va_start(argptr,fmt);
1441 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1444 Con_MaskPrint(mask, msg);
1452 void Con_Print(const char *msg)
1454 Con_MaskPrint(CON_MASK_PRINT, msg);
1462 void Con_Printf(const char *fmt, ...)
1465 char msg[MAX_INPUTLINE];
1467 va_start(argptr,fmt);
1468 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1471 Con_MaskPrint(CON_MASK_PRINT, msg);
1479 void Con_DPrint(const char *msg)
1481 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1484 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1492 void Con_DPrintf(const char *fmt, ...)
1495 char msg[MAX_INPUTLINE];
1497 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1500 va_start(argptr,fmt);
1501 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1504 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1509 ==============================================================================
1513 ==============================================================================
1520 It draws either the console input line or the chat input line (if is_console is false)
1521 The input line scrolls horizontally if typing goes beyond the right edge
1523 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1526 static void Con_DrawInput(qbool is_console, float x, float v, float inputsize)
1528 int y, i, col_out, linepos, text_start, prefix_start = 0;
1529 char text[MAX_INPUTLINE + 5 + 9 + 1]; // space for ^xRGB, "say_team:" and \0
1535 if (is_console && !key_consoleactive)
1536 return; // don't draw anything
1540 // empty prefix because ] is part of the console edit line
1542 strlcpy(text, key_line, sizeof(text));
1543 linepos = key_linepos;
1551 prefix = "say_team:";
1554 strlcpy(text, chat_buffer, sizeof(text));
1555 linepos = chat_bufferpos;
1559 y = (int)strlen(text);
1561 // make the color code visible when the cursor is inside it
1562 if(text[linepos] != 0)
1564 for(i=1; i < 5 && linepos - i > 0; ++i)
1565 if(text[linepos-i] == STRING_COLOR_TAG)
1567 int caret_pos, ofs = 0;
1568 caret_pos = linepos - i;
1569 if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG)
1571 else if(i == 1 && isdigit(text[caret_pos+1]))
1573 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]))
1575 if(ofs && (size_t)(y + ofs + 1) < sizeof(text))
1578 while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG)
1582 // str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing)
1583 // str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing)
1584 memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos);
1585 text[caret_pos + ofs] = STRING_COLOR_TAG;
1597 x += DrawQ_TextWidth(prefix, 0, inputsize, inputsize, false, fnt);
1604 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, inputsize, inputsize, &col_out, false, fnt, 1000000000);
1606 text_start = x + (vid_conwidth.value - x) * 0.95 - xo; // scroll
1609 else if (!is_console)
1610 prefix_start -= (x - text_start);
1613 DrawQ_String(prefix_start, v, prefix, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1615 DrawQ_String(text_start, v, text, y + 3, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1617 // draw a cursor on top of this
1618 if ((int)(host.realtime*con_cursorspeed) & 1) // cursor is visible
1620 if (!utf8_enable.integer)
1622 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1630 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1631 memcpy(text, curbuf, len);
1634 DrawQ_String(text_start + xo, v, text, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, fnt);
1641 float alignment; // 0 = left, 0.5 = center, 1 = right
1647 const char *continuationString;
1650 int colorindex; // init to -1
1654 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1656 con_text_info_t *ti = (con_text_info_t *) passthrough;
1659 ti->colorindex = -1;
1660 return ti->fontsize * ti->font->maxwidth;
1663 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1664 else if(maxWidth == -1)
1665 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1668 Sys_Printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1669 // Note: this is NOT a Con_Printf, as it could print recursively
1674 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1680 (void) isContinuation;
1684 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1686 con_text_info_t *ti = (con_text_info_t *) passthrough;
1688 if(ti->y < ti->ymin - 0.001)
1690 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1694 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1695 if(isContinuation && *ti->continuationString)
1696 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);
1698 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);
1701 ti->y += ti->fontsize;
1705 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)
1709 int maxlines = (int) floor(height / fontsize + 0.01f);
1712 int continuationWidth = 0;
1714 double t = cl.time; // saved so it won't change
1717 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1718 ti.fontsize = fontsize;
1719 ti.alignment = alignment_x;
1722 ti.ymax = y + height;
1723 ti.continuationString = continuationString;
1726 Con_WordWidthFunc(&ti, NULL, &len, -1);
1727 len = strlen(continuationString);
1728 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &len, -1);
1730 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1731 startidx = CON_LINES_COUNT;
1732 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1734 con_lineinfo_t *l = &CON_LINES(i);
1737 if((l->mask & mask_must) != mask_must)
1739 if(l->mask & mask_mustnot)
1741 if(maxage && (l->addtime < t - maxage))
1745 // Calculate its actual height...
1746 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1747 if(lines + mylines >= maxlines)
1749 nskip = lines + mylines - maxlines;
1758 // then center according to the calculated amount of lines...
1760 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1762 // then actually draw
1763 for(i = startidx; i < CON_LINES_COUNT; ++i)
1765 con_lineinfo_t *l = &CON_LINES(i);
1767 if((l->mask & mask_must) != mask_must)
1769 if(l->mask & mask_mustnot)
1771 if(maxage && (l->addtime < t - maxage))
1774 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1784 Draws the last few lines of output transparently over the game top
1787 void Con_DrawNotify (void)
1790 float chatstart, notifystart, inputsize, height;
1795 if (con_mutex) Thread_LockMutex(con_mutex);
1796 ConBuffer_FixTimes(&con);
1798 numChatlines = con_chat.integer;
1800 chatpos = con_chatpos.integer;
1802 if (con_notify.integer < 0)
1803 Cvar_SetValueQuick(&con_notify, 0);
1804 if (gamemode == GAME_TRANSFUSION)
1805 v = 8; // vertical offset
1809 // GAME_NEXUIZ: center, otherwise left justify
1810 align = con_notifyalign.value;
1811 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1813 if(IS_OLDNEXUIZ_DERIVED(gamemode))
1817 if(numChatlines || !con_chatrect.integer)
1821 // first chat, input line, then notify
1823 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1825 else if(chatpos > 0)
1827 // first notify, then (chatpos-1) empty lines, then chat, then input
1829 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1831 else // if(chatpos < 0)
1833 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1835 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1840 // just notify and input
1842 chatstart = 0; // shut off gcc warning
1845 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, "");
1847 if(con_chatrect.integer)
1849 x = con_chatrect_x.value * vid_conwidth.value;
1850 v = con_chatrect_y.value * vid_conheight.value;
1855 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1858 height = numChatlines * con_chatsize.value;
1862 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 ... ");
1865 if (key_dest == key_message)
1867 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1868 Con_DrawInput(false, x, v, inputsize);
1873 if (con_mutex) Thread_UnlockMutex(con_mutex);
1880 Returns the height of a given console line; calculates it if necessary.
1883 static int Con_LineHeight(int lineno)
1885 con_lineinfo_t *li = &CON_LINES(lineno);
1886 if(li->height == -1)
1888 float width = vid_conwidth.value;
1890 ti.fontsize = con_textsize.value;
1891 ti.font = FONT_CONSOLE;
1892 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1901 Draws a line of the console; returns its height in lines.
1902 If alpha is 0, the line is not drawn, but still wrapped and its height
1906 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1908 float width = vid_conwidth.value;
1910 con_lineinfo_t *li = &CON_LINES(lineno);
1912 if((li->mask & mask_must) != mask_must)
1914 if((li->mask & mask_mustnot) != 0)
1917 ti.continuationString = "";
1919 ti.fontsize = con_textsize.value;
1920 ti.font = FONT_CONSOLE;
1922 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1927 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1934 Calculates the last visible line index and how much to show of it based on
1938 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1943 if(con_backscroll < 0)
1948 // now count until we saw con_backscroll actual lines
1949 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1950 if((CON_LINES(i).mask & mask_must) == mask_must)
1951 if((CON_LINES(i).mask & mask_mustnot) == 0)
1953 int h = Con_LineHeight(i);
1955 // line is the last visible line?
1957 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1959 *limitlast = lines_seen + h - con_backscroll;
1966 // if we get here, no line was on screen - scroll so that one line is
1968 con_backscroll = lines_seen - 1;
1976 Draws the console with the solid background
1977 The typing input line at the bottom should only be drawn if typing is allowed
1980 void Con_DrawConsole (int lines)
1982 float alpha, alpha0;
1985 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1986 cachepic_t *conbackpic;
1987 unsigned int conbackflags;
1992 if (con_mutex) Thread_LockMutex(con_mutex);
1994 if (con_backscroll < 0)
1997 con_vislines = lines;
1999 r_draw2d_force = true;
2001 // draw the background
2002 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
2003 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
2005 sx = scr_conscroll_x.value;
2006 sy = scr_conscroll_y.value;
2007 conbackflags = CACHEPICFLAG_FAILONMISSING; // So console is readable when game content is missing
2008 if (sx != 0 || sy != 0)
2009 conbackflags &= CACHEPICFLAG_NOCLAMP;
2010 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", conbackflags) : NULL;
2011 sx *= host.realtime; sy *= host.realtime;
2012 sx -= floor(sx); sy -= floor(sy);
2013 if (Draw_IsPicLoaded(conbackpic))
2014 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2015 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2016 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2017 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2018 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2021 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
2023 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
2025 sx = scr_conscroll2_x.value;
2026 sy = scr_conscroll2_y.value;
2027 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2028 sx *= host.realtime; sy *= host.realtime;
2029 sx -= floor(sx); sy -= floor(sy);
2030 if(Draw_IsPicLoaded(conbackpic))
2031 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2032 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2033 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2034 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2035 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2038 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
2040 sx = scr_conscroll3_x.value;
2041 sy = scr_conscroll3_y.value;
2042 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2043 sx *= host.realtime; sy *= host.realtime;
2044 sx -= floor(sx); sy -= floor(sy);
2045 if(Draw_IsPicLoaded(conbackpic))
2046 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2047 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2048 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2049 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2050 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2053 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);
2059 int count = CON_LINES_COUNT;
2060 float ymax = con_vislines - 2 * con_textsize.value;
2061 float y = ymax + con_textsize.value * con_backscroll;
2062 for (i = 0;i < count && y >= 0;i++)
2063 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
2064 // fix any excessive scrollback for the next frame
2065 if (i >= count && y >= 0)
2067 con_backscroll -= (int)(y / con_textsize.value);
2068 if (con_backscroll < 0)
2073 if(CON_LINES_COUNT > 0)
2075 int i, last, limitlast;
2077 float ymax = con_vislines - 2 * con_textsize.value;
2078 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2079 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2080 y = ymax - con_textsize.value;
2083 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
2088 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
2090 break; // top of console buffer
2092 break; // top of console window
2099 // draw the input prompt, user text, and cursor if desired
2100 Con_DrawInput(true, 0, con_vislines - con_textsize.value * 2, con_textsize.value);
2102 r_draw2d_force = false;
2103 if (con_mutex) Thread_UnlockMutex(con_mutex);
2110 Prints not only map filename, but also
2111 its format (q1/q2/q3/hl) and even its message
2113 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2114 //LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2115 //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
2116 //LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2117 qbool GetMapList (const char *s, char *completedname, int completednamebufferlength)
2121 int i, k, max, p, o, min;
2124 unsigned char buf[1024];
2126 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2127 t = FS_Search(message, 1, true, NULL);
2130 if (t->numfilenames > 1)
2131 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2132 len = (unsigned char *)Z_Malloc(t->numfilenames);
2134 for(max=i=0;i<t->numfilenames;i++)
2136 k = (int)strlen(t->filenames[i]);
2146 for(i=0;i<t->numfilenames;i++)
2148 int lumpofs = 0, lumplen = 0;
2149 char *entities = NULL;
2150 const char *data = NULL;
2152 char entfilename[MAX_QPATH];
2155 strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2157 f = FS_OpenVirtualFile(t->filenames[i], true);
2160 strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2161 memset(buf, 0, 1024);
2162 FS_Read(f, buf, 1024);
2163 if (!memcmp(buf, "IBSP", 4))
2165 p = LittleLong(((int *)buf)[1]);
2166 if (p == Q3BSPVERSION)
2168 q3dheader_t *header = (q3dheader_t *)buf;
2169 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2170 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2171 dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2173 else if (p == Q2BSPVERSION)
2175 q2dheader_t *header = (q2dheader_t *)buf;
2176 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2177 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2178 dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2181 dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2183 else if (BuffLittleLong(buf) == BSPVERSION)
2185 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2186 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2187 dpsnprintf(desc, sizeof(desc), "BSP29");
2189 else if (BuffLittleLong(buf) == 30)
2191 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2192 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2193 dpsnprintf(desc, sizeof(desc), "BSPHL");
2195 else if (!memcmp(buf, "BSP2", 4))
2197 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2198 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2199 dpsnprintf(desc, sizeof(desc), "BSP2");
2201 else if (!memcmp(buf, "2PSB", 4))
2203 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2204 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2205 dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2207 else if(!memcmp(buf, "VBSP", 4))
2209 hl2dheader_t *header = (hl2dheader_t *)buf;
2210 lumpofs = LittleLong(header->lumps[HL2LUMP_ENTITIES].fileofs);
2211 lumplen = LittleLong(header->lumps[HL2LUMP_ENTITIES].filelen);
2212 dpsnprintf(desc, sizeof(desc), "VBSP%i", LittleLong(((int *)buf)[1]));
2215 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2216 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2217 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2218 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2219 if (!entities && lumplen >= 10)
2221 FS_Seek(f, lumpofs, SEEK_SET);
2222 entities = (char *)Z_Malloc(lumplen + 1);
2223 FS_Read(f, entities, lumplen);
2227 // if there are entities to parse, a missing message key just
2228 // means there is no title, so clear the message string now
2234 if (!COM_ParseToken_Simple(&data, false, false, true))
2236 if (com_token[0] == '{')
2238 if (com_token[0] == '}')
2240 // skip leading whitespace
2241 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2242 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2243 keyname[l] = com_token[k+l];
2245 if (!COM_ParseToken_Simple(&data, false, false, true))
2247 if (developer_extra.integer)
2248 Con_DPrintf("key: %s %s\n", keyname, com_token);
2249 if (!strcmp(keyname, "message"))
2251 // get the message contents
2252 strlcpy(message, com_token, sizeof(message));
2262 *(t->filenames[i]+len[i]+5) = 0;
2263 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2268 k = *(t->filenames[0]+5+p);
2271 for(i=1;i<t->numfilenames;i++)
2272 if(*(t->filenames[i]+5+p) != k)
2276 if(p > o && completedname && completednamebufferlength > 0)
2278 memset(completedname, 0, completednamebufferlength);
2279 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2289 New function for tab-completion system
2290 Added by EvilTypeGuy
2291 MEGA Thanks to Taniwha
2294 void Con_DisplayList(const char **list)
2296 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2297 const char **walk = list;
2300 len = (int)strlen(*walk);
2308 len = (int)strlen(*list);
2309 if (pos + maxlen >= width) {
2315 for (i = 0; i < (maxlen - len); i++)
2327 // Now it becomes TRICKY :D --blub
2328 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2329 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2330 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2331 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
2332 static int Nicks_matchpos;
2334 // co against <<:BLASTER:>> is true!?
2335 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2339 if(tolower(*a) == tolower(*b))
2353 return (*a < *b) ? -1 : 1;
2357 return (*a < *b) ? -1 : 1;
2361 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2364 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2366 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2367 return Nicks_strncasecmp_nospaces(a, b, a_len);
2368 return strncasecmp(a, b, a_len);
2371 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2373 // ignore non alphanumerics of B
2374 // if A contains a non-alphanumeric, B must contain it as well though!
2377 qbool alnum_a, alnum_b;
2379 if(tolower(*a) == tolower(*b))
2381 if(*a == 0) // end of both strings, they're equal
2388 // not equal, end of one string?
2393 // ignore non alphanumerics
2394 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2395 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2396 if(!alnum_a) // b must contain this
2397 return (*a < *b) ? -1 : 1;
2400 // otherwise, both are alnum, they're just not equal, return the appropriate number
2402 return (*a < *b) ? -1 : 1;
2408 /* Nicks_CompleteCountPossible
2410 Count the number of possible nicks to complete
2412 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qbool isCon)
2414 char name[MAX_SCOREBOARDNAME];
2420 if(!con_nickcompletion.integer)
2423 // changed that to 1
2424 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2427 for(i = 0; i < cl.maxclients; ++i)
2430 if(!cl.scores[p].name[0])
2433 SanitizeString(cl.scores[p].name, name);
2434 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2440 spos = pos - 1; // no need for a minimum of characters :)
2444 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2446 if(!(isCon && spos == 1)) // console start
2452 if(isCon && spos == 0)
2454 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2460 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2461 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2463 // the sanitized list
2464 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2467 Nicks_matchpos = match;
2470 Nicks_offset[count] = s - (&line[match]);
2471 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2478 static void Cmd_CompleteNicksPrint(int count)
2481 for(i = 0; i < count; ++i)
2482 Con_Printf("%s\n", Nicks_list[i]);
2485 static void Nicks_CutMatchesNormal(int count)
2487 // cut match 0 down to the longest possible completion
2490 c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2491 for(i = 1; i < count; ++i)
2493 l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2497 for(l = 0; l <= c; ++l)
2498 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2504 Nicks_sanlist[0][c+1] = 0;
2505 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2508 static unsigned int Nicks_strcleanlen(const char *s)
2513 if( (*s >= 'a' && *s <= 'z') ||
2514 (*s >= 'A' && *s <= 'Z') ||
2515 (*s >= '0' && *s <= '9') ||
2523 static void Nicks_CutMatchesAlphaNumeric(int count)
2525 // cut match 0 down to the longest possible completion
2528 char tempstr[sizeof(Nicks_sanlist[0])];
2530 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2532 c = (unsigned int)strlen(Nicks_sanlist[0]);
2533 for(i = 0, l = 0; i < (int)c; ++i)
2535 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2536 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2537 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2539 tempstr[l++] = Nicks_sanlist[0][i];
2544 for(i = 1; i < count; ++i)
2547 b = Nicks_sanlist[i];
2557 if(tolower(*a) == tolower(*b))
2563 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2565 // b is alnum, so cut
2572 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2573 Nicks_CutMatchesNormal(count);
2574 //if(!Nicks_sanlist[0][0])
2575 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2577 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2578 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2582 static void Nicks_CutMatchesNoSpaces(int count)
2584 // cut match 0 down to the longest possible completion
2587 char tempstr[sizeof(Nicks_sanlist[0])];
2590 c = (unsigned int)strlen(Nicks_sanlist[0]);
2591 for(i = 0, l = 0; i < (int)c; ++i)
2593 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2595 tempstr[l++] = Nicks_sanlist[0][i];
2600 for(i = 1; i < count; ++i)
2603 b = Nicks_sanlist[i];
2613 if(tolower(*a) == tolower(*b))
2627 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2628 Nicks_CutMatchesNormal(count);
2629 //if(!Nicks_sanlist[0][0])
2630 //Con_Printf("TS: %s\n", tempstr);
2631 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2633 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2634 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2638 static void Nicks_CutMatches(int count)
2640 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2641 Nicks_CutMatchesAlphaNumeric(count);
2642 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2643 Nicks_CutMatchesNoSpaces(count);
2645 Nicks_CutMatchesNormal(count);
2648 static const char **Nicks_CompleteBuildList(int count)
2652 // the list is freed by Con_CompleteCommandLine, so create a char**
2653 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2655 for(; bpos < count; ++bpos)
2656 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2658 Nicks_CutMatches(count);
2666 Restores the previous used color, after the autocompleted name.
2668 static int Nicks_AddLastColor(char *buffer, int pos)
2670 qbool quote_added = false;
2672 int color = STRING_COLOR_DEFAULT + '0';
2673 char r = 0, g = 0, b = 0;
2675 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2677 // we'll have to add a quote :)
2678 buffer[pos++] = '\"';
2682 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2684 // add color when no quote was added, or when flags &4?
2686 for(match = Nicks_matchpos-1; match >= 0; --match)
2688 if(buffer[match] == STRING_COLOR_TAG)
2690 if( isdigit(buffer[match+1]) )
2692 color = buffer[match+1];
2695 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2697 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2699 r = buffer[match+2];
2700 g = buffer[match+3];
2701 b = buffer[match+4];
2710 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2712 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2713 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2716 buffer[pos++] = STRING_COLOR_TAG;
2719 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2725 buffer[pos++] = color;
2731 Con_CompleteCommandLine
2733 New function for tab-completion system
2734 Added by EvilTypeGuy
2735 Thanks to Fett erich@heintz.com
2737 Enhanced to tab-complete map names by [515]
2740 int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console)
2742 const char *text = "";
2744 const char **list[4] = {0, 0, 0, 0};
2747 int c, v, a, i, cmd_len, pos, k;
2748 int n; // nicks --blub
2749 const char *space, *patterns;
2753 int linestart, linepos;
2754 unsigned int linesize;
2758 linepos = key_linepos;
2759 linesize = sizeof(key_line);
2765 linepos = chat_bufferpos;
2766 linesize = sizeof(chat_buffer);
2770 //find what we want to complete
2772 while(--pos >= linestart)
2775 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2781 strlcpy(s2, line + linepos, sizeof(s2)); //save chars after cursor
2782 line[linepos] = 0; //hide them
2784 c = v = a = n = cmd_len = 0;
2788 space = strchr(line + 1, ' ');
2789 if(space && pos == (space - line) + 1)
2791 strlcpy(command, line + 1, min(sizeof(command), (unsigned int)(space - line)));
2793 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?
2794 if(patterns && !*patterns)
2795 patterns = NULL; // get rid of the empty string
2797 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2801 if (GetMapList(s, t, sizeof(t)))
2803 // first move the cursor
2804 linepos += (int)strlen(t) - (int)strlen(s);
2806 // and now do the actual work
2808 strlcat(line, t, MAX_INPUTLINE);
2809 strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2811 // and fix the cursor
2812 if(linepos > (int) strlen(line))
2813 linepos = (int) strlen(line);
2822 stringlist_t resultbuf, dirbuf;
2825 // // store completion patterns (space separated) for command foo in con_completion_foo
2826 // set con_completion_foo "foodata/*.foodefault *.foo"
2829 // Note: patterns with slash are always treated as absolute
2830 // patterns; patterns without slash search in the innermost
2831 // directory the user specified. There is no way to "complete into"
2832 // a directory as of now, as directories seem to be unknown to the
2836 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2837 // set con_completion_playdemo "*.dem"
2838 // set con_completion_play "*.wav *.ogg"
2840 // TODO somehow add support for directories; these shall complete
2841 // to their name + an appended slash.
2843 stringlistinit(&resultbuf);
2844 stringlistinit(&dirbuf);
2845 while(COM_ParseToken_Simple(&patterns, false, false, true))
2848 if(strchr(com_token, '/'))
2850 search = FS_Search(com_token, true, true, NULL);
2854 const char *slash = strrchr(s, '/');
2857 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2858 strlcat(t, com_token, sizeof(t));
2859 search = FS_Search(t, true, true, NULL);
2862 search = FS_Search(com_token, true, true, NULL);
2866 for(i = 0; i < search->numfilenames; ++i)
2867 if(!strncmp(search->filenames[i], s, strlen(s)))
2868 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2869 stringlistappend(&resultbuf, search->filenames[i]);
2870 FS_FreeSearch(search);
2874 // In any case, add directory names
2877 const char *slash = strrchr(s, '/');
2880 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2881 strlcat(t, "*", sizeof(t));
2882 search = FS_Search(t, true, true, NULL);
2885 search = FS_Search("*", true, true, NULL);
2888 for(i = 0; i < search->numfilenames; ++i)
2889 if(!strncmp(search->filenames[i], s, strlen(s)))
2890 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2891 stringlistappend(&dirbuf, search->filenames[i]);
2892 FS_FreeSearch(search);
2896 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2899 unsigned int matchchars;
2900 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2902 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2905 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2907 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2911 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2912 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2913 for(i = 0; i < dirbuf.numstrings; ++i)
2915 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2917 for(i = 0; i < resultbuf.numstrings; ++i)
2919 Con_Printf("%s\n", resultbuf.strings[i]);
2921 matchchars = sizeof(t) - 1;
2922 if(resultbuf.numstrings > 0)
2924 p = resultbuf.strings[0];
2925 q = resultbuf.strings[resultbuf.numstrings - 1];
2926 for(; *p && *p == *q; ++p, ++q);
2927 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2929 if(dirbuf.numstrings > 0)
2931 p = dirbuf.strings[0];
2932 q = dirbuf.strings[dirbuf.numstrings - 1];
2933 for(; *p && *p == *q; ++p, ++q);
2934 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2936 // now p points to the first non-equal character, or to the end
2937 // of resultbuf.strings[0]. We want to append the characters
2938 // from resultbuf.strings[0] to (not including) p as these are
2939 // the unique prefix
2940 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2943 // first move the cursor
2944 linepos += (int)strlen(t) - (int)strlen(s);
2946 // and now do the actual work
2948 strlcat(line, t, MAX_INPUTLINE);
2949 strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2951 // and fix the cursor
2952 if(linepos > (int) strlen(line))
2953 linepos = (int) strlen(line);
2955 stringlistfreecontents(&resultbuf);
2956 stringlistfreecontents(&dirbuf);
2958 return linepos; // bail out, when we complete for a command that wants a file name
2963 // Count number of possible matches and print them
2964 c = Cmd_CompleteCountPossible(cmd, s);
2967 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2968 Cmd_CompleteCommandPrint(cmd, s);
2970 v = Cvar_CompleteCountPossible(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2973 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2974 Cvar_CompleteCvarPrint(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2976 a = Cmd_CompleteAliasCountPossible(cmd, s);
2979 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2980 Cmd_CompleteAliasPrint(cmd, s);
2984 n = Nicks_CompleteCountPossible(line, linepos, s, is_console);
2987 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2988 Cmd_CompleteNicksPrint(n);
2991 if (!(c + v + a + n)) // No possible matches
2994 strlcpy(&line[linepos], s2, linesize - linepos);
2999 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
3001 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
3003 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
3007 text = *(list[3] = Nicks_CompleteBuildList(n));
3009 text = *(Nicks_CompleteBuildList(n));
3012 for (cmd_len = (int)strlen(s);;cmd_len++)
3015 for (i = 0; i < 3; i++)
3017 for (l = list[i];*l;l++)
3018 if ((*l)[cmd_len] != text[cmd_len])
3020 // all possible matches share this character, so we continue...
3023 // if all matches ended at the same position, stop
3024 // (this means there is only one match)
3030 // prevent a buffer overrun by limiting cmd_len according to remaining space
3031 cmd_len = min(cmd_len, (int)linesize - 1 - pos);
3035 memcpy(&line[linepos], text, cmd_len);
3037 // if there is only one match, add a space after it
3038 if (c + v + a + n == 1 && linepos < (int)linesize - 1)
3041 { // was a nick, might have an offset, and needs colors ;) --blub
3042 linepos = pos - Nicks_offset[0];
3043 cmd_len = (int)strlen(Nicks_list[0]);
3044 cmd_len = min(cmd_len, (int)linesize - 3 - pos);
3046 memcpy(&line[linepos] , Nicks_list[0], cmd_len);
3048 if(linepos < (int)(linesize - 7)) // space for color code (^[0-9] or ^xrgb), space and \0
3049 linepos = Nicks_AddLastColor(line, linepos);
3051 line[linepos++] = ' ';
3055 // use strlcat to avoid a buffer overrun
3057 strlcat(line, s2, linesize);
3062 // free the command, cvar, and alias lists
3063 for (i = 0; i < 4; i++)
3065 Mem_Free((void *)list[i]);