2 Copyright (C) 1996-1997 Id Software, Inc.
3 Copyright (C) 2000-2020 DarkPlaces contributors
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 See the GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #if !defined(WIN32) || defined(__MINGW32__)
34 float con_cursorspeed = 4;
36 // lines up from bottom to display
40 void *con_mutex = NULL;
42 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
43 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
44 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
46 cvar_t con_notifytime = {CF_CLIENT | CF_ARCHIVE, "con_notifytime","3", "how long notify lines last, in seconds"};
47 cvar_t con_notify = {CF_CLIENT | CF_ARCHIVE, "con_notify","4", "how many notify lines to show"};
48 cvar_t con_notifyalign = {CF_CLIENT | CF_ARCHIVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
50 cvar_t con_chattime = {CF_CLIENT | CF_ARCHIVE, "con_chattime","30", "how long chat lines last, in seconds"};
51 cvar_t con_chat = {CF_CLIENT | CF_ARCHIVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
52 cvar_t con_chatpos = {CF_CLIENT | CF_ARCHIVE, "con_chatpos","0", "where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top)"};
53 cvar_t con_chatrect = {CF_CLIENT | CF_ARCHIVE, "con_chatrect","0", "use con_chatrect_x and _y to position con_notify and con_chat freely instead of con_chatpos"};
54 cvar_t con_chatrect_x = {CF_CLIENT | CF_ARCHIVE, "con_chatrect_x","", "where to put chat, relative x coordinate of left edge on screen (use con_chatwidth for width)"};
55 cvar_t con_chatrect_y = {CF_CLIENT | CF_ARCHIVE, "con_chatrect_y","", "where to put chat, relative y coordinate of top edge on screen (use con_chat for line count)"};
56 cvar_t con_chatwidth = {CF_CLIENT | CF_ARCHIVE, "con_chatwidth","1.0", "relative chat window width"};
57 cvar_t con_textsize = {CF_CLIENT | CF_ARCHIVE, "con_textsize","8", "console text size in virtual 2D pixels"};
58 cvar_t con_notifysize = {CF_CLIENT | CF_ARCHIVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
59 cvar_t con_chatsize = {CF_CLIENT | CF_ARCHIVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
60 cvar_t con_chatsound = {CF_CLIENT | CF_ARCHIVE, "con_chatsound","1", "enables chat sound to play on message"};
61 cvar_t con_chatsound_file = {CF_CLIENT, "con_chatsound_file","sound/misc/talk.wav", "The sound to play for chat messages"};
62 cvar_t con_chatsound_team_file = {CF_CLIENT, "con_chatsound_team_file","sound/misc/talk2.wav", "The sound to play for team chat messages"};
63 cvar_t con_chatsound_team_mask = {CF_CLIENT, "con_chatsound_team_mask","40","Magic ASCII code that denotes a team chat message"};
65 cvar_t sys_specialcharactertranslation = {CF_CLIENT | CF_SERVER, "sys_specialcharactertranslation", "1", "terminal console conchars to ASCII translation (set to 0 if your conchars.tga is for an 8bit character set or if you want raw output)"};
67 cvar_t sys_colortranslation = {CF_CLIENT | CF_SERVER, "sys_colortranslation", "0", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
69 cvar_t sys_colortranslation = {CF_CLIENT | CF_SERVER, "sys_colortranslation", "1", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
73 cvar_t con_nickcompletion = {CF_CLIENT | CF_ARCHIVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
74 cvar_t con_nickcompletion_flags = {CF_CLIENT | CF_ARCHIVE, "con_nickcompletion_flags", "11", "Bitfield: "
75 "0: add nothing after completion. "
76 "1: add the last color after completion. "
77 "2: add a quote when starting a quote instead of the color. "
78 "4: will replace 1, will force color, even after a quote. "
79 "8: ignore non-alphanumerics. "
80 "16: ignore spaces. "};
81 #define NICKS_ADD_COLOR 1
82 #define NICKS_ADD_QUOTE 2
83 #define NICKS_FORCE_COLOR 4
84 #define NICKS_ALPHANUMERICS_ONLY 8
85 #define NICKS_NO_SPACES 16
87 cvar_t con_completion_playdemo = {CF_CLIENT | CF_ARCHIVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
88 cvar_t con_completion_timedemo = {CF_CLIENT | CF_ARCHIVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
89 cvar_t con_completion_exec = {CF_CLIENT | CF_ARCHIVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
91 cvar_t condump_stripcolors = {CF_CLIENT | CF_SERVER| CF_ARCHIVE, "condump_stripcolors", "0", "strip color codes from console dumps"};
93 cvar_t rcon_password = {CF_CLIENT | CF_SERVER | CF_PRIVATE, "rcon_password", "", "password to authenticate rcon commands; NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"};
94 cvar_t rcon_secure = {CF_CLIENT | CF_SERVER, "rcon_secure", "0", "force secure rcon authentication (1 = time based, 2 = challenge based); NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password"};
95 cvar_t rcon_secure_challengetimeout = {CF_CLIENT, "rcon_secure_challengetimeout", "5", "challenge-based secure rcon: time out requests if no challenge came within this time interval"};
96 cvar_t rcon_address = {CF_CLIENT, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"};
101 qbool con_initialized;
103 // used for server replies to rcon command
104 lhnetsocket_t *rcon_redirect_sock = NULL;
105 lhnetaddress_t *rcon_redirect_dest = NULL;
106 int rcon_redirect_bufferpos = 0;
107 char rcon_redirect_buffer[1400];
108 qbool rcon_redirect_proquakeprotocol = false;
110 // generic functions for console buffers
112 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
115 buf->textsize = textsize;
116 buf->text = (char *) Mem_Alloc(mempool, textsize);
117 buf->maxlines = maxlines;
118 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
119 buf->lines_first = 0;
120 buf->lines_count = 0;
123 /*! The translation table between the graphical font and plain ASCII --KB */
124 static char qfont_table[256] = {
125 '\0', '#', '#', '#', '#', '.', '#', '#',
126 '#', 9, 10, '#', ' ', 13, '.', '.',
127 '[', ']', '0', '1', '2', '3', '4', '5',
128 '6', '7', '8', '9', '.', '<', '=', '>',
129 ' ', '!', '"', '#', '$', '%', '&', '\'',
130 '(', ')', '*', '+', ',', '-', '.', '/',
131 '0', '1', '2', '3', '4', '5', '6', '7',
132 '8', '9', ':', ';', '<', '=', '>', '?',
133 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
134 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
135 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
136 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
137 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
138 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
139 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
140 'x', 'y', 'z', '{', '|', '}', '~', '<',
142 '<', '=', '>', '#', '#', '.', '#', '#',
143 '#', '#', ' ', '#', ' ', '>', '.', '.',
144 '[', ']', '0', '1', '2', '3', '4', '5',
145 '6', '7', '8', '9', '.', '<', '=', '>',
146 ' ', '!', '"', '#', '$', '%', '&', '\'',
147 '(', ')', '*', '+', ',', '-', '.', '/',
148 '0', '1', '2', '3', '4', '5', '6', '7',
149 '8', '9', ':', ';', '<', '=', '>', '?',
150 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
151 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
152 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
153 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
154 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
155 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
156 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
157 'x', 'y', 'z', '{', '|', '}', '~', '<'
161 SanitizeString strips color tags from the string in
162 and writes the result on string out
164 static void SanitizeString(char *in, char *out)
168 if(*in == STRING_COLOR_TAG)
173 out[0] = STRING_COLOR_TAG;
177 else if (*in >= '0' && *in <= '9') // ^[0-9] found
184 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
187 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
189 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
196 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
201 else if (*in != STRING_COLOR_TAG)
204 *out = qfont_table[*(unsigned char*)in];
216 void ConBuffer_Clear (conbuffer_t *buf)
218 buf->lines_count = 0;
226 void ConBuffer_Shutdown(conbuffer_t *buf)
232 Mem_Free(buf->lines);
241 Notifies the console code about the current time
242 (and shifts back times of other entries when the time
246 void ConBuffer_FixTimes(conbuffer_t *buf)
249 if(buf->lines_count >= 1)
251 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
254 for(i = 0; i < buf->lines_count; ++i)
255 CONBUFFER_LINES(buf, i).addtime += diff;
264 Deletes the first line from the console history.
267 void ConBuffer_DeleteLine(conbuffer_t *buf)
269 if(buf->lines_count == 0)
272 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
277 ConBuffer_DeleteLastLine
279 Deletes the last line from the console history.
282 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
284 if(buf->lines_count == 0)
293 Checks if there is space for a line of the given length, and if yes, returns a
294 pointer to the start of such a space, and NULL otherwise.
297 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
299 if(len > buf->textsize)
301 if(buf->lines_count == 0)
305 char *firstline_start = buf->lines[buf->lines_first].start;
306 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
307 // the buffer is cyclic, so we first have two cases...
308 if(firstline_start < lastline_onepastend) // buffer is contiguous
311 if(len <= buf->text + buf->textsize - lastline_onepastend)
312 return lastline_onepastend;
314 else if(len <= firstline_start - buf->text)
319 else // buffer has a contiguous hole
321 if(len <= firstline_start - lastline_onepastend)
322 return lastline_onepastend;
333 Appends a given string as a new line to the console.
336 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
341 // developer_memory 1 during shutdown prints while conbuffer_t is being freed
345 ConBuffer_FixTimes(buf);
347 if(len >= buf->textsize)
350 // only display end of line.
351 line += len - buf->textsize + 1;
352 len = buf->textsize - 1;
354 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
355 ConBuffer_DeleteLine(buf);
356 memcpy(putpos, line, len);
360 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
362 p = &CONBUFFER_LINES_LAST(buf);
365 p->addtime = cl.time;
367 p->height = -1; // calculate when needed
370 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
374 start = buf->lines_count;
375 for(i = start - 1; i >= 0; --i)
377 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
379 if((l->mask & mask_must) != mask_must)
381 if(l->mask & mask_mustnot)
390 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
392 static char copybuf[MAX_INPUTLINE]; // client only
393 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
394 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
395 strlcpy(copybuf, l->start, sz);
400 ==============================================================================
404 ==============================================================================
409 cvar_t log_file = {CF_CLIENT | CF_SERVER, "log_file", "", "filename to log messages to"};
410 cvar_t log_file_stripcolors = {CF_CLIENT | CF_SERVER, "log_file_stripcolors", "0", "strip color codes from log messages"};
411 cvar_t log_dest_udp = {CF_CLIENT | CF_SERVER, "log_dest_udp", "", "UDP address to log messages to (in QW rcon compatible format); multiple destinations can be separated by spaces; DO NOT SPECIFY DNS NAMES HERE"};
412 char log_dest_buffer[1400]; // UDP packet
413 size_t log_dest_buffer_pos;
414 unsigned int log_dest_buffer_appending;
415 char crt_log_file [MAX_OSPATH] = "";
416 qfile_t* logfile = NULL;
418 unsigned char* logqueue = NULL;
420 size_t logq_size = 0;
422 void Log_ConPrint (const char *msg);
424 static void Log_DestBuffer_Init(void)
426 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
427 log_dest_buffer_pos = 5;
430 static void Log_DestBuffer_Flush_NoLock(void)
432 lhnetaddress_t log_dest_addr;
433 lhnetsocket_t *log_dest_socket;
434 const char *s = log_dest_udp.string;
435 qbool have_opened_temp_sockets = false;
436 if(s) if(log_dest_buffer_pos > 5)
438 ++log_dest_buffer_appending;
439 log_dest_buffer[log_dest_buffer_pos++] = 0;
441 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
443 have_opened_temp_sockets = true;
444 NetConn_OpenServerPorts(true);
447 while(COM_ParseToken_Console(&s))
448 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
450 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
452 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
454 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
457 if(have_opened_temp_sockets)
458 NetConn_CloseServerPorts();
459 --log_dest_buffer_appending;
461 log_dest_buffer_pos = 0;
469 void Log_DestBuffer_Flush(void)
472 Thread_LockMutex(con_mutex);
473 Log_DestBuffer_Flush_NoLock();
475 Thread_UnlockMutex(con_mutex);
478 static const char* Log_Timestamp (const char *desc)
480 static char timestamp [128]; // init/shutdown only
487 char timestring [64];
489 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
492 localtime_s (&crt_tm, &crt_time);
493 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
495 crt_tm = localtime (&crt_time);
496 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
500 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
502 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
507 static void Log_Open (void)
509 if (logfile != NULL || log_file.string[0] == '\0')
512 logfile = FS_OpenRealFile(log_file.string, "a", false);
515 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
516 FS_Print (logfile, Log_Timestamp ("Log started"));
525 void Log_Close (void)
527 qfile_t* l = logfile;
532 FS_Print (l, Log_Timestamp ("Log stopped"));
537 crt_log_file[0] = '\0';
546 void Log_Start (void)
552 // Dump the contents of the log queue into the log file and free it
553 if (logqueue != NULL)
555 unsigned char *temp = logqueue;
560 FS_Write (logfile, temp, logq_ind);
561 if(*log_dest_udp.string)
563 for(pos = 0; pos < logq_ind; )
565 if(log_dest_buffer_pos == 0)
566 Log_DestBuffer_Init();
567 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
568 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
569 log_dest_buffer_pos += n;
570 Log_DestBuffer_Flush_NoLock();
588 void Log_ConPrint (const char *msg)
590 static qbool inprogress = false;
592 // don't allow feedback loops with memory error reports
597 // Until the host is completely initialized, we maintain a log queue
598 // to store the messages, since the log can't be started before
599 if (logqueue != NULL)
601 size_t remain = logq_size - logq_ind;
602 size_t len = strlen (msg);
604 // If we need to enlarge the log queue
607 size_t factor = ((logq_ind + len) / logq_size) + 1;
608 unsigned char* newqueue;
611 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
612 memcpy (newqueue, logqueue, logq_ind);
615 remain = logq_size - logq_ind;
617 memcpy (&logqueue[logq_ind], msg, len);
624 // Check if log_file has changed
625 if (strcmp (crt_log_file, log_file.string) != 0)
631 // If a log file is available
634 if (log_file_stripcolors.integer)
637 size_t len = strlen(msg);
638 char* sanitizedmsg = (char*)Mem_Alloc(tempmempool, len + 1);
639 memcpy (sanitizedmsg, msg, len);
640 SanitizeString(sanitizedmsg, sanitizedmsg); // SanitizeString's in pointer is always ahead of the out pointer, so this should work.
641 FS_Print (logfile, sanitizedmsg);
642 Mem_Free(sanitizedmsg);
646 FS_Print (logfile, msg);
659 void Log_Printf (const char *logfilename, const char *fmt, ...)
663 file = FS_OpenRealFile(logfilename, "a", true);
668 va_start (argptr, fmt);
669 FS_VPrintf (file, fmt, argptr);
678 ==============================================================================
682 ==============================================================================
690 void Con_ToggleConsole_f(cmd_state_t *cmd)
692 if (Sys_CheckParm ("-noconsole"))
693 if (!(key_consoleactive & KEY_CONSOLEACTIVE_USER))
694 return; // only allow the key bind to turn off console
696 // toggle the 'user wants console' bit
697 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
706 void Con_ClearNotify (void)
709 for(i = 0; i < CON_LINES_COUNT; ++i)
710 if(!(CON_LINES(i).mask & CON_MASK_CHAT))
711 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
720 static void Con_MessageMode_f(cmd_state_t *cmd)
722 key_dest = key_message;
723 chat_mode = 0; // "say"
724 if(Cmd_Argc(cmd) > 1)
726 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
727 chat_bufferpos = (unsigned int)strlen(chat_buffer);
737 static void Con_MessageMode2_f(cmd_state_t *cmd)
739 key_dest = key_message;
740 chat_mode = 1; // "say_team"
741 if(Cmd_Argc(cmd) > 1)
743 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
744 chat_bufferpos = (unsigned int)strlen(chat_buffer);
753 static void Con_CommandMode_f(cmd_state_t *cmd)
755 key_dest = key_message;
756 if(Cmd_Argc(cmd) > 1)
758 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
759 chat_bufferpos = (unsigned int)strlen(chat_buffer);
761 chat_mode = -1; // command
769 void Con_CheckResize (void)
774 f = bound(1, con_textsize.value, 128);
775 if(f != con_textsize.value)
776 Cvar_SetValueQuick(&con_textsize, f);
777 width = (int)floor(vid_conwidth.value / con_textsize.value);
778 width = bound(1, width, con.textsize/4);
779 // FIXME uses con in a non abstracted way
781 if (width == con_linewidth)
784 con_linewidth = width;
786 for(i = 0; i < CON_LINES_COUNT; ++i)
787 CON_LINES(i).height = -1; // recalculate when next needed
793 //[515]: the simplest command ever
794 //LadyHavoc: not so simple after I made it print usage...
795 static void Con_Maps_f(cmd_state_t *cmd)
797 if (Cmd_Argc(cmd) > 2)
799 Con_Printf("usage: maps [mapnameprefix]\n");
802 else if (Cmd_Argc(cmd) == 2)
803 GetMapList(Cmd_Argv(cmd, 1), NULL, 0);
805 GetMapList("", NULL, 0);
808 static void Con_ConDump_f(cmd_state_t *cmd)
812 if (Cmd_Argc(cmd) != 2)
814 Con_Printf("usage: condump <filename>\n");
817 file = FS_OpenRealFile(Cmd_Argv(cmd, 1), "w", false);
820 Con_Printf(CON_ERROR "condump: unable to write file \"%s\"\n", Cmd_Argv(cmd, 1));
823 if (con_mutex) Thread_LockMutex(con_mutex);
824 for(i = 0; i < CON_LINES_COUNT; ++i)
826 if (condump_stripcolors.integer)
829 size_t len = CON_LINES(i).len;
830 char* sanitizedmsg = (char*)Mem_Alloc(tempmempool, len + 1);
831 memcpy (sanitizedmsg, CON_LINES(i).start, len);
832 SanitizeString(sanitizedmsg, sanitizedmsg); // SanitizeString's in pointer is always ahead of the out pointer, so this should work.
833 FS_Write(file, sanitizedmsg, strlen(sanitizedmsg));
834 Mem_Free(sanitizedmsg);
838 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
840 FS_Write(file, "\n", 1);
842 if (con_mutex) Thread_UnlockMutex(con_mutex);
846 void Con_Clear_f(cmd_state_t *cmd)
848 if (con_mutex) Thread_LockMutex(con_mutex);
849 ConBuffer_Clear(&con);
850 if (con_mutex) Thread_UnlockMutex(con_mutex);
853 static void Con_RCon_ClearPassword_c(cvar_t *var)
855 // whenever rcon_secure is changed to 0, clear rcon_password for
856 // security reasons (prevents a send-rcon-password-as-plaintext
857 // attack based on NQ protocol session takeover and svc_stufftext)
858 if(var->integer <= 0)
859 Cvar_SetQuick(&rcon_password, "");
870 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
871 if (Thread_HasThreads())
872 con_mutex = Thread_CreateMutex();
874 // Allocate a log queue, this will be freed after configs are parsed
875 logq_size = MAX_INPUTLINE;
876 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
879 Cvar_RegisterVariable (&sys_colortranslation);
880 Cvar_RegisterVariable (&sys_specialcharactertranslation);
882 Cvar_RegisterVariable (&log_file);
883 Cvar_RegisterVariable (&log_file_stripcolors);
884 Cvar_RegisterVariable (&log_dest_udp);
886 // support for the classic Quake option
887 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
888 if (Sys_CheckParm ("-condebug") != 0)
889 Cvar_SetQuick (&log_file, "qconsole.log");
891 // register our cvars
892 Cvar_RegisterVariable (&con_chat);
893 Cvar_RegisterVariable (&con_chatpos);
894 Cvar_RegisterVariable (&con_chatrect_x);
895 Cvar_RegisterVariable (&con_chatrect_y);
896 Cvar_RegisterVariable (&con_chatrect);
897 Cvar_RegisterVariable (&con_chatsize);
898 Cvar_RegisterVariable (&con_chattime);
899 Cvar_RegisterVariable (&con_chatwidth);
900 Cvar_RegisterVariable (&con_notify);
901 Cvar_RegisterVariable (&con_notifyalign);
902 Cvar_RegisterVariable (&con_notifysize);
903 Cvar_RegisterVariable (&con_notifytime);
904 Cvar_RegisterVariable (&con_textsize);
905 Cvar_RegisterVariable (&con_chatsound);
906 Cvar_RegisterVariable (&con_chatsound_file);
907 Cvar_RegisterVariable (&con_chatsound_team_file);
908 Cvar_RegisterVariable (&con_chatsound_team_mask);
911 Cvar_RegisterVariable (&con_nickcompletion);
912 Cvar_RegisterVariable (&con_nickcompletion_flags);
914 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
915 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
916 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
918 Cvar_RegisterVariable (&condump_stripcolors);
920 Cvar_RegisterVariable(&rcon_address);
921 Cvar_RegisterVariable(&rcon_secure);
922 Cvar_RegisterCallback(&rcon_secure, Con_RCon_ClearPassword_c);
923 Cvar_RegisterVariable(&rcon_secure_challengetimeout);
924 Cvar_RegisterVariable(&rcon_password);
926 // register our commands
927 Cmd_AddCommand(CF_CLIENT, "toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
928 Cmd_AddCommand(CF_CLIENT, "messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
929 Cmd_AddCommand(CF_CLIENT, "messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
930 Cmd_AddCommand(CF_CLIENT, "commandmode", Con_CommandMode_f, "input a console command");
931 Cmd_AddCommand(CF_SHARED, "clear", Con_Clear_f, "clear console history");
932 Cmd_AddCommand(CF_SHARED, "maps", Con_Maps_f, "list information about available maps");
933 Cmd_AddCommand(CF_SHARED, "condump", Con_ConDump_f, "output console history to a file (see also log_file)");
935 con_initialized = true;
937 Con_Print("Console initialized.\n");
940 void Con_Shutdown (void)
942 if (con_mutex) Thread_LockMutex(con_mutex);
943 ConBuffer_Shutdown(&con);
944 if (con_mutex) Thread_UnlockMutex(con_mutex);
945 if (con_mutex) Thread_DestroyMutex(con_mutex);con_mutex = NULL;
952 Handles cursor positioning, line wrapping, etc
953 All console printing must go through this in order to be displayed
954 If no console is visible, the notify window will pop up.
957 static void Con_PrintToHistory(const char *txt, int mask)
960 // \n goes to next line
961 // \r deletes current line and makes a new one
963 static int cr_pending = 0;
964 static char buf[CON_TEXTSIZE]; // con_mutex
965 static int bufpos = 0;
967 if(!con.text) // FIXME uses a non-abstracted property of con
974 ConBuffer_DeleteLastLine(&con);
982 ConBuffer_AddLine(&con, buf, bufpos, mask);
987 ConBuffer_AddLine(&con, buf, bufpos, mask);
991 buf[bufpos++] = *txt;
992 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
994 ConBuffer_AddLine(&con, buf, bufpos, mask);
1002 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qbool proquakeprotocol)
1004 rcon_redirect_sock = sock;
1005 rcon_redirect_dest = dest;
1006 rcon_redirect_proquakeprotocol = proquakeprotocol;
1007 if (rcon_redirect_proquakeprotocol)
1009 // reserve space for the packet header
1010 rcon_redirect_buffer[0] = 0;
1011 rcon_redirect_buffer[1] = 0;
1012 rcon_redirect_buffer[2] = 0;
1013 rcon_redirect_buffer[3] = 0;
1014 // this is a reply to a CCREQ_RCON
1015 rcon_redirect_buffer[4] = (unsigned char)CCREP_RCON;
1018 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1019 rcon_redirect_bufferpos = 5;
1022 static void Con_Rcon_Redirect_Flush(void)
1024 if(rcon_redirect_sock)
1026 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
1027 if (rcon_redirect_proquakeprotocol)
1029 // update the length in the packet header
1030 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
1032 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
1034 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1035 rcon_redirect_bufferpos = 5;
1036 rcon_redirect_proquakeprotocol = false;
1039 void Con_Rcon_Redirect_End(void)
1041 Con_Rcon_Redirect_Flush();
1042 rcon_redirect_dest = NULL;
1043 rcon_redirect_sock = NULL;
1046 void Con_Rcon_Redirect_Abort(void)
1048 rcon_redirect_dest = NULL;
1049 rcon_redirect_sock = NULL;
1057 /// Adds a character to the rcon buffer.
1058 static void Con_Rcon_AddChar(int c)
1060 if(log_dest_buffer_appending)
1062 ++log_dest_buffer_appending;
1064 // if this print is in response to an rcon command, add the character
1065 // to the rcon redirect buffer
1067 if (rcon_redirect_dest)
1069 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
1070 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
1071 Con_Rcon_Redirect_Flush();
1073 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
1075 if(log_dest_buffer_pos == 0)
1076 Log_DestBuffer_Init();
1077 log_dest_buffer[log_dest_buffer_pos++] = c;
1078 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
1079 Log_DestBuffer_Flush_NoLock();
1082 log_dest_buffer_pos = 0;
1084 --log_dest_buffer_appending;
1088 * Convert an RGB color to its nearest quake color.
1089 * I'll cheat on this a bit by translating the colors to HSV first,
1090 * S and V decide if it's black or white, otherwise, H will decide the
1092 * @param _r Red (0-255)
1093 * @param _g Green (0-255)
1094 * @param _b Blue (0-255)
1095 * @return A quake color character.
1097 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
1099 float r = ((float)_r)/255.0;
1100 float g = ((float)_g)/255.0;
1101 float b = ((float)_b)/255.0;
1102 float min = min(r, min(g, b));
1103 float max = max(r, max(g, b));
1105 int h; ///< Hue angle [0,360]
1106 float s; ///< Saturation [0,1]
1107 float v = max; ///< In HSV v == max [0,1]
1112 s = 1.0 - (min/max);
1114 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1117 // If the value is less than half, return a black color code.
1118 // Otherwise return a white one.
1124 // Let's get the hue angle to define some colors:
1128 h = (int)(60.0 * (g-b)/(max-min))%360;
1130 h = (int)(60.0 * (b-r)/(max-min) + 120);
1131 else // if(max == b) redundant check
1132 h = (int)(60.0 * (r-g)/(max-min) + 240);
1134 if(h < 36) // *red* to orange
1136 else if(h < 80) // orange over *yellow* to evilish-bright-green
1138 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1140 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1142 else if(h < 270) // darkish blue over *dark blue* to cool purple
1144 else if(h < 330) // cool purple over *purple* to ugly swiny red
1146 else // ugly red to red closes the circly
1155 extern cvar_t timestamps;
1156 extern cvar_t timeformat;
1157 extern qbool sys_nostdout;
1158 void Con_MaskPrint(int additionalmask, const char *msg)
1160 static int mask = 0;
1161 static int index = 0;
1162 static char line[MAX_INPUTLINE];
1165 Thread_LockMutex(con_mutex);
1169 Con_Rcon_AddChar(*msg);
1170 // if this is the beginning of a new line, print timestamp
1173 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1175 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1176 line[index++] = STRING_COLOR_TAG;
1177 // assert( STRING_COLOR_DEFAULT < 10 )
1178 line[index++] = STRING_COLOR_DEFAULT + '0';
1179 // special color codes for chat messages must always come first
1180 // for Con_PrintToHistory to work properly
1181 if (*msg == 1 || *msg == 2 || *msg == 3)
1186 if (con_chatsound.value)
1188 if(msg[1] == con_chatsound_team_mask.integer && cl.foundteamchatsound)
1189 S_LocalSound (con_chatsound_team_file.string);
1191 S_LocalSound (con_chatsound_file.string);
1194 // Send to chatbox for say/tell (1) and messages (3)
1195 // 3 is just so that a message can be sent to the chatbox without a sound.
1196 if (*msg == 1 || *msg == 3)
1197 mask = CON_MASK_CHAT;
1199 line[index++] = STRING_COLOR_TAG;
1200 line[index++] = '3';
1202 Con_Rcon_AddChar(*msg);
1205 for (;*timestamp;index++, timestamp++)
1206 if (index < (int)sizeof(line) - 2)
1207 line[index] = *timestamp;
1209 mask |= additionalmask;
1211 // append the character
1212 line[index++] = *msg;
1213 // if this is a newline character, we have a complete line to print
1214 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1216 // terminate the line
1220 // send to scrollable buffer
1221 if (con_initialized && cls.state != ca_dedicated)
1223 Con_PrintToHistory(line, mask);
1225 // send to terminal or dedicated server window
1227 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1229 if(sys_specialcharactertranslation.integer)
1236 int ch = u8_getchar(p, &q);
1237 if(ch >= 0xE000 && ch <= 0xE0FF && ((unsigned char) qfont_table[ch - 0xE000]) >= 0x20)
1239 *p = qfont_table[ch - 0xE000];
1241 memmove(p+1, q, strlen(q)+1);
1249 if(sys_colortranslation.integer == 1) // ANSI
1251 static char printline[MAX_INPUTLINE * 4 + 3];
1252 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1253 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1258 for(in = line, out = printline; *in; ++in)
1262 case STRING_COLOR_TAG:
1263 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1265 char r = tolower(in[2]);
1266 char g = tolower(in[3]);
1267 char b = tolower(in[4]);
1268 // it's a hex digit already, so the else part needs no check --blub
1269 if(isdigit(r)) r -= '0';
1271 if(isdigit(g)) g -= '0';
1273 if(isdigit(b)) b -= '0';
1276 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1277 in += 3; // 3 only, the switch down there does the fourth
1284 case STRING_COLOR_TAG:
1286 *out++ = STRING_COLOR_TAG;
1292 if(lastcolor == 0) break; else lastcolor = 0;
1293 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1298 if(lastcolor == 1) break; else lastcolor = 1;
1299 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1304 if(lastcolor == 2) break; else lastcolor = 2;
1305 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1310 if(lastcolor == 3) break; else lastcolor = 3;
1311 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1316 if(lastcolor == 4) break; else lastcolor = 4;
1317 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1322 if(lastcolor == 5) break; else lastcolor = 5;
1323 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1328 if(lastcolor == 6) break; else lastcolor = 6;
1329 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1334 // bold normal color
1336 if(lastcolor == 8) break; else lastcolor = 8;
1337 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1340 *out++ = STRING_COLOR_TAG;
1347 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1364 Sys_Print(printline);
1366 else if(sys_colortranslation.integer == 2) // Quake
1372 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1375 for(in = line, out = printline; *in; ++in)
1379 case STRING_COLOR_TAG:
1382 case STRING_COLOR_RGB_TAG_CHAR:
1383 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1388 *out++ = STRING_COLOR_TAG;
1389 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1392 case STRING_COLOR_TAG:
1394 *out++ = STRING_COLOR_TAG;
1409 *out++ = STRING_COLOR_TAG;
1419 Sys_Print(printline);
1422 // empty the line buffer
1429 Thread_UnlockMutex(con_mutex);
1437 void Con_MaskPrintf(int mask, const char *fmt, ...)
1440 char msg[MAX_INPUTLINE];
1442 va_start(argptr,fmt);
1443 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1446 Con_MaskPrint(mask, msg);
1454 void Con_Print(const char *msg)
1456 Con_MaskPrint(CON_MASK_PRINT, msg);
1464 void Con_Printf(const char *fmt, ...)
1467 char msg[MAX_INPUTLINE];
1469 va_start(argptr,fmt);
1470 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1473 Con_MaskPrint(CON_MASK_PRINT, msg);
1481 void Con_DPrint(const char *msg)
1483 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1486 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1494 void Con_DPrintf(const char *fmt, ...)
1497 char msg[MAX_INPUTLINE];
1499 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1502 va_start(argptr,fmt);
1503 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1506 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1511 ==============================================================================
1515 ==============================================================================
1522 It draws either the console input line or the chat input line (if is_console is false)
1523 The input line scrolls horizontally if typing goes beyond the right edge
1525 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1528 static void Con_DrawInput(qbool is_console, float x, float v, float inputsize)
1530 int y, i, col_out, linepos, text_start, prefix_start = 0;
1531 char text[MAX_INPUTLINE + 5 + 9 + 1]; // space for ^xRGB, "say_team:" and \0
1537 if (is_console && !key_consoleactive)
1538 return; // don't draw anything
1542 // empty prefix because ] is part of the console edit line
1544 strlcpy(text, key_line, sizeof(text));
1545 linepos = key_linepos;
1553 prefix = "say_team:";
1556 strlcpy(text, chat_buffer, sizeof(text));
1557 linepos = chat_bufferpos;
1561 y = (int)strlen(text);
1563 // make the color code visible when the cursor is inside it
1564 if(text[linepos] != 0)
1566 for(i=1; i < 5 && linepos - i > 0; ++i)
1567 if(text[linepos-i] == STRING_COLOR_TAG)
1569 int caret_pos, ofs = 0;
1570 caret_pos = linepos - i;
1571 if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG)
1573 else if(i == 1 && isdigit(text[caret_pos+1]))
1575 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]))
1577 if(ofs && (size_t)(y + ofs + 1) < sizeof(text))
1580 while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG)
1584 // str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing)
1585 // str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing)
1586 memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos);
1587 text[caret_pos + ofs] = STRING_COLOR_TAG;
1599 x += DrawQ_TextWidth(prefix, 0, inputsize, inputsize, false, fnt);
1606 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, inputsize, inputsize, &col_out, false, fnt, 1000000000);
1608 text_start = x + (vid_conwidth.value - x) * 0.95 - xo; // scroll
1611 else if (!is_console)
1612 prefix_start -= (x - text_start);
1615 DrawQ_String(prefix_start, v, prefix, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1617 DrawQ_String(text_start, v, text, y + 3, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1619 // draw a cursor on top of this
1620 if ((int)(host.realtime*con_cursorspeed) & 1) // cursor is visible
1622 if (!utf8_enable.integer)
1624 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1632 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1633 memcpy(text, curbuf, len);
1636 DrawQ_String(text_start + xo, v, text, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, fnt);
1643 float alignment; // 0 = left, 0.5 = center, 1 = right
1649 const char *continuationString;
1652 int colorindex; // init to -1
1656 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1658 con_text_info_t *ti = (con_text_info_t *) passthrough;
1661 ti->colorindex = -1;
1662 return ti->fontsize * ti->font->maxwidth;
1665 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1666 else if(maxWidth == -1)
1667 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1670 Sys_Printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1671 // Note: this is NOT a Con_Printf, as it could print recursively
1676 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1682 (void) isContinuation;
1686 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1688 con_text_info_t *ti = (con_text_info_t *) passthrough;
1690 if(ti->y < ti->ymin - 0.001)
1692 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1696 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1697 if(isContinuation && *ti->continuationString)
1698 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);
1700 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);
1703 ti->y += ti->fontsize;
1707 static int Con_DrawNotifyRect(int mask_must, int mask_mustnot, float maxage, float x, float y, float width, float height, float fontsize, float alignment_x, float alignment_y, const char *continuationString)
1711 int maxlines = (int) floor(height / fontsize + 0.01f);
1714 int continuationWidth = 0;
1716 double t = cl.time; // saved so it won't change
1719 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1720 ti.fontsize = fontsize;
1721 ti.alignment = alignment_x;
1724 ti.ymax = y + height;
1725 ti.continuationString = continuationString;
1728 Con_WordWidthFunc(&ti, NULL, &len, -1);
1729 len = strlen(continuationString);
1730 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &len, -1);
1732 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1733 startidx = CON_LINES_COUNT;
1734 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1736 con_lineinfo_t *l = &CON_LINES(i);
1739 if((l->mask & mask_must) != mask_must)
1741 if(l->mask & mask_mustnot)
1743 if(maxage && (l->addtime < t - maxage))
1747 // Calculate its actual height...
1748 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1749 if(lines + mylines >= maxlines)
1751 nskip = lines + mylines - maxlines;
1760 // then center according to the calculated amount of lines...
1762 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1764 // then actually draw
1765 for(i = startidx; i < CON_LINES_COUNT; ++i)
1767 con_lineinfo_t *l = &CON_LINES(i);
1769 if((l->mask & mask_must) != mask_must)
1771 if(l->mask & mask_mustnot)
1773 if(maxage && (l->addtime < t - maxage))
1776 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1786 Draws the last few lines of output transparently over the game top
1789 void Con_DrawNotify (void)
1792 float chatstart, notifystart, inputsize, height;
1797 if (con_mutex) Thread_LockMutex(con_mutex);
1798 ConBuffer_FixTimes(&con);
1800 numChatlines = con_chat.integer;
1802 chatpos = con_chatpos.integer;
1804 if (con_notify.integer < 0)
1805 Cvar_SetValueQuick(&con_notify, 0);
1806 if (gamemode == GAME_TRANSFUSION)
1807 v = 8; // vertical offset
1811 // GAME_NEXUIZ: center, otherwise left justify
1812 align = con_notifyalign.value;
1813 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1815 if(IS_OLDNEXUIZ_DERIVED(gamemode))
1819 if(numChatlines || !con_chatrect.integer)
1823 // first chat, input line, then notify
1825 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1827 else if(chatpos > 0)
1829 // first notify, then (chatpos-1) empty lines, then chat, then input
1831 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1833 else // if(chatpos < 0)
1835 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1837 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1842 // just notify and input
1844 chatstart = 0; // shut off gcc warning
1847 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, "");
1849 if(con_chatrect.integer)
1851 x = con_chatrect_x.value * vid_conwidth.value;
1852 v = con_chatrect_y.value * vid_conheight.value;
1857 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1860 height = numChatlines * con_chatsize.value;
1864 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 ... ");
1867 if (key_dest == key_message)
1869 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1870 Con_DrawInput(false, x, v, inputsize);
1875 if (con_mutex) Thread_UnlockMutex(con_mutex);
1882 Returns the height of a given console line; calculates it if necessary.
1885 static int Con_LineHeight(int lineno)
1887 con_lineinfo_t *li = &CON_LINES(lineno);
1888 if(li->height == -1)
1890 float width = vid_conwidth.value;
1892 ti.fontsize = con_textsize.value;
1893 ti.font = FONT_CONSOLE;
1894 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1903 Draws a line of the console; returns its height in lines.
1904 If alpha is 0, the line is not drawn, but still wrapped and its height
1908 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1910 float width = vid_conwidth.value;
1912 con_lineinfo_t *li = &CON_LINES(lineno);
1914 if((li->mask & mask_must) != mask_must)
1916 if((li->mask & mask_mustnot) != 0)
1919 ti.continuationString = "";
1921 ti.fontsize = con_textsize.value;
1922 ti.font = FONT_CONSOLE;
1924 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1929 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1936 Calculates the last visible line index and how much to show of it based on
1940 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1945 if(con_backscroll < 0)
1950 // now count until we saw con_backscroll actual lines
1951 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1952 if((CON_LINES(i).mask & mask_must) == mask_must)
1953 if((CON_LINES(i).mask & mask_mustnot) == 0)
1955 int h = Con_LineHeight(i);
1957 // line is the last visible line?
1959 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1961 *limitlast = lines_seen + h - con_backscroll;
1968 // if we get here, no line was on screen - scroll so that one line is
1970 con_backscroll = lines_seen - 1;
1978 Draws the console with the solid background
1979 The typing input line at the bottom should only be drawn if typing is allowed
1982 void Con_DrawConsole (int lines)
1984 float alpha, alpha0;
1987 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1988 cachepic_t *conbackpic;
1989 unsigned int conbackflags;
1994 if (con_mutex) Thread_LockMutex(con_mutex);
1996 if (con_backscroll < 0)
1999 con_vislines = lines;
2001 r_draw2d_force = true;
2003 // draw the background
2004 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
2005 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
2007 sx = scr_conscroll_x.value;
2008 sy = scr_conscroll_y.value;
2009 conbackflags = CACHEPICFLAG_FAILONMISSING; // So console is readable when game content is missing
2010 if (sx != 0 || sy != 0)
2011 conbackflags &= CACHEPICFLAG_NOCLAMP;
2012 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", conbackflags) : NULL;
2013 sx *= host.realtime; sy *= host.realtime;
2014 sx -= floor(sx); sy -= floor(sy);
2015 if (Draw_IsPicLoaded(conbackpic))
2016 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2017 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2018 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2019 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2020 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2023 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
2025 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
2027 sx = scr_conscroll2_x.value;
2028 sy = scr_conscroll2_y.value;
2029 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2030 sx *= host.realtime; sy *= host.realtime;
2031 sx -= floor(sx); sy -= floor(sy);
2032 if(Draw_IsPicLoaded(conbackpic))
2033 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2034 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2035 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2036 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2037 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2040 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
2042 sx = scr_conscroll3_x.value;
2043 sy = scr_conscroll3_y.value;
2044 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2045 sx *= host.realtime; sy *= host.realtime;
2046 sx -= floor(sx); sy -= floor(sy);
2047 if(Draw_IsPicLoaded(conbackpic))
2048 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2049 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2050 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2051 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2052 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2055 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);
2061 int count = CON_LINES_COUNT;
2062 float ymax = con_vislines - 2 * con_textsize.value;
2063 float y = ymax + con_textsize.value * con_backscroll;
2064 for (i = 0;i < count && y >= 0;i++)
2065 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
2066 // fix any excessive scrollback for the next frame
2067 if (i >= count && y >= 0)
2069 con_backscroll -= (int)(y / con_textsize.value);
2070 if (con_backscroll < 0)
2075 if(CON_LINES_COUNT > 0)
2077 int i, last, limitlast;
2079 float ymax = con_vislines - 2 * con_textsize.value;
2080 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2081 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2082 y = ymax - con_textsize.value;
2085 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
2090 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
2092 break; // top of console buffer
2094 break; // top of console window
2101 // draw the input prompt, user text, and cursor if desired
2102 Con_DrawInput(true, 0, con_vislines - con_textsize.value * 2, con_textsize.value);
2104 r_draw2d_force = false;
2105 if (con_mutex) Thread_UnlockMutex(con_mutex);
2112 Prints not only map filename, but also
2113 its format (q1/q2/q3/hl) and even its message
2115 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2116 //LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2117 //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
2118 //LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2119 qbool GetMapList (const char *s, char *completedname, int completednamebufferlength)
2123 int i, k, max, p, o, min;
2126 unsigned char buf[1024];
2128 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2129 t = FS_Search(message, 1, true, NULL);
2132 if (t->numfilenames > 1)
2133 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2134 len = (unsigned char *)Z_Malloc(t->numfilenames);
2136 for(max=i=0;i<t->numfilenames;i++)
2138 k = (int)strlen(t->filenames[i]);
2148 for(i=0;i<t->numfilenames;i++)
2150 int lumpofs = 0, lumplen = 0;
2151 char *entities = NULL;
2152 const char *data = NULL;
2154 char entfilename[MAX_QPATH];
2157 strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2159 f = FS_OpenVirtualFile(t->filenames[i], true);
2162 strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2163 memset(buf, 0, 1024);
2164 FS_Read(f, buf, 1024);
2165 if (!memcmp(buf, "IBSP", 4))
2167 p = LittleLong(((int *)buf)[1]);
2168 if (p == Q3BSPVERSION)
2170 q3dheader_t *header = (q3dheader_t *)buf;
2171 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2172 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2173 dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2175 else if (p == Q2BSPVERSION)
2177 q2dheader_t *header = (q2dheader_t *)buf;
2178 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2179 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2180 dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2183 dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2185 else if (BuffLittleLong(buf) == BSPVERSION)
2187 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2188 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2189 dpsnprintf(desc, sizeof(desc), "BSP29");
2191 else if (BuffLittleLong(buf) == 30)
2193 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2194 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2195 dpsnprintf(desc, sizeof(desc), "BSPHL");
2197 else if (!memcmp(buf, "BSP2", 4))
2199 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2200 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2201 dpsnprintf(desc, sizeof(desc), "BSP2");
2203 else if (!memcmp(buf, "2PSB", 4))
2205 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2206 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2207 dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2209 else if(!memcmp(buf, "VBSP", 4))
2211 hl2dheader_t *header = (hl2dheader_t *)buf;
2212 lumpofs = LittleLong(header->lumps[HL2LUMP_ENTITIES].fileofs);
2213 lumplen = LittleLong(header->lumps[HL2LUMP_ENTITIES].filelen);
2214 dpsnprintf(desc, sizeof(desc), "VBSP%i", LittleLong(((int *)buf)[1]));
2217 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2218 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2219 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2220 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2221 if (!entities && lumplen >= 10)
2223 FS_Seek(f, lumpofs, SEEK_SET);
2224 entities = (char *)Z_Malloc(lumplen + 1);
2225 FS_Read(f, entities, lumplen);
2229 // if there are entities to parse, a missing message key just
2230 // means there is no title, so clear the message string now
2236 if (!COM_ParseToken_Simple(&data, false, false, true))
2238 if (com_token[0] == '{')
2240 if (com_token[0] == '}')
2242 // skip leading whitespace
2243 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2244 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2245 keyname[l] = com_token[k+l];
2247 if (!COM_ParseToken_Simple(&data, false, false, true))
2249 if (developer_extra.integer)
2250 Con_DPrintf("key: %s %s\n", keyname, com_token);
2251 if (!strcmp(keyname, "message"))
2253 // get the message contents
2254 strlcpy(message, com_token, sizeof(message));
2264 *(t->filenames[i]+len[i]+5) = 0;
2265 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2270 k = *(t->filenames[0]+5+p);
2273 for(i=1;i<t->numfilenames;i++)
2274 if(*(t->filenames[i]+5+p) != k)
2278 if(p > o && completedname && completednamebufferlength > 0)
2280 memset(completedname, 0, completednamebufferlength);
2281 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2291 New function for tab-completion system
2292 Added by EvilTypeGuy
2293 MEGA Thanks to Taniwha
2296 void Con_DisplayList(const char **list)
2298 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2299 const char **walk = list;
2302 len = (int)strlen(*walk);
2310 len = (int)strlen(*list);
2311 if (pos + maxlen >= width) {
2317 for (i = 0; i < (maxlen - len); i++)
2329 // Now it becomes TRICKY :D --blub
2330 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2331 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2332 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2333 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
2334 static int Nicks_matchpos;
2336 // co against <<:BLASTER:>> is true!?
2337 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2341 if(tolower(*a) == tolower(*b))
2355 return (*a < *b) ? -1 : 1;
2359 return (*a < *b) ? -1 : 1;
2363 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2366 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2368 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2369 return Nicks_strncasecmp_nospaces(a, b, a_len);
2370 return strncasecmp(a, b, a_len);
2373 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2375 // ignore non alphanumerics of B
2376 // if A contains a non-alphanumeric, B must contain it as well though!
2379 qbool alnum_a, alnum_b;
2381 if(tolower(*a) == tolower(*b))
2383 if(*a == 0) // end of both strings, they're equal
2390 // not equal, end of one string?
2395 // ignore non alphanumerics
2396 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2397 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2398 if(!alnum_a) // b must contain this
2399 return (*a < *b) ? -1 : 1;
2402 // otherwise, both are alnum, they're just not equal, return the appropriate number
2404 return (*a < *b) ? -1 : 1;
2410 /* Nicks_CompleteCountPossible
2412 Count the number of possible nicks to complete
2414 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qbool isCon)
2416 char name[MAX_SCOREBOARDNAME];
2422 if(!con_nickcompletion.integer)
2425 // changed that to 1
2426 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2429 for(i = 0; i < cl.maxclients; ++i)
2432 if(!cl.scores[p].name[0])
2435 SanitizeString(cl.scores[p].name, name);
2436 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2442 spos = pos - 1; // no need for a minimum of characters :)
2446 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2448 if(!(isCon && spos == 1)) // console start
2454 if(isCon && spos == 0)
2456 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2462 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2463 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2465 // the sanitized list
2466 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2469 Nicks_matchpos = match;
2472 Nicks_offset[count] = s - (&line[match]);
2473 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2480 static void Cmd_CompleteNicksPrint(int count)
2483 for(i = 0; i < count; ++i)
2484 Con_Printf("%s\n", Nicks_list[i]);
2487 static void Nicks_CutMatchesNormal(int count)
2489 // cut match 0 down to the longest possible completion
2492 c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2493 for(i = 1; i < count; ++i)
2495 l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2499 for(l = 0; l <= c; ++l)
2500 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2506 Nicks_sanlist[0][c+1] = 0;
2507 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2510 static unsigned int Nicks_strcleanlen(const char *s)
2515 if( (*s >= 'a' && *s <= 'z') ||
2516 (*s >= 'A' && *s <= 'Z') ||
2517 (*s >= '0' && *s <= '9') ||
2525 static void Nicks_CutMatchesAlphaNumeric(int count)
2527 // cut match 0 down to the longest possible completion
2530 char tempstr[sizeof(Nicks_sanlist[0])];
2532 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2534 c = (unsigned int)strlen(Nicks_sanlist[0]);
2535 for(i = 0, l = 0; i < (int)c; ++i)
2537 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2538 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2539 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2541 tempstr[l++] = Nicks_sanlist[0][i];
2546 for(i = 1; i < count; ++i)
2549 b = Nicks_sanlist[i];
2559 if(tolower(*a) == tolower(*b))
2565 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2567 // b is alnum, so cut
2574 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2575 Nicks_CutMatchesNormal(count);
2576 //if(!Nicks_sanlist[0][0])
2577 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2579 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2580 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2584 static void Nicks_CutMatchesNoSpaces(int count)
2586 // cut match 0 down to the longest possible completion
2589 char tempstr[sizeof(Nicks_sanlist[0])];
2592 c = (unsigned int)strlen(Nicks_sanlist[0]);
2593 for(i = 0, l = 0; i < (int)c; ++i)
2595 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2597 tempstr[l++] = Nicks_sanlist[0][i];
2602 for(i = 1; i < count; ++i)
2605 b = Nicks_sanlist[i];
2615 if(tolower(*a) == tolower(*b))
2629 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2630 Nicks_CutMatchesNormal(count);
2631 //if(!Nicks_sanlist[0][0])
2632 //Con_Printf("TS: %s\n", tempstr);
2633 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2635 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2636 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2640 static void Nicks_CutMatches(int count)
2642 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2643 Nicks_CutMatchesAlphaNumeric(count);
2644 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2645 Nicks_CutMatchesNoSpaces(count);
2647 Nicks_CutMatchesNormal(count);
2650 static const char **Nicks_CompleteBuildList(int count)
2654 // the list is freed by Con_CompleteCommandLine, so create a char**
2655 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2657 for(; bpos < count; ++bpos)
2658 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2660 Nicks_CutMatches(count);
2668 Restores the previous used color, after the autocompleted name.
2670 static int Nicks_AddLastColor(char *buffer, int pos)
2672 qbool quote_added = false;
2674 int color = STRING_COLOR_DEFAULT + '0';
2675 char r = 0, g = 0, b = 0;
2677 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2679 // we'll have to add a quote :)
2680 buffer[pos++] = '\"';
2684 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2686 // add color when no quote was added, or when flags &4?
2688 for(match = Nicks_matchpos-1; match >= 0; --match)
2690 if(buffer[match] == STRING_COLOR_TAG)
2692 if( isdigit(buffer[match+1]) )
2694 color = buffer[match+1];
2697 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2699 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2701 r = buffer[match+2];
2702 g = buffer[match+3];
2703 b = buffer[match+4];
2712 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2714 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2715 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2718 buffer[pos++] = STRING_COLOR_TAG;
2721 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2727 buffer[pos++] = color;
2733 Con_CompleteCommandLine
2735 New function for tab-completion system
2736 Added by EvilTypeGuy
2737 Thanks to Fett erich@heintz.com
2739 Enhanced to tab-complete map names by [515]
2742 int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console)
2744 const char *text = "";
2746 const char **list[4] = {0, 0, 0, 0};
2749 int c, v, a, i, cmd_len, pos, k;
2750 int n; // nicks --blub
2751 const char *space, *patterns;
2755 int linestart, linepos;
2756 unsigned int linesize;
2760 linepos = key_linepos;
2761 linesize = sizeof(key_line);
2767 linepos = chat_bufferpos;
2768 linesize = sizeof(chat_buffer);
2772 //find what we want to complete
2774 while(--pos >= linestart)
2777 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2783 strlcpy(s2, line + linepos, sizeof(s2)); //save chars after cursor
2784 line[linepos] = 0; //hide them
2786 c = v = a = n = cmd_len = 0;
2790 space = strchr(line + 1, ' ');
2791 if(space && pos == (space - line) + 1)
2793 strlcpy(command, line + 1, min(sizeof(command), (unsigned int)(space - line)));
2795 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?
2796 if(patterns && !*patterns)
2797 patterns = NULL; // get rid of the empty string
2799 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2803 if (GetMapList(s, t, sizeof(t)))
2805 // first move the cursor
2806 linepos += (int)strlen(t) - (int)strlen(s);
2808 // and now do the actual work
2810 strlcat(line, t, MAX_INPUTLINE);
2811 strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2813 // and fix the cursor
2814 if(linepos > (int) strlen(line))
2815 linepos = (int) strlen(line);
2824 stringlist_t resultbuf, dirbuf;
2827 // // store completion patterns (space separated) for command foo in con_completion_foo
2828 // set con_completion_foo "foodata/*.foodefault *.foo"
2831 // Note: patterns with slash are always treated as absolute
2832 // patterns; patterns without slash search in the innermost
2833 // directory the user specified. There is no way to "complete into"
2834 // a directory as of now, as directories seem to be unknown to the
2838 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2839 // set con_completion_playdemo "*.dem"
2840 // set con_completion_play "*.wav *.ogg"
2842 // TODO somehow add support for directories; these shall complete
2843 // to their name + an appended slash.
2845 stringlistinit(&resultbuf);
2846 stringlistinit(&dirbuf);
2847 while(COM_ParseToken_Simple(&patterns, false, false, true))
2850 if(strchr(com_token, '/'))
2852 search = FS_Search(com_token, true, true, NULL);
2856 const char *slash = strrchr(s, '/');
2859 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2860 strlcat(t, com_token, sizeof(t));
2861 search = FS_Search(t, true, true, NULL);
2864 search = FS_Search(com_token, true, true, NULL);
2868 for(i = 0; i < search->numfilenames; ++i)
2869 if(!strncmp(search->filenames[i], s, strlen(s)))
2870 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2871 stringlistappend(&resultbuf, search->filenames[i]);
2872 FS_FreeSearch(search);
2876 // In any case, add directory names
2879 const char *slash = strrchr(s, '/');
2882 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2883 strlcat(t, "*", sizeof(t));
2884 search = FS_Search(t, true, true, NULL);
2887 search = FS_Search("*", true, true, NULL);
2890 for(i = 0; i < search->numfilenames; ++i)
2891 if(!strncmp(search->filenames[i], s, strlen(s)))
2892 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2893 stringlistappend(&dirbuf, search->filenames[i]);
2894 FS_FreeSearch(search);
2898 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2901 unsigned int matchchars;
2902 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2904 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2907 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2909 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2913 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2914 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2915 for(i = 0; i < dirbuf.numstrings; ++i)
2917 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2919 for(i = 0; i < resultbuf.numstrings; ++i)
2921 Con_Printf("%s\n", resultbuf.strings[i]);
2923 matchchars = sizeof(t) - 1;
2924 if(resultbuf.numstrings > 0)
2926 p = resultbuf.strings[0];
2927 q = resultbuf.strings[resultbuf.numstrings - 1];
2928 for(; *p && *p == *q; ++p, ++q);
2929 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2931 if(dirbuf.numstrings > 0)
2933 p = dirbuf.strings[0];
2934 q = dirbuf.strings[dirbuf.numstrings - 1];
2935 for(; *p && *p == *q; ++p, ++q);
2936 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2938 // now p points to the first non-equal character, or to the end
2939 // of resultbuf.strings[0]. We want to append the characters
2940 // from resultbuf.strings[0] to (not including) p as these are
2941 // the unique prefix
2942 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2945 // first move the cursor
2946 linepos += (int)strlen(t) - (int)strlen(s);
2948 // and now do the actual work
2950 strlcat(line, t, MAX_INPUTLINE);
2951 strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2953 // and fix the cursor
2954 if(linepos > (int) strlen(line))
2955 linepos = (int) strlen(line);
2957 stringlistfreecontents(&resultbuf);
2958 stringlistfreecontents(&dirbuf);
2960 return linepos; // bail out, when we complete for a command that wants a file name
2965 // Count number of possible matches and print them
2966 c = Cmd_CompleteCountPossible(cmd, s);
2969 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2970 Cmd_CompleteCommandPrint(cmd, s);
2972 v = Cvar_CompleteCountPossible(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2975 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2976 Cvar_CompleteCvarPrint(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2978 a = Cmd_CompleteAliasCountPossible(cmd, s);
2981 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2982 Cmd_CompleteAliasPrint(cmd, s);
2986 n = Nicks_CompleteCountPossible(line, linepos, s, is_console);
2989 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2990 Cmd_CompleteNicksPrint(n);
2993 if (!(c + v + a + n)) // No possible matches
2996 strlcpy(&line[linepos], s2, linesize - linepos);
3001 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
3003 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
3005 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
3009 text = *(list[3] = Nicks_CompleteBuildList(n));
3011 text = *(Nicks_CompleteBuildList(n));
3014 for (cmd_len = (int)strlen(s);;cmd_len++)
3017 for (i = 0; i < 3; i++)
3019 for (l = list[i];*l;l++)
3020 if ((*l)[cmd_len] != text[cmd_len])
3022 // all possible matches share this character, so we continue...
3025 // if all matches ended at the same position, stop
3026 // (this means there is only one match)
3032 // prevent a buffer overrun by limiting cmd_len according to remaining space
3033 cmd_len = min(cmd_len, (int)linesize - 1 - pos);
3037 memcpy(&line[linepos], text, cmd_len);
3039 // if there is only one match, add a space after it
3040 if (c + v + a + n == 1 && linepos < (int)linesize - 1)
3043 { // was a nick, might have an offset, and needs colors ;) --blub
3044 linepos = pos - Nicks_offset[0];
3045 cmd_len = (int)strlen(Nicks_list[0]);
3046 cmd_len = min(cmd_len, (int)linesize - 3 - pos);
3048 memcpy(&line[linepos] , Nicks_list[0], cmd_len);
3050 if(linepos < (int)(linesize - 7)) // space for color code (^[0-9] or ^xrgb), space and \0
3051 linepos = Nicks_AddLastColor(line, linepos);
3053 line[linepos++] = ' ';
3057 // use strlcat to avoid a buffer overrun
3059 strlcat(line, s2, linesize);
3064 // free the command, cvar, and alias lists
3065 for (i = 0; i < 4; i++)
3067 Mem_Free((void *)list[i]);