2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 #if !defined(WIN32) || defined(__MINGW32__)
29 float con_cursorspeed = 4;
31 #define CON_TEXTSIZE 1048576
32 #define CON_MAXLINES 16384
34 // lines up from bottom to display
39 #define CON_LINES_IDX(i) CONBUFFER_LINES_IDX(&con, i)
40 #define CON_LINES_UNIDX(i) CONBUFFER_LINES_UNIDX(&con, i)
41 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
42 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
43 #define CON_LINES_PRED(i) CONBUFFER_LINES_PRED(&con, i)
44 #define CON_LINES_SUCC(i) CONBUFFER_LINES_SUCC(&con, i)
45 #define CON_LINES_FIRST CONBUFFER_LINES_FIRST(&con)
46 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
48 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
49 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
50 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
52 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
53 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
54 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)"};
55 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
56 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
57 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
58 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
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"};
84 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem"};
85 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg"};
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 = Mem_Alloc(mempool, textsize);
104 buf->maxlines = maxlines;
105 buf->lines = 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 - (buf->lines + 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 = CONBUFFER_LINES_IDX(buf, 1);
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 = buf->lines[CONBUFFER_LINES_LAST(buf)].start + buf->lines[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 = buf->lines + 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 ==============================================================================
317 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
318 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"};
319 char log_dest_buffer[1400]; // UDP packet
320 size_t log_dest_buffer_pos;
321 unsigned int log_dest_buffer_appending;
322 char crt_log_file [MAX_OSPATH] = "";
323 qfile_t* logfile = NULL;
325 unsigned char* logqueue = NULL;
327 size_t logq_size = 0;
329 void Log_ConPrint (const char *msg);
336 static void Log_DestBuffer_Init()
338 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
339 log_dest_buffer_pos = 5;
347 void Log_DestBuffer_Flush()
349 lhnetaddress_t log_dest_addr;
350 lhnetsocket_t *log_dest_socket;
351 const char *s = log_dest_udp.string;
352 qboolean have_opened_temp_sockets = false;
353 if(s) if(log_dest_buffer_pos > 5)
355 ++log_dest_buffer_appending;
356 log_dest_buffer[log_dest_buffer_pos++] = 0;
358 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
360 have_opened_temp_sockets = true;
361 NetConn_OpenServerPorts(true);
364 while(COM_ParseToken_Console(&s))
365 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
367 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
369 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
371 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
374 if(have_opened_temp_sockets)
375 NetConn_CloseServerPorts();
376 --log_dest_buffer_appending;
378 log_dest_buffer_pos = 0;
386 const char* Log_Timestamp (const char *desc)
388 static char timestamp [128];
395 char timestring [64];
397 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
400 localtime_s (&crt_tm, &crt_time);
401 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
403 crt_tm = localtime (&crt_time);
404 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
408 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
410 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
423 if (logfile != NULL || log_file.string[0] == '\0')
426 logfile = FS_OpenRealFile(log_file.string, "a", false);
429 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
430 FS_Print (logfile, Log_Timestamp ("Log started"));
440 void Log_Close (void)
445 FS_Print (logfile, Log_Timestamp ("Log stopped"));
446 FS_Print (logfile, "\n");
450 crt_log_file[0] = '\0';
459 void Log_Start (void)
465 // Dump the contents of the log queue into the log file and free it
466 if (logqueue != NULL)
468 unsigned char *temp = logqueue;
473 FS_Write (logfile, temp, logq_ind);
474 if(*log_dest_udp.string)
476 for(pos = 0; pos < logq_ind; )
478 if(log_dest_buffer_pos == 0)
479 Log_DestBuffer_Init();
480 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
481 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
482 log_dest_buffer_pos += n;
483 Log_DestBuffer_Flush();
500 void Log_ConPrint (const char *msg)
502 static qboolean inprogress = false;
504 // don't allow feedback loops with memory error reports
509 // Until the host is completely initialized, we maintain a log queue
510 // to store the messages, since the log can't be started before
511 if (logqueue != NULL)
513 size_t remain = logq_size - logq_ind;
514 size_t len = strlen (msg);
516 // If we need to enlarge the log queue
519 size_t factor = ((logq_ind + len) / logq_size) + 1;
520 unsigned char* newqueue;
523 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
524 memcpy (newqueue, logqueue, logq_ind);
527 remain = logq_size - logq_ind;
529 memcpy (&logqueue[logq_ind], msg, len);
536 // Check if log_file has changed
537 if (strcmp (crt_log_file, log_file.string) != 0)
543 // If a log file is available
545 FS_Print (logfile, msg);
556 void Log_Printf (const char *logfilename, const char *fmt, ...)
560 file = FS_OpenRealFile(logfilename, "a", true);
565 va_start (argptr, fmt);
566 FS_VPrintf (file, fmt, argptr);
575 ==============================================================================
579 ==============================================================================
587 void Con_ToggleConsole_f (void)
589 // toggle the 'user wants console' bit
590 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
598 Clear all notify lines.
601 void Con_ClearNotify (void)
604 for(i = 0; i < CON_LINES_COUNT; ++i)
605 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
614 void Con_MessageMode_f (void)
616 key_dest = key_message;
617 chat_mode = 0; // "say"
626 void Con_MessageMode2_f (void)
628 key_dest = key_message;
629 chat_mode = 1; // "say_team"
637 void Con_CommandMode_f (void)
639 key_dest = key_message;
642 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
643 chat_bufferlen = strlen(chat_buffer);
645 chat_mode = -1; // command
652 If the line width has changed, reformat the buffer.
655 void Con_CheckResize (void)
660 f = bound(1, con_textsize.value, 128);
661 if(f != con_textsize.value)
662 Cvar_SetValueQuick(&con_textsize, f);
663 width = (int)floor(vid_conwidth.value / con_textsize.value);
664 width = bound(1, width, con.textsize/4);
665 // FIXME uses con in a non abstracted way
667 if (width == con_linewidth)
670 con_linewidth = width;
672 for(i = 0; i < CON_LINES_COUNT; ++i)
673 CON_LINES(i).height = -1; // recalculate when next needed
679 //[515]: the simplest command ever
680 //LordHavoc: not so simple after I made it print usage...
681 static void Con_Maps_f (void)
685 Con_Printf("usage: maps [mapnameprefix]\n");
688 else if (Cmd_Argc() == 2)
689 GetMapList(Cmd_Argv(1), NULL, 0);
691 GetMapList("", NULL, 0);
694 void Con_ConDump_f (void)
700 Con_Printf("usage: condump <filename>\n");
703 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
706 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
709 for(i = 0; i < CON_LINES_COUNT; ++i)
711 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
712 FS_Write(file, "\n", 1);
719 ConBuffer_Clear(&con);
730 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
732 // Allocate a log queue, this will be freed after configs are parsed
733 logq_size = MAX_INPUTLINE;
734 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
737 Cvar_RegisterVariable (&sys_colortranslation);
738 Cvar_RegisterVariable (&sys_specialcharactertranslation);
740 Cvar_RegisterVariable (&log_file);
741 Cvar_RegisterVariable (&log_dest_udp);
743 // support for the classic Quake option
744 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
745 if (COM_CheckParm ("-condebug") != 0)
746 Cvar_SetQuick (&log_file, "qconsole.log");
748 // register our cvars
749 Cvar_RegisterVariable (&con_chat);
750 Cvar_RegisterVariable (&con_chatpos);
751 Cvar_RegisterVariable (&con_chatsize);
752 Cvar_RegisterVariable (&con_chattime);
753 Cvar_RegisterVariable (&con_chatwidth);
754 Cvar_RegisterVariable (&con_notify);
755 Cvar_RegisterVariable (&con_notifyalign);
756 Cvar_RegisterVariable (&con_notifysize);
757 Cvar_RegisterVariable (&con_notifytime);
758 Cvar_RegisterVariable (&con_textsize);
761 Cvar_RegisterVariable (&con_nickcompletion);
762 Cvar_RegisterVariable (&con_nickcompletion_flags);
764 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
765 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
766 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
768 // register our commands
769 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
770 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
771 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
772 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
773 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
774 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
775 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
777 con_initialized = true;
778 Con_DPrint("Console initialized.\n");
781 void Con_Shutdown (void)
783 ConBuffer_Shutdown(&con);
790 Handles cursor positioning, line wrapping, etc
791 All console printing must go through this in order to be displayed
792 If no console is visible, the notify window will pop up.
795 void Con_PrintToHistory(const char *txt, int mask)
798 // \n goes to next line
799 // \r deletes current line and makes a new one
801 static int cr_pending = 0;
802 static char buf[CON_TEXTSIZE];
803 static int bufpos = 0;
805 if(!con.text) // FIXME uses a non-abstracted property of con
812 ConBuffer_DeleteLastLine(&con);
820 ConBuffer_AddLine(&con, buf, bufpos, mask);
825 ConBuffer_AddLine(&con, buf, bufpos, mask);
829 buf[bufpos++] = *txt;
830 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
832 ConBuffer_AddLine(&con, buf, bufpos, mask);
840 /* The translation table between the graphical font and plain ASCII --KB */
841 static char qfont_table[256] = {
842 '\0', '#', '#', '#', '#', '.', '#', '#',
843 '#', 9, 10, '#', ' ', 13, '.', '.',
844 '[', ']', '0', '1', '2', '3', '4', '5',
845 '6', '7', '8', '9', '.', '<', '=', '>',
846 ' ', '!', '"', '#', '$', '%', '&', '\'',
847 '(', ')', '*', '+', ',', '-', '.', '/',
848 '0', '1', '2', '3', '4', '5', '6', '7',
849 '8', '9', ':', ';', '<', '=', '>', '?',
850 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
851 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
852 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
853 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
854 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
855 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
856 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
857 'x', 'y', 'z', '{', '|', '}', '~', '<',
859 '<', '=', '>', '#', '#', '.', '#', '#',
860 '#', '#', ' ', '#', ' ', '>', '.', '.',
861 '[', ']', '0', '1', '2', '3', '4', '5',
862 '6', '7', '8', '9', '.', '<', '=', '>',
863 ' ', '!', '"', '#', '$', '%', '&', '\'',
864 '(', ')', '*', '+', ',', '-', '.', '/',
865 '0', '1', '2', '3', '4', '5', '6', '7',
866 '8', '9', ':', ';', '<', '=', '>', '?',
867 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
868 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
869 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
870 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
871 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
872 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
873 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
874 'x', 'y', 'z', '{', '|', '}', '~', '<'
877 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest)
879 rcon_redirect_sock = sock;
880 rcon_redirect_dest = dest;
881 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
882 rcon_redirect_bufferpos = 5;
885 void Con_Rcon_Redirect_Flush()
887 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
888 NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
889 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
890 rcon_redirect_bufferpos = 5;
893 void Con_Rcon_Redirect_End()
895 Con_Rcon_Redirect_Flush();
896 rcon_redirect_dest = NULL;
897 rcon_redirect_sock = NULL;
900 void Con_Rcon_Redirect_Abort()
902 rcon_redirect_dest = NULL;
903 rcon_redirect_sock = NULL;
910 Adds a character to the rcon buffer
913 void Con_Rcon_AddChar(int c)
915 if(log_dest_buffer_appending)
917 ++log_dest_buffer_appending;
919 // if this print is in response to an rcon command, add the character
920 // to the rcon redirect buffer
922 if (rcon_redirect_dest)
924 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
925 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
926 Con_Rcon_Redirect_Flush();
928 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
930 if(log_dest_buffer_pos == 0)
931 Log_DestBuffer_Init();
932 log_dest_buffer[log_dest_buffer_pos++] = c;
933 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
934 Log_DestBuffer_Flush();
937 log_dest_buffer_pos = 0;
939 --log_dest_buffer_appending;
943 * Convert an RGB color to its nearest quake color.
944 * I'll cheat on this a bit by translating the colors to HSV first,
945 * S and V decide if it's black or white, otherwise, H will decide the
947 * @param _r Red (0-255)
948 * @param _g Green (0-255)
949 * @param _b Blue (0-255)
950 * @return A quake color character.
952 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
954 float r = ((float)_r)/255.0;
955 float g = ((float)_g)/255.0;
956 float b = ((float)_b)/255.0;
957 float min = min(r, min(g, b));
958 float max = max(r, max(g, b));
960 int h; ///< Hue angle [0,360]
961 float s; ///< Saturation [0,1]
962 float v = max; ///< In HSV v == max [0,1]
969 // Saturation threshold. We now say 0.2 is the minimum value for a color!
972 // If the value is less than half, return a black color code.
973 // Otherwise return a white one.
979 // Let's get the hue angle to define some colors:
983 h = (int)(60.0 * (g-b)/(max-min))%360;
985 h = (int)(60.0 * (b-r)/(max-min) + 120);
986 else // if(max == b) redundant check
987 h = (int)(60.0 * (r-g)/(max-min) + 240);
989 if(h < 36) // *red* to orange
991 else if(h < 80) // orange over *yellow* to evilish-bright-green
993 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
995 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
997 else if(h < 270) // darkish blue over *dark blue* to cool purple
999 else if(h < 330) // cool purple over *purple* to ugly swiny red
1001 else // ugly red to red closes the circly
1009 Prints to all appropriate console targets, and adds timestamps
1012 extern cvar_t timestamps;
1013 extern cvar_t timeformat;
1014 extern qboolean sys_nostdout;
1015 void Con_Print(const char *msg)
1017 static int mask = 0;
1018 static int index = 0;
1019 static char line[MAX_INPUTLINE];
1023 Con_Rcon_AddChar(*msg);
1024 // if this is the beginning of a new line, print timestamp
1027 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1029 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1030 line[index++] = STRING_COLOR_TAG;
1031 // assert( STRING_COLOR_DEFAULT < 10 )
1032 line[index++] = STRING_COLOR_DEFAULT + '0';
1033 // special color codes for chat messages must always come first
1034 // for Con_PrintToHistory to work properly
1035 if (*msg == 1 || *msg == 2)
1040 if(gamemode == GAME_NEXUIZ)
1042 if(msg[1] == '\r' && cl.foundtalk2wav)
1043 S_LocalSound ("sound/misc/talk2.wav");
1045 S_LocalSound ("sound/misc/talk.wav");
1049 if (msg[1] == '(' && cl.foundtalk2wav)
1050 S_LocalSound ("sound/misc/talk2.wav");
1052 S_LocalSound ("sound/misc/talk.wav");
1054 mask = CON_MASK_CHAT;
1056 line[index++] = STRING_COLOR_TAG;
1057 line[index++] = '3';
1059 Con_Rcon_AddChar(*msg);
1062 for (;*timestamp;index++, timestamp++)
1063 if (index < (int)sizeof(line) - 2)
1064 line[index] = *timestamp;
1066 // append the character
1067 line[index++] = *msg;
1068 // if this is a newline character, we have a complete line to print
1069 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1071 // terminate the line
1075 // send to scrollable buffer
1076 if (con_initialized && cls.state != ca_dedicated)
1078 Con_PrintToHistory(line, mask);
1081 // send to terminal or dedicated server window
1085 if(sys_specialcharactertranslation.integer)
1087 for (p = (unsigned char *) line;*p; p++)
1088 *p = qfont_table[*p];
1091 if(sys_colortranslation.integer == 1) // ANSI
1093 static char printline[MAX_INPUTLINE * 4 + 3];
1094 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1095 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1100 for(in = line, out = printline; *in; ++in)
1104 case STRING_COLOR_TAG:
1105 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1107 char r = tolower(in[2]);
1108 char g = tolower(in[3]);
1109 char b = tolower(in[4]);
1110 // it's a hex digit already, so the else part needs no check --blub
1111 if(isdigit(r)) r -= '0';
1113 if(isdigit(g)) g -= '0';
1115 if(isdigit(b)) b -= '0';
1118 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1119 in += 3; // 3 only, the switch down there does the fourth
1126 case STRING_COLOR_TAG:
1128 *out++ = STRING_COLOR_TAG;
1134 if(lastcolor == 0) break; else lastcolor = 0;
1135 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1140 if(lastcolor == 1) break; else lastcolor = 1;
1141 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1146 if(lastcolor == 2) break; else lastcolor = 2;
1147 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1152 if(lastcolor == 3) break; else lastcolor = 3;
1153 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1158 if(lastcolor == 4) break; else lastcolor = 4;
1159 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1164 if(lastcolor == 5) break; else lastcolor = 5;
1165 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1170 if(lastcolor == 6) break; else lastcolor = 6;
1171 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1176 // bold normal color
1178 if(lastcolor == 8) break; else lastcolor = 8;
1179 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1182 *out++ = STRING_COLOR_TAG;
1189 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1206 Sys_PrintToTerminal(printline);
1208 else if(sys_colortranslation.integer == 2) // Quake
1210 Sys_PrintToTerminal(line);
1214 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1217 for(in = line, out = printline; *in; ++in)
1221 case STRING_COLOR_TAG:
1224 case STRING_COLOR_RGB_TAG_CHAR:
1225 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1230 *out++ = STRING_COLOR_TAG;
1231 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1234 case STRING_COLOR_TAG:
1236 *out++ = STRING_COLOR_TAG;
1251 *out++ = STRING_COLOR_TAG;
1261 Sys_PrintToTerminal(printline);
1264 // empty the line buffer
1275 Prints to all appropriate console targets
1278 void Con_Printf(const char *fmt, ...)
1281 char msg[MAX_INPUTLINE];
1283 va_start(argptr,fmt);
1284 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1294 A Con_Print that only shows up if the "developer" cvar is set
1297 void Con_DPrint(const char *msg)
1299 if (!developer.integer)
1300 return; // don't confuse non-developers with techie stuff...
1308 A Con_Printf that only shows up if the "developer" cvar is set
1311 void Con_DPrintf(const char *fmt, ...)
1314 char msg[MAX_INPUTLINE];
1316 if (!developer.integer)
1317 return; // don't confuse non-developers with techie stuff...
1319 va_start(argptr,fmt);
1320 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1328 ==============================================================================
1332 ==============================================================================
1339 The input line scrolls horizontally if typing goes beyond the right edge
1341 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1344 void Con_DrawInput (void)
1348 char editlinecopy[MAX_INPUTLINE+1], *text;
1351 if (!key_consoleactive)
1352 return; // don't draw anything
1354 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1355 text = editlinecopy;
1357 // Advanced Console Editing by Radix radix@planetquake.com
1358 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1359 // use strlen of edit_line instead of key_linepos to allow editing
1360 // of early characters w/o erasing
1362 y = (int)strlen(text);
1364 // fill out remainder with spaces
1365 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1368 // add the cursor frame
1369 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1370 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1372 // text[key_linepos + 1] = 0;
1374 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1379 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 );
1382 // key_line[key_linepos] = 0;
1388 float alignment; // 0 = left, 0.5 = center, 1 = right
1394 const char *continuationString;
1397 int colorindex; // init to -1
1401 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1403 con_text_info_t *ti = (con_text_info_t *) passthrough;
1406 ti->colorindex = -1;
1407 return ti->fontsize * ti->font->maxwidth;
1410 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1411 else if(maxWidth == -1)
1412 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1415 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1416 // Note: this is NOT a Con_Printf, as it could print recursively
1421 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1427 (void) isContinuation;
1431 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1433 con_text_info_t *ti = (con_text_info_t *) passthrough;
1435 if(ti->y < ti->ymin - 0.001)
1437 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1441 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1442 if(isContinuation && *ti->continuationString)
1443 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);
1445 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);
1448 ti->y += ti->fontsize;
1452 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)
1456 int maxlines = (int) floor(height / fontsize + 0.01f);
1459 int continuationWidth = 0;
1461 double t = cl.time; // saved so it won't change
1464 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1465 ti.fontsize = fontsize;
1466 ti.alignment = alignment_x;
1469 ti.ymax = y + height;
1470 ti.continuationString = continuationString;
1473 Con_WordWidthFunc(&ti, NULL, &l, -1);
1474 l = strlen(continuationString);
1475 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1477 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1478 startidx = CON_LINES_COUNT;
1479 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1481 con_lineinfo_t *l = &CON_LINES(i);
1484 if((l->mask & mask_must) != mask_must)
1486 if(l->mask & mask_mustnot)
1488 if(maxage && (l->addtime < t - maxage))
1492 // Calculate its actual height...
1493 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1494 if(lines + mylines >= maxlines)
1496 nskip = lines + mylines - maxlines;
1505 // then center according to the calculated amount of lines...
1507 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1509 // then actually draw
1510 for(i = startidx; i < CON_LINES_COUNT; ++i)
1512 con_lineinfo_t *l = &CON_LINES(i);
1514 if((l->mask & mask_must) != mask_must)
1516 if(l->mask & mask_mustnot)
1518 if(maxage && (l->addtime < t - maxage))
1521 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1531 Draws the last few lines of output transparently over the game top
1534 void Con_DrawNotify (void)
1537 float chatstart, notifystart, inputsize;
1539 char temptext[MAX_INPUTLINE];
1543 ConBuffer_FixTimes(&con);
1545 numChatlines = con_chat.integer;
1546 chatpos = con_chatpos.integer;
1548 if (con_notify.integer < 0)
1549 Cvar_SetValueQuick(&con_notify, 0);
1550 if (gamemode == GAME_TRANSFUSION)
1551 v = 8; // vertical offset
1555 // GAME_NEXUIZ: center, otherwise left justify
1556 align = con_notifyalign.value;
1557 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1559 if(gamemode == GAME_NEXUIZ)
1567 // first chat, input line, then notify
1569 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1571 else if(chatpos > 0)
1573 // first notify, then (chatpos-1) empty lines, then chat, then input
1575 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1577 else // if(chatpos < 0)
1579 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1581 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1586 // just notify and input
1588 chatstart = 0; // shut off gcc warning
1591 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, "");
1596 v = chatstart + numChatlines * con_chatsize.value;
1597 Con_DrawNotifyRect(CON_MASK_CHAT, CON_MASK_INPUT, con_chattime.value, 0, chatstart, vid_conwidth.value * con_chatwidth.value, v - chatstart, con_chatsize.value, 0.0, 1.0, "^3\014\014\014 "); // 015 is ·> character in conchars.tga
1600 if (key_dest == key_message)
1602 int colorindex = -1;
1604 // LordHavoc: speedup, and other improvements
1606 dpsnprintf(temptext, sizeof(temptext), "]%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1608 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1610 dpsnprintf(temptext, sizeof(temptext), "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1613 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1614 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1617 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1623 Con_MeasureConsoleLine
1625 Counts the number of lines for a line on the console.
1628 int Con_MeasureConsoleLine(int lineno)
1630 float width = vid_conwidth.value;
1633 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1636 ti.fontsize = con_textsize.value;
1637 ti.font = FONT_CONSOLE;
1639 return COM_Wordwrap(con.lines[lineno].start, con.lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1640 // FIXME uses con in a non abstracted way
1647 Returns the height of a given console line; calculates it if necessary.
1650 int Con_LineHeight(int i)
1652 int h = con.lines[i].height;
1655 return con.lines[i].height = Con_MeasureConsoleLine(i);
1656 // FIXME uses con in a non abstracted way
1663 Draws a line of the console; returns its height in lines.
1664 If alpha is 0, the line is not drawn, but still wrapped and its height
1668 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1670 float width = vid_conwidth.value;
1673 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1676 ti.continuationString = "";
1678 ti.fontsize = con_textsize.value;
1679 ti.font = FONT_CONSOLE;
1681 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1686 return COM_Wordwrap(con.lines[lineno].start, con.lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1687 // FIXME uses con in a non abstracted way
1694 Calculates the last visible line index and how much to show of it based on
1698 void Con_LastVisibleLine(int *last, int *limitlast)
1703 if(con_backscroll < 0)
1706 // now count until we saw con_backscroll actual lines
1707 for(ic = 0; ic < CON_LINES_COUNT; ++ic)
1709 int i = CON_LINES_IDX(CON_LINES_COUNT - 1 - ic);
1710 int h = Con_LineHeight(i);
1712 // line is the last visible line?
1713 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1716 *limitlast = lines_seen + h - con_backscroll;
1723 // if we get here, no line was on screen - scroll so that one line is
1725 con_backscroll = lines_seen - 1;
1726 *last = con.lines_first;
1727 // FIXME uses con in a non abstracted way
1735 Draws the console with the solid background
1736 The typing input line at the bottom should only be drawn if typing is allowed
1739 void Con_DrawConsole (int lines)
1741 int i, last, limitlast;
1747 con_vislines = lines;
1749 // draw the background
1750 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
1751 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);
1754 if(CON_LINES_COUNT > 0)
1756 float ymax = con_vislines - 2 * con_textsize.value;
1757 Con_LastVisibleLine(&last, &limitlast);
1758 y = ymax - con_textsize.value;
1761 y += (con.lines[last].height - limitlast) * con_textsize.value;
1762 // FIXME uses con in a non abstracted way
1767 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1768 if(i == con.lines_first)
1769 // FIXME uses con in a non abstracted way
1770 break; // top of console buffer
1772 break; // top of console window
1774 i = CON_LINES_PRED(i);
1778 // draw the input prompt, user text, and cursor if desired
1786 Prints not only map filename, but also
1787 its format (q1/q2/q3/hl) and even its message
1789 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1790 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1791 //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
1792 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1793 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1797 int i, k, max, p, o, min;
1800 unsigned char buf[1024];
1802 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1803 t = FS_Search(message, 1, true);
1806 if (t->numfilenames > 1)
1807 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1808 len = (unsigned char *)Z_Malloc(t->numfilenames);
1810 for(max=i=0;i<t->numfilenames;i++)
1812 k = (int)strlen(t->filenames[i]);
1822 for(i=0;i<t->numfilenames;i++)
1824 int lumpofs = 0, lumplen = 0;
1825 char *entities = NULL;
1826 const char *data = NULL;
1828 char entfilename[MAX_QPATH];
1829 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1831 f = FS_OpenVirtualFile(t->filenames[i], true);
1834 memset(buf, 0, 1024);
1835 FS_Read(f, buf, 1024);
1836 if (!memcmp(buf, "IBSP", 4))
1838 p = LittleLong(((int *)buf)[1]);
1839 if (p == Q3BSPVERSION)
1841 q3dheader_t *header = (q3dheader_t *)buf;
1842 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1843 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1845 else if (p == Q2BSPVERSION)
1847 q2dheader_t *header = (q2dheader_t *)buf;
1848 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1849 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1852 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1854 dheader_t *header = (dheader_t *)buf;
1855 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1856 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1860 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1861 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1862 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1863 if (!entities && lumplen >= 10)
1865 FS_Seek(f, lumpofs, SEEK_SET);
1866 entities = (char *)Z_Malloc(lumplen + 1);
1867 FS_Read(f, entities, lumplen);
1871 // if there are entities to parse, a missing message key just
1872 // means there is no title, so clear the message string now
1878 if (!COM_ParseToken_Simple(&data, false, false))
1880 if (com_token[0] == '{')
1882 if (com_token[0] == '}')
1884 // skip leading whitespace
1885 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
1886 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
1887 keyname[l] = com_token[k+l];
1889 if (!COM_ParseToken_Simple(&data, false, false))
1891 if (developer.integer >= 100)
1892 Con_Printf("key: %s %s\n", keyname, com_token);
1893 if (!strcmp(keyname, "message"))
1895 // get the message contents
1896 strlcpy(message, com_token, sizeof(message));
1906 *(t->filenames[i]+len[i]+5) = 0;
1909 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1910 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1911 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1912 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1913 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1915 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1920 k = *(t->filenames[0]+5+p);
1923 for(i=1;i<t->numfilenames;i++)
1924 if(*(t->filenames[i]+5+p) != k)
1928 if(p > o && completedname && completednamebufferlength > 0)
1930 memset(completedname, 0, completednamebufferlength);
1931 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1941 New function for tab-completion system
1942 Added by EvilTypeGuy
1943 MEGA Thanks to Taniwha
1946 void Con_DisplayList(const char **list)
1948 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1949 const char **walk = list;
1952 len = (int)strlen(*walk);
1960 len = (int)strlen(*list);
1961 if (pos + maxlen >= width) {
1967 for (i = 0; i < (maxlen - len); i++)
1979 SanitizeString strips color tags from the string in
1980 and writes the result on string out
1982 void SanitizeString(char *in, char *out)
1986 if(*in == STRING_COLOR_TAG)
1991 out[0] = STRING_COLOR_TAG;
1995 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2002 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2005 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2007 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2014 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2019 else if (*in != STRING_COLOR_TAG)
2022 *out = qfont_table[*(unsigned char*)in];
2029 // Now it becomes TRICKY :D --blub
2030 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2031 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2032 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2033 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
2034 static int Nicks_matchpos;
2036 // co against <<:BLASTER:>> is true!?
2037 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2041 if(tolower(*a) == tolower(*b))
2055 return (*a < *b) ? -1 : 1;
2059 return (*a < *b) ? -1 : 1;
2063 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2066 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2068 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2069 return Nicks_strncasecmp_nospaces(a, b, a_len);
2070 return strncasecmp(a, b, a_len);
2073 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2075 // ignore non alphanumerics of B
2076 // if A contains a non-alphanumeric, B must contain it as well though!
2079 qboolean alnum_a, alnum_b;
2081 if(tolower(*a) == tolower(*b))
2083 if(*a == 0) // end of both strings, they're equal
2090 // not equal, end of one string?
2095 // ignore non alphanumerics
2096 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2097 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2098 if(!alnum_a) // b must contain this
2099 return (*a < *b) ? -1 : 1;
2102 // otherwise, both are alnum, they're just not equal, return the appropriate number
2104 return (*a < *b) ? -1 : 1;
2110 /* Nicks_CompleteCountPossible
2112 Count the number of possible nicks to complete
2114 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2123 if(!con_nickcompletion.integer)
2126 // changed that to 1
2127 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2130 for(i = 0; i < cl.maxclients; ++i)
2133 if(!cl.scores[p].name[0])
2136 SanitizeString(cl.scores[p].name, name);
2137 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2142 length = strlen(name);
2144 spos = pos - 1; // no need for a minimum of characters :)
2148 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2150 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2151 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2157 if(isCon && spos == 0)
2159 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2165 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2166 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2168 // the sanitized list
2169 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2172 Nicks_matchpos = match;
2175 Nicks_offset[count] = s - (&line[match]);
2176 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2183 void Cmd_CompleteNicksPrint(int count)
2186 for(i = 0; i < count; ++i)
2187 Con_Printf("%s\n", Nicks_list[i]);
2190 void Nicks_CutMatchesNormal(int count)
2192 // cut match 0 down to the longest possible completion
2195 c = strlen(Nicks_sanlist[0]) - 1;
2196 for(i = 1; i < count; ++i)
2198 l = strlen(Nicks_sanlist[i]) - 1;
2202 for(l = 0; l <= c; ++l)
2203 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2209 Nicks_sanlist[0][c+1] = 0;
2210 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2213 unsigned int Nicks_strcleanlen(const char *s)
2218 if( (*s >= 'a' && *s <= 'z') ||
2219 (*s >= 'A' && *s <= 'Z') ||
2220 (*s >= '0' && *s <= '9') ||
2228 void Nicks_CutMatchesAlphaNumeric(int count)
2230 // cut match 0 down to the longest possible completion
2233 char tempstr[sizeof(Nicks_sanlist[0])];
2235 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2237 c = strlen(Nicks_sanlist[0]);
2238 for(i = 0, l = 0; i < (int)c; ++i)
2240 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2241 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2242 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2244 tempstr[l++] = Nicks_sanlist[0][i];
2249 for(i = 1; i < count; ++i)
2252 b = Nicks_sanlist[i];
2262 if(tolower(*a) == tolower(*b))
2268 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2270 // b is alnum, so cut
2277 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2278 Nicks_CutMatchesNormal(count);
2279 //if(!Nicks_sanlist[0][0])
2280 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2282 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2283 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2287 void Nicks_CutMatchesNoSpaces(int count)
2289 // cut match 0 down to the longest possible completion
2292 char tempstr[sizeof(Nicks_sanlist[0])];
2295 c = strlen(Nicks_sanlist[0]);
2296 for(i = 0, l = 0; i < (int)c; ++i)
2298 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2300 tempstr[l++] = Nicks_sanlist[0][i];
2305 for(i = 1; i < count; ++i)
2308 b = Nicks_sanlist[i];
2318 if(tolower(*a) == tolower(*b))
2332 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2333 Nicks_CutMatchesNormal(count);
2334 //if(!Nicks_sanlist[0][0])
2335 //Con_Printf("TS: %s\n", tempstr);
2336 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2338 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2339 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2343 void Nicks_CutMatches(int count)
2345 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2346 Nicks_CutMatchesAlphaNumeric(count);
2347 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2348 Nicks_CutMatchesNoSpaces(count);
2350 Nicks_CutMatchesNormal(count);
2353 const char **Nicks_CompleteBuildList(int count)
2357 // the list is freed by Con_CompleteCommandLine, so create a char**
2358 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2360 for(; bpos < count; ++bpos)
2361 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2363 Nicks_CutMatches(count);
2371 Restores the previous used color, after the autocompleted name.
2373 int Nicks_AddLastColor(char *buffer, int pos)
2375 qboolean quote_added = false;
2377 int color = STRING_COLOR_DEFAULT + '0';
2378 char r = 0, g = 0, b = 0;
2380 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2382 // we'll have to add a quote :)
2383 buffer[pos++] = '\"';
2387 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2389 // add color when no quote was added, or when flags &4?
2391 for(match = Nicks_matchpos-1; match >= 0; --match)
2393 if(buffer[match] == STRING_COLOR_TAG)
2395 if( isdigit(buffer[match+1]) )
2397 color = buffer[match+1];
2400 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2402 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2404 r = buffer[match+2];
2405 g = buffer[match+3];
2406 b = buffer[match+4];
2415 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2417 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2418 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2421 buffer[pos++] = STRING_COLOR_TAG;
2424 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2430 buffer[pos++] = color;
2435 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2438 /*if(!con_nickcompletion.integer)
2439 return; is tested in Nicks_CompletionCountPossible */
2440 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2446 msg = Nicks_list[0];
2447 len = min(size - Nicks_matchpos - 3, strlen(msg));
2448 memcpy(&buffer[Nicks_matchpos], msg, len);
2449 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2450 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2451 buffer[len++] = ' ';
2458 Con_Printf("\n%i possible nicks:\n", n);
2459 Cmd_CompleteNicksPrint(n);
2461 Nicks_CutMatches(n);
2463 msg = Nicks_sanlist[0];
2464 len = min(size - Nicks_matchpos, strlen(msg));
2465 memcpy(&buffer[Nicks_matchpos], msg, len);
2466 buffer[Nicks_matchpos + len] = 0;
2468 return Nicks_matchpos + len;
2475 Con_CompleteCommandLine
2477 New function for tab-completion system
2478 Added by EvilTypeGuy
2479 Thanks to Fett erich@heintz.com
2481 Enhanced to tab-complete map names by [515]
2484 void Con_CompleteCommandLine (void)
2486 const char *cmd = "";
2488 const char **list[4] = {0, 0, 0, 0};
2491 int c, v, a, i, cmd_len, pos, k;
2492 int n; // nicks --blub
2493 const char *space, *patterns;
2495 //find what we want to complete
2500 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2506 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2507 key_line[key_linepos] = 0; //hide them
2509 space = strchr(key_line + 1, ' ');
2510 if(space && pos == (space - key_line) + 1)
2512 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2514 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2515 if(patterns && !*patterns)
2516 patterns = NULL; // get rid of the empty string
2518 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2522 if (GetMapList(s, t, sizeof(t)))
2524 // first move the cursor
2525 key_linepos += (int)strlen(t) - (int)strlen(s);
2527 // and now do the actual work
2529 strlcat(key_line, t, MAX_INPUTLINE);
2530 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2532 // and fix the cursor
2533 if(key_linepos > (int) strlen(key_line))
2534 key_linepos = (int) strlen(key_line);
2543 stringlist_t resultbuf, dirbuf;
2546 // // store completion patterns (space separated) for command foo in con_completion_foo
2547 // set con_completion_foo "foodata/*.foodefault *.foo"
2550 // Note: patterns with slash are always treated as absolute
2551 // patterns; patterns without slash search in the innermost
2552 // directory the user specified. There is no way to "complete into"
2553 // a directory as of now, as directories seem to be unknown to the
2557 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2558 // set con_completion_playdemo "*.dem"
2559 // set con_completion_play "*.wav *.ogg"
2561 // TODO somehow add support for directories; these shall complete
2562 // to their name + an appended slash.
2564 stringlistinit(&resultbuf);
2565 stringlistinit(&dirbuf);
2566 while(COM_ParseToken_Simple(&patterns, false, false))
2569 if(strchr(com_token, '/'))
2571 search = FS_Search(com_token, true, true);
2575 const char *slash = strrchr(s, '/');
2578 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2579 strlcat(t, com_token, sizeof(t));
2580 search = FS_Search(t, true, true);
2583 search = FS_Search(com_token, true, true);
2587 for(i = 0; i < search->numfilenames; ++i)
2588 if(!strncmp(search->filenames[i], s, strlen(s)))
2589 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2590 stringlistappend(&resultbuf, search->filenames[i]);
2591 FS_FreeSearch(search);
2595 // In any case, add directory names
2598 const char *slash = strrchr(s, '/');
2601 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2602 strlcat(t, "*", sizeof(t));
2603 search = FS_Search(t, true, true);
2606 search = FS_Search("*", 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_DIRECTORY)
2612 stringlistappend(&dirbuf, search->filenames[i]);
2613 FS_FreeSearch(search);
2617 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2620 unsigned int matchchars;
2621 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2623 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2626 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2628 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2632 stringlistsort(&resultbuf); // dirbuf is already sorted
2633 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2634 for(i = 0; i < dirbuf.numstrings; ++i)
2636 Con_Printf("%s/\n", dirbuf.strings[i]);
2638 for(i = 0; i < resultbuf.numstrings; ++i)
2640 Con_Printf("%s\n", resultbuf.strings[i]);
2642 matchchars = sizeof(t) - 1;
2643 if(resultbuf.numstrings > 0)
2645 p = resultbuf.strings[0];
2646 q = resultbuf.strings[resultbuf.numstrings - 1];
2647 for(; *p && *p == *q; ++p, ++q);
2648 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2650 if(dirbuf.numstrings > 0)
2652 p = dirbuf.strings[0];
2653 q = dirbuf.strings[dirbuf.numstrings - 1];
2654 for(; *p && *p == *q; ++p, ++q);
2655 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2657 // now p points to the first non-equal character, or to the end
2658 // of resultbuf.strings[0]. We want to append the characters
2659 // from resultbuf.strings[0] to (not including) p as these are
2660 // the unique prefix
2661 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2664 // first move the cursor
2665 key_linepos += (int)strlen(t) - (int)strlen(s);
2667 // and now do the actual work
2669 strlcat(key_line, t, MAX_INPUTLINE);
2670 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2672 // and fix the cursor
2673 if(key_linepos > (int) strlen(key_line))
2674 key_linepos = (int) strlen(key_line);
2676 stringlistfreecontents(&resultbuf);
2677 stringlistfreecontents(&dirbuf);
2679 return; // bail out, when we complete for a command that wants a file name
2684 // Count number of possible matches and print them
2685 c = Cmd_CompleteCountPossible(s);
2688 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2689 Cmd_CompleteCommandPrint(s);
2691 v = Cvar_CompleteCountPossible(s);
2694 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2695 Cvar_CompleteCvarPrint(s);
2697 a = Cmd_CompleteAliasCountPossible(s);
2700 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2701 Cmd_CompleteAliasPrint(s);
2703 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2706 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2707 Cmd_CompleteNicksPrint(n);
2710 if (!(c + v + a + n)) // No possible matches
2713 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2718 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2720 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2722 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2724 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2726 for (cmd_len = (int)strlen(s);;cmd_len++)
2729 for (i = 0; i < 3; i++)
2731 for (l = list[i];*l;l++)
2732 if ((*l)[cmd_len] != cmd[cmd_len])
2734 // all possible matches share this character, so we continue...
2737 // if all matches ended at the same position, stop
2738 // (this means there is only one match)
2744 // prevent a buffer overrun by limiting cmd_len according to remaining space
2745 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2749 memcpy(&key_line[key_linepos], cmd, cmd_len);
2750 key_linepos += cmd_len;
2751 // if there is only one match, add a space after it
2752 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2755 { // was a nick, might have an offset, and needs colors ;) --blub
2756 key_linepos = pos - Nicks_offset[0];
2757 cmd_len = strlen(Nicks_list[0]);
2758 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2760 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2761 key_linepos += cmd_len;
2762 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2763 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2765 key_line[key_linepos++] = ' ';
2769 // use strlcat to avoid a buffer overrun
2770 key_line[key_linepos] = 0;
2771 strlcat(key_line, s2, sizeof(key_line));
2773 // free the command, cvar, and alias lists
2774 for (i = 0; i < 4; i++)
2776 Mem_Free((void *)list[i]);