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, unsigned 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, unsigned mask_must, unsigned 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);
395 dp_ustr2stp(copybuf, sizeof(copybuf), l->start, l->len);
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 dp_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 chat_bufferpos = dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
723 if (chat_bufferpos < 0)
735 static void Con_MessageMode_f(cmd_state_t *cmd)
737 Con_MsgCmdMode(cmd, 0);
747 static void Con_MessageMode2_f(cmd_state_t *cmd)
749 Con_MsgCmdMode(cmd, 1);
757 static void Con_CommandMode_f(cmd_state_t *cmd)
759 Con_MsgCmdMode(cmd, -1);
767 void Con_CheckResize (void)
772 f = bound(1, con_textsize.value, 128);
773 if(f != con_textsize.value)
774 Cvar_SetValueQuick(&con_textsize, f);
775 width = (int)floor(vid_conwidth.value / con_textsize.value);
776 width = bound(1, width, con.textsize/4);
777 // FIXME uses con in a non abstracted way
779 if (width == con_linewidth)
782 con_linewidth = width;
784 for(i = 0; i < CON_LINES_COUNT; ++i)
785 CON_LINES(i).height = -1; // recalculate when next needed
791 //[515]: the simplest command ever
792 //LadyHavoc: not so simple after I made it print usage...
793 static void Con_Maps_f(cmd_state_t *cmd)
795 if (Cmd_Argc(cmd) > 2)
797 Con_Printf("usage: maps [mapnameprefix]\n");
800 else if (Cmd_Argc(cmd) == 2)
801 GetMapList(Cmd_Argv(cmd, 1), NULL, 0);
803 GetMapList("", NULL, 0);
806 static void Con_ConDump_f(cmd_state_t *cmd)
810 if (Cmd_Argc(cmd) != 2)
812 Con_Printf("usage: condump <filename>\n");
815 file = FS_OpenRealFile(Cmd_Argv(cmd, 1), "w", false);
818 Con_Printf(CON_ERROR "condump: unable to write file \"%s\"\n", Cmd_Argv(cmd, 1));
821 if (con_mutex) Thread_LockMutex(con_mutex);
822 for(i = 0; i < CON_LINES_COUNT; ++i)
824 if (condump_stripcolors.integer)
827 size_t len = CON_LINES(i).len;
828 char* sanitizedmsg = (char*)Mem_Alloc(tempmempool, len + 1);
829 memcpy (sanitizedmsg, CON_LINES(i).start, len);
830 SanitizeString(sanitizedmsg, sanitizedmsg); // SanitizeString's in pointer is always ahead of the out pointer, so this should work.
831 FS_Write(file, sanitizedmsg, strlen(sanitizedmsg));
832 Mem_Free(sanitizedmsg);
836 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
838 FS_Write(file, "\n", 1);
840 if (con_mutex) Thread_UnlockMutex(con_mutex);
844 void Con_Clear_f(cmd_state_t *cmd)
846 if (con_mutex) Thread_LockMutex(con_mutex);
847 ConBuffer_Clear(&con);
848 if (con_mutex) Thread_UnlockMutex(con_mutex);
851 static void Con_RCon_ClearPassword_c(cvar_t *var)
853 // whenever rcon_secure is changed to 0, clear rcon_password for
854 // security reasons (prevents a send-rcon-password-as-plaintext
855 // attack based on NQ protocol session takeover and svc_stufftext)
856 if(var->integer <= 0)
857 Cvar_SetQuick(&rcon_password, "");
868 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
869 if (Thread_HasThreads())
870 con_mutex = Thread_CreateMutex();
872 // Allocate a log queue, this will be freed after configs are parsed
873 logq_size = MAX_INPUTLINE;
874 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
877 Cvar_RegisterVariable (&sys_colortranslation);
878 Cvar_RegisterVariable (&sys_specialcharactertranslation);
880 Cvar_RegisterVariable (&log_file);
881 Cvar_RegisterVariable (&log_file_stripcolors);
882 Cvar_RegisterVariable (&log_dest_udp);
884 // support for the classic Quake option
885 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
886 if (Sys_CheckParm ("-condebug") != 0)
887 Cvar_SetQuick (&log_file, "qconsole.log");
889 // register our cvars
890 Cvar_RegisterVariable (&con_chat);
891 Cvar_RegisterVariable (&con_chatpos);
892 Cvar_RegisterVariable (&con_chatrect_x);
893 Cvar_RegisterVariable (&con_chatrect_y);
894 Cvar_RegisterVariable (&con_chatrect);
895 Cvar_RegisterVariable (&con_chatsize);
896 Cvar_RegisterVariable (&con_chattime);
897 Cvar_RegisterVariable (&con_chatwidth);
898 Cvar_RegisterVariable (&con_notify);
899 Cvar_RegisterVariable (&con_notifyalign);
900 Cvar_RegisterVariable (&con_notifysize);
901 Cvar_RegisterVariable (&con_notifytime);
902 Cvar_RegisterVariable (&con_textsize);
903 Cvar_RegisterVariable (&con_chatsound);
904 Cvar_RegisterVariable (&con_chatsound_file);
905 Cvar_RegisterVariable (&con_chatsound_team_file);
906 Cvar_RegisterVariable (&con_chatsound_team_mask);
909 Cvar_RegisterVariable (&con_nickcompletion);
910 Cvar_RegisterVariable (&con_nickcompletion_flags);
912 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
913 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
914 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
916 Cvar_RegisterVariable (&condump_stripcolors);
918 Cvar_RegisterVariable(&rcon_address);
919 Cvar_RegisterVariable(&rcon_secure);
920 Cvar_RegisterCallback(&rcon_secure, Con_RCon_ClearPassword_c);
921 Cvar_RegisterVariable(&rcon_secure_challengetimeout);
922 Cvar_RegisterVariable(&rcon_password);
924 // register our commands
925 Cmd_AddCommand(CF_CLIENT, "toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
926 Cmd_AddCommand(CF_CLIENT, "messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
927 Cmd_AddCommand(CF_CLIENT, "messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
928 Cmd_AddCommand(CF_CLIENT, "commandmode", Con_CommandMode_f, "input a console command");
929 Cmd_AddCommand(CF_SHARED, "clear", Con_Clear_f, "clear console history");
930 Cmd_AddCommand(CF_SHARED, "maps", Con_Maps_f, "list information about available maps");
931 Cmd_AddCommand(CF_SHARED, "condump", Con_ConDump_f, "output console history to a file (see also log_file)");
933 con_initialized = true;
936 void Con_Shutdown (void)
938 if (con_mutex) Thread_LockMutex(con_mutex);
939 ConBuffer_Shutdown(&con);
940 if (con_mutex) Thread_UnlockMutex(con_mutex);
941 if (con_mutex) Thread_DestroyMutex(con_mutex);
949 Handles cursor positioning, line wrapping, etc
950 All console printing must go through this in order to be displayed
951 If no console is visible, the notify window will pop up.
954 static void Con_PrintToHistory(const char *txt, int mask)
957 // \n goes to next line
958 // \r deletes current line and makes a new one
960 static int cr_pending = 0;
961 static char buf[CON_TEXTSIZE]; // con_mutex
962 static int bufpos = 0;
964 if(!con.text) // FIXME uses a non-abstracted property of con
971 ConBuffer_DeleteLastLine(&con);
979 ConBuffer_AddLine(&con, buf, bufpos, mask);
984 ConBuffer_AddLine(&con, buf, bufpos, mask);
988 buf[bufpos++] = *txt;
989 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
991 ConBuffer_AddLine(&con, buf, bufpos, mask);
999 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qbool proquakeprotocol)
1001 rcon_redirect_sock = sock;
1002 rcon_redirect_dest = dest;
1003 rcon_redirect_proquakeprotocol = proquakeprotocol;
1004 if (rcon_redirect_proquakeprotocol)
1006 // reserve space for the packet header
1007 rcon_redirect_buffer[0] = 0;
1008 rcon_redirect_buffer[1] = 0;
1009 rcon_redirect_buffer[2] = 0;
1010 rcon_redirect_buffer[3] = 0;
1011 // this is a reply to a CCREQ_RCON
1012 rcon_redirect_buffer[4] = (unsigned char)CCREP_RCON;
1015 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1016 rcon_redirect_bufferpos = 5;
1019 static void Con_Rcon_Redirect_Flush(void)
1021 if(rcon_redirect_sock)
1023 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
1024 if (rcon_redirect_proquakeprotocol)
1026 // update the length in the packet header
1027 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
1029 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
1031 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1032 rcon_redirect_bufferpos = 5;
1033 rcon_redirect_proquakeprotocol = false;
1036 void Con_Rcon_Redirect_End(void)
1038 Con_Rcon_Redirect_Flush();
1039 rcon_redirect_dest = NULL;
1040 rcon_redirect_sock = NULL;
1043 void Con_Rcon_Redirect_Abort(void)
1045 rcon_redirect_dest = NULL;
1046 rcon_redirect_sock = NULL;
1054 /// Adds a character to the rcon buffer.
1055 static void Con_Rcon_AddChar(int c)
1057 if(log_dest_buffer_appending)
1059 ++log_dest_buffer_appending;
1061 // if this print is in response to an rcon command, add the character
1062 // to the rcon redirect buffer
1064 if (rcon_redirect_dest)
1066 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
1067 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
1068 Con_Rcon_Redirect_Flush();
1070 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
1072 if(log_dest_buffer_pos == 0)
1073 Log_DestBuffer_Init();
1074 log_dest_buffer[log_dest_buffer_pos++] = c;
1075 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
1076 Log_DestBuffer_Flush_NoLock();
1079 log_dest_buffer_pos = 0;
1081 --log_dest_buffer_appending;
1085 * Convert an RGB color to its nearest quake color.
1086 * I'll cheat on this a bit by translating the colors to HSV first,
1087 * S and V decide if it's black or white, otherwise, H will decide the
1089 * @param _r Red (0-255)
1090 * @param _g Green (0-255)
1091 * @param _b Blue (0-255)
1092 * @return A quake color character.
1094 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
1096 float r = ((float)_r)/255.0;
1097 float g = ((float)_g)/255.0;
1098 float b = ((float)_b)/255.0;
1099 float min = min(r, min(g, b));
1100 float max = max(r, max(g, b));
1102 int h; ///< Hue angle [0,360]
1103 float s; ///< Saturation [0,1]
1104 float v = max; ///< In HSV v == max [0,1]
1109 s = 1.0 - (min/max);
1111 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1114 // If the value is less than half, return a black color code.
1115 // Otherwise return a white one.
1121 // Let's get the hue angle to define some colors:
1125 h = (int)(60.0 * (g-b)/(max-min))%360;
1127 h = (int)(60.0 * (b-r)/(max-min) + 120);
1128 else // if(max == b) redundant check
1129 h = (int)(60.0 * (r-g)/(max-min) + 240);
1131 if(h < 36) // *red* to orange
1133 else if(h < 80) // orange over *yellow* to evilish-bright-green
1135 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1137 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1139 else if(h < 270) // darkish blue over *dark blue* to cool purple
1141 else if(h < 330) // cool purple over *purple* to ugly swiny red
1143 else // ugly red to red closes the circly
1152 extern cvar_t timestamps;
1153 extern cvar_t timeformat;
1154 void Con_MaskPrint(unsigned additionalmask, const char *msg)
1156 static unsigned mask = 0;
1157 static unsigned 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, out - printline);
1362 else if(sys_colortranslation.integer == 2) // Quake
1364 Sys_Print(line, index);
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, out - printline);
1418 // empty the line buffer
1425 Thread_UnlockMutex(con_mutex);
1433 void Con_MaskPrintf(unsigned 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 dp_strlcpy(text, key_line, sizeof(text));
1541 linepos = key_linepos;
1549 prefix = "say_team:";
1552 dp_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(unsigned mask_must, unsigned 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(unsigned mask_must, unsigned 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(unsigned mask_must, unsigned 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 dp_strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2155 f = FS_OpenVirtualFile(t->filenames[i], true);
2158 dp_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 dp_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 dp_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 dp_strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2461 // the sanitized list
2462 dp_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 dp_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 dp_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 dp_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 // adding 1 to line drops the leading ]
2790 dp_ustr2stp(command, sizeof(command), line + 1, space - (line + 1));
2792 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?
2793 if(patterns && !*patterns)
2794 patterns = NULL; // get rid of the empty string
2796 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2800 if (GetMapList(s, t, sizeof(t)))
2802 // first move the cursor
2803 linepos += (int)strlen(t) - (int)strlen(s);
2805 // and now do the actual work
2807 dp_strlcat(line, t, MAX_INPUTLINE);
2808 dp_strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2810 // and fix the cursor
2811 if(linepos > (int) strlen(line))
2812 linepos = (int) strlen(line);
2821 stringlist_t resultbuf, dirbuf;
2824 // // store completion patterns (space separated) for command foo in con_completion_foo
2825 // set con_completion_foo "foodata/*.foodefault *.foo"
2828 // Note: patterns with slash are always treated as absolute
2829 // patterns; patterns without slash search in the innermost
2830 // directory the user specified. There is no way to "complete into"
2831 // a directory as of now, as directories seem to be unknown to the
2835 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2836 // set con_completion_playdemo "*.dem"
2837 // set con_completion_play "*.wav *.ogg"
2839 // TODO somehow add support for directories; these shall complete
2840 // to their name + an appended slash.
2842 stringlistinit(&resultbuf);
2843 stringlistinit(&dirbuf);
2844 while(COM_ParseToken_Simple(&patterns, false, false, true))
2847 if(strchr(com_token, '/'))
2849 search = FS_Search(com_token, true, true, NULL);
2853 const char *slash = strrchr(s, '/');
2856 dp_strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2857 dp_strlcat(t, com_token, sizeof(t));
2858 search = FS_Search(t, true, true, NULL);
2861 search = FS_Search(com_token, true, true, NULL);
2865 for(i = 0; i < search->numfilenames; ++i)
2866 if(!strncmp(search->filenames[i], s, strlen(s)))
2867 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2868 stringlistappend(&resultbuf, search->filenames[i]);
2869 FS_FreeSearch(search);
2873 // In any case, add directory names
2876 const char *slash = strrchr(s, '/');
2879 dp_strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2880 dp_strlcat(t, "*", sizeof(t));
2881 search = FS_Search(t, true, true, NULL);
2884 search = FS_Search("*", true, true, NULL);
2887 for(i = 0; i < search->numfilenames; ++i)
2888 if(!strncmp(search->filenames[i], s, strlen(s)))
2889 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2890 stringlistappend(&dirbuf, search->filenames[i]);
2891 FS_FreeSearch(search);
2895 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2898 unsigned int matchchars;
2899 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2901 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2904 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2906 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2910 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2911 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2912 for(i = 0; i < dirbuf.numstrings; ++i)
2914 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2916 for(i = 0; i < resultbuf.numstrings; ++i)
2918 Con_Printf("%s\n", resultbuf.strings[i]);
2920 matchchars = sizeof(t) - 1;
2921 if(resultbuf.numstrings > 0)
2923 p = resultbuf.strings[0];
2924 q = resultbuf.strings[resultbuf.numstrings - 1];
2925 for(; *p && *p == *q; ++p, ++q);
2926 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2928 if(dirbuf.numstrings > 0)
2930 p = dirbuf.strings[0];
2931 q = dirbuf.strings[dirbuf.numstrings - 1];
2932 for(; *p && *p == *q; ++p, ++q);
2933 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2935 // now p points to the first non-equal character, or to the end
2936 // of resultbuf.strings[0]. We want to append the characters
2937 // from resultbuf.strings[0] to (not including) p as these are
2938 // the unique prefix
2939 dp_strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2942 // first move the cursor
2943 linepos += (int)strlen(t) - (int)strlen(s);
2945 // and now do the actual work
2947 dp_strlcat(line, t, MAX_INPUTLINE);
2948 dp_strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2950 // and fix the cursor
2951 if(linepos > (int) strlen(line))
2952 linepos = (int) strlen(line);
2954 stringlistfreecontents(&resultbuf);
2955 stringlistfreecontents(&dirbuf);
2957 return linepos; // bail out, when we complete for a command that wants a file name
2962 // Count number of possible matches and print them
2963 c = Cmd_CompleteCountPossible(cmd, s);
2966 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2967 Cmd_CompleteCommandPrint(cmd, s);
2969 v = Cvar_CompleteCountPossible(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2972 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2973 Cvar_CompleteCvarPrint(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2975 a = Cmd_CompleteAliasCountPossible(cmd, s);
2978 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2979 Cmd_CompleteAliasPrint(cmd, s);
2983 n = Nicks_CompleteCountPossible(line, linepos, s, is_console);
2986 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2987 Cmd_CompleteNicksPrint(n);
2990 if (!(c + v + a + n)) // No possible matches
2993 dp_strlcpy(&line[linepos], s2, linesize - linepos);
2998 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
3000 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
3002 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
3006 text = *(list[3] = Nicks_CompleteBuildList(n));
3008 text = *(Nicks_CompleteBuildList(n));
3011 for (cmd_len = (int)strlen(s);;cmd_len++)
3014 for (i = 0; i < 3; i++)
3016 for (l = list[i];*l;l++)
3017 if ((*l)[cmd_len] != text[cmd_len])
3019 // all possible matches share this character, so we continue...
3022 // if all matches ended at the same position, stop
3023 // (this means there is only one match)
3029 // prevent a buffer overrun by limiting cmd_len according to remaining space
3030 cmd_len = min(cmd_len, (int)linesize - 1 - pos);
3034 memcpy(&line[linepos], text, cmd_len);
3036 // if there is only one match, add a space after it
3037 if (c + v + a + n == 1 && linepos < (int)linesize - 1)
3040 { // was a nick, might have an offset, and needs colors ;) --blub
3041 linepos = pos - Nicks_offset[0];
3042 cmd_len = (int)strlen(Nicks_list[0]);
3043 cmd_len = min(cmd_len, (int)linesize - 3 - pos);
3045 memcpy(&line[linepos] , Nicks_list[0], cmd_len);
3047 if(linepos < (int)(linesize - 7)) // space for color code (^[0-9] or ^xrgb), space and \0
3048 linepos = Nicks_AddLastColor(line, linepos);
3050 line[linepos++] = ' ';
3054 // use strlcat to avoid a buffer overrun
3056 dp_strlcat(line, s2, linesize);
3061 // free the command, cvar, and alias lists
3062 for (i = 0; i < 4; i++)
3064 Mem_Free((void *)list[i]);