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"};
62 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)"};
64 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)"};
66 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)"};
70 cvar_t con_nickcompletion = {CVAR_CLIENT | CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
71 cvar_t con_nickcompletion_flags = {CVAR_CLIENT | CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
72 "0: add nothing after completion. "
73 "1: add the last color after completion. "
74 "2: add a quote when starting a quote instead of the color. "
75 "4: will replace 1, will force color, even after a quote. "
76 "8: ignore non-alphanumerics. "
77 "16: ignore spaces. "};
78 #define NICKS_ADD_COLOR 1
79 #define NICKS_ADD_QUOTE 2
80 #define NICKS_FORCE_COLOR 4
81 #define NICKS_ALPHANUMERICS_ONLY 8
82 #define NICKS_NO_SPACES 16
84 cvar_t con_completion_playdemo = {CVAR_CLIENT | CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
85 cvar_t con_completion_timedemo = {CVAR_CLIENT | CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
86 cvar_t con_completion_exec = {CVAR_CLIENT | CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
88 cvar_t condump_stripcolors = {CVAR_CLIENT | CVAR_SERVER| CVAR_SAVE, "condump_stripcolors", "0", "strip color codes from console dumps"};
93 qboolean con_initialized;
95 // used for server replies to rcon command
96 lhnetsocket_t *rcon_redirect_sock = NULL;
97 lhnetaddress_t *rcon_redirect_dest = NULL;
98 int rcon_redirect_bufferpos = 0;
99 char rcon_redirect_buffer[1400];
100 qboolean rcon_redirect_proquakeprotocol = false;
102 // generic functions for console buffers
104 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
107 buf->textsize = textsize;
108 buf->text = (char *) Mem_Alloc(mempool, textsize);
109 buf->maxlines = maxlines;
110 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
111 buf->lines_first = 0;
112 buf->lines_count = 0;
115 /*! The translation table between the graphical font and plain ASCII --KB */
116 static char qfont_table[256] = {
117 '\0', '#', '#', '#', '#', '.', '#', '#',
118 '#', 9, 10, '#', ' ', 13, '.', '.',
119 '[', ']', '0', '1', '2', '3', '4', '5',
120 '6', '7', '8', '9', '.', '<', '=', '>',
121 ' ', '!', '"', '#', '$', '%', '&', '\'',
122 '(', ')', '*', '+', ',', '-', '.', '/',
123 '0', '1', '2', '3', '4', '5', '6', '7',
124 '8', '9', ':', ';', '<', '=', '>', '?',
125 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
126 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
127 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
128 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
129 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
130 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
131 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
132 'x', 'y', 'z', '{', '|', '}', '~', '<',
134 '<', '=', '>', '#', '#', '.', '#', '#',
135 '#', '#', ' ', '#', ' ', '>', '.', '.',
136 '[', ']', '0', '1', '2', '3', '4', '5',
137 '6', '7', '8', '9', '.', '<', '=', '>',
138 ' ', '!', '"', '#', '$', '%', '&', '\'',
139 '(', ')', '*', '+', ',', '-', '.', '/',
140 '0', '1', '2', '3', '4', '5', '6', '7',
141 '8', '9', ':', ';', '<', '=', '>', '?',
142 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
143 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
144 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
145 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
146 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
147 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
148 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
149 'x', 'y', 'z', '{', '|', '}', '~', '<'
153 SanitizeString strips color tags from the string in
154 and writes the result on string out
156 static void SanitizeString(char *in, char *out)
160 if(*in == STRING_COLOR_TAG)
165 out[0] = STRING_COLOR_TAG;
169 else if (*in >= '0' && *in <= '9') // ^[0-9] found
176 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
179 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
181 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
188 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
193 else if (*in != STRING_COLOR_TAG)
196 *out = qfont_table[*(unsigned char*)in];
208 void ConBuffer_Clear (conbuffer_t *buf)
210 buf->lines_count = 0;
218 void ConBuffer_Shutdown(conbuffer_t *buf)
224 Mem_Free(buf->lines);
233 Notifies the console code about the current time
234 (and shifts back times of other entries when the time
238 void ConBuffer_FixTimes(conbuffer_t *buf)
241 if(buf->lines_count >= 1)
243 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
246 for(i = 0; i < buf->lines_count; ++i)
247 CONBUFFER_LINES(buf, i).addtime += diff;
256 Deletes the first line from the console history.
259 void ConBuffer_DeleteLine(conbuffer_t *buf)
261 if(buf->lines_count == 0)
264 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
269 ConBuffer_DeleteLastLine
271 Deletes the last line from the console history.
274 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
276 if(buf->lines_count == 0)
285 Checks if there is space for a line of the given length, and if yes, returns a
286 pointer to the start of such a space, and NULL otherwise.
289 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
291 if(len > buf->textsize)
293 if(buf->lines_count == 0)
297 char *firstline_start = buf->lines[buf->lines_first].start;
298 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
299 // the buffer is cyclic, so we first have two cases...
300 if(firstline_start < lastline_onepastend) // buffer is contiguous
303 if(len <= buf->text + buf->textsize - lastline_onepastend)
304 return lastline_onepastend;
306 else if(len <= firstline_start - buf->text)
311 else // buffer has a contiguous hole
313 if(len <= firstline_start - lastline_onepastend)
314 return lastline_onepastend;
325 Appends a given string as a new line to the console.
328 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
333 // developer_memory 1 during shutdown prints while conbuffer_t is being freed
337 ConBuffer_FixTimes(buf);
339 if(len >= buf->textsize)
342 // only display end of line.
343 line += len - buf->textsize + 1;
344 len = buf->textsize - 1;
346 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
347 ConBuffer_DeleteLine(buf);
348 memcpy(putpos, line, len);
352 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
354 p = &CONBUFFER_LINES_LAST(buf);
357 p->addtime = cl.time;
359 p->height = -1; // calculate when needed
362 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
366 start = buf->lines_count;
367 for(i = start - 1; i >= 0; --i)
369 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
371 if((l->mask & mask_must) != mask_must)
373 if(l->mask & mask_mustnot)
382 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
384 static char copybuf[MAX_INPUTLINE]; // client only
385 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
386 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
387 strlcpy(copybuf, l->start, sz);
392 ==============================================================================
396 ==============================================================================
401 cvar_t log_file = {CVAR_CLIENT | CVAR_SERVER, "log_file", "", "filename to log messages to"};
402 cvar_t log_file_stripcolors = {CVAR_CLIENT | CVAR_SERVER, "log_file_stripcolors", "0", "strip color codes from log messages"};
403 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"};
404 char log_dest_buffer[1400]; // UDP packet
405 size_t log_dest_buffer_pos;
406 unsigned int log_dest_buffer_appending;
407 char crt_log_file [MAX_OSPATH] = "";
408 qfile_t* logfile = NULL;
410 unsigned char* logqueue = NULL;
412 size_t logq_size = 0;
414 void Log_ConPrint (const char *msg);
416 static void Log_DestBuffer_Init(void)
418 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
419 log_dest_buffer_pos = 5;
422 static void Log_DestBuffer_Flush_NoLock(void)
424 lhnetaddress_t log_dest_addr;
425 lhnetsocket_t *log_dest_socket;
426 const char *s = log_dest_udp.string;
427 qboolean have_opened_temp_sockets = false;
428 if(s) if(log_dest_buffer_pos > 5)
430 ++log_dest_buffer_appending;
431 log_dest_buffer[log_dest_buffer_pos++] = 0;
433 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
435 have_opened_temp_sockets = true;
436 NetConn_OpenServerPorts(true);
439 while(COM_ParseToken_Console(&s))
440 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
442 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
444 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
446 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
449 if(have_opened_temp_sockets)
450 NetConn_CloseServerPorts();
451 --log_dest_buffer_appending;
453 log_dest_buffer_pos = 0;
461 void Log_DestBuffer_Flush(void)
464 Thread_LockMutex(con_mutex);
465 Log_DestBuffer_Flush_NoLock();
467 Thread_UnlockMutex(con_mutex);
470 static const char* Log_Timestamp (const char *desc)
472 static char timestamp [128]; // init/shutdown only
479 char timestring [64];
481 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
484 localtime_s (&crt_tm, &crt_time);
485 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
487 crt_tm = localtime (&crt_time);
488 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
492 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
494 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
499 static void Log_Open (void)
501 if (logfile != NULL || log_file.string[0] == '\0')
504 logfile = FS_OpenRealFile(log_file.string, "a", false);
507 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
508 FS_Print (logfile, Log_Timestamp ("Log started"));
517 void Log_Close (void)
522 FS_Print (logfile, Log_Timestamp ("Log stopped"));
523 FS_Print (logfile, "\n");
527 crt_log_file[0] = '\0';
536 void Log_Start (void)
542 // Dump the contents of the log queue into the log file and free it
543 if (logqueue != NULL)
545 unsigned char *temp = logqueue;
550 FS_Write (logfile, temp, logq_ind);
551 if(*log_dest_udp.string)
553 for(pos = 0; pos < logq_ind; )
555 if(log_dest_buffer_pos == 0)
556 Log_DestBuffer_Init();
557 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
558 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
559 log_dest_buffer_pos += n;
560 Log_DestBuffer_Flush_NoLock();
578 void Log_ConPrint (const char *msg)
580 static qboolean inprogress = false;
582 // don't allow feedback loops with memory error reports
587 // Until the host is completely initialized, we maintain a log queue
588 // to store the messages, since the log can't be started before
589 if (logqueue != NULL)
591 size_t remain = logq_size - logq_ind;
592 size_t len = strlen (msg);
594 // If we need to enlarge the log queue
597 size_t factor = ((logq_ind + len) / logq_size) + 1;
598 unsigned char* newqueue;
601 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
602 memcpy (newqueue, logqueue, logq_ind);
605 remain = logq_size - logq_ind;
607 memcpy (&logqueue[logq_ind], msg, len);
614 // Check if log_file has changed
615 if (strcmp (crt_log_file, log_file.string) != 0)
621 // If a log file is available
624 if (log_file_stripcolors.integer)
627 size_t len = strlen(msg);
628 char* sanitizedmsg = (char*)Mem_Alloc(tempmempool, len + 1);
629 memcpy (sanitizedmsg, msg, len);
630 SanitizeString(sanitizedmsg, sanitizedmsg); // SanitizeString's in pointer is always ahead of the out pointer, so this should work.
631 FS_Print (logfile, sanitizedmsg);
632 Mem_Free(sanitizedmsg);
636 FS_Print (logfile, msg);
649 void Log_Printf (const char *logfilename, const char *fmt, ...)
653 file = FS_OpenRealFile(logfilename, "a", true);
658 va_start (argptr, fmt);
659 FS_VPrintf (file, fmt, argptr);
668 ==============================================================================
672 ==============================================================================
680 void Con_ToggleConsole_f(cmd_state_t *cmd)
682 if (COM_CheckParm ("-noconsole"))
683 if (!(key_consoleactive & KEY_CONSOLEACTIVE_USER))
684 return; // only allow the key bind to turn off console
686 // toggle the 'user wants console' bit
687 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
696 void Con_ClearNotify (void)
699 for(i = 0; i < CON_LINES_COUNT; ++i)
700 if(!(CON_LINES(i).mask & CON_MASK_CHAT))
701 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
710 static void Con_MessageMode_f(cmd_state_t *cmd)
712 key_dest = key_message;
713 chat_mode = 0; // "say"
714 if(Cmd_Argc(cmd) > 1)
716 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
717 chat_bufferlen = (unsigned int)strlen(chat_buffer);
727 static void Con_MessageMode2_f(cmd_state_t *cmd)
729 key_dest = key_message;
730 chat_mode = 1; // "say_team"
731 if(Cmd_Argc(cmd) > 1)
733 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
734 chat_bufferlen = (unsigned int)strlen(chat_buffer);
743 static void Con_CommandMode_f(cmd_state_t *cmd)
745 key_dest = key_message;
746 if(Cmd_Argc(cmd) > 1)
748 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args(cmd));
749 chat_bufferlen = (unsigned int)strlen(chat_buffer);
751 chat_mode = -1; // command
759 void Con_CheckResize (void)
764 f = bound(1, con_textsize.value, 128);
765 if(f != con_textsize.value)
766 Cvar_SetValueQuick(&con_textsize, f);
767 width = (int)floor(vid_conwidth.value / con_textsize.value);
768 width = bound(1, width, con.textsize/4);
769 // FIXME uses con in a non abstracted way
771 if (width == con_linewidth)
774 con_linewidth = width;
776 for(i = 0; i < CON_LINES_COUNT; ++i)
777 CON_LINES(i).height = -1; // recalculate when next needed
783 //[515]: the simplest command ever
784 //LadyHavoc: not so simple after I made it print usage...
785 static void Con_Maps_f(cmd_state_t *cmd)
787 if (Cmd_Argc(cmd) > 2)
789 Con_Printf("usage: maps [mapnameprefix]\n");
792 else if (Cmd_Argc(cmd) == 2)
793 GetMapList(Cmd_Argv(cmd, 1), NULL, 0);
795 GetMapList("", NULL, 0);
798 static void Con_ConDump_f(cmd_state_t *cmd)
802 if (Cmd_Argc(cmd) != 2)
804 Con_Printf("usage: condump <filename>\n");
807 file = FS_OpenRealFile(Cmd_Argv(cmd, 1), "w", false);
810 Con_Errorf("condump: unable to write file \"%s\"\n", Cmd_Argv(cmd, 1));
813 if (con_mutex) Thread_LockMutex(con_mutex);
814 for(i = 0; i < CON_LINES_COUNT; ++i)
816 if (condump_stripcolors.integer)
819 size_t len = CON_LINES(i).len;
820 char* sanitizedmsg = (char*)Mem_Alloc(tempmempool, len + 1);
821 memcpy (sanitizedmsg, CON_LINES(i).start, len);
822 SanitizeString(sanitizedmsg, sanitizedmsg); // SanitizeString's in pointer is always ahead of the out pointer, so this should work.
823 FS_Write(file, sanitizedmsg, strlen(sanitizedmsg));
824 Mem_Free(sanitizedmsg);
828 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
830 FS_Write(file, "\n", 1);
832 if (con_mutex) Thread_UnlockMutex(con_mutex);
836 void Con_Clear_f(cmd_state_t *cmd)
838 if (con_mutex) Thread_LockMutex(con_mutex);
839 ConBuffer_Clear(&con);
840 if (con_mutex) Thread_UnlockMutex(con_mutex);
851 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
852 if (Thread_HasThreads())
853 con_mutex = Thread_CreateMutex();
855 // Allocate a log queue, this will be freed after configs are parsed
856 logq_size = MAX_INPUTLINE;
857 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
860 Cvar_RegisterVariable (&sys_colortranslation);
861 Cvar_RegisterVariable (&sys_specialcharactertranslation);
863 Cvar_RegisterVariable (&log_file);
864 Cvar_RegisterVariable (&log_file_stripcolors);
865 Cvar_RegisterVariable (&log_dest_udp);
867 // support for the classic Quake option
868 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
869 if (COM_CheckParm ("-condebug") != 0)
870 Cvar_SetQuick (&log_file, "qconsole.log");
872 // register our cvars
873 Cvar_RegisterVariable (&con_chat);
874 Cvar_RegisterVariable (&con_chatpos);
875 Cvar_RegisterVariable (&con_chatrect_x);
876 Cvar_RegisterVariable (&con_chatrect_y);
877 Cvar_RegisterVariable (&con_chatrect);
878 Cvar_RegisterVariable (&con_chatsize);
879 Cvar_RegisterVariable (&con_chattime);
880 Cvar_RegisterVariable (&con_chatwidth);
881 Cvar_RegisterVariable (&con_notify);
882 Cvar_RegisterVariable (&con_notifyalign);
883 Cvar_RegisterVariable (&con_notifysize);
884 Cvar_RegisterVariable (&con_notifytime);
885 Cvar_RegisterVariable (&con_textsize);
886 Cvar_RegisterVariable (&con_chatsound);
889 Cvar_RegisterVariable (&con_nickcompletion);
890 Cvar_RegisterVariable (&con_nickcompletion_flags);
892 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
893 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
894 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
896 Cvar_RegisterVariable (&condump_stripcolors);
898 // register our commands
899 Cmd_AddCommand(CMD_CLIENT, "toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
900 Cmd_AddCommand(CMD_CLIENT, "messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
901 Cmd_AddCommand(CMD_CLIENT, "messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
902 Cmd_AddCommand(CMD_CLIENT, "commandmode", Con_CommandMode_f, "input a console command");
903 Cmd_AddCommand(CMD_SHARED, "clear", Con_Clear_f, "clear console history");
904 Cmd_AddCommand(CMD_SHARED, "maps", Con_Maps_f, "list information about available maps");
905 Cmd_AddCommand(CMD_SHARED, "condump", Con_ConDump_f, "output console history to a file (see also log_file)");
907 con_initialized = true;
908 Con_DPrint("Console initialized.\n");
911 void Con_Shutdown (void)
913 if (con_mutex) Thread_LockMutex(con_mutex);
914 ConBuffer_Shutdown(&con);
915 if (con_mutex) Thread_UnlockMutex(con_mutex);
916 if (con_mutex) Thread_DestroyMutex(con_mutex);con_mutex = NULL;
923 Handles cursor positioning, line wrapping, etc
924 All console printing must go through this in order to be displayed
925 If no console is visible, the notify window will pop up.
928 static void Con_PrintToHistory(const char *txt, int mask)
931 // \n goes to next line
932 // \r deletes current line and makes a new one
934 static int cr_pending = 0;
935 static char buf[CON_TEXTSIZE]; // con_mutex
936 static int bufpos = 0;
938 if(!con.text) // FIXME uses a non-abstracted property of con
945 ConBuffer_DeleteLastLine(&con);
953 ConBuffer_AddLine(&con, buf, bufpos, mask);
958 ConBuffer_AddLine(&con, buf, bufpos, mask);
962 buf[bufpos++] = *txt;
963 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
965 ConBuffer_AddLine(&con, buf, bufpos, mask);
973 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol)
975 rcon_redirect_sock = sock;
976 rcon_redirect_dest = dest;
977 rcon_redirect_proquakeprotocol = proquakeprotocol;
978 if (rcon_redirect_proquakeprotocol)
980 // reserve space for the packet header
981 rcon_redirect_buffer[0] = 0;
982 rcon_redirect_buffer[1] = 0;
983 rcon_redirect_buffer[2] = 0;
984 rcon_redirect_buffer[3] = 0;
985 // this is a reply to a CCREQ_RCON
986 rcon_redirect_buffer[4] = (unsigned char)CCREP_RCON;
989 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
990 rcon_redirect_bufferpos = 5;
993 static void Con_Rcon_Redirect_Flush(void)
995 if(rcon_redirect_sock)
997 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
998 if (rcon_redirect_proquakeprotocol)
1000 // update the length in the packet header
1001 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
1003 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
1005 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1006 rcon_redirect_bufferpos = 5;
1007 rcon_redirect_proquakeprotocol = false;
1010 void Con_Rcon_Redirect_End(void)
1012 Con_Rcon_Redirect_Flush();
1013 rcon_redirect_dest = NULL;
1014 rcon_redirect_sock = NULL;
1017 void Con_Rcon_Redirect_Abort(void)
1019 rcon_redirect_dest = NULL;
1020 rcon_redirect_sock = NULL;
1028 /// Adds a character to the rcon buffer.
1029 static void Con_Rcon_AddChar(int c)
1031 if(log_dest_buffer_appending)
1033 ++log_dest_buffer_appending;
1035 // if this print is in response to an rcon command, add the character
1036 // to the rcon redirect buffer
1038 if (rcon_redirect_dest)
1040 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
1041 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
1042 Con_Rcon_Redirect_Flush();
1044 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
1046 if(log_dest_buffer_pos == 0)
1047 Log_DestBuffer_Init();
1048 log_dest_buffer[log_dest_buffer_pos++] = c;
1049 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
1050 Log_DestBuffer_Flush_NoLock();
1053 log_dest_buffer_pos = 0;
1055 --log_dest_buffer_appending;
1059 * Convert an RGB color to its nearest quake color.
1060 * I'll cheat on this a bit by translating the colors to HSV first,
1061 * S and V decide if it's black or white, otherwise, H will decide the
1063 * @param _r Red (0-255)
1064 * @param _g Green (0-255)
1065 * @param _b Blue (0-255)
1066 * @return A quake color character.
1068 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
1070 float r = ((float)_r)/255.0;
1071 float g = ((float)_g)/255.0;
1072 float b = ((float)_b)/255.0;
1073 float min = min(r, min(g, b));
1074 float max = max(r, max(g, b));
1076 int h; ///< Hue angle [0,360]
1077 float s; ///< Saturation [0,1]
1078 float v = max; ///< In HSV v == max [0,1]
1083 s = 1.0 - (min/max);
1085 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1088 // If the value is less than half, return a black color code.
1089 // Otherwise return a white one.
1095 // Let's get the hue angle to define some colors:
1099 h = (int)(60.0 * (g-b)/(max-min))%360;
1101 h = (int)(60.0 * (b-r)/(max-min) + 120);
1102 else // if(max == b) redundant check
1103 h = (int)(60.0 * (r-g)/(max-min) + 240);
1105 if(h < 36) // *red* to orange
1107 else if(h < 80) // orange over *yellow* to evilish-bright-green
1109 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1111 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1113 else if(h < 270) // darkish blue over *dark blue* to cool purple
1115 else if(h < 330) // cool purple over *purple* to ugly swiny red
1117 else // ugly red to red closes the circly
1126 extern cvar_t timestamps;
1127 extern cvar_t timeformat;
1128 extern qboolean sys_nostdout;
1129 void Con_MaskPrint(int additionalmask, const char *msg)
1131 static int mask = 0;
1132 static int index = 0;
1133 static char line[MAX_INPUTLINE];
1136 Thread_LockMutex(con_mutex);
1140 Con_Rcon_AddChar(*msg);
1141 // if this is the beginning of a new line, print timestamp
1144 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1146 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1147 line[index++] = STRING_COLOR_TAG;
1148 // assert( STRING_COLOR_DEFAULT < 10 )
1149 line[index++] = STRING_COLOR_DEFAULT + '0';
1150 // special color codes for chat messages must always come first
1151 // for Con_PrintToHistory to work properly
1152 if (*msg == 1 || *msg == 2 || *msg == 3)
1157 if (con_chatsound.value)
1159 if(IS_NEXUIZ_DERIVED(gamemode))
1161 if(msg[1] == '\r' && cl.foundtalk2wav)
1162 S_LocalSound ("sound/misc/talk2.wav");
1164 S_LocalSound ("sound/misc/talk.wav");
1168 if (msg[1] == '(' && cl.foundtalk2wav)
1169 S_LocalSound ("sound/misc/talk2.wav");
1171 S_LocalSound ("sound/misc/talk.wav");
1176 // Send to chatbox for say/tell (1) and messages (3)
1177 // 3 is just so that a message can be sent to the chatbox without a sound.
1178 if (*msg == 1 || *msg == 3)
1179 mask = CON_MASK_CHAT;
1181 line[index++] = STRING_COLOR_TAG;
1182 line[index++] = '3';
1184 Con_Rcon_AddChar(*msg);
1187 for (;*timestamp;index++, timestamp++)
1188 if (index < (int)sizeof(line) - 2)
1189 line[index] = *timestamp;
1191 mask |= additionalmask;
1193 // append the character
1194 line[index++] = *msg;
1195 // if this is a newline character, we have a complete line to print
1196 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1198 // terminate the line
1202 // send to scrollable buffer
1203 if (con_initialized && cls.state != ca_dedicated)
1205 Con_PrintToHistory(line, mask);
1207 // send to terminal or dedicated server window
1209 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1211 if(sys_specialcharactertranslation.integer)
1218 int ch = u8_getchar(p, &q);
1219 if(ch >= 0xE000 && ch <= 0xE0FF && ((unsigned char) qfont_table[ch - 0xE000]) >= 0x20)
1221 *p = qfont_table[ch - 0xE000];
1223 memmove(p+1, q, strlen(q)+1);
1231 if(sys_colortranslation.integer == 1) // ANSI
1233 static char printline[MAX_INPUTLINE * 4 + 3];
1234 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1235 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1240 for(in = line, out = printline; *in; ++in)
1244 case STRING_COLOR_TAG:
1245 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1247 char r = tolower(in[2]);
1248 char g = tolower(in[3]);
1249 char b = tolower(in[4]);
1250 // it's a hex digit already, so the else part needs no check --blub
1251 if(isdigit(r)) r -= '0';
1253 if(isdigit(g)) g -= '0';
1255 if(isdigit(b)) b -= '0';
1258 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1259 in += 3; // 3 only, the switch down there does the fourth
1266 case STRING_COLOR_TAG:
1268 *out++ = STRING_COLOR_TAG;
1274 if(lastcolor == 0) break; else lastcolor = 0;
1275 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1280 if(lastcolor == 1) break; else lastcolor = 1;
1281 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1286 if(lastcolor == 2) break; else lastcolor = 2;
1287 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1292 if(lastcolor == 3) break; else lastcolor = 3;
1293 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1298 if(lastcolor == 4) break; else lastcolor = 4;
1299 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1304 if(lastcolor == 5) break; else lastcolor = 5;
1305 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1310 if(lastcolor == 6) break; else lastcolor = 6;
1311 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1316 // bold normal color
1318 if(lastcolor == 8) break; else lastcolor = 8;
1319 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1322 *out++ = STRING_COLOR_TAG;
1329 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1346 Sys_PrintToTerminal(printline);
1348 else if(sys_colortranslation.integer == 2) // Quake
1350 Sys_PrintToTerminal(line);
1354 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1357 for(in = line, out = printline; *in; ++in)
1361 case STRING_COLOR_TAG:
1364 case STRING_COLOR_RGB_TAG_CHAR:
1365 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1370 *out++ = STRING_COLOR_TAG;
1371 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1374 case STRING_COLOR_TAG:
1376 *out++ = STRING_COLOR_TAG;
1391 *out++ = STRING_COLOR_TAG;
1401 Sys_PrintToTerminal(printline);
1404 // empty the line buffer
1411 Thread_UnlockMutex(con_mutex);
1419 void Con_MaskPrintf(int mask, const char *fmt, ...)
1422 char msg[MAX_INPUTLINE];
1424 va_start(argptr,fmt);
1425 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1428 Con_MaskPrint(mask, msg);
1436 void Con_Print(const char *msg)
1438 Con_MaskPrint(CON_MASK_PRINT, msg);
1446 void Con_Printf(const char *fmt, ...)
1449 char msg[MAX_INPUTLINE];
1451 va_start(argptr,fmt);
1452 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1455 Con_MaskPrint(CON_MASK_PRINT, msg);
1463 void Con_Warn(const char *msg)
1465 Con_Printf("^3%s",msg);
1473 void Con_Warnf(const char *fmt, ...)
1476 char msg[MAX_INPUTLINE];
1478 va_start(argptr,fmt);
1479 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1482 Con_Printf("^3%s",msg);
1490 void Con_Error(const char *msg)
1492 Con_Printf("^1%s",msg);
1500 void Con_Errorf(const char *fmt, ...)
1503 char msg[MAX_INPUTLINE];
1505 va_start(argptr,fmt);
1506 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1509 Con_Printf("^1%s",msg);
1518 void Con_DPrint(const char *msg)
1520 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1523 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1531 void Con_DPrintf(const char *fmt, ...)
1534 char msg[MAX_INPUTLINE];
1536 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1539 va_start(argptr,fmt);
1540 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1543 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1548 ==============================================================================
1552 ==============================================================================
1559 The input line scrolls horizontally if typing goes beyond the right edge
1561 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1564 static void Con_DrawInput (void)
1568 char text[sizeof(key_line)+5+1]; // space for ^^xRGB too
1573 if (!key_consoleactive)
1574 return; // don't draw anything
1576 strlcpy(text, key_line, sizeof(text));
1578 // Advanced Console Editing by Radix radix@planetquake.com
1579 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1581 y = (int)strlen(text);
1583 // make the color code visible when the cursor is inside it
1584 if(text[key_linepos] != 0)
1586 for(i=1; i < 5 && key_linepos - i > 0; ++i)
1587 if(text[key_linepos-i] == STRING_COLOR_TAG)
1589 int caret_pos, ofs = 0;
1590 caret_pos = key_linepos - i;
1591 if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG)
1593 else if(i == 1 && isdigit(text[caret_pos+1]))
1595 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]))
1597 if(ofs && (size_t)(y + ofs + 1) < sizeof(text))
1600 while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG)
1604 // str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing)
1605 // str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing)
1606 memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos);
1607 text[caret_pos + ofs] = STRING_COLOR_TAG;
1616 len_out = key_linepos;
1618 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1619 x = vid_conwidth.value * 0.95 - xo; // scroll
1624 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 );
1626 // draw a cursor on top of this
1627 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1629 if (!utf8_enable.integer)
1631 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1639 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1640 memcpy(text, curbuf, len);
1643 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);
1650 float alignment; // 0 = left, 0.5 = center, 1 = right
1656 const char *continuationString;
1659 int colorindex; // init to -1
1663 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1665 con_text_info_t *ti = (con_text_info_t *) passthrough;
1668 ti->colorindex = -1;
1669 return ti->fontsize * ti->font->maxwidth;
1672 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1673 else if(maxWidth == -1)
1674 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1677 Sys_PrintfToTerminal("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1678 // Note: this is NOT a Con_Printf, as it could print recursively
1683 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1689 (void) isContinuation;
1693 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1695 con_text_info_t *ti = (con_text_info_t *) passthrough;
1697 if(ti->y < ti->ymin - 0.001)
1699 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1703 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1704 if(isContinuation && *ti->continuationString)
1705 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);
1707 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);
1710 ti->y += ti->fontsize;
1714 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)
1718 int maxlines = (int) floor(height / fontsize + 0.01f);
1721 int continuationWidth = 0;
1723 double t = cl.time; // saved so it won't change
1726 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1727 ti.fontsize = fontsize;
1728 ti.alignment = alignment_x;
1731 ti.ymax = y + height;
1732 ti.continuationString = continuationString;
1735 Con_WordWidthFunc(&ti, NULL, &len, -1);
1736 len = strlen(continuationString);
1737 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &len, -1);
1739 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1740 startidx = CON_LINES_COUNT;
1741 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1743 con_lineinfo_t *l = &CON_LINES(i);
1746 if((l->mask & mask_must) != mask_must)
1748 if(l->mask & mask_mustnot)
1750 if(maxage && (l->addtime < t - maxage))
1754 // Calculate its actual height...
1755 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1756 if(lines + mylines >= maxlines)
1758 nskip = lines + mylines - maxlines;
1767 // then center according to the calculated amount of lines...
1769 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1771 // then actually draw
1772 for(i = startidx; i < CON_LINES_COUNT; ++i)
1774 con_lineinfo_t *l = &CON_LINES(i);
1776 if((l->mask & mask_must) != mask_must)
1778 if(l->mask & mask_mustnot)
1780 if(maxage && (l->addtime < t - maxage))
1783 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1793 Draws the last few lines of output transparently over the game top
1796 void Con_DrawNotify (void)
1799 float chatstart, notifystart, inputsize, height;
1801 char temptext[MAX_INPUTLINE];
1805 if (con_mutex) Thread_LockMutex(con_mutex);
1806 ConBuffer_FixTimes(&con);
1808 numChatlines = con_chat.integer;
1810 chatpos = con_chatpos.integer;
1812 if (con_notify.integer < 0)
1813 Cvar_SetValueQuick(&con_notify, 0);
1814 if (gamemode == GAME_TRANSFUSION)
1815 v = 8; // vertical offset
1819 // GAME_NEXUIZ: center, otherwise left justify
1820 align = con_notifyalign.value;
1821 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1823 if(IS_OLDNEXUIZ_DERIVED(gamemode))
1827 if(numChatlines || !con_chatrect.integer)
1831 // first chat, input line, then notify
1833 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1835 else if(chatpos > 0)
1837 // first notify, then (chatpos-1) empty lines, then chat, then input
1839 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1841 else // if(chatpos < 0)
1843 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1845 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1850 // just notify and input
1852 chatstart = 0; // shut off gcc warning
1855 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, "");
1857 if(con_chatrect.integer)
1859 x = con_chatrect_x.value * vid_conwidth.value;
1860 v = con_chatrect_y.value * vid_conheight.value;
1865 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1868 height = numChatlines * con_chatsize.value;
1872 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 ... ");
1875 if (key_dest == key_message)
1877 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1878 int colorindex = -1;
1881 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL, charbuf16);
1883 // LadyHavoc: speedup, and other improvements
1885 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1887 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1889 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1892 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1893 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1895 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1897 if (con_mutex) Thread_UnlockMutex(con_mutex);
1904 Returns the height of a given console line; calculates it if necessary.
1907 static int Con_LineHeight(int lineno)
1909 con_lineinfo_t *li = &CON_LINES(lineno);
1910 if(li->height == -1)
1912 float width = vid_conwidth.value;
1914 ti.fontsize = con_textsize.value;
1915 ti.font = FONT_CONSOLE;
1916 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1925 Draws a line of the console; returns its height in lines.
1926 If alpha is 0, the line is not drawn, but still wrapped and its height
1930 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1932 float width = vid_conwidth.value;
1934 con_lineinfo_t *li = &CON_LINES(lineno);
1936 if((li->mask & mask_must) != mask_must)
1938 if((li->mask & mask_mustnot) != 0)
1941 ti.continuationString = "";
1943 ti.fontsize = con_textsize.value;
1944 ti.font = FONT_CONSOLE;
1946 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1951 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1958 Calculates the last visible line index and how much to show of it based on
1962 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1967 if(con_backscroll < 0)
1972 // now count until we saw con_backscroll actual lines
1973 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1974 if((CON_LINES(i).mask & mask_must) == mask_must)
1975 if((CON_LINES(i).mask & mask_mustnot) == 0)
1977 int h = Con_LineHeight(i);
1979 // line is the last visible line?
1981 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1983 *limitlast = lines_seen + h - con_backscroll;
1990 // if we get here, no line was on screen - scroll so that one line is
1992 con_backscroll = lines_seen - 1;
2000 Draws the console with the solid background
2001 The typing input line at the bottom should only be drawn if typing is allowed
2004 void Con_DrawConsole (int lines)
2006 float alpha, alpha0;
2009 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
2010 cachepic_t *conbackpic;
2011 unsigned int conbackflags;
2016 if (con_mutex) Thread_LockMutex(con_mutex);
2018 if (con_backscroll < 0)
2021 con_vislines = lines;
2023 r_draw2d_force = true;
2025 // draw the background
2026 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
2027 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
2029 sx = scr_conscroll_x.value;
2030 sy = scr_conscroll_y.value;
2031 conbackflags = CACHEPICFLAG_FAILONMISSING; // So console is readable when game content is missing
2032 if (sx != 0 || sy != 0)
2033 conbackflags &= CACHEPICFLAG_NOCLAMP;
2034 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", conbackflags) : NULL;
2035 sx *= realtime; sy *= realtime;
2036 sx -= floor(sx); sy -= floor(sy);
2037 if (Draw_IsPicLoaded(conbackpic))
2038 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2039 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2040 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2041 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2042 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2045 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
2047 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
2049 sx = scr_conscroll2_x.value;
2050 sy = scr_conscroll2_y.value;
2051 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2052 sx *= realtime; sy *= realtime;
2053 sx -= floor(sx); sy -= floor(sy);
2054 if(Draw_IsPicLoaded(conbackpic))
2055 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2056 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2057 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2058 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2059 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2062 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
2064 sx = scr_conscroll3_x.value;
2065 sy = scr_conscroll3_y.value;
2066 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2067 sx *= realtime; sy *= realtime;
2068 sx -= floor(sx); sy -= floor(sy);
2069 if(Draw_IsPicLoaded(conbackpic))
2070 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2071 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2072 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2073 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2074 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2077 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);
2083 int count = CON_LINES_COUNT;
2084 float ymax = con_vislines - 2 * con_textsize.value;
2085 float y = ymax + con_textsize.value * con_backscroll;
2086 for (i = 0;i < count && y >= 0;i++)
2087 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
2088 // fix any excessive scrollback for the next frame
2089 if (i >= count && y >= 0)
2091 con_backscroll -= (int)(y / con_textsize.value);
2092 if (con_backscroll < 0)
2097 if(CON_LINES_COUNT > 0)
2099 int i, last, limitlast;
2101 float ymax = con_vislines - 2 * con_textsize.value;
2102 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2103 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2104 y = ymax - con_textsize.value;
2107 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
2112 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
2114 break; // top of console buffer
2116 break; // top of console window
2123 // draw the input prompt, user text, and cursor if desired
2126 r_draw2d_force = false;
2127 if (con_mutex) Thread_UnlockMutex(con_mutex);
2134 Prints not only map filename, but also
2135 its format (q1/q2/q3/hl) and even its message
2137 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2138 //LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2139 //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
2140 //LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2141 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
2145 int i, k, max, p, o, min;
2148 unsigned char buf[1024];
2150 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2151 t = FS_Search(message, 1, true);
2154 if (t->numfilenames > 1)
2155 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2156 len = (unsigned char *)Z_Malloc(t->numfilenames);
2158 for(max=i=0;i<t->numfilenames;i++)
2160 k = (int)strlen(t->filenames[i]);
2170 for(i=0;i<t->numfilenames;i++)
2172 int lumpofs = 0, lumplen = 0;
2173 char *entities = NULL;
2174 const char *data = NULL;
2176 char entfilename[MAX_QPATH];
2179 strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2181 f = FS_OpenVirtualFile(t->filenames[i], true);
2184 strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2185 memset(buf, 0, 1024);
2186 FS_Read(f, buf, 1024);
2187 if (!memcmp(buf, "IBSP", 4))
2189 p = LittleLong(((int *)buf)[1]);
2190 if (p == Q3BSPVERSION)
2192 q3dheader_t *header = (q3dheader_t *)buf;
2193 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2194 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2195 dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2197 else if (p == Q2BSPVERSION)
2199 q2dheader_t *header = (q2dheader_t *)buf;
2200 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2201 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2202 dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2205 dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2207 else if (BuffLittleLong(buf) == BSPVERSION)
2209 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2210 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2211 dpsnprintf(desc, sizeof(desc), "BSP29");
2213 else if (BuffLittleLong(buf) == 30)
2215 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2216 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2217 dpsnprintf(desc, sizeof(desc), "BSPHL");
2219 else if (!memcmp(buf, "BSP2", 4))
2221 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2222 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2223 dpsnprintf(desc, sizeof(desc), "BSP2");
2225 else if (!memcmp(buf, "2PSB", 4))
2227 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2228 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2229 dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2233 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2235 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2236 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2237 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2238 if (!entities && lumplen >= 10)
2240 FS_Seek(f, lumpofs, SEEK_SET);
2241 entities = (char *)Z_Malloc(lumplen + 1);
2242 FS_Read(f, entities, lumplen);
2246 // if there are entities to parse, a missing message key just
2247 // means there is no title, so clear the message string now
2253 if (!COM_ParseToken_Simple(&data, false, false, true))
2255 if (com_token[0] == '{')
2257 if (com_token[0] == '}')
2259 // skip leading whitespace
2260 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2261 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2262 keyname[l] = com_token[k+l];
2264 if (!COM_ParseToken_Simple(&data, false, false, true))
2266 if (developer_extra.integer)
2267 Con_DPrintf("key: %s %s\n", keyname, com_token);
2268 if (!strcmp(keyname, "message"))
2270 // get the message contents
2271 strlcpy(message, com_token, sizeof(message));
2281 *(t->filenames[i]+len[i]+5) = 0;
2282 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2287 k = *(t->filenames[0]+5+p);
2290 for(i=1;i<t->numfilenames;i++)
2291 if(*(t->filenames[i]+5+p) != k)
2295 if(p > o && completedname && completednamebufferlength > 0)
2297 memset(completedname, 0, completednamebufferlength);
2298 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2308 New function for tab-completion system
2309 Added by EvilTypeGuy
2310 MEGA Thanks to Taniwha
2313 void Con_DisplayList(const char **list)
2315 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2316 const char **walk = list;
2319 len = (int)strlen(*walk);
2327 len = (int)strlen(*list);
2328 if (pos + maxlen >= width) {
2334 for (i = 0; i < (maxlen - len); i++)
2346 // Now it becomes TRICKY :D --blub
2347 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2348 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2349 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2350 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
2351 static int Nicks_matchpos;
2353 // co against <<:BLASTER:>> is true!?
2354 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2358 if(tolower(*a) == tolower(*b))
2372 return (*a < *b) ? -1 : 1;
2376 return (*a < *b) ? -1 : 1;
2380 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2383 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2385 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2386 return Nicks_strncasecmp_nospaces(a, b, a_len);
2387 return strncasecmp(a, b, a_len);
2390 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2392 // ignore non alphanumerics of B
2393 // if A contains a non-alphanumeric, B must contain it as well though!
2396 qboolean alnum_a, alnum_b;
2398 if(tolower(*a) == tolower(*b))
2400 if(*a == 0) // end of both strings, they're equal
2407 // not equal, end of one string?
2412 // ignore non alphanumerics
2413 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2414 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2415 if(!alnum_a) // b must contain this
2416 return (*a < *b) ? -1 : 1;
2419 // otherwise, both are alnum, they're just not equal, return the appropriate number
2421 return (*a < *b) ? -1 : 1;
2427 /* Nicks_CompleteCountPossible
2429 Count the number of possible nicks to complete
2431 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2439 if(!con_nickcompletion.integer)
2442 // changed that to 1
2443 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2446 for(i = 0; i < cl.maxclients; ++i)
2449 if(!cl.scores[p].name[0])
2452 SanitizeString(cl.scores[p].name, name);
2453 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2459 spos = pos - 1; // no need for a minimum of characters :)
2463 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2465 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2466 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2472 if(isCon && spos == 0)
2474 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2480 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2481 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2483 // the sanitized list
2484 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2487 Nicks_matchpos = match;
2490 Nicks_offset[count] = s - (&line[match]);
2491 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2498 static void Cmd_CompleteNicksPrint(int count)
2501 for(i = 0; i < count; ++i)
2502 Con_Printf("%s\n", Nicks_list[i]);
2505 static void Nicks_CutMatchesNormal(int count)
2507 // cut match 0 down to the longest possible completion
2510 c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2511 for(i = 1; i < count; ++i)
2513 l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2517 for(l = 0; l <= c; ++l)
2518 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2524 Nicks_sanlist[0][c+1] = 0;
2525 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2528 static unsigned int Nicks_strcleanlen(const char *s)
2533 if( (*s >= 'a' && *s <= 'z') ||
2534 (*s >= 'A' && *s <= 'Z') ||
2535 (*s >= '0' && *s <= '9') ||
2543 static void Nicks_CutMatchesAlphaNumeric(int count)
2545 // cut match 0 down to the longest possible completion
2548 char tempstr[sizeof(Nicks_sanlist[0])];
2550 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2552 c = (unsigned int)strlen(Nicks_sanlist[0]);
2553 for(i = 0, l = 0; i < (int)c; ++i)
2555 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2556 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2557 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2559 tempstr[l++] = Nicks_sanlist[0][i];
2564 for(i = 1; i < count; ++i)
2567 b = Nicks_sanlist[i];
2577 if(tolower(*a) == tolower(*b))
2583 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2585 // b is alnum, so cut
2592 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2593 Nicks_CutMatchesNormal(count);
2594 //if(!Nicks_sanlist[0][0])
2595 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2597 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2598 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2602 static void Nicks_CutMatchesNoSpaces(int count)
2604 // cut match 0 down to the longest possible completion
2607 char tempstr[sizeof(Nicks_sanlist[0])];
2610 c = (unsigned int)strlen(Nicks_sanlist[0]);
2611 for(i = 0, l = 0; i < (int)c; ++i)
2613 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2615 tempstr[l++] = Nicks_sanlist[0][i];
2620 for(i = 1; i < count; ++i)
2623 b = Nicks_sanlist[i];
2633 if(tolower(*a) == tolower(*b))
2647 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2648 Nicks_CutMatchesNormal(count);
2649 //if(!Nicks_sanlist[0][0])
2650 //Con_Printf("TS: %s\n", tempstr);
2651 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2653 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2654 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2658 static void Nicks_CutMatches(int count)
2660 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2661 Nicks_CutMatchesAlphaNumeric(count);
2662 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2663 Nicks_CutMatchesNoSpaces(count);
2665 Nicks_CutMatchesNormal(count);
2668 static const char **Nicks_CompleteBuildList(int count)
2672 // the list is freed by Con_CompleteCommandLine, so create a char**
2673 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2675 for(; bpos < count; ++bpos)
2676 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2678 Nicks_CutMatches(count);
2686 Restores the previous used color, after the autocompleted name.
2688 static int Nicks_AddLastColor(char *buffer, int pos)
2690 qboolean quote_added = false;
2692 int color = STRING_COLOR_DEFAULT + '0';
2693 char r = 0, g = 0, b = 0;
2695 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2697 // we'll have to add a quote :)
2698 buffer[pos++] = '\"';
2702 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2704 // add color when no quote was added, or when flags &4?
2706 for(match = Nicks_matchpos-1; match >= 0; --match)
2708 if(buffer[match] == STRING_COLOR_TAG)
2710 if( isdigit(buffer[match+1]) )
2712 color = buffer[match+1];
2715 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2717 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2719 r = buffer[match+2];
2720 g = buffer[match+3];
2721 b = buffer[match+4];
2730 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2732 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2733 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2736 buffer[pos++] = STRING_COLOR_TAG;
2739 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2745 buffer[pos++] = color;
2750 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2753 /*if(!con_nickcompletion.integer)
2754 return; is tested in Nicks_CompletionCountPossible */
2755 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2761 msg = Nicks_list[0];
2762 len = min(size - Nicks_matchpos - 3, strlen(msg));
2763 memcpy(&buffer[Nicks_matchpos], msg, len);
2764 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2765 len = (int)Nicks_AddLastColor(buffer, Nicks_matchpos+(int)len);
2766 buffer[len++] = ' ';
2773 Con_Printf("\n%i possible nicks:\n", n);
2774 Cmd_CompleteNicksPrint(n);
2776 Nicks_CutMatches(n);
2778 msg = Nicks_sanlist[0];
2779 len = (int)min(size - Nicks_matchpos, strlen(msg));
2780 memcpy(&buffer[Nicks_matchpos], msg, len);
2781 buffer[Nicks_matchpos + len] = 0;
2783 return Nicks_matchpos + len;
2790 Con_CompleteCommandLine
2792 New function for tab-completion system
2793 Added by EvilTypeGuy
2794 Thanks to Fett erich@heintz.com
2796 Enhanced to tab-complete map names by [515]
2799 void Con_CompleteCommandLine (cmd_state_t *cmd)
2801 const char *text = "";
2803 const char **list[4] = {0, 0, 0, 0};
2806 int c, v, a, i, cmd_len, pos, k;
2807 int n; // nicks --blub
2808 const char *space, *patterns;
2811 //find what we want to complete
2816 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2822 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2823 key_line[key_linepos] = 0; //hide them
2825 space = strchr(key_line + 1, ' ');
2826 if(space && pos == (space - key_line) + 1)
2828 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2830 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?
2831 if(patterns && !*patterns)
2832 patterns = NULL; // get rid of the empty string
2834 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2838 if (GetMapList(s, t, sizeof(t)))
2840 // first move the cursor
2841 key_linepos += (int)strlen(t) - (int)strlen(s);
2843 // and now do the actual work
2845 strlcat(key_line, t, MAX_INPUTLINE);
2846 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2848 // and fix the cursor
2849 if(key_linepos > (int) strlen(key_line))
2850 key_linepos = (int) strlen(key_line);
2859 stringlist_t resultbuf, dirbuf;
2862 // // store completion patterns (space separated) for command foo in con_completion_foo
2863 // set con_completion_foo "foodata/*.foodefault *.foo"
2866 // Note: patterns with slash are always treated as absolute
2867 // patterns; patterns without slash search in the innermost
2868 // directory the user specified. There is no way to "complete into"
2869 // a directory as of now, as directories seem to be unknown to the
2873 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2874 // set con_completion_playdemo "*.dem"
2875 // set con_completion_play "*.wav *.ogg"
2877 // TODO somehow add support for directories; these shall complete
2878 // to their name + an appended slash.
2880 stringlistinit(&resultbuf);
2881 stringlistinit(&dirbuf);
2882 while(COM_ParseToken_Simple(&patterns, false, false, true))
2885 if(strchr(com_token, '/'))
2887 search = FS_Search(com_token, true, true);
2891 const char *slash = strrchr(s, '/');
2894 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2895 strlcat(t, com_token, sizeof(t));
2896 search = FS_Search(t, true, true);
2899 search = FS_Search(com_token, true, true);
2903 for(i = 0; i < search->numfilenames; ++i)
2904 if(!strncmp(search->filenames[i], s, strlen(s)))
2905 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2906 stringlistappend(&resultbuf, search->filenames[i]);
2907 FS_FreeSearch(search);
2911 // In any case, add directory names
2914 const char *slash = strrchr(s, '/');
2917 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2918 strlcat(t, "*", sizeof(t));
2919 search = FS_Search(t, true, true);
2922 search = FS_Search("*", true, true);
2925 for(i = 0; i < search->numfilenames; ++i)
2926 if(!strncmp(search->filenames[i], s, strlen(s)))
2927 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2928 stringlistappend(&dirbuf, search->filenames[i]);
2929 FS_FreeSearch(search);
2933 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2936 unsigned int matchchars;
2937 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2939 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2942 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2944 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2948 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2949 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2950 for(i = 0; i < dirbuf.numstrings; ++i)
2952 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2954 for(i = 0; i < resultbuf.numstrings; ++i)
2956 Con_Printf("%s\n", resultbuf.strings[i]);
2958 matchchars = sizeof(t) - 1;
2959 if(resultbuf.numstrings > 0)
2961 p = resultbuf.strings[0];
2962 q = resultbuf.strings[resultbuf.numstrings - 1];
2963 for(; *p && *p == *q; ++p, ++q);
2964 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2966 if(dirbuf.numstrings > 0)
2968 p = dirbuf.strings[0];
2969 q = dirbuf.strings[dirbuf.numstrings - 1];
2970 for(; *p && *p == *q; ++p, ++q);
2971 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2973 // now p points to the first non-equal character, or to the end
2974 // of resultbuf.strings[0]. We want to append the characters
2975 // from resultbuf.strings[0] to (not including) p as these are
2976 // the unique prefix
2977 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2980 // first move the cursor
2981 key_linepos += (int)strlen(t) - (int)strlen(s);
2983 // and now do the actual work
2985 strlcat(key_line, t, MAX_INPUTLINE);
2986 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2988 // and fix the cursor
2989 if(key_linepos > (int) strlen(key_line))
2990 key_linepos = (int) strlen(key_line);
2992 stringlistfreecontents(&resultbuf);
2993 stringlistfreecontents(&dirbuf);
2995 return; // bail out, when we complete for a command that wants a file name
3000 // Count number of possible matches and print them
3001 c = Cmd_CompleteCountPossible(cmd, s);
3004 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
3005 Cmd_CompleteCommandPrint(cmd, s);
3007 v = Cvar_CompleteCountPossible(cmd->cvars, s, CVAR_CLIENT | CVAR_SERVER);
3010 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
3011 Cvar_CompleteCvarPrint(cmd->cvars, s, CVAR_CLIENT | CVAR_SERVER);
3013 a = Cmd_CompleteAliasCountPossible(cmd, s);
3016 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
3017 Cmd_CompleteAliasPrint(cmd, s);
3019 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
3022 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
3023 Cmd_CompleteNicksPrint(n);
3026 if (!(c + v + a + n)) // No possible matches
3029 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
3034 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
3036 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
3038 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
3040 text = *(list[3] = Nicks_CompleteBuildList(n));
3042 for (cmd_len = (int)strlen(s);;cmd_len++)
3045 for (i = 0; i < 3; i++)
3047 for (l = list[i];*l;l++)
3048 if ((*l)[cmd_len] != text[cmd_len])
3050 // all possible matches share this character, so we continue...
3053 // if all matches ended at the same position, stop
3054 // (this means there is only one match)
3060 // prevent a buffer overrun by limiting cmd_len according to remaining space
3061 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
3065 memcpy(&key_line[key_linepos], text, cmd_len);
3066 key_linepos += cmd_len;
3067 // if there is only one match, add a space after it
3068 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
3071 { // was a nick, might have an offset, and needs colors ;) --blub
3072 key_linepos = pos - Nicks_offset[0];
3073 cmd_len = (int)strlen(Nicks_list[0]);
3074 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
3076 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
3077 key_linepos += cmd_len;
3078 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
3079 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
3081 key_line[key_linepos++] = ' ';
3085 // use strlcat to avoid a buffer overrun
3086 key_line[key_linepos] = 0;
3087 strlcat(key_line, s2, sizeof(key_line));
3089 // free the command, cvar, and alias lists
3090 for (i = 0; i < 4; i++)
3092 Mem_Free((void *)list[i]);