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__)
33 float con_cursorspeed = 4;
35 #define CON_TEXTSIZE 1048576
36 #define CON_MAXLINES 16384
38 // lines up from bottom to display
43 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
44 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
45 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
47 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
48 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
49 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
51 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
52 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
53 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)"};
54 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
55 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
56 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
57 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
58 cvar_t con_chatsound = {CVAR_SAVE, "con_chatsound","1", "enables chat sound to play on message"};
61 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)"};
63 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)"};
65 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)"};
69 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
70 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
71 "0: add nothing after completion. "
72 "1: add the last color after completion. "
73 "2: add a quote when starting a quote instead of the color. "
74 "4: will replace 1, will force color, even after a quote. "
75 "8: ignore non-alphanumerics. "
76 "16: ignore spaces. "};
77 #define NICKS_ADD_COLOR 1
78 #define NICKS_ADD_QUOTE 2
79 #define NICKS_FORCE_COLOR 4
80 #define NICKS_ALPHANUMERICS_ONLY 8
81 #define NICKS_NO_SPACES 16
83 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
84 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
85 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
90 qboolean con_initialized;
92 // used for server replies to rcon command
93 lhnetsocket_t *rcon_redirect_sock = NULL;
94 lhnetaddress_t *rcon_redirect_dest = NULL;
95 int rcon_redirect_bufferpos = 0;
96 char rcon_redirect_buffer[1400];
98 // generic functions for console buffers
100 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
102 buf->textsize = textsize;
103 buf->text = (char *) Mem_Alloc(mempool, textsize);
104 buf->maxlines = maxlines;
105 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
106 buf->lines_first = 0;
107 buf->lines_count = 0;
115 void ConBuffer_Clear (conbuffer_t *buf)
117 buf->lines_count = 0;
125 void ConBuffer_Shutdown(conbuffer_t *buf)
128 Mem_Free(buf->lines);
137 Notifies the console code about the current time
138 (and shifts back times of other entries when the time
142 void ConBuffer_FixTimes(conbuffer_t *buf)
145 if(buf->lines_count >= 1)
147 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
150 for(i = 0; i < buf->lines_count; ++i)
151 CONBUFFER_LINES(buf, i).addtime += diff;
160 Deletes the first line from the console history.
163 void ConBuffer_DeleteLine(conbuffer_t *buf)
165 if(buf->lines_count == 0)
168 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
173 ConBuffer_DeleteLastLine
175 Deletes the last line from the console history.
178 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
180 if(buf->lines_count == 0)
189 Checks if there is space for a line of the given length, and if yes, returns a
190 pointer to the start of such a space, and NULL otherwise.
193 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
195 if(len > buf->textsize)
197 if(buf->lines_count == 0)
201 char *firstline_start = buf->lines[buf->lines_first].start;
202 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
203 // the buffer is cyclic, so we first have two cases...
204 if(firstline_start < lastline_onepastend) // buffer is contiguous
207 if(len <= buf->text + buf->textsize - lastline_onepastend)
208 return lastline_onepastend;
210 else if(len <= firstline_start - buf->text)
215 else // buffer has a contiguous hole
217 if(len <= firstline_start - lastline_onepastend)
218 return lastline_onepastend;
229 Appends a given string as a new line to the console.
232 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
237 ConBuffer_FixTimes(buf);
239 if(len >= buf->textsize)
242 // only display end of line.
243 line += len - buf->textsize + 1;
244 len = buf->textsize - 1;
246 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
247 ConBuffer_DeleteLine(buf);
248 memcpy(putpos, line, len);
252 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
254 p = &CONBUFFER_LINES_LAST(buf);
257 p->addtime = cl.time;
259 p->height = -1; // calculate when needed
262 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
266 start = buf->lines_count;
267 for(i = start - 1; i >= 0; --i)
269 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
271 if((l->mask & mask_must) != mask_must)
273 if(l->mask & mask_mustnot)
282 int Con_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
285 for(i = start + 1; i < buf->lines_count; ++i)
287 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
289 if((l->mask & mask_must) != mask_must)
291 if(l->mask & mask_mustnot)
300 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
302 static char copybuf[MAX_INPUTLINE];
303 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
304 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
305 strlcpy(copybuf, l->start, sz);
310 ==============================================================================
314 ==============================================================================
319 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
320 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"};
321 char log_dest_buffer[1400]; // UDP packet
322 size_t log_dest_buffer_pos;
323 unsigned int log_dest_buffer_appending;
324 char crt_log_file [MAX_OSPATH] = "";
325 qfile_t* logfile = NULL;
327 unsigned char* logqueue = NULL;
329 size_t logq_size = 0;
331 void Log_ConPrint (const char *msg);
338 static void Log_DestBuffer_Init(void)
340 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
341 log_dest_buffer_pos = 5;
349 void Log_DestBuffer_Flush(void)
351 lhnetaddress_t log_dest_addr;
352 lhnetsocket_t *log_dest_socket;
353 const char *s = log_dest_udp.string;
354 qboolean have_opened_temp_sockets = false;
355 if(s) if(log_dest_buffer_pos > 5)
357 ++log_dest_buffer_appending;
358 log_dest_buffer[log_dest_buffer_pos++] = 0;
360 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
362 have_opened_temp_sockets = true;
363 NetConn_OpenServerPorts(true);
366 while(COM_ParseToken_Console(&s))
367 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
369 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
371 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
373 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
376 if(have_opened_temp_sockets)
377 NetConn_CloseServerPorts();
378 --log_dest_buffer_appending;
380 log_dest_buffer_pos = 0;
388 const char* Log_Timestamp (const char *desc)
390 static char timestamp [128];
397 char timestring [64];
399 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
402 localtime_s (&crt_tm, &crt_time);
403 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
405 crt_tm = localtime (&crt_time);
406 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
410 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
412 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
425 if (logfile != NULL || log_file.string[0] == '\0')
428 logfile = FS_OpenRealFile(log_file.string, "a", false);
431 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
432 FS_Print (logfile, Log_Timestamp ("Log started"));
442 void Log_Close (void)
447 FS_Print (logfile, Log_Timestamp ("Log stopped"));
448 FS_Print (logfile, "\n");
452 crt_log_file[0] = '\0';
461 void Log_Start (void)
467 // Dump the contents of the log queue into the log file and free it
468 if (logqueue != NULL)
470 unsigned char *temp = logqueue;
475 FS_Write (logfile, temp, logq_ind);
476 if(*log_dest_udp.string)
478 for(pos = 0; pos < logq_ind; )
480 if(log_dest_buffer_pos == 0)
481 Log_DestBuffer_Init();
482 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
483 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
484 log_dest_buffer_pos += n;
485 Log_DestBuffer_Flush();
502 void Log_ConPrint (const char *msg)
504 static qboolean inprogress = false;
506 // don't allow feedback loops with memory error reports
511 // Until the host is completely initialized, we maintain a log queue
512 // to store the messages, since the log can't be started before
513 if (logqueue != NULL)
515 size_t remain = logq_size - logq_ind;
516 size_t len = strlen (msg);
518 // If we need to enlarge the log queue
521 size_t factor = ((logq_ind + len) / logq_size) + 1;
522 unsigned char* newqueue;
525 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
526 memcpy (newqueue, logqueue, logq_ind);
529 remain = logq_size - logq_ind;
531 memcpy (&logqueue[logq_ind], msg, len);
538 // Check if log_file has changed
539 if (strcmp (crt_log_file, log_file.string) != 0)
545 // If a log file is available
547 FS_Print (logfile, msg);
558 void Log_Printf (const char *logfilename, const char *fmt, ...)
562 file = FS_OpenRealFile(logfilename, "a", true);
567 va_start (argptr, fmt);
568 FS_VPrintf (file, fmt, argptr);
577 ==============================================================================
581 ==============================================================================
589 void Con_ToggleConsole_f (void)
591 // toggle the 'user wants console' bit
592 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
601 void Con_ClearNotify (void)
604 for(i = 0; i < CON_LINES_COUNT; ++i)
605 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
608 void Con_MessageMode_SetCursor(size_t pos)
610 chat_bufferlen = pos;
611 //chat_bufferlen = strlen(chat_buffer);
619 void Con_MessageMode_f (void)
621 key_dest = key_message;
622 chat_mode = 0; // "say"
626 UIM_EnterBuffer(chat_buffer, sizeof(chat_buffer), 0, &Con_MessageMode_SetCursor);
635 void Con_MessageMode2_f (void)
637 key_dest = key_message;
638 chat_mode = 1; // "say_team"
648 void Con_CommandMode_f (void)
650 key_dest = key_message;
653 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
654 chat_bufferlen = strlen(chat_buffer);
656 chat_mode = -1; // command
664 void Con_CheckResize (void)
669 f = bound(1, con_textsize.value, 128);
670 if(f != con_textsize.value)
671 Cvar_SetValueQuick(&con_textsize, f);
672 width = (int)floor(vid_conwidth.value / con_textsize.value);
673 width = bound(1, width, con.textsize/4);
674 // FIXME uses con in a non abstracted way
676 if (width == con_linewidth)
679 con_linewidth = width;
681 for(i = 0; i < CON_LINES_COUNT; ++i)
682 CON_LINES(i).height = -1; // recalculate when next needed
688 //[515]: the simplest command ever
689 //LordHavoc: not so simple after I made it print usage...
690 static void Con_Maps_f (void)
694 Con_Printf("usage: maps [mapnameprefix]\n");
697 else if (Cmd_Argc() == 2)
698 GetMapList(Cmd_Argv(1), NULL, 0);
700 GetMapList("", NULL, 0);
703 void Con_ConDump_f (void)
709 Con_Printf("usage: condump <filename>\n");
712 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
715 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
718 for(i = 0; i < CON_LINES_COUNT; ++i)
720 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
721 FS_Write(file, "\n", 1);
726 void Con_Clear_f (void)
728 ConBuffer_Clear(&con);
739 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
741 // Allocate a log queue, this will be freed after configs are parsed
742 logq_size = MAX_INPUTLINE;
743 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
746 Cvar_RegisterVariable (&sys_colortranslation);
747 Cvar_RegisterVariable (&sys_specialcharactertranslation);
749 Cvar_RegisterVariable (&log_file);
750 Cvar_RegisterVariable (&log_dest_udp);
752 // support for the classic Quake option
753 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
754 if (COM_CheckParm ("-condebug") != 0)
755 Cvar_SetQuick (&log_file, "qconsole.log");
757 // register our cvars
758 Cvar_RegisterVariable (&con_chat);
759 Cvar_RegisterVariable (&con_chatpos);
760 Cvar_RegisterVariable (&con_chatsize);
761 Cvar_RegisterVariable (&con_chattime);
762 Cvar_RegisterVariable (&con_chatwidth);
763 Cvar_RegisterVariable (&con_notify);
764 Cvar_RegisterVariable (&con_notifyalign);
765 Cvar_RegisterVariable (&con_notifysize);
766 Cvar_RegisterVariable (&con_notifytime);
767 Cvar_RegisterVariable (&con_textsize);
768 Cvar_RegisterVariable (&con_chatsound);
771 Cvar_RegisterVariable (&con_nickcompletion);
772 Cvar_RegisterVariable (&con_nickcompletion_flags);
774 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
775 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
776 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
778 // register our commands
779 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
780 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
781 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
782 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
783 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
784 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
785 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
787 con_initialized = true;
788 Con_DPrint("Console initialized.\n");
791 void Con_Shutdown (void)
793 ConBuffer_Shutdown(&con);
800 Handles cursor positioning, line wrapping, etc
801 All console printing must go through this in order to be displayed
802 If no console is visible, the notify window will pop up.
805 void Con_PrintToHistory(const char *txt, int mask)
808 // \n goes to next line
809 // \r deletes current line and makes a new one
811 static int cr_pending = 0;
812 static char buf[CON_TEXTSIZE];
813 static int bufpos = 0;
815 if(!con.text) // FIXME uses a non-abstracted property of con
822 ConBuffer_DeleteLastLine(&con);
830 ConBuffer_AddLine(&con, buf, bufpos, mask);
835 ConBuffer_AddLine(&con, buf, bufpos, mask);
839 buf[bufpos++] = *txt;
840 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
842 ConBuffer_AddLine(&con, buf, bufpos, mask);
850 /*! The translation table between the graphical font and plain ASCII --KB */
851 static char qfont_table[256] = {
852 '\0', '#', '#', '#', '#', '.', '#', '#',
853 '#', 9, 10, '#', ' ', 13, '.', '.',
854 '[', ']', '0', '1', '2', '3', '4', '5',
855 '6', '7', '8', '9', '.', '<', '=', '>',
856 ' ', '!', '"', '#', '$', '%', '&', '\'',
857 '(', ')', '*', '+', ',', '-', '.', '/',
858 '0', '1', '2', '3', '4', '5', '6', '7',
859 '8', '9', ':', ';', '<', '=', '>', '?',
860 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
861 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
862 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
863 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
864 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
865 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
866 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
867 'x', 'y', 'z', '{', '|', '}', '~', '<',
869 '<', '=', '>', '#', '#', '.', '#', '#',
870 '#', '#', ' ', '#', ' ', '>', '.', '.',
871 '[', ']', '0', '1', '2', '3', '4', '5',
872 '6', '7', '8', '9', '.', '<', '=', '>',
873 ' ', '!', '"', '#', '$', '%', '&', '\'',
874 '(', ')', '*', '+', ',', '-', '.', '/',
875 '0', '1', '2', '3', '4', '5', '6', '7',
876 '8', '9', ':', ';', '<', '=', '>', '?',
877 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
878 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
879 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
880 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
881 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
882 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
883 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
884 'x', 'y', 'z', '{', '|', '}', '~', '<'
887 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest)
889 rcon_redirect_sock = sock;
890 rcon_redirect_dest = dest;
891 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
892 rcon_redirect_bufferpos = 5;
895 void Con_Rcon_Redirect_Flush(void)
897 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
898 NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
899 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
900 rcon_redirect_bufferpos = 5;
903 void Con_Rcon_Redirect_End(void)
905 Con_Rcon_Redirect_Flush();
906 rcon_redirect_dest = NULL;
907 rcon_redirect_sock = NULL;
910 void Con_Rcon_Redirect_Abort(void)
912 rcon_redirect_dest = NULL;
913 rcon_redirect_sock = NULL;
921 /// Adds a character to the rcon buffer.
922 void Con_Rcon_AddChar(int c)
924 if(log_dest_buffer_appending)
926 ++log_dest_buffer_appending;
928 // if this print is in response to an rcon command, add the character
929 // to the rcon redirect buffer
931 if (rcon_redirect_dest)
933 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
934 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
935 Con_Rcon_Redirect_Flush();
937 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
939 if(log_dest_buffer_pos == 0)
940 Log_DestBuffer_Init();
941 log_dest_buffer[log_dest_buffer_pos++] = c;
942 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
943 Log_DestBuffer_Flush();
946 log_dest_buffer_pos = 0;
948 --log_dest_buffer_appending;
952 * Convert an RGB color to its nearest quake color.
953 * I'll cheat on this a bit by translating the colors to HSV first,
954 * S and V decide if it's black or white, otherwise, H will decide the
956 * @param _r Red (0-255)
957 * @param _g Green (0-255)
958 * @param _b Blue (0-255)
959 * @return A quake color character.
961 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
963 float r = ((float)_r)/255.0;
964 float g = ((float)_g)/255.0;
965 float b = ((float)_b)/255.0;
966 float min = min(r, min(g, b));
967 float max = max(r, max(g, b));
969 int h; ///< Hue angle [0,360]
970 float s; ///< Saturation [0,1]
971 float v = max; ///< In HSV v == max [0,1]
978 // Saturation threshold. We now say 0.2 is the minimum value for a color!
981 // If the value is less than half, return a black color code.
982 // Otherwise return a white one.
988 // Let's get the hue angle to define some colors:
992 h = (int)(60.0 * (g-b)/(max-min))%360;
994 h = (int)(60.0 * (b-r)/(max-min) + 120);
995 else // if(max == b) redundant check
996 h = (int)(60.0 * (r-g)/(max-min) + 240);
998 if(h < 36) // *red* to orange
1000 else if(h < 80) // orange over *yellow* to evilish-bright-green
1002 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1004 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1006 else if(h < 270) // darkish blue over *dark blue* to cool purple
1008 else if(h < 330) // cool purple over *purple* to ugly swiny red
1010 else // ugly red to red closes the circly
1019 extern cvar_t timestamps;
1020 extern cvar_t timeformat;
1021 extern qboolean sys_nostdout;
1022 void Con_Print(const char *msg)
1024 static int mask = 0;
1025 static int index = 0;
1026 static char line[MAX_INPUTLINE];
1030 Con_Rcon_AddChar(*msg);
1031 // if this is the beginning of a new line, print timestamp
1034 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1036 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1037 line[index++] = STRING_COLOR_TAG;
1038 // assert( STRING_COLOR_DEFAULT < 10 )
1039 line[index++] = STRING_COLOR_DEFAULT + '0';
1040 // special color codes for chat messages must always come first
1041 // for Con_PrintToHistory to work properly
1042 if (*msg == 1 || *msg == 2)
1047 if (con_chatsound.value)
1049 if(gamemode == GAME_NEXUIZ)
1051 if(msg[1] == '\r' && cl.foundtalk2wav)
1052 S_LocalSound ("sound/misc/talk2.wav");
1054 S_LocalSound ("sound/misc/talk.wav");
1058 if (msg[1] == '(' && cl.foundtalk2wav)
1059 S_LocalSound ("sound/misc/talk2.wav");
1061 S_LocalSound ("sound/misc/talk.wav");
1064 mask = CON_MASK_CHAT;
1066 line[index++] = STRING_COLOR_TAG;
1067 line[index++] = '3';
1069 Con_Rcon_AddChar(*msg);
1072 for (;*timestamp;index++, timestamp++)
1073 if (index < (int)sizeof(line) - 2)
1074 line[index] = *timestamp;
1076 // append the character
1077 line[index++] = *msg;
1078 // if this is a newline character, we have a complete line to print
1079 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1081 // terminate the line
1085 // send to scrollable buffer
1086 if (con_initialized && cls.state != ca_dedicated)
1088 Con_PrintToHistory(line, mask);
1091 // send to terminal or dedicated server window
1095 if(sys_specialcharactertranslation.integer)
1097 for (p = (unsigned char *) line;*p; p++)
1098 *p = qfont_table[*p];
1101 if(sys_colortranslation.integer == 1) // ANSI
1103 static char printline[MAX_INPUTLINE * 4 + 3];
1104 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1105 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1110 for(in = line, out = printline; *in; ++in)
1114 case STRING_COLOR_TAG:
1115 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1117 char r = tolower(in[2]);
1118 char g = tolower(in[3]);
1119 char b = tolower(in[4]);
1120 // it's a hex digit already, so the else part needs no check --blub
1121 if(isdigit(r)) r -= '0';
1123 if(isdigit(g)) g -= '0';
1125 if(isdigit(b)) b -= '0';
1128 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1129 in += 3; // 3 only, the switch down there does the fourth
1136 case STRING_COLOR_TAG:
1138 *out++ = STRING_COLOR_TAG;
1144 if(lastcolor == 0) break; else lastcolor = 0;
1145 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1150 if(lastcolor == 1) break; else lastcolor = 1;
1151 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1156 if(lastcolor == 2) break; else lastcolor = 2;
1157 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1162 if(lastcolor == 3) break; else lastcolor = 3;
1163 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1168 if(lastcolor == 4) break; else lastcolor = 4;
1169 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1174 if(lastcolor == 5) break; else lastcolor = 5;
1175 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1180 if(lastcolor == 6) break; else lastcolor = 6;
1181 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1186 // bold normal color
1188 if(lastcolor == 8) break; else lastcolor = 8;
1189 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1192 *out++ = STRING_COLOR_TAG;
1199 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1216 Sys_PrintToTerminal(printline);
1218 else if(sys_colortranslation.integer == 2) // Quake
1220 Sys_PrintToTerminal(line);
1224 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1227 for(in = line, out = printline; *in; ++in)
1231 case STRING_COLOR_TAG:
1234 case STRING_COLOR_RGB_TAG_CHAR:
1235 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1240 *out++ = STRING_COLOR_TAG;
1241 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1244 case STRING_COLOR_TAG:
1246 *out++ = STRING_COLOR_TAG;
1261 *out++ = STRING_COLOR_TAG;
1271 Sys_PrintToTerminal(printline);
1274 // empty the line buffer
1286 void Con_Printf(const char *fmt, ...)
1289 char msg[MAX_INPUTLINE];
1291 va_start(argptr,fmt);
1292 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1303 void Con_DPrint(const char *msg)
1305 if (!developer.integer)
1306 return; // don't confuse non-developers with techie stuff...
1315 void Con_DPrintf(const char *fmt, ...)
1318 char msg[MAX_INPUTLINE];
1320 if (!developer.integer)
1321 return; // don't confuse non-developers with techie stuff...
1323 va_start(argptr,fmt);
1324 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1332 ==============================================================================
1336 ==============================================================================
1343 The input line scrolls horizontally if typing goes beyond the right edge
1345 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1348 void Con_DrawInput (void)
1352 char editlinecopy[MAX_INPUTLINE+1], *text;
1355 if (!key_consoleactive)
1356 return; // don't draw anything
1358 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1359 text = editlinecopy;
1361 // Advanced Console Editing by Radix radix@planetquake.com
1362 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1363 // use strlen of edit_line instead of key_linepos to allow editing
1364 // of early characters w/o erasing
1366 y = (int)strlen(text);
1368 // fill out remainder with spaces
1369 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1372 // add the cursor frame
1373 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1375 if (y + 3 < (int)sizeof(editlinecopy)-1)
1377 int ofs = u8_bytelen(text + key_linepos, 1);
1378 memmove(text + key_linepos + 3, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - 3);
1381 text[key_linepos] = '\xee';
1382 text[key_linepos+1] = '\x80';
1383 text[key_linepos+2] = '\x8b';
1385 text[key_linepos] = '\xee';
1386 text[key_linepos+1] = '\x82';
1387 text[key_linepos+2] = '\x82';
1390 //text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1391 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1395 // text[key_linepos + 1] = 0;
1397 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1402 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 );
1405 // key_line[key_linepos] = 0;
1411 float alignment; // 0 = left, 0.5 = center, 1 = right
1417 const char *continuationString;
1420 int colorindex; // init to -1
1424 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1426 con_text_info_t *ti = (con_text_info_t *) passthrough;
1429 ti->colorindex = -1;
1430 return ti->fontsize * ti->font->maxwidth;
1433 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1434 else if(maxWidth == -1)
1435 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1438 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1439 // Note: this is NOT a Con_Printf, as it could print recursively
1444 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1450 (void) isContinuation;
1454 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1456 con_text_info_t *ti = (con_text_info_t *) passthrough;
1458 if(ti->y < ti->ymin - 0.001)
1460 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1464 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1465 if(isContinuation && *ti->continuationString)
1466 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);
1468 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);
1471 ti->y += ti->fontsize;
1475 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)
1479 int maxlines = (int) floor(height / fontsize + 0.01f);
1482 int continuationWidth = 0;
1484 double t = cl.time; // saved so it won't change
1487 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1488 ti.fontsize = fontsize;
1489 ti.alignment = alignment_x;
1492 ti.ymax = y + height;
1493 ti.continuationString = continuationString;
1496 Con_WordWidthFunc(&ti, NULL, &l, -1);
1497 l = strlen(continuationString);
1498 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1500 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1501 startidx = CON_LINES_COUNT;
1502 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1504 con_lineinfo_t *l = &CON_LINES(i);
1507 if((l->mask & mask_must) != mask_must)
1509 if(l->mask & mask_mustnot)
1511 if(maxage && (l->addtime < t - maxage))
1515 // Calculate its actual height...
1516 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1517 if(lines + mylines >= maxlines)
1519 nskip = lines + mylines - maxlines;
1528 // then center according to the calculated amount of lines...
1530 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1532 // then actually draw
1533 for(i = startidx; i < CON_LINES_COUNT; ++i)
1535 con_lineinfo_t *l = &CON_LINES(i);
1537 if((l->mask & mask_must) != mask_must)
1539 if(l->mask & mask_mustnot)
1541 if(maxage && (l->addtime < t - maxage))
1544 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1554 Draws the last few lines of output transparently over the game top
1557 void Con_DrawNotify (void)
1560 float chatstart, notifystart, inputsize;
1562 char temptext[MAX_INPUTLINE];
1566 ConBuffer_FixTimes(&con);
1568 numChatlines = con_chat.integer;
1569 chatpos = con_chatpos.integer;
1571 if (con_notify.integer < 0)
1572 Cvar_SetValueQuick(&con_notify, 0);
1573 if (gamemode == GAME_TRANSFUSION)
1574 v = 8; // vertical offset
1578 // GAME_NEXUIZ: center, otherwise left justify
1579 align = con_notifyalign.value;
1580 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1582 if(gamemode == GAME_NEXUIZ)
1590 // first chat, input line, then notify
1592 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1594 else if(chatpos > 0)
1596 // first notify, then (chatpos-1) empty lines, then chat, then input
1598 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1600 else // if(chatpos < 0)
1602 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1604 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1609 // just notify and input
1611 chatstart = 0; // shut off gcc warning
1614 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, "");
1619 v = chatstart + numChatlines * con_chatsize.value;
1620 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
1623 if (key_dest == key_message)
1625 static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1626 int colorindex = -1;
1628 // LordHavoc: speedup, and other improvements
1630 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor[(int)(realtime*con_cursorspeed)&1]);
1632 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor[(int)(realtime*con_cursorspeed)&1]);
1634 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor[(int)(realtime*con_cursorspeed)&1]);
1637 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1638 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1641 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1647 Con_MeasureConsoleLine
1649 Counts the number of lines for a line on the console.
1652 int Con_MeasureConsoleLine(int lineno)
1654 float width = vid_conwidth.value;
1656 con_lineinfo_t *li = &CON_LINES(lineno);
1658 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1661 ti.fontsize = con_textsize.value;
1662 ti.font = FONT_CONSOLE;
1664 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1671 Returns the height of a given console line; calculates it if necessary.
1674 int Con_LineHeight(int i)
1676 con_lineinfo_t *li = &CON_LINES(i);
1680 return li->height = Con_MeasureConsoleLine(i);
1687 Draws a line of the console; returns its height in lines.
1688 If alpha is 0, the line is not drawn, but still wrapped and its height
1692 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1694 float width = vid_conwidth.value;
1696 con_lineinfo_t *li = &CON_LINES(lineno);
1698 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1701 ti.continuationString = "";
1703 ti.fontsize = con_textsize.value;
1704 ti.font = FONT_CONSOLE;
1706 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1711 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1718 Calculates the last visible line index and how much to show of it based on
1722 void Con_LastVisibleLine(int *last, int *limitlast)
1727 if(con_backscroll < 0)
1730 // now count until we saw con_backscroll actual lines
1731 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1733 int h = Con_LineHeight(i);
1735 // line is the last visible line?
1736 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1739 *limitlast = lines_seen + h - con_backscroll;
1746 // if we get here, no line was on screen - scroll so that one line is
1748 con_backscroll = lines_seen - 1;
1749 *last = con.lines_first;
1750 // FIXME uses con in a non abstracted way
1758 Draws the console with the solid background
1759 The typing input line at the bottom should only be drawn if typing is allowed
1762 void Con_DrawConsole (int lines)
1764 int i, last, limitlast;
1770 con_vislines = lines;
1772 // draw the background
1773 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
1774 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);
1777 if(CON_LINES_COUNT > 0)
1779 float ymax = con_vislines - 2 * con_textsize.value;
1780 Con_LastVisibleLine(&last, &limitlast);
1781 y = ymax - con_textsize.value;
1784 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1785 // FIXME uses con in a non abstracted way
1790 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1792 break; // top of console buffer
1794 break; // top of console window
1800 // draw the input prompt, user text, and cursor if desired
1808 Prints not only map filename, but also
1809 its format (q1/q2/q3/hl) and even its message
1811 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1812 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1813 //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
1814 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1815 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1819 int i, k, max, p, o, min;
1822 unsigned char buf[1024];
1824 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1825 t = FS_Search(message, 1, true);
1828 if (t->numfilenames > 1)
1829 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1830 len = (unsigned char *)Z_Malloc(t->numfilenames);
1832 for(max=i=0;i<t->numfilenames;i++)
1834 k = (int)strlen(t->filenames[i]);
1844 for(i=0;i<t->numfilenames;i++)
1846 int lumpofs = 0, lumplen = 0;
1847 char *entities = NULL;
1848 const char *data = NULL;
1850 char entfilename[MAX_QPATH];
1851 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1853 f = FS_OpenVirtualFile(t->filenames[i], true);
1856 memset(buf, 0, 1024);
1857 FS_Read(f, buf, 1024);
1858 if (!memcmp(buf, "IBSP", 4))
1860 p = LittleLong(((int *)buf)[1]);
1861 if (p == Q3BSPVERSION)
1863 q3dheader_t *header = (q3dheader_t *)buf;
1864 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1865 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1867 else if (p == Q2BSPVERSION)
1869 q2dheader_t *header = (q2dheader_t *)buf;
1870 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1871 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1874 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1876 dheader_t *header = (dheader_t *)buf;
1877 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1878 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1882 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1883 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1884 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1885 if (!entities && lumplen >= 10)
1887 FS_Seek(f, lumpofs, SEEK_SET);
1888 entities = (char *)Z_Malloc(lumplen + 1);
1889 FS_Read(f, entities, lumplen);
1893 // if there are entities to parse, a missing message key just
1894 // means there is no title, so clear the message string now
1900 if (!COM_ParseToken_Simple(&data, false, false))
1902 if (com_token[0] == '{')
1904 if (com_token[0] == '}')
1906 // skip leading whitespace
1907 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
1908 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
1909 keyname[l] = com_token[k+l];
1911 if (!COM_ParseToken_Simple(&data, false, false))
1913 if (developer.integer >= 100)
1914 Con_Printf("key: %s %s\n", keyname, com_token);
1915 if (!strcmp(keyname, "message"))
1917 // get the message contents
1918 strlcpy(message, com_token, sizeof(message));
1928 *(t->filenames[i]+len[i]+5) = 0;
1931 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1932 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1933 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1934 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1935 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1937 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1942 k = *(t->filenames[0]+5+p);
1945 for(i=1;i<t->numfilenames;i++)
1946 if(*(t->filenames[i]+5+p) != k)
1950 if(p > o && completedname && completednamebufferlength > 0)
1952 memset(completedname, 0, completednamebufferlength);
1953 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1963 New function for tab-completion system
1964 Added by EvilTypeGuy
1965 MEGA Thanks to Taniwha
1968 void Con_DisplayList(const char **list)
1970 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1971 const char **walk = list;
1974 len = (int)strlen(*walk);
1982 len = (int)strlen(*list);
1983 if (pos + maxlen >= width) {
1989 for (i = 0; i < (maxlen - len); i++)
2001 SanitizeString strips color tags from the string in
2002 and writes the result on string out
2004 void SanitizeString(char *in, char *out)
2008 if(*in == STRING_COLOR_TAG)
2013 out[0] = STRING_COLOR_TAG;
2017 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2024 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2027 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2029 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2036 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2041 else if (*in != STRING_COLOR_TAG)
2044 *out = qfont_table[*(unsigned char*)in];
2051 // Now it becomes TRICKY :D --blub
2052 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2053 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2054 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2055 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
2056 static int Nicks_matchpos;
2058 // co against <<:BLASTER:>> is true!?
2059 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2063 if(tolower(*a) == tolower(*b))
2077 return (*a < *b) ? -1 : 1;
2081 return (*a < *b) ? -1 : 1;
2085 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2088 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2090 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2091 return Nicks_strncasecmp_nospaces(a, b, a_len);
2092 return strncasecmp(a, b, a_len);
2095 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2097 // ignore non alphanumerics of B
2098 // if A contains a non-alphanumeric, B must contain it as well though!
2101 qboolean alnum_a, alnum_b;
2103 if(tolower(*a) == tolower(*b))
2105 if(*a == 0) // end of both strings, they're equal
2112 // not equal, end of one string?
2117 // ignore non alphanumerics
2118 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2119 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2120 if(!alnum_a) // b must contain this
2121 return (*a < *b) ? -1 : 1;
2124 // otherwise, both are alnum, they're just not equal, return the appropriate number
2126 return (*a < *b) ? -1 : 1;
2132 /* Nicks_CompleteCountPossible
2134 Count the number of possible nicks to complete
2136 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2145 if(!con_nickcompletion.integer)
2148 // changed that to 1
2149 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2152 for(i = 0; i < cl.maxclients; ++i)
2155 if(!cl.scores[p].name[0])
2158 SanitizeString(cl.scores[p].name, name);
2159 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2164 length = strlen(name);
2166 spos = pos - 1; // no need for a minimum of characters :)
2170 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2172 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2173 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2179 if(isCon && spos == 0)
2181 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2187 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2188 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2190 // the sanitized list
2191 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2194 Nicks_matchpos = match;
2197 Nicks_offset[count] = s - (&line[match]);
2198 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2205 void Cmd_CompleteNicksPrint(int count)
2208 for(i = 0; i < count; ++i)
2209 Con_Printf("%s\n", Nicks_list[i]);
2212 void Nicks_CutMatchesNormal(int count)
2214 // cut match 0 down to the longest possible completion
2217 c = strlen(Nicks_sanlist[0]) - 1;
2218 for(i = 1; i < count; ++i)
2220 l = strlen(Nicks_sanlist[i]) - 1;
2224 for(l = 0; l <= c; ++l)
2225 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2231 Nicks_sanlist[0][c+1] = 0;
2232 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2235 unsigned int Nicks_strcleanlen(const char *s)
2240 if( (*s >= 'a' && *s <= 'z') ||
2241 (*s >= 'A' && *s <= 'Z') ||
2242 (*s >= '0' && *s <= '9') ||
2250 void Nicks_CutMatchesAlphaNumeric(int count)
2252 // cut match 0 down to the longest possible completion
2255 char tempstr[sizeof(Nicks_sanlist[0])];
2257 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2259 c = strlen(Nicks_sanlist[0]);
2260 for(i = 0, l = 0; i < (int)c; ++i)
2262 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2263 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2264 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2266 tempstr[l++] = Nicks_sanlist[0][i];
2271 for(i = 1; i < count; ++i)
2274 b = Nicks_sanlist[i];
2284 if(tolower(*a) == tolower(*b))
2290 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2292 // b is alnum, so cut
2299 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2300 Nicks_CutMatchesNormal(count);
2301 //if(!Nicks_sanlist[0][0])
2302 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2304 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2305 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2309 void Nicks_CutMatchesNoSpaces(int count)
2311 // cut match 0 down to the longest possible completion
2314 char tempstr[sizeof(Nicks_sanlist[0])];
2317 c = strlen(Nicks_sanlist[0]);
2318 for(i = 0, l = 0; i < (int)c; ++i)
2320 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2322 tempstr[l++] = Nicks_sanlist[0][i];
2327 for(i = 1; i < count; ++i)
2330 b = Nicks_sanlist[i];
2340 if(tolower(*a) == tolower(*b))
2354 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2355 Nicks_CutMatchesNormal(count);
2356 //if(!Nicks_sanlist[0][0])
2357 //Con_Printf("TS: %s\n", tempstr);
2358 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2360 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2361 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2365 void Nicks_CutMatches(int count)
2367 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2368 Nicks_CutMatchesAlphaNumeric(count);
2369 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2370 Nicks_CutMatchesNoSpaces(count);
2372 Nicks_CutMatchesNormal(count);
2375 const char **Nicks_CompleteBuildList(int count)
2379 // the list is freed by Con_CompleteCommandLine, so create a char**
2380 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2382 for(; bpos < count; ++bpos)
2383 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2385 Nicks_CutMatches(count);
2393 Restores the previous used color, after the autocompleted name.
2395 int Nicks_AddLastColor(char *buffer, int pos)
2397 qboolean quote_added = false;
2399 int color = STRING_COLOR_DEFAULT + '0';
2400 char r = 0, g = 0, b = 0;
2402 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2404 // we'll have to add a quote :)
2405 buffer[pos++] = '\"';
2409 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2411 // add color when no quote was added, or when flags &4?
2413 for(match = Nicks_matchpos-1; match >= 0; --match)
2415 if(buffer[match] == STRING_COLOR_TAG)
2417 if( isdigit(buffer[match+1]) )
2419 color = buffer[match+1];
2422 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2424 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2426 r = buffer[match+2];
2427 g = buffer[match+3];
2428 b = buffer[match+4];
2437 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2439 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2440 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2443 buffer[pos++] = STRING_COLOR_TAG;
2446 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2452 buffer[pos++] = color;
2457 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2460 /*if(!con_nickcompletion.integer)
2461 return; is tested in Nicks_CompletionCountPossible */
2462 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2468 msg = Nicks_list[0];
2469 len = min(size - Nicks_matchpos - 3, strlen(msg));
2470 memcpy(&buffer[Nicks_matchpos], msg, len);
2471 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2472 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2473 buffer[len++] = ' ';
2480 Con_Printf("\n%i possible nicks:\n", n);
2481 Cmd_CompleteNicksPrint(n);
2483 Nicks_CutMatches(n);
2485 msg = Nicks_sanlist[0];
2486 len = min(size - Nicks_matchpos, strlen(msg));
2487 memcpy(&buffer[Nicks_matchpos], msg, len);
2488 buffer[Nicks_matchpos + len] = 0;
2490 return Nicks_matchpos + len;
2497 Con_CompleteCommandLine
2499 New function for tab-completion system
2500 Added by EvilTypeGuy
2501 Thanks to Fett erich@heintz.com
2503 Enhanced to tab-complete map names by [515]
2506 void Con_CompleteCommandLine (void)
2508 const char *cmd = "";
2510 const char **list[4] = {0, 0, 0, 0};
2513 int c, v, a, i, cmd_len, pos, k;
2514 int n; // nicks --blub
2515 const char *space, *patterns;
2517 //find what we want to complete
2522 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2528 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2529 key_line[key_linepos] = 0; //hide them
2531 space = strchr(key_line + 1, ' ');
2532 if(space && pos == (space - key_line) + 1)
2534 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2536 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2537 if(patterns && !*patterns)
2538 patterns = NULL; // get rid of the empty string
2540 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2544 if (GetMapList(s, t, sizeof(t)))
2546 // first move the cursor
2547 key_linepos += (int)strlen(t) - (int)strlen(s);
2549 // and now do the actual work
2551 strlcat(key_line, t, MAX_INPUTLINE);
2552 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2554 // and fix the cursor
2555 if(key_linepos > (int) strlen(key_line))
2556 key_linepos = (int) strlen(key_line);
2565 stringlist_t resultbuf, dirbuf;
2568 // // store completion patterns (space separated) for command foo in con_completion_foo
2569 // set con_completion_foo "foodata/*.foodefault *.foo"
2572 // Note: patterns with slash are always treated as absolute
2573 // patterns; patterns without slash search in the innermost
2574 // directory the user specified. There is no way to "complete into"
2575 // a directory as of now, as directories seem to be unknown to the
2579 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2580 // set con_completion_playdemo "*.dem"
2581 // set con_completion_play "*.wav *.ogg"
2583 // TODO somehow add support for directories; these shall complete
2584 // to their name + an appended slash.
2586 stringlistinit(&resultbuf);
2587 stringlistinit(&dirbuf);
2588 while(COM_ParseToken_Simple(&patterns, false, false))
2591 if(strchr(com_token, '/'))
2593 search = FS_Search(com_token, true, true);
2597 const char *slash = strrchr(s, '/');
2600 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2601 strlcat(t, com_token, sizeof(t));
2602 search = FS_Search(t, true, true);
2605 search = FS_Search(com_token, true, true);
2609 for(i = 0; i < search->numfilenames; ++i)
2610 if(!strncmp(search->filenames[i], s, strlen(s)))
2611 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2612 stringlistappend(&resultbuf, search->filenames[i]);
2613 FS_FreeSearch(search);
2617 // In any case, add directory names
2620 const char *slash = strrchr(s, '/');
2623 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2624 strlcat(t, "*", sizeof(t));
2625 search = FS_Search(t, true, true);
2628 search = FS_Search("*", true, true);
2631 for(i = 0; i < search->numfilenames; ++i)
2632 if(!strncmp(search->filenames[i], s, strlen(s)))
2633 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2634 stringlistappend(&dirbuf, search->filenames[i]);
2635 FS_FreeSearch(search);
2639 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2642 unsigned int matchchars;
2643 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2645 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2648 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2650 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2654 stringlistsort(&resultbuf); // dirbuf is already sorted
2655 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2656 for(i = 0; i < dirbuf.numstrings; ++i)
2658 Con_Printf("%s/\n", dirbuf.strings[i]);
2660 for(i = 0; i < resultbuf.numstrings; ++i)
2662 Con_Printf("%s\n", resultbuf.strings[i]);
2664 matchchars = sizeof(t) - 1;
2665 if(resultbuf.numstrings > 0)
2667 p = resultbuf.strings[0];
2668 q = resultbuf.strings[resultbuf.numstrings - 1];
2669 for(; *p && *p == *q; ++p, ++q);
2670 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2672 if(dirbuf.numstrings > 0)
2674 p = dirbuf.strings[0];
2675 q = dirbuf.strings[dirbuf.numstrings - 1];
2676 for(; *p && *p == *q; ++p, ++q);
2677 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2679 // now p points to the first non-equal character, or to the end
2680 // of resultbuf.strings[0]. We want to append the characters
2681 // from resultbuf.strings[0] to (not including) p as these are
2682 // the unique prefix
2683 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2686 // first move the cursor
2687 key_linepos += (int)strlen(t) - (int)strlen(s);
2689 // and now do the actual work
2691 strlcat(key_line, t, MAX_INPUTLINE);
2692 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2694 // and fix the cursor
2695 if(key_linepos > (int) strlen(key_line))
2696 key_linepos = (int) strlen(key_line);
2698 stringlistfreecontents(&resultbuf);
2699 stringlistfreecontents(&dirbuf);
2701 return; // bail out, when we complete for a command that wants a file name
2706 // Count number of possible matches and print them
2707 c = Cmd_CompleteCountPossible(s);
2710 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2711 Cmd_CompleteCommandPrint(s);
2713 v = Cvar_CompleteCountPossible(s);
2716 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2717 Cvar_CompleteCvarPrint(s);
2719 a = Cmd_CompleteAliasCountPossible(s);
2722 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2723 Cmd_CompleteAliasPrint(s);
2725 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2728 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2729 Cmd_CompleteNicksPrint(n);
2732 if (!(c + v + a + n)) // No possible matches
2735 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2740 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2742 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2744 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2746 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2748 for (cmd_len = (int)strlen(s);;cmd_len++)
2751 for (i = 0; i < 3; i++)
2753 for (l = list[i];*l;l++)
2754 if ((*l)[cmd_len] != cmd[cmd_len])
2756 // all possible matches share this character, so we continue...
2759 // if all matches ended at the same position, stop
2760 // (this means there is only one match)
2766 // prevent a buffer overrun by limiting cmd_len according to remaining space
2767 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2771 memcpy(&key_line[key_linepos], cmd, cmd_len);
2772 key_linepos += cmd_len;
2773 // if there is only one match, add a space after it
2774 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2777 { // was a nick, might have an offset, and needs colors ;) --blub
2778 key_linepos = pos - Nicks_offset[0];
2779 cmd_len = strlen(Nicks_list[0]);
2780 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2782 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2783 key_linepos += cmd_len;
2784 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2785 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2787 key_line[key_linepos++] = ' ';
2791 // use strlcat to avoid a buffer overrun
2792 key_line[key_linepos] = 0;
2793 strlcat(key_line, s2, sizeof(key_line));
2795 // free the command, cvar, and alias lists
2796 for (i = 0; i < 4; i++)
2798 Mem_Free((void *)list[i]);