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 DrawQ_Pic(0, lines - vid_conheight.integer, scr_conbrightness.value >= 0.01f ? Draw_CachePic("gfx/conback", false) : NULL, vid_conwidth.integer, vid_conheight.integer, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, scr_conalpha.value, 0);
844 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);
847 con_vislines = lines;
849 rows = (int)ceil((lines/con_textsize.value)-2); // rows of text to draw
850 y = lines - (rows+2)*con_textsize.value; // may start slightly negative
852 for (i = con_current - rows + 1;i <= con_current;i++, y += con_textsize.value)
854 j = max(i - con_backscroll, 0);
855 text = con_text + (j % con_totallines)*con_linewidth;
857 DrawQ_ColoredString( 0, y, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
860 // draw the input prompt, user text, and cursor if desired
868 Prints not only map filename, but also
869 its format (q1/q2/q3/hl) and even its message
871 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
872 //LordHavoc: rewrote bsp type detection, added mcbsp support and rewrote message extraction to do proper worldspawn parsing
873 //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
874 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
875 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
879 int i, k, max, p, o, min;
882 unsigned char buf[1024];
884 sprintf(message, "maps/%s*.bsp", s);
885 t = FS_Search(message, 1, true);
888 if (t->numfilenames > 1)
889 Con_Printf("^1 %i maps found :\n", t->numfilenames);
890 len = Z_Malloc(t->numfilenames);
892 for(max=i=0;i<t->numfilenames;i++)
894 k = (int)strlen(t->filenames[i]);
904 for(i=0;i<t->numfilenames;i++)
906 int lumpofs = 0, lumplen = 0;
907 char *entities = NULL;
908 const char *data = NULL;
910 char entfilename[MAX_QPATH];
911 strcpy(message, "^1**ERROR**^7");
913 f = FS_Open(t->filenames[i], "rb", true, false);
916 memset(buf, 0, 1024);
917 FS_Read(f, buf, 1024);
918 if (!memcmp(buf, "IBSP", 4))
920 p = LittleLong(((int *)buf)[1]);
921 if (p == Q3BSPVERSION)
923 q3dheader_t *header = (q3dheader_t *)buf;
924 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
925 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
927 else if (p == Q2BSPVERSION)
929 q2dheader_t *header = (q2dheader_t *)buf;
930 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
931 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
934 else if (!memcmp(buf, "MCBSPpad", 8))
936 p = LittleLong(((int *)buf)[2]);
937 if (p == MCBSPVERSION)
939 int numhulls = LittleLong(((int *)buf)[3]);
940 lumpofs = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+0]);
941 lumplen = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+1]);
944 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
946 dheader_t *header = (dheader_t *)buf;
947 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
948 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
952 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
953 strcpy(entfilename + strlen(entfilename) - 4, ".ent");
954 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
955 if (!entities && lumplen >= 10)
957 FS_Seek(f, lumpofs, SEEK_SET);
958 entities = Z_Malloc(lumplen + 1);
959 FS_Read(f, entities, lumplen);
963 // if there are entities to parse, a missing message key just
964 // means there is no title, so clear the message string now
970 if (!COM_ParseToken(&data, false))
972 if (com_token[0] == '{')
974 if (com_token[0] == '}')
976 // skip leading whitespace
977 for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
978 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
979 keyname[l] = com_token[k+l];
981 if (!COM_ParseToken(&data, false))
983 if (developer.integer >= 2)
984 Con_Printf("key: %s %s\n", keyname, com_token);
985 if (!strcmp(keyname, "message"))
987 // get the message contents
988 strlcpy(message, com_token, sizeof(message));
998 *(t->filenames[i]+len[i]+5) = 0;
1001 case Q3BSPVERSION: strcpy((char *)buf, "Q3");break;
1002 case Q2BSPVERSION: strcpy((char *)buf, "Q2");break;
1003 case BSPVERSION: strcpy((char *)buf, "Q1");break;
1004 case MCBSPVERSION: strcpy((char *)buf, "MC");break;
1005 case 30: strcpy((char *)buf, "HL");break;
1006 default: strcpy((char *)buf, "??");break;
1008 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1013 k = *(t->filenames[0]+5+p);
1016 for(i=1;i<t->numfilenames;i++)
1017 if(*(t->filenames[i]+5+p) != k)
1023 memset(completedname, 0, completednamebufferlength);
1024 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1034 New function for tab-completion system
1035 Added by EvilTypeGuy
1036 MEGA Thanks to Taniwha
1039 void Con_DisplayList(const char **list)
1041 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1042 const char **walk = list;
1045 len = (int)strlen(*walk);
1053 len = (int)strlen(*list);
1054 if (pos + maxlen >= width) {
1060 for (i = 0; i < (maxlen - len); i++)
1072 Con_CompleteCommandLine
1074 New function for tab-completion system
1075 Added by EvilTypeGuy
1076 Thanks to Fett erich@heintz.com
1078 Enhanced to tab-complete map names by [515]
1081 void Con_CompleteCommandLine (void)
1083 const char *cmd = "";
1085 const char **list[3] = {0, 0, 0};
1087 int c, v, a, i, cmd_len, pos, k;
1089 //find what we want to complete
1093 k = key_lines[edit_line][pos];
1094 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
1099 s = key_lines[edit_line] + pos;
1100 strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2)); //save chars after cursor
1101 key_lines[edit_line][key_linepos] = 0; //hide them
1104 for(k=pos-1;k>2;k--)
1105 if(key_lines[edit_line][k] != ' ')
1107 if(key_lines[edit_line][k] == '\"' || key_lines[edit_line][k] == ';' || key_lines[edit_line][k] == '\'')
1109 if ((pos+k > 2 && !strncmp(key_lines[edit_line]+k-2, "map", 3))
1110 || (pos+k > 10 && !strncmp(key_lines[edit_line]+k-10, "changelevel", 11)))
1113 if (GetMapList(s, t, sizeof(t)))
1115 // first move the cursor
1116 key_linepos += (int)strlen(t) - (int)strlen(s);
1118 // and now do the actual work
1120 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
1121 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
1123 // and fix the cursor
1124 if(key_linepos > (int) strlen(key_lines[edit_line]))
1125 key_linepos = (int) strlen(key_lines[edit_line]);
1131 // Count number of possible matches and print them
1132 c = Cmd_CompleteCountPossible(s);
1135 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
1136 Cmd_CompleteCommandPrint(s);
1138 v = Cvar_CompleteCountPossible(s);
1141 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
1142 Cvar_CompleteCvarPrint(s);
1144 a = Cmd_CompleteAliasCountPossible(s);
1147 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
1148 Cmd_CompleteAliasPrint(s);
1151 if (!(c + v + a)) // No possible matches
1154 strcpy(&key_lines[edit_line][key_linepos], s2);
1159 cmd = *(list[0] = Cmd_CompleteBuildList(s));
1161 cmd = *(list[1] = Cvar_CompleteBuildList(s));
1163 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
1165 for (cmd_len = (int)strlen(s);;cmd_len++)
1168 for (i = 0; i < 3; i++)
1170 for (l = list[i];*l;l++)
1171 if ((*l)[cmd_len] != cmd[cmd_len])
1173 // all possible matches share this character, so we continue...
1176 // if all matches ended at the same position, stop
1177 // (this means there is only one match)
1183 // prevent a buffer overrun by limiting cmd_len according to remaining space
1184 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
1188 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
1189 key_linepos += cmd_len;
1190 // if there is only one match, add a space after it
1191 if (c + v + a == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
1192 key_lines[edit_line][key_linepos++] = ' ';
1195 // use strlcat to avoid a buffer overrun
1196 key_lines[edit_line][key_linepos] = 0;
1197 strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
1199 // free the command, cvar, and alias lists
1200 for (i = 0; i < 3; i++)
1202 Mem_Free((void *)list[i]);