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 = {CVAR_CLIENT | CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
46 cvar_t con_notify = {CVAR_CLIENT | CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
47 cvar_t con_notifyalign = {CVAR_CLIENT | CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
49 cvar_t con_chattime = {CVAR_CLIENT | CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
50 cvar_t con_chat = {CVAR_CLIENT | CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
51 cvar_t con_chatpos = {CVAR_CLIENT | CVAR_SAVE, "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 = {CVAR_CLIENT | CVAR_SAVE, "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 = {CVAR_CLIENT | CVAR_SAVE, "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 = {CVAR_CLIENT | CVAR_SAVE, "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 = {CVAR_CLIENT | CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
56 cvar_t con_textsize = {CVAR_CLIENT | CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
57 cvar_t con_notifysize = {CVAR_CLIENT | CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
58 cvar_t con_chatsize = {CVAR_CLIENT | CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
59 cvar_t con_chatsound = {CVAR_CLIENT | CVAR_SAVE, "con_chatsound","1", "enables chat sound to play on message"};
60 cvar_t con_chatsound_file = {CVAR_CLIENT, "con_chatsound_file","sound/misc/talk.wav", "The sound to play for chat messages"};
61 cvar_t con_chatsound_team_file = {CVAR_CLIENT, "con_chatsound_team_file","sound/misc/talk2.wav", "The sound to play for team chat messages"};
62 cvar_t con_chatsound_team_mask = {CVAR_CLIENT | CVAR_READONLY, "con_chatsound_team_mask","40","Magic ASCII code that denotes a team chat message"};
64 cvar_t sys_specialcharactertranslation = {CVAR_CLIENT | CVAR_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 = {CVAR_CLIENT | CVAR_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 = {CVAR_CLIENT | CVAR_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 = {CVAR_CLIENT | CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
73 cvar_t con_nickcompletion_flags = {CVAR_CLIENT | CVAR_SAVE, "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 = {CVAR_CLIENT | CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
87 cvar_t con_completion_timedemo = {CVAR_CLIENT | CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
88 cvar_t con_completion_exec = {CVAR_CLIENT | CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
90 cvar_t condump_stripcolors = {CVAR_CLIENT | CVAR_SERVER| CVAR_SAVE, "condump_stripcolors", "0", "strip color codes from console dumps"};
95 qboolean con_initialized;
97 // used for server replies to rcon command
98 lhnetsocket_t *rcon_redirect_sock = NULL;
99 lhnetaddress_t *rcon_redirect_dest = NULL;
100 int rcon_redirect_bufferpos = 0;
101 char rcon_redirect_buffer[1400];
102 qboolean rcon_redirect_proquakeprotocol = false;
104 // generic functions for console buffers
106 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
109 buf->textsize = textsize;
110 buf->text = (char *) Mem_Alloc(mempool, textsize);
111 buf->maxlines = maxlines;
112 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
113 buf->lines_first = 0;
114 buf->lines_count = 0;
117 /*! The translation table between the graphical font and plain ASCII --KB */
118 static char qfont_table[256] = {
119 '\0', '#', '#', '#', '#', '.', '#', '#',
120 '#', 9, 10, '#', ' ', 13, '.', '.',
121 '[', ']', '0', '1', '2', '3', '4', '5',
122 '6', '7', '8', '9', '.', '<', '=', '>',
123 ' ', '!', '"', '#', '$', '%', '&', '\'',
124 '(', ')', '*', '+', ',', '-', '.', '/',
125 '0', '1', '2', '3', '4', '5', '6', '7',
126 '8', '9', ':', ';', '<', '=', '>', '?',
127 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
128 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
129 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
130 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
131 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
132 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
133 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
134 'x', 'y', 'z', '{', '|', '}', '~', '<',
136 '<', '=', '>', '#', '#', '.', '#', '#',
137 '#', '#', ' ', '#', ' ', '>', '.', '.',
138 '[', ']', '0', '1', '2', '3', '4', '5',
139 '6', '7', '8', '9', '.', '<', '=', '>',
140 ' ', '!', '"', '#', '$', '%', '&', '\'',
141 '(', ')', '*', '+', ',', '-', '.', '/',
142 '0', '1', '2', '3', '4', '5', '6', '7',
143 '8', '9', ':', ';', '<', '=', '>', '?',
144 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
145 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
146 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
147 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
148 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
149 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
150 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
151 'x', 'y', 'z', '{', '|', '}', '~', '<'
155 SanitizeString strips color tags from the string in
156 and writes the result on string out
158 static void SanitizeString(char *in, char *out)
162 if(*in == STRING_COLOR_TAG)
167 out[0] = STRING_COLOR_TAG;
171 else if (*in >= '0' && *in <= '9') // ^[0-9] found
178 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
181 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
183 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
190 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
195 else if (*in != STRING_COLOR_TAG)
198 *out = qfont_table[*(unsigned char*)in];
210 void ConBuffer_Clear (conbuffer_t *buf)
212 buf->lines_count = 0;
220 void ConBuffer_Shutdown(conbuffer_t *buf)
226 Mem_Free(buf->lines);
235 Notifies the console code about the current time
236 (and shifts back times of other entries when the time
240 void ConBuffer_FixTimes(conbuffer_t *buf)
243 if(buf->lines_count >= 1)
245 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
248 for(i = 0; i < buf->lines_count; ++i)
249 CONBUFFER_LINES(buf, i).addtime += diff;
258 Deletes the first line from the console history.
261 void ConBuffer_DeleteLine(conbuffer_t *buf)
263 if(buf->lines_count == 0)
266 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
271 ConBuffer_DeleteLastLine
273 Deletes the last line from the console history.
276 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
278 if(buf->lines_count == 0)
287 Checks if there is space for a line of the given length, and if yes, returns a
288 pointer to the start of such a space, and NULL otherwise.
291 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
293 if(len > buf->textsize)
295 if(buf->lines_count == 0)
299 char *firstline_start = buf->lines[buf->lines_first].start;
300 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
301 // the buffer is cyclic, so we first have two cases...
302 if(firstline_start < lastline_onepastend) // buffer is contiguous
305 if(len <= buf->text + buf->textsize - lastline_onepastend)
306 return lastline_onepastend;
308 else if(len <= firstline_start - buf->text)
313 else // buffer has a contiguous hole
315 if(len <= firstline_start - lastline_onepastend)
316 return lastline_onepastend;
327 Appends a given string as a new line to the console.
330 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
335 // developer_memory 1 during shutdown prints while conbuffer_t is being freed
339 ConBuffer_FixTimes(buf);
341 if(len >= buf->textsize)
344 // only display end of line.
345 line += len - buf->textsize + 1;
346 len = buf->textsize - 1;
348 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
349 ConBuffer_DeleteLine(buf);
350 memcpy(putpos, line, len);
354 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
356 p = &CONBUFFER_LINES_LAST(buf);
359 p->addtime = cl.time;
361 p->height = -1; // calculate when needed
364 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
368 start = buf->lines_count;
369 for(i = start - 1; i >= 0; --i)
371 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
373 if((l->mask & mask_must) != mask_must)
375 if(l->mask & mask_mustnot)
384 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
386 static char copybuf[MAX_INPUTLINE]; // client only
387 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
388 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
389 strlcpy(copybuf, l->start, sz);
394 ==============================================================================
398 ==============================================================================
403 cvar_t log_file = {CVAR_CLIENT | CVAR_SERVER, "log_file", "", "filename to log messages to"};
404 cvar_t log_file_stripcolors = {CVAR_CLIENT | CVAR_SERVER, "log_file_stripcolors", "0", "strip color codes from log messages"};
405 cvar_t log_dest_udp = {CVAR_CLIENT | CVAR_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"};
406 char log_dest_buffer[1400]; // UDP packet
407 size_t log_dest_buffer_pos;
408 unsigned int log_dest_buffer_appending;
409 char crt_log_file [MAX_OSPATH] = "";
410 qfile_t* logfile = NULL;
412 unsigned char* logqueue = NULL;
414 size_t logq_size = 0;
416 void Log_ConPrint (const char *msg);
418 static void Log_DestBuffer_Init(void)
420 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
421 log_dest_buffer_pos = 5;
424 static void Log_DestBuffer_Flush_NoLock(void)
426 lhnetaddress_t log_dest_addr;
427 lhnetsocket_t *log_dest_socket;
428 const char *s = log_dest_udp.string;
429 qboolean have_opened_temp_sockets = false;
430 if(s) if(log_dest_buffer_pos > 5)
432 ++log_dest_buffer_appending;
433 log_dest_buffer[log_dest_buffer_pos++] = 0;
435 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
437 have_opened_temp_sockets = true;
438 NetConn_OpenServerPorts(true);
441 while(COM_ParseToken_Console(&s))
442 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
444 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
446 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
448 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
451 if(have_opened_temp_sockets)
452 NetConn_CloseServerPorts();
453 --log_dest_buffer_appending;
455 log_dest_buffer_pos = 0;
463 void Log_DestBuffer_Flush(void)
466 Thread_LockMutex(con_mutex);
467 Log_DestBuffer_Flush_NoLock();
469 Thread_UnlockMutex(con_mutex);
472 static const char* Log_Timestamp (const char *desc)
474 static char timestamp [128]; // init/shutdown only
481 char timestring [64];
483 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
486 localtime_s (&crt_tm, &crt_time);
487 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
489 crt_tm = localtime (&crt_time);
490 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
494 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
496 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
501 static void Log_Open (void)
503 if (logfile != NULL || log_file.string[0] == '\0')
506 logfile = FS_OpenRealFile(log_file.string, "a", false);
509 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
510 FS_Print (logfile, Log_Timestamp ("Log started"));
519 void Log_Close (void)
524 FS_Print (logfile, Log_Timestamp ("Log stopped"));
525 FS_Print (logfile, "\n");
529 crt_log_file[0] = '\0';
538 void Log_Start (void)
544 // Dump the contents of the log queue into the log file and free it
545 if (logqueue != NULL)
547 unsigned char *temp = logqueue;
552 FS_Write (logfile, temp, logq_ind);
553 if(*log_dest_udp.string)
555 for(pos = 0; pos < logq_ind; )
557 if(log_dest_buffer_pos == 0)
558 Log_DestBuffer_Init();
559 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
560 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
561 log_dest_buffer_pos += n;
562 Log_DestBuffer_Flush_NoLock();
580 void Log_ConPrint (const char *msg)
582 static qboolean inprogress = false;
584 // don't allow feedback loops with memory error reports
589 // Until the host is completely initialized, we maintain a log queue
590 // to store the messages, since the log can't be started before
591 if (logqueue != NULL)
593 size_t remain = logq_size - logq_ind;
594 size_t len = strlen (msg);
596 // If we need to enlarge the log queue
599 size_t factor = ((logq_ind + len) / logq_size) + 1;
600 unsigned char* newqueue;
603 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
604 memcpy (newqueue, logqueue, logq_ind);
607 remain = logq_size - logq_ind;
609 memcpy (&logqueue[logq_ind], msg, len);
616 // Check if log_file has changed
617 if (strcmp (crt_log_file, log_file.string) != 0)
623 // If a log file is available
626 if (log_file_stripcolors.integer)
629 size_t len = strlen(msg);
630 char* sanitizedmsg = (char*)Mem_Alloc(tempmempool, len + 1);
631 memcpy (sanitizedmsg, msg, len);
632 SanitizeString(sanitizedmsg, sanitizedmsg); // SanitizeString's in pointer is always ahead of the out pointer, so this should work.
633 FS_Print (logfile, sanitizedmsg);
634 Mem_Free(sanitizedmsg);
638 FS_Print (logfile, msg);
651 void Log_Printf (const char *logfilename, const char *fmt, ...)
655 file = FS_OpenRealFile(logfilename, "a", true);
660 va_start (argptr, fmt);
661 FS_VPrintf (file, fmt, argptr);
670 ==============================================================================
674 ==============================================================================
682 void Con_ToggleConsole_f(cmd_state_t *cmd)
684 if (COM_CheckParm ("-noconsole"))
685 if (!(key_consoleactive & KEY_CONSOLEACTIVE_USER))
686 return; // only allow the key bind to turn off console
688 // toggle the 'user wants console' bit
689 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
698 void Con_ClearNotify (void)
701 for(i = 0; i < CON_LINES_COUNT; ++i)
702 if(!(CON_LINES(i).mask & CON_MASK_CHAT))
703 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
712 static void Con_MessageMode_f(cmd_state_t *cmd)
714 key_dest = key_message;
715 chat_mode = 0; // "say"
716 if(Cmd_Argc(cmd) > 1)
718 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
719 chat_bufferlen = (unsigned int)strlen(chat_buffer);
729 static void Con_MessageMode2_f(cmd_state_t *cmd)
731 key_dest = key_message;
732 chat_mode = 1; // "say_team"
733 if(Cmd_Argc(cmd) > 1)
735 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
736 chat_bufferlen = (unsigned int)strlen(chat_buffer);
745 static void Con_CommandMode_f(cmd_state_t *cmd)
747 key_dest = key_message;
748 if(Cmd_Argc(cmd) > 1)
750 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
751 chat_bufferlen = (unsigned int)strlen(chat_buffer);
753 chat_mode = -1; // command
761 void Con_CheckResize (void)
766 f = bound(1, con_textsize.value, 128);
767 if(f != con_textsize.value)
768 Cvar_SetValueQuick(&con_textsize, f);
769 width = (int)floor(vid_conwidth.value / con_textsize.value);
770 width = bound(1, width, con.textsize/4);
771 // FIXME uses con in a non abstracted way
773 if (width == con_linewidth)
776 con_linewidth = width;
778 for(i = 0; i < CON_LINES_COUNT; ++i)
779 CON_LINES(i).height = -1; // recalculate when next needed
785 //[515]: the simplest command ever
786 //LadyHavoc: not so simple after I made it print usage...
787 static void Con_Maps_f(cmd_state_t *cmd)
789 if (Cmd_Argc(cmd) > 2)
791 Con_Printf("usage: maps [mapnameprefix]\n");
794 else if (Cmd_Argc(cmd) == 2)
795 GetMapList(Cmd_Argv(cmd, 1), NULL, 0);
797 GetMapList("", NULL, 0);
800 static void Con_ConDump_f(cmd_state_t *cmd)
804 if (Cmd_Argc(cmd) != 2)
806 Con_Printf("usage: condump <filename>\n");
809 file = FS_OpenRealFile(Cmd_Argv(cmd, 1), "w", false);
812 Con_Errorf("condump: unable to write file \"%s\"\n", Cmd_Argv(cmd, 1));
815 if (con_mutex) Thread_LockMutex(con_mutex);
816 for(i = 0; i < CON_LINES_COUNT; ++i)
818 if (condump_stripcolors.integer)
821 size_t len = CON_LINES(i).len;
822 char* sanitizedmsg = (char*)Mem_Alloc(tempmempool, len + 1);
823 memcpy (sanitizedmsg, CON_LINES(i).start, len);
824 SanitizeString(sanitizedmsg, sanitizedmsg); // SanitizeString's in pointer is always ahead of the out pointer, so this should work.
825 FS_Write(file, sanitizedmsg, strlen(sanitizedmsg));
826 Mem_Free(sanitizedmsg);
830 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
832 FS_Write(file, "\n", 1);
834 if (con_mutex) Thread_UnlockMutex(con_mutex);
838 void Con_Clear_f(cmd_state_t *cmd)
840 if (con_mutex) Thread_LockMutex(con_mutex);
841 ConBuffer_Clear(&con);
842 if (con_mutex) Thread_UnlockMutex(con_mutex);
853 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
854 if (Thread_HasThreads())
855 con_mutex = Thread_CreateMutex();
857 // Allocate a log queue, this will be freed after configs are parsed
858 logq_size = MAX_INPUTLINE;
859 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
862 Cvar_RegisterVariable (&sys_colortranslation);
863 Cvar_RegisterVariable (&sys_specialcharactertranslation);
865 Cvar_RegisterVariable (&log_file);
866 Cvar_RegisterVariable (&log_file_stripcolors);
867 Cvar_RegisterVariable (&log_dest_udp);
869 // support for the classic Quake option
870 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
871 if (COM_CheckParm ("-condebug") != 0)
872 Cvar_SetQuick (&log_file, "qconsole.log");
874 // register our cvars
875 Cvar_RegisterVariable (&con_chat);
876 Cvar_RegisterVariable (&con_chatpos);
877 Cvar_RegisterVariable (&con_chatrect_x);
878 Cvar_RegisterVariable (&con_chatrect_y);
879 Cvar_RegisterVariable (&con_chatrect);
880 Cvar_RegisterVariable (&con_chatsize);
881 Cvar_RegisterVariable (&con_chattime);
882 Cvar_RegisterVariable (&con_chatwidth);
883 Cvar_RegisterVariable (&con_notify);
884 Cvar_RegisterVariable (&con_notifyalign);
885 Cvar_RegisterVariable (&con_notifysize);
886 Cvar_RegisterVariable (&con_notifytime);
887 Cvar_RegisterVariable (&con_textsize);
888 Cvar_RegisterVariable (&con_chatsound);
889 Cvar_RegisterVariable (&con_chatsound_file);
890 Cvar_RegisterVariable (&con_chatsound_team_file);
891 Cvar_RegisterVariable (&con_chatsound_team_mask);
894 Cvar_RegisterVariable (&con_nickcompletion);
895 Cvar_RegisterVariable (&con_nickcompletion_flags);
897 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
898 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
899 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
901 Cvar_RegisterVariable (&condump_stripcolors);
903 // register our commands
904 Cmd_AddCommand(CMD_CLIENT, "toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
905 Cmd_AddCommand(CMD_CLIENT, "messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
906 Cmd_AddCommand(CMD_CLIENT, "messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
907 Cmd_AddCommand(CMD_CLIENT, "commandmode", Con_CommandMode_f, "input a console command");
908 Cmd_AddCommand(CMD_SHARED, "clear", Con_Clear_f, "clear console history");
909 Cmd_AddCommand(CMD_SHARED, "maps", Con_Maps_f, "list information about available maps");
910 Cmd_AddCommand(CMD_SHARED, "condump", Con_ConDump_f, "output console history to a file (see also log_file)");
912 con_initialized = true;
913 Con_DPrint("Console initialized.\n");
916 void Con_Shutdown (void)
918 if (con_mutex) Thread_LockMutex(con_mutex);
919 ConBuffer_Shutdown(&con);
920 if (con_mutex) Thread_UnlockMutex(con_mutex);
921 if (con_mutex) Thread_DestroyMutex(con_mutex);con_mutex = NULL;
928 Handles cursor positioning, line wrapping, etc
929 All console printing must go through this in order to be displayed
930 If no console is visible, the notify window will pop up.
933 static void Con_PrintToHistory(const char *txt, int mask)
936 // \n goes to next line
937 // \r deletes current line and makes a new one
939 static int cr_pending = 0;
940 static char buf[CON_TEXTSIZE]; // con_mutex
941 static int bufpos = 0;
943 if(!con.text) // FIXME uses a non-abstracted property of con
950 ConBuffer_DeleteLastLine(&con);
958 ConBuffer_AddLine(&con, buf, bufpos, mask);
963 ConBuffer_AddLine(&con, buf, bufpos, mask);
967 buf[bufpos++] = *txt;
968 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
970 ConBuffer_AddLine(&con, buf, bufpos, mask);
978 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol)
980 rcon_redirect_sock = sock;
981 rcon_redirect_dest = dest;
982 rcon_redirect_proquakeprotocol = proquakeprotocol;
983 if (rcon_redirect_proquakeprotocol)
985 // reserve space for the packet header
986 rcon_redirect_buffer[0] = 0;
987 rcon_redirect_buffer[1] = 0;
988 rcon_redirect_buffer[2] = 0;
989 rcon_redirect_buffer[3] = 0;
990 // this is a reply to a CCREQ_RCON
991 rcon_redirect_buffer[4] = (unsigned char)CCREP_RCON;
994 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
995 rcon_redirect_bufferpos = 5;
998 static void Con_Rcon_Redirect_Flush(void)
1000 if(rcon_redirect_sock)
1002 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
1003 if (rcon_redirect_proquakeprotocol)
1005 // update the length in the packet header
1006 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
1008 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
1010 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1011 rcon_redirect_bufferpos = 5;
1012 rcon_redirect_proquakeprotocol = false;
1015 void Con_Rcon_Redirect_End(void)
1017 Con_Rcon_Redirect_Flush();
1018 rcon_redirect_dest = NULL;
1019 rcon_redirect_sock = NULL;
1022 void Con_Rcon_Redirect_Abort(void)
1024 rcon_redirect_dest = NULL;
1025 rcon_redirect_sock = NULL;
1033 /// Adds a character to the rcon buffer.
1034 static void Con_Rcon_AddChar(int c)
1036 if(log_dest_buffer_appending)
1038 ++log_dest_buffer_appending;
1040 // if this print is in response to an rcon command, add the character
1041 // to the rcon redirect buffer
1043 if (rcon_redirect_dest)
1045 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
1046 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
1047 Con_Rcon_Redirect_Flush();
1049 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
1051 if(log_dest_buffer_pos == 0)
1052 Log_DestBuffer_Init();
1053 log_dest_buffer[log_dest_buffer_pos++] = c;
1054 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
1055 Log_DestBuffer_Flush_NoLock();
1058 log_dest_buffer_pos = 0;
1060 --log_dest_buffer_appending;
1064 * Convert an RGB color to its nearest quake color.
1065 * I'll cheat on this a bit by translating the colors to HSV first,
1066 * S and V decide if it's black or white, otherwise, H will decide the
1068 * @param _r Red (0-255)
1069 * @param _g Green (0-255)
1070 * @param _b Blue (0-255)
1071 * @return A quake color character.
1073 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
1075 float r = ((float)_r)/255.0;
1076 float g = ((float)_g)/255.0;
1077 float b = ((float)_b)/255.0;
1078 float min = min(r, min(g, b));
1079 float max = max(r, max(g, b));
1081 int h; ///< Hue angle [0,360]
1082 float s; ///< Saturation [0,1]
1083 float v = max; ///< In HSV v == max [0,1]
1088 s = 1.0 - (min/max);
1090 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1093 // If the value is less than half, return a black color code.
1094 // Otherwise return a white one.
1100 // Let's get the hue angle to define some colors:
1104 h = (int)(60.0 * (g-b)/(max-min))%360;
1106 h = (int)(60.0 * (b-r)/(max-min) + 120);
1107 else // if(max == b) redundant check
1108 h = (int)(60.0 * (r-g)/(max-min) + 240);
1110 if(h < 36) // *red* to orange
1112 else if(h < 80) // orange over *yellow* to evilish-bright-green
1114 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1116 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1118 else if(h < 270) // darkish blue over *dark blue* to cool purple
1120 else if(h < 330) // cool purple over *purple* to ugly swiny red
1122 else // ugly red to red closes the circly
1131 extern cvar_t timestamps;
1132 extern cvar_t timeformat;
1133 extern qboolean sys_nostdout;
1134 void Con_MaskPrint(int additionalmask, const char *msg)
1136 static int mask = 0;
1137 static int index = 0;
1138 static char line[MAX_INPUTLINE];
1141 Thread_LockMutex(con_mutex);
1145 Con_Rcon_AddChar(*msg);
1146 // if this is the beginning of a new line, print timestamp
1149 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1151 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1152 line[index++] = STRING_COLOR_TAG;
1153 // assert( STRING_COLOR_DEFAULT < 10 )
1154 line[index++] = STRING_COLOR_DEFAULT + '0';
1155 // special color codes for chat messages must always come first
1156 // for Con_PrintToHistory to work properly
1157 if (*msg == 1 || *msg == 2 || *msg == 3)
1162 if (con_chatsound.value)
1164 if(msg[1] == con_chatsound_team_mask.integer && cl.foundteamchatsound)
1165 S_LocalSound (con_chatsound_team_file.string);
1167 S_LocalSound (con_chatsound_file.string);
1170 // Send to chatbox for say/tell (1) and messages (3)
1171 // 3 is just so that a message can be sent to the chatbox without a sound.
1172 if (*msg == 1 || *msg == 3)
1173 mask = CON_MASK_CHAT;
1175 line[index++] = STRING_COLOR_TAG;
1176 line[index++] = '3';
1178 Con_Rcon_AddChar(*msg);
1181 for (;*timestamp;index++, timestamp++)
1182 if (index < (int)sizeof(line) - 2)
1183 line[index] = *timestamp;
1185 mask |= additionalmask;
1187 // append the character
1188 line[index++] = *msg;
1189 // if this is a newline character, we have a complete line to print
1190 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1192 // terminate the line
1196 // send to scrollable buffer
1197 if (con_initialized && cls.state != ca_dedicated)
1199 Con_PrintToHistory(line, mask);
1201 // send to terminal or dedicated server window
1203 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1205 if(sys_specialcharactertranslation.integer)
1212 int ch = u8_getchar(p, &q);
1213 if(ch >= 0xE000 && ch <= 0xE0FF && ((unsigned char) qfont_table[ch - 0xE000]) >= 0x20)
1215 *p = qfont_table[ch - 0xE000];
1217 memmove(p+1, q, strlen(q)+1);
1225 if(sys_colortranslation.integer == 1) // ANSI
1227 static char printline[MAX_INPUTLINE * 4 + 3];
1228 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1229 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1234 for(in = line, out = printline; *in; ++in)
1238 case STRING_COLOR_TAG:
1239 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1241 char r = tolower(in[2]);
1242 char g = tolower(in[3]);
1243 char b = tolower(in[4]);
1244 // it's a hex digit already, so the else part needs no check --blub
1245 if(isdigit(r)) r -= '0';
1247 if(isdigit(g)) g -= '0';
1249 if(isdigit(b)) b -= '0';
1252 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1253 in += 3; // 3 only, the switch down there does the fourth
1260 case STRING_COLOR_TAG:
1262 *out++ = STRING_COLOR_TAG;
1268 if(lastcolor == 0) break; else lastcolor = 0;
1269 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1274 if(lastcolor == 1) break; else lastcolor = 1;
1275 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1280 if(lastcolor == 2) break; else lastcolor = 2;
1281 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1286 if(lastcolor == 3) break; else lastcolor = 3;
1287 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1292 if(lastcolor == 4) break; else lastcolor = 4;
1293 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1298 if(lastcolor == 5) break; else lastcolor = 5;
1299 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1304 if(lastcolor == 6) break; else lastcolor = 6;
1305 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1310 // bold normal color
1312 if(lastcolor == 8) break; else lastcolor = 8;
1313 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1316 *out++ = STRING_COLOR_TAG;
1323 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1340 Sys_PrintToTerminal(printline);
1342 else if(sys_colortranslation.integer == 2) // Quake
1344 Sys_PrintToTerminal(line);
1348 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1351 for(in = line, out = printline; *in; ++in)
1355 case STRING_COLOR_TAG:
1358 case STRING_COLOR_RGB_TAG_CHAR:
1359 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1364 *out++ = STRING_COLOR_TAG;
1365 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1368 case STRING_COLOR_TAG:
1370 *out++ = STRING_COLOR_TAG;
1385 *out++ = STRING_COLOR_TAG;
1395 Sys_PrintToTerminal(printline);
1398 // empty the line buffer
1405 Thread_UnlockMutex(con_mutex);
1413 void Con_MaskPrintf(int mask, const char *fmt, ...)
1416 char msg[MAX_INPUTLINE];
1418 va_start(argptr,fmt);
1419 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1422 Con_MaskPrint(mask, msg);
1430 void Con_Print(const char *msg)
1432 Con_MaskPrint(CON_MASK_PRINT, msg);
1440 void Con_Printf(const char *fmt, ...)
1443 char msg[MAX_INPUTLINE];
1445 va_start(argptr,fmt);
1446 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1449 Con_MaskPrint(CON_MASK_PRINT, msg);
1457 void Con_Warn(const char *msg)
1459 Con_Printf("^3%s",msg);
1467 void Con_Warnf(const char *fmt, ...)
1470 char msg[MAX_INPUTLINE];
1472 va_start(argptr,fmt);
1473 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1476 Con_Printf("^3%s",msg);
1484 void Con_Error(const char *msg)
1486 Con_Printf("^1%s",msg);
1494 void Con_Errorf(const char *fmt, ...)
1497 char msg[MAX_INPUTLINE];
1499 va_start(argptr,fmt);
1500 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1503 Con_Printf("^1%s",msg);
1512 void Con_DPrint(const char *msg)
1514 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1517 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1525 void Con_DPrintf(const char *fmt, ...)
1528 char msg[MAX_INPUTLINE];
1530 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1533 va_start(argptr,fmt);
1534 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1537 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1542 ==============================================================================
1546 ==============================================================================
1553 The input line scrolls horizontally if typing goes beyond the right edge
1555 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1558 static void Con_DrawInput (void)
1562 char text[sizeof(key_line)+5+1]; // space for ^^xRGB too
1567 if (!key_consoleactive)
1568 return; // don't draw anything
1570 strlcpy(text, key_line, sizeof(text));
1572 // Advanced Console Editing by Radix radix@planetquake.com
1573 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1575 y = (int)strlen(text);
1577 // make the color code visible when the cursor is inside it
1578 if(text[key_linepos] != 0)
1580 for(i=1; i < 5 && key_linepos - i > 0; ++i)
1581 if(text[key_linepos-i] == STRING_COLOR_TAG)
1583 int caret_pos, ofs = 0;
1584 caret_pos = key_linepos - i;
1585 if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG)
1587 else if(i == 1 && isdigit(text[caret_pos+1]))
1589 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]))
1591 if(ofs && (size_t)(y + ofs + 1) < sizeof(text))
1594 while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG)
1598 // str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing)
1599 // str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing)
1600 memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos);
1601 text[caret_pos + ofs] = STRING_COLOR_TAG;
1610 len_out = key_linepos;
1612 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1613 x = vid_conwidth.value * 0.95 - xo; // scroll
1618 DrawQ_String(x, con_vislines - con_textsize.value*2, text, y + 3, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, FONT_CONSOLE );
1620 // draw a cursor on top of this
1621 if ((int)(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(x + xo, con_vislines - con_textsize.value*2, text, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, FONT_CONSOLE);
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_PrintfToTerminal("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, qboolean isContinuation)
1683 (void) isContinuation;
1687 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean 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;
1795 char temptext[MAX_INPUTLINE];
1799 if (con_mutex) Thread_LockMutex(con_mutex);
1800 ConBuffer_FixTimes(&con);
1802 numChatlines = con_chat.integer;
1804 chatpos = con_chatpos.integer;
1806 if (con_notify.integer < 0)
1807 Cvar_SetValueQuick(&con_notify, 0);
1808 if (gamemode == GAME_TRANSFUSION)
1809 v = 8; // vertical offset
1813 // GAME_NEXUIZ: center, otherwise left justify
1814 align = con_notifyalign.value;
1815 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1817 if(IS_OLDNEXUIZ_DERIVED(gamemode))
1821 if(numChatlines || !con_chatrect.integer)
1825 // first chat, input line, then notify
1827 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1829 else if(chatpos > 0)
1831 // first notify, then (chatpos-1) empty lines, then chat, then input
1833 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1835 else // if(chatpos < 0)
1837 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1839 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1844 // just notify and input
1846 chatstart = 0; // shut off gcc warning
1849 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, "");
1851 if(con_chatrect.integer)
1853 x = con_chatrect_x.value * vid_conwidth.value;
1854 v = con_chatrect_y.value * vid_conheight.value;
1859 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1862 height = numChatlines * con_chatsize.value;
1866 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 ... ");
1869 if (key_dest == key_message)
1871 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1872 int colorindex = -1;
1875 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL, charbuf16);
1877 // LadyHavoc: speedup, and other improvements
1879 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1881 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1883 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1886 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1887 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1889 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1891 if (con_mutex) Thread_UnlockMutex(con_mutex);
1898 Returns the height of a given console line; calculates it if necessary.
1901 static int Con_LineHeight(int lineno)
1903 con_lineinfo_t *li = &CON_LINES(lineno);
1904 if(li->height == -1)
1906 float width = vid_conwidth.value;
1908 ti.fontsize = con_textsize.value;
1909 ti.font = FONT_CONSOLE;
1910 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1919 Draws a line of the console; returns its height in lines.
1920 If alpha is 0, the line is not drawn, but still wrapped and its height
1924 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1926 float width = vid_conwidth.value;
1928 con_lineinfo_t *li = &CON_LINES(lineno);
1930 if((li->mask & mask_must) != mask_must)
1932 if((li->mask & mask_mustnot) != 0)
1935 ti.continuationString = "";
1937 ti.fontsize = con_textsize.value;
1938 ti.font = FONT_CONSOLE;
1940 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1945 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1952 Calculates the last visible line index and how much to show of it based on
1956 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1961 if(con_backscroll < 0)
1966 // now count until we saw con_backscroll actual lines
1967 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1968 if((CON_LINES(i).mask & mask_must) == mask_must)
1969 if((CON_LINES(i).mask & mask_mustnot) == 0)
1971 int h = Con_LineHeight(i);
1973 // line is the last visible line?
1975 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1977 *limitlast = lines_seen + h - con_backscroll;
1984 // if we get here, no line was on screen - scroll so that one line is
1986 con_backscroll = lines_seen - 1;
1994 Draws the console with the solid background
1995 The typing input line at the bottom should only be drawn if typing is allowed
1998 void Con_DrawConsole (int lines)
2000 float alpha, alpha0;
2003 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
2004 cachepic_t *conbackpic;
2005 unsigned int conbackflags;
2010 if (con_mutex) Thread_LockMutex(con_mutex);
2012 if (con_backscroll < 0)
2015 con_vislines = lines;
2017 r_draw2d_force = true;
2019 // draw the background
2020 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
2021 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
2023 sx = scr_conscroll_x.value;
2024 sy = scr_conscroll_y.value;
2025 conbackflags = CACHEPICFLAG_FAILONMISSING; // So console is readable when game content is missing
2026 if (sx != 0 || sy != 0)
2027 conbackflags &= CACHEPICFLAG_NOCLAMP;
2028 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", conbackflags) : NULL;
2029 sx *= realtime; sy *= 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 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
2041 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
2043 sx = scr_conscroll2_x.value;
2044 sy = scr_conscroll2_y.value;
2045 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2046 sx *= realtime; sy *= 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 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
2058 sx = scr_conscroll3_x.value;
2059 sy = scr_conscroll3_y.value;
2060 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2061 sx *= realtime; sy *= realtime;
2062 sx -= floor(sx); sy -= floor(sy);
2063 if(Draw_IsPicLoaded(conbackpic))
2064 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2065 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2066 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2067 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2068 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2071 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);
2077 int count = CON_LINES_COUNT;
2078 float ymax = con_vislines - 2 * con_textsize.value;
2079 float y = ymax + con_textsize.value * con_backscroll;
2080 for (i = 0;i < count && y >= 0;i++)
2081 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
2082 // fix any excessive scrollback for the next frame
2083 if (i >= count && y >= 0)
2085 con_backscroll -= (int)(y / con_textsize.value);
2086 if (con_backscroll < 0)
2091 if(CON_LINES_COUNT > 0)
2093 int i, last, limitlast;
2095 float ymax = con_vislines - 2 * con_textsize.value;
2096 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2097 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2098 y = ymax - con_textsize.value;
2101 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
2106 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
2108 break; // top of console buffer
2110 break; // top of console window
2117 // draw the input prompt, user text, and cursor if desired
2120 r_draw2d_force = false;
2121 if (con_mutex) Thread_UnlockMutex(con_mutex);
2128 Prints not only map filename, but also
2129 its format (q1/q2/q3/hl) and even its message
2131 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2132 //LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2133 //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
2134 //LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2135 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
2139 int i, k, max, p, o, min;
2142 unsigned char buf[1024];
2144 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2145 t = FS_Search(message, 1, true);
2148 if (t->numfilenames > 1)
2149 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2150 len = (unsigned char *)Z_Malloc(t->numfilenames);
2152 for(max=i=0;i<t->numfilenames;i++)
2154 k = (int)strlen(t->filenames[i]);
2164 for(i=0;i<t->numfilenames;i++)
2166 int lumpofs = 0, lumplen = 0;
2167 char *entities = NULL;
2168 const char *data = NULL;
2170 char entfilename[MAX_QPATH];
2173 strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2175 f = FS_OpenVirtualFile(t->filenames[i], true);
2178 strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2179 memset(buf, 0, 1024);
2180 FS_Read(f, buf, 1024);
2181 if (!memcmp(buf, "IBSP", 4))
2183 p = LittleLong(((int *)buf)[1]);
2184 if (p == Q3BSPVERSION)
2186 q3dheader_t *header = (q3dheader_t *)buf;
2187 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2188 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2189 dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2191 else if (p == Q2BSPVERSION)
2193 q2dheader_t *header = (q2dheader_t *)buf;
2194 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2195 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2196 dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2199 dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2201 else if (BuffLittleLong(buf) == BSPVERSION)
2203 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2204 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2205 dpsnprintf(desc, sizeof(desc), "BSP29");
2207 else if (BuffLittleLong(buf) == 30)
2209 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2210 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2211 dpsnprintf(desc, sizeof(desc), "BSPHL");
2213 else if (!memcmp(buf, "BSP2", 4))
2215 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2216 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2217 dpsnprintf(desc, sizeof(desc), "BSP2");
2219 else if (!memcmp(buf, "2PSB", 4))
2221 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2222 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2223 dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2227 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2229 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2230 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2231 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2232 if (!entities && lumplen >= 10)
2234 FS_Seek(f, lumpofs, SEEK_SET);
2235 entities = (char *)Z_Malloc(lumplen + 1);
2236 FS_Read(f, entities, lumplen);
2240 // if there are entities to parse, a missing message key just
2241 // means there is no title, so clear the message string now
2247 if (!COM_ParseToken_Simple(&data, false, false, true))
2249 if (com_token[0] == '{')
2251 if (com_token[0] == '}')
2253 // skip leading whitespace
2254 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2255 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2256 keyname[l] = com_token[k+l];
2258 if (!COM_ParseToken_Simple(&data, false, false, true))
2260 if (developer_extra.integer)
2261 Con_DPrintf("key: %s %s\n", keyname, com_token);
2262 if (!strcmp(keyname, "message"))
2264 // get the message contents
2265 strlcpy(message, com_token, sizeof(message));
2275 *(t->filenames[i]+len[i]+5) = 0;
2276 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2281 k = *(t->filenames[0]+5+p);
2284 for(i=1;i<t->numfilenames;i++)
2285 if(*(t->filenames[i]+5+p) != k)
2289 if(p > o && completedname && completednamebufferlength > 0)
2291 memset(completedname, 0, completednamebufferlength);
2292 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2302 New function for tab-completion system
2303 Added by EvilTypeGuy
2304 MEGA Thanks to Taniwha
2307 void Con_DisplayList(const char **list)
2309 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2310 const char **walk = list;
2313 len = (int)strlen(*walk);
2321 len = (int)strlen(*list);
2322 if (pos + maxlen >= width) {
2328 for (i = 0; i < (maxlen - len); i++)
2340 // Now it becomes TRICKY :D --blub
2341 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2342 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2343 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2344 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
2345 static int Nicks_matchpos;
2347 // co against <<:BLASTER:>> is true!?
2348 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2352 if(tolower(*a) == tolower(*b))
2366 return (*a < *b) ? -1 : 1;
2370 return (*a < *b) ? -1 : 1;
2374 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2377 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2379 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2380 return Nicks_strncasecmp_nospaces(a, b, a_len);
2381 return strncasecmp(a, b, a_len);
2384 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2386 // ignore non alphanumerics of B
2387 // if A contains a non-alphanumeric, B must contain it as well though!
2390 qboolean alnum_a, alnum_b;
2392 if(tolower(*a) == tolower(*b))
2394 if(*a == 0) // end of both strings, they're equal
2401 // not equal, end of one string?
2406 // ignore non alphanumerics
2407 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2408 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2409 if(!alnum_a) // b must contain this
2410 return (*a < *b) ? -1 : 1;
2413 // otherwise, both are alnum, they're just not equal, return the appropriate number
2415 return (*a < *b) ? -1 : 1;
2421 /* Nicks_CompleteCountPossible
2423 Count the number of possible nicks to complete
2425 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2433 if(!con_nickcompletion.integer)
2436 // changed that to 1
2437 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2440 for(i = 0; i < cl.maxclients; ++i)
2443 if(!cl.scores[p].name[0])
2446 SanitizeString(cl.scores[p].name, name);
2447 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2453 spos = pos - 1; // no need for a minimum of characters :)
2457 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2459 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2460 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2466 if(isCon && spos == 0)
2468 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2474 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2475 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2477 // the sanitized list
2478 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2481 Nicks_matchpos = match;
2484 Nicks_offset[count] = s - (&line[match]);
2485 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2492 static void Cmd_CompleteNicksPrint(int count)
2495 for(i = 0; i < count; ++i)
2496 Con_Printf("%s\n", Nicks_list[i]);
2499 static void Nicks_CutMatchesNormal(int count)
2501 // cut match 0 down to the longest possible completion
2504 c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2505 for(i = 1; i < count; ++i)
2507 l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2511 for(l = 0; l <= c; ++l)
2512 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2518 Nicks_sanlist[0][c+1] = 0;
2519 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2522 static unsigned int Nicks_strcleanlen(const char *s)
2527 if( (*s >= 'a' && *s <= 'z') ||
2528 (*s >= 'A' && *s <= 'Z') ||
2529 (*s >= '0' && *s <= '9') ||
2537 static void Nicks_CutMatchesAlphaNumeric(int count)
2539 // cut match 0 down to the longest possible completion
2542 char tempstr[sizeof(Nicks_sanlist[0])];
2544 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2546 c = (unsigned int)strlen(Nicks_sanlist[0]);
2547 for(i = 0, l = 0; i < (int)c; ++i)
2549 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2550 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2551 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2553 tempstr[l++] = Nicks_sanlist[0][i];
2558 for(i = 1; i < count; ++i)
2561 b = Nicks_sanlist[i];
2571 if(tolower(*a) == tolower(*b))
2577 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2579 // b is alnum, so cut
2586 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2587 Nicks_CutMatchesNormal(count);
2588 //if(!Nicks_sanlist[0][0])
2589 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2591 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2592 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2596 static void Nicks_CutMatchesNoSpaces(int count)
2598 // cut match 0 down to the longest possible completion
2601 char tempstr[sizeof(Nicks_sanlist[0])];
2604 c = (unsigned int)strlen(Nicks_sanlist[0]);
2605 for(i = 0, l = 0; i < (int)c; ++i)
2607 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2609 tempstr[l++] = Nicks_sanlist[0][i];
2614 for(i = 1; i < count; ++i)
2617 b = Nicks_sanlist[i];
2627 if(tolower(*a) == tolower(*b))
2641 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2642 Nicks_CutMatchesNormal(count);
2643 //if(!Nicks_sanlist[0][0])
2644 //Con_Printf("TS: %s\n", tempstr);
2645 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2647 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2648 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2652 static void Nicks_CutMatches(int count)
2654 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2655 Nicks_CutMatchesAlphaNumeric(count);
2656 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2657 Nicks_CutMatchesNoSpaces(count);
2659 Nicks_CutMatchesNormal(count);
2662 static const char **Nicks_CompleteBuildList(int count)
2666 // the list is freed by Con_CompleteCommandLine, so create a char**
2667 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2669 for(; bpos < count; ++bpos)
2670 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2672 Nicks_CutMatches(count);
2680 Restores the previous used color, after the autocompleted name.
2682 static int Nicks_AddLastColor(char *buffer, int pos)
2684 qboolean quote_added = false;
2686 int color = STRING_COLOR_DEFAULT + '0';
2687 char r = 0, g = 0, b = 0;
2689 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2691 // we'll have to add a quote :)
2692 buffer[pos++] = '\"';
2696 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2698 // add color when no quote was added, or when flags &4?
2700 for(match = Nicks_matchpos-1; match >= 0; --match)
2702 if(buffer[match] == STRING_COLOR_TAG)
2704 if( isdigit(buffer[match+1]) )
2706 color = buffer[match+1];
2709 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2711 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2713 r = buffer[match+2];
2714 g = buffer[match+3];
2715 b = buffer[match+4];
2724 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2726 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2727 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2730 buffer[pos++] = STRING_COLOR_TAG;
2733 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2739 buffer[pos++] = color;
2744 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2747 /*if(!con_nickcompletion.integer)
2748 return; is tested in Nicks_CompletionCountPossible */
2749 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2755 msg = Nicks_list[0];
2756 len = min(size - Nicks_matchpos - 3, strlen(msg));
2757 memcpy(&buffer[Nicks_matchpos], msg, len);
2758 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2759 len = (int)Nicks_AddLastColor(buffer, Nicks_matchpos+(int)len);
2760 buffer[len++] = ' ';
2767 Con_Printf("\n%i possible nicks:\n", n);
2768 Cmd_CompleteNicksPrint(n);
2770 Nicks_CutMatches(n);
2772 msg = Nicks_sanlist[0];
2773 len = (int)min(size - Nicks_matchpos, strlen(msg));
2774 memcpy(&buffer[Nicks_matchpos], msg, len);
2775 buffer[Nicks_matchpos + len] = 0;
2777 return Nicks_matchpos + len;
2784 Con_CompleteCommandLine
2786 New function for tab-completion system
2787 Added by EvilTypeGuy
2788 Thanks to Fett erich@heintz.com
2790 Enhanced to tab-complete map names by [515]
2793 void Con_CompleteCommandLine (cmd_state_t *cmd)
2795 const char *text = "";
2797 const char **list[4] = {0, 0, 0, 0};
2800 int c, v, a, i, cmd_len, pos, k;
2801 int n; // nicks --blub
2802 const char *space, *patterns;
2805 //find what we want to complete
2810 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2816 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2817 key_line[key_linepos] = 0; //hide them
2819 space = strchr(key_line + 1, ' ');
2820 if(space && pos == (space - key_line) + 1)
2822 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2824 patterns = Cvar_VariableString(cmd->cvars, va(vabuf, sizeof(vabuf), "con_completion_%s", command), CVAR_CLIENT | CVAR_SERVER); // TODO maybe use a better place for this?
2825 if(patterns && !*patterns)
2826 patterns = NULL; // get rid of the empty string
2828 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2832 if (GetMapList(s, t, sizeof(t)))
2834 // first move the cursor
2835 key_linepos += (int)strlen(t) - (int)strlen(s);
2837 // and now do the actual work
2839 strlcat(key_line, t, MAX_INPUTLINE);
2840 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2842 // and fix the cursor
2843 if(key_linepos > (int) strlen(key_line))
2844 key_linepos = (int) strlen(key_line);
2853 stringlist_t resultbuf, dirbuf;
2856 // // store completion patterns (space separated) for command foo in con_completion_foo
2857 // set con_completion_foo "foodata/*.foodefault *.foo"
2860 // Note: patterns with slash are always treated as absolute
2861 // patterns; patterns without slash search in the innermost
2862 // directory the user specified. There is no way to "complete into"
2863 // a directory as of now, as directories seem to be unknown to the
2867 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2868 // set con_completion_playdemo "*.dem"
2869 // set con_completion_play "*.wav *.ogg"
2871 // TODO somehow add support for directories; these shall complete
2872 // to their name + an appended slash.
2874 stringlistinit(&resultbuf);
2875 stringlistinit(&dirbuf);
2876 while(COM_ParseToken_Simple(&patterns, false, false, true))
2879 if(strchr(com_token, '/'))
2881 search = FS_Search(com_token, true, true);
2885 const char *slash = strrchr(s, '/');
2888 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2889 strlcat(t, com_token, sizeof(t));
2890 search = FS_Search(t, true, true);
2893 search = FS_Search(com_token, true, true);
2897 for(i = 0; i < search->numfilenames; ++i)
2898 if(!strncmp(search->filenames[i], s, strlen(s)))
2899 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2900 stringlistappend(&resultbuf, search->filenames[i]);
2901 FS_FreeSearch(search);
2905 // In any case, add directory names
2908 const char *slash = strrchr(s, '/');
2911 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2912 strlcat(t, "*", sizeof(t));
2913 search = FS_Search(t, true, true);
2916 search = FS_Search("*", true, true);
2919 for(i = 0; i < search->numfilenames; ++i)
2920 if(!strncmp(search->filenames[i], s, strlen(s)))
2921 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2922 stringlistappend(&dirbuf, search->filenames[i]);
2923 FS_FreeSearch(search);
2927 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2930 unsigned int matchchars;
2931 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2933 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2936 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2938 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2942 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2943 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2944 for(i = 0; i < dirbuf.numstrings; ++i)
2946 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2948 for(i = 0; i < resultbuf.numstrings; ++i)
2950 Con_Printf("%s\n", resultbuf.strings[i]);
2952 matchchars = sizeof(t) - 1;
2953 if(resultbuf.numstrings > 0)
2955 p = resultbuf.strings[0];
2956 q = resultbuf.strings[resultbuf.numstrings - 1];
2957 for(; *p && *p == *q; ++p, ++q);
2958 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2960 if(dirbuf.numstrings > 0)
2962 p = dirbuf.strings[0];
2963 q = dirbuf.strings[dirbuf.numstrings - 1];
2964 for(; *p && *p == *q; ++p, ++q);
2965 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2967 // now p points to the first non-equal character, or to the end
2968 // of resultbuf.strings[0]. We want to append the characters
2969 // from resultbuf.strings[0] to (not including) p as these are
2970 // the unique prefix
2971 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2974 // first move the cursor
2975 key_linepos += (int)strlen(t) - (int)strlen(s);
2977 // and now do the actual work
2979 strlcat(key_line, t, MAX_INPUTLINE);
2980 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2982 // and fix the cursor
2983 if(key_linepos > (int) strlen(key_line))
2984 key_linepos = (int) strlen(key_line);
2986 stringlistfreecontents(&resultbuf);
2987 stringlistfreecontents(&dirbuf);
2989 return; // bail out, when we complete for a command that wants a file name
2994 // Count number of possible matches and print them
2995 c = Cmd_CompleteCountPossible(cmd, s);
2998 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2999 Cmd_CompleteCommandPrint(cmd, s);
3001 v = Cvar_CompleteCountPossible(cmd->cvars, s, CVAR_CLIENT | CVAR_SERVER);
3004 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
3005 Cvar_CompleteCvarPrint(cmd->cvars, s, CVAR_CLIENT | CVAR_SERVER);
3007 a = Cmd_CompleteAliasCountPossible(cmd, s);
3010 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
3011 Cmd_CompleteAliasPrint(cmd, s);
3013 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
3016 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
3017 Cmd_CompleteNicksPrint(n);
3020 if (!(c + v + a + n)) // No possible matches
3023 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
3028 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
3030 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
3032 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
3034 text = *(list[3] = Nicks_CompleteBuildList(n));
3036 for (cmd_len = (int)strlen(s);;cmd_len++)
3039 for (i = 0; i < 3; i++)
3041 for (l = list[i];*l;l++)
3042 if ((*l)[cmd_len] != text[cmd_len])
3044 // all possible matches share this character, so we continue...
3047 // if all matches ended at the same position, stop
3048 // (this means there is only one match)
3054 // prevent a buffer overrun by limiting cmd_len according to remaining space
3055 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
3059 memcpy(&key_line[key_linepos], text, cmd_len);
3060 key_linepos += cmd_len;
3061 // if there is only one match, add a space after it
3062 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
3065 { // was a nick, might have an offset, and needs colors ;) --blub
3066 key_linepos = pos - Nicks_offset[0];
3067 cmd_len = (int)strlen(Nicks_list[0]);
3068 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
3070 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
3071 key_linepos += cmd_len;
3072 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
3073 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
3075 key_line[key_linepos++] = ' ';
3079 // use strlcat to avoid a buffer overrun
3080 key_line[key_linepos] = 0;
3081 strlcat(key_line, s2, sizeof(key_line));
3083 // free the command, cvar, and alias lists
3084 for (i = 0; i < 4; i++)
3086 Mem_Free((void *)list[i]);