2 Copyright (C) 1996-1997 Id Software, Inc.
3 Copyright (C) 2000-2020 DarkPlaces contributors
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 See the GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #if !defined(WIN32) || defined(__MINGW32__)
34 float con_cursorspeed = 4;
36 // lines up from bottom to display
40 void *con_mutex = NULL;
42 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
43 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
44 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
46 cvar_t con_notifytime = {CF_CLIENT | CF_ARCHIVE, "con_notifytime","3", "how long notify lines last, in seconds"};
47 cvar_t con_notify = {CF_CLIENT | CF_ARCHIVE, "con_notify","4", "how many notify lines to show"};
48 cvar_t con_notifyalign = {CF_CLIENT | CF_ARCHIVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
50 cvar_t con_chattime = {CF_CLIENT | CF_ARCHIVE, "con_chattime","30", "how long chat lines last, in seconds"};
51 cvar_t con_chat = {CF_CLIENT | CF_ARCHIVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
52 cvar_t con_chatpos = {CF_CLIENT | CF_ARCHIVE, "con_chatpos","0", "where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top)"};
53 cvar_t con_chatrect = {CF_CLIENT | CF_ARCHIVE, "con_chatrect","0", "use con_chatrect_x and _y to position con_notify and con_chat freely instead of con_chatpos"};
54 cvar_t con_chatrect_x = {CF_CLIENT | CF_ARCHIVE, "con_chatrect_x","", "where to put chat, relative x coordinate of left edge on screen (use con_chatwidth for width)"};
55 cvar_t con_chatrect_y = {CF_CLIENT | CF_ARCHIVE, "con_chatrect_y","", "where to put chat, relative y coordinate of top edge on screen (use con_chat for line count)"};
56 cvar_t con_chatwidth = {CF_CLIENT | CF_ARCHIVE, "con_chatwidth","1.0", "relative chat window width"};
57 cvar_t con_textsize = {CF_CLIENT | CF_ARCHIVE, "con_textsize","8", "console text size in virtual 2D pixels"};
58 cvar_t con_notifysize = {CF_CLIENT | CF_ARCHIVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
59 cvar_t con_chatsize = {CF_CLIENT | CF_ARCHIVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
60 cvar_t con_chatsound = {CF_CLIENT | CF_ARCHIVE, "con_chatsound","1", "enables chat sound to play on message"};
61 cvar_t con_chatsound_file = {CF_CLIENT, "con_chatsound_file","sound/misc/talk.wav", "The sound to play for chat messages"};
62 cvar_t con_chatsound_team_file = {CF_CLIENT, "con_chatsound_team_file","sound/misc/talk2.wav", "The sound to play for team chat messages"};
63 cvar_t con_chatsound_team_mask = {CF_CLIENT, "con_chatsound_team_mask","40","Magic ASCII code that denotes a team chat message"};
65 cvar_t sys_specialcharactertranslation = {CF_CLIENT | CF_SERVER, "sys_specialcharactertranslation", "1", "terminal console conchars to ASCII translation (set to 0 if your conchars.tga is for an 8bit character set or if you want raw output)"};
67 cvar_t sys_colortranslation = {CF_CLIENT | CF_SERVER, "sys_colortranslation", "0", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
69 cvar_t sys_colortranslation = {CF_CLIENT | CF_SERVER, "sys_colortranslation", "1", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
73 cvar_t con_nickcompletion = {CF_CLIENT | CF_ARCHIVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
74 cvar_t con_nickcompletion_flags = {CF_CLIENT | CF_ARCHIVE, "con_nickcompletion_flags", "11", "Bitfield: "
75 "0: add nothing after completion. "
76 "1: add the last color after completion. "
77 "2: add a quote when starting a quote instead of the color. "
78 "4: will replace 1, will force color, even after a quote. "
79 "8: ignore non-alphanumerics. "
80 "16: ignore spaces. "};
81 #define NICKS_ADD_COLOR 1
82 #define NICKS_ADD_QUOTE 2
83 #define NICKS_FORCE_COLOR 4
84 #define NICKS_ALPHANUMERICS_ONLY 8
85 #define NICKS_NO_SPACES 16
87 cvar_t con_completion_playdemo = {CF_CLIENT | CF_ARCHIVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
88 cvar_t con_completion_timedemo = {CF_CLIENT | CF_ARCHIVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
89 cvar_t con_completion_exec = {CF_CLIENT | CF_ARCHIVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
91 cvar_t condump_stripcolors = {CF_CLIENT | CF_SERVER| CF_ARCHIVE, "condump_stripcolors", "0", "strip color codes from console dumps"};
93 cvar_t rcon_password = {CF_CLIENT | CF_SERVER | CF_PRIVATE, "rcon_password", "", "password to authenticate rcon commands; NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"};
94 cvar_t rcon_secure = {CF_CLIENT | CF_SERVER, "rcon_secure", "0", "force secure rcon authentication (1 = time based, 2 = challenge based); NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password"};
95 cvar_t rcon_secure_challengetimeout = {CF_CLIENT, "rcon_secure_challengetimeout", "5", "challenge-based secure rcon: time out requests if no challenge came within this time interval"};
96 cvar_t rcon_address = {CF_CLIENT, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"};
101 qbool con_initialized;
103 // used for server replies to rcon command
104 lhnetsocket_t *rcon_redirect_sock = NULL;
105 lhnetaddress_t *rcon_redirect_dest = NULL;
106 int rcon_redirect_bufferpos = 0;
107 char rcon_redirect_buffer[1400];
108 qbool rcon_redirect_proquakeprotocol = false;
110 // generic functions for console buffers
112 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
115 buf->textsize = textsize;
116 buf->text = (char *) Mem_Alloc(mempool, textsize);
117 buf->maxlines = maxlines;
118 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
119 buf->lines_first = 0;
120 buf->lines_count = 0;
123 /*! The translation table between the graphical font and plain ASCII --KB */
124 static char qfont_table[256] = {
125 '\0', '#', '#', '#', '#', '.', '#', '#',
126 '#', 9, 10, '#', ' ', 13, '.', '.',
127 '[', ']', '0', '1', '2', '3', '4', '5',
128 '6', '7', '8', '9', '.', '<', '=', '>',
129 ' ', '!', '"', '#', '$', '%', '&', '\'',
130 '(', ')', '*', '+', ',', '-', '.', '/',
131 '0', '1', '2', '3', '4', '5', '6', '7',
132 '8', '9', ':', ';', '<', '=', '>', '?',
133 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
134 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
135 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
136 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
137 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
138 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
139 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
140 'x', 'y', 'z', '{', '|', '}', '~', '<',
142 '<', '=', '>', '#', '#', '.', '#', '#',
143 '#', '#', ' ', '#', ' ', '>', '.', '.',
144 '[', ']', '0', '1', '2', '3', '4', '5',
145 '6', '7', '8', '9', '.', '<', '=', '>',
146 ' ', '!', '"', '#', '$', '%', '&', '\'',
147 '(', ')', '*', '+', ',', '-', '.', '/',
148 '0', '1', '2', '3', '4', '5', '6', '7',
149 '8', '9', ':', ';', '<', '=', '>', '?',
150 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
151 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
152 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
153 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
154 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
155 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
156 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
157 'x', 'y', 'z', '{', '|', '}', '~', '<'
161 SanitizeString strips color tags from the string in
162 and writes the result on string out
164 static void SanitizeString(char *in, char *out)
168 if(*in == STRING_COLOR_TAG)
173 out[0] = STRING_COLOR_TAG;
177 else if (*in >= '0' && *in <= '9') // ^[0-9] found
184 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
187 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
189 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
196 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
201 else if (*in != STRING_COLOR_TAG)
204 *out = qfont_table[*(unsigned char*)in];
216 void ConBuffer_Clear (conbuffer_t *buf)
218 buf->lines_count = 0;
226 void ConBuffer_Shutdown(conbuffer_t *buf)
232 Mem_Free(buf->lines);
241 Notifies the console code about the current time
242 (and shifts back times of other entries when the time
246 void ConBuffer_FixTimes(conbuffer_t *buf)
249 if(buf->lines_count >= 1)
251 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
254 for(i = 0; i < buf->lines_count; ++i)
255 CONBUFFER_LINES(buf, i).addtime += diff;
264 Deletes the first line from the console history.
267 void ConBuffer_DeleteLine(conbuffer_t *buf)
269 if(buf->lines_count == 0)
272 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
277 ConBuffer_DeleteLastLine
279 Deletes the last line from the console history.
282 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
284 if(buf->lines_count == 0)
293 Checks if there is space for a line of the given length, and if yes, returns a
294 pointer to the start of such a space, and NULL otherwise.
297 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
299 if(len > buf->textsize)
301 if(buf->lines_count == 0)
305 char *firstline_start = buf->lines[buf->lines_first].start;
306 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
307 // the buffer is cyclic, so we first have two cases...
308 if(firstline_start < lastline_onepastend) // buffer is contiguous
311 if(len <= buf->text + buf->textsize - lastline_onepastend)
312 return lastline_onepastend;
314 else if(len <= firstline_start - buf->text)
319 else // buffer has a contiguous hole
321 if(len <= firstline_start - lastline_onepastend)
322 return lastline_onepastend;
333 Appends a given string as a new line to the console.
336 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
341 // developer_memory 1 during shutdown prints while conbuffer_t is being freed
345 ConBuffer_FixTimes(buf);
347 if(len >= buf->textsize)
350 // only display end of line.
351 line += len - buf->textsize + 1;
352 len = buf->textsize - 1;
354 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
355 ConBuffer_DeleteLine(buf);
356 memcpy(putpos, line, len);
360 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
362 p = &CONBUFFER_LINES_LAST(buf);
365 p->addtime = cl.time;
367 p->height = -1; // calculate when needed
370 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
374 start = buf->lines_count;
375 for(i = start - 1; i >= 0; --i)
377 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
379 if((l->mask & mask_must) != mask_must)
381 if(l->mask & mask_mustnot)
390 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
392 static char copybuf[MAX_INPUTLINE]; // client only
393 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
394 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
395 strlcpy(copybuf, l->start, sz);
400 ==============================================================================
404 ==============================================================================
409 cvar_t log_file = {CF_CLIENT | CF_SERVER, "log_file", "", "filename to log messages to"};
410 cvar_t log_file_stripcolors = {CF_CLIENT | CF_SERVER, "log_file_stripcolors", "0", "strip color codes from log messages"};
411 cvar_t log_dest_udp = {CF_CLIENT | CF_SERVER, "log_dest_udp", "", "UDP address to log messages to (in QW rcon compatible format); multiple destinations can be separated by spaces; DO NOT SPECIFY DNS NAMES HERE"};
412 char log_dest_buffer[1400]; // UDP packet
413 size_t log_dest_buffer_pos;
414 unsigned int log_dest_buffer_appending;
415 char crt_log_file [MAX_OSPATH] = "";
416 qfile_t* logfile = NULL;
418 unsigned char* logqueue = NULL;
420 size_t logq_size = 0;
422 void Log_ConPrint (const char *msg);
424 static void Log_DestBuffer_Init(void)
426 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
427 log_dest_buffer_pos = 5;
430 static void Log_DestBuffer_Flush_NoLock(void)
432 lhnetaddress_t log_dest_addr;
433 lhnetsocket_t *log_dest_socket;
434 const char *s = log_dest_udp.string;
435 qbool have_opened_temp_sockets = false;
436 if(s) if(log_dest_buffer_pos > 5)
438 ++log_dest_buffer_appending;
439 log_dest_buffer[log_dest_buffer_pos++] = 0;
441 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
443 have_opened_temp_sockets = true;
444 NetConn_OpenServerPorts(true);
447 while(COM_ParseToken_Console(&s))
448 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
450 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
452 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
454 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
457 if(have_opened_temp_sockets)
458 NetConn_CloseServerPorts();
459 --log_dest_buffer_appending;
461 log_dest_buffer_pos = 0;
469 void Log_DestBuffer_Flush(void)
472 Thread_LockMutex(con_mutex);
473 Log_DestBuffer_Flush_NoLock();
475 Thread_UnlockMutex(con_mutex);
478 static const char* Log_Timestamp (const char *desc)
480 static char timestamp [128]; // init/shutdown only
487 char timestring [64];
489 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
492 localtime_s (&crt_tm, &crt_time);
493 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
495 crt_tm = localtime (&crt_time);
496 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
500 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
502 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
507 static void Log_Open (void)
509 if (logfile != NULL || log_file.string[0] == '\0')
512 logfile = FS_OpenRealFile(log_file.string, "a", false);
515 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
516 FS_Print (logfile, Log_Timestamp ("Log started"));
525 void Log_Close (void)
527 qfile_t* l = logfile;
532 FS_Print (l, Log_Timestamp ("Log stopped"));
537 crt_log_file[0] = '\0';
546 void Log_Start (void)
552 // Dump the contents of the log queue into the log file and free it
553 if (logqueue != NULL)
555 unsigned char *temp = logqueue;
560 FS_Write (logfile, temp, logq_ind);
561 if(*log_dest_udp.string)
563 for(pos = 0; pos < logq_ind; )
565 if(log_dest_buffer_pos == 0)
566 Log_DestBuffer_Init();
567 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
568 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
569 log_dest_buffer_pos += n;
570 Log_DestBuffer_Flush_NoLock();
588 void Log_ConPrint (const char *msg)
590 static qbool inprogress = false;
592 // don't allow feedback loops with memory error reports
597 // Until the host is completely initialized, we maintain a log queue
598 // to store the messages, since the log can't be started before
599 if (logqueue != NULL)
601 size_t remain = logq_size - logq_ind;
602 size_t len = strlen (msg);
604 // If we need to enlarge the log queue
607 size_t factor = ((logq_ind + len) / logq_size) + 1;
608 unsigned char* newqueue;
611 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
612 memcpy (newqueue, logqueue, logq_ind);
615 remain = logq_size - logq_ind;
617 memcpy (&logqueue[logq_ind], msg, len);
624 // Check if log_file has changed
625 if (strcmp (crt_log_file, log_file.string) != 0)
631 // If a log file is available
634 if (log_file_stripcolors.integer)
637 size_t len = strlen(msg);
638 char* sanitizedmsg = (char*)Mem_Alloc(tempmempool, len + 1);
639 memcpy (sanitizedmsg, msg, len);
640 SanitizeString(sanitizedmsg, sanitizedmsg); // SanitizeString's in pointer is always ahead of the out pointer, so this should work.
641 FS_Print (logfile, sanitizedmsg);
642 Mem_Free(sanitizedmsg);
646 FS_Print (logfile, msg);
659 void Log_Printf (const char *logfilename, const char *fmt, ...)
663 file = FS_OpenRealFile(logfilename, "a", true);
668 va_start (argptr, fmt);
669 FS_VPrintf (file, fmt, argptr);
678 ==============================================================================
682 ==============================================================================
690 void Con_ToggleConsole_f(cmd_state_t *cmd)
692 if (Sys_CheckParm ("-noconsole"))
693 if (!(key_consoleactive & KEY_CONSOLEACTIVE_USER))
694 return; // only allow the key bind to turn off console
696 // toggle the 'user wants console' bit
697 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
706 void Con_ClearNotify (void)
709 for(i = 0; i < CON_LINES_COUNT; ++i)
710 if(!(CON_LINES(i).mask & CON_MASK_CHAT))
711 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
720 static void Con_MessageMode_f(cmd_state_t *cmd)
722 key_dest = key_message;
723 chat_mode = 0; // "say"
724 if(Cmd_Argc(cmd) > 1)
726 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
727 chat_bufferpos = (unsigned int)strlen(chat_buffer);
737 static void Con_MessageMode2_f(cmd_state_t *cmd)
739 key_dest = key_message;
740 chat_mode = 1; // "say_team"
741 if(Cmd_Argc(cmd) > 1)
743 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
744 chat_bufferpos = (unsigned int)strlen(chat_buffer);
753 static void Con_CommandMode_f(cmd_state_t *cmd)
755 key_dest = key_message;
756 if(Cmd_Argc(cmd) > 1)
758 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
759 chat_bufferpos = (unsigned int)strlen(chat_buffer);
761 chat_mode = -1; // command
769 void Con_CheckResize (void)
774 f = bound(1, con_textsize.value, 128);
775 if(f != con_textsize.value)
776 Cvar_SetValueQuick(&con_textsize, f);
777 width = (int)floor(vid_conwidth.value / con_textsize.value);
778 width = bound(1, width, con.textsize/4);
779 // FIXME uses con in a non abstracted way
781 if (width == con_linewidth)
784 con_linewidth = width;
786 for(i = 0; i < CON_LINES_COUNT; ++i)
787 CON_LINES(i).height = -1; // recalculate when next needed
793 //[515]: the simplest command ever
794 //LadyHavoc: not so simple after I made it print usage...
795 static void Con_Maps_f(cmd_state_t *cmd)
797 if (Cmd_Argc(cmd) > 2)
799 Con_Printf("usage: maps [mapnameprefix]\n");
802 else if (Cmd_Argc(cmd) == 2)
803 GetMapList(Cmd_Argv(cmd, 1), NULL, 0);
805 GetMapList("", NULL, 0);
808 static void Con_ConDump_f(cmd_state_t *cmd)
812 if (Cmd_Argc(cmd) != 2)
814 Con_Printf("usage: condump <filename>\n");
817 file = FS_OpenRealFile(Cmd_Argv(cmd, 1), "w", false);
820 Con_Printf(CON_ERROR "condump: unable to write file \"%s\"\n", Cmd_Argv(cmd, 1));
823 if (con_mutex) Thread_LockMutex(con_mutex);
824 for(i = 0; i < CON_LINES_COUNT; ++i)
826 if (condump_stripcolors.integer)
829 size_t len = CON_LINES(i).len;
830 char* sanitizedmsg = (char*)Mem_Alloc(tempmempool, len + 1);
831 memcpy (sanitizedmsg, CON_LINES(i).start, len);
832 SanitizeString(sanitizedmsg, sanitizedmsg); // SanitizeString's in pointer is always ahead of the out pointer, so this should work.
833 FS_Write(file, sanitizedmsg, strlen(sanitizedmsg));
834 Mem_Free(sanitizedmsg);
838 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
840 FS_Write(file, "\n", 1);
842 if (con_mutex) Thread_UnlockMutex(con_mutex);
846 void Con_Clear_f(cmd_state_t *cmd)
848 if (con_mutex) Thread_LockMutex(con_mutex);
849 ConBuffer_Clear(&con);
850 if (con_mutex) Thread_UnlockMutex(con_mutex);
853 static void Con_RCon_ClearPassword_c(cvar_t *var)
855 // whenever rcon_secure is changed to 0, clear rcon_password for
856 // security reasons (prevents a send-rcon-password-as-plaintext
857 // attack based on NQ protocol session takeover and svc_stufftext)
858 if(var->integer <= 0)
859 Cvar_SetQuick(&rcon_password, "");
870 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
871 if (Thread_HasThreads())
872 con_mutex = Thread_CreateMutex();
874 // Allocate a log queue, this will be freed after configs are parsed
875 logq_size = MAX_INPUTLINE;
876 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
879 Cvar_RegisterVariable (&sys_colortranslation);
880 Cvar_RegisterVariable (&sys_specialcharactertranslation);
882 Cvar_RegisterVariable (&log_file);
883 Cvar_RegisterVariable (&log_file_stripcolors);
884 Cvar_RegisterVariable (&log_dest_udp);
886 // support for the classic Quake option
887 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
888 if (Sys_CheckParm ("-condebug") != 0)
889 Cvar_SetQuick (&log_file, "qconsole.log");
891 // register our cvars
892 Cvar_RegisterVariable (&con_chat);
893 Cvar_RegisterVariable (&con_chatpos);
894 Cvar_RegisterVariable (&con_chatrect_x);
895 Cvar_RegisterVariable (&con_chatrect_y);
896 Cvar_RegisterVariable (&con_chatrect);
897 Cvar_RegisterVariable (&con_chatsize);
898 Cvar_RegisterVariable (&con_chattime);
899 Cvar_RegisterVariable (&con_chatwidth);
900 Cvar_RegisterVariable (&con_notify);
901 Cvar_RegisterVariable (&con_notifyalign);
902 Cvar_RegisterVariable (&con_notifysize);
903 Cvar_RegisterVariable (&con_notifytime);
904 Cvar_RegisterVariable (&con_textsize);
905 Cvar_RegisterVariable (&con_chatsound);
906 Cvar_RegisterVariable (&con_chatsound_file);
907 Cvar_RegisterVariable (&con_chatsound_team_file);
908 Cvar_RegisterVariable (&con_chatsound_team_mask);
911 Cvar_RegisterVariable (&con_nickcompletion);
912 Cvar_RegisterVariable (&con_nickcompletion_flags);
914 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
915 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
916 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
918 Cvar_RegisterVariable (&condump_stripcolors);
920 Cvar_RegisterVariable(&rcon_address);
921 Cvar_RegisterVariable(&rcon_secure);
922 Cvar_RegisterCallback(&rcon_secure, Con_RCon_ClearPassword_c);
923 Cvar_RegisterVariable(&rcon_secure_challengetimeout);
924 Cvar_RegisterVariable(&rcon_password);
926 // register our commands
927 Cmd_AddCommand(CF_CLIENT, "toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
928 Cmd_AddCommand(CF_CLIENT, "messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
929 Cmd_AddCommand(CF_CLIENT, "messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
930 Cmd_AddCommand(CF_CLIENT, "commandmode", Con_CommandMode_f, "input a console command");
931 Cmd_AddCommand(CF_SHARED, "clear", Con_Clear_f, "clear console history");
932 Cmd_AddCommand(CF_SHARED, "maps", Con_Maps_f, "list information about available maps");
933 Cmd_AddCommand(CF_SHARED, "condump", Con_ConDump_f, "output console history to a file (see also log_file)");
935 con_initialized = true;
937 Con_Print("Console initialized.\n");
940 void Con_Shutdown (void)
942 if (con_mutex) Thread_LockMutex(con_mutex);
943 ConBuffer_Shutdown(&con);
944 if (con_mutex) Thread_UnlockMutex(con_mutex);
945 if (con_mutex) Thread_DestroyMutex(con_mutex);
953 Handles cursor positioning, line wrapping, etc
954 All console printing must go through this in order to be displayed
955 If no console is visible, the notify window will pop up.
958 static void Con_PrintToHistory(const char *txt, int mask)
961 // \n goes to next line
962 // \r deletes current line and makes a new one
964 static int cr_pending = 0;
965 static char buf[CON_TEXTSIZE]; // con_mutex
966 static int bufpos = 0;
968 if(!con.text) // FIXME uses a non-abstracted property of con
975 ConBuffer_DeleteLastLine(&con);
983 ConBuffer_AddLine(&con, buf, bufpos, mask);
988 ConBuffer_AddLine(&con, buf, bufpos, mask);
992 buf[bufpos++] = *txt;
993 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
995 ConBuffer_AddLine(&con, buf, bufpos, mask);
1003 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qbool proquakeprotocol)
1005 rcon_redirect_sock = sock;
1006 rcon_redirect_dest = dest;
1007 rcon_redirect_proquakeprotocol = proquakeprotocol;
1008 if (rcon_redirect_proquakeprotocol)
1010 // reserve space for the packet header
1011 rcon_redirect_buffer[0] = 0;
1012 rcon_redirect_buffer[1] = 0;
1013 rcon_redirect_buffer[2] = 0;
1014 rcon_redirect_buffer[3] = 0;
1015 // this is a reply to a CCREQ_RCON
1016 rcon_redirect_buffer[4] = (unsigned char)CCREP_RCON;
1019 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1020 rcon_redirect_bufferpos = 5;
1023 static void Con_Rcon_Redirect_Flush(void)
1025 if(rcon_redirect_sock)
1027 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
1028 if (rcon_redirect_proquakeprotocol)
1030 // update the length in the packet header
1031 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
1033 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
1035 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1036 rcon_redirect_bufferpos = 5;
1037 rcon_redirect_proquakeprotocol = false;
1040 void Con_Rcon_Redirect_End(void)
1042 Con_Rcon_Redirect_Flush();
1043 rcon_redirect_dest = NULL;
1044 rcon_redirect_sock = NULL;
1047 void Con_Rcon_Redirect_Abort(void)
1049 rcon_redirect_dest = NULL;
1050 rcon_redirect_sock = NULL;
1058 /// Adds a character to the rcon buffer.
1059 static void Con_Rcon_AddChar(int c)
1061 if(log_dest_buffer_appending)
1063 ++log_dest_buffer_appending;
1065 // if this print is in response to an rcon command, add the character
1066 // to the rcon redirect buffer
1068 if (rcon_redirect_dest)
1070 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
1071 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
1072 Con_Rcon_Redirect_Flush();
1074 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
1076 if(log_dest_buffer_pos == 0)
1077 Log_DestBuffer_Init();
1078 log_dest_buffer[log_dest_buffer_pos++] = c;
1079 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
1080 Log_DestBuffer_Flush_NoLock();
1083 log_dest_buffer_pos = 0;
1085 --log_dest_buffer_appending;
1089 * Convert an RGB color to its nearest quake color.
1090 * I'll cheat on this a bit by translating the colors to HSV first,
1091 * S and V decide if it's black or white, otherwise, H will decide the
1093 * @param _r Red (0-255)
1094 * @param _g Green (0-255)
1095 * @param _b Blue (0-255)
1096 * @return A quake color character.
1098 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
1100 float r = ((float)_r)/255.0;
1101 float g = ((float)_g)/255.0;
1102 float b = ((float)_b)/255.0;
1103 float min = min(r, min(g, b));
1104 float max = max(r, max(g, b));
1106 int h; ///< Hue angle [0,360]
1107 float s; ///< Saturation [0,1]
1108 float v = max; ///< In HSV v == max [0,1]
1113 s = 1.0 - (min/max);
1115 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1118 // If the value is less than half, return a black color code.
1119 // Otherwise return a white one.
1125 // Let's get the hue angle to define some colors:
1129 h = (int)(60.0 * (g-b)/(max-min))%360;
1131 h = (int)(60.0 * (b-r)/(max-min) + 120);
1132 else // if(max == b) redundant check
1133 h = (int)(60.0 * (r-g)/(max-min) + 240);
1135 if(h < 36) // *red* to orange
1137 else if(h < 80) // orange over *yellow* to evilish-bright-green
1139 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1141 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1143 else if(h < 270) // darkish blue over *dark blue* to cool purple
1145 else if(h < 330) // cool purple over *purple* to ugly swiny red
1147 else // ugly red to red closes the circly
1156 extern cvar_t timestamps;
1157 extern cvar_t timeformat;
1158 extern qbool sys_nostdout;
1159 void Con_MaskPrint(int additionalmask, const char *msg)
1161 static int mask = 0;
1162 static int index = 0;
1163 static char line[MAX_INPUTLINE];
1166 Thread_LockMutex(con_mutex);
1170 Con_Rcon_AddChar(*msg);
1171 // if this is the beginning of a new line, print timestamp
1174 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1176 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1177 line[index++] = STRING_COLOR_TAG;
1178 // assert( STRING_COLOR_DEFAULT < 10 )
1179 line[index++] = STRING_COLOR_DEFAULT + '0';
1180 // special color codes for chat messages must always come first
1181 // for Con_PrintToHistory to work properly
1182 if (*msg == 1 || *msg == 2 || *msg == 3)
1187 if (con_chatsound.value)
1189 if(msg[1] == con_chatsound_team_mask.integer && cl.foundteamchatsound)
1190 S_LocalSound (con_chatsound_team_file.string);
1192 S_LocalSound (con_chatsound_file.string);
1195 // Send to chatbox for say/tell (1) and messages (3)
1196 // 3 is just so that a message can be sent to the chatbox without a sound.
1197 if (*msg == 1 || *msg == 3)
1198 mask = CON_MASK_CHAT;
1200 line[index++] = STRING_COLOR_TAG;
1201 line[index++] = '3';
1203 Con_Rcon_AddChar(*msg);
1206 for (;*timestamp;index++, timestamp++)
1207 if (index < (int)sizeof(line) - 2)
1208 line[index] = *timestamp;
1210 mask |= additionalmask;
1212 // append the character
1213 line[index++] = *msg;
1214 // if this is a newline character, we have a complete line to print
1215 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1217 // terminate the line
1221 // send to scrollable buffer
1222 if (con_initialized && cls.state != ca_dedicated)
1224 Con_PrintToHistory(line, mask);
1226 // send to terminal or dedicated server window
1228 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1230 if(sys_specialcharactertranslation.integer)
1237 int ch = u8_getchar(p, &q);
1238 if(ch >= 0xE000 && ch <= 0xE0FF && ((unsigned char) qfont_table[ch - 0xE000]) >= 0x20)
1240 *p = qfont_table[ch - 0xE000];
1242 memmove(p+1, q, strlen(q)+1);
1250 if(sys_colortranslation.integer == 1) // ANSI
1252 static char printline[MAX_INPUTLINE * 4 + 3];
1253 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1254 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1259 for(in = line, out = printline; *in; ++in)
1263 case STRING_COLOR_TAG:
1264 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1266 char r = tolower(in[2]);
1267 char g = tolower(in[3]);
1268 char b = tolower(in[4]);
1269 // it's a hex digit already, so the else part needs no check --blub
1270 if(isdigit(r)) r -= '0';
1272 if(isdigit(g)) g -= '0';
1274 if(isdigit(b)) b -= '0';
1277 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1278 in += 3; // 3 only, the switch down there does the fourth
1285 case STRING_COLOR_TAG:
1287 *out++ = STRING_COLOR_TAG;
1293 if(lastcolor == 0) break; else lastcolor = 0;
1294 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1299 if(lastcolor == 1) break; else lastcolor = 1;
1300 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1305 if(lastcolor == 2) break; else lastcolor = 2;
1306 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1311 if(lastcolor == 3) break; else lastcolor = 3;
1312 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1317 if(lastcolor == 4) break; else lastcolor = 4;
1318 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1323 if(lastcolor == 5) break; else lastcolor = 5;
1324 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1329 if(lastcolor == 6) break; else lastcolor = 6;
1330 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1335 // bold normal color
1337 if(lastcolor == 8) break; else lastcolor = 8;
1338 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1341 *out++ = STRING_COLOR_TAG;
1348 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1365 Sys_Print(printline);
1367 else if(sys_colortranslation.integer == 2) // Quake
1373 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1376 for(in = line, out = printline; *in; ++in)
1380 case STRING_COLOR_TAG:
1383 case STRING_COLOR_RGB_TAG_CHAR:
1384 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1389 *out++ = STRING_COLOR_TAG;
1390 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1393 case STRING_COLOR_TAG:
1395 *out++ = STRING_COLOR_TAG;
1410 *out++ = STRING_COLOR_TAG;
1420 Sys_Print(printline);
1423 // empty the line buffer
1430 Thread_UnlockMutex(con_mutex);
1438 void Con_MaskPrintf(int mask, const char *fmt, ...)
1441 char msg[MAX_INPUTLINE];
1443 va_start(argptr,fmt);
1444 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1447 Con_MaskPrint(mask, msg);
1455 void Con_Print(const char *msg)
1457 Con_MaskPrint(CON_MASK_PRINT, msg);
1465 void Con_Printf(const char *fmt, ...)
1468 char msg[MAX_INPUTLINE];
1470 va_start(argptr,fmt);
1471 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1474 Con_MaskPrint(CON_MASK_PRINT, msg);
1482 void Con_DPrint(const char *msg)
1484 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1487 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1495 void Con_DPrintf(const char *fmt, ...)
1498 char msg[MAX_INPUTLINE];
1500 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1503 va_start(argptr,fmt);
1504 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1507 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1512 ==============================================================================
1516 ==============================================================================
1523 It draws either the console input line or the chat input line (if is_console is false)
1524 The input line scrolls horizontally if typing goes beyond the right edge
1526 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1529 static void Con_DrawInput(qbool is_console, float x, float v, float inputsize)
1531 int y, i, col_out, linepos, text_start, prefix_start = 0;
1532 char text[MAX_INPUTLINE + 5 + 9 + 1]; // space for ^xRGB, "say_team:" and \0
1538 if (is_console && !key_consoleactive)
1539 return; // don't draw anything
1543 // empty prefix because ] is part of the console edit line
1545 strlcpy(text, key_line, sizeof(text));
1546 linepos = key_linepos;
1554 prefix = "say_team:";
1557 strlcpy(text, chat_buffer, sizeof(text));
1558 linepos = chat_bufferpos;
1562 y = (int)strlen(text);
1564 // make the color code visible when the cursor is inside it
1565 if(text[linepos] != 0)
1567 for(i=1; i < 5 && linepos - i > 0; ++i)
1568 if(text[linepos-i] == STRING_COLOR_TAG)
1570 int caret_pos, ofs = 0;
1571 caret_pos = linepos - i;
1572 if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG)
1574 else if(i == 1 && isdigit(text[caret_pos+1]))
1576 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]))
1578 if(ofs && (size_t)(y + ofs + 1) < sizeof(text))
1581 while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG)
1585 // str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing)
1586 // str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing)
1587 memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos);
1588 text[caret_pos + ofs] = STRING_COLOR_TAG;
1600 x += DrawQ_TextWidth(prefix, 0, inputsize, inputsize, false, fnt);
1607 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, inputsize, inputsize, &col_out, false, fnt, 1000000000);
1609 text_start = x + (vid_conwidth.value - x) * 0.95 - xo; // scroll
1612 else if (!is_console)
1613 prefix_start -= (x - text_start);
1616 DrawQ_String(prefix_start, v, prefix, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1618 DrawQ_String(text_start, v, text, y + 3, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
1620 // draw a cursor on top of this
1621 if ((int)(host.realtime*con_cursorspeed) & 1) // cursor is visible
1623 if (!utf8_enable.integer)
1625 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1633 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1634 memcpy(text, curbuf, len);
1637 DrawQ_String(text_start + xo, v, text, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, fnt);
1644 float alignment; // 0 = left, 0.5 = center, 1 = right
1650 const char *continuationString;
1653 int colorindex; // init to -1
1657 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1659 con_text_info_t *ti = (con_text_info_t *) passthrough;
1662 ti->colorindex = -1;
1663 return ti->fontsize * ti->font->maxwidth;
1666 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1667 else if(maxWidth == -1)
1668 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1671 Sys_Printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1672 // Note: this is NOT a Con_Printf, as it could print recursively
1677 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1683 (void) isContinuation;
1687 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
1689 con_text_info_t *ti = (con_text_info_t *) passthrough;
1691 if(ti->y < ti->ymin - 0.001)
1693 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1697 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1698 if(isContinuation && *ti->continuationString)
1699 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);
1701 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);
1704 ti->y += ti->fontsize;
1708 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)
1712 int maxlines = (int) floor(height / fontsize + 0.01f);
1715 int continuationWidth = 0;
1717 double t = cl.time; // saved so it won't change
1720 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1721 ti.fontsize = fontsize;
1722 ti.alignment = alignment_x;
1725 ti.ymax = y + height;
1726 ti.continuationString = continuationString;
1729 Con_WordWidthFunc(&ti, NULL, &len, -1);
1730 len = strlen(continuationString);
1731 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &len, -1);
1733 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1734 startidx = CON_LINES_COUNT;
1735 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1737 con_lineinfo_t *l = &CON_LINES(i);
1740 if((l->mask & mask_must) != mask_must)
1742 if(l->mask & mask_mustnot)
1744 if(maxage && (l->addtime < t - maxage))
1748 // Calculate its actual height...
1749 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1750 if(lines + mylines >= maxlines)
1752 nskip = lines + mylines - maxlines;
1761 // then center according to the calculated amount of lines...
1763 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1765 // then actually draw
1766 for(i = startidx; i < CON_LINES_COUNT; ++i)
1768 con_lineinfo_t *l = &CON_LINES(i);
1770 if((l->mask & mask_must) != mask_must)
1772 if(l->mask & mask_mustnot)
1774 if(maxage && (l->addtime < t - maxage))
1777 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1787 Draws the last few lines of output transparently over the game top
1790 void Con_DrawNotify (void)
1793 float chatstart, notifystart, inputsize, height;
1798 if (con_mutex) Thread_LockMutex(con_mutex);
1799 ConBuffer_FixTimes(&con);
1801 numChatlines = con_chat.integer;
1803 chatpos = con_chatpos.integer;
1805 if (con_notify.integer < 0)
1806 Cvar_SetValueQuick(&con_notify, 0);
1807 if (gamemode == GAME_TRANSFUSION)
1808 v = 8; // vertical offset
1812 // GAME_NEXUIZ: center, otherwise left justify
1813 align = con_notifyalign.value;
1814 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1816 if(IS_OLDNEXUIZ_DERIVED(gamemode))
1820 if(numChatlines || !con_chatrect.integer)
1824 // first chat, input line, then notify
1826 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1828 else if(chatpos > 0)
1830 // first notify, then (chatpos-1) empty lines, then chat, then input
1832 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1834 else // if(chatpos < 0)
1836 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1838 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1843 // just notify and input
1845 chatstart = 0; // shut off gcc warning
1848 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, "");
1850 if(con_chatrect.integer)
1852 x = con_chatrect_x.value * vid_conwidth.value;
1853 v = con_chatrect_y.value * vid_conheight.value;
1858 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1861 height = numChatlines * con_chatsize.value;
1865 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 ... ");
1868 if (key_dest == key_message)
1870 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1871 Con_DrawInput(false, x, v, inputsize);
1876 if (con_mutex) Thread_UnlockMutex(con_mutex);
1883 Returns the height of a given console line; calculates it if necessary.
1886 static int Con_LineHeight(int lineno)
1888 con_lineinfo_t *li = &CON_LINES(lineno);
1889 if(li->height == -1)
1891 float width = vid_conwidth.value;
1893 ti.fontsize = con_textsize.value;
1894 ti.font = FONT_CONSOLE;
1895 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1904 Draws a line of the console; returns its height in lines.
1905 If alpha is 0, the line is not drawn, but still wrapped and its height
1909 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1911 float width = vid_conwidth.value;
1913 con_lineinfo_t *li = &CON_LINES(lineno);
1915 if((li->mask & mask_must) != mask_must)
1917 if((li->mask & mask_mustnot) != 0)
1920 ti.continuationString = "";
1922 ti.fontsize = con_textsize.value;
1923 ti.font = FONT_CONSOLE;
1925 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1930 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1937 Calculates the last visible line index and how much to show of it based on
1941 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1946 if(con_backscroll < 0)
1951 // now count until we saw con_backscroll actual lines
1952 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1953 if((CON_LINES(i).mask & mask_must) == mask_must)
1954 if((CON_LINES(i).mask & mask_mustnot) == 0)
1956 int h = Con_LineHeight(i);
1958 // line is the last visible line?
1960 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1962 *limitlast = lines_seen + h - con_backscroll;
1969 // if we get here, no line was on screen - scroll so that one line is
1971 con_backscroll = lines_seen - 1;
1979 Draws the console with the solid background
1980 The typing input line at the bottom should only be drawn if typing is allowed
1983 void Con_DrawConsole (int lines)
1985 float alpha, alpha0;
1988 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1989 cachepic_t *conbackpic;
1990 unsigned int conbackflags;
1995 if (con_mutex) Thread_LockMutex(con_mutex);
1997 if (con_backscroll < 0)
2000 con_vislines = lines;
2002 r_draw2d_force = true;
2004 // draw the background
2005 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
2006 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
2008 sx = scr_conscroll_x.value;
2009 sy = scr_conscroll_y.value;
2010 conbackflags = CACHEPICFLAG_FAILONMISSING; // So console is readable when game content is missing
2011 if (sx != 0 || sy != 0)
2012 conbackflags &= CACHEPICFLAG_NOCLAMP;
2013 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", conbackflags) : NULL;
2014 sx *= host.realtime; sy *= host.realtime;
2015 sx -= floor(sx); sy -= floor(sy);
2016 if (Draw_IsPicLoaded(conbackpic))
2017 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2018 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2019 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2020 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2021 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2024 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
2026 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
2028 sx = scr_conscroll2_x.value;
2029 sy = scr_conscroll2_y.value;
2030 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2031 sx *= host.realtime; sy *= host.realtime;
2032 sx -= floor(sx); sy -= floor(sy);
2033 if(Draw_IsPicLoaded(conbackpic))
2034 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2035 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2036 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2037 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2038 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2041 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
2043 sx = scr_conscroll3_x.value;
2044 sy = scr_conscroll3_y.value;
2045 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2046 sx *= host.realtime; sy *= host.realtime;
2047 sx -= floor(sx); sy -= floor(sy);
2048 if(Draw_IsPicLoaded(conbackpic))
2049 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2050 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2051 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2052 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2053 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2056 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);
2062 int count = CON_LINES_COUNT;
2063 float ymax = con_vislines - 2 * con_textsize.value;
2064 float y = ymax + con_textsize.value * con_backscroll;
2065 for (i = 0;i < count && y >= 0;i++)
2066 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
2067 // fix any excessive scrollback for the next frame
2068 if (i >= count && y >= 0)
2070 con_backscroll -= (int)(y / con_textsize.value);
2071 if (con_backscroll < 0)
2076 if(CON_LINES_COUNT > 0)
2078 int i, last, limitlast;
2080 float ymax = con_vislines - 2 * con_textsize.value;
2081 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2082 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2083 y = ymax - con_textsize.value;
2086 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
2091 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
2093 break; // top of console buffer
2095 break; // top of console window
2102 // draw the input prompt, user text, and cursor if desired
2103 Con_DrawInput(true, 0, con_vislines - con_textsize.value * 2, con_textsize.value);
2105 r_draw2d_force = false;
2106 if (con_mutex) Thread_UnlockMutex(con_mutex);
2113 Prints not only map filename, but also
2114 its format (q1/q2/q3/hl) and even its message
2116 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2117 //LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2118 //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
2119 //LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2120 qbool GetMapList (const char *s, char *completedname, int completednamebufferlength)
2124 int i, k, max, p, o, min;
2127 unsigned char buf[1024];
2129 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2130 t = FS_Search(message, 1, true, NULL);
2133 if (t->numfilenames > 1)
2134 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2135 len = (unsigned char *)Z_Malloc(t->numfilenames);
2137 for(max=i=0;i<t->numfilenames;i++)
2139 k = (int)strlen(t->filenames[i]);
2149 for(i=0;i<t->numfilenames;i++)
2151 int lumpofs = 0, lumplen = 0;
2152 char *entities = NULL;
2153 const char *data = NULL;
2155 char entfilename[MAX_QPATH];
2158 strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2160 f = FS_OpenVirtualFile(t->filenames[i], true);
2163 strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2164 memset(buf, 0, 1024);
2165 FS_Read(f, buf, 1024);
2166 if (!memcmp(buf, "IBSP", 4))
2168 p = LittleLong(((int *)buf)[1]);
2169 if (p == Q3BSPVERSION)
2171 q3dheader_t *header = (q3dheader_t *)buf;
2172 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2173 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2174 dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2176 else if (p == Q2BSPVERSION)
2178 q2dheader_t *header = (q2dheader_t *)buf;
2179 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2180 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2181 dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2184 dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2186 else if (BuffLittleLong(buf) == BSPVERSION)
2188 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2189 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2190 dpsnprintf(desc, sizeof(desc), "BSP29");
2192 else if (BuffLittleLong(buf) == 30)
2194 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2195 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2196 dpsnprintf(desc, sizeof(desc), "BSPHL");
2198 else if (!memcmp(buf, "BSP2", 4))
2200 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2201 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2202 dpsnprintf(desc, sizeof(desc), "BSP2");
2204 else if (!memcmp(buf, "2PSB", 4))
2206 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2207 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2208 dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2210 else if(!memcmp(buf, "VBSP", 4))
2212 hl2dheader_t *header = (hl2dheader_t *)buf;
2213 lumpofs = LittleLong(header->lumps[HL2LUMP_ENTITIES].fileofs);
2214 lumplen = LittleLong(header->lumps[HL2LUMP_ENTITIES].filelen);
2215 dpsnprintf(desc, sizeof(desc), "VBSP%i", LittleLong(((int *)buf)[1]));
2218 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2219 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2220 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2221 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2222 if (!entities && lumplen >= 10)
2224 FS_Seek(f, lumpofs, SEEK_SET);
2225 entities = (char *)Z_Malloc(lumplen + 1);
2226 FS_Read(f, entities, lumplen);
2230 // if there are entities to parse, a missing message key just
2231 // means there is no title, so clear the message string now
2237 if (!COM_ParseToken_Simple(&data, false, false, true))
2239 if (com_token[0] == '{')
2241 if (com_token[0] == '}')
2243 // skip leading whitespace
2244 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2245 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2246 keyname[l] = com_token[k+l];
2248 if (!COM_ParseToken_Simple(&data, false, false, true))
2250 if (developer_extra.integer)
2251 Con_DPrintf("key: %s %s\n", keyname, com_token);
2252 if (!strcmp(keyname, "message"))
2254 // get the message contents
2255 strlcpy(message, com_token, sizeof(message));
2265 *(t->filenames[i]+len[i]+5) = 0;
2266 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2271 k = *(t->filenames[0]+5+p);
2274 for(i=1;i<t->numfilenames;i++)
2275 if(*(t->filenames[i]+5+p) != k)
2279 if(p > o && completedname && completednamebufferlength > 0)
2281 memset(completedname, 0, completednamebufferlength);
2282 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2292 New function for tab-completion system
2293 Added by EvilTypeGuy
2294 MEGA Thanks to Taniwha
2297 void Con_DisplayList(const char **list)
2299 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2300 const char **walk = list;
2303 len = (int)strlen(*walk);
2311 len = (int)strlen(*list);
2312 if (pos + maxlen >= width) {
2318 for (i = 0; i < (maxlen - len); i++)
2330 // Now it becomes TRICKY :D --blub
2331 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2332 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2333 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2334 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
2335 static int Nicks_matchpos;
2337 // co against <<:BLASTER:>> is true!?
2338 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2342 if(tolower(*a) == tolower(*b))
2356 return (*a < *b) ? -1 : 1;
2360 return (*a < *b) ? -1 : 1;
2364 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2367 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2369 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2370 return Nicks_strncasecmp_nospaces(a, b, a_len);
2371 return strncasecmp(a, b, a_len);
2374 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2376 // ignore non alphanumerics of B
2377 // if A contains a non-alphanumeric, B must contain it as well though!
2380 qbool alnum_a, alnum_b;
2382 if(tolower(*a) == tolower(*b))
2384 if(*a == 0) // end of both strings, they're equal
2391 // not equal, end of one string?
2396 // ignore non alphanumerics
2397 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2398 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2399 if(!alnum_a) // b must contain this
2400 return (*a < *b) ? -1 : 1;
2403 // otherwise, both are alnum, they're just not equal, return the appropriate number
2405 return (*a < *b) ? -1 : 1;
2411 /* Nicks_CompleteCountPossible
2413 Count the number of possible nicks to complete
2415 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qbool isCon)
2417 char name[MAX_SCOREBOARDNAME];
2423 if(!con_nickcompletion.integer)
2426 // changed that to 1
2427 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2430 for(i = 0; i < cl.maxclients; ++i)
2433 if(!cl.scores[p].name[0])
2436 SanitizeString(cl.scores[p].name, name);
2437 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2443 spos = pos - 1; // no need for a minimum of characters :)
2447 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2449 if(!(isCon && spos == 1)) // console start
2455 if(isCon && spos == 0)
2457 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2463 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2464 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2466 // the sanitized list
2467 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2470 Nicks_matchpos = match;
2473 Nicks_offset[count] = s - (&line[match]);
2474 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2481 static void Cmd_CompleteNicksPrint(int count)
2484 for(i = 0; i < count; ++i)
2485 Con_Printf("%s\n", Nicks_list[i]);
2488 static void Nicks_CutMatchesNormal(int count)
2490 // cut match 0 down to the longest possible completion
2493 c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2494 for(i = 1; i < count; ++i)
2496 l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2500 for(l = 0; l <= c; ++l)
2501 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2507 Nicks_sanlist[0][c+1] = 0;
2508 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2511 static unsigned int Nicks_strcleanlen(const char *s)
2516 if( (*s >= 'a' && *s <= 'z') ||
2517 (*s >= 'A' && *s <= 'Z') ||
2518 (*s >= '0' && *s <= '9') ||
2526 static void Nicks_CutMatchesAlphaNumeric(int count)
2528 // cut match 0 down to the longest possible completion
2531 char tempstr[sizeof(Nicks_sanlist[0])];
2533 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2535 c = (unsigned int)strlen(Nicks_sanlist[0]);
2536 for(i = 0, l = 0; i < (int)c; ++i)
2538 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2539 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2540 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2542 tempstr[l++] = Nicks_sanlist[0][i];
2547 for(i = 1; i < count; ++i)
2550 b = Nicks_sanlist[i];
2560 if(tolower(*a) == tolower(*b))
2566 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2568 // b is alnum, so cut
2575 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2576 Nicks_CutMatchesNormal(count);
2577 //if(!Nicks_sanlist[0][0])
2578 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2580 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2581 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2585 static void Nicks_CutMatchesNoSpaces(int count)
2587 // cut match 0 down to the longest possible completion
2590 char tempstr[sizeof(Nicks_sanlist[0])];
2593 c = (unsigned int)strlen(Nicks_sanlist[0]);
2594 for(i = 0, l = 0; i < (int)c; ++i)
2596 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2598 tempstr[l++] = Nicks_sanlist[0][i];
2603 for(i = 1; i < count; ++i)
2606 b = Nicks_sanlist[i];
2616 if(tolower(*a) == tolower(*b))
2630 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2631 Nicks_CutMatchesNormal(count);
2632 //if(!Nicks_sanlist[0][0])
2633 //Con_Printf("TS: %s\n", tempstr);
2634 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2636 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2637 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2641 static void Nicks_CutMatches(int count)
2643 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2644 Nicks_CutMatchesAlphaNumeric(count);
2645 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2646 Nicks_CutMatchesNoSpaces(count);
2648 Nicks_CutMatchesNormal(count);
2651 static const char **Nicks_CompleteBuildList(int count)
2655 // the list is freed by Con_CompleteCommandLine, so create a char**
2656 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2658 for(; bpos < count; ++bpos)
2659 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2661 Nicks_CutMatches(count);
2669 Restores the previous used color, after the autocompleted name.
2671 static int Nicks_AddLastColor(char *buffer, int pos)
2673 qbool quote_added = false;
2675 int color = STRING_COLOR_DEFAULT + '0';
2676 char r = 0, g = 0, b = 0;
2678 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2680 // we'll have to add a quote :)
2681 buffer[pos++] = '\"';
2685 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2687 // add color when no quote was added, or when flags &4?
2689 for(match = Nicks_matchpos-1; match >= 0; --match)
2691 if(buffer[match] == STRING_COLOR_TAG)
2693 if( isdigit(buffer[match+1]) )
2695 color = buffer[match+1];
2698 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2700 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2702 r = buffer[match+2];
2703 g = buffer[match+3];
2704 b = buffer[match+4];
2713 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2715 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2716 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2719 buffer[pos++] = STRING_COLOR_TAG;
2722 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2728 buffer[pos++] = color;
2734 Con_CompleteCommandLine
2736 New function for tab-completion system
2737 Added by EvilTypeGuy
2738 Thanks to Fett erich@heintz.com
2740 Enhanced to tab-complete map names by [515]
2743 int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console)
2745 const char *text = "";
2747 const char **list[4] = {0, 0, 0, 0};
2750 int c, v, a, i, cmd_len, pos, k;
2751 int n; // nicks --blub
2752 const char *space, *patterns;
2756 int linestart, linepos;
2757 unsigned int linesize;
2761 linepos = key_linepos;
2762 linesize = sizeof(key_line);
2768 linepos = chat_bufferpos;
2769 linesize = sizeof(chat_buffer);
2773 //find what we want to complete
2775 while(--pos >= linestart)
2778 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2784 strlcpy(s2, line + linepos, sizeof(s2)); //save chars after cursor
2785 line[linepos] = 0; //hide them
2787 c = v = a = n = cmd_len = 0;
2791 space = strchr(line + 1, ' ');
2792 if(space && pos == (space - line) + 1)
2794 strlcpy(command, line + 1, min(sizeof(command), (unsigned int)(space - line)));
2796 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?
2797 if(patterns && !*patterns)
2798 patterns = NULL; // get rid of the empty string
2800 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2804 if (GetMapList(s, t, sizeof(t)))
2806 // first move the cursor
2807 linepos += (int)strlen(t) - (int)strlen(s);
2809 // and now do the actual work
2811 strlcat(line, t, MAX_INPUTLINE);
2812 strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2814 // and fix the cursor
2815 if(linepos > (int) strlen(line))
2816 linepos = (int) strlen(line);
2825 stringlist_t resultbuf, dirbuf;
2828 // // store completion patterns (space separated) for command foo in con_completion_foo
2829 // set con_completion_foo "foodata/*.foodefault *.foo"
2832 // Note: patterns with slash are always treated as absolute
2833 // patterns; patterns without slash search in the innermost
2834 // directory the user specified. There is no way to "complete into"
2835 // a directory as of now, as directories seem to be unknown to the
2839 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2840 // set con_completion_playdemo "*.dem"
2841 // set con_completion_play "*.wav *.ogg"
2843 // TODO somehow add support for directories; these shall complete
2844 // to their name + an appended slash.
2846 stringlistinit(&resultbuf);
2847 stringlistinit(&dirbuf);
2848 while(COM_ParseToken_Simple(&patterns, false, false, true))
2851 if(strchr(com_token, '/'))
2853 search = FS_Search(com_token, true, true, NULL);
2857 const char *slash = strrchr(s, '/');
2860 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2861 strlcat(t, com_token, sizeof(t));
2862 search = FS_Search(t, true, true, NULL);
2865 search = FS_Search(com_token, true, true, NULL);
2869 for(i = 0; i < search->numfilenames; ++i)
2870 if(!strncmp(search->filenames[i], s, strlen(s)))
2871 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2872 stringlistappend(&resultbuf, search->filenames[i]);
2873 FS_FreeSearch(search);
2877 // In any case, add directory names
2880 const char *slash = strrchr(s, '/');
2883 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2884 strlcat(t, "*", sizeof(t));
2885 search = FS_Search(t, true, true, NULL);
2888 search = FS_Search("*", true, true, NULL);
2891 for(i = 0; i < search->numfilenames; ++i)
2892 if(!strncmp(search->filenames[i], s, strlen(s)))
2893 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2894 stringlistappend(&dirbuf, search->filenames[i]);
2895 FS_FreeSearch(search);
2899 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2902 unsigned int matchchars;
2903 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2905 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2908 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2910 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2914 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2915 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2916 for(i = 0; i < dirbuf.numstrings; ++i)
2918 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2920 for(i = 0; i < resultbuf.numstrings; ++i)
2922 Con_Printf("%s\n", resultbuf.strings[i]);
2924 matchchars = sizeof(t) - 1;
2925 if(resultbuf.numstrings > 0)
2927 p = resultbuf.strings[0];
2928 q = resultbuf.strings[resultbuf.numstrings - 1];
2929 for(; *p && *p == *q; ++p, ++q);
2930 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2932 if(dirbuf.numstrings > 0)
2934 p = dirbuf.strings[0];
2935 q = dirbuf.strings[dirbuf.numstrings - 1];
2936 for(; *p && *p == *q; ++p, ++q);
2937 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2939 // now p points to the first non-equal character, or to the end
2940 // of resultbuf.strings[0]. We want to append the characters
2941 // from resultbuf.strings[0] to (not including) p as these are
2942 // the unique prefix
2943 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2946 // first move the cursor
2947 linepos += (int)strlen(t) - (int)strlen(s);
2949 // and now do the actual work
2951 strlcat(line, t, MAX_INPUTLINE);
2952 strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor
2954 // and fix the cursor
2955 if(linepos > (int) strlen(line))
2956 linepos = (int) strlen(line);
2958 stringlistfreecontents(&resultbuf);
2959 stringlistfreecontents(&dirbuf);
2961 return linepos; // bail out, when we complete for a command that wants a file name
2966 // Count number of possible matches and print them
2967 c = Cmd_CompleteCountPossible(cmd, s);
2970 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2971 Cmd_CompleteCommandPrint(cmd, s);
2973 v = Cvar_CompleteCountPossible(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2976 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2977 Cvar_CompleteCvarPrint(cmd->cvars, s, CF_CLIENT | CF_SERVER);
2979 a = Cmd_CompleteAliasCountPossible(cmd, s);
2982 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2983 Cmd_CompleteAliasPrint(cmd, s);
2987 n = Nicks_CompleteCountPossible(line, linepos, s, is_console);
2990 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2991 Cmd_CompleteNicksPrint(n);
2994 if (!(c + v + a + n)) // No possible matches
2997 strlcpy(&line[linepos], s2, linesize - linepos);
3002 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
3004 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
3006 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
3010 text = *(list[3] = Nicks_CompleteBuildList(n));
3012 text = *(Nicks_CompleteBuildList(n));
3015 for (cmd_len = (int)strlen(s);;cmd_len++)
3018 for (i = 0; i < 3; i++)
3020 for (l = list[i];*l;l++)
3021 if ((*l)[cmd_len] != text[cmd_len])
3023 // all possible matches share this character, so we continue...
3026 // if all matches ended at the same position, stop
3027 // (this means there is only one match)
3033 // prevent a buffer overrun by limiting cmd_len according to remaining space
3034 cmd_len = min(cmd_len, (int)linesize - 1 - pos);
3038 memcpy(&line[linepos], text, cmd_len);
3040 // if there is only one match, add a space after it
3041 if (c + v + a + n == 1 && linepos < (int)linesize - 1)
3044 { // was a nick, might have an offset, and needs colors ;) --blub
3045 linepos = pos - Nicks_offset[0];
3046 cmd_len = (int)strlen(Nicks_list[0]);
3047 cmd_len = min(cmd_len, (int)linesize - 3 - pos);
3049 memcpy(&line[linepos] , Nicks_list[0], cmd_len);
3051 if(linepos < (int)(linesize - 7)) // space for color code (^[0-9] or ^xrgb), space and \0
3052 linepos = Nicks_AddLastColor(line, linepos);
3054 line[linepos++] = ' ';
3058 // use strlcat to avoid a buffer overrun
3060 strlcat(line, s2, linesize);
3065 // free the command, cvar, and alias lists
3066 for (i = 0; i < 4; i++)
3068 Mem_Free((void *)list[i]);