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_IDX(i) CONBUF_LINES_IDX(&con, i)
40 #define CON_LINES_UNIDX(i) CONBUF_LINES_UNIDX(&con, i)
41 #define CON_LINES_LAST CONBUF_LINES_LAST(&con)
42 #define CON_LINES(i) CONBUF_LINES(&con, i)
43 #define CON_LINES_PRED(i) CONBUF_LINES_PRED(&con, i)
44 #define CON_LINES_SUCC(i) CONBUF_LINES_SUCC(&con, i)
46 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
47 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
48 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
50 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
51 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
52 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)"};
53 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
54 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
55 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
56 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
59 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)"};
61 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)"};
63 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)"};
67 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
68 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
69 "0: add nothing after completion. "
70 "1: add the last color after completion. "
71 "2: add a quote when starting a quote instead of the color. "
72 "4: will replace 1, will force color, even after a quote. "
73 "8: ignore non-alphanumerics. "
74 "16: ignore spaces. "};
75 #define NICKS_ADD_COLOR 1
76 #define NICKS_ADD_QUOTE 2
77 #define NICKS_FORCE_COLOR 4
78 #define NICKS_ALPHANUMERICS_ONLY 8
79 #define NICKS_NO_SPACES 16
81 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem"};
82 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem"};
83 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg"};
88 qboolean con_initialized;
90 // used for server replies to rcon command
91 lhnetsocket_t *rcon_redirect_sock = NULL;
92 lhnetaddress_t *rcon_redirect_dest = NULL;
93 int rcon_redirect_bufferpos = 0;
94 char rcon_redirect_buffer[1400];
96 // generic functions for console buffers
98 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
100 buf->textsize = textsize;
101 buf->text = Mem_Alloc(mempool, textsize);
102 buf->maxlines = maxlines;
103 buf->lines = Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
104 buf->lines_first = 0;
105 buf->lines_count = 0;
113 void ConBuffer_Clear (conbuffer_t *buf)
115 buf->lines_count = 0;
123 void ConBuffer_Shutdown(conbuffer_t *buf)
126 Mem_Free(buf->lines);
133 Notifies the console code about the current time
134 (and shifts back times of other entries when the time
138 void ConBuffer_FixTimes(conbuffer_t *buf)
141 if(buf->lines_count >= 1)
143 double diff = cl.time - (buf->lines + CONBUF_LINES_LAST(buf))->addtime;
146 for(i = 0; i < buf->lines_count; ++i)
147 CONBUF_LINES(buf, i).addtime += diff;
156 Deletes the first line from the console history.
159 void ConBuffer_DeleteLine(conbuffer_t *buf)
161 if(buf->lines_count == 0)
164 buf->lines_first = CONBUF_LINES_IDX(buf, 1);
169 ConBuffer_DeleteLastLine
171 Deletes the last line from the console history.
174 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
176 if(buf->lines_count == 0)
185 Checks if there is space for a line of the given length, and if yes, returns a
186 pointer to the start of such a space, and NULL otherwise.
189 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
191 if(len > buf->textsize)
193 if(buf->lines_count == 0)
197 char *firstline_start = buf->lines[buf->lines_first].start;
198 char *lastline_onepastend = buf->lines[CONBUF_LINES_LAST(buf)].start + buf->lines[CONBUF_LINES_LAST(buf)].len;
199 // the buffer is cyclic, so we first have two cases...
200 if(firstline_start < lastline_onepastend) // buffer is contiguous
203 if(len <= buf->text + buf->textsize - lastline_onepastend)
204 return lastline_onepastend;
206 else if(len <= firstline_start - buf->text)
211 else // buffer has a contiguous hole
213 if(len <= firstline_start - lastline_onepastend)
214 return lastline_onepastend;
225 Appends a given string as a new line to the console.
228 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
233 ConBuffer_FixTimes(buf);
235 if(len >= buf->textsize)
238 // only display end of line.
239 line += len - buf->textsize + 1;
240 len = buf->textsize - 1;
242 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
243 ConBuffer_DeleteLine(buf);
244 memcpy(putpos, line, len);
248 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
250 p = buf->lines + CONBUF_LINES_LAST(buf);
253 p->addtime = cl.time;
255 p->height = -1; // calculate when needed
258 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
262 start = buf->lines_count;
263 for(i = start - 1; i >= 0; --i)
265 con_lineinfo_t *l = &CONBUF_LINES(buf, i);
267 if((l->mask & mask_must) != mask_must)
269 if(l->mask & mask_mustnot)
278 int Con_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
281 for(i = start + 1; i < buf->lines_count; ++i)
283 con_lineinfo_t *l = &CONBUF_LINES(buf, i);
285 if((l->mask & mask_must) != mask_must)
287 if(l->mask & mask_mustnot)
296 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
298 static char copybuf[MAX_INPUTLINE];
299 con_lineinfo_t *l = &CONBUF_LINES(buf, i);
300 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
301 strlcpy(copybuf, l->start, sz);
306 ==============================================================================
310 ==============================================================================
313 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
314 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"};
315 char log_dest_buffer[1400]; // UDP packet
316 size_t log_dest_buffer_pos;
317 unsigned int log_dest_buffer_appending;
318 char crt_log_file [MAX_OSPATH] = "";
319 qfile_t* logfile = NULL;
321 unsigned char* logqueue = NULL;
323 size_t logq_size = 0;
325 void Log_ConPrint (const char *msg);
332 static void Log_DestBuffer_Init()
334 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
335 log_dest_buffer_pos = 5;
343 void Log_DestBuffer_Flush()
345 lhnetaddress_t log_dest_addr;
346 lhnetsocket_t *log_dest_socket;
347 const char *s = log_dest_udp.string;
348 qboolean have_opened_temp_sockets = false;
349 if(s) if(log_dest_buffer_pos > 5)
351 ++log_dest_buffer_appending;
352 log_dest_buffer[log_dest_buffer_pos++] = 0;
354 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
356 have_opened_temp_sockets = true;
357 NetConn_OpenServerPorts(true);
360 while(COM_ParseToken_Console(&s))
361 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
363 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
365 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
367 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
370 if(have_opened_temp_sockets)
371 NetConn_CloseServerPorts();
372 --log_dest_buffer_appending;
374 log_dest_buffer_pos = 0;
382 const char* Log_Timestamp (const char *desc)
384 static char timestamp [128];
391 char timestring [64];
393 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
396 localtime_s (&crt_tm, &crt_time);
397 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
399 crt_tm = localtime (&crt_time);
400 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
404 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
406 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
419 if (logfile != NULL || log_file.string[0] == '\0')
422 logfile = FS_OpenRealFile(log_file.string, "a", false);
425 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
426 FS_Print (logfile, Log_Timestamp ("Log started"));
436 void Log_Close (void)
441 FS_Print (logfile, Log_Timestamp ("Log stopped"));
442 FS_Print (logfile, "\n");
446 crt_log_file[0] = '\0';
455 void Log_Start (void)
461 // Dump the contents of the log queue into the log file and free it
462 if (logqueue != NULL)
464 unsigned char *temp = logqueue;
469 FS_Write (logfile, temp, logq_ind);
470 if(*log_dest_udp.string)
472 for(pos = 0; pos < logq_ind; )
474 if(log_dest_buffer_pos == 0)
475 Log_DestBuffer_Init();
476 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
477 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
478 log_dest_buffer_pos += n;
479 Log_DestBuffer_Flush();
496 void Log_ConPrint (const char *msg)
498 static qboolean inprogress = false;
500 // don't allow feedback loops with memory error reports
505 // Until the host is completely initialized, we maintain a log queue
506 // to store the messages, since the log can't be started before
507 if (logqueue != NULL)
509 size_t remain = logq_size - logq_ind;
510 size_t len = strlen (msg);
512 // If we need to enlarge the log queue
515 size_t factor = ((logq_ind + len) / logq_size) + 1;
516 unsigned char* newqueue;
519 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
520 memcpy (newqueue, logqueue, logq_ind);
523 remain = logq_size - logq_ind;
525 memcpy (&logqueue[logq_ind], msg, len);
532 // Check if log_file has changed
533 if (strcmp (crt_log_file, log_file.string) != 0)
539 // If a log file is available
541 FS_Print (logfile, msg);
552 void Log_Printf (const char *logfilename, const char *fmt, ...)
556 file = FS_OpenRealFile(logfilename, "a", true);
561 va_start (argptr, fmt);
562 FS_VPrintf (file, fmt, argptr);
571 ==============================================================================
575 ==============================================================================
583 void Con_ToggleConsole_f (void)
585 // toggle the 'user wants console' bit
586 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
594 Clear all notify lines.
597 void Con_ClearNotify (void)
600 for(i = 0; i < con.lines_count; ++i)
601 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
610 void Con_MessageMode_f (void)
612 key_dest = key_message;
613 chat_mode = 0; // "say"
622 void Con_MessageMode2_f (void)
624 key_dest = key_message;
625 chat_mode = 1; // "say_team"
633 void Con_CommandMode_f (void)
635 key_dest = key_message;
638 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
639 chat_bufferlen = strlen(chat_buffer);
641 chat_mode = -1; // command
648 If the line width has changed, reformat the buffer.
651 void Con_CheckResize (void)
656 f = bound(1, con_textsize.value, 128);
657 if(f != con_textsize.value)
658 Cvar_SetValueQuick(&con_textsize, f);
659 width = (int)floor(vid_conwidth.value / con_textsize.value);
660 width = bound(1, width, con.textsize/4);
662 if (width == con_linewidth)
665 con_linewidth = width;
667 for(i = 0; i < con.lines_count; ++i)
668 CON_LINES(i).height = -1; // recalculate when next needed
674 //[515]: the simplest command ever
675 //LordHavoc: not so simple after I made it print usage...
676 static void Con_Maps_f (void)
680 Con_Printf("usage: maps [mapnameprefix]\n");
683 else if (Cmd_Argc() == 2)
684 GetMapList(Cmd_Argv(1), NULL, 0);
686 GetMapList("", NULL, 0);
689 void Con_ConDump_f (void)
695 Con_Printf("usage: condump <filename>\n");
698 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
701 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
704 for(i = 0; i < con.lines_count; ++i)
706 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
707 FS_Write(file, "\n", 1);
714 ConBuffer_Clear(&con);
725 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
727 // Allocate a log queue, this will be freed after configs are parsed
728 logq_size = MAX_INPUTLINE;
729 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
732 Cvar_RegisterVariable (&sys_colortranslation);
733 Cvar_RegisterVariable (&sys_specialcharactertranslation);
735 Cvar_RegisterVariable (&log_file);
736 Cvar_RegisterVariable (&log_dest_udp);
738 // support for the classic Quake option
739 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
740 if (COM_CheckParm ("-condebug") != 0)
741 Cvar_SetQuick (&log_file, "qconsole.log");
743 // register our cvars
744 Cvar_RegisterVariable (&con_chat);
745 Cvar_RegisterVariable (&con_chatpos);
746 Cvar_RegisterVariable (&con_chatsize);
747 Cvar_RegisterVariable (&con_chattime);
748 Cvar_RegisterVariable (&con_chatwidth);
749 Cvar_RegisterVariable (&con_notify);
750 Cvar_RegisterVariable (&con_notifyalign);
751 Cvar_RegisterVariable (&con_notifysize);
752 Cvar_RegisterVariable (&con_notifytime);
753 Cvar_RegisterVariable (&con_textsize);
756 Cvar_RegisterVariable (&con_nickcompletion);
757 Cvar_RegisterVariable (&con_nickcompletion_flags);
759 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
760 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
761 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
763 // register our commands
764 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
765 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
766 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
767 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
768 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
769 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
770 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
772 con_initialized = true;
773 Con_DPrint("Console initialized.\n");
781 Handles cursor positioning, line wrapping, etc
782 All console printing must go through this in order to be displayed
783 If no console is visible, the notify window will pop up.
786 void Con_PrintToHistory(const char *txt, int mask)
789 // \n goes to next line
790 // \r deletes current line and makes a new one
792 static int cr_pending = 0;
793 static char buf[CON_TEXTSIZE];
794 static int bufpos = 0;
800 ConBuffer_DeleteLastLine(&con);
808 ConBuffer_AddLine(&con, buf, bufpos, mask);
813 ConBuffer_AddLine(&con, buf, bufpos, mask);
817 buf[bufpos++] = *txt;
818 if(bufpos >= con.textsize - 1)
820 ConBuffer_AddLine(&con, buf, bufpos, mask);
828 /* The translation table between the graphical font and plain ASCII --KB */
829 static char qfont_table[256] = {
830 '\0', '#', '#', '#', '#', '.', '#', '#',
831 '#', 9, 10, '#', ' ', 13, '.', '.',
832 '[', ']', '0', '1', '2', '3', '4', '5',
833 '6', '7', '8', '9', '.', '<', '=', '>',
834 ' ', '!', '"', '#', '$', '%', '&', '\'',
835 '(', ')', '*', '+', ',', '-', '.', '/',
836 '0', '1', '2', '3', '4', '5', '6', '7',
837 '8', '9', ':', ';', '<', '=', '>', '?',
838 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
839 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
840 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
841 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
842 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
843 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
844 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
845 'x', 'y', 'z', '{', '|', '}', '~', '<',
847 '<', '=', '>', '#', '#', '.', '#', '#',
848 '#', '#', ' ', '#', ' ', '>', '.', '.',
849 '[', ']', '0', '1', '2', '3', '4', '5',
850 '6', '7', '8', '9', '.', '<', '=', '>',
851 ' ', '!', '"', '#', '$', '%', '&', '\'',
852 '(', ')', '*', '+', ',', '-', '.', '/',
853 '0', '1', '2', '3', '4', '5', '6', '7',
854 '8', '9', ':', ';', '<', '=', '>', '?',
855 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
856 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
857 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
858 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
859 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
860 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
861 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
862 'x', 'y', 'z', '{', '|', '}', '~', '<'
865 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest)
867 rcon_redirect_sock = sock;
868 rcon_redirect_dest = dest;
869 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
870 rcon_redirect_bufferpos = 5;
873 void Con_Rcon_Redirect_Flush()
875 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
876 NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
877 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
878 rcon_redirect_bufferpos = 5;
881 void Con_Rcon_Redirect_End()
883 Con_Rcon_Redirect_Flush();
884 rcon_redirect_dest = NULL;
885 rcon_redirect_sock = NULL;
888 void Con_Rcon_Redirect_Abort()
890 rcon_redirect_dest = NULL;
891 rcon_redirect_sock = NULL;
898 Adds a character to the rcon buffer
901 void Con_Rcon_AddChar(int c)
903 if(log_dest_buffer_appending)
905 ++log_dest_buffer_appending;
907 // if this print is in response to an rcon command, add the character
908 // to the rcon redirect buffer
910 if (rcon_redirect_dest)
912 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
913 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
914 Con_Rcon_Redirect_Flush();
916 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
918 if(log_dest_buffer_pos == 0)
919 Log_DestBuffer_Init();
920 log_dest_buffer[log_dest_buffer_pos++] = c;
921 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
922 Log_DestBuffer_Flush();
925 log_dest_buffer_pos = 0;
927 --log_dest_buffer_appending;
931 * Convert an RGB color to its nearest quake color.
932 * I'll cheat on this a bit by translating the colors to HSV first,
933 * S and V decide if it's black or white, otherwise, H will decide the
935 * @param _r Red (0-255)
936 * @param _g Green (0-255)
937 * @param _b Blue (0-255)
938 * @return A quake color character.
940 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
942 float r = ((float)_r)/255.0;
943 float g = ((float)_g)/255.0;
944 float b = ((float)_b)/255.0;
945 float min = min(r, min(g, b));
946 float max = max(r, max(g, b));
948 int h; ///< Hue angle [0,360]
949 float s; ///< Saturation [0,1]
950 float v = max; ///< In HSV v == max [0,1]
957 // Saturation threshold. We now say 0.2 is the minimum value for a color!
960 // If the value is less than half, return a black color code.
961 // Otherwise return a white one.
967 // Let's get the hue angle to define some colors:
971 h = (int)(60.0 * (g-b)/(max-min))%360;
973 h = (int)(60.0 * (b-r)/(max-min) + 120);
974 else // if(max == b) redundant check
975 h = (int)(60.0 * (r-g)/(max-min) + 240);
977 if(h < 36) // *red* to orange
979 else if(h < 80) // orange over *yellow* to evilish-bright-green
981 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
983 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
985 else if(h < 270) // darkish blue over *dark blue* to cool purple
987 else if(h < 330) // cool purple over *purple* to ugly swiny red
989 else // ugly red to red closes the circly
997 Prints to all appropriate console targets, and adds timestamps
1000 extern cvar_t timestamps;
1001 extern cvar_t timeformat;
1002 extern qboolean sys_nostdout;
1003 void Con_Print(const char *msg)
1005 static int mask = 0;
1006 static int index = 0;
1007 static char line[MAX_INPUTLINE];
1011 Con_Rcon_AddChar(*msg);
1012 // if this is the beginning of a new line, print timestamp
1015 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1017 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1018 line[index++] = STRING_COLOR_TAG;
1019 // assert( STRING_COLOR_DEFAULT < 10 )
1020 line[index++] = STRING_COLOR_DEFAULT + '0';
1021 // special color codes for chat messages must always come first
1022 // for Con_PrintToHistory to work properly
1023 if (*msg == 1 || *msg == 2)
1028 if(gamemode == GAME_NEXUIZ)
1030 if(msg[1] == '\r' && cl.foundtalk2wav)
1031 S_LocalSound ("sound/misc/talk2.wav");
1033 S_LocalSound ("sound/misc/talk.wav");
1037 if (msg[1] == '(' && cl.foundtalk2wav)
1038 S_LocalSound ("sound/misc/talk2.wav");
1040 S_LocalSound ("sound/misc/talk.wav");
1042 mask = CON_MASK_CHAT;
1044 line[index++] = STRING_COLOR_TAG;
1045 line[index++] = '3';
1047 Con_Rcon_AddChar(*msg);
1050 for (;*timestamp;index++, timestamp++)
1051 if (index < (int)sizeof(line) - 2)
1052 line[index] = *timestamp;
1054 // append the character
1055 line[index++] = *msg;
1056 // if this is a newline character, we have a complete line to print
1057 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1059 // terminate the line
1063 // send to scrollable buffer
1064 if (con_initialized && cls.state != ca_dedicated)
1066 Con_PrintToHistory(line, mask);
1069 // send to terminal or dedicated server window
1073 if(sys_specialcharactertranslation.integer)
1075 for (p = (unsigned char *) line;*p; p++)
1076 *p = qfont_table[*p];
1079 if(sys_colortranslation.integer == 1) // ANSI
1081 static char printline[MAX_INPUTLINE * 4 + 3];
1082 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1083 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1088 for(in = line, out = printline; *in; ++in)
1092 case STRING_COLOR_TAG:
1093 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1095 char r = tolower(in[2]);
1096 char g = tolower(in[3]);
1097 char b = tolower(in[4]);
1098 // it's a hex digit already, so the else part needs no check --blub
1099 if(isdigit(r)) r -= '0';
1101 if(isdigit(g)) g -= '0';
1103 if(isdigit(b)) b -= '0';
1106 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1107 in += 3; // 3 only, the switch down there does the fourth
1114 case STRING_COLOR_TAG:
1116 *out++ = STRING_COLOR_TAG;
1122 if(lastcolor == 0) break; else lastcolor = 0;
1123 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1128 if(lastcolor == 1) break; else lastcolor = 1;
1129 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1134 if(lastcolor == 2) break; else lastcolor = 2;
1135 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1140 if(lastcolor == 3) break; else lastcolor = 3;
1141 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1146 if(lastcolor == 4) break; else lastcolor = 4;
1147 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1152 if(lastcolor == 5) break; else lastcolor = 5;
1153 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1158 if(lastcolor == 6) break; else lastcolor = 6;
1159 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1164 // bold normal color
1166 if(lastcolor == 8) break; else lastcolor = 8;
1167 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1170 *out++ = STRING_COLOR_TAG;
1177 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1194 Sys_PrintToTerminal(printline);
1196 else if(sys_colortranslation.integer == 2) // Quake
1198 Sys_PrintToTerminal(line);
1202 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1205 for(in = line, out = printline; *in; ++in)
1209 case STRING_COLOR_TAG:
1212 case STRING_COLOR_RGB_TAG_CHAR:
1213 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1218 *out++ = STRING_COLOR_TAG;
1219 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1222 case STRING_COLOR_TAG:
1224 *out++ = STRING_COLOR_TAG;
1239 *out++ = STRING_COLOR_TAG;
1249 Sys_PrintToTerminal(printline);
1252 // empty the line buffer
1263 Prints to all appropriate console targets
1266 void Con_Printf(const char *fmt, ...)
1269 char msg[MAX_INPUTLINE];
1271 va_start(argptr,fmt);
1272 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1282 A Con_Print that only shows up if the "developer" cvar is set
1285 void Con_DPrint(const char *msg)
1287 if (!developer.integer)
1288 return; // don't confuse non-developers with techie stuff...
1296 A Con_Printf that only shows up if the "developer" cvar is set
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;
1621 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1624 ti.fontsize = con_textsize.value;
1625 ti.font = FONT_CONSOLE;
1627 return COM_Wordwrap(con.lines[lineno].start, con.lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1634 Returns the height of a given console line; calculates it if necessary.
1637 int Con_LineHeight(int i)
1639 int h = con.lines[i].height;
1642 return con.lines[i].height = Con_MeasureConsoleLine(i);
1649 Draws a line of the console; returns its height in lines.
1650 If alpha is 0, the line is not drawn, but still wrapped and its height
1654 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1656 float width = vid_conwidth.value;
1659 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1662 ti.continuationString = "";
1664 ti.fontsize = con_textsize.value;
1665 ti.font = FONT_CONSOLE;
1667 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1672 return COM_Wordwrap(con.lines[lineno].start, con.lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1679 Calculates the last visible line index and how much to show of it based on
1683 void Con_LastVisibleLine(int *last, int *limitlast)
1688 if(con_backscroll < 0)
1691 // now count until we saw con_backscroll actual lines
1692 for(ic = 0; ic < con.lines_count; ++ic)
1694 int i = CON_LINES_IDX(con.lines_count - 1 - ic);
1695 int h = Con_LineHeight(i);
1697 // line is the last visible line?
1698 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1701 *limitlast = lines_seen + h - con_backscroll;
1708 // if we get here, no line was on screen - scroll so that one line is
1710 con_backscroll = lines_seen - 1;
1711 *last = con.lines_first;
1719 Draws the console with the solid background
1720 The typing input line at the bottom should only be drawn if typing is allowed
1723 void Con_DrawConsole (int lines)
1725 int i, last, limitlast;
1731 con_vislines = lines;
1733 // draw the background
1734 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
1735 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);
1738 if(con.lines_count > 0)
1740 float ymax = con_vislines - 2 * con_textsize.value;
1741 Con_LastVisibleLine(&last, &limitlast);
1742 y = ymax - con_textsize.value;
1745 y += (con.lines[last].height - limitlast) * con_textsize.value;
1750 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1751 if(i == con.lines_first)
1752 break; // top of console buffer
1754 break; // top of console window
1756 i = CON_LINES_PRED(i);
1760 // draw the input prompt, user text, and cursor if desired
1768 Prints not only map filename, but also
1769 its format (q1/q2/q3/hl) and even its message
1771 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1772 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1773 //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
1774 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1775 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1779 int i, k, max, p, o, min;
1782 unsigned char buf[1024];
1784 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1785 t = FS_Search(message, 1, true);
1788 if (t->numfilenames > 1)
1789 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1790 len = (unsigned char *)Z_Malloc(t->numfilenames);
1792 for(max=i=0;i<t->numfilenames;i++)
1794 k = (int)strlen(t->filenames[i]);
1804 for(i=0;i<t->numfilenames;i++)
1806 int lumpofs = 0, lumplen = 0;
1807 char *entities = NULL;
1808 const char *data = NULL;
1810 char entfilename[MAX_QPATH];
1811 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1813 f = FS_OpenVirtualFile(t->filenames[i], true);
1816 memset(buf, 0, 1024);
1817 FS_Read(f, buf, 1024);
1818 if (!memcmp(buf, "IBSP", 4))
1820 p = LittleLong(((int *)buf)[1]);
1821 if (p == Q3BSPVERSION)
1823 q3dheader_t *header = (q3dheader_t *)buf;
1824 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1825 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1827 else if (p == Q2BSPVERSION)
1829 q2dheader_t *header = (q2dheader_t *)buf;
1830 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1831 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1834 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1836 dheader_t *header = (dheader_t *)buf;
1837 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1838 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1842 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1843 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1844 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1845 if (!entities && lumplen >= 10)
1847 FS_Seek(f, lumpofs, SEEK_SET);
1848 entities = (char *)Z_Malloc(lumplen + 1);
1849 FS_Read(f, entities, lumplen);
1853 // if there are entities to parse, a missing message key just
1854 // means there is no title, so clear the message string now
1860 if (!COM_ParseToken_Simple(&data, false, false))
1862 if (com_token[0] == '{')
1864 if (com_token[0] == '}')
1866 // skip leading whitespace
1867 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
1868 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
1869 keyname[l] = com_token[k+l];
1871 if (!COM_ParseToken_Simple(&data, false, false))
1873 if (developer.integer >= 100)
1874 Con_Printf("key: %s %s\n", keyname, com_token);
1875 if (!strcmp(keyname, "message"))
1877 // get the message contents
1878 strlcpy(message, com_token, sizeof(message));
1888 *(t->filenames[i]+len[i]+5) = 0;
1891 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1892 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1893 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1894 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1895 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1897 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1902 k = *(t->filenames[0]+5+p);
1905 for(i=1;i<t->numfilenames;i++)
1906 if(*(t->filenames[i]+5+p) != k)
1910 if(p > o && completedname && completednamebufferlength > 0)
1912 memset(completedname, 0, completednamebufferlength);
1913 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1923 New function for tab-completion system
1924 Added by EvilTypeGuy
1925 MEGA Thanks to Taniwha
1928 void Con_DisplayList(const char **list)
1930 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1931 const char **walk = list;
1934 len = (int)strlen(*walk);
1942 len = (int)strlen(*list);
1943 if (pos + maxlen >= width) {
1949 for (i = 0; i < (maxlen - len); i++)
1961 SanitizeString strips color tags from the string in
1962 and writes the result on string out
1964 void SanitizeString(char *in, char *out)
1968 if(*in == STRING_COLOR_TAG)
1973 out[0] = STRING_COLOR_TAG;
1977 else if (*in >= '0' && *in <= '9') // ^[0-9] found
1984 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
1987 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
1989 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
1996 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2001 else if (*in != STRING_COLOR_TAG)
2004 *out = qfont_table[*(unsigned char*)in];
2011 // Now it becomes TRICKY :D --blub
2012 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2013 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2014 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2015 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
2016 static int Nicks_matchpos;
2018 // co against <<:BLASTER:>> is true!?
2019 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2023 if(tolower(*a) == tolower(*b))
2037 return (*a < *b) ? -1 : 1;
2041 return (*a < *b) ? -1 : 1;
2045 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2048 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2050 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2051 return Nicks_strncasecmp_nospaces(a, b, a_len);
2052 return strncasecmp(a, b, a_len);
2055 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2057 // ignore non alphanumerics of B
2058 // if A contains a non-alphanumeric, B must contain it as well though!
2061 qboolean alnum_a, alnum_b;
2063 if(tolower(*a) == tolower(*b))
2065 if(*a == 0) // end of both strings, they're equal
2072 // not equal, end of one string?
2077 // ignore non alphanumerics
2078 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2079 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2080 if(!alnum_a) // b must contain this
2081 return (*a < *b) ? -1 : 1;
2084 // otherwise, both are alnum, they're just not equal, return the appropriate number
2086 return (*a < *b) ? -1 : 1;
2092 /* Nicks_CompleteCountPossible
2094 Count the number of possible nicks to complete
2096 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2105 if(!con_nickcompletion.integer)
2108 // changed that to 1
2109 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2112 for(i = 0; i < cl.maxclients; ++i)
2115 if(!cl.scores[p].name[0])
2118 SanitizeString(cl.scores[p].name, name);
2119 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2124 length = strlen(name);
2126 spos = pos - 1; // no need for a minimum of characters :)
2130 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2132 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2133 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2139 if(isCon && spos == 0)
2141 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2147 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2148 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2150 // the sanitized list
2151 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2154 Nicks_matchpos = match;
2157 Nicks_offset[count] = s - (&line[match]);
2158 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2165 void Cmd_CompleteNicksPrint(int count)
2168 for(i = 0; i < count; ++i)
2169 Con_Printf("%s\n", Nicks_list[i]);
2172 void Nicks_CutMatchesNormal(int count)
2174 // cut match 0 down to the longest possible completion
2177 c = strlen(Nicks_sanlist[0]) - 1;
2178 for(i = 1; i < count; ++i)
2180 l = strlen(Nicks_sanlist[i]) - 1;
2184 for(l = 0; l <= c; ++l)
2185 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2191 Nicks_sanlist[0][c+1] = 0;
2192 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2195 unsigned int Nicks_strcleanlen(const char *s)
2200 if( (*s >= 'a' && *s <= 'z') ||
2201 (*s >= 'A' && *s <= 'Z') ||
2202 (*s >= '0' && *s <= '9') ||
2210 void Nicks_CutMatchesAlphaNumeric(int count)
2212 // cut match 0 down to the longest possible completion
2215 char tempstr[sizeof(Nicks_sanlist[0])];
2217 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2219 c = strlen(Nicks_sanlist[0]);
2220 for(i = 0, l = 0; i < (int)c; ++i)
2222 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2223 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2224 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2226 tempstr[l++] = Nicks_sanlist[0][i];
2231 for(i = 1; i < count; ++i)
2234 b = Nicks_sanlist[i];
2244 if(tolower(*a) == tolower(*b))
2250 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2252 // b is alnum, so cut
2259 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2260 Nicks_CutMatchesNormal(count);
2261 //if(!Nicks_sanlist[0][0])
2262 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2264 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2265 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2269 void Nicks_CutMatchesNoSpaces(int count)
2271 // cut match 0 down to the longest possible completion
2274 char tempstr[sizeof(Nicks_sanlist[0])];
2277 c = strlen(Nicks_sanlist[0]);
2278 for(i = 0, l = 0; i < (int)c; ++i)
2280 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2282 tempstr[l++] = Nicks_sanlist[0][i];
2287 for(i = 1; i < count; ++i)
2290 b = Nicks_sanlist[i];
2300 if(tolower(*a) == tolower(*b))
2314 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2315 Nicks_CutMatchesNormal(count);
2316 //if(!Nicks_sanlist[0][0])
2317 //Con_Printf("TS: %s\n", tempstr);
2318 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2320 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2321 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2325 void Nicks_CutMatches(int count)
2327 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2328 Nicks_CutMatchesAlphaNumeric(count);
2329 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2330 Nicks_CutMatchesNoSpaces(count);
2332 Nicks_CutMatchesNormal(count);
2335 const char **Nicks_CompleteBuildList(int count)
2339 // the list is freed by Con_CompleteCommandLine, so create a char**
2340 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2342 for(; bpos < count; ++bpos)
2343 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2345 Nicks_CutMatches(count);
2353 Restores the previous used color, after the autocompleted name.
2355 int Nicks_AddLastColor(char *buffer, int pos)
2357 qboolean quote_added = false;
2359 int color = STRING_COLOR_DEFAULT + '0';
2360 char r = 0, g = 0, b = 0;
2362 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2364 // we'll have to add a quote :)
2365 buffer[pos++] = '\"';
2369 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2371 // add color when no quote was added, or when flags &4?
2373 for(match = Nicks_matchpos-1; match >= 0; --match)
2375 if(buffer[match] == STRING_COLOR_TAG)
2377 if( isdigit(buffer[match+1]) )
2379 color = buffer[match+1];
2382 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2384 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2386 r = buffer[match+2];
2387 g = buffer[match+3];
2388 b = buffer[match+4];
2397 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2399 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2400 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2403 buffer[pos++] = STRING_COLOR_TAG;
2406 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2412 buffer[pos++] = color;
2417 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2420 /*if(!con_nickcompletion.integer)
2421 return; is tested in Nicks_CompletionCountPossible */
2422 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2428 msg = Nicks_list[0];
2429 len = min(size - Nicks_matchpos - 3, strlen(msg));
2430 memcpy(&buffer[Nicks_matchpos], msg, len);
2431 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2432 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2433 buffer[len++] = ' ';
2440 Con_Printf("\n%i possible nicks:\n", n);
2441 Cmd_CompleteNicksPrint(n);
2443 Nicks_CutMatches(n);
2445 msg = Nicks_sanlist[0];
2446 len = min(size - Nicks_matchpos, strlen(msg));
2447 memcpy(&buffer[Nicks_matchpos], msg, len);
2448 buffer[Nicks_matchpos + len] = 0;
2450 return Nicks_matchpos + len;
2457 Con_CompleteCommandLine
2459 New function for tab-completion system
2460 Added by EvilTypeGuy
2461 Thanks to Fett erich@heintz.com
2463 Enhanced to tab-complete map names by [515]
2466 void Con_CompleteCommandLine (void)
2468 const char *cmd = "";
2470 const char **list[4] = {0, 0, 0, 0};
2473 int c, v, a, i, cmd_len, pos, k;
2474 int n; // nicks --blub
2475 const char *space, *patterns;
2477 //find what we want to complete
2482 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2488 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2489 key_line[key_linepos] = 0; //hide them
2491 space = strchr(key_line + 1, ' ');
2492 if(space && pos == (space - key_line) + 1)
2494 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2496 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2497 if(patterns && !*patterns)
2498 patterns = NULL; // get rid of the empty string
2500 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2504 if (GetMapList(s, t, sizeof(t)))
2506 // first move the cursor
2507 key_linepos += (int)strlen(t) - (int)strlen(s);
2509 // and now do the actual work
2511 strlcat(key_line, t, MAX_INPUTLINE);
2512 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2514 // and fix the cursor
2515 if(key_linepos > (int) strlen(key_line))
2516 key_linepos = (int) strlen(key_line);
2525 stringlist_t resultbuf, dirbuf;
2528 // // store completion patterns (space separated) for command foo in con_completion_foo
2529 // set con_completion_foo "foodata/*.foodefault *.foo"
2532 // Note: patterns with slash are always treated as absolute
2533 // patterns; patterns without slash search in the innermost
2534 // directory the user specified. There is no way to "complete into"
2535 // a directory as of now, as directories seem to be unknown to the
2539 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2540 // set con_completion_playdemo "*.dem"
2541 // set con_completion_play "*.wav *.ogg"
2543 // TODO somehow add support for directories; these shall complete
2544 // to their name + an appended slash.
2546 stringlistinit(&resultbuf);
2547 stringlistinit(&dirbuf);
2548 while(COM_ParseToken_Simple(&patterns, false, false))
2551 if(strchr(com_token, '/'))
2553 search = FS_Search(com_token, true, true);
2557 const char *slash = strrchr(s, '/');
2560 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2561 strlcat(t, com_token, sizeof(t));
2562 search = FS_Search(t, true, true);
2565 search = FS_Search(com_token, true, true);
2569 for(i = 0; i < search->numfilenames; ++i)
2570 if(!strncmp(search->filenames[i], s, strlen(s)))
2571 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2572 stringlistappend(&resultbuf, search->filenames[i]);
2573 FS_FreeSearch(search);
2577 // In any case, add directory names
2580 const char *slash = strrchr(s, '/');
2583 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2584 strlcat(t, "*", sizeof(t));
2585 search = FS_Search(t, true, true);
2588 search = FS_Search("*", true, true);
2591 for(i = 0; i < search->numfilenames; ++i)
2592 if(!strncmp(search->filenames[i], s, strlen(s)))
2593 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2594 stringlistappend(&dirbuf, search->filenames[i]);
2595 FS_FreeSearch(search);
2599 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2602 unsigned int matchchars;
2603 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2605 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2608 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2610 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2614 stringlistsort(&resultbuf); // dirbuf is already sorted
2615 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2616 for(i = 0; i < dirbuf.numstrings; ++i)
2618 Con_Printf("%s/\n", dirbuf.strings[i]);
2620 for(i = 0; i < resultbuf.numstrings; ++i)
2622 Con_Printf("%s\n", resultbuf.strings[i]);
2624 matchchars = sizeof(t) - 1;
2625 if(resultbuf.numstrings > 0)
2627 p = resultbuf.strings[0];
2628 q = resultbuf.strings[resultbuf.numstrings - 1];
2629 for(; *p && *p == *q; ++p, ++q);
2630 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2632 if(dirbuf.numstrings > 0)
2634 p = dirbuf.strings[0];
2635 q = dirbuf.strings[dirbuf.numstrings - 1];
2636 for(; *p && *p == *q; ++p, ++q);
2637 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2639 // now p points to the first non-equal character, or to the end
2640 // of resultbuf.strings[0]. We want to append the characters
2641 // from resultbuf.strings[0] to (not including) p as these are
2642 // the unique prefix
2643 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2646 // first move the cursor
2647 key_linepos += (int)strlen(t) - (int)strlen(s);
2649 // and now do the actual work
2651 strlcat(key_line, t, MAX_INPUTLINE);
2652 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2654 // and fix the cursor
2655 if(key_linepos > (int) strlen(key_line))
2656 key_linepos = (int) strlen(key_line);
2658 stringlistfreecontents(&resultbuf);
2659 stringlistfreecontents(&dirbuf);
2661 return; // bail out, when we complete for a command that wants a file name
2666 // Count number of possible matches and print them
2667 c = Cmd_CompleteCountPossible(s);
2670 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2671 Cmd_CompleteCommandPrint(s);
2673 v = Cvar_CompleteCountPossible(s);
2676 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2677 Cvar_CompleteCvarPrint(s);
2679 a = Cmd_CompleteAliasCountPossible(s);
2682 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2683 Cmd_CompleteAliasPrint(s);
2685 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2688 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2689 Cmd_CompleteNicksPrint(n);
2692 if (!(c + v + a + n)) // No possible matches
2695 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2700 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2702 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2704 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2706 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2708 for (cmd_len = (int)strlen(s);;cmd_len++)
2711 for (i = 0; i < 3; i++)
2713 for (l = list[i];*l;l++)
2714 if ((*l)[cmd_len] != cmd[cmd_len])
2716 // all possible matches share this character, so we continue...
2719 // if all matches ended at the same position, stop
2720 // (this means there is only one match)
2726 // prevent a buffer overrun by limiting cmd_len according to remaining space
2727 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2731 memcpy(&key_line[key_linepos], cmd, cmd_len);
2732 key_linepos += cmd_len;
2733 // if there is only one match, add a space after it
2734 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2737 { // was a nick, might have an offset, and needs colors ;) --blub
2738 key_linepos = pos - Nicks_offset[0];
2739 cmd_len = strlen(Nicks_list[0]);
2740 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2742 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2743 key_linepos += cmd_len;
2744 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2745 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2747 key_line[key_linepos++] = ' ';
2751 // use strlcat to avoid a buffer overrun
2752 key_line[key_linepos] = 0;
2753 strlcat(key_line, s2, sizeof(key_line));
2755 // free the command, cvar, and alias lists
2756 for (i = 0; i < 4; i++)
2758 Mem_Free((void *)list[i]);