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, "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 // initialize console window (only used by sys_win.c)
916 Con_DPrint("Console initialized.\n");
919 void Con_Shutdown (void)
921 if (con_mutex) Thread_LockMutex(con_mutex);
922 ConBuffer_Shutdown(&con);
923 if (con_mutex) Thread_UnlockMutex(con_mutex);
924 if (con_mutex) Thread_DestroyMutex(con_mutex);con_mutex = NULL;
931 Handles cursor positioning, line wrapping, etc
932 All console printing must go through this in order to be displayed
933 If no console is visible, the notify window will pop up.
936 static void Con_PrintToHistory(const char *txt, int mask)
939 // \n goes to next line
940 // \r deletes current line and makes a new one
942 static int cr_pending = 0;
943 static char buf[CON_TEXTSIZE]; // con_mutex
944 static int bufpos = 0;
946 if(!con.text) // FIXME uses a non-abstracted property of con
953 ConBuffer_DeleteLastLine(&con);
961 ConBuffer_AddLine(&con, buf, bufpos, mask);
966 ConBuffer_AddLine(&con, buf, bufpos, mask);
970 buf[bufpos++] = *txt;
971 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
973 ConBuffer_AddLine(&con, buf, bufpos, mask);
981 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol)
983 rcon_redirect_sock = sock;
984 rcon_redirect_dest = dest;
985 rcon_redirect_proquakeprotocol = proquakeprotocol;
986 if (rcon_redirect_proquakeprotocol)
988 // reserve space for the packet header
989 rcon_redirect_buffer[0] = 0;
990 rcon_redirect_buffer[1] = 0;
991 rcon_redirect_buffer[2] = 0;
992 rcon_redirect_buffer[3] = 0;
993 // this is a reply to a CCREQ_RCON
994 rcon_redirect_buffer[4] = (unsigned char)CCREP_RCON;
997 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
998 rcon_redirect_bufferpos = 5;
1001 static void Con_Rcon_Redirect_Flush(void)
1003 if(rcon_redirect_sock)
1005 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
1006 if (rcon_redirect_proquakeprotocol)
1008 // update the length in the packet header
1009 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
1011 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
1013 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1014 rcon_redirect_bufferpos = 5;
1015 rcon_redirect_proquakeprotocol = false;
1018 void Con_Rcon_Redirect_End(void)
1020 Con_Rcon_Redirect_Flush();
1021 rcon_redirect_dest = NULL;
1022 rcon_redirect_sock = NULL;
1025 void Con_Rcon_Redirect_Abort(void)
1027 rcon_redirect_dest = NULL;
1028 rcon_redirect_sock = NULL;
1036 /// Adds a character to the rcon buffer.
1037 static void Con_Rcon_AddChar(int c)
1039 if(log_dest_buffer_appending)
1041 ++log_dest_buffer_appending;
1043 // if this print is in response to an rcon command, add the character
1044 // to the rcon redirect buffer
1046 if (rcon_redirect_dest)
1048 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
1049 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
1050 Con_Rcon_Redirect_Flush();
1052 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
1054 if(log_dest_buffer_pos == 0)
1055 Log_DestBuffer_Init();
1056 log_dest_buffer[log_dest_buffer_pos++] = c;
1057 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
1058 Log_DestBuffer_Flush_NoLock();
1061 log_dest_buffer_pos = 0;
1063 --log_dest_buffer_appending;
1067 * Convert an RGB color to its nearest quake color.
1068 * I'll cheat on this a bit by translating the colors to HSV first,
1069 * S and V decide if it's black or white, otherwise, H will decide the
1071 * @param _r Red (0-255)
1072 * @param _g Green (0-255)
1073 * @param _b Blue (0-255)
1074 * @return A quake color character.
1076 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
1078 float r = ((float)_r)/255.0;
1079 float g = ((float)_g)/255.0;
1080 float b = ((float)_b)/255.0;
1081 float min = min(r, min(g, b));
1082 float max = max(r, max(g, b));
1084 int h; ///< Hue angle [0,360]
1085 float s; ///< Saturation [0,1]
1086 float v = max; ///< In HSV v == max [0,1]
1091 s = 1.0 - (min/max);
1093 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1096 // If the value is less than half, return a black color code.
1097 // Otherwise return a white one.
1103 // Let's get the hue angle to define some colors:
1107 h = (int)(60.0 * (g-b)/(max-min))%360;
1109 h = (int)(60.0 * (b-r)/(max-min) + 120);
1110 else // if(max == b) redundant check
1111 h = (int)(60.0 * (r-g)/(max-min) + 240);
1113 if(h < 36) // *red* to orange
1115 else if(h < 80) // orange over *yellow* to evilish-bright-green
1117 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1119 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1121 else if(h < 270) // darkish blue over *dark blue* to cool purple
1123 else if(h < 330) // cool purple over *purple* to ugly swiny red
1125 else // ugly red to red closes the circly
1134 extern cvar_t timestamps;
1135 extern cvar_t timeformat;
1136 extern qboolean sys_nostdout;
1137 void Con_MaskPrint(int additionalmask, const char *msg)
1139 static int mask = 0;
1140 static int index = 0;
1141 static char line[MAX_INPUTLINE];
1144 Thread_LockMutex(con_mutex);
1148 Con_Rcon_AddChar(*msg);
1149 // if this is the beginning of a new line, print timestamp
1152 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1154 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1155 line[index++] = STRING_COLOR_TAG;
1156 // assert( STRING_COLOR_DEFAULT < 10 )
1157 line[index++] = STRING_COLOR_DEFAULT + '0';
1158 // special color codes for chat messages must always come first
1159 // for Con_PrintToHistory to work properly
1160 if (*msg == 1 || *msg == 2 || *msg == 3)
1165 if (con_chatsound.value)
1167 if(msg[1] == con_chatsound_team_mask.integer && cl.foundteamchatsound)
1168 S_LocalSound (con_chatsound_team_file.string);
1170 S_LocalSound (con_chatsound_file.string);
1173 // Send to chatbox for say/tell (1) and messages (3)
1174 // 3 is just so that a message can be sent to the chatbox without a sound.
1175 if (*msg == 1 || *msg == 3)
1176 mask = CON_MASK_CHAT;
1178 line[index++] = STRING_COLOR_TAG;
1179 line[index++] = '3';
1181 Con_Rcon_AddChar(*msg);
1184 for (;*timestamp;index++, timestamp++)
1185 if (index < (int)sizeof(line) - 2)
1186 line[index] = *timestamp;
1188 mask |= additionalmask;
1190 // append the character
1191 line[index++] = *msg;
1192 // if this is a newline character, we have a complete line to print
1193 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1195 // terminate the line
1199 // send to scrollable buffer
1200 if (con_initialized && cls.state != ca_dedicated)
1202 Con_PrintToHistory(line, mask);
1204 // send to terminal or dedicated server window
1206 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1208 if(sys_specialcharactertranslation.integer)
1215 int ch = u8_getchar(p, &q);
1216 if(ch >= 0xE000 && ch <= 0xE0FF && ((unsigned char) qfont_table[ch - 0xE000]) >= 0x20)
1218 *p = qfont_table[ch - 0xE000];
1220 memmove(p+1, q, strlen(q)+1);
1228 if(sys_colortranslation.integer == 1) // ANSI
1230 static char printline[MAX_INPUTLINE * 4 + 3];
1231 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1232 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1237 for(in = line, out = printline; *in; ++in)
1241 case STRING_COLOR_TAG:
1242 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1244 char r = tolower(in[2]);
1245 char g = tolower(in[3]);
1246 char b = tolower(in[4]);
1247 // it's a hex digit already, so the else part needs no check --blub
1248 if(isdigit(r)) r -= '0';
1250 if(isdigit(g)) g -= '0';
1252 if(isdigit(b)) b -= '0';
1255 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1256 in += 3; // 3 only, the switch down there does the fourth
1263 case STRING_COLOR_TAG:
1265 *out++ = STRING_COLOR_TAG;
1271 if(lastcolor == 0) break; else lastcolor = 0;
1272 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1277 if(lastcolor == 1) break; else lastcolor = 1;
1278 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1283 if(lastcolor == 2) break; else lastcolor = 2;
1284 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1289 if(lastcolor == 3) break; else lastcolor = 3;
1290 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1295 if(lastcolor == 4) break; else lastcolor = 4;
1296 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1301 if(lastcolor == 5) break; else lastcolor = 5;
1302 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1307 if(lastcolor == 6) break; else lastcolor = 6;
1308 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1313 // bold normal color
1315 if(lastcolor == 8) break; else lastcolor = 8;
1316 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1319 *out++ = STRING_COLOR_TAG;
1326 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1343 Sys_PrintToTerminal(printline);
1345 else if(sys_colortranslation.integer == 2) // Quake
1347 Sys_PrintToTerminal(line);
1351 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1354 for(in = line, out = printline; *in; ++in)
1358 case STRING_COLOR_TAG:
1361 case STRING_COLOR_RGB_TAG_CHAR:
1362 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1367 *out++ = STRING_COLOR_TAG;
1368 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1371 case STRING_COLOR_TAG:
1373 *out++ = STRING_COLOR_TAG;
1388 *out++ = STRING_COLOR_TAG;
1398 Sys_PrintToTerminal(printline);
1401 // empty the line buffer
1408 Thread_UnlockMutex(con_mutex);
1416 void Con_MaskPrintf(int mask, const char *fmt, ...)
1419 char msg[MAX_INPUTLINE];
1421 va_start(argptr,fmt);
1422 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1425 Con_MaskPrint(mask, msg);
1433 void Con_Print(const char *msg)
1435 Con_MaskPrint(CON_MASK_PRINT, msg);
1443 void Con_Printf(const char *fmt, ...)
1446 char msg[MAX_INPUTLINE];
1448 va_start(argptr,fmt);
1449 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1452 Con_MaskPrint(CON_MASK_PRINT, msg);
1460 void Con_Warn(const char *msg)
1462 Con_Printf("^3%s",msg);
1470 void Con_Warnf(const char *fmt, ...)
1473 char msg[MAX_INPUTLINE];
1475 va_start(argptr,fmt);
1476 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1479 Con_Printf("^3%s",msg);
1487 void Con_Error(const char *msg)
1489 Con_Printf("^1%s",msg);
1497 void Con_Errorf(const char *fmt, ...)
1500 char msg[MAX_INPUTLINE];
1502 va_start(argptr,fmt);
1503 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1506 Con_Printf("^1%s",msg);
1515 void Con_DPrint(const char *msg)
1517 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1520 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1528 void Con_DPrintf(const char *fmt, ...)
1531 char msg[MAX_INPUTLINE];
1533 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1536 va_start(argptr,fmt);
1537 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1540 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1545 ==============================================================================
1549 ==============================================================================
1556 The input line scrolls horizontally if typing goes beyond the right edge
1558 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1561 static void Con_DrawInput (void)
1565 char text[sizeof(key_line)+5+1]; // space for ^^xRGB too
1570 if (!key_consoleactive)
1571 return; // don't draw anything
1573 strlcpy(text, key_line, sizeof(text));
1575 // Advanced Console Editing by Radix radix@planetquake.com
1576 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1578 y = (int)strlen(text);
1580 // make the color code visible when the cursor is inside it
1581 if(text[key_linepos] != 0)
1583 for(i=1; i < 5 && key_linepos - i > 0; ++i)
1584 if(text[key_linepos-i] == STRING_COLOR_TAG)
1586 int caret_pos, ofs = 0;
1587 caret_pos = key_linepos - i;
1588 if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG)
1590 else if(i == 1 && isdigit(text[caret_pos+1]))
1592 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]))
1594 if(ofs && (size_t)(y + ofs + 1) < sizeof(text))
1597 while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG)
1601 // str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing)
1602 // str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing)
1603 memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos);
1604 text[caret_pos + ofs] = STRING_COLOR_TAG;
1613 len_out = key_linepos;
1615 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1616 x = vid_conwidth.value * 0.95 - xo; // scroll
1621 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 );
1623 // draw a cursor on top of this
1624 if ((int)(host.realtime*con_cursorspeed) & 1) // cursor is visible
1626 if (!utf8_enable.integer)
1628 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1636 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1637 memcpy(text, curbuf, len);
1640 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);
1647 float alignment; // 0 = left, 0.5 = center, 1 = right
1653 const char *continuationString;
1656 int colorindex; // init to -1
1660 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1662 con_text_info_t *ti = (con_text_info_t *) passthrough;
1665 ti->colorindex = -1;
1666 return ti->fontsize * ti->font->maxwidth;
1669 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1670 else if(maxWidth == -1)
1671 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1674 Sys_PrintfToTerminal("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1675 // Note: this is NOT a Con_Printf, as it could print recursively
1680 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1686 (void) isContinuation;
1690 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1692 con_text_info_t *ti = (con_text_info_t *) passthrough;
1694 if(ti->y < ti->ymin - 0.001)
1696 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1700 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1701 if(isContinuation && *ti->continuationString)
1702 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);
1704 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);
1707 ti->y += ti->fontsize;
1711 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)
1715 int maxlines = (int) floor(height / fontsize + 0.01f);
1718 int continuationWidth = 0;
1720 double t = cl.time; // saved so it won't change
1723 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1724 ti.fontsize = fontsize;
1725 ti.alignment = alignment_x;
1728 ti.ymax = y + height;
1729 ti.continuationString = continuationString;
1732 Con_WordWidthFunc(&ti, NULL, &len, -1);
1733 len = strlen(continuationString);
1734 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &len, -1);
1736 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1737 startidx = CON_LINES_COUNT;
1738 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1740 con_lineinfo_t *l = &CON_LINES(i);
1743 if((l->mask & mask_must) != mask_must)
1745 if(l->mask & mask_mustnot)
1747 if(maxage && (l->addtime < t - maxage))
1751 // Calculate its actual height...
1752 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1753 if(lines + mylines >= maxlines)
1755 nskip = lines + mylines - maxlines;
1764 // then center according to the calculated amount of lines...
1766 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1768 // then actually draw
1769 for(i = startidx; i < CON_LINES_COUNT; ++i)
1771 con_lineinfo_t *l = &CON_LINES(i);
1773 if((l->mask & mask_must) != mask_must)
1775 if(l->mask & mask_mustnot)
1777 if(maxage && (l->addtime < t - maxage))
1780 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1790 Draws the last few lines of output transparently over the game top
1793 void Con_DrawNotify (void)
1796 float chatstart, notifystart, inputsize, height;
1798 char temptext[MAX_INPUTLINE];
1802 if (con_mutex) Thread_LockMutex(con_mutex);
1803 ConBuffer_FixTimes(&con);
1805 numChatlines = con_chat.integer;
1807 chatpos = con_chatpos.integer;
1809 if (con_notify.integer < 0)
1810 Cvar_SetValueQuick(&con_notify, 0);
1811 if (gamemode == GAME_TRANSFUSION)
1812 v = 8; // vertical offset
1816 // GAME_NEXUIZ: center, otherwise left justify
1817 align = con_notifyalign.value;
1818 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1820 if(IS_OLDNEXUIZ_DERIVED(gamemode))
1824 if(numChatlines || !con_chatrect.integer)
1828 // first chat, input line, then notify
1830 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1832 else if(chatpos > 0)
1834 // first notify, then (chatpos-1) empty lines, then chat, then input
1836 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1838 else // if(chatpos < 0)
1840 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1842 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1847 // just notify and input
1849 chatstart = 0; // shut off gcc warning
1852 v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_INPUT | CON_MASK_HIDENOTIFY | (numChatlines ? CON_MASK_CHAT : 0) | CON_MASK_DEVELOPER, con_notifytime.value, 0, notifystart, vid_conwidth.value, con_notify.value * con_notifysize.value, con_notifysize.value, align, 0.0, "");
1854 if(con_chatrect.integer)
1856 x = con_chatrect_x.value * vid_conwidth.value;
1857 v = con_chatrect_y.value * vid_conheight.value;
1862 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1865 height = numChatlines * con_chatsize.value;
1869 Con_DrawNotifyRect(CON_MASK_CHAT, CON_MASK_INPUT, con_chattime.value, x, v, vid_conwidth.value * con_chatwidth.value, height, con_chatsize.value, 0.0, 1.0, "^3 ... ");
1872 if (key_dest == key_message)
1874 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1875 int colorindex = -1;
1878 cursor = u8_encodech(0xE00A + ((int)(host.realtime * con_cursorspeed)&1), NULL, charbuf16);
1880 // LadyHavoc: speedup, and other improvements
1882 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1884 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1886 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1889 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1890 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1892 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1894 if (con_mutex) Thread_UnlockMutex(con_mutex);
1901 Returns the height of a given console line; calculates it if necessary.
1904 static int Con_LineHeight(int lineno)
1906 con_lineinfo_t *li = &CON_LINES(lineno);
1907 if(li->height == -1)
1909 float width = vid_conwidth.value;
1911 ti.fontsize = con_textsize.value;
1912 ti.font = FONT_CONSOLE;
1913 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1922 Draws a line of the console; returns its height in lines.
1923 If alpha is 0, the line is not drawn, but still wrapped and its height
1927 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1929 float width = vid_conwidth.value;
1931 con_lineinfo_t *li = &CON_LINES(lineno);
1933 if((li->mask & mask_must) != mask_must)
1935 if((li->mask & mask_mustnot) != 0)
1938 ti.continuationString = "";
1940 ti.fontsize = con_textsize.value;
1941 ti.font = FONT_CONSOLE;
1943 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1948 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1955 Calculates the last visible line index and how much to show of it based on
1959 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1964 if(con_backscroll < 0)
1969 // now count until we saw con_backscroll actual lines
1970 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1971 if((CON_LINES(i).mask & mask_must) == mask_must)
1972 if((CON_LINES(i).mask & mask_mustnot) == 0)
1974 int h = Con_LineHeight(i);
1976 // line is the last visible line?
1978 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1980 *limitlast = lines_seen + h - con_backscroll;
1987 // if we get here, no line was on screen - scroll so that one line is
1989 con_backscroll = lines_seen - 1;
1997 Draws the console with the solid background
1998 The typing input line at the bottom should only be drawn if typing is allowed
2001 void Con_DrawConsole (int lines)
2003 float alpha, alpha0;
2006 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
2007 cachepic_t *conbackpic;
2008 unsigned int conbackflags;
2013 if (con_mutex) Thread_LockMutex(con_mutex);
2015 if (con_backscroll < 0)
2018 con_vislines = lines;
2020 r_draw2d_force = true;
2022 // draw the background
2023 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
2024 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
2026 sx = scr_conscroll_x.value;
2027 sy = scr_conscroll_y.value;
2028 conbackflags = CACHEPICFLAG_FAILONMISSING; // So console is readable when game content is missing
2029 if (sx != 0 || sy != 0)
2030 conbackflags &= CACHEPICFLAG_NOCLAMP;
2031 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", conbackflags) : NULL;
2032 sx *= host.realtime; sy *= host.realtime;
2033 sx -= floor(sx); sy -= floor(sy);
2034 if (Draw_IsPicLoaded(conbackpic))
2035 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2036 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2037 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2038 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2039 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2042 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
2044 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
2046 sx = scr_conscroll2_x.value;
2047 sy = scr_conscroll2_y.value;
2048 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2049 sx *= host.realtime; sy *= host.realtime;
2050 sx -= floor(sx); sy -= floor(sy);
2051 if(Draw_IsPicLoaded(conbackpic))
2052 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2053 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2054 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2055 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2056 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2059 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
2061 sx = scr_conscroll3_x.value;
2062 sy = scr_conscroll3_y.value;
2063 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2064 sx *= host.realtime; sy *= host.realtime;
2065 sx -= floor(sx); sy -= floor(sy);
2066 if(Draw_IsPicLoaded(conbackpic))
2067 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2068 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2069 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2070 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2071 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2074 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);
2080 int count = CON_LINES_COUNT;
2081 float ymax = con_vislines - 2 * con_textsize.value;
2082 float y = ymax + con_textsize.value * con_backscroll;
2083 for (i = 0;i < count && y >= 0;i++)
2084 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
2085 // fix any excessive scrollback for the next frame
2086 if (i >= count && y >= 0)
2088 con_backscroll -= (int)(y / con_textsize.value);
2089 if (con_backscroll < 0)
2094 if(CON_LINES_COUNT > 0)
2096 int i, last, limitlast;
2098 float ymax = con_vislines - 2 * con_textsize.value;
2099 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2100 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2101 y = ymax - con_textsize.value;
2104 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
2109 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
2111 break; // top of console buffer
2113 break; // top of console window
2120 // draw the input prompt, user text, and cursor if desired
2123 r_draw2d_force = false;
2124 if (con_mutex) Thread_UnlockMutex(con_mutex);
2131 Prints not only map filename, but also
2132 its format (q1/q2/q3/hl) and even its message
2134 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2135 //LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2136 //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
2137 //LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2138 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
2142 int i, k, max, p, o, min;
2145 unsigned char buf[1024];
2147 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2148 t = FS_Search(message, 1, true);
2151 if (t->numfilenames > 1)
2152 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2153 len = (unsigned char *)Z_Malloc(t->numfilenames);
2155 for(max=i=0;i<t->numfilenames;i++)
2157 k = (int)strlen(t->filenames[i]);
2167 for(i=0;i<t->numfilenames;i++)
2169 int lumpofs = 0, lumplen = 0;
2170 char *entities = NULL;
2171 const char *data = NULL;
2173 char entfilename[MAX_QPATH];
2176 strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2178 f = FS_OpenVirtualFile(t->filenames[i], true);
2181 strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2182 memset(buf, 0, 1024);
2183 FS_Read(f, buf, 1024);
2184 if (!memcmp(buf, "IBSP", 4))
2186 p = LittleLong(((int *)buf)[1]);
2187 if (p == Q3BSPVERSION)
2189 q3dheader_t *header = (q3dheader_t *)buf;
2190 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2191 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2192 dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2194 else if (p == Q2BSPVERSION)
2196 q2dheader_t *header = (q2dheader_t *)buf;
2197 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2198 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2199 dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2202 dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2204 else if (BuffLittleLong(buf) == BSPVERSION)
2206 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2207 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2208 dpsnprintf(desc, sizeof(desc), "BSP29");
2210 else if (BuffLittleLong(buf) == 30)
2212 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2213 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2214 dpsnprintf(desc, sizeof(desc), "BSPHL");
2216 else if (!memcmp(buf, "BSP2", 4))
2218 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2219 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2220 dpsnprintf(desc, sizeof(desc), "BSP2");
2222 else if (!memcmp(buf, "2PSB", 4))
2224 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2225 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2226 dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2230 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2232 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2233 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2234 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2235 if (!entities && lumplen >= 10)
2237 FS_Seek(f, lumpofs, SEEK_SET);
2238 entities = (char *)Z_Malloc(lumplen + 1);
2239 FS_Read(f, entities, lumplen);
2243 // if there are entities to parse, a missing message key just
2244 // means there is no title, so clear the message string now
2250 if (!COM_ParseToken_Simple(&data, false, false, true))
2252 if (com_token[0] == '{')
2254 if (com_token[0] == '}')
2256 // skip leading whitespace
2257 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2258 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2259 keyname[l] = com_token[k+l];
2261 if (!COM_ParseToken_Simple(&data, false, false, true))
2263 if (developer_extra.integer)
2264 Con_DPrintf("key: %s %s\n", keyname, com_token);
2265 if (!strcmp(keyname, "message"))
2267 // get the message contents
2268 strlcpy(message, com_token, sizeof(message));
2278 *(t->filenames[i]+len[i]+5) = 0;
2279 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2284 k = *(t->filenames[0]+5+p);
2287 for(i=1;i<t->numfilenames;i++)
2288 if(*(t->filenames[i]+5+p) != k)
2292 if(p > o && completedname && completednamebufferlength > 0)
2294 memset(completedname, 0, completednamebufferlength);
2295 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2305 New function for tab-completion system
2306 Added by EvilTypeGuy
2307 MEGA Thanks to Taniwha
2310 void Con_DisplayList(const char **list)
2312 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2313 const char **walk = list;
2316 len = (int)strlen(*walk);
2324 len = (int)strlen(*list);
2325 if (pos + maxlen >= width) {
2331 for (i = 0; i < (maxlen - len); i++)
2343 // Now it becomes TRICKY :D --blub
2344 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2345 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2346 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2347 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
2348 static int Nicks_matchpos;
2350 // co against <<:BLASTER:>> is true!?
2351 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2355 if(tolower(*a) == tolower(*b))
2369 return (*a < *b) ? -1 : 1;
2373 return (*a < *b) ? -1 : 1;
2377 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2380 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2382 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2383 return Nicks_strncasecmp_nospaces(a, b, a_len);
2384 return strncasecmp(a, b, a_len);
2387 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2389 // ignore non alphanumerics of B
2390 // if A contains a non-alphanumeric, B must contain it as well though!
2393 qboolean alnum_a, alnum_b;
2395 if(tolower(*a) == tolower(*b))
2397 if(*a == 0) // end of both strings, they're equal
2404 // not equal, end of one string?
2409 // ignore non alphanumerics
2410 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2411 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2412 if(!alnum_a) // b must contain this
2413 return (*a < *b) ? -1 : 1;
2416 // otherwise, both are alnum, they're just not equal, return the appropriate number
2418 return (*a < *b) ? -1 : 1;
2424 /* Nicks_CompleteCountPossible
2426 Count the number of possible nicks to complete
2428 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2430 char name[MAX_SCOREBOARDNAME];
2436 if(!con_nickcompletion.integer)
2439 // changed that to 1
2440 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2443 for(i = 0; i < cl.maxclients; ++i)
2446 if(!cl.scores[p].name[0])
2449 SanitizeString(cl.scores[p].name, name);
2450 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2456 spos = pos - 1; // no need for a minimum of characters :)
2460 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2462 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2463 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2469 if(isCon && spos == 0)
2471 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2477 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2478 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2480 // the sanitized list
2481 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2484 Nicks_matchpos = match;
2487 Nicks_offset[count] = s - (&line[match]);
2488 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2495 static void Cmd_CompleteNicksPrint(int count)
2498 for(i = 0; i < count; ++i)
2499 Con_Printf("%s\n", Nicks_list[i]);
2502 static void Nicks_CutMatchesNormal(int count)
2504 // cut match 0 down to the longest possible completion
2507 c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2508 for(i = 1; i < count; ++i)
2510 l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2514 for(l = 0; l <= c; ++l)
2515 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2521 Nicks_sanlist[0][c+1] = 0;
2522 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2525 static unsigned int Nicks_strcleanlen(const char *s)
2530 if( (*s >= 'a' && *s <= 'z') ||
2531 (*s >= 'A' && *s <= 'Z') ||
2532 (*s >= '0' && *s <= '9') ||
2540 static void Nicks_CutMatchesAlphaNumeric(int count)
2542 // cut match 0 down to the longest possible completion
2545 char tempstr[sizeof(Nicks_sanlist[0])];
2547 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2549 c = (unsigned int)strlen(Nicks_sanlist[0]);
2550 for(i = 0, l = 0; i < (int)c; ++i)
2552 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2553 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2554 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2556 tempstr[l++] = Nicks_sanlist[0][i];
2561 for(i = 1; i < count; ++i)
2564 b = Nicks_sanlist[i];
2574 if(tolower(*a) == tolower(*b))
2580 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2582 // b is alnum, so cut
2589 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2590 Nicks_CutMatchesNormal(count);
2591 //if(!Nicks_sanlist[0][0])
2592 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2594 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2595 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2599 static void Nicks_CutMatchesNoSpaces(int count)
2601 // cut match 0 down to the longest possible completion
2604 char tempstr[sizeof(Nicks_sanlist[0])];
2607 c = (unsigned int)strlen(Nicks_sanlist[0]);
2608 for(i = 0, l = 0; i < (int)c; ++i)
2610 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2612 tempstr[l++] = Nicks_sanlist[0][i];
2617 for(i = 1; i < count; ++i)
2620 b = Nicks_sanlist[i];
2630 if(tolower(*a) == tolower(*b))
2644 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2645 Nicks_CutMatchesNormal(count);
2646 //if(!Nicks_sanlist[0][0])
2647 //Con_Printf("TS: %s\n", tempstr);
2648 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2650 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2651 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2655 static void Nicks_CutMatches(int count)
2657 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2658 Nicks_CutMatchesAlphaNumeric(count);
2659 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2660 Nicks_CutMatchesNoSpaces(count);
2662 Nicks_CutMatchesNormal(count);
2665 static const char **Nicks_CompleteBuildList(int count)
2669 // the list is freed by Con_CompleteCommandLine, so create a char**
2670 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2672 for(; bpos < count; ++bpos)
2673 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2675 Nicks_CutMatches(count);
2683 Restores the previous used color, after the autocompleted name.
2685 static int Nicks_AddLastColor(char *buffer, int pos)
2687 qboolean quote_added = false;
2689 int color = STRING_COLOR_DEFAULT + '0';
2690 char r = 0, g = 0, b = 0;
2692 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2694 // we'll have to add a quote :)
2695 buffer[pos++] = '\"';
2699 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2701 // add color when no quote was added, or when flags &4?
2703 for(match = Nicks_matchpos-1; match >= 0; --match)
2705 if(buffer[match] == STRING_COLOR_TAG)
2707 if( isdigit(buffer[match+1]) )
2709 color = buffer[match+1];
2712 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2714 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2716 r = buffer[match+2];
2717 g = buffer[match+3];
2718 b = buffer[match+4];
2727 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2729 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2730 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2733 buffer[pos++] = STRING_COLOR_TAG;
2736 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2742 buffer[pos++] = color;
2747 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2750 /*if(!con_nickcompletion.integer)
2751 return; is tested in Nicks_CompletionCountPossible */
2752 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2758 msg = Nicks_list[0];
2759 len = min(size - Nicks_matchpos - 3, strlen(msg));
2760 memcpy(&buffer[Nicks_matchpos], msg, len);
2761 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2762 len = (int)Nicks_AddLastColor(buffer, Nicks_matchpos+(int)len);
2763 buffer[len++] = ' ';
2770 Con_Printf("\n%i possible nicks:\n", n);
2771 Cmd_CompleteNicksPrint(n);
2773 Nicks_CutMatches(n);
2775 msg = Nicks_sanlist[0];
2776 len = (int)min(size - Nicks_matchpos, strlen(msg));
2777 memcpy(&buffer[Nicks_matchpos], msg, len);
2778 buffer[Nicks_matchpos + len] = 0;
2780 return Nicks_matchpos + len;
2787 Con_CompleteCommandLine
2789 New function for tab-completion system
2790 Added by EvilTypeGuy
2791 Thanks to Fett erich@heintz.com
2793 Enhanced to tab-complete map names by [515]
2796 void Con_CompleteCommandLine (cmd_state_t *cmd)
2798 const char *text = "";
2800 const char **list[4] = {0, 0, 0, 0};
2803 int c, v, a, i, cmd_len, pos, k;
2804 int n; // nicks --blub
2805 const char *space, *patterns;
2808 //find what we want to complete
2813 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2819 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2820 key_line[key_linepos] = 0; //hide them
2822 space = strchr(key_line + 1, ' ');
2823 if(space && pos == (space - key_line) + 1)
2825 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2827 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?
2828 if(patterns && !*patterns)
2829 patterns = NULL; // get rid of the empty string
2831 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2835 if (GetMapList(s, t, sizeof(t)))
2837 // first move the cursor
2838 key_linepos += (int)strlen(t) - (int)strlen(s);
2840 // and now do the actual work
2842 strlcat(key_line, t, MAX_INPUTLINE);
2843 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2845 // and fix the cursor
2846 if(key_linepos > (int) strlen(key_line))
2847 key_linepos = (int) strlen(key_line);
2856 stringlist_t resultbuf, dirbuf;
2859 // // store completion patterns (space separated) for command foo in con_completion_foo
2860 // set con_completion_foo "foodata/*.foodefault *.foo"
2863 // Note: patterns with slash are always treated as absolute
2864 // patterns; patterns without slash search in the innermost
2865 // directory the user specified. There is no way to "complete into"
2866 // a directory as of now, as directories seem to be unknown to the
2870 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2871 // set con_completion_playdemo "*.dem"
2872 // set con_completion_play "*.wav *.ogg"
2874 // TODO somehow add support for directories; these shall complete
2875 // to their name + an appended slash.
2877 stringlistinit(&resultbuf);
2878 stringlistinit(&dirbuf);
2879 while(COM_ParseToken_Simple(&patterns, false, false, true))
2882 if(strchr(com_token, '/'))
2884 search = FS_Search(com_token, true, true);
2888 const char *slash = strrchr(s, '/');
2891 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2892 strlcat(t, com_token, sizeof(t));
2893 search = FS_Search(t, true, true);
2896 search = FS_Search(com_token, true, true);
2900 for(i = 0; i < search->numfilenames; ++i)
2901 if(!strncmp(search->filenames[i], s, strlen(s)))
2902 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2903 stringlistappend(&resultbuf, search->filenames[i]);
2904 FS_FreeSearch(search);
2908 // In any case, add directory names
2911 const char *slash = strrchr(s, '/');
2914 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2915 strlcat(t, "*", sizeof(t));
2916 search = FS_Search(t, true, true);
2919 search = FS_Search("*", true, true);
2922 for(i = 0; i < search->numfilenames; ++i)
2923 if(!strncmp(search->filenames[i], s, strlen(s)))
2924 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2925 stringlistappend(&dirbuf, search->filenames[i]);
2926 FS_FreeSearch(search);
2930 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2933 unsigned int matchchars;
2934 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2936 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2939 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2941 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2945 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2946 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2947 for(i = 0; i < dirbuf.numstrings; ++i)
2949 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2951 for(i = 0; i < resultbuf.numstrings; ++i)
2953 Con_Printf("%s\n", resultbuf.strings[i]);
2955 matchchars = sizeof(t) - 1;
2956 if(resultbuf.numstrings > 0)
2958 p = resultbuf.strings[0];
2959 q = resultbuf.strings[resultbuf.numstrings - 1];
2960 for(; *p && *p == *q; ++p, ++q);
2961 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2963 if(dirbuf.numstrings > 0)
2965 p = dirbuf.strings[0];
2966 q = dirbuf.strings[dirbuf.numstrings - 1];
2967 for(; *p && *p == *q; ++p, ++q);
2968 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2970 // now p points to the first non-equal character, or to the end
2971 // of resultbuf.strings[0]. We want to append the characters
2972 // from resultbuf.strings[0] to (not including) p as these are
2973 // the unique prefix
2974 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2977 // first move the cursor
2978 key_linepos += (int)strlen(t) - (int)strlen(s);
2980 // and now do the actual work
2982 strlcat(key_line, t, MAX_INPUTLINE);
2983 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2985 // and fix the cursor
2986 if(key_linepos > (int) strlen(key_line))
2987 key_linepos = (int) strlen(key_line);
2989 stringlistfreecontents(&resultbuf);
2990 stringlistfreecontents(&dirbuf);
2992 return; // bail out, when we complete for a command that wants a file name
2997 // Count number of possible matches and print them
2998 c = Cmd_CompleteCountPossible(cmd, s);
3001 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
3002 Cmd_CompleteCommandPrint(cmd, s);
3004 v = Cvar_CompleteCountPossible(cmd->cvars, s, CVAR_CLIENT | CVAR_SERVER);
3007 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
3008 Cvar_CompleteCvarPrint(cmd->cvars, s, CVAR_CLIENT | CVAR_SERVER);
3010 a = Cmd_CompleteAliasCountPossible(cmd, s);
3013 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
3014 Cmd_CompleteAliasPrint(cmd, s);
3016 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
3019 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
3020 Cmd_CompleteNicksPrint(n);
3023 if (!(c + v + a + n)) // No possible matches
3026 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
3031 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
3033 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
3035 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
3037 text = *(list[3] = Nicks_CompleteBuildList(n));
3039 for (cmd_len = (int)strlen(s);;cmd_len++)
3042 for (i = 0; i < 3; i++)
3044 for (l = list[i];*l;l++)
3045 if ((*l)[cmd_len] != text[cmd_len])
3047 // all possible matches share this character, so we continue...
3050 // if all matches ended at the same position, stop
3051 // (this means there is only one match)
3057 // prevent a buffer overrun by limiting cmd_len according to remaining space
3058 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
3062 memcpy(&key_line[key_linepos], text, cmd_len);
3063 key_linepos += cmd_len;
3064 // if there is only one match, add a space after it
3065 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
3068 { // was a nick, might have an offset, and needs colors ;) --blub
3069 key_linepos = pos - Nicks_offset[0];
3070 cmd_len = (int)strlen(Nicks_list[0]);
3071 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
3073 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
3074 key_linepos += cmd_len;
3075 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
3076 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
3078 key_line[key_linepos++] = ' ';
3082 // use strlcat to avoid a buffer overrun
3083 key_line[key_linepos] = 0;
3084 strlcat(key_line, s2, sizeof(key_line));
3086 // free the command, cvar, and alias lists
3087 for (i = 0; i < 4; i++)
3089 Mem_Free((void *)list[i]);