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;
58 ==============================================================================
62 ==============================================================================
65 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
66 char crt_log_file [MAX_OSPATH] = "";
67 qfile_t* logfile = NULL;
69 unsigned char* logqueue = NULL;
73 void Log_ConPrint (const char *msg);
80 const char* Log_Timestamp (const char *desc)
82 static char timestamp [128];
84 const struct tm *crt_tm;
87 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
89 crt_tm = localtime (&crt_time);
90 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
93 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
95 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
108 if (logfile != NULL || log_file.string[0] == '\0')
111 logfile = FS_Open (log_file.string, "ab", false, false);
114 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
115 FS_Print (logfile, Log_Timestamp ("Log started"));
125 void Log_Close (void)
130 FS_Print (logfile, Log_Timestamp ("Log stopped"));
131 FS_Print (logfile, "\n");
135 crt_log_file[0] = '\0';
144 void Log_Start (void)
148 // Dump the contents of the log queue into the log file and free it
149 if (logqueue != NULL)
151 if (logfile != NULL && logq_ind != 0)
152 FS_Write (logfile, logqueue, logq_ind);
166 void Log_ConPrint (const char *msg)
168 static qboolean inprogress = false;
170 // don't allow feedback loops with memory error reports
175 // Until the host is completely initialized, we maintain a log queue
176 // to store the messages, since the log can't be started before
177 if (logqueue != NULL)
179 size_t remain = logq_size - logq_ind;
180 size_t len = strlen (msg);
182 // If we need to enlarge the log queue
185 size_t factor = ((logq_ind + len) / logq_size) + 1;
186 unsigned char* newqueue;
189 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
190 memcpy (newqueue, logqueue, logq_ind);
193 remain = logq_size - logq_ind;
195 memcpy (&logqueue[logq_ind], msg, len);
202 // Check if log_file has changed
203 if (strcmp (crt_log_file, log_file.string) != 0)
209 // If a log file is available
211 FS_Print (logfile, msg);
221 void Log_Printf (const char *logfilename, const char *fmt, ...)
225 file = FS_Open (logfilename, "ab", true, false);
230 va_start (argptr, fmt);
231 FS_VPrintf (file, fmt, argptr);
240 ==============================================================================
244 ==============================================================================
252 void Con_ToggleConsole_f (void)
254 // toggle the 'user wants console' bit
255 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
256 memset (con_times, 0, sizeof(con_times));
264 void Con_Clear_f (void)
267 memset (con_text, ' ', CON_TEXTSIZE);
276 void Con_ClearNotify (void)
280 for (i=0 ; i<MAX_NOTIFYLINES ; i++)
290 void Con_MessageMode_f (void)
292 key_dest = key_message;
302 void Con_MessageMode2_f (void)
304 key_dest = key_message;
313 If the line width has changed, reformat the buffer.
316 void Con_CheckResize (void)
318 int i, j, width, oldwidth, oldtotallines, numlines, numchars;
320 char tbuf[CON_TEXTSIZE];
322 f = bound(1, con_textsize.value, 128);
323 if(f != con_textsize.value)
324 Cvar_SetValueQuick(&con_textsize, f);
325 width = (int)floor(vid_conwidth.value / con_textsize.value);
326 width = bound(1, width, CON_TEXTSIZE/4);
328 if (width == con_linewidth)
331 oldwidth = con_linewidth;
332 con_linewidth = width;
333 oldtotallines = con_totallines;
334 con_totallines = CON_TEXTSIZE / con_linewidth;
335 numlines = oldtotallines;
337 if (con_totallines < numlines)
338 numlines = con_totallines;
342 if (con_linewidth < numchars)
343 numchars = con_linewidth;
345 memcpy (tbuf, con_text, CON_TEXTSIZE);
346 memset (con_text, ' ', CON_TEXTSIZE);
348 for (i=0 ; i<numlines ; i++)
350 for (j=0 ; j<numchars ; j++)
352 con_text[(con_totallines - 1 - i) * con_linewidth + j] =
353 tbuf[((con_current - i + oldtotallines) %
354 oldtotallines) * oldwidth + j];
361 con_current = con_totallines - 1;
364 //[515]: the simplest command ever
365 //LordHavoc: not so simple after I made it print usage...
366 static void Con_Maps_f (void)
370 Con_Printf("usage: maps [mapnameprefix]\n");
373 else if (Cmd_Argc() == 2)
374 GetMapList(Cmd_Argv(1), NULL, 0);
376 GetMapList("", NULL, 0);
386 memset (con_text, ' ', CON_TEXTSIZE);
388 con_totallines = CON_TEXTSIZE / con_linewidth;
390 // Allocate a log queue
391 logq_size = MAX_INPUTLINE;
392 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
395 Cvar_RegisterVariable (&log_file);
397 // support for the classic Quake option
398 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
399 if (COM_CheckParm ("-condebug") != 0)
400 Cvar_SetQuick (&log_file, "qconsole.log");
403 void Con_Init_Commands (void)
405 // register our cvars
406 Cvar_RegisterVariable (&con_notifytime);
407 Cvar_RegisterVariable (&con_notify);
408 Cvar_RegisterVariable (&con_textsize);
410 // register our commands
411 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
412 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
413 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
414 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
415 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps"); // By [515]
417 con_initialized = true;
418 Con_Print("Console initialized.\n");
427 void Con_Linefeed (void)
434 memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth);
441 Handles cursor positioning, line wrapping, etc
442 All console printing must go through this in order to be displayed
443 If no console is visible, the notify window will pop up.
446 void Con_PrintToHistory(const char *txt, int mask)
454 for (l=0 ; l< con_linewidth ; l++)
459 if (l != con_linewidth && (con_x + l > con_linewidth) )
474 // mark time for transparent overlay
475 if (con_current >= 0)
477 if (con_notify.integer < 0)
478 Cvar_SetValueQuick(&con_notify, 0);
479 if (con_notify.integer > MAX_NOTIFYLINES)
480 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
481 if (con_notify.integer > 0)
482 con_times[con_current % con_notify.integer] = cl.time;
497 default: // display character and advance
498 y = con_current % con_totallines;
499 con_text[y*con_linewidth+con_x] = c | mask;
501 if (con_x >= con_linewidth)
509 /* The translation table between the graphical font and plain ASCII --KB */
510 static char qfont_table[256] = {
511 '\0', '#', '#', '#', '#', '.', '#', '#',
512 '#', 9, 10, '#', ' ', 13, '.', '.',
513 '[', ']', '0', '1', '2', '3', '4', '5',
514 '6', '7', '8', '9', '.', '<', '=', '>',
515 ' ', '!', '"', '#', '$', '%', '&', '\'',
516 '(', ')', '*', '+', ',', '-', '.', '/',
517 '0', '1', '2', '3', '4', '5', '6', '7',
518 '8', '9', ':', ';', '<', '=', '>', '?',
519 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
520 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
521 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
522 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
523 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
524 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
525 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
526 'x', 'y', 'z', '{', '|', '}', '~', '<',
528 '<', '=', '>', '#', '#', '.', '#', '#',
529 '#', '#', ' ', '#', ' ', '>', '.', '.',
530 '[', ']', '0', '1', '2', '3', '4', '5',
531 '6', '7', '8', '9', '.', '<', '=', '>',
532 ' ', '!', '"', '#', '$', '%', '&', '\'',
533 '(', ')', '*', '+', ',', '-', '.', '/',
534 '0', '1', '2', '3', '4', '5', '6', '7',
535 '8', '9', ':', ';', '<', '=', '>', '?',
536 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
537 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
538 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
539 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
540 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
541 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
542 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
543 'x', 'y', 'z', '{', '|', '}', '~', '<'
550 Prints to all appropriate console targets, and adds timestamps
553 extern cvar_t timestamps;
554 extern cvar_t timeformat;
555 extern qboolean sys_nostdout;
556 void Con_Print(const char *msg)
559 static int index = 0;
560 static char line[MAX_INPUTLINE];
566 // if this is the beginning of a new line, print timestamp
567 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
569 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
570 line[index++] = STRING_COLOR_TAG;
571 // assert( STRING_COLOR_DEFAULT < 10 )
572 line[index++] = STRING_COLOR_DEFAULT + '0';
573 // special color codes for chat messages must always come first
574 // for Con_PrintToHistory to work properly
580 S_LocalSound ("sound/misc/talk.wav");
582 //if (gamemode == GAME_NEXUIZ)
584 line[index++] = STRING_COLOR_TAG;
589 // // go to colored text
595 for (;*timestamp;index++, timestamp++)
596 if (index < (int)sizeof(line) - 2)
597 line[index] = *timestamp;
599 // append the character
600 line[index++] = *msg;
601 // if this is a newline character, we have a complete line to print
602 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
604 // terminate the line
608 // send to scrollable buffer
609 if (con_initialized && cls.state != ca_dedicated)
610 Con_PrintToHistory(line, mask);
611 // send to terminal or dedicated server window
615 for (p = (unsigned char *) line;*p; p++)
616 *p = qfont_table[*p];
617 Sys_PrintToTerminal(line);
619 // empty the line buffer
630 Prints to all appropriate console targets
633 void Con_Printf(const char *fmt, ...)
636 char msg[MAX_INPUTLINE];
638 va_start(argptr,fmt);
639 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
649 A Con_Print that only shows up if the "developer" cvar is set
652 void Con_DPrint(const char *msg)
654 if (!developer.integer)
655 return; // don't confuse non-developers with techie stuff...
663 A Con_Printf that only shows up if the "developer" cvar is set
666 void Con_DPrintf(const char *fmt, ...)
669 char msg[MAX_INPUTLINE];
671 if (!developer.integer)
672 return; // don't confuse non-developers with techie stuff...
674 va_start(argptr,fmt);
675 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
683 ==============================================================================
687 ==============================================================================
694 The input line scrolls horizontally if typing goes beyond the right edge
696 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
699 void Con_DrawInput (void)
703 char editlinecopy[MAX_INPUTLINE+1], *text;
705 if (!key_consoleactive)
706 return; // don't draw anything
708 text = strcpy(editlinecopy, key_lines[edit_line]);
710 // Advanced Console Editing by Radix radix@planetquake.com
711 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
712 // use strlen of edit_line instead of key_linepos to allow editing
713 // of early characters w/o erasing
715 y = (int)strlen(text);
717 // fill out remainder with spaces
718 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
721 // add the cursor frame
722 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
723 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
725 // text[key_linepos + 1] = 0;
727 // prestep if horizontally scrolling
728 if (key_linepos >= con_linewidth)
729 text += 1 + key_linepos - con_linewidth;
732 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 );
735 // key_lines[edit_line][key_linepos] = 0;
743 Draws the last few lines of output transparently over the game top
746 void Con_DrawNotify (void)
752 char temptext[MAX_INPUTLINE];
753 int colorindex = -1; //-1 for default
755 if (con_notify.integer < 0)
756 Cvar_SetValueQuick(&con_notify, 0);
757 if (con_notify.integer > MAX_NOTIFYLINES)
758 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
759 if (gamemode == GAME_TRANSFUSION)
763 for (i= con_current-con_notify.integer+1 ; i<=con_current ; i++)
768 time = con_times[i % con_notify.integer];
771 time = cl.time - time;
772 if (time > con_notifytime.value)
774 text = con_text + (i % con_totallines)*con_linewidth;
776 if (gamemode == GAME_NEXUIZ) {
779 for (linewidth = con_linewidth; linewidth && text[linewidth-1] == ' '; linewidth--);
780 x = (vid_conwidth.integer - linewidth * con_textsize.value) * 0.5;
784 DrawQ_ColoredString( x, v, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
786 v += con_textsize.value;
790 if (key_dest == key_message)
796 // LordHavoc: speedup, and other improvements
798 sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
800 sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
801 while ((int)strlen(temptext) >= con_linewidth)
803 DrawQ_ColoredString( 0, v, temptext, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
804 strcpy(temptext, &temptext[con_linewidth]);
805 v += con_textsize.value;
807 if (strlen(temptext) > 0)
809 DrawQ_ColoredString( 0, v, temptext, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
810 v += con_textsize.value;
819 Draws the console with the solid background
820 The typing input line at the bottom should only be drawn if typing is allowed
823 void Con_DrawConsole (int lines)
833 // draw the background
834 if (scr_conbrightness.value >= 0.01f)
835 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);
837 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0, 0, 0, scr_conalpha.value, 0);
838 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);
841 con_vislines = lines;
843 rows = (int)ceil((lines/con_textsize.value)-2); // rows of text to draw
844 y = lines - (rows+2)*con_textsize.value; // may start slightly negative
846 for (i = con_current - rows + 1;i <= con_current;i++, y += con_textsize.value)
848 j = max(i - con_backscroll, 0);
849 text = con_text + (j % con_totallines)*con_linewidth;
851 DrawQ_ColoredString( 0, y, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex );
854 // draw the input prompt, user text, and cursor if desired
862 Prints not only map filename, but also
863 its format (q1/q2/q3/hl) and even its message
865 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
866 //LordHavoc: rewrote bsp type detection, added mcbsp support and rewrote message extraction to do proper worldspawn parsing
867 //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
868 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
869 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
873 int i, k, max, p, o, min;
876 unsigned char buf[1024];
878 sprintf(message, "maps/%s*.bsp", s);
879 t = FS_Search(message, 1, true);
882 if (t->numfilenames > 1)
883 Con_Printf("^1 %i maps found :\n", t->numfilenames);
884 len = Z_Malloc(t->numfilenames);
886 for(max=i=0;i<t->numfilenames;i++)
888 k = (int)strlen(t->filenames[i]);
898 for(i=0;i<t->numfilenames;i++)
900 int lumpofs = 0, lumplen = 0;
901 char *entities = NULL;
902 const char *data = NULL;
904 char entfilename[MAX_QPATH];
905 strcpy(message, "^1**ERROR**^7");
907 f = FS_Open(t->filenames[i], "rb", true, false);
910 memset(buf, 0, 1024);
911 FS_Read(f, buf, 1024);
912 if (!memcmp(buf, "IBSP", 4))
914 p = LittleLong(((int *)buf)[1]);
915 if (p == Q3BSPVERSION)
917 q3dheader_t *header = (q3dheader_t *)buf;
918 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
919 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
921 else if (p == Q2BSPVERSION)
923 q2dheader_t *header = (q2dheader_t *)buf;
924 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
925 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
928 else if (!memcmp(buf, "MCBSPpad", 8))
930 p = LittleLong(((int *)buf)[2]);
931 if (p == MCBSPVERSION)
933 int numhulls = LittleLong(((int *)buf)[3]);
934 lumpofs = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+0]);
935 lumplen = LittleLong(((int *)buf)[3 + numhulls + LUMP_ENTITIES*2+1]);
938 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
940 dheader_t *header = (dheader_t *)buf;
941 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
942 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
946 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
947 strcpy(entfilename + strlen(entfilename) - 4, ".ent");
948 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
949 if (!entities && lumplen >= 10)
951 FS_Seek(f, lumpofs, SEEK_SET);
952 entities = Z_Malloc(lumplen + 1);
953 FS_Read(f, entities, lumplen);
957 // if there are entities to parse, a missing message key just
958 // means there is no title, so clear the message string now
964 if (!COM_ParseToken(&data, false))
966 if (com_token[0] == '{')
968 if (com_token[0] == '}')
970 // skip leading whitespace
971 for (k = 0;com_token[k] && com_token[k] <= ' ';k++);
972 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && com_token[k+l] > ' ';l++)
973 keyname[l] = com_token[k+l];
975 if (!COM_ParseToken(&data, false))
977 if (developer.integer >= 2)
978 Con_Printf("key: %s %s\n", keyname, com_token);
979 if (!strcmp(keyname, "message"))
981 // get the message contents
982 strlcpy(message, com_token, sizeof(message));
992 *(t->filenames[i]+len[i]+5) = 0;
995 case Q3BSPVERSION: strcpy((char *)buf, "Q3");break;
996 case Q2BSPVERSION: strcpy((char *)buf, "Q2");break;
997 case BSPVERSION: strcpy((char *)buf, "Q1");break;
998 case MCBSPVERSION: strcpy((char *)buf, "MC");break;
999 case 30: strcpy((char *)buf, "HL");break;
1000 default: strcpy((char *)buf, "??");break;
1002 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1007 k = *(t->filenames[0]+5+p);
1010 for(i=1;i<t->numfilenames;i++)
1011 if(*(t->filenames[i]+5+p) != k)
1017 memset(completedname, 0, completednamebufferlength);
1018 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1028 New function for tab-completion system
1029 Added by EvilTypeGuy
1030 MEGA Thanks to Taniwha
1033 void Con_DisplayList(const char **list)
1035 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1036 const char **walk = list;
1039 len = (int)strlen(*walk);
1047 len = (int)strlen(*list);
1048 if (pos + maxlen >= width) {
1054 for (i = 0; i < (maxlen - len); i++)
1066 Con_CompleteCommandLine
1068 New function for tab-completion system
1069 Added by EvilTypeGuy
1070 Thanks to Fett erich@heintz.com
1072 Enhanced to tab-complete map names by [515]
1075 void Con_CompleteCommandLine (void)
1077 const char *cmd = "";
1079 const char **list[3] = {0, 0, 0};
1081 int c, v, a, i, cmd_len, pos, k;
1083 //find what we want to complete
1087 k = key_lines[edit_line][pos];
1088 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
1093 s = key_lines[edit_line] + pos;
1094 strlcpy(s2, key_lines[edit_line] + key_linepos, sizeof(s2)); //save chars after cursor
1095 key_lines[edit_line][key_linepos] = 0; //hide them
1098 for(k=pos-1;k>2;k--)
1099 if(key_lines[edit_line][k] != ' ')
1101 if(key_lines[edit_line][k] == '\"' || key_lines[edit_line][k] == ';' || key_lines[edit_line][k] == '\'')
1103 if ((pos+k > 2 && !strncmp(key_lines[edit_line]+k-2, "map", 3))
1104 || (pos+k > 10 && !strncmp(key_lines[edit_line]+k-10, "changelevel", 11)))
1107 if (GetMapList(s, t, sizeof(t)))
1109 // first move the cursor
1110 key_linepos += (int)strlen(t) - (int)strlen(s);
1112 // and now do the actual work
1114 strlcat(key_lines[edit_line], t, MAX_INPUTLINE);
1115 strlcat(key_lines[edit_line], s2, MAX_INPUTLINE); //add back chars after cursor
1117 // and fix the cursor
1118 if(key_linepos > (int) strlen(key_lines[edit_line]))
1119 key_linepos = (int) strlen(key_lines[edit_line]);
1125 // Count number of possible matches and print them
1126 c = Cmd_CompleteCountPossible(s);
1129 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
1130 Cmd_CompleteCommandPrint(s);
1132 v = Cvar_CompleteCountPossible(s);
1135 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
1136 Cvar_CompleteCvarPrint(s);
1138 a = Cmd_CompleteAliasCountPossible(s);
1141 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
1142 Cmd_CompleteAliasPrint(s);
1145 if (!(c + v + a)) // No possible matches
1148 strcpy(&key_lines[edit_line][key_linepos], s2);
1153 cmd = *(list[0] = Cmd_CompleteBuildList(s));
1155 cmd = *(list[1] = Cvar_CompleteBuildList(s));
1157 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
1159 for (cmd_len = (int)strlen(s);;cmd_len++)
1162 for (i = 0; i < 3; i++)
1164 for (l = list[i];*l;l++)
1165 if ((*l)[cmd_len] != cmd[cmd_len])
1167 // all possible matches share this character, so we continue...
1170 // if all matches ended at the same position, stop
1171 // (this means there is only one match)
1177 // prevent a buffer overrun by limiting cmd_len according to remaining space
1178 cmd_len = min(cmd_len, (int)sizeof(key_lines[edit_line]) - 1 - pos);
1182 memcpy(&key_lines[edit_line][key_linepos], cmd, cmd_len);
1183 key_linepos += cmd_len;
1184 // if there is only one match, add a space after it
1185 if (c + v + a == 1 && key_linepos < (int)sizeof(key_lines[edit_line]) - 1)
1186 key_lines[edit_line][key_linepos++] = ' ';
1189 // use strlcat to avoid a buffer overrun
1190 key_lines[edit_line][key_linepos] = 0;
1191 strlcat(key_lines[edit_line], s2, sizeof(key_lines[edit_line]));
1193 // free the command, cvar, and alias lists
1194 for (i = 0; i < 3; i++)
1196 Mem_Free((void *)list[i]);