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)"};
56 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)"};
58 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)"};
60 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)"};
64 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
65 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
66 "0: add nothing after completion. "
67 "1: add the last color after completion. "
68 "2: add a quote when starting a quote instead of the color. "
69 "4: will replace 1, will force color, even after a quote. "
70 "8: ignore non-alphanumerics. "
71 "16: ignore spaces. "};
72 #define NICKS_ADD_COLOR 1
73 #define NICKS_ADD_QUOTE 2
74 #define NICKS_FORCE_COLOR 4
75 #define NICKS_ALPHANUMERICS_ONLY 8
76 #define NICKS_NO_SPACES 16
78 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
79 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
80 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
85 qboolean con_initialized;
87 // used for server replies to rcon command
88 lhnetsocket_t *rcon_redirect_sock = NULL;
89 lhnetaddress_t *rcon_redirect_dest = NULL;
90 int rcon_redirect_bufferpos = 0;
91 char rcon_redirect_buffer[1400];
93 // generic functions for console buffers
95 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
97 buf->textsize = textsize;
98 buf->text = (char *) Mem_Alloc(mempool, textsize);
99 buf->maxlines = maxlines;
100 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
101 buf->lines_first = 0;
102 buf->lines_count = 0;
110 void ConBuffer_Clear (conbuffer_t *buf)
112 buf->lines_count = 0;
120 void ConBuffer_Shutdown(conbuffer_t *buf)
123 Mem_Free(buf->lines);
132 Notifies the console code about the current time
133 (and shifts back times of other entries when the time
137 void ConBuffer_FixTimes(conbuffer_t *buf)
140 if(buf->lines_count >= 1)
142 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
145 for(i = 0; i < buf->lines_count; ++i)
146 CONBUFFER_LINES(buf, i).addtime += diff;
155 Deletes the first line from the console history.
158 void ConBuffer_DeleteLine(conbuffer_t *buf)
160 if(buf->lines_count == 0)
163 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
168 ConBuffer_DeleteLastLine
170 Deletes the last line from the console history.
173 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
175 if(buf->lines_count == 0)
184 Checks if there is space for a line of the given length, and if yes, returns a
185 pointer to the start of such a space, and NULL otherwise.
188 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
190 if(len > buf->textsize)
192 if(buf->lines_count == 0)
196 char *firstline_start = buf->lines[buf->lines_first].start;
197 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
198 // the buffer is cyclic, so we first have two cases...
199 if(firstline_start < lastline_onepastend) // buffer is contiguous
202 if(len <= buf->text + buf->textsize - lastline_onepastend)
203 return lastline_onepastend;
205 else if(len <= firstline_start - buf->text)
210 else // buffer has a contiguous hole
212 if(len <= firstline_start - lastline_onepastend)
213 return lastline_onepastend;
224 Appends a given string as a new line to the console.
227 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
232 ConBuffer_FixTimes(buf);
234 if(len >= buf->textsize)
237 // only display end of line.
238 line += len - buf->textsize + 1;
239 len = buf->textsize - 1;
241 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
242 ConBuffer_DeleteLine(buf);
243 memcpy(putpos, line, len);
247 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
249 p = &CONBUFFER_LINES_LAST(buf);
252 p->addtime = cl.time;
254 p->height = -1; // calculate when needed
257 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
261 start = buf->lines_count;
262 for(i = start - 1; i >= 0; --i)
264 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
266 if((l->mask & mask_must) != mask_must)
268 if(l->mask & mask_mustnot)
277 int Con_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
280 for(i = start + 1; i < buf->lines_count; ++i)
282 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
284 if((l->mask & mask_must) != mask_must)
286 if(l->mask & mask_mustnot)
295 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
297 static char copybuf[MAX_INPUTLINE];
298 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
299 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
300 strlcpy(copybuf, l->start, sz);
305 ==============================================================================
309 ==============================================================================
312 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
313 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"};
314 char log_dest_buffer[1400]; // UDP packet
315 size_t log_dest_buffer_pos;
316 unsigned int log_dest_buffer_appending;
317 char crt_log_file [MAX_OSPATH] = "";
318 qfile_t* logfile = NULL;
320 unsigned char* logqueue = NULL;
322 size_t logq_size = 0;
324 void Log_ConPrint (const char *msg);
331 static void Log_DestBuffer_Init()
333 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
334 log_dest_buffer_pos = 5;
342 void Log_DestBuffer_Flush()
344 lhnetaddress_t log_dest_addr;
345 lhnetsocket_t *log_dest_socket;
346 const char *s = log_dest_udp.string;
347 qboolean have_opened_temp_sockets = false;
348 if(s) if(log_dest_buffer_pos > 5)
350 ++log_dest_buffer_appending;
351 log_dest_buffer[log_dest_buffer_pos++] = 0;
353 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
355 have_opened_temp_sockets = true;
356 NetConn_OpenServerPorts(true);
359 while(COM_ParseToken_Console(&s))
360 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
362 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
364 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
366 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
369 if(have_opened_temp_sockets)
370 NetConn_CloseServerPorts();
371 --log_dest_buffer_appending;
373 log_dest_buffer_pos = 0;
381 const char* Log_Timestamp (const char *desc)
383 static char timestamp [128];
390 char timestring [64];
392 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
395 localtime_s (&crt_tm, &crt_time);
396 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
398 crt_tm = localtime (&crt_time);
399 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
403 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
405 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
418 if (logfile != NULL || log_file.string[0] == '\0')
421 logfile = FS_OpenRealFile(log_file.string, "a", false);
424 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
425 FS_Print (logfile, Log_Timestamp ("Log started"));
435 void Log_Close (void)
440 FS_Print (logfile, Log_Timestamp ("Log stopped"));
441 FS_Print (logfile, "\n");
445 crt_log_file[0] = '\0';
454 void Log_Start (void)
460 // Dump the contents of the log queue into the log file and free it
461 if (logqueue != NULL)
463 unsigned char *temp = logqueue;
468 FS_Write (logfile, temp, logq_ind);
469 if(*log_dest_udp.string)
471 for(pos = 0; pos < logq_ind; )
473 if(log_dest_buffer_pos == 0)
474 Log_DestBuffer_Init();
475 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
476 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
477 log_dest_buffer_pos += n;
478 Log_DestBuffer_Flush();
495 void Log_ConPrint (const char *msg)
497 static qboolean inprogress = false;
499 // don't allow feedback loops with memory error reports
504 // Until the host is completely initialized, we maintain a log queue
505 // to store the messages, since the log can't be started before
506 if (logqueue != NULL)
508 size_t remain = logq_size - logq_ind;
509 size_t len = strlen (msg);
511 // If we need to enlarge the log queue
514 size_t factor = ((logq_ind + len) / logq_size) + 1;
515 unsigned char* newqueue;
518 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
519 memcpy (newqueue, logqueue, logq_ind);
522 remain = logq_size - logq_ind;
524 memcpy (&logqueue[logq_ind], msg, len);
531 // Check if log_file has changed
532 if (strcmp (crt_log_file, log_file.string) != 0)
538 // If a log file is available
540 FS_Print (logfile, msg);
551 void Log_Printf (const char *logfilename, const char *fmt, ...)
555 file = FS_OpenRealFile(logfilename, "a", true);
560 va_start (argptr, fmt);
561 FS_VPrintf (file, fmt, argptr);
570 ==============================================================================
574 ==============================================================================
582 void Con_ToggleConsole_f (void)
584 // toggle the 'user wants console' bit
585 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
593 Clear all notify lines.
596 void Con_ClearNotify (void)
599 for(i = 0; i < CON_LINES_COUNT; ++i)
600 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
609 void Con_MessageMode_f (void)
611 key_dest = key_message;
612 chat_mode = 0; // "say"
621 void Con_MessageMode2_f (void)
623 key_dest = key_message;
624 chat_mode = 1; // "say_team"
632 void Con_CommandMode_f (void)
634 key_dest = key_message;
637 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
638 chat_bufferlen = strlen(chat_buffer);
640 chat_mode = -1; // command
647 If the line width has changed, reformat the buffer.
650 void Con_CheckResize (void)
655 f = bound(1, con_textsize.value, 128);
656 if(f != con_textsize.value)
657 Cvar_SetValueQuick(&con_textsize, f);
658 width = (int)floor(vid_conwidth.value / con_textsize.value);
659 width = bound(1, width, con.textsize/4);
660 // FIXME uses con in a non abstracted way
662 if (width == con_linewidth)
665 con_linewidth = width;
667 for(i = 0; i < CON_LINES_COUNT; ++i)
668 CON_LINES(i).height = -1; // recalculate when next needed
674 //[515]: the simplest command ever
675 //LordHavoc: not so simple after I made it print usage...
676 static void Con_Maps_f (void)
680 Con_Printf("usage: maps [mapnameprefix]\n");
683 else if (Cmd_Argc() == 2)
684 GetMapList(Cmd_Argv(1), NULL, 0);
686 GetMapList("", NULL, 0);
689 void Con_ConDump_f (void)
695 Con_Printf("usage: condump <filename>\n");
698 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
701 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
704 for(i = 0; i < CON_LINES_COUNT; ++i)
706 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
707 FS_Write(file, "\n", 1);
714 ConBuffer_Clear(&con);
725 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
727 // Allocate a log queue, this will be freed after configs are parsed
728 logq_size = MAX_INPUTLINE;
729 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
732 Cvar_RegisterVariable (&sys_colortranslation);
733 Cvar_RegisterVariable (&sys_specialcharactertranslation);
735 Cvar_RegisterVariable (&log_file);
736 Cvar_RegisterVariable (&log_dest_udp);
738 // support for the classic Quake option
739 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
740 if (COM_CheckParm ("-condebug") != 0)
741 Cvar_SetQuick (&log_file, "qconsole.log");
743 // register our cvars
744 Cvar_RegisterVariable (&con_chat);
745 Cvar_RegisterVariable (&con_chatpos);
746 Cvar_RegisterVariable (&con_chatsize);
747 Cvar_RegisterVariable (&con_chattime);
748 Cvar_RegisterVariable (&con_chatwidth);
749 Cvar_RegisterVariable (&con_notify);
750 Cvar_RegisterVariable (&con_notifyalign);
751 Cvar_RegisterVariable (&con_notifysize);
752 Cvar_RegisterVariable (&con_notifytime);
753 Cvar_RegisterVariable (&con_textsize);
756 Cvar_RegisterVariable (&con_nickcompletion);
757 Cvar_RegisterVariable (&con_nickcompletion_flags);
759 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
760 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
761 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
763 // register our commands
764 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
765 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
766 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
767 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
768 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
769 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
770 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
772 con_initialized = true;
773 Con_DPrint("Console initialized.\n");
776 void Con_Shutdown (void)
778 ConBuffer_Shutdown(&con);
785 Handles cursor positioning, line wrapping, etc
786 All console printing must go through this in order to be displayed
787 If no console is visible, the notify window will pop up.
790 void Con_PrintToHistory(const char *txt, int mask)
793 // \n goes to next line
794 // \r deletes current line and makes a new one
796 static int cr_pending = 0;
797 static char buf[CON_TEXTSIZE];
798 static int bufpos = 0;
800 if(!con.text) // FIXME uses a non-abstracted property of con
807 ConBuffer_DeleteLastLine(&con);
815 ConBuffer_AddLine(&con, buf, bufpos, mask);
820 ConBuffer_AddLine(&con, buf, bufpos, mask);
824 buf[bufpos++] = *txt;
825 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
827 ConBuffer_AddLine(&con, buf, bufpos, mask);
835 /* The translation table between the graphical font and plain ASCII --KB */
836 static char qfont_table[256] = {
837 '\0', '#', '#', '#', '#', '.', '#', '#',
838 '#', 9, 10, '#', ' ', 13, '.', '.',
839 '[', ']', '0', '1', '2', '3', '4', '5',
840 '6', '7', '8', '9', '.', '<', '=', '>',
841 ' ', '!', '"', '#', '$', '%', '&', '\'',
842 '(', ')', '*', '+', ',', '-', '.', '/',
843 '0', '1', '2', '3', '4', '5', '6', '7',
844 '8', '9', ':', ';', '<', '=', '>', '?',
845 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
846 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
847 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
848 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
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', '{', '|', '}', '~', '<',
854 '<', '=', '>', '#', '#', '.', '#', '#',
855 '#', '#', ' ', '#', ' ', '>', '.', '.',
856 '[', ']', '0', '1', '2', '3', '4', '5',
857 '6', '7', '8', '9', '.', '<', '=', '>',
858 ' ', '!', '"', '#', '$', '%', '&', '\'',
859 '(', ')', '*', '+', ',', '-', '.', '/',
860 '0', '1', '2', '3', '4', '5', '6', '7',
861 '8', '9', ':', ';', '<', '=', '>', '?',
862 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
863 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
864 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
865 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
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', '{', '|', '}', '~', '<'
872 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest)
874 rcon_redirect_sock = sock;
875 rcon_redirect_dest = dest;
876 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
877 rcon_redirect_bufferpos = 5;
880 void Con_Rcon_Redirect_Flush()
882 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
883 NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
884 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
885 rcon_redirect_bufferpos = 5;
888 void Con_Rcon_Redirect_End()
890 Con_Rcon_Redirect_Flush();
891 rcon_redirect_dest = NULL;
892 rcon_redirect_sock = NULL;
895 void Con_Rcon_Redirect_Abort()
897 rcon_redirect_dest = NULL;
898 rcon_redirect_sock = NULL;
905 Adds a character to the rcon buffer
908 void Con_Rcon_AddChar(int c)
910 if(log_dest_buffer_appending)
912 ++log_dest_buffer_appending;
914 // if this print is in response to an rcon command, add the character
915 // to the rcon redirect buffer
917 if (rcon_redirect_dest)
919 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
920 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
921 Con_Rcon_Redirect_Flush();
923 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
925 if(log_dest_buffer_pos == 0)
926 Log_DestBuffer_Init();
927 log_dest_buffer[log_dest_buffer_pos++] = c;
928 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
929 Log_DestBuffer_Flush();
932 log_dest_buffer_pos = 0;
934 --log_dest_buffer_appending;
938 * Convert an RGB color to its nearest quake color.
939 * I'll cheat on this a bit by translating the colors to HSV first,
940 * S and V decide if it's black or white, otherwise, H will decide the
942 * @param _r Red (0-255)
943 * @param _g Green (0-255)
944 * @param _b Blue (0-255)
945 * @return A quake color character.
947 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
949 float r = ((float)_r)/255.0;
950 float g = ((float)_g)/255.0;
951 float b = ((float)_b)/255.0;
952 float min = min(r, min(g, b));
953 float max = max(r, max(g, b));
955 int h; ///< Hue angle [0,360]
956 float s; ///< Saturation [0,1]
957 float v = max; ///< In HSV v == max [0,1]
964 // Saturation threshold. We now say 0.2 is the minimum value for a color!
967 // If the value is less than half, return a black color code.
968 // Otherwise return a white one.
974 // Let's get the hue angle to define some colors:
978 h = (int)(60.0 * (g-b)/(max-min))%360;
980 h = (int)(60.0 * (b-r)/(max-min) + 120);
981 else // if(max == b) redundant check
982 h = (int)(60.0 * (r-g)/(max-min) + 240);
984 if(h < 36) // *red* to orange
986 else if(h < 80) // orange over *yellow* to evilish-bright-green
988 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
990 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
992 else if(h < 270) // darkish blue over *dark blue* to cool purple
994 else if(h < 330) // cool purple over *purple* to ugly swiny red
996 else // ugly red to red closes the circly
1004 Prints to all appropriate console targets, and adds timestamps
1007 extern cvar_t timestamps;
1008 extern cvar_t timeformat;
1009 extern qboolean sys_nostdout;
1010 void Con_Print(const char *msg)
1012 static int mask = 0;
1013 static int index = 0;
1014 static char line[MAX_INPUTLINE];
1018 Con_Rcon_AddChar(*msg);
1019 // if this is the beginning of a new line, print timestamp
1022 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1024 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1025 line[index++] = STRING_COLOR_TAG;
1026 // assert( STRING_COLOR_DEFAULT < 10 )
1027 line[index++] = STRING_COLOR_DEFAULT + '0';
1028 // special color codes for chat messages must always come first
1029 // for Con_PrintToHistory to work properly
1030 if (*msg == 1 || *msg == 2)
1035 if(gamemode == GAME_NEXUIZ)
1037 if(msg[1] == '\r' && cl.foundtalk2wav)
1038 S_LocalSound ("sound/misc/talk2.wav");
1040 S_LocalSound ("sound/misc/talk.wav");
1044 if (msg[1] == '(' && cl.foundtalk2wav)
1045 S_LocalSound ("sound/misc/talk2.wav");
1047 S_LocalSound ("sound/misc/talk.wav");
1049 mask = CON_MASK_CHAT;
1051 line[index++] = STRING_COLOR_TAG;
1052 line[index++] = '3';
1054 Con_Rcon_AddChar(*msg);
1057 for (;*timestamp;index++, timestamp++)
1058 if (index < (int)sizeof(line) - 2)
1059 line[index] = *timestamp;
1061 // append the character
1062 line[index++] = *msg;
1063 // if this is a newline character, we have a complete line to print
1064 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1066 // terminate the line
1070 // send to scrollable buffer
1071 if (con_initialized && cls.state != ca_dedicated)
1073 Con_PrintToHistory(line, mask);
1076 // send to terminal or dedicated server window
1080 if(sys_specialcharactertranslation.integer)
1082 for (p = (unsigned char *) line;*p; p++)
1083 *p = qfont_table[*p];
1086 if(sys_colortranslation.integer == 1) // ANSI
1088 static char printline[MAX_INPUTLINE * 4 + 3];
1089 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1090 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1095 for(in = line, out = printline; *in; ++in)
1099 case STRING_COLOR_TAG:
1100 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1102 char r = tolower(in[2]);
1103 char g = tolower(in[3]);
1104 char b = tolower(in[4]);
1105 // it's a hex digit already, so the else part needs no check --blub
1106 if(isdigit(r)) r -= '0';
1108 if(isdigit(g)) g -= '0';
1110 if(isdigit(b)) b -= '0';
1113 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1114 in += 3; // 3 only, the switch down there does the fourth
1121 case STRING_COLOR_TAG:
1123 *out++ = STRING_COLOR_TAG;
1129 if(lastcolor == 0) break; else lastcolor = 0;
1130 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1135 if(lastcolor == 1) break; else lastcolor = 1;
1136 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1141 if(lastcolor == 2) break; else lastcolor = 2;
1142 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1147 if(lastcolor == 3) break; else lastcolor = 3;
1148 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1153 if(lastcolor == 4) break; else lastcolor = 4;
1154 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1159 if(lastcolor == 5) break; else lastcolor = 5;
1160 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1165 if(lastcolor == 6) break; else lastcolor = 6;
1166 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1171 // bold normal color
1173 if(lastcolor == 8) break; else lastcolor = 8;
1174 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1177 *out++ = STRING_COLOR_TAG;
1184 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1201 Sys_PrintToTerminal(printline);
1203 else if(sys_colortranslation.integer == 2) // Quake
1205 Sys_PrintToTerminal(line);
1209 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1212 for(in = line, out = printline; *in; ++in)
1216 case STRING_COLOR_TAG:
1219 case STRING_COLOR_RGB_TAG_CHAR:
1220 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1225 *out++ = STRING_COLOR_TAG;
1226 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1229 case STRING_COLOR_TAG:
1231 *out++ = STRING_COLOR_TAG;
1246 *out++ = STRING_COLOR_TAG;
1256 Sys_PrintToTerminal(printline);
1259 // empty the line buffer
1270 Prints to all appropriate console targets
1273 void Con_Printf(const char *fmt, ...)
1276 char msg[MAX_INPUTLINE];
1278 va_start(argptr,fmt);
1279 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1289 A Con_Print that only shows up if the "developer" cvar is set
1292 void Con_DPrint(const char *msg)
1294 if (!developer.integer)
1295 return; // don't confuse non-developers with techie stuff...
1303 A Con_Printf that only shows up if the "developer" cvar is set
1306 void Con_DPrintf(const char *fmt, ...)
1309 char msg[MAX_INPUTLINE];
1311 if (!developer.integer)
1312 return; // don't confuse non-developers with techie stuff...
1314 va_start(argptr,fmt);
1315 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1323 ==============================================================================
1327 ==============================================================================
1334 The input line scrolls horizontally if typing goes beyond the right edge
1336 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1339 void Con_DrawInput (void)
1343 char editlinecopy[MAX_INPUTLINE+1], *text;
1346 if (!key_consoleactive)
1347 return; // don't draw anything
1349 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1350 text = editlinecopy;
1352 // Advanced Console Editing by Radix radix@planetquake.com
1353 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1354 // use strlen of edit_line instead of key_linepos to allow editing
1355 // of early characters w/o erasing
1357 y = (int)strlen(text);
1359 // fill out remainder with spaces
1360 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1363 // add the cursor frame
1364 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1365 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1367 // text[key_linepos + 1] = 0;
1369 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1374 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 );
1377 // key_line[key_linepos] = 0;
1383 float alignment; // 0 = left, 0.5 = center, 1 = right
1389 const char *continuationString;
1392 int colorindex; // init to -1
1396 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1398 con_text_info_t *ti = (con_text_info_t *) passthrough;
1401 ti->colorindex = -1;
1402 return ti->fontsize * ti->font->maxwidth;
1405 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1406 else if(maxWidth == -1)
1407 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1410 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1411 // Note: this is NOT a Con_Printf, as it could print recursively
1416 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1422 (void) isContinuation;
1426 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1428 con_text_info_t *ti = (con_text_info_t *) passthrough;
1430 if(ti->y < ti->ymin - 0.001)
1432 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1436 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1437 if(isContinuation && *ti->continuationString)
1438 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);
1440 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);
1443 ti->y += ti->fontsize;
1447 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)
1451 int maxlines = (int) floor(height / fontsize + 0.01f);
1454 int continuationWidth = 0;
1456 double t = cl.time; // saved so it won't change
1459 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1460 ti.fontsize = fontsize;
1461 ti.alignment = alignment_x;
1464 ti.ymax = y + height;
1465 ti.continuationString = continuationString;
1468 Con_WordWidthFunc(&ti, NULL, &l, -1);
1469 l = strlen(continuationString);
1470 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1472 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1473 startidx = CON_LINES_COUNT;
1474 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1476 con_lineinfo_t *l = &CON_LINES(i);
1479 if((l->mask & mask_must) != mask_must)
1481 if(l->mask & mask_mustnot)
1483 if(maxage && (l->addtime < t - maxage))
1487 // Calculate its actual height...
1488 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1489 if(lines + mylines >= maxlines)
1491 nskip = lines + mylines - maxlines;
1500 // then center according to the calculated amount of lines...
1502 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1504 // then actually draw
1505 for(i = startidx; i < CON_LINES_COUNT; ++i)
1507 con_lineinfo_t *l = &CON_LINES(i);
1509 if((l->mask & mask_must) != mask_must)
1511 if(l->mask & mask_mustnot)
1513 if(maxage && (l->addtime < t - maxage))
1516 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1526 Draws the last few lines of output transparently over the game top
1529 void Con_DrawNotify (void)
1532 float chatstart, notifystart, inputsize;
1534 char temptext[MAX_INPUTLINE];
1538 ConBuffer_FixTimes(&con);
1540 numChatlines = con_chat.integer;
1541 chatpos = con_chatpos.integer;
1543 if (con_notify.integer < 0)
1544 Cvar_SetValueQuick(&con_notify, 0);
1545 if (gamemode == GAME_TRANSFUSION)
1546 v = 8; // vertical offset
1550 // GAME_NEXUIZ: center, otherwise left justify
1551 align = con_notifyalign.value;
1552 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1554 if(gamemode == GAME_NEXUIZ)
1562 // first chat, input line, then notify
1564 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1566 else if(chatpos > 0)
1568 // first notify, then (chatpos-1) empty lines, then chat, then input
1570 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1572 else // if(chatpos < 0)
1574 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1576 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1581 // just notify and input
1583 chatstart = 0; // shut off gcc warning
1586 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, "");
1591 v = chatstart + numChatlines * con_chatsize.value;
1592 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
1595 if (key_dest == key_message)
1597 int colorindex = -1;
1599 // LordHavoc: speedup, and other improvements
1601 dpsnprintf(temptext, sizeof(temptext), "]%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1603 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1605 dpsnprintf(temptext, sizeof(temptext), "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1608 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1609 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1612 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1618 Con_MeasureConsoleLine
1620 Counts the number of lines for a line on the console.
1623 int Con_MeasureConsoleLine(int lineno)
1625 float width = vid_conwidth.value;
1627 con_lineinfo_t *li = &CON_LINES(lineno);
1629 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1632 ti.fontsize = con_textsize.value;
1633 ti.font = FONT_CONSOLE;
1635 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1642 Returns the height of a given console line; calculates it if necessary.
1645 int Con_LineHeight(int i)
1647 con_lineinfo_t *li = &CON_LINES(i);
1651 return li->height = Con_MeasureConsoleLine(i);
1658 Draws a line of the console; returns its height in lines.
1659 If alpha is 0, the line is not drawn, but still wrapped and its height
1663 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1665 float width = vid_conwidth.value;
1667 con_lineinfo_t *li = &CON_LINES(lineno);
1669 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1672 ti.continuationString = "";
1674 ti.fontsize = con_textsize.value;
1675 ti.font = FONT_CONSOLE;
1677 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1682 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1689 Calculates the last visible line index and how much to show of it based on
1693 void Con_LastVisibleLine(int *last, int *limitlast)
1698 if(con_backscroll < 0)
1701 // now count until we saw con_backscroll actual lines
1702 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1704 int h = Con_LineHeight(i);
1706 // line is the last visible line?
1707 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1710 *limitlast = lines_seen + h - con_backscroll;
1717 // if we get here, no line was on screen - scroll so that one line is
1719 con_backscroll = lines_seen - 1;
1720 *last = con.lines_first;
1721 // FIXME uses con in a non abstracted way
1729 Draws the console with the solid background
1730 The typing input line at the bottom should only be drawn if typing is allowed
1733 void Con_DrawConsole (int lines)
1735 int i, last, limitlast;
1741 con_vislines = lines;
1743 // draw the background
1744 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
1745 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);
1748 if(CON_LINES_COUNT > 0)
1750 float ymax = con_vislines - 2 * con_textsize.value;
1751 Con_LastVisibleLine(&last, &limitlast);
1752 y = ymax - con_textsize.value;
1755 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1756 // FIXME uses con in a non abstracted way
1761 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1763 break; // top of console buffer
1765 break; // top of console window
1771 // draw the input prompt, user text, and cursor if desired
1779 Prints not only map filename, but also
1780 its format (q1/q2/q3/hl) and even its message
1782 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1783 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1784 //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
1785 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1786 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1790 int i, k, max, p, o, min;
1793 unsigned char buf[1024];
1795 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1796 t = FS_Search(message, 1, true);
1799 if (t->numfilenames > 1)
1800 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1801 len = (unsigned char *)Z_Malloc(t->numfilenames);
1803 for(max=i=0;i<t->numfilenames;i++)
1805 k = (int)strlen(t->filenames[i]);
1815 for(i=0;i<t->numfilenames;i++)
1817 int lumpofs = 0, lumplen = 0;
1818 char *entities = NULL;
1819 const char *data = NULL;
1821 char entfilename[MAX_QPATH];
1822 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1824 f = FS_OpenVirtualFile(t->filenames[i], true);
1827 memset(buf, 0, 1024);
1828 FS_Read(f, buf, 1024);
1829 if (!memcmp(buf, "IBSP", 4))
1831 p = LittleLong(((int *)buf)[1]);
1832 if (p == Q3BSPVERSION)
1834 q3dheader_t *header = (q3dheader_t *)buf;
1835 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1836 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1838 else if (p == Q2BSPVERSION)
1840 q2dheader_t *header = (q2dheader_t *)buf;
1841 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1842 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1845 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1847 dheader_t *header = (dheader_t *)buf;
1848 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1849 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1853 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1854 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1855 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1856 if (!entities && lumplen >= 10)
1858 FS_Seek(f, lumpofs, SEEK_SET);
1859 entities = (char *)Z_Malloc(lumplen + 1);
1860 FS_Read(f, entities, lumplen);
1864 // if there are entities to parse, a missing message key just
1865 // means there is no title, so clear the message string now
1871 if (!COM_ParseToken_Simple(&data, false, false))
1873 if (com_token[0] == '{')
1875 if (com_token[0] == '}')
1877 // skip leading whitespace
1878 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
1879 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
1880 keyname[l] = com_token[k+l];
1882 if (!COM_ParseToken_Simple(&data, false, false))
1884 if (developer.integer >= 100)
1885 Con_Printf("key: %s %s\n", keyname, com_token);
1886 if (!strcmp(keyname, "message"))
1888 // get the message contents
1889 strlcpy(message, com_token, sizeof(message));
1899 *(t->filenames[i]+len[i]+5) = 0;
1902 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1903 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1904 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1905 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1906 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1908 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1913 k = *(t->filenames[0]+5+p);
1916 for(i=1;i<t->numfilenames;i++)
1917 if(*(t->filenames[i]+5+p) != k)
1921 if(p > o && completedname && completednamebufferlength > 0)
1923 memset(completedname, 0, completednamebufferlength);
1924 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1934 New function for tab-completion system
1935 Added by EvilTypeGuy
1936 MEGA Thanks to Taniwha
1939 void Con_DisplayList(const char **list)
1941 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1942 const char **walk = list;
1945 len = (int)strlen(*walk);
1953 len = (int)strlen(*list);
1954 if (pos + maxlen >= width) {
1960 for (i = 0; i < (maxlen - len); i++)
1972 SanitizeString strips color tags from the string in
1973 and writes the result on string out
1975 void SanitizeString(char *in, char *out)
1979 if(*in == STRING_COLOR_TAG)
1984 out[0] = STRING_COLOR_TAG;
1988 else if (*in >= '0' && *in <= '9') // ^[0-9] found
1995 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
1998 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2000 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2007 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2012 else if (*in != STRING_COLOR_TAG)
2015 *out = qfont_table[*(unsigned char*)in];
2022 // Now it becomes TRICKY :D --blub
2023 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2024 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2025 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2026 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
2027 static int Nicks_matchpos;
2029 // co against <<:BLASTER:>> is true!?
2030 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2034 if(tolower(*a) == tolower(*b))
2048 return (*a < *b) ? -1 : 1;
2052 return (*a < *b) ? -1 : 1;
2056 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2059 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2061 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2062 return Nicks_strncasecmp_nospaces(a, b, a_len);
2063 return strncasecmp(a, b, a_len);
2066 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2068 // ignore non alphanumerics of B
2069 // if A contains a non-alphanumeric, B must contain it as well though!
2072 qboolean alnum_a, alnum_b;
2074 if(tolower(*a) == tolower(*b))
2076 if(*a == 0) // end of both strings, they're equal
2083 // not equal, end of one string?
2088 // ignore non alphanumerics
2089 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2090 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2091 if(!alnum_a) // b must contain this
2092 return (*a < *b) ? -1 : 1;
2095 // otherwise, both are alnum, they're just not equal, return the appropriate number
2097 return (*a < *b) ? -1 : 1;
2103 /* Nicks_CompleteCountPossible
2105 Count the number of possible nicks to complete
2107 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2116 if(!con_nickcompletion.integer)
2119 // changed that to 1
2120 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2123 for(i = 0; i < cl.maxclients; ++i)
2126 if(!cl.scores[p].name[0])
2129 SanitizeString(cl.scores[p].name, name);
2130 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2135 length = strlen(name);
2137 spos = pos - 1; // no need for a minimum of characters :)
2141 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2143 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2144 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2150 if(isCon && spos == 0)
2152 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2158 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2159 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2161 // the sanitized list
2162 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2165 Nicks_matchpos = match;
2168 Nicks_offset[count] = s - (&line[match]);
2169 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2176 void Cmd_CompleteNicksPrint(int count)
2179 for(i = 0; i < count; ++i)
2180 Con_Printf("%s\n", Nicks_list[i]);
2183 void Nicks_CutMatchesNormal(int count)
2185 // cut match 0 down to the longest possible completion
2188 c = strlen(Nicks_sanlist[0]) - 1;
2189 for(i = 1; i < count; ++i)
2191 l = strlen(Nicks_sanlist[i]) - 1;
2195 for(l = 0; l <= c; ++l)
2196 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2202 Nicks_sanlist[0][c+1] = 0;
2203 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2206 unsigned int Nicks_strcleanlen(const char *s)
2211 if( (*s >= 'a' && *s <= 'z') ||
2212 (*s >= 'A' && *s <= 'Z') ||
2213 (*s >= '0' && *s <= '9') ||
2221 void Nicks_CutMatchesAlphaNumeric(int count)
2223 // cut match 0 down to the longest possible completion
2226 char tempstr[sizeof(Nicks_sanlist[0])];
2228 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2230 c = strlen(Nicks_sanlist[0]);
2231 for(i = 0, l = 0; i < (int)c; ++i)
2233 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2234 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2235 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2237 tempstr[l++] = Nicks_sanlist[0][i];
2242 for(i = 1; i < count; ++i)
2245 b = Nicks_sanlist[i];
2255 if(tolower(*a) == tolower(*b))
2261 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2263 // b is alnum, so cut
2270 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2271 Nicks_CutMatchesNormal(count);
2272 //if(!Nicks_sanlist[0][0])
2273 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2275 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2276 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2280 void Nicks_CutMatchesNoSpaces(int count)
2282 // cut match 0 down to the longest possible completion
2285 char tempstr[sizeof(Nicks_sanlist[0])];
2288 c = strlen(Nicks_sanlist[0]);
2289 for(i = 0, l = 0; i < (int)c; ++i)
2291 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2293 tempstr[l++] = Nicks_sanlist[0][i];
2298 for(i = 1; i < count; ++i)
2301 b = Nicks_sanlist[i];
2311 if(tolower(*a) == tolower(*b))
2325 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2326 Nicks_CutMatchesNormal(count);
2327 //if(!Nicks_sanlist[0][0])
2328 //Con_Printf("TS: %s\n", tempstr);
2329 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2331 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2332 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2336 void Nicks_CutMatches(int count)
2338 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2339 Nicks_CutMatchesAlphaNumeric(count);
2340 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2341 Nicks_CutMatchesNoSpaces(count);
2343 Nicks_CutMatchesNormal(count);
2346 const char **Nicks_CompleteBuildList(int count)
2350 // the list is freed by Con_CompleteCommandLine, so create a char**
2351 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2353 for(; bpos < count; ++bpos)
2354 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2356 Nicks_CutMatches(count);
2364 Restores the previous used color, after the autocompleted name.
2366 int Nicks_AddLastColor(char *buffer, int pos)
2368 qboolean quote_added = false;
2370 int color = STRING_COLOR_DEFAULT + '0';
2371 char r = 0, g = 0, b = 0;
2373 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2375 // we'll have to add a quote :)
2376 buffer[pos++] = '\"';
2380 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2382 // add color when no quote was added, or when flags &4?
2384 for(match = Nicks_matchpos-1; match >= 0; --match)
2386 if(buffer[match] == STRING_COLOR_TAG)
2388 if( isdigit(buffer[match+1]) )
2390 color = buffer[match+1];
2393 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2395 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2397 r = buffer[match+2];
2398 g = buffer[match+3];
2399 b = buffer[match+4];
2408 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2410 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2411 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2414 buffer[pos++] = STRING_COLOR_TAG;
2417 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2423 buffer[pos++] = color;
2428 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2431 /*if(!con_nickcompletion.integer)
2432 return; is tested in Nicks_CompletionCountPossible */
2433 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2439 msg = Nicks_list[0];
2440 len = min(size - Nicks_matchpos - 3, strlen(msg));
2441 memcpy(&buffer[Nicks_matchpos], msg, len);
2442 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2443 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2444 buffer[len++] = ' ';
2451 Con_Printf("\n%i possible nicks:\n", n);
2452 Cmd_CompleteNicksPrint(n);
2454 Nicks_CutMatches(n);
2456 msg = Nicks_sanlist[0];
2457 len = min(size - Nicks_matchpos, strlen(msg));
2458 memcpy(&buffer[Nicks_matchpos], msg, len);
2459 buffer[Nicks_matchpos + len] = 0;
2461 return Nicks_matchpos + len;
2468 Con_CompleteCommandLine
2470 New function for tab-completion system
2471 Added by EvilTypeGuy
2472 Thanks to Fett erich@heintz.com
2474 Enhanced to tab-complete map names by [515]
2477 void Con_CompleteCommandLine (void)
2479 const char *cmd = "";
2481 const char **list[4] = {0, 0, 0, 0};
2484 int c, v, a, i, cmd_len, pos, k;
2485 int n; // nicks --blub
2486 const char *space, *patterns;
2488 //find what we want to complete
2493 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2499 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2500 key_line[key_linepos] = 0; //hide them
2502 space = strchr(key_line + 1, ' ');
2503 if(space && pos == (space - key_line) + 1)
2505 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2507 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2508 if(patterns && !*patterns)
2509 patterns = NULL; // get rid of the empty string
2511 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2515 if (GetMapList(s, t, sizeof(t)))
2517 // first move the cursor
2518 key_linepos += (int)strlen(t) - (int)strlen(s);
2520 // and now do the actual work
2522 strlcat(key_line, t, MAX_INPUTLINE);
2523 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2525 // and fix the cursor
2526 if(key_linepos > (int) strlen(key_line))
2527 key_linepos = (int) strlen(key_line);
2536 stringlist_t resultbuf, dirbuf;
2539 // // store completion patterns (space separated) for command foo in con_completion_foo
2540 // set con_completion_foo "foodata/*.foodefault *.foo"
2543 // Note: patterns with slash are always treated as absolute
2544 // patterns; patterns without slash search in the innermost
2545 // directory the user specified. There is no way to "complete into"
2546 // a directory as of now, as directories seem to be unknown to the
2550 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2551 // set con_completion_playdemo "*.dem"
2552 // set con_completion_play "*.wav *.ogg"
2554 // TODO somehow add support for directories; these shall complete
2555 // to their name + an appended slash.
2557 stringlistinit(&resultbuf);
2558 stringlistinit(&dirbuf);
2559 while(COM_ParseToken_Simple(&patterns, false, false))
2562 if(strchr(com_token, '/'))
2564 search = FS_Search(com_token, true, true);
2568 const char *slash = strrchr(s, '/');
2571 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2572 strlcat(t, com_token, sizeof(t));
2573 search = FS_Search(t, true, true);
2576 search = FS_Search(com_token, true, true);
2580 for(i = 0; i < search->numfilenames; ++i)
2581 if(!strncmp(search->filenames[i], s, strlen(s)))
2582 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2583 stringlistappend(&resultbuf, search->filenames[i]);
2584 FS_FreeSearch(search);
2588 // In any case, add directory names
2591 const char *slash = strrchr(s, '/');
2594 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2595 strlcat(t, "*", sizeof(t));
2596 search = FS_Search(t, true, true);
2599 search = FS_Search("*", true, true);
2602 for(i = 0; i < search->numfilenames; ++i)
2603 if(!strncmp(search->filenames[i], s, strlen(s)))
2604 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2605 stringlistappend(&dirbuf, search->filenames[i]);
2606 FS_FreeSearch(search);
2610 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2613 unsigned int matchchars;
2614 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2616 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2619 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2621 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2625 stringlistsort(&resultbuf); // dirbuf is already sorted
2626 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2627 for(i = 0; i < dirbuf.numstrings; ++i)
2629 Con_Printf("%s/\n", dirbuf.strings[i]);
2631 for(i = 0; i < resultbuf.numstrings; ++i)
2633 Con_Printf("%s\n", resultbuf.strings[i]);
2635 matchchars = sizeof(t) - 1;
2636 if(resultbuf.numstrings > 0)
2638 p = resultbuf.strings[0];
2639 q = resultbuf.strings[resultbuf.numstrings - 1];
2640 for(; *p && *p == *q; ++p, ++q);
2641 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2643 if(dirbuf.numstrings > 0)
2645 p = dirbuf.strings[0];
2646 q = dirbuf.strings[dirbuf.numstrings - 1];
2647 for(; *p && *p == *q; ++p, ++q);
2648 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2650 // now p points to the first non-equal character, or to the end
2651 // of resultbuf.strings[0]. We want to append the characters
2652 // from resultbuf.strings[0] to (not including) p as these are
2653 // the unique prefix
2654 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2657 // first move the cursor
2658 key_linepos += (int)strlen(t) - (int)strlen(s);
2660 // and now do the actual work
2662 strlcat(key_line, t, MAX_INPUTLINE);
2663 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2665 // and fix the cursor
2666 if(key_linepos > (int) strlen(key_line))
2667 key_linepos = (int) strlen(key_line);
2669 stringlistfreecontents(&resultbuf);
2670 stringlistfreecontents(&dirbuf);
2672 return; // bail out, when we complete for a command that wants a file name
2677 // Count number of possible matches and print them
2678 c = Cmd_CompleteCountPossible(s);
2681 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2682 Cmd_CompleteCommandPrint(s);
2684 v = Cvar_CompleteCountPossible(s);
2687 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2688 Cvar_CompleteCvarPrint(s);
2690 a = Cmd_CompleteAliasCountPossible(s);
2693 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2694 Cmd_CompleteAliasPrint(s);
2696 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2699 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2700 Cmd_CompleteNicksPrint(n);
2703 if (!(c + v + a + n)) // No possible matches
2706 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2711 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2713 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2715 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2717 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2719 for (cmd_len = (int)strlen(s);;cmd_len++)
2722 for (i = 0; i < 3; i++)
2724 for (l = list[i];*l;l++)
2725 if ((*l)[cmd_len] != cmd[cmd_len])
2727 // all possible matches share this character, so we continue...
2730 // if all matches ended at the same position, stop
2731 // (this means there is only one match)
2737 // prevent a buffer overrun by limiting cmd_len according to remaining space
2738 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2742 memcpy(&key_line[key_linepos], cmd, cmd_len);
2743 key_linepos += cmd_len;
2744 // if there is only one match, add a space after it
2745 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2748 { // was a nick, might have an offset, and needs colors ;) --blub
2749 key_linepos = pos - Nicks_offset[0];
2750 cmd_len = strlen(Nicks_list[0]);
2751 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2753 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2754 key_linepos += cmd_len;
2755 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2756 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2758 key_line[key_linepos++] = ' ';
2762 // use strlcat to avoid a buffer overrun
2763 key_line[key_linepos] = 0;
2764 strlcat(key_line, s2, sizeof(key_line));
2766 // free the command, cvar, and alias lists
2767 for (i = 0; i < 4; i++)
2769 Mem_Free((void *)list[i]);