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"}; //[515]: console text size in 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",
53 cvar_t sys_colortranslation = {0, "sys_colortranslation", "1",
55 "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
57 #define MAX_NOTIFYLINES 32
58 // cl.time time the line was generated for transparent notify lines
59 float con_times[MAX_NOTIFYLINES];
63 qboolean con_initialized;
65 // used for server replies to rcon command
66 qboolean rcon_redirect = false;
67 int rcon_redirect_bufferpos = 0;
68 char rcon_redirect_buffer[1400];
72 ==============================================================================
76 ==============================================================================
79 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
80 char crt_log_file [MAX_OSPATH] = "";
81 qfile_t* logfile = NULL;
83 unsigned char* logqueue = NULL;
87 void Log_ConPrint (const char *msg);
94 const char* Log_Timestamp (const char *desc)
96 static char timestamp [128];
98 const struct tm *crt_tm;
101 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
103 crt_tm = localtime (&crt_time);
104 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
107 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
109 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
122 if (logfile != NULL || log_file.string[0] == '\0')
125 logfile = FS_Open (log_file.string, "ab", false, false);
128 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
129 FS_Print (logfile, Log_Timestamp ("Log started"));
139 void Log_Close (void)
144 FS_Print (logfile, Log_Timestamp ("Log stopped"));
145 FS_Print (logfile, "\n");
149 crt_log_file[0] = '\0';
158 void Log_Start (void)
162 // Dump the contents of the log queue into the log file and free it
163 if (logqueue != NULL)
165 unsigned char *temp = logqueue;
167 if (logfile != NULL && logq_ind != 0)
168 FS_Write (logfile, temp, logq_ind);
181 void Log_ConPrint (const char *msg)
183 static qboolean inprogress = false;
185 // don't allow feedback loops with memory error reports
190 // Until the host is completely initialized, we maintain a log queue
191 // to store the messages, since the log can't be started before
192 if (logqueue != NULL)
194 size_t remain = logq_size - logq_ind;
195 size_t len = strlen (msg);
197 // If we need to enlarge the log queue
200 size_t factor = ((logq_ind + len) / logq_size) + 1;
201 unsigned char* newqueue;
204 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
205 memcpy (newqueue, logqueue, logq_ind);
208 remain = logq_size - logq_ind;
210 memcpy (&logqueue[logq_ind], msg, len);
217 // Check if log_file has changed
218 if (strcmp (crt_log_file, log_file.string) != 0)
224 // If a log file is available
226 FS_Print (logfile, msg);
236 void Log_Printf (const char *logfilename, const char *fmt, ...)
240 file = FS_Open (logfilename, "ab", true, false);
245 va_start (argptr, fmt);
246 FS_VPrintf (file, fmt, argptr);
255 ==============================================================================
259 ==============================================================================
267 void Con_ToggleConsole_f (void)
269 // toggle the 'user wants console' bit
270 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
271 memset (con_times, 0, sizeof(con_times));
279 void Con_Clear_f (void)
282 memset (con_text, ' ', CON_TEXTSIZE);
291 void Con_ClearNotify (void)
295 for (i=0 ; i<MAX_NOTIFYLINES ; i++)
305 void Con_MessageMode_f (void)
307 key_dest = key_message;
317 void Con_MessageMode2_f (void)
319 key_dest = key_message;
328 If the line width has changed, reformat the buffer.
331 void Con_CheckResize (void)
333 int i, j, width, oldwidth, oldtotallines, numlines, numchars;
335 char tbuf[CON_TEXTSIZE];
337 f = bound(1, con_textsize.value, 128);
338 if(f != con_textsize.value)
339 Cvar_SetValueQuick(&con_textsize, f);
340 width = (int)floor(vid_conwidth.value / con_textsize.value);
341 width = bound(1, width, CON_TEXTSIZE/4);
343 if (width == con_linewidth)
346 oldwidth = con_linewidth;
347 con_linewidth = width;
348 oldtotallines = con_totallines;
349 con_totallines = CON_TEXTSIZE / con_linewidth;
350 numlines = oldtotallines;
352 if (con_totallines < numlines)
353 numlines = con_totallines;
357 if (con_linewidth < numchars)
358 numchars = con_linewidth;
360 memcpy (tbuf, con_text, CON_TEXTSIZE);
361 memset (con_text, ' ', CON_TEXTSIZE);
363 for (i=0 ; i<numlines ; i++)
365 for (j=0 ; j<numchars ; j++)
367 con_text[(con_totallines - 1 - i) * con_linewidth + j] =
368 tbuf[((con_current - i + oldtotallines) %
369 oldtotallines) * oldwidth + j];
376 con_current = con_totallines - 1;
379 //[515]: the simplest command ever
380 //LordHavoc: not so simple after I made it print usage...
381 static void Con_Maps_f (void)
385 Con_Printf("usage: maps [mapnameprefix]\n");
388 else if (Cmd_Argc() == 2)
389 GetMapList(Cmd_Argv(1), NULL, 0);
391 GetMapList("", NULL, 0);
394 void Con_ConDump_f (void)
397 qboolean allblankssofar;
400 char temp[MAX_INPUTLINE+2];
403 Con_Printf("usage: condump <filename>\n");
406 file = FS_Open(Cmd_Argv(1), "wb", false, false);
409 Con_Printf("condump: unable to write file \"%s\"\n", file);
412 // iterate over the entire console history buffer line by line
413 allblankssofar = true;
414 for (i = 0;i < con_totallines;i++)
416 text = con_text + ((con_current + 1 + i) % con_totallines)*con_linewidth;
417 // count the used characters on this line
418 for (l = min(con_linewidth, (int)sizeof(temp));l > 0 && text[l-1] == ' ';l--);
419 // if not a blank line, begin output
421 allblankssofar = false;
422 // output the current line to the file
426 memcpy(temp, text, l);
429 FS_Print(file, temp);
442 memset (con_text, ' ', CON_TEXTSIZE);
444 con_totallines = CON_TEXTSIZE / con_linewidth;
446 // Allocate a log queue, this will be freed after configs are parsed
447 logq_size = MAX_INPUTLINE;
448 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
451 Cvar_RegisterVariable (&sys_colortranslation);
452 Cvar_RegisterVariable (&sys_specialcharactertranslation);
454 Cvar_RegisterVariable (&log_file);
456 // support for the classic Quake option
457 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
458 if (COM_CheckParm ("-condebug") != 0)
459 Cvar_SetQuick (&log_file, "qconsole.log");
461 // register our cvars
462 Cvar_RegisterVariable (&con_notifytime);
463 Cvar_RegisterVariable (&con_notify);
464 Cvar_RegisterVariable (&con_textsize);
466 // register our commands
467 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
468 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
469 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
470 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
471 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps"); // By [515]
472 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
474 con_initialized = true;
475 Con_Print("Console initialized.\n");
484 void Con_Linefeed (void)
491 memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth);
498 Handles cursor positioning, line wrapping, etc
499 All console printing must go through this in order to be displayed
500 If no console is visible, the notify window will pop up.
503 void Con_PrintToHistory(const char *txt, int mask)
511 for (l=0 ; l< con_linewidth ; l++)
516 if (l != con_linewidth && (con_x + l > con_linewidth) )
531 // mark time for transparent overlay
532 if (con_current >= 0)
534 if (con_notify.integer < 0)
535 Cvar_SetValueQuick(&con_notify, 0);
536 if (con_notify.integer > MAX_NOTIFYLINES)
537 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
538 if (con_notify.integer > 0)
539 con_times[con_current % con_notify.integer] = cl.time;
554 default: // display character and advance
555 y = con_current % con_totallines;
556 con_text[y*con_linewidth+con_x] = c | mask;
558 if (con_x >= con_linewidth)
566 /* The translation table between the graphical font and plain ASCII --KB */
567 static char qfont_table[256] = {
568 '\0', '#', '#', '#', '#', '.', '#', '#',
569 '#', 9, 10, '#', ' ', 13, '.', '.',
570 '[', ']', '0', '1', '2', '3', '4', '5',
571 '6', '7', '8', '9', '.', '<', '=', '>',
572 ' ', '!', '"', '#', '$', '%', '&', '\'',
573 '(', ')', '*', '+', ',', '-', '.', '/',
574 '0', '1', '2', '3', '4', '5', '6', '7',
575 '8', '9', ':', ';', '<', '=', '>', '?',
576 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
577 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
578 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
579 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
580 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
581 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
582 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
583 'x', 'y', 'z', '{', '|', '}', '~', '<',
585 '<', '=', '>', '#', '#', '.', '#', '#',
586 '#', '#', ' ', '#', ' ', '>', '.', '.',
587 '[', ']', '0', '1', '2', '3', '4', '5',
588 '6', '7', '8', '9', '.', '<', '=', '>',
589 ' ', '!', '"', '#', '$', '%', '&', '\'',
590 '(', ')', '*', '+', ',', '-', '.', '/',
591 '0', '1', '2', '3', '4', '5', '6', '7',
592 '8', '9', ':', ';', '<', '=', '>', '?',
593 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
594 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
595 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
596 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
597 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
598 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
599 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
600 'x', 'y', 'z', '{', '|', '}', '~', '<'
607 Prints to all appropriate console targets, and adds timestamps
610 extern cvar_t timestamps;
611 extern cvar_t timeformat;
612 extern qboolean sys_nostdout;
613 void Con_Print(const char *msg)
616 static int index = 0;
617 static char line[MAX_INPUTLINE];
621 // if this print is in response to an rcon command, add the character
622 // to the rcon redirect buffer
623 if (rcon_redirect && rcon_redirect_bufferpos < (int)sizeof(rcon_redirect_buffer) - 1)
624 rcon_redirect_buffer[rcon_redirect_bufferpos++] = *msg;
625 // if this is the beginning of a new line, print timestamp
628 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
630 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
631 line[index++] = STRING_COLOR_TAG;
632 // assert( STRING_COLOR_DEFAULT < 10 )
633 line[index++] = STRING_COLOR_DEFAULT + '0';
634 // special color codes for chat messages must always come first
635 // for Con_PrintToHistory to work properly
636 if (*msg == 1 || *msg == 2)
640 S_LocalSound ("sound/misc/talk.wav");
641 line[index++] = STRING_COLOR_TAG;
646 for (;*timestamp;index++, timestamp++)
647 if (index < (int)sizeof(line) - 2)
648 line[index] = *timestamp;
650 // append the character
651 line[index++] = *msg;
652 // if this is a newline character, we have a complete line to print
653 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
655 // terminate the line
659 // send to scrollable buffer
660 if (con_initialized && cls.state != ca_dedicated)
661 Con_PrintToHistory(line, mask);
662 // send to terminal or dedicated server window
666 if(sys_specialcharactertranslation.integer)
668 for (p = (unsigned char *) line;*p; p++)
669 *p = qfont_table[*p];
672 if(sys_colortranslation.integer == 1) // ANSI
674 static char printline[MAX_INPUTLINE * 4 + 3];
675 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
676 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
680 for(in = line, out = printline; *in; ++in)
684 case STRING_COLOR_TAG:
687 case STRING_COLOR_TAG:
689 *out++ = STRING_COLOR_TAG;
695 if(lastcolor == 0) break; else lastcolor = 0;
696 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
701 if(lastcolor == 1) break; else lastcolor = 1;
702 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
707 if(lastcolor == 2) break; else lastcolor = 2;
708 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
713 if(lastcolor == 3) break; else lastcolor = 3;
714 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
719 if(lastcolor == 4) break; else lastcolor = 4;
720 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
725 if(lastcolor == 5) break; else lastcolor = 5;
726 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
731 if(lastcolor == 6) break; else lastcolor = 6;
732 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
739 if(lastcolor == 8) break; else lastcolor = 8;
740 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
743 *out++ = STRING_COLOR_TAG;
750 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
767 Sys_PrintToTerminal(printline);
769 else if(sys_colortranslation.integer == 2) // Quake
771 Sys_PrintToTerminal(line);
775 static char printline[MAX_INPUTLINE]; // it can only get shorter here
778 for(in = line, out = printline; *in; ++in)
782 case STRING_COLOR_TAG:
785 case STRING_COLOR_TAG:
787 *out++ = STRING_COLOR_TAG;
802 *out++ = STRING_COLOR_TAG;
812 Sys_PrintToTerminal(printline);
815 // empty the line buffer
826 Prints to all appropriate console targets
829 void Con_Printf(const char *fmt, ...)
832 char msg[MAX_INPUTLINE];
834 va_start(argptr,fmt);
835 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
845 A Con_Print that only shows up if the "developer" cvar is set
848 void Con_DPrint(const char *msg)
850 if (!developer.integer)
851 return; // don't confuse non-developers with techie stuff...
859 A Con_Printf that only shows up if the "developer" cvar is set
862 void Con_DPrintf(const char *fmt, ...)
865 char msg[MAX_INPUTLINE];
867 if (!developer.integer)
868 return; // don't confuse non-developers with techie stuff...
870 va_start(argptr,fmt);
871 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
879 ==============================================================================
883 ==============================================================================
890 The input line scrolls horizontally if typing goes beyond the right edge
892 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
895 void Con_DrawInput (void)
899 char editlinecopy[MAX_INPUTLINE+1], *text;
901 if (!key_consoleactive)
902 return; // don't draw anything
904 strlcpy(editlinecopy, key_lines[edit_line], sizeof(editlinecopy));
907 // Advanced Console Editing by Radix radix@planetquake.com
908 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
909 // use strlen of edit_line instead of key_linepos to allow editing
910 // of early characters w/o erasing
912 y = (int)strlen(text);
914 // fill out remainder with spaces
915 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
918 // add the cursor frame
919 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
920 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
922 // text[key_linepos + 1] = 0;
924 // prestep if horizontally scrolling
925 if (key_linepos >= con_linewidth)
926 text += 1 + key_linepos - con_linewidth;
929 DrawQ_ColoredString(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 );
932 // key_lines[edit_line][key_linepos] = 0;
940 Draws the last few lines of output transparently over the game top
943 void Con_DrawNotify (void)
949 char temptext[MAX_INPUTLINE];
950 int colorindex = -1; //-1 for default
952 if (con_notify.integer < 0)
953 Cvar_SetValueQuick(&con_notify, 0);
954 if (con_notify.integer > MAX_NOTIFYLINES)
955 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
956 if (gamemode == GAME_TRANSFUSION)
960 // 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
962 for (i= stop-con_notify.integer+1 ; i<=stop ; i++)
967 time = con_times[i % con_notify.integer];
970 time = cl.time - time;
971 if (time > con_notifytime.value)
973 text = con_text + (i % con_totallines)*con_linewidth;
975 if (gamemode == GAME_NEXUIZ) {
980 // count up to the last non-whitespace, and ignore color codes
981 for (j = 0;j < con_linewidth && text[j];j++)
983 if (text[j] == STRING_COLOR_TAG && (text[j+1] >= '0' && text[j+1] <= '9'))
993 // center the line using the calculated width
994 x = (vid_conwidth.integer - finalchars * con_textsize.value) * 0.5;
998 DrawQ_ColoredString( x, v, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
1000 v += con_textsize.value;
1004 if (key_dest == key_message)
1006 int colorindex = -1;
1010 // LordHavoc: speedup, and other improvements
1012 sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1014 sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
1015 while ((int)strlen(temptext) >= con_linewidth)
1017 DrawQ_ColoredString( 0, v, temptext, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
1018 strlcpy(temptext, &temptext[con_linewidth], sizeof(temptext));
1019 v += con_textsize.value;
1021 if (strlen(temptext) > 0)
1023 DrawQ_ColoredString( 0, v, temptext, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
1024 v += con_textsize.value;
1033 Draws the console with the solid background
1034 The typing input line at the bottom should only be drawn if typing is allowed
1037 void Con_DrawConsole (int lines)
1039 int i, rows, j, stop;
1042 int colorindex = -1;
1047 // draw the background
1048 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);
1049 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);
1052 con_vislines = lines;
1054 rows = (int)ceil((lines/con_textsize.value)-2); // rows of text to draw
1055 y = lines - (rows+2)*con_textsize.value; // may start slightly negative
1057 // 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
1059 for (i = stop - rows + 1;i <= stop;i++, y += con_textsize.value)
1061 j = max(i - con_backscroll, 0);
1062 text = con_text + (j % con_totallines)*con_linewidth;
1064 DrawQ_ColoredString( 0, y, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
1067 // draw the input prompt, user text, and cursor if desired
1075 Prints not only map filename, but also
1076 its format (q1/q2/q3/hl) and even its message
1078 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1079 //LordHavoc: rewrote bsp type detection, added mcbsp support and rewrote message extraction to do proper worldspawn parsing
1080 //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
1081 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1082 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1086 int i, k, max, p, o, min;
1089 unsigned char buf[1024];
1091 sprintf(message, "maps/%s*.bsp", s);
1092 t = FS_Search(message, 1, true);
1095 if (t->numfilenames > 1)
1096 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1097 len = (unsigned char *)Z_Malloc(t->numfilenames);
1099 for(max=i=0;i<t->numfilenames;i++)
1101 k = (int)strlen(t->filenames[i]);
1111 for(i=0;i<t->numfilenames;i++)
1113 int lumpofs = 0, lumplen = 0;
1114 char *entities = NULL;
1115 const char *data = NULL;
1117 char entfilename[MAX_QPATH];
1118 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1120 f = FS_Open(t->filenames[i], "rb", true, false);
1123 memset(buf, 0, 1024);
1124 FS_Read(f, buf, 1024);
1125 if (!memcmp(buf, "IBSP", 4))
1127 p = LittleLong(((int *)buf)[1]);
1128 if (p == Q3BSPVERSION)
1130 q3dheader_t *header = (q3dheader_t *)buf;
1131 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1132 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1134 else if (p == Q2BSPVERSION)
1136 q2dheader_t *header = (q2dheader_t *)buf;
1137 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1138 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1141 else if (!memcmp(buf, "MCBSPpad", 8))
1143 p = LittleLong(((int *)buf)[2]);
1144 if (p == MCBSPVERSION)
1146 int numhulls = LittleLong(((int *)buf)[3]);
1147 lumpofs = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+0]);
1148 lumplen = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+1]);
1151 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1153 dheader_t *header = (dheader_t *)buf;
1154 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1155 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1159 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1160 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1161 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1162 if (!entities && lumplen >= 10)
1164 FS_Seek(f, lumpofs, SEEK_SET);
1165 entities = (char *)Z_Malloc(lumplen + 1);
1166 FS_Read(f, entities, lumplen);
1170 // if there are entities to parse, a missing message key just
1171 // means there is no title, so clear the message string now
1177 if (!COM_ParseTokenConsole(&data))
1179 if (com_token[0] == '{')
1181 if (com_token[0] == '}')
1183 // skip leading whitespace
1184 for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
1185 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
1186 keyname[l] = com_token[k+l];
1188 if (!COM_ParseTokenConsole(&data))
1190 if (developer.integer >= 100)
1191 Con_Printf("key: %s %s\n", keyname, com_token);
1192 if (!strcmp(keyname, "message"))
1194 // get the message contents
1195 strlcpy(message, com_token, sizeof(message));
1205 *(t->filenames[i]+len[i]+5) = 0;
1208 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1209 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1210 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1211 case MCBSPVERSION: strlcpy((char *)buf, "MC", sizeof(buf));break;
1212 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1213 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1215 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1220 k = *(t->filenames[0]+5+p);
1223 for(i=1;i<t->numfilenames;i++)
1224 if(*(t->filenames[i]+5+p) != k)
1230 memset(completedname, 0, completednamebufferlength);
1231 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1241 New function for tab-completion system
1242 Added by EvilTypeGuy
1243 MEGA Thanks to Taniwha
1246 void Con_DisplayList(const char **list)
1248 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1249 const char **walk = list;
1252 len = (int)strlen(*walk);
1260 len = (int)strlen(*list);
1261 if (pos + maxlen >= width) {
1267 for (i = 0; i < (maxlen - len); i++)
1279 Con_CompleteCommandLine
1281 New function for tab-completion system
1282 Added by EvilTypeGuy
1283 Thanks to Fett erich@heintz.com
1285 Enhanced to tab-complete map names by [515]
1288 void Con_CompleteCommandLine (void)
1290 const char *cmd = "";
1292 const char **list[3] = {0, 0, 0};
1294 int c, v, a, i, cmd_len, pos, k;
1296 //find what we want to complete
1300 k = key_lines[edit_line][pos];
1301 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
1306 s = key_lines[edit_line] + pos;
1307 strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2)); //save chars after cursor
1308 key_lines[edit_line][key_linepos] = 0; //hide them
1311 for(k=pos-1;k>2;k--)
1312 if(key_lines[edit_line][k] != ' ')
1314 if(key_lines[edit_line][k] == '\"' || key_lines[edit_line][k] == ';' || key_lines[edit_line][k] == '\'')
1316 if ((pos+k > 2 && !strncmp(key_lines[edit_line]+k-2, "map", 3))
1317 || (pos+k > 10 && !strncmp(key_lines[edit_line]+k-10, "changelevel", 11)))
1320 if (GetMapList(s, t, sizeof(t)))
1322 // first move the cursor
1323 key_linepos += (int)strlen(t) - (int)strlen(s);
1325 // and now do the actual work
1327 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
1328 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
1330 // and fix the cursor
1331 if(key_linepos > (int) strlen(key_lines[edit_line]))
1332 key_linepos = (int) strlen(key_lines[edit_line]);
1338 // Count number of possible matches and print them
1339 c = Cmd_CompleteCountPossible(s);
1342 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
1343 Cmd_CompleteCommandPrint(s);
1345 v = Cvar_CompleteCountPossible(s);
1348 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
1349 Cvar_CompleteCvarPrint(s);
1351 a = Cmd_CompleteAliasCountPossible(s);
1354 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
1355 Cmd_CompleteAliasPrint(s);
1358 if (!(c + v + a)) // No possible matches
1361 strlcpy(&key_lines[edit_line][key_linepos], s2, sizeof(key_lines[edit_line]) - key_linepos);
1366 cmd = *(list[0] = Cmd_CompleteBuildList(s));
1368 cmd = *(list[1] = Cvar_CompleteBuildList(s));
1370 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
1372 for (cmd_len = (int)strlen(s);;cmd_len++)
1375 for (i = 0; i < 3; i++)
1377 for (l = list[i];*l;l++)
1378 if ((*l)[cmd_len] != cmd[cmd_len])
1380 // all possible matches share this character, so we continue...
1383 // if all matches ended at the same position, stop
1384 // (this means there is only one match)
1390 // prevent a buffer overrun by limiting cmd_len according to remaining space
1391 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
1395 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
1396 key_linepos += cmd_len;
1397 // if there is only one match, add a space after it
1398 if (c + v + a == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
1399 key_lines[edit_line][key_linepos++] = ' ';
1402 // use strlcat to avoid a buffer overrun
1403 key_lines[edit_line][key_linepos] = 0;
1404 strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
1406 // free the command, cvar, and alias lists
1407 for (i = 0; i < 3; i++)
1409 Mem_Free((void *)list[i]);