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 if (cls.demoplayback)
724 key_dest = key_message;
725 chat_mode = 0; // "say"
726 if(Cmd_Argc(cmd) > 1)
728 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
729 chat_bufferpos = (unsigned int)strlen(chat_buffer);
739 static void Con_MessageMode2_f(cmd_state_t *cmd)
741 if (cls.demoplayback)
743 key_dest = key_message;
744 chat_mode = 1; // "say_team"
745 if(Cmd_Argc(cmd) > 1)
747 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
748 chat_bufferpos = (unsigned int)strlen(chat_buffer);
757 static void Con_CommandMode_f(cmd_state_t *cmd)
759 key_dest = key_message;
760 if(Cmd_Argc(cmd) > 1)
762 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
763 chat_bufferpos = (unsigned int)strlen(chat_buffer);
765 chat_mode = -1; // command
773 void Con_CheckResize (void)
778 f = bound(1, con_textsize.value, 128);
779 if(f != con_textsize.value)
780 Cvar_SetValueQuick(&con_textsize, f);
781 width = (int)floor(vid_conwidth.value / con_textsize.value);
782 width = bound(1, width, con.textsize/4);
783 // FIXME uses con in a non abstracted way
785 if (width == con_linewidth)
788 con_linewidth = width;
790 for(i = 0; i < CON_LINES_COUNT; ++i)
791 CON_LINES(i).height = -1; // recalculate when next needed
797 //[515]: the simplest command ever
798 //LadyHavoc: not so simple after I made it print usage...
799 static void Con_Maps_f(cmd_state_t *cmd)
801 if (Cmd_Argc(cmd) > 2)
803 Con_Printf("usage: maps [mapnameprefix]\n");
806 else if (Cmd_Argc(cmd) == 2)
807 GetMapList(Cmd_Argv(cmd, 1), NULL, 0);
809 GetMapList("", NULL, 0);
812 static void Con_ConDump_f(cmd_state_t *cmd)
816 if (Cmd_Argc(cmd) != 2)
818 Con_Printf("usage: condump <filename>\n");
821 file = FS_OpenRealFile(Cmd_Argv(cmd, 1), "w", false);
824 Con_Printf(CON_ERROR "condump: unable to write file \"%s\"\n", Cmd_Argv(cmd, 1));
827 if (con_mutex) Thread_LockMutex(con_mutex);
828 for(i = 0; i < CON_LINES_COUNT; ++i)
830 if (condump_stripcolors.integer)
833 size_t len = CON_LINES(i).len;
834 char* sanitizedmsg = (char*)Mem_Alloc(tempmempool, len + 1);
835 memcpy (sanitizedmsg, CON_LINES(i).start, len);
836 SanitizeString(sanitizedmsg, sanitizedmsg); // SanitizeString's in pointer is always ahead of the out pointer, so this should work.
837 FS_Write(file, sanitizedmsg, strlen(sanitizedmsg));
838 Mem_Free(sanitizedmsg);
842 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
844 FS_Write(file, "\n", 1);
846 if (con_mutex) Thread_UnlockMutex(con_mutex);
850 void Con_Clear_f(cmd_state_t *cmd)
852 if (con_mutex) Thread_LockMutex(con_mutex);
853 ConBuffer_Clear(&con);
854 if (con_mutex) Thread_UnlockMutex(con_mutex);
857 static void Con_RCon_ClearPassword_c(cvar_t *var)
859 // whenever rcon_secure is changed to 0, clear rcon_password for
860 // security reasons (prevents a send-rcon-password-as-plaintext
861 // attack based on NQ protocol session takeover and svc_stufftext)
862 if(var->integer <= 0)
863 Cvar_SetQuick(&rcon_password, "");
874 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
875 if (Thread_HasThreads())
876 con_mutex = Thread_CreateMutex();
878 // Allocate a log queue, this will be freed after configs are parsed
879 logq_size = MAX_INPUTLINE;
880 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
883 Cvar_RegisterVariable (&sys_colortranslation);
884 Cvar_RegisterVariable (&sys_specialcharactertranslation);
886 Cvar_RegisterVariable (&log_file);
887 Cvar_RegisterVariable (&log_file_stripcolors);
888 Cvar_RegisterVariable (&log_dest_udp);
890 // support for the classic Quake option
891 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
892 if (Sys_CheckParm ("-condebug") != 0)
893 Cvar_SetQuick (&log_file, "qconsole.log");
895 // register our cvars
896 Cvar_RegisterVariable (&con_chat);
897 Cvar_RegisterVariable (&con_chatpos);
898 Cvar_RegisterVariable (&con_chatrect_x);
899 Cvar_RegisterVariable (&con_chatrect_y);
900 Cvar_RegisterVariable (&con_chatrect);
901 Cvar_RegisterVariable (&con_chatsize);
902 Cvar_RegisterVariable (&con_chattime);
903 Cvar_RegisterVariable (&con_chatwidth);
904 Cvar_RegisterVariable (&con_notify);
905 Cvar_RegisterVariable (&con_notifyalign);
906 Cvar_RegisterVariable (&con_notifysize);
907 Cvar_RegisterVariable (&con_notifytime);
908 Cvar_RegisterVariable (&con_textsize);
909 Cvar_RegisterVariable (&con_chatsound);
910 Cvar_RegisterVariable (&con_chatsound_file);
911 Cvar_RegisterVariable (&con_chatsound_team_file);
912 Cvar_RegisterVariable (&con_chatsound_team_mask);
915 Cvar_RegisterVariable (&con_nickcompletion);
916 Cvar_RegisterVariable (&con_nickcompletion_flags);
918 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
919 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
920 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
922 Cvar_RegisterVariable (&condump_stripcolors);
924 Cvar_RegisterVariable(&rcon_address);
925 Cvar_RegisterVariable(&rcon_secure);
926 Cvar_RegisterCallback(&rcon_secure, Con_RCon_ClearPassword_c);
927 Cvar_RegisterVariable(&rcon_secure_challengetimeout);
928 Cvar_RegisterVariable(&rcon_password);
930 // register our commands
931 Cmd_AddCommand(CF_CLIENT, "toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
932 Cmd_AddCommand(CF_CLIENT, "messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
933 Cmd_AddCommand(CF_CLIENT, "messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
934 Cmd_AddCommand(CF_CLIENT, "commandmode", Con_CommandMode_f, "input a console command");
935 Cmd_AddCommand(CF_SHARED, "clear", Con_Clear_f, "clear console history");
936 Cmd_AddCommand(CF_SHARED, "maps", Con_Maps_f, "list information about available maps");
937 Cmd_AddCommand(CF_SHARED, "condump", Con_ConDump_f, "output console history to a file (see also log_file)");
939 con_initialized = true;
941 Con_Print("Console initialized.\n");
944 void Con_Shutdown (void)
946 if (con_mutex) Thread_LockMutex(con_mutex);
947 ConBuffer_Shutdown(&con);
948 if (con_mutex) Thread_UnlockMutex(con_mutex);
949 if (con_mutex) Thread_DestroyMutex(con_mutex);
957 Handles cursor positioning, line wrapping, etc
958 All console printing must go through this in order to be displayed
959 If no console is visible, the notify window will pop up.
962 static void Con_PrintToHistory(const char *txt, int mask)
965 // \n goes to next line
966 // \r deletes current line and makes a new one
968 static int cr_pending = 0;
969 static char buf[CON_TEXTSIZE]; // con_mutex
970 static int bufpos = 0;
972 if(!con.text) // FIXME uses a non-abstracted property of con
979 ConBuffer_DeleteLastLine(&con);
987 ConBuffer_AddLine(&con, buf, bufpos, mask);
992 ConBuffer_AddLine(&con, buf, bufpos, mask);
996 buf[bufpos++] = *txt;
997 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
999 ConBuffer_AddLine(&con, buf, bufpos, mask);
1007 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qbool proquakeprotocol)
1009 rcon_redirect_sock = sock;
1010 rcon_redirect_dest = dest;
1011 rcon_redirect_proquakeprotocol = proquakeprotocol;
1012 if (rcon_redirect_proquakeprotocol)
1014 // reserve space for the packet header
1015 rcon_redirect_buffer[0] = 0;
1016 rcon_redirect_buffer[1] = 0;
1017 rcon_redirect_buffer[2] = 0;
1018 rcon_redirect_buffer[3] = 0;
1019 // this is a reply to a CCREQ_RCON
1020 rcon_redirect_buffer[4] = (unsigned char)CCREP_RCON;
1023 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1024 rcon_redirect_bufferpos = 5;
1027 static void Con_Rcon_Redirect_Flush(void)
1029 if(rcon_redirect_sock)
1031 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
1032 if (rcon_redirect_proquakeprotocol)
1034 // update the length in the packet header
1035 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
1037 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
1039 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1040 rcon_redirect_bufferpos = 5;
1041 rcon_redirect_proquakeprotocol = false;
1044 void Con_Rcon_Redirect_End(void)
1046 Con_Rcon_Redirect_Flush();
1047 rcon_redirect_dest = NULL;
1048 rcon_redirect_sock = NULL;
1051 void Con_Rcon_Redirect_Abort(void)
1053 rcon_redirect_dest = NULL;
1054 rcon_redirect_sock = NULL;
1062 /// Adds a character to the rcon buffer.
1063 static void Con_Rcon_AddChar(int c)
1065 if(log_dest_buffer_appending)
1067 ++log_dest_buffer_appending;
1069 // if this print is in response to an rcon command, add the character
1070 // to the rcon redirect buffer
1072 if (rcon_redirect_dest)
1074 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
1075 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
1076 Con_Rcon_Redirect_Flush();
1078 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
1080 if(log_dest_buffer_pos == 0)
1081 Log_DestBuffer_Init();
1082 log_dest_buffer[log_dest_buffer_pos++] = c;
1083 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
1084 Log_DestBuffer_Flush_NoLock();
1087 log_dest_buffer_pos = 0;
1089 --log_dest_buffer_appending;
1093 * Convert an RGB color to its nearest quake color.
1094 * I'll cheat on this a bit by translating the colors to HSV first,
1095 * S and V decide if it's black or white, otherwise, H will decide the
1097 * @param _r Red (0-255)
1098 * @param _g Green (0-255)
1099 * @param _b Blue (0-255)
1100 * @return A quake color character.
1102 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
1104 float r = ((float)_r)/255.0;
1105 float g = ((float)_g)/255.0;
1106 float b = ((float)_b)/255.0;
1107 float min = min(r, min(g, b));
1108 float max = max(r, max(g, b));
1110 int h; ///< Hue angle [0,360]
1111 float s; ///< Saturation [0,1]
1112 float v = max; ///< In HSV v == max [0,1]
1117 s = 1.0 - (min/max);
1119 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1122 // If the value is less than half, return a black color code.
1123 // Otherwise return a white one.
1129 // Let's get the hue angle to define some colors:
1133 h = (int)(60.0 * (g-b)/(max-min))%360;
1135 h = (int)(60.0 * (b-r)/(max-min) + 120);
1136 else // if(max == b) redundant check
1137 h = (int)(60.0 * (r-g)/(max-min) + 240);
1139 if(h < 36) // *red* to orange
1141 else if(h < 80) // orange over *yellow* to evilish-bright-green
1143 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1145 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1147 else if(h < 270) // darkish blue over *dark blue* to cool purple
1149 else if(h < 330) // cool purple over *purple* to ugly swiny red
1151 else // ugly red to red closes the circly
1160 extern cvar_t timestamps;
1161 extern cvar_t timeformat;
1162 extern qbool sys_nostdout;
1163 void Con_MaskPrint(int additionalmask, const char *msg)
1165 static int mask = 0;
1166 static int index = 0;
1167 static char line[MAX_INPUTLINE];
1170 Thread_LockMutex(con_mutex);
1174 Con_Rcon_AddChar(*msg);
1175 // if this is the beginning of a new line, print timestamp
1178 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1180 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1181 line[index++] = STRING_COLOR_TAG;
1182 // assert( STRING_COLOR_DEFAULT < 10 )
1183 line[index++] = STRING_COLOR_DEFAULT + '0';
1184 // special color codes for chat messages must always come first
1185 // for Con_PrintToHistory to work properly
1186 if (*msg == 1 || *msg == 2 || *msg == 3)
1191 if (con_chatsound.value)
1193 if(msg[1] == con_chatsound_team_mask.integer && cl.foundteamchatsound)
1194 S_LocalSound (con_chatsound_team_file.string);
1196 S_LocalSound (con_chatsound_file.string);
1199 // Send to chatbox for say/tell (1) and messages (3)
1200 // 3 is just so that a message can be sent to the chatbox without a sound.
1201 if (*msg == 1 || *msg == 3)
1202 mask = CON_MASK_CHAT;
1204 line[index++] = STRING_COLOR_TAG;
1205 line[index++] = '3';
1207 Con_Rcon_AddChar(*msg);
1210 for (;*timestamp;index++, timestamp++)
1211 if (index < (int)sizeof(line) - 2)
1212 line[index] = *timestamp;
1214 mask |= additionalmask;
1216 // append the character
1217 line[index++] = *msg;
1218 // if this is a newline character, we have a complete line to print
1219 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1221 // terminate the line
1225 // send to scrollable buffer
1226 if (con_initialized && cls.state != ca_dedicated)
1228 Con_PrintToHistory(line, mask);
1230 // send to terminal or dedicated server window
1232 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1234 if(sys_specialcharactertranslation.integer)
1241 int ch = u8_getchar(p, &q);
1242 if(ch >= 0xE000 && ch <= 0xE0FF && ((unsigned char) qfont_table[ch - 0xE000]) >= 0x20)
1244 *p = qfont_table[ch - 0xE000];
1246 memmove(p+1, q, strlen(q)+1);
1254 if(sys_colortranslation.integer == 1) // ANSI
1256 static char printline[MAX_INPUTLINE * 4 + 3];
1257 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1258 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1263 for(in = line, out = printline; *in; ++in)
1267 case STRING_COLOR_TAG:
1268 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1270 char r = tolower(in[2]);
1271 char g = tolower(in[3]);
1272 char b = tolower(in[4]);
1273 // it's a hex digit already, so the else part needs no check --blub
1274 if(isdigit(r)) r -= '0';
1276 if(isdigit(g)) g -= '0';
1278 if(isdigit(b)) b -= '0';
1281 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1282 in += 3; // 3 only, the switch down there does the fourth
1289 case STRING_COLOR_TAG:
1291 *out++ = STRING_COLOR_TAG;
1297 if(lastcolor == 0) break; else lastcolor = 0;
1298 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1303 if(lastcolor == 1) break; else lastcolor = 1;
1304 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1309 if(lastcolor == 2) break; else lastcolor = 2;
1310 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1315 if(lastcolor == 3) break; else lastcolor = 3;
1316 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1321 if(lastcolor == 4) break; else lastcolor = 4;
1322 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1327 if(lastcolor == 5) break; else lastcolor = 5;
1328 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1333 if(lastcolor == 6) break; else lastcolor = 6;
1334 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1339 // bold normal color
1341 if(lastcolor == 8) break; else lastcolor = 8;
1342 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1345 *out++ = STRING_COLOR_TAG;
1352 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1369 Sys_Print(printline);
1371 else if(sys_colortranslation.integer == 2) // Quake
1377 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1380 for(in = line, out = printline; *in; ++in)
1384 case STRING_COLOR_TAG:
1387 case STRING_COLOR_RGB_TAG_CHAR:
1388 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1393 *out++ = STRING_COLOR_TAG;
1394 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1397 case STRING_COLOR_TAG:
1399 *out++ = STRING_COLOR_TAG;
1414 *out++ = STRING_COLOR_TAG;
1424 Sys_Print(printline);
1427 // empty the line buffer
1434 Thread_UnlockMutex(con_mutex);
1442 void Con_MaskPrintf(int mask, const char *fmt, ...)
1445 char msg[MAX_INPUTLINE];
1447 va_start(argptr,fmt);
1448 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1451 Con_MaskPrint(mask, msg);
1459 void Con_Print(const char *msg)
1461 Con_MaskPrint(CON_MASK_PRINT, msg);
1469 void Con_Printf(const char *fmt, ...)
1472 char msg[MAX_INPUTLINE];
1474 va_start(argptr,fmt);
1475 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1478 Con_MaskPrint(CON_MASK_PRINT, msg);
1486 void Con_DPrint(const char *msg)
1488 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1491 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1499 void Con_DPrintf(const char *fmt, ...)
1502 char msg[MAX_INPUTLINE];
1504 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1507 va_start(argptr,fmt);
1508 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1511 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1516 ==============================================================================
1520 ==============================================================================
1527 It draws either the console input line or the chat input line (if is_console is false)
1528 The input line scrolls horizontally if typing goes beyond the right edge
1530 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1533 static void Con_DrawInput(qbool is_console, float x, float v, float inputsize)
1535 int y, i, col_out, linepos, text_start, prefix_start = 0;
1536 char text[MAX_INPUTLINE + 5 + 9 + 1]; // space for ^xRGB, "say_team:" and \0
1542 if (is_console && !key_consoleactive)
1543 return; // don't draw anything
1547 // empty prefix because ] is part of the console edit line
1549 strlcpy(text, key_line, sizeof(text));
1550 linepos = key_linepos;
1558 prefix = "say_team:";
1561 strlcpy(text, chat_buffer, sizeof(text));
1562 linepos = chat_bufferpos;
1566 y = (int)strlen(text);
1568 // make the color code visible when the cursor is inside it
1569 if(text[linepos] != 0)
1571 for(i=1; i < 5 && linepos - i > 0; ++i)
1572 if(text[linepos-i] == STRING_COLOR_TAG)
1574 int caret_pos, ofs = 0;
1575 caret_pos = linepos - i;
1576 if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG)
1578 else if(i == 1 && isdigit(text[caret_pos+1]))
1580 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]))
1582 if(ofs && (size_t)(y + ofs + 1) < sizeof(text))
1585 while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG)
1589 // str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing)
1590 // str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing)
1591 memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos);
1592 text[caret_pos + ofs] = STRING_COLOR_TAG;
1604 x += DrawQ_TextWidth(prefix, 0, inputsize, inputsize, false, fnt);
1611 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, inputsize, inputsize, &col_out, false, fnt, 1000000000);
1613 text_start = x + (vid_conwidth.value - x) * 0.95 - xo; // scroll
1616 else if (!is_console)
1617 prefix_start -= (x - text_start);
1620 DrawQ_String(prefix_start, v, prefix, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1622 DrawQ_String(text_start, v, text, y + 3, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1624 // draw a cursor on top of this
1625 if ((int)(host.realtime*con_cursorspeed) & 1) // cursor is visible
1627 if (!utf8_enable.integer)
1629 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1637 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1638 memcpy(text, curbuf, len);
1641 DrawQ_String(text_start + xo, v, text, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, fnt);
1648 float alignment; // 0 = left, 0.5 = center, 1 = right
1654 const char *continuationString;
1657 int colorindex; // init to -1
1661 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1663 con_text_info_t *ti = (con_text_info_t *) passthrough;
1666 ti->colorindex = -1;
1667 return ti->fontsize * ti->font->maxwidth;
1670 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1671 else if(maxWidth == -1)
1672 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1675 Sys_Printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1676 // Note: this is NOT a Con_Printf, as it could print recursively
1681 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1687 (void) isContinuation;
1691 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1693 con_text_info_t *ti = (con_text_info_t *) passthrough;
1695 if(ti->y < ti->ymin - 0.001)
1697 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1701 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1702 if(isContinuation && *ti->continuationString)
1703 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);
1705 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);
1708 ti->y += ti->fontsize;
1712 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)
1716 int maxlines = (int) floor(height / fontsize + 0.01f);
1719 int continuationWidth = 0;
1721 double t = cl.time; // saved so it won't change
1724 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1725 ti.fontsize = fontsize;
1726 ti.alignment = alignment_x;
1729 ti.ymax = y + height;
1730 ti.continuationString = continuationString;
1733 Con_WordWidthFunc(&ti, NULL, &len, -1);
1734 len = strlen(continuationString);
1735 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &len, -1);
1737 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1738 startidx = CON_LINES_COUNT;
1739 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1741 con_lineinfo_t *l = &CON_LINES(i);
1744 if((l->mask & mask_must) != mask_must)
1746 if(l->mask & mask_mustnot)
1748 if(maxage && (l->addtime < t - maxage))
1752 // Calculate its actual height...
1753 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1754 if(lines + mylines >= maxlines)
1756 nskip = lines + mylines - maxlines;
1765 // then center according to the calculated amount of lines...
1767 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1769 // then actually draw
1770 for(i = startidx; i < CON_LINES_COUNT; ++i)
1772 con_lineinfo_t *l = &CON_LINES(i);
1774 if((l->mask & mask_must) != mask_must)
1776 if(l->mask & mask_mustnot)
1778 if(maxage && (l->addtime < t - maxage))
1781 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1791 Draws the last few lines of output transparently over the game top
1794 void Con_DrawNotify (void)
1797 float chatstart, notifystart, inputsize, height;
1802 if (con_mutex) Thread_LockMutex(con_mutex);
1803 ConBuffer_FixTimes(&con);
1805 numChatlines = con_chat.integer;
1807 chatpos = con_chatpos.integer;
1809 if (con_notify.integer < 0)
1810 Cvar_SetValueQuick(&con_notify, 0);
1811 if (gamemode == GAME_TRANSFUSION)
1812 v = 8; // vertical offset
1816 // GAME_NEXUIZ: center, otherwise left justify
1817 align = con_notifyalign.value;
1818 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1820 if(IS_OLDNEXUIZ_DERIVED(gamemode))
1824 if(numChatlines || !con_chatrect.integer)
1828 // first chat, input line, then notify
1830 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1832 else if(chatpos > 0)
1834 // first notify, then (chatpos-1) empty lines, then chat, then input
1836 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1838 else // if(chatpos < 0)
1840 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1842 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1847 // just notify and input
1849 chatstart = 0; // shut off gcc warning
1852 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, "");
1854 if(con_chatrect.integer)
1856 x = con_chatrect_x.value * vid_conwidth.value;
1857 v = con_chatrect_y.value * vid_conheight.value;
1862 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1865 height = numChatlines * con_chatsize.value;
1869 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 ... ");
1872 if (key_dest == key_message)
1874 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1875 Con_DrawInput(false, x, v, inputsize);
1880 if (con_mutex) Thread_UnlockMutex(con_mutex);
1887 Returns the height of a given console line; calculates it if necessary.
1890 static int Con_LineHeight(int lineno)
1892 con_lineinfo_t *li = &CON_LINES(lineno);
1893 if(li->height == -1)
1895 float width = vid_conwidth.value;
1897 ti.fontsize = con_textsize.value;
1898 ti.font = FONT_CONSOLE;
1899 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1908 Draws a line of the console; returns its height in lines.
1909 If alpha is 0, the line is not drawn, but still wrapped and its height
1913 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1915 float width = vid_conwidth.value;
1917 con_lineinfo_t *li = &CON_LINES(lineno);
1919 if((li->mask & mask_must) != mask_must)
1921 if((li->mask & mask_mustnot) != 0)
1924 ti.continuationString = "";
1926 ti.fontsize = con_textsize.value;
1927 ti.font = FONT_CONSOLE;
1929 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1934 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1941 Calculates the last visible line index and how much to show of it based on
1945 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1950 if(con_backscroll < 0)
1955 // now count until we saw con_backscroll actual lines
1956 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1957 if((CON_LINES(i).mask & mask_must) == mask_must)
1958 if((CON_LINES(i).mask & mask_mustnot) == 0)
1960 int h = Con_LineHeight(i);
1962 // line is the last visible line?
1964 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1966 *limitlast = lines_seen + h - con_backscroll;
1973 // if we get here, no line was on screen - scroll so that one line is
1975 con_backscroll = lines_seen - 1;
1983 Draws the console with the solid background
1984 The typing input line at the bottom should only be drawn if typing is allowed
1987 void Con_DrawConsole (int lines)
1989 float alpha, alpha0;
1992 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1993 cachepic_t *conbackpic;
1994 unsigned int conbackflags;
1999 if (con_mutex) Thread_LockMutex(con_mutex);
2001 if (con_backscroll < 0)
2004 con_vislines = lines;
2006 r_draw2d_force = true;
2008 // draw the background
2009 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
2010 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
2012 sx = scr_conscroll_x.value;
2013 sy = scr_conscroll_y.value;
2014 conbackflags = CACHEPICFLAG_FAILONMISSING; // So console is readable when game content is missing
2015 if (sx != 0 || sy != 0)
2016 conbackflags &= CACHEPICFLAG_NOCLAMP;
2017 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", conbackflags) : NULL;
2018 sx *= host.realtime; sy *= host.realtime;
2019 sx -= floor(sx); sy -= floor(sy);
2020 if (Draw_IsPicLoaded(conbackpic))
2021 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2022 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2023 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2024 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2025 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2028 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
2030 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
2032 sx = scr_conscroll2_x.value;
2033 sy = scr_conscroll2_y.value;
2034 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2035 sx *= host.realtime; sy *= host.realtime;
2036 sx -= floor(sx); sy -= floor(sy);
2037 if(Draw_IsPicLoaded(conbackpic))
2038 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2039 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2040 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2041 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2042 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2045 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
2047 sx = scr_conscroll3_x.value;
2048 sy = scr_conscroll3_y.value;
2049 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2050 sx *= host.realtime; sy *= host.realtime;
2051 sx -= floor(sx); sy -= floor(sy);
2052 if(Draw_IsPicLoaded(conbackpic))
2053 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2054 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2055 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2056 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2057 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2060 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);
2066 int count = CON_LINES_COUNT;
2067 float ymax = con_vislines - 2 * con_textsize.value;
2068 float y = ymax + con_textsize.value * con_backscroll;
2069 for (i = 0;i < count && y >= 0;i++)
2070 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
2071 // fix any excessive scrollback for the next frame
2072 if (i >= count && y >= 0)
2074 con_backscroll -= (int)(y / con_textsize.value);
2075 if (con_backscroll < 0)
2080 if(CON_LINES_COUNT > 0)
2082 int i, last, limitlast;
2084 float ymax = con_vislines - 2 * con_textsize.value;
2085 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2086 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2087 y = ymax - con_textsize.value;
2090 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
2095 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
2097 break; // top of console buffer
2099 break; // top of console window
2106 // draw the input prompt, user text, and cursor if desired
2107 Con_DrawInput(true, 0, con_vislines - con_textsize.value * 2, con_textsize.value);
2109 r_draw2d_force = false;
2110 if (con_mutex) Thread_UnlockMutex(con_mutex);
2117 Prints not only map filename, but also
2118 its format (q1/q2/q3/hl) and even its message
2120 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2121 //LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2122 //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
2123 //LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2124 qbool GetMapList (const char *s, char *completedname, int completednamebufferlength)
2128 int i, k, max, p, o, min;
2131 unsigned char buf[1024];
2133 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2134 t = FS_Search(message, 1, true, NULL);
2137 if (t->numfilenames > 1)
2138 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2139 len = (unsigned char *)Z_Malloc(t->numfilenames);
2141 for(max=i=0;i<t->numfilenames;i++)
2143 k = (int)strlen(t->filenames[i]);
2153 for(i=0;i<t->numfilenames;i++)
2155 int lumpofs = 0, lumplen = 0;
2156 char *entities = NULL;
2157 const char *data = NULL;
2159 char entfilename[MAX_QPATH];
2162 strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2164 f = FS_OpenVirtualFile(t->filenames[i], true);
2167 strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2168 memset(buf, 0, 1024);
2169 FS_Read(f, buf, 1024);
2170 if (!memcmp(buf, "IBSP", 4))
2172 p = LittleLong(((int *)buf)[1]);
2173 if (p == Q3BSPVERSION)
2175 q3dheader_t *header = (q3dheader_t *)buf;
2176 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2177 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2178 dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2180 else if (p == Q2BSPVERSION)
2182 q2dheader_t *header = (q2dheader_t *)buf;
2183 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2184 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2185 dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2188 dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2190 else if (BuffLittleLong(buf) == BSPVERSION)
2192 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2193 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2194 dpsnprintf(desc, sizeof(desc), "BSP29");
2196 else if (BuffLittleLong(buf) == 30)
2198 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2199 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2200 dpsnprintf(desc, sizeof(desc), "BSPHL");
2202 else if (!memcmp(buf, "BSP2", 4))
2204 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2205 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2206 dpsnprintf(desc, sizeof(desc), "BSP2");
2208 else if (!memcmp(buf, "2PSB", 4))
2210 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2211 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2212 dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2214 else if(!memcmp(buf, "VBSP", 4))
2216 hl2dheader_t *header = (hl2dheader_t *)buf;
2217 lumpofs = LittleLong(header->lumps[HL2LUMP_ENTITIES].fileofs);
2218 lumplen = LittleLong(header->lumps[HL2LUMP_ENTITIES].filelen);
2219 dpsnprintf(desc, sizeof(desc), "VBSP%i", LittleLong(((int *)buf)[1]));
2222 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2223 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2224 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2225 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2226 if (!entities && lumplen >= 10)
2228 FS_Seek(f, lumpofs, SEEK_SET);
2229 entities = (char *)Z_Malloc(lumplen + 1);
2230 FS_Read(f, entities, lumplen);
2234 // if there are entities to parse, a missing message key just
2235 // means there is no title, so clear the message string now
2241 if (!COM_ParseToken_Simple(&data, false, false, true))
2243 if (com_token[0] == '{')
2245 if (com_token[0] == '}')
2247 // skip leading whitespace
2248 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2249 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2250 keyname[l] = com_token[k+l];
2252 if (!COM_ParseToken_Simple(&data, false, false, true))
2254 if (developer_extra.integer)
2255 Con_DPrintf("key: %s %s\n", keyname, com_token);
2256 if (!strcmp(keyname, "message"))
2258 // get the message contents
2259 strlcpy(message, com_token, sizeof(message));
2269 *(t->filenames[i]+len[i]+5) = 0;
2270 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2275 k = *(t->filenames[0]+5+p);
2278 for(i=1;i<t->numfilenames;i++)
2279 if(*(t->filenames[i]+5+p) != k)
2283 if(p > o && completedname && completednamebufferlength > 0)
2285 memset(completedname, 0, completednamebufferlength);
2286 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2296 New function for tab-completion system
2297 Added by EvilTypeGuy
2298 MEGA Thanks to Taniwha
2301 void Con_DisplayList(const char **list)
2303 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2304 const char **walk = list;
2307 len = (int)strlen(*walk);
2315 len = (int)strlen(*list);
2316 if (pos + maxlen >= width) {
2322 for (i = 0; i < (maxlen - len); i++)
2334 // Now it becomes TRICKY :D --blub
2335 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2336 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2337 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2338 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
2339 static int Nicks_matchpos;
2341 // co against <<:BLASTER:>> is true!?
2342 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2346 if(tolower(*a) == tolower(*b))
2360 return (*a < *b) ? -1 : 1;
2364 return (*a < *b) ? -1 : 1;
2368 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2371 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2373 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2374 return Nicks_strncasecmp_nospaces(a, b, a_len);
2375 return strncasecmp(a, b, a_len);
2378 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2380 // ignore non alphanumerics of B
2381 // if A contains a non-alphanumeric, B must contain it as well though!
2384 qbool alnum_a, alnum_b;
2386 if(tolower(*a) == tolower(*b))
2388 if(*a == 0) // end of both strings, they're equal
2395 // not equal, end of one string?
2400 // ignore non alphanumerics
2401 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2402 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2403 if(!alnum_a) // b must contain this
2404 return (*a < *b) ? -1 : 1;
2407 // otherwise, both are alnum, they're just not equal, return the appropriate number
2409 return (*a < *b) ? -1 : 1;
2415 /* Nicks_CompleteCountPossible
2417 Count the number of possible nicks to complete
2419 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qbool isCon)
2421 char name[MAX_SCOREBOARDNAME];
2427 if(!con_nickcompletion.integer)
2430 // changed that to 1
2431 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2434 for(i = 0; i < cl.maxclients; ++i)
2437 if(!cl.scores[p].name[0])
2440 SanitizeString(cl.scores[p].name, name);
2441 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2447 spos = pos - 1; // no need for a minimum of characters :)
2451 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2453 if(!(isCon && spos == 1)) // console start
2459 if(isCon && spos == 0)
2461 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2467 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2468 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2470 // the sanitized list
2471 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2474 Nicks_matchpos = match;
2477 Nicks_offset[count] = s - (&line[match]);
2478 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2485 static void Cmd_CompleteNicksPrint(int count)
2488 for(i = 0; i < count; ++i)
2489 Con_Printf("%s\n", Nicks_list[i]);
2492 static void Nicks_CutMatchesNormal(int count)
2494 // cut match 0 down to the longest possible completion
2497 c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2498 for(i = 1; i < count; ++i)
2500 l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2504 for(l = 0; l <= c; ++l)
2505 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2511 Nicks_sanlist[0][c+1] = 0;
2512 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2515 static unsigned int Nicks_strcleanlen(const char *s)
2520 if( (*s >= 'a' && *s <= 'z') ||
2521 (*s >= 'A' && *s <= 'Z') ||
2522 (*s >= '0' && *s <= '9') ||
2530 static void Nicks_CutMatchesAlphaNumeric(int count)
2532 // cut match 0 down to the longest possible completion
2535 char tempstr[sizeof(Nicks_sanlist[0])];
2537 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2539 c = (unsigned int)strlen(Nicks_sanlist[0]);
2540 for(i = 0, l = 0; i < (int)c; ++i)
2542 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2543 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2544 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2546 tempstr[l++] = Nicks_sanlist[0][i];
2551 for(i = 1; i < count; ++i)
2554 b = Nicks_sanlist[i];
2564 if(tolower(*a) == tolower(*b))
2570 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2572 // b is alnum, so cut
2579 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2580 Nicks_CutMatchesNormal(count);
2581 //if(!Nicks_sanlist[0][0])
2582 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2584 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2585 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2589 static void Nicks_CutMatchesNoSpaces(int count)
2591 // cut match 0 down to the longest possible completion
2594 char tempstr[sizeof(Nicks_sanlist[0])];
2597 c = (unsigned int)strlen(Nicks_sanlist[0]);
2598 for(i = 0, l = 0; i < (int)c; ++i)
2600 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2602 tempstr[l++] = Nicks_sanlist[0][i];
2607 for(i = 1; i < count; ++i)
2610 b = Nicks_sanlist[i];
2620 if(tolower(*a) == tolower(*b))
2634 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2635 Nicks_CutMatchesNormal(count);
2636 //if(!Nicks_sanlist[0][0])
2637 //Con_Printf("TS: %s\n", tempstr);
2638 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2640 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2641 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2645 static void Nicks_CutMatches(int count)
2647 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2648 Nicks_CutMatchesAlphaNumeric(count);
2649 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2650 Nicks_CutMatchesNoSpaces(count);
2652 Nicks_CutMatchesNormal(count);
2655 static const char **Nicks_CompleteBuildList(int count)
2659 // the list is freed by Con_CompleteCommandLine, so create a char**
2660 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2662 for(; bpos < count; ++bpos)
2663 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2665 Nicks_CutMatches(count);
2673 Restores the previous used color, after the autocompleted name.
2675 static int Nicks_AddLastColor(char *buffer, int pos)
2677 qbool quote_added = false;
2679 int color = STRING_COLOR_DEFAULT + '0';
2680 char r = 0, g = 0, b = 0;
2682 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2684 // we'll have to add a quote :)
2685 buffer[pos++] = '\"';
2689 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2691 // add color when no quote was added, or when flags &4?
2693 for(match = Nicks_matchpos-1; match >= 0; --match)
2695 if(buffer[match] == STRING_COLOR_TAG)
2697 if( isdigit(buffer[match+1]) )
2699 color = buffer[match+1];
2702 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2704 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2706 r = buffer[match+2];
2707 g = buffer[match+3];
2708 b = buffer[match+4];
2717 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2719 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2720 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2723 buffer[pos++] = STRING_COLOR_TAG;
2726 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2732 buffer[pos++] = color;
2738 Con_CompleteCommandLine
2740 New function for tab-completion system
2741 Added by EvilTypeGuy
2742 Thanks to Fett erich@heintz.com
2744 Enhanced to tab-complete map names by [515]
2747 int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console)
2749 const char *text = "";
2751 const char **list[4] = {0, 0, 0, 0};
2754 int c, v, a, i, cmd_len, pos, k;
2755 int n; // nicks --blub
2756 const char *space, *patterns;
2760 int linestart, linepos;
2761 unsigned int linesize;
2765 linepos = key_linepos;
2766 linesize = sizeof(key_line);
2772 linepos = chat_bufferpos;
2773 linesize = sizeof(chat_buffer);
2777 //find what we want to complete
2779 while(--pos >= linestart)
2782 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2788 strlcpy(s2, line + linepos, sizeof(s2)); //save chars after cursor
2789 line[linepos] = 0; //hide them
2791 c = v = a = n = cmd_len = 0;
2795 space = strchr(line + 1, ' ');
2796 if(space && pos == (space - line) + 1)
2798 strlcpy(command, line + 1, min(sizeof(command), (unsigned int)(space - line)));
2800 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?
2801 if(patterns && !*patterns)
2802 patterns = NULL; // get rid of the empty string
2804 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2808 if (GetMapList(s, t, sizeof(t)))
2810 // first move the cursor
2811 linepos += (int)strlen(t) - (int)strlen(s);
2813 // and now do the actual work
2815 strlcat(line, t, MAX_INPUTLINE);
2816 strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2818 // and fix the cursor
2819 if(linepos > (int) strlen(line))
2820 linepos = (int) strlen(line);
2829 stringlist_t resultbuf, dirbuf;
2832 // // store completion patterns (space separated) for command foo in con_completion_foo
2833 // set con_completion_foo "foodata/*.foodefault *.foo"
2836 // Note: patterns with slash are always treated as absolute
2837 // patterns; patterns without slash search in the innermost
2838 // directory the user specified. There is no way to "complete into"
2839 // a directory as of now, as directories seem to be unknown to the
2843 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2844 // set con_completion_playdemo "*.dem"
2845 // set con_completion_play "*.wav *.ogg"
2847 // TODO somehow add support for directories; these shall complete
2848 // to their name + an appended slash.
2850 stringlistinit(&resultbuf);
2851 stringlistinit(&dirbuf);
2852 while(COM_ParseToken_Simple(&patterns, false, false, true))
2855 if(strchr(com_token, '/'))
2857 search = FS_Search(com_token, true, true, NULL);
2861 const char *slash = strrchr(s, '/');
2864 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2865 strlcat(t, com_token, sizeof(t));
2866 search = FS_Search(t, true, true, NULL);
2869 search = FS_Search(com_token, true, true, NULL);
2873 for(i = 0; i < search->numfilenames; ++i)
2874 if(!strncmp(search->filenames[i], s, strlen(s)))
2875 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2876 stringlistappend(&resultbuf, search->filenames[i]);
2877 FS_FreeSearch(search);
2881 // In any case, add directory names
2884 const char *slash = strrchr(s, '/');
2887 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2888 strlcat(t, "*", sizeof(t));
2889 search = FS_Search(t, true, true, NULL);
2892 search = FS_Search("*", true, true, NULL);
2895 for(i = 0; i < search->numfilenames; ++i)
2896 if(!strncmp(search->filenames[i], s, strlen(s)))
2897 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2898 stringlistappend(&dirbuf, search->filenames[i]);
2899 FS_FreeSearch(search);
2903 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2906 unsigned int matchchars;
2907 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2909 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2912 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2914 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2918 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2919 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2920 for(i = 0; i < dirbuf.numstrings; ++i)
2922 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2924 for(i = 0; i < resultbuf.numstrings; ++i)
2926 Con_Printf("%s\n", resultbuf.strings[i]);
2928 matchchars = sizeof(t) - 1;
2929 if(resultbuf.numstrings > 0)
2931 p = resultbuf.strings[0];
2932 q = resultbuf.strings[resultbuf.numstrings - 1];
2933 for(; *p && *p == *q; ++p, ++q);
2934 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2936 if(dirbuf.numstrings > 0)
2938 p = dirbuf.strings[0];
2939 q = dirbuf.strings[dirbuf.numstrings - 1];
2940 for(; *p && *p == *q; ++p, ++q);
2941 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2943 // now p points to the first non-equal character, or to the end
2944 // of resultbuf.strings[0]. We want to append the characters
2945 // from resultbuf.strings[0] to (not including) p as these are
2946 // the unique prefix
2947 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2950 // first move the cursor
2951 linepos += (int)strlen(t) - (int)strlen(s);
2953 // and now do the actual work
2955 strlcat(line, t, MAX_INPUTLINE);
2956 strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2958 // and fix the cursor
2959 if(linepos > (int) strlen(line))
2960 linepos = (int) strlen(line);
2962 stringlistfreecontents(&resultbuf);
2963 stringlistfreecontents(&dirbuf);
2965 return linepos; // bail out, when we complete for a command that wants a file name
2970 // Count number of possible matches and print them
2971 c = Cmd_CompleteCountPossible(cmd, s);
2974 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2975 Cmd_CompleteCommandPrint(cmd, s);
2977 v = Cvar_CompleteCountPossible(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2980 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2981 Cvar_CompleteCvarPrint(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2983 a = Cmd_CompleteAliasCountPossible(cmd, s);
2986 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2987 Cmd_CompleteAliasPrint(cmd, s);
2991 n = Nicks_CompleteCountPossible(line, linepos, s, is_console);
2994 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2995 Cmd_CompleteNicksPrint(n);
2998 if (!(c + v + a + n)) // No possible matches
3001 strlcpy(&line[linepos], s2, linesize - linepos);
3006 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
3008 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
3010 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
3014 text = *(list[3] = Nicks_CompleteBuildList(n));
3016 text = *(Nicks_CompleteBuildList(n));
3019 for (cmd_len = (int)strlen(s);;cmd_len++)
3022 for (i = 0; i < 3; i++)
3024 for (l = list[i];*l;l++)
3025 if ((*l)[cmd_len] != text[cmd_len])
3027 // all possible matches share this character, so we continue...
3030 // if all matches ended at the same position, stop
3031 // (this means there is only one match)
3037 // prevent a buffer overrun by limiting cmd_len according to remaining space
3038 cmd_len = min(cmd_len, (int)linesize - 1 - pos);
3042 memcpy(&line[linepos], text, cmd_len);
3044 // if there is only one match, add a space after it
3045 if (c + v + a + n == 1 && linepos < (int)linesize - 1)
3048 { // was a nick, might have an offset, and needs colors ;) --blub
3049 linepos = pos - Nicks_offset[0];
3050 cmd_len = (int)strlen(Nicks_list[0]);
3051 cmd_len = min(cmd_len, (int)linesize - 3 - pos);
3053 memcpy(&line[linepos] , Nicks_list[0], cmd_len);
3055 if(linepos < (int)(linesize - 7)) // space for color code (^[0-9] or ^xrgb), space and \0
3056 linepos = Nicks_AddLastColor(line, linepos);
3058 line[linepos++] = ' ';
3062 // use strlcat to avoid a buffer overrun
3064 strlcat(line, s2, linesize);
3069 // free the command, cvar, and alias lists
3070 for (i = 0; i < 4; i++)
3072 Mem_Free((void *)list[i]);