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.
22 #if !defined(WIN32) || defined(__MINGW32__)
30 float con_cursorspeed = 4;
32 #define CON_TEXTSIZE 131072
34 // total lines in console scrollback
36 // lines up from bottom to display
38 // where next message will be printed
40 // offset in current line for next print
42 char con_text[CON_TEXTSIZE];
44 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
45 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show (0-32)"};
46 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
49 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)"};
51 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)"};
53 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)"};
56 #define MAX_NOTIFYLINES 32
57 // cl.time time the line was generated for transparent notify lines
58 float con_times[MAX_NOTIFYLINES];
62 qboolean con_initialized;
64 // used for server replies to rcon command
65 qboolean rcon_redirect = false;
66 int rcon_redirect_bufferpos = 0;
67 char rcon_redirect_buffer[1400];
71 ==============================================================================
75 ==============================================================================
78 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
79 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"};
80 char log_dest_buffer[1500]; // UDP packet
81 size_t log_dest_buffer_pos;
82 char crt_log_file [MAX_OSPATH] = "";
83 qfile_t* logfile = NULL;
85 unsigned char* logqueue = NULL;
89 void Log_ConPrint (const char *msg);
96 static void Log_DestBuffer_Init()
98 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
99 log_dest_buffer_pos = 5;
107 void Log_DestBuffer_Flush()
109 lhnetaddress_t log_dest_addr;
110 lhnetsocket_t *log_dest_socket;
111 const char *s = log_dest_udp.string;
112 qboolean have_opened_temp_sockets = false;
113 if(s) if(log_dest_buffer_pos > 5)
115 log_dest_buffer[log_dest_buffer_pos++] = 0;
117 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
119 have_opened_temp_sockets = true;
120 NetConn_OpenServerPorts(true);
123 while(COM_ParseToken_Console(&s))
124 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
126 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
128 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
130 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
133 if(have_opened_temp_sockets)
134 NetConn_CloseServerPorts();
136 log_dest_buffer_pos = 0;
144 const char* Log_Timestamp (const char *desc)
146 static char timestamp [128];
148 const struct tm *crt_tm;
149 char timestring [64];
151 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
153 crt_tm = localtime (&crt_time);
154 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
157 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
159 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
172 if (logfile != NULL || log_file.string[0] == '\0')
175 logfile = FS_Open (log_file.string, "ab", false, false);
178 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
179 FS_Print (logfile, Log_Timestamp ("Log started"));
189 void Log_Close (void)
194 FS_Print (logfile, Log_Timestamp ("Log stopped"));
195 FS_Print (logfile, "\n");
199 crt_log_file[0] = '\0';
208 void Log_Start (void)
214 // Dump the contents of the log queue into the log file and free it
215 if (logqueue != NULL)
217 unsigned char *temp = logqueue;
222 FS_Write (logfile, temp, logq_ind);
223 if(*log_dest_udp.string)
225 for(pos = 0; pos < logq_ind; )
227 if(log_dest_buffer_pos == 0)
228 Log_DestBuffer_Init();
229 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
230 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
231 log_dest_buffer_pos += n;
232 Log_DestBuffer_Flush();
249 void Log_ConPrint (const char *msg)
251 static qboolean inprogress = false;
253 // don't allow feedback loops with memory error reports
258 // Until the host is completely initialized, we maintain a log queue
259 // to store the messages, since the log can't be started before
260 if (logqueue != NULL)
262 size_t remain = logq_size - logq_ind;
263 size_t len = strlen (msg);
265 // If we need to enlarge the log queue
268 size_t factor = ((logq_ind + len) / logq_size) + 1;
269 unsigned char* newqueue;
272 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
273 memcpy (newqueue, logqueue, logq_ind);
276 remain = logq_size - logq_ind;
278 memcpy (&logqueue[logq_ind], msg, len);
285 // Check if log_file has changed
286 if (strcmp (crt_log_file, log_file.string) != 0)
292 // If a log file is available
294 FS_Print (logfile, msg);
305 void Log_Printf (const char *logfilename, const char *fmt, ...)
309 file = FS_Open (logfilename, "ab", true, false);
314 va_start (argptr, fmt);
315 FS_VPrintf (file, fmt, argptr);
324 ==============================================================================
328 ==============================================================================
336 void Con_ToggleConsole_f (void)
338 // toggle the 'user wants console' bit
339 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
340 memset (con_times, 0, sizeof(con_times));
348 void Con_Clear_f (void)
351 memset (con_text, ' ', CON_TEXTSIZE);
360 void Con_ClearNotify (void)
364 for (i=0 ; i<MAX_NOTIFYLINES ; i++)
374 void Con_MessageMode_f (void)
376 key_dest = key_message;
386 void Con_MessageMode2_f (void)
388 key_dest = key_message;
397 If the line width has changed, reformat the buffer.
400 void Con_CheckResize (void)
402 int i, j, width, oldwidth, oldtotallines, numlines, numchars;
404 char tbuf[CON_TEXTSIZE];
406 f = bound(1, con_textsize.value, 128);
407 if(f != con_textsize.value)
408 Cvar_SetValueQuick(&con_textsize, f);
409 width = (int)floor(vid_conwidth.value / con_textsize.value);
410 width = bound(1, width, CON_TEXTSIZE/4);
412 if (width == con_linewidth)
415 oldwidth = con_linewidth;
416 con_linewidth = width;
417 oldtotallines = con_totallines;
418 con_totallines = CON_TEXTSIZE / con_linewidth;
419 numlines = oldtotallines;
421 if (con_totallines < numlines)
422 numlines = con_totallines;
426 if (con_linewidth < numchars)
427 numchars = con_linewidth;
429 memcpy (tbuf, con_text, CON_TEXTSIZE);
430 memset (con_text, ' ', CON_TEXTSIZE);
432 for (i=0 ; i<numlines ; i++)
434 for (j=0 ; j<numchars ; j++)
436 con_text[(con_totallines - 1 - i) * con_linewidth + j] =
437 tbuf[((con_current - i + oldtotallines) %
438 oldtotallines) * oldwidth + j];
445 con_current = con_totallines - 1;
448 //[515]: the simplest command ever
449 //LordHavoc: not so simple after I made it print usage...
450 static void Con_Maps_f (void)
454 Con_Printf("usage: maps [mapnameprefix]\n");
457 else if (Cmd_Argc() == 2)
458 GetMapList(Cmd_Argv(1), NULL, 0);
460 GetMapList("", NULL, 0);
463 void Con_ConDump_f (void)
466 qboolean allblankssofar;
469 char temp[MAX_INPUTLINE+2];
472 Con_Printf("usage: condump <filename>\n");
475 file = FS_Open(Cmd_Argv(1), "wb", false, false);
478 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
481 // iterate over the entire console history buffer line by line
482 allblankssofar = true;
483 for (i = 0;i < con_totallines;i++)
485 text = con_text + ((con_current + 1 + i) % con_totallines)*con_linewidth;
486 // count the used characters on this line
487 for (l = min(con_linewidth, (int)sizeof(temp));l > 0 && text[l-1] == ' ';l--);
488 // if not a blank line, begin output
490 allblankssofar = false;
491 // output the current line to the file
495 memcpy(temp, text, l);
498 FS_Print(file, temp);
511 memset (con_text, ' ', CON_TEXTSIZE);
513 con_totallines = CON_TEXTSIZE / con_linewidth;
515 // Allocate a log queue, this will be freed after configs are parsed
516 logq_size = MAX_INPUTLINE;
517 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
520 Cvar_RegisterVariable (&sys_colortranslation);
521 Cvar_RegisterVariable (&sys_specialcharactertranslation);
523 Cvar_RegisterVariable (&log_file);
524 Cvar_RegisterVariable (&log_dest_udp);
526 // support for the classic Quake option
527 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
528 if (COM_CheckParm ("-condebug") != 0)
529 Cvar_SetQuick (&log_file, "qconsole.log");
531 // register our cvars
532 Cvar_RegisterVariable (&con_notifytime);
533 Cvar_RegisterVariable (&con_notify);
534 Cvar_RegisterVariable (&con_textsize);
536 // register our commands
537 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
538 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
539 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
540 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
541 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
542 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
544 con_initialized = true;
545 Con_Print("Console initialized.\n");
554 void Con_Linefeed (void)
561 memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth);
568 Handles cursor positioning, line wrapping, etc
569 All console printing must go through this in order to be displayed
570 If no console is visible, the notify window will pop up.
573 void Con_PrintToHistory(const char *txt, int mask)
581 for (l=0 ; l< con_linewidth ; l++)
586 if (l != con_linewidth && (con_x + l > con_linewidth) )
601 // mark time for transparent overlay
602 if (con_current >= 0)
604 if (con_notify.integer < 0)
605 Cvar_SetValueQuick(&con_notify, 0);
606 if (con_notify.integer > MAX_NOTIFYLINES)
607 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
608 if (con_notify.integer > 0)
609 con_times[con_current % con_notify.integer] = cl.time;
624 default: // display character and advance
625 y = con_current % con_totallines;
626 con_text[y*con_linewidth+con_x] = c | mask;
628 if (con_x >= con_linewidth)
636 /* The translation table between the graphical font and plain ASCII --KB */
637 static char qfont_table[256] = {
638 '\0', '#', '#', '#', '#', '.', '#', '#',
639 '#', 9, 10, '#', ' ', 13, '.', '.',
640 '[', ']', '0', '1', '2', '3', '4', '5',
641 '6', '7', '8', '9', '.', '<', '=', '>',
642 ' ', '!', '"', '#', '$', '%', '&', '\'',
643 '(', ')', '*', '+', ',', '-', '.', '/',
644 '0', '1', '2', '3', '4', '5', '6', '7',
645 '8', '9', ':', ';', '<', '=', '>', '?',
646 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
647 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
648 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
649 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
650 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
651 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
652 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
653 'x', 'y', 'z', '{', '|', '}', '~', '<',
655 '<', '=', '>', '#', '#', '.', '#', '#',
656 '#', '#', ' ', '#', ' ', '>', '.', '.',
657 '[', ']', '0', '1', '2', '3', '4', '5',
658 '6', '7', '8', '9', '.', '<', '=', '>',
659 ' ', '!', '"', '#', '$', '%', '&', '\'',
660 '(', ')', '*', '+', ',', '-', '.', '/',
661 '0', '1', '2', '3', '4', '5', '6', '7',
662 '8', '9', ':', ';', '<', '=', '>', '?',
663 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
664 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
665 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
666 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
667 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
668 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
669 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
670 'x', 'y', 'z', '{', '|', '}', '~', '<'
677 Prints to all appropriate console targets, and adds timestamps
680 extern cvar_t timestamps;
681 extern cvar_t timeformat;
682 extern qboolean sys_nostdout;
683 void Con_Print(const char *msg)
686 static int index = 0;
687 static char line[MAX_INPUTLINE];
691 // if this print is in response to an rcon command, add the character
692 // to the rcon redirect buffer
693 if (rcon_redirect && rcon_redirect_bufferpos < (int)sizeof(rcon_redirect_buffer) - 1)
694 rcon_redirect_buffer[rcon_redirect_bufferpos++] = *msg;
695 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
697 if(log_dest_buffer_pos == 0)
698 Log_DestBuffer_Init();
699 log_dest_buffer[log_dest_buffer_pos++] = *msg;
700 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
701 Log_DestBuffer_Flush();
704 log_dest_buffer_pos = 0;
706 // if this is the beginning of a new line, print timestamp
709 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
711 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
712 line[index++] = STRING_COLOR_TAG;
713 // assert( STRING_COLOR_DEFAULT < 10 )
714 line[index++] = STRING_COLOR_DEFAULT + '0';
715 // special color codes for chat messages must always come first
716 // for Con_PrintToHistory to work properly
717 if (*msg == 1 || *msg == 2)
722 if (msg[1] == '(' && cl.foundtalk2wav)
723 S_LocalSound ("sound/misc/talk2.wav");
725 S_LocalSound ("sound/misc/talk.wav");
727 line[index++] = STRING_COLOR_TAG;
732 for (;*timestamp;index++, timestamp++)
733 if (index < (int)sizeof(line) - 2)
734 line[index] = *timestamp;
736 // append the character
737 line[index++] = *msg;
738 // if this is a newline character, we have a complete line to print
739 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
741 // terminate the line
745 // send to scrollable buffer
746 if (con_initialized && cls.state != ca_dedicated)
747 Con_PrintToHistory(line, mask);
748 // send to terminal or dedicated server window
752 if(sys_specialcharactertranslation.integer)
754 for (p = (unsigned char *) line;*p; p++)
755 *p = qfont_table[*p];
758 if(sys_colortranslation.integer == 1) // ANSI
760 static char printline[MAX_INPUTLINE * 4 + 3];
761 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
762 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
766 for(in = line, out = printline; *in; ++in)
770 case STRING_COLOR_TAG:
773 case STRING_COLOR_TAG:
775 *out++ = STRING_COLOR_TAG;
781 if(lastcolor == 0) break; else lastcolor = 0;
782 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
787 if(lastcolor == 1) break; else lastcolor = 1;
788 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
793 if(lastcolor == 2) break; else lastcolor = 2;
794 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
799 if(lastcolor == 3) break; else lastcolor = 3;
800 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
805 if(lastcolor == 4) break; else lastcolor = 4;
806 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
811 if(lastcolor == 5) break; else lastcolor = 5;
812 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
817 if(lastcolor == 6) break; else lastcolor = 6;
818 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
825 if(lastcolor == 8) break; else lastcolor = 8;
826 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
829 *out++ = STRING_COLOR_TAG;
836 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
853 Sys_PrintToTerminal(printline);
855 else if(sys_colortranslation.integer == 2) // Quake
857 Sys_PrintToTerminal(line);
861 static char printline[MAX_INPUTLINE]; // it can only get shorter here
864 for(in = line, out = printline; *in; ++in)
868 case STRING_COLOR_TAG:
871 case STRING_COLOR_TAG:
873 *out++ = STRING_COLOR_TAG;
888 *out++ = STRING_COLOR_TAG;
898 Sys_PrintToTerminal(printline);
901 // empty the line buffer
912 Prints to all appropriate console targets
915 void Con_Printf(const char *fmt, ...)
918 char msg[MAX_INPUTLINE];
920 va_start(argptr,fmt);
921 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
931 A Con_Print that only shows up if the "developer" cvar is set
934 void Con_DPrint(const char *msg)
936 if (!developer.integer)
937 return; // don't confuse non-developers with techie stuff...
945 A Con_Printf that only shows up if the "developer" cvar is set
948 void Con_DPrintf(const char *fmt, ...)
951 char msg[MAX_INPUTLINE];
953 if (!developer.integer)
954 return; // don't confuse non-developers with techie stuff...
956 va_start(argptr,fmt);
957 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
965 ==============================================================================
969 ==============================================================================
976 The input line scrolls horizontally if typing goes beyond the right edge
978 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
981 void Con_DrawInput (void)
985 char editlinecopy[MAX_INPUTLINE+1], *text;
987 if (!key_consoleactive)
988 return; // don't draw anything
990 strlcpy(editlinecopy, key_lines[edit_line], sizeof(editlinecopy));
993 // Advanced Console Editing by Radix radix@planetquake.com
994 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
995 // use strlen of edit_line instead of key_linepos to allow editing
996 // of early characters w/o erasing
998 y = (int)strlen(text);
1000 // fill out remainder with spaces
1001 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1004 // add the cursor frame
1005 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1006 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1008 // text[key_linepos + 1] = 0;
1010 // prestep if horizontally scrolling
1011 if (key_linepos >= con_linewidth)
1012 text += 1 + key_linepos - con_linewidth;
1015 DrawQ_String(0, con_vislines - con_textsize.value*2, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false );
1018 // key_lines[edit_line][key_linepos] = 0;
1026 Draws the last few lines of output transparently over the game top
1029 void Con_DrawNotify (void)
1035 char temptext[MAX_INPUTLINE];
1036 int colorindex = -1; //-1 for default
1038 if (con_notify.integer < 0)
1039 Cvar_SetValueQuick(&con_notify, 0);
1040 if (con_notify.integer > MAX_NOTIFYLINES)
1041 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
1042 if (gamemode == GAME_TRANSFUSION)
1046 // make a copy of con_current here so that we can't get in a runaway loop printing new messages while drawing the notify text
1048 for (i= stop-con_notify.integer+1 ; i<=stop ; i++)
1053 time = con_times[i % con_notify.integer];
1056 time = cl.time - time;
1057 if (time > con_notifytime.value)
1059 text = con_text + (i % con_totallines)*con_linewidth;
1061 if (gamemode == GAME_NEXUIZ) {
1066 // count up to the last non-whitespace, and ignore color codes
1067 for (j = 0;j < con_linewidth && text[j];j++)
1069 if (text[j] == STRING_COLOR_TAG && (text[j+1] >= '0' && text[j+1] <= '9'))
1079 // center the line using the calculated width
1080 x = (vid_conwidth.integer - finalchars * con_textsize.value) * 0.5;
1084 DrawQ_String( x, v, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1086 v += con_textsize.value;
1090 if (key_dest == key_message)
1092 int colorindex = -1;
1096 // LordHavoc: speedup, and other improvements
1098 sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1100 sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1101 while ((int)strlen(temptext) >= con_linewidth)
1103 DrawQ_String( 0, v, temptext, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1104 strlcpy(temptext, &temptext[con_linewidth], sizeof(temptext));
1105 v += con_textsize.value;
1107 if (strlen(temptext) > 0)
1109 DrawQ_String( 0, v, temptext, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1110 v += con_textsize.value;
1119 Draws the console with the solid background
1120 The typing input line at the bottom should only be drawn if typing is allowed
1123 void Con_DrawConsole (int lines)
1125 int i, rows, j, stop;
1128 int colorindex = -1;
1133 // draw the background
1134 DrawQ_Pic(0, lines - vid_conheight.integer, scr_conbrightness.value >= 0.01f ? Draw_CachePic("gfx/conback", true) : NULL, vid_conwidth.integer, vid_conheight.integer, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, scr_conalpha.value, 0);
1135 DrawQ_String(vid_conwidth.integer - strlen(engineversion) * con_textsize.value - con_textsize.value, lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0, NULL, true);
1138 con_vislines = lines;
1140 rows = (int)ceil((lines/con_textsize.value)-2); // rows of text to draw
1141 y = lines - (rows+2)*con_textsize.value; // may start slightly negative
1143 // make a copy of con_current here so that we can't get in a runaway loop printing new messages while drawing the notify text
1145 for (i = stop - rows + 1;i <= stop;i++, y += con_textsize.value)
1147 j = max(i - con_backscroll, 0);
1148 text = con_text + (j % con_totallines)*con_linewidth;
1150 DrawQ_String( 0, y, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false );
1153 // draw the input prompt, user text, and cursor if desired
1161 Prints not only map filename, but also
1162 its format (q1/q2/q3/hl) and even its message
1164 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1165 //LordHavoc: rewrote bsp type detection, added mcbsp support and rewrote message extraction to do proper worldspawn parsing
1166 //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
1167 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1168 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1172 int i, k, max, p, o, min;
1175 unsigned char buf[1024];
1177 sprintf(message, "maps/%s*.bsp", s);
1178 t = FS_Search(message, 1, true);
1181 if (t->numfilenames > 1)
1182 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1183 len = (unsigned char *)Z_Malloc(t->numfilenames);
1185 for(max=i=0;i<t->numfilenames;i++)
1187 k = (int)strlen(t->filenames[i]);
1197 for(i=0;i<t->numfilenames;i++)
1199 int lumpofs = 0, lumplen = 0;
1200 char *entities = NULL;
1201 const char *data = NULL;
1203 char entfilename[MAX_QPATH];
1204 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1206 f = FS_Open(t->filenames[i], "rb", true, false);
1209 memset(buf, 0, 1024);
1210 FS_Read(f, buf, 1024);
1211 if (!memcmp(buf, "IBSP", 4))
1213 p = LittleLong(((int *)buf)[1]);
1214 if (p == Q3BSPVERSION)
1216 q3dheader_t *header = (q3dheader_t *)buf;
1217 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1218 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1220 else if (p == Q2BSPVERSION)
1222 q2dheader_t *header = (q2dheader_t *)buf;
1223 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1224 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1227 else if (!memcmp(buf, "MCBSPpad", 8))
1229 p = LittleLong(((int *)buf)[2]);
1230 if (p == MCBSPVERSION)
1232 int numhulls = LittleLong(((int *)buf)[3]);
1233 lumpofs = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+0]);
1234 lumplen = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+1]);
1237 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1239 dheader_t *header = (dheader_t *)buf;
1240 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1241 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1245 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1246 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1247 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1248 if (!entities && lumplen >= 10)
1250 FS_Seek(f, lumpofs, SEEK_SET);
1251 entities = (char *)Z_Malloc(lumplen + 1);
1252 FS_Read(f, entities, lumplen);
1256 // if there are entities to parse, a missing message key just
1257 // means there is no title, so clear the message string now
1263 if (!COM_ParseToken_Simple(&data, false))
1265 if (com_token[0] == '{')
1267 if (com_token[0] == '}')
1269 // skip leading whitespace
1270 for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
1271 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
1272 keyname[l] = com_token[k+l];
1274 if (!COM_ParseToken_Simple(&data, false))
1276 if (developer.integer >= 100)
1277 Con_Printf("key: %s %s\n", keyname, com_token);
1278 if (!strcmp(keyname, "message"))
1280 // get the message contents
1281 strlcpy(message, com_token, sizeof(message));
1291 *(t->filenames[i]+len[i]+5) = 0;
1294 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1295 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1296 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1297 case MCBSPVERSION: strlcpy((char *)buf, "MC", sizeof(buf));break;
1298 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1299 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1301 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1306 k = *(t->filenames[0]+5+p);
1309 for(i=1;i<t->numfilenames;i++)
1310 if(*(t->filenames[i]+5+p) != k)
1314 if(p > o && completedname && completednamebufferlength > 0)
1316 memset(completedname, 0, completednamebufferlength);
1317 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1327 New function for tab-completion system
1328 Added by EvilTypeGuy
1329 MEGA Thanks to Taniwha
1332 void Con_DisplayList(const char **list)
1334 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1335 const char **walk = list;
1338 len = (int)strlen(*walk);
1346 len = (int)strlen(*list);
1347 if (pos + maxlen >= width) {
1353 for (i = 0; i < (maxlen - len); i++)
1365 Con_CompleteCommandLine
1367 New function for tab-completion system
1368 Added by EvilTypeGuy
1369 Thanks to Fett erich@heintz.com
1371 Enhanced to tab-complete map names by [515]
1374 void Con_CompleteCommandLine (void)
1376 const char *cmd = "";
1378 const char **list[3] = {0, 0, 0};
1380 int c, v, a, i, cmd_len, pos, k;
1382 //find what we want to complete
1386 k = key_lines[edit_line][pos];
1387 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
1392 s = key_lines[edit_line] + pos;
1393 strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2)); //save chars after cursor
1394 key_lines[edit_line][key_linepos] = 0; //hide them
1397 for(k=pos-1;k>2;k--)
1398 if(key_lines[edit_line][k] != ' ')
1400 if(key_lines[edit_line][k] == '\"' || key_lines[edit_line][k] == ';' || key_lines[edit_line][k] == '\'')
1402 if ((pos+k > 2 && !strncmp(key_lines[edit_line]+k-2, "map", 3))
1403 || (pos+k > 10 && !strncmp(key_lines[edit_line]+k-10, "changelevel", 11)))
1406 if (GetMapList(s, t, sizeof(t)))
1408 // first move the cursor
1409 key_linepos += (int)strlen(t) - (int)strlen(s);
1411 // and now do the actual work
1413 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
1414 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
1416 // and fix the cursor
1417 if(key_linepos > (int) strlen(key_lines[edit_line]))
1418 key_linepos = (int) strlen(key_lines[edit_line]);
1424 // Count number of possible matches and print them
1425 c = Cmd_CompleteCountPossible(s);
1428 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
1429 Cmd_CompleteCommandPrint(s);
1431 v = Cvar_CompleteCountPossible(s);
1434 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
1435 Cvar_CompleteCvarPrint(s);
1437 a = Cmd_CompleteAliasCountPossible(s);
1440 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
1441 Cmd_CompleteAliasPrint(s);
1444 if (!(c + v + a)) // No possible matches
1447 strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
1452 cmd = *(list[0] = Cmd_CompleteBuildList(s));
1454 cmd = *(list[1] = Cvar_CompleteBuildList(s));
1456 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
1458 for (cmd_len = (int)strlen(s);;cmd_len++)
1461 for (i = 0; i < 3; i++)
1463 for (l = list[i];*l;l++)
1464 if ((*l)[cmd_len] != cmd[cmd_len])
1466 // all possible matches share this character, so we continue...
1469 // if all matches ended at the same position, stop
1470 // (this means there is only one match)
1476 // prevent a buffer overrun by limiting cmd_len according to remaining space
1477 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
1481 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
1482 key_linepos += cmd_len;
1483 // if there is only one match, add a space after it
1484 if (c + v + a == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
1485 key_lines[edit_line][key_linepos++] = ' ';
1488 // use strlcat to avoid a buffer overrun
1489 key_lines[edit_line][key_linepos] = 0;
1490 strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
1492 // free the command, cvar, and alias lists
1493 for (i = 0; i < 3; i++)
1495 Mem_Free((void *)list[i]);