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_client, "clear", Con_Clear_f, "clear console history");
904 Cmd_AddCommand(&cmd_client, "maps", Con_Maps_f, "list information about available maps");
905 Cmd_AddCommand(&cmd_client, "condump", Con_ConDump_f, "output console history to a file (see also log_file)");
907 Cmd_AddCommand(&cmd_server, "maps", Con_Maps_f, "list information about available maps");
908 Cmd_AddCommand(&cmd_server, "condump", Con_ConDump_f, "output console history to a file (see also log_file)");
910 con_initialized = true;
911 Con_DPrint("Console initialized.\n");
914 void Con_Shutdown (void)
916 if (con_mutex) Thread_LockMutex(con_mutex);
917 ConBuffer_Shutdown(&con);
918 if (con_mutex) Thread_UnlockMutex(con_mutex);
919 if (con_mutex) Thread_DestroyMutex(con_mutex);con_mutex = NULL;
926 Handles cursor positioning, line wrapping, etc
927 All console printing must go through this in order to be displayed
928 If no console is visible, the notify window will pop up.
931 static void Con_PrintToHistory(const char *txt, int mask)
934 // \n goes to next line
935 // \r deletes current line and makes a new one
937 static int cr_pending = 0;
938 static char buf[CON_TEXTSIZE]; // con_mutex
939 static int bufpos = 0;
941 if(!con.text) // FIXME uses a non-abstracted property of con
948 ConBuffer_DeleteLastLine(&con);
956 ConBuffer_AddLine(&con, buf, bufpos, mask);
961 ConBuffer_AddLine(&con, buf, bufpos, mask);
965 buf[bufpos++] = *txt;
966 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
968 ConBuffer_AddLine(&con, buf, bufpos, mask);
976 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol)
978 rcon_redirect_sock = sock;
979 rcon_redirect_dest = dest;
980 rcon_redirect_proquakeprotocol = proquakeprotocol;
981 if (rcon_redirect_proquakeprotocol)
983 // reserve space for the packet header
984 rcon_redirect_buffer[0] = 0;
985 rcon_redirect_buffer[1] = 0;
986 rcon_redirect_buffer[2] = 0;
987 rcon_redirect_buffer[3] = 0;
988 // this is a reply to a CCREQ_RCON
989 rcon_redirect_buffer[4] = (unsigned char)CCREP_RCON;
992 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
993 rcon_redirect_bufferpos = 5;
996 static void Con_Rcon_Redirect_Flush(void)
998 if(rcon_redirect_sock)
1000 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
1001 if (rcon_redirect_proquakeprotocol)
1003 // update the length in the packet header
1004 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
1006 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
1008 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1009 rcon_redirect_bufferpos = 5;
1010 rcon_redirect_proquakeprotocol = false;
1013 void Con_Rcon_Redirect_End(void)
1015 Con_Rcon_Redirect_Flush();
1016 rcon_redirect_dest = NULL;
1017 rcon_redirect_sock = NULL;
1020 void Con_Rcon_Redirect_Abort(void)
1022 rcon_redirect_dest = NULL;
1023 rcon_redirect_sock = NULL;
1031 /// Adds a character to the rcon buffer.
1032 static void Con_Rcon_AddChar(int c)
1034 if(log_dest_buffer_appending)
1036 ++log_dest_buffer_appending;
1038 // if this print is in response to an rcon command, add the character
1039 // to the rcon redirect buffer
1041 if (rcon_redirect_dest)
1043 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
1044 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
1045 Con_Rcon_Redirect_Flush();
1047 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
1049 if(log_dest_buffer_pos == 0)
1050 Log_DestBuffer_Init();
1051 log_dest_buffer[log_dest_buffer_pos++] = c;
1052 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
1053 Log_DestBuffer_Flush_NoLock();
1056 log_dest_buffer_pos = 0;
1058 --log_dest_buffer_appending;
1062 * Convert an RGB color to its nearest quake color.
1063 * I'll cheat on this a bit by translating the colors to HSV first,
1064 * S and V decide if it's black or white, otherwise, H will decide the
1066 * @param _r Red (0-255)
1067 * @param _g Green (0-255)
1068 * @param _b Blue (0-255)
1069 * @return A quake color character.
1071 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
1073 float r = ((float)_r)/255.0;
1074 float g = ((float)_g)/255.0;
1075 float b = ((float)_b)/255.0;
1076 float min = min(r, min(g, b));
1077 float max = max(r, max(g, b));
1079 int h; ///< Hue angle [0,360]
1080 float s; ///< Saturation [0,1]
1081 float v = max; ///< In HSV v == max [0,1]
1086 s = 1.0 - (min/max);
1088 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1091 // If the value is less than half, return a black color code.
1092 // Otherwise return a white one.
1098 // Let's get the hue angle to define some colors:
1102 h = (int)(60.0 * (g-b)/(max-min))%360;
1104 h = (int)(60.0 * (b-r)/(max-min) + 120);
1105 else // if(max == b) redundant check
1106 h = (int)(60.0 * (r-g)/(max-min) + 240);
1108 if(h < 36) // *red* to orange
1110 else if(h < 80) // orange over *yellow* to evilish-bright-green
1112 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1114 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1116 else if(h < 270) // darkish blue over *dark blue* to cool purple
1118 else if(h < 330) // cool purple over *purple* to ugly swiny red
1120 else // ugly red to red closes the circly
1129 extern cvar_t timestamps;
1130 extern cvar_t timeformat;
1131 extern qboolean sys_nostdout;
1132 void Con_MaskPrint(int additionalmask, const char *msg)
1134 static int mask = 0;
1135 static int index = 0;
1136 static char line[MAX_INPUTLINE];
1139 Thread_LockMutex(con_mutex);
1143 Con_Rcon_AddChar(*msg);
1144 // if this is the beginning of a new line, print timestamp
1147 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1149 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1150 line[index++] = STRING_COLOR_TAG;
1151 // assert( STRING_COLOR_DEFAULT < 10 )
1152 line[index++] = STRING_COLOR_DEFAULT + '0';
1153 // special color codes for chat messages must always come first
1154 // for Con_PrintToHistory to work properly
1155 if (*msg == 1 || *msg == 2 || *msg == 3)
1160 if (con_chatsound.value)
1162 if(IS_NEXUIZ_DERIVED(gamemode))
1164 if(msg[1] == '\r' && cl.foundtalk2wav)
1165 S_LocalSound ("sound/misc/talk2.wav");
1167 S_LocalSound ("sound/misc/talk.wav");
1171 if (msg[1] == '(' && cl.foundtalk2wav)
1172 S_LocalSound ("sound/misc/talk2.wav");
1174 S_LocalSound ("sound/misc/talk.wav");
1179 // Send to chatbox for say/tell (1) and messages (3)
1180 // 3 is just so that a message can be sent to the chatbox without a sound.
1181 if (*msg == 1 || *msg == 3)
1182 mask = CON_MASK_CHAT;
1184 line[index++] = STRING_COLOR_TAG;
1185 line[index++] = '3';
1187 Con_Rcon_AddChar(*msg);
1190 for (;*timestamp;index++, timestamp++)
1191 if (index < (int)sizeof(line) - 2)
1192 line[index] = *timestamp;
1194 mask |= additionalmask;
1196 // append the character
1197 line[index++] = *msg;
1198 // if this is a newline character, we have a complete line to print
1199 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1201 // terminate the line
1205 // send to scrollable buffer
1206 if (con_initialized && cls.state != ca_dedicated)
1208 Con_PrintToHistory(line, mask);
1210 // send to terminal or dedicated server window
1212 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1214 if(sys_specialcharactertranslation.integer)
1221 int ch = u8_getchar(p, &q);
1222 if(ch >= 0xE000 && ch <= 0xE0FF && ((unsigned char) qfont_table[ch - 0xE000]) >= 0x20)
1224 *p = qfont_table[ch - 0xE000];
1226 memmove(p+1, q, strlen(q)+1);
1234 if(sys_colortranslation.integer == 1) // ANSI
1236 static char printline[MAX_INPUTLINE * 4 + 3];
1237 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1238 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1243 for(in = line, out = printline; *in; ++in)
1247 case STRING_COLOR_TAG:
1248 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1250 char r = tolower(in[2]);
1251 char g = tolower(in[3]);
1252 char b = tolower(in[4]);
1253 // it's a hex digit already, so the else part needs no check --blub
1254 if(isdigit(r)) r -= '0';
1256 if(isdigit(g)) g -= '0';
1258 if(isdigit(b)) b -= '0';
1261 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1262 in += 3; // 3 only, the switch down there does the fourth
1269 case STRING_COLOR_TAG:
1271 *out++ = STRING_COLOR_TAG;
1277 if(lastcolor == 0) break; else lastcolor = 0;
1278 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1283 if(lastcolor == 1) break; else lastcolor = 1;
1284 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1289 if(lastcolor == 2) break; else lastcolor = 2;
1290 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1295 if(lastcolor == 3) break; else lastcolor = 3;
1296 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1301 if(lastcolor == 4) break; else lastcolor = 4;
1302 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1307 if(lastcolor == 5) break; else lastcolor = 5;
1308 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1313 if(lastcolor == 6) break; else lastcolor = 6;
1314 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1319 // bold normal color
1321 if(lastcolor == 8) break; else lastcolor = 8;
1322 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1325 *out++ = STRING_COLOR_TAG;
1332 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1349 Sys_PrintToTerminal(printline);
1351 else if(sys_colortranslation.integer == 2) // Quake
1353 Sys_PrintToTerminal(line);
1357 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1360 for(in = line, out = printline; *in; ++in)
1364 case STRING_COLOR_TAG:
1367 case STRING_COLOR_RGB_TAG_CHAR:
1368 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1373 *out++ = STRING_COLOR_TAG;
1374 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1377 case STRING_COLOR_TAG:
1379 *out++ = STRING_COLOR_TAG;
1394 *out++ = STRING_COLOR_TAG;
1404 Sys_PrintToTerminal(printline);
1407 // empty the line buffer
1414 Thread_UnlockMutex(con_mutex);
1422 void Con_MaskPrintf(int mask, const char *fmt, ...)
1425 char msg[MAX_INPUTLINE];
1427 va_start(argptr,fmt);
1428 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1431 Con_MaskPrint(mask, msg);
1439 void Con_Print(const char *msg)
1441 Con_MaskPrint(CON_MASK_PRINT, msg);
1449 void Con_Printf(const char *fmt, ...)
1452 char msg[MAX_INPUTLINE];
1454 va_start(argptr,fmt);
1455 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1458 Con_MaskPrint(CON_MASK_PRINT, msg);
1466 void Con_Warn(const char *msg)
1468 Con_Printf("^3%s",msg);
1476 void Con_Warnf(const char *fmt, ...)
1479 char msg[MAX_INPUTLINE];
1481 va_start(argptr,fmt);
1482 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1485 Con_Printf("^3%s",msg);
1493 void Con_Error(const char *msg)
1495 Con_Printf("^1%s",msg);
1503 void Con_Errorf(const char *fmt, ...)
1506 char msg[MAX_INPUTLINE];
1508 va_start(argptr,fmt);
1509 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1512 Con_Printf("^1%s",msg);
1521 void Con_DPrint(const char *msg)
1523 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1526 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1534 void Con_DPrintf(const char *fmt, ...)
1537 char msg[MAX_INPUTLINE];
1539 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1542 va_start(argptr,fmt);
1543 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1546 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1551 ==============================================================================
1555 ==============================================================================
1562 The input line scrolls horizontally if typing goes beyond the right edge
1564 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1567 static void Con_DrawInput (void)
1571 char text[sizeof(key_line)+5+1]; // space for ^^xRGB too
1576 if (!key_consoleactive)
1577 return; // don't draw anything
1579 strlcpy(text, key_line, sizeof(text));
1581 // Advanced Console Editing by Radix radix@planetquake.com
1582 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1584 y = (int)strlen(text);
1586 // make the color code visible when the cursor is inside it
1587 if(text[key_linepos] != 0)
1589 for(i=1; i < 5 && key_linepos - i > 0; ++i)
1590 if(text[key_linepos-i] == STRING_COLOR_TAG)
1592 int caret_pos, ofs = 0;
1593 caret_pos = key_linepos - i;
1594 if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG)
1596 else if(i == 1 && isdigit(text[caret_pos+1]))
1598 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]))
1600 if(ofs && (size_t)(y + ofs + 1) < sizeof(text))
1603 while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG)
1607 // str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing)
1608 // str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing)
1609 memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos);
1610 text[caret_pos + ofs] = STRING_COLOR_TAG;
1619 len_out = key_linepos;
1621 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1622 x = vid_conwidth.value * 0.95 - xo; // scroll
1627 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 );
1629 // draw a cursor on top of this
1630 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1632 if (!utf8_enable.integer)
1634 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1642 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1643 memcpy(text, curbuf, len);
1646 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);
1653 float alignment; // 0 = left, 0.5 = center, 1 = right
1659 const char *continuationString;
1662 int colorindex; // init to -1
1666 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1668 con_text_info_t *ti = (con_text_info_t *) passthrough;
1671 ti->colorindex = -1;
1672 return ti->fontsize * ti->font->maxwidth;
1675 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1676 else if(maxWidth == -1)
1677 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1680 Sys_PrintfToTerminal("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1681 // Note: this is NOT a Con_Printf, as it could print recursively
1686 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1692 (void) isContinuation;
1696 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1698 con_text_info_t *ti = (con_text_info_t *) passthrough;
1700 if(ti->y < ti->ymin - 0.001)
1702 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1706 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1707 if(isContinuation && *ti->continuationString)
1708 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);
1710 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);
1713 ti->y += ti->fontsize;
1717 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)
1721 int maxlines = (int) floor(height / fontsize + 0.01f);
1724 int continuationWidth = 0;
1726 double t = cl.time; // saved so it won't change
1729 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1730 ti.fontsize = fontsize;
1731 ti.alignment = alignment_x;
1734 ti.ymax = y + height;
1735 ti.continuationString = continuationString;
1738 Con_WordWidthFunc(&ti, NULL, &len, -1);
1739 len = strlen(continuationString);
1740 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &len, -1);
1742 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1743 startidx = CON_LINES_COUNT;
1744 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1746 con_lineinfo_t *l = &CON_LINES(i);
1749 if((l->mask & mask_must) != mask_must)
1751 if(l->mask & mask_mustnot)
1753 if(maxage && (l->addtime < t - maxage))
1757 // Calculate its actual height...
1758 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1759 if(lines + mylines >= maxlines)
1761 nskip = lines + mylines - maxlines;
1770 // then center according to the calculated amount of lines...
1772 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1774 // then actually draw
1775 for(i = startidx; i < CON_LINES_COUNT; ++i)
1777 con_lineinfo_t *l = &CON_LINES(i);
1779 if((l->mask & mask_must) != mask_must)
1781 if(l->mask & mask_mustnot)
1783 if(maxage && (l->addtime < t - maxage))
1786 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1796 Draws the last few lines of output transparently over the game top
1799 void Con_DrawNotify (void)
1802 float chatstart, notifystart, inputsize, height;
1804 char temptext[MAX_INPUTLINE];
1808 if (con_mutex) Thread_LockMutex(con_mutex);
1809 ConBuffer_FixTimes(&con);
1811 numChatlines = con_chat.integer;
1813 chatpos = con_chatpos.integer;
1815 if (con_notify.integer < 0)
1816 Cvar_SetValueQuick(&con_notify, 0);
1817 if (gamemode == GAME_TRANSFUSION)
1818 v = 8; // vertical offset
1822 // GAME_NEXUIZ: center, otherwise left justify
1823 align = con_notifyalign.value;
1824 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1826 if(IS_OLDNEXUIZ_DERIVED(gamemode))
1830 if(numChatlines || !con_chatrect.integer)
1834 // first chat, input line, then notify
1836 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1838 else if(chatpos > 0)
1840 // first notify, then (chatpos-1) empty lines, then chat, then input
1842 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1844 else // if(chatpos < 0)
1846 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1848 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1853 // just notify and input
1855 chatstart = 0; // shut off gcc warning
1858 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, "");
1860 if(con_chatrect.integer)
1862 x = con_chatrect_x.value * vid_conwidth.value;
1863 v = con_chatrect_y.value * vid_conheight.value;
1868 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1871 height = numChatlines * con_chatsize.value;
1875 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 ... ");
1878 if (key_dest == key_message)
1880 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1881 int colorindex = -1;
1884 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL, charbuf16);
1886 // LadyHavoc: speedup, and other improvements
1888 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1890 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1892 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1895 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1896 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1898 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1900 if (con_mutex) Thread_UnlockMutex(con_mutex);
1907 Returns the height of a given console line; calculates it if necessary.
1910 static int Con_LineHeight(int lineno)
1912 con_lineinfo_t *li = &CON_LINES(lineno);
1913 if(li->height == -1)
1915 float width = vid_conwidth.value;
1917 ti.fontsize = con_textsize.value;
1918 ti.font = FONT_CONSOLE;
1919 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1928 Draws a line of the console; returns its height in lines.
1929 If alpha is 0, the line is not drawn, but still wrapped and its height
1933 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1935 float width = vid_conwidth.value;
1937 con_lineinfo_t *li = &CON_LINES(lineno);
1939 if((li->mask & mask_must) != mask_must)
1941 if((li->mask & mask_mustnot) != 0)
1944 ti.continuationString = "";
1946 ti.fontsize = con_textsize.value;
1947 ti.font = FONT_CONSOLE;
1949 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1954 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1961 Calculates the last visible line index and how much to show of it based on
1965 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1970 if(con_backscroll < 0)
1975 // now count until we saw con_backscroll actual lines
1976 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1977 if((CON_LINES(i).mask & mask_must) == mask_must)
1978 if((CON_LINES(i).mask & mask_mustnot) == 0)
1980 int h = Con_LineHeight(i);
1982 // line is the last visible line?
1984 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1986 *limitlast = lines_seen + h - con_backscroll;
1993 // if we get here, no line was on screen - scroll so that one line is
1995 con_backscroll = lines_seen - 1;
2003 Draws the console with the solid background
2004 The typing input line at the bottom should only be drawn if typing is allowed
2007 void Con_DrawConsole (int lines)
2009 float alpha, alpha0;
2012 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
2013 cachepic_t *conbackpic;
2014 unsigned int conbackflags;
2019 if (con_mutex) Thread_LockMutex(con_mutex);
2021 if (con_backscroll < 0)
2024 con_vislines = lines;
2026 r_draw2d_force = true;
2028 // draw the background
2029 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
2030 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
2032 sx = scr_conscroll_x.value;
2033 sy = scr_conscroll_y.value;
2034 conbackflags = CACHEPICFLAG_FAILONMISSING; // So console is readable when game content is missing
2035 if (sx != 0 || sy != 0)
2036 conbackflags &= CACHEPICFLAG_NOCLAMP;
2037 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", conbackflags) : NULL;
2038 sx *= realtime; sy *= realtime;
2039 sx -= floor(sx); sy -= floor(sy);
2040 if (Draw_IsPicLoaded(conbackpic))
2041 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2042 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2043 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2044 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2045 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2048 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
2050 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
2052 sx = scr_conscroll2_x.value;
2053 sy = scr_conscroll2_y.value;
2054 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2055 sx *= realtime; sy *= realtime;
2056 sx -= floor(sx); sy -= floor(sy);
2057 if(Draw_IsPicLoaded(conbackpic))
2058 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2059 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2060 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2061 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2062 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2065 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
2067 sx = scr_conscroll3_x.value;
2068 sy = scr_conscroll3_y.value;
2069 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2070 sx *= realtime; sy *= realtime;
2071 sx -= floor(sx); sy -= floor(sy);
2072 if(Draw_IsPicLoaded(conbackpic))
2073 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2074 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2075 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2076 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2077 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2080 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);
2086 int count = CON_LINES_COUNT;
2087 float ymax = con_vislines - 2 * con_textsize.value;
2088 float y = ymax + con_textsize.value * con_backscroll;
2089 for (i = 0;i < count && y >= 0;i++)
2090 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
2091 // fix any excessive scrollback for the next frame
2092 if (i >= count && y >= 0)
2094 con_backscroll -= (int)(y / con_textsize.value);
2095 if (con_backscroll < 0)
2100 if(CON_LINES_COUNT > 0)
2102 int i, last, limitlast;
2104 float ymax = con_vislines - 2 * con_textsize.value;
2105 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2106 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2107 y = ymax - con_textsize.value;
2110 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
2115 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
2117 break; // top of console buffer
2119 break; // top of console window
2126 // draw the input prompt, user text, and cursor if desired
2129 r_draw2d_force = false;
2130 if (con_mutex) Thread_UnlockMutex(con_mutex);
2137 Prints not only map filename, but also
2138 its format (q1/q2/q3/hl) and even its message
2140 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2141 //LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2142 //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
2143 //LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2144 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
2148 int i, k, max, p, o, min;
2151 unsigned char buf[1024];
2153 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2154 t = FS_Search(message, 1, true);
2157 if (t->numfilenames > 1)
2158 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2159 len = (unsigned char *)Z_Malloc(t->numfilenames);
2161 for(max=i=0;i<t->numfilenames;i++)
2163 k = (int)strlen(t->filenames[i]);
2173 for(i=0;i<t->numfilenames;i++)
2175 int lumpofs = 0, lumplen = 0;
2176 char *entities = NULL;
2177 const char *data = NULL;
2179 char entfilename[MAX_QPATH];
2182 strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2184 f = FS_OpenVirtualFile(t->filenames[i], true);
2187 strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2188 memset(buf, 0, 1024);
2189 FS_Read(f, buf, 1024);
2190 if (!memcmp(buf, "IBSP", 4))
2192 p = LittleLong(((int *)buf)[1]);
2193 if (p == Q3BSPVERSION)
2195 q3dheader_t *header = (q3dheader_t *)buf;
2196 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2197 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2198 dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2200 else if (p == Q2BSPVERSION)
2202 q2dheader_t *header = (q2dheader_t *)buf;
2203 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2204 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2205 dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2208 dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2210 else if (BuffLittleLong(buf) == BSPVERSION)
2212 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2213 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2214 dpsnprintf(desc, sizeof(desc), "BSP29");
2216 else if (BuffLittleLong(buf) == 30)
2218 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2219 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2220 dpsnprintf(desc, sizeof(desc), "BSPHL");
2222 else if (!memcmp(buf, "BSP2", 4))
2224 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2225 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2226 dpsnprintf(desc, sizeof(desc), "BSP2");
2228 else if (!memcmp(buf, "2PSB", 4))
2230 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2231 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2232 dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2236 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2238 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2239 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2240 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2241 if (!entities && lumplen >= 10)
2243 FS_Seek(f, lumpofs, SEEK_SET);
2244 entities = (char *)Z_Malloc(lumplen + 1);
2245 FS_Read(f, entities, lumplen);
2249 // if there are entities to parse, a missing message key just
2250 // means there is no title, so clear the message string now
2256 if (!COM_ParseToken_Simple(&data, false, false, true))
2258 if (com_token[0] == '{')
2260 if (com_token[0] == '}')
2262 // skip leading whitespace
2263 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2264 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2265 keyname[l] = com_token[k+l];
2267 if (!COM_ParseToken_Simple(&data, false, false, true))
2269 if (developer_extra.integer)
2270 Con_DPrintf("key: %s %s\n", keyname, com_token);
2271 if (!strcmp(keyname, "message"))
2273 // get the message contents
2274 strlcpy(message, com_token, sizeof(message));
2284 *(t->filenames[i]+len[i]+5) = 0;
2285 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2290 k = *(t->filenames[0]+5+p);
2293 for(i=1;i<t->numfilenames;i++)
2294 if(*(t->filenames[i]+5+p) != k)
2298 if(p > o && completedname && completednamebufferlength > 0)
2300 memset(completedname, 0, completednamebufferlength);
2301 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2311 New function for tab-completion system
2312 Added by EvilTypeGuy
2313 MEGA Thanks to Taniwha
2316 void Con_DisplayList(const char **list)
2318 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2319 const char **walk = list;
2322 len = (int)strlen(*walk);
2330 len = (int)strlen(*list);
2331 if (pos + maxlen >= width) {
2337 for (i = 0; i < (maxlen - len); i++)
2349 // Now it becomes TRICKY :D --blub
2350 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2351 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2352 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2353 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
2354 static int Nicks_matchpos;
2356 // co against <<:BLASTER:>> is true!?
2357 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2361 if(tolower(*a) == tolower(*b))
2375 return (*a < *b) ? -1 : 1;
2379 return (*a < *b) ? -1 : 1;
2383 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2386 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2388 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2389 return Nicks_strncasecmp_nospaces(a, b, a_len);
2390 return strncasecmp(a, b, a_len);
2393 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2395 // ignore non alphanumerics of B
2396 // if A contains a non-alphanumeric, B must contain it as well though!
2399 qboolean alnum_a, alnum_b;
2401 if(tolower(*a) == tolower(*b))
2403 if(*a == 0) // end of both strings, they're equal
2410 // not equal, end of one string?
2415 // ignore non alphanumerics
2416 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2417 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2418 if(!alnum_a) // b must contain this
2419 return (*a < *b) ? -1 : 1;
2422 // otherwise, both are alnum, they're just not equal, return the appropriate number
2424 return (*a < *b) ? -1 : 1;
2430 /* Nicks_CompleteCountPossible
2432 Count the number of possible nicks to complete
2434 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2442 if(!con_nickcompletion.integer)
2445 // changed that to 1
2446 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2449 for(i = 0; i < cl.maxclients; ++i)
2452 if(!cl.scores[p].name[0])
2455 SanitizeString(cl.scores[p].name, name);
2456 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2462 spos = pos - 1; // no need for a minimum of characters :)
2466 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2468 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2469 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2475 if(isCon && spos == 0)
2477 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2483 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2484 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2486 // the sanitized list
2487 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2490 Nicks_matchpos = match;
2493 Nicks_offset[count] = s - (&line[match]);
2494 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2501 static void Cmd_CompleteNicksPrint(int count)
2504 for(i = 0; i < count; ++i)
2505 Con_Printf("%s\n", Nicks_list[i]);
2508 static void Nicks_CutMatchesNormal(int count)
2510 // cut match 0 down to the longest possible completion
2513 c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2514 for(i = 1; i < count; ++i)
2516 l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2520 for(l = 0; l <= c; ++l)
2521 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2527 Nicks_sanlist[0][c+1] = 0;
2528 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2531 static unsigned int Nicks_strcleanlen(const char *s)
2536 if( (*s >= 'a' && *s <= 'z') ||
2537 (*s >= 'A' && *s <= 'Z') ||
2538 (*s >= '0' && *s <= '9') ||
2546 static void Nicks_CutMatchesAlphaNumeric(int count)
2548 // cut match 0 down to the longest possible completion
2551 char tempstr[sizeof(Nicks_sanlist[0])];
2553 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2555 c = (unsigned int)strlen(Nicks_sanlist[0]);
2556 for(i = 0, l = 0; i < (int)c; ++i)
2558 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2559 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2560 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2562 tempstr[l++] = Nicks_sanlist[0][i];
2567 for(i = 1; i < count; ++i)
2570 b = Nicks_sanlist[i];
2580 if(tolower(*a) == tolower(*b))
2586 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2588 // b is alnum, so cut
2595 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2596 Nicks_CutMatchesNormal(count);
2597 //if(!Nicks_sanlist[0][0])
2598 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2600 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2601 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2605 static void Nicks_CutMatchesNoSpaces(int count)
2607 // cut match 0 down to the longest possible completion
2610 char tempstr[sizeof(Nicks_sanlist[0])];
2613 c = (unsigned int)strlen(Nicks_sanlist[0]);
2614 for(i = 0, l = 0; i < (int)c; ++i)
2616 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2618 tempstr[l++] = Nicks_sanlist[0][i];
2623 for(i = 1; i < count; ++i)
2626 b = Nicks_sanlist[i];
2636 if(tolower(*a) == tolower(*b))
2650 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2651 Nicks_CutMatchesNormal(count);
2652 //if(!Nicks_sanlist[0][0])
2653 //Con_Printf("TS: %s\n", tempstr);
2654 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2656 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2657 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2661 static void Nicks_CutMatches(int count)
2663 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2664 Nicks_CutMatchesAlphaNumeric(count);
2665 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2666 Nicks_CutMatchesNoSpaces(count);
2668 Nicks_CutMatchesNormal(count);
2671 static const char **Nicks_CompleteBuildList(int count)
2675 // the list is freed by Con_CompleteCommandLine, so create a char**
2676 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2678 for(; bpos < count; ++bpos)
2679 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2681 Nicks_CutMatches(count);
2689 Restores the previous used color, after the autocompleted name.
2691 static int Nicks_AddLastColor(char *buffer, int pos)
2693 qboolean quote_added = false;
2695 int color = STRING_COLOR_DEFAULT + '0';
2696 char r = 0, g = 0, b = 0;
2698 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2700 // we'll have to add a quote :)
2701 buffer[pos++] = '\"';
2705 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2707 // add color when no quote was added, or when flags &4?
2709 for(match = Nicks_matchpos-1; match >= 0; --match)
2711 if(buffer[match] == STRING_COLOR_TAG)
2713 if( isdigit(buffer[match+1]) )
2715 color = buffer[match+1];
2718 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2720 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2722 r = buffer[match+2];
2723 g = buffer[match+3];
2724 b = buffer[match+4];
2733 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2735 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2736 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2739 buffer[pos++] = STRING_COLOR_TAG;
2742 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2748 buffer[pos++] = color;
2753 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2756 /*if(!con_nickcompletion.integer)
2757 return; is tested in Nicks_CompletionCountPossible */
2758 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2764 msg = Nicks_list[0];
2765 len = min(size - Nicks_matchpos - 3, strlen(msg));
2766 memcpy(&buffer[Nicks_matchpos], msg, len);
2767 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2768 len = (int)Nicks_AddLastColor(buffer, Nicks_matchpos+(int)len);
2769 buffer[len++] = ' ';
2776 Con_Printf("\n%i possible nicks:\n", n);
2777 Cmd_CompleteNicksPrint(n);
2779 Nicks_CutMatches(n);
2781 msg = Nicks_sanlist[0];
2782 len = (int)min(size - Nicks_matchpos, strlen(msg));
2783 memcpy(&buffer[Nicks_matchpos], msg, len);
2784 buffer[Nicks_matchpos + len] = 0;
2786 return Nicks_matchpos + len;
2793 Con_CompleteCommandLine
2795 New function for tab-completion system
2796 Added by EvilTypeGuy
2797 Thanks to Fett erich@heintz.com
2799 Enhanced to tab-complete map names by [515]
2802 void Con_CompleteCommandLine (cmd_state_t *cmd)
2804 const char *text = "";
2806 const char **list[4] = {0, 0, 0, 0};
2809 int c, v, a, i, cmd_len, pos, k;
2810 int n; // nicks --blub
2811 const char *space, *patterns;
2814 //find what we want to complete
2819 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2825 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2826 key_line[key_linepos] = 0; //hide them
2828 space = strchr(key_line + 1, ' ');
2829 if(space && pos == (space - key_line) + 1)
2831 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2833 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?
2834 if(patterns && !*patterns)
2835 patterns = NULL; // get rid of the empty string
2837 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2841 if (GetMapList(s, t, sizeof(t)))
2843 // first move the cursor
2844 key_linepos += (int)strlen(t) - (int)strlen(s);
2846 // and now do the actual work
2848 strlcat(key_line, t, MAX_INPUTLINE);
2849 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2851 // and fix the cursor
2852 if(key_linepos > (int) strlen(key_line))
2853 key_linepos = (int) strlen(key_line);
2862 stringlist_t resultbuf, dirbuf;
2865 // // store completion patterns (space separated) for command foo in con_completion_foo
2866 // set con_completion_foo "foodata/*.foodefault *.foo"
2869 // Note: patterns with slash are always treated as absolute
2870 // patterns; patterns without slash search in the innermost
2871 // directory the user specified. There is no way to "complete into"
2872 // a directory as of now, as directories seem to be unknown to the
2876 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2877 // set con_completion_playdemo "*.dem"
2878 // set con_completion_play "*.wav *.ogg"
2880 // TODO somehow add support for directories; these shall complete
2881 // to their name + an appended slash.
2883 stringlistinit(&resultbuf);
2884 stringlistinit(&dirbuf);
2885 while(COM_ParseToken_Simple(&patterns, false, false, true))
2888 if(strchr(com_token, '/'))
2890 search = FS_Search(com_token, true, true);
2894 const char *slash = strrchr(s, '/');
2897 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2898 strlcat(t, com_token, sizeof(t));
2899 search = FS_Search(t, true, true);
2902 search = FS_Search(com_token, true, true);
2906 for(i = 0; i < search->numfilenames; ++i)
2907 if(!strncmp(search->filenames[i], s, strlen(s)))
2908 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2909 stringlistappend(&resultbuf, search->filenames[i]);
2910 FS_FreeSearch(search);
2914 // In any case, add directory names
2917 const char *slash = strrchr(s, '/');
2920 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2921 strlcat(t, "*", sizeof(t));
2922 search = FS_Search(t, true, true);
2925 search = FS_Search("*", true, true);
2928 for(i = 0; i < search->numfilenames; ++i)
2929 if(!strncmp(search->filenames[i], s, strlen(s)))
2930 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2931 stringlistappend(&dirbuf, search->filenames[i]);
2932 FS_FreeSearch(search);
2936 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2939 unsigned int matchchars;
2940 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2942 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2945 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2947 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2951 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2952 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2953 for(i = 0; i < dirbuf.numstrings; ++i)
2955 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2957 for(i = 0; i < resultbuf.numstrings; ++i)
2959 Con_Printf("%s\n", resultbuf.strings[i]);
2961 matchchars = sizeof(t) - 1;
2962 if(resultbuf.numstrings > 0)
2964 p = resultbuf.strings[0];
2965 q = resultbuf.strings[resultbuf.numstrings - 1];
2966 for(; *p && *p == *q; ++p, ++q);
2967 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2969 if(dirbuf.numstrings > 0)
2971 p = dirbuf.strings[0];
2972 q = dirbuf.strings[dirbuf.numstrings - 1];
2973 for(; *p && *p == *q; ++p, ++q);
2974 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2976 // now p points to the first non-equal character, or to the end
2977 // of resultbuf.strings[0]. We want to append the characters
2978 // from resultbuf.strings[0] to (not including) p as these are
2979 // the unique prefix
2980 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2983 // first move the cursor
2984 key_linepos += (int)strlen(t) - (int)strlen(s);
2986 // and now do the actual work
2988 strlcat(key_line, t, MAX_INPUTLINE);
2989 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2991 // and fix the cursor
2992 if(key_linepos > (int) strlen(key_line))
2993 key_linepos = (int) strlen(key_line);
2995 stringlistfreecontents(&resultbuf);
2996 stringlistfreecontents(&dirbuf);
2998 return; // bail out, when we complete for a command that wants a file name
3003 // Count number of possible matches and print them
3004 c = Cmd_CompleteCountPossible(cmd, s);
3007 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
3008 Cmd_CompleteCommandPrint(cmd, s);
3010 v = Cvar_CompleteCountPossible(cmd->cvars, s, CVAR_CLIENT | CVAR_SERVER);
3013 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
3014 Cvar_CompleteCvarPrint(cmd->cvars, s, CVAR_CLIENT | CVAR_SERVER);
3016 a = Cmd_CompleteAliasCountPossible(cmd, s);
3019 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
3020 Cmd_CompleteAliasPrint(cmd, s);
3022 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
3025 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
3026 Cmd_CompleteNicksPrint(n);
3029 if (!(c + v + a + n)) // No possible matches
3032 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
3037 text = *(list[0] = Cmd_CompleteBuildList(cmd, s));
3039 text = *(list[1] = Cvar_CompleteBuildList(cmd->cvars, s, cmd->cvars_flagsmask));
3041 text = *(list[2] = Cmd_CompleteAliasBuildList(cmd, s));
3043 text = *(list[3] = Nicks_CompleteBuildList(n));
3045 for (cmd_len = (int)strlen(s);;cmd_len++)
3048 for (i = 0; i < 3; i++)
3050 for (l = list[i];*l;l++)
3051 if ((*l)[cmd_len] != text[cmd_len])
3053 // all possible matches share this character, so we continue...
3056 // if all matches ended at the same position, stop
3057 // (this means there is only one match)
3063 // prevent a buffer overrun by limiting cmd_len according to remaining space
3064 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
3068 memcpy(&key_line[key_linepos], text, cmd_len);
3069 key_linepos += cmd_len;
3070 // if there is only one match, add a space after it
3071 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
3074 { // was a nick, might have an offset, and needs colors ;) --blub
3075 key_linepos = pos - Nicks_offset[0];
3076 cmd_len = (int)strlen(Nicks_list[0]);
3077 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
3079 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
3080 key_linepos += cmd_len;
3081 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
3082 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
3084 key_line[key_linepos++] = ' ';
3088 // use strlcat to avoid a buffer overrun
3089 key_line[key_linepos] = 0;
3090 strlcat(key_line, s2, sizeof(key_line));
3092 // free the command, cvar, and alias lists
3093 for (i = 0; i < 4; i++)
3095 Mem_Free((void *)list[i]);