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 ==============================================================================
314 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
315 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"};
316 char log_dest_buffer[1400]; // UDP packet
317 size_t log_dest_buffer_pos;
318 unsigned int log_dest_buffer_appending;
319 char crt_log_file [MAX_OSPATH] = "";
320 qfile_t* logfile = NULL;
322 unsigned char* logqueue = NULL;
324 size_t logq_size = 0;
326 void Log_ConPrint (const char *msg);
333 static void Log_DestBuffer_Init()
335 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
336 log_dest_buffer_pos = 5;
344 void Log_DestBuffer_Flush()
346 lhnetaddress_t log_dest_addr;
347 lhnetsocket_t *log_dest_socket;
348 const char *s = log_dest_udp.string;
349 qboolean have_opened_temp_sockets = false;
350 if(s) if(log_dest_buffer_pos > 5)
352 ++log_dest_buffer_appending;
353 log_dest_buffer[log_dest_buffer_pos++] = 0;
355 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
357 have_opened_temp_sockets = true;
358 NetConn_OpenServerPorts(true);
361 while(COM_ParseToken_Console(&s))
362 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
364 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
366 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
368 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
371 if(have_opened_temp_sockets)
372 NetConn_CloseServerPorts();
373 --log_dest_buffer_appending;
375 log_dest_buffer_pos = 0;
383 const char* Log_Timestamp (const char *desc)
385 static char timestamp [128];
392 char timestring [64];
394 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
397 localtime_s (&crt_tm, &crt_time);
398 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
400 crt_tm = localtime (&crt_time);
401 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
405 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
407 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
420 if (logfile != NULL || log_file.string[0] == '\0')
423 logfile = FS_OpenRealFile(log_file.string, "a", false);
426 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
427 FS_Print (logfile, Log_Timestamp ("Log started"));
437 void Log_Close (void)
442 FS_Print (logfile, Log_Timestamp ("Log stopped"));
443 FS_Print (logfile, "\n");
447 crt_log_file[0] = '\0';
456 void Log_Start (void)
462 // Dump the contents of the log queue into the log file and free it
463 if (logqueue != NULL)
465 unsigned char *temp = logqueue;
470 FS_Write (logfile, temp, logq_ind);
471 if(*log_dest_udp.string)
473 for(pos = 0; pos < logq_ind; )
475 if(log_dest_buffer_pos == 0)
476 Log_DestBuffer_Init();
477 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
478 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
479 log_dest_buffer_pos += n;
480 Log_DestBuffer_Flush();
497 void Log_ConPrint (const char *msg)
499 static qboolean inprogress = false;
501 // don't allow feedback loops with memory error reports
506 // Until the host is completely initialized, we maintain a log queue
507 // to store the messages, since the log can't be started before
508 if (logqueue != NULL)
510 size_t remain = logq_size - logq_ind;
511 size_t len = strlen (msg);
513 // If we need to enlarge the log queue
516 size_t factor = ((logq_ind + len) / logq_size) + 1;
517 unsigned char* newqueue;
520 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
521 memcpy (newqueue, logqueue, logq_ind);
524 remain = logq_size - logq_ind;
526 memcpy (&logqueue[logq_ind], msg, len);
533 // Check if log_file has changed
534 if (strcmp (crt_log_file, log_file.string) != 0)
540 // If a log file is available
542 FS_Print (logfile, msg);
553 void Log_Printf (const char *logfilename, const char *fmt, ...)
557 file = FS_OpenRealFile(logfilename, "a", true);
562 va_start (argptr, fmt);
563 FS_VPrintf (file, fmt, argptr);
572 ==============================================================================
576 ==============================================================================
584 void Con_ToggleConsole_f (void)
586 // toggle the 'user wants console' bit
587 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
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"
623 void Con_MessageMode2_f (void)
625 key_dest = key_message;
626 chat_mode = 1; // "say_team"
636 void Con_CommandMode_f (void)
638 key_dest = key_message;
641 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
642 chat_bufferlen = strlen(chat_buffer);
644 chat_mode = -1; // command
652 void Con_CheckResize (void)
657 f = bound(1, con_textsize.value, 128);
658 if(f != con_textsize.value)
659 Cvar_SetValueQuick(&con_textsize, f);
660 width = (int)floor(vid_conwidth.value / con_textsize.value);
661 width = bound(1, width, con.textsize/4);
662 // FIXME uses con in a non abstracted way
664 if (width == con_linewidth)
667 con_linewidth = width;
669 for(i = 0; i < CON_LINES_COUNT; ++i)
670 CON_LINES(i).height = -1; // recalculate when next needed
676 //[515]: the simplest command ever
677 //LordHavoc: not so simple after I made it print usage...
678 static void Con_Maps_f (void)
682 Con_Printf("usage: maps [mapnameprefix]\n");
685 else if (Cmd_Argc() == 2)
686 GetMapList(Cmd_Argv(1), NULL, 0);
688 GetMapList("", NULL, 0);
691 void Con_ConDump_f (void)
697 Con_Printf("usage: condump <filename>\n");
700 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
703 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
706 for(i = 0; i < CON_LINES_COUNT; ++i)
708 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
709 FS_Write(file, "\n", 1);
716 ConBuffer_Clear(&con);
727 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
729 // Allocate a log queue, this will be freed after configs are parsed
730 logq_size = MAX_INPUTLINE;
731 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
734 Cvar_RegisterVariable (&sys_colortranslation);
735 Cvar_RegisterVariable (&sys_specialcharactertranslation);
737 Cvar_RegisterVariable (&log_file);
738 Cvar_RegisterVariable (&log_dest_udp);
740 // support for the classic Quake option
741 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
742 if (COM_CheckParm ("-condebug") != 0)
743 Cvar_SetQuick (&log_file, "qconsole.log");
745 // register our cvars
746 Cvar_RegisterVariable (&con_chat);
747 Cvar_RegisterVariable (&con_chatpos);
748 Cvar_RegisterVariable (&con_chatsize);
749 Cvar_RegisterVariable (&con_chattime);
750 Cvar_RegisterVariable (&con_chatwidth);
751 Cvar_RegisterVariable (&con_notify);
752 Cvar_RegisterVariable (&con_notifyalign);
753 Cvar_RegisterVariable (&con_notifysize);
754 Cvar_RegisterVariable (&con_notifytime);
755 Cvar_RegisterVariable (&con_textsize);
758 Cvar_RegisterVariable (&con_nickcompletion);
759 Cvar_RegisterVariable (&con_nickcompletion_flags);
761 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
762 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
763 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
765 // register our commands
766 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
767 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
768 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
769 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
770 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
771 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
772 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
774 con_initialized = true;
775 Con_DPrint("Console initialized.\n");
778 void Con_Shutdown (void)
780 ConBuffer_Shutdown(&con);
787 Handles cursor positioning, line wrapping, etc
788 All console printing must go through this in order to be displayed
789 If no console is visible, the notify window will pop up.
792 void Con_PrintToHistory(const char *txt, int mask)
795 // \n goes to next line
796 // \r deletes current line and makes a new one
798 static int cr_pending = 0;
799 static char buf[CON_TEXTSIZE];
800 static int bufpos = 0;
802 if(!con.text) // FIXME uses a non-abstracted property of con
809 ConBuffer_DeleteLastLine(&con);
817 ConBuffer_AddLine(&con, buf, bufpos, mask);
822 ConBuffer_AddLine(&con, buf, bufpos, mask);
826 buf[bufpos++] = *txt;
827 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
829 ConBuffer_AddLine(&con, buf, bufpos, mask);
837 /*! The translation table between the graphical font and plain ASCII --KB */
838 static char qfont_table[256] = {
839 '\0', '#', '#', '#', '#', '.', '#', '#',
840 '#', 9, 10, '#', ' ', 13, '.', '.',
841 '[', ']', '0', '1', '2', '3', '4', '5',
842 '6', '7', '8', '9', '.', '<', '=', '>',
843 ' ', '!', '"', '#', '$', '%', '&', '\'',
844 '(', ')', '*', '+', ',', '-', '.', '/',
845 '0', '1', '2', '3', '4', '5', '6', '7',
846 '8', '9', ':', ';', '<', '=', '>', '?',
847 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
848 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
849 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
850 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
851 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
852 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
853 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
854 'x', 'y', 'z', '{', '|', '}', '~', '<',
856 '<', '=', '>', '#', '#', '.', '#', '#',
857 '#', '#', ' ', '#', ' ', '>', '.', '.',
858 '[', ']', '0', '1', '2', '3', '4', '5',
859 '6', '7', '8', '9', '.', '<', '=', '>',
860 ' ', '!', '"', '#', '$', '%', '&', '\'',
861 '(', ')', '*', '+', ',', '-', '.', '/',
862 '0', '1', '2', '3', '4', '5', '6', '7',
863 '8', '9', ':', ';', '<', '=', '>', '?',
864 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
865 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
866 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
867 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
868 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
869 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
870 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
871 'x', 'y', 'z', '{', '|', '}', '~', '<'
874 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest)
876 rcon_redirect_sock = sock;
877 rcon_redirect_dest = dest;
878 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
879 rcon_redirect_bufferpos = 5;
882 void Con_Rcon_Redirect_Flush()
884 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
885 NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
886 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
887 rcon_redirect_bufferpos = 5;
890 void Con_Rcon_Redirect_End()
892 Con_Rcon_Redirect_Flush();
893 rcon_redirect_dest = NULL;
894 rcon_redirect_sock = NULL;
897 void Con_Rcon_Redirect_Abort()
899 rcon_redirect_dest = NULL;
900 rcon_redirect_sock = NULL;
908 /// Adds a character to the rcon buffer.
909 void Con_Rcon_AddChar(int c)
911 if(log_dest_buffer_appending)
913 ++log_dest_buffer_appending;
915 // if this print is in response to an rcon command, add the character
916 // to the rcon redirect buffer
918 if (rcon_redirect_dest)
920 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
921 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
922 Con_Rcon_Redirect_Flush();
924 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
926 if(log_dest_buffer_pos == 0)
927 Log_DestBuffer_Init();
928 log_dest_buffer[log_dest_buffer_pos++] = c;
929 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
930 Log_DestBuffer_Flush();
933 log_dest_buffer_pos = 0;
935 --log_dest_buffer_appending;
939 * Convert an RGB color to its nearest quake color.
940 * I'll cheat on this a bit by translating the colors to HSV first,
941 * S and V decide if it's black or white, otherwise, H will decide the
943 * @param _r Red (0-255)
944 * @param _g Green (0-255)
945 * @param _b Blue (0-255)
946 * @return A quake color character.
948 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
950 float r = ((float)_r)/255.0;
951 float g = ((float)_g)/255.0;
952 float b = ((float)_b)/255.0;
953 float min = min(r, min(g, b));
954 float max = max(r, max(g, b));
956 int h; ///< Hue angle [0,360]
957 float s; ///< Saturation [0,1]
958 float v = max; ///< In HSV v == max [0,1]
965 // Saturation threshold. We now say 0.2 is the minimum value for a color!
968 // If the value is less than half, return a black color code.
969 // Otherwise return a white one.
975 // Let's get the hue angle to define some colors:
979 h = (int)(60.0 * (g-b)/(max-min))%360;
981 h = (int)(60.0 * (b-r)/(max-min) + 120);
982 else // if(max == b) redundant check
983 h = (int)(60.0 * (r-g)/(max-min) + 240);
985 if(h < 36) // *red* to orange
987 else if(h < 80) // orange over *yellow* to evilish-bright-green
989 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
991 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
993 else if(h < 270) // darkish blue over *dark blue* to cool purple
995 else if(h < 330) // cool purple over *purple* to ugly swiny red
997 else // ugly red to red closes the circly
1006 extern cvar_t timestamps;
1007 extern cvar_t timeformat;
1008 extern qboolean sys_nostdout;
1009 void Con_Print(const char *msg)
1011 static int mask = 0;
1012 static int index = 0;
1013 static char line[MAX_INPUTLINE];
1017 Con_Rcon_AddChar(*msg);
1018 // if this is the beginning of a new line, print timestamp
1021 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1023 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1024 line[index++] = STRING_COLOR_TAG;
1025 // assert( STRING_COLOR_DEFAULT < 10 )
1026 line[index++] = STRING_COLOR_DEFAULT + '0';
1027 // special color codes for chat messages must always come first
1028 // for Con_PrintToHistory to work properly
1029 if (*msg == 1 || *msg == 2)
1034 if(gamemode == GAME_NEXUIZ)
1036 if(msg[1] == '\r' && cl.foundtalk2wav)
1037 S_LocalSound ("sound/misc/talk2.wav");
1039 S_LocalSound ("sound/misc/talk.wav");
1043 if (msg[1] == '(' && cl.foundtalk2wav)
1044 S_LocalSound ("sound/misc/talk2.wav");
1046 S_LocalSound ("sound/misc/talk.wav");
1048 mask = CON_MASK_CHAT;
1050 line[index++] = STRING_COLOR_TAG;
1051 line[index++] = '3';
1053 Con_Rcon_AddChar(*msg);
1056 for (;*timestamp;index++, timestamp++)
1057 if (index < (int)sizeof(line) - 2)
1058 line[index] = *timestamp;
1060 // append the character
1061 line[index++] = *msg;
1062 // if this is a newline character, we have a complete line to print
1063 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1065 // terminate the line
1069 // send to scrollable buffer
1070 if (con_initialized && cls.state != ca_dedicated)
1072 Con_PrintToHistory(line, mask);
1075 // send to terminal or dedicated server window
1079 if(sys_specialcharactertranslation.integer)
1081 for (p = (unsigned char *) line;*p; p++)
1082 *p = qfont_table[*p];
1085 if(sys_colortranslation.integer == 1) // ANSI
1087 static char printline[MAX_INPUTLINE * 4 + 3];
1088 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1089 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1094 for(in = line, out = printline; *in; ++in)
1098 case STRING_COLOR_TAG:
1099 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1101 char r = tolower(in[2]);
1102 char g = tolower(in[3]);
1103 char b = tolower(in[4]);
1104 // it's a hex digit already, so the else part needs no check --blub
1105 if(isdigit(r)) r -= '0';
1107 if(isdigit(g)) g -= '0';
1109 if(isdigit(b)) b -= '0';
1112 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1113 in += 3; // 3 only, the switch down there does the fourth
1120 case STRING_COLOR_TAG:
1122 *out++ = STRING_COLOR_TAG;
1128 if(lastcolor == 0) break; else lastcolor = 0;
1129 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1134 if(lastcolor == 1) break; else lastcolor = 1;
1135 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1140 if(lastcolor == 2) break; else lastcolor = 2;
1141 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1146 if(lastcolor == 3) break; else lastcolor = 3;
1147 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1152 if(lastcolor == 4) break; else lastcolor = 4;
1153 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1158 if(lastcolor == 5) break; else lastcolor = 5;
1159 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1164 if(lastcolor == 6) break; else lastcolor = 6;
1165 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1170 // bold normal color
1172 if(lastcolor == 8) break; else lastcolor = 8;
1173 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1176 *out++ = STRING_COLOR_TAG;
1183 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1200 Sys_PrintToTerminal(printline);
1202 else if(sys_colortranslation.integer == 2) // Quake
1204 Sys_PrintToTerminal(line);
1208 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1211 for(in = line, out = printline; *in; ++in)
1215 case STRING_COLOR_TAG:
1218 case STRING_COLOR_RGB_TAG_CHAR:
1219 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1224 *out++ = STRING_COLOR_TAG;
1225 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1228 case STRING_COLOR_TAG:
1230 *out++ = STRING_COLOR_TAG;
1245 *out++ = STRING_COLOR_TAG;
1255 Sys_PrintToTerminal(printline);
1258 // empty the line buffer
1270 void Con_Printf(const char *fmt, ...)
1273 char msg[MAX_INPUTLINE];
1275 va_start(argptr,fmt);
1276 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1287 void Con_DPrint(const char *msg)
1289 if (!developer.integer)
1290 return; // don't confuse non-developers with techie stuff...
1299 void Con_DPrintf(const char *fmt, ...)
1302 char msg[MAX_INPUTLINE];
1304 if (!developer.integer)
1305 return; // don't confuse non-developers with techie stuff...
1307 va_start(argptr,fmt);
1308 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1316 ==============================================================================
1320 ==============================================================================
1327 The input line scrolls horizontally if typing goes beyond the right edge
1329 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1332 void Con_DrawInput (void)
1336 char editlinecopy[MAX_INPUTLINE+1], *text;
1339 if (!key_consoleactive)
1340 return; // don't draw anything
1342 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1343 text = editlinecopy;
1345 // Advanced Console Editing by Radix radix@planetquake.com
1346 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1347 // use strlen of edit_line instead of key_linepos to allow editing
1348 // of early characters w/o erasing
1350 y = (int)strlen(text);
1352 // fill out remainder with spaces
1353 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1356 // add the cursor frame
1357 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1358 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1360 // text[key_linepos + 1] = 0;
1362 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1367 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 );
1370 // key_line[key_linepos] = 0;
1376 float alignment; // 0 = left, 0.5 = center, 1 = right
1382 const char *continuationString;
1385 int colorindex; // init to -1
1389 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1391 con_text_info_t *ti = (con_text_info_t *) passthrough;
1394 ti->colorindex = -1;
1395 return ti->fontsize * ti->font->maxwidth;
1398 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1399 else if(maxWidth == -1)
1400 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1403 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1404 // Note: this is NOT a Con_Printf, as it could print recursively
1409 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1415 (void) isContinuation;
1419 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1421 con_text_info_t *ti = (con_text_info_t *) passthrough;
1423 if(ti->y < ti->ymin - 0.001)
1425 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1429 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1430 if(isContinuation && *ti->continuationString)
1431 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);
1433 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);
1436 ti->y += ti->fontsize;
1440 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)
1444 int maxlines = (int) floor(height / fontsize + 0.01f);
1447 int continuationWidth = 0;
1449 double t = cl.time; // saved so it won't change
1452 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1453 ti.fontsize = fontsize;
1454 ti.alignment = alignment_x;
1457 ti.ymax = y + height;
1458 ti.continuationString = continuationString;
1461 Con_WordWidthFunc(&ti, NULL, &l, -1);
1462 l = strlen(continuationString);
1463 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1465 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1466 startidx = CON_LINES_COUNT;
1467 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1469 con_lineinfo_t *l = &CON_LINES(i);
1472 if((l->mask & mask_must) != mask_must)
1474 if(l->mask & mask_mustnot)
1476 if(maxage && (l->addtime < t - maxage))
1480 // Calculate its actual height...
1481 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1482 if(lines + mylines >= maxlines)
1484 nskip = lines + mylines - maxlines;
1493 // then center according to the calculated amount of lines...
1495 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1497 // then actually draw
1498 for(i = startidx; i < CON_LINES_COUNT; ++i)
1500 con_lineinfo_t *l = &CON_LINES(i);
1502 if((l->mask & mask_must) != mask_must)
1504 if(l->mask & mask_mustnot)
1506 if(maxage && (l->addtime < t - maxage))
1509 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1519 Draws the last few lines of output transparently over the game top
1522 void Con_DrawNotify (void)
1525 float chatstart, notifystart, inputsize;
1527 char temptext[MAX_INPUTLINE];
1531 ConBuffer_FixTimes(&con);
1533 numChatlines = con_chat.integer;
1534 chatpos = con_chatpos.integer;
1536 if (con_notify.integer < 0)
1537 Cvar_SetValueQuick(&con_notify, 0);
1538 if (gamemode == GAME_TRANSFUSION)
1539 v = 8; // vertical offset
1543 // GAME_NEXUIZ: center, otherwise left justify
1544 align = con_notifyalign.value;
1545 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1547 if(gamemode == GAME_NEXUIZ)
1555 // first chat, input line, then notify
1557 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1559 else if(chatpos > 0)
1561 // first notify, then (chatpos-1) empty lines, then chat, then input
1563 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1565 else // if(chatpos < 0)
1567 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1569 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1574 // just notify and input
1576 chatstart = 0; // shut off gcc warning
1579 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, "");
1584 v = chatstart + numChatlines * con_chatsize.value;
1585 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
1588 if (key_dest == key_message)
1590 int colorindex = -1;
1592 // LordHavoc: speedup, and other improvements
1594 dpsnprintf(temptext, sizeof(temptext), "]%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1596 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1598 dpsnprintf(temptext, sizeof(temptext), "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1601 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1602 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1605 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1611 Con_MeasureConsoleLine
1613 Counts the number of lines for a line on the console.
1616 int Con_MeasureConsoleLine(int lineno)
1618 float width = vid_conwidth.value;
1620 con_lineinfo_t *li = &CON_LINES(lineno);
1622 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1625 ti.fontsize = con_textsize.value;
1626 ti.font = FONT_CONSOLE;
1628 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1635 Returns the height of a given console line; calculates it if necessary.
1638 int Con_LineHeight(int i)
1640 con_lineinfo_t *li = &CON_LINES(i);
1644 return li->height = Con_MeasureConsoleLine(i);
1651 Draws a line of the console; returns its height in lines.
1652 If alpha is 0, the line is not drawn, but still wrapped and its height
1656 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1658 float width = vid_conwidth.value;
1660 con_lineinfo_t *li = &CON_LINES(lineno);
1662 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1665 ti.continuationString = "";
1667 ti.fontsize = con_textsize.value;
1668 ti.font = FONT_CONSOLE;
1670 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1675 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1682 Calculates the last visible line index and how much to show of it based on
1686 void Con_LastVisibleLine(int *last, int *limitlast)
1691 if(con_backscroll < 0)
1694 // now count until we saw con_backscroll actual lines
1695 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1697 int h = Con_LineHeight(i);
1699 // line is the last visible line?
1700 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1703 *limitlast = lines_seen + h - con_backscroll;
1710 // if we get here, no line was on screen - scroll so that one line is
1712 con_backscroll = lines_seen - 1;
1713 *last = con.lines_first;
1714 // FIXME uses con in a non abstracted way
1722 Draws the console with the solid background
1723 The typing input line at the bottom should only be drawn if typing is allowed
1726 void Con_DrawConsole (int lines)
1728 int i, last, limitlast;
1734 con_vislines = lines;
1736 // draw the background
1737 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
1738 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);
1741 if(CON_LINES_COUNT > 0)
1743 float ymax = con_vislines - 2 * con_textsize.value;
1744 Con_LastVisibleLine(&last, &limitlast);
1745 y = ymax - con_textsize.value;
1748 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1749 // FIXME uses con in a non abstracted way
1754 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1756 break; // top of console buffer
1758 break; // top of console window
1764 // draw the input prompt, user text, and cursor if desired
1772 Prints not only map filename, but also
1773 its format (q1/q2/q3/hl) and even its message
1775 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1776 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1777 //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
1778 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1779 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1783 int i, k, max, p, o, min;
1786 unsigned char buf[1024];
1788 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1789 t = FS_Search(message, 1, true);
1792 if (t->numfilenames > 1)
1793 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1794 len = (unsigned char *)Z_Malloc(t->numfilenames);
1796 for(max=i=0;i<t->numfilenames;i++)
1798 k = (int)strlen(t->filenames[i]);
1808 for(i=0;i<t->numfilenames;i++)
1810 int lumpofs = 0, lumplen = 0;
1811 char *entities = NULL;
1812 const char *data = NULL;
1814 char entfilename[MAX_QPATH];
1815 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1817 f = FS_OpenVirtualFile(t->filenames[i], true);
1820 memset(buf, 0, 1024);
1821 FS_Read(f, buf, 1024);
1822 if (!memcmp(buf, "IBSP", 4))
1824 p = LittleLong(((int *)buf)[1]);
1825 if (p == Q3BSPVERSION)
1827 q3dheader_t *header = (q3dheader_t *)buf;
1828 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1829 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1831 else if (p == Q2BSPVERSION)
1833 q2dheader_t *header = (q2dheader_t *)buf;
1834 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1835 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1838 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1840 dheader_t *header = (dheader_t *)buf;
1841 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1842 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1846 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1847 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1848 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1849 if (!entities && lumplen >= 10)
1851 FS_Seek(f, lumpofs, SEEK_SET);
1852 entities = (char *)Z_Malloc(lumplen + 1);
1853 FS_Read(f, entities, lumplen);
1857 // if there are entities to parse, a missing message key just
1858 // means there is no title, so clear the message string now
1864 if (!COM_ParseToken_Simple(&data, false, false))
1866 if (com_token[0] == '{')
1868 if (com_token[0] == '}')
1870 // skip leading whitespace
1871 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
1872 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
1873 keyname[l] = com_token[k+l];
1875 if (!COM_ParseToken_Simple(&data, false, false))
1877 if (developer.integer >= 100)
1878 Con_Printf("key: %s %s\n", keyname, com_token);
1879 if (!strcmp(keyname, "message"))
1881 // get the message contents
1882 strlcpy(message, com_token, sizeof(message));
1892 *(t->filenames[i]+len[i]+5) = 0;
1895 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1896 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1897 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1898 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1899 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1901 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1906 k = *(t->filenames[0]+5+p);
1909 for(i=1;i<t->numfilenames;i++)
1910 if(*(t->filenames[i]+5+p) != k)
1914 if(p > o && completedname && completednamebufferlength > 0)
1916 memset(completedname, 0, completednamebufferlength);
1917 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1927 New function for tab-completion system
1928 Added by EvilTypeGuy
1929 MEGA Thanks to Taniwha
1932 void Con_DisplayList(const char **list)
1934 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1935 const char **walk = list;
1938 len = (int)strlen(*walk);
1946 len = (int)strlen(*list);
1947 if (pos + maxlen >= width) {
1953 for (i = 0; i < (maxlen - len); i++)
1965 SanitizeString strips color tags from the string in
1966 and writes the result on string out
1968 void SanitizeString(char *in, char *out)
1972 if(*in == STRING_COLOR_TAG)
1977 out[0] = STRING_COLOR_TAG;
1981 else if (*in >= '0' && *in <= '9') // ^[0-9] found
1988 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
1991 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
1993 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2000 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2005 else if (*in != STRING_COLOR_TAG)
2008 *out = qfont_table[*(unsigned char*)in];
2015 // Now it becomes TRICKY :D --blub
2016 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2017 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2018 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2019 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
2020 static int Nicks_matchpos;
2022 // co against <<:BLASTER:>> is true!?
2023 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2027 if(tolower(*a) == tolower(*b))
2041 return (*a < *b) ? -1 : 1;
2045 return (*a < *b) ? -1 : 1;
2049 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2052 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2054 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2055 return Nicks_strncasecmp_nospaces(a, b, a_len);
2056 return strncasecmp(a, b, a_len);
2059 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2061 // ignore non alphanumerics of B
2062 // if A contains a non-alphanumeric, B must contain it as well though!
2065 qboolean alnum_a, alnum_b;
2067 if(tolower(*a) == tolower(*b))
2069 if(*a == 0) // end of both strings, they're equal
2076 // not equal, end of one string?
2081 // ignore non alphanumerics
2082 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2083 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2084 if(!alnum_a) // b must contain this
2085 return (*a < *b) ? -1 : 1;
2088 // otherwise, both are alnum, they're just not equal, return the appropriate number
2090 return (*a < *b) ? -1 : 1;
2096 /* Nicks_CompleteCountPossible
2098 Count the number of possible nicks to complete
2100 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2109 if(!con_nickcompletion.integer)
2112 // changed that to 1
2113 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2116 for(i = 0; i < cl.maxclients; ++i)
2119 if(!cl.scores[p].name[0])
2122 SanitizeString(cl.scores[p].name, name);
2123 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2128 length = strlen(name);
2130 spos = pos - 1; // no need for a minimum of characters :)
2134 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2136 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2137 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2143 if(isCon && spos == 0)
2145 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2151 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2152 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2154 // the sanitized list
2155 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2158 Nicks_matchpos = match;
2161 Nicks_offset[count] = s - (&line[match]);
2162 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2169 void Cmd_CompleteNicksPrint(int count)
2172 for(i = 0; i < count; ++i)
2173 Con_Printf("%s\n", Nicks_list[i]);
2176 void Nicks_CutMatchesNormal(int count)
2178 // cut match 0 down to the longest possible completion
2181 c = strlen(Nicks_sanlist[0]) - 1;
2182 for(i = 1; i < count; ++i)
2184 l = strlen(Nicks_sanlist[i]) - 1;
2188 for(l = 0; l <= c; ++l)
2189 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2195 Nicks_sanlist[0][c+1] = 0;
2196 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2199 unsigned int Nicks_strcleanlen(const char *s)
2204 if( (*s >= 'a' && *s <= 'z') ||
2205 (*s >= 'A' && *s <= 'Z') ||
2206 (*s >= '0' && *s <= '9') ||
2214 void Nicks_CutMatchesAlphaNumeric(int count)
2216 // cut match 0 down to the longest possible completion
2219 char tempstr[sizeof(Nicks_sanlist[0])];
2221 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2223 c = strlen(Nicks_sanlist[0]);
2224 for(i = 0, l = 0; i < (int)c; ++i)
2226 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2227 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2228 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2230 tempstr[l++] = Nicks_sanlist[0][i];
2235 for(i = 1; i < count; ++i)
2238 b = Nicks_sanlist[i];
2248 if(tolower(*a) == tolower(*b))
2254 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2256 // b is alnum, so cut
2263 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2264 Nicks_CutMatchesNormal(count);
2265 //if(!Nicks_sanlist[0][0])
2266 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2268 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2269 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2273 void Nicks_CutMatchesNoSpaces(int count)
2275 // cut match 0 down to the longest possible completion
2278 char tempstr[sizeof(Nicks_sanlist[0])];
2281 c = strlen(Nicks_sanlist[0]);
2282 for(i = 0, l = 0; i < (int)c; ++i)
2284 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2286 tempstr[l++] = Nicks_sanlist[0][i];
2291 for(i = 1; i < count; ++i)
2294 b = Nicks_sanlist[i];
2304 if(tolower(*a) == tolower(*b))
2318 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2319 Nicks_CutMatchesNormal(count);
2320 //if(!Nicks_sanlist[0][0])
2321 //Con_Printf("TS: %s\n", tempstr);
2322 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2324 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2325 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2329 void Nicks_CutMatches(int count)
2331 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2332 Nicks_CutMatchesAlphaNumeric(count);
2333 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2334 Nicks_CutMatchesNoSpaces(count);
2336 Nicks_CutMatchesNormal(count);
2339 const char **Nicks_CompleteBuildList(int count)
2343 // the list is freed by Con_CompleteCommandLine, so create a char**
2344 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2346 for(; bpos < count; ++bpos)
2347 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2349 Nicks_CutMatches(count);
2357 Restores the previous used color, after the autocompleted name.
2359 int Nicks_AddLastColor(char *buffer, int pos)
2361 qboolean quote_added = false;
2363 int color = STRING_COLOR_DEFAULT + '0';
2364 char r = 0, g = 0, b = 0;
2366 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2368 // we'll have to add a quote :)
2369 buffer[pos++] = '\"';
2373 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2375 // add color when no quote was added, or when flags &4?
2377 for(match = Nicks_matchpos-1; match >= 0; --match)
2379 if(buffer[match] == STRING_COLOR_TAG)
2381 if( isdigit(buffer[match+1]) )
2383 color = buffer[match+1];
2386 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2388 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2390 r = buffer[match+2];
2391 g = buffer[match+3];
2392 b = buffer[match+4];
2401 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2403 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2404 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2407 buffer[pos++] = STRING_COLOR_TAG;
2410 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2416 buffer[pos++] = color;
2421 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2424 /*if(!con_nickcompletion.integer)
2425 return; is tested in Nicks_CompletionCountPossible */
2426 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2432 msg = Nicks_list[0];
2433 len = min(size - Nicks_matchpos - 3, strlen(msg));
2434 memcpy(&buffer[Nicks_matchpos], msg, len);
2435 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2436 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2437 buffer[len++] = ' ';
2444 Con_Printf("\n%i possible nicks:\n", n);
2445 Cmd_CompleteNicksPrint(n);
2447 Nicks_CutMatches(n);
2449 msg = Nicks_sanlist[0];
2450 len = min(size - Nicks_matchpos, strlen(msg));
2451 memcpy(&buffer[Nicks_matchpos], msg, len);
2452 buffer[Nicks_matchpos + len] = 0;
2454 return Nicks_matchpos + len;
2461 Con_CompleteCommandLine
2463 New function for tab-completion system
2464 Added by EvilTypeGuy
2465 Thanks to Fett erich@heintz.com
2467 Enhanced to tab-complete map names by [515]
2470 void Con_CompleteCommandLine (void)
2472 const char *cmd = "";
2474 const char **list[4] = {0, 0, 0, 0};
2477 int c, v, a, i, cmd_len, pos, k;
2478 int n; // nicks --blub
2479 const char *space, *patterns;
2481 //find what we want to complete
2486 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2492 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2493 key_line[key_linepos] = 0; //hide them
2495 space = strchr(key_line + 1, ' ');
2496 if(space && pos == (space - key_line) + 1)
2498 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2500 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2501 if(patterns && !*patterns)
2502 patterns = NULL; // get rid of the empty string
2504 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2508 if (GetMapList(s, t, sizeof(t)))
2510 // first move the cursor
2511 key_linepos += (int)strlen(t) - (int)strlen(s);
2513 // and now do the actual work
2515 strlcat(key_line, t, MAX_INPUTLINE);
2516 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2518 // and fix the cursor
2519 if(key_linepos > (int) strlen(key_line))
2520 key_linepos = (int) strlen(key_line);
2529 stringlist_t resultbuf, dirbuf;
2532 // // store completion patterns (space separated) for command foo in con_completion_foo
2533 // set con_completion_foo "foodata/*.foodefault *.foo"
2536 // Note: patterns with slash are always treated as absolute
2537 // patterns; patterns without slash search in the innermost
2538 // directory the user specified. There is no way to "complete into"
2539 // a directory as of now, as directories seem to be unknown to the
2543 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2544 // set con_completion_playdemo "*.dem"
2545 // set con_completion_play "*.wav *.ogg"
2547 // TODO somehow add support for directories; these shall complete
2548 // to their name + an appended slash.
2550 stringlistinit(&resultbuf);
2551 stringlistinit(&dirbuf);
2552 while(COM_ParseToken_Simple(&patterns, false, false))
2555 if(strchr(com_token, '/'))
2557 search = FS_Search(com_token, true, true);
2561 const char *slash = strrchr(s, '/');
2564 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2565 strlcat(t, com_token, sizeof(t));
2566 search = FS_Search(t, true, true);
2569 search = FS_Search(com_token, true, true);
2573 for(i = 0; i < search->numfilenames; ++i)
2574 if(!strncmp(search->filenames[i], s, strlen(s)))
2575 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2576 stringlistappend(&resultbuf, search->filenames[i]);
2577 FS_FreeSearch(search);
2581 // In any case, add directory names
2584 const char *slash = strrchr(s, '/');
2587 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2588 strlcat(t, "*", sizeof(t));
2589 search = FS_Search(t, true, true);
2592 search = FS_Search("*", true, true);
2595 for(i = 0; i < search->numfilenames; ++i)
2596 if(!strncmp(search->filenames[i], s, strlen(s)))
2597 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2598 stringlistappend(&dirbuf, search->filenames[i]);
2599 FS_FreeSearch(search);
2603 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2606 unsigned int matchchars;
2607 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2609 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2612 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2614 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2618 stringlistsort(&resultbuf); // dirbuf is already sorted
2619 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2620 for(i = 0; i < dirbuf.numstrings; ++i)
2622 Con_Printf("%s/\n", dirbuf.strings[i]);
2624 for(i = 0; i < resultbuf.numstrings; ++i)
2626 Con_Printf("%s\n", resultbuf.strings[i]);
2628 matchchars = sizeof(t) - 1;
2629 if(resultbuf.numstrings > 0)
2631 p = resultbuf.strings[0];
2632 q = resultbuf.strings[resultbuf.numstrings - 1];
2633 for(; *p && *p == *q; ++p, ++q);
2634 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2636 if(dirbuf.numstrings > 0)
2638 p = dirbuf.strings[0];
2639 q = dirbuf.strings[dirbuf.numstrings - 1];
2640 for(; *p && *p == *q; ++p, ++q);
2641 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2643 // now p points to the first non-equal character, or to the end
2644 // of resultbuf.strings[0]. We want to append the characters
2645 // from resultbuf.strings[0] to (not including) p as these are
2646 // the unique prefix
2647 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2650 // first move the cursor
2651 key_linepos += (int)strlen(t) - (int)strlen(s);
2653 // and now do the actual work
2655 strlcat(key_line, t, MAX_INPUTLINE);
2656 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2658 // and fix the cursor
2659 if(key_linepos > (int) strlen(key_line))
2660 key_linepos = (int) strlen(key_line);
2662 stringlistfreecontents(&resultbuf);
2663 stringlistfreecontents(&dirbuf);
2665 return; // bail out, when we complete for a command that wants a file name
2670 // Count number of possible matches and print them
2671 c = Cmd_CompleteCountPossible(s);
2674 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2675 Cmd_CompleteCommandPrint(s);
2677 v = Cvar_CompleteCountPossible(s);
2680 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2681 Cvar_CompleteCvarPrint(s);
2683 a = Cmd_CompleteAliasCountPossible(s);
2686 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2687 Cmd_CompleteAliasPrint(s);
2689 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2692 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2693 Cmd_CompleteNicksPrint(n);
2696 if (!(c + v + a + n)) // No possible matches
2699 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2704 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2706 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2708 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2710 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2712 for (cmd_len = (int)strlen(s);;cmd_len++)
2715 for (i = 0; i < 3; i++)
2717 for (l = list[i];*l;l++)
2718 if ((*l)[cmd_len] != cmd[cmd_len])
2720 // all possible matches share this character, so we continue...
2723 // if all matches ended at the same position, stop
2724 // (this means there is only one match)
2730 // prevent a buffer overrun by limiting cmd_len according to remaining space
2731 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2735 memcpy(&key_line[key_linepos], cmd, cmd_len);
2736 key_linepos += cmd_len;
2737 // if there is only one match, add a space after it
2738 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2741 { // was a nick, might have an offset, and needs colors ;) --blub
2742 key_linepos = pos - Nicks_offset[0];
2743 cmd_len = strlen(Nicks_list[0]);
2744 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2746 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2747 key_linepos += cmd_len;
2748 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2749 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2751 key_line[key_linepos++] = ' ';
2755 // use strlcat to avoid a buffer overrun
2756 key_line[key_linepos] = 0;
2757 strlcat(key_line, s2, sizeof(key_line));
2759 // free the command, cvar, and alias lists
2760 for (i = 0; i < 4; i++)
2762 Mem_Free((void *)list[i]);