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.
24 #if !defined(WIN32) || defined(__MINGW32__)
29 float con_cursorspeed = 4;
31 #define CON_TEXTSIZE 1048576
32 #define CON_MAXLINES 16384
34 // lines up from bottom to display
39 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
40 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
41 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
43 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
44 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
45 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
47 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
48 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
49 cvar_t con_chatpos = {CVAR_SAVE, "con_chatpos","0", "where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top)"};
50 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
51 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
52 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
53 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
54 cvar_t con_chatsound = {CVAR_SAVE, "con_chatsound","1", "enables chat sound to play on message"};
57 cvar_t sys_specialcharactertranslation = {0, "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)"};
59 cvar_t sys_colortranslation = {0, "sys_colortranslation", "0", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
61 cvar_t sys_colortranslation = {0, "sys_colortranslation", "1", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
65 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
66 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
67 "0: add nothing after completion. "
68 "1: add the last color after completion. "
69 "2: add a quote when starting a quote instead of the color. "
70 "4: will replace 1, will force color, even after a quote. "
71 "8: ignore non-alphanumerics. "
72 "16: ignore spaces. "};
73 #define NICKS_ADD_COLOR 1
74 #define NICKS_ADD_QUOTE 2
75 #define NICKS_FORCE_COLOR 4
76 #define NICKS_ALPHANUMERICS_ONLY 8
77 #define NICKS_NO_SPACES 16
79 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
80 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
81 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
86 qboolean con_initialized;
88 // used for server replies to rcon command
89 lhnetsocket_t *rcon_redirect_sock = NULL;
90 lhnetaddress_t *rcon_redirect_dest = NULL;
91 int rcon_redirect_bufferpos = 0;
92 char rcon_redirect_buffer[1400];
94 // generic functions for console buffers
96 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
98 buf->textsize = textsize;
99 buf->text = (char *) Mem_Alloc(mempool, textsize);
100 buf->maxlines = maxlines;
101 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
102 buf->lines_first = 0;
103 buf->lines_count = 0;
111 void ConBuffer_Clear (conbuffer_t *buf)
113 buf->lines_count = 0;
121 void ConBuffer_Shutdown(conbuffer_t *buf)
124 Mem_Free(buf->lines);
133 Notifies the console code about the current time
134 (and shifts back times of other entries when the time
138 void ConBuffer_FixTimes(conbuffer_t *buf)
141 if(buf->lines_count >= 1)
143 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
146 for(i = 0; i < buf->lines_count; ++i)
147 CONBUFFER_LINES(buf, i).addtime += diff;
156 Deletes the first line from the console history.
159 void ConBuffer_DeleteLine(conbuffer_t *buf)
161 if(buf->lines_count == 0)
164 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
169 ConBuffer_DeleteLastLine
171 Deletes the last line from the console history.
174 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
176 if(buf->lines_count == 0)
185 Checks if there is space for a line of the given length, and if yes, returns a
186 pointer to the start of such a space, and NULL otherwise.
189 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
191 if(len > buf->textsize)
193 if(buf->lines_count == 0)
197 char *firstline_start = buf->lines[buf->lines_first].start;
198 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
199 // the buffer is cyclic, so we first have two cases...
200 if(firstline_start < lastline_onepastend) // buffer is contiguous
203 if(len <= buf->text + buf->textsize - lastline_onepastend)
204 return lastline_onepastend;
206 else if(len <= firstline_start - buf->text)
211 else // buffer has a contiguous hole
213 if(len <= firstline_start - lastline_onepastend)
214 return lastline_onepastend;
225 Appends a given string as a new line to the console.
228 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
233 ConBuffer_FixTimes(buf);
235 if(len >= buf->textsize)
238 // only display end of line.
239 line += len - buf->textsize + 1;
240 len = buf->textsize - 1;
242 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
243 ConBuffer_DeleteLine(buf);
244 memcpy(putpos, line, len);
248 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
250 p = &CONBUFFER_LINES_LAST(buf);
253 p->addtime = cl.time;
255 p->height = -1; // calculate when needed
258 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
262 start = buf->lines_count;
263 for(i = start - 1; i >= 0; --i)
265 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
267 if((l->mask & mask_must) != mask_must)
269 if(l->mask & mask_mustnot)
278 int Con_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
281 for(i = start + 1; i < buf->lines_count; ++i)
283 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
285 if((l->mask & mask_must) != mask_must)
287 if(l->mask & mask_mustnot)
296 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
298 static char copybuf[MAX_INPUTLINE];
299 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
300 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
301 strlcpy(copybuf, l->start, sz);
306 ==============================================================================
310 ==============================================================================
315 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
316 cvar_t log_dest_udp = {0, "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"};
317 char log_dest_buffer[1400]; // UDP packet
318 size_t log_dest_buffer_pos;
319 unsigned int log_dest_buffer_appending;
320 char crt_log_file [MAX_OSPATH] = "";
321 qfile_t* logfile = NULL;
323 unsigned char* logqueue = NULL;
325 size_t logq_size = 0;
327 void Log_ConPrint (const char *msg);
334 static void Log_DestBuffer_Init(void)
336 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
337 log_dest_buffer_pos = 5;
345 void Log_DestBuffer_Flush(void)
347 lhnetaddress_t log_dest_addr;
348 lhnetsocket_t *log_dest_socket;
349 const char *s = log_dest_udp.string;
350 qboolean have_opened_temp_sockets = false;
351 if(s) if(log_dest_buffer_pos > 5)
353 ++log_dest_buffer_appending;
354 log_dest_buffer[log_dest_buffer_pos++] = 0;
356 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
358 have_opened_temp_sockets = true;
359 NetConn_OpenServerPorts(true);
362 while(COM_ParseToken_Console(&s))
363 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
365 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
367 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
369 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
372 if(have_opened_temp_sockets)
373 NetConn_CloseServerPorts();
374 --log_dest_buffer_appending;
376 log_dest_buffer_pos = 0;
384 const char* Log_Timestamp (const char *desc)
386 static char timestamp [128];
393 char timestring [64];
395 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
398 localtime_s (&crt_tm, &crt_time);
399 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
401 crt_tm = localtime (&crt_time);
402 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
406 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
408 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
421 if (logfile != NULL || log_file.string[0] == '\0')
424 logfile = FS_OpenRealFile(log_file.string, "a", false);
427 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
428 FS_Print (logfile, Log_Timestamp ("Log started"));
438 void Log_Close (void)
443 FS_Print (logfile, Log_Timestamp ("Log stopped"));
444 FS_Print (logfile, "\n");
448 crt_log_file[0] = '\0';
457 void Log_Start (void)
463 // Dump the contents of the log queue into the log file and free it
464 if (logqueue != NULL)
466 unsigned char *temp = logqueue;
471 FS_Write (logfile, temp, logq_ind);
472 if(*log_dest_udp.string)
474 for(pos = 0; pos < logq_ind; )
476 if(log_dest_buffer_pos == 0)
477 Log_DestBuffer_Init();
478 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
479 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
480 log_dest_buffer_pos += n;
481 Log_DestBuffer_Flush();
498 void Log_ConPrint (const char *msg)
500 static qboolean inprogress = false;
502 // don't allow feedback loops with memory error reports
507 // Until the host is completely initialized, we maintain a log queue
508 // to store the messages, since the log can't be started before
509 if (logqueue != NULL)
511 size_t remain = logq_size - logq_ind;
512 size_t len = strlen (msg);
514 // If we need to enlarge the log queue
517 size_t factor = ((logq_ind + len) / logq_size) + 1;
518 unsigned char* newqueue;
521 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
522 memcpy (newqueue, logqueue, logq_ind);
525 remain = logq_size - logq_ind;
527 memcpy (&logqueue[logq_ind], msg, len);
534 // Check if log_file has changed
535 if (strcmp (crt_log_file, log_file.string) != 0)
541 // If a log file is available
543 FS_Print (logfile, msg);
554 void Log_Printf (const char *logfilename, const char *fmt, ...)
558 file = FS_OpenRealFile(logfilename, "a", true);
563 va_start (argptr, fmt);
564 FS_VPrintf (file, fmt, argptr);
573 ==============================================================================
577 ==============================================================================
585 void Con_ToggleConsole_f (void)
587 // toggle the 'user wants console' bit
588 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
597 void Con_ClearNotify (void)
600 for(i = 0; i < CON_LINES_COUNT; ++i)
601 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
610 void Con_MessageMode_f (void)
612 key_dest = key_message;
613 chat_mode = 0; // "say"
624 void Con_MessageMode2_f (void)
626 key_dest = key_message;
627 chat_mode = 1; // "say_team"
637 void Con_CommandMode_f (void)
639 key_dest = key_message;
642 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
643 chat_bufferlen = strlen(chat_buffer);
645 chat_mode = -1; // command
653 void Con_CheckResize (void)
658 f = bound(1, con_textsize.value, 128);
659 if(f != con_textsize.value)
660 Cvar_SetValueQuick(&con_textsize, f);
661 width = (int)floor(vid_conwidth.value / con_textsize.value);
662 width = bound(1, width, con.textsize/4);
663 // FIXME uses con in a non abstracted way
665 if (width == con_linewidth)
668 con_linewidth = width;
670 for(i = 0; i < CON_LINES_COUNT; ++i)
671 CON_LINES(i).height = -1; // recalculate when next needed
677 //[515]: the simplest command ever
678 //LordHavoc: not so simple after I made it print usage...
679 static void Con_Maps_f (void)
683 Con_Printf("usage: maps [mapnameprefix]\n");
686 else if (Cmd_Argc() == 2)
687 GetMapList(Cmd_Argv(1), NULL, 0);
689 GetMapList("", NULL, 0);
692 void Con_ConDump_f (void)
698 Con_Printf("usage: condump <filename>\n");
701 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
704 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
707 for(i = 0; i < CON_LINES_COUNT; ++i)
709 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
710 FS_Write(file, "\n", 1);
715 void Con_Clear_f (void)
717 ConBuffer_Clear(&con);
728 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
730 // Allocate a log queue, this will be freed after configs are parsed
731 logq_size = MAX_INPUTLINE;
732 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
735 Cvar_RegisterVariable (&sys_colortranslation);
736 Cvar_RegisterVariable (&sys_specialcharactertranslation);
738 Cvar_RegisterVariable (&log_file);
739 Cvar_RegisterVariable (&log_dest_udp);
741 // support for the classic Quake option
742 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
743 if (COM_CheckParm ("-condebug") != 0)
744 Cvar_SetQuick (&log_file, "qconsole.log");
746 // register our cvars
747 Cvar_RegisterVariable (&con_chat);
748 Cvar_RegisterVariable (&con_chatpos);
749 Cvar_RegisterVariable (&con_chatsize);
750 Cvar_RegisterVariable (&con_chattime);
751 Cvar_RegisterVariable (&con_chatwidth);
752 Cvar_RegisterVariable (&con_notify);
753 Cvar_RegisterVariable (&con_notifyalign);
754 Cvar_RegisterVariable (&con_notifysize);
755 Cvar_RegisterVariable (&con_notifytime);
756 Cvar_RegisterVariable (&con_textsize);
757 Cvar_RegisterVariable (&con_chatsound);
760 Cvar_RegisterVariable (&con_nickcompletion);
761 Cvar_RegisterVariable (&con_nickcompletion_flags);
763 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
764 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
765 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
767 // register our commands
768 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
769 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
770 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
771 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
772 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
773 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
774 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
776 con_initialized = true;
777 Con_DPrint("Console initialized.\n");
780 void Con_Shutdown (void)
782 ConBuffer_Shutdown(&con);
789 Handles cursor positioning, line wrapping, etc
790 All console printing must go through this in order to be displayed
791 If no console is visible, the notify window will pop up.
794 void Con_PrintToHistory(const char *txt, int mask)
797 // \n goes to next line
798 // \r deletes current line and makes a new one
800 static int cr_pending = 0;
801 static char buf[CON_TEXTSIZE];
802 static int bufpos = 0;
804 if(!con.text) // FIXME uses a non-abstracted property of con
811 ConBuffer_DeleteLastLine(&con);
819 ConBuffer_AddLine(&con, buf, bufpos, mask);
824 ConBuffer_AddLine(&con, buf, bufpos, mask);
828 buf[bufpos++] = *txt;
829 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
831 ConBuffer_AddLine(&con, buf, bufpos, mask);
839 /*! The translation table between the graphical font and plain ASCII --KB */
840 static char qfont_table[256] = {
841 '\0', '#', '#', '#', '#', '.', '#', '#',
842 '#', 9, 10, '#', ' ', 13, '.', '.',
843 '[', ']', '0', '1', '2', '3', '4', '5',
844 '6', '7', '8', '9', '.', '<', '=', '>',
845 ' ', '!', '"', '#', '$', '%', '&', '\'',
846 '(', ')', '*', '+', ',', '-', '.', '/',
847 '0', '1', '2', '3', '4', '5', '6', '7',
848 '8', '9', ':', ';', '<', '=', '>', '?',
849 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
850 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
851 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
852 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
853 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
854 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
855 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
856 'x', 'y', 'z', '{', '|', '}', '~', '<',
858 '<', '=', '>', '#', '#', '.', '#', '#',
859 '#', '#', ' ', '#', ' ', '>', '.', '.',
860 '[', ']', '0', '1', '2', '3', '4', '5',
861 '6', '7', '8', '9', '.', '<', '=', '>',
862 ' ', '!', '"', '#', '$', '%', '&', '\'',
863 '(', ')', '*', '+', ',', '-', '.', '/',
864 '0', '1', '2', '3', '4', '5', '6', '7',
865 '8', '9', ':', ';', '<', '=', '>', '?',
866 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
867 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
868 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
869 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
870 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
871 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
872 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
873 'x', 'y', 'z', '{', '|', '}', '~', '<'
876 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest)
878 rcon_redirect_sock = sock;
879 rcon_redirect_dest = dest;
880 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
881 rcon_redirect_bufferpos = 5;
884 void Con_Rcon_Redirect_Flush(void)
886 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
887 NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
888 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
889 rcon_redirect_bufferpos = 5;
892 void Con_Rcon_Redirect_End(void)
894 Con_Rcon_Redirect_Flush();
895 rcon_redirect_dest = NULL;
896 rcon_redirect_sock = NULL;
899 void Con_Rcon_Redirect_Abort(void)
901 rcon_redirect_dest = NULL;
902 rcon_redirect_sock = NULL;
910 /// Adds a character to the rcon buffer.
911 void Con_Rcon_AddChar(int c)
913 if(log_dest_buffer_appending)
915 ++log_dest_buffer_appending;
917 // if this print is in response to an rcon command, add the character
918 // to the rcon redirect buffer
920 if (rcon_redirect_dest)
922 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
923 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
924 Con_Rcon_Redirect_Flush();
926 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
928 if(log_dest_buffer_pos == 0)
929 Log_DestBuffer_Init();
930 log_dest_buffer[log_dest_buffer_pos++] = c;
931 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
932 Log_DestBuffer_Flush();
935 log_dest_buffer_pos = 0;
937 --log_dest_buffer_appending;
941 * Convert an RGB color to its nearest quake color.
942 * I'll cheat on this a bit by translating the colors to HSV first,
943 * S and V decide if it's black or white, otherwise, H will decide the
945 * @param _r Red (0-255)
946 * @param _g Green (0-255)
947 * @param _b Blue (0-255)
948 * @return A quake color character.
950 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
952 float r = ((float)_r)/255.0;
953 float g = ((float)_g)/255.0;
954 float b = ((float)_b)/255.0;
955 float min = min(r, min(g, b));
956 float max = max(r, max(g, b));
958 int h; ///< Hue angle [0,360]
959 float s; ///< Saturation [0,1]
960 float v = max; ///< In HSV v == max [0,1]
967 // Saturation threshold. We now say 0.2 is the minimum value for a color!
970 // If the value is less than half, return a black color code.
971 // Otherwise return a white one.
977 // Let's get the hue angle to define some colors:
981 h = (int)(60.0 * (g-b)/(max-min))%360;
983 h = (int)(60.0 * (b-r)/(max-min) + 120);
984 else // if(max == b) redundant check
985 h = (int)(60.0 * (r-g)/(max-min) + 240);
987 if(h < 36) // *red* to orange
989 else if(h < 80) // orange over *yellow* to evilish-bright-green
991 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
993 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
995 else if(h < 270) // darkish blue over *dark blue* to cool purple
997 else if(h < 330) // cool purple over *purple* to ugly swiny red
999 else // ugly red to red closes the circly
1008 extern cvar_t timestamps;
1009 extern cvar_t timeformat;
1010 extern qboolean sys_nostdout;
1011 void Con_Print(const char *msg)
1013 static int mask = 0;
1014 static int index = 0;
1015 static char line[MAX_INPUTLINE];
1019 Con_Rcon_AddChar(*msg);
1020 // if this is the beginning of a new line, print timestamp
1023 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1025 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1026 line[index++] = STRING_COLOR_TAG;
1027 // assert( STRING_COLOR_DEFAULT < 10 )
1028 line[index++] = STRING_COLOR_DEFAULT + '0';
1029 // special color codes for chat messages must always come first
1030 // for Con_PrintToHistory to work properly
1031 if (*msg == 1 || *msg == 2)
1036 if (con_chatsound.value)
1038 if(gamemode == GAME_NEXUIZ)
1040 if(msg[1] == '\r' && cl.foundtalk2wav)
1041 S_LocalSound ("sound/misc/talk2.wav");
1043 S_LocalSound ("sound/misc/talk.wav");
1047 if (msg[1] == '(' && cl.foundtalk2wav)
1048 S_LocalSound ("sound/misc/talk2.wav");
1050 S_LocalSound ("sound/misc/talk.wav");
1053 mask = CON_MASK_CHAT;
1055 line[index++] = STRING_COLOR_TAG;
1056 line[index++] = '3';
1058 Con_Rcon_AddChar(*msg);
1061 for (;*timestamp;index++, timestamp++)
1062 if (index < (int)sizeof(line) - 2)
1063 line[index] = *timestamp;
1065 // append the character
1066 line[index++] = *msg;
1067 // if this is a newline character, we have a complete line to print
1068 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1070 // terminate the line
1074 // send to scrollable buffer
1075 if (con_initialized && cls.state != ca_dedicated)
1077 Con_PrintToHistory(line, mask);
1080 // send to terminal or dedicated server window
1084 if(sys_specialcharactertranslation.integer)
1086 for (p = (unsigned char *) line;*p; p++)
1087 *p = qfont_table[*p];
1090 if(sys_colortranslation.integer == 1) // ANSI
1092 static char printline[MAX_INPUTLINE * 4 + 3];
1093 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1094 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1099 for(in = line, out = printline; *in; ++in)
1103 case STRING_COLOR_TAG:
1104 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1106 char r = tolower(in[2]);
1107 char g = tolower(in[3]);
1108 char b = tolower(in[4]);
1109 // it's a hex digit already, so the else part needs no check --blub
1110 if(isdigit(r)) r -= '0';
1112 if(isdigit(g)) g -= '0';
1114 if(isdigit(b)) b -= '0';
1117 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1118 in += 3; // 3 only, the switch down there does the fourth
1125 case STRING_COLOR_TAG:
1127 *out++ = STRING_COLOR_TAG;
1133 if(lastcolor == 0) break; else lastcolor = 0;
1134 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1139 if(lastcolor == 1) break; else lastcolor = 1;
1140 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1145 if(lastcolor == 2) break; else lastcolor = 2;
1146 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1151 if(lastcolor == 3) break; else lastcolor = 3;
1152 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1157 if(lastcolor == 4) break; else lastcolor = 4;
1158 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1163 if(lastcolor == 5) break; else lastcolor = 5;
1164 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1169 if(lastcolor == 6) break; else lastcolor = 6;
1170 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1175 // bold normal color
1177 if(lastcolor == 8) break; else lastcolor = 8;
1178 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1181 *out++ = STRING_COLOR_TAG;
1188 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1205 Sys_PrintToTerminal(printline);
1207 else if(sys_colortranslation.integer == 2) // Quake
1209 Sys_PrintToTerminal(line);
1213 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1216 for(in = line, out = printline; *in; ++in)
1220 case STRING_COLOR_TAG:
1223 case STRING_COLOR_RGB_TAG_CHAR:
1224 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1229 *out++ = STRING_COLOR_TAG;
1230 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1233 case STRING_COLOR_TAG:
1235 *out++ = STRING_COLOR_TAG;
1250 *out++ = STRING_COLOR_TAG;
1260 Sys_PrintToTerminal(printline);
1263 // empty the line buffer
1275 void Con_Printf(const char *fmt, ...)
1278 char msg[MAX_INPUTLINE];
1280 va_start(argptr,fmt);
1281 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1292 void Con_DPrint(const char *msg)
1294 if (!developer.integer)
1295 return; // don't confuse non-developers with techie stuff...
1304 void Con_DPrintf(const char *fmt, ...)
1307 char msg[MAX_INPUTLINE];
1309 if (!developer.integer)
1310 return; // don't confuse non-developers with techie stuff...
1312 va_start(argptr,fmt);
1313 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1321 ==============================================================================
1325 ==============================================================================
1332 The input line scrolls horizontally if typing goes beyond the right edge
1334 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1337 void Con_DrawInput (void)
1341 char editlinecopy[MAX_INPUTLINE+1], *text;
1344 if (!key_consoleactive)
1345 return; // don't draw anything
1347 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1348 text = editlinecopy;
1350 // Advanced Console Editing by Radix radix@planetquake.com
1351 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1352 // use strlen of edit_line instead of key_linepos to allow editing
1353 // of early characters w/o erasing
1355 y = (int)strlen(text);
1357 // fill out remainder with spaces
1358 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1361 // add the cursor frame
1362 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1363 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1365 // text[key_linepos + 1] = 0;
1367 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1372 DrawQ_String_Font(x, con_vislines - con_textsize.value*2, text, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, FONT_CONSOLE );
1375 // key_line[key_linepos] = 0;
1381 float alignment; // 0 = left, 0.5 = center, 1 = right
1387 const char *continuationString;
1390 int colorindex; // init to -1
1394 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1396 con_text_info_t *ti = (con_text_info_t *) passthrough;
1399 ti->colorindex = -1;
1400 return ti->fontsize * ti->font->maxwidth;
1403 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1404 else if(maxWidth == -1)
1405 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1408 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1409 // Note: this is NOT a Con_Printf, as it could print recursively
1414 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1420 (void) isContinuation;
1424 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1426 con_text_info_t *ti = (con_text_info_t *) passthrough;
1428 if(ti->y < ti->ymin - 0.001)
1430 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1434 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1435 if(isContinuation && *ti->continuationString)
1436 x += (int) DrawQ_String_Font(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);
1438 DrawQ_String_Font(x, ti->y, line, length, ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, &(ti->colorindex), false, ti->font);
1441 ti->y += ti->fontsize;
1445 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)
1449 int maxlines = (int) floor(height / fontsize + 0.01f);
1452 int continuationWidth = 0;
1454 double t = cl.time; // saved so it won't change
1457 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1458 ti.fontsize = fontsize;
1459 ti.alignment = alignment_x;
1462 ti.ymax = y + height;
1463 ti.continuationString = continuationString;
1466 Con_WordWidthFunc(&ti, NULL, &l, -1);
1467 l = strlen(continuationString);
1468 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1470 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1471 startidx = CON_LINES_COUNT;
1472 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1474 con_lineinfo_t *l = &CON_LINES(i);
1477 if((l->mask & mask_must) != mask_must)
1479 if(l->mask & mask_mustnot)
1481 if(maxage && (l->addtime < t - maxage))
1485 // Calculate its actual height...
1486 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1487 if(lines + mylines >= maxlines)
1489 nskip = lines + mylines - maxlines;
1498 // then center according to the calculated amount of lines...
1500 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1502 // then actually draw
1503 for(i = startidx; i < CON_LINES_COUNT; ++i)
1505 con_lineinfo_t *l = &CON_LINES(i);
1507 if((l->mask & mask_must) != mask_must)
1509 if(l->mask & mask_mustnot)
1511 if(maxage && (l->addtime < t - maxage))
1514 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1524 Draws the last few lines of output transparently over the game top
1527 void Con_DrawNotify (void)
1530 float chatstart, notifystart, inputsize;
1532 char temptext[MAX_INPUTLINE];
1536 ConBuffer_FixTimes(&con);
1538 numChatlines = con_chat.integer;
1539 chatpos = con_chatpos.integer;
1541 if (con_notify.integer < 0)
1542 Cvar_SetValueQuick(&con_notify, 0);
1543 if (gamemode == GAME_TRANSFUSION)
1544 v = 8; // vertical offset
1548 // GAME_NEXUIZ: center, otherwise left justify
1549 align = con_notifyalign.value;
1550 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1552 if(gamemode == GAME_NEXUIZ)
1560 // first chat, input line, then notify
1562 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1564 else if(chatpos > 0)
1566 // first notify, then (chatpos-1) empty lines, then chat, then input
1568 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1570 else // if(chatpos < 0)
1572 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1574 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1579 // just notify and input
1581 chatstart = 0; // shut off gcc warning
1584 v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_INPUT | CON_MASK_HIDENOTIFY | (numChatlines ? CON_MASK_CHAT : 0), con_notifytime.value, 0, notifystart, vid_conwidth.value, con_notify.value * con_notifysize.value, con_notifysize.value, align, 0.0, "");
1589 v = chatstart + numChatlines * con_chatsize.value;
1590 Con_DrawNotifyRect(CON_MASK_CHAT, CON_MASK_INPUT, con_chattime.value, 0, chatstart, vid_conwidth.value * con_chatwidth.value, v - chatstart, con_chatsize.value, 0.0, 1.0, "^3\014\014\014 "); // 015 is ·> character in conchars.tga
1593 if (key_dest == key_message)
1595 int colorindex = -1;
1597 // LordHavoc: speedup, and other improvements
1599 dpsnprintf(temptext, sizeof(temptext), "]%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1601 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1603 dpsnprintf(temptext, sizeof(temptext), "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1606 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1607 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1610 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1616 Con_MeasureConsoleLine
1618 Counts the number of lines for a line on the console.
1621 int Con_MeasureConsoleLine(int lineno)
1623 float width = vid_conwidth.value;
1625 con_lineinfo_t *li = &CON_LINES(lineno);
1627 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1630 ti.fontsize = con_textsize.value;
1631 ti.font = FONT_CONSOLE;
1633 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1640 Returns the height of a given console line; calculates it if necessary.
1643 int Con_LineHeight(int i)
1645 con_lineinfo_t *li = &CON_LINES(i);
1649 return li->height = Con_MeasureConsoleLine(i);
1656 Draws a line of the console; returns its height in lines.
1657 If alpha is 0, the line is not drawn, but still wrapped and its height
1661 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1663 float width = vid_conwidth.value;
1665 con_lineinfo_t *li = &CON_LINES(lineno);
1667 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1670 ti.continuationString = "";
1672 ti.fontsize = con_textsize.value;
1673 ti.font = FONT_CONSOLE;
1675 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1680 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1687 Calculates the last visible line index and how much to show of it based on
1691 void Con_LastVisibleLine(int *last, int *limitlast)
1696 if(con_backscroll < 0)
1699 // now count until we saw con_backscroll actual lines
1700 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1702 int h = Con_LineHeight(i);
1704 // line is the last visible line?
1705 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1708 *limitlast = lines_seen + h - con_backscroll;
1715 // if we get here, no line was on screen - scroll so that one line is
1717 con_backscroll = lines_seen - 1;
1718 *last = con.lines_first;
1719 // FIXME uses con in a non abstracted way
1727 Draws the console with the solid background
1728 The typing input line at the bottom should only be drawn if typing is allowed
1731 void Con_DrawConsole (int lines)
1733 int i, last, limitlast;
1739 con_vislines = lines;
1741 // draw the background
1742 DrawQ_Pic(0, lines - vid_conheight.integer, scr_conbrightness.value >= 0.01f ? Draw_CachePic ("gfx/conback") : NULL, vid_conwidth.integer, vid_conheight.integer, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, cls.signon == SIGNONS ? scr_conalpha.value : 1.0, 0); // always full alpha when not in game
1743 DrawQ_String_Font(vid_conwidth.integer - DrawQ_TextWidth_Font(engineversion, 0, false, FONT_CONSOLE) * con_textsize.value, lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0, NULL, true, FONT_CONSOLE);
1746 if(CON_LINES_COUNT > 0)
1748 float ymax = con_vislines - 2 * con_textsize.value;
1749 Con_LastVisibleLine(&last, &limitlast);
1750 y = ymax - con_textsize.value;
1753 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1754 // FIXME uses con in a non abstracted way
1759 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1761 break; // top of console buffer
1763 break; // top of console window
1769 // draw the input prompt, user text, and cursor if desired
1777 Prints not only map filename, but also
1778 its format (q1/q2/q3/hl) and even its message
1780 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1781 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1782 //LordHavoc: 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
1783 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1784 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1788 int i, k, max, p, o, min;
1791 unsigned char buf[1024];
1793 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1794 t = FS_Search(message, 1, true);
1797 if (t->numfilenames > 1)
1798 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1799 len = (unsigned char *)Z_Malloc(t->numfilenames);
1801 for(max=i=0;i<t->numfilenames;i++)
1803 k = (int)strlen(t->filenames[i]);
1813 for(i=0;i<t->numfilenames;i++)
1815 int lumpofs = 0, lumplen = 0;
1816 char *entities = NULL;
1817 const char *data = NULL;
1819 char entfilename[MAX_QPATH];
1820 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1822 f = FS_OpenVirtualFile(t->filenames[i], true);
1825 memset(buf, 0, 1024);
1826 FS_Read(f, buf, 1024);
1827 if (!memcmp(buf, "IBSP", 4))
1829 p = LittleLong(((int *)buf)[1]);
1830 if (p == Q3BSPVERSION)
1832 q3dheader_t *header = (q3dheader_t *)buf;
1833 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1834 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1836 else if (p == Q2BSPVERSION)
1838 q2dheader_t *header = (q2dheader_t *)buf;
1839 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1840 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1843 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1845 dheader_t *header = (dheader_t *)buf;
1846 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1847 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1851 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1852 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1853 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1854 if (!entities && lumplen >= 10)
1856 FS_Seek(f, lumpofs, SEEK_SET);
1857 entities = (char *)Z_Malloc(lumplen + 1);
1858 FS_Read(f, entities, lumplen);
1862 // if there are entities to parse, a missing message key just
1863 // means there is no title, so clear the message string now
1869 if (!COM_ParseToken_Simple(&data, false, false))
1871 if (com_token[0] == '{')
1873 if (com_token[0] == '}')
1875 // skip leading whitespace
1876 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
1877 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
1878 keyname[l] = com_token[k+l];
1880 if (!COM_ParseToken_Simple(&data, false, false))
1882 if (developer.integer >= 100)
1883 Con_Printf("key: %s %s\n", keyname, com_token);
1884 if (!strcmp(keyname, "message"))
1886 // get the message contents
1887 strlcpy(message, com_token, sizeof(message));
1897 *(t->filenames[i]+len[i]+5) = 0;
1900 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1901 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1902 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1903 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1904 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1906 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1911 k = *(t->filenames[0]+5+p);
1914 for(i=1;i<t->numfilenames;i++)
1915 if(*(t->filenames[i]+5+p) != k)
1919 if(p > o && completedname && completednamebufferlength > 0)
1921 memset(completedname, 0, completednamebufferlength);
1922 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1932 New function for tab-completion system
1933 Added by EvilTypeGuy
1934 MEGA Thanks to Taniwha
1937 void Con_DisplayList(const char **list)
1939 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1940 const char **walk = list;
1943 len = (int)strlen(*walk);
1951 len = (int)strlen(*list);
1952 if (pos + maxlen >= width) {
1958 for (i = 0; i < (maxlen - len); i++)
1970 SanitizeString strips color tags from the string in
1971 and writes the result on string out
1973 void SanitizeString(char *in, char *out)
1977 if(*in == STRING_COLOR_TAG)
1982 out[0] = STRING_COLOR_TAG;
1986 else if (*in >= '0' && *in <= '9') // ^[0-9] found
1993 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
1996 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
1998 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2005 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2010 else if (*in != STRING_COLOR_TAG)
2013 *out = qfont_table[*(unsigned char*)in];
2020 // Now it becomes TRICKY :D --blub
2021 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2022 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2023 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2024 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
2025 static int Nicks_matchpos;
2027 // co against <<:BLASTER:>> is true!?
2028 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2032 if(tolower(*a) == tolower(*b))
2046 return (*a < *b) ? -1 : 1;
2050 return (*a < *b) ? -1 : 1;
2054 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2057 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2059 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2060 return Nicks_strncasecmp_nospaces(a, b, a_len);
2061 return strncasecmp(a, b, a_len);
2064 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2066 // ignore non alphanumerics of B
2067 // if A contains a non-alphanumeric, B must contain it as well though!
2070 qboolean alnum_a, alnum_b;
2072 if(tolower(*a) == tolower(*b))
2074 if(*a == 0) // end of both strings, they're equal
2081 // not equal, end of one string?
2086 // ignore non alphanumerics
2087 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2088 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2089 if(!alnum_a) // b must contain this
2090 return (*a < *b) ? -1 : 1;
2093 // otherwise, both are alnum, they're just not equal, return the appropriate number
2095 return (*a < *b) ? -1 : 1;
2101 /* Nicks_CompleteCountPossible
2103 Count the number of possible nicks to complete
2105 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2114 if(!con_nickcompletion.integer)
2117 // changed that to 1
2118 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2121 for(i = 0; i < cl.maxclients; ++i)
2124 if(!cl.scores[p].name[0])
2127 SanitizeString(cl.scores[p].name, name);
2128 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2133 length = strlen(name);
2135 spos = pos - 1; // no need for a minimum of characters :)
2139 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2141 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2142 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2148 if(isCon && spos == 0)
2150 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2156 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2157 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2159 // the sanitized list
2160 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2163 Nicks_matchpos = match;
2166 Nicks_offset[count] = s - (&line[match]);
2167 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2174 void Cmd_CompleteNicksPrint(int count)
2177 for(i = 0; i < count; ++i)
2178 Con_Printf("%s\n", Nicks_list[i]);
2181 void Nicks_CutMatchesNormal(int count)
2183 // cut match 0 down to the longest possible completion
2186 c = strlen(Nicks_sanlist[0]) - 1;
2187 for(i = 1; i < count; ++i)
2189 l = strlen(Nicks_sanlist[i]) - 1;
2193 for(l = 0; l <= c; ++l)
2194 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2200 Nicks_sanlist[0][c+1] = 0;
2201 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2204 unsigned int Nicks_strcleanlen(const char *s)
2209 if( (*s >= 'a' && *s <= 'z') ||
2210 (*s >= 'A' && *s <= 'Z') ||
2211 (*s >= '0' && *s <= '9') ||
2219 void Nicks_CutMatchesAlphaNumeric(int count)
2221 // cut match 0 down to the longest possible completion
2224 char tempstr[sizeof(Nicks_sanlist[0])];
2226 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2228 c = strlen(Nicks_sanlist[0]);
2229 for(i = 0, l = 0; i < (int)c; ++i)
2231 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2232 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2233 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2235 tempstr[l++] = Nicks_sanlist[0][i];
2240 for(i = 1; i < count; ++i)
2243 b = Nicks_sanlist[i];
2253 if(tolower(*a) == tolower(*b))
2259 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2261 // b is alnum, so cut
2268 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2269 Nicks_CutMatchesNormal(count);
2270 //if(!Nicks_sanlist[0][0])
2271 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2273 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2274 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2278 void Nicks_CutMatchesNoSpaces(int count)
2280 // cut match 0 down to the longest possible completion
2283 char tempstr[sizeof(Nicks_sanlist[0])];
2286 c = strlen(Nicks_sanlist[0]);
2287 for(i = 0, l = 0; i < (int)c; ++i)
2289 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2291 tempstr[l++] = Nicks_sanlist[0][i];
2296 for(i = 1; i < count; ++i)
2299 b = Nicks_sanlist[i];
2309 if(tolower(*a) == tolower(*b))
2323 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2324 Nicks_CutMatchesNormal(count);
2325 //if(!Nicks_sanlist[0][0])
2326 //Con_Printf("TS: %s\n", tempstr);
2327 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2329 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2330 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2334 void Nicks_CutMatches(int count)
2336 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2337 Nicks_CutMatchesAlphaNumeric(count);
2338 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2339 Nicks_CutMatchesNoSpaces(count);
2341 Nicks_CutMatchesNormal(count);
2344 const char **Nicks_CompleteBuildList(int count)
2348 // the list is freed by Con_CompleteCommandLine, so create a char**
2349 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2351 for(; bpos < count; ++bpos)
2352 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2354 Nicks_CutMatches(count);
2362 Restores the previous used color, after the autocompleted name.
2364 int Nicks_AddLastColor(char *buffer, int pos)
2366 qboolean quote_added = false;
2368 int color = STRING_COLOR_DEFAULT + '0';
2369 char r = 0, g = 0, b = 0;
2371 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2373 // we'll have to add a quote :)
2374 buffer[pos++] = '\"';
2378 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2380 // add color when no quote was added, or when flags &4?
2382 for(match = Nicks_matchpos-1; match >= 0; --match)
2384 if(buffer[match] == STRING_COLOR_TAG)
2386 if( isdigit(buffer[match+1]) )
2388 color = buffer[match+1];
2391 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2393 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2395 r = buffer[match+2];
2396 g = buffer[match+3];
2397 b = buffer[match+4];
2406 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2408 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2409 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2412 buffer[pos++] = STRING_COLOR_TAG;
2415 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2421 buffer[pos++] = color;
2426 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2429 /*if(!con_nickcompletion.integer)
2430 return; is tested in Nicks_CompletionCountPossible */
2431 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2437 msg = Nicks_list[0];
2438 len = min(size - Nicks_matchpos - 3, strlen(msg));
2439 memcpy(&buffer[Nicks_matchpos], msg, len);
2440 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2441 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2442 buffer[len++] = ' ';
2449 Con_Printf("\n%i possible nicks:\n", n);
2450 Cmd_CompleteNicksPrint(n);
2452 Nicks_CutMatches(n);
2454 msg = Nicks_sanlist[0];
2455 len = min(size - Nicks_matchpos, strlen(msg));
2456 memcpy(&buffer[Nicks_matchpos], msg, len);
2457 buffer[Nicks_matchpos + len] = 0;
2459 return Nicks_matchpos + len;
2466 Con_CompleteCommandLine
2468 New function for tab-completion system
2469 Added by EvilTypeGuy
2470 Thanks to Fett erich@heintz.com
2472 Enhanced to tab-complete map names by [515]
2475 void Con_CompleteCommandLine (void)
2477 const char *cmd = "";
2479 const char **list[4] = {0, 0, 0, 0};
2482 int c, v, a, i, cmd_len, pos, k;
2483 int n; // nicks --blub
2484 const char *space, *patterns;
2486 //find what we want to complete
2491 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2497 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2498 key_line[key_linepos] = 0; //hide them
2500 space = strchr(key_line + 1, ' ');
2501 if(space && pos == (space - key_line) + 1)
2503 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2505 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2506 if(patterns && !*patterns)
2507 patterns = NULL; // get rid of the empty string
2509 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2513 if (GetMapList(s, t, sizeof(t)))
2515 // first move the cursor
2516 key_linepos += (int)strlen(t) - (int)strlen(s);
2518 // and now do the actual work
2520 strlcat(key_line, t, MAX_INPUTLINE);
2521 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2523 // and fix the cursor
2524 if(key_linepos > (int) strlen(key_line))
2525 key_linepos = (int) strlen(key_line);
2534 stringlist_t resultbuf, dirbuf;
2537 // // store completion patterns (space separated) for command foo in con_completion_foo
2538 // set con_completion_foo "foodata/*.foodefault *.foo"
2541 // Note: patterns with slash are always treated as absolute
2542 // patterns; patterns without slash search in the innermost
2543 // directory the user specified. There is no way to "complete into"
2544 // a directory as of now, as directories seem to be unknown to the
2548 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2549 // set con_completion_playdemo "*.dem"
2550 // set con_completion_play "*.wav *.ogg"
2552 // TODO somehow add support for directories; these shall complete
2553 // to their name + an appended slash.
2555 stringlistinit(&resultbuf);
2556 stringlistinit(&dirbuf);
2557 while(COM_ParseToken_Simple(&patterns, false, false))
2560 if(strchr(com_token, '/'))
2562 search = FS_Search(com_token, true, true);
2566 const char *slash = strrchr(s, '/');
2569 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2570 strlcat(t, com_token, sizeof(t));
2571 search = FS_Search(t, true, true);
2574 search = FS_Search(com_token, true, true);
2578 for(i = 0; i < search->numfilenames; ++i)
2579 if(!strncmp(search->filenames[i], s, strlen(s)))
2580 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2581 stringlistappend(&resultbuf, search->filenames[i]);
2582 FS_FreeSearch(search);
2586 // In any case, add directory names
2589 const char *slash = strrchr(s, '/');
2592 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2593 strlcat(t, "*", sizeof(t));
2594 search = FS_Search(t, true, true);
2597 search = FS_Search("*", true, true);
2600 for(i = 0; i < search->numfilenames; ++i)
2601 if(!strncmp(search->filenames[i], s, strlen(s)))
2602 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2603 stringlistappend(&dirbuf, search->filenames[i]);
2604 FS_FreeSearch(search);
2608 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2611 unsigned int matchchars;
2612 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2614 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2617 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2619 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2623 stringlistsort(&resultbuf); // dirbuf is already sorted
2624 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2625 for(i = 0; i < dirbuf.numstrings; ++i)
2627 Con_Printf("%s/\n", dirbuf.strings[i]);
2629 for(i = 0; i < resultbuf.numstrings; ++i)
2631 Con_Printf("%s\n", resultbuf.strings[i]);
2633 matchchars = sizeof(t) - 1;
2634 if(resultbuf.numstrings > 0)
2636 p = resultbuf.strings[0];
2637 q = resultbuf.strings[resultbuf.numstrings - 1];
2638 for(; *p && *p == *q; ++p, ++q);
2639 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2641 if(dirbuf.numstrings > 0)
2643 p = dirbuf.strings[0];
2644 q = dirbuf.strings[dirbuf.numstrings - 1];
2645 for(; *p && *p == *q; ++p, ++q);
2646 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2648 // now p points to the first non-equal character, or to the end
2649 // of resultbuf.strings[0]. We want to append the characters
2650 // from resultbuf.strings[0] to (not including) p as these are
2651 // the unique prefix
2652 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2655 // first move the cursor
2656 key_linepos += (int)strlen(t) - (int)strlen(s);
2658 // and now do the actual work
2660 strlcat(key_line, t, MAX_INPUTLINE);
2661 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2663 // and fix the cursor
2664 if(key_linepos > (int) strlen(key_line))
2665 key_linepos = (int) strlen(key_line);
2667 stringlistfreecontents(&resultbuf);
2668 stringlistfreecontents(&dirbuf);
2670 return; // bail out, when we complete for a command that wants a file name
2675 // Count number of possible matches and print them
2676 c = Cmd_CompleteCountPossible(s);
2679 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2680 Cmd_CompleteCommandPrint(s);
2682 v = Cvar_CompleteCountPossible(s);
2685 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2686 Cvar_CompleteCvarPrint(s);
2688 a = Cmd_CompleteAliasCountPossible(s);
2691 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2692 Cmd_CompleteAliasPrint(s);
2694 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2697 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2698 Cmd_CompleteNicksPrint(n);
2701 if (!(c + v + a + n)) // No possible matches
2704 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2709 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2711 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2713 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2715 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2717 for (cmd_len = (int)strlen(s);;cmd_len++)
2720 for (i = 0; i < 3; i++)
2722 for (l = list[i];*l;l++)
2723 if ((*l)[cmd_len] != cmd[cmd_len])
2725 // all possible matches share this character, so we continue...
2728 // if all matches ended at the same position, stop
2729 // (this means there is only one match)
2735 // prevent a buffer overrun by limiting cmd_len according to remaining space
2736 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2740 memcpy(&key_line[key_linepos], cmd, cmd_len);
2741 key_linepos += cmd_len;
2742 // if there is only one match, add a space after it
2743 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2746 { // was a nick, might have an offset, and needs colors ;) --blub
2747 key_linepos = pos - Nicks_offset[0];
2748 cmd_len = strlen(Nicks_list[0]);
2749 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2751 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2752 key_linepos += cmd_len;
2753 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2754 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2756 key_line[key_linepos++] = ' ';
2760 // use strlcat to avoid a buffer overrun
2761 key_line[key_linepos] = 0;
2762 strlcat(key_line, s2, sizeof(key_line));
2764 // free the command, cvar, and alias lists
2765 for (i = 0; i < 4; i++)
2767 Mem_Free((void *)list[i]);