2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 #if !defined(WIN32) || defined(__MINGW32__)
33 float con_cursorspeed = 4;
35 // lines up from bottom to display
39 void *con_mutex = NULL;
41 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
42 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
43 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
45 cvar_t con_notifytime = {CF_CLIENT | CF_ARCHIVE, "con_notifytime","3", "how long notify lines last, in seconds"};
46 cvar_t con_notify = {CF_CLIENT | CF_ARCHIVE, "con_notify","4", "how many notify lines to show"};
47 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)"};
49 cvar_t con_chattime = {CF_CLIENT | CF_ARCHIVE, "con_chattime","30", "how long chat lines last, in seconds"};
50 cvar_t con_chat = {CF_CLIENT | CF_ARCHIVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
51 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)"};
52 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"};
53 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)"};
54 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)"};
55 cvar_t con_chatwidth = {CF_CLIENT | CF_ARCHIVE, "con_chatwidth","1.0", "relative chat window width"};
56 cvar_t con_textsize = {CF_CLIENT | CF_ARCHIVE, "con_textsize","8", "console text size in virtual 2D pixels"};
57 cvar_t con_notifysize = {CF_CLIENT | CF_ARCHIVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
58 cvar_t con_chatsize = {CF_CLIENT | CF_ARCHIVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
59 cvar_t con_chatsound = {CF_CLIENT | CF_ARCHIVE, "con_chatsound","1", "enables chat sound to play on message"};
60 cvar_t con_chatsound_file = {CF_CLIENT, "con_chatsound_file","sound/misc/talk.wav", "The sound to play for chat messages"};
61 cvar_t con_chatsound_team_file = {CF_CLIENT, "con_chatsound_team_file","sound/misc/talk2.wav", "The sound to play for team chat messages"};
62 cvar_t con_chatsound_team_mask = {CF_CLIENT, "con_chatsound_team_mask","40","Magic ASCII code that denotes a team chat message"};
64 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)"};
66 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)"};
68 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)"};
72 cvar_t con_nickcompletion = {CF_CLIENT | CF_ARCHIVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
73 cvar_t con_nickcompletion_flags = {CF_CLIENT | CF_ARCHIVE, "con_nickcompletion_flags", "11", "Bitfield: "
74 "0: add nothing after completion. "
75 "1: add the last color after completion. "
76 "2: add a quote when starting a quote instead of the color. "
77 "4: will replace 1, will force color, even after a quote. "
78 "8: ignore non-alphanumerics. "
79 "16: ignore spaces. "};
80 #define NICKS_ADD_COLOR 1
81 #define NICKS_ADD_QUOTE 2
82 #define NICKS_FORCE_COLOR 4
83 #define NICKS_ALPHANUMERICS_ONLY 8
84 #define NICKS_NO_SPACES 16
86 cvar_t con_completion_playdemo = {CF_CLIENT | CF_ARCHIVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
87 cvar_t con_completion_timedemo = {CF_CLIENT | CF_ARCHIVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
88 cvar_t con_completion_exec = {CF_CLIENT | CF_ARCHIVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
90 cvar_t condump_stripcolors = {CF_CLIENT | CF_SERVER| CF_ARCHIVE, "condump_stripcolors", "0", "strip color codes from console dumps"};
92 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"};
93 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"};
94 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"};
95 cvar_t rcon_address = {CF_CLIENT, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"};
100 qbool con_initialized;
102 // used for server replies to rcon command
103 lhnetsocket_t *rcon_redirect_sock = NULL;
104 lhnetaddress_t *rcon_redirect_dest = NULL;
105 int rcon_redirect_bufferpos = 0;
106 char rcon_redirect_buffer[1400];
107 qbool rcon_redirect_proquakeprotocol = false;
109 // generic functions for console buffers
111 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
114 buf->textsize = textsize;
115 buf->text = (char *) Mem_Alloc(mempool, textsize);
116 buf->maxlines = maxlines;
117 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
118 buf->lines_first = 0;
119 buf->lines_count = 0;
122 /*! The translation table between the graphical font and plain ASCII --KB */
123 static char qfont_table[256] = {
124 '\0', '#', '#', '#', '#', '.', '#', '#',
125 '#', 9, 10, '#', ' ', 13, '.', '.',
126 '[', ']', '0', '1', '2', '3', '4', '5',
127 '6', '7', '8', '9', '.', '<', '=', '>',
128 ' ', '!', '"', '#', '$', '%', '&', '\'',
129 '(', ')', '*', '+', ',', '-', '.', '/',
130 '0', '1', '2', '3', '4', '5', '6', '7',
131 '8', '9', ':', ';', '<', '=', '>', '?',
132 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
133 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
134 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
135 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
136 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
137 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
138 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
139 'x', 'y', 'z', '{', '|', '}', '~', '<',
141 '<', '=', '>', '#', '#', '.', '#', '#',
142 '#', '#', ' ', '#', ' ', '>', '.', '.',
143 '[', ']', '0', '1', '2', '3', '4', '5',
144 '6', '7', '8', '9', '.', '<', '=', '>',
145 ' ', '!', '"', '#', '$', '%', '&', '\'',
146 '(', ')', '*', '+', ',', '-', '.', '/',
147 '0', '1', '2', '3', '4', '5', '6', '7',
148 '8', '9', ':', ';', '<', '=', '>', '?',
149 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
150 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
151 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
152 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
153 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
154 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
155 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
156 'x', 'y', 'z', '{', '|', '}', '~', '<'
160 SanitizeString strips color tags from the string in
161 and writes the result on string out
163 static void SanitizeString(char *in, char *out)
167 if(*in == STRING_COLOR_TAG)
172 out[0] = STRING_COLOR_TAG;
176 else if (*in >= '0' && *in <= '9') // ^[0-9] found
183 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
186 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
188 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
195 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
200 else if (*in != STRING_COLOR_TAG)
203 *out = qfont_table[*(unsigned char*)in];
215 void ConBuffer_Clear (conbuffer_t *buf)
217 buf->lines_count = 0;
225 void ConBuffer_Shutdown(conbuffer_t *buf)
231 Mem_Free(buf->lines);
240 Notifies the console code about the current time
241 (and shifts back times of other entries when the time
245 void ConBuffer_FixTimes(conbuffer_t *buf)
248 if(buf->lines_count >= 1)
250 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
253 for(i = 0; i < buf->lines_count; ++i)
254 CONBUFFER_LINES(buf, i).addtime += diff;
263 Deletes the first line from the console history.
266 void ConBuffer_DeleteLine(conbuffer_t *buf)
268 if(buf->lines_count == 0)
271 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
276 ConBuffer_DeleteLastLine
278 Deletes the last line from the console history.
281 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
283 if(buf->lines_count == 0)
292 Checks if there is space for a line of the given length, and if yes, returns a
293 pointer to the start of such a space, and NULL otherwise.
296 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
298 if(len > buf->textsize)
300 if(buf->lines_count == 0)
304 char *firstline_start = buf->lines[buf->lines_first].start;
305 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
306 // the buffer is cyclic, so we first have two cases...
307 if(firstline_start < lastline_onepastend) // buffer is contiguous
310 if(len <= buf->text + buf->textsize - lastline_onepastend)
311 return lastline_onepastend;
313 else if(len <= firstline_start - buf->text)
318 else // buffer has a contiguous hole
320 if(len <= firstline_start - lastline_onepastend)
321 return lastline_onepastend;
332 Appends a given string as a new line to the console.
335 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
340 // developer_memory 1 during shutdown prints while conbuffer_t is being freed
344 ConBuffer_FixTimes(buf);
346 if(len >= buf->textsize)
349 // only display end of line.
350 line += len - buf->textsize + 1;
351 len = buf->textsize - 1;
353 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
354 ConBuffer_DeleteLine(buf);
355 memcpy(putpos, line, len);
359 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
361 p = &CONBUFFER_LINES_LAST(buf);
364 p->addtime = cl.time;
366 p->height = -1; // calculate when needed
369 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
373 start = buf->lines_count;
374 for(i = start - 1; i >= 0; --i)
376 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
378 if((l->mask & mask_must) != mask_must)
380 if(l->mask & mask_mustnot)
389 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
391 static char copybuf[MAX_INPUTLINE]; // client only
392 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
393 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
394 strlcpy(copybuf, l->start, sz);
399 ==============================================================================
403 ==============================================================================
408 cvar_t log_file = {CF_CLIENT | CF_SERVER, "log_file", "", "filename to log messages to"};
409 cvar_t log_file_stripcolors = {CF_CLIENT | CF_SERVER, "log_file_stripcolors", "0", "strip color codes from log messages"};
410 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"};
411 char log_dest_buffer[1400]; // UDP packet
412 size_t log_dest_buffer_pos;
413 unsigned int log_dest_buffer_appending;
414 char crt_log_file [MAX_OSPATH] = "";
415 qfile_t* logfile = NULL;
417 unsigned char* logqueue = NULL;
419 size_t logq_size = 0;
421 void Log_ConPrint (const char *msg);
423 static void Log_DestBuffer_Init(void)
425 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
426 log_dest_buffer_pos = 5;
429 static void Log_DestBuffer_Flush_NoLock(void)
431 lhnetaddress_t log_dest_addr;
432 lhnetsocket_t *log_dest_socket;
433 const char *s = log_dest_udp.string;
434 qbool have_opened_temp_sockets = false;
435 if(s) if(log_dest_buffer_pos > 5)
437 ++log_dest_buffer_appending;
438 log_dest_buffer[log_dest_buffer_pos++] = 0;
440 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
442 have_opened_temp_sockets = true;
443 NetConn_OpenServerPorts(true);
446 while(COM_ParseToken_Console(&s))
447 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
449 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
451 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
453 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
456 if(have_opened_temp_sockets)
457 NetConn_CloseServerPorts();
458 --log_dest_buffer_appending;
460 log_dest_buffer_pos = 0;
468 void Log_DestBuffer_Flush(void)
471 Thread_LockMutex(con_mutex);
472 Log_DestBuffer_Flush_NoLock();
474 Thread_UnlockMutex(con_mutex);
477 static const char* Log_Timestamp (const char *desc)
479 static char timestamp [128]; // init/shutdown only
486 char timestring [64];
488 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
491 localtime_s (&crt_tm, &crt_time);
492 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
494 crt_tm = localtime (&crt_time);
495 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
499 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
501 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
506 static void Log_Open (void)
508 if (logfile != NULL || log_file.string[0] == '\0')
511 logfile = FS_OpenRealFile(log_file.string, "a", false);
514 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
515 FS_Print (logfile, Log_Timestamp ("Log started"));
524 void Log_Close (void)
529 FS_Print (logfile, Log_Timestamp ("Log stopped"));
530 FS_Print (logfile, "\n");
534 crt_log_file[0] = '\0';
543 void Log_Start (void)
549 // Dump the contents of the log queue into the log file and free it
550 if (logqueue != NULL)
552 unsigned char *temp = logqueue;
557 FS_Write (logfile, temp, logq_ind);
558 if(*log_dest_udp.string)
560 for(pos = 0; pos < logq_ind; )
562 if(log_dest_buffer_pos == 0)
563 Log_DestBuffer_Init();
564 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
565 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
566 log_dest_buffer_pos += n;
567 Log_DestBuffer_Flush_NoLock();
585 void Log_ConPrint (const char *msg)
587 static qbool inprogress = false;
589 // don't allow feedback loops with memory error reports
594 // Until the host is completely initialized, we maintain a log queue
595 // to store the messages, since the log can't be started before
596 if (logqueue != NULL)
598 size_t remain = logq_size - logq_ind;
599 size_t len = strlen (msg);
601 // If we need to enlarge the log queue
604 size_t factor = ((logq_ind + len) / logq_size) + 1;
605 unsigned char* newqueue;
608 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
609 memcpy (newqueue, logqueue, logq_ind);
612 remain = logq_size - logq_ind;
614 memcpy (&logqueue[logq_ind], msg, len);
621 // Check if log_file has changed
622 if (strcmp (crt_log_file, log_file.string) != 0)
628 // If a log file is available
631 if (log_file_stripcolors.integer)
634 size_t len = strlen(msg);
635 char* sanitizedmsg = (char*)Mem_Alloc(tempmempool, len + 1);
636 memcpy (sanitizedmsg, msg, len);
637 SanitizeString(sanitizedmsg, sanitizedmsg); // SanitizeString's in pointer is always ahead of the out pointer, so this should work.
638 FS_Print (logfile, sanitizedmsg);
639 Mem_Free(sanitizedmsg);
643 FS_Print (logfile, msg);
656 void Log_Printf (const char *logfilename, const char *fmt, ...)
660 file = FS_OpenRealFile(logfilename, "a", true);
665 va_start (argptr, fmt);
666 FS_VPrintf (file, fmt, argptr);
675 ==============================================================================
679 ==============================================================================
687 void Con_ToggleConsole_f(cmd_state_t *cmd)
689 if (Sys_CheckParm ("-noconsole"))
690 if (!(key_consoleactive & KEY_CONSOLEACTIVE_USER))
691 return; // only allow the key bind to turn off console
693 // toggle the 'user wants console' bit
694 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
703 void Con_ClearNotify (void)
706 for(i = 0; i < CON_LINES_COUNT; ++i)
707 if(!(CON_LINES(i).mask & CON_MASK_CHAT))
708 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
717 static void Con_MessageMode_f(cmd_state_t *cmd)
719 key_dest = key_message;
720 chat_mode = 0; // "say"
721 if(Cmd_Argc(cmd) > 1)
723 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
724 chat_bufferpos = (unsigned int)strlen(chat_buffer);
734 static void Con_MessageMode2_f(cmd_state_t *cmd)
736 key_dest = key_message;
737 chat_mode = 1; // "say_team"
738 if(Cmd_Argc(cmd) > 1)
740 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
741 chat_bufferpos = (unsigned int)strlen(chat_buffer);
750 static void Con_CommandMode_f(cmd_state_t *cmd)
752 key_dest = key_message;
753 if(Cmd_Argc(cmd) > 1)
755 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
756 chat_bufferpos = (unsigned int)strlen(chat_buffer);
758 chat_mode = -1; // command
766 void Con_CheckResize (void)
771 f = bound(1, con_textsize.value, 128);
772 if(f != con_textsize.value)
773 Cvar_SetValueQuick(&con_textsize, f);
774 width = (int)floor(vid_conwidth.value / con_textsize.value);
775 width = bound(1, width, con.textsize/4);
776 // FIXME uses con in a non abstracted way
778 if (width == con_linewidth)
781 con_linewidth = width;
783 for(i = 0; i < CON_LINES_COUNT; ++i)
784 CON_LINES(i).height = -1; // recalculate when next needed
790 //[515]: the simplest command ever
791 //LadyHavoc: not so simple after I made it print usage...
792 static void Con_Maps_f(cmd_state_t *cmd)
794 if (Cmd_Argc(cmd) > 2)
796 Con_Printf("usage: maps [mapnameprefix]\n");
799 else if (Cmd_Argc(cmd) == 2)
800 GetMapList(Cmd_Argv(cmd, 1), NULL, 0);
802 GetMapList("", NULL, 0);
805 static void Con_ConDump_f(cmd_state_t *cmd)
809 if (Cmd_Argc(cmd) != 2)
811 Con_Printf("usage: condump <filename>\n");
814 file = FS_OpenRealFile(Cmd_Argv(cmd, 1), "w", false);
817 Con_Printf(CON_ERROR "condump: unable to write file \"%s\"\n", Cmd_Argv(cmd, 1));
820 if (con_mutex) Thread_LockMutex(con_mutex);
821 for(i = 0; i < CON_LINES_COUNT; ++i)
823 if (condump_stripcolors.integer)
826 size_t len = CON_LINES(i).len;
827 char* sanitizedmsg = (char*)Mem_Alloc(tempmempool, len + 1);
828 memcpy (sanitizedmsg, CON_LINES(i).start, len);
829 SanitizeString(sanitizedmsg, sanitizedmsg); // SanitizeString's in pointer is always ahead of the out pointer, so this should work.
830 FS_Write(file, sanitizedmsg, strlen(sanitizedmsg));
831 Mem_Free(sanitizedmsg);
835 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
837 FS_Write(file, "\n", 1);
839 if (con_mutex) Thread_UnlockMutex(con_mutex);
843 void Con_Clear_f(cmd_state_t *cmd)
845 if (con_mutex) Thread_LockMutex(con_mutex);
846 ConBuffer_Clear(&con);
847 if (con_mutex) Thread_UnlockMutex(con_mutex);
850 static void Con_RCon_ClearPassword_c(cvar_t *var)
852 // whenever rcon_secure is changed to 0, clear rcon_password for
853 // security reasons (prevents a send-rcon-password-as-plaintext
854 // attack based on NQ protocol session takeover and svc_stufftext)
855 if(var->integer <= 0)
856 Cvar_SetQuick(&rcon_password, "");
867 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
868 if (Thread_HasThreads())
869 con_mutex = Thread_CreateMutex();
871 // Allocate a log queue, this will be freed after configs are parsed
872 logq_size = MAX_INPUTLINE;
873 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
876 Cvar_RegisterVariable (&sys_colortranslation);
877 Cvar_RegisterVariable (&sys_specialcharactertranslation);
879 Cvar_RegisterVariable (&log_file);
880 Cvar_RegisterVariable (&log_file_stripcolors);
881 Cvar_RegisterVariable (&log_dest_udp);
883 // support for the classic Quake option
884 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
885 if (Sys_CheckParm ("-condebug") != 0)
886 Cvar_SetQuick (&log_file, "qconsole.log");
888 // register our cvars
889 Cvar_RegisterVariable (&con_chat);
890 Cvar_RegisterVariable (&con_chatpos);
891 Cvar_RegisterVariable (&con_chatrect_x);
892 Cvar_RegisterVariable (&con_chatrect_y);
893 Cvar_RegisterVariable (&con_chatrect);
894 Cvar_RegisterVariable (&con_chatsize);
895 Cvar_RegisterVariable (&con_chattime);
896 Cvar_RegisterVariable (&con_chatwidth);
897 Cvar_RegisterVariable (&con_notify);
898 Cvar_RegisterVariable (&con_notifyalign);
899 Cvar_RegisterVariable (&con_notifysize);
900 Cvar_RegisterVariable (&con_notifytime);
901 Cvar_RegisterVariable (&con_textsize);
902 Cvar_RegisterVariable (&con_chatsound);
903 Cvar_RegisterVariable (&con_chatsound_file);
904 Cvar_RegisterVariable (&con_chatsound_team_file);
905 Cvar_RegisterVariable (&con_chatsound_team_mask);
908 Cvar_RegisterVariable (&con_nickcompletion);
909 Cvar_RegisterVariable (&con_nickcompletion_flags);
911 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
912 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
913 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
915 Cvar_RegisterVariable (&condump_stripcolors);
917 Cvar_RegisterVariable(&rcon_address);
918 Cvar_RegisterVariable(&rcon_secure);
919 Cvar_RegisterCallback(&rcon_secure, Con_RCon_ClearPassword_c);
920 Cvar_RegisterVariable(&rcon_secure_challengetimeout);
921 Cvar_RegisterVariable(&rcon_password);
923 // register our commands
924 Cmd_AddCommand(CF_CLIENT, "toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
925 Cmd_AddCommand(CF_CLIENT, "messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
926 Cmd_AddCommand(CF_CLIENT, "messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
927 Cmd_AddCommand(CF_CLIENT, "commandmode", Con_CommandMode_f, "input a console command");
928 Cmd_AddCommand(CF_SHARED, "clear", Con_Clear_f, "clear console history");
929 Cmd_AddCommand(CF_SHARED, "maps", Con_Maps_f, "list information about available maps");
930 Cmd_AddCommand(CF_SHARED, "condump", Con_ConDump_f, "output console history to a file (see also log_file)");
932 con_initialized = true;
933 // initialize console window (only used by sys_win.c)
936 Con_Print("Console initialized.\n");
939 void Con_Shutdown (void)
941 if (con_mutex) Thread_LockMutex(con_mutex);
942 ConBuffer_Shutdown(&con);
943 if (con_mutex) Thread_UnlockMutex(con_mutex);
944 if (con_mutex) Thread_DestroyMutex(con_mutex);con_mutex = NULL;
951 Handles cursor positioning, line wrapping, etc
952 All console printing must go through this in order to be displayed
953 If no console is visible, the notify window will pop up.
956 static void Con_PrintToHistory(const char *txt, int mask)
959 // \n goes to next line
960 // \r deletes current line and makes a new one
962 static int cr_pending = 0;
963 static char buf[CON_TEXTSIZE]; // con_mutex
964 static int bufpos = 0;
966 if(!con.text) // FIXME uses a non-abstracted property of con
973 ConBuffer_DeleteLastLine(&con);
981 ConBuffer_AddLine(&con, buf, bufpos, mask);
986 ConBuffer_AddLine(&con, buf, bufpos, mask);
990 buf[bufpos++] = *txt;
991 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
993 ConBuffer_AddLine(&con, buf, bufpos, mask);
1001 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qbool proquakeprotocol)
1003 rcon_redirect_sock = sock;
1004 rcon_redirect_dest = dest;
1005 rcon_redirect_proquakeprotocol = proquakeprotocol;
1006 if (rcon_redirect_proquakeprotocol)
1008 // reserve space for the packet header
1009 rcon_redirect_buffer[0] = 0;
1010 rcon_redirect_buffer[1] = 0;
1011 rcon_redirect_buffer[2] = 0;
1012 rcon_redirect_buffer[3] = 0;
1013 // this is a reply to a CCREQ_RCON
1014 rcon_redirect_buffer[4] = (unsigned char)CCREP_RCON;
1017 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1018 rcon_redirect_bufferpos = 5;
1021 static void Con_Rcon_Redirect_Flush(void)
1023 if(rcon_redirect_sock)
1025 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
1026 if (rcon_redirect_proquakeprotocol)
1028 // update the length in the packet header
1029 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
1031 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
1033 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1034 rcon_redirect_bufferpos = 5;
1035 rcon_redirect_proquakeprotocol = false;
1038 void Con_Rcon_Redirect_End(void)
1040 Con_Rcon_Redirect_Flush();
1041 rcon_redirect_dest = NULL;
1042 rcon_redirect_sock = NULL;
1045 void Con_Rcon_Redirect_Abort(void)
1047 rcon_redirect_dest = NULL;
1048 rcon_redirect_sock = NULL;
1056 /// Adds a character to the rcon buffer.
1057 static void Con_Rcon_AddChar(int c)
1059 if(log_dest_buffer_appending)
1061 ++log_dest_buffer_appending;
1063 // if this print is in response to an rcon command, add the character
1064 // to the rcon redirect buffer
1066 if (rcon_redirect_dest)
1068 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
1069 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
1070 Con_Rcon_Redirect_Flush();
1072 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
1074 if(log_dest_buffer_pos == 0)
1075 Log_DestBuffer_Init();
1076 log_dest_buffer[log_dest_buffer_pos++] = c;
1077 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
1078 Log_DestBuffer_Flush_NoLock();
1081 log_dest_buffer_pos = 0;
1083 --log_dest_buffer_appending;
1087 * Convert an RGB color to its nearest quake color.
1088 * I'll cheat on this a bit by translating the colors to HSV first,
1089 * S and V decide if it's black or white, otherwise, H will decide the
1091 * @param _r Red (0-255)
1092 * @param _g Green (0-255)
1093 * @param _b Blue (0-255)
1094 * @return A quake color character.
1096 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
1098 float r = ((float)_r)/255.0;
1099 float g = ((float)_g)/255.0;
1100 float b = ((float)_b)/255.0;
1101 float min = min(r, min(g, b));
1102 float max = max(r, max(g, b));
1104 int h; ///< Hue angle [0,360]
1105 float s; ///< Saturation [0,1]
1106 float v = max; ///< In HSV v == max [0,1]
1111 s = 1.0 - (min/max);
1113 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1116 // If the value is less than half, return a black color code.
1117 // Otherwise return a white one.
1123 // Let's get the hue angle to define some colors:
1127 h = (int)(60.0 * (g-b)/(max-min))%360;
1129 h = (int)(60.0 * (b-r)/(max-min) + 120);
1130 else // if(max == b) redundant check
1131 h = (int)(60.0 * (r-g)/(max-min) + 240);
1133 if(h < 36) // *red* to orange
1135 else if(h < 80) // orange over *yellow* to evilish-bright-green
1137 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1139 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1141 else if(h < 270) // darkish blue over *dark blue* to cool purple
1143 else if(h < 330) // cool purple over *purple* to ugly swiny red
1145 else // ugly red to red closes the circly
1154 extern cvar_t timestamps;
1155 extern cvar_t timeformat;
1156 extern qbool sys_nostdout;
1157 void Con_MaskPrint(int additionalmask, const char *msg)
1159 static int mask = 0;
1160 static int index = 0;
1161 static char line[MAX_INPUTLINE];
1164 Thread_LockMutex(con_mutex);
1168 Con_Rcon_AddChar(*msg);
1169 // if this is the beginning of a new line, print timestamp
1172 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1174 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1175 line[index++] = STRING_COLOR_TAG;
1176 // assert( STRING_COLOR_DEFAULT < 10 )
1177 line[index++] = STRING_COLOR_DEFAULT + '0';
1178 // special color codes for chat messages must always come first
1179 // for Con_PrintToHistory to work properly
1180 if (*msg == 1 || *msg == 2 || *msg == 3)
1185 if (con_chatsound.value)
1187 if(msg[1] == con_chatsound_team_mask.integer && cl.foundteamchatsound)
1188 S_LocalSound (con_chatsound_team_file.string);
1190 S_LocalSound (con_chatsound_file.string);
1193 // Send to chatbox for say/tell (1) and messages (3)
1194 // 3 is just so that a message can be sent to the chatbox without a sound.
1195 if (*msg == 1 || *msg == 3)
1196 mask = CON_MASK_CHAT;
1198 line[index++] = STRING_COLOR_TAG;
1199 line[index++] = '3';
1201 Con_Rcon_AddChar(*msg);
1204 for (;*timestamp;index++, timestamp++)
1205 if (index < (int)sizeof(line) - 2)
1206 line[index] = *timestamp;
1208 mask |= additionalmask;
1210 // append the character
1211 line[index++] = *msg;
1212 // if this is a newline character, we have a complete line to print
1213 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1215 // terminate the line
1219 // send to scrollable buffer
1220 if (con_initialized && cls.state != ca_dedicated)
1222 Con_PrintToHistory(line, mask);
1224 // send to terminal or dedicated server window
1226 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1228 if(sys_specialcharactertranslation.integer)
1235 int ch = u8_getchar(p, &q);
1236 if(ch >= 0xE000 && ch <= 0xE0FF && ((unsigned char) qfont_table[ch - 0xE000]) >= 0x20)
1238 *p = qfont_table[ch - 0xE000];
1240 memmove(p+1, q, strlen(q)+1);
1248 if(sys_colortranslation.integer == 1) // ANSI
1250 static char printline[MAX_INPUTLINE * 4 + 3];
1251 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1252 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1257 for(in = line, out = printline; *in; ++in)
1261 case STRING_COLOR_TAG:
1262 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1264 char r = tolower(in[2]);
1265 char g = tolower(in[3]);
1266 char b = tolower(in[4]);
1267 // it's a hex digit already, so the else part needs no check --blub
1268 if(isdigit(r)) r -= '0';
1270 if(isdigit(g)) g -= '0';
1272 if(isdigit(b)) b -= '0';
1275 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1276 in += 3; // 3 only, the switch down there does the fourth
1283 case STRING_COLOR_TAG:
1285 *out++ = STRING_COLOR_TAG;
1291 if(lastcolor == 0) break; else lastcolor = 0;
1292 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1297 if(lastcolor == 1) break; else lastcolor = 1;
1298 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1303 if(lastcolor == 2) break; else lastcolor = 2;
1304 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1309 if(lastcolor == 3) break; else lastcolor = 3;
1310 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1315 if(lastcolor == 4) break; else lastcolor = 4;
1316 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1321 if(lastcolor == 5) break; else lastcolor = 5;
1322 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1327 if(lastcolor == 6) break; else lastcolor = 6;
1328 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1333 // bold normal color
1335 if(lastcolor == 8) break; else lastcolor = 8;
1336 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1339 *out++ = STRING_COLOR_TAG;
1346 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1363 Sys_PrintToTerminal(printline);
1365 else if(sys_colortranslation.integer == 2) // Quake
1367 Sys_PrintToTerminal(line);
1371 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1374 for(in = line, out = printline; *in; ++in)
1378 case STRING_COLOR_TAG:
1381 case STRING_COLOR_RGB_TAG_CHAR:
1382 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1387 *out++ = STRING_COLOR_TAG;
1388 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1391 case STRING_COLOR_TAG:
1393 *out++ = STRING_COLOR_TAG;
1408 *out++ = STRING_COLOR_TAG;
1418 Sys_PrintToTerminal(printline);
1421 // empty the line buffer
1428 Thread_UnlockMutex(con_mutex);
1436 void Con_MaskPrintf(int mask, const char *fmt, ...)
1439 char msg[MAX_INPUTLINE];
1441 va_start(argptr,fmt);
1442 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1445 Con_MaskPrint(mask, msg);
1453 void Con_Print(const char *msg)
1455 Con_MaskPrint(CON_MASK_PRINT, msg);
1463 void Con_Printf(const char *fmt, ...)
1466 char msg[MAX_INPUTLINE];
1468 va_start(argptr,fmt);
1469 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1472 Con_MaskPrint(CON_MASK_PRINT, msg);
1480 void Con_DPrint(const char *msg)
1482 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1485 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1493 void Con_DPrintf(const char *fmt, ...)
1496 char msg[MAX_INPUTLINE];
1498 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1501 va_start(argptr,fmt);
1502 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1505 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1510 ==============================================================================
1514 ==============================================================================
1521 It draws either the console input line or the chat input line (if is_console is false)
1522 The input line scrolls horizontally if typing goes beyond the right edge
1524 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1527 static void Con_DrawInput(qbool is_console, float x, float v, float inputsize)
1529 int y, i, col_out, linepos, text_start, prefix_start = 0;
1530 char text[MAX_INPUTLINE + 5 + 9 + 1]; // space for ^xRGB, "say_team:" and \0
1536 if (is_console && !key_consoleactive)
1537 return; // don't draw anything
1541 // empty prefix because ] is part of the console edit line
1543 strlcpy(text, key_line, sizeof(text));
1544 linepos = key_linepos;
1552 prefix = "say_team:";
1555 strlcpy(text, chat_buffer, sizeof(text));
1556 linepos = chat_bufferpos;
1560 y = (int)strlen(text);
1562 // make the color code visible when the cursor is inside it
1563 if(text[linepos] != 0)
1565 for(i=1; i < 5 && linepos - i > 0; ++i)
1566 if(text[linepos-i] == STRING_COLOR_TAG)
1568 int caret_pos, ofs = 0;
1569 caret_pos = linepos - i;
1570 if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG)
1572 else if(i == 1 && isdigit(text[caret_pos+1]))
1574 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]))
1576 if(ofs && (size_t)(y + ofs + 1) < sizeof(text))
1579 while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG)
1583 // str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing)
1584 // str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing)
1585 memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos);
1586 text[caret_pos + ofs] = STRING_COLOR_TAG;
1598 x += DrawQ_TextWidth(prefix, 0, inputsize, inputsize, false, fnt);
1605 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, inputsize, inputsize, &col_out, false, fnt, 1000000000);
1607 text_start = x + (vid_conwidth.value - x) * 0.95 - xo; // scroll
1610 else if (!is_console)
1611 prefix_start -= (x - text_start);
1614 DrawQ_String(prefix_start, v, prefix, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1616 DrawQ_String(text_start, v, text, y + 3, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1618 // draw a cursor on top of this
1619 if ((int)(host.realtime*con_cursorspeed) & 1) // cursor is visible
1621 if (!utf8_enable.integer)
1623 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1631 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1632 memcpy(text, curbuf, len);
1635 DrawQ_String(text_start + xo, v, text, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, fnt);
1642 float alignment; // 0 = left, 0.5 = center, 1 = right
1648 const char *continuationString;
1651 int colorindex; // init to -1
1655 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1657 con_text_info_t *ti = (con_text_info_t *) passthrough;
1660 ti->colorindex = -1;
1661 return ti->fontsize * ti->font->maxwidth;
1664 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1665 else if(maxWidth == -1)
1666 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1669 Sys_PrintfToTerminal("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1670 // Note: this is NOT a Con_Printf, as it could print recursively
1675 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1681 (void) isContinuation;
1685 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1687 con_text_info_t *ti = (con_text_info_t *) passthrough;
1689 if(ti->y < ti->ymin - 0.001)
1691 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1695 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1696 if(isContinuation && *ti->continuationString)
1697 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);
1699 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);
1702 ti->y += ti->fontsize;
1706 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)
1710 int maxlines = (int) floor(height / fontsize + 0.01f);
1713 int continuationWidth = 0;
1715 double t = cl.time; // saved so it won't change
1718 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1719 ti.fontsize = fontsize;
1720 ti.alignment = alignment_x;
1723 ti.ymax = y + height;
1724 ti.continuationString = continuationString;
1727 Con_WordWidthFunc(&ti, NULL, &len, -1);
1728 len = strlen(continuationString);
1729 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &len, -1);
1731 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1732 startidx = CON_LINES_COUNT;
1733 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1735 con_lineinfo_t *l = &CON_LINES(i);
1738 if((l->mask & mask_must) != mask_must)
1740 if(l->mask & mask_mustnot)
1742 if(maxage && (l->addtime < t - maxage))
1746 // Calculate its actual height...
1747 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1748 if(lines + mylines >= maxlines)
1750 nskip = lines + mylines - maxlines;
1759 // then center according to the calculated amount of lines...
1761 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1763 // then actually draw
1764 for(i = startidx; i < CON_LINES_COUNT; ++i)
1766 con_lineinfo_t *l = &CON_LINES(i);
1768 if((l->mask & mask_must) != mask_must)
1770 if(l->mask & mask_mustnot)
1772 if(maxage && (l->addtime < t - maxage))
1775 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1785 Draws the last few lines of output transparently over the game top
1788 void Con_DrawNotify (void)
1791 float chatstart, notifystart, inputsize, height;
1796 if (con_mutex) Thread_LockMutex(con_mutex);
1797 ConBuffer_FixTimes(&con);
1799 numChatlines = con_chat.integer;
1801 chatpos = con_chatpos.integer;
1803 if (con_notify.integer < 0)
1804 Cvar_SetValueQuick(&con_notify, 0);
1805 if (gamemode == GAME_TRANSFUSION)
1806 v = 8; // vertical offset
1810 // GAME_NEXUIZ: center, otherwise left justify
1811 align = con_notifyalign.value;
1812 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1814 if(IS_OLDNEXUIZ_DERIVED(gamemode))
1818 if(numChatlines || !con_chatrect.integer)
1822 // first chat, input line, then notify
1824 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1826 else if(chatpos > 0)
1828 // first notify, then (chatpos-1) empty lines, then chat, then input
1830 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1832 else // if(chatpos < 0)
1834 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1836 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1841 // just notify and input
1843 chatstart = 0; // shut off gcc warning
1846 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, "");
1848 if(con_chatrect.integer)
1850 x = con_chatrect_x.value * vid_conwidth.value;
1851 v = con_chatrect_y.value * vid_conheight.value;
1856 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1859 height = numChatlines * con_chatsize.value;
1863 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 ... ");
1866 if (key_dest == key_message)
1868 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1869 Con_DrawInput(false, x, v, inputsize);
1874 if (con_mutex) Thread_UnlockMutex(con_mutex);
1881 Returns the height of a given console line; calculates it if necessary.
1884 static int Con_LineHeight(int lineno)
1886 con_lineinfo_t *li = &CON_LINES(lineno);
1887 if(li->height == -1)
1889 float width = vid_conwidth.value;
1891 ti.fontsize = con_textsize.value;
1892 ti.font = FONT_CONSOLE;
1893 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1902 Draws a line of the console; returns its height in lines.
1903 If alpha is 0, the line is not drawn, but still wrapped and its height
1907 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1909 float width = vid_conwidth.value;
1911 con_lineinfo_t *li = &CON_LINES(lineno);
1913 if((li->mask & mask_must) != mask_must)
1915 if((li->mask & mask_mustnot) != 0)
1918 ti.continuationString = "";
1920 ti.fontsize = con_textsize.value;
1921 ti.font = FONT_CONSOLE;
1923 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1928 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1935 Calculates the last visible line index and how much to show of it based on
1939 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1944 if(con_backscroll < 0)
1949 // now count until we saw con_backscroll actual lines
1950 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1951 if((CON_LINES(i).mask & mask_must) == mask_must)
1952 if((CON_LINES(i).mask & mask_mustnot) == 0)
1954 int h = Con_LineHeight(i);
1956 // line is the last visible line?
1958 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1960 *limitlast = lines_seen + h - con_backscroll;
1967 // if we get here, no line was on screen - scroll so that one line is
1969 con_backscroll = lines_seen - 1;
1977 Draws the console with the solid background
1978 The typing input line at the bottom should only be drawn if typing is allowed
1981 void Con_DrawConsole (int lines)
1983 float alpha, alpha0;
1986 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1987 cachepic_t *conbackpic;
1988 unsigned int conbackflags;
1993 if (con_mutex) Thread_LockMutex(con_mutex);
1995 if (con_backscroll < 0)
1998 con_vislines = lines;
2000 r_draw2d_force = true;
2002 // draw the background
2003 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
2004 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
2006 sx = scr_conscroll_x.value;
2007 sy = scr_conscroll_y.value;
2008 conbackflags = CACHEPICFLAG_FAILONMISSING; // So console is readable when game content is missing
2009 if (sx != 0 || sy != 0)
2010 conbackflags &= CACHEPICFLAG_NOCLAMP;
2011 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", conbackflags) : NULL;
2012 sx *= host.realtime; sy *= host.realtime;
2013 sx -= floor(sx); sy -= floor(sy);
2014 if (Draw_IsPicLoaded(conbackpic))
2015 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2016 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2017 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2018 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2019 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2022 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
2024 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
2026 sx = scr_conscroll2_x.value;
2027 sy = scr_conscroll2_y.value;
2028 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2029 sx *= host.realtime; sy *= host.realtime;
2030 sx -= floor(sx); sy -= floor(sy);
2031 if(Draw_IsPicLoaded(conbackpic))
2032 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2033 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2034 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2035 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2036 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2039 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
2041 sx = scr_conscroll3_x.value;
2042 sy = scr_conscroll3_y.value;
2043 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2044 sx *= host.realtime; sy *= host.realtime;
2045 sx -= floor(sx); sy -= floor(sy);
2046 if(Draw_IsPicLoaded(conbackpic))
2047 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2048 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2049 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2050 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2051 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2054 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);
2060 int count = CON_LINES_COUNT;
2061 float ymax = con_vislines - 2 * con_textsize.value;
2062 float y = ymax + con_textsize.value * con_backscroll;
2063 for (i = 0;i < count && y >= 0;i++)
2064 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
2065 // fix any excessive scrollback for the next frame
2066 if (i >= count && y >= 0)
2068 con_backscroll -= (int)(y / con_textsize.value);
2069 if (con_backscroll < 0)
2074 if(CON_LINES_COUNT > 0)
2076 int i, last, limitlast;
2078 float ymax = con_vislines - 2 * con_textsize.value;
2079 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2080 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2081 y = ymax - con_textsize.value;
2084 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
2089 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
2091 break; // top of console buffer
2093 break; // top of console window
2100 // draw the input prompt, user text, and cursor if desired
2101 Con_DrawInput(true, 0, con_vislines - con_textsize.value * 2, con_textsize.value);
2103 r_draw2d_force = false;
2104 if (con_mutex) Thread_UnlockMutex(con_mutex);
2111 Prints not only map filename, but also
2112 its format (q1/q2/q3/hl) and even its message
2114 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2115 //LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2116 //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
2117 //LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2118 qbool GetMapList (const char *s, char *completedname, int completednamebufferlength)
2122 int i, k, max, p, o, min;
2125 unsigned char buf[1024];
2127 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2128 t = FS_Search(message, 1, true, NULL);
2131 if (t->numfilenames > 1)
2132 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2133 len = (unsigned char *)Z_Malloc(t->numfilenames);
2135 for(max=i=0;i<t->numfilenames;i++)
2137 k = (int)strlen(t->filenames[i]);
2147 for(i=0;i<t->numfilenames;i++)
2149 int lumpofs = 0, lumplen = 0;
2150 char *entities = NULL;
2151 const char *data = NULL;
2153 char entfilename[MAX_QPATH];
2156 strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2158 f = FS_OpenVirtualFile(t->filenames[i], true);
2161 strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2162 memset(buf, 0, 1024);
2163 FS_Read(f, buf, 1024);
2164 if (!memcmp(buf, "IBSP", 4))
2166 p = LittleLong(((int *)buf)[1]);
2167 if (p == Q3BSPVERSION)
2169 q3dheader_t *header = (q3dheader_t *)buf;
2170 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2171 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2172 dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2174 else if (p == Q2BSPVERSION)
2176 q2dheader_t *header = (q2dheader_t *)buf;
2177 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2178 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2179 dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2182 dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2184 else if (BuffLittleLong(buf) == BSPVERSION)
2186 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2187 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2188 dpsnprintf(desc, sizeof(desc), "BSP29");
2190 else if (BuffLittleLong(buf) == 30)
2192 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2193 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2194 dpsnprintf(desc, sizeof(desc), "BSPHL");
2196 else if (!memcmp(buf, "BSP2", 4))
2198 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2199 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2200 dpsnprintf(desc, sizeof(desc), "BSP2");
2202 else if (!memcmp(buf, "2PSB", 4))
2204 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2205 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2206 dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2208 else if(!memcmp(buf, "VBSP", 4))
2210 hl2dheader_t *header = (hl2dheader_t *)buf;
2211 lumpofs = LittleLong(header->lumps[HL2LUMP_ENTITIES].fileofs);
2212 lumplen = LittleLong(header->lumps[HL2LUMP_ENTITIES].filelen);
2213 dpsnprintf(desc, sizeof(desc), "VBSP%i", LittleLong(((int *)buf)[1]));
2216 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2217 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2218 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2219 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2220 if (!entities && lumplen >= 10)
2222 FS_Seek(f, lumpofs, SEEK_SET);
2223 entities = (char *)Z_Malloc(lumplen + 1);
2224 FS_Read(f, entities, lumplen);
2228 // if there are entities to parse, a missing message key just
2229 // means there is no title, so clear the message string now
2235 if (!COM_ParseToken_Simple(&data, false, false, true))
2237 if (com_token[0] == '{')
2239 if (com_token[0] == '}')
2241 // skip leading whitespace
2242 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2243 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2244 keyname[l] = com_token[k+l];
2246 if (!COM_ParseToken_Simple(&data, false, false, true))
2248 if (developer_extra.integer)
2249 Con_DPrintf("key: %s %s\n", keyname, com_token);
2250 if (!strcmp(keyname, "message"))
2252 // get the message contents
2253 strlcpy(message, com_token, sizeof(message));
2263 *(t->filenames[i]+len[i]+5) = 0;
2264 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2269 k = *(t->filenames[0]+5+p);
2272 for(i=1;i<t->numfilenames;i++)
2273 if(*(t->filenames[i]+5+p) != k)
2277 if(p > o && completedname && completednamebufferlength > 0)
2279 memset(completedname, 0, completednamebufferlength);
2280 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2290 New function for tab-completion system
2291 Added by EvilTypeGuy
2292 MEGA Thanks to Taniwha
2295 void Con_DisplayList(const char **list)
2297 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2298 const char **walk = list;
2301 len = (int)strlen(*walk);
2309 len = (int)strlen(*list);
2310 if (pos + maxlen >= width) {
2316 for (i = 0; i < (maxlen - len); i++)
2328 // Now it becomes TRICKY :D --blub
2329 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2330 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2331 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2332 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
2333 static int Nicks_matchpos;
2335 // co against <<:BLASTER:>> is true!?
2336 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2340 if(tolower(*a) == tolower(*b))
2354 return (*a < *b) ? -1 : 1;
2358 return (*a < *b) ? -1 : 1;
2362 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2365 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2367 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2368 return Nicks_strncasecmp_nospaces(a, b, a_len);
2369 return strncasecmp(a, b, a_len);
2372 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2374 // ignore non alphanumerics of B
2375 // if A contains a non-alphanumeric, B must contain it as well though!
2378 qbool alnum_a, alnum_b;
2380 if(tolower(*a) == tolower(*b))
2382 if(*a == 0) // end of both strings, they're equal
2389 // not equal, end of one string?
2394 // ignore non alphanumerics
2395 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2396 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2397 if(!alnum_a) // b must contain this
2398 return (*a < *b) ? -1 : 1;
2401 // otherwise, both are alnum, they're just not equal, return the appropriate number
2403 return (*a < *b) ? -1 : 1;
2409 /* Nicks_CompleteCountPossible
2411 Count the number of possible nicks to complete
2413 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qbool isCon)
2415 char name[MAX_SCOREBOARDNAME];
2421 if(!con_nickcompletion.integer)
2424 // changed that to 1
2425 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2428 for(i = 0; i < cl.maxclients; ++i)
2431 if(!cl.scores[p].name[0])
2434 SanitizeString(cl.scores[p].name, name);
2435 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2441 spos = pos - 1; // no need for a minimum of characters :)
2445 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2447 if(!(isCon && spos == 1)) // console start
2453 if(isCon && spos == 0)
2455 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2461 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2462 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2464 // the sanitized list
2465 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2468 Nicks_matchpos = match;
2471 Nicks_offset[count] = s - (&line[match]);
2472 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2479 static void Cmd_CompleteNicksPrint(int count)
2482 for(i = 0; i < count; ++i)
2483 Con_Printf("%s\n", Nicks_list[i]);
2486 static void Nicks_CutMatchesNormal(int count)
2488 // cut match 0 down to the longest possible completion
2491 c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2492 for(i = 1; i < count; ++i)
2494 l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2498 for(l = 0; l <= c; ++l)
2499 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2505 Nicks_sanlist[0][c+1] = 0;
2506 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2509 static unsigned int Nicks_strcleanlen(const char *s)
2514 if( (*s >= 'a' && *s <= 'z') ||
2515 (*s >= 'A' && *s <= 'Z') ||
2516 (*s >= '0' && *s <= '9') ||
2524 static void Nicks_CutMatchesAlphaNumeric(int count)
2526 // cut match 0 down to the longest possible completion
2529 char tempstr[sizeof(Nicks_sanlist[0])];
2531 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2533 c = (unsigned int)strlen(Nicks_sanlist[0]);
2534 for(i = 0, l = 0; i < (int)c; ++i)
2536 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2537 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2538 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2540 tempstr[l++] = Nicks_sanlist[0][i];
2545 for(i = 1; i < count; ++i)
2548 b = Nicks_sanlist[i];
2558 if(tolower(*a) == tolower(*b))
2564 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2566 // b is alnum, so cut
2573 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2574 Nicks_CutMatchesNormal(count);
2575 //if(!Nicks_sanlist[0][0])
2576 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2578 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2579 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2583 static void Nicks_CutMatchesNoSpaces(int count)
2585 // cut match 0 down to the longest possible completion
2588 char tempstr[sizeof(Nicks_sanlist[0])];
2591 c = (unsigned int)strlen(Nicks_sanlist[0]);
2592 for(i = 0, l = 0; i < (int)c; ++i)
2594 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2596 tempstr[l++] = Nicks_sanlist[0][i];
2601 for(i = 1; i < count; ++i)
2604 b = Nicks_sanlist[i];
2614 if(tolower(*a) == tolower(*b))
2628 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2629 Nicks_CutMatchesNormal(count);
2630 //if(!Nicks_sanlist[0][0])
2631 //Con_Printf("TS: %s\n", tempstr);
2632 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2634 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2635 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2639 static void Nicks_CutMatches(int count)
2641 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2642 Nicks_CutMatchesAlphaNumeric(count);
2643 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2644 Nicks_CutMatchesNoSpaces(count);
2646 Nicks_CutMatchesNormal(count);
2649 static const char **Nicks_CompleteBuildList(int count)
2653 // the list is freed by Con_CompleteCommandLine, so create a char**
2654 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2656 for(; bpos < count; ++bpos)
2657 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2659 Nicks_CutMatches(count);
2667 Restores the previous used color, after the autocompleted name.
2669 static int Nicks_AddLastColor(char *buffer, int pos)
2671 qbool quote_added = false;
2673 int color = STRING_COLOR_DEFAULT + '0';
2674 char r = 0, g = 0, b = 0;
2676 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2678 // we'll have to add a quote :)
2679 buffer[pos++] = '\"';
2683 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2685 // add color when no quote was added, or when flags &4?
2687 for(match = Nicks_matchpos-1; match >= 0; --match)
2689 if(buffer[match] == STRING_COLOR_TAG)
2691 if( isdigit(buffer[match+1]) )
2693 color = buffer[match+1];
2696 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2698 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2700 r = buffer[match+2];
2701 g = buffer[match+3];
2702 b = buffer[match+4];
2711 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2713 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2714 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2717 buffer[pos++] = STRING_COLOR_TAG;
2720 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2726 buffer[pos++] = color;
2732 Con_CompleteCommandLine
2734 New function for tab-completion system
2735 Added by EvilTypeGuy
2736 Thanks to Fett erich@heintz.com
2738 Enhanced to tab-complete map names by [515]
2741 int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console)
2743 const char *text = "";
2745 const char **list[4] = {0, 0, 0, 0};
2748 int c, v, a, i, cmd_len, pos, k;
2749 int n; // nicks --blub
2750 const char *space, *patterns;
2754 int linestart, linepos;
2755 unsigned int linesize;
2759 linepos = key_linepos;
2760 linesize = sizeof(key_line);
2766 linepos = chat_bufferpos;
2767 linesize = sizeof(chat_buffer);
2771 //find what we want to complete
2773 while(--pos >= linestart)
2776 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2782 strlcpy(s2, line + linepos, sizeof(s2)); //save chars after cursor
2783 line[linepos] = 0; //hide them
2785 c = v = a = n = cmd_len = 0;
2789 space = strchr(line + 1, ' ');
2790 if(space && pos == (space - line) + 1)
2792 strlcpy(command, line + 1, min(sizeof(command), (unsigned int)(space - line)));
2794 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?
2795 if(patterns && !*patterns)
2796 patterns = NULL; // get rid of the empty string
2798 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2802 if (GetMapList(s, t, sizeof(t)))
2804 // first move the cursor
2805 linepos += (int)strlen(t) - (int)strlen(s);
2807 // and now do the actual work
2809 strlcat(line, t, MAX_INPUTLINE);
2810 strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2812 // and fix the cursor
2813 if(linepos > (int) strlen(line))
2814 linepos = (int) strlen(line);
2823 stringlist_t resultbuf, dirbuf;
2826 // // store completion patterns (space separated) for command foo in con_completion_foo
2827 // set con_completion_foo "foodata/*.foodefault *.foo"
2830 // Note: patterns with slash are always treated as absolute
2831 // patterns; patterns without slash search in the innermost
2832 // directory the user specified. There is no way to "complete into"
2833 // a directory as of now, as directories seem to be unknown to the
2837 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2838 // set con_completion_playdemo "*.dem"
2839 // set con_completion_play "*.wav *.ogg"
2841 // TODO somehow add support for directories; these shall complete
2842 // to their name + an appended slash.
2844 stringlistinit(&resultbuf);
2845 stringlistinit(&dirbuf);
2846 while(COM_ParseToken_Simple(&patterns, false, false, true))
2849 if(strchr(com_token, '/'))
2851 search = FS_Search(com_token, true, true, NULL);
2855 const char *slash = strrchr(s, '/');
2858 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2859 strlcat(t, com_token, sizeof(t));
2860 search = FS_Search(t, true, true, NULL);
2863 search = FS_Search(com_token, true, true, NULL);
2867 for(i = 0; i < search->numfilenames; ++i)
2868 if(!strncmp(search->filenames[i], s, strlen(s)))
2869 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2870 stringlistappend(&resultbuf, search->filenames[i]);
2871 FS_FreeSearch(search);
2875 // In any case, add directory names
2878 const char *slash = strrchr(s, '/');
2881 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2882 strlcat(t, "*", sizeof(t));
2883 search = FS_Search(t, true, true, NULL);
2886 search = FS_Search("*", true, true, NULL);
2889 for(i = 0; i < search->numfilenames; ++i)
2890 if(!strncmp(search->filenames[i], s, strlen(s)))
2891 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2892 stringlistappend(&dirbuf, search->filenames[i]);
2893 FS_FreeSearch(search);
2897 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2900 unsigned int matchchars;
2901 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2903 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2906 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2908 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2912 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2913 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2914 for(i = 0; i < dirbuf.numstrings; ++i)
2916 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2918 for(i = 0; i < resultbuf.numstrings; ++i)
2920 Con_Printf("%s\n", resultbuf.strings[i]);
2922 matchchars = sizeof(t) - 1;
2923 if(resultbuf.numstrings > 0)
2925 p = resultbuf.strings[0];
2926 q = resultbuf.strings[resultbuf.numstrings - 1];
2927 for(; *p && *p == *q; ++p, ++q);
2928 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2930 if(dirbuf.numstrings > 0)
2932 p = dirbuf.strings[0];
2933 q = dirbuf.strings[dirbuf.numstrings - 1];
2934 for(; *p && *p == *q; ++p, ++q);
2935 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2937 // now p points to the first non-equal character, or to the end
2938 // of resultbuf.strings[0]. We want to append the characters
2939 // from resultbuf.strings[0] to (not including) p as these are
2940 // the unique prefix
2941 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2944 // first move the cursor
2945 linepos += (int)strlen(t) - (int)strlen(s);
2947 // and now do the actual work
2949 strlcat(line, t, MAX_INPUTLINE);
2950 strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2952 // and fix the cursor
2953 if(linepos > (int) strlen(line))
2954 linepos = (int) strlen(line);
2956 stringlistfreecontents(&resultbuf);
2957 stringlistfreecontents(&dirbuf);
2959 return linepos; // bail out, when we complete for a command that wants a file name
2964 // Count number of possible matches and print them
2965 c = Cmd_CompleteCountPossible(cmd, s);
2968 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2969 Cmd_CompleteCommandPrint(cmd, s);
2971 v = Cvar_CompleteCountPossible(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2974 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2975 Cvar_CompleteCvarPrint(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2977 a = Cmd_CompleteAliasCountPossible(cmd, s);
2980 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2981 Cmd_CompleteAliasPrint(cmd, s);
2985 n = Nicks_CompleteCountPossible(line, linepos, s, is_console);
2988 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2989 Cmd_CompleteNicksPrint(n);
2992 if (!(c + v + a + n)) // No possible matches
2995 strlcpy(&line[linepos], s2, linesize - linepos);
3000 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
3002 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
3004 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
3008 text = *(list[3] = Nicks_CompleteBuildList(n));
3010 text = *(Nicks_CompleteBuildList(n));
3013 for (cmd_len = (int)strlen(s);;cmd_len++)
3016 for (i = 0; i < 3; i++)
3018 for (l = list[i];*l;l++)
3019 if ((*l)[cmd_len] != text[cmd_len])
3021 // all possible matches share this character, so we continue...
3024 // if all matches ended at the same position, stop
3025 // (this means there is only one match)
3031 // prevent a buffer overrun by limiting cmd_len according to remaining space
3032 cmd_len = min(cmd_len, (int)linesize - 1 - pos);
3036 memcpy(&line[linepos], text, cmd_len);
3038 // if there is only one match, add a space after it
3039 if (c + v + a + n == 1 && linepos < (int)linesize - 1)
3042 { // was a nick, might have an offset, and needs colors ;) --blub
3043 linepos = pos - Nicks_offset[0];
3044 cmd_len = (int)strlen(Nicks_list[0]);
3045 cmd_len = min(cmd_len, (int)linesize - 3 - pos);
3047 memcpy(&line[linepos] , Nicks_list[0], cmd_len);
3049 if(linepos < (int)(linesize - 7)) // space for color code (^[0-9] or ^xrgb), space and \0
3050 linepos = Nicks_AddLastColor(line, linepos);
3052 line[linepos++] = ' ';
3056 // use strlcat to avoid a buffer overrun
3058 strlcat(line, s2, linesize);
3063 // free the command, cvar, and alias lists
3064 for (i = 0; i < 4; i++)
3066 Mem_Free((void *)list[i]);