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 // lines up from bottom to display
36 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
37 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
38 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
40 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
41 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
42 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
44 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
45 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
46 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)"};
47 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
48 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
49 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
50 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
51 cvar_t con_chatsound = {CVAR_SAVE, "con_chatsound","1", "enables chat sound to play on message"};
54 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)"};
56 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)"};
58 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)"};
62 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
63 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
64 "0: add nothing after completion. "
65 "1: add the last color after completion. "
66 "2: add a quote when starting a quote instead of the color. "
67 "4: will replace 1, will force color, even after a quote. "
68 "8: ignore non-alphanumerics. "
69 "16: ignore spaces. "};
70 #define NICKS_ADD_COLOR 1
71 #define NICKS_ADD_QUOTE 2
72 #define NICKS_FORCE_COLOR 4
73 #define NICKS_ALPHANUMERICS_ONLY 8
74 #define NICKS_NO_SPACES 16
76 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
77 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
78 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
83 qboolean con_initialized;
85 // used for server replies to rcon command
86 lhnetsocket_t *rcon_redirect_sock = NULL;
87 lhnetaddress_t *rcon_redirect_dest = NULL;
88 int rcon_redirect_bufferpos = 0;
89 char rcon_redirect_buffer[1400];
91 // generic functions for console buffers
93 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
95 buf->textsize = textsize;
96 buf->text = (char *) Mem_Alloc(mempool, textsize);
97 buf->maxlines = maxlines;
98 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
100 buf->lines_count = 0;
108 void ConBuffer_Clear (conbuffer_t *buf)
110 buf->lines_count = 0;
118 void ConBuffer_Shutdown(conbuffer_t *buf)
121 Mem_Free(buf->lines);
130 Notifies the console code about the current time
131 (and shifts back times of other entries when the time
135 void ConBuffer_FixTimes(conbuffer_t *buf)
138 if(buf->lines_count >= 1)
140 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
143 for(i = 0; i < buf->lines_count; ++i)
144 CONBUFFER_LINES(buf, i).addtime += diff;
153 Deletes the first line from the console history.
156 void ConBuffer_DeleteLine(conbuffer_t *buf)
158 if(buf->lines_count == 0)
161 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
166 ConBuffer_DeleteLastLine
168 Deletes the last line from the console history.
171 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
173 if(buf->lines_count == 0)
182 Checks if there is space for a line of the given length, and if yes, returns a
183 pointer to the start of such a space, and NULL otherwise.
186 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
188 if(len > buf->textsize)
190 if(buf->lines_count == 0)
194 char *firstline_start = buf->lines[buf->lines_first].start;
195 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
196 // the buffer is cyclic, so we first have two cases...
197 if(firstline_start < lastline_onepastend) // buffer is contiguous
200 if(len <= buf->text + buf->textsize - lastline_onepastend)
201 return lastline_onepastend;
203 else if(len <= firstline_start - buf->text)
208 else // buffer has a contiguous hole
210 if(len <= firstline_start - lastline_onepastend)
211 return lastline_onepastend;
222 Appends a given string as a new line to the console.
225 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
230 ConBuffer_FixTimes(buf);
232 if(len >= buf->textsize)
235 // only display end of line.
236 line += len - buf->textsize + 1;
237 len = buf->textsize - 1;
239 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
240 ConBuffer_DeleteLine(buf);
241 memcpy(putpos, line, len);
245 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
247 p = &CONBUFFER_LINES_LAST(buf);
250 p->addtime = cl.time;
252 p->height = -1; // calculate when needed
255 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
259 start = buf->lines_count;
260 for(i = start - 1; i >= 0; --i)
262 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
264 if((l->mask & mask_must) != mask_must)
266 if(l->mask & mask_mustnot)
275 int Con_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
278 for(i = start + 1; i < buf->lines_count; ++i)
280 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
282 if((l->mask & mask_must) != mask_must)
284 if(l->mask & mask_mustnot)
293 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
295 static char copybuf[MAX_INPUTLINE];
296 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
297 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
298 strlcpy(copybuf, l->start, sz);
303 ==============================================================================
307 ==============================================================================
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(void)
333 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
334 log_dest_buffer_pos = 5;
342 void Log_DestBuffer_Flush(void)
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;
594 void Con_ClearNotify (void)
597 for(i = 0; i < CON_LINES_COUNT; ++i)
598 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
607 void Con_MessageMode_f (void)
609 key_dest = key_message;
610 chat_mode = 0; // "say"
621 void Con_MessageMode2_f (void)
623 key_dest = key_message;
624 chat_mode = 1; // "say_team"
634 void Con_CommandMode_f (void)
636 key_dest = key_message;
639 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
640 chat_bufferlen = strlen(chat_buffer);
642 chat_mode = -1; // command
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);
712 void Con_Clear_f (void)
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);
754 Cvar_RegisterVariable (&con_chatsound);
757 Cvar_RegisterVariable (&con_nickcompletion);
758 Cvar_RegisterVariable (&con_nickcompletion_flags);
760 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
761 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
762 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
764 // register our commands
765 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
766 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
767 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
768 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
769 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
770 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
771 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
773 con_initialized = true;
774 Con_DPrint("Console initialized.\n");
777 void Con_Shutdown (void)
779 ConBuffer_Shutdown(&con);
786 Handles cursor positioning, line wrapping, etc
787 All console printing must go through this in order to be displayed
788 If no console is visible, the notify window will pop up.
791 void Con_PrintToHistory(const char *txt, int mask)
794 // \n goes to next line
795 // \r deletes current line and makes a new one
797 static int cr_pending = 0;
798 static char buf[CON_TEXTSIZE];
799 static int bufpos = 0;
801 if(!con.text) // FIXME uses a non-abstracted property of con
808 ConBuffer_DeleteLastLine(&con);
816 ConBuffer_AddLine(&con, buf, bufpos, mask);
821 ConBuffer_AddLine(&con, buf, bufpos, mask);
825 buf[bufpos++] = *txt;
826 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
828 ConBuffer_AddLine(&con, buf, bufpos, mask);
836 /*! The translation table between the graphical font and plain ASCII --KB */
837 static char qfont_table[256] = {
838 '\0', '#', '#', '#', '#', '.', '#', '#',
839 '#', 9, 10, '#', ' ', 13, '.', '.',
840 '[', ']', '0', '1', '2', '3', '4', '5',
841 '6', '7', '8', '9', '.', '<', '=', '>',
842 ' ', '!', '"', '#', '$', '%', '&', '\'',
843 '(', ')', '*', '+', ',', '-', '.', '/',
844 '0', '1', '2', '3', '4', '5', '6', '7',
845 '8', '9', ':', ';', '<', '=', '>', '?',
846 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
847 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
848 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
849 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
850 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
851 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
852 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
853 'x', 'y', 'z', '{', '|', '}', '~', '<',
855 '<', '=', '>', '#', '#', '.', '#', '#',
856 '#', '#', ' ', '#', ' ', '>', '.', '.',
857 '[', ']', '0', '1', '2', '3', '4', '5',
858 '6', '7', '8', '9', '.', '<', '=', '>',
859 ' ', '!', '"', '#', '$', '%', '&', '\'',
860 '(', ')', '*', '+', ',', '-', '.', '/',
861 '0', '1', '2', '3', '4', '5', '6', '7',
862 '8', '9', ':', ';', '<', '=', '>', '?',
863 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
864 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
865 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
866 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
867 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
868 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
869 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
870 'x', 'y', 'z', '{', '|', '}', '~', '<'
873 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest)
875 rcon_redirect_sock = sock;
876 rcon_redirect_dest = dest;
877 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
878 rcon_redirect_bufferpos = 5;
881 void Con_Rcon_Redirect_Flush(void)
883 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
884 NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
885 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
886 rcon_redirect_bufferpos = 5;
889 void Con_Rcon_Redirect_End(void)
891 Con_Rcon_Redirect_Flush();
892 rcon_redirect_dest = NULL;
893 rcon_redirect_sock = NULL;
896 void Con_Rcon_Redirect_Abort(void)
898 rcon_redirect_dest = NULL;
899 rcon_redirect_sock = NULL;
907 /// 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
1005 extern cvar_t timestamps;
1006 extern cvar_t timeformat;
1007 extern qboolean sys_nostdout;
1008 void Con_Print(const char *msg)
1010 static int mask = 0;
1011 static int index = 0;
1012 static char line[MAX_INPUTLINE];
1016 Con_Rcon_AddChar(*msg);
1017 // if this is the beginning of a new line, print timestamp
1020 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1022 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1023 line[index++] = STRING_COLOR_TAG;
1024 // assert( STRING_COLOR_DEFAULT < 10 )
1025 line[index++] = STRING_COLOR_DEFAULT + '0';
1026 // special color codes for chat messages must always come first
1027 // for Con_PrintToHistory to work properly
1028 if (*msg == 1 || *msg == 2)
1033 if (con_chatsound.value)
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");
1050 mask = CON_MASK_CHAT;
1052 line[index++] = STRING_COLOR_TAG;
1053 line[index++] = '3';
1055 Con_Rcon_AddChar(*msg);
1058 for (;*timestamp;index++, timestamp++)
1059 if (index < (int)sizeof(line) - 2)
1060 line[index] = *timestamp;
1062 // append the character
1063 line[index++] = *msg;
1064 // if this is a newline character, we have a complete line to print
1065 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1067 // terminate the line
1071 // send to scrollable buffer
1072 if (con_initialized && cls.state != ca_dedicated)
1074 Con_PrintToHistory(line, mask);
1077 // send to terminal or dedicated server window
1081 if(sys_specialcharactertranslation.integer)
1083 for (p = (unsigned char *) line;*p; p++)
1084 *p = qfont_table[*p];
1087 if(sys_colortranslation.integer == 1) // ANSI
1089 static char printline[MAX_INPUTLINE * 4 + 3];
1090 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1091 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1096 for(in = line, out = printline; *in; ++in)
1100 case STRING_COLOR_TAG:
1101 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1103 char r = tolower(in[2]);
1104 char g = tolower(in[3]);
1105 char b = tolower(in[4]);
1106 // it's a hex digit already, so the else part needs no check --blub
1107 if(isdigit(r)) r -= '0';
1109 if(isdigit(g)) g -= '0';
1111 if(isdigit(b)) b -= '0';
1114 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1115 in += 3; // 3 only, the switch down there does the fourth
1122 case STRING_COLOR_TAG:
1124 *out++ = STRING_COLOR_TAG;
1130 if(lastcolor == 0) break; else lastcolor = 0;
1131 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1136 if(lastcolor == 1) break; else lastcolor = 1;
1137 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1142 if(lastcolor == 2) break; else lastcolor = 2;
1143 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1148 if(lastcolor == 3) break; else lastcolor = 3;
1149 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1154 if(lastcolor == 4) break; else lastcolor = 4;
1155 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1160 if(lastcolor == 5) break; else lastcolor = 5;
1161 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1166 if(lastcolor == 6) break; else lastcolor = 6;
1167 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1172 // bold normal color
1174 if(lastcolor == 8) break; else lastcolor = 8;
1175 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1178 *out++ = STRING_COLOR_TAG;
1185 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1202 Sys_PrintToTerminal(printline);
1204 else if(sys_colortranslation.integer == 2) // Quake
1206 Sys_PrintToTerminal(line);
1210 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1213 for(in = line, out = printline; *in; ++in)
1217 case STRING_COLOR_TAG:
1220 case STRING_COLOR_RGB_TAG_CHAR:
1221 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1226 *out++ = STRING_COLOR_TAG;
1227 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1230 case STRING_COLOR_TAG:
1232 *out++ = STRING_COLOR_TAG;
1247 *out++ = STRING_COLOR_TAG;
1257 Sys_PrintToTerminal(printline);
1260 // empty the line buffer
1272 void Con_Printf(const char *fmt, ...)
1275 char msg[MAX_INPUTLINE];
1277 va_start(argptr,fmt);
1278 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1289 void Con_DPrint(const char *msg)
1291 if (!developer.integer)
1292 return; // don't confuse non-developers with techie stuff...
1301 void Con_DPrintf(const char *fmt, ...)
1304 char msg[MAX_INPUTLINE];
1306 if (!developer.integer)
1307 return; // don't confuse non-developers with techie stuff...
1309 va_start(argptr,fmt);
1310 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1318 ==============================================================================
1322 ==============================================================================
1329 The input line scrolls horizontally if typing goes beyond the right edge
1331 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1334 void Con_DrawInput (void)
1338 char editlinecopy[MAX_INPUTLINE+1], *text;
1341 if (!key_consoleactive)
1342 return; // don't draw anything
1344 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1345 text = editlinecopy;
1347 // Advanced Console Editing by Radix radix@planetquake.com
1348 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1349 // use strlen of edit_line instead of key_linepos to allow editing
1350 // of early characters w/o erasing
1352 y = (int)strlen(text);
1354 // fill out remainder with spaces
1355 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1358 // add the cursor frame
1359 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1360 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1362 // text[key_linepos + 1] = 0;
1364 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1369 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 );
1372 // key_line[key_linepos] = 0;
1378 float alignment; // 0 = left, 0.5 = center, 1 = right
1384 const char *continuationString;
1387 int colorindex; // init to -1
1391 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1393 con_text_info_t *ti = (con_text_info_t *) passthrough;
1396 ti->colorindex = -1;
1397 return ti->fontsize * ti->font->maxwidth;
1400 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1401 else if(maxWidth == -1)
1402 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1405 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1406 // Note: this is NOT a Con_Printf, as it could print recursively
1411 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1417 (void) isContinuation;
1421 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1423 con_text_info_t *ti = (con_text_info_t *) passthrough;
1425 if(ti->y < ti->ymin - 0.001)
1427 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1431 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1432 if(isContinuation && *ti->continuationString)
1433 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);
1435 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);
1438 ti->y += ti->fontsize;
1442 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)
1446 int maxlines = (int) floor(height / fontsize + 0.01f);
1449 int continuationWidth = 0;
1451 double t = cl.time; // saved so it won't change
1454 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1455 ti.fontsize = fontsize;
1456 ti.alignment = alignment_x;
1459 ti.ymax = y + height;
1460 ti.continuationString = continuationString;
1463 Con_WordWidthFunc(&ti, NULL, &l, -1);
1464 l = strlen(continuationString);
1465 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1467 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1468 startidx = CON_LINES_COUNT;
1469 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1471 con_lineinfo_t *l = &CON_LINES(i);
1474 if((l->mask & mask_must) != mask_must)
1476 if(l->mask & mask_mustnot)
1478 if(maxage && (l->addtime < t - maxage))
1482 // Calculate its actual height...
1483 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1484 if(lines + mylines >= maxlines)
1486 nskip = lines + mylines - maxlines;
1495 // then center according to the calculated amount of lines...
1497 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1499 // then actually draw
1500 for(i = startidx; i < CON_LINES_COUNT; ++i)
1502 con_lineinfo_t *l = &CON_LINES(i);
1504 if((l->mask & mask_must) != mask_must)
1506 if(l->mask & mask_mustnot)
1508 if(maxage && (l->addtime < t - maxage))
1511 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1521 Draws the last few lines of output transparently over the game top
1524 void Con_DrawNotify (void)
1527 float chatstart, notifystart, inputsize;
1529 char temptext[MAX_INPUTLINE];
1533 ConBuffer_FixTimes(&con);
1535 numChatlines = con_chat.integer;
1536 chatpos = con_chatpos.integer;
1538 if (con_notify.integer < 0)
1539 Cvar_SetValueQuick(&con_notify, 0);
1540 if (gamemode == GAME_TRANSFUSION)
1541 v = 8; // vertical offset
1545 // GAME_NEXUIZ: center, otherwise left justify
1546 align = con_notifyalign.value;
1547 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1549 if(gamemode == GAME_NEXUIZ)
1557 // first chat, input line, then notify
1559 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1561 else if(chatpos > 0)
1563 // first notify, then (chatpos-1) empty lines, then chat, then input
1565 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1567 else // if(chatpos < 0)
1569 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1571 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1576 // just notify and input
1578 chatstart = 0; // shut off gcc warning
1581 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, "");
1586 v = chatstart + numChatlines * con_chatsize.value;
1587 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
1590 if (key_dest == key_message)
1592 int colorindex = -1;
1594 // LordHavoc: speedup, and other improvements
1596 dpsnprintf(temptext, sizeof(temptext), "]%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1598 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1600 dpsnprintf(temptext, sizeof(temptext), "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1603 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1604 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1607 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1613 Con_MeasureConsoleLine
1615 Counts the number of lines for a line on the console.
1618 int Con_MeasureConsoleLine(int lineno)
1620 float width = vid_conwidth.value;
1622 con_lineinfo_t *li = &CON_LINES(lineno);
1624 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1627 ti.fontsize = con_textsize.value;
1628 ti.font = FONT_CONSOLE;
1630 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1637 Returns the height of a given console line; calculates it if necessary.
1640 int Con_LineHeight(int i)
1642 con_lineinfo_t *li = &CON_LINES(i);
1646 return li->height = Con_MeasureConsoleLine(i);
1653 Draws a line of the console; returns its height in lines.
1654 If alpha is 0, the line is not drawn, but still wrapped and its height
1658 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1660 float width = vid_conwidth.value;
1662 con_lineinfo_t *li = &CON_LINES(lineno);
1664 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1667 ti.continuationString = "";
1669 ti.fontsize = con_textsize.value;
1670 ti.font = FONT_CONSOLE;
1672 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1677 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1684 Calculates the last visible line index and how much to show of it based on
1688 void Con_LastVisibleLine(int *last, int *limitlast)
1693 if(con_backscroll < 0)
1696 // now count until we saw con_backscroll actual lines
1697 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1699 int h = Con_LineHeight(i);
1701 // line is the last visible line?
1702 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1705 *limitlast = lines_seen + h - con_backscroll;
1712 // if we get here, no line was on screen - scroll so that one line is
1714 con_backscroll = lines_seen - 1;
1715 *last = con.lines_first;
1716 // FIXME uses con in a non abstracted way
1724 Draws the console with the solid background
1725 The typing input line at the bottom should only be drawn if typing is allowed
1728 void Con_DrawConsole (int lines)
1730 int i, last, limitlast;
1736 con_vislines = lines;
1738 // draw the background
1739 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
1740 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);
1743 if(CON_LINES_COUNT > 0)
1745 float ymax = con_vislines - 2 * con_textsize.value;
1746 Con_LastVisibleLine(&last, &limitlast);
1747 y = ymax - con_textsize.value;
1750 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1751 // FIXME uses con in a non abstracted way
1756 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1758 break; // top of console buffer
1760 break; // top of console window
1766 // draw the input prompt, user text, and cursor if desired
1774 Prints not only map filename, but also
1775 its format (q1/q2/q3/hl) and even its message
1777 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1778 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1779 //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
1780 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1781 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1785 int i, k, max, p, o, min;
1788 unsigned char buf[1024];
1790 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1791 t = FS_Search(message, 1, true);
1794 if (t->numfilenames > 1)
1795 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1796 len = (unsigned char *)Z_Malloc(t->numfilenames);
1798 for(max=i=0;i<t->numfilenames;i++)
1800 k = (int)strlen(t->filenames[i]);
1810 for(i=0;i<t->numfilenames;i++)
1812 int lumpofs = 0, lumplen = 0;
1813 char *entities = NULL;
1814 const char *data = NULL;
1816 char entfilename[MAX_QPATH];
1817 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1819 f = FS_OpenVirtualFile(t->filenames[i], true);
1822 memset(buf, 0, 1024);
1823 FS_Read(f, buf, 1024);
1824 if (!memcmp(buf, "IBSP", 4))
1826 p = LittleLong(((int *)buf)[1]);
1827 if (p == Q3BSPVERSION)
1829 q3dheader_t *header = (q3dheader_t *)buf;
1830 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1831 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1833 else if (p == Q2BSPVERSION)
1835 q2dheader_t *header = (q2dheader_t *)buf;
1836 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1837 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1840 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1842 dheader_t *header = (dheader_t *)buf;
1843 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1844 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1848 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1849 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1850 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1851 if (!entities && lumplen >= 10)
1853 FS_Seek(f, lumpofs, SEEK_SET);
1854 entities = (char *)Z_Malloc(lumplen + 1);
1855 FS_Read(f, entities, lumplen);
1859 // if there are entities to parse, a missing message key just
1860 // means there is no title, so clear the message string now
1866 if (!COM_ParseToken_Simple(&data, false, false))
1868 if (com_token[0] == '{')
1870 if (com_token[0] == '}')
1872 // skip leading whitespace
1873 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
1874 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
1875 keyname[l] = com_token[k+l];
1877 if (!COM_ParseToken_Simple(&data, false, false))
1879 if (developer.integer >= 100)
1880 Con_Printf("key: %s %s\n", keyname, com_token);
1881 if (!strcmp(keyname, "message"))
1883 // get the message contents
1884 strlcpy(message, com_token, sizeof(message));
1894 *(t->filenames[i]+len[i]+5) = 0;
1897 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1898 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1899 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1900 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1901 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1903 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1908 k = *(t->filenames[0]+5+p);
1911 for(i=1;i<t->numfilenames;i++)
1912 if(*(t->filenames[i]+5+p) != k)
1916 if(p > o && completedname && completednamebufferlength > 0)
1918 memset(completedname, 0, completednamebufferlength);
1919 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1929 New function for tab-completion system
1930 Added by EvilTypeGuy
1931 MEGA Thanks to Taniwha
1934 void Con_DisplayList(const char **list)
1936 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1937 const char **walk = list;
1940 len = (int)strlen(*walk);
1948 len = (int)strlen(*list);
1949 if (pos + maxlen >= width) {
1955 for (i = 0; i < (maxlen - len); i++)
1967 SanitizeString strips color tags from the string in
1968 and writes the result on string out
1970 void SanitizeString(char *in, char *out)
1974 if(*in == STRING_COLOR_TAG)
1979 out[0] = STRING_COLOR_TAG;
1983 else if (*in >= '0' && *in <= '9') // ^[0-9] found
1990 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
1993 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
1995 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2002 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2007 else if (*in != STRING_COLOR_TAG)
2010 *out = qfont_table[*(unsigned char*)in];
2017 // Now it becomes TRICKY :D --blub
2018 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2019 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2020 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2021 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
2022 static int Nicks_matchpos;
2024 // co against <<:BLASTER:>> is true!?
2025 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2029 if(tolower(*a) == tolower(*b))
2043 return (*a < *b) ? -1 : 1;
2047 return (*a < *b) ? -1 : 1;
2051 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2054 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2056 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2057 return Nicks_strncasecmp_nospaces(a, b, a_len);
2058 return strncasecmp(a, b, a_len);
2061 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2063 // ignore non alphanumerics of B
2064 // if A contains a non-alphanumeric, B must contain it as well though!
2067 qboolean alnum_a, alnum_b;
2069 if(tolower(*a) == tolower(*b))
2071 if(*a == 0) // end of both strings, they're equal
2078 // not equal, end of one string?
2083 // ignore non alphanumerics
2084 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2085 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2086 if(!alnum_a) // b must contain this
2087 return (*a < *b) ? -1 : 1;
2090 // otherwise, both are alnum, they're just not equal, return the appropriate number
2092 return (*a < *b) ? -1 : 1;
2098 /* Nicks_CompleteCountPossible
2100 Count the number of possible nicks to complete
2102 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2111 if(!con_nickcompletion.integer)
2114 // changed that to 1
2115 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2118 for(i = 0; i < cl.maxclients; ++i)
2121 if(!cl.scores[p].name[0])
2124 SanitizeString(cl.scores[p].name, name);
2125 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2130 length = strlen(name);
2132 spos = pos - 1; // no need for a minimum of characters :)
2136 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2138 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2139 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2145 if(isCon && spos == 0)
2147 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2153 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2154 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2156 // the sanitized list
2157 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2160 Nicks_matchpos = match;
2163 Nicks_offset[count] = s - (&line[match]);
2164 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2171 void Cmd_CompleteNicksPrint(int count)
2174 for(i = 0; i < count; ++i)
2175 Con_Printf("%s\n", Nicks_list[i]);
2178 void Nicks_CutMatchesNormal(int count)
2180 // cut match 0 down to the longest possible completion
2183 c = strlen(Nicks_sanlist[0]) - 1;
2184 for(i = 1; i < count; ++i)
2186 l = strlen(Nicks_sanlist[i]) - 1;
2190 for(l = 0; l <= c; ++l)
2191 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2197 Nicks_sanlist[0][c+1] = 0;
2198 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2201 unsigned int Nicks_strcleanlen(const char *s)
2206 if( (*s >= 'a' && *s <= 'z') ||
2207 (*s >= 'A' && *s <= 'Z') ||
2208 (*s >= '0' && *s <= '9') ||
2216 void Nicks_CutMatchesAlphaNumeric(int count)
2218 // cut match 0 down to the longest possible completion
2221 char tempstr[sizeof(Nicks_sanlist[0])];
2223 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2225 c = strlen(Nicks_sanlist[0]);
2226 for(i = 0, l = 0; i < (int)c; ++i)
2228 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2229 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2230 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2232 tempstr[l++] = Nicks_sanlist[0][i];
2237 for(i = 1; i < count; ++i)
2240 b = Nicks_sanlist[i];
2250 if(tolower(*a) == tolower(*b))
2256 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2258 // b is alnum, so cut
2265 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2266 Nicks_CutMatchesNormal(count);
2267 //if(!Nicks_sanlist[0][0])
2268 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2270 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2271 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2275 void Nicks_CutMatchesNoSpaces(int count)
2277 // cut match 0 down to the longest possible completion
2280 char tempstr[sizeof(Nicks_sanlist[0])];
2283 c = strlen(Nicks_sanlist[0]);
2284 for(i = 0, l = 0; i < (int)c; ++i)
2286 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2288 tempstr[l++] = Nicks_sanlist[0][i];
2293 for(i = 1; i < count; ++i)
2296 b = Nicks_sanlist[i];
2306 if(tolower(*a) == tolower(*b))
2320 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2321 Nicks_CutMatchesNormal(count);
2322 //if(!Nicks_sanlist[0][0])
2323 //Con_Printf("TS: %s\n", tempstr);
2324 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2326 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2327 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2331 void Nicks_CutMatches(int count)
2333 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2334 Nicks_CutMatchesAlphaNumeric(count);
2335 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2336 Nicks_CutMatchesNoSpaces(count);
2338 Nicks_CutMatchesNormal(count);
2341 const char **Nicks_CompleteBuildList(int count)
2345 // the list is freed by Con_CompleteCommandLine, so create a char**
2346 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2348 for(; bpos < count; ++bpos)
2349 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2351 Nicks_CutMatches(count);
2359 Restores the previous used color, after the autocompleted name.
2361 int Nicks_AddLastColor(char *buffer, int pos)
2363 qboolean quote_added = false;
2365 int color = STRING_COLOR_DEFAULT + '0';
2366 char r = 0, g = 0, b = 0;
2368 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2370 // we'll have to add a quote :)
2371 buffer[pos++] = '\"';
2375 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2377 // add color when no quote was added, or when flags &4?
2379 for(match = Nicks_matchpos-1; match >= 0; --match)
2381 if(buffer[match] == STRING_COLOR_TAG)
2383 if( isdigit(buffer[match+1]) )
2385 color = buffer[match+1];
2388 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2390 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2392 r = buffer[match+2];
2393 g = buffer[match+3];
2394 b = buffer[match+4];
2403 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2405 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2406 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2409 buffer[pos++] = STRING_COLOR_TAG;
2412 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2418 buffer[pos++] = color;
2423 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2426 /*if(!con_nickcompletion.integer)
2427 return; is tested in Nicks_CompletionCountPossible */
2428 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2434 msg = Nicks_list[0];
2435 len = min(size - Nicks_matchpos - 3, strlen(msg));
2436 memcpy(&buffer[Nicks_matchpos], msg, len);
2437 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2438 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2439 buffer[len++] = ' ';
2446 Con_Printf("\n%i possible nicks:\n", n);
2447 Cmd_CompleteNicksPrint(n);
2449 Nicks_CutMatches(n);
2451 msg = Nicks_sanlist[0];
2452 len = min(size - Nicks_matchpos, strlen(msg));
2453 memcpy(&buffer[Nicks_matchpos], msg, len);
2454 buffer[Nicks_matchpos + len] = 0;
2456 return Nicks_matchpos + len;
2463 Con_CompleteCommandLine
2465 New function for tab-completion system
2466 Added by EvilTypeGuy
2467 Thanks to Fett erich@heintz.com
2469 Enhanced to tab-complete map names by [515]
2472 void Con_CompleteCommandLine (void)
2474 const char *cmd = "";
2476 const char **list[4] = {0, 0, 0, 0};
2479 int c, v, a, i, cmd_len, pos, k;
2480 int n; // nicks --blub
2481 const char *space, *patterns;
2483 //find what we want to complete
2488 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2494 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2495 key_line[key_linepos] = 0; //hide them
2497 space = strchr(key_line + 1, ' ');
2498 if(space && pos == (space - key_line) + 1)
2500 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2502 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2503 if(patterns && !*patterns)
2504 patterns = NULL; // get rid of the empty string
2506 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2510 if (GetMapList(s, t, sizeof(t)))
2512 // first move the cursor
2513 key_linepos += (int)strlen(t) - (int)strlen(s);
2515 // and now do the actual work
2517 strlcat(key_line, t, MAX_INPUTLINE);
2518 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2520 // and fix the cursor
2521 if(key_linepos > (int) strlen(key_line))
2522 key_linepos = (int) strlen(key_line);
2531 stringlist_t resultbuf, dirbuf;
2534 // // store completion patterns (space separated) for command foo in con_completion_foo
2535 // set con_completion_foo "foodata/*.foodefault *.foo"
2538 // Note: patterns with slash are always treated as absolute
2539 // patterns; patterns without slash search in the innermost
2540 // directory the user specified. There is no way to "complete into"
2541 // a directory as of now, as directories seem to be unknown to the
2545 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2546 // set con_completion_playdemo "*.dem"
2547 // set con_completion_play "*.wav *.ogg"
2549 // TODO somehow add support for directories; these shall complete
2550 // to their name + an appended slash.
2552 stringlistinit(&resultbuf);
2553 stringlistinit(&dirbuf);
2554 while(COM_ParseToken_Simple(&patterns, false, false))
2557 if(strchr(com_token, '/'))
2559 search = FS_Search(com_token, true, true);
2563 const char *slash = strrchr(s, '/');
2566 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2567 strlcat(t, com_token, sizeof(t));
2568 search = FS_Search(t, true, true);
2571 search = FS_Search(com_token, true, true);
2575 for(i = 0; i < search->numfilenames; ++i)
2576 if(!strncmp(search->filenames[i], s, strlen(s)))
2577 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2578 stringlistappend(&resultbuf, search->filenames[i]);
2579 FS_FreeSearch(search);
2583 // In any case, add directory names
2586 const char *slash = strrchr(s, '/');
2589 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2590 strlcat(t, "*", sizeof(t));
2591 search = FS_Search(t, true, true);
2594 search = FS_Search("*", true, true);
2597 for(i = 0; i < search->numfilenames; ++i)
2598 if(!strncmp(search->filenames[i], s, strlen(s)))
2599 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2600 stringlistappend(&dirbuf, search->filenames[i]);
2601 FS_FreeSearch(search);
2605 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2608 unsigned int matchchars;
2609 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2611 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2614 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2616 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2620 stringlistsort(&resultbuf); // dirbuf is already sorted
2621 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2622 for(i = 0; i < dirbuf.numstrings; ++i)
2624 Con_Printf("%s/\n", dirbuf.strings[i]);
2626 for(i = 0; i < resultbuf.numstrings; ++i)
2628 Con_Printf("%s\n", resultbuf.strings[i]);
2630 matchchars = sizeof(t) - 1;
2631 if(resultbuf.numstrings > 0)
2633 p = resultbuf.strings[0];
2634 q = resultbuf.strings[resultbuf.numstrings - 1];
2635 for(; *p && *p == *q; ++p, ++q);
2636 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2638 if(dirbuf.numstrings > 0)
2640 p = dirbuf.strings[0];
2641 q = dirbuf.strings[dirbuf.numstrings - 1];
2642 for(; *p && *p == *q; ++p, ++q);
2643 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2645 // now p points to the first non-equal character, or to the end
2646 // of resultbuf.strings[0]. We want to append the characters
2647 // from resultbuf.strings[0] to (not including) p as these are
2648 // the unique prefix
2649 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2652 // first move the cursor
2653 key_linepos += (int)strlen(t) - (int)strlen(s);
2655 // and now do the actual work
2657 strlcat(key_line, t, MAX_INPUTLINE);
2658 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2660 // and fix the cursor
2661 if(key_linepos > (int) strlen(key_line))
2662 key_linepos = (int) strlen(key_line);
2664 stringlistfreecontents(&resultbuf);
2665 stringlistfreecontents(&dirbuf);
2667 return; // bail out, when we complete for a command that wants a file name
2672 // Count number of possible matches and print them
2673 c = Cmd_CompleteCountPossible(s);
2676 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2677 Cmd_CompleteCommandPrint(s);
2679 v = Cvar_CompleteCountPossible(s);
2682 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2683 Cvar_CompleteCvarPrint(s);
2685 a = Cmd_CompleteAliasCountPossible(s);
2688 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2689 Cmd_CompleteAliasPrint(s);
2691 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2694 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2695 Cmd_CompleteNicksPrint(n);
2698 if (!(c + v + a + n)) // No possible matches
2701 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2706 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2708 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2710 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2712 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2714 for (cmd_len = (int)strlen(s);;cmd_len++)
2717 for (i = 0; i < 3; i++)
2719 for (l = list[i];*l;l++)
2720 if ((*l)[cmd_len] != cmd[cmd_len])
2722 // all possible matches share this character, so we continue...
2725 // if all matches ended at the same position, stop
2726 // (this means there is only one match)
2732 // prevent a buffer overrun by limiting cmd_len according to remaining space
2733 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2737 memcpy(&key_line[key_linepos], cmd, cmd_len);
2738 key_linepos += cmd_len;
2739 // if there is only one match, add a space after it
2740 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2743 { // was a nick, might have an offset, and needs colors ;) --blub
2744 key_linepos = pos - Nicks_offset[0];
2745 cmd_len = strlen(Nicks_list[0]);
2746 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2748 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2749 key_linepos += cmd_len;
2750 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2751 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2753 key_line[key_linepos++] = ' ';
2757 // use strlcat to avoid a buffer overrun
2758 key_line[key_linepos] = 0;
2759 strlcat(key_line, s2, sizeof(key_line));
2761 // free the command, cvar, and alias lists
2762 for (i = 0; i < 4; i++)
2764 Mem_Free((void *)list[i]);