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);
845 void Con_Init_Commands(void)
847 Cvar_RegisterVariable (&sys_colortranslation);
848 Cvar_RegisterVariable (&sys_specialcharactertranslation);
850 Cvar_RegisterVariable (&log_file);
851 Cvar_RegisterVariable (&log_file_stripcolors);
852 Cvar_RegisterVariable (&log_dest_udp);
854 // support for the classic Quake option
855 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
856 if (COM_CheckParm ("-condebug") != 0)
857 Cvar_SetQuick (&log_file, "qconsole.log");
859 // register our cvars
860 Cvar_RegisterVariable (&con_chat);
861 Cvar_RegisterVariable (&con_chatpos);
862 Cvar_RegisterVariable (&con_chatrect_x);
863 Cvar_RegisterVariable (&con_chatrect_y);
864 Cvar_RegisterVariable (&con_chatrect);
865 Cvar_RegisterVariable (&con_chatsize);
866 Cvar_RegisterVariable (&con_chattime);
867 Cvar_RegisterVariable (&con_chatwidth);
868 Cvar_RegisterVariable (&con_notify);
869 Cvar_RegisterVariable (&con_notifyalign);
870 Cvar_RegisterVariable (&con_notifysize);
871 Cvar_RegisterVariable (&con_notifytime);
872 Cvar_RegisterVariable (&con_textsize);
873 Cvar_RegisterVariable (&con_chatsound);
874 Cvar_RegisterVariable (&con_chatsound_file);
875 Cvar_RegisterVariable (&con_chatsound_team_file);
876 Cvar_RegisterVariable (&con_chatsound_team_mask);
879 Cvar_RegisterVariable (&con_nickcompletion);
880 Cvar_RegisterVariable (&con_nickcompletion_flags);
882 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
883 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
884 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
886 Cvar_RegisterVariable (&condump_stripcolors);
888 // register our commands
889 Cmd_AddCommand(CMD_CLIENT, "toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
890 Cmd_AddCommand(CMD_CLIENT, "messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
891 Cmd_AddCommand(CMD_CLIENT, "messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
892 Cmd_AddCommand(CMD_CLIENT, "commandmode", Con_CommandMode_f, "input a console command");
893 Cmd_AddCommand(CMD_SHARED, "clear", Con_Clear_f, "clear console history");
894 Cmd_AddCommand(CMD_SHARED, "maps", Con_Maps_f, "list information about available maps");
895 Cmd_AddCommand(CMD_SHARED, "condump", Con_ConDump_f, "output console history to a file (see also log_file)");
907 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
908 if (Thread_HasThreads())
909 con_mutex = Thread_CreateMutex();
911 // Allocate a log queue, this will be freed after configs are parsed
912 logq_size = MAX_INPUTLINE;
913 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
916 con_initialized = true;
917 Con_DPrint("Console initialized.\n");
920 void Con_Shutdown (void)
922 if (con_mutex) Thread_LockMutex(con_mutex);
923 ConBuffer_Shutdown(&con);
924 if (con_mutex) Thread_UnlockMutex(con_mutex);
925 if (con_mutex) Thread_DestroyMutex(con_mutex);con_mutex = NULL;
932 Handles cursor positioning, line wrapping, etc
933 All console printing must go through this in order to be displayed
934 If no console is visible, the notify window will pop up.
937 static void Con_PrintToHistory(const char *txt, int mask)
940 // \n goes to next line
941 // \r deletes current line and makes a new one
943 static int cr_pending = 0;
944 static char buf[CON_TEXTSIZE]; // con_mutex
945 static int bufpos = 0;
947 if(!con.text) // FIXME uses a non-abstracted property of con
954 ConBuffer_DeleteLastLine(&con);
962 ConBuffer_AddLine(&con, buf, bufpos, mask);
967 ConBuffer_AddLine(&con, buf, bufpos, mask);
971 buf[bufpos++] = *txt;
972 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
974 ConBuffer_AddLine(&con, buf, bufpos, mask);
982 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol)
984 rcon_redirect_sock = sock;
985 rcon_redirect_dest = dest;
986 rcon_redirect_proquakeprotocol = proquakeprotocol;
987 if (rcon_redirect_proquakeprotocol)
989 // reserve space for the packet header
990 rcon_redirect_buffer[0] = 0;
991 rcon_redirect_buffer[1] = 0;
992 rcon_redirect_buffer[2] = 0;
993 rcon_redirect_buffer[3] = 0;
994 // this is a reply to a CCREQ_RCON
995 rcon_redirect_buffer[4] = (unsigned char)CCREP_RCON;
998 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
999 rcon_redirect_bufferpos = 5;
1002 static void Con_Rcon_Redirect_Flush(void)
1004 if(rcon_redirect_sock)
1006 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
1007 if (rcon_redirect_proquakeprotocol)
1009 // update the length in the packet header
1010 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
1012 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
1014 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1015 rcon_redirect_bufferpos = 5;
1016 rcon_redirect_proquakeprotocol = false;
1019 void Con_Rcon_Redirect_End(void)
1021 Con_Rcon_Redirect_Flush();
1022 rcon_redirect_dest = NULL;
1023 rcon_redirect_sock = NULL;
1026 void Con_Rcon_Redirect_Abort(void)
1028 rcon_redirect_dest = NULL;
1029 rcon_redirect_sock = NULL;
1037 /// Adds a character to the rcon buffer.
1038 static void Con_Rcon_AddChar(int c)
1040 if(log_dest_buffer_appending)
1042 ++log_dest_buffer_appending;
1044 // if this print is in response to an rcon command, add the character
1045 // to the rcon redirect buffer
1047 if (rcon_redirect_dest)
1049 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
1050 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
1051 Con_Rcon_Redirect_Flush();
1053 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
1055 if(log_dest_buffer_pos == 0)
1056 Log_DestBuffer_Init();
1057 log_dest_buffer[log_dest_buffer_pos++] = c;
1058 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
1059 Log_DestBuffer_Flush_NoLock();
1062 log_dest_buffer_pos = 0;
1064 --log_dest_buffer_appending;
1068 * Convert an RGB color to its nearest quake color.
1069 * I'll cheat on this a bit by translating the colors to HSV first,
1070 * S and V decide if it's black or white, otherwise, H will decide the
1072 * @param _r Red (0-255)
1073 * @param _g Green (0-255)
1074 * @param _b Blue (0-255)
1075 * @return A quake color character.
1077 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
1079 float r = ((float)_r)/255.0;
1080 float g = ((float)_g)/255.0;
1081 float b = ((float)_b)/255.0;
1082 float min = min(r, min(g, b));
1083 float max = max(r, max(g, b));
1085 int h; ///< Hue angle [0,360]
1086 float s; ///< Saturation [0,1]
1087 float v = max; ///< In HSV v == max [0,1]
1092 s = 1.0 - (min/max);
1094 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1097 // If the value is less than half, return a black color code.
1098 // Otherwise return a white one.
1104 // Let's get the hue angle to define some colors:
1108 h = (int)(60.0 * (g-b)/(max-min))%360;
1110 h = (int)(60.0 * (b-r)/(max-min) + 120);
1111 else // if(max == b) redundant check
1112 h = (int)(60.0 * (r-g)/(max-min) + 240);
1114 if(h < 36) // *red* to orange
1116 else if(h < 80) // orange over *yellow* to evilish-bright-green
1118 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1120 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1122 else if(h < 270) // darkish blue over *dark blue* to cool purple
1124 else if(h < 330) // cool purple over *purple* to ugly swiny red
1126 else // ugly red to red closes the circly
1135 extern cvar_t timestamps;
1136 extern cvar_t timeformat;
1137 extern qboolean sys_nostdout;
1138 void Con_MaskPrint(int additionalmask, const char *msg)
1140 static int mask = 0;
1141 static int index = 0;
1142 static char line[MAX_INPUTLINE];
1145 Thread_LockMutex(con_mutex);
1149 Con_Rcon_AddChar(*msg);
1150 // if this is the beginning of a new line, print timestamp
1153 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1155 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1156 line[index++] = STRING_COLOR_TAG;
1157 // assert( STRING_COLOR_DEFAULT < 10 )
1158 line[index++] = STRING_COLOR_DEFAULT + '0';
1159 // special color codes for chat messages must always come first
1160 // for Con_PrintToHistory to work properly
1161 if (*msg == 1 || *msg == 2 || *msg == 3)
1166 if (con_chatsound.value)
1168 if(msg[1] == con_chatsound_team_mask.integer && cl.foundteamchatsound)
1169 S_LocalSound (con_chatsound_team_file.string);
1171 S_LocalSound (con_chatsound_file.string);
1174 // Send to chatbox for say/tell (1) and messages (3)
1175 // 3 is just so that a message can be sent to the chatbox without a sound.
1176 if (*msg == 1 || *msg == 3)
1177 mask = CON_MASK_CHAT;
1179 line[index++] = STRING_COLOR_TAG;
1180 line[index++] = '3';
1182 Con_Rcon_AddChar(*msg);
1185 for (;*timestamp;index++, timestamp++)
1186 if (index < (int)sizeof(line) - 2)
1187 line[index] = *timestamp;
1189 mask |= additionalmask;
1191 // append the character
1192 line[index++] = *msg;
1193 // if this is a newline character, we have a complete line to print
1194 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1196 // terminate the line
1200 // send to scrollable buffer
1201 if (con_initialized && cls.state != ca_dedicated)
1203 Con_PrintToHistory(line, mask);
1205 // send to terminal or dedicated server window
1207 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1209 if(sys_specialcharactertranslation.integer)
1216 int ch = u8_getchar(p, &q);
1217 if(ch >= 0xE000 && ch <= 0xE0FF && ((unsigned char) qfont_table[ch - 0xE000]) >= 0x20)
1219 *p = qfont_table[ch - 0xE000];
1221 memmove(p+1, q, strlen(q)+1);
1229 if(sys_colortranslation.integer == 1) // ANSI
1231 static char printline[MAX_INPUTLINE * 4 + 3];
1232 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1233 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1238 for(in = line, out = printline; *in; ++in)
1242 case STRING_COLOR_TAG:
1243 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1245 char r = tolower(in[2]);
1246 char g = tolower(in[3]);
1247 char b = tolower(in[4]);
1248 // it's a hex digit already, so the else part needs no check --blub
1249 if(isdigit(r)) r -= '0';
1251 if(isdigit(g)) g -= '0';
1253 if(isdigit(b)) b -= '0';
1256 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1257 in += 3; // 3 only, the switch down there does the fourth
1264 case STRING_COLOR_TAG:
1266 *out++ = STRING_COLOR_TAG;
1272 if(lastcolor == 0) break; else lastcolor = 0;
1273 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1278 if(lastcolor == 1) break; else lastcolor = 1;
1279 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1284 if(lastcolor == 2) break; else lastcolor = 2;
1285 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1290 if(lastcolor == 3) break; else lastcolor = 3;
1291 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1296 if(lastcolor == 4) break; else lastcolor = 4;
1297 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1302 if(lastcolor == 5) break; else lastcolor = 5;
1303 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1308 if(lastcolor == 6) break; else lastcolor = 6;
1309 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1314 // bold normal color
1316 if(lastcolor == 8) break; else lastcolor = 8;
1317 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1320 *out++ = STRING_COLOR_TAG;
1327 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1344 Sys_PrintToTerminal(printline);
1346 else if(sys_colortranslation.integer == 2) // Quake
1348 Sys_PrintToTerminal(line);
1352 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1355 for(in = line, out = printline; *in; ++in)
1359 case STRING_COLOR_TAG:
1362 case STRING_COLOR_RGB_TAG_CHAR:
1363 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1368 *out++ = STRING_COLOR_TAG;
1369 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1372 case STRING_COLOR_TAG:
1374 *out++ = STRING_COLOR_TAG;
1389 *out++ = STRING_COLOR_TAG;
1399 Sys_PrintToTerminal(printline);
1402 // empty the line buffer
1409 Thread_UnlockMutex(con_mutex);
1417 void Con_MaskPrintf(int mask, const char *fmt, ...)
1420 char msg[MAX_INPUTLINE];
1422 va_start(argptr,fmt);
1423 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1426 Con_MaskPrint(mask, msg);
1434 void Con_Print(const char *msg)
1436 Con_MaskPrint(CON_MASK_PRINT, msg);
1444 void Con_Printf(const char *fmt, ...)
1447 char msg[MAX_INPUTLINE];
1449 va_start(argptr,fmt);
1450 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1453 Con_MaskPrint(CON_MASK_PRINT, msg);
1461 void Con_Warn(const char *msg)
1463 Con_Printf("^3%s",msg);
1471 void Con_Warnf(const char *fmt, ...)
1474 char msg[MAX_INPUTLINE];
1476 va_start(argptr,fmt);
1477 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1480 Con_Printf("^3%s",msg);
1488 void Con_Error(const char *msg)
1490 Con_Printf("^1%s",msg);
1498 void Con_Errorf(const char *fmt, ...)
1501 char msg[MAX_INPUTLINE];
1503 va_start(argptr,fmt);
1504 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1507 Con_Printf("^1%s",msg);
1516 void Con_DPrint(const char *msg)
1518 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1521 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1529 void Con_DPrintf(const char *fmt, ...)
1532 char msg[MAX_INPUTLINE];
1534 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1537 va_start(argptr,fmt);
1538 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1541 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1546 ==============================================================================
1550 ==============================================================================
1557 The input line scrolls horizontally if typing goes beyond the right edge
1559 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1562 static void Con_DrawInput (void)
1566 char text[sizeof(key_line)+5+1]; // space for ^^xRGB too
1571 if (!key_consoleactive)
1572 return; // don't draw anything
1574 strlcpy(text, key_line, sizeof(text));
1576 // Advanced Console Editing by Radix radix@planetquake.com
1577 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1579 y = (int)strlen(text);
1581 // make the color code visible when the cursor is inside it
1582 if(text[key_linepos] != 0)
1584 for(i=1; i < 5 && key_linepos - i > 0; ++i)
1585 if(text[key_linepos-i] == STRING_COLOR_TAG)
1587 int caret_pos, ofs = 0;
1588 caret_pos = key_linepos - i;
1589 if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG)
1591 else if(i == 1 && isdigit(text[caret_pos+1]))
1593 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]))
1595 if(ofs && (size_t)(y + ofs + 1) < sizeof(text))
1598 while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG)
1602 // str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing)
1603 // str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing)
1604 memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos);
1605 text[caret_pos + ofs] = STRING_COLOR_TAG;
1614 len_out = key_linepos;
1616 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1617 x = vid_conwidth.value * 0.95 - xo; // scroll
1622 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 );
1624 // draw a cursor on top of this
1625 if ((int)(host.realtime*con_cursorspeed) & 1) // cursor is visible
1627 if (!utf8_enable.integer)
1629 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1637 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1638 memcpy(text, curbuf, len);
1641 DrawQ_String(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);
1648 float alignment; // 0 = left, 0.5 = center, 1 = right
1654 const char *continuationString;
1657 int colorindex; // init to -1
1661 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1663 con_text_info_t *ti = (con_text_info_t *) passthrough;
1666 ti->colorindex = -1;
1667 return ti->fontsize * ti->font->maxwidth;
1670 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1671 else if(maxWidth == -1)
1672 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1675 Sys_PrintfToTerminal("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1676 // Note: this is NOT a Con_Printf, as it could print recursively
1681 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1687 (void) isContinuation;
1691 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1693 con_text_info_t *ti = (con_text_info_t *) passthrough;
1695 if(ti->y < ti->ymin - 0.001)
1697 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1701 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1702 if(isContinuation && *ti->continuationString)
1703 x = (int) DrawQ_String(x, ti->y, ti->continuationString, strlen(ti->continuationString), ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, ti->font);
1705 DrawQ_String(x, ti->y, line, length, ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, &(ti->colorindex), false, ti->font);
1708 ti->y += ti->fontsize;
1712 static int Con_DrawNotifyRect(int mask_must, int mask_mustnot, float maxage, float x, float y, float width, float height, float fontsize, float alignment_x, float alignment_y, const char *continuationString)
1716 int maxlines = (int) floor(height / fontsize + 0.01f);
1719 int continuationWidth = 0;
1721 double t = cl.time; // saved so it won't change
1724 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1725 ti.fontsize = fontsize;
1726 ti.alignment = alignment_x;
1729 ti.ymax = y + height;
1730 ti.continuationString = continuationString;
1733 Con_WordWidthFunc(&ti, NULL, &len, -1);
1734 len = strlen(continuationString);
1735 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &len, -1);
1737 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1738 startidx = CON_LINES_COUNT;
1739 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1741 con_lineinfo_t *l = &CON_LINES(i);
1744 if((l->mask & mask_must) != mask_must)
1746 if(l->mask & mask_mustnot)
1748 if(maxage && (l->addtime < t - maxage))
1752 // Calculate its actual height...
1753 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1754 if(lines + mylines >= maxlines)
1756 nskip = lines + mylines - maxlines;
1765 // then center according to the calculated amount of lines...
1767 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1769 // then actually draw
1770 for(i = startidx; i < CON_LINES_COUNT; ++i)
1772 con_lineinfo_t *l = &CON_LINES(i);
1774 if((l->mask & mask_must) != mask_must)
1776 if(l->mask & mask_mustnot)
1778 if(maxage && (l->addtime < t - maxage))
1781 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1791 Draws the last few lines of output transparently over the game top
1794 void Con_DrawNotify (void)
1797 float chatstart, notifystart, inputsize, height;
1799 char temptext[MAX_INPUTLINE];
1803 if (con_mutex) Thread_LockMutex(con_mutex);
1804 ConBuffer_FixTimes(&con);
1806 numChatlines = con_chat.integer;
1808 chatpos = con_chatpos.integer;
1810 if (con_notify.integer < 0)
1811 Cvar_SetValueQuick(&con_notify, 0);
1812 if (gamemode == GAME_TRANSFUSION)
1813 v = 8; // vertical offset
1817 // GAME_NEXUIZ: center, otherwise left justify
1818 align = con_notifyalign.value;
1819 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1821 if(IS_OLDNEXUIZ_DERIVED(gamemode))
1825 if(numChatlines || !con_chatrect.integer)
1829 // first chat, input line, then notify
1831 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1833 else if(chatpos > 0)
1835 // first notify, then (chatpos-1) empty lines, then chat, then input
1837 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1839 else // if(chatpos < 0)
1841 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1843 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1848 // just notify and input
1850 chatstart = 0; // shut off gcc warning
1853 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, "");
1855 if(con_chatrect.integer)
1857 x = con_chatrect_x.value * vid_conwidth.value;
1858 v = con_chatrect_y.value * vid_conheight.value;
1863 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1866 height = numChatlines * con_chatsize.value;
1870 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 ... ");
1873 if (key_dest == key_message)
1875 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1876 int colorindex = -1;
1879 cursor = u8_encodech(0xE00A + ((int)(host.realtime * con_cursorspeed)&1), NULL, charbuf16);
1881 // LadyHavoc: speedup, and other improvements
1883 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1885 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1887 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1890 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1891 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1893 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1895 if (con_mutex) Thread_UnlockMutex(con_mutex);
1902 Returns the height of a given console line; calculates it if necessary.
1905 static int Con_LineHeight(int lineno)
1907 con_lineinfo_t *li = &CON_LINES(lineno);
1908 if(li->height == -1)
1910 float width = vid_conwidth.value;
1912 ti.fontsize = con_textsize.value;
1913 ti.font = FONT_CONSOLE;
1914 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1923 Draws a line of the console; returns its height in lines.
1924 If alpha is 0, the line is not drawn, but still wrapped and its height
1928 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1930 float width = vid_conwidth.value;
1932 con_lineinfo_t *li = &CON_LINES(lineno);
1934 if((li->mask & mask_must) != mask_must)
1936 if((li->mask & mask_mustnot) != 0)
1939 ti.continuationString = "";
1941 ti.fontsize = con_textsize.value;
1942 ti.font = FONT_CONSOLE;
1944 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1949 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1956 Calculates the last visible line index and how much to show of it based on
1960 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1965 if(con_backscroll < 0)
1970 // now count until we saw con_backscroll actual lines
1971 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1972 if((CON_LINES(i).mask & mask_must) == mask_must)
1973 if((CON_LINES(i).mask & mask_mustnot) == 0)
1975 int h = Con_LineHeight(i);
1977 // line is the last visible line?
1979 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1981 *limitlast = lines_seen + h - con_backscroll;
1988 // if we get here, no line was on screen - scroll so that one line is
1990 con_backscroll = lines_seen - 1;
1998 Draws the console with the solid background
1999 The typing input line at the bottom should only be drawn if typing is allowed
2002 void Con_DrawConsole (int lines)
2004 float alpha, alpha0;
2007 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
2008 cachepic_t *conbackpic;
2009 unsigned int conbackflags;
2014 if (con_mutex) Thread_LockMutex(con_mutex);
2016 if (con_backscroll < 0)
2019 con_vislines = lines;
2021 r_draw2d_force = true;
2023 // draw the background
2024 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
2025 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
2027 sx = scr_conscroll_x.value;
2028 sy = scr_conscroll_y.value;
2029 conbackflags = CACHEPICFLAG_FAILONMISSING; // So console is readable when game content is missing
2030 if (sx != 0 || sy != 0)
2031 conbackflags &= CACHEPICFLAG_NOCLAMP;
2032 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", conbackflags) : NULL;
2033 sx *= host.realtime; sy *= host.realtime;
2034 sx -= floor(sx); sy -= floor(sy);
2035 if (Draw_IsPicLoaded(conbackpic))
2036 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2037 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2038 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2039 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2040 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2043 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
2045 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
2047 sx = scr_conscroll2_x.value;
2048 sy = scr_conscroll2_y.value;
2049 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2050 sx *= host.realtime; sy *= host.realtime;
2051 sx -= floor(sx); sy -= floor(sy);
2052 if(Draw_IsPicLoaded(conbackpic))
2053 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2054 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2055 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2056 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2057 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2060 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
2062 sx = scr_conscroll3_x.value;
2063 sy = scr_conscroll3_y.value;
2064 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2065 sx *= host.realtime; sy *= host.realtime;
2066 sx -= floor(sx); sy -= floor(sy);
2067 if(Draw_IsPicLoaded(conbackpic))
2068 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2069 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2070 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2071 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2072 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2075 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);
2081 int count = CON_LINES_COUNT;
2082 float ymax = con_vislines - 2 * con_textsize.value;
2083 float y = ymax + con_textsize.value * con_backscroll;
2084 for (i = 0;i < count && y >= 0;i++)
2085 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
2086 // fix any excessive scrollback for the next frame
2087 if (i >= count && y >= 0)
2089 con_backscroll -= (int)(y / con_textsize.value);
2090 if (con_backscroll < 0)
2095 if(CON_LINES_COUNT > 0)
2097 int i, last, limitlast;
2099 float ymax = con_vislines - 2 * con_textsize.value;
2100 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2101 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2102 y = ymax - con_textsize.value;
2105 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
2110 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
2112 break; // top of console buffer
2114 break; // top of console window
2121 // draw the input prompt, user text, and cursor if desired
2124 r_draw2d_force = false;
2125 if (con_mutex) Thread_UnlockMutex(con_mutex);
2132 Prints not only map filename, but also
2133 its format (q1/q2/q3/hl) and even its message
2135 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2136 //LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2137 //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
2138 //LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2139 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
2143 int i, k, max, p, o, min;
2146 unsigned char buf[1024];
2148 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2149 t = FS_Search(message, 1, true);
2152 if (t->numfilenames > 1)
2153 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2154 len = (unsigned char *)Z_Malloc(t->numfilenames);
2156 for(max=i=0;i<t->numfilenames;i++)
2158 k = (int)strlen(t->filenames[i]);
2168 for(i=0;i<t->numfilenames;i++)
2170 int lumpofs = 0, lumplen = 0;
2171 char *entities = NULL;
2172 const char *data = NULL;
2174 char entfilename[MAX_QPATH];
2177 strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2179 f = FS_OpenVirtualFile(t->filenames[i], true);
2182 strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2183 memset(buf, 0, 1024);
2184 FS_Read(f, buf, 1024);
2185 if (!memcmp(buf, "IBSP", 4))
2187 p = LittleLong(((int *)buf)[1]);
2188 if (p == Q3BSPVERSION)
2190 q3dheader_t *header = (q3dheader_t *)buf;
2191 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2192 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2193 dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2195 else if (p == Q2BSPVERSION)
2197 q2dheader_t *header = (q2dheader_t *)buf;
2198 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2199 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2200 dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2203 dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2205 else if (BuffLittleLong(buf) == BSPVERSION)
2207 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2208 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2209 dpsnprintf(desc, sizeof(desc), "BSP29");
2211 else if (BuffLittleLong(buf) == 30)
2213 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2214 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2215 dpsnprintf(desc, sizeof(desc), "BSPHL");
2217 else if (!memcmp(buf, "BSP2", 4))
2219 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2220 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2221 dpsnprintf(desc, sizeof(desc), "BSP2");
2223 else if (!memcmp(buf, "2PSB", 4))
2225 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2226 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2227 dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2231 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2233 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2234 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2235 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2236 if (!entities && lumplen >= 10)
2238 FS_Seek(f, lumpofs, SEEK_SET);
2239 entities = (char *)Z_Malloc(lumplen + 1);
2240 FS_Read(f, entities, lumplen);
2244 // if there are entities to parse, a missing message key just
2245 // means there is no title, so clear the message string now
2251 if (!COM_ParseToken_Simple(&data, false, false, true))
2253 if (com_token[0] == '{')
2255 if (com_token[0] == '}')
2257 // skip leading whitespace
2258 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2259 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2260 keyname[l] = com_token[k+l];
2262 if (!COM_ParseToken_Simple(&data, false, false, true))
2264 if (developer_extra.integer)
2265 Con_DPrintf("key: %s %s\n", keyname, com_token);
2266 if (!strcmp(keyname, "message"))
2268 // get the message contents
2269 strlcpy(message, com_token, sizeof(message));
2279 *(t->filenames[i]+len[i]+5) = 0;
2280 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2285 k = *(t->filenames[0]+5+p);
2288 for(i=1;i<t->numfilenames;i++)
2289 if(*(t->filenames[i]+5+p) != k)
2293 if(p > o && completedname && completednamebufferlength > 0)
2295 memset(completedname, 0, completednamebufferlength);
2296 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2306 New function for tab-completion system
2307 Added by EvilTypeGuy
2308 MEGA Thanks to Taniwha
2311 void Con_DisplayList(const char **list)
2313 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2314 const char **walk = list;
2317 len = (int)strlen(*walk);
2325 len = (int)strlen(*list);
2326 if (pos + maxlen >= width) {
2332 for (i = 0; i < (maxlen - len); i++)
2344 // Now it becomes TRICKY :D --blub
2345 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2346 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2347 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2348 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
2349 static int Nicks_matchpos;
2351 // co against <<:BLASTER:>> is true!?
2352 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2356 if(tolower(*a) == tolower(*b))
2370 return (*a < *b) ? -1 : 1;
2374 return (*a < *b) ? -1 : 1;
2378 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2381 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2383 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2384 return Nicks_strncasecmp_nospaces(a, b, a_len);
2385 return strncasecmp(a, b, a_len);
2388 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2390 // ignore non alphanumerics of B
2391 // if A contains a non-alphanumeric, B must contain it as well though!
2394 qboolean alnum_a, alnum_b;
2396 if(tolower(*a) == tolower(*b))
2398 if(*a == 0) // end of both strings, they're equal
2405 // not equal, end of one string?
2410 // ignore non alphanumerics
2411 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2412 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2413 if(!alnum_a) // b must contain this
2414 return (*a < *b) ? -1 : 1;
2417 // otherwise, both are alnum, they're just not equal, return the appropriate number
2419 return (*a < *b) ? -1 : 1;
2425 /* Nicks_CompleteCountPossible
2427 Count the number of possible nicks to complete
2429 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2437 if(!con_nickcompletion.integer)
2440 // changed that to 1
2441 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2444 for(i = 0; i < cl.maxclients; ++i)
2447 if(!cl.scores[p].name[0])
2450 SanitizeString(cl.scores[p].name, name);
2451 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2457 spos = pos - 1; // no need for a minimum of characters :)
2461 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2463 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2464 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2470 if(isCon && spos == 0)
2472 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2478 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2479 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2481 // the sanitized list
2482 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2485 Nicks_matchpos = match;
2488 Nicks_offset[count] = s - (&line[match]);
2489 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2496 static void Cmd_CompleteNicksPrint(int count)
2499 for(i = 0; i < count; ++i)
2500 Con_Printf("%s\n", Nicks_list[i]);
2503 static void Nicks_CutMatchesNormal(int count)
2505 // cut match 0 down to the longest possible completion
2508 c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2509 for(i = 1; i < count; ++i)
2511 l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2515 for(l = 0; l <= c; ++l)
2516 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2522 Nicks_sanlist[0][c+1] = 0;
2523 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2526 static unsigned int Nicks_strcleanlen(const char *s)
2531 if( (*s >= 'a' && *s <= 'z') ||
2532 (*s >= 'A' && *s <= 'Z') ||
2533 (*s >= '0' && *s <= '9') ||
2541 static void Nicks_CutMatchesAlphaNumeric(int count)
2543 // cut match 0 down to the longest possible completion
2546 char tempstr[sizeof(Nicks_sanlist[0])];
2548 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2550 c = (unsigned int)strlen(Nicks_sanlist[0]);
2551 for(i = 0, l = 0; i < (int)c; ++i)
2553 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2554 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2555 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2557 tempstr[l++] = Nicks_sanlist[0][i];
2562 for(i = 1; i < count; ++i)
2565 b = Nicks_sanlist[i];
2575 if(tolower(*a) == tolower(*b))
2581 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2583 // b is alnum, so cut
2590 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2591 Nicks_CutMatchesNormal(count);
2592 //if(!Nicks_sanlist[0][0])
2593 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2595 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2596 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2600 static void Nicks_CutMatchesNoSpaces(int count)
2602 // cut match 0 down to the longest possible completion
2605 char tempstr[sizeof(Nicks_sanlist[0])];
2608 c = (unsigned int)strlen(Nicks_sanlist[0]);
2609 for(i = 0, l = 0; i < (int)c; ++i)
2611 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2613 tempstr[l++] = Nicks_sanlist[0][i];
2618 for(i = 1; i < count; ++i)
2621 b = Nicks_sanlist[i];
2631 if(tolower(*a) == tolower(*b))
2645 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2646 Nicks_CutMatchesNormal(count);
2647 //if(!Nicks_sanlist[0][0])
2648 //Con_Printf("TS: %s\n", tempstr);
2649 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2651 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2652 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2656 static void Nicks_CutMatches(int count)
2658 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2659 Nicks_CutMatchesAlphaNumeric(count);
2660 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2661 Nicks_CutMatchesNoSpaces(count);
2663 Nicks_CutMatchesNormal(count);
2666 static const char **Nicks_CompleteBuildList(int count)
2670 // the list is freed by Con_CompleteCommandLine, so create a char**
2671 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2673 for(; bpos < count; ++bpos)
2674 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2676 Nicks_CutMatches(count);
2684 Restores the previous used color, after the autocompleted name.
2686 static int Nicks_AddLastColor(char *buffer, int pos)
2688 qboolean quote_added = false;
2690 int color = STRING_COLOR_DEFAULT + '0';
2691 char r = 0, g = 0, b = 0;
2693 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2695 // we'll have to add a quote :)
2696 buffer[pos++] = '\"';
2700 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2702 // add color when no quote was added, or when flags &4?
2704 for(match = Nicks_matchpos-1; match >= 0; --match)
2706 if(buffer[match] == STRING_COLOR_TAG)
2708 if( isdigit(buffer[match+1]) )
2710 color = buffer[match+1];
2713 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2715 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2717 r = buffer[match+2];
2718 g = buffer[match+3];
2719 b = buffer[match+4];
2728 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2730 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2731 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2734 buffer[pos++] = STRING_COLOR_TAG;
2737 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2743 buffer[pos++] = color;
2748 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2751 /*if(!con_nickcompletion.integer)
2752 return; is tested in Nicks_CompletionCountPossible */
2753 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2759 msg = Nicks_list[0];
2760 len = min(size - Nicks_matchpos - 3, strlen(msg));
2761 memcpy(&buffer[Nicks_matchpos], msg, len);
2762 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2763 len = (int)Nicks_AddLastColor(buffer, Nicks_matchpos+(int)len);
2764 buffer[len++] = ' ';
2771 Con_Printf("\n%i possible nicks:\n", n);
2772 Cmd_CompleteNicksPrint(n);
2774 Nicks_CutMatches(n);
2776 msg = Nicks_sanlist[0];
2777 len = (int)min(size - Nicks_matchpos, strlen(msg));
2778 memcpy(&buffer[Nicks_matchpos], msg, len);
2779 buffer[Nicks_matchpos + len] = 0;
2781 return Nicks_matchpos + len;
2788 Con_CompleteCommandLine
2790 New function for tab-completion system
2791 Added by EvilTypeGuy
2792 Thanks to Fett erich@heintz.com
2794 Enhanced to tab-complete map names by [515]
2797 void Con_CompleteCommandLine (cmd_state_t *cmd)
2799 const char *text = "";
2801 const char **list[4] = {0, 0, 0, 0};
2804 int c, v, a, i, cmd_len, pos, k;
2805 int n; // nicks --blub
2806 const char *space, *patterns;
2809 //find what we want to complete
2814 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2820 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2821 key_line[key_linepos] = 0; //hide them
2823 space = strchr(key_line + 1, ' ');
2824 if(space && pos == (space - key_line) + 1)
2826 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2828 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?
2829 if(patterns && !*patterns)
2830 patterns = NULL; // get rid of the empty string
2832 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2836 if (GetMapList(s, t, sizeof(t)))
2838 // first move the cursor
2839 key_linepos += (int)strlen(t) - (int)strlen(s);
2841 // and now do the actual work
2843 strlcat(key_line, t, MAX_INPUTLINE);
2844 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2846 // and fix the cursor
2847 if(key_linepos > (int) strlen(key_line))
2848 key_linepos = (int) strlen(key_line);
2857 stringlist_t resultbuf, dirbuf;
2860 // // store completion patterns (space separated) for command foo in con_completion_foo
2861 // set con_completion_foo "foodata/*.foodefault *.foo"
2864 // Note: patterns with slash are always treated as absolute
2865 // patterns; patterns without slash search in the innermost
2866 // directory the user specified. There is no way to "complete into"
2867 // a directory as of now, as directories seem to be unknown to the
2871 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2872 // set con_completion_playdemo "*.dem"
2873 // set con_completion_play "*.wav *.ogg"
2875 // TODO somehow add support for directories; these shall complete
2876 // to their name + an appended slash.
2878 stringlistinit(&resultbuf);
2879 stringlistinit(&dirbuf);
2880 while(COM_ParseToken_Simple(&patterns, false, false, true))
2883 if(strchr(com_token, '/'))
2885 search = FS_Search(com_token, true, true);
2889 const char *slash = strrchr(s, '/');
2892 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2893 strlcat(t, com_token, sizeof(t));
2894 search = FS_Search(t, true, true);
2897 search = FS_Search(com_token, true, true);
2901 for(i = 0; i < search->numfilenames; ++i)
2902 if(!strncmp(search->filenames[i], s, strlen(s)))
2903 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2904 stringlistappend(&resultbuf, search->filenames[i]);
2905 FS_FreeSearch(search);
2909 // In any case, add directory names
2912 const char *slash = strrchr(s, '/');
2915 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2916 strlcat(t, "*", sizeof(t));
2917 search = FS_Search(t, true, true);
2920 search = FS_Search("*", true, true);
2923 for(i = 0; i < search->numfilenames; ++i)
2924 if(!strncmp(search->filenames[i], s, strlen(s)))
2925 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2926 stringlistappend(&dirbuf, search->filenames[i]);
2927 FS_FreeSearch(search);
2931 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2934 unsigned int matchchars;
2935 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2937 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2940 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2942 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2946 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2947 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2948 for(i = 0; i < dirbuf.numstrings; ++i)
2950 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2952 for(i = 0; i < resultbuf.numstrings; ++i)
2954 Con_Printf("%s\n", resultbuf.strings[i]);
2956 matchchars = sizeof(t) - 1;
2957 if(resultbuf.numstrings > 0)
2959 p = resultbuf.strings[0];
2960 q = resultbuf.strings[resultbuf.numstrings - 1];
2961 for(; *p && *p == *q; ++p, ++q);
2962 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2964 if(dirbuf.numstrings > 0)
2966 p = dirbuf.strings[0];
2967 q = dirbuf.strings[dirbuf.numstrings - 1];
2968 for(; *p && *p == *q; ++p, ++q);
2969 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2971 // now p points to the first non-equal character, or to the end
2972 // of resultbuf.strings[0]. We want to append the characters
2973 // from resultbuf.strings[0] to (not including) p as these are
2974 // the unique prefix
2975 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2978 // first move the cursor
2979 key_linepos += (int)strlen(t) - (int)strlen(s);
2981 // and now do the actual work
2983 strlcat(key_line, t, MAX_INPUTLINE);
2984 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2986 // and fix the cursor
2987 if(key_linepos > (int) strlen(key_line))
2988 key_linepos = (int) strlen(key_line);
2990 stringlistfreecontents(&resultbuf);
2991 stringlistfreecontents(&dirbuf);
2993 return; // bail out, when we complete for a command that wants a file name
2998 // Count number of possible matches and print them
2999 c = Cmd_CompleteCountPossible(cmd, s);
3002 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
3003 Cmd_CompleteCommandPrint(cmd, s);
3005 v = Cvar_CompleteCountPossible(cmd->cvars, s, CVAR_CLIENT | CVAR_SERVER);
3008 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
3009 Cvar_CompleteCvarPrint(cmd->cvars, s, CVAR_CLIENT | CVAR_SERVER);
3011 a = Cmd_CompleteAliasCountPossible(cmd, s);
3014 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
3015 Cmd_CompleteAliasPrint(cmd, s);
3017 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
3020 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
3021 Cmd_CompleteNicksPrint(n);
3024 if (!(c + v + a + n)) // No possible matches
3027 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
3032 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
3034 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
3036 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
3038 text = *(list[3] = Nicks_CompleteBuildList(n));
3040 for (cmd_len = (int)strlen(s);;cmd_len++)
3043 for (i = 0; i < 3; i++)
3045 for (l = list[i];*l;l++)
3046 if ((*l)[cmd_len] != text[cmd_len])
3048 // all possible matches share this character, so we continue...
3051 // if all matches ended at the same position, stop
3052 // (this means there is only one match)
3058 // prevent a buffer overrun by limiting cmd_len according to remaining space
3059 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
3063 memcpy(&key_line[key_linepos], text, cmd_len);
3064 key_linepos += cmd_len;
3065 // if there is only one match, add a space after it
3066 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
3069 { // was a nick, might have an offset, and needs colors ;) --blub
3070 key_linepos = pos - Nicks_offset[0];
3071 cmd_len = (int)strlen(Nicks_list[0]);
3072 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
3074 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
3075 key_linepos += cmd_len;
3076 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
3077 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
3079 key_line[key_linepos++] = ' ';
3083 // use strlcat to avoid a buffer overrun
3084 key_line[key_linepos] = 0;
3085 strlcat(key_line, s2, sizeof(key_line));
3087 // free the command, cvar, and alias lists
3088 for (i = 0; i < 4; i++)
3090 Mem_Free((void *)list[i]);