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
48 #define MAX_NOTIFYLINES 32
49 // cl.time time the line was generated for transparent notify lines
50 float con_times[MAX_NOTIFYLINES];
54 qboolean con_initialized;
56 // used for server replies to rcon command
57 qboolean rcon_redirect = false;
58 int rcon_redirect_bufferpos = 0;
59 char rcon_redirect_buffer[1400];
63 ==============================================================================
67 ==============================================================================
70 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
71 char crt_log_file [MAX_OSPATH] = "";
72 qfile_t* logfile = NULL;
74 unsigned char* logqueue = NULL;
78 void Log_ConPrint (const char *msg);
85 const char* Log_Timestamp (const char *desc)
87 static char timestamp [128];
89 const struct tm *crt_tm;
92 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
94 crt_tm = localtime (&crt_time);
95 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
98 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
100 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
113 if (logfile != NULL || log_file.string[0] == '\0')
116 logfile = FS_Open (log_file.string, "ab", false, false);
119 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
120 FS_Print (logfile, Log_Timestamp ("Log started"));
130 void Log_Close (void)
135 FS_Print (logfile, Log_Timestamp ("Log stopped"));
136 FS_Print (logfile, "\n");
140 crt_log_file[0] = '\0';
149 void Log_Start (void)
153 // Dump the contents of the log queue into the log file and free it
154 if (logqueue != NULL)
156 if (logfile != NULL && logq_ind != 0)
157 FS_Write (logfile, logqueue, logq_ind);
171 void Log_ConPrint (const char *msg)
173 static qboolean inprogress = false;
175 // don't allow feedback loops with memory error reports
180 // Until the host is completely initialized, we maintain a log queue
181 // to store the messages, since the log can't be started before
182 if (logqueue != NULL)
184 size_t remain = logq_size - logq_ind;
185 size_t len = strlen (msg);
187 // If we need to enlarge the log queue
190 size_t factor = ((logq_ind + len) / logq_size) + 1;
191 unsigned char* newqueue;
194 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
195 memcpy (newqueue, logqueue, logq_ind);
198 remain = logq_size - logq_ind;
200 memcpy (&logqueue[logq_ind], msg, len);
207 // Check if log_file has changed
208 if (strcmp (crt_log_file, log_file.string) != 0)
214 // If a log file is available
216 FS_Print (logfile, msg);
226 void Log_Printf (const char *logfilename, const char *fmt, ...)
230 file = FS_Open (logfilename, "ab", true, false);
235 va_start (argptr, fmt);
236 FS_VPrintf (file, fmt, argptr);
245 ==============================================================================
249 ==============================================================================
257 void Con_ToggleConsole_f (void)
259 // toggle the 'user wants console' bit
260 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
261 memset (con_times, 0, sizeof(con_times));
269 void Con_Clear_f (void)
272 memset (con_text, ' ', CON_TEXTSIZE);
281 void Con_ClearNotify (void)
285 for (i=0 ; i<MAX_NOTIFYLINES ; i++)
295 void Con_MessageMode_f (void)
297 key_dest = key_message;
307 void Con_MessageMode2_f (void)
309 key_dest = key_message;
318 If the line width has changed, reformat the buffer.
321 void Con_CheckResize (void)
323 int i, j, width, oldwidth, oldtotallines, numlines, numchars;
325 char tbuf[CON_TEXTSIZE];
327 f = bound(1, con_textsize.value, 128);
328 if(f != con_textsize.value)
329 Cvar_SetValueQuick(&con_textsize, f);
330 width = (int)floor(vid_conwidth.value / con_textsize.value);
331 width = bound(1, width, CON_TEXTSIZE/4);
333 if (width == con_linewidth)
336 oldwidth = con_linewidth;
337 con_linewidth = width;
338 oldtotallines = con_totallines;
339 con_totallines = CON_TEXTSIZE / con_linewidth;
340 numlines = oldtotallines;
342 if (con_totallines < numlines)
343 numlines = con_totallines;
347 if (con_linewidth < numchars)
348 numchars = con_linewidth;
350 memcpy (tbuf, con_text, CON_TEXTSIZE);
351 memset (con_text, ' ', CON_TEXTSIZE);
353 for (i=0 ; i<numlines ; i++)
355 for (j=0 ; j<numchars ; j++)
357 con_text[(con_totallines - 1 - i) * con_linewidth + j] =
358 tbuf[((con_current - i + oldtotallines) %
359 oldtotallines) * oldwidth + j];
366 con_current = con_totallines - 1;
369 //[515]: the simplest command ever
370 //LordHavoc: not so simple after I made it print usage...
371 static void Con_Maps_f (void)
375 Con_Printf("usage: maps [mapnameprefix]\n");
378 else if (Cmd_Argc() == 2)
379 GetMapList(Cmd_Argv(1), NULL, 0);
381 GetMapList("", NULL, 0);
391 memset (con_text, ' ', CON_TEXTSIZE);
393 con_totallines = CON_TEXTSIZE / con_linewidth;
395 // Allocate a log queue
396 logq_size = MAX_INPUTLINE;
397 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
400 Cvar_RegisterVariable (&log_file);
402 // support for the classic Quake option
403 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
404 if (COM_CheckParm ("-condebug") != 0)
405 Cvar_SetQuick (&log_file, "qconsole.log");
408 void Con_Init_Commands (void)
410 // register our cvars
411 Cvar_RegisterVariable (&con_notifytime);
412 Cvar_RegisterVariable (&con_notify);
413 Cvar_RegisterVariable (&con_textsize);
415 // register our commands
416 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
417 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
418 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
419 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
420 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps"); // By [515]
422 con_initialized = true;
423 Con_Print("Console initialized.\n");
432 void Con_Linefeed (void)
439 memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth);
446 Handles cursor positioning, line wrapping, etc
447 All console printing must go through this in order to be displayed
448 If no console is visible, the notify window will pop up.
451 void Con_PrintToHistory(const char *txt, int mask)
459 for (l=0 ; l< con_linewidth ; l++)
464 if (l != con_linewidth && (con_x + l > con_linewidth) )
479 // mark time for transparent overlay
480 if (con_current >= 0)
482 if (con_notify.integer < 0)
483 Cvar_SetValueQuick(&con_notify, 0);
484 if (con_notify.integer > MAX_NOTIFYLINES)
485 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
486 if (con_notify.integer > 0)
487 con_times[con_current % con_notify.integer] = cl.time;
502 default: // display character and advance
503 y = con_current % con_totallines;
504 con_text[y*con_linewidth+con_x] = c | mask;
506 if (con_x >= con_linewidth)
514 /* The translation table between the graphical font and plain ASCII --KB */
515 static char qfont_table[256] = {
516 '\0', '#', '#', '#', '#', '.', '#', '#',
517 '#', 9, 10, '#', ' ', 13, '.', '.',
518 '[', ']', '0', '1', '2', '3', '4', '5',
519 '6', '7', '8', '9', '.', '<', '=', '>',
520 ' ', '!', '"', '#', '$', '%', '&', '\'',
521 '(', ')', '*', '+', ',', '-', '.', '/',
522 '0', '1', '2', '3', '4', '5', '6', '7',
523 '8', '9', ':', ';', '<', '=', '>', '?',
524 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
525 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
526 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
527 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
528 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
529 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
530 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
531 'x', 'y', 'z', '{', '|', '}', '~', '<',
533 '<', '=', '>', '#', '#', '.', '#', '#',
534 '#', '#', ' ', '#', ' ', '>', '.', '.',
535 '[', ']', '0', '1', '2', '3', '4', '5',
536 '6', '7', '8', '9', '.', '<', '=', '>',
537 ' ', '!', '"', '#', '$', '%', '&', '\'',
538 '(', ')', '*', '+', ',', '-', '.', '/',
539 '0', '1', '2', '3', '4', '5', '6', '7',
540 '8', '9', ':', ';', '<', '=', '>', '?',
541 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
542 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
543 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
544 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
545 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
546 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
547 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
548 'x', 'y', 'z', '{', '|', '}', '~', '<'
555 Prints to all appropriate console targets, and adds timestamps
558 extern cvar_t timestamps;
559 extern cvar_t timeformat;
560 extern qboolean sys_nostdout;
561 void Con_Print(const char *msg)
564 static int index = 0;
565 static char line[MAX_INPUTLINE];
569 // if this print is in response to an rcon command, add the character
570 // to the rcon redirect buffer
571 if (rcon_redirect && rcon_redirect_bufferpos < (int)sizeof(rcon_redirect_buffer) - 1)
572 rcon_redirect_buffer[rcon_redirect_bufferpos++] = *msg;
573 // if this is the beginning of a new line, print timestamp
576 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
578 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
579 line[index++] = STRING_COLOR_TAG;
580 // assert( STRING_COLOR_DEFAULT < 10 )
581 line[index++] = STRING_COLOR_DEFAULT + '0';
582 // special color codes for chat messages must always come first
583 // for Con_PrintToHistory to work properly
589 S_LocalSound ("sound/misc/talk.wav");
591 //if (gamemode == GAME_NEXUIZ)
593 line[index++] = STRING_COLOR_TAG;
598 // // go to colored text
604 for (;*timestamp;index++, timestamp++)
605 if (index < (int)sizeof(line) - 2)
606 line[index] = *timestamp;
608 // append the character
609 line[index++] = *msg;
610 // if this is a newline character, we have a complete line to print
611 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
613 // terminate the line
617 // send to scrollable buffer
618 if (con_initialized && cls.state != ca_dedicated)
619 Con_PrintToHistory(line, mask);
620 // send to terminal or dedicated server window
624 for (p = (unsigned char *) line;*p; p++)
625 *p = qfont_table[*p];
626 Sys_PrintToTerminal(line);
628 // empty the line buffer
639 Prints to all appropriate console targets
642 void Con_Printf(const char *fmt, ...)
645 char msg[MAX_INPUTLINE];
647 va_start(argptr,fmt);
648 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
658 A Con_Print that only shows up if the "developer" cvar is set
661 void Con_DPrint(const char *msg)
663 if (!developer.integer)
664 return; // don't confuse non-developers with techie stuff...
672 A Con_Printf that only shows up if the "developer" cvar is set
675 void Con_DPrintf(const char *fmt, ...)
678 char msg[MAX_INPUTLINE];
680 if (!developer.integer)
681 return; // don't confuse non-developers with techie stuff...
683 va_start(argptr,fmt);
684 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
692 ==============================================================================
696 ==============================================================================
703 The input line scrolls horizontally if typing goes beyond the right edge
705 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
708 void Con_DrawInput (void)
712 char editlinecopy[MAX_INPUTLINE+1], *text;
714 if (!key_consoleactive)
715 return; // don't draw anything
717 text = strcpy(editlinecopy, key_lines[edit_line]);
719 // Advanced Console Editing by Radix radix@planetquake.com
720 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
721 // use strlen of edit_line instead of key_linepos to allow editing
722 // of early characters w/o erasing
724 y = (int)strlen(text);
726 // fill out remainder with spaces
727 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
730 // add the cursor frame
731 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
732 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
734 // text[key_linepos + 1] = 0;
736 // prestep if horizontally scrolling
737 if (key_linepos >= con_linewidth)
738 text += 1 + key_linepos - con_linewidth;
741 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 );
744 // key_lines[edit_line][key_linepos] = 0;
752 Draws the last few lines of output transparently over the game top
755 void Con_DrawNotify (void)
761 char temptext[MAX_INPUTLINE];
762 int colorindex = -1; //-1 for default
764 if (con_notify.integer < 0)
765 Cvar_SetValueQuick(&con_notify, 0);
766 if (con_notify.integer > MAX_NOTIFYLINES)
767 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
768 if (gamemode == GAME_TRANSFUSION)
772 for (i= con_current-con_notify.integer+1 ; i<=con_current ; i++)
777 time = con_times[i % con_notify.integer];
780 time = cl.time - time;
781 if (time > con_notifytime.value)
783 text = con_text + (i % con_totallines)*con_linewidth;
785 if (gamemode == GAME_NEXUIZ) {
788 for (linewidth = con_linewidth; linewidth && text[linewidth-1] == ' '; linewidth--);
789 x = (vid_conwidth.integer - linewidth * con_textsize.value) * 0.5;
793 DrawQ_ColoredString( x, v, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
795 v += con_textsize.value;
799 if (key_dest == key_message)
805 // LordHavoc: speedup, and other improvements
807 sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
809 sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
810 while ((int)strlen(temptext) >= con_linewidth)
812 DrawQ_ColoredString( 0, v, temptext, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
813 strcpy(temptext, &temptext[con_linewidth]);
814 v += con_textsize.value;
816 if (strlen(temptext) > 0)
818 DrawQ_ColoredString( 0, v, temptext, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
819 v += con_textsize.value;
828 Draws the console with the solid background
829 The typing input line at the bottom should only be drawn if typing is allowed
832 void Con_DrawConsole (int lines)
842 // draw the background
843 if (scr_conbrightness.value >= 0.01f)
844 DrawQ_Pic(0, lines - vid_conheight.integer, "gfx/conback", vid_conwidth.integer, vid_conheight.integer, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, scr_conalpha.value, 0);
846 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0, 0, 0, scr_conalpha.value, 0);
847 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);
850 con_vislines = lines;
852 rows = (int)ceil((lines/con_textsize.value)-2); // rows of text to draw
853 y = lines - (rows+2)*con_textsize.value; // may start slightly negative
855 for (i = con_current - rows + 1;i <= con_current;i++, y += con_textsize.value)
857 j = max(i - con_backscroll, 0);
858 text = con_text + (j % con_totallines)*con_linewidth;
860 DrawQ_ColoredString( 0, y, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
863 // draw the input prompt, user text, and cursor if desired
871 Prints not only map filename, but also
872 its format (q1/q2/q3/hl) and even its message
874 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
875 //LordHavoc: rewrote bsp type detection, added mcbsp support and rewrote message extraction to do proper worldspawn parsing
876 //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
877 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
878 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
882 int i, k, max, p, o, min;
885 unsigned char buf[1024];
887 sprintf(message, "maps/%s*.bsp", s);
888 t = FS_Search(message, 1, true);
891 if (t->numfilenames > 1)
892 Con_Printf("^1 %i maps found :\n", t->numfilenames);
893 len = Z_Malloc(t->numfilenames);
895 for(max=i=0;i<t->numfilenames;i++)
897 k = (int)strlen(t->filenames[i]);
907 for(i=0;i<t->numfilenames;i++)
909 int lumpofs = 0, lumplen = 0;
910 char *entities = NULL;
911 const char *data = NULL;
913 char entfilename[MAX_QPATH];
914 strcpy(message, "^1**ERROR**^7");
916 f = FS_Open(t->filenames[i], "rb", true, false);
919 memset(buf, 0, 1024);
920 FS_Read(f, buf, 1024);
921 if (!memcmp(buf, "IBSP", 4))
923 p = LittleLong(((int *)buf)[1]);
924 if (p == Q3BSPVERSION)
926 q3dheader_t *header = (q3dheader_t *)buf;
927 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
928 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
930 else if (p == Q2BSPVERSION)
932 q2dheader_t *header = (q2dheader_t *)buf;
933 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
934 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
937 else if (!memcmp(buf, "MCBSPpad", 8))
939 p = LittleLong(((int *)buf)[2]);
940 if (p == MCBSPVERSION)
942 int numhulls = LittleLong(((int *)buf)[3]);
943 lumpofs = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+0]);
944 lumplen = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+1]);
947 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
949 dheader_t *header = (dheader_t *)buf;
950 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
951 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
955 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
956 strcpy(entfilename + strlen(entfilename) - 4, ".ent");
957 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
958 if (!entities && lumplen >= 10)
960 FS_Seek(f, lumpofs, SEEK_SET);
961 entities = Z_Malloc(lumplen + 1);
962 FS_Read(f, entities, lumplen);
966 // if there are entities to parse, a missing message key just
967 // means there is no title, so clear the message string now
973 if (!COM_ParseToken(&data, false))
975 if (com_token[0] == '{')
977 if (com_token[0] == '}')
979 // skip leading whitespace
980 for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
981 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
982 keyname[l] = com_token[k+l];
984 if (!COM_ParseToken(&data, false))
986 if (developer.integer >= 2)
987 Con_Printf("key: %s %s\n", keyname, com_token);
988 if (!strcmp(keyname, "message"))
990 // get the message contents
991 strlcpy(message, com_token, sizeof(message));
1001 *(t->filenames[i]+len[i]+5) = 0;
1004 case Q3BSPVERSION: strcpy((char *)buf, "Q3");break;
1005 case Q2BSPVERSION: strcpy((char *)buf, "Q2");break;
1006 case BSPVERSION: strcpy((char *)buf, "Q1");break;
1007 case MCBSPVERSION: strcpy((char *)buf, "MC");break;
1008 case 30: strcpy((char *)buf, "HL");break;
1009 default: strcpy((char *)buf, "??");break;
1011 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1016 k = *(t->filenames[0]+5+p);
1019 for(i=1;i<t->numfilenames;i++)
1020 if(*(t->filenames[i]+5+p) != k)
1026 memset(completedname, 0, completednamebufferlength);
1027 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1037 New function for tab-completion system
1038 Added by EvilTypeGuy
1039 MEGA Thanks to Taniwha
1042 void Con_DisplayList(const char **list)
1044 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1045 const char **walk = list;
1048 len = (int)strlen(*walk);
1056 len = (int)strlen(*list);
1057 if (pos + maxlen >= width) {
1063 for (i = 0; i < (maxlen - len); i++)
1075 Con_CompleteCommandLine
1077 New function for tab-completion system
1078 Added by EvilTypeGuy
1079 Thanks to Fett erich@heintz.com
1081 Enhanced to tab-complete map names by [515]
1084 void Con_CompleteCommandLine (void)
1086 const char *cmd = "";
1088 const char **list[3] = {0, 0, 0};
1090 int c, v, a, i, cmd_len, pos, k;
1092 //find what we want to complete
1096 k = key_lines[edit_line][pos];
1097 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
1102 s = key_lines[edit_line] + pos;
1103 strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2)); //save chars after cursor
1104 key_lines[edit_line][key_linepos] = 0; //hide them
1107 for(k=pos-1;k>2;k--)
1108 if(key_lines[edit_line][k] != ' ')
1110 if(key_lines[edit_line][k] == '\"' || key_lines[edit_line][k] == ';' || key_lines[edit_line][k] == '\'')
1112 if ((pos+k > 2 && !strncmp(key_lines[edit_line]+k-2, "map", 3))
1113 || (pos+k > 10 && !strncmp(key_lines[edit_line]+k-10, "changelevel", 11)))
1116 if (GetMapList(s, t, sizeof(t)))
1118 // first move the cursor
1119 key_linepos += (int)strlen(t) - (int)strlen(s);
1121 // and now do the actual work
1123 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
1124 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
1126 // and fix the cursor
1127 if(key_linepos > (int) strlen(key_lines[edit_line]))
1128 key_linepos = (int) strlen(key_lines[edit_line]);
1134 // Count number of possible matches and print them
1135 c = Cmd_CompleteCountPossible(s);
1138 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
1139 Cmd_CompleteCommandPrint(s);
1141 v = Cvar_CompleteCountPossible(s);
1144 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
1145 Cvar_CompleteCvarPrint(s);
1147 a = Cmd_CompleteAliasCountPossible(s);
1150 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
1151 Cmd_CompleteAliasPrint(s);
1154 if (!(c + v + a)) // No possible matches
1157 strcpy(&key_lines[edit_line][key_linepos], s2);
1162 cmd = *(list[0] = Cmd_CompleteBuildList(s));
1164 cmd = *(list[1] = Cvar_CompleteBuildList(s));
1166 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
1168 for (cmd_len = (int)strlen(s);;cmd_len++)
1171 for (i = 0; i < 3; i++)
1173 for (l = list[i];*l;l++)
1174 if ((*l)[cmd_len] != cmd[cmd_len])
1176 // all possible matches share this character, so we continue...
1179 // if all matches ended at the same position, stop
1180 // (this means there is only one match)
1186 // prevent a buffer overrun by limiting cmd_len according to remaining space
1187 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
1191 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
1192 key_linepos += cmd_len;
1193 // if there is only one match, add a space after it
1194 if (c + v + a == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
1195 key_lines[edit_line][key_linepos++] = ' ';
1198 // use strlcat to avoid a buffer overrun
1199 key_lines[edit_line][key_linepos] = 0;
1200 strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
1202 // free the command, cvar, and alias lists
1203 for (i = 0; i < 3; i++)
1205 Mem_Free((void *)list[i]);