2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 #if !defined(WIN32) || defined(__MINGW32__)
29 float con_cursorspeed = 4;
31 #define CON_TEXTSIZE 1048576
32 #define CON_MAXLINES 16384
34 // lines up from bottom to display
39 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
40 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
41 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
43 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
44 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
45 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
47 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
48 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
49 cvar_t con_chatpos = {CVAR_SAVE, "con_chatpos","0", "where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top)"};
50 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
51 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
52 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
53 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
56 cvar_t sys_specialcharactertranslation = {0, "sys_specialcharactertranslation", "1", "terminal console conchars to ASCII translation (set to 0 if your conchars.tga is for an 8bit character set or if you want raw output)"};
58 cvar_t sys_colortranslation = {0, "sys_colortranslation", "0", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
60 cvar_t sys_colortranslation = {0, "sys_colortranslation", "1", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
64 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
65 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
66 "0: add nothing after completion. "
67 "1: add the last color after completion. "
68 "2: add a quote when starting a quote instead of the color. "
69 "4: will replace 1, will force color, even after a quote. "
70 "8: ignore non-alphanumerics. "
71 "16: ignore spaces. "};
72 #define NICKS_ADD_COLOR 1
73 #define NICKS_ADD_QUOTE 2
74 #define NICKS_FORCE_COLOR 4
75 #define NICKS_ALPHANUMERICS_ONLY 8
76 #define NICKS_NO_SPACES 16
78 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
79 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
80 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
85 qboolean con_initialized;
87 // used for server replies to rcon command
88 lhnetsocket_t *rcon_redirect_sock = NULL;
89 lhnetaddress_t *rcon_redirect_dest = NULL;
90 int rcon_redirect_bufferpos = 0;
91 char rcon_redirect_buffer[1400];
93 // generic functions for console buffers
95 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
97 buf->textsize = textsize;
98 buf->text = (char *) Mem_Alloc(mempool, textsize);
99 buf->maxlines = maxlines;
100 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
101 buf->lines_first = 0;
102 buf->lines_count = 0;
110 void ConBuffer_Clear (conbuffer_t *buf)
112 buf->lines_count = 0;
120 void ConBuffer_Shutdown(conbuffer_t *buf)
123 Mem_Free(buf->lines);
132 Notifies the console code about the current time
133 (and shifts back times of other entries when the time
137 void ConBuffer_FixTimes(conbuffer_t *buf)
140 if(buf->lines_count >= 1)
142 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
145 for(i = 0; i < buf->lines_count; ++i)
146 CONBUFFER_LINES(buf, i).addtime += diff;
155 Deletes the first line from the console history.
158 void ConBuffer_DeleteLine(conbuffer_t *buf)
160 if(buf->lines_count == 0)
163 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
168 ConBuffer_DeleteLastLine
170 Deletes the last line from the console history.
173 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
175 if(buf->lines_count == 0)
184 Checks if there is space for a line of the given length, and if yes, returns a
185 pointer to the start of such a space, and NULL otherwise.
188 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
190 if(len > buf->textsize)
192 if(buf->lines_count == 0)
196 char *firstline_start = buf->lines[buf->lines_first].start;
197 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
198 // the buffer is cyclic, so we first have two cases...
199 if(firstline_start < lastline_onepastend) // buffer is contiguous
202 if(len <= buf->text + buf->textsize - lastline_onepastend)
203 return lastline_onepastend;
205 else if(len <= firstline_start - buf->text)
210 else // buffer has a contiguous hole
212 if(len <= firstline_start - lastline_onepastend)
213 return lastline_onepastend;
224 Appends a given string as a new line to the console.
227 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
232 ConBuffer_FixTimes(buf);
234 if(len >= buf->textsize)
237 // only display end of line.
238 line += len - buf->textsize + 1;
239 len = buf->textsize - 1;
241 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
242 ConBuffer_DeleteLine(buf);
243 memcpy(putpos, line, len);
247 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
249 p = &CONBUFFER_LINES_LAST(buf);
252 p->addtime = cl.time;
254 p->height = -1; // calculate when needed
257 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
261 start = buf->lines_count;
262 for(i = start - 1; i >= 0; --i)
264 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
266 if((l->mask & mask_must) != mask_must)
268 if(l->mask & mask_mustnot)
277 int Con_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
280 for(i = start + 1; i < buf->lines_count; ++i)
282 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
284 if((l->mask & mask_must) != mask_must)
286 if(l->mask & mask_mustnot)
295 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
297 static char copybuf[MAX_INPUTLINE];
298 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
299 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
300 strlcpy(copybuf, l->start, sz);
305 ==============================================================================
309 ==============================================================================
312 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
313 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"};
314 char log_dest_buffer[1400]; // UDP packet
315 size_t log_dest_buffer_pos;
316 unsigned int log_dest_buffer_appending;
317 char crt_log_file [MAX_OSPATH] = "";
318 qfile_t* logfile = NULL;
320 unsigned char* logqueue = NULL;
322 size_t logq_size = 0;
324 void Log_ConPrint (const char *msg);
331 static void Log_DestBuffer_Init()
333 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
334 log_dest_buffer_pos = 5;
342 void Log_DestBuffer_Flush()
344 lhnetaddress_t log_dest_addr;
345 lhnetsocket_t *log_dest_socket;
346 const char *s = log_dest_udp.string;
347 qboolean have_opened_temp_sockets = false;
348 if(s) if(log_dest_buffer_pos > 5)
350 ++log_dest_buffer_appending;
351 log_dest_buffer[log_dest_buffer_pos++] = 0;
353 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
355 have_opened_temp_sockets = true;
356 NetConn_OpenServerPorts(true);
359 while(COM_ParseToken_Console(&s))
360 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
362 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
364 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
366 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
369 if(have_opened_temp_sockets)
370 NetConn_CloseServerPorts();
371 --log_dest_buffer_appending;
373 log_dest_buffer_pos = 0;
381 const char* Log_Timestamp (const char *desc)
383 static char timestamp [128];
390 char timestring [64];
392 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
395 localtime_s (&crt_tm, &crt_time);
396 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
398 crt_tm = localtime (&crt_time);
399 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
403 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
405 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
418 if (logfile != NULL || log_file.string[0] == '\0')
421 logfile = FS_OpenRealFile(log_file.string, "a", false);
424 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
425 FS_Print (logfile, Log_Timestamp ("Log started"));
435 void Log_Close (void)
440 FS_Print (logfile, Log_Timestamp ("Log stopped"));
441 FS_Print (logfile, "\n");
445 crt_log_file[0] = '\0';
454 void Log_Start (void)
460 // Dump the contents of the log queue into the log file and free it
461 if (logqueue != NULL)
463 unsigned char *temp = logqueue;
468 FS_Write (logfile, temp, logq_ind);
469 if(*log_dest_udp.string)
471 for(pos = 0; pos < logq_ind; )
473 if(log_dest_buffer_pos == 0)
474 Log_DestBuffer_Init();
475 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
476 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
477 log_dest_buffer_pos += n;
478 Log_DestBuffer_Flush();
495 void Log_ConPrint (const char *msg)
497 static qboolean inprogress = false;
499 // don't allow feedback loops with memory error reports
504 // Until the host is completely initialized, we maintain a log queue
505 // to store the messages, since the log can't be started before
506 if (logqueue != NULL)
508 size_t remain = logq_size - logq_ind;
509 size_t len = strlen (msg);
511 // If we need to enlarge the log queue
514 size_t factor = ((logq_ind + len) / logq_size) + 1;
515 unsigned char* newqueue;
518 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
519 memcpy (newqueue, logqueue, logq_ind);
522 remain = logq_size - logq_ind;
524 memcpy (&logqueue[logq_ind], msg, len);
531 // Check if log_file has changed
532 if (strcmp (crt_log_file, log_file.string) != 0)
538 // If a log file is available
540 FS_Print (logfile, msg);
551 void Log_Printf (const char *logfilename, const char *fmt, ...)
555 file = FS_OpenRealFile(logfilename, "a", true);
560 va_start (argptr, fmt);
561 FS_VPrintf (file, fmt, argptr);
570 ==============================================================================
574 ==============================================================================
582 void Con_ToggleConsole_f (void)
584 // toggle the 'user wants console' bit
585 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
593 Clear all notify lines.
596 void Con_ClearNotify (void)
599 for(i = 0; i < CON_LINES_COUNT; ++i)
600 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
609 void Con_MessageMode_f (void)
611 key_dest = key_message;
612 chat_mode = 0; // "say"
623 void Con_MessageMode2_f (void)
625 key_dest = key_message;
626 chat_mode = 1; // "say_team"
636 void Con_CommandMode_f (void)
638 key_dest = key_message;
641 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
642 chat_bufferlen = strlen(chat_buffer);
644 chat_mode = -1; // command
651 If the line width has changed, reformat the buffer.
654 void Con_CheckResize (void)
659 f = bound(1, con_textsize.value, 128);
660 if(f != con_textsize.value)
661 Cvar_SetValueQuick(&con_textsize, f);
662 width = (int)floor(vid_conwidth.value / con_textsize.value);
663 width = bound(1, width, con.textsize/4);
664 // FIXME uses con in a non abstracted way
666 if (width == con_linewidth)
669 con_linewidth = width;
671 for(i = 0; i < CON_LINES_COUNT; ++i)
672 CON_LINES(i).height = -1; // recalculate when next needed
678 //[515]: the simplest command ever
679 //LordHavoc: not so simple after I made it print usage...
680 static void Con_Maps_f (void)
684 Con_Printf("usage: maps [mapnameprefix]\n");
687 else if (Cmd_Argc() == 2)
688 GetMapList(Cmd_Argv(1), NULL, 0);
690 GetMapList("", NULL, 0);
693 void Con_ConDump_f (void)
699 Con_Printf("usage: condump <filename>\n");
702 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
705 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
708 for(i = 0; i < CON_LINES_COUNT; ++i)
710 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
711 FS_Write(file, "\n", 1);
718 ConBuffer_Clear(&con);
729 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
731 // Allocate a log queue, this will be freed after configs are parsed
732 logq_size = MAX_INPUTLINE;
733 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
736 Cvar_RegisterVariable (&sys_colortranslation);
737 Cvar_RegisterVariable (&sys_specialcharactertranslation);
739 Cvar_RegisterVariable (&log_file);
740 Cvar_RegisterVariable (&log_dest_udp);
742 // support for the classic Quake option
743 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
744 if (COM_CheckParm ("-condebug") != 0)
745 Cvar_SetQuick (&log_file, "qconsole.log");
747 // register our cvars
748 Cvar_RegisterVariable (&con_chat);
749 Cvar_RegisterVariable (&con_chatpos);
750 Cvar_RegisterVariable (&con_chatsize);
751 Cvar_RegisterVariable (&con_chattime);
752 Cvar_RegisterVariable (&con_chatwidth);
753 Cvar_RegisterVariable (&con_notify);
754 Cvar_RegisterVariable (&con_notifyalign);
755 Cvar_RegisterVariable (&con_notifysize);
756 Cvar_RegisterVariable (&con_notifytime);
757 Cvar_RegisterVariable (&con_textsize);
760 Cvar_RegisterVariable (&con_nickcompletion);
761 Cvar_RegisterVariable (&con_nickcompletion_flags);
763 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
764 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
765 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
767 // register our commands
768 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
769 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
770 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
771 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
772 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
773 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
774 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
776 con_initialized = true;
777 Con_DPrint("Console initialized.\n");
780 void Con_Shutdown (void)
782 ConBuffer_Shutdown(&con);
789 Handles cursor positioning, line wrapping, etc
790 All console printing must go through this in order to be displayed
791 If no console is visible, the notify window will pop up.
794 void Con_PrintToHistory(const char *txt, int mask)
797 // \n goes to next line
798 // \r deletes current line and makes a new one
800 static int cr_pending = 0;
801 static char buf[CON_TEXTSIZE];
802 static int bufpos = 0;
804 if(!con.text) // FIXME uses a non-abstracted property of con
811 ConBuffer_DeleteLastLine(&con);
819 ConBuffer_AddLine(&con, buf, bufpos, mask);
824 ConBuffer_AddLine(&con, buf, bufpos, mask);
828 buf[bufpos++] = *txt;
829 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
831 ConBuffer_AddLine(&con, buf, bufpos, mask);
839 /* The translation table between the graphical font and plain ASCII --KB */
840 static char qfont_table[256] = {
841 '\0', '#', '#', '#', '#', '.', '#', '#',
842 '#', 9, 10, '#', ' ', 13, '.', '.',
843 '[', ']', '0', '1', '2', '3', '4', '5',
844 '6', '7', '8', '9', '.', '<', '=', '>',
845 ' ', '!', '"', '#', '$', '%', '&', '\'',
846 '(', ')', '*', '+', ',', '-', '.', '/',
847 '0', '1', '2', '3', '4', '5', '6', '7',
848 '8', '9', ':', ';', '<', '=', '>', '?',
849 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
850 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
851 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
852 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
853 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
854 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
855 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
856 'x', 'y', 'z', '{', '|', '}', '~', '<',
858 '<', '=', '>', '#', '#', '.', '#', '#',
859 '#', '#', ' ', '#', ' ', '>', '.', '.',
860 '[', ']', '0', '1', '2', '3', '4', '5',
861 '6', '7', '8', '9', '.', '<', '=', '>',
862 ' ', '!', '"', '#', '$', '%', '&', '\'',
863 '(', ')', '*', '+', ',', '-', '.', '/',
864 '0', '1', '2', '3', '4', '5', '6', '7',
865 '8', '9', ':', ';', '<', '=', '>', '?',
866 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
867 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
868 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
869 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
870 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
871 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
872 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
873 'x', 'y', 'z', '{', '|', '}', '~', '<'
876 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest)
878 rcon_redirect_sock = sock;
879 rcon_redirect_dest = dest;
880 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
881 rcon_redirect_bufferpos = 5;
884 void Con_Rcon_Redirect_Flush()
886 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
887 NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
888 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
889 rcon_redirect_bufferpos = 5;
892 void Con_Rcon_Redirect_End()
894 Con_Rcon_Redirect_Flush();
895 rcon_redirect_dest = NULL;
896 rcon_redirect_sock = NULL;
899 void Con_Rcon_Redirect_Abort()
901 rcon_redirect_dest = NULL;
902 rcon_redirect_sock = NULL;
909 Adds a character to the rcon buffer
912 void Con_Rcon_AddChar(int c)
914 if(log_dest_buffer_appending)
916 ++log_dest_buffer_appending;
918 // if this print is in response to an rcon command, add the character
919 // to the rcon redirect buffer
921 if (rcon_redirect_dest)
923 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
924 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
925 Con_Rcon_Redirect_Flush();
927 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
929 if(log_dest_buffer_pos == 0)
930 Log_DestBuffer_Init();
931 log_dest_buffer[log_dest_buffer_pos++] = c;
932 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
933 Log_DestBuffer_Flush();
936 log_dest_buffer_pos = 0;
938 --log_dest_buffer_appending;
942 * Convert an RGB color to its nearest quake color.
943 * I'll cheat on this a bit by translating the colors to HSV first,
944 * S and V decide if it's black or white, otherwise, H will decide the
946 * @param _r Red (0-255)
947 * @param _g Green (0-255)
948 * @param _b Blue (0-255)
949 * @return A quake color character.
951 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
953 float r = ((float)_r)/255.0;
954 float g = ((float)_g)/255.0;
955 float b = ((float)_b)/255.0;
956 float min = min(r, min(g, b));
957 float max = max(r, max(g, b));
959 int h; ///< Hue angle [0,360]
960 float s; ///< Saturation [0,1]
961 float v = max; ///< In HSV v == max [0,1]
968 // Saturation threshold. We now say 0.2 is the minimum value for a color!
971 // If the value is less than half, return a black color code.
972 // Otherwise return a white one.
978 // Let's get the hue angle to define some colors:
982 h = (int)(60.0 * (g-b)/(max-min))%360;
984 h = (int)(60.0 * (b-r)/(max-min) + 120);
985 else // if(max == b) redundant check
986 h = (int)(60.0 * (r-g)/(max-min) + 240);
988 if(h < 36) // *red* to orange
990 else if(h < 80) // orange over *yellow* to evilish-bright-green
992 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
994 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
996 else if(h < 270) // darkish blue over *dark blue* to cool purple
998 else if(h < 330) // cool purple over *purple* to ugly swiny red
1000 else // ugly red to red closes the circly
1008 Prints to all appropriate console targets, and adds timestamps
1011 extern cvar_t timestamps;
1012 extern cvar_t timeformat;
1013 extern qboolean sys_nostdout;
1014 void Con_Print(const char *msg)
1016 static int mask = 0;
1017 static int index = 0;
1018 static char line[MAX_INPUTLINE];
1022 Con_Rcon_AddChar(*msg);
1023 // if this is the beginning of a new line, print timestamp
1026 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1028 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1029 line[index++] = STRING_COLOR_TAG;
1030 // assert( STRING_COLOR_DEFAULT < 10 )
1031 line[index++] = STRING_COLOR_DEFAULT + '0';
1032 // special color codes for chat messages must always come first
1033 // for Con_PrintToHistory to work properly
1034 if (*msg == 1 || *msg == 2)
1039 if(gamemode == GAME_NEXUIZ)
1041 if(msg[1] == '\r' && cl.foundtalk2wav)
1042 S_LocalSound ("sound/misc/talk2.wav");
1044 S_LocalSound ("sound/misc/talk.wav");
1048 if (msg[1] == '(' && cl.foundtalk2wav)
1049 S_LocalSound ("sound/misc/talk2.wav");
1051 S_LocalSound ("sound/misc/talk.wav");
1053 mask = CON_MASK_CHAT;
1055 line[index++] = STRING_COLOR_TAG;
1056 line[index++] = '3';
1058 Con_Rcon_AddChar(*msg);
1061 for (;*timestamp;index++, timestamp++)
1062 if (index < (int)sizeof(line) - 2)
1063 line[index] = *timestamp;
1065 // append the character
1066 line[index++] = *msg;
1067 // if this is a newline character, we have a complete line to print
1068 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1070 // terminate the line
1074 // send to scrollable buffer
1075 if (con_initialized && cls.state != ca_dedicated)
1077 Con_PrintToHistory(line, mask);
1080 // send to terminal or dedicated server window
1084 if(sys_specialcharactertranslation.integer)
1086 for (p = (unsigned char *) line;*p; p++)
1087 *p = qfont_table[*p];
1090 if(sys_colortranslation.integer == 1) // ANSI
1092 static char printline[MAX_INPUTLINE * 4 + 3];
1093 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1094 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1099 for(in = line, out = printline; *in; ++in)
1103 case STRING_COLOR_TAG:
1104 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1106 char r = tolower(in[2]);
1107 char g = tolower(in[3]);
1108 char b = tolower(in[4]);
1109 // it's a hex digit already, so the else part needs no check --blub
1110 if(isdigit(r)) r -= '0';
1112 if(isdigit(g)) g -= '0';
1114 if(isdigit(b)) b -= '0';
1117 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1118 in += 3; // 3 only, the switch down there does the fourth
1125 case STRING_COLOR_TAG:
1127 *out++ = STRING_COLOR_TAG;
1133 if(lastcolor == 0) break; else lastcolor = 0;
1134 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1139 if(lastcolor == 1) break; else lastcolor = 1;
1140 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1145 if(lastcolor == 2) break; else lastcolor = 2;
1146 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1151 if(lastcolor == 3) break; else lastcolor = 3;
1152 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1157 if(lastcolor == 4) break; else lastcolor = 4;
1158 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1163 if(lastcolor == 5) break; else lastcolor = 5;
1164 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1169 if(lastcolor == 6) break; else lastcolor = 6;
1170 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1175 // bold normal color
1177 if(lastcolor == 8) break; else lastcolor = 8;
1178 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1181 *out++ = STRING_COLOR_TAG;
1188 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1205 Sys_PrintToTerminal(printline);
1207 else if(sys_colortranslation.integer == 2) // Quake
1209 Sys_PrintToTerminal(line);
1213 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1216 for(in = line, out = printline; *in; ++in)
1220 case STRING_COLOR_TAG:
1223 case STRING_COLOR_RGB_TAG_CHAR:
1224 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1229 *out++ = STRING_COLOR_TAG;
1230 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1233 case STRING_COLOR_TAG:
1235 *out++ = STRING_COLOR_TAG;
1250 *out++ = STRING_COLOR_TAG;
1260 Sys_PrintToTerminal(printline);
1263 // empty the line buffer
1274 Prints to all appropriate console targets
1277 void Con_Printf(const char *fmt, ...)
1280 char msg[MAX_INPUTLINE];
1282 va_start(argptr,fmt);
1283 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1293 A Con_Print that only shows up if the "developer" cvar is set
1296 void Con_DPrint(const char *msg)
1298 if (!developer.integer)
1299 return; // don't confuse non-developers with techie stuff...
1307 A Con_Printf that only shows up if the "developer" cvar is set
1310 void Con_DPrintf(const char *fmt, ...)
1313 char msg[MAX_INPUTLINE];
1315 if (!developer.integer)
1316 return; // don't confuse non-developers with techie stuff...
1318 va_start(argptr,fmt);
1319 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1327 ==============================================================================
1331 ==============================================================================
1338 The input line scrolls horizontally if typing goes beyond the right edge
1340 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1343 void Con_DrawInput (void)
1347 char editlinecopy[MAX_INPUTLINE+1], *text;
1350 if (!key_consoleactive)
1351 return; // don't draw anything
1353 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1354 text = editlinecopy;
1356 // Advanced Console Editing by Radix radix@planetquake.com
1357 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1358 // use strlen of edit_line instead of key_linepos to allow editing
1359 // of early characters w/o erasing
1361 y = (int)strlen(text);
1363 // fill out remainder with spaces
1364 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1367 // add the cursor frame
1368 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1369 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1371 // text[key_linepos + 1] = 0;
1373 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1378 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 );
1381 // key_line[key_linepos] = 0;
1387 float alignment; // 0 = left, 0.5 = center, 1 = right
1393 const char *continuationString;
1396 int colorindex; // init to -1
1400 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1402 con_text_info_t *ti = (con_text_info_t *) passthrough;
1405 ti->colorindex = -1;
1406 return ti->fontsize * ti->font->maxwidth;
1409 return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize;
1410 else if(maxWidth == -1)
1411 return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize;
1414 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1415 // Note: this is NOT a Con_Printf, as it could print recursively
1420 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1426 (void) isContinuation;
1430 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1432 con_text_info_t *ti = (con_text_info_t *) passthrough;
1434 if(ti->y < ti->ymin - 0.001)
1436 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1440 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1441 if(isContinuation && *ti->continuationString)
1442 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);
1444 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);
1447 ti->y += ti->fontsize;
1451 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)
1455 int maxlines = (int) floor(height / fontsize + 0.01f);
1458 int continuationWidth = 0;
1460 double t = cl.time; // saved so it won't change
1463 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1464 ti.fontsize = fontsize;
1465 ti.alignment = alignment_x;
1468 ti.ymax = y + height;
1469 ti.continuationString = continuationString;
1472 Con_WordWidthFunc(&ti, NULL, &l, -1);
1473 l = strlen(continuationString);
1474 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1476 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1477 startidx = CON_LINES_COUNT;
1478 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1480 con_lineinfo_t *l = &CON_LINES(i);
1483 if((l->mask & mask_must) != mask_must)
1485 if(l->mask & mask_mustnot)
1487 if(maxage && (l->addtime < t - maxage))
1491 // Calculate its actual height...
1492 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1493 if(lines + mylines >= maxlines)
1495 nskip = lines + mylines - maxlines;
1504 // then center according to the calculated amount of lines...
1506 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1508 // then actually draw
1509 for(i = startidx; i < CON_LINES_COUNT; ++i)
1511 con_lineinfo_t *l = &CON_LINES(i);
1513 if((l->mask & mask_must) != mask_must)
1515 if(l->mask & mask_mustnot)
1517 if(maxage && (l->addtime < t - maxage))
1520 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1530 Draws the last few lines of output transparently over the game top
1533 void Con_DrawNotify (void)
1536 float chatstart, notifystart, inputsize;
1538 char temptext[MAX_INPUTLINE];
1542 ConBuffer_FixTimes(&con);
1544 numChatlines = con_chat.integer;
1545 chatpos = con_chatpos.integer;
1547 if (con_notify.integer < 0)
1548 Cvar_SetValueQuick(&con_notify, 0);
1549 if (gamemode == GAME_TRANSFUSION)
1550 v = 8; // vertical offset
1554 // GAME_NEXUIZ: center, otherwise left justify
1555 align = con_notifyalign.value;
1556 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1558 if(gamemode == GAME_NEXUIZ)
1566 // first chat, input line, then notify
1568 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1570 else if(chatpos > 0)
1572 // first notify, then (chatpos-1) empty lines, then chat, then input
1574 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1576 else // if(chatpos < 0)
1578 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1580 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1585 // just notify and input
1587 chatstart = 0; // shut off gcc warning
1590 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, "");
1595 v = chatstart + numChatlines * con_chatsize.value;
1596 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
1599 if (key_dest == key_message)
1601 int colorindex = -1;
1603 // LordHavoc: speedup, and other improvements
1605 dpsnprintf(temptext, sizeof(temptext), "]%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1607 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1609 dpsnprintf(temptext, sizeof(temptext), "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1612 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1613 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1616 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1622 Con_MeasureConsoleLine
1624 Counts the number of lines for a line on the console.
1627 int Con_MeasureConsoleLine(int lineno)
1629 float width = vid_conwidth.value;
1631 con_lineinfo_t *li = &CON_LINES(lineno);
1633 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1636 ti.fontsize = con_textsize.value;
1637 ti.font = FONT_CONSOLE;
1639 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1646 Returns the height of a given console line; calculates it if necessary.
1649 int Con_LineHeight(int i)
1651 con_lineinfo_t *li = &CON_LINES(i);
1655 return li->height = Con_MeasureConsoleLine(i);
1662 Draws a line of the console; returns its height in lines.
1663 If alpha is 0, the line is not drawn, but still wrapped and its height
1667 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1669 float width = vid_conwidth.value;
1671 con_lineinfo_t *li = &CON_LINES(lineno);
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(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1693 Calculates the last visible line index and how much to show of it based on
1697 void Con_LastVisibleLine(int *last, int *limitlast)
1702 if(con_backscroll < 0)
1705 // now count until we saw con_backscroll actual lines
1706 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1708 int h = Con_LineHeight(i);
1710 // line is the last visible line?
1711 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1714 *limitlast = lines_seen + h - con_backscroll;
1721 // if we get here, no line was on screen - scroll so that one line is
1723 con_backscroll = lines_seen - 1;
1724 *last = con.lines_first;
1725 // FIXME uses con in a non abstracted way
1733 Draws the console with the solid background
1734 The typing input line at the bottom should only be drawn if typing is allowed
1737 void Con_DrawConsole (int lines)
1739 int i, last, limitlast;
1745 con_vislines = lines;
1747 // draw the background
1748 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
1749 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);
1752 if(CON_LINES_COUNT > 0)
1754 float ymax = con_vislines - 2 * con_textsize.value;
1755 Con_LastVisibleLine(&last, &limitlast);
1756 y = ymax - con_textsize.value;
1759 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1760 // FIXME uses con in a non abstracted way
1765 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1767 break; // top of console buffer
1769 break; // top of console window
1775 // draw the input prompt, user text, and cursor if desired
1783 Prints not only map filename, but also
1784 its format (q1/q2/q3/hl) and even its message
1786 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1787 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1788 //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
1789 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1790 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1794 int i, k, max, p, o, min;
1797 unsigned char buf[1024];
1799 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1800 t = FS_Search(message, 1, true);
1803 if (t->numfilenames > 1)
1804 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1805 len = (unsigned char *)Z_Malloc(t->numfilenames);
1807 for(max=i=0;i<t->numfilenames;i++)
1809 k = (int)strlen(t->filenames[i]);
1819 for(i=0;i<t->numfilenames;i++)
1821 int lumpofs = 0, lumplen = 0;
1822 char *entities = NULL;
1823 const char *data = NULL;
1825 char entfilename[MAX_QPATH];
1826 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1828 f = FS_OpenVirtualFile(t->filenames[i], true);
1831 memset(buf, 0, 1024);
1832 FS_Read(f, buf, 1024);
1833 if (!memcmp(buf, "IBSP", 4))
1835 p = LittleLong(((int *)buf)[1]);
1836 if (p == Q3BSPVERSION)
1838 q3dheader_t *header = (q3dheader_t *)buf;
1839 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1840 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1842 else if (p == Q2BSPVERSION)
1844 q2dheader_t *header = (q2dheader_t *)buf;
1845 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1846 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1849 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1851 dheader_t *header = (dheader_t *)buf;
1852 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1853 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1857 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1858 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1859 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1860 if (!entities && lumplen >= 10)
1862 FS_Seek(f, lumpofs, SEEK_SET);
1863 entities = (char *)Z_Malloc(lumplen + 1);
1864 FS_Read(f, entities, lumplen);
1868 // if there are entities to parse, a missing message key just
1869 // means there is no title, so clear the message string now
1875 if (!COM_ParseToken_Simple(&data, false, false))
1877 if (com_token[0] == '{')
1879 if (com_token[0] == '}')
1881 // skip leading whitespace
1882 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
1883 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
1884 keyname[l] = com_token[k+l];
1886 if (!COM_ParseToken_Simple(&data, false, false))
1888 if (developer.integer >= 100)
1889 Con_Printf("key: %s %s\n", keyname, com_token);
1890 if (!strcmp(keyname, "message"))
1892 // get the message contents
1893 strlcpy(message, com_token, sizeof(message));
1903 *(t->filenames[i]+len[i]+5) = 0;
1906 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1907 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1908 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1909 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1910 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1912 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1917 k = *(t->filenames[0]+5+p);
1920 for(i=1;i<t->numfilenames;i++)
1921 if(*(t->filenames[i]+5+p) != k)
1925 if(p > o && completedname && completednamebufferlength > 0)
1927 memset(completedname, 0, completednamebufferlength);
1928 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1938 New function for tab-completion system
1939 Added by EvilTypeGuy
1940 MEGA Thanks to Taniwha
1943 void Con_DisplayList(const char **list)
1945 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1946 const char **walk = list;
1949 len = (int)strlen(*walk);
1957 len = (int)strlen(*list);
1958 if (pos + maxlen >= width) {
1964 for (i = 0; i < (maxlen - len); i++)
1976 SanitizeString strips color tags from the string in
1977 and writes the result on string out
1979 void SanitizeString(char *in, char *out)
1983 if(*in == STRING_COLOR_TAG)
1988 out[0] = STRING_COLOR_TAG;
1992 else if (*in >= '0' && *in <= '9') // ^[0-9] found
1999 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2002 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2004 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2011 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2016 else if (*in != STRING_COLOR_TAG)
2019 *out = qfont_table[*(unsigned char*)in];
2026 // Now it becomes TRICKY :D --blub
2027 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2028 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2029 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2030 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
2031 static int Nicks_matchpos;
2033 // co against <<:BLASTER:>> is true!?
2034 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2038 if(tolower(*a) == tolower(*b))
2052 return (*a < *b) ? -1 : 1;
2056 return (*a < *b) ? -1 : 1;
2060 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2063 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2065 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2066 return Nicks_strncasecmp_nospaces(a, b, a_len);
2067 return strncasecmp(a, b, a_len);
2070 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2072 // ignore non alphanumerics of B
2073 // if A contains a non-alphanumeric, B must contain it as well though!
2076 qboolean alnum_a, alnum_b;
2078 if(tolower(*a) == tolower(*b))
2080 if(*a == 0) // end of both strings, they're equal
2087 // not equal, end of one string?
2092 // ignore non alphanumerics
2093 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2094 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2095 if(!alnum_a) // b must contain this
2096 return (*a < *b) ? -1 : 1;
2099 // otherwise, both are alnum, they're just not equal, return the appropriate number
2101 return (*a < *b) ? -1 : 1;
2107 /* Nicks_CompleteCountPossible
2109 Count the number of possible nicks to complete
2111 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2120 if(!con_nickcompletion.integer)
2123 // changed that to 1
2124 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2127 for(i = 0; i < cl.maxclients; ++i)
2130 if(!cl.scores[p].name[0])
2133 SanitizeString(cl.scores[p].name, name);
2134 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2139 length = strlen(name);
2141 spos = pos - 1; // no need for a minimum of characters :)
2145 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2147 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2148 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2154 if(isCon && spos == 0)
2156 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2162 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2163 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2165 // the sanitized list
2166 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2169 Nicks_matchpos = match;
2172 Nicks_offset[count] = s - (&line[match]);
2173 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2180 void Cmd_CompleteNicksPrint(int count)
2183 for(i = 0; i < count; ++i)
2184 Con_Printf("%s\n", Nicks_list[i]);
2187 void Nicks_CutMatchesNormal(int count)
2189 // cut match 0 down to the longest possible completion
2192 c = strlen(Nicks_sanlist[0]) - 1;
2193 for(i = 1; i < count; ++i)
2195 l = strlen(Nicks_sanlist[i]) - 1;
2199 for(l = 0; l <= c; ++l)
2200 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2206 Nicks_sanlist[0][c+1] = 0;
2207 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2210 unsigned int Nicks_strcleanlen(const char *s)
2215 if( (*s >= 'a' && *s <= 'z') ||
2216 (*s >= 'A' && *s <= 'Z') ||
2217 (*s >= '0' && *s <= '9') ||
2225 void Nicks_CutMatchesAlphaNumeric(int count)
2227 // cut match 0 down to the longest possible completion
2230 char tempstr[sizeof(Nicks_sanlist[0])];
2232 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2234 c = strlen(Nicks_sanlist[0]);
2235 for(i = 0, l = 0; i < (int)c; ++i)
2237 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2238 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2239 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2241 tempstr[l++] = Nicks_sanlist[0][i];
2246 for(i = 1; i < count; ++i)
2249 b = Nicks_sanlist[i];
2259 if(tolower(*a) == tolower(*b))
2265 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2267 // b is alnum, so cut
2274 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2275 Nicks_CutMatchesNormal(count);
2276 //if(!Nicks_sanlist[0][0])
2277 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2279 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2280 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2284 void Nicks_CutMatchesNoSpaces(int count)
2286 // cut match 0 down to the longest possible completion
2289 char tempstr[sizeof(Nicks_sanlist[0])];
2292 c = strlen(Nicks_sanlist[0]);
2293 for(i = 0, l = 0; i < (int)c; ++i)
2295 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2297 tempstr[l++] = Nicks_sanlist[0][i];
2302 for(i = 1; i < count; ++i)
2305 b = Nicks_sanlist[i];
2315 if(tolower(*a) == tolower(*b))
2329 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2330 Nicks_CutMatchesNormal(count);
2331 //if(!Nicks_sanlist[0][0])
2332 //Con_Printf("TS: %s\n", tempstr);
2333 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2335 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2336 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2340 void Nicks_CutMatches(int count)
2342 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2343 Nicks_CutMatchesAlphaNumeric(count);
2344 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2345 Nicks_CutMatchesNoSpaces(count);
2347 Nicks_CutMatchesNormal(count);
2350 const char **Nicks_CompleteBuildList(int count)
2354 // the list is freed by Con_CompleteCommandLine, so create a char**
2355 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2357 for(; bpos < count; ++bpos)
2358 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2360 Nicks_CutMatches(count);
2368 Restores the previous used color, after the autocompleted name.
2370 int Nicks_AddLastColor(char *buffer, int pos)
2372 qboolean quote_added = false;
2374 int color = STRING_COLOR_DEFAULT + '0';
2375 char r = 0, g = 0, b = 0;
2377 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2379 // we'll have to add a quote :)
2380 buffer[pos++] = '\"';
2384 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2386 // add color when no quote was added, or when flags &4?
2388 for(match = Nicks_matchpos-1; match >= 0; --match)
2390 if(buffer[match] == STRING_COLOR_TAG)
2392 if( isdigit(buffer[match+1]) )
2394 color = buffer[match+1];
2397 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2399 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2401 r = buffer[match+2];
2402 g = buffer[match+3];
2403 b = buffer[match+4];
2412 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2414 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2415 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2418 buffer[pos++] = STRING_COLOR_TAG;
2421 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2427 buffer[pos++] = color;
2432 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2435 /*if(!con_nickcompletion.integer)
2436 return; is tested in Nicks_CompletionCountPossible */
2437 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2443 msg = Nicks_list[0];
2444 len = min(size - Nicks_matchpos - 3, strlen(msg));
2445 memcpy(&buffer[Nicks_matchpos], msg, len);
2446 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2447 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2448 buffer[len++] = ' ';
2455 Con_Printf("\n%i possible nicks:\n", n);
2456 Cmd_CompleteNicksPrint(n);
2458 Nicks_CutMatches(n);
2460 msg = Nicks_sanlist[0];
2461 len = min(size - Nicks_matchpos, strlen(msg));
2462 memcpy(&buffer[Nicks_matchpos], msg, len);
2463 buffer[Nicks_matchpos + len] = 0;
2465 return Nicks_matchpos + len;
2472 Con_CompleteCommandLine
2474 New function for tab-completion system
2475 Added by EvilTypeGuy
2476 Thanks to Fett erich@heintz.com
2478 Enhanced to tab-complete map names by [515]
2481 void Con_CompleteCommandLine (void)
2483 const char *cmd = "";
2485 const char **list[4] = {0, 0, 0, 0};
2488 int c, v, a, i, cmd_len, pos, k;
2489 int n; // nicks --blub
2490 const char *space, *patterns;
2492 //find what we want to complete
2497 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2503 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2504 key_line[key_linepos] = 0; //hide them
2506 space = strchr(key_line + 1, ' ');
2507 if(space && pos == (space - key_line) + 1)
2509 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2511 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2512 if(patterns && !*patterns)
2513 patterns = NULL; // get rid of the empty string
2515 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2519 if (GetMapList(s, t, sizeof(t)))
2521 // first move the cursor
2522 key_linepos += (int)strlen(t) - (int)strlen(s);
2524 // and now do the actual work
2526 strlcat(key_line, t, MAX_INPUTLINE);
2527 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2529 // and fix the cursor
2530 if(key_linepos > (int) strlen(key_line))
2531 key_linepos = (int) strlen(key_line);
2540 stringlist_t resultbuf, dirbuf;
2543 // // store completion patterns (space separated) for command foo in con_completion_foo
2544 // set con_completion_foo "foodata/*.foodefault *.foo"
2547 // Note: patterns with slash are always treated as absolute
2548 // patterns; patterns without slash search in the innermost
2549 // directory the user specified. There is no way to "complete into"
2550 // a directory as of now, as directories seem to be unknown to the
2554 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2555 // set con_completion_playdemo "*.dem"
2556 // set con_completion_play "*.wav *.ogg"
2558 // TODO somehow add support for directories; these shall complete
2559 // to their name + an appended slash.
2561 stringlistinit(&resultbuf);
2562 stringlistinit(&dirbuf);
2563 while(COM_ParseToken_Simple(&patterns, false, false))
2566 if(strchr(com_token, '/'))
2568 search = FS_Search(com_token, true, true);
2572 const char *slash = strrchr(s, '/');
2575 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2576 strlcat(t, com_token, sizeof(t));
2577 search = FS_Search(t, true, true);
2580 search = FS_Search(com_token, true, true);
2584 for(i = 0; i < search->numfilenames; ++i)
2585 if(!strncmp(search->filenames[i], s, strlen(s)))
2586 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2587 stringlistappend(&resultbuf, search->filenames[i]);
2588 FS_FreeSearch(search);
2592 // In any case, add directory names
2595 const char *slash = strrchr(s, '/');
2598 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2599 strlcat(t, "*", sizeof(t));
2600 search = FS_Search(t, true, true);
2603 search = FS_Search("*", true, true);
2606 for(i = 0; i < search->numfilenames; ++i)
2607 if(!strncmp(search->filenames[i], s, strlen(s)))
2608 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2609 stringlistappend(&dirbuf, search->filenames[i]);
2610 FS_FreeSearch(search);
2614 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2617 unsigned int matchchars;
2618 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2620 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2623 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2625 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2629 stringlistsort(&resultbuf); // dirbuf is already sorted
2630 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2631 for(i = 0; i < dirbuf.numstrings; ++i)
2633 Con_Printf("%s/\n", dirbuf.strings[i]);
2635 for(i = 0; i < resultbuf.numstrings; ++i)
2637 Con_Printf("%s\n", resultbuf.strings[i]);
2639 matchchars = sizeof(t) - 1;
2640 if(resultbuf.numstrings > 0)
2642 p = resultbuf.strings[0];
2643 q = resultbuf.strings[resultbuf.numstrings - 1];
2644 for(; *p && *p == *q; ++p, ++q);
2645 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2647 if(dirbuf.numstrings > 0)
2649 p = dirbuf.strings[0];
2650 q = dirbuf.strings[dirbuf.numstrings - 1];
2651 for(; *p && *p == *q; ++p, ++q);
2652 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2654 // now p points to the first non-equal character, or to the end
2655 // of resultbuf.strings[0]. We want to append the characters
2656 // from resultbuf.strings[0] to (not including) p as these are
2657 // the unique prefix
2658 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2661 // first move the cursor
2662 key_linepos += (int)strlen(t) - (int)strlen(s);
2664 // and now do the actual work
2666 strlcat(key_line, t, MAX_INPUTLINE);
2667 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2669 // and fix the cursor
2670 if(key_linepos > (int) strlen(key_line))
2671 key_linepos = (int) strlen(key_line);
2673 stringlistfreecontents(&resultbuf);
2674 stringlistfreecontents(&dirbuf);
2676 return; // bail out, when we complete for a command that wants a file name
2681 // Count number of possible matches and print them
2682 c = Cmd_CompleteCountPossible(s);
2685 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2686 Cmd_CompleteCommandPrint(s);
2688 v = Cvar_CompleteCountPossible(s);
2691 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2692 Cvar_CompleteCvarPrint(s);
2694 a = Cmd_CompleteAliasCountPossible(s);
2697 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2698 Cmd_CompleteAliasPrint(s);
2700 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2703 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2704 Cmd_CompleteNicksPrint(n);
2707 if (!(c + v + a + n)) // No possible matches
2710 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2715 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2717 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2719 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2721 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2723 for (cmd_len = (int)strlen(s);;cmd_len++)
2726 for (i = 0; i < 3; i++)
2728 for (l = list[i];*l;l++)
2729 if ((*l)[cmd_len] != cmd[cmd_len])
2731 // all possible matches share this character, so we continue...
2734 // if all matches ended at the same position, stop
2735 // (this means there is only one match)
2741 // prevent a buffer overrun by limiting cmd_len according to remaining space
2742 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2746 memcpy(&key_line[key_linepos], cmd, cmd_len);
2747 key_linepos += cmd_len;
2748 // if there is only one match, add a space after it
2749 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2752 { // was a nick, might have an offset, and needs colors ;) --blub
2753 key_linepos = pos - Nicks_offset[0];
2754 cmd_len = strlen(Nicks_list[0]);
2755 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2757 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2758 key_linepos += cmd_len;
2759 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2760 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2762 key_line[key_linepos++] = ' ';
2766 // use strlcat to avoid a buffer overrun
2767 key_line[key_linepos] = 0;
2768 strlcat(key_line, s2, sizeof(key_line));
2770 // free the command, cvar, and alias lists
2771 for (i = 0; i < 4; i++)
2773 Mem_Free((void *)list[i]);