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__)
32 float con_cursorspeed = 4;
34 #define CON_TEXTSIZE 1048576
35 #define CON_MAXLINES 16384
37 // lines up from bottom to display
42 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
43 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
44 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
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)"};
57 cvar_t con_chatsound = {CVAR_SAVE, "con_chatsound","1", "enables chat sound to play on message"};
60 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)"};
62 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)"};
64 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)"};
68 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
69 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
70 "0: add nothing after completion. "
71 "1: add the last color after completion. "
72 "2: add a quote when starting a quote instead of the color. "
73 "4: will replace 1, will force color, even after a quote. "
74 "8: ignore non-alphanumerics. "
75 "16: ignore spaces. "};
76 #define NICKS_ADD_COLOR 1
77 #define NICKS_ADD_QUOTE 2
78 #define NICKS_FORCE_COLOR 4
79 #define NICKS_ALPHANUMERICS_ONLY 8
80 #define NICKS_NO_SPACES 16
82 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
83 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
84 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
89 qboolean con_initialized;
91 // used for server replies to rcon command
92 lhnetsocket_t *rcon_redirect_sock = NULL;
93 lhnetaddress_t *rcon_redirect_dest = NULL;
94 int rcon_redirect_bufferpos = 0;
95 char rcon_redirect_buffer[1400];
97 // generic functions for console buffers
99 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
101 buf->textsize = textsize;
102 buf->text = (char *) Mem_Alloc(mempool, textsize);
103 buf->maxlines = maxlines;
104 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
105 buf->lines_first = 0;
106 buf->lines_count = 0;
114 void ConBuffer_Clear (conbuffer_t *buf)
116 buf->lines_count = 0;
124 void ConBuffer_Shutdown(conbuffer_t *buf)
127 Mem_Free(buf->lines);
136 Notifies the console code about the current time
137 (and shifts back times of other entries when the time
141 void ConBuffer_FixTimes(conbuffer_t *buf)
144 if(buf->lines_count >= 1)
146 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
149 for(i = 0; i < buf->lines_count; ++i)
150 CONBUFFER_LINES(buf, i).addtime += diff;
159 Deletes the first line from the console history.
162 void ConBuffer_DeleteLine(conbuffer_t *buf)
164 if(buf->lines_count == 0)
167 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
172 ConBuffer_DeleteLastLine
174 Deletes the last line from the console history.
177 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
179 if(buf->lines_count == 0)
188 Checks if there is space for a line of the given length, and if yes, returns a
189 pointer to the start of such a space, and NULL otherwise.
192 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
194 if(len > buf->textsize)
196 if(buf->lines_count == 0)
200 char *firstline_start = buf->lines[buf->lines_first].start;
201 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
202 // the buffer is cyclic, so we first have two cases...
203 if(firstline_start < lastline_onepastend) // buffer is contiguous
206 if(len <= buf->text + buf->textsize - lastline_onepastend)
207 return lastline_onepastend;
209 else if(len <= firstline_start - buf->text)
214 else // buffer has a contiguous hole
216 if(len <= firstline_start - lastline_onepastend)
217 return lastline_onepastend;
228 Appends a given string as a new line to the console.
231 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
236 ConBuffer_FixTimes(buf);
238 if(len >= buf->textsize)
241 // only display end of line.
242 line += len - buf->textsize + 1;
243 len = buf->textsize - 1;
245 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
246 ConBuffer_DeleteLine(buf);
247 memcpy(putpos, line, len);
251 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
253 p = &CONBUFFER_LINES_LAST(buf);
256 p->addtime = cl.time;
258 p->height = -1; // calculate when needed
261 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
265 start = buf->lines_count;
266 for(i = start - 1; i >= 0; --i)
268 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
270 if((l->mask & mask_must) != mask_must)
272 if(l->mask & mask_mustnot)
281 int Con_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
284 for(i = start + 1; i < buf->lines_count; ++i)
286 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
288 if((l->mask & mask_must) != mask_must)
290 if(l->mask & mask_mustnot)
299 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
301 static char copybuf[MAX_INPUTLINE];
302 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
303 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
304 strlcpy(copybuf, l->start, sz);
309 ==============================================================================
313 ==============================================================================
318 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
319 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"};
320 char log_dest_buffer[1400]; // UDP packet
321 size_t log_dest_buffer_pos;
322 unsigned int log_dest_buffer_appending;
323 char crt_log_file [MAX_OSPATH] = "";
324 qfile_t* logfile = NULL;
326 unsigned char* logqueue = NULL;
328 size_t logq_size = 0;
330 void Log_ConPrint (const char *msg);
337 static void Log_DestBuffer_Init(void)
339 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
340 log_dest_buffer_pos = 5;
348 void Log_DestBuffer_Flush(void)
350 lhnetaddress_t log_dest_addr;
351 lhnetsocket_t *log_dest_socket;
352 const char *s = log_dest_udp.string;
353 qboolean have_opened_temp_sockets = false;
354 if(s) if(log_dest_buffer_pos > 5)
356 ++log_dest_buffer_appending;
357 log_dest_buffer[log_dest_buffer_pos++] = 0;
359 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
361 have_opened_temp_sockets = true;
362 NetConn_OpenServerPorts(true);
365 while(COM_ParseToken_Console(&s))
366 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
368 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
370 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
372 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
375 if(have_opened_temp_sockets)
376 NetConn_CloseServerPorts();
377 --log_dest_buffer_appending;
379 log_dest_buffer_pos = 0;
387 const char* Log_Timestamp (const char *desc)
389 static char timestamp [128];
396 char timestring [64];
398 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
401 localtime_s (&crt_tm, &crt_time);
402 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
404 crt_tm = localtime (&crt_time);
405 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
409 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
411 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
424 if (logfile != NULL || log_file.string[0] == '\0')
427 logfile = FS_OpenRealFile(log_file.string, "a", false);
430 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
431 FS_Print (logfile, Log_Timestamp ("Log started"));
441 void Log_Close (void)
446 FS_Print (logfile, Log_Timestamp ("Log stopped"));
447 FS_Print (logfile, "\n");
451 crt_log_file[0] = '\0';
460 void Log_Start (void)
466 // Dump the contents of the log queue into the log file and free it
467 if (logqueue != NULL)
469 unsigned char *temp = logqueue;
474 FS_Write (logfile, temp, logq_ind);
475 if(*log_dest_udp.string)
477 for(pos = 0; pos < logq_ind; )
479 if(log_dest_buffer_pos == 0)
480 Log_DestBuffer_Init();
481 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
482 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
483 log_dest_buffer_pos += n;
484 Log_DestBuffer_Flush();
501 void Log_ConPrint (const char *msg)
503 static qboolean inprogress = false;
505 // don't allow feedback loops with memory error reports
510 // Until the host is completely initialized, we maintain a log queue
511 // to store the messages, since the log can't be started before
512 if (logqueue != NULL)
514 size_t remain = logq_size - logq_ind;
515 size_t len = strlen (msg);
517 // If we need to enlarge the log queue
520 size_t factor = ((logq_ind + len) / logq_size) + 1;
521 unsigned char* newqueue;
524 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
525 memcpy (newqueue, logqueue, logq_ind);
528 remain = logq_size - logq_ind;
530 memcpy (&logqueue[logq_ind], msg, len);
537 // Check if log_file has changed
538 if (strcmp (crt_log_file, log_file.string) != 0)
544 // If a log file is available
546 FS_Print (logfile, msg);
557 void Log_Printf (const char *logfilename, const char *fmt, ...)
561 file = FS_OpenRealFile(logfilename, "a", true);
566 va_start (argptr, fmt);
567 FS_VPrintf (file, fmt, argptr);
576 ==============================================================================
580 ==============================================================================
588 void Con_ToggleConsole_f (void)
590 // toggle the 'user wants console' bit
591 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
600 void Con_ClearNotify (void)
603 for(i = 0; i < CON_LINES_COUNT; ++i)
604 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
613 void Con_MessageMode_f (void)
615 key_dest = key_message;
616 chat_mode = 0; // "say"
627 void Con_MessageMode2_f (void)
629 key_dest = key_message;
630 chat_mode = 1; // "say_team"
640 void Con_CommandMode_f (void)
642 key_dest = key_message;
645 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
646 chat_bufferlen = strlen(chat_buffer);
648 chat_mode = -1; // command
656 void Con_CheckResize (void)
661 f = bound(1, con_textsize.value, 128);
662 if(f != con_textsize.value)
663 Cvar_SetValueQuick(&con_textsize, f);
664 width = (int)floor(vid_conwidth.value / con_textsize.value);
665 width = bound(1, width, con.textsize/4);
666 // FIXME uses con in a non abstracted way
668 if (width == con_linewidth)
671 con_linewidth = width;
673 for(i = 0; i < CON_LINES_COUNT; ++i)
674 CON_LINES(i).height = -1; // recalculate when next needed
680 //[515]: the simplest command ever
681 //LordHavoc: not so simple after I made it print usage...
682 static void Con_Maps_f (void)
686 Con_Printf("usage: maps [mapnameprefix]\n");
689 else if (Cmd_Argc() == 2)
690 GetMapList(Cmd_Argv(1), NULL, 0);
692 GetMapList("", NULL, 0);
695 void Con_ConDump_f (void)
701 Con_Printf("usage: condump <filename>\n");
704 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
707 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
710 for(i = 0; i < CON_LINES_COUNT; ++i)
712 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
713 FS_Write(file, "\n", 1);
718 void Con_Clear_f (void)
720 ConBuffer_Clear(&con);
731 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
733 // Allocate a log queue, this will be freed after configs are parsed
734 logq_size = MAX_INPUTLINE;
735 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
738 Cvar_RegisterVariable (&sys_colortranslation);
739 Cvar_RegisterVariable (&sys_specialcharactertranslation);
741 Cvar_RegisterVariable (&log_file);
742 Cvar_RegisterVariable (&log_dest_udp);
744 // support for the classic Quake option
745 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
746 if (COM_CheckParm ("-condebug") != 0)
747 Cvar_SetQuick (&log_file, "qconsole.log");
749 // register our cvars
750 Cvar_RegisterVariable (&con_chat);
751 Cvar_RegisterVariable (&con_chatpos);
752 Cvar_RegisterVariable (&con_chatsize);
753 Cvar_RegisterVariable (&con_chattime);
754 Cvar_RegisterVariable (&con_chatwidth);
755 Cvar_RegisterVariable (&con_notify);
756 Cvar_RegisterVariable (&con_notifyalign);
757 Cvar_RegisterVariable (&con_notifysize);
758 Cvar_RegisterVariable (&con_notifytime);
759 Cvar_RegisterVariable (&con_textsize);
760 Cvar_RegisterVariable (&con_chatsound);
763 Cvar_RegisterVariable (&con_nickcompletion);
764 Cvar_RegisterVariable (&con_nickcompletion_flags);
766 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
767 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
768 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
770 // register our commands
771 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
772 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
773 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
774 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
775 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
776 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
777 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
779 con_initialized = true;
780 Con_DPrint("Console initialized.\n");
783 void Con_Shutdown (void)
785 ConBuffer_Shutdown(&con);
792 Handles cursor positioning, line wrapping, etc
793 All console printing must go through this in order to be displayed
794 If no console is visible, the notify window will pop up.
797 void Con_PrintToHistory(const char *txt, int mask)
800 // \n goes to next line
801 // \r deletes current line and makes a new one
803 static int cr_pending = 0;
804 static char buf[CON_TEXTSIZE];
805 static int bufpos = 0;
807 if(!con.text) // FIXME uses a non-abstracted property of con
814 ConBuffer_DeleteLastLine(&con);
822 ConBuffer_AddLine(&con, buf, bufpos, mask);
827 ConBuffer_AddLine(&con, buf, bufpos, mask);
831 buf[bufpos++] = *txt;
832 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
834 ConBuffer_AddLine(&con, buf, bufpos, mask);
842 /*! The translation table between the graphical font and plain ASCII --KB */
843 static char qfont_table[256] = {
844 '\0', '#', '#', '#', '#', '.', '#', '#',
845 '#', 9, 10, '#', ' ', 13, '.', '.',
846 '[', ']', '0', '1', '2', '3', '4', '5',
847 '6', '7', '8', '9', '.', '<', '=', '>',
848 ' ', '!', '"', '#', '$', '%', '&', '\'',
849 '(', ')', '*', '+', ',', '-', '.', '/',
850 '0', '1', '2', '3', '4', '5', '6', '7',
851 '8', '9', ':', ';', '<', '=', '>', '?',
852 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
853 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
854 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
855 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
856 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
857 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
858 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
859 'x', 'y', 'z', '{', '|', '}', '~', '<',
861 '<', '=', '>', '#', '#', '.', '#', '#',
862 '#', '#', ' ', '#', ' ', '>', '.', '.',
863 '[', ']', '0', '1', '2', '3', '4', '5',
864 '6', '7', '8', '9', '.', '<', '=', '>',
865 ' ', '!', '"', '#', '$', '%', '&', '\'',
866 '(', ')', '*', '+', ',', '-', '.', '/',
867 '0', '1', '2', '3', '4', '5', '6', '7',
868 '8', '9', ':', ';', '<', '=', '>', '?',
869 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
870 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
871 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
872 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
873 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
874 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
875 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
876 'x', 'y', 'z', '{', '|', '}', '~', '<'
879 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest)
881 rcon_redirect_sock = sock;
882 rcon_redirect_dest = dest;
883 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
884 rcon_redirect_bufferpos = 5;
887 void Con_Rcon_Redirect_Flush(void)
889 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
890 NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
891 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
892 rcon_redirect_bufferpos = 5;
895 void Con_Rcon_Redirect_End(void)
897 Con_Rcon_Redirect_Flush();
898 rcon_redirect_dest = NULL;
899 rcon_redirect_sock = NULL;
902 void Con_Rcon_Redirect_Abort(void)
904 rcon_redirect_dest = NULL;
905 rcon_redirect_sock = NULL;
913 /// Adds a character to the rcon buffer.
914 void Con_Rcon_AddChar(int c)
916 if(log_dest_buffer_appending)
918 ++log_dest_buffer_appending;
920 // if this print is in response to an rcon command, add the character
921 // to the rcon redirect buffer
923 if (rcon_redirect_dest)
925 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
926 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
927 Con_Rcon_Redirect_Flush();
929 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
931 if(log_dest_buffer_pos == 0)
932 Log_DestBuffer_Init();
933 log_dest_buffer[log_dest_buffer_pos++] = c;
934 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
935 Log_DestBuffer_Flush();
938 log_dest_buffer_pos = 0;
940 --log_dest_buffer_appending;
944 * Convert an RGB color to its nearest quake color.
945 * I'll cheat on this a bit by translating the colors to HSV first,
946 * S and V decide if it's black or white, otherwise, H will decide the
948 * @param _r Red (0-255)
949 * @param _g Green (0-255)
950 * @param _b Blue (0-255)
951 * @return A quake color character.
953 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
955 float r = ((float)_r)/255.0;
956 float g = ((float)_g)/255.0;
957 float b = ((float)_b)/255.0;
958 float min = min(r, min(g, b));
959 float max = max(r, max(g, b));
961 int h; ///< Hue angle [0,360]
962 float s; ///< Saturation [0,1]
963 float v = max; ///< In HSV v == max [0,1]
970 // Saturation threshold. We now say 0.2 is the minimum value for a color!
973 // If the value is less than half, return a black color code.
974 // Otherwise return a white one.
980 // Let's get the hue angle to define some colors:
984 h = (int)(60.0 * (g-b)/(max-min))%360;
986 h = (int)(60.0 * (b-r)/(max-min) + 120);
987 else // if(max == b) redundant check
988 h = (int)(60.0 * (r-g)/(max-min) + 240);
990 if(h < 36) // *red* to orange
992 else if(h < 80) // orange over *yellow* to evilish-bright-green
994 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
996 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
998 else if(h < 270) // darkish blue over *dark blue* to cool purple
1000 else if(h < 330) // cool purple over *purple* to ugly swiny red
1002 else // ugly red to red closes the circly
1011 extern cvar_t timestamps;
1012 extern cvar_t timeformat;
1013 extern qboolean sys_nostdout;
1014 void Con_Print(const char *msg)
1016 static int mask = 0;
1017 static int index = 0;
1018 static char line[MAX_INPUTLINE];
1022 Con_Rcon_AddChar(*msg);
1023 // if this is the beginning of a new line, print timestamp
1026 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1028 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1029 line[index++] = STRING_COLOR_TAG;
1030 // assert( STRING_COLOR_DEFAULT < 10 )
1031 line[index++] = STRING_COLOR_DEFAULT + '0';
1032 // special color codes for chat messages must always come first
1033 // for Con_PrintToHistory to work properly
1034 if (*msg == 1 || *msg == 2)
1039 if (con_chatsound.value)
1041 if(gamemode == GAME_NEXUIZ)
1043 if(msg[1] == '\r' && cl.foundtalk2wav)
1044 S_LocalSound ("sound/misc/talk2.wav");
1046 S_LocalSound ("sound/misc/talk.wav");
1050 if (msg[1] == '(' && cl.foundtalk2wav)
1051 S_LocalSound ("sound/misc/talk2.wav");
1053 S_LocalSound ("sound/misc/talk.wav");
1056 mask = CON_MASK_CHAT;
1058 line[index++] = STRING_COLOR_TAG;
1059 line[index++] = '3';
1061 Con_Rcon_AddChar(*msg);
1064 for (;*timestamp;index++, timestamp++)
1065 if (index < (int)sizeof(line) - 2)
1066 line[index] = *timestamp;
1068 // append the character
1069 line[index++] = *msg;
1070 // if this is a newline character, we have a complete line to print
1071 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1073 // terminate the line
1077 // send to scrollable buffer
1078 if (con_initialized && cls.state != ca_dedicated)
1080 Con_PrintToHistory(line, mask);
1083 // send to terminal or dedicated server window
1087 if(sys_specialcharactertranslation.integer)
1089 for (p = (unsigned char *) line;*p; p++)
1090 *p = qfont_table[*p];
1093 if(sys_colortranslation.integer == 1) // ANSI
1095 static char printline[MAX_INPUTLINE * 4 + 3];
1096 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1097 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1102 for(in = line, out = printline; *in; ++in)
1106 case STRING_COLOR_TAG:
1107 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1109 char r = tolower(in[2]);
1110 char g = tolower(in[3]);
1111 char b = tolower(in[4]);
1112 // it's a hex digit already, so the else part needs no check --blub
1113 if(isdigit(r)) r -= '0';
1115 if(isdigit(g)) g -= '0';
1117 if(isdigit(b)) b -= '0';
1120 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1121 in += 3; // 3 only, the switch down there does the fourth
1128 case STRING_COLOR_TAG:
1130 *out++ = STRING_COLOR_TAG;
1136 if(lastcolor == 0) break; else lastcolor = 0;
1137 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1142 if(lastcolor == 1) break; else lastcolor = 1;
1143 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1148 if(lastcolor == 2) break; else lastcolor = 2;
1149 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1154 if(lastcolor == 3) break; else lastcolor = 3;
1155 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1160 if(lastcolor == 4) break; else lastcolor = 4;
1161 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1166 if(lastcolor == 5) break; else lastcolor = 5;
1167 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1172 if(lastcolor == 6) break; else lastcolor = 6;
1173 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1178 // bold normal color
1180 if(lastcolor == 8) break; else lastcolor = 8;
1181 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1184 *out++ = STRING_COLOR_TAG;
1191 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1208 Sys_PrintToTerminal(printline);
1210 else if(sys_colortranslation.integer == 2) // Quake
1212 Sys_PrintToTerminal(line);
1216 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1219 for(in = line, out = printline; *in; ++in)
1223 case STRING_COLOR_TAG:
1226 case STRING_COLOR_RGB_TAG_CHAR:
1227 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1232 *out++ = STRING_COLOR_TAG;
1233 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1236 case STRING_COLOR_TAG:
1238 *out++ = STRING_COLOR_TAG;
1253 *out++ = STRING_COLOR_TAG;
1263 Sys_PrintToTerminal(printline);
1266 // empty the line buffer
1278 void Con_Printf(const char *fmt, ...)
1281 char msg[MAX_INPUTLINE];
1283 va_start(argptr,fmt);
1284 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1295 void Con_DPrint(const char *msg)
1297 if (!developer.integer)
1298 return; // don't confuse non-developers with techie stuff...
1307 void Con_DPrintf(const char *fmt, ...)
1310 char msg[MAX_INPUTLINE];
1312 if (!developer.integer)
1313 return; // don't confuse non-developers with techie stuff...
1315 va_start(argptr,fmt);
1316 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1324 ==============================================================================
1328 ==============================================================================
1335 The input line scrolls horizontally if typing goes beyond the right edge
1337 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1340 void Con_DrawInput (void)
1344 char editlinecopy[MAX_INPUTLINE+1], *text;
1345 char tempbuf[MAX_INPUTLINE+1];
1348 if (!key_consoleactive)
1349 return; // don't draw anything
1351 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1352 text = editlinecopy;
1354 // Advanced Console Editing by Radix radix@planetquake.com
1355 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1356 // use strlen of edit_line instead of key_linepos to allow editing
1357 // of early characters w/o erasing
1359 y = (int)strlen(text);
1361 // fill out remainder with spaces
1362 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1365 // add the cursor frame
1366 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1368 if (y + 3 < (int)sizeof(editlinecopy)-1)
1370 // get the character after the current key linepos:
1371 int ofs = u8_bytelen(text + key_linepos+1, 1);
1372 // copy the remainder
1373 memcpy(tempbuf, text + key_linepos+ofs, sizeof(tempbuf) - key_linepos - ofs);
1376 text[key_linepos] = '\xee';
1377 text[key_linepos+1] = '\x80';
1378 text[key_linepos+2] = '\x8b';
1380 text[key_linepos] = '\xee';
1381 text[key_linepos+1] = '\x82';
1382 text[key_linepos+2] = '\x82';
1384 memcpy(text + key_linepos + 3, tempbuf, sizeof(tempbuf) - key_linepos - ofs);
1386 //text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1387 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1391 // text[key_linepos + 1] = 0;
1393 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1398 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 );
1401 // key_line[key_linepos] = 0;
1407 float alignment; // 0 = left, 0.5 = center, 1 = right
1413 const char *continuationString;
1416 int colorindex; // init to -1
1420 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1422 con_text_info_t *ti = (con_text_info_t *) passthrough;
1425 ti->colorindex = -1;
1426 return ti->fontsize * ti->font->maxwidth;
1429 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1430 else if(maxWidth == -1)
1431 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1434 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1435 // Note: this is NOT a Con_Printf, as it could print recursively
1440 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1446 (void) isContinuation;
1450 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1452 con_text_info_t *ti = (con_text_info_t *) passthrough;
1454 if(ti->y < ti->ymin - 0.001)
1456 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1460 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1461 if(isContinuation && *ti->continuationString)
1462 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);
1464 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);
1467 ti->y += ti->fontsize;
1471 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)
1475 int maxlines = (int) floor(height / fontsize + 0.01f);
1478 int continuationWidth = 0;
1480 double t = cl.time; // saved so it won't change
1483 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1484 ti.fontsize = fontsize;
1485 ti.alignment = alignment_x;
1488 ti.ymax = y + height;
1489 ti.continuationString = continuationString;
1492 Con_WordWidthFunc(&ti, NULL, &l, -1);
1493 l = strlen(continuationString);
1494 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1496 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1497 startidx = CON_LINES_COUNT;
1498 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1500 con_lineinfo_t *l = &CON_LINES(i);
1503 if((l->mask & mask_must) != mask_must)
1505 if(l->mask & mask_mustnot)
1507 if(maxage && (l->addtime < t - maxage))
1511 // Calculate its actual height...
1512 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1513 if(lines + mylines >= maxlines)
1515 nskip = lines + mylines - maxlines;
1524 // then center according to the calculated amount of lines...
1526 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1528 // then actually draw
1529 for(i = startidx; i < CON_LINES_COUNT; ++i)
1531 con_lineinfo_t *l = &CON_LINES(i);
1533 if((l->mask & mask_must) != mask_must)
1535 if(l->mask & mask_mustnot)
1537 if(maxage && (l->addtime < t - maxage))
1540 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1550 Draws the last few lines of output transparently over the game top
1553 void Con_DrawNotify (void)
1556 float chatstart, notifystart, inputsize;
1558 char temptext[MAX_INPUTLINE];
1562 ConBuffer_FixTimes(&con);
1564 numChatlines = con_chat.integer;
1565 chatpos = con_chatpos.integer;
1567 if (con_notify.integer < 0)
1568 Cvar_SetValueQuick(&con_notify, 0);
1569 if (gamemode == GAME_TRANSFUSION)
1570 v = 8; // vertical offset
1574 // GAME_NEXUIZ: center, otherwise left justify
1575 align = con_notifyalign.value;
1576 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1578 if(gamemode == GAME_NEXUIZ)
1586 // first chat, input line, then notify
1588 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1590 else if(chatpos > 0)
1592 // first notify, then (chatpos-1) empty lines, then chat, then input
1594 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1596 else // if(chatpos < 0)
1598 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1600 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1605 // just notify and input
1607 chatstart = 0; // shut off gcc warning
1610 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, "");
1615 v = chatstart + numChatlines * con_chatsize.value;
1616 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 "*/ "^3\xee\x80\x8d\xee\x80\x8d\xee\x80\x8d "); // 015 is ·> character in conchars.tga
1619 if (key_dest == key_message)
1621 static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1622 int colorindex = -1;
1624 // LordHavoc: speedup, and other improvements
1626 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor[(int)(realtime*con_cursorspeed)&1]);
1628 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor[(int)(realtime*con_cursorspeed)&1]);
1630 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor[(int)(realtime*con_cursorspeed)&1]);
1633 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1634 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1637 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1643 Con_MeasureConsoleLine
1645 Counts the number of lines for a line on the console.
1648 int Con_MeasureConsoleLine(int lineno)
1650 float width = vid_conwidth.value;
1652 con_lineinfo_t *li = &CON_LINES(lineno);
1654 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1657 ti.fontsize = con_textsize.value;
1658 ti.font = FONT_CONSOLE;
1660 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1667 Returns the height of a given console line; calculates it if necessary.
1670 int Con_LineHeight(int i)
1672 con_lineinfo_t *li = &CON_LINES(i);
1676 return li->height = Con_MeasureConsoleLine(i);
1683 Draws a line of the console; returns its height in lines.
1684 If alpha is 0, the line is not drawn, but still wrapped and its height
1688 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1690 float width = vid_conwidth.value;
1692 con_lineinfo_t *li = &CON_LINES(lineno);
1694 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1697 ti.continuationString = "";
1699 ti.fontsize = con_textsize.value;
1700 ti.font = FONT_CONSOLE;
1702 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1707 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1714 Calculates the last visible line index and how much to show of it based on
1718 void Con_LastVisibleLine(int *last, int *limitlast)
1723 if(con_backscroll < 0)
1726 // now count until we saw con_backscroll actual lines
1727 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1729 int h = Con_LineHeight(i);
1731 // line is the last visible line?
1732 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1735 *limitlast = lines_seen + h - con_backscroll;
1742 // if we get here, no line was on screen - scroll so that one line is
1744 con_backscroll = lines_seen - 1;
1745 *last = con.lines_first;
1746 // FIXME uses con in a non abstracted way
1754 Draws the console with the solid background
1755 The typing input line at the bottom should only be drawn if typing is allowed
1758 void Con_DrawConsole (int lines)
1760 int i, last, limitlast;
1766 con_vislines = lines;
1768 // draw the background
1769 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
1770 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);
1773 if(CON_LINES_COUNT > 0)
1775 float ymax = con_vislines - 2 * con_textsize.value;
1776 Con_LastVisibleLine(&last, &limitlast);
1777 y = ymax - con_textsize.value;
1780 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1781 // FIXME uses con in a non abstracted way
1786 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1788 break; // top of console buffer
1790 break; // top of console window
1796 // draw the input prompt, user text, and cursor if desired
1804 Prints not only map filename, but also
1805 its format (q1/q2/q3/hl) and even its message
1807 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1808 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1809 //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
1810 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1811 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1815 int i, k, max, p, o, min;
1818 unsigned char buf[1024];
1820 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1821 t = FS_Search(message, 1, true);
1824 if (t->numfilenames > 1)
1825 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1826 len = (unsigned char *)Z_Malloc(t->numfilenames);
1828 for(max=i=0;i<t->numfilenames;i++)
1830 k = (int)strlen(t->filenames[i]);
1840 for(i=0;i<t->numfilenames;i++)
1842 int lumpofs = 0, lumplen = 0;
1843 char *entities = NULL;
1844 const char *data = NULL;
1846 char entfilename[MAX_QPATH];
1847 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1849 f = FS_OpenVirtualFile(t->filenames[i], true);
1852 memset(buf, 0, 1024);
1853 FS_Read(f, buf, 1024);
1854 if (!memcmp(buf, "IBSP", 4))
1856 p = LittleLong(((int *)buf)[1]);
1857 if (p == Q3BSPVERSION)
1859 q3dheader_t *header = (q3dheader_t *)buf;
1860 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1861 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1863 else if (p == Q2BSPVERSION)
1865 q2dheader_t *header = (q2dheader_t *)buf;
1866 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1867 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1870 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1872 dheader_t *header = (dheader_t *)buf;
1873 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1874 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1878 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1879 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1880 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1881 if (!entities && lumplen >= 10)
1883 FS_Seek(f, lumpofs, SEEK_SET);
1884 entities = (char *)Z_Malloc(lumplen + 1);
1885 FS_Read(f, entities, lumplen);
1889 // if there are entities to parse, a missing message key just
1890 // means there is no title, so clear the message string now
1896 if (!COM_ParseToken_Simple(&data, false, false))
1898 if (com_token[0] == '{')
1900 if (com_token[0] == '}')
1902 // skip leading whitespace
1903 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
1904 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
1905 keyname[l] = com_token[k+l];
1907 if (!COM_ParseToken_Simple(&data, false, false))
1909 if (developer.integer >= 100)
1910 Con_Printf("key: %s %s\n", keyname, com_token);
1911 if (!strcmp(keyname, "message"))
1913 // get the message contents
1914 strlcpy(message, com_token, sizeof(message));
1924 *(t->filenames[i]+len[i]+5) = 0;
1927 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1928 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1929 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1930 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1931 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1933 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1938 k = *(t->filenames[0]+5+p);
1941 for(i=1;i<t->numfilenames;i++)
1942 if(*(t->filenames[i]+5+p) != k)
1946 if(p > o && completedname && completednamebufferlength > 0)
1948 memset(completedname, 0, completednamebufferlength);
1949 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1959 New function for tab-completion system
1960 Added by EvilTypeGuy
1961 MEGA Thanks to Taniwha
1964 void Con_DisplayList(const char **list)
1966 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1967 const char **walk = list;
1970 len = (int)strlen(*walk);
1978 len = (int)strlen(*list);
1979 if (pos + maxlen >= width) {
1985 for (i = 0; i < (maxlen - len); i++)
1997 SanitizeString strips color tags from the string in
1998 and writes the result on string out
2000 void SanitizeString(char *in, char *out)
2004 if(*in == STRING_COLOR_TAG)
2009 out[0] = STRING_COLOR_TAG;
2013 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2020 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2023 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2025 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2032 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2037 else if (*in != STRING_COLOR_TAG)
2040 *out = qfont_table[*(unsigned char*)in];
2047 // Now it becomes TRICKY :D --blub
2048 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2049 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2050 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2051 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
2052 static int Nicks_matchpos;
2054 // co against <<:BLASTER:>> is true!?
2055 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2059 if(tolower(*a) == tolower(*b))
2073 return (*a < *b) ? -1 : 1;
2077 return (*a < *b) ? -1 : 1;
2081 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2084 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2086 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2087 return Nicks_strncasecmp_nospaces(a, b, a_len);
2088 return strncasecmp(a, b, a_len);
2091 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2093 // ignore non alphanumerics of B
2094 // if A contains a non-alphanumeric, B must contain it as well though!
2097 qboolean alnum_a, alnum_b;
2099 if(tolower(*a) == tolower(*b))
2101 if(*a == 0) // end of both strings, they're equal
2108 // not equal, end of one string?
2113 // ignore non alphanumerics
2114 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2115 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2116 if(!alnum_a) // b must contain this
2117 return (*a < *b) ? -1 : 1;
2120 // otherwise, both are alnum, they're just not equal, return the appropriate number
2122 return (*a < *b) ? -1 : 1;
2128 /* Nicks_CompleteCountPossible
2130 Count the number of possible nicks to complete
2132 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2141 if(!con_nickcompletion.integer)
2144 // changed that to 1
2145 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2148 for(i = 0; i < cl.maxclients; ++i)
2151 if(!cl.scores[p].name[0])
2154 SanitizeString(cl.scores[p].name, name);
2155 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2160 length = strlen(name);
2162 spos = pos - 1; // no need for a minimum of characters :)
2166 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2168 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2169 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2175 if(isCon && spos == 0)
2177 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2183 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2184 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2186 // the sanitized list
2187 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2190 Nicks_matchpos = match;
2193 Nicks_offset[count] = s - (&line[match]);
2194 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2201 void Cmd_CompleteNicksPrint(int count)
2204 for(i = 0; i < count; ++i)
2205 Con_Printf("%s\n", Nicks_list[i]);
2208 void Nicks_CutMatchesNormal(int count)
2210 // cut match 0 down to the longest possible completion
2213 c = strlen(Nicks_sanlist[0]) - 1;
2214 for(i = 1; i < count; ++i)
2216 l = strlen(Nicks_sanlist[i]) - 1;
2220 for(l = 0; l <= c; ++l)
2221 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2227 Nicks_sanlist[0][c+1] = 0;
2228 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2231 unsigned int Nicks_strcleanlen(const char *s)
2236 if( (*s >= 'a' && *s <= 'z') ||
2237 (*s >= 'A' && *s <= 'Z') ||
2238 (*s >= '0' && *s <= '9') ||
2246 void Nicks_CutMatchesAlphaNumeric(int count)
2248 // cut match 0 down to the longest possible completion
2251 char tempstr[sizeof(Nicks_sanlist[0])];
2253 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2255 c = strlen(Nicks_sanlist[0]);
2256 for(i = 0, l = 0; i < (int)c; ++i)
2258 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2259 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2260 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2262 tempstr[l++] = Nicks_sanlist[0][i];
2267 for(i = 1; i < count; ++i)
2270 b = Nicks_sanlist[i];
2280 if(tolower(*a) == tolower(*b))
2286 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2288 // b is alnum, so cut
2295 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2296 Nicks_CutMatchesNormal(count);
2297 //if(!Nicks_sanlist[0][0])
2298 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2300 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2301 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2305 void Nicks_CutMatchesNoSpaces(int count)
2307 // cut match 0 down to the longest possible completion
2310 char tempstr[sizeof(Nicks_sanlist[0])];
2313 c = strlen(Nicks_sanlist[0]);
2314 for(i = 0, l = 0; i < (int)c; ++i)
2316 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2318 tempstr[l++] = Nicks_sanlist[0][i];
2323 for(i = 1; i < count; ++i)
2326 b = Nicks_sanlist[i];
2336 if(tolower(*a) == tolower(*b))
2350 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2351 Nicks_CutMatchesNormal(count);
2352 //if(!Nicks_sanlist[0][0])
2353 //Con_Printf("TS: %s\n", tempstr);
2354 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2356 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2357 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2361 void Nicks_CutMatches(int count)
2363 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2364 Nicks_CutMatchesAlphaNumeric(count);
2365 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2366 Nicks_CutMatchesNoSpaces(count);
2368 Nicks_CutMatchesNormal(count);
2371 const char **Nicks_CompleteBuildList(int count)
2375 // the list is freed by Con_CompleteCommandLine, so create a char**
2376 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2378 for(; bpos < count; ++bpos)
2379 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2381 Nicks_CutMatches(count);
2389 Restores the previous used color, after the autocompleted name.
2391 int Nicks_AddLastColor(char *buffer, int pos)
2393 qboolean quote_added = false;
2395 int color = STRING_COLOR_DEFAULT + '0';
2396 char r = 0, g = 0, b = 0;
2398 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2400 // we'll have to add a quote :)
2401 buffer[pos++] = '\"';
2405 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2407 // add color when no quote was added, or when flags &4?
2409 for(match = Nicks_matchpos-1; match >= 0; --match)
2411 if(buffer[match] == STRING_COLOR_TAG)
2413 if( isdigit(buffer[match+1]) )
2415 color = buffer[match+1];
2418 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2420 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2422 r = buffer[match+2];
2423 g = buffer[match+3];
2424 b = buffer[match+4];
2433 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2435 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2436 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2439 buffer[pos++] = STRING_COLOR_TAG;
2442 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2448 buffer[pos++] = color;
2453 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2456 /*if(!con_nickcompletion.integer)
2457 return; is tested in Nicks_CompletionCountPossible */
2458 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2464 msg = Nicks_list[0];
2465 len = min(size - Nicks_matchpos - 3, strlen(msg));
2466 memcpy(&buffer[Nicks_matchpos], msg, len);
2467 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2468 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2469 buffer[len++] = ' ';
2476 Con_Printf("\n%i possible nicks:\n", n);
2477 Cmd_CompleteNicksPrint(n);
2479 Nicks_CutMatches(n);
2481 msg = Nicks_sanlist[0];
2482 len = min(size - Nicks_matchpos, strlen(msg));
2483 memcpy(&buffer[Nicks_matchpos], msg, len);
2484 buffer[Nicks_matchpos + len] = 0;
2486 return Nicks_matchpos + len;
2493 Con_CompleteCommandLine
2495 New function for tab-completion system
2496 Added by EvilTypeGuy
2497 Thanks to Fett erich@heintz.com
2499 Enhanced to tab-complete map names by [515]
2502 void Con_CompleteCommandLine (void)
2504 const char *cmd = "";
2506 const char **list[4] = {0, 0, 0, 0};
2509 int c, v, a, i, cmd_len, pos, k;
2510 int n; // nicks --blub
2511 const char *space, *patterns;
2513 //find what we want to complete
2518 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2524 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2525 key_line[key_linepos] = 0; //hide them
2527 space = strchr(key_line + 1, ' ');
2528 if(space && pos == (space - key_line) + 1)
2530 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2532 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2533 if(patterns && !*patterns)
2534 patterns = NULL; // get rid of the empty string
2536 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2540 if (GetMapList(s, t, sizeof(t)))
2542 // first move the cursor
2543 key_linepos += (int)strlen(t) - (int)strlen(s);
2545 // and now do the actual work
2547 strlcat(key_line, t, MAX_INPUTLINE);
2548 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2550 // and fix the cursor
2551 if(key_linepos > (int) strlen(key_line))
2552 key_linepos = (int) strlen(key_line);
2561 stringlist_t resultbuf, dirbuf;
2564 // // store completion patterns (space separated) for command foo in con_completion_foo
2565 // set con_completion_foo "foodata/*.foodefault *.foo"
2568 // Note: patterns with slash are always treated as absolute
2569 // patterns; patterns without slash search in the innermost
2570 // directory the user specified. There is no way to "complete into"
2571 // a directory as of now, as directories seem to be unknown to the
2575 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2576 // set con_completion_playdemo "*.dem"
2577 // set con_completion_play "*.wav *.ogg"
2579 // TODO somehow add support for directories; these shall complete
2580 // to their name + an appended slash.
2582 stringlistinit(&resultbuf);
2583 stringlistinit(&dirbuf);
2584 while(COM_ParseToken_Simple(&patterns, false, false))
2587 if(strchr(com_token, '/'))
2589 search = FS_Search(com_token, true, true);
2593 const char *slash = strrchr(s, '/');
2596 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2597 strlcat(t, com_token, sizeof(t));
2598 search = FS_Search(t, true, true);
2601 search = FS_Search(com_token, true, true);
2605 for(i = 0; i < search->numfilenames; ++i)
2606 if(!strncmp(search->filenames[i], s, strlen(s)))
2607 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2608 stringlistappend(&resultbuf, search->filenames[i]);
2609 FS_FreeSearch(search);
2613 // In any case, add directory names
2616 const char *slash = strrchr(s, '/');
2619 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2620 strlcat(t, "*", sizeof(t));
2621 search = FS_Search(t, true, true);
2624 search = FS_Search("*", true, true);
2627 for(i = 0; i < search->numfilenames; ++i)
2628 if(!strncmp(search->filenames[i], s, strlen(s)))
2629 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2630 stringlistappend(&dirbuf, search->filenames[i]);
2631 FS_FreeSearch(search);
2635 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2638 unsigned int matchchars;
2639 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2641 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2644 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2646 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2650 stringlistsort(&resultbuf); // dirbuf is already sorted
2651 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2652 for(i = 0; i < dirbuf.numstrings; ++i)
2654 Con_Printf("%s/\n", dirbuf.strings[i]);
2656 for(i = 0; i < resultbuf.numstrings; ++i)
2658 Con_Printf("%s\n", resultbuf.strings[i]);
2660 matchchars = sizeof(t) - 1;
2661 if(resultbuf.numstrings > 0)
2663 p = resultbuf.strings[0];
2664 q = resultbuf.strings[resultbuf.numstrings - 1];
2665 for(; *p && *p == *q; ++p, ++q);
2666 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2668 if(dirbuf.numstrings > 0)
2670 p = dirbuf.strings[0];
2671 q = dirbuf.strings[dirbuf.numstrings - 1];
2672 for(; *p && *p == *q; ++p, ++q);
2673 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2675 // now p points to the first non-equal character, or to the end
2676 // of resultbuf.strings[0]. We want to append the characters
2677 // from resultbuf.strings[0] to (not including) p as these are
2678 // the unique prefix
2679 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2682 // first move the cursor
2683 key_linepos += (int)strlen(t) - (int)strlen(s);
2685 // and now do the actual work
2687 strlcat(key_line, t, MAX_INPUTLINE);
2688 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2690 // and fix the cursor
2691 if(key_linepos > (int) strlen(key_line))
2692 key_linepos = (int) strlen(key_line);
2694 stringlistfreecontents(&resultbuf);
2695 stringlistfreecontents(&dirbuf);
2697 return; // bail out, when we complete for a command that wants a file name
2702 // Count number of possible matches and print them
2703 c = Cmd_CompleteCountPossible(s);
2706 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2707 Cmd_CompleteCommandPrint(s);
2709 v = Cvar_CompleteCountPossible(s);
2712 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2713 Cvar_CompleteCvarPrint(s);
2715 a = Cmd_CompleteAliasCountPossible(s);
2718 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2719 Cmd_CompleteAliasPrint(s);
2721 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2724 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2725 Cmd_CompleteNicksPrint(n);
2728 if (!(c + v + a + n)) // No possible matches
2731 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2736 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2738 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2740 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2742 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2744 for (cmd_len = (int)strlen(s);;cmd_len++)
2747 for (i = 0; i < 3; i++)
2749 for (l = list[i];*l;l++)
2750 if ((*l)[cmd_len] != cmd[cmd_len])
2752 // all possible matches share this character, so we continue...
2755 // if all matches ended at the same position, stop
2756 // (this means there is only one match)
2762 // prevent a buffer overrun by limiting cmd_len according to remaining space
2763 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2767 memcpy(&key_line[key_linepos], cmd, cmd_len);
2768 key_linepos += cmd_len;
2769 // if there is only one match, add a space after it
2770 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2773 { // was a nick, might have an offset, and needs colors ;) --blub
2774 key_linepos = pos - Nicks_offset[0];
2775 cmd_len = strlen(Nicks_list[0]);
2776 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2778 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2779 key_linepos += cmd_len;
2780 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2781 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2783 key_line[key_linepos++] = ' ';
2787 // use strlcat to avoid a buffer overrun
2788 key_line[key_linepos] = 0;
2789 strlcat(key_line, s2, sizeof(key_line));
2791 // free the command, cvar, and alias lists
2792 for (i = 0; i < 4; i++)
2794 Mem_Free((void *)list[i]);