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 * @brief Returns a horizontal line
1508 * @details Returns a graphical horizontal line of length len, but never wider than the
1509 * console. Includes a newline, unless len is >= to the console width
1510 * @note Authored by johnfitz
1512 * @param[in] len Length of the horizontal line
1514 * @return A string of the line
1516 const char *Con_Quakebar(int len, char *bar, size_t barsize)
1518 assert(barsize >= 5);
1520 len = min(len, (int)barsize - 2);
1521 len = min(len, con_linewidth);
1524 memset(&bar[1], '\36', len - 2);
1525 bar[len - 1] = '\37';
1527 if (len < con_linewidth)
1539 * @brief Left-pad a string with spaces to make it appear centered
1540 * @note Authored by johnfitz
1542 * @param[in] maxLineLength Center-align
1543 * @param[in] fmt A printf format string
1544 * @param[in] <unnamed> Zero or more values used by fmt
1546 void Con_CenterPrintf(int maxLineLength, const char *fmt, ...)
1549 char msg[MAX_INPUTLINE]; // the original message
1550 char line[MAX_INPUTLINE]; // one line from the message
1551 char spaces[21]; // buffer for spaces
1552 char *msgCursor, *lineEnding;
1553 int lineLength, msgLength;
1556 va_start(argptr, fmt);
1557 msgLength = dpvsnprintf(msg, sizeof (msg), fmt, argptr);
1562 Con_Printf(CON_WARN "The message given to Con_CenterPrintf was too long\n");
1566 maxLineLength = min(maxLineLength, con_linewidth);
1568 for (msgCursor = msg; *msgCursor;)
1570 lineEnding = strchr(msgCursor, '\n');
1573 lineLength = dp_ustr2stp(line, sizeof(line), msgCursor, lineEnding - msgCursor) - line;
1574 msgCursor = lineEnding + 1;
1578 lineLength = dp_strlcpy(line, msgCursor, sizeof(line));
1579 msgCursor = msg + msgLength;
1582 if (lineLength < maxLineLength)
1584 indentSize = min(sizeof(spaces) - 1, (size_t)(maxLineLength - lineLength) / 2);
1585 memset(spaces, ' ', indentSize);
1586 spaces[indentSize] = 0;
1587 Con_MaskPrintf(CON_MASK_HIDENOTIFY, "%s%s\n", spaces, line);
1590 Con_MaskPrintf(CON_MASK_HIDENOTIFY, "%s\n", line);
1595 * @brief Prints a center-aligned message to the console
1596 * @note Authored by johnfitz
1598 * @param[in] str A multiline string to print
1600 void Con_CenterPrint(const char *str)
1604 Con_MaskPrintf(CON_MASK_HIDENOTIFY, "%s", Con_Quakebar(40, bar, sizeof(bar)));
1605 Con_CenterPrintf(40, "%s\n", str);
1606 Con_MaskPrintf(CON_MASK_HIDENOTIFY, "%s", bar);
1612 ==============================================================================
1616 ==============================================================================
1623 It draws either the console input line or the chat input line (if is_console is false)
1624 The input line scrolls horizontally if typing goes beyond the right edge
1626 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1629 static void Con_DrawInput(qbool is_console, float x, float v, float inputsize)
1631 int y, i, col_out, linepos, text_start, prefix_start = 0;
1632 char text[MAX_INPUTLINE + 5 + 9 + 1]; // space for ^xRGB, "say_team:" and \0
1638 if (is_console && !key_consoleactive)
1639 return; // don't draw anything
1643 // empty prefix because ] is part of the console edit line
1645 dp_strlcpy(text, key_line, sizeof(text));
1646 linepos = key_linepos;
1654 prefix = "say_team:";
1657 dp_strlcpy(text, chat_buffer, sizeof(text));
1658 linepos = chat_bufferpos;
1662 y = (int)strlen(text);
1664 // make the color code visible when the cursor is inside it
1665 if(text[linepos] != 0)
1667 for(i=1; i < 5 && linepos - i > 0; ++i)
1668 if(text[linepos-i] == STRING_COLOR_TAG)
1670 int caret_pos, ofs = 0;
1671 caret_pos = linepos - i;
1672 if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG)
1674 else if(i == 1 && isdigit(text[caret_pos+1]))
1676 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]))
1678 if(ofs && (size_t)(y + ofs + 1) < sizeof(text))
1681 while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG)
1685 // str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing)
1686 // str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing)
1687 memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos);
1688 text[caret_pos + ofs] = STRING_COLOR_TAG;
1700 x += DrawQ_TextWidth(prefix, 0, inputsize, inputsize, false, fnt);
1707 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, inputsize, inputsize, &col_out, false, fnt, 1000000000);
1709 text_start = x + (vid_conwidth.value - x) * 0.95 - xo; // scroll
1712 else if (!is_console)
1713 prefix_start -= (x - text_start);
1716 DrawQ_String(prefix_start, v, prefix, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1718 DrawQ_String(text_start, v, text, y + 3, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1720 // draw a cursor on top of this
1721 if ((int)(host.realtime*con_cursorspeed) & 1) // cursor is visible
1723 if (!utf8_enable.integer)
1725 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1733 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1734 memcpy(text, curbuf, len);
1737 DrawQ_String(text_start + xo, v, text, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, fnt);
1744 float alignment; // 0 = left, 0.5 = center, 1 = right
1750 const char *continuationString;
1753 int colorindex; // init to -1
1757 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1759 con_text_info_t *ti = (con_text_info_t *) passthrough;
1762 ti->colorindex = -1;
1763 return ti->fontsize * ti->font->maxwidth;
1766 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1767 else if(maxWidth == -1)
1768 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1771 Sys_Printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1772 // Note: this is NOT a Con_Printf, as it could print recursively
1777 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1783 (void) isContinuation;
1787 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1789 con_text_info_t *ti = (con_text_info_t *) passthrough;
1791 if(ti->y < ti->ymin - 0.001)
1793 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1797 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1798 if(isContinuation && *ti->continuationString)
1799 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);
1801 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);
1804 ti->y += ti->fontsize;
1808 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)
1812 int maxlines = (int) floor(height / fontsize + 0.01f);
1815 int continuationWidth = 0;
1817 double t = cl.time; // saved so it won't change
1820 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1821 ti.fontsize = fontsize;
1822 ti.alignment = alignment_x;
1825 ti.ymax = y + height;
1826 ti.continuationString = continuationString;
1829 Con_WordWidthFunc(&ti, NULL, &len, -1);
1830 len = strlen(continuationString);
1831 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &len, -1);
1833 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1834 startidx = CON_LINES_COUNT;
1835 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1837 con_lineinfo_t *l = &CON_LINES(i);
1840 if((l->mask & mask_must) != mask_must)
1842 if(l->mask & mask_mustnot)
1844 if(maxage && (l->addtime < t - maxage))
1848 // Calculate its actual height...
1849 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1850 if(lines + mylines >= maxlines)
1852 nskip = lines + mylines - maxlines;
1861 // then center according to the calculated amount of lines...
1863 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1865 // then actually draw
1866 for(i = startidx; i < CON_LINES_COUNT; ++i)
1868 con_lineinfo_t *l = &CON_LINES(i);
1870 if((l->mask & mask_must) != mask_must)
1872 if(l->mask & mask_mustnot)
1874 if(maxage && (l->addtime < t - maxage))
1877 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1887 Draws the last few lines of output transparently over the game top
1890 void Con_DrawNotify (void)
1893 float chatstart, notifystart, inputsize, height;
1898 if (con_mutex) Thread_LockMutex(con_mutex);
1899 ConBuffer_FixTimes(&con);
1901 numChatlines = con_chat.integer;
1903 chatpos = con_chatpos.integer;
1905 if (con_notify.integer < 0)
1906 Cvar_SetValueQuick(&con_notify, 0);
1907 if (gamemode == GAME_TRANSFUSION)
1908 v = 8; // vertical offset
1912 // GAME_NEXUIZ: center, otherwise left justify
1913 align = con_notifyalign.value;
1914 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1916 if(IS_OLDNEXUIZ_DERIVED(gamemode))
1920 if(numChatlines || !con_chatrect.integer)
1924 // first chat, input line, then notify
1926 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1928 else if(chatpos > 0)
1930 // first notify, then (chatpos-1) empty lines, then chat, then input
1932 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1934 else // if(chatpos < 0)
1936 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1938 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1943 // just notify and input
1945 chatstart = 0; // shut off gcc warning
1948 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, "");
1950 if(con_chatrect.integer)
1952 x = con_chatrect_x.value * vid_conwidth.value;
1953 v = con_chatrect_y.value * vid_conheight.value;
1958 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1961 height = numChatlines * con_chatsize.value;
1965 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 ... ");
1968 if (key_dest == key_message)
1970 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1971 Con_DrawInput(false, x, v, inputsize);
1976 if (con_mutex) Thread_UnlockMutex(con_mutex);
1983 Returns the height of a given console line; calculates it if necessary.
1986 static int Con_LineHeight(int lineno)
1988 con_lineinfo_t *li = &CON_LINES(lineno);
1989 if(li->height == -1)
1991 float width = vid_conwidth.value;
1993 ti.fontsize = con_textsize.value;
1994 ti.font = FONT_CONSOLE;
1995 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
2004 Draws a line of the console; returns its height in lines.
2005 If alpha is 0, the line is not drawn, but still wrapped and its height
2009 static int Con_DrawConsoleLine(unsigned mask_must, unsigned mask_mustnot, float y, int lineno, float ymin, float ymax)
2011 float width = vid_conwidth.value;
2013 con_lineinfo_t *li = &CON_LINES(lineno);
2015 if((li->mask & mask_must) != mask_must)
2017 if((li->mask & mask_mustnot) != 0)
2020 ti.continuationString = "";
2022 ti.fontsize = con_textsize.value;
2023 ti.font = FONT_CONSOLE;
2025 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
2030 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
2037 Calculates the last visible line index and how much to show of it based on
2041 static void Con_LastVisibleLine(unsigned mask_must, unsigned mask_mustnot, int *last, int *limitlast)
2046 if(con_backscroll < 0)
2051 // now count until we saw con_backscroll actual lines
2052 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
2053 if((CON_LINES(i).mask & mask_must) == mask_must)
2054 if((CON_LINES(i).mask & mask_mustnot) == 0)
2056 int h = Con_LineHeight(i);
2058 // line is the last visible line?
2060 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
2062 *limitlast = lines_seen + h - con_backscroll;
2069 // if we get here, no line was on screen - scroll so that one line is
2071 con_backscroll = lines_seen - 1;
2079 Draws the console with the solid background
2080 The typing input line at the bottom should only be drawn if typing is allowed
2083 void Con_DrawConsole (int lines, qbool forcedfullscreen)
2085 float alpha, alpha0;
2088 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
2089 cachepic_t *conbackpic;
2090 unsigned int conbackflags;
2095 if (con_mutex) Thread_LockMutex(con_mutex);
2097 if (con_backscroll < 0)
2100 con_vislines = lines;
2102 r_draw2d_force = true;
2104 // draw the background
2105 alpha0 = forcedfullscreen ? 1.0f : scr_conalpha.value; // always full alpha when not forced fullscreen
2106 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
2108 sx = scr_conscroll_x.value;
2109 sy = scr_conscroll_y.value;
2110 conbackflags = CACHEPICFLAG_FAILONMISSING; // So console is readable when game content is missing
2111 if (sx != 0 || sy != 0)
2112 conbackflags &= CACHEPICFLAG_NOCLAMP;
2113 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", conbackflags) : NULL;
2114 sx *= host.realtime; sy *= host.realtime;
2115 sx -= floor(sx); sy -= floor(sy);
2116 if (Draw_IsPicLoaded(conbackpic))
2117 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2118 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2119 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2120 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2121 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2124 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
2126 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
2128 sx = scr_conscroll2_x.value;
2129 sy = scr_conscroll2_y.value;
2130 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2131 sx *= host.realtime; sy *= host.realtime;
2132 sx -= floor(sx); sy -= floor(sy);
2133 if(Draw_IsPicLoaded(conbackpic))
2134 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2135 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2136 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2137 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2138 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2141 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
2143 sx = scr_conscroll3_x.value;
2144 sy = scr_conscroll3_y.value;
2145 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2146 sx *= host.realtime; sy *= host.realtime;
2147 sx -= floor(sx); sy -= floor(sy);
2148 if(Draw_IsPicLoaded(conbackpic))
2149 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2150 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2151 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2152 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2153 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2156 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);
2162 int count = CON_LINES_COUNT;
2163 float ymax = con_vislines - 2 * con_textsize.value;
2164 float y = ymax + con_textsize.value * con_backscroll;
2165 for (i = 0;i < count && y >= 0;i++)
2166 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
2167 // fix any excessive scrollback for the next frame
2168 if (i >= count && y >= 0)
2170 con_backscroll -= (int)(y / con_textsize.value);
2171 if (con_backscroll < 0)
2176 if(CON_LINES_COUNT > 0)
2178 int i, last, limitlast;
2180 float ymax = con_vislines - 2 * con_textsize.value;
2181 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2182 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2183 y = ymax - con_textsize.value;
2186 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
2191 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
2193 break; // top of console buffer
2195 break; // top of console window
2202 // draw the input prompt, user text, and cursor if desired
2203 Con_DrawInput(true, 0, con_vislines - con_textsize.value * 2, con_textsize.value);
2205 r_draw2d_force = false;
2206 if (con_mutex) Thread_UnlockMutex(con_mutex);
2213 Prints not only map filename, but also
2214 its format (q1/q2/q3/hl) and even its message
2216 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2217 //LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2218 //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
2219 //LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2220 qbool GetMapList (const char *s, char *completedname, int completednamebufferlength)
2224 int i, k, max, p, o, min;
2227 unsigned char buf[1024];
2229 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2230 t = FS_Search(message, 1, true, NULL);
2233 if (t->numfilenames > 1)
2234 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2235 len = (unsigned char *)Z_Malloc(t->numfilenames);
2237 for(max=i=0;i<t->numfilenames;i++)
2239 k = (int)strlen(t->filenames[i]);
2249 for(i=0;i<t->numfilenames;i++)
2251 int lumpofs = 0, lumplen = 0;
2252 char *entities = NULL;
2253 const char *data = NULL;
2255 char entfilename[MAX_QPATH];
2258 dp_strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2260 f = FS_OpenVirtualFile(t->filenames[i], true);
2263 dp_strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2264 memset(buf, 0, 1024);
2265 FS_Read(f, buf, 1024);
2266 if (!memcmp(buf, "IBSP", 4))
2268 p = LittleLong(((int *)buf)[1]);
2269 if (p == Q3BSPVERSION || p == Q3BSPVERSION_LIVE || p == Q3BSPVERSION_IG)
2271 q3dheader_t *header = (q3dheader_t *)buf;
2272 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2273 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2274 dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2276 else if (p == Q2BSPVERSION)
2278 q2dheader_t *header = (q2dheader_t *)buf;
2279 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2280 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2281 dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2284 dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2286 else if (BuffLittleLong(buf) == BSPVERSION)
2288 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2289 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2290 dpsnprintf(desc, sizeof(desc), "BSP29");
2292 else if (BuffLittleLong(buf) == 30)
2294 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2295 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2296 dpsnprintf(desc, sizeof(desc), "BSPHL");
2298 else if (!memcmp(buf, "BSP2", 4))
2300 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2301 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2302 dpsnprintf(desc, sizeof(desc), "BSP2");
2304 else if (!memcmp(buf, "2PSB", 4))
2306 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2307 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2308 dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2310 else if(!memcmp(buf, "VBSP", 4))
2312 hl2dheader_t *header = (hl2dheader_t *)buf;
2313 lumpofs = LittleLong(header->lumps[HL2LUMP_ENTITIES].fileofs);
2314 lumplen = LittleLong(header->lumps[HL2LUMP_ENTITIES].filelen);
2315 dpsnprintf(desc, sizeof(desc), "VBSP%i", LittleLong(((int *)buf)[1]));
2318 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2319 dp_strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2320 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2321 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2322 if (!entities && lumplen >= 10)
2324 FS_Seek(f, lumpofs, SEEK_SET);
2325 entities = (char *)Z_Malloc(lumplen + 1);
2326 FS_Read(f, entities, lumplen);
2330 // if there are entities to parse, a missing message key just
2331 // means there is no title, so clear the message string now
2337 if (!COM_ParseToken_Simple(&data, false, false, true))
2339 if (com_token[0] == '{')
2341 if (com_token[0] == '}')
2343 // skip leading whitespace
2344 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2345 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2346 keyname[l] = com_token[k+l];
2348 if (!COM_ParseToken_Simple(&data, false, false, true))
2350 if (developer_extra.integer)
2351 Con_DPrintf("key: %s %s\n", keyname, com_token);
2352 if (!strcmp(keyname, "message"))
2354 // get the message contents
2355 dp_strlcpy(message, com_token, sizeof(message));
2365 *(t->filenames[i]+len[i]+5) = 0;
2366 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2371 k = *(t->filenames[0]+5+p);
2374 for(i=1;i<t->numfilenames;i++)
2375 if(*(t->filenames[i]+5+p) != k)
2379 if(p > o && completedname && completednamebufferlength > 0)
2381 memset(completedname, 0, completednamebufferlength);
2382 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2392 New function for tab-completion system
2393 Added by EvilTypeGuy
2394 MEGA Thanks to Taniwha
2397 void Con_DisplayList(const char **list)
2399 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2400 const char **walk = list;
2403 len = (int)strlen(*walk);
2411 len = (int)strlen(*list);
2412 if (pos + maxlen >= width) {
2418 for (i = 0; i < (maxlen - len); i++)
2430 // Now it becomes TRICKY :D --blub
2431 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2432 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2433 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2434 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
2435 static int Nicks_matchpos;
2437 // co against <<:BLASTER:>> is true!?
2438 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2442 if(tolower(*a) == tolower(*b))
2456 return (*a < *b) ? -1 : 1;
2460 return (*a < *b) ? -1 : 1;
2464 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2467 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2469 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2470 return Nicks_strncasecmp_nospaces(a, b, a_len);
2471 return strncasecmp(a, b, a_len);
2474 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2476 // ignore non alphanumerics of B
2477 // if A contains a non-alphanumeric, B must contain it as well though!
2480 qbool alnum_a, alnum_b;
2482 if(tolower(*a) == tolower(*b))
2484 if(*a == 0) // end of both strings, they're equal
2491 // not equal, end of one string?
2496 // ignore non alphanumerics
2497 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2498 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2499 if(!alnum_a) // b must contain this
2500 return (*a < *b) ? -1 : 1;
2503 // otherwise, both are alnum, they're just not equal, return the appropriate number
2505 return (*a < *b) ? -1 : 1;
2511 /* Nicks_CompleteCountPossible
2513 Count the number of possible nicks to complete
2515 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qbool isCon)
2517 char name[MAX_SCOREBOARDNAME];
2523 if(!con_nickcompletion.integer)
2526 // changed that to 1
2527 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2530 for(i = 0; i < cl.maxclients; ++i)
2533 if(!cl.scores[p].name[0])
2536 SanitizeString(cl.scores[p].name, name);
2537 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2543 spos = pos - 1; // no need for a minimum of characters :)
2547 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2549 if(!(isCon && spos == 1)) // console start
2555 if(isCon && spos == 0)
2557 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2563 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2564 dp_strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2566 // the sanitized list
2567 dp_strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2570 Nicks_matchpos = match;
2573 Nicks_offset[count] = s - (&line[match]);
2574 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2581 static void Cmd_CompleteNicksPrint(int count)
2584 for(i = 0; i < count; ++i)
2585 Con_Printf("%s\n", Nicks_list[i]);
2588 static void Nicks_CutMatchesNormal(int count)
2590 // cut match 0 down to the longest possible completion
2593 c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2594 for(i = 1; i < count; ++i)
2596 l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2600 for(l = 0; l <= c; ++l)
2601 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2607 Nicks_sanlist[0][c+1] = 0;
2608 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2611 static unsigned int Nicks_strcleanlen(const char *s)
2616 if( (*s >= 'a' && *s <= 'z') ||
2617 (*s >= 'A' && *s <= 'Z') ||
2618 (*s >= '0' && *s <= '9') ||
2626 static void Nicks_CutMatchesAlphaNumeric(int count)
2628 // cut match 0 down to the longest possible completion
2631 char tempstr[sizeof(Nicks_sanlist[0])];
2633 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2635 c = (unsigned int)strlen(Nicks_sanlist[0]);
2636 for(i = 0, l = 0; i < (int)c; ++i)
2638 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2639 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2640 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2642 tempstr[l++] = Nicks_sanlist[0][i];
2647 for(i = 1; i < count; ++i)
2650 b = Nicks_sanlist[i];
2660 if(tolower(*a) == tolower(*b))
2666 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2668 // b is alnum, so cut
2675 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2676 Nicks_CutMatchesNormal(count);
2677 //if(!Nicks_sanlist[0][0])
2678 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2680 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2681 dp_strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2685 static void Nicks_CutMatchesNoSpaces(int count)
2687 // cut match 0 down to the longest possible completion
2690 char tempstr[sizeof(Nicks_sanlist[0])];
2693 c = (unsigned int)strlen(Nicks_sanlist[0]);
2694 for(i = 0, l = 0; i < (int)c; ++i)
2696 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2698 tempstr[l++] = Nicks_sanlist[0][i];
2703 for(i = 1; i < count; ++i)
2706 b = Nicks_sanlist[i];
2716 if(tolower(*a) == tolower(*b))
2730 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2731 Nicks_CutMatchesNormal(count);
2732 //if(!Nicks_sanlist[0][0])
2733 //Con_Printf("TS: %s\n", tempstr);
2734 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2736 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2737 dp_strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2741 static void Nicks_CutMatches(int count)
2743 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2744 Nicks_CutMatchesAlphaNumeric(count);
2745 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2746 Nicks_CutMatchesNoSpaces(count);
2748 Nicks_CutMatchesNormal(count);
2751 static const char **Nicks_CompleteBuildList(int count)
2755 // the list is freed by Con_CompleteCommandLine, so create a char**
2756 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2758 for(; bpos < count; ++bpos)
2759 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2761 Nicks_CutMatches(count);
2769 Restores the previous used color, after the autocompleted name.
2771 static int Nicks_AddLastColor(char *buffer, int pos)
2773 qbool quote_added = false;
2775 int color = STRING_COLOR_DEFAULT + '0';
2776 char r = 0, g = 0, b = 0;
2778 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2780 // we'll have to add a quote :)
2781 buffer[pos++] = '\"';
2785 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2787 // add color when no quote was added, or when flags &4?
2789 for(match = Nicks_matchpos-1; match >= 0; --match)
2791 if(buffer[match] == STRING_COLOR_TAG)
2793 if( isdigit(buffer[match+1]) )
2795 color = buffer[match+1];
2798 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2800 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2802 r = buffer[match+2];
2803 g = buffer[match+3];
2804 b = buffer[match+4];
2813 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2815 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2816 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2819 buffer[pos++] = STRING_COLOR_TAG;
2822 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2828 buffer[pos++] = color;
2834 Con_CompleteCommandLine
2836 New function for tab-completion system
2837 Added by EvilTypeGuy
2838 Thanks to Fett erich@heintz.com
2840 Enhanced to tab-complete map names by [515]
2843 int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console)
2845 const char *text = "";
2847 const char **list[4] = {0, 0, 0, 0};
2850 int c, v, a, i, cmd_len, pos, k;
2851 int n; // nicks --blub
2852 const char *space, *patterns;
2856 int linestart, linepos;
2857 unsigned int linesize;
2861 linepos = key_linepos;
2862 linesize = sizeof(key_line);
2868 linepos = chat_bufferpos;
2869 linesize = sizeof(chat_buffer);
2873 //find what we want to complete
2875 while(--pos >= linestart)
2878 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2884 dp_strlcpy(s2, line + linepos, sizeof(s2)); //save chars after cursor
2885 line[linepos] = 0; //hide them
2887 c = v = a = n = cmd_len = 0;
2891 space = strchr(line + 1, ' ');
2892 if(space && pos == (space - line) + 1)
2894 // adding 1 to line drops the leading ]
2895 dp_ustr2stp(command, sizeof(command), line + 1, space - (line + 1));
2897 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?
2898 if(patterns && !*patterns)
2899 patterns = NULL; // get rid of the empty string
2901 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2905 if (GetMapList(s, t, sizeof(t)))
2907 // first move the cursor
2908 linepos += (int)strlen(t) - (int)strlen(s);
2910 // and now do the actual work
2912 dp_strlcat(line, t, MAX_INPUTLINE);
2913 dp_strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2915 // and fix the cursor
2916 if(linepos > (int) strlen(line))
2917 linepos = (int) strlen(line);
2926 stringlist_t resultbuf, dirbuf;
2929 // // store completion patterns (space separated) for command foo in con_completion_foo
2930 // set con_completion_foo "foodata/*.foodefault *.foo"
2933 // Note: patterns with slash are always treated as absolute
2934 // patterns; patterns without slash search in the innermost
2935 // directory the user specified. There is no way to "complete into"
2936 // a directory as of now, as directories seem to be unknown to the
2940 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2941 // set con_completion_playdemo "*.dem"
2942 // set con_completion_play "*.wav *.ogg"
2944 // TODO somehow add support for directories; these shall complete
2945 // to their name + an appended slash.
2947 stringlistinit(&resultbuf);
2948 stringlistinit(&dirbuf);
2949 while(COM_ParseToken_Simple(&patterns, false, false, true))
2952 if(strchr(com_token, '/'))
2954 search = FS_Search(com_token, true, true, NULL);
2958 const char *slash = strrchr(s, '/');
2961 dp_strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2962 dp_strlcat(t, com_token, sizeof(t));
2963 search = FS_Search(t, true, true, NULL);
2966 search = FS_Search(com_token, true, true, NULL);
2970 for(i = 0; i < search->numfilenames; ++i)
2971 if(!strncmp(search->filenames[i], s, strlen(s)))
2972 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2973 stringlistappend(&resultbuf, search->filenames[i]);
2974 FS_FreeSearch(search);
2978 // In any case, add directory names
2981 const char *slash = strrchr(s, '/');
2984 dp_strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2985 dp_strlcat(t, "*", sizeof(t));
2986 search = FS_Search(t, true, true, NULL);
2989 search = FS_Search("*", true, true, NULL);
2992 for(i = 0; i < search->numfilenames; ++i)
2993 if(!strncmp(search->filenames[i], s, strlen(s)))
2994 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2995 stringlistappend(&dirbuf, search->filenames[i]);
2996 FS_FreeSearch(search);
3000 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
3003 unsigned int matchchars;
3004 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
3006 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
3009 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
3011 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
3015 stringlistsort(&resultbuf, true); // dirbuf is already sorted
3016 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
3017 for(i = 0; i < dirbuf.numstrings; ++i)
3019 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
3021 for(i = 0; i < resultbuf.numstrings; ++i)
3023 Con_Printf("%s\n", resultbuf.strings[i]);
3025 matchchars = sizeof(t) - 1;
3026 if(resultbuf.numstrings > 0)
3028 p = resultbuf.strings[0];
3029 q = resultbuf.strings[resultbuf.numstrings - 1];
3030 for(; *p && *p == *q; ++p, ++q);
3031 matchchars = (unsigned int)(p - resultbuf.strings[0]);
3033 if(dirbuf.numstrings > 0)
3035 p = dirbuf.strings[0];
3036 q = dirbuf.strings[dirbuf.numstrings - 1];
3037 for(; *p && *p == *q; ++p, ++q);
3038 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
3040 // now p points to the first non-equal character, or to the end
3041 // of resultbuf.strings[0]. We want to append the characters
3042 // from resultbuf.strings[0] to (not including) p as these are
3043 // the unique prefix
3044 dp_strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
3047 // first move the cursor
3048 linepos += (int)strlen(t) - (int)strlen(s);
3050 // and now do the actual work
3052 dp_strlcat(line, t, MAX_INPUTLINE);
3053 dp_strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
3055 // and fix the cursor
3056 if(linepos > (int) strlen(line))
3057 linepos = (int) strlen(line);
3059 stringlistfreecontents(&resultbuf);
3060 stringlistfreecontents(&dirbuf);
3062 return linepos; // bail out, when we complete for a command that wants a file name
3067 // Count number of possible matches and print them
3068 c = Cmd_CompleteCountPossible(cmd, s);
3071 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
3072 Cmd_CompleteCommandPrint(cmd, s);
3074 v = Cvar_CompleteCountPossible(cmd->cvars, s, CF_CLIENT | CF_SERVER);
3077 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
3078 Cvar_CompleteCvarPrint(cmd->cvars, s, CF_CLIENT | CF_SERVER);
3080 a = Cmd_CompleteAliasCountPossible(cmd, s);
3083 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
3084 Cmd_CompleteAliasPrint(cmd, s);
3088 n = Nicks_CompleteCountPossible(line, linepos, s, is_console);
3091 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
3092 Cmd_CompleteNicksPrint(n);
3095 if (!(c + v + a + n)) // No possible matches
3098 dp_strlcpy(&line[linepos], s2, linesize - linepos);
3103 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
3105 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
3107 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
3111 text = *(list[3] = Nicks_CompleteBuildList(n));
3113 text = *(Nicks_CompleteBuildList(n));
3116 for (cmd_len = (int)strlen(s);;cmd_len++)
3119 for (i = 0; i < 3; i++)
3121 for (l = list[i];*l;l++)
3122 if ((*l)[cmd_len] != text[cmd_len])
3124 // all possible matches share this character, so we continue...
3127 // if all matches ended at the same position, stop
3128 // (this means there is only one match)
3134 // prevent a buffer overrun by limiting cmd_len according to remaining space
3135 cmd_len = min(cmd_len, (int)linesize - 1 - pos);
3139 memcpy(&line[linepos], text, cmd_len);
3141 // if there is only one match, add a space after it
3142 if (c + v + a + n == 1 && linepos < (int)linesize - 1)
3145 { // was a nick, might have an offset, and needs colors ;) --blub
3146 linepos = pos - Nicks_offset[0];
3147 cmd_len = (int)strlen(Nicks_list[0]);
3148 cmd_len = min(cmd_len, (int)linesize - 3 - pos);
3150 memcpy(&line[linepos] , Nicks_list[0], cmd_len);
3152 if(linepos < (int)(linesize - 7)) // space for color code (^[0-9] or ^xrgb), space and \0
3153 linepos = Nicks_AddLastColor(line, linepos);
3155 line[linepos++] = ' ';
3159 // use strlcat to avoid a buffer overrun
3161 dp_strlcat(line, s2, linesize);
3166 // free the command, cvar, and alias lists
3167 for (i = 0; i < 4; i++)
3169 Mem_Free((void *)list[i]);